diff --git a/.rebase/replace/code/src/vs/platform/product/common/product.ts.json b/.rebase/replace/code/src/vs/platform/product/common/product.ts.json index f8995bf3620..898510b06df 100644 --- a/.rebase/replace/code/src/vs/platform/product/common/product.ts.json +++ b/.rebase/replace/code/src/vs/platform/product/common/product.ts.json @@ -4,7 +4,7 @@ "by": "import { ISandboxConfiguration } from 'vs/base/parts/sandbox/common/sandboxTypes';\\\nimport { loadFromFileSystem } from 'vs/platform/product/common/che/product';" }, { - "from": "product = { /\\*BUILD->INSERT_PRODUCT_CONFIGURATION\\*/ } as any;", - "by": "product = { /\\*BUILD->INSERT_PRODUCT_CONFIGURATION\\*/ } as any;\\\n\\\tproduct = loadFromFileSystem();" + "from": "product = { /\\*BUILD->INSERT_PRODUCT_CONFIGURATION\\*/ } as unknown as IProductConfiguration;", + "by": "product = { /\\*BUILD->INSERT_PRODUCT_CONFIGURATION\\*/ } as unknown as IProductConfiguration;\\\n\\\tproduct = loadFromFileSystem();" } ] diff --git a/build/dockerfiles/clg-rebase-assembly.Containerfile b/build/dockerfiles/clg-rebase-assembly.Containerfile new file mode 100644 index 00000000000..29bc6524a68 --- /dev/null +++ b/build/dockerfiles/clg-rebase-assembly.Containerfile @@ -0,0 +1,47 @@ +# Copyright (c) 2021-2024 Red Hat, Inc. +# This program and the accompanying materials are made +# available under the terms of the Eclipse Public License 2.0 +# which is available at https://www.eclipse.org/legal/epl-2.0/ +# +# SPDX-License-Identifier: EPL-2.0 +# + +# Grab content from previously build images +# FROM localhost/linux-libc-ubi8:latest as linux-libc-ubi8-content +FROM localhost/linux-libc-ubi9 as linux-libc-ubi9-content +# FROM linux-musl as linux-musl-content + +# https://quay.io/eclipse/che-machine-exec#^7\. +FROM quay.io/eclipse/che-machine-exec:7.56.0 as machine-exec + +# https://registry.access.redhat.com/ubi8/ubi +FROM registry.access.redhat.com/ubi8/ubi:8.10 AS ubi-builder +RUN mkdir -p /mnt/rootfs +RUN yum install --installroot /mnt/rootfs brotli libstdc++ coreutils glibc-minimal-langpack --releasever 8 --setopt install_weak_deps=false --nodocs -y && yum --installroot /mnt/rootfs clean all +RUN rm -rf /mnt/rootfs/var/cache/* /mnt/rootfs/var/log/dnf* /mnt/rootfs/var/log/yum.* + +WORKDIR /mnt/rootfs + +# COPY --from=linux-musl-content --chown=0:0 /checode-linux-musl /mnt/rootfs/checode-linux-musl +# COPY --from=linux-libc-ubi8-content --chown=0:0 /checode-linux-libc/ubi8 /mnt/rootfs/checode-linux-libc/ubi8 +COPY --from=linux-libc-ubi9-content --chown=0:0 /checode-linux-libc/ubi9 /mnt/rootfs/checode-linux-libc/ubi9 + +RUN mkdir -p /mnt/rootfs/projects && mkdir -p /mnt/rootfs/home/che && mkdir -p /mnt/rootfs/bin/ +RUN cat /mnt/rootfs/etc/passwd | sed s#root:x.*#root:x:\${USER_ID}:\${GROUP_ID}::\${HOME}:/bin/bash#g > /mnt/rootfs/home/che/.passwd.template \ + && cat /mnt/rootfs/etc/group | sed s#root:x:0:#root:x:0:0,\${USER_ID}:#g > /mnt/rootfs/home/che/.group.template +RUN for f in "/mnt/rootfs/bin/" "/mnt/rootfs/home/che" "/mnt/rootfs/etc/group" "/mnt/rootfs/projects" ; do\ + chgrp -R 0 ${f} && \ + chmod -R g+rwX ${f}; \ + done +RUN chmod -R g-w /mnt/rootfs/etc/passwd + +COPY --from=machine-exec --chown=0:0 /go/bin/che-machine-exec /mnt/rootfs/bin/machine-exec +COPY --chmod=755 /build/scripts/*.sh /mnt/rootfs/ +COPY --chmod=755 /build/remote-config /mnt/rootfs/remote/data/Machine/ + +# Create all-in-one image +FROM scratch +COPY --from=ubi-builder /mnt/rootfs/ / +ENV HOME=/home/che +USER 1001 +ENTRYPOINT /entrypoint.sh \ No newline at end of file diff --git a/build/dockerfiles/clg-rebase-linux-libc-ubi9.Containerfile b/build/dockerfiles/clg-rebase-linux-libc-ubi9.Containerfile new file mode 100644 index 00000000000..84d184d6440 --- /dev/null +++ b/build/dockerfiles/clg-rebase-linux-libc-ubi9.Containerfile @@ -0,0 +1,164 @@ +# Copyright (c) 2024 Red Hat, Inc. +# This program and the accompanying materials are made +# available under the terms of the Eclipse Public License 2.0 +# which is available at https://www.eclipse.org/legal/epl-2.0/ +# +# SPDX-License-Identifier: EPL-2.0 +# + +# https://registry.access.redhat.com/ubi9/nodejs-20 +# FROM registry.access.redhat.com/ubi9/nodejs-22:9.6-1760386551 as linux-libc-ubi9-builder +FROM quay.io/cgruver0/che/vscode-builder:latest as linux-libc-ubi9-builder + +USER root + +# Export GITHUB_TOKEN into environment variable +ARG GITHUB_TOKEN='' +ENV GITHUB_TOKEN=$GITHUB_TOKEN + +# Unset GITHUB_TOKEN environment variable if it is empty. +# This is needed for some tools which use this variable and will fail with 401 Unauthorized error if it is invalid. +# For example, vscode ripgrep downloading is an example of such case. +RUN if [ -z $GITHUB_TOKEN ]; then unset GITHUB_TOKEN; fi + +# Install libsecret-devel on s390x and ppc64le for keytar build (binary included in npm package for x86) +RUN { if [[ $(uname -m) == "s390x" ]]; then LIBSECRET="\ + https://rpmfind.net/linux/centos-stream/9-stream/AppStream/s390x/os/Packages/libsecret-0.20.4-4.el9.s390x.rpm \ + https://rpmfind.net/linux/centos-stream/9-stream/AppStream/s390x/os/Packages/libsecret-devel-0.20.4-4.el9.s390x.rpm"; \ + elif [[ $(uname -m) == "ppc64le" ]]; then LIBSECRET="\ + libsecret \ + https://rpmfind.net/linux/centos-stream/9-stream/AppStream/ppc64le/os/Packages/libsecret-devel-0.20.4-4.el9.ppc64le.rpm"; \ + elif [[ $(uname -m) == "x86_64" ]]; then LIBSECRET="\ + https://rpmfind.net/linux/centos-stream/9-stream/AppStream/x86_64/os/Packages/libsecret-devel-0.20.4-4.el9.x86_64.rpm \ + libsecret"; \ + elif [[ $(uname -m) == "aarch64" ]]; then LIBSECRET="\ + https://rpmfind.net/linux/centos-stream/9-stream/AppStream/aarch64/os/Packages/libsecret-devel-0.20.4-4.el9.aarch64.rpm \ + libsecret"; \ + else \ + LIBSECRET=""; echo "Warning: arch $(uname -m) not supported"; \ + fi; } \ + && { if [[ $(uname -m) == "x86_64" ]]; then LIBKEYBOARD="\ + https://rpmfind.net/linux/centos-stream/9-stream/AppStream/x86_64/os/Packages/libxkbfile-1.1.0-8.el9.x86_64.rpm \ + https://rpmfind.net/linux/centos-stream/9-stream/CRB/x86_64/os/Packages/libxkbfile-devel-1.1.0-8.el9.x86_64.rpm"; \ + elif [[ $(uname -m) == "ppc64le" ]]; then LIBKEYBOARD="\ + https://rpmfind.net/linux/centos-stream/9-stream/AppStream/ppc64le/os/Packages/libxkbfile-1.1.0-8.el9.ppc64le.rpm \ + https://rpmfind.net/linux/centos-stream/9-stream/CRB/ppc64le/os/Packages/libxkbfile-devel-1.1.0-8.el9.ppc64le.rpm"; \ + elif [[ $(uname -m) == "aarch64" ]]; then LIBKEYBOARD="\ + https://rpmfind.net/linux/centos-stream/9-stream/AppStream/aarch64/os/Packages/libxkbfile-1.1.0-8.el9.aarch64.rpm \ + https://rpmfind.net/linux/centos-stream/9-stream/CRB/aarch64/os/Packages/libxkbfile-devel-1.1.0-8.el9.aarch64.rpm"; \ + else \ + LIBKEYBOARD=""; echo "Warning: arch $(uname -m) not supported"; \ + fi; } \ + && yum install -y $LIBSECRET $LIBKEYBOARD make cmake gcc gcc-c++ python3.9 git git-core-doc openssh less libX11-devel libxkbcommon krb5-devel bash tar gzip rsync patch \ + && yum -y clean all && rm -rf /var/cache/yum + +######################################################### +# +# Copy Che-Code to the container +# +######################################################### +COPY code /checode-compilation +WORKDIR /checode-compilation +ENV ELECTRON_SKIP_BINARY_DOWNLOAD=1 \ + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 \ + VSCODE_MANGLE_WORKERS=1 + +# Initialize a git repository for code build tools +RUN git init . + +# change network timeout (slow using multi-arch build) +RUN npm config set fetch-retry-mintimeout 100000 && npm config set fetch-retry-maxtimeout 600000 + +# Grab dependencies (and force to rebuild them) +RUN rm -rf /checode-compilation/node_modules && npm install --force + +# Compile +RUN NODE_ARCH=$(echo "console.log(process.arch)" | node) \ + && NODE_VERSION=$(cat /checode-compilation/remote/.npmrc | grep target | cut -d '=' -f 2 | tr -d '"') \ + # cache node from this image to avoid to grab it from within the build + && mkdir -p /checode-compilation/.build/node/v${NODE_VERSION}/linux-${NODE_ARCH} \ + && echo "caching /checode-compilation/.build/node/v${NODE_VERSION}/linux-${NODE_ARCH}/node" \ + && cp /usr/bin/node /checode-compilation/.build/node/v${NODE_VERSION}/linux-${NODE_ARCH}/node \ + && NODE_OPTIONS="--max-old-space-size=49152" ./node_modules/.bin/gulp vscode-reh-web-linux-${NODE_ARCH}-min \ + && cp -r ../vscode-reh-web-linux-${NODE_ARCH} /checode \ + # cache shared libs from this image to provide them to a user's container + && mkdir -p /checode/ld_libs \ + && find /usr/lib64 -name 'libbrotli*' 2>/dev/null | xargs -I {} cp -t /checode/ld_libs {} \ + && find /usr/lib64 -name 'libnode.so*' -exec cp -P -t /checode/ld_libs/ {} + \ + && find /usr/lib64 -name 'libz.so*' -exec cp -P -t /checode/ld_libs/ {} + + +RUN chmod a+x /checode/out/server-main.js \ + && chgrp -R 0 /checode && chmod -R g+rwX /checode + +### Beginning of tests +# Do not change line above! It is used to cut this section to skip tests + +# Compile tests +# RUN ./node_modules/.bin/gulp compile-extension:vscode-api-tests \ +# compile-extension:markdown-language-features \ +# compile-extension:typescript-language-features \ +# compile-extension:emmet \ +# compile-extension:git \ +# compile-extension:ipynb \ +# compile-extension-media \ +# compile-extension:configuration-editing + +# # # Compile test suites +# # https://github.com/microsoft/vscode/blob/cdde5bedbf3ed88f93b5090bb3ed9ef2deb7a1b4/test/integration/browser/README.md#compile +# RUN if [ "$(uname -m)" = "x86_64" ]; then npm --prefix test/smoke run compile && npm --prefix test/integration/browser run compile; fi + +# # install test dependencies +# ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=0 +# RUN if [ "$(uname -m)" = "x86_64" ]; then npm run playwright-install; fi +# # Install procps to manage to kill processes and centos stream repository +# RUN if [ "$(uname -m)" = "x86_64" ]; then \ +# ARCH=$(uname -m) && \ +# yum install --nobest -y procps \ +# https://rpmfind.net/linux/epel/9/Everything/x86_64/Packages/e/epel-release-9-10.el9.noarch.rpm \ +# https://rpmfind.net/linux/centos-stream/9-stream/BaseOS/x86_64/os/Packages/centos-gpg-keys-9.0-23.el9.noarch.rpm \ +# https://rpmfind.net/linux/centos-stream/9-stream/BaseOS/x86_64/os/Packages/centos-stream-repos-9.0-23.el9.noarch.rpm; \ +# fi + +# RUN if [ "$(uname -m)" = "x86_64" ]; then \ +# yum install -y chromium && \ +# PLAYWRIGHT_CHROMIUM_PATH=$(echo /opt/app-root/src/.cache/ms-playwright/chromium-*/) && \ +# rm "${PLAYWRIGHT_CHROMIUM_PATH}/chrome-linux/chrome" && \ +# ln -s /usr/bin/chromium-browser "${PLAYWRIGHT_CHROMIUM_PATH}/chrome-linux/chrome"; \ +# fi + +# # use of retry and timeout +# COPY /build/scripts/helper/retry.sh /opt/app-root/src/retry.sh +# RUN chmod u+x /opt/app-root/src/retry.sh + +# # Run integration tests (Browser) +# RUN if [ "$(uname -m)" = "x86_64" ]; then \ +# NODE_ARCH=$(echo "console.log(process.arch)" | node) \ +# VSCODE_REMOTE_SERVER_PATH="$(pwd)/../vscode-reh-web-linux-${NODE_ARCH}" \ +# /opt/app-root/src/retry.sh -v -t 3 -s 2 -- timeout -v 5m ./scripts/test-web-integration.sh --browser chromium; \ +# fi + +# # Run smoke tests (Browser) +# RUN if [ "$(uname -m)" = "x86_64" ]; then \ +# NODE_ARCH=$(echo "console.log(process.arch)" | node) \ +# VSCODE_REMOTE_SERVER_PATH="$(pwd)/../vscode-reh-web-linux-${NODE_ARCH}" \ +# /opt/app-root/src/retry.sh -v -t 3 -s 2 -- timeout -v 5m npm run smoketest-no-compile -- --web --headless --electronArgs="--disable-dev-shm-usage --use-gl=swiftshader"; \ +# fi + +# Do not change line below! It is used to cut this section to skip tests +### Ending of tests + +######################################################### +# +# Copy VS Code launcher to the container +# +######################################################### +COPY launcher /checode-launcher +WORKDIR /checode-launcher +RUN npm install \ + && mkdir /checode/launcher \ + && cp -r out/src/*.js /checode/launcher \ + && chgrp -R 0 /checode && chmod -R g+rwX /checode + +# Store the content of the result +FROM scratch as linux-libc-content +COPY --from=linux-libc-ubi9-builder /checode /checode-linux-libc/ubi9 \ No newline at end of file diff --git a/code/.config/guardian/.gdnbaselines b/code/.config/guardian/.gdnbaselines deleted file mode 100644 index 9408d050c9a..00000000000 --- a/code/.config/guardian/.gdnbaselines +++ /dev/null @@ -1,67 +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-08-25 17:55:20Z" - } - }, - "results": { - "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-08-25 17:54:06Z" - }, - "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-08-25 17:54:06Z" - }, - "4dbc45d0405de2e83d53f10227e36b2a9d15eceb2c5e6934da5c4a1bffbfad89": { - "signature": "4dbc45d0405de2e83d53f10227e36b2a9d15eceb2c5e6934da5c4a1bffbfad89", - "alternativeSignatures": [ - "b6bab85ba5e97bc4e6ff2e8a7913cb9f4f3346f7bda435d176e0b1e3cfb883cf" - ], - "target": "file:///D:/a/_work/1/vscode-server-win32-arm64/node.exe", - "memberOf": [ - "default" - ], - "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/code/.config/guardian/.gdnsuppress b/code/.config/guardian/.gdnsuppress index 6f6833c5422..3ac8361e5fc 100644 --- a/code/.config/guardian/.gdnsuppress +++ b/code/.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,70 +7,62 @@ "suppressionSets": { "default": { "name": "default", - "createdDate": "2025-03-17 11:52:32Z", - "lastUpdatedDate": "2025-08-06 13:58:56Z" + "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" }, - "9d60fae9db4b8d511637e4a0f902820fbabf962c64ce2b2b8c8ae54c0c06d3ab": { - "signature": "9d60fae9db4b8d511637e4a0f902820fbabf962c64ce2b2b8c8ae54c0c06d3ab", + "024ff37af329b63d2d9c83784cc071badf63b7729e3dd0969ab921d2f04d8e09": { + "signature": "024ff37af329b63d2d9c83784cc071badf63b7729e3dd0969ab921d2f04d8e09", "alternativeSignatures": [ - "d06382c4909cfa81370526b06d4c47ebdf4425fc0b36053d3f457d5cdf5df8a8" + "b46b7d6ed331f3e62eff23c57d3a074f76ef618f108929851065904200f5a572" ], + "target": "file:///D:/a/_work/1/vscode-server-win32-arm64-web/node.exe", "memberOf": [ "default" ], - "createdDate": "2025-08-06 13:58:56Z" - }, - "7f0626fd14d60d2810a8ddfc1e9fdf1563b991dc8e1ac5880eca42449f752e90": { - "signature": "7f0626fd14d60d2810a8ddfc1e9fdf1563b991dc8e1ac5880eca42449f752e90", - "alternativeSignatures": [ - "6d3dc1d67e5413347520202ce038daf52825c58099670688103db2661facf187" - ], - "memberOf": [ - "default" - ], - "createdDate": "2025-08-06 13:58:56Z" - }, - "40121a40ac42fef69ebcb2b8c2ec7ee659c8d10bc7ab4e95d2a290a48b3d281f": { - "signature": "40121a40ac42fef69ebcb2b8c2ec7ee659c8d10bc7ab4e95d2a290a48b3d281f", - "alternativeSignatures": [ - "3f4bc3f870aa2c71232dd65907522d59a1964c148e373105dec71e0d3da9427f" - ], - "memberOf": [ - "default" - ], - "createdDate": "2025-08-06 13:58:56Z" + "tool": "binskim", + "ruleId": "BA2008", + "createdDate": "2025-08-25 17:45:35Z" } } } diff --git a/code/.eslint-ignore b/code/.eslint-ignore index e493198185e..c65ccc2baac 100644 --- a/code/.eslint-ignore +++ b/code/.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/code/.eslint-plugin-local/README.md b/code/.eslint-plugin-local/README.md new file mode 100644 index 00000000000..1d100cfba07 --- /dev/null +++ b/code/.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/code/.eslint-plugin-local/code-amd-node-module.ts b/code/.eslint-plugin-local/code-amd-node-module.ts index b622c98a89a..eb6a40c5e30 100644 --- a/code/.eslint-plugin-local/code-amd-node-module.ts +++ b/code/.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/code/.eslint-plugin-local/code-declare-service-brand.ts b/code/.eslint-plugin-local/code-declare-service-brand.ts index 85cf0671545..a077e7b38c6 100644 --- a/code/.eslint-plugin-local/code-declare-service-brand.ts +++ b/code/.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/code/.eslint-plugin-local/code-ensure-no-disposables-leak-in-test.ts b/code/.eslint-plugin-local/code-ensure-no-disposables-leak-in-test.ts index c657df9bd30..7f1d20482b8 100644 --- a/code/.eslint-plugin-local/code-ensure-no-disposables-leak-in-test.ts +++ b/code/.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/code/.eslint-plugin-local/code-import-patterns.ts b/code/.eslint-plugin-local/code-import-patterns.ts index e4beb9a4738..419e26d41ac 100644 --- a/code/.eslint-plugin-local/code-import-patterns.ts +++ b/code/.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/code/.eslint-plugin-local/code-layering.ts b/code/.eslint-plugin-local/code-layering.ts index f8b769a1bf6..ac77eb97cf0 100644 --- a/code/.eslint-plugin-local/code-layering.ts +++ b/code/.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/code/.eslint-plugin-local/code-limited-top-functions.ts b/code/.eslint-plugin-local/code-limited-top-functions.ts index 7b48d02a0fe..8c6abacc9d8 100644 --- a/code/.eslint-plugin-local/code-limited-top-functions.ts +++ b/code/.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/code/.eslint-plugin-local/code-must-use-result.ts b/code/.eslint-plugin-local/code-must-use-result.ts index e249f36dccf..b97396c7e52 100644 --- a/code/.eslint-plugin-local/code-must-use-result.ts +++ b/code/.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/code/.eslint-plugin-local/code-must-use-super-dispose.ts b/code/.eslint-plugin-local/code-must-use-super-dispose.ts index ca776d8a2ad..0213d200957 100644 --- a/code/.eslint-plugin-local/code-must-use-super-dispose.ts +++ b/code/.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/code/.eslint-plugin-local/code-no-any-casts.ts b/code/.eslint-plugin-local/code-no-any-casts.ts new file mode 100644 index 00000000000..87c3c9466cd --- /dev/null +++ b/code/.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/code/.eslint-plugin-local/code-no-dangerous-type-assertions.ts b/code/.eslint-plugin-local/code-no-dangerous-type-assertions.ts index f900d778a94..b2e97943670 100644 --- a/code/.eslint-plugin-local/code-no-dangerous-type-assertions.ts +++ b/code/.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/code/.eslint-plugin-local/code-no-deep-import-of-internal.ts b/code/.eslint-plugin-local/code-no-deep-import-of-internal.ts index 3f54665b49a..cb2d450d2ee 100644 --- a/code/.eslint-plugin-local/code-no-deep-import-of-internal.ts +++ b/code/.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/code/.eslint-plugin-local/code-no-global-document-listener.ts b/code/.eslint-plugin-local/code-no-global-document-listener.ts index 049426a5a03..ad4ec0da820 100644 --- a/code/.eslint-plugin-local/code-no-global-document-listener.ts +++ b/code/.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/code/.eslint-plugin-local/code-no-in-operator.ts b/code/.eslint-plugin-local/code-no-in-operator.ts new file mode 100644 index 00000000000..026a8f5fe7a --- /dev/null +++ b/code/.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/code/.eslint-plugin-local/code-no-localization-template-literals.ts b/code/.eslint-plugin-local/code-no-localization-template-literals.ts new file mode 100644 index 00000000000..30a5de7f364 --- /dev/null +++ b/code/.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/code/.eslint-plugin-local/code-no-localized-model-description.ts b/code/.eslint-plugin-local/code-no-localized-model-description.ts new file mode 100644 index 00000000000..a624aeb8619 --- /dev/null +++ b/code/.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/code/.eslint-plugin-local/code-no-native-private.ts b/code/.eslint-plugin-local/code-no-native-private.ts index e2d20694ca8..5d945ec34f7 100644 --- a/code/.eslint-plugin-local/code-no-native-private.ts +++ b/code/.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/code/.eslint-plugin-local/code-no-nls-in-standalone-editor.ts b/code/.eslint-plugin-local/code-no-nls-in-standalone-editor.ts index c0d60985604..2b3896795a8 100644 --- a/code/.eslint-plugin-local/code-no-nls-in-standalone-editor.ts +++ b/code/.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/code/.eslint-plugin-local/code-no-observable-get-in-reactive-context.ts b/code/.eslint-plugin-local/code-no-observable-get-in-reactive-context.ts new file mode 100644 index 00000000000..94d3a1b4ead --- /dev/null +++ b/code/.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/code/.eslint-plugin-local/code-no-potentially-unsafe-disposables.ts b/code/.eslint-plugin-local/code-no-potentially-unsafe-disposables.ts index 69976275051..bc250df1182 100644 --- a/code/.eslint-plugin-local/code-no-potentially-unsafe-disposables.ts +++ b/code/.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/code/.eslint-plugin-local/code-no-reader-after-await.ts b/code/.eslint-plugin-local/code-no-reader-after-await.ts new file mode 100644 index 00000000000..6d0e8d39b06 --- /dev/null +++ b/code/.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/code/.eslint-plugin-local/code-no-runtime-import.ts b/code/.eslint-plugin-local/code-no-runtime-import.ts index afebe0b0d68..2c53d84f973 100644 --- a/code/.eslint-plugin-local/code-no-runtime-import.ts +++ b/code/.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/code/.eslint-plugin-local/code-no-standalone-editor.ts b/code/.eslint-plugin-local/code-no-standalone-editor.ts index 36bf48b1417..dca4e22bfb0 100644 --- a/code/.eslint-plugin-local/code-no-standalone-editor.ts +++ b/code/.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/code/.eslint-plugin-local/code-no-static-self-ref.ts b/code/.eslint-plugin-local/code-no-static-self-ref.ts index f620645565a..9a47f87b9c1 100644 --- a/code/.eslint-plugin-local/code-no-static-self-ref.ts +++ b/code/.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/code/.eslint-plugin-local/code-no-test-async-suite.ts b/code/.eslint-plugin-local/code-no-test-async-suite.ts index 7d5fadfad0d..b53747390b0 100644 --- a/code/.eslint-plugin-local/code-no-test-async-suite.ts +++ b/code/.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/code/.eslint-plugin-local/code-no-test-only.ts b/code/.eslint-plugin-local/code-no-test-only.ts index d4751eef2ee..389d32fe13b 100644 --- a/code/.eslint-plugin-local/code-no-test-only.ts +++ b/code/.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/code/.eslint-plugin-local/code-no-unexternalized-strings.ts b/code/.eslint-plugin-local/code-no-unexternalized-strings.ts index abb3980eb54..a7065cb2a0d 100644 --- a/code/.eslint-plugin-local/code-no-unexternalized-strings.ts +++ b/code/.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/code/.eslint-plugin-local/code-no-unused-expressions.ts b/code/.eslint-plugin-local/code-no-unused-expressions.ts index bd632884dbd..c481313a9a2 100644 --- a/code/.eslint-plugin-local/code-no-unused-expressions.ts +++ b/code/.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/code/.eslint-plugin-local/code-parameter-properties-must-have-explicit-accessibility.ts b/code/.eslint-plugin-local/code-parameter-properties-must-have-explicit-accessibility.ts index c9837052fa5..f00d3e1435c 100644 --- a/code/.eslint-plugin-local/code-parameter-properties-must-have-explicit-accessibility.ts +++ b/code/.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/code/.eslint-plugin-local/code-policy-localization-key-match.ts b/code/.eslint-plugin-local/code-policy-localization-key-match.ts new file mode 100644 index 00000000000..10749d5bb00 --- /dev/null +++ b/code/.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/code/.eslint-plugin-local/code-translation-remind.ts b/code/.eslint-plugin-local/code-translation-remind.ts index cceaba4c419..42032321167 100644 --- a/code/.eslint-plugin-local/code-translation-remind.ts +++ b/code/.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/code/.eslint-plugin-local/index.js b/code/.eslint-plugin-local/index.js deleted file mode 100644 index 3646c8c4157..00000000000 --- a/code/.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/code/.eslint-plugin-local/index.ts b/code/.eslint-plugin-local/index.ts new file mode 100644 index 00000000000..101733773f0 --- /dev/null +++ b/code/.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/code/.eslint-plugin-local/package.json b/code/.eslint-plugin-local/package.json index a0df0c86778..90e7facf0a0 100644 --- a/code/.eslint-plugin-local/package.json +++ b/code/.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/code/.eslint-plugin-local/tests/code-no-observable-get-in-reactive-context-test.ts b/code/.eslint-plugin-local/tests/code-no-observable-get-in-reactive-context-test.ts new file mode 100644 index 00000000000..fd92a45e22b --- /dev/null +++ b/code/.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/code/.eslint-plugin-local/tests/code-no-reader-after-await-test.ts b/code/.eslint-plugin-local/tests/code-no-reader-after-await-test.ts new file mode 100644 index 00000000000..2dc0f457a7f --- /dev/null +++ b/code/.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/code/.eslint-plugin-local/tsconfig.json b/code/.eslint-plugin-local/tsconfig.json index 7676f59a781..0de6dacc146 100644 --- a/code/.eslint-plugin-local/tsconfig.json +++ b/code/.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/code/.eslint-plugin-local/utils.ts b/code/.eslint-plugin-local/utils.ts index b7457884f85..e956e679148 100644 --- a/code/.eslint-plugin-local/utils.ts +++ b/code/.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/code/.eslint-plugin-local/vscode-dts-cancellation.ts b/code/.eslint-plugin-local/vscode-dts-cancellation.ts index 5e8e875af21..dd5ca293727 100644 --- a/code/.eslint-plugin-local/vscode-dts-cancellation.ts +++ b/code/.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/code/.eslint-plugin-local/vscode-dts-create-func.ts b/code/.eslint-plugin-local/vscode-dts-create-func.ts index 01db244ce76..91589f91584 100644 --- a/code/.eslint-plugin-local/vscode-dts-create-func.ts +++ b/code/.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/code/.eslint-plugin-local/vscode-dts-event-naming.ts b/code/.eslint-plugin-local/vscode-dts-event-naming.ts index c27d934f4f9..6f75c50ca12 100644 --- a/code/.eslint-plugin-local/vscode-dts-event-naming.ts +++ b/code/.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/code/.eslint-plugin-local/vscode-dts-interface-naming.ts b/code/.eslint-plugin-local/vscode-dts-interface-naming.ts index 6b33f9c5343..d6591b97d8d 100644 --- a/code/.eslint-plugin-local/vscode-dts-interface-naming.ts +++ b/code/.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/code/.eslint-plugin-local/vscode-dts-literal-or-types.ts b/code/.eslint-plugin-local/vscode-dts-literal-or-types.ts index 44ef0fd2a7c..0815720cf92 100644 --- a/code/.eslint-plugin-local/vscode-dts-literal-or-types.ts +++ b/code/.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/code/.eslint-plugin-local/vscode-dts-provider-naming.ts b/code/.eslint-plugin-local/vscode-dts-provider-naming.ts index db8350dd9bc..64e0101a71e 100644 --- a/code/.eslint-plugin-local/vscode-dts-provider-naming.ts +++ b/code/.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/code/.eslint-plugin-local/vscode-dts-string-type-literals.ts b/code/.eslint-plugin-local/vscode-dts-string-type-literals.ts index 0f6d711a3db..ee70d663281 100644 --- a/code/.eslint-plugin-local/vscode-dts-string-type-literals.ts +++ b/code/.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/code/.eslint-plugin-local/vscode-dts-use-export.ts b/code/.eslint-plugin-local/vscode-dts-use-export.ts index 904feaeec36..798572d4f21 100644 --- a/code/.eslint-plugin-local/vscode-dts-use-export.ts +++ b/code/.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/code/.eslint-plugin-local/vscode-dts-use-thenable.ts b/code/.eslint-plugin-local/vscode-dts-use-thenable.ts index 683394ad115..2c1ff4c9296 100644 --- a/code/.eslint-plugin-local/vscode-dts-use-thenable.ts +++ b/code/.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/code/.eslint-plugin-local/vscode-dts-vscode-in-comments.ts b/code/.eslint-plugin-local/vscode-dts-vscode-in-comments.ts index 80d3b7003d7..ab3c338096c 100644 --- a/code/.eslint-plugin-local/vscode-dts-vscode-in-comments.ts +++ b/code/.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/code/.github/CODENOTIFY b/code/.github/CODENOTIFY new file mode 100644 index 00000000000..ac22ac40d26 --- /dev/null +++ b/code/.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/code/.github/CODEOWNERS b/code/.github/CODEOWNERS index c669df38155..0b3388f9aeb 100644 --- a/code/.github/CODEOWNERS +++ b/code/.github/CODEOWNERS @@ -9,31 +9,11 @@ .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 -src/vs/base/browser/ui/tree/** @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/grid/** @joaomoreno @benibenj -src/bootstrap-cli.ts @bpasero @deepak1556 -src/bootstrap-esm.ts @bpasero @deepak1556 -src/bootstrap-fork.ts @bpasero @deepak1556 -src/bootstrap-import.ts @bpasero @deepak1556 -src/bootstrap-meta.ts @bpasero @deepak1556 -src/bootstrap-node.ts @bpasero @deepak1556 -src/bootstrap-server.ts @bpasero @deepak1556 -src/cli.ts @bpasero @deepak1556 -src/main.ts @bpasero @deepak1556 -src/server-cli.ts @bpasero @deepak1556 -src/server-main.ts @bpasero @deepak1556 -src/vs/base/parts/sandbox/** @bpasero @deepak1556 -src/vs/base/parts/storage/** @bpasero @deepak1556 -src/vs/platform/backup/** @bpasero -src/vs/platform/files/** @bpasero @deepak1556 -src/vs/base/node/pfs.ts @bpasero @deepak1556 -src/vs/code/** @bpasero @deepak1556 -src/vs/workbench/services/textfile/** @bpasero -src/vs/workbench/services/workingCopy/** @bpasero -# 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/code/.github/agents/data.md b/code/.github/agents/data.md new file mode 100644 index 00000000000..5809fb06d86 --- /dev/null +++ b/code/.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/code/.github/agents/demonstrate.md b/code/.github/agents/demonstrate.md new file mode 100644 index 00000000000..7b2e66cda93 --- /dev/null +++ b/code/.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/code/.github/agents/engineering.md b/code/.github/agents/engineering.md new file mode 100644 index 00000000000..1cfad832f7a --- /dev/null +++ b/code/.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/code/.github/classifier.json b/code/.github/classifier.json index 0e95e0ed13a..1ca855d5074 100644 --- a/code/.github/classifier.json +++ b/code/.github/classifier.json @@ -17,6 +17,7 @@ "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"]}, @@ -112,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"]}, @@ -223,33 +224,34 @@ "terminal": {"assign": ["meganrogge"]}, "terminal-accessibility": {"assign": ["meganrogge"]}, "terminal-conpty": {"assign": ["meganrogge"]}, - "terminal-editors": {"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", "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", "meganrogge"]}, - "terminal-process": {"assign": ["Tyriar", "meganrogge"]}, + "terminal-persistence": {"assign": ["Tyriar"]}, + "terminal-process": {"assign": ["anthonykim1"]}, "terminal-profiles": {"assign": ["meganrogge"]}, "terminal-quick-fix": {"assign": ["meganrogge"]}, "terminal-rendering": {"assign": ["Tyriar"]}, - "terminal-search": {"assign": ["Tyriar", "meganrogge"]}, "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-sh": {"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/code/.github/commands.json b/code/.github/commands.json index 84e5668ed8e..29288f1309b 100644 --- a/code/.github/commands.json +++ b/code/.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." @@ -630,7 +632,43 @@ "removeLabel": "~capi", "assign": [ "samvantran", - "thispaul" - ] + "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/code/.github/copilot-instructions.md b/code/.github/copilot-instructions.md index a17472617fc..af0becdc630 100644 --- a/code/.github/copilot-instructions.md +++ b/code/.github/copilot-instructions.md @@ -48,12 +48,10 @@ Each extension follows the standard VS Code extension structure with `package.js ## Validating TypeScript changes -You MUST check compilation output before running ANY script or declaring work complete! +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. -1. **ALWAYS** check the `VS Code - Build` watch task output for compilation errors -2. **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 +- 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 @@ -61,8 +59,7 @@ You MUST check compilation output before running ANY script or declaring work co - Start the task if it's not already running in the background ### TypeScript validation steps -- Use run test tool or `scripts/test.sh` (`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 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 @@ -92,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). @@ -132,4 +130,10 @@ 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. +- Do not duplicate code. Always look for existing utility functions, helpers, or patterns in the codebase before implementing new functionality. Reuse and extend existing code whenever possible. diff --git a/code/.github/instructions/disposable.instructions.md b/code/.github/instructions/disposable.instructions.md new file mode 100644 index 00000000000..06e1b62f7e3 --- /dev/null +++ b/code/.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/code/.github/instructions/learnings.instructions.md b/code/.github/instructions/learnings.instructions.md new file mode 100644 index 00000000000..78a9f52a06e --- /dev/null +++ b/code/.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/code/.github/instructions/observables.instructions.md b/code/.github/instructions/observables.instructions.md new file mode 100644 index 00000000000..2aedc290be1 --- /dev/null +++ b/code/.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/code/.github/instructions/tree-widgets.instructions.md b/code/.github/instructions/tree-widgets.instructions.md new file mode 100644 index 00000000000..b5df37f75e6 --- /dev/null +++ b/code/.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/code/.github/prompts/build-champ.prompt.md b/code/.github/prompts/build-champ.prompt.md new file mode 100644 index 00000000000..58d22ecc41d --- /dev/null +++ b/code/.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/code/.github/prompts/codenotify.prompt.md b/code/.github/prompts/codenotify.prompt.md new file mode 100644 index 00000000000..23b42157397 --- /dev/null +++ b/code/.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/code/.github/prompts/component.prompt.md b/code/.github/prompts/component.prompt.md new file mode 100644 index 00000000000..5297e2b6ad4 --- /dev/null +++ b/code/.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/code/.github/prompts/doc-comments.prompt.md b/code/.github/prompts/doc-comments.prompt.md new file mode 100644 index 00000000000..b3848435901 --- /dev/null +++ b/code/.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/code/.github/prompts/find-duplicates.prompt.md b/code/.github/prompts/find-duplicates.prompt.md new file mode 100644 index 00000000000..7bda0fd83af --- /dev/null +++ b/code/.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/code/.github/prompts/find-issue.prompt.md b/code/.github/prompts/find-issue.prompt.md new file mode 100644 index 00000000000..dfdfdd56b69 --- /dev/null +++ b/code/.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/code/.github/prompts/fixIssueNo.prompt.md b/code/.github/prompts/fixIssueNo.prompt.md new file mode 100644 index 00000000000..22130f14047 --- /dev/null +++ b/code/.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/code/.github/prompts/implement.prompt.md b/code/.github/prompts/implement.prompt.md index 4766b33436f..4deb7ba76d7 100644 --- a/code/.github/prompts/implement.prompt.md +++ b/code/.github/prompts/implement.prompt.md @@ -1,7 +1,7 @@ --- -mode: agent -description: 'Implement the solution for a problem.' -tools: ['edit', 'notebooks', 'search', 'runCommands', 'runTasks', 'usages', 'vscodeAPI', 'problems', 'changes', 'testFailure', 'openSimpleBrowser', 'fetch', 'githubRepo', 'todos', 'runTests'] +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/code/.github/prompts/issue-grouping.prompt.md b/code/.github/prompts/issue-grouping.prompt.md new file mode 100644 index 00000000000..8f6bfea7660 --- /dev/null +++ b/code/.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/code/.github/prompts/micro-perf.prompt.md b/code/.github/prompts/micro-perf.prompt.md new file mode 100644 index 00000000000..dbfad2dfc27 --- /dev/null +++ b/code/.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/code/.github/prompts/migrate.prompt.md b/code/.github/prompts/migrate.prompt.md new file mode 100644 index 00000000000..d404ebf6f4b --- /dev/null +++ b/code/.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/code/.github/prompts/no-any.prompt.md b/code/.github/prompts/no-any.prompt.md new file mode 100644 index 00000000000..7e78fefa27e --- /dev/null +++ b/code/.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/code/.github/prompts/plan-deep.prompt.md b/code/.github/prompts/plan-deep.prompt.md new file mode 100644 index 00000000000..a321fbe2cbb --- /dev/null +++ b/code/.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/code/.github/prompts/plan-fast.prompt.md b/code/.github/prompts/plan-fast.prompt.md new file mode 100644 index 00000000000..2ae8191933a --- /dev/null +++ b/code/.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/code/.github/prompts/plan.prompt.md b/code/.github/prompts/plan.prompt.md index a0822ae4f3c..e94ee7b24eb 100644 --- a/code/.github/prompts/plan.prompt.md +++ b/code/.github/prompts/plan.prompt.md @@ -1,19 +1,5 @@ --- -mode: agent -description: 'Plan the solution for a problem.' -tools: ['getNotebookSummary', 'readNotebookCellOutput', 'search', 'getTerminalOutput', 'terminalSelection', 'terminalLastCommand', 'usages', 'vscodeAPI', 'think', 'problems', 'changes', 'testFailure', 'fetch', 'githubRepo', 'todos', 'get_issue', 'get_issue_comments', 'get_me'] +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/code/.github/prompts/playwright.prompt.md b/code/.github/prompts/playwright.prompt.md deleted file mode 100644 index 107c91475c0..00000000000 --- a/code/.github/prompts/playwright.prompt.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -mode: agent -description: 'Use playwright & automation tools to _see_ the code changes you have made' -tools: ['codebase', 'usages', 'vscodeAPI', 'problems', 'changes', 'testFailure', 'openSimpleBrowser', 'fetch', 'findTestFiles', 'searchResults', 'githubRepo', 'todos', 'runTests', 'editFiles', 'runNotebooks', 'search', 'new', 'runCommands', 'runTasks', 'vscode-playwright-mcp', 'get_commit', 'get_discussion', 'get_discussion_comments', 'get_issue', 'get_issue_comments'] ---- -You are being requested to visually confirm the code changes you are making using vscode-playwright-mcp. - -You MUST run vscode_automation_start & browser_snapshot. -You MUST verify the bad behavior you are investigating using vscode-playwright-mcp. -You MUST verify the code changes you have made using vscode-playwright-mcp. -You MUST take before and after screenshots. -Remember, you are NOT writing playwright tests; instead, focus on using the tools to validate and explore the changes. -You MAY need to make multiple passes, iterating between making code changes and verifying them with the tools. -You MUST reload the window (`Developer: Reload Window` command) after making changes to ensure they are applied correctly. -You MAY make temporary changes to the code to facilitate testing and exploration. For example, using the quick pick in the Configure Display Language action as a scratch pad to add buttons to it. diff --git a/code/.github/prompts/setup-environment.prompt.md b/code/.github/prompts/setup-environment.prompt.md new file mode 100644 index 00000000000..fa934ad7029 --- /dev/null +++ b/code/.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/code/.github/prompts/update-instructions.prompt.md b/code/.github/prompts/update-instructions.prompt.md new file mode 100644 index 00000000000..b38001671ef --- /dev/null +++ b/code/.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/code/.github/workflows/copilot-setup-steps.yml b/code/.github/workflows/copilot-setup-steps.yml new file mode 100644 index 00000000000..8cf20c233b0 --- /dev/null +++ b/code/.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-all2 -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/code/.github/workflows/monaco-editor.yml b/code/.github/workflows/monaco-editor.yml index 95ed1811177..822210da8d0 100644 --- a/code/.github/workflows/monaco-editor.yml +++ b/code/.github/workflows/monaco-editor.yml @@ -19,20 +19,20 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v5 + - 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/code/.github/workflows/no-engineering-system-changes.yml b/code/.github/workflows/no-engineering-system-changes.yml new file mode 100644 index 00000000000..45d1ae55f62 --- /dev/null +++ b/code/.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/code/.github/workflows/no-yarn-lock-changes.yml b/code/.github/workflows/no-yarn-lock-changes.yml deleted file mode 100644 index 5727d1c511c..00000000000 --- a/code/.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/code/.github/workflows/pr-darwin-test.yml b/code/.github/workflows/pr-darwin-test.yml index 655c8e9e6c3..c876d2a3782 100644 --- a/code/.github/workflows/pr-darwin-test.yml +++ b/code/.github/workflows/pr-darwin-test.yml @@ -24,21 +24,19 @@ jobs: VSCODE_ARCH: arm64 steps: - name: Checkout microsoft/vscode - uses: actions/checkout@v5 + 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 }} @@ -111,7 +109,7 @@ jobs: set -e for i in {1..3}; do # try 3 times (matching retryCountOnTaskFailure: 3) - if npm exec -- npm-run-all -lp "electron ${{ env.VSCODE_ARCH }}" "playwright-install"; then + if npm exec -- npm-run-all2 -lp "electron ${{ env.VSCODE_ARCH }}" "playwright-install"; then echo "Download successful on attempt $i" break fi @@ -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/code/.github/workflows/pr-linux-cli-test.yml b/code/.github/workflows/pr-linux-cli-test.yml index 1b9a52a821e..003e1344fb6 100644 --- a/code/.github/workflows/pr-linux-cli-test.yml +++ b/code/.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@v5 + uses: actions/checkout@v6 - name: Install Rust run: | diff --git a/code/.github/workflows/pr-linux-test.yml b/code/.github/workflows/pr-linux-test.yml index 593c6ad9f4d..df6ab20e586 100644 --- a/code/.github/workflows/pr-linux-test.yml +++ b/code/.github/workflows/pr-linux-test.yml @@ -24,14 +24,12 @@ jobs: VSCODE_ARCH: x64 steps: - name: Checkout microsoft/vscode - uses: actions/checkout@v5 + 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 }} @@ -139,7 +137,7 @@ jobs: set -e for i in {1..3}; do # try 3 times (matching retryCountOnTaskFailure: 3) - if npm exec -- npm-run-all -lp "electron ${{ env.VSCODE_ARCH }}" "playwright-install"; then + if npm exec -- npm-run-all2 -lp "electron ${{ env.VSCODE_ARCH }}" "playwright-install"; then echo "Download successful on attempt $i" break fi @@ -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/code/.github/workflows/pr-node-modules.yml b/code/.github/workflows/pr-node-modules.yml index a8dfc6a22e7..68e65fd1298 100644 --- a/code/.github/workflows/pr-node-modules.yml +++ b/code/.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@v5 + 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@v5 + 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') }}" @@ -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@v5 + 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@v5 + 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/code/.github/workflows/pr-win32-test.yml b/code/.github/workflows/pr-win32-test.yml index ac4140211a3..bd4a62d42fa 100644 --- a/code/.github/workflows/pr-win32-test.yml +++ b/code/.github/workflows/pr-win32-test.yml @@ -24,23 +24,21 @@ jobs: VSCODE_ARCH: x64 steps: - name: Checkout microsoft/vscode - uses: actions/checkout@v5 + 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-all2 -lp "electron ${{ env.VSCODE_ARCH }}" "playwright-install" break } catch { @@ -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/code/.github/workflows/pr.yml b/code/.github/workflows/pr.yml index f3568b59873..c6af7adaa7a 100644 --- a/code/.github/workflows/pr.yml +++ b/code/.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@v5 + 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-all2 -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/code/.github/workflows/telemetry.yml b/code/.github/workflows/telemetry.yml index 1f43144f1dc..e30d3cc8da3 100644 --- a/code/.github/workflows/telemetry.yml +++ b/code/.github/workflows/telemetry.yml @@ -7,11 +7,11 @@ jobs: runs-on: 'ubuntu-latest' steps: - - uses: 'actions/checkout@v5' + - 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/code/.gitignore b/code/.gitignore index 62394c60784..92971a7a573 100644 --- a/code/.gitignore +++ b/code/.gitignore @@ -22,3 +22,4 @@ product.overrides.json *.snap.actual *.tsbuildinfo .vscode-test +vscode-telemetry-docs/ diff --git a/code/.npmrc b/code/.npmrc index 749c3c543e2..060337bfad8 100644 --- a/code/.npmrc +++ b/code/.npmrc @@ -1,8 +1,7 @@ disturl="https://electronjs.org/headers" -target="37.3.1" -ms_build_id="12404162" +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/code/.nvmrc b/code/.nvmrc index 91d5f6ff8e3..5767036af0e 100644 --- a/code/.nvmrc +++ b/code/.nvmrc @@ -1 +1 @@ -22.18.0 +22.21.1 diff --git a/code/.vscode-test.js b/code/.vscode-test.js index 2e49c90126b..4c093d0e2b3 100644 --- a/code/.vscode-test.js +++ b/code/.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/code/.vscode/extensions.json b/code/.vscode/extensions.json index 737efece5a4..3fb87652c81 100644 --- a/code/.vscode/extensions.json +++ b/code/.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/code/.vscode/extensions/vscode-selfhost-test-provider/src/extension.ts b/code/.vscode/extensions/vscode-selfhost-test-provider/src/extension.ts index cbb8d50bf99..2f9ac62abe4 100644 --- a/code/.vscode/extensions/vscode-selfhost-test-provider/src/extension.ts +++ b/code/.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/code/.vscode/launch.json b/code/.vscode/launch.json index 216afd8b573..a7a15cc31a6 100644 --- a/code/.vscode/launch.json +++ b/code/.vscode/launch.json @@ -282,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" }, @@ -298,7 +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": [ @@ -322,6 +325,7 @@ "presentation": { "hidden": true, }, + "preLaunchTask": "Launch Monaco Editor Vite" }, { "type": "node", @@ -588,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 @@ -616,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/code/.vscode/notebooks/api.github-issues b/code/.vscode/notebooks/api.github-issues index 13759d8256e..d466fa1b04b 100644 --- a/code/.vscode/notebooks/api.github-issues +++ b/code/.vscode/notebooks/api.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPO=repo:microsoft/vscode\n$MILESTONE=milestone:\"August 2025\"" + "value": "$REPO=repo:microsoft/vscode\n$MILESTONE=milestone:\"October 2025\"" }, { "kind": 1, diff --git a/code/.vscode/notebooks/endgame.github-issues b/code/.vscode/notebooks/endgame.github-issues index 201c0c3c5e0..f84006de400 100644 --- a/code/.vscode/notebooks/endgame.github-issues +++ b/code/.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:\"August 2025\"" + "value": "$MILESTONE=milestone:\"December 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/code/.vscode/notebooks/my-endgame.github-issues b/code/.vscode/notebooks/my-endgame.github-issues index c39de9b210a..d52b8e3bd1f 100644 --- a/code/.vscode/notebooks/my-endgame.github-issues +++ b/code/.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 repo:microsoft/vscode-copilot-evaluation\n\n$MILESTONE=milestone:\"August 2025\"\n\n$MINE=assignee:@me" + "value": "$MILESTONE=milestone:\"December 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/code/.vscode/notebooks/my-work.github-issues b/code/.vscode/notebooks/my-work.github-issues index 226a6e653eb..2190d869ee7 100644 --- a/code/.vscode/notebooks/my-work.github-issues +++ b/code/.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:\"August 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:\"December 2025\"\n" }, { "kind": 1, diff --git a/code/.vscode/notebooks/verification.github-issues b/code/.vscode/notebooks/verification.github-issues index 81ab3dbca2e..1c7e9dc1843 100644 --- a/code/.vscode/notebooks/verification.github-issues +++ b/code/.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:\"August 2025\"\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/code/.vscode/notebooks/vscode-dev.github-issues b/code/.vscode/notebooks/vscode-dev.github-issues index bbdbfc4f79f..4ba4724804c 100644 --- a/code/.vscode/notebooks/vscode-dev.github-issues +++ b/code/.vscode/notebooks/vscode-dev.github-issues @@ -2,7 +2,7 @@ { "kind": 2, "language": "github-issues", - "value": "$milestone=milestone:\"August 2025\"" + "value": "$milestone=milestone:\"October 2025\"" }, { "kind": 1, diff --git a/code/.vscode/searches/no-any-casts.code-search b/code/.vscode/searches/no-any-casts.code-search new file mode 100644 index 00000000000..c430ea202fc --- /dev/null +++ b/code/.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/code/.vscode/settings.json b/code/.vscode/settings.json index a9497c8fc00..bec2efbe491 100644 --- a/code/.vscode/settings.json +++ b/code/.vscode/settings.json @@ -1,9 +1,7 @@ { // --- 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, @@ -18,7 +16,6 @@ // "editor.experimental.preferTreeSitter.typescript": true, // "editor.experimental.preferTreeSitter.regex": true, // "editor.experimental.preferTreeSitter.css": true, - // --- Language Specific --- "[plaintext]": { "files.insertFinalNewline": false @@ -63,7 +60,6 @@ "**/yarn.lock": true, "**/package-lock.json": true, "**/Cargo.lock": true, - "build/**/*.js": true, "out/**": true, "out-build/**": true, "out-vscode/**": true, @@ -75,12 +71,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": { @@ -101,7 +91,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 --- @@ -138,6 +129,7 @@ "git.ignoreLimitWarning": true, "git.branchProtection": [ "main", + "main-*", "distro", "release/*" ], @@ -211,12 +203,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/code/.vscode/tasks.json b/code/.vscode/tasks.json index 51a34c77a57..6ae56ad639e 100644 --- a/code/.vscode/tasks.json +++ b/code/.vscode/tasks.json @@ -257,7 +257,7 @@ }, { "type": "shell", - "command": "node build/lib/preLaunch.js", + "command": "node build/lib/preLaunch.ts", "label": "Ensure Prelaunch Dependencies", "presentation": { "reveal": "silent", @@ -279,23 +279,13 @@ "detail": "node_modules/tsec/bin/tsec -p src/tsconfig.json --noEmit" }, { - "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", diff --git a/code/AGENTS.md b/code/AGENTS.md new file mode 100644 index 00000000000..d6abb76ab83 --- /dev/null +++ b/code/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/code/README.md b/code/README.md index b7a101827f6..6f1e94e4844 100644 --- a/code/README.md +++ b/code/README.md @@ -44,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. @@ -54,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 diff --git a/code/ThirdPartyNotices.txt b/code/ThirdPartyNotices.txt index 8ca3533fe08..096807654bf 100644 --- a/code/ThirdPartyNotices.txt +++ b/code/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/code/build/.cachesalt b/code/build/.cachesalt index d55dde3c035..9cb204bfdb3 100644 --- a/code/build/.cachesalt +++ b/code/build/.cachesalt @@ -1 +1 @@ -2025-07-23T19:44:03.051Z +2025-12-10T20:11:58.882Z diff --git a/code/build/.gitignore b/code/build/.gitignore index 61cf49cb9a1..679674617c7 100644 --- a/code/build/.gitignore +++ b/code/build/.gitignore @@ -1 +1,2 @@ *.js.map +lib/policies/policyDto.* diff --git a/code/build/.moduleignore b/code/build/.moduleignore index 3e654cfe5c3..fc7c538c6cc 100644 --- a/code/build/.moduleignore +++ b/code/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/code/build/azure-pipelines/alpine/cli-build-alpine.yml b/code/build/azure-pipelines/alpine/cli-build-alpine.yml deleted file mode 100644 index d40b310ce1b..00000000000 --- a/code/build/azure-pipelines/alpine/cli-build-alpine.yml +++ /dev/null @@ -1,107 +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 - - - 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) }}: - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(Build.ArtifactStagingDirectory)/vscode_cli_alpine_arm64_cli.tar.gz - artifactName: vscode_cli_alpine_arm64_cli - displayName: Publish vscode_cli_alpine_arm64_cli artifact - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/cli - sbomPackageName: "VS Code Alpine arm64 CLI" - sbomPackageVersion: $(Build.SourceVersion) - - - ${{ if eq(parameters.VSCODE_BUILD_ALPINE, true) }}: - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(Build.ArtifactStagingDirectory)/vscode_cli_alpine_x64_cli.tar.gz - artifactName: vscode_cli_alpine_x64_cli - displayName: Publish vscode_cli_alpine_x64_cli artifact - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/cli - sbomPackageName: "VS Code Alpine x64 CLI" - sbomPackageVersion: $(Build.SourceVersion) diff --git a/code/build/azure-pipelines/alpine/product-build-alpine-cli.yml b/code/build/azure-pipelines/alpine/product-build-alpine-cli.yml new file mode 100644 index 00000000000..9f3f60a6b24 --- /dev/null +++ b/code/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/code/build/azure-pipelines/alpine/product-build-alpine-node-modules.yml b/code/build/azure-pipelines/alpine/product-build-alpine-node-modules.yml new file mode 100644 index 00000000000..cc53000a15c --- /dev/null +++ b/code/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/code/build/azure-pipelines/alpine/product-build-alpine.yml b/code/build/azure-pipelines/alpine/product-build-alpine.yml index 6af958e170f..5c5714e9d5b 100644 --- a/code/build/azure-pipelines/alpine/product-build-alpine.yml +++ b/code/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 - - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(SERVER_PATH) - artifactName: $(ARTIFACT_PREFIX)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) - condition: and(succeededOrFailed(), ne(variables['SERVER_PATH'], ''), ne(variables['VSCODE_ARCH'], 'x64')) - - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(WEB_PATH) - artifactName: $(ARTIFACT_PREFIX)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) - condition: and(succeededOrFailed(), ne(variables['WEB_PATH'], ''), ne(variables['VSCODE_ARCH'], 'x64')) - - # same as above, keep legacy name - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(SERVER_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_server_linux_alpine_archive-unsigned - displayName: Publish x64 server archive - sbomEnabled: false - condition: and(succeededOrFailed(), ne(variables['SERVER_PATH'], ''), eq(variables['VSCODE_ARCH'], 'x64')) - - # same as above, keep legacy name - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(WEB_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_web_linux_alpine_archive-unsigned - displayName: Publish x64 web server archive - sbomEnabled: false - 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/code/build/azure-pipelines/cli/cli-apply-patches.yml b/code/build/azure-pipelines/cli/cli-apply-patches.yml index 2815124efb6..e04951f3f56 100644 --- a/code/build/azure-pipelines/cli/cli-apply-patches.yml +++ b/code/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/code/build/azure-pipelines/cli/cli-compile.yml b/code/build/azure-pipelines/cli/cli-compile.yml index 769a1153bc1..2abefa7b6a4 100644 --- a/code/build/azure-pipelines/cli/cli-compile.yml +++ b/code/build/azure-pipelines/cli/cli-compile.yml @@ -35,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/code/build/azure-pipelines/cli/cli-darwin-sign.yml b/code/build/azure-pipelines/cli/cli-darwin-sign.yml deleted file mode 100644 index d5c188037ca..00000000000 --- a/code/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 - - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(Build.ArtifactStagingDirectory)/pkg/${{ target }}/$(ASSET_ID).zip - artifactName: $(ASSET_ID) - displayName: Publish signed artifact with ID $(ASSET_ID) - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/sign/${{ target }} - sbomPackageName: "VS Code macOS ${{ target }} CLI" - sbomPackageVersion: $(Build.SourceVersion) diff --git a/code/build/azure-pipelines/cli/cli-win32-sign.yml b/code/build/azure-pipelines/cli/cli-win32-sign.yml deleted file mode 100644 index 3c972ae0282..00000000000 --- a/code/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 - - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(Build.ArtifactStagingDirectory)/$(ASSET_ID).zip - artifactName: $(ASSET_ID) - displayName: Publish signed artifact with ID $(ASSET_ID) - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/sign/${{ target }} - sbomPackageName: "VS Code Windows ${{ target }} CLI" - sbomPackageVersion: $(Build.SourceVersion) diff --git a/code/build/azure-pipelines/cli/install-rust-posix.yml b/code/build/azure-pipelines/cli/install-rust-posix.yml index 0607cde33e5..d9bae080cc2 100644 --- a/code/build/azure-pipelines/cli/install-rust-posix.yml +++ b/code/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/code/build/azure-pipelines/cli/install-rust-win32.yml b/code/build/azure-pipelines/cli/install-rust-win32.yml deleted file mode 100644 index bff114fccd0..00000000000 --- a/code/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/code/build/azure-pipelines/common/checkForArtifact.js b/code/build/azure-pipelines/common/checkForArtifact.js deleted file mode 100644 index 371ca6b9520..00000000000 --- a/code/build/azure-pipelines/common/checkForArtifact.js +++ /dev/null @@ -1,34 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/azure-pipelines/common/checkForArtifact.ts b/code/build/azure-pipelines/common/checkForArtifact.ts index e0a1a2ce1d3..21a30552e58 100644 --- a/code/build/azure-pipelines/common/checkForArtifact.ts +++ b/code/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/code/build/azure-pipelines/common/checkout.yml b/code/build/azure-pipelines/common/checkout.yml new file mode 100644 index 00000000000..6f57a9ad9b4 --- /dev/null +++ b/code/build/azure-pipelines/common/checkout.yml @@ -0,0 +1,5 @@ +steps: + - checkout: self + fetchDepth: 1 + fetchTags: false + displayName: Checkout microsoft/vscode diff --git a/code/build/azure-pipelines/common/codesign.js b/code/build/azure-pipelines/common/codesign.js deleted file mode 100644 index 4e82538d105..00000000000 --- a/code/build/azure-pipelines/common/codesign.js +++ /dev/null @@ -1,30 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.printBanner = printBanner; -exports.streamProcessOutputAndCheckResult = streamProcessOutputAndCheckResult; -exports.spawnCodesignProcess = spawnCodesignProcess; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/azure-pipelines/common/codesign.ts b/code/build/azure-pipelines/common/codesign.ts index 9f26b3924b5..4c27048093b 100644 --- a/code/build/azure-pipelines/common/codesign.ts +++ b/code/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/code/build/azure-pipelines/common/computeBuiltInDepsCacheKey.js b/code/build/azure-pipelines/common/computeBuiltInDepsCacheKey.js deleted file mode 100644 index 8f8c833f226..00000000000 --- a/code/build/azure-pipelines/common/computeBuiltInDepsCacheKey.js +++ /dev/null @@ -1,19 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * 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 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/code/build/azure-pipelines/common/computeBuiltInDepsCacheKey.ts b/code/build/azure-pipelines/common/computeBuiltInDepsCacheKey.ts index 8abaaccb654..8e172ee5ecb 100644 --- a/code/build/azure-pipelines/common/computeBuiltInDepsCacheKey.ts +++ b/code/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/code/build/azure-pipelines/common/computeNodeModulesCacheKey.js b/code/build/azure-pipelines/common/computeNodeModulesCacheKey.js deleted file mode 100644 index 59d570e96e6..00000000000 --- a/code/build/azure-pipelines/common/computeNodeModulesCacheKey.js +++ /dev/null @@ -1,40 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * 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 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/code/build/azure-pipelines/common/computeNodeModulesCacheKey.ts b/code/build/azure-pipelines/common/computeNodeModulesCacheKey.ts index 57b35dc78de..e5dbc06aa94 100644 --- a/code/build/azure-pipelines/common/computeNodeModulesCacheKey.ts +++ b/code/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/code/build/azure-pipelines/common/createBuild.js b/code/build/azure-pipelines/common/createBuild.js deleted file mode 100644 index feb06cbe67f..00000000000 --- a/code/build/azure-pipelines/common/createBuild.js +++ /dev/null @@ -1,55 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/azure-pipelines/common/createBuild.ts b/code/build/azure-pipelines/common/createBuild.ts index 6afeb01e6cc..2524e7405a8 100644 --- a/code/build/azure-pipelines/common/createBuild.ts +++ b/code/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/code/build/azure-pipelines/common/getPublishAuthTokens.js b/code/build/azure-pipelines/common/getPublishAuthTokens.js deleted file mode 100644 index d0ead104b1c..00000000000 --- a/code/build/azure-pipelines/common/getPublishAuthTokens.js +++ /dev/null @@ -1,43 +0,0 @@ -"use strict"; -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/code/build/azure-pipelines/common/getPublishAuthTokens.ts b/code/build/azure-pipelines/common/getPublishAuthTokens.ts index 68e76de1a83..2293480b306 100644 --- a/code/build/azure-pipelines/common/getPublishAuthTokens.ts +++ b/code/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/code/build/azure-pipelines/common/install-builtin-extensions.yml b/code/build/azure-pipelines/common/install-builtin-extensions.yml index c1ee18d05b5..f9cbfd4b085 100644 --- a/code/build/azure-pipelines/common/install-builtin-extensions.yml +++ b/code/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/code/build/azure-pipelines/common/listNodeModules.js b/code/build/azure-pipelines/common/listNodeModules.js deleted file mode 100644 index 7112ecab9cc..00000000000 --- a/code/build/azure-pipelines/common/listNodeModules.js +++ /dev/null @@ -1,44 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * 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")); -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/code/build/azure-pipelines/common/listNodeModules.ts b/code/build/azure-pipelines/common/listNodeModules.ts index fb85b25cfd1..5ab955faca4 100644 --- a/code/build/azure-pipelines/common/listNodeModules.ts +++ b/code/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/code/build/azure-pipelines/common/publish-artifact.yml b/code/build/azure-pipelines/common/publish-artifact.yml index ba4d9f13355..1cb18fb5301 100644 --- a/code/build/azure-pipelines/common/publish-artifact.yml +++ b/code/build/azure-pipelines/common/publish-artifact.yml @@ -77,6 +77,7 @@ steps: targetPath: ${{ parameters.targetPath }} artifactName: $(ARTIFACT_NAME) sbomEnabled: ${{ parameters.sbomEnabled }} + isProduction: ${{ parameters.sbomEnabled }} ${{ if ne(parameters.sbomBuildDropPath, '') }}: sbomBuildDropPath: ${{ parameters.sbomBuildDropPath }} ${{ if ne(parameters.sbomPackageName, '') }}: diff --git a/code/build/azure-pipelines/common/publish.js b/code/build/azure-pipelines/common/publish.js deleted file mode 100644 index 2199846a7c1..00000000000 --- a/code/build/azure-pipelines/common/publish.js +++ /dev/null @@ -1,722 +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.e = e; -exports.requestAZDOAPI = requestAZDOAPI; -/*--------------------------------------------------------------------------------------------- - * 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 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/code/build/azure-pipelines/common/publish.ts b/code/build/azure-pipelines/common/publish.ts index 2b1c15007b3..5761c0d06df 100644 --- a/code/build/azure-pipelines/common/publish.ts +++ b/code/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/code/build/azure-pipelines/common/releaseBuild.js b/code/build/azure-pipelines/common/releaseBuild.js deleted file mode 100644 index 9ba2454cfc5..00000000000 --- a/code/build/azure-pipelines/common/releaseBuild.js +++ /dev/null @@ -1,57 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/azure-pipelines/common/releaseBuild.ts b/code/build/azure-pipelines/common/releaseBuild.ts index b7762de7df6..01792fd22e1 100644 --- a/code/build/azure-pipelines/common/releaseBuild.ts +++ b/code/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/code/build/azure-pipelines/common/retry.js b/code/build/azure-pipelines/common/retry.js deleted file mode 100644 index aaa90dd7006..00000000000 --- a/code/build/azure-pipelines/common/retry.js +++ /dev/null @@ -1,27 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.retry = retry; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/azure-pipelines/common/sign-win32.js b/code/build/azure-pipelines/common/sign-win32.js deleted file mode 100644 index 1a6a6a97e02..00000000000 --- a/code/build/azure-pipelines/common/sign-win32.js +++ /dev/null @@ -1,18 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/azure-pipelines/common/sign-win32.ts b/code/build/azure-pipelines/common/sign-win32.ts index ad88435b5a3..677c2024b9c 100644 --- a/code/build/azure-pipelines/common/sign-win32.ts +++ b/code/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/code/build/azure-pipelines/common/sign.js b/code/build/azure-pipelines/common/sign.js deleted file mode 100644 index 2136e03d1f1..00000000000 --- a/code/build/azure-pipelines/common/sign.js +++ /dev/null @@ -1,209 +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.Temp = void 0; -exports.main = main; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/azure-pipelines/common/sign.ts b/code/build/azure-pipelines/common/sign.ts index 19a288483c8..d93f752eeeb 100644 --- a/code/build/azure-pipelines/common/sign.ts +++ b/code/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/code/build/azure-pipelines/common/waitForArtifacts.js b/code/build/azure-pipelines/common/waitForArtifacts.js deleted file mode 100644 index d84a34ecea7..00000000000 --- a/code/build/azure-pipelines/common/waitForArtifacts.js +++ /dev/null @@ -1,46 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/azure-pipelines/common/waitForArtifacts.ts b/code/build/azure-pipelines/common/waitForArtifacts.ts index 3fed6cd38d2..1b48a70d994 100644 --- a/code/build/azure-pipelines/common/waitForArtifacts.ts +++ b/code/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/code/build/azure-pipelines/config/CredScanSuppressions.json b/code/build/azure-pipelines/config/CredScanSuppressions.json index bf52c06cf89..7cb84e9d0ca 100644 --- a/code/build/azure-pipelines/config/CredScanSuppressions.json +++ b/code/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/code/build/azure-pipelines/darwin/cli-build-darwin.yml b/code/build/azure-pipelines/darwin/cli-build-darwin.yml deleted file mode 100644 index 06e7ee74085..00000000000 --- a/code/build/azure-pipelines/darwin/cli-build-darwin.yml +++ /dev/null @@ -1,87 +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 - - - 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) }}: - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(Build.ArtifactStagingDirectory)/unsigned_vscode_cli_darwin_x64_cli.zip - artifactName: unsigned_vscode_cli_darwin_x64_cli - displayName: Publish unsigned_vscode_cli_darwin_x64_cli artifact - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/cli - sbomPackageName: "VS Code macOS x64 CLI (unsigned)" - sbomPackageVersion: $(Build.SourceVersion) - - - ${{ if eq(parameters.VSCODE_BUILD_MACOS_ARM64, true) }}: - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(Build.ArtifactStagingDirectory)/unsigned_vscode_cli_darwin_arm64_cli.zip - artifactName: unsigned_vscode_cli_darwin_arm64_cli - displayName: Publish unsigned_vscode_cli_darwin_arm64_cli artifact - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/cli - sbomPackageName: "VS Code macOS arm64 CLI (unsigned)" - sbomPackageVersion: $(Build.SourceVersion) diff --git a/code/build/azure-pipelines/darwin/codesign.js b/code/build/azure-pipelines/darwin/codesign.js deleted file mode 100644 index d779280a7be..00000000000 --- a/code/build/azure-pipelines/darwin/codesign.js +++ /dev/null @@ -1,30 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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}/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/code/build/azure-pipelines/darwin/codesign.ts b/code/build/azure-pipelines/darwin/codesign.ts index e6f6a5ce754..848fb0f4647 100644 --- a/code/build/azure-pipelines/darwin/codesign.ts +++ b/code/build/azure-pipelines/darwin/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 arch = e('VSCODE_ARCH'); diff --git a/code/build/azure-pipelines/darwin/product-build-darwin-ci.yml b/code/build/azure-pipelines/darwin/product-build-darwin-ci.yml new file mode 100644 index 00000000000..3920c4ec799 --- /dev/null +++ b/code/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/code/build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml b/code/build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml index 0450a17d2bf..94eee5e476c 100644 --- a/code/build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml +++ b/code/build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml @@ -4,60 +4,83 @@ parameters: - name: VSCODE_BUILD_MACOS_ARM64 type: boolean -steps: - - task: NodeTool@0 - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - - - 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 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 - - - template: ../cli/cli-darwin-sign.yml@self - parameters: - VSCODE_CLI_ARTIFACTS: +jobs: + - job: macOSCLISign + timeoutInMinutes: 90 + templateContext: + outputParentDirectory: $(Build.ArtifactStagingDirectory)/out + outputs: - ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}: - - unsigned_vscode_cli_darwin_x64_cli + - 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) }}: - - unsigned_vscode_cli_darwin_arm64_cli + - 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 + + - task: NodeTool@0 + 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" + + - 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 + + - 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/code/build/azure-pipelines/darwin/product-build-darwin-cli.yml b/code/build/azure-pipelines/darwin/product-build-darwin-cli.yml new file mode 100644 index 00000000000..35a9b3566ce --- /dev/null +++ b/code/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/code/build/azure-pipelines/darwin/product-build-darwin-node-modules.yml b/code/build/azure-pipelines/darwin/product-build-darwin-node-modules.yml new file mode 100644 index 00000000000..19b38a60952 --- /dev/null +++ b/code/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/code/build/azure-pipelines/darwin/product-build-darwin-test.yml b/code/build/azure-pipelines/darwin/product-build-darwin-test.yml deleted file mode 100644 index f2b5e697c4d..00000000000 --- a/code/build/azure-pipelines/darwin/product-build-darwin-test.yml +++ /dev/null @@ -1,170 +0,0 @@ -parameters: - - 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 - -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() - - - template: ../common/publish-artifact.yml@self - parameters: - 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) - displayName: "Publish Crash Reports" - sbomEnabled: false - continueOnError: true - condition: failed() - - # In order to properly symbolify above crash reports - # (if any), we need the compiled native modules too - - template: ../common/publish-artifact.yml@self - parameters: - 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) - displayName: "Publish Node Modules" - sbomEnabled: false - continueOnError: true - condition: failed() - - - template: ../common/publish-artifact.yml@self - parameters: - 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) - displayName: "Publish Log Files" - sbomEnabled: false - 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/code/build/azure-pipelines/darwin/product-build-darwin-universal.yml b/code/build/azure-pipelines/darwin/product-build-darwin-universal.yml index be30724f1ad..81bff1ae5f6 100644 --- a/code/build/azure-pipelines/darwin/product-build-darwin-universal.yml +++ b/code/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')) - - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(Pipeline.Workspace)/unsigned_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) +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/code/build/azure-pipelines/darwin/product-build-darwin.yml b/code/build/azure-pipelines/darwin/product-build-darwin.yml index f8ce9a18162..770a54f7925 100644 --- a/code/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/code/build/azure-pipelines/darwin/product-build-darwin.yml @@ -12,320 +12,68 @@ parameters: - name: VSCODE_RUN_REMOTE_TESTS type: boolean default: false - - name: VSCODE_TEST_ARTIFACT_NAME - type: string - default: "" - -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" - - - 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')) - - - 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 - - - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: - - script: node build/lib/policies 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.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 - - - 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 - - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(CLIENT_PATH) - artifactName: unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive - displayName: Publish client archive (unsigned) - 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')) - - # 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.js $(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: and(succeededOrFailed(), 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.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_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_CIBUILD, true) }}: - - script: npx deemon --attach node build/azure-pipelines/darwin/codesign.js - 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: mv $(Pipeline.Workspace)/vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-x64.zip $(Pipeline.Workspace)/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')) - - - template: ../common/publish-artifact.yml@self - parameters: - ${{ if eq(parameters.VSCODE_ARCH, 'arm64') }}: - targetPath: $(Pipeline.Workspace)/vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-arm64.zip - ${{ else }}: - targetPath: $(Pipeline.Workspace)/vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin.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) - - - script: echo "##vso[task.setvariable variable=ARTIFACT_PREFIX]attempt$(System.JobAttempt)_" - condition: and(succeededOrFailed(), notIn(variables['Agent.JobStatus'], 'Succeeded', 'SucceededWithIssues')) - displayName: Generate artifact prefix - - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(SERVER_PATH) - artifactName: $(ARTIFACT_PREFIX)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) - condition: and(succeededOrFailed(), ne(variables['SERVER_PATH'], '')) - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(WEB_PATH) - artifactName: $(ARTIFACT_PREFIX)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) - condition: and(succeededOrFailed(), ne(variables['WEB_PATH'], '')) +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/code/build/azure-pipelines/darwin/steps/product-build-darwin-cli-sign.yml b/code/build/azure-pipelines/darwin/steps/product-build-darwin-cli-sign.yml new file mode 100644 index 00000000000..1cd0fe2a824 --- /dev/null +++ b/code/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/code/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml b/code/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml new file mode 100644 index 00000000000..1d38413bde4 --- /dev/null +++ b/code/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/code/build/azure-pipelines/darwin/steps/product-build-darwin-test.yml b/code/build/azure-pipelines/darwin/steps/product-build-darwin-test.yml new file mode 100644 index 00000000000..80be5496d97 --- /dev/null +++ b/code/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-all2 -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/code/build/azure-pipelines/distro-build.yml b/code/build/azure-pipelines/distro-build.yml index ae11345bb6d..1d0a50b1297 100644 --- a/code/build/azure-pipelines/distro-build.yml +++ b/code/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/code/build/azure-pipelines/distro/mixin-npm.js b/code/build/azure-pipelines/distro/mixin-npm.js deleted file mode 100644 index 40f5ee134af..00000000000 --- a/code/build/azure-pipelines/distro/mixin-npm.js +++ /dev/null @@ -1,38 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * 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 { 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/code/build/azure-pipelines/distro/mixin-npm.ts b/code/build/azure-pipelines/distro/mixin-npm.ts index 6e32f10db50..d5caa0a9502 100644 --- a/code/build/azure-pipelines/distro/mixin-npm.ts +++ b/code/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/code/build/azure-pipelines/distro/mixin-quality.js b/code/build/azure-pipelines/distro/mixin-quality.js deleted file mode 100644 index 08aee7c89bd..00000000000 --- a/code/build/azure-pipelines/distro/mixin-quality.js +++ /dev/null @@ -1,56 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * 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")); -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/code/build/azure-pipelines/distro/mixin-quality.ts b/code/build/azure-pipelines/distro/mixin-quality.ts index 29c90f00a65..c8ed6886b79 100644 --- a/code/build/azure-pipelines/distro/mixin-quality.ts +++ b/code/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/code/build/azure-pipelines/linux/cli-build-linux.yml b/code/build/azure-pipelines/linux/cli-build-linux.yml deleted file mode 100644 index 918d6aae14f..00000000000 --- a/code/build/azure-pipelines/linux/cli-build-linux.yml +++ /dev/null @@ -1,157 +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 - - - 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.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) }}: - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(Build.ArtifactStagingDirectory)/vscode_cli_linux_armhf_cli.tar.gz - artifactName: vscode_cli_linux_armhf_cli - displayName: Publish vscode_cli_linux_armhf_cli artifact - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/cli - sbomPackageName: "VS Code Linux armhf CLI" - sbomPackageVersion: $(Build.SourceVersion) - - - ${{ if eq(parameters.VSCODE_BUILD_LINUX, true) }}: - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(Build.ArtifactStagingDirectory)/vscode_cli_linux_x64_cli.tar.gz - artifactName: vscode_cli_linux_x64_cli - displayName: Publish vscode_cli_linux_x64_cli artifact - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/cli - sbomPackageName: "VS Code Linux x64 CLI" - sbomPackageVersion: $(Build.SourceVersion) - - - ${{ if eq(parameters.VSCODE_BUILD_LINUX_ARM64, true) }}: - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(Build.ArtifactStagingDirectory)/vscode_cli_linux_arm64_cli.tar.gz - artifactName: vscode_cli_linux_arm64_cli - displayName: Publish vscode_cli_linux_arm64_cli artifact - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/cli - sbomPackageName: "VS Code Linux arm64 CLI" - sbomPackageVersion: $(Build.SourceVersion) diff --git a/code/build/azure-pipelines/linux/codesign.js b/code/build/azure-pipelines/linux/codesign.js deleted file mode 100644 index 93ec2434ea8..00000000000 --- a/code/build/azure-pipelines/linux/codesign.js +++ /dev/null @@ -1,29 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/azure-pipelines/linux/codesign.ts b/code/build/azure-pipelines/linux/codesign.ts index 1f74cc21ee9..67a34d9e7a1 100644 --- a/code/build/azure-pipelines/linux/codesign.ts +++ b/code/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/code/build/azure-pipelines/linux/product-build-linux-ci.yml b/code/build/azure-pipelines/linux/product-build-linux-ci.yml new file mode 100644 index 00000000000..6c6b102891a --- /dev/null +++ b/code/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/code/build/azure-pipelines/linux/product-build-linux-cli.yml b/code/build/azure-pipelines/linux/product-build-linux-cli.yml new file mode 100644 index 00000000000..ef160c2cc38 --- /dev/null +++ b/code/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/code/build/azure-pipelines/linux/product-build-linux-node-modules.yml b/code/build/azure-pipelines/linux/product-build-linux-node-modules.yml new file mode 100644 index 00000000000..290a3fe1b29 --- /dev/null +++ b/code/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/code/build/azure-pipelines/linux/product-build-linux.yml b/code/build/azure-pipelines/linux/product-build-linux.yml index 8a0d1c99c29..31eb7c3d466 100644 --- a/code/build/azure-pipelines/linux/product-build-linux.yml +++ b/code/build/azure-pipelines/linux/product-build-linux.yml @@ -1,10 +1,12 @@ 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 @@ -17,416 +19,94 @@ parameters: - name: VSCODE_RUN_REMOTE_TESTS type: boolean default: false - - name: VSCODE_TEST_ARTIFACT_NAME - type: string - default: "" - -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: | - 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')) - - - 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-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 - - - ${{ 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" - 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 - - - ${{ 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-*') - 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_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_CIBUILD, true) }}: - - 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 - - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(CLIENT_PATH) - artifactName: $(ARTIFACT_PREFIX)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) - condition: and(succeededOrFailed(), ne(variables['CLIENT_PATH'], '')) - - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(SERVER_PATH) - artifactName: $(ARTIFACT_PREFIX)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) - condition: and(succeededOrFailed(), ne(variables['SERVER_PATH'], '')) - - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(WEB_PATH) - artifactName: $(ARTIFACT_PREFIX)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) - condition: and(succeededOrFailed(), ne(variables['WEB_PATH'], '')) - - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(DEB_PATH) - artifactName: $(ARTIFACT_PREFIX)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) - condition: and(succeededOrFailed(), ne(variables['DEB_PATH'], '')) - - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(RPM_PATH) - artifactName: $(ARTIFACT_PREFIX)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) - condition: and(succeededOrFailed(), ne(variables['RPM_PATH'], '')) - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(SNAP_PATH) - 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) - condition: and(succeededOrFailed(), ne(variables['SNAP_PATH'], '')) +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/code/build/azure-pipelines/linux/setup-env.sh b/code/build/azure-pipelines/linux/setup-env.sh index 5a467aff3a4..8e14d2eba03 100755 --- a/code/build/azure-pipelines/linux/setup-env.sh +++ b/code/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.235/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.235:build/config/arm.gni - # https://source.chromium.org/chromium/chromium/src/+/refs/tags/138.0.7204.235:build/config/compiler/BUILD.gn - # https://source.chromium.org/chromium/chromium/src/+/refs/tags/138.0.7204.235: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/code/build/azure-pipelines/linux/steps/product-build-linux-compile.yml b/code/build/azure-pipelines/linux/steps/product-build-linux-compile.yml new file mode 100644 index 00000000000..89199ebbbb1 --- /dev/null +++ b/code/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/code/build/azure-pipelines/linux/product-build-linux-test.yml b/code/build/azure-pipelines/linux/steps/product-build-linux-test.yml similarity index 77% rename from code/build/azure-pipelines/linux/product-build-linux-test.yml rename to code/build/azure-pipelines/linux/steps/product-build-linux-test.yml index e4dbfecd91b..c91f3e8ec0e 100644 --- a/code/build/azure-pipelines/linux/product-build-linux-test.yml +++ b/code/build/azure-pipelines/linux/steps/product-build-linux-test.yml @@ -5,11 +5,9 @@ parameters: type: boolean - name: VSCODE_RUN_REMOTE_TESTS type: boolean - - name: VSCODE_TEST_ARTIFACT_NAME - type: string steps: - - script: npm exec -- npm-run-all -lp "electron $(VSCODE_ARCH)" "playwright-install" + - script: npm exec -- npm-run-all2 -lp "electron $(VSCODE_ARCH)" "playwright-install" env: GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Download Electron and Playwright @@ -139,44 +137,6 @@ steps: continueOnError: true condition: succeededOrFailed() - - template: ../common/publish-artifact.yml@self - parameters: - 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) - displayName: "Publish Crash Reports" - sbomEnabled: false - continueOnError: true - condition: failed() - - # In order to properly symbolify above crash reports - # (if any), we need the compiled native modules too - - template: ../common/publish-artifact.yml@self - parameters: - 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) - displayName: "Publish Node Modules" - sbomEnabled: false - continueOnError: true - condition: failed() - - - template: ../common/publish-artifact.yml@self - parameters: - 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) - displayName: "Publish Log Files" - sbomEnabled: false - continueOnError: true - condition: succeededOrFailed() - - task: PublishTestResults@2 displayName: Publish Tests Results inputs: diff --git a/code/build/azure-pipelines/linux/verify-glibc-requirements.sh b/code/build/azure-pipelines/linux/verify-glibc-requirements.sh index 529417761f9..3db90471faa 100755 --- a/code/build/azure-pipelines/linux/verify-glibc-requirements.sh +++ b/code/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/code/build/azure-pipelines/product-build-macos.yml b/code/build/azure-pipelines/product-build-macos.yml index 4c14e0b1ed5..57d88a6c3d7 100644 --- a/code/build/azure-pipelines/product-build-macos.yml +++ b/code/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,59 +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 + - 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_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_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_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/code/build/azure-pipelines/product-build.yml b/code/build/azure-pipelines/product-build.yml index 886d102928a..516b3b4fffd 100644 --- a/code/build/azure-pipelines/product-build.yml +++ b/code/build/azure-pipelines/product-build.yml @@ -98,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 @@ -193,130 +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 + - 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 @@ -343,88 +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_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 @@ -437,93 +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 - 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 + - 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))) }}: - - job: Linuxx64 - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: x64 + - 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_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 }} + 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_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 @@ -531,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 @@ -565,117 +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_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_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_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_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - - - job: macOSCLI - timeoutInMinutes: 90 - steps: - - 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 }} + 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_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 @@ -691,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/code/build/azure-pipelines/product-compile.yml b/code/build/azure-pipelines/product-compile.yml index a406a6e8da7..2aba62deea2 100644 --- a/code/build/azure-pipelines/product-compile.yml +++ b/code/build/azure-pipelines/product-compile.yml @@ -1,149 +1,154 @@ -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" - - - 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 - - - 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: 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 - - - 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 - - - template: common/publish-artifact.yml@self - parameters: - targetPath: $(Build.ArtifactStagingDirectory)/compilation.tar.gz - artifactName: Compilation - displayName: Publish compilation artifact - sbomEnabled: false - - - 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-all2 -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/code/build/azure-pipelines/product-npm-package-validate.yml b/code/build/azure-pipelines/product-npm-package-validate.yml index d4bc480ed72..d596f9f7b37 100644 --- a/code/build/azure-pipelines/product-npm-package-validate.yml +++ b/code/build/azure-pipelines/product-npm-package-validate.yml @@ -25,7 +25,6 @@ jobs: inputs: versionSource: fromFile versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - script: | set -e @@ -48,7 +47,7 @@ jobs: fi displayName: Check if package files were modified - - script: node build/setup-npm-registry.js $NPM_REGISTRY + - 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 @@ -85,11 +84,11 @@ jobs: echo "Attempt $attempt: Running npm ci" if npm i --ignore-scripts; then - if node build/npm/postinstall.js; then + if node build/npm/postinstall.ts; then echo "npm i succeeded on attempt $attempt" exit 0 else - echo "node build/npm/postinstall.js failed on attempt $attempt" + echo "node build/npm/postinstall.ts failed on attempt $attempt" fi else echo "npm i failed on attempt $attempt" diff --git a/code/build/azure-pipelines/product-publish.yml b/code/build/azure-pipelines/product-publish.yml index 858ce5e4d04..165fb177a9a 100644 --- a/code/build/azure-pipelines/product-publish.yml +++ b/code/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 - - template: common/publish-artifact.yml@self - parameters: - 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 - 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/code/build/azure-pipelines/product-release.yml b/code/build/azure-pipelines/product-release.yml index 87896f9340b..72b33a78ad1 100644 --- a/code/build/azure-pipelines/product-release.yml +++ b/code/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/code/build/azure-pipelines/publish-types/check-version.js b/code/build/azure-pipelines/publish-types/check-version.js deleted file mode 100644 index e9564a469d1..00000000000 --- a/code/build/azure-pipelines/publish-types/check-version.js +++ /dev/null @@ -1,40 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/azure-pipelines/publish-types/publish-types.yml b/code/build/azure-pipelines/publish-types/publish-types.yml index 5f60ae5a262..25dbf1f185a 100644 --- a/code/build/azure-pipelines/publish-types/publish-types.yml +++ b/code/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/code/build/azure-pipelines/publish-types/update-types.js b/code/build/azure-pipelines/publish-types/update-types.js deleted file mode 100644 index a528a6027d9..00000000000 --- a/code/build/azure-pipelines/publish-types/update-types.js +++ /dev/null @@ -1,76 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * 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 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/code/build/azure-pipelines/publish-types/update-types.ts b/code/build/azure-pipelines/publish-types/update-types.ts index 0f99b07cf9a..05482ab452e 100644 --- a/code/build/azure-pipelines/publish-types/update-types.ts +++ b/code/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/code/build/azure-pipelines/upload-cdn.js b/code/build/azure-pipelines/upload-cdn.js deleted file mode 100644 index fcf24ad6073..00000000000 --- a/code/build/azure-pipelines/upload-cdn.js +++ /dev/null @@ -1,120 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/azure-pipelines/upload-cdn.ts b/code/build/azure-pipelines/upload-cdn.ts index 61d7cea523c..e3a715b4e53 100644 --- a/code/build/azure-pipelines/upload-cdn.ts +++ b/code/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/code/build/azure-pipelines/upload-nlsmetadata.js b/code/build/azure-pipelines/upload-nlsmetadata.js deleted file mode 100644 index 18ff71b7191..00000000000 --- a/code/build/azure-pipelines/upload-nlsmetadata.js +++ /dev/null @@ -1,127 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/azure-pipelines/upload-nlsmetadata.ts b/code/build/azure-pipelines/upload-nlsmetadata.ts index 1a4f2665617..9d6a803e169 100644 --- a/code/build/azure-pipelines/upload-nlsmetadata.ts +++ b/code/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/code/build/azure-pipelines/upload-sourcemaps.js b/code/build/azure-pipelines/upload-sourcemaps.js deleted file mode 100644 index ecb8679d684..00000000000 --- a/code/build/azure-pipelines/upload-sourcemaps.js +++ /dev/null @@ -1,101 +0,0 @@ -"use strict"; -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 }); -/*--------------------------------------------------------------------------------------------- - * 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 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/code/build/azure-pipelines/upload-sourcemaps.ts b/code/build/azure-pipelines/upload-sourcemaps.ts index 0c51827fef4..d5a72de54bf 100644 --- a/code/build/azure-pipelines/upload-sourcemaps.ts +++ b/code/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/code/build/azure-pipelines/web/product-build-web-node-modules.yml b/code/build/azure-pipelines/web/product-build-web-node-modules.yml new file mode 100644 index 00000000000..75a0cc6cd6e --- /dev/null +++ b/code/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/code/build/azure-pipelines/web/product-build-web.yml b/code/build/azure-pipelines/web/product-build-web.yml index 41c8494c176..1d5dd9798e7 100644 --- a/code/build/azure-pipelines/web/product-build-web.yml +++ b/code/build/azure-pipelines/web/product-build-web.yml @@ -1,173 +1,178 @@ -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 - - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(WEB_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_web_linux_standalone_archive-unsigned - displayName: Publish web archive - sbomBuildDropPath: $(Agent.BuildDirectory)/vscode-web - sbomPackageName: "VS Code Web" - sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['WEB_PATH'], '')) +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.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.ts 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.ts + displayName: Upload NLS Metadata diff --git a/code/build/azure-pipelines/win32/cli-build-win32.yml b/code/build/azure-pipelines/win32/cli-build-win32.yml deleted file mode 100644 index e40acc0bcef..00000000000 --- a/code/build/azure-pipelines/win32/cli-build-win32.yml +++ /dev/null @@ -1,90 +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 - - - 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) }}: - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(Build.ArtifactStagingDirectory)/unsigned_vscode_cli_win32_arm64_cli.zip - artifactName: unsigned_vscode_cli_win32_arm64_cli - displayName: Publish unsigned_vscode_cli_win32_arm64_cli artifact - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/cli - sbomPackageName: "VS Code Windows arm64 CLI (unsigned)" - sbomPackageVersion: $(Build.SourceVersion) - - - ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}: - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(Build.ArtifactStagingDirectory)/unsigned_vscode_cli_win32_x64_cli.zip - artifactName: unsigned_vscode_cli_win32_x64_cli - displayName: Publish unsigned_vscode_cli_win32_x64_cli artifact - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/cli - sbomPackageName: "VS Code Windows x64 CLI (unsigned)" - sbomPackageVersion: $(Build.SourceVersion) diff --git a/code/build/azure-pipelines/win32/codesign.js b/code/build/azure-pipelines/win32/codesign.js deleted file mode 100644 index cee33ae3208..00000000000 --- a/code/build/azure-pipelines/win32/codesign.js +++ /dev/null @@ -1,73 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/azure-pipelines/win32/codesign.ts b/code/build/azure-pipelines/win32/codesign.ts index 7e7170709b5..183bd3cc9fa 100644 --- a/code/build/azure-pipelines/win32/codesign.ts +++ b/code/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); } @@ -71,7 +68,7 @@ async function main() { // Sign setup if (process.env['BUILT_CLIENT']) { printBanner('Sign setup packages (system, user)'); - const task = $`npm exec -- npm-run-all -lp "gulp vscode-win32-${arch}-system-setup -- --sign" "gulp vscode-win32-${arch}-user-setup -- --sign"`; + const task = $`npm exec -- npm-run-all2 -lp "gulp vscode-win32-${arch}-system-setup -- --sign" "gulp vscode-win32-${arch}-user-setup -- --sign"`; await streamProcessOutputAndCheckResult('Sign setup packages (system, user)', task); } } diff --git a/code/build/azure-pipelines/win32/product-build-win32-ci.yml b/code/build/azure-pipelines/win32/product-build-win32-ci.yml new file mode 100644 index 00000000000..eefacfdf8e9 --- /dev/null +++ b/code/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/code/build/azure-pipelines/win32/product-build-win32-cli-sign.yml b/code/build/azure-pipelines/win32/product-build-win32-cli-sign.yml index 29e20937e50..fa1328d99e2 100644 --- a/code/build/azure-pipelines/win32/product-build-win32-cli-sign.yml +++ b/code/build/azure-pipelines/win32/product-build-win32-cli-sign.yml @@ -4,57 +4,80 @@ parameters: - name: VSCODE_BUILD_WIN32_ARM64 type: boolean -steps: - - task: NodeTool@0 - displayName: "Use Node.js" - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - - - 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/code/build/azure-pipelines/win32/product-build-win32-cli.yml b/code/build/azure-pipelines/win32/product-build-win32-cli.yml new file mode 100644 index 00000000000..5dd69c3b50d --- /dev/null +++ b/code/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/code/build/azure-pipelines/win32/product-build-win32-node-modules.yml b/code/build/azure-pipelines/win32/product-build-win32-node-modules.yml new file mode 100644 index 00000000000..6780073f57a --- /dev/null +++ b/code/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/code/build/azure-pipelines/win32/product-build-win32.yml b/code/build/azure-pipelines/win32/product-build-win32.yml index 01d00e0c380..3a91d3cdd97 100644 --- a/code/build/azure-pipelines/win32/product-build-win32.yml +++ b/code/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,361 +14,82 @@ parameters: - name: VSCODE_RUN_REMOTE_TESTS type: boolean default: false - - name: VSCODE_TEST_ARTIFACT_NAME - type: string - default: "" - -steps: - - 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 - - - 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')) - - - 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 - - - powershell: node build/azure-pipelines/distro/mixin-quality - displayName: Mixin distro quality - - - template: ../common/install-builtin-extensions.yml@self - - - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: - - powershell: node build\lib\policies 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 .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) }}: - - 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 - - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - mkdir -Force .build/node-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/node-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_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_CIBUILD, true) }}: - - 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/node-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 - - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: .build/node-cpuprofile - artifactName: $(ARTIFACT_PREFIX)node-cpuprofile-$(VSCODE_ARCH) - displayName: Publish Codesign cpu profile - sbomEnabled: false - - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(CLIENT_PATH) - artifactName: $(ARTIFACT_PREFIX)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'], '')) - - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(SERVER_PATH) - artifactName: $(ARTIFACT_PREFIX)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'], '')) - - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(WEB_PATH) - artifactName: $(ARTIFACT_PREFIX)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'], '')) - - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(SYSTEM_SETUP_PATH) - artifactName: $(ARTIFACT_PREFIX)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) - condition: and(succeededOrFailed(), ne(variables['SYSTEM_SETUP_PATH'], '')) - - template: ../common/publish-artifact.yml@self - parameters: - targetPath: $(USER_SETUP_PATH) - artifactName: $(ARTIFACT_PREFIX)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) - condition: and(succeededOrFailed(), ne(variables['USER_SETUP_PATH'], '')) +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) + - 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) + - 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) + 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/code/build/azure-pipelines/win32/sdl-scan-win32.yml b/code/build/azure-pipelines/win32/sdl-scan-win32.yml index bf6819a4b47..e3356effa95 100644 --- a/code/build/azure-pipelines/win32/sdl-scan-win32.yml +++ b/code/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/code/build/azure-pipelines/win32/steps/product-build-win32-cli-sign.yml b/code/build/azure-pipelines/win32/steps/product-build-win32-cli-sign.yml new file mode 100644 index 00000000000..0caba3d1a2b --- /dev/null +++ b/code/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/code/build/azure-pipelines/win32/steps/product-build-win32-compile.yml b/code/build/azure-pipelines/win32/steps/product-build-win32-compile.yml new file mode 100644 index 00000000000..d6412c23420 --- /dev/null +++ b/code/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/code/build/azure-pipelines/win32/steps/product-build-win32-install-rust.yml b/code/build/azure-pipelines/win32/steps/product-build-win32-install-rust.yml new file mode 100644 index 00000000000..a9c3b7e6432 --- /dev/null +++ b/code/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/code/build/azure-pipelines/win32/product-build-win32-test.yml b/code/build/azure-pipelines/win32/steps/product-build-win32-test.yml similarity index 77% rename from code/build/azure-pipelines/win32/product-build-win32-test.yml rename to code/build/azure-pipelines/win32/steps/product-build-win32-test.yml index 154ddcf4485..f8b4eb57440 100644 --- a/code/build/azure-pipelines/win32/product-build-win32-test.yml +++ b/code/build/azure-pipelines/win32/steps/product-build-win32-test.yml @@ -7,11 +7,9 @@ parameters: type: boolean - name: VSCODE_RUN_REMOTE_TESTS type: boolean - - name: VSCODE_TEST_ARTIFACT_NAME - type: string steps: - - powershell: npm exec -- npm-run-all -lp "electron $(VSCODE_ARCH)" "playwright-install" + - powershell: npm exec -- npm-run-all2 -lp "electron $(VSCODE_ARCH)" "playwright-install" env: GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Download Electron and Playwright @@ -78,7 +76,8 @@ steps: . 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 + $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)" @@ -100,7 +99,8 @@ steps: . 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 + $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)" @@ -142,44 +142,6 @@ steps: continueOnError: true condition: succeededOrFailed() - - template: ../common/publish-artifact.yml@self - parameters: - 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 - - template: ../common/publish-artifact.yml@self - parameters: - 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() - - - template: ../common/publish-artifact.yml@self - parameters: - 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: diff --git a/code/build/buildfile.js b/code/build/buildfile.js deleted file mode 100644 index 83f84563275..00000000000 --- a/code/build/buildfile.js +++ /dev/null @@ -1,60 +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 - -/** - * @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/code/build/buildfile.ts b/code/build/buildfile.ts new file mode 100644 index 00000000000..99a9832f404 --- /dev/null +++ b/code/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'); + +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/code/build/checker/layersChecker.js b/code/build/checker/layersChecker.js deleted file mode 100644 index a59901d8a57..00000000000 --- a/code/build/checker/layersChecker.js +++ /dev/null @@ -1,136 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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 */ /* 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/code/build/checker/layersChecker.ts b/code/build/checker/layersChecker.ts index 68e12e61c40..87341dcffd0 100644 --- a/code/build/checker/layersChecker.ts +++ b/code/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/code/build/checker/tsconfig.browser.json b/code/build/checker/tsconfig.browser.json index 67868ef7575..91cb9fe0e70 100644 --- a/code/build/checker/tsconfig.browser.json +++ b/code/build/checker/tsconfig.browser.json @@ -2,7 +2,7 @@ "extends": "../../src/tsconfig.base.json", "compilerOptions": { "lib": [ - "ES2022", + "ES2024", "DOM", "DOM.Iterable" ], diff --git a/code/build/checker/tsconfig.node.json b/code/build/checker/tsconfig.node.json index 2e483136e08..4fe5c10623d 100644 --- a/code/build/checker/tsconfig.node.json +++ b/code/build/checker/tsconfig.node.json @@ -2,7 +2,7 @@ "extends": "../../src/tsconfig.base.json", "compilerOptions": { "lib": [ - "ES2022" + "ES2024" ], "types": [ "node" diff --git a/code/build/checker/tsconfig.worker.json b/code/build/checker/tsconfig.worker.json index ebb919bf0f2..39d3a584532 100644 --- a/code/build/checker/tsconfig.worker.json +++ b/code/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/code/build/checksums/electron.txt b/code/build/checksums/electron.txt index 05a15cbe6b4..d5b72a3e55d 100644 --- a/code/build/checksums/electron.txt +++ b/code/build/checksums/electron.txt @@ -1,75 +1,75 @@ -f6e9c5bdf45d3e17ef90036265a190e55fb2c15c840c2f898f7b503882dcbdac *chromedriver-v37.3.1-darwin-arm64.zip -473bae1c5226e2b1b7cebe71a2f983955e886d65683b00d302850071026e0bdb *chromedriver-v37.3.1-darwin-x64.zip -db0b310b297cb3c38655ca2d91c892e463f6e73d45b1487aa5f7271dd5f54315 *chromedriver-v37.3.1-linux-arm64.zip -e4cc211fc92da230acbf2139333051a105a97f7a6a52ace5b0f289bcffcf1ce4 *chromedriver-v37.3.1-linux-armv7l.zip -6f59f1b86c4538bdf7857fa90afd3f459a8f32bc600480774ae4dc50fc89208c *chromedriver-v37.3.1-linux-x64.zip -20cfc1a780d31560ba9aa3aea28ff0bcc7ef9fba7e3cd88411d6ecce71c64783 *chromedriver-v37.3.1-mas-arm64.zip -461f509e9dfef36e1afa1f458b0660df5fe8152418c87766a3b396582844695e *chromedriver-v37.3.1-mas-x64.zip -aa0d683f7d16dc4bb0d20cd9750f35b9297a815b3daee42c28f6cffa397ce05c *chromedriver-v37.3.1-win32-arm64.zip -85399e3f24d4c58b4b2dd0aa43cf4cfe729c95514429cadfe315768ae371f63d *chromedriver-v37.3.1-win32-ia32.zip -51478feb8295e6fddfb179943a52255bfe167ddba3d1992d70de446967f59a27 *chromedriver-v37.3.1-win32-x64.zip -ce50fd1f0868419830159291ab5dd8b50422d1cfb4e8e76fa6faa0adbd525fd8 *electron-api.json -d560b5b3eca64a42cf415674576d28535a619f3e2592d8301439f09eb7e9c44b *electron-v37.3.1-darwin-arm64-dsym-snapshot.zip -99db7059c2958ace970d5b91ca936b3684d786dfa628a39d7f05bf08df26dd25 *electron-v37.3.1-darwin-arm64-dsym.zip -bfe2ccab48e2ceb5248f913b8aca3d9249eae27e67e5f6a18bd94538e84979d2 *electron-v37.3.1-darwin-arm64-symbols.zip -7f390efeca2d2153e29c5ea13305915fb3f853b2a4e9a00be07183c6e09ac6de *electron-v37.3.1-darwin-arm64.zip -22b53f05594a0d9699dc60f29fc3cd217ae72a5b8c95887f8b56f550b40dfb4c *electron-v37.3.1-darwin-x64-dsym-snapshot.zip -f165d0aaca2dfdf034786f7c57a9212f0157cff44143910fad13c967aacef896 *electron-v37.3.1-darwin-x64-dsym.zip -bea0c8b15714c970f7772c4116f210ef51b4755fec7f5ebb65a5427df3aaaa32 *electron-v37.3.1-darwin-x64-symbols.zip -0a6a55de6c49d6eb929f01632701bd25f7e515d7b4042614dd5a1ec6c079f3f3 *electron-v37.3.1-darwin-x64.zip -4d058758fa31db78b2ca90ab35e8726e59a8ac400c453c449ccbb7b4b1f72985 *electron-v37.3.1-linux-arm64-debug.zip -ca1e80d12024d0586de824a49d77447e0e56b470cbb296d19d19b3fa10b156fb *electron-v37.3.1-linux-arm64-symbols.zip -c5c8ec46d9e291cd9dddb40c635d947d0f17873739f93b069e75b4bdadd75f5d *electron-v37.3.1-linux-arm64.zip -4d058758fa31db78b2ca90ab35e8726e59a8ac400c453c449ccbb7b4b1f72985 *electron-v37.3.1-linux-armv7l-debug.zip -94583e6468fa1870546b20468fb660d26c68fefe04536e4a0cd233cc5babf6c0 *electron-v37.3.1-linux-armv7l-symbols.zip -0871625623efb0edbb4d93ec9e036e01837f9d9ffaf4f1c05ae95f30ff823987 *electron-v37.3.1-linux-armv7l.zip -65c3b723888150cf39287e0b7344518ec34780dfc3555ab21bdb1711cf4dcadb *electron-v37.3.1-linux-x64-debug.zip -1a1d1a68471fc8634222c6e0b522490b1b71325c4050bbf605b5e388afc26a81 *electron-v37.3.1-linux-x64-symbols.zip -9c379b91f7ff65311f2b040299ee95c137fcb8e7e1bef87f9225d608cf579548 *electron-v37.3.1-linux-x64.zip -af77d211929ca0ac9be99c36ee67a685b07d85f7208529d8a902cc17a95ae137 *electron-v37.3.1-mas-arm64-dsym-snapshot.zip -51a76b7f69a2abaffc0cf6e21abb193900b2e8416bd57933dec8b90eed6dba23 *electron-v37.3.1-mas-arm64-dsym.zip -105235e95242ab8569640dff17629420d91112f850331707ef9809dfcbffc5e0 *electron-v37.3.1-mas-arm64-symbols.zip -b25b0e73ca90adf049001d130e23563860c017dd0932dc3b683b8fddd7450c26 *electron-v37.3.1-mas-arm64.zip -2623c5ad40f06b5cc3da6c8346790a0820641e7e257cc18fa0ed8d8fbe72973e *electron-v37.3.1-mas-x64-dsym-snapshot.zip -f6bad32fe64a747a00a65aa05a05949b71b52cb0276b885481d0ea0b2a9408f9 *electron-v37.3.1-mas-x64-dsym.zip -5e0cf1965b2022e5bdcd4aaf4ca951e48168f6a3275013cb2926c656fc2ac572 *electron-v37.3.1-mas-x64-symbols.zip -79b5d45c0e3d315d49789a358f0483b786b111b311e1ef70c1b877968ea7b3c9 *electron-v37.3.1-mas-x64.zip -62cfaee6d7b5e2aab4da0210b7dbeb562180aa463790515d42a411fd3809aa7a *electron-v37.3.1-win32-arm64-pdb.zip -b0163b74a6ddb801ff045707fbce432895c524f9fb114b2cdcb191b0e8aeab74 *electron-v37.3.1-win32-arm64-symbols.zip -d6170c4da775b061bf8acca64432a1bc16636251d0c2d0b777a12f759e3a0319 *electron-v37.3.1-win32-arm64-toolchain-profile.zip -bf4e9e622c09a4837493aa1184ef90c14f98459a68fcabf72ac16f9182cd2c8c *electron-v37.3.1-win32-arm64.zip -3f7f45c90ac6c3bade54ca3065a6eb7a84ceb014d133ac8aee25a7c1554eec5d *electron-v37.3.1-win32-ia32-pdb.zip -7c2ab030c6fa37f804593fe396baba664afc8e4475ea24b79ee861221713b64d *electron-v37.3.1-win32-ia32-symbols.zip -d6170c4da775b061bf8acca64432a1bc16636251d0c2d0b777a12f759e3a0319 *electron-v37.3.1-win32-ia32-toolchain-profile.zip -e6d2167adaa0b64b921a014eff2c6d670912b26ee3261357a4474cbc8dc79255 *electron-v37.3.1-win32-ia32.zip -b7e48513d06464bfcb97ef68b21c8601e9b584fa3ca59e0b32cae4ca5aa9f1bd *electron-v37.3.1-win32-x64-pdb.zip -b63200053e6f2e4d789918f77c55409136719ba83bdfc4852d8ec57ceeafee09 *electron-v37.3.1-win32-x64-symbols.zip -d6170c4da775b061bf8acca64432a1bc16636251d0c2d0b777a12f759e3a0319 *electron-v37.3.1-win32-x64-toolchain-profile.zip -32ad7124107ffef2aa92abb03175c4a8a60133d50cda338c2ab2917c5efc8c32 *electron-v37.3.1-win32-x64.zip -ad1fe6a7873a642885b6a95bf5e182a5d2f85bdfebb18d816fc39982f26a0f61 *electron.d.ts -5ec0166be035bfda6fa940d90e8eb3c2fb473c73022061d577d5c657b60399f6 *ffmpeg-v37.3.1-darwin-arm64.zip -1afd9975b951018f4d0b0c85dfd565516f5c49b74d9f10d06c6b9cd665bd42a0 *ffmpeg-v37.3.1-darwin-x64.zip -f0792bdd28ac2231e2d10bdb89da0221e9b15149a4761448e6bfd50ba8e76895 *ffmpeg-v37.3.1-linux-arm64.zip -5bd4adf23596c09bbb671952b73427f6701a7e9aee647979925e9cc4ff973045 *ffmpeg-v37.3.1-linux-armv7l.zip -561a7685536c133d2072e2e2b5a64ca3897bb8c71624158a6fe8e07cae9116c9 *ffmpeg-v37.3.1-linux-x64.zip -5ec0166be035bfda6fa940d90e8eb3c2fb473c73022061d577d5c657b60399f6 *ffmpeg-v37.3.1-mas-arm64.zip -1afd9975b951018f4d0b0c85dfd565516f5c49b74d9f10d06c6b9cd665bd42a0 *ffmpeg-v37.3.1-mas-x64.zip -f8ff1f5dbc7a974380fd26138247b51ec1b6a83b09c70310a5038ebab9f902b7 *ffmpeg-v37.3.1-win32-arm64.zip -257f11de301c99c256651f8ae38894a33190201e9ee275fb79a8419827eb070d *ffmpeg-v37.3.1-win32-ia32.zip -f51580bc5eef64cfe33ab6fbd1c9423c58f8904dd22a0a78256646be2b6f0706 *ffmpeg-v37.3.1-win32-x64.zip -9a3617b21cbd2c438997466f735ecbf8fe28565c060bb7548eaa9fdb2180efc9 *hunspell_dictionaries.zip -22f20c293a8c578089be2231f52312db8459a27fa7e9197d53753b3475c60184 *libcxx-objects-v37.3.1-linux-arm64.zip -420ce50e293e6f250e5e30c19b88ae010f1b4abc09b2421c16dd28b9badd3375 *libcxx-objects-v37.3.1-linux-armv7l.zip -a2f31afafb8e9d771ba8f667d6fde90d091739bdd1f568567c5a4b0a5bdd550d *libcxx-objects-v37.3.1-linux-x64.zip -5760013c0a4cc128a5cc2745756063b60552ecabe24206204ece2bfd808b0637 *libcxx_headers.zip -06659d8c13cf63ef52ee06be71be0e4d83612c577539f630c97274cbe1ec9ad2 *libcxxabi_headers.zip -c2e75472195627c3d7e5717d6ef058eede2aa098e27bdf7360e797f60ec0f120 *mksnapshot-v37.3.1-darwin-arm64.zip -c84cd7c3a6184c892602e38e682e09791bd1b8df138217ae433cbe7cddd6602a *mksnapshot-v37.3.1-darwin-x64.zip -078aed275562e60b14e55b2d700a11b660aa484bd98dc8b3567e4ddbeb3fd020 *mksnapshot-v37.3.1-linux-arm64-x64.zip -7cf2011a1274ec39bc5b1f6a7bb4c28aefa2e03cc0c49419199dc7f38d865420 *mksnapshot-v37.3.1-linux-armv7l-x64.zip -3099dc0eaedc447a76ed02cfd26498901ee4be449dea8f6873956c2abd4d640d *mksnapshot-v37.3.1-linux-x64.zip -d1ab919cc4edadf5781412e4962f3622f60664ca10787d4b6260dda52c4fbac1 *mksnapshot-v37.3.1-mas-arm64.zip -d62460a609d390c970e7919da040434d8968d2605214c3fde5e32b4b36b9bf38 *mksnapshot-v37.3.1-mas-x64.zip -c4358188116397a03f2d3c01de46dea440f1aac48e4165a0b5d884e6eb567d5f *mksnapshot-v37.3.1-win32-arm64-x64.zip -b27f88cd2bf7f1cfad9fabd68364548ba26311a1f1756fcccf18e49768040949 *mksnapshot-v37.3.1-win32-ia32.zip -ff8926915db67748ea168066e80e8b087fbfd99e4b0ff54c6c29c7049f0d23d9 *mksnapshot-v37.3.1-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/code/build/checksums/explorer-dll.txt b/code/build/checksums/explorer-dll.txt index fb8ad756847..4d34e265297 100644 --- a/code/build/checksums/explorer-dll.txt +++ b/code/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/code/build/checksums/nodejs.txt b/code/build/checksums/nodejs.txt index 7bc49dadc7a..43aace217e9 100644 --- a/code/build/checksums/nodejs.txt +++ b/code/build/checksums/nodejs.txt @@ -1,7 +1,7 @@ -2c12913cba67af77ded8a399df3fd91c2e7f8628c7079da40bb9ff33bf00dfc0 node-v22.18.0-darwin-arm64.tar.gz -9c8aa1e5ff5780b38cc1134e2263d84e2f4308eb84c02515e3af33936ca02cdc node-v22.18.0-darwin-x64.tar.gz -d415eeea90a2fdb60c66dd386b258acbfc4d1fa4720a8df5dea7369fbdbcddee node-v22.18.0-linux-arm64.tar.gz -57830914581dc3640e8d95378b76c6910860f42531959e4e88eb445e0cd982b0 node-v22.18.0-linux-armv7l.tar.gz -a2e703725d8683be86bb5da967bf8272f4518bdaf10f21389e2b2c9eaeae8c8a node-v22.18.0-linux-x64.tar.gz -9cdd74e4d0fde4d8b43f2370577a194ebf3fc844cd6d177e98bc7c3f432e972a win-arm64/node.exe -c22d1c59a1f767a1ed0178445a027f2257d318c55430fc819d48f269586822b7 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/code/build/darwin/create-universal-app.js b/code/build/darwin/create-universal-app.js deleted file mode 100644 index b5ae1a86745..00000000000 --- a/code/build/darwin/create-universal-app.js +++ /dev/null @@ -1,63 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * 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")); -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,**/extensions/microsoft-authentication/dist/libmsalruntime.dylib,**/extensions/microsoft-authentication/dist/msal-node-runtime.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/code/build/darwin/create-universal-app.ts b/code/build/darwin/create-universal-app.ts index 41bae77cd12..6bda47add71 100644 --- a/code/build/darwin/create-universal-app.ts +++ b/code/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({ @@ -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/code/build/darwin/sign.js b/code/build/darwin/sign.js deleted file mode 100644 index 5824fdef8f5..00000000000 --- a/code/build/darwin/sign.js +++ /dev/null @@ -1,105 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * 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 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/code/build/darwin/sign.ts b/code/build/darwin/sign.ts index 83f18c6a5a7..fcdcb2b2d45 100644 --- a/code/build/darwin/sign.ts +++ b/code/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/code/build/darwin/verify-macho.js b/code/build/darwin/verify-macho.js deleted file mode 100644 index 6a5b4a0895d..00000000000 --- a/code/build/darwin/verify-macho.js +++ /dev/null @@ -1,136 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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 minimatch_1 = __importDefault(require("minimatch")); -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, -]); -// 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) { - return FILES_TO_SKIP.some(pattern => (0, minimatch_1.default)(file, pattern)); -} -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 => { - // 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'); - } -}).catch(err => { - console.error(err); - process.exit(1); -}); -//# sourceMappingURL=verify-macho.js.map \ No newline at end of file diff --git a/code/build/eslint.js b/code/build/eslint.js deleted file mode 100644 index e952c546eec..00000000000 --- a/code/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. - *--------------------------------------------------------------------------------------------*/ -// @ts-check -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/code/build/eslint.ts b/code/build/eslint.ts new file mode 100644 index 00000000000..a2ef396a16c --- /dev/null +++ b/code/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/code/build/filters.js b/code/build/filters.js deleted file mode 100644 index 1ef28f08b4e..00000000000 --- a/code/build/filters.js +++ /dev/null @@ -1,223 +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 - -/** - * 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*/**', - '!extensions/**/out*/**', - '!test/**/out/**', - '!**/node_modules/**', - '!**/*.js.map', -]; - -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/code/build/filters.ts b/code/build/filters.ts new file mode 100644 index 00000000000..04c72e27cbc --- /dev/null +++ b/code/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/code/build/gulp-eslint.js b/code/build/gulp-eslint.js deleted file mode 100644 index 793c16c2f30..00000000000 --- a/code/build/gulp-eslint.js +++ /dev/null @@ -1,86 +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, default: Stream } = require('stream'); -const { relative } = require('path'); -const fancyLog = require('fancy-log'); - -/** - * @typedef {ESLint.LintResult[] & { errorCount: number, warningCount: number}} ESLintResults - */ - -/** - * @param {(results: ESLintResults) => void} action - A function to handle all ESLint results - */ -function eslint(action) { - const linter = new ESLint({}); - const formatter = linter.loadFormatter('compact'); - - /** @type {ESLintResults} results */ - 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); - } - }); -} - -/** - * @param {Stream.TransformOptions['transform']} transform - * @param {Stream.TransformOptions['flush']} flush - */ -function transform(transform, flush) { - return new Transform({ - objectMode: true, - transform, - flush - }); -} - -module.exports = eslint; diff --git a/code/build/gulp-eslint.ts b/code/build/gulp-eslint.ts new file mode 100644 index 00000000000..1e953cdba7b --- /dev/null +++ b/code/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/code/build/gulpfile.cli.js b/code/build/gulpfile.cli.js deleted file mode 100644 index 63e0ae0b847..00000000000 --- a/code/build/gulpfile.cli.js +++ /dev/null @@ -1,154 +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 { 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 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/code/build/gulpfile.cli.ts b/code/build/gulpfile.cli.ts new file mode 100644 index 00000000000..e746a00e2bb --- /dev/null +++ b/code/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/code/build/gulpfile.compile.js b/code/build/gulpfile.compile.js deleted file mode 100644 index 0c0a024c8fc..00000000000 --- a/code/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/code/build/gulpfile.compile.ts b/code/build/gulpfile.compile.ts new file mode 100644 index 00000000000..fcfdf2dca57 --- /dev/null +++ b/code/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/code/build/gulpfile.editor.js b/code/build/gulpfile.editor.js deleted file mode 100644 index ece30e13152..00000000000 --- a/code/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/code/build/gulpfile.editor.ts b/code/build/gulpfile.editor.ts new file mode 100644 index 00000000000..447b76fa16c --- /dev/null +++ b/code/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/code/build/gulpfile.extensions.js b/code/build/gulpfile.extensions.js deleted file mode 100644 index b0bedffacc1..00000000000 --- a/code/build/gulpfile.extensions.js +++ /dev/null @@ -1,307 +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/che-activity-tracker/tsconfig.json', - 'extensions/che-api/tsconfig.json', - 'extensions/che-commands/tsconfig.json', - 'extensions/che-port/tsconfig.json', - 'extensions/che-remote/tsconfig.json', - 'extensions/che-resource-monitor/tsconfig.json', - 'extensions/che-terminal/tsconfig.json', - 'extensions/che-telemetry/tsconfig.json', - 'extensions/che-github-authentication/tsconfig.json', - '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/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 => `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); - - 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(false, false).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(false, false).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/code/build/gulpfile.extensions.ts b/code/build/gulpfile.extensions.ts new file mode 100644 index 00000000000..140d89d7d38 --- /dev/null +++ b/code/build/gulpfile.extensions.ts @@ -0,0 +1,282 @@ +/*--------------------------------------------------------------------------------------------- + * 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/che-activity-tracker/tsconfig.json', + 'extensions/che-api/tsconfig.json', + 'extensions/che-commands/tsconfig.json', + 'extensions/che-port/tsconfig.json', + 'extensions/che-remote/tsconfig.json', + 'extensions/che-resource-monitor/tsconfig.json', + 'extensions/che-terminal/tsconfig.json', + 'extensions/che-telemetry/tsconfig.json', + 'extensions/che-github-authentication/tsconfig.json', + '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/code/build/gulpfile.hygiene.js b/code/build/gulpfile.hygiene.js deleted file mode 100644 index c76fab7abc6..00000000000 --- a/code/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/code/build/gulpfile.hygiene.ts b/code/build/gulpfile.hygiene.ts new file mode 100644 index 00000000000..24595643c86 --- /dev/null +++ b/code/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/code/build/gulpfile.js b/code/build/gulpfile.js deleted file mode 100644 index 97971eec63e..00000000000 --- a/code/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 { 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/code/build/gulpfile.reh.js b/code/build/gulpfile.reh.js deleted file mode 100644 index 6057834f458..00000000000 --- a/code/build/gulpfile.reh.js +++ /dev/null @@ -1,490 +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 workbenchConfig = require('../src/vs/code/browser/workbench/che/workbench-config.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: 'ppc64' }, - { platform: 'linux', arch: 's390x' }, - { 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, workbenchConfig) - } - } - ) - )); - - 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/code/build/gulpfile.reh.ts b/code/build/gulpfile.reh.ts new file mode 100644 index 00000000000..8e87c57b942 --- /dev/null +++ b/code/build/gulpfile.reh.ts @@ -0,0 +1,492 @@ +/*--------------------------------------------------------------------------------------------- + * 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'; +// GLG-REBASE +import workbenchConfig from '../src/vs/code/browser/workbench/che/workbench-config.json' with { type: 'json' }; +// GLG-REBASE + +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: 'ppc64' }, + { platform: 'linux', arch: 's390x' }, + { 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, workbenchConfig) + } + } + ) + )); + + 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/code/build/gulpfile.scan.js b/code/build/gulpfile.scan.js deleted file mode 100644 index aafc64e81c2..00000000000 --- a/code/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/code/build/gulpfile.scan.ts b/code/build/gulpfile.scan.ts new file mode 100644 index 00000000000..19e50c016e6 --- /dev/null +++ b/code/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/code/build/gulpfile.ts b/code/build/gulpfile.ts new file mode 100644 index 00000000000..e83b9a08d28 --- /dev/null +++ b/code/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/code/build/gulpfile.vscode.js b/code/build/gulpfile.vscode.js deleted file mode 100644 index ed06b6a5aa8..00000000000 --- a/code/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 === '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.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/code/build/gulpfile.vscode.linux.js b/code/build/gulpfile.vscode.linux.js deleted file mode 100644 index 9cf6411e46a..00000000000 --- a/code/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/code/build/gulpfile.vscode.linux.ts b/code/build/gulpfile.vscode.linux.ts new file mode 100644 index 00000000000..c5d216319ce --- /dev/null +++ b/code/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/code/build/gulpfile.vscode.ts b/code/build/gulpfile.vscode.ts new file mode 100644 index 00000000000..ac70ecbd57f --- /dev/null +++ b/code/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/code/build/gulpfile.vscode.web.js b/code/build/gulpfile.vscode.web.js deleted file mode 100644 index c54d2df34fe..00000000000 --- a/code/build/gulpfile.vscode.web.js +++ /dev/null @@ -1,247 +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 workbenchConfig = require('../src/vs/code/browser/workbench/che/workbench-config.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, workbenchConfig) => { - /** - * @param {string} path - * @returns {((content: string) => string) | undefined} - */ - 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 ]*/); - }; - } else if (path.endsWith('vs/code/browser/workbench/che/workbench-che-config.js')) { - return content => { - const workbenchConfiguration = JSON.stringify({ ...workbenchConfig }); - return content.replace('/*BUILD->INSERT_WORKBENCH_CONFIGURATION*/', workbenchConfiguration.substr(1, workbenchConfiguration.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, workbenchConfig) - } - } - ) -)); - -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); - -/** - * @param {string} sourceFolderName - * @param {string} destinationFolderName - */ -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/code/build/gulpfile.vscode.web.ts b/code/build/gulpfile.vscode.web.ts new file mode 100644 index 00000000000..5f286383da4 --- /dev/null +++ b/code/build/gulpfile.vscode.web.ts @@ -0,0 +1,237 @@ +/*--------------------------------------------------------------------------------------------- + * 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 VinylFile from 'vinyl'; +import jsonEditor from 'gulp-json-editor'; +import buildfile from './buildfile.ts'; +// GLG-REBASE +import workbenchConfig from '../src/vs/code/browser/workbench/che/workbench-config.json' with { type: 'json' }; +// GLG-REBASE + +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, + buildfile.entrypoint('vs/workbench/workbench.web.main.internal') // TODO@esm remove line when we stop supporting web-amd-esm-bridge +].flat(); + +/** + * @param extensionsRoot The location where extension will be read from + * @param product The parsed product.json file contents + */ +// GLG-REBASE +export const createVSCodeWebFileContentMapper = (extensionsRoot: string, product: typeof import('../product.json'), workbenchConfig: typeof import('../src/vs/code/browser/workbench/che/workbench-config.json')) => { +// GLG-REBASE + 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 ]*/); + }; + } else if (path.endsWith('vs/code/browser/workbench/che/workbench-che-config.js')) { + return content => { + const workbenchConfiguration = JSON.stringify({ ...workbenchConfig }); + return content.replace('/*BUILD->INSERT_WORKBENCH_CONFIGURATION*/', workbenchConfiguration.substr(1, workbenchConfiguration.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, workbenchConfig) + } + } + ) +)); + +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 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(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/code/build/gulpfile.vscode.win32.js b/code/build/gulpfile.vscode.win32.js deleted file mode 100644 index 9207df5a44b..00000000000 --- a/code/build/gulpfile.vscode.win32.js +++ /dev/null @@ -1,159 +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/code/build/gulpfile.vscode.win32.ts b/code/build/gulpfile.vscode.win32.ts new file mode 100644 index 00000000000..a7b01f0a371 --- /dev/null +++ b/code/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/code/build/hygiene.js b/code/build/hygiene.js deleted file mode 100644 index 294681e0ae0..00000000000 --- a/code/build/hygiene.js +++ /dev/null @@ -1,333 +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 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.', - ' *--------------------------------------------------------------------------------------------*/', -]; - -/** - * @param {string[] | NodeJS.ReadWriteStream} some - * @param {boolean} linting - */ -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')); - - // CHECODE: we have openvsx - /* - if (product.extensionsGallery) { - console.error(`product.json: Contains 'extensionsGallery'`); - errorCount++; - } - */ - - this.emit('data', file); - }); - - const unicode = es.through(function (file) { - /** @type {string[]} */ - 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) { - /** @type {string[]} */ - 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) { - // CHECODE: skip che relative copyright files - if (/^.*\/vs\/server\/che.*/.test(file.relative)) { - this.emit('data', file); - return; - } - 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(undefined, 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); - - /** @type {import('stream').Stream[]} */ - 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; - -/** - * @param {string[]} paths - */ -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) => { - /** @type {Promise} */ - return (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/code/build/hygiene.ts b/code/build/hygiene.ts new file mode 100644 index 00000000000..f19aeb3374e --- /dev/null +++ b/code/build/hygiene.ts @@ -0,0 +1,323 @@ +/*--------------------------------------------------------------------------------------------- + * 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')); + + // CHECODE: we have openvsx + /* + 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) { + // CLG-REBASE + // CHECODE: skip che relative copyright files + if (/^.*\/vs\/server\/che.*/.test(file.relative)) { + this.emit('data', file); + return; + } + // CLG-REBASE + 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/code/build/lib/asar.js b/code/build/lib/asar.js deleted file mode 100644 index 2da31a93904..00000000000 --- a/code/build/lib/asar.js +++ /dev/null @@ -1,156 +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.createAsar = createAsar; -/*--------------------------------------------------------------------------------------------- - * 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 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/code/build/lib/asar.ts b/code/build/lib/asar.ts index 5f2df925bde..873b3f946fd 100644 --- a/code/build/lib/asar.ts +++ b/code/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/code/build/lib/builtInExtensions.js b/code/build/lib/builtInExtensions.js deleted file mode 100644 index 95fe7a09bac..00000000000 --- a/code/build/lib/builtInExtensions.js +++ /dev/null @@ -1,179 +0,0 @@ -"use strict"; -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; -/*--------------------------------------------------------------------------------------------- - * 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 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/code/build/lib/builtInExtensions.ts b/code/build/lib/builtInExtensions.ts index e9a1180ce35..d52567b17d1 100644 --- a/code/build/lib/builtInExtensions.ts +++ b/code/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/code/build/lib/builtInExtensionsCG.js b/code/build/lib/builtInExtensionsCG.js deleted file mode 100644 index 70546237ba4..00000000000 --- a/code/build/lib/builtInExtensionsCG.js +++ /dev/null @@ -1,81 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * 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 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/code/build/lib/builtInExtensionsCG.ts b/code/build/lib/builtInExtensionsCG.ts index 4628b365a2e..1c4ce609c3d 100644 --- a/code/build/lib/builtInExtensionsCG.ts +++ b/code/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/code/build/lib/bundle.js b/code/build/lib/bundle.js deleted file mode 100644 index 08f29d10847..00000000000 --- a/code/build/lib/bundle.js +++ /dev/null @@ -1,58 +0,0 @@ -"use strict"; -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/code/build/lib/compilation.js b/code/build/lib/compilation.js deleted file mode 100644 index fb326dfd2b1..00000000000 --- a/code/build/lib/compilation.js +++ /dev/null @@ -1,340 +0,0 @@ -"use strict"; -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; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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: !!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/code/build/lib/compilation.ts b/code/build/lib/compilation.ts index aea06e8a242..948c6b4ef4f 100644 --- a/code/build/lib/compilation.ts +++ b/code/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); @@ -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); } diff --git a/code/build/lib/date.js b/code/build/lib/date.js deleted file mode 100644 index d189815ab06..00000000000 --- a/code/build/lib/date.js +++ /dev/null @@ -1,35 +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.writeISODate = writeISODate; -exports.readISODate = readISODate; -/*--------------------------------------------------------------------------------------------- - * 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")); -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/code/build/lib/date.ts b/code/build/lib/date.ts index 8a933178952..9c20c9eeb22 100644 --- a/code/build/lib/date.ts +++ b/code/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/code/build/lib/dependencies.js b/code/build/lib/dependencies.js deleted file mode 100644 index c6baaafa070..00000000000 --- a/code/build/lib/dependencies.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.getProductionDependencies = getProductionDependencies; -/*--------------------------------------------------------------------------------------------- - * 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 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/code/build/lib/dependencies.ts b/code/build/lib/dependencies.ts index a5bc70088a7..ed7cbfbef02 100644 --- a/code/build/lib/dependencies.ts +++ b/code/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/code/build/lib/electron.js b/code/build/lib/electron.js deleted file mode 100644 index 0602307f4c3..00000000000 --- a/code/build/lib/electron.js +++ /dev/null @@ -1,258 +0,0 @@ -"use strict"; -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; -/*--------------------------------------------------------------------------------------------- - * 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 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/code/build/lib/electron.ts b/code/build/lib/electron.ts index 3bb047dfcee..8cc36de49ea 100644 --- a/code/build/lib/electron.ts +++ b/code/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/code/build/lib/extensions.js b/code/build/lib/extensions.js deleted file mode 100644 index aeea722d372..00000000000 --- a/code/build/lib/extensions.js +++ /dev/null @@ -1,620 +0,0 @@ -"use strict"; -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; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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 webpackConfigFileName = forWeb - ? `extension-browser.webpack.config.js` - : `extension.webpack.config.js`; - 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)).default; - 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).default; - 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.mjs', - 'markdown-language-features/esbuild-preview.mjs', - 'markdown-math/esbuild.mjs', - 'notebook-renderers/esbuild.mjs', - 'ipynb/esbuild.mjs', - 'simple-browser/esbuild-preview.mjs', -]; -async function webpackExtensions(taskName, isWatch, webpackConfigLocations) { - const webpack = require('webpack'); - const webpackConfigs = []; - for (const { configPath, outputRoot } of webpackConfigLocations) { - const configOrFnOrArray = require(configPath).default; - 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/code/build/lib/extensions.ts b/code/build/lib/extensions.ts index b997fe4046e..24462a3b26e 100644 --- a/code/build/lib/extensions.ts +++ b/code/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}`; @@ -117,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); @@ -175,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'); @@ -215,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); @@ -333,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 || []; @@ -417,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)); @@ -559,11 +561,12 @@ const extensionsPath = path.join(root, 'extensions'); // Additional projects to run esbuild on. These typically build code for webviews const esbuildMediaScripts = [ + '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', - 'ipynb/esbuild.mjs', 'simple-browser/esbuild-preview.mjs', ]; diff --git a/code/build/lib/fetch.js b/code/build/lib/fetch.js deleted file mode 100644 index 25587697016..00000000000 --- a/code/build/lib/fetch.js +++ /dev/null @@ -1,141 +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.fetchUrls = fetchUrls; -exports.fetchUrl = fetchUrl; -exports.fetchGithub = fetchGithub; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/lib/fetch.ts b/code/build/lib/fetch.ts index f09b53e121c..970887b3e55 100644 --- a/code/build/lib/fetch.ts +++ b/code/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/code/build/lib/formatter.js b/code/build/lib/formatter.js deleted file mode 100644 index 1085ea8f488..00000000000 --- a/code/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/code/build/lib/formatter.ts b/code/build/lib/formatter.ts index 993722e5f92..09c1de929ba 100644 --- a/code/build/lib/formatter.ts +++ b/code/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/code/build/lib/getVersion.js b/code/build/lib/getVersion.js deleted file mode 100644 index 94744415d60..00000000000 --- a/code/build/lib/getVersion.js +++ /dev/null @@ -1,49 +0,0 @@ -"use strict"; -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; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/lib/getVersion.ts b/code/build/lib/getVersion.ts index 2fddb309f83..1dc4600dadf 100644 --- a/code/build/lib/getVersion.ts +++ b/code/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/code/build/lib/git.js b/code/build/lib/git.js deleted file mode 100644 index 30de97ed6e3..00000000000 --- a/code/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/code/build/lib/i18n.js b/code/build/lib/i18n.js deleted file mode 100644 index b325ad2964a..00000000000 --- a/code/build/lib/i18n.js +++ /dev/null @@ -1,784 +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.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; -/*--------------------------------------------------------------------------------------------- - * 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")); -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'); - try { - const json = (0, l10n_dev_1.getL10nJson)([{ contents, extension }]); - callback(undefined, new vinyl_1.default({ - path: `extensions/${extensionFolderName}/bundle.l10n.json`, - contents: Buffer.from(JSON.stringify(json), 'utf8') - })); - } - catch (error) { - callback(new Error(`File ${file.relative} threw an error when parsing: ${error}`)); - } - // 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/code/build/lib/i18n.ts b/code/build/lib/i18n.ts index 72dcd5f7afd..1aae607a8f0 100644 --- a/code/build/lib/i18n.ts +++ b/code/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 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 @@ -552,11 +537,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()) { @@ -579,10 +564,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); @@ -597,7 +582,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 }; @@ -637,8 +622,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') { @@ -743,8 +728,8 @@ 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'); @@ -802,10 +787,10 @@ 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()); parsePromises.push(parsePromise); diff --git a/code/build/lib/inlineMeta.js b/code/build/lib/inlineMeta.js deleted file mode 100644 index b997f5e9a78..00000000000 --- a/code/build/lib/inlineMeta.js +++ /dev/null @@ -1,51 +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.inlineMeta = inlineMeta; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/lib/mangle/index.js b/code/build/lib/mangle/index.js deleted file mode 100644 index 8814a1e193a..00000000000 --- a/code/build/lib/mangle/index.js +++ /dev/null @@ -1,676 +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.Mangler = void 0; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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) { - 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) - 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', - // che-api contains few interfaces (with Symbol usage) that are not handled correctly by the mangle logic - 'devfile-service', - 'github-service', - 'telemetry-service', - 'workspace-service', - // 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; - defaultWorkersNumber = 4; - 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: (() => { - const envVal = Number(process.env['VSCODE_MANGLE_WORKERS'] ?? ''); - if (Number.isFinite(envVal) && envVal > 0) { - const maxWorkersNumber = Math.min(8, Math.max(1, Math.floor(envVal))); - this.log(`env.VSCODE_MANGLE_WORKERS is set to ${envVal}, using ${maxWorkersNumber} number of maxWorkers`); - return maxWorkersNumber; - } - this.log(`env.VSCODE_MANGLE_WORKERS is not set, using the default number of maxWorkers: ${this.defaultWorkersNumber}`); - return this.defaultWorkersNumber; - })(), - 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/code/build/lib/mangle/index.ts b/code/build/lib/mangle/index.ts index 98c0a0fc17d..ae214711a3e 100644 --- a/code/build/lib/mangle/index.ts +++ b/code/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; @@ -338,12 +352,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(); } @@ -405,13 +423,20 @@ export class Mangler { private readonly renameWorkerPool: workerpool.WorkerPool; private readonly defaultWorkersNumber = 4; + 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: ((): number => { const envVal = Number(process.env['VSCODE_MANGLE_WORKERS'] ?? ''); if (Number.isFinite(envVal) && envVal > 0) { @@ -763,7 +788,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'); @@ -784,6 +809,6 @@ async function _run() { } } -if (__filename === argv[1]) { +if (import.meta.main) { _run(); } diff --git a/code/build/lib/mangle/renameWorker.js b/code/build/lib/mangle/renameWorker.js deleted file mode 100644 index d34e0a2346f..00000000000 --- a/code/build/lib/mangle/renameWorker.js +++ /dev/null @@ -1,25 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/lib/mangle/renameWorker.ts b/code/build/lib/mangle/renameWorker.ts index 0cce5677593..b7bfb539398 100644 --- a/code/build/lib/mangle/renameWorker.ts +++ b/code/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/code/build/lib/mangle/staticLanguageServiceHost.js b/code/build/lib/mangle/staticLanguageServiceHost.js deleted file mode 100644 index e17846f717f..00000000000 --- a/code/build/lib/mangle/staticLanguageServiceHost.js +++ /dev/null @@ -1,68 +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.StaticLanguageServiceHost = void 0; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/lib/mangle/staticLanguageServiceHost.ts b/code/build/lib/mangle/staticLanguageServiceHost.ts index b41b4e52133..4fcf107f716 100644 --- a/code/build/lib/mangle/staticLanguageServiceHost.ts +++ b/code/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/code/build/lib/monaco-api.js b/code/build/lib/monaco-api.js deleted file mode 100644 index cc801849a5d..00000000000 --- a/code/build/lib/monaco-api.js +++ /dev/null @@ -1,630 +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.DeclarationResolver = exports.FSProvider = exports.RECIPE_PATH = void 0; -exports.run3 = run3; -exports.execute = execute; -/*--------------------------------------------------------------------------------------------- - * 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 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/code/build/lib/monaco-api.ts b/code/build/lib/monaco-api.ts index 5dc9a04266c..fa6c2a28c91 100644 --- a/code/build/lib/monaco-api.ts +++ b/code/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/code/build/lib/nls.js b/code/build/lib/nls.js deleted file mode 100644 index 492dbdae8ce..00000000000 --- a/code/build/lib/nls.js +++ /dev/null @@ -1,407 +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.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/code/build/lib/nls.ts b/code/build/lib/nls.ts index a21102b0ccc..39cc07d9d01 100644 --- a/code/build/lib/nls.ts +++ b/code/build/lib/nls.ts @@ -3,22 +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'; -type FileSourceMap = File & { 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[] = []; @@ -40,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; } /** @@ -52,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.`)); } @@ -112,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 @@ -178,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(''); } @@ -227,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); @@ -242,7 +248,7 @@ 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` @@ -254,15 +260,15 @@ 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 @@ -274,7 +280,7 @@ module _nls { // custom named `localize` read-only references const namedLocalizeReferences = allLocalizeImportDeclarations - .filter(d => d.propertyName && d.propertyName.getText() === functionName) + .filter(d => !!d.propertyName && d.propertyName.getText() === functionName) .map(n => service.getReferencesAtPosition(filename, n.name.pos + 1) ?? []) .flatten() .filter(r => !r.isWriteAccess); @@ -285,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 @@ -492,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$/, '') @@ -503,12 +508,12 @@ module _nls { ts, typescript, javascriptFile.contents!.toString(), - (javascriptFile).sourceMap, + javascriptFile.sourceMap, options ); const result = fileFrom(javascriptFile, javascript); - (result).sourceMap = sourcemap; + result.sourceMap = sourcemap; if (nlsKeys) { moduleToNLSKeys[moduleId] = nlsKeys; @@ -522,4 +527,12 @@ module _nls { return result; } -} + + return { + moduleToNLSKeys, + moduleToNLSMessages, + allNLSMessages, + allNLSModulesAndKeys, + patchFile + }; +})(); diff --git a/code/build/lib/node.js b/code/build/lib/node.js deleted file mode 100644 index 62533fbe6ca..00000000000 --- a/code/build/lib/node.js +++ /dev/null @@ -1,21 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * 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")); -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/code/build/lib/node.ts b/code/build/lib/node.ts index a2fdc361aa1..1825546deb9 100644 --- a/code/build/lib/node.ts +++ b/code/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/code/build/lib/optimize.js b/code/build/lib/optimize.js deleted file mode 100644 index fbc455b1cd1..00000000000 --- a/code/build/lib/optimize.js +++ /dev/null @@ -1,224 +0,0 @@ -"use strict"; -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; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/lib/optimize.ts b/code/build/lib/optimize.ts index d89d0d627f9..58b8e07fdb3 100644 --- a/code/build/lib/optimize.ts +++ b/code/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', @@ -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/code/build/lib/policies.js b/code/build/lib/policies.js deleted file mode 100644 index d2ef760870d..00000000000 --- a/code/build/lib/policies.js +++ /dev/null @@ -1,887 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/lib/policies.ts b/code/build/lib/policies.ts deleted file mode 100644 index 381d2f4c1ac..00000000000 --- a/code/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/code/build/lib/policies/basePolicy.ts b/code/build/lib/policies/basePolicy.ts new file mode 100644 index 00000000000..7f650ba7b2e --- /dev/null +++ b/code/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/code/build/lib/policies/booleanPolicy.ts b/code/build/lib/policies/booleanPolicy.ts new file mode 100644 index 00000000000..59e2402eb3c --- /dev/null +++ b/code/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/code/build/lib/policies/copyPolicyDto.ts b/code/build/lib/policies/copyPolicyDto.ts new file mode 100644 index 00000000000..6bf8cd88802 --- /dev/null +++ b/code/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/code/build/lib/policies/numberPolicy.ts b/code/build/lib/policies/numberPolicy.ts new file mode 100644 index 00000000000..3091e004677 --- /dev/null +++ b/code/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/code/build/lib/policies/objectPolicy.ts b/code/build/lib/policies/objectPolicy.ts new file mode 100644 index 00000000000..b565b06e8bb --- /dev/null +++ b/code/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/code/build/lib/policies/policyData.jsonc b/code/build/lib/policies/policyData.jsonc new file mode 100644 index 00000000000..b8f4106fc97 --- /dev/null +++ b/code/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/code/build/lib/policies/policyGenerator.ts b/code/build/lib/policies/policyGenerator.ts new file mode 100644 index 00000000000..e0de81f4d32 --- /dev/null +++ b/code/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/code/build/lib/policies/render.ts b/code/build/lib/policies/render.ts new file mode 100644 index 00000000000..47b485d1bf0 --- /dev/null +++ b/code/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/code/build/lib/policies/stringEnumPolicy.ts b/code/build/lib/policies/stringEnumPolicy.ts new file mode 100644 index 00000000000..b590abcc87b --- /dev/null +++ b/code/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/code/build/lib/policies/stringPolicy.ts b/code/build/lib/policies/stringPolicy.ts new file mode 100644 index 00000000000..e4e07e42c69 --- /dev/null +++ b/code/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/code/build/lib/policies/types.ts b/code/build/lib/policies/types.ts new file mode 100644 index 00000000000..4fe801c23d6 --- /dev/null +++ b/code/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/code/build/lib/preLaunch.js b/code/build/lib/preLaunch.js deleted file mode 100644 index ca79a09b068..00000000000 --- a/code/build/lib/preLaunch.js +++ /dev/null @@ -1,59 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * 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_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/code/build/lib/preLaunch.ts b/code/build/lib/preLaunch.ts index 0c178afcb59..5e175afde28 100644 --- a/code/build/lib/preLaunch.ts +++ b/code/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/code/build/lib/propertyInitOrderChecker.js b/code/build/lib/propertyInitOrderChecker.js deleted file mode 100644 index 48134856308..00000000000 --- a/code/build/lib/propertyInitOrderChecker.js +++ /dev/null @@ -1,249 +0,0 @@ -"use strict"; -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 }); -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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 -// -// ############################################################################################# -// -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 || (EntryKind = {})); -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 !== 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 = {})); -//# sourceMappingURL=propertyInitOrderChecker.js.map \ No newline at end of file diff --git a/code/build/lib/propertyInitOrderChecker.ts b/code/build/lib/propertyInitOrderChecker.ts index fc958c475c0..2c07f9c8757 100644 --- a/code/build/lib/propertyInitOrderChecker.ts +++ b/code/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,13 +22,15 @@ const TS_CONFIG_PATH = path.join(__dirname, '../../', 'src', 'tsconfig.json'); // ############################################################################################# // -enum EntryKind { - Span, - Node, - StringLiteral, - SearchedLocalFoundProperty, - SearchedPropertyFoundLocal, -} +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, @@ -248,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 { @@ -261,24 +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 }; - -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; @@ -291,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/code/build/lib/reporter.js b/code/build/lib/reporter.js deleted file mode 100644 index da3ca0f46a1..00000000000 --- a/code/build/lib/reporter.js +++ /dev/null @@ -1,105 +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.createReporter = createReporter; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/lib/reporter.ts b/code/build/lib/reporter.ts index c21fd841c0d..31a0cb3945d 100644 --- a/code/build/lib/reporter.ts +++ b/code/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/code/build/lib/snapshotLoader.js b/code/build/lib/snapshotLoader.js deleted file mode 100644 index 315ebcc1e01..00000000000 --- a/code/build/lib/snapshotLoader.js +++ /dev/null @@ -1,58 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.snaps = void 0; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/lib/snapshotLoader.ts b/code/build/lib/snapshotLoader.ts index 3cb2191144d..3df83f73447 100644 --- a/code/build/lib/snapshotLoader.ts +++ b/code/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/code/build/lib/standalone.js b/code/build/lib/standalone.js deleted file mode 100644 index 94aea68a9be..00000000000 --- a/code/build/lib/standalone.js +++ /dev/null @@ -1,214 +0,0 @@ -"use strict"; -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; -/*--------------------------------------------------------------------------------------------- - * 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 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/code/build/lib/standalone.ts b/code/build/lib/standalone.ts index b18908dcb03..3e1006fce12 100644 --- a/code/build/lib/standalone.ts +++ b/code/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/code/build/lib/stats.js b/code/build/lib/stats.js deleted file mode 100644 index e6a4f9f633e..00000000000 --- a/code/build/lib/stats.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.createStatsStream = createStatsStream; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/lib/stats.ts b/code/build/lib/stats.ts index 8db55d3e777..83bf0a4a7ae 100644 --- a/code/build/lib/stats.ts +++ b/code/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/code/build/lib/stylelint/validateVariableNames.js b/code/build/lib/stylelint/validateVariableNames.js deleted file mode 100644 index a5e84d415ba..00000000000 --- a/code/build/lib/stylelint/validateVariableNames.js +++ /dev/null @@ -1,37 +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.getVariableNameValidator = getVariableNameValidator; -/*--------------------------------------------------------------------------------------------- - * 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 = 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/code/build/lib/stylelint/validateVariableNames.ts b/code/build/lib/stylelint/validateVariableNames.ts index b28aed13f4b..3cab12ac98d 100644 --- a/code/build/lib/stylelint/validateVariableNames.ts +++ b/code/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/code/build/lib/stylelint/vscode-known-variables.json b/code/build/lib/stylelint/vscode-known-variables.json index ad3a5d26e92..8f6ce9b030d 100644 --- a/code/build/lib/stylelint/vscode-known-variables.json +++ b/code/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", @@ -54,13 +57,6 @@ "--vscode-chat-avatarForeground", "--vscode-chat-checkpointSeparator", "--vscode-chat-editedFileForeground", - "--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", "--vscode-chat-linesAddedForeground", "--vscode-chat-linesRemovedForeground", "--vscode-chat-requestBackground", @@ -70,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", @@ -165,6 +162,7 @@ "--vscode-editor-foldPlaceholderForeground", "--vscode-editor-foreground", "--vscode-editor-hoverHighlightBackground", + "--vscode-editor-inactiveLineHighlightBackground", "--vscode-editor-inactiveSelectionBackground", "--vscode-editor-inlineValuesBackground", "--vscode-editor-inlineValuesForeground", @@ -245,6 +243,7 @@ "--vscode-editorGutter-addedBackground", "--vscode-editorGutter-addedSecondaryBackground", "--vscode-editorGutter-background", + "--vscode-editorGutter-commentDraftGlyphForeground", "--vscode-editorGutter-commentGlyphForeground", "--vscode-editorGutter-commentRangeForeground", "--vscode-editorGutter-commentUnresolvedGlyphForeground", @@ -308,6 +307,7 @@ "--vscode-editorOverviewRuler-background", "--vscode-editorOverviewRuler-border", "--vscode-editorOverviewRuler-bracketMatchForeground", + "--vscode-editorOverviewRuler-commentDraftForeground", "--vscode-editorOverviewRuler-commentForeground", "--vscode-editorOverviewRuler-commentUnresolvedForeground", "--vscode-editorOverviewRuler-commonContentForeground", @@ -349,7 +349,6 @@ "--vscode-editorWarning-background", "--vscode-editorWarning-border", "--vscode-editorWarning-foreground", - "--vscode-editorWatermark-foreground", "--vscode-editorWhitespace-foreground", "--vscode-editorWidget-background", "--vscode-editorWidget-border", @@ -383,6 +382,7 @@ "--vscode-inlineChat-background", "--vscode-inlineChat-border", "--vscode-inlineChat-foreground", + "--vscode-inlineChat-regionHighlight", "--vscode-inlineChat-shadow", "--vscode-inlineChatDiff-inserted", "--vscode-inlineChatDiff-removed", @@ -463,6 +463,11 @@ "--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", @@ -621,6 +626,7 @@ "--vscode-scmGraph-historyItemHoverLabelForeground", "--vscode-scmGraph-historyItemRefColor", "--vscode-scmGraph-historyItemRemoteRefColor", + "--vscode-scrollbar-background", "--vscode-scrollbar-shadow", "--vscode-scrollbarSlider-activeBackground", "--vscode-scrollbarSlider-background", @@ -821,6 +827,7 @@ "--vscode-terminalSymbolIcon-pullRequestForeground", "--vscode-terminalSymbolIcon-remoteForeground", "--vscode-terminalSymbolIcon-stashForeground", + "--vscode-terminalSymbolIcon-symbolText", "--vscode-terminalSymbolIcon-symbolicLinkFileForeground", "--vscode-terminalSymbolIcon-symbolicLinkFolderForeground", "--vscode-terminalSymbolIcon-tagForeground", @@ -862,6 +869,7 @@ "--vscode-textLink-activeForeground", "--vscode-textLink-foreground", "--vscode-textPreformat-background", + "--vscode-textPreformat-border", "--vscode-textPreformat-foreground", "--vscode-textSeparator-foreground", "--vscode-titleBar-activeBackground", @@ -894,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", @@ -978,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" ] } diff --git a/code/build/lib/task.js b/code/build/lib/task.js deleted file mode 100644 index 025e0a4e8f2..00000000000 --- a/code/build/lib/task.js +++ /dev/null @@ -1,100 +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.series = series; -exports.parallel = parallel; -exports.define = define; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/lib/task.ts b/code/build/lib/task.ts index 6af23983178..085843a93b7 100644 --- a/code/build/lib/task.ts +++ b/code/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/code/build/lib/test/booleanPolicy.test.ts b/code/build/lib/test/booleanPolicy.test.ts new file mode 100644 index 00000000000..d64f9fff646 --- /dev/null +++ b/code/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/code/build/lib/test/fixtures/policies/darwin/com.visualstudio.code.oss.mobileconfig b/code/build/lib/test/fixtures/policies/darwin/com.visualstudio.code.oss.mobileconfig new file mode 100644 index 00000000000..0f0ae365d7c --- /dev/null +++ b/code/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/code/build/lib/test/fixtures/policies/darwin/en-us/com.visualstudio.code.oss.plist b/code/build/lib/test/fixtures/policies/darwin/en-us/com.visualstudio.code.oss.plist new file mode 100644 index 00000000000..f90533cfeaa --- /dev/null +++ b/code/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/code/build/lib/test/fixtures/policies/darwin/fr-fr/com.visualstudio.code.oss.plist b/code/build/lib/test/fixtures/policies/darwin/fr-fr/com.visualstudio.code.oss.plist new file mode 100644 index 00000000000..60f244be3b5 --- /dev/null +++ b/code/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/code/build/lib/test/fixtures/policies/linux/policy.json b/code/build/lib/test/fixtures/policies/linux/policy.json new file mode 100644 index 00000000000..3a941999da6 --- /dev/null +++ b/code/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/code/build/lib/test/fixtures/policies/win32/CodeOSS.admx b/code/build/lib/test/fixtures/policies/win32/CodeOSS.admx new file mode 100644 index 00000000000..c78eaebaf28 --- /dev/null +++ b/code/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/code/build/lib/test/fixtures/policies/win32/en-us/CodeOSS.adml b/code/build/lib/test/fixtures/policies/win32/en-us/CodeOSS.adml new file mode 100644 index 00000000000..39b05ddc72d --- /dev/null +++ b/code/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/code/build/lib/test/fixtures/policies/win32/fr-fr/CodeOSS.adml b/code/build/lib/test/fixtures/policies/win32/fr-fr/CodeOSS.adml new file mode 100644 index 00000000000..eab50282b2d --- /dev/null +++ b/code/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/code/build/lib/test/i18n.test.js b/code/build/lib/test/i18n.test.js deleted file mode 100644 index da7a426a103..00000000000 --- a/code/build/lib/test/i18n.test.js +++ /dev/null @@ -1,77 +0,0 @@ -"use strict"; -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 }); -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/lib/test/i18n.test.ts b/code/build/lib/test/i18n.test.ts index 4e4545548b8..7d5bb0433fe 100644 --- a/code/build/lib/test/i18n.test.ts +++ b/code/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/code/build/lib/test/numberPolicy.test.ts b/code/build/lib/test/numberPolicy.test.ts new file mode 100644 index 00000000000..503403ca5c0 --- /dev/null +++ b/code/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/code/build/lib/test/objectPolicy.test.ts b/code/build/lib/test/objectPolicy.test.ts new file mode 100644 index 00000000000..8e688d19b8f --- /dev/null +++ b/code/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/code/build/lib/test/policyConversion.test.ts b/code/build/lib/test/policyConversion.test.ts new file mode 100644 index 00000000000..bb4036a7ab9 --- /dev/null +++ b/code/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/code/build/lib/test/render.test.ts b/code/build/lib/test/render.test.ts new file mode 100644 index 00000000000..130bbc78132 --- /dev/null +++ b/code/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/code/build/lib/test/stringEnumPolicy.test.ts b/code/build/lib/test/stringEnumPolicy.test.ts new file mode 100644 index 00000000000..f27f9ec0a17 --- /dev/null +++ b/code/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/code/build/lib/test/stringPolicy.test.ts b/code/build/lib/test/stringPolicy.test.ts new file mode 100644 index 00000000000..7f69da33869 --- /dev/null +++ b/code/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/code/build/lib/treeshaking.js b/code/build/lib/treeshaking.js deleted file mode 100644 index a42fbf961ab..00000000000 --- a/code/build/lib/treeshaking.js +++ /dev/null @@ -1,910 +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.toStringShakeLevel = toStringShakeLevel; -exports.shake = shake; -/*--------------------------------------------------------------------------------------------- - * 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_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 || (ShakeLevel = {})); -function toStringShakeLevel(shakeLevel) { - switch (shakeLevel) { - case ShakeLevel.Files: - return 'Files (0)'; - case ShakeLevel.InnerFile: - return 'InnerFile (1)'; - case 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 === 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 === 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 === 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 === 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/code/build/lib/treeshaking.ts b/code/build/lib/treeshaking.ts index 2f55e3d42a9..463e701f73f 100644 --- a/code/build/lib/treeshaking.ts +++ b/code/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 +}); -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/code/build/lib/tsb/builder.js b/code/build/lib/tsb/builder.js deleted file mode 100644 index 6267db35f0b..00000000000 --- a/code/build/lib/tsb/builder.js +++ /dev/null @@ -1,662 +0,0 @@ -"use strict"; -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; -/*--------------------------------------------------------------------------------------------- - * 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 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/code/build/lib/tsb/builder.ts b/code/build/lib/tsb/builder.ts index 79ae06d73f7..628afc05427 100644 --- a/code/build/lib/tsb/builder.ts +++ b/code/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/code/build/lib/tsb/index.js b/code/build/lib/tsb/index.js deleted file mode 100644 index af10bf8ce19..00000000000 --- a/code/build/lib/tsb/index.js +++ /dev/null @@ -1,171 +0,0 @@ -"use strict"; -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; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/lib/tsb/index.ts b/code/build/lib/tsb/index.ts index 5399a2ead03..31c1c3f15f8 100644 --- a/code/build/lib/tsb/index.ts +++ b/code/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/code/build/lib/tsb/transpiler.js b/code/build/lib/tsb/transpiler.js deleted file mode 100644 index 4720ce43975..00000000000 --- a/code/build/lib/tsb/transpiler.js +++ /dev/null @@ -1,304 +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.ESBuildTranspiler = exports.TscTranspiler = void 0; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/lib/tsb/transpiler.ts b/code/build/lib/tsb/transpiler.ts index 16a3b347538..72883a2ab0c 100644 --- a/code/build/lib/tsb/transpiler.ts +++ b/code/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/code/build/lib/tsb/utils.js b/code/build/lib/tsb/utils.js deleted file mode 100644 index 72de33b5ac7..00000000000 --- a/code/build/lib/tsb/utils.js +++ /dev/null @@ -1,96 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.graph = exports.strings = void 0; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/lib/tsb/utils.ts b/code/build/lib/tsb/utils.ts index 16f93d6838f..4c5abb3e9c6 100644 --- a/code/build/lib/tsb/utils.ts +++ b/code/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/code/build/lib/tsconfigUtils.ts b/code/build/lib/tsconfigUtils.ts new file mode 100644 index 00000000000..0afb2f02ae7 --- /dev/null +++ b/code/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/code/build/lib/typeScriptLanguageServiceHost.ts b/code/build/lib/typeScriptLanguageServiceHost.ts new file mode 100644 index 00000000000..94c304fe094 --- /dev/null +++ b/code/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/code/build/lib/typings/@vscode/gulp-electron.d.ts b/code/build/lib/typings/@vscode/gulp-electron.d.ts new file mode 100644 index 00000000000..aaf1b861a87 --- /dev/null +++ b/code/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/code/build/lib/typings/asar.d.ts b/code/build/lib/typings/asar.d.ts new file mode 100644 index 00000000000..cdb5b6395c5 --- /dev/null +++ b/code/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/code/build/lib/typings/chromium-pickle-js.d.ts b/code/build/lib/typings/chromium-pickle-js.d.ts new file mode 100644 index 00000000000..e2fcd8dc096 --- /dev/null +++ b/code/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/code/build/lib/typings/event-stream.d.ts b/code/build/lib/typings/event-stream.d.ts index 2b021ef258e..2b9679bfc82 100644 --- a/code/build/lib/typings/event-stream.d.ts +++ b/code/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/code/build/lib/typings/gulp-azure-storage.d.ts b/code/build/lib/typings/gulp-azure-storage.d.ts new file mode 100644 index 00000000000..4e9f560c8f2 --- /dev/null +++ b/code/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/code/build/lib/typings/gulp-gunzip.d.ts b/code/build/lib/typings/gulp-gunzip.d.ts new file mode 100644 index 00000000000..a68a350d5e7 --- /dev/null +++ b/code/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/code/build/lib/typings/gulp-untar.d.ts b/code/build/lib/typings/gulp-untar.d.ts new file mode 100644 index 00000000000..b4007983cac --- /dev/null +++ b/code/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/code/build/lib/typings/gulp-vinyl-zip.d.ts b/code/build/lib/typings/gulp-vinyl-zip.d.ts new file mode 100644 index 00000000000..d28166ffa77 --- /dev/null +++ b/code/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/code/build/lib/typings/rcedit.d.ts b/code/build/lib/typings/rcedit.d.ts new file mode 100644 index 00000000000..e18d3f93584 --- /dev/null +++ b/code/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/code/build/lib/typings/vscode-gulp-watch.d.ts b/code/build/lib/typings/vscode-gulp-watch.d.ts new file mode 100644 index 00000000000..24316c07f16 --- /dev/null +++ b/code/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/code/build/lib/util.js b/code/build/lib/util.js deleted file mode 100644 index 389b9e0cd4f..00000000000 --- a/code/build/lib/util.js +++ /dev/null @@ -1,314 +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.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; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/lib/util.ts b/code/build/lib/util.ts index 49313429da9..f1354b858c9 100644 --- a/code/build/lib/util.ts +++ b/code/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,7 +129,8 @@ 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; return f; @@ -202,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; @@ -243,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; })); @@ -282,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; @@ -349,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; @@ -378,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/code/build/lib/watch/index.js b/code/build/lib/watch/index.js deleted file mode 100644 index 21dc978dbfc..00000000000 --- a/code/build/lib/watch/index.js +++ /dev/null @@ -1,11 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/lib/watch/index.ts b/code/build/lib/watch/index.ts index ce4bdfd75ed..763cacc6d89 100644 --- a/code/build/lib/watch/index.ts +++ b/code/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/code/build/lib/watch/watch-win32.js b/code/build/lib/watch/watch-win32.js deleted file mode 100644 index 4113d93526e..00000000000 --- a/code/build/lib/watch/watch-win32.js +++ /dev/null @@ -1,104 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * 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 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/code/build/lib/watch/watch-win32.ts b/code/build/lib/watch/watch-win32.ts index bbfde6afba9..12b8ffc0ac3 100644 --- a/code/build/lib/watch/watch-win32.ts +++ b/code/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/code/build/linux/debian/calculate-deps.js b/code/build/linux/debian/calculate-deps.js deleted file mode 100644 index c9c96967f47..00000000000 --- a/code/build/linux/debian/calculate-deps.js +++ /dev/null @@ -1,89 +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.generatePackageDeps = generatePackageDeps; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/linux/debian/calculate-deps.ts b/code/build/linux/debian/calculate-deps.ts index addc38696a8..98a96302e19 100644 --- a/code/build/linux/debian/calculate-deps.ts +++ b/code/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/code/build/linux/debian/dep-lists.js b/code/build/linux/debian/dep-lists.js deleted file mode 100644 index f58d0f8e866..00000000000 --- a/code/build/linux/debian/dep-lists.js +++ /dev/null @@ -1,143 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.referenceGeneratedDepsByArch = exports.recommendedDeps = exports.additionalDeps = void 0; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -// 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/code/build/linux/debian/dep-lists.ts b/code/build/linux/debian/dep-lists.ts index 5b7ccd51e09..d00eb59e3a2 100644 --- a/code/build/linux/debian/dep-lists.ts +++ b/code/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/code/build/linux/debian/install-sysroot.js b/code/build/linux/debian/install-sysroot.js deleted file mode 100644 index d16e13bd63e..00000000000 --- a/code/build/linux/debian/install-sysroot.js +++ /dev/null @@ -1,227 +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.getVSCodeSysroot = getVSCodeSysroot; -exports.getChromiumSysroot = getChromiumSysroot; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/linux/debian/install-sysroot.ts b/code/build/linux/debian/install-sysroot.ts index 670fb68adcf..2cab657c1b7 100644 --- a/code/build/linux/debian/install-sysroot.ts +++ b/code/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/code/build/linux/dependencies-generator.ts b/code/build/linux/dependencies-generator.ts index 00d7bc6ae25..d80346365f8 100644 --- a/code/build/linux/dependencies-generator.ts +++ b/code/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.235: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/code/build/linux/libcxx-fetcher.js b/code/build/linux/libcxx-fetcher.js deleted file mode 100644 index 710a8be7434..00000000000 --- a/code/build/linux/libcxx-fetcher.js +++ /dev/null @@ -1,73 +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.downloadLibcxxHeaders = downloadLibcxxHeaders; -exports.downloadLibcxxObjects = downloadLibcxxObjects; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -// 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/code/build/linux/libcxx-fetcher.ts b/code/build/linux/libcxx-fetcher.ts index 6bdbd8a4f30..981fbd3392e 100644 --- a/code/build/linux/libcxx-fetcher.ts +++ b/code/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/code/build/linux/rpm/calculate-deps.js b/code/build/linux/rpm/calculate-deps.js deleted file mode 100644 index b31a5aa9d5c..00000000000 --- a/code/build/linux/rpm/calculate-deps.js +++ /dev/null @@ -1,35 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.generatePackageDeps = generatePackageDeps; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -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/code/build/linux/rpm/calculate-deps.ts b/code/build/linux/rpm/calculate-deps.ts index 4be2200c018..0a1f0107594 100644 --- a/code/build/linux/rpm/calculate-deps.ts +++ b/code/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/code/build/linux/rpm/dep-lists.js b/code/build/linux/rpm/dep-lists.js deleted file mode 100644 index 74156ebe47a..00000000000 --- a/code/build/linux/rpm/dep-lists.js +++ /dev/null @@ -1,321 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.referenceGeneratedDepsByArch = exports.additionalDeps = void 0; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -// 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/code/build/linux/rpm/dep-lists.ts b/code/build/linux/rpm/dep-lists.ts index 90b97bed301..783923f34d9 100644 --- a/code/build/linux/rpm/dep-lists.ts +++ b/code/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/code/build/linux/rpm/types.js b/code/build/linux/rpm/types.js deleted file mode 100644 index 39b1334cc7b..00000000000 --- a/code/build/linux/rpm/types.js +++ /dev/null @@ -1,7 +0,0 @@ -"use strict"; -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/code/build/monaco/monaco.d.ts.recipe b/code/build/monaco/monaco.d.ts.recipe index 7bf0ba227d0..b1f676796af 100644 --- a/code/build/monaco/monaco.d.ts.recipe +++ b/code/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,27 +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): -#include(vs/editor/common/model/mirrorTextModel): IModelContentChange -#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; @@ -130,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/code/build/monaco/monaco.usage.recipe b/code/build/monaco/monaco.usage.recipe index 9e96a68568a..b0526a4506e 100644 --- a/code/build/monaco/monaco.usage.recipe +++ b/code/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/code/build/npm/dirs.js b/code/build/npm/dirs.js deleted file mode 100644 index 6a7343daff7..00000000000 --- a/code/build/npm/dirs.js +++ /dev/null @@ -1,74 +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/che-activity-tracker', - 'extensions/che-api', - 'extensions/che-commands', - 'extensions/che-port', - 'extensions/che-remote', - 'extensions/che-resource-monitor', - 'extensions/che-terminal', - 'extensions/che-github-authentication', - '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', - 'test/mcp', - '.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/code/build/npm/dirs.ts b/code/build/npm/dirs.ts new file mode 100644 index 00000000000..745663cdaea --- /dev/null +++ b/code/build/npm/dirs.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 { existsSync } from 'fs'; + +/** + * Complete list of directories where npm should be executed to install node modules + */ +export const dirs = [ + '', + 'build', + 'build/vite', + 'extensions', + 'extensions/che-activity-tracker', + 'extensions/che-api', + 'extensions/che-commands', + 'extensions/che-port', + 'extensions/che-remote', + 'extensions/che-resource-monitor', + 'extensions/che-terminal', + 'extensions/che-github-authentication', + '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/code/build/npm/gyp/custom-headers/v8-source-location.patch b/code/build/npm/gyp/custom-headers/v8-source-location.patch new file mode 100644 index 00000000000..545eb9a118b --- /dev/null +++ b/code/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/code/build/npm/gyp/package-lock.json b/code/build/npm/gyp/package-lock.json index e01a62304ed..130eb4f42b0 100644 --- a/code/build/npm/gyp/package-lock.json +++ b/code/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": { @@ -351,9 +351,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/code/build/npm/jsconfig.json b/code/build/npm/jsconfig.json deleted file mode 100644 index fa767b17d0f..00000000000 --- a/code/build/npm/jsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "target": "es2024", - "lib": [ - "ES2024" - ], - "module": "node16", - "checkJs": true, - "noEmit": true - } -} diff --git a/code/build/npm/mixin-telemetry-docs.ts b/code/build/npm/mixin-telemetry-docs.ts new file mode 100644 index 00000000000..be33793431a --- /dev/null +++ b/code/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/code/build/npm/postinstall.js b/code/build/npm/postinstall.js deleted file mode 100644 index 1033e4ecf68..00000000000 --- a/code/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/code/build/npm/postinstall.ts b/code/build/npm/postinstall.ts new file mode 100644 index 00000000000..3e260853a53 --- /dev/null +++ b/code/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/code/build/npm/preinstall.js b/code/build/npm/preinstall.js deleted file mode 100644 index e4b47859576..00000000000 --- a/code/build/npm/preinstall.js +++ /dev/null @@ -1,132 +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'); - 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() { - 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']; - - 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() { - 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/code/build/npm/preinstall.ts b/code/build/npm/preinstall.ts new file mode 100644 index 00000000000..3476fcabb50 --- /dev/null +++ b/code/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/code/build/npm/update-all-grammars.mjs b/code/build/npm/update-all-grammars.mjs deleted file mode 100644 index 7e303a655f7..00000000000 --- a/code/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/code/build/npm/update-all-grammars.ts b/code/build/npm/update-all-grammars.ts new file mode 100644 index 00000000000..aae11ae1326 --- /dev/null +++ b/code/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/code/build/npm/update-distro.mjs b/code/build/npm/update-distro.mjs deleted file mode 100644 index 655d9f2c243..00000000000 --- a/code/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/code/build/npm/update-distro.ts b/code/build/npm/update-distro.ts new file mode 100644 index 00000000000..3c58af6197e --- /dev/null +++ b/code/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/code/build/npm/update-localization-extension.js b/code/build/npm/update-localization-extension.js deleted file mode 100644 index 6274323f747..00000000000 --- a/code/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/code/build/npm/update-localization-extension.ts b/code/build/npm/update-localization-extension.ts new file mode 100644 index 00000000000..cb7981b9388 --- /dev/null +++ b/code/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/code/build/package-lock.json b/code/build/package-lock.json index 8f7fc557242..93fa45fd0fe 100644 --- a/code/build/package-lock.json +++ b/code/build/package-lock.json @@ -38,7 +38,7 @@ "@types/mime": "0.0.29", "@types/minimatch": "^3.0.5", "@types/minimist": "^1.2.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", @@ -47,9 +47,9 @@ "@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", @@ -58,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", @@ -73,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", @@ -446,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", @@ -966,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", @@ -988,216 +1096,601 @@ "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-flatmap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/gulp-flatmap/-/gulp-flatmap-1.0.0.tgz", - "integrity": "sha512-GTv0a9BxhbWYkxaPDCqnZFI13pXUUpJ90hBWkhGOQQ76qDDtHWugr0+IEiTEc0KYS0bOs80YszZE7WFNA5ndfg==", + "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": "*", - "@types/vinyl": "*" + "node-sarif-builder": "^3.2.0" } }, - "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-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/node": "*" + "@secretlint/types": "^10.2.2" + }, + "engines": { + "node": ">=20.0.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-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/js-beautify": "*", - "@types/node": "*" + "license": "MIT", + "engines": { + "node": ">=20.0.0" } }, - "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==", + "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/node": "*" + "@secretlint/types": "^10.2.2", + "istextorbinary": "^9.5.0" + }, + "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/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-replace": { - "version": "0.0.31", - "resolved": "https://registry.npmjs.org/@types/gulp-replace/-/gulp-replace-0.0.31.tgz", - "integrity": "sha512-dbgQ1u0N9ShXrzahBgQfMSu6qUh8nlTLt7whhQ0S0sEUHhV3scysppJ1UX0fl53PJENgAL99ueykddyrCaDt7g==", + "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, - "license": "MIT", - "dependencies": { - "@types/node": "*" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" } }, - "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/@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": "*", @@ -1289,15 +1782,22 @@ "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", @@ -1333,6 +1833,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", @@ -1417,10 +1924,11 @@ } }, "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 + "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", @@ -1436,26 +1944,36 @@ } }, "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==", + "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-devops-node-api": "^11.0.1", - "chalk": "^2.4.2", + "@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", - "commander": "^6.1.0", - "glob": "^7.0.6", + "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": "^12.3.2", + "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.1", + "tmp": "^0.2.3", "typed-rest-client": "^1.8.4", "url-join": "^4.0.1", "xml2js": "^0.5.0", @@ -1466,19 +1984,258 @@ "vsce": "vsce" }, "engines": { - "node": ">= 14" + "node": ">= 20" }, "optionalDependencies": { "keytar": "^7.7.0" } }, - "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-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/@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/@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, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "win32" + ] + }, + "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": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": ">= 6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "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, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "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/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, + "license": "BlueOak-1.0.0", + "dependencies": { + "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/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": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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": ">=8" } }, "node_modules/@vscode/vsce/node_modules/jsonc-parser": { @@ -1499,6 +2256,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", @@ -1531,6 +2301,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", @@ -1541,6 +2328,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", @@ -1553,6 +2356,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", @@ -1588,10 +2404,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", @@ -1620,6 +2447,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", @@ -1635,11 +2472,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" @@ -1680,6 +2525,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", @@ -1705,6 +2566,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", @@ -1804,17 +2672,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" @@ -1962,6 +2842,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", @@ -1986,6 +2876,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", @@ -2042,12 +2955,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" @@ -2104,23 +3018,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", @@ -2143,6 +3040,16 @@ "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": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", @@ -2225,6 +3132,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", @@ -2237,6 +3159,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", @@ -2246,6 +3175,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", @@ -2276,14 +3229,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" } @@ -2293,6 +3257,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" } @@ -2354,6 +3348,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", @@ -2421,12 +3429,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", @@ -2450,6 +3499,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", @@ -2498,12 +3557,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", @@ -2549,21 +3642,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" @@ -2572,6 +3672,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", @@ -2677,13 +3791,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" @@ -2777,23 +3913,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" }, @@ -2801,11 +3926,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" }, @@ -2818,6 +3947,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" }, @@ -2924,6 +4054,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", @@ -2996,6 +4149,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", @@ -3068,6 +4231,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", @@ -3080,6 +4298,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", @@ -3131,23 +4356,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" } }, @@ -3167,24 +4394,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" } }, @@ -3219,12 +4447,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": { @@ -3239,6 +4468,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", @@ -3261,29 +4497,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", @@ -3311,11 +4547,22 @@ "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", @@ -3323,6 +4570,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", @@ -3335,6 +4606,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", @@ -3363,6 +4657,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", @@ -3371,10 +4675,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", @@ -3443,6 +4748,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", @@ -3486,10 +4891,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" }, @@ -3557,6 +4963,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", @@ -3627,17 +5084,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" }, @@ -3696,6 +5201,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", @@ -3761,13 +5276,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.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -3776,6 +5302,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", @@ -3804,16 +5351,95 @@ "rc": "cli.js" } }, - "node_modules/read": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", - "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", + "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, - "dependencies": { - "mute-stream": "~0.0.4" + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "license": "MIT", "engines": { - "node": ">=0.8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/readable-stream": { @@ -3857,6 +5483,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", @@ -3875,6 +5511,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", @@ -3893,6 +5540,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", @@ -3905,6 +5576,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", @@ -3946,23 +5639,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", @@ -3985,15 +5661,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" @@ -4002,6 +5736,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", @@ -4049,51 +5796,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": { @@ -4150,6 +6117,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", @@ -4174,6 +6151,86 @@ "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.4", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", @@ -4205,6 +6262,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", @@ -4227,6 +6301,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", @@ -4367,6 +6464,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" } @@ -4402,6 +6500,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", @@ -4409,24 +6508,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", @@ -4463,6 +6577,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", @@ -4623,6 +6761,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/code/build/package.json b/code/build/package.json index bccdedd5501..50c4f26d15b 100644 --- a/code/build/package.json +++ b/code/build/package.json @@ -32,7 +32,7 @@ "@types/mime": "0.0.29", "@types/minimatch": "^3.0.5", "@types/minimist": "^1.2.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", @@ -41,9 +41,9 @@ "@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", @@ -52,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", @@ -62,11 +62,13 @@ "workerpool": "^6.4.0", "yauzl": "^2.10.0" }, - "type": "commonjs", + "type": "module", "scripts": { - "compile": "cd .. && npx tsgo --project build/tsconfig.build.json", - "watch": "cd .. && npx tsgo --project build/tsconfig.build.json --watch", - "npmCheckJs": "cd .. && npx tsgo --project build/tsconfig.build.json --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", @@ -75,6 +77,9 @@ "overrides": { "prebuild-install": { "tar-fs": "2.1.4" + }, + "path-scurry": { + "lru-cache": "11.2.1" } } } diff --git a/code/build/setup-npm-registry.js b/code/build/setup-npm-registry.js deleted file mode 100644 index 5d637f97632..00000000000 --- a/code/build/setup-npm-registry.js +++ /dev/null @@ -1,54 +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 fs = require('fs').promises; -const path = require('path'); - -/** - * @param {string} dir - * - * @returns {AsyncGenerator} - */ -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; - } - } -} - -/** - * @param {string} url - * @param {string} file - */ -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); -} - -/** - * @param {string} url - * @param {string} dir - */ -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/code/build/setup-npm-registry.ts b/code/build/setup-npm-registry.ts new file mode 100644 index 00000000000..670c3e339db --- /dev/null +++ b/code/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/code/build/stylelint.js b/code/build/stylelint.js deleted file mode 100644 index 03c1611c4e0..00000000000 --- a/code/build/stylelint.js +++ /dev/null @@ -1,63 +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 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 - * - * @param {function(string, boolean):void} reporter - */ -function gulpstylelint(reporter) { - const variableValidator = getVariableNameValidator(); - let errorCount = 0; - return es.through(function (file) { - /** @type {string[]} */ - 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/code/build/stylelint.ts b/code/build/stylelint.ts new file mode 100644 index 00000000000..037fe110615 --- /dev/null +++ b/code/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/code/build/tsconfig.build.json b/code/build/tsconfig.build.json deleted file mode 100644 index dc3305690bc..00000000000 --- a/code/build/tsconfig.build.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "allowJs": false, - "checkJs": false, - "noEmit": false, - "skipLibCheck": true - }, - "include": [ - "**/*.ts" - ] -} diff --git a/code/build/tsconfig.json b/code/build/tsconfig.json index ab72dda392a..383d5342c04 100644 --- a/code/build/tsconfig.json +++ b/code/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/code/build/vite/index-workbench.ts b/code/build/vite/index-workbench.ts new file mode 100644 index 00000000000..e237f661f5d --- /dev/null +++ b/code/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/code/build/vite/index.html b/code/build/vite/index.html new file mode 100644 index 00000000000..c3a0e36b8ef --- /dev/null +++ b/code/build/vite/index.html @@ -0,0 +1,9 @@ + + + + +
+

Use the Playground Launch Config for a better dev experience

+ + + diff --git a/code/build/vite/index.ts b/code/build/vite/index.ts new file mode 100644 index 00000000000..b852612bc66 --- /dev/null +++ b/code/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/code/build/vite/package-lock.json b/code/build/vite/package-lock.json new file mode 100644 index 00000000000..4fd63116305 --- /dev/null +++ b/code/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/code/build/vite/package.json b/code/build/vite/package.json new file mode 100644 index 00000000000..ea7b609e280 --- /dev/null +++ b/code/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/code/build/vite/rollup-url-to-module-plugin/index.mjs b/code/build/vite/rollup-url-to-module-plugin/index.mjs new file mode 100644 index 00000000000..8a0168bd4ac --- /dev/null +++ b/code/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/code/build/vite/setup-dev.ts b/code/build/vite/setup-dev.ts new file mode 100644 index 00000000000..c1df4861082 --- /dev/null +++ b/code/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/code/build/vite/style.css b/code/build/vite/style.css new file mode 100644 index 00000000000..b9573061e51 --- /dev/null +++ b/code/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/code/build/vite/tsconfig.json b/code/build/vite/tsconfig.json new file mode 100644 index 00000000000..454dc14491f --- /dev/null +++ b/code/build/vite/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "allowImportingTsExtensions": true, + "noEmit": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "experimentalDecorators": true, + }, + "include": ["**/*.ts"] +} diff --git a/code/build/vite/vite.config.ts b/code/build/vite/vite.config.ts new file mode 100644 index 00000000000..5736a474d6b --- /dev/null +++ b/code/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/code/build/vite/workbench-electron.ts b/code/build/vite/workbench-electron.ts new file mode 100644 index 00000000000..49578ca4948 --- /dev/null +++ b/code/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/code/build/vite/workbench-vite-electron.html b/code/build/vite/workbench-vite-electron.html new file mode 100644 index 00000000000..87019c6c01a --- /dev/null +++ b/code/build/vite/workbench-vite-electron.html @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/code/build/vite/workbench-vite.html b/code/build/vite/workbench-vite.html new file mode 100644 index 00000000000..99ed4e75415 --- /dev/null +++ b/code/build/vite/workbench-vite.html @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + diff --git a/code/build/win32/Cargo.lock b/code/build/win32/Cargo.lock index e91718ee79a..d35c41e4098 100644 --- a/code/build/win32/Cargo.lock +++ b/code/build/win32/Cargo.lock @@ -129,7 +129,7 @@ checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "inno_updater" -version = "0.16.0" +version = "0.18.2" dependencies = [ "byteorder", "crc", @@ -546,4 +546,4 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags 2.9.1", -] \ No newline at end of file +] diff --git a/code/build/win32/Cargo.toml b/code/build/win32/Cargo.toml index 37d78fc177c..40e1a7a60fd 100644 --- a/code/build/win32/Cargo.toml +++ b/code/build/win32/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "inno_updater" -version = "0.16.0" +version = "0.18.2" authors = ["Microsoft "] build = "build.rs" diff --git a/code/build/win32/code-insider.iss b/code/build/win32/code-insider.iss new file mode 100644 index 00000000000..2cbf252779b --- /dev/null +++ b/code/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/code/build/win32/code.iss b/code/build/win32/code.iss index a1847c512ec..cc11cbe80c1 100644 --- a/code/build/win32/code.iss +++ b/code/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 diff --git a/code/build/win32/explorer-dll-fetcher.js b/code/build/win32/explorer-dll-fetcher.js deleted file mode 100644 index dfb9ce97ff4..00000000000 --- a/code/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/code/build/win32/explorer-dll-fetcher.ts b/code/build/win32/explorer-dll-fetcher.ts index 724a35bc568..09bd2691843 100644 --- a/code/build/win32/explorer-dll-fetcher.ts +++ b/code/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/code/build/win32/inno_updater.exe b/code/build/win32/inno_updater.exe index 14ae7b2dd63..c3c4a0cd2bc 100644 Binary files a/code/build/win32/inno_updater.exe and b/code/build/win32/inno_updater.exe differ diff --git a/code/cglicenses.json b/code/cglicenses.json index 9651f2e2c72..8ee75c0fb34 100644 --- a/code/cglicenses.json +++ b/code/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/code/cgmanifest.json b/code/cgmanifest.json index b0dbfd23271..cab19515d67 100644 --- a/code/cgmanifest.json +++ b/code/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "chromium", "repositoryUrl": "https://chromium.googlesource.com/chromium/src", - "commitHash": "f15f401fb04924bb6505c6146a0dc3deb154b505" + "commitHash": "4d74005947d2522c31942de3d609355124455643" } }, "licenseDetail": [ @@ -40,7 +40,7 @@ "SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ], "isOnlyProductionDependency": true, - "version": "138.0.7204.235" + "version": "142.0.7444.235" }, { "component": { @@ -516,11 +516,12 @@ "git": { "name": "nodejs", "repositoryUrl": "https://github.com/nodejs/node", - "commitHash": "c9ff1aecf268802fc29272c93aaa4b0691c7c6d8" + "commitHash": "6ac4ab19ad02803f03b54501193397563e99988e", + "tag": "22.21.1" } }, "isOnlyProductionDependency": true, - "version": "22.18.0" + "version": "22.21.1" }, { "component": { @@ -528,12 +529,13 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "23a02934510fcf951428e14573d9b2d2a3c4f28b" + "commitHash": "4d18062d0f0ca34c455bc7ec032dd7959a0365b6", + "tag": "39.2.7" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "37.3.1" + "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/code/cli/ThirdPartyNotices.txt b/code/cli/ThirdPartyNotices.txt index 00fd53fd890..c2f4df84706 100644 --- a/code/cli/ThirdPartyNotices.txt +++ b/code/cli/ThirdPartyNotices.txt @@ -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) @@ -7227,10 +7227,9 @@ DEALINGS IN THE SOFTWARE. rand_core 0.5.1 - MIT OR Apache-2.0 rand_core 0.6.4 - MIT OR Apache-2.0 -https://github.com/rust-random/rand +https://github.com/rust-random/rand_core -Copyright 2018 Developers of the Rand project -Copyright (c) 2014 The Rust Project Developers +Copyright (c) 2018-2025 The Rand Project Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated @@ -8591,7 +8590,8 @@ Unless you explicitly state otherwise, any contribution intentionally submitted [//]: # (crates) -[`ascon‑hash`]: ./ascon-hash +[`ascon‑hash`]: ./ascon-hash256 +[`bash‑hash`]: ./bash-hash [`belt‑hash`]: ./belt-hash [`blake2`]: ./blake2 [`fsb`]: ./fsb @@ -8635,6 +8635,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 @@ -8685,7 +8686,8 @@ Unless you explicitly state otherwise, any contribution intentionally submitted [//]: # (crates) -[`ascon‑hash`]: ./ascon-hash +[`ascon‑hash`]: ./ascon-hash256 +[`bash‑hash`]: ./bash-hash [`belt‑hash`]: ./belt-hash [`blake2`]: ./blake2 [`fsb`]: ./fsb @@ -8729,6 +8731,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 @@ -11510,69 +11513,17 @@ 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/ - -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. +https://github.com/z-galaxy/zbus/ -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 --------------------------------------------------------- --------------------------------------------------------- zbus_macros 3.15.2 - MIT -https://github.com/dbus2/zbus/ +https://github.com/z-galaxy/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 --------------------------------------------------------- --------------------------------------------------------- @@ -11580,33 +11531,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 +11643,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 +11827,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 +11835,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 +11843,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/code/cli/src/commands/args.rs b/code/cli/src/commands/args.rs index 52c5af6d7d4..6301bdd3104 100644 --- a/code/cli/src/commands/args.rs +++ b/code/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/code/cli/src/tunnels/code_server.rs b/code/cli/src/tunnels/code_server.rs index cf00bc42835..bbabadcf90a 100644 --- a/code/cli/src/tunnels/code_server.rs +++ b/code/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/code/cli/src/update_service.rs b/code/cli/src/update_service.rs index 90339148188..55f1dadccdf 100644 --- a/code/cli/src/update_service.rs +++ b/code/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/code/eslint.config.js b/code/eslint.config.js index d7b29f29cc0..e9809e60dc0 100644 --- a/code/eslint.config.js +++ b/code/eslint.config.js @@ -8,7 +8,7 @@ import path from 'path'; import tseslint from 'typescript-eslint'; 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'; @@ -75,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', @@ -127,7 +133,7 @@ export default tseslint.config( // TS { files: [ - '**/*.ts', + '**/*.{ts,tsx,mts,cts}', ], languageOptions: { parser: tseslint.parser, @@ -139,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': [ @@ -174,6 +182,620 @@ 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/attachments/chatAttachmentResolveService.ts', + 'src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatAttachmentsContentPart.ts', + 'src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatConfirmationWidget.ts', + 'src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatElicitationContentPart.ts', + 'src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatReferencesContentPart.ts', + 'src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatTreeContentPart.ts', + 'src/vs/workbench/contrib/chat/browser/widget/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/widget/chatContentParts/chatInlineAnchorWidget.ts', + 'src/vs/workbench/contrib/chat/browser/accessibility/chatResponseAccessibleView.ts', + 'src/vs/workbench/contrib/chat/browser/widget/input/editor/chatInputCompletions.ts', + 'src/vs/workbench/contrib/chat/common/model/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/builtinTools/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', + 'src/vs/workbench/workbench.web.main.internal.ts', + 'src/vs/workbench/workbench.web.main.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: [ @@ -186,10 +808,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', [ @@ -307,7 +929,8 @@ export default tseslint.config( 'terminate', 'trigger', 'unregister', - 'write' + 'write', + 'commit' ] } ] @@ -562,6 +1185,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': [ @@ -763,6 +1414,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', { @@ -779,7 +1441,7 @@ export default tseslint.config( // - electron-main 'when': 'hasNode', 'allow': [ - '@parcel/watcher', + '@vscode/watcher', '@vscode/sqlite3', '@vscode/vscode-languagedetection', '@vscode/ripgrep', @@ -811,8 +1473,9 @@ export default tseslint.config( 'readline', 'stream', 'string_decoder', - 'tas-client-umd', + 'tas-client', 'tls', + 'undici', 'undici-types', 'url', 'util', @@ -899,7 +1562,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/ @@ -1017,7 +1680,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 @@ -1071,6 +1734,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' @@ -1268,7 +1932,6 @@ export default tseslint.config( 'test/automation', 'test/smoke/**', '@vscode/*', - '@parcel/*', '@playwright/*', '*' // node modules ] @@ -1278,7 +1941,6 @@ export default tseslint.config( 'restrictions': [ 'test/automation/**', '@vscode/*', - '@parcel/*', 'playwright-core/**', '@playwright/*', '*' // node modules @@ -1289,7 +1951,6 @@ export default tseslint.config( 'restrictions': [ 'test/integration/**', '@vscode/*', - '@parcel/*', '@playwright/*', '*' // node modules ] @@ -1299,7 +1960,6 @@ export default tseslint.config( 'restrictions': [ 'test/monaco/**', '@vscode/*', - '@parcel/*', '@playwright/*', '*' // node modules ] @@ -1310,7 +1970,6 @@ export default tseslint.config( 'test/automation', 'test/mcp/**', '@vscode/*', - '@parcel/*', '@playwright/*', '@modelcontextprotocol/sdk/**/*', '*' // node modules @@ -1410,6 +2069,7 @@ export default tseslint.config( { files: [ 'extensions/markdown-language-features/**/*.ts', + 'extensions/mermaid-chat-features/**/*.ts', 'extensions/media-preview/**/*.ts', 'extensions/simple-browser/**/*.ts', 'extensions/typescript-language-features/**/*.ts', @@ -1430,6 +2090,10 @@ export default tseslint.config( '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', diff --git a/code/extensions/configuration-editing/tsconfig.json b/code/extensions/configuration-editing/tsconfig.json index 3013ee54227..7106538eb99 100644 --- a/code/extensions/configuration-editing/tsconfig.json +++ b/code/extensions/configuration-editing/tsconfig.json @@ -4,6 +4,9 @@ "outDir": "./out", "types": [ "node" + ], + "typeRoots": [ + "./node_modules/@types" ] }, "include": [ diff --git a/code/extensions/cpp/language-configuration.json b/code/extensions/cpp/language-configuration.json index cb1fb733b99..a4468a758f9 100644 --- a/code/extensions/cpp/language-configuration.json +++ b/code/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/code/extensions/csharp/cgmanifest.json b/code/extensions/csharp/cgmanifest.json index 58ae5ece50a..61e941c3488 100644 --- a/code/extensions/csharp/cgmanifest.json +++ b/code/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/code/extensions/csharp/syntaxes/csharp.tmLanguage.json b/code/extensions/csharp/syntaxes/csharp.tmLanguage.json index 1afcc3053b6..b360a96cb65 100644 --- a/code/extensions/csharp/syntaxes/csharp.tmLanguage.json +++ b/code/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/code/extensions/css-language-features/client/src/cssClient.ts b/code/extensions/css-language-features/client/src/cssClient.ts index 4e90b3482e4..49bacd90a5c 100644 --- a/code/extensions/css-language-features/client/src/cssClient.ts +++ b/code/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/code/extensions/css-language-features/client/tsconfig.json b/code/extensions/css-language-features/client/tsconfig.json index 5284e093858..51303a368a2 100644 --- a/code/extensions/css-language-features/client/tsconfig.json +++ b/code/extensions/css-language-features/client/tsconfig.json @@ -6,6 +6,9 @@ "webworker" ], "module": "Node16", + "typeRoots": [ + "../node_modules/@types" + ] }, "include": [ "src/**/*", diff --git a/code/extensions/css-language-features/package-lock.json b/code/extensions/css-language-features/package-lock.json index dcd8acd1aab..42656ea4bae 100644 --- a/code/extensions/css-language-features/package-lock.json +++ b/code/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/code/extensions/css-language-features/package.json b/code/extensions/css-language-features/package.json index cd46d998e65..ab57bc0c9b2 100644 --- a/code/extensions/css-language-features/package.json +++ b/code/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/code/extensions/css-language-features/server/package-lock.json b/code/extensions/css-language-features/server/package-lock.json index 95454ebf76c..60165842552 100644 --- a/code/extensions/css-language-features/server/package-lock.json +++ b/code/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/code/extensions/css-language-features/server/package.json b/code/extensions/css-language-features/server/package.json index 093366e0389..1d6d0cd2cbc 100644 --- a/code/extensions/css-language-features/server/package.json +++ b/code/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/code/extensions/css-language-features/server/src/cssServer.ts b/code/extensions/css-language-features/server/src/cssServer.ts index 8b365f41b6b..b46e20bb7c1 100644 --- a/code/extensions/css-language-features/server/src/cssServer.ts +++ b/code/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/code/extensions/css-language-features/server/tsconfig.json b/code/extensions/css-language-features/server/tsconfig.json index 4f24a50855c..0b49ec72b8f 100644 --- a/code/extensions/css-language-features/server/tsconfig.json +++ b/code/extensions/css-language-features/server/tsconfig.json @@ -7,6 +7,9 @@ "WebWorker" ], "module": "Node16", + "typeRoots": [ + "../node_modules/@types" + ] }, "include": [ "src/**/*" diff --git a/code/extensions/debug-auto-launch/src/extension.ts b/code/extensions/debug-auto-launch/src/extension.ts index 7d06c56d47f..a17b47ecf7d 100644 --- a/code/extensions/debug-auto-launch/src/extension.ts +++ b/code/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/code/extensions/debug-auto-launch/tsconfig.json b/code/extensions/debug-auto-launch/tsconfig.json index bfcf873c749..22c47de77db 100644 --- a/code/extensions/debug-auto-launch/tsconfig.json +++ b/code/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/code/extensions/debug-server-ready/tsconfig.json b/code/extensions/debug-server-ready/tsconfig.json index 9bf747283ca..21e3648ffed 100644 --- a/code/extensions/debug-server-ready/tsconfig.json +++ b/code/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/code/extensions/docker/cgmanifest.json b/code/extensions/docker/cgmanifest.json index 8462de7dd72..942ba14ebd8 100644 --- a/code/extensions/docker/cgmanifest.json +++ b/code/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/code/extensions/dotenv/.vscodeignore b/code/extensions/dotenv/.vscodeignore new file mode 100644 index 00000000000..0a622e7e300 --- /dev/null +++ b/code/extensions/dotenv/.vscodeignore @@ -0,0 +1,2 @@ +test/** +cgmanifest.json diff --git a/code/extensions/dotenv/cgmanifest.json b/code/extensions/dotenv/cgmanifest.json new file mode 100644 index 00000000000..637e505549b --- /dev/null +++ b/code/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/code/extensions/dotenv/language-configuration.json b/code/extensions/dotenv/language-configuration.json new file mode 100644 index 00000000000..77e01182ddd --- /dev/null +++ b/code/extensions/dotenv/language-configuration.json @@ -0,0 +1,24 @@ +{ + "comments": { + "lineComment": "#" + }, + "brackets": [ + ["{", "}"], + ["[", "]"], + ["(", ")"] + ], + "autoClosingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["\"", "\""], + ["'", "'"] + ], + "surroundingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["\"", "\""], + ["'", "'"] + ] +} diff --git a/code/extensions/dotenv/package.json b/code/extensions/dotenv/package.json new file mode 100644 index 00000000000..2adbc86ff36 --- /dev/null +++ b/code/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/code/extensions/dotenv/package.nls.json b/code/extensions/dotenv/package.nls.json new file mode 100644 index 00000000000..acebc955157 --- /dev/null +++ b/code/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/code/extensions/dotenv/syntaxes/dotenv.tmLanguage.json b/code/extensions/dotenv/syntaxes/dotenv.tmLanguage.json new file mode 100644 index 00000000000..1cf105b0c18 --- /dev/null +++ b/code/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/code/extensions/emmet/src/emmetCommon.ts b/code/extensions/emmet/src/emmetCommon.ts index daed62d7c65..da364e19fa4 100644 --- a/code/extensions/emmet/src/emmetCommon.ts +++ b/code/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/code/extensions/emmet/src/test/completion.test.ts b/code/extensions/emmet/src/test/completion.test.ts index 4f74ba92e25..bf61f338f21 100644 --- a/code/extensions/emmet/src/test/completion.test.ts +++ b/code/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/code/extensions/emmet/src/updateImageSize.ts b/code/extensions/emmet/src/updateImageSize.ts index 23838576c50..204388f0055 100644 --- a/code/extensions/emmet/src/updateImageSize.ts +++ b/code/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/code/extensions/emmet/src/util.ts b/code/extensions/emmet/src/util.ts index adbfe963a5b..e934df84e71 100644 --- a/code/extensions/emmet/src/util.ts +++ b/code/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/code/extensions/emmet/tsconfig.json b/code/extensions/emmet/tsconfig.json index 994fa239537..a6353d515d3 100644 --- a/code/extensions/emmet/tsconfig.json +++ b/code/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/code/extensions/esbuild-webview-common.mjs b/code/extensions/esbuild-webview-common.mjs index 76d03abad7d..7b704b3b7f3 100644 --- a/code/extensions/esbuild-webview-common.mjs +++ b/code/extensions/esbuild-webview-common.mjs @@ -82,7 +82,7 @@ export async function run(config, args, didBuild) { const isWatch = args.indexOf('--watch') >= 0; if (isWatch) { await tryBuild(resolvedOptions, didBuild); - const watcher = await import('@parcel/watcher'); + 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/code/extensions/extension-editing/src/extensionLinter.ts b/code/extensions/extension-editing/src/extensionLinter.ts index be7eea1a49b..187100b563f 100644 --- a/code/extensions/extension-editing/src/extensionLinter.ts +++ b/code/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/code/extensions/extension-editing/tsconfig.json b/code/extensions/extension-editing/tsconfig.json index 3714bd09958..796a159a61c 100644 --- a/code/extensions/extension-editing/tsconfig.json +++ b/code/extensions/extension-editing/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "outDir": "./out", "typeRoots": [ - "node_modules/@types" + "./node_modules/@types" ] }, "include": [ diff --git a/code/extensions/fsharp/cgmanifest.json b/code/extensions/fsharp/cgmanifest.json index c36d14e1d88..d5c42026169 100644 --- a/code/extensions/fsharp/cgmanifest.json +++ b/code/extensions/fsharp/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "ionide/ionide-fsgrammar", "repositoryUrl": "https://github.com/ionide/ionide-fsgrammar", - "commitHash": "be3c51d2c28d3aaacd89ecd067e766bebe387f89" + "commitHash": "0cb968a4b8fdb2e0656b95342cdffbeff04a1248" } }, "license": "MIT", diff --git a/code/extensions/fsharp/syntaxes/fsharp.tmLanguage.json b/code/extensions/fsharp/syntaxes/fsharp.tmLanguage.json index 97f607a6b22..fd1b6b09aa1 100644 --- a/code/extensions/fsharp/syntaxes/fsharp.tmLanguage.json +++ b/code/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/be3c51d2c28d3aaacd89ecd067e766bebe387f89", + "version": "https://github.com/ionide/ionide-fsgrammar/commit/0cb968a4b8fdb2e0656b95342cdffbeff04a1248", "name": "fsharp", "scopeName": "source.fsharp", "patterns": [ @@ -1831,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/code/extensions/git-base/languages/ignore.language-configuration.json b/code/extensions/git-base/languages/ignore.language-configuration.json index ad8b8ee5cd8..03e8ed3611c 100644 --- a/code/extensions/git-base/languages/ignore.language-configuration.json +++ b/code/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/code/extensions/git-base/src/api/api1.ts b/code/extensions/git-base/src/api/api1.ts index 005a7930356..19038bc1eec 100644 --- a/code/extensions/git-base/src/api/api1.ts +++ b/code/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/code/extensions/git-base/src/extension.ts b/code/extensions/git-base/src/extension.ts index 17ffb89f82d..453d8f7850f 100644 --- a/code/extensions/git-base/src/extension.ts +++ b/code/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/code/extensions/git-base/src/foldingProvider.ts b/code/extensions/git-base/src/foldingProvider.ts new file mode 100644 index 00000000000..b1c1cc45171 --- /dev/null +++ b/code/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/code/extensions/git-base/src/remoteSource.ts b/code/extensions/git-base/src/remoteSource.ts index eb86b27367a..9c6f1b02fa4 100644 --- a/code/extensions/git-base/src/remoteSource.ts +++ b/code/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/code/extensions/git-base/src/test/foldingProvider.test.ts b/code/extensions/git-base/src/test/foldingProvider.test.ts new file mode 100644 index 00000000000..69f7d35bf18 --- /dev/null +++ b/code/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/code/extensions/git/package-lock.json b/code/extensions/git/package-lock.json index 4f119e2c3f8..45e3989e801 100644 --- a/code/extensions/git/package-lock.json +++ b/code/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/code/extensions/git/package.json b/code/extensions/git/package.json index 6e5deb004a1..b8b97f4f116 100644 --- a/code/extensions/git/package.json +++ b/code/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", @@ -445,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" }, { @@ -547,6 +549,7 @@ { "command": "git.createTag", "title": "%command.createTag%", + "icon": "$(plus)", "category": "Git", "enablement": "!operationInProgress" }, @@ -575,8 +578,8 @@ "enablement": "!operationInProgress" }, { - "command": "git.deleteWorktreeFromPalette", - "title": "%command.deleteWorktreeFromPalette%", + "command": "git.deleteWorktree2", + "title": "%command.deleteWorktree2%", "category": "Git", "enablement": "!operationInProgress" }, @@ -998,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": [ @@ -1346,10 +1485,6 @@ "command": "git.createWorktree", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, - { - "command": "git.deleteWorktree", - "when": "false" - }, { "command": "git.openWorktree", "when": "false" @@ -1359,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" @@ -1536,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", @@ -1594,6 +1733,10 @@ "command": "git.graph.deleteBranch", "when": "false" }, + { + "command": "git.graph.compareRef", + "when": "false" + }, { "command": "git.graph.deleteTag", "when": "false" @@ -1602,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$/" @@ -1609,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": [ @@ -1693,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", @@ -1722,11 +2017,126 @@ "when": "scmProvider == git && scmProviderContext == worktree" }, { - "command": "git.deleteWorktree", + "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": [ { "command": "git.stageAllMerge", @@ -1750,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" }, { @@ -1810,7 +2220,7 @@ }, { "command": "git.viewUntrackedChanges", - "when": "scmProvider == git && scmResourceGroup == untracked && config.multiDiffEditor.experimental.enabled", + "when": "scmProvider == git && scmResourceGroup == untracked", "group": "inline@1" }, { @@ -2171,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", @@ -2211,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", @@ -2297,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": [ @@ -2349,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", @@ -2359,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", @@ -2624,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": [ @@ -2660,7 +3108,7 @@ }, { "when": "scmProviderContext == worktree", - "command": "git.deleteWorktree", + "command": "git.deleteWorktree2", "group": "worktrees@2" } ] @@ -2697,6 +3145,11 @@ { "id": "git.worktrees", "label": "%submenu.worktrees%" + }, + { + "id": "git.repositories.stash", + "label": "%submenu.stash%", + "icon": "$(plus)" } ], "configuration": { @@ -3115,7 +3568,7 @@ "git.detectWorktreesLimit": { "type": "number", "scope": "resource", - "default": 10, + "default": 50, "description": "%config.detectWorktreesLimit%" }, "git.alwaysShowStagedChangesResourceGroup": { @@ -3436,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, @@ -3446,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, @@ -3732,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/code/extensions/git/package.nls.json b/code/extensions/git/package.nls.json index ae4159e566d..c8834151c6f 100644 --- a/code/extensions/git/package.nls.json +++ b/code/extensions/git/package.nls.json @@ -11,7 +11,9 @@ "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", @@ -60,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...", @@ -74,14 +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)", @@ -121,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", @@ -137,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", @@ -293,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/code/extensions/git/src/api/api1.ts b/code/extensions/git/src/api/api1.ts index 2be6cec8dea..d8ae8777166 100644 --- a/code/extensions/git/src/api/api1.ts +++ b/code/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,14 @@ export class ApiRepository implements Repository { return this.#repository.diffBetween(ref1, ref2, path); } + diffBetweenPatch(ref1: string, ref2: string, path?: string): Promise { + return this.#repository.diffBetweenPatch(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 +316,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 +331,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 +365,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 +428,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 +440,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 +558,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/code/extensions/git/src/api/extension.ts b/code/extensions/git/src/api/extension.ts index d381ebc7f64..a716fa00dae 100644 --- a/code/extensions/git/src/api/extension.ts +++ b/code/extensions/git/src/api/extension.ts @@ -7,14 +7,15 @@ import { Model } from '../model'; import { GitExtension, Repository, API } from './git'; import { ApiRepository, ApiImpl } from './api1'; import { Event, EventEmitter } from 'vscode'; +import { CloneManager } from '../cloneManager'; -function deprecated(original: any, context: ClassMemberDecoratorContext) { - if (context.kind !== 'method') { +function deprecated(original: unknown, context: ClassMemberDecoratorContext) { + if (typeof original !== 'function' || context.kind !== 'method') { throw new Error('not supported'); } const key = context.name.toString(); - return function (this: any, ...args: any[]): any { + return function (this: unknown, ...args: unknown[]) { console.warn(`Git extension API method '${key}' is deprecated.`); 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/code/extensions/git/src/api/git.d.ts b/code/extensions/git/src/api/git.d.ts index fdfb8b397bc..285d76ee55b 100644 --- a/code/extensions/git/src/api/git.d.ts +++ b/code/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,19 @@ 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; + diffBetweenPatch(ref1: string, ref2: string, path?: string): Promise; + diffBetweenWithStats(ref1: string, ref2: string, path?: string): Promise; hashObject(data: string): Promise; @@ -249,7 +279,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 +301,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 +401,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/code/extensions/git/src/artifactProvider.ts b/code/extensions/git/src/artifactProvider.ts new file mode 100644 index 00000000000..f99e262b9c4 --- /dev/null +++ b/code/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/code/extensions/git/src/askpass-main.ts b/code/extensions/git/src/askpass-main.ts index cb93adf2821..21402fbaf34 100644 --- a/code/extensions/git/src/askpass-main.ts +++ b/code/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/code/extensions/git/src/blame.ts b/code/extensions/git/src/blame.ts index 379f70fd3a5..c6f121a01b3 100644 --- a/code/extensions/git/src/blame.ts +++ b/code/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, documentUri]))} "${l10n.t('Open Commit')}")`); - markdownString.appendMarkdown(' '); - markdownString.appendMarkdown(`[$(copy)](command:git.copyContentToClipboard?${encodeURIComponent(JSON.stringify(hash))} "${l10n.t('Copy Commit Hash')}")`); + const commands: Command[][] = [ + getHoverCommitHashCommands(documentUri, hash), + processHoverRemoteCommands(remoteHoverCommands, hash) + ]; - // Remote hover commands - if (remoteHoverCommands.length > 0) { - markdownString.appendMarkdown('  |  '); + commands.push([{ + title: `$(gear)`, + tooltip: l10n.t('Open Settings'), + command: 'workbench.action.openSettings', + arguments: ['git.blame'] + }] satisfies Command[]); - const remoteCommandsMarkdown = remoteHoverCommands - .map(command => `[${command.title}](command:${command.command}?${encodeURIComponent(JSON.stringify([...command.arguments ?? [], hash]))} "${command.tooltip}")`); - markdownString.appendMarkdown(remoteCommandsMarkdown.join(' ')); - } - - 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); diff --git a/code/extensions/git/src/cache.ts b/code/extensions/git/src/cache.ts index df0c0df5561..ad2db75edc8 100644 --- a/code/extensions/git/src/cache.ts +++ b/code/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/code/extensions/git/src/cloneManager.ts b/code/extensions/git/src/cloneManager.ts new file mode 100644 index 00000000000..49d57d8763c --- /dev/null +++ b/code/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/code/extensions/git/src/commands.ts b/code/extensions/git/src/commands.ts index 9682b979e84..a0e362d56cf 100644 --- a/code/extensions/git/src/commands.ts +++ b/code/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,8 +373,8 @@ interface ScmCommand { const Commands: ScmCommand[] = []; function command(commandId: string, options: ScmCommandOptions = {}): Function { - return (value: any, context: ClassMethodDecoratorContext) => { - if (context.kind !== 'method') { + return (value: unknown, context: ClassMethodDecoratorContext) => { + if (typeof value !== 'function' || context.kind !== 'method') { throw new Error('not supported'); } const key = context.name.toString(); @@ -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') @@ -1825,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) { @@ -2136,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) { @@ -2381,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; @@ -2541,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; } @@ -2554,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); @@ -3233,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 }); @@ -3393,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 }) @@ -3436,89 +3404,35 @@ export class CommandCenter { @command('git.migrateWorktreeChanges', { repository: true, repositoryFilter: ['repository', 'submodule'] }) async migrateWorktreeChanges(repository: Repository): Promise { - const worktreePicks = async (): Promise => { - const worktrees = await repository.getWorktrees(); - return worktrees.length === 0 - ? [{ label: l10n.t('$(info) This repository has no worktrees.') }] - : worktrees.map(worktree => new WorktreeItem(worktree)); - }; - - const placeHolder = l10n.t('Select a worktree to migrate changes from'); - const choice = await this.pickRef(worktreePicks(), placeHolder); - - if (!choice || !(choice instanceof WorktreeItem)) { - return; - } - - const worktreeRepository = this.model.getRepository(choice.worktree.path); - if (!worktreeRepository) { - return; - } - - if (worktreeRepository.indexGroup.resourceStates.length === 0 && - worktreeRepository.workingTreeGroup.resourceStates.length === 0 && - worktreeRepository.untrackedGroup.resourceStates.length === 0) { - await window.showInformationMessage(l10n.t('There are no changes in the selected worktree to migrate.')); - return; - } - - const worktreeChangedFilePaths = [ - ...worktreeRepository.indexGroup.resourceStates, - ...worktreeRepository.workingTreeGroup.resourceStates, - ...worktreeRepository.untrackedGroup.resourceStates - ].map(resource => path.relative(worktreeRepository.root, resource.resourceUri.fsPath)); - - const targetChangedFilePaths = [ - ...repository.workingTreeGroup.resourceStates, - ...repository.untrackedGroup.resourceStates - ].map(resource => path.relative(repository.root, resource.resourceUri.fsPath)); + let worktreeRepository: Repository | undefined; - // Detect overlapping unstaged files in worktree stash and target repository - const conflicts = worktreeChangedFilePaths.filter(path => targetChangedFilePaths.includes(path)); + 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)); + }; - // Check for 'LocalChangesOverwritten' error - if (conflicts.length > 0) { - const maxFilesShown = 5; - const filesToShow = conflicts.slice(0, maxFilesShown); - const remainingCount = conflicts.length - maxFilesShown; + const placeHolder = l10n.t('Select a worktree to migrate changes from'); + const choice = await this.pickRef(worktreePicks(), placeHolder); - const fileList = filesToShow.join('\n ') + - (remainingCount > 0 ? l10n.t('\n and {0} more file{1}...', remainingCount, remainingCount > 1 ? 's' : '') : ''); + if (!choice || !(choice instanceof WorktreeItem)) { + return; + } - 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; + worktreeRepository = this.model.getRepository(choice.worktree.path); } - 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) { + if (!worktreeRepository || worktreeRepository.kind !== 'worktree') { return; } - await worktreeRepository.createStash(undefined, true); - const stashes = await worktreeRepository.getStashes(); - - try { - await repository.applyStash(stashes[0].index); - worktreeRepository.dropStash(stashes[0].index); - } catch (err) { - if (err.gitErrorCode !== GitErrorCodes.StashConflict) { - await worktreeRepository.popStash(); - throw err; - } - repository.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'); - } - worktreeRepository.dropStash(stashes[0].index); - } + await repository.migrateChanges(worktreeRepository.root, { + confirmation: true, deleteFromSource: true, untracked: true + }); } @command('git.openWorktreeMergeEditor') @@ -3537,42 +3451,51 @@ export class CommandCenter { }); } - @command('git.createWorktree') - async createWorktree(repository: any): Promise { - repository = this.model.getRepository(repository); - + @command('git.createWorktree', { repository: true, repositoryFilter: ['repository', 'submodule'] }) + async createWorktree(repository?: Repository): Promise { if (!repository) { - // Single repository/submodule/worktree - if (this.model.repositories.length === 1) { - repository = this.model.repositories[0]; - } + return; } - if (!repository) { - // Single repository/submodule - const repositories = this.model.repositories - .filter(r => r.kind === 'repository' || r.kind === 'submodule'); + await this._createWorktree(repository); + } - if (repositories.length === 1) { - repository = repositories[0]; - } - } + async _createWorktree(repository: Repository): Promise { + const config = workspace.getConfiguration('git'); + const branchPrefix = config.get('branchPrefix')!; - if (!repository) { - // Multiple repositories/submodules - repository = await this.model.pickRepository(['repository', 'submodule']); + // Get commitish and branch for the new worktree + const worktreeDetails = await this.getWorktreeCommitishAndBranch(repository); + if (!worktreeDetails) { + return; } - if (!repository) { + 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; } - await this._createWorktree(repository); + 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 _createWorktree(repository: Repository): Promise { - const config = workspace.getConfiguration('git'); - const branchPrefix = config.get('branchPrefix')!; + 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(); @@ -3591,23 +3514,21 @@ export class CommandCenter { 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) { @@ -3616,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 @@ -3634,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({ @@ -3680,10 +3597,12 @@ 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); + : repository.kind === 'worktree' + ? path.join(path.dirname(repository.root), worktreeName) + : path.join(path.dirname(repository.root), `${path.basename(repository.root)}.worktrees`, worktreeName); const disposables: Disposable[] = []; const inputBox = window.createInputBox(); @@ -3719,33 +3638,11 @@ 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; - } + return worktreePath; } - private async handleWorktreeBranchAlreadyUsed(err: any): Promise { - const match = err.stderr.match(/fatal: '([^']+)' is already used by worktree at '([^']+)'/); + private async handleWorktreeBranchAlreadyUsed(err: GitError): Promise { + const match = err.stderr?.match(/fatal: '([^']+)' is already used by worktree at '([^']+)'/); if (!match) { return; @@ -3756,8 +3653,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; @@ -3789,48 +3686,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'); @@ -3841,6 +3706,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) { @@ -4693,7 +4573,7 @@ export class CommandCenter { return; } - await this._stashDrop(repository, stash); + await this._stashDrop(repository, stash.index, stash.description); } @command('git.stashDropAll', { repository: true }) @@ -4726,15 +4606,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 ); @@ -4742,7 +4622,7 @@ export class CommandCenter { return false; } - await repository.dropStash(stash.index); + await repository.dropStash(index); return true; } @@ -4755,36 +4635,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 { @@ -4829,6 +4680,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( @@ -4860,7 +4744,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 { @@ -4903,10 +4787,10 @@ 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) }; @@ -4977,7 +4861,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); } @@ -5172,12 +5056,12 @@ 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; @@ -5210,6 +5094,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; @@ -5368,7 +5518,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/code/extensions/git/src/decorationProvider.ts b/code/extensions/git/src/decorationProvider.ts index 2e72a1e4114..fb895d5aff2 100644 --- a/code/extensions/git/src/decorationProvider.ts +++ b/code/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/code/extensions/git/src/decorators.ts b/code/extensions/git/src/decorators.ts index cd1c7d72d3b..0e59a849ed2 100644 --- a/code/extensions/git/src/decorators.ts +++ b/code/extensions/git/src/decorators.ts @@ -6,8 +6,8 @@ import { done } from './util'; function decorate(decorator: (fn: Function, key: string) => Function): Function { - return function (original: any, context: ClassMethodDecoratorContext) { - if (context.kind === 'method' || context.kind === 'getter' || context.kind === 'setter') { + 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()); } throw new Error('not supported'); diff --git a/code/extensions/git/src/emoji.ts b/code/extensions/git/src/emoji.ts index bd686b0160d..7c41ce6952e 100644 --- a/code/extensions/git/src/emoji.ts +++ b/code/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/code/extensions/git/src/git-editor-main.ts b/code/extensions/git/src/git-editor-main.ts index eb4da4a40b5..80615b56e5a 100644 --- a/code/extensions/git/src/git-editor-main.ts +++ b/code/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/code/extensions/git/src/git.ts b/code/extensions/git/src/git.ts index e04a1a754c7..5cd79ce82d5 100644 --- a/code/extensions/git/src/git.ts +++ b/code/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; @@ -676,6 +678,7 @@ export class Git { options.env = assign({}, process.env, this.env, options.env || {}, { VSCODE_GIT_COMMAND: args[0], + LANGUAGE: 'en', LC_ALL: 'en_US.UTF-8', LANG: 'en_US.UTF-8', GIT_PAGER: 'cat' @@ -865,12 +868,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 +996,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 +1009,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 +1085,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 +1304,10 @@ export interface PullOptions { readonly cancellationToken?: CancellationToken; } +export interface Worktree extends ApiWorktree { + readonly commitDetails?: ApiCommit; +} + export class Repository { private _isUsingRefTable = false; @@ -1620,7 +1696,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 +1704,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 +1726,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 +1734,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 +1763,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 +1772,54 @@ 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 diffBetweenPatch(ref: string, options: { path?: string }): Promise { + const args = ['diff', ref, '--']; + + 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 result.stdout; + } + + async diffBetweenWithStats(ref: string, options: { path?: string; similarityThreshold?: number }): Promise { + const args = ['diff', '--raw', '--numstat', '--diff-filter=ADMR', '-z',]; + + 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 +1828,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 +1867,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 { @@ -1797,7 +1936,15 @@ export class Repository { async stage(path: string, data: Uint8Array): Promise { const relativePath = this.sanitizeRelativePath(path); const child = this.stream(['hash-object', '--stdin', '-w', '--path', relativePath], { stdio: [null, null, null] }); - child.stdin!.end(data); + + if (!child.stdin) { + throw new GitError({ + message: 'Failed to spawn git process', + exitCode: -1 + }); + } + + child.stdin.end(data); const { exitCode, stdout } = await exec(child); const hash = stdout.toString('utf8'); @@ -1939,11 +2086,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 +2225,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 +2527,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 +2576,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); } @@ -2546,19 +2704,23 @@ export class Repository { if (limit !== 0 && parser.status.length > limit) { child.removeListener('close', onClose); - child.stdout!.removeListener('data', onStdoutData); + child.stdout?.removeListener('data', onStdoutData); child.kill(); c({ status: parser.status.slice(0, limit), statusLength: parser.status.length, didHitLimit: true }); } }; - child.stdout!.setEncoding('utf8'); - child.stdout!.on('data', onStdoutData); + if (child.stdout) { + child.stdout.setEncoding('utf8'); + child.stdout.on('data', onStdoutData); + } const stderrData: string[] = []; - child.stderr!.setEncoding('utf8'); - child.stderr!.on('data', raw => stderrData.push(raw as string)); + if (child.stderr) { + child.stderr.setEncoding('utf8'); + child.stderr.on('data', raw => stderrData.push(raw as string)); + } child.on('error', cpErrorHandler(e)); child.on('close', onClose); @@ -2779,14 +2941,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 +2965,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 +3128,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 +3209,27 @@ export class Repository { return commits[0]; } - async showCommit(ref: string): Promise { + async showChanges(ref: string): Promise { try { - const result = await this.exec(['show', ref]); + 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 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/code/extensions/git/src/gitEditor.ts b/code/extensions/git/src/gitEditor.ts index 6291e5152a7..cbbea2c6d78 100644 --- a/code/extensions/git/src/gitEditor.ts +++ b/code/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/code/extensions/git/src/historyProvider.ts b/code/extensions/git/src/historyProvider.ts index 9830928fcd3..f921f5734a5 100644 --- a/code/extensions/git/src/historyProvider.ts +++ b/code/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/code/extensions/git/src/hover.ts b/code/extensions/git/src/hover.ts new file mode 100644 index 00000000000..7d33893a348 --- /dev/null +++ b/code/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/code/extensions/git/src/ipc/ipcClient.ts b/code/extensions/git/src/ipc/ipcClient.ts index f623b3f7b6f..9aab55e44a3 100644 --- a/code/extensions/git/src/ipc/ipcClient.ts +++ b/code/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/code/extensions/git/src/ipc/ipcServer.ts b/code/extensions/git/src/ipc/ipcServer.ts index a7142fe22e1..5e56f9ceef5 100644 --- a/code/extensions/git/src/ipc/ipcServer.ts +++ b/code/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/code/extensions/git/src/main.ts b/code/extensions/git/src/main.ts index 228e981f6ce..535c0f2f30e 100644 --- a/code/extensions/git/src/main.ts +++ b/code/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/code/extensions/git/src/model.ts b/code/extensions/git/src/model.ts index a199e010be6..b40de9b49d6 100644 --- a/code/extensions/git/src/model.ts +++ b/code/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(); } @@ -862,7 +877,8 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi } const repositories = this.openRepositories - .filter(r => !repositoryFilter || repositoryFilter.includes(r.repository.kind)); + .filter(r => !r.repository.isHidden && + (!repositoryFilter || repositoryFilter.includes(r.repository.kind))); if (repositories.length === 0) { throw new Error(l10n.t('There are no available repositories matching the filter')); @@ -886,11 +902,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 +929,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 +1095,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 +1180,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/code/extensions/git/src/operation.ts b/code/extensions/git/src/operation.ts index eaa91d4a047..96fffa4dc87 100644 --- a/code/extensions/git/src/operation.ts +++ b/code/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/code/extensions/git/src/protocolHandler.ts b/code/extensions/git/src/protocolHandler.ts index 90491fecd50..42289abcb9c 100644 --- a/code/extensions/git/src/protocolHandler.ts +++ b/code/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/code/extensions/git/src/repository.ts b/code/extensions/git/src/repository.ts index 35aa9b99ce2..6a978f7d860 100644 --- a/code/extensions/git/src/repository.ts +++ b/code/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)); @@ -184,7 +186,7 @@ export class Resource implements SourceControlResourceState { 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'), @@ -209,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; @@ -690,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; @@ -722,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; @@ -872,9 +873,15 @@ 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; } + private _isHidden: boolean; + get isHidden(): boolean { return this._isHidden; } + private isRepositoryHuge: false | { limit: number } = false; private didWarnAboutLimit = false; @@ -893,9 +900,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); @@ -934,17 +942,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. + this._isHidden = 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, this._isHidden, parent); this._sourceControl.contextValue = repository.kind; this._sourceControl.quickDiffProvider = this; @@ -954,6 +973,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); @@ -1116,17 +1139,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; } @@ -1197,6 +1213,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; @@ -1211,6 +1231,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; @@ -1229,11 +1253,26 @@ 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)); + diffBetweenPatch(ref1: string, ref2: string, path?: string): Promise { + return this.run(Operation.Diff, () => + this.repository.diffBetweenPatch(`${ref1}...${ref2}`, { path })); + } + + 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); @@ -1739,7 +1778,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 { @@ -1770,12 +1839,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 { @@ -1813,8 +1944,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 { @@ -1843,11 +1978,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 { @@ -1975,7 +2122,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 @@ -2127,7 +2278,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 { @@ -2136,26 +2287,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 { @@ -2204,7 +2355,15 @@ export class Repository implements Disposable { // https://git-scm.com/docs/git-check-ignore#git-check-ignore--z const child = this.repository.stream(['check-ignore', '-v', '-z', '--stdin'], { stdio: [null, null, null] }); - child.stdin!.end(filePaths.join('\0'), 'utf8'); + + if (!child.stdin) { + return reject(new GitError({ + message: 'Failed to spawn git process', + exitCode: -1 + })); + } + + child.stdin.end(filePaths.join('\0'), 'utf8'); const onExit = (exitCode: number) => { if (exitCode === 1) { @@ -2226,12 +2385,16 @@ export class Repository implements Disposable { data += raw; }; - child.stdout!.setEncoding('utf8'); - child.stdout!.on('data', onStdoutData); + if (child.stdout) { + child.stdout.setEncoding('utf8'); + child.stdout.on('data', onStdoutData); + } let stderr: string = ''; - child.stderr!.setEncoding('utf8'); - child.stderr!.on('data', raw => stderr += raw); + if (child.stderr) { + child.stderr.setEncoding('utf8'); + child.stderr.on('data', raw => stderr += raw); + } child.on('error', reject); child.on('exit', onExit); @@ -2283,14 +2446,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); @@ -2306,7 +2470,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; } @@ -2321,7 +2485,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) { @@ -2661,7 +2908,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/code/extensions/git/src/repositoryCache.ts b/code/extensions/git/src/repositoryCache.ts new file mode 100644 index 00000000000..5e3f8cbe594 --- /dev/null +++ b/code/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/code/extensions/git/src/statusbar.ts b/code/extensions/git/src/statusbar.ts index e58096442f2..6a06209eb41 100644 --- a/code/extensions/git/src/statusbar.ts +++ b/code/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/code/extensions/git/src/test/repositoryCache.test.ts b/code/extensions/git/src/test/repositoryCache.test.ts new file mode 100644 index 00000000000..ce3ec98272d --- /dev/null +++ b/code/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/code/extensions/git/src/timelineProvider.ts b/code/extensions/git/src/timelineProvider.ts index 0e90abd0082..1ccf04a423d 100644 --- a/code/extensions/git/src/timelineProvider.ts +++ b/code/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, uri]))} "${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/code/extensions/git/src/util.ts b/code/extensions/git/src/util.ts index a4c5036255f..c6ec6ece45c 100644 --- a/code/extensions/git/src/util.ts +++ b/code/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/code/extensions/git/tsconfig.json b/code/extensions/git/tsconfig.json index 8bc4c420ef6..eac688f81de 100644 --- a/code/extensions/git/tsconfig.json +++ b/code/extensions/git/tsconfig.json @@ -4,7 +4,8 @@ "outDir": "./out", "typeRoots": [ "./node_modules/@types" - ] + ], + "skipLibCheck": true }, "include": [ "src/**/*", @@ -15,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/code/extensions/github-authentication/package-lock.json b/code/extensions/github-authentication/package-lock.json index 60fbdfe141c..93b4b14be17 100644 --- a/code/extensions/github-authentication/package-lock.json +++ b/code/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/code/extensions/github-authentication/package.json b/code/extensions/github-authentication/package.json index 193580b094a..5f479502f6a 100644 --- a/code/extensions/github-authentication/package.json +++ b/code/extensions/github-authentication/package.json @@ -33,6 +33,48 @@ ] } }, + "contributes": { + "authentication": [ + { + "label": "GitHub", + "id": "github", + "authorizationServerGlobs": [ + "https://github.com/login/oauth" + ] + }, + { + "label": "GitHub Enterprise Server", + "id": "github-enterprise", + "authorizationServerGlobs": [ + "*" + ] + } + ], + "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", "browser": "./dist/browser/extension.js", @@ -49,7 +91,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/code/extensions/github-authentication/package.nls.json b/code/extensions/github-authentication/package.nls.json index 4f45cd5d4f6..dfbd033224d 100644 --- a/code/extensions/github-authentication/package.nls.json +++ b/code/extensions/github-authentication/package.nls.json @@ -3,5 +3,6 @@ "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-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.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/code/extensions/github-authentication/src/common/logger.ts b/code/extensions/github-authentication/src/common/logger.ts index cf90c4176a9..429ecdf596c 100644 --- a/code/extensions/github-authentication/src/common/logger.ts +++ b/code/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/code/extensions/github-authentication/src/flows.ts b/code/extensions/github-authentication/src/flows.ts index 6e37dfe5dfd..76da600118a 100644 --- a/code/extensions/github-authentication/src/flows.ts +++ b/code/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'; @@ -112,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...'); @@ -130,13 +164,15 @@ 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: { @@ -199,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); @@ -236,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; }); } @@ -283,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); @@ -329,6 +377,7 @@ class LocalServerFlow implements IFlow { baseUri.with({ path: '/login/oauth/access_token' }), redirectUri, codeToExchange, + codeVerifier, enterpriseUri); return accessToken; }); @@ -358,6 +407,7 @@ class DeviceCodeFlow implements IFlow { }); const result = await fetching(uri.toString(true), { logger, + retryFallbacks: true, expectJSON: true, method: 'POST', headers: { @@ -436,6 +486,7 @@ class DeviceCodeFlow implements IFlow { try { accessTokenResult = await fetching(refreshTokenUri.toString(true), { logger, + retryFallbacks: true, expectJSON: true, method: 'POST', headers: { @@ -531,6 +582,7 @@ class PatFlow implements IFlow { logger.info('Getting token scopes...'); const result = await fetching(serverUri.toString(), { logger, + retryFallbacks: true, expectJSON: false, headers: { Authorization: `token ${token}`, @@ -560,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: @@ -596,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/code/extensions/github-authentication/src/githubServer.ts b/code/extensions/github-authentication/src/githubServer.ts index b71bf5ec6d7..b8646696a8a 100644 --- a/code/extensions/github-authentication/src/githubServer.ts +++ b/code/extensions/github-authentication/src/githubServer.ts @@ -179,6 +179,7 @@ export class GitHubServer implements IGitHubServer { // 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: { @@ -222,6 +223,7 @@ export class GitHubServer implements IGitHubServer { this._logger.info('Getting user info...'); result = await fetching(this.getServerUri('/user').toString(), { logger: this._logger, + retryFallbacks: true, expectJSON: true, headers: { Authorization: `token ${token}`, @@ -282,6 +284,7 @@ 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}`, @@ -298,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'; } @@ -324,6 +329,7 @@ export class GitHubServer implements IGitHubServer { 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}`, diff --git a/code/extensions/github-authentication/src/node/crypto.ts b/code/extensions/github-authentication/src/node/crypto.ts index 367246fdbed..27b3cafd8b1 100644 --- a/code/extensions/github-authentication/src/node/crypto.ts +++ b/code/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/code/extensions/github-authentication/src/node/fetch.ts b/code/extensions/github-authentication/src/node/fetch.ts index a324e547876..7bd5f929029 100644 --- a/code/extensions/github-authentication/src/node/fetch.ts +++ b/code/extensions/github-authentication/src/node/fetch.ts @@ -7,9 +7,11 @@ 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; @@ -64,97 +66,87 @@ _fetchers.push({ }); export function createFetch(): Fetch { - let _fetcher: Fetcher | undefined; + let fetchers: readonly Fetcher[] = _fetchers; return async (url, options) => { - if (!_fetcher) { - let firstResponse: FetchResponse | undefined; - let firstError: any; - for (const fetcher of _fetchers) { - try { - const res = await fetcher.fetch(url, options); - if (fetcher === _fetchers[0]) { - firstResponse = res; - } - if (!res.ok) { - options.logger.info(`fetching: ${fetcher.name} failed with status: ${res.status} ${res.statusText}`); - continue; - } - if (!options.expectJSON) { - options.logger.info(`fetching: ${fetcher.name} succeeded (not JSON)`); - _fetcher = fetcher; - return res; - } - const text = await res.text(); - if (fetcher === _fetchers[0]) { - // Update to unconsumed response - firstResponse = new FetchResponseImpl( - res.status, - res.statusText, - res.headers, - async () => text, - async () => JSON.parse(text), - ); - } - const json = JSON.parse(text); // Verify JSON - options.logger.info(`fetching: ${fetcher.name} succeeded (JSON)`); - if (fetcher !== _fetchers[0]) { - const retry = await retryFetch(_fetchers[0], url, options); - if ('res' in retry && retry.res.ok) { - _fetcher = _fetchers[0]; - return retry.res; - } - } - _fetcher = fetcher; - return new FetchResponseImpl( - res.status, - res.statusText, - res.headers, - async () => text, - async () => json, - ); - } catch (err) { - if (fetcher === _fetchers[0]) { - firstError = err; - } - options.logger.info(`fetching: ${fetcher.name} failed with error: ${err.message}`); + 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; } - _fetcher = _fetchers[0]; // Do this only once - if (firstResponse) { - return firstResponse; + 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 }; } - throw firstError; + return { response: result.response }; } - return _fetcher.fetch(url, options); - }; + if ('response' in firstResult!) { + return { response: firstResult.response }; + } + throw firstResult!.err; + } + return { response: await availableFetchers[0].fetch(url, options) }; } -async function retryFetch(fetcher: Fetcher, url: string, options: FetchOptions): Promise<{ res: FetchResponse } | { err: any }> { +async function tryFetch(fetcher: Fetcher, url: string, options: FetchOptions, logService: Log): Promise<{ ok: boolean; response: FetchResponse } | { ok: false; err: any }> { try { - const res = await fetcher.fetch(url, options); - if (!res.ok) { - options.logger.info(`fetching: ${fetcher.name} failed with status: ${res.status} ${res.statusText}`); - return { res }; + 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) { - options.logger.info(`fetching: ${fetcher.name} succeeded (not JSON)`); - return { res }; + 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])) }; } - const text = await res.text(); - const json = JSON.parse(text); // Verify JSON - options.logger.info(`fetching: ${fetcher.name} succeeded (JSON)`); - return { - res: new FetchResponseImpl( - res.status, - res.statusText, - res.headers, - async () => text, - async () => json, - ) - }; } catch (err) { - options.logger.info(`fetching: ${fetcher.name} failed with error: ${err.message}`); - return { err }; + logService.info(`FetcherService: ${fetcher.name} failed with error: ${err.message}`); + return { ok: false, err }; } } @@ -168,6 +160,7 @@ class FetchResponseImpl implements FetchResponse { public readonly headers: FetchHeaders, public readonly text: () => Promise, public readonly json: () => Promise, + public readonly body: () => Promise, ) { this.ok = this.status >= 200 && this.status < 300; } @@ -192,6 +185,7 @@ async function nodeHTTP(url: string, options: FetchOptions): Promise nodeFetcherResponse.text(), async () => nodeFetcherResponse.json(), + async () => nodeFetcherResponse.body(), )); }); req.setTimeout(60 * 1000); // time out after 60s of receiving no data diff --git a/code/extensions/github-authentication/src/test/flows.test.ts b/code/extensions/github-authentication/src/test/flows.test.ts index 77c023e4819..fdcdd0f3f45 100644 --- a/code/extensions/github-authentication/src/test/flows.test.ts +++ b/code/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/code/extensions/github-authentication/src/test/node/fetch.test.ts b/code/extensions/github-authentication/src/test/node/fetch.test.ts index 1be678d40de..211b133e406 100644 --- a/code/extensions/github-authentication/src/test/node/fetch.test.ts +++ b/code/extensions/github-authentication/src/test/node/fetch.test.ts @@ -76,6 +76,7 @@ suite('fetching', () => { 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'; @@ -87,6 +88,7 @@ suite('fetching', () => { 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'; @@ -98,6 +100,7 @@ suite('fetching', () => { 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'; @@ -109,6 +112,7 @@ suite('fetching', () => { 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'; @@ -120,6 +124,7 @@ suite('fetching', () => { 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'; @@ -131,6 +136,7 @@ suite('fetching', () => { 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'; @@ -138,4 +144,46 @@ suite('fetching', () => { 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/code/extensions/github/package.json b/code/extensions/github/package.json index 726a882ebc3..cd70cfea26b 100644 --- a/code/extensions/github/package.json +++ b/code/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/code/extensions/github/src/historyItemDetailsProvider.ts b/code/extensions/github/src/historyItemDetailsProvider.ts index 1a5d58a1c52..9a267b9e844 100644 --- a/code/extensions/github/src/historyItemDetailsProvider.ts +++ b/code/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/code/extensions/github/src/links.ts b/code/extensions/github/src/links.ts index 8eb0f6b23f6..b4f8379e5f7 100644 --- a/code/extensions/github/src/links.ts +++ b/code/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/code/extensions/github/src/remoteSourceProvider.ts b/code/extensions/github/src/remoteSourceProvider.ts index 291a3f1a6ba..bed2bb1aa6b 100644 --- a/code/extensions/github/src/remoteSourceProvider.ts +++ b/code/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/code/extensions/github/src/test/github.test.ts b/code/extensions/github/src/test/github.test.ts index db0eba515cb..838fd37923e 100644 --- a/code/extensions/github/src/test/github.test.ts +++ b/code/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/code/extensions/github/src/util.ts b/code/extensions/github/src/util.ts index d2ccc426170..f7f54ec5f3f 100644 --- a/code/extensions/github/src/util.ts +++ b/code/extensions/github/src/util.ts @@ -74,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)); @@ -82,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); - return remote.fetchUrl; + if (remote) { + 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/code/extensions/github/tsconfig.json b/code/extensions/github/tsconfig.json index 63a4cd931d9..c82524f6d26 100644 --- a/code/extensions/github/tsconfig.json +++ b/code/extensions/github/tsconfig.json @@ -5,7 +5,6 @@ "moduleResolution": "NodeNext", "outDir": "./out", "skipLibCheck": true, - "allowSyntheticDefaultImports": false, "typeRoots": [ "./node_modules/@types" ] diff --git a/code/extensions/grunt/src/main.ts b/code/extensions/grunt/src/main.ts index fd99ba335c4..b94b00c4462 100644 --- a/code/extensions/grunt/src/main.ts +++ b/code/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/code/extensions/grunt/tsconfig.json b/code/extensions/grunt/tsconfig.json index 7234fdfeb97..22c47de77db 100644 --- a/code/extensions/grunt/tsconfig.json +++ b/code/extensions/grunt/tsconfig.json @@ -4,6 +4,9 @@ "outDir": "./out", "types": [ "node" + ], + "typeRoots": [ + "./node_modules/@types" ] }, "include": [ diff --git a/code/extensions/gulp/src/main.ts b/code/extensions/gulp/src/main.ts index b0b85ca29b9..c83bdb65897 100644 --- a/code/extensions/gulp/src/main.ts +++ b/code/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/code/extensions/gulp/tsconfig.json b/code/extensions/gulp/tsconfig.json index 7234fdfeb97..22c47de77db 100644 --- a/code/extensions/gulp/tsconfig.json +++ b/code/extensions/gulp/tsconfig.json @@ -4,6 +4,9 @@ "outDir": "./out", "types": [ "node" + ], + "typeRoots": [ + "./node_modules/@types" ] }, "include": [ diff --git a/code/extensions/html-language-features/client/src/htmlClient.ts b/code/extensions/html-language-features/client/src/htmlClient.ts index 2b0f961899b..54fc91469da 100644 --- a/code/extensions/html-language-features/client/src/htmlClient.ts +++ b/code/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/code/extensions/html-language-features/client/tsconfig.json b/code/extensions/html-language-features/client/tsconfig.json index 615adaeea04..051d5823fe5 100644 --- a/code/extensions/html-language-features/client/tsconfig.json +++ b/code/extensions/html-language-features/client/tsconfig.json @@ -6,6 +6,9 @@ "webworker" ], "module": "Node16", + "typeRoots": [ + "../node_modules/@types" + ] }, "include": [ "src/**/*", diff --git a/code/extensions/html-language-features/package-lock.json b/code/extensions/html-language-features/package-lock.json index b30e18f8c65..442b79121eb 100644 --- a/code/extensions/html-language-features/package-lock.json +++ b/code/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.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" @@ -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/code/extensions/html-language-features/package.json b/code/extensions/html-language-features/package.json index 4380032fada..4e630b72736 100644 --- a/code/extensions/html-language-features/package.json +++ b/code/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/code/extensions/html-language-features/package.nls.json b/code/extensions/html-language-features/package.nls.json index f36ecf34f02..d8390703757 100644 --- a/code/extensions/html-language-features/package.nls.json +++ b/code/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/code/extensions/html-language-features/server/package-lock.json b/code/extensions/html-language-features/server/package-lock.json index 4dbf88130c0..dc257f8b9d7 100644 --- a/code/extensions/html-language-features/server/package-lock.json +++ b/code/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/code/extensions/html-language-features/server/package.json b/code/extensions/html-language-features/server/package.json index 3df971c7eef..8208d3f22e6 100644 --- a/code/extensions/html-language-features/server/package.json +++ b/code/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/code/extensions/html-language-features/server/src/htmlServer.ts b/code/extensions/html-language-features/server/src/htmlServer.ts index 22ab2e18076..877c42f5c7c 100644 --- a/code/extensions/html-language-features/server/src/htmlServer.ts +++ b/code/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/code/extensions/html-language-features/server/tsconfig.json b/code/extensions/html-language-features/server/tsconfig.json index 4f24a50855c..0b49ec72b8f 100644 --- a/code/extensions/html-language-features/server/tsconfig.json +++ b/code/extensions/html-language-features/server/tsconfig.json @@ -7,6 +7,9 @@ "WebWorker" ], "module": "Node16", + "typeRoots": [ + "../node_modules/@types" + ] }, "include": [ "src/**/*" diff --git a/code/extensions/ini/package.json b/code/extensions/ini/package.json index 8523df264c1..f4837eb881f 100644 --- a/code/extensions/ini/package.json +++ b/code/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/code/extensions/ipynb/package-lock.json b/code/extensions/ipynb/package-lock.json index 7042d6a22b2..8dc9c5a8a42 100644 --- a/code/extensions/ipynb/package-lock.json +++ b/code/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/code/extensions/ipynb/package.json b/code/extensions/ipynb/package.json index 69f458bc7d4..89a24e5cc15 100644 --- a/code/extensions/ipynb/package.json +++ b/code/extensions/ipynb/package.json @@ -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/code/extensions/ipynb/src/common.ts b/code/extensions/ipynb/src/common.ts index dbd3ea1a618..f0330c88440 100644 --- a/code/extensions/ipynb/src/common.ts +++ b/code/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/code/extensions/ipynb/src/deserializers.ts b/code/extensions/ipynb/src/deserializers.ts index e49931e060c..596a03db468 100644 --- a/code/extensions/ipynb/src/deserializers.ts +++ b/code/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/code/extensions/ipynb/src/helper.ts b/code/extensions/ipynb/src/helper.ts index 6d67b7d529f..6a23633f52c 100644 --- a/code/extensions/ipynb/src/helper.ts +++ b/code/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/code/extensions/ipynb/src/notebookImagePaste.ts b/code/extensions/ipynb/src/notebookImagePaste.ts index 70a24e9bf2d..97c2ee73946 100644 --- a/code/extensions/ipynb/src/notebookImagePaste.ts +++ b/code/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/code/extensions/ipynb/src/serializers.ts b/code/extensions/ipynb/src/serializers.ts index 54a05aba205..6647c27176f 100644 --- a/code/extensions/ipynb/src/serializers.ts +++ b/code/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/code/extensions/ipynb/src/test/notebookModelStoreSync.test.ts b/code/extensions/ipynb/src/test/notebookModelStoreSync.test.ts index 7174678ad61..42395b0a238 100644 --- a/code/extensions/ipynb/src/test/notebookModelStoreSync.test.ts +++ b/code/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/code/extensions/ipynb/src/test/serializers.test.ts b/code/extensions/ipynb/src/test/serializers.test.ts index e132b6b2b1d..acc13995ff5 100644 --- a/code/extensions/ipynb/src/test/serializers.test.ts +++ b/code/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/code/extensions/ipynb/tsconfig.json b/code/extensions/ipynb/tsconfig.json index ee21f68d22a..39ab6fc882d 100644 --- a/code/extensions/ipynb/tsconfig.json +++ b/code/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/code/extensions/jake/src/main.ts b/code/extensions/jake/src/main.ts index a2511dc62df..654cc951b9c 100644 --- a/code/extensions/jake/src/main.ts +++ b/code/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/code/extensions/jake/tsconfig.json b/code/extensions/jake/tsconfig.json index 7234fdfeb97..22c47de77db 100644 --- a/code/extensions/jake/tsconfig.json +++ b/code/extensions/jake/tsconfig.json @@ -4,6 +4,9 @@ "outDir": "./out", "types": [ "node" + ], + "typeRoots": [ + "./node_modules/@types" ] }, "include": [ diff --git a/code/extensions/java/cgmanifest.json b/code/extensions/java/cgmanifest.json index ecfeb0eb668..ebb3d19beb5 100644 --- a/code/extensions/java/cgmanifest.json +++ b/code/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/code/extensions/javascript/javascript-language-configuration.json b/code/extensions/javascript/javascript-language-configuration.json index f458f66187f..7ca6762946c 100644 --- a/code/extensions/javascript/javascript-language-configuration.json +++ b/code/extensions/javascript/javascript-language-configuration.json @@ -26,6 +26,10 @@ ] ], "autoClosingPairs": [ + { + "open": "${", + "close": "}" + }, { "open": "{", "close": "}" @@ -70,6 +74,14 @@ } ], "surroundingPairs": [ + [ + "${", + "}" + ], + [ + "$", + "" + ], [ "{", "}" diff --git a/code/extensions/json-language-features/client/src/jsonClient.ts b/code/extensions/json-language-features/client/src/jsonClient.ts index 35fbb3da59d..6d832e6c159 100644 --- a/code/extensions/json-language-features/client/src/jsonClient.ts +++ b/code/extensions/json-language-features/client/src/jsonClient.ts @@ -771,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/code/extensions/json-language-features/client/tsconfig.json b/code/extensions/json-language-features/client/tsconfig.json index cf91914c874..bc775d950e5 100644 --- a/code/extensions/json-language-features/client/tsconfig.json +++ b/code/extensions/json-language-features/client/tsconfig.json @@ -6,6 +6,9 @@ "webworker" ], "module": "Node16", + "typeRoots": [ + "../node_modules/@types" + ] }, "include": [ "src/**/*", diff --git a/code/extensions/json-language-features/package-lock.json b/code/extensions/json-language-features/package-lock.json index 11c7b3a7a91..c7c66f40ccc 100644 --- a/code/extensions/json-language-features/package-lock.json +++ b/code/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/code/extensions/json-language-features/package.json b/code/extensions/json-language-features/package.json index 322e508b93e..50da0468e48 100644 --- a/code/extensions/json-language-features/package.json +++ b/code/extensions/json-language-features/package.json @@ -171,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/code/extensions/json-language-features/server/README.md b/code/extensions/json-language-features/server/README.md index 1c382916072..8047006f4e3 100644 --- a/code/extensions/json-language-features/server/README.md +++ b/code/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/code/extensions/json-language-features/server/package-lock.json b/code/extensions/json-language-features/server/package-lock.json index acf3ef20ed7..fc31206a0cd 100644 --- a/code/extensions/json-language-features/server/package-lock.json +++ b/code/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/code/extensions/json-language-features/server/package.json b/code/extensions/json-language-features/server/package.json index 5e91799aa2d..00fff97cbe7 100644 --- a/code/extensions/json-language-features/server/package.json +++ b/code/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/code/extensions/json-language-features/server/src/jsonServer.ts b/code/extensions/json-language-features/server/src/jsonServer.ts index 830ee8c4393..cbe1e7d02b4 100644 --- a/code/extensions/json-language-features/server/src/jsonServer.ts +++ b/code/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/code/extensions/json-language-features/server/tsconfig.json b/code/extensions/json-language-features/server/tsconfig.json index 424b140b4a0..07433e08b62 100644 --- a/code/extensions/json-language-features/server/tsconfig.json +++ b/code/extensions/json-language-features/server/tsconfig.json @@ -9,6 +9,9 @@ "WebWorker" ], "module": "Node16", + "typeRoots": [ + "../node_modules/@types" + ] }, "include": [ "src/**/*" diff --git a/code/extensions/json/build/update-grammars.js b/code/extensions/json/build/update-grammars.js index 2b7f76f8f90..13356a2c4c4 100644 --- a/code/extensions/json/build/update-grammars.js +++ b/code/extensions/json/build/update-grammars.js @@ -9,7 +9,7 @@ var updateGrammar = require('vscode-grammar-updater'); function adaptJSON(grammar, name, replacementScope, replaceeScope = 'json') { grammar.name = name; grammar.scopeName = `source${replacementScope}`; - const regex = new RegExp(`\.${replaceeScope}`, 'g'); + const regex = new RegExp(`\\.${replaceeScope}`, 'g'); var fixScopeNames = function (rule) { if (typeof rule.name === 'string') { rule.name = rule.name.replace(regex, replacementScope); diff --git a/code/extensions/json/syntaxes/snippets.tmLanguage.json b/code/extensions/json/syntaxes/snippets.tmLanguage.json index 289bc18f8c6..fd22457a797 100644 --- a/code/extensions/json/syntaxes/snippets.tmLanguage.json +++ b/code/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/code/extensions/latex/syntaxes/TeX.tmLanguage.json b/code/extensions/latex/syntaxes/TeX.tmLanguage.json index b31ccccb631..1a2e3211ae6 100644 --- a/code/extensions/latex/syntaxes/TeX.tmLanguage.json +++ b/code/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/code/extensions/mangle-loader.js b/code/extensions/mangle-loader.js index 016d0f69033..ed32a85e633 100644 --- a/code/extensions/mangle-loader.js +++ b/code/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/code/extensions/markdown-basics/package.json b/code/extensions/markdown-basics/package.json index baf50c80bb2..cb1351a71cf 100644 --- a/code/extensions/markdown-basics/package.json +++ b/code/extensions/markdown-basics/package.json @@ -28,6 +28,9 @@ ".mdtext", ".workbook" ], + "filenamePatterns": [ + "**/.cursor/**/*.mdc" + ], "configuration": "./language-configuration.json" } ], diff --git a/code/extensions/markdown-language-features/notebook/index.ts b/code/extensions/markdown-language-features/notebook/index.ts index b7c1471c7dc..1c6e68f4de8 100644 --- a/code/extensions/markdown-language-features/notebook/index.ts +++ b/code/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/code/extensions/markdown-language-features/notebook/tsconfig.json b/code/extensions/markdown-language-features/notebook/tsconfig.json index 426411ea857..90241aa7803 100644 --- a/code/extensions/markdown-language-features/notebook/tsconfig.json +++ b/code/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/code/extensions/markdown-language-features/package-lock.json b/code/extensions/markdown-language-features/package-lock.json index d8169c35e67..bb129cc3624 100644 --- a/code/extensions/markdown-language-features/package-lock.json +++ b/code/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", @@ -373,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" @@ -576,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/code/extensions/markdown-language-features/package.json b/code/extensions/markdown-language-features/package.json index 922fe087af5..1df0b43d840 100644 --- a/code/extensions/markdown-language-features/package.json +++ b/code/extensions/markdown-language-features/package.json @@ -19,7 +19,7 @@ "onLanguage:markdown", "onLanguage:prompt", "onLanguage:instructions", - "onLanguage:chatmode", + "onLanguage:chatagent", "onCommand:markdown.api.render", "onCommand:markdown.api.reloadPlugins", "onWebviewPanel:markdown.preview" @@ -181,13 +181,13 @@ "command": "markdown.editor.insertLinkFromWorkspace", "title": "%markdown.editor.insertLinkFromWorkspace%", "category": "Markdown", - "enablement": "editorLangId == markdown && !activeEditorIsReadonly" + "enablement": "editorLangId =~ /^(markdown|prompt|instructions|chatagent)$/ && !activeEditorIsReadonly" }, { "command": "markdown.editor.insertImageFromWorkspace", "title": "%markdown.editor.insertImageFromWorkspace%", "category": "Markdown", - "enablement": "editorLangId == markdown && !activeEditorIsReadonly" + "enablement": "editorLangId =~ /^(markdown|prompt|instructions|chatagent)$/ && !activeEditorIsReadonly" } ], "menus": { @@ -204,7 +204,7 @@ "editor/title": [ { "command": "markdown.showPreviewToSide", - "when": "editorLangId == markdown && !notebookEditorFocused && !hasCustomMarkdownPreview", + "when": "editorLangId =~ /^(markdown|prompt|instructions|chatagent)$/ && !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|chatagent)$/ && !hasCustomMarkdownPreview", "group": "navigation" }, { "command": "markdown.findAllFileReferences", - "when": "resourceLangId == markdown", + "when": "resourceLangId =~ /^(markdown|prompt|instructions|chatagent)$/", "group": "4_search" } ], "editor/title/context": [ { "command": "markdown.showPreview", - "when": "resourceLangId == markdown && !hasCustomMarkdownPreview", + "when": "resourceLangId =~ /^(markdown|prompt|instructions|chatagent)$/ && !hasCustomMarkdownPreview", "group": "1_open" }, { "command": "markdown.findAllFileReferences", - "when": "resourceLangId == markdown" + "when": "resourceLangId =~ /^(markdown|prompt|instructions|chatagent)$/" } ], "commandPalette": [ @@ -263,17 +263,17 @@ }, { "command": "markdown.showPreview", - "when": "editorLangId == markdown && !notebookEditorFocused", + "when": "editorLangId =~ /^(markdown|prompt|instructions|chatagent)$/ && !notebookEditorFocused", "group": "navigation" }, { "command": "markdown.showPreviewToSide", - "when": "editorLangId == markdown && !notebookEditorFocused", + "when": "editorLangId =~ /^(markdown|prompt|instructions|chatagent)$/ && !notebookEditorFocused", "group": "navigation" }, { "command": "markdown.showLockedPreviewToSide", - "when": "editorLangId == markdown && !notebookEditorFocused", + "when": "editorLangId =~ /^(markdown|prompt|instructions|chatagent)$/ && !notebookEditorFocused", "group": "navigation" }, { @@ -283,7 +283,7 @@ }, { "command": "markdown.showPreviewSecuritySelector", - "when": "editorLangId == markdown && !notebookEditorFocused" + "when": "editorLangId =~ /^(markdown|prompt|instructions|chatagent)$/ && !notebookEditorFocused" }, { "command": "markdown.showPreviewSecuritySelector", @@ -295,7 +295,7 @@ }, { "command": "markdown.preview.refresh", - "when": "editorLangId == markdown && !notebookEditorFocused" + "when": "editorLangId =~ /^(markdown|prompt|instructions|chatagent)$/ && !notebookEditorFocused" }, { "command": "markdown.preview.refresh", @@ -303,7 +303,7 @@ }, { "command": "markdown.findAllFileReferences", - "when": "editorLangId == markdown" + "when": "editorLangId =~ /^(markdown|prompt|instructions|chatagent)$/" } ] }, @@ -312,13 +312,13 @@ "command": "markdown.showPreview", "key": "shift+ctrl+v", "mac": "shift+cmd+v", - "when": "editorLangId == markdown && !notebookEditorFocused" + "when": "editorLangId =~ /^(markdown|prompt|instructions|chatagent)$/ && !notebookEditorFocused" }, { "command": "markdown.showPreviewToSide", "key": "ctrl+k v", "mac": "cmd+k v", - "when": "editorLangId == markdown && !notebookEditorFocused" + "when": "editorLangId =~ /^(markdown|prompt|instructions|chatagent)$/ && !notebookEditorFocused" } ], "configuration": { @@ -759,7 +759,7 @@ "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-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", @@ -767,7 +767,7 @@ }, "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/code/extensions/markdown-language-features/preview-src/index.ts b/code/extensions/markdown-language-features/preview-src/index.ts index 8bb5440ffe1..b6200b8ceb9 100644 --- a/code/extensions/markdown-language-features/preview-src/index.ts +++ b/code/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') { @@ -434,6 +441,7 @@ function domEval(el: Element): void { for (const key of preservedScriptAttributes) { 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/code/extensions/markdown-language-features/preview-src/scroll-sync.ts b/code/extensions/markdown-language-features/preview-src/scroll-sync.ts index cba22fc48d5..33d81094cb5 100644 --- a/code/extensions/markdown-language-features/preview-src/scroll-sync.ts +++ b/code/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/code/extensions/markdown-language-features/preview-src/tsconfig.json b/code/extensions/markdown-language-features/preview-src/tsconfig.json index e401dbe43fb..4001ffeb068 100644 --- a/code/extensions/markdown-language-features/preview-src/tsconfig.json +++ b/code/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/code/extensions/markdown-language-features/src/extension.shared.ts b/code/extensions/markdown-language-features/src/extension.shared.ts index e062666c748..6f245bd8ab7 100644 --- a/code/extensions/markdown-language-features/src/extension.shared.ts +++ b/code/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/code/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts b/code/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts index 48e579da7fc..095e8f25c12 100644 --- a/code/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts +++ b/code/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/code/extensions/markdown-language-features/src/languageFeatures/linkUpdater.ts b/code/extensions/markdown-language-features/src/languageFeatures/linkUpdater.ts index 50a4d90c570..5d42a033842 100644 --- a/code/extensions/markdown-language-features/src/languageFeatures/linkUpdater.ts +++ b/code/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'; @@ -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/code/extensions/markdown-language-features/src/markdownEngine.ts b/code/extensions/markdown-language-features/src/markdownEngine.ts index ea8ce5c90c7..e2cea47e718 100644 --- a/code/extensions/markdown-language-features/src/markdownEngine.ts +++ b/code/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 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) { diff --git a/code/extensions/markdown-language-features/src/preview/documentRenderer.ts b/code/extensions/markdown-language-features/src/preview/documentRenderer.ts index c7bc75b9849..61182a24436 100644 --- a/code/extensions/markdown-language-features/src/preview/documentRenderer.ts +++ b/code/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/code/extensions/markdown-language-features/src/preview/preview.ts b/code/extensions/markdown-language-features/src/preview/preview.ts index 1a7a859d446..19d1755e7eb 100644 --- a/code/extensions/markdown-language-features/src/preview/preview.ts +++ b/code/extensions/markdown-language-features/src/preview/preview.ts @@ -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/code/extensions/markdown-language-features/src/slugify.ts b/code/extensions/markdown-language-features/src/slugify.ts index 0d4b1896d8c..645d9ab6ae8 100644 --- a/code/extensions/markdown-language-features/src/slugify.ts +++ b/code/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/code/extensions/markdown-language-features/src/util/dom.ts b/code/extensions/markdown-language-features/src/util/dom.ts index 8bbce79c303..16c825c68ff 100644 --- a/code/extensions/markdown-language-features/src/util/dom.ts +++ b/code/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/code/extensions/markdown-language-features/src/util/file.ts b/code/extensions/markdown-language-features/src/util/file.ts index f99135b0200..aa793045278 100644 --- a/code/extensions/markdown-language-features/src/util/file.ts +++ b/code/extensions/markdown-language-features/src/util/file.ts @@ -19,7 +19,7 @@ export const markdownFileExtensions = Object.freeze([ 'workbook', ]); -export const markdownLanguageIds = ['markdown', 'prompt', 'instructions', 'chatmode']; +export const markdownLanguageIds = ['markdown', 'prompt', 'instructions', 'chatagent']; export function isMarkdownFile(document: vscode.TextDocument) { return markdownLanguageIds.indexOf(document.languageId) !== -1; diff --git a/code/extensions/markdown-language-features/src/util/uuid.ts b/code/extensions/markdown-language-features/src/util/uuid.ts new file mode 100644 index 00000000000..ca420b3b6af --- /dev/null +++ b/code/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/code/extensions/markdown-language-features/tsconfig.browser.json b/code/extensions/markdown-language-features/tsconfig.browser.json index e4c0db0fd10..dbacbb22fdf 100644 --- a/code/extensions/markdown-language-features/tsconfig.browser.json +++ b/code/extensions/markdown-language-features/tsconfig.browser.json @@ -1,8 +1,6 @@ { "extends": "./tsconfig.json", - "compilerOptions": { - "types": [] - }, + "compilerOptions": {}, "exclude": [ "./src/test/**" ] diff --git a/code/extensions/markdown-language-features/tsconfig.json b/code/extensions/markdown-language-features/tsconfig.json index fcd79775de5..0e7a865e1f5 100644 --- a/code/extensions/markdown-language-features/tsconfig.json +++ b/code/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/code/extensions/markdown-math/notebook/tsconfig.json b/code/extensions/markdown-math/notebook/tsconfig.json index ae9e837683c..def3077d238 100644 --- a/code/extensions/markdown-math/notebook/tsconfig.json +++ b/code/extensions/markdown-math/notebook/tsconfig.json @@ -8,6 +8,12 @@ "ES2024", "DOM", "DOM.Iterable" + ], + "types": [ + "node" + ], + "typeRoots": [ + "../node_modules/@types" ] } } diff --git a/code/extensions/markdown-math/tsconfig.json b/code/extensions/markdown-math/tsconfig.json index c5194e2e33c..40e645a1ed6 100644 --- a/code/extensions/markdown-math/tsconfig.json +++ b/code/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/code/extensions/media-preview/media/imagePreview.js b/code/extensions/media-preview/media/imagePreview.js index ab8ad542a2d..d31728e76bc 100644 --- a/code/extensions/media-preview/media/imagePreview.js +++ b/code/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/code/extensions/media-preview/package-lock.json b/code/extensions/media-preview/package-lock.json index d26855f3ad2..fcd827cb0c3 100644 --- a/code/extensions/media-preview/package-lock.json +++ b/code/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/code/extensions/media-preview/package.json b/code/extensions/media-preview/package.json index 02b0134e4cf..3f7e1c01653 100644 --- a/code/extensions/media-preview/package.json +++ b/code/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/code/extensions/media-preview/src/audioPreview.ts b/code/extensions/media-preview/src/audioPreview.ts index 5058f7e978e..282d579b380 100644 --- a/code/extensions/media-preview/src/audioPreview.ts +++ b/code/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/code/extensions/media-preview/src/imagePreview/index.ts b/code/extensions/media-preview/src/imagePreview/index.ts index b405cd652c4..6c2c8a73f66 100644 --- a/code/extensions/media-preview/src/imagePreview/index.ts +++ b/code/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/code/extensions/media-preview/src/util/dom.ts b/code/extensions/media-preview/src/util/dom.ts index 0f6c00da9da..f89d668c74d 100644 --- a/code/extensions/media-preview/src/util/dom.ts +++ b/code/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/code/extensions/media-preview/src/util/uuid.ts b/code/extensions/media-preview/src/util/uuid.ts new file mode 100644 index 00000000000..ca420b3b6af --- /dev/null +++ b/code/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/code/extensions/media-preview/src/videoPreview.ts b/code/extensions/media-preview/src/videoPreview.ts index 67012128cf7..1cb74c58426 100644 --- a/code/extensions/media-preview/src/videoPreview.ts +++ b/code/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/code/extensions/media-preview/tsconfig.json b/code/extensions/media-preview/tsconfig.json index fcd79775de5..796a159a61c 100644 --- a/code/extensions/media-preview/tsconfig.json +++ b/code/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/code/extensions/merge-conflict/tsconfig.json b/code/extensions/merge-conflict/tsconfig.json index 7234fdfeb97..22c47de77db 100644 --- a/code/extensions/merge-conflict/tsconfig.json +++ b/code/extensions/merge-conflict/tsconfig.json @@ -4,6 +4,9 @@ "outDir": "./out", "types": [ "node" + ], + "typeRoots": [ + "./node_modules/@types" ] }, "include": [ diff --git a/code/extensions/mermaid-chat-features/.gitignore b/code/extensions/mermaid-chat-features/.gitignore new file mode 100644 index 00000000000..2877bd189bb --- /dev/null +++ b/code/extensions/mermaid-chat-features/.gitignore @@ -0,0 +1 @@ +chat-webview-out diff --git a/code/extensions/mermaid-chat-features/.npmrc b/code/extensions/mermaid-chat-features/.npmrc new file mode 100644 index 00000000000..a9c57709666 --- /dev/null +++ b/code/extensions/mermaid-chat-features/.npmrc @@ -0,0 +1,2 @@ +legacy-peer-deps="true" +timeout=180000 diff --git a/code/extensions/mermaid-chat-features/.vscodeignore b/code/extensions/mermaid-chat-features/.vscodeignore new file mode 100644 index 00000000000..4722e586990 --- /dev/null +++ b/code/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/code/extensions/mermaid-chat-features/README.md b/code/extensions/mermaid-chat-features/README.md new file mode 100644 index 00000000000..4df5d17d156 --- /dev/null +++ b/code/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/code/extensions/mermaid-chat-features/cgmanifest.json b/code/extensions/mermaid-chat-features/cgmanifest.json new file mode 100644 index 00000000000..0c39c97297b --- /dev/null +++ b/code/extensions/mermaid-chat-features/cgmanifest.json @@ -0,0 +1,4 @@ +{ + "registrations": [], + "version": 1 +} diff --git a/code/extensions/mermaid-chat-features/chat-webview-src/index.ts b/code/extensions/mermaid-chat-features/chat-webview-src/index.ts new file mode 100644 index 00000000000..9b3c9df71b6 --- /dev/null +++ b/code/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/code/extensions/mermaid-chat-features/chat-webview-src/tsconfig.json b/code/extensions/mermaid-chat-features/chat-webview-src/tsconfig.json new file mode 100644 index 00000000000..a57ffcaeba0 --- /dev/null +++ b/code/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/code/extensions/mermaid-chat-features/esbuild-chat-webview.mjs b/code/extensions/mermaid-chat-features/esbuild-chat-webview.mjs new file mode 100644 index 00000000000..b23de5746fa --- /dev/null +++ b/code/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/code/extensions/mermaid-chat-features/extension-browser.webpack.config.js b/code/extensions/mermaid-chat-features/extension-browser.webpack.config.js new file mode 100644 index 00000000000..b758f2d8155 --- /dev/null +++ b/code/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/code/extensions/mermaid-chat-features/extension.webpack.config.js b/code/extensions/mermaid-chat-features/extension.webpack.config.js new file mode 100644 index 00000000000..4928186ae55 --- /dev/null +++ b/code/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/code/extensions/mermaid-chat-features/package-lock.json b/code/extensions/mermaid-chat-features/package-lock.json new file mode 100644 index 00000000000..185afcde646 --- /dev/null +++ b/code/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/code/extensions/mermaid-chat-features/package.json b/code/extensions/mermaid-chat-features/package.json new file mode 100644 index 00000000000..2311521c9b1 --- /dev/null +++ b/code/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/code/extensions/mermaid-chat-features/package.nls.json b/code/extensions/mermaid-chat-features/package.nls.json new file mode 100644 index 00000000000..d2fe3d44c30 --- /dev/null +++ b/code/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/code/extensions/mermaid-chat-features/src/extension.ts b/code/extensions/mermaid-chat-features/src/extension.ts new file mode 100644 index 00000000000..51294649f4f --- /dev/null +++ b/code/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/code/extensions/mermaid-chat-features/src/uuid.ts b/code/extensions/mermaid-chat-features/src/uuid.ts new file mode 100644 index 00000000000..ca420b3b6af --- /dev/null +++ b/code/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/code/extensions/mermaid-chat-features/tsconfig.json b/code/extensions/mermaid-chat-features/tsconfig.json new file mode 100644 index 00000000000..35a9a9ad8a0 --- /dev/null +++ b/code/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/code/extensions/microsoft-authentication/.vscodeignore b/code/extensions/microsoft-authentication/.vscodeignore index e7feddb5da8..e2daf4b8a89 100644 --- a/code/extensions/microsoft-authentication/.vscodeignore +++ b/code/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/code/extensions/microsoft-authentication/extension-browser.webpack.config.js b/code/extensions/microsoft-authentication/extension-browser.webpack.config.js deleted file mode 100644 index daf3fdf8447..00000000000 --- a/code/extensions/microsoft-authentication/extension-browser.webpack.config.js +++ /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 -import path from 'path'; -import { browser as withBrowserDefaults } from '../shared.webpack.config.mjs'; - -export default withBrowserDefaults({ - context: import.meta.dirname, - node: { - global: true, - __filename: false, - __dirname: false, - }, - entry: { - extension: './src/extension.ts', - }, - resolve: { - alias: { - './node/authServer': path.resolve(import.meta.dirname, 'src/browser/authServer'), - './node/buffer': path.resolve(import.meta.dirname, 'src/browser/buffer'), - './node/fetch': path.resolve(import.meta.dirname, 'src/browser/fetch'), - './node/authProvider': path.resolve(import.meta.dirname, 'src/browser/authProvider'), - } - } -}); diff --git a/code/extensions/microsoft-authentication/extension.webpack.config.js b/code/extensions/microsoft-authentication/extension.webpack.config.js index 2182e98e213..a46d5a527df 100644 --- a/code/extensions/microsoft-authentication/extension.webpack.config.js +++ b/code/extensions/microsoft-authentication/extension.webpack.config.js @@ -8,20 +8,41 @@ import CopyWebpackPlugin from 'copy-webpack-plugin'; import path from 'path'; const isWindows = process.platform === 'win32'; -const windowsArches = ['x64']; const isMacOS = process.platform === 'darwin'; -const macOSArches = ['arm64']; +const isLinux = !isWindows && !isMacOS; + +const windowsArches = ['x64']; +const linuxArches = ['x64']; + +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 arch = process.arch; +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 && macOSArches.includes(arch))) { +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/(lib|)msal*.(node|dll|dylib)', + from: `**/dist/${platformFolder}/${arch}/(lib|)msal*.(node|dll|dylib|so)`, to: '[name][ext]' } ] diff --git a/code/extensions/microsoft-authentication/package-lock.json b/code/extensions/microsoft-authentication/package-lock.json index 173f118f0aa..7c21b73e9ea 100644 --- a/code/extensions/microsoft-authentication/package-lock.json +++ b/code/extensions/microsoft-authentication/package-lock.json @@ -13,8 +13,8 @@ ], "dependencies": { "@azure/ms-rest-azure-env": "^2.0.0", - "@azure/msal-node": "^3.7.3", - "@azure/msal-node-extensions": "^1.5.22", + "@azure/msal-node": "^3.8.3", + "@azure/msal-node-extensions": "^1.5.25", "@vscode/extension-telemetry": "^0.9.8", "keytar": "workspace:*", "vscode-tas-client": "^0.1.84" @@ -36,21 +36,21 @@ "integrity": "sha512-dG76W7ElfLi+fbTjnZVGj+M9e0BIEJmRxU6fHaUQ12bZBe8EJKYb2GV50YWNaP2uJiVQ5+7nXEVj1VN1UQtaEw==" }, "node_modules/@azure/msal-common": { - "version": "15.12.0", - "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-15.12.0.tgz", - "integrity": "sha512-4ucXbjVw8KJ5QBgnGJUeA07c8iznwlk5ioHIhI4ASXcXgcf2yRFhWzYOyWg/cI49LC9ekpFJeQtO3zjDTbl6TQ==", + "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": "3.7.3", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-3.7.3.tgz", - "integrity": "sha512-MoJxkKM/YpChfq4g2o36tElyzNUMG8mfD6u8NbuaPAsqfGpaw249khAcJYNoIOigUzRw45OjXCOrexE6ImdUxg==", + "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": "15.12.0", + "@azure/msal-common": "15.13.2", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" }, @@ -59,14 +59,14 @@ } }, "node_modules/@azure/msal-node-extensions": { - "version": "1.5.22", - "resolved": "https://registry.npmjs.org/@azure/msal-node-extensions/-/msal-node-extensions-1.5.22.tgz", - "integrity": "sha512-pdHcoyxmCszjXhc59jZ+7WKH0ZqebH0ZVvKlBwFD57TQzAOnXPS6q76/dsVO/Y7EXAlvkoCmcebNc6eP4cQulA==", + "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": "15.12.0", - "@azure/msal-node-runtime": "^0.19.0", + "@azure/msal-common": "15.13.2", + "@azure/msal-node-runtime": "^0.20.0", "keytar": "^7.8.0" }, "engines": { @@ -74,9 +74,9 @@ } }, "node_modules/@azure/msal-node-runtime": { - "version": "0.19.4", - "resolved": "https://registry.npmjs.org/@azure/msal-node-runtime/-/msal-node-runtime-0.19.4.tgz", - "integrity": "sha512-v90QdV/VKG6gvHx2bQp82yRCJ8IGG7OOk6gDQgbKvoHtOs7mSEz2CIqWydUpwCryJkUwgNfgMPAMoGhe/a4fOw==", + "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" }, @@ -271,7 +271,8 @@ "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", @@ -327,6 +328,7 @@ "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" } @@ -523,21 +525,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" } }, @@ -635,7 +639,8 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/semver": { "version": "7.6.2", diff --git a/code/extensions/microsoft-authentication/package.json b/code/extensions/microsoft-authentication/package.json index 63aa999bbf9..d4083775d82 100644 --- a/code/extensions/microsoft-authentication/package.json +++ b/code/extensions/microsoft-authentication/package.json @@ -14,7 +14,6 @@ ], "activationEvents": [], "enabledApiProposals": [ - "idToken", "nativeWindowHandle", "authIssuers", "authenticationChallenges" @@ -111,13 +110,11 @@ "default": "msal", "enum": [ "msal", - "msal-no-broker", - "classic" + "msal-no-broker" ], "enumDescriptions": [ "%microsoft-authentication.implementation.enumDescriptions.msal%", - "%microsoft-authentication.implementation.enumDescriptions.msal-no-broker%", - "%microsoft-authentication.implementation.enumDescriptions.classic%" + "%microsoft-authentication.implementation.enumDescriptions.msal-no-broker%" ], "markdownDescription": "%microsoft-authentication.implementation.description%", "tags": [ @@ -130,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", @@ -147,8 +141,8 @@ }, "dependencies": { "@azure/ms-rest-azure-env": "^2.0.0", - "@azure/msal-node": "^3.7.3", - "@azure/msal-node-extensions": "^1.5.22", + "@azure/msal-node": "^3.8.3", + "@azure/msal-node-extensions": "^1.5.25", "@vscode/extension-telemetry": "^0.9.8", "keytar": "workspace:*", "vscode-tas-client": "^0.1.84" diff --git a/code/extensions/microsoft-authentication/package.nls.json b/code/extensions/microsoft-authentication/package.nls.json index 3b14adfa58e..4fcd2d27b74 100644 --- a/code/extensions/microsoft-authentication/package.nls.json +++ b/code/extensions/microsoft-authentication/package.nls.json @@ -3,16 +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 in a future release. If the `msal` implementation does not work 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.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-authentication.implementation.enumDescriptions.classic": "(deprecated) Use the classic authentication flow to sign in with a Microsoft account.", "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/code/extensions/microsoft-authentication/src/AADHelper.ts b/code/extensions/microsoft-authentication/src/AADHelper.ts deleted file mode 100644 index 8117b0b0f5b..00000000000 --- a/code/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/code/extensions/microsoft-authentication/src/browser/authProvider.ts b/code/extensions/microsoft-authentication/src/browser/authProvider.ts deleted file mode 100644 index 3b4da5b18fa..00000000000 --- a/code/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/code/extensions/microsoft-authentication/src/browser/authServer.ts b/code/extensions/microsoft-authentication/src/browser/authServer.ts deleted file mode 100644 index 60b53c713a8..00000000000 --- a/code/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/code/extensions/microsoft-authentication/src/browser/buffer.ts b/code/extensions/microsoft-authentication/src/browser/buffer.ts deleted file mode 100644 index 794bb19f579..00000000000 --- a/code/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/code/extensions/microsoft-authentication/src/browser/fetch.ts b/code/extensions/microsoft-authentication/src/browser/fetch.ts deleted file mode 100644 index c61281ca8f8..00000000000 --- a/code/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/code/extensions/microsoft-authentication/src/common/loopbackClientAndOpener.ts b/code/extensions/microsoft-authentication/src/common/loopbackClientAndOpener.ts index e68663efe43..f7e41805d70 100644 --- a/code/extensions/microsoft-authentication/src/common/loopbackClientAndOpener.ts +++ b/code/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/code/extensions/microsoft-authentication/src/common/publicClientCache.ts b/code/extensions/microsoft-authentication/src/common/publicClientCache.ts index dfbca6b5057..f39ae8297f9 100644 --- a/code/extensions/microsoft-authentication/src/common/publicClientCache.ts +++ b/code/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,6 +10,7 @@ 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[]; diff --git a/code/extensions/microsoft-authentication/src/common/telemetryReporter.ts b/code/extensions/microsoft-authentication/src/common/telemetryReporter.ts index 8fd26a54060..5fe773a877e 100644 --- a/code/extensions/microsoft-authentication/src/common/telemetryReporter.ts +++ b/code/extensions/microsoft-authentication/src/common/telemetryReporter.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { AuthError } from '@azure/msal-node'; +import { AuthError, ClientAuthError } from '@azure/msal-node'; import TelemetryReporter, { TelemetryEventProperties } from '@vscode/extension-telemetry'; import { IExperimentationTelemetry } from 'vscode-tas-client'; @@ -43,17 +43,6 @@ export class MicrosoftAuthenticationTelemetryReporter implements IExperimentatio this._telemetryReporter.sendTelemetryEvent('activatingmsalnobroker'); } - sendActivatedWithClassicImplementationEvent(reason: 'setting' | 'web'): void { - /* __GDPR__ - "activatingClassic" : { - "owner": "TylerLeonhardt", - "comment": "Used to determine how often users use the classic login flow.", - "reason": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Why classic was used" } - } - */ - this._telemetryReporter.sendTelemetryEvent('activatingClassic', { reason }); - } - sendLoginEvent(scopes: readonly string[]): void { /* __GDPR__ "login" : { @@ -86,7 +75,7 @@ export class MicrosoftAuthenticationTelemetryReporter implements IExperimentatio this._telemetryReporter.sendTelemetryEvent('logoutFailed'); } - sendTelemetryErrorEvent(error: unknown): void { + sendTelemetryErrorEvent(error: Error | string): void { let errorMessage: string | undefined; let errorName: string | undefined; let errorCode: string | undefined; @@ -94,7 +83,7 @@ export class MicrosoftAuthenticationTelemetryReporter implements IExperimentatio if (typeof error === 'string') { errorMessage = error; } else { - const authError: AuthError = error as any; + const authError: AuthError = error as AuthError; // don't set error message or stack because it contains PII errorCode = authError.errorCode; errorCorrelationId = authError.correlationId; @@ -119,6 +108,43 @@ export class MicrosoftAuthenticationTelemetryReporter implements IExperimentatio }); } + 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 + }); + } + /** * Sends an event for an account type available at startup. * @param scopes The scopes for the session diff --git a/code/extensions/microsoft-authentication/src/common/test/loopbackClientAndOpener.test.ts b/code/extensions/microsoft-authentication/src/common/test/loopbackClientAndOpener.test.ts index 69d7afaa38a..31375af860f 100644 --- a/code/extensions/microsoft-authentication/src/common/test/loopbackClientAndOpener.test.ts +++ b/code/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/code/extensions/microsoft-authentication/src/extension.ts b/code/extensions/microsoft-authentication/src/extension.ts index 620a10e1a29..c7cf62b15e3 100644 --- a/code/extensions/microsoft-authentication/src/extension.ts +++ b/code/extensions/microsoft-authentication/src/extension.ts @@ -3,13 +3,71 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { commands, ExtensionContext, l10n, window, workspace } from 'vscode'; -import * as extensionV1 from './extensionV1'; -import * as extensionV2 from './extensionV2'; -import { MicrosoftAuthenticationTelemetryReporter } from './common/telemetryReporter'; +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'; -let implementation: 'msal' | 'msal-no-broker' | 'classic' = 'msal'; -const getImplementation = () => workspace.getConfiguration('microsoft-authentication').get<'msal' | 'msal-no-broker' | 'classic'>('implementation') ?? '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 (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, supportsChallenges: true } + ); + context.subscriptions.push(disposable); + return disposable; +} export async function activate(context: ExtensionContext) { const mainTelemetryReporter = new MicrosoftAuthenticationTelemetryReporter(context.extension.packageJSON.aiKey); @@ -39,34 +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 (!isNodeEnvironment) { - mainTelemetryReporter.sendActivatedWithClassicImplementationEvent('web'); - return await extensionV1.activate(context, mainTelemetryReporter.telemetryReporter); - } switch (implementation) { case 'msal-no-broker': mainTelemetryReporter.sendActivatedWithMsalNoBrokerEvent(); - await extensionV2.activate(context, mainTelemetryReporter); - break; - case 'classic': - mainTelemetryReporter.sendActivatedWithClassicImplementationEvent('setting'); - await extensionV1.activate(context, mainTelemetryReporter.telemetryReporter); break; case 'msal': default: - await extensionV2.activate(context, mainTelemetryReporter); 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 (implementation !== 'classic') { - extensionV2.deactivate(); - } else { - extensionV1.deactivate(); - } + Logger.info('Microsoft Authentication is deactivating...'); } diff --git a/code/extensions/microsoft-authentication/src/extensionV1.ts b/code/extensions/microsoft-authentication/src/extensionV1.ts deleted file mode 100644 index 02248dd989d..00000000000 --- a/code/extensions/microsoft-authentication/src/extensionV1.ts +++ /dev/null @@ -1,193 +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/*'), - 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/code/extensions/microsoft-authentication/src/extensionV2.ts b/code/extensions/microsoft-authentication/src/extensionV2.ts deleted file mode 100644 index bafc8454f8c..00000000000 --- a/code/extensions/microsoft-authentication/src/extensionV2.ts +++ /dev/null @@ -1,102 +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, supportsChallenges: 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, - 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() { } diff --git a/code/extensions/microsoft-authentication/src/node/authProvider.ts b/code/extensions/microsoft-authentication/src/node/authProvider.ts index bccfcc2e9e4..6b980dc7641 100644 --- a/code/extensions/microsoft-authentication/src/node/authProvider.ts +++ b/code/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, SilentFlowRequest } from '@azure/msal-node'; -import { AuthenticationChallenge, AuthenticationConstraint, 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,15 +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 { DEFAULT_REDIRECT_URI } from '../common/env'; +import { isSupportedClient } from '../common/env'; 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 }[]; @@ -209,11 +224,10 @@ 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 }); @@ -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); @@ -290,8 +305,11 @@ export class MsalAuthProvider implements AuthenticationProvider { async getSessionsFromChallenges(constraint: AuthenticationConstraint, options: AuthenticationProviderSessionOptions): Promise { this._logger.info('[getSessionsFromChallenges]', 'starting with', constraint.challenges.length, 'challenges'); - // Use scopes from constraint if provided, otherwise extract from challenges - const scopes = constraint.scopes?.length ? [...constraint.scopes] : this.extractScopesFromChallenges(constraint.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'); @@ -309,8 +327,11 @@ export class MsalAuthProvider implements AuthenticationProvider { async createSessionFromChallenges(constraint: AuthenticationConstraint, options: AuthenticationProviderSessionOptions): Promise { this._logger.info('[createSessionFromChallenges]', 'starting with', constraint.challenges.length, 'challenges'); - // Use scopes from constraint if provided, otherwise extract from challenges - const scopes = constraint.scopes?.length ? [...constraint.scopes] : this.extractScopesFromChallenges(constraint.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 @@ -339,12 +360,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, - isBrokerSupported: cachedPca.isBrokerAvailable + 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(); @@ -367,7 +387,8 @@ export class MsalAuthProvider implements AuthenticationProvider { windowHandle: window.nativeHandle ? Buffer.from(window.nativeHandle) : undefined, logger: this._logger, uriHandler: this._uriHandler, - claims: scopeData.claims + claims: scopeData.claims, + callbackUri }; const result = await flow.trigger(authRequest); @@ -392,14 +413,13 @@ export class MsalAuthProvider implements AuthenticationProvider { throw lastError ?? new Error('No auth flow succeeded'); } - private extractScopesFromChallenges(challenges: readonly AuthenticationChallenge[]): string[] { - const scopes: string[] = []; + private extractScopesFromChallenges(challenges: readonly AuthenticationChallenge[]): string[] | undefined { for (const challenge of challenges) { if (challenge.scheme.toLowerCase() === 'bearer' && challenge.params.scope) { - scopes.push(...challenge.params.scope.split(' ')); + return challenge.params.scope.split(' '); } } - return scopes; + return undefined; } private extractClaimsFromChallenges(challenges: readonly AuthenticationChallenge[]): string | undefined { @@ -490,6 +510,7 @@ export class MsalAuthProvider implements AuthenticationProvider { 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, @@ -502,7 +523,11 @@ 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/code/extensions/microsoft-authentication/src/node/cachedPublicClientApplication.ts b/code/extensions/microsoft-authentication/src/node/cachedPublicClientApplication.ts index 6c11983b6c3..e86269833a8 100644 --- a/code/extensions/microsoft-authentication/src/node/cachedPublicClientApplication.ts +++ b/code/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, BrokerOptions } 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, workspace } 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'; @@ -51,8 +51,8 @@ export class CachedPublicClientApplication implements ICachedPublicClientApplica const loggerOptions = new MsalLoggerOptions(_logger, telemetryReporter); let broker: BrokerOptions | undefined; - if (process.platform !== 'win32') { - this._logger.info(`[${this._clientId}] Native Broker is only available on Windows`); + 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 { @@ -228,6 +228,81 @@ export class CachedPublicClientApplication implements ICachedPublicClientApplica 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) { return this._accountAccess.setAllowedAccess(account, false); diff --git a/code/extensions/microsoft-authentication/src/node/flows.ts b/code/extensions/microsoft-authentication/src/node/flows.ts index 2457f69cba0..22782330bd6 100644 --- a/code/extensions/microsoft-authentication/src/node/flows.ts +++ b/code/extensions/microsoft-authentication/src/node/flows.ts @@ -14,20 +14,21 @@ import { Config } from '../common/config'; 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; @@ -45,7 +46,8 @@ class DefaultLoopbackFlow implements IMsalFlow { label = 'default'; options: IMsalFlowOptions = { supportsRemoteExtensionHost: false, - supportsWebWorkerExtensionHost: false + supportsUnsupportedClient: true, + supportsBroker: true }; async trigger({ cachedPca, authority, scopes, claims, loginHint, windowHandle, logger }: IMsalFlowTriggerOptions): Promise { @@ -73,12 +75,13 @@ class UrlHandlerFlow implements IMsalFlow { label = 'protocol handler'; options: IMsalFlowOptions = { supportsRemoteExtensionHost: true, - supportsWebWorkerExtensionHost: false + supportsUnsupportedClient: false, + supportsBroker: false }; - async trigger({ cachedPca, authority, scopes, claims, 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, DEFAULT_REDIRECT_URI, logger); + const loopbackClient = new UriHandlerLoopbackClient(uriHandler, DEFAULT_REDIRECT_URI, callbackUri, logger); let redirectUri: string | undefined; if (cachedPca.isBrokerAvailable && process.platform === 'darwin') { redirectUri = Config.macOSBrokerRedirectUri; @@ -97,13 +100,33 @@ class UrlHandlerFlow implements IMsalFlow { } } +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; } @@ -111,20 +134,13 @@ export function getMsalFlows(query: IMsalFlowQuery): IMsalFlow[] { 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); - if (query.isBrokerSupported) { - // If broker is supported, only use the first valid flow - return flows; - } } } return flows; diff --git a/code/extensions/microsoft-authentication/src/node/test/flows.test.ts b/code/extensions/microsoft-authentication/src/node/test/flows.test.ts new file mode 100644 index 00000000000..b2685e783cc --- /dev/null +++ b/code/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/code/extensions/microsoft-authentication/tsconfig.json b/code/extensions/microsoft-authentication/tsconfig.json index 59947e2d995..e9a3ade3ed5 100644 --- a/code/extensions/microsoft-authentication/tsconfig.json +++ b/code/extensions/microsoft-authentication/tsconfig.json @@ -1,27 +1,14 @@ { "extends": "../tsconfig.base.json", "compilerOptions": { - "baseUrl": ".", - "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.authenticationChallenges.d.ts" diff --git a/code/extensions/notebook-renderers/package-lock.json b/code/extensions/notebook-renderers/package-lock.json index 8dbc5f5ad4c..85357e3c855 100644 --- a/code/extensions/notebook-renderers/package-lock.json +++ b/code/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/code/extensions/notebook-renderers/package.json b/code/extensions/notebook-renderers/package.json index b4a5236f7d8..77c042ee663 100644 --- a/code/extensions/notebook-renderers/package.json +++ b/code/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" }, @@ -46,9 +46,9 @@ "watch": "npx gulp compile-watch:notebook-renderers", "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/code/extensions/notebook-renderers/src/htmlHelper.ts b/code/extensions/notebook-renderers/src/htmlHelper.ts index 819a3a640af..dd22022ce3d 100644 --- a/code/extensions/notebook-renderers/src/htmlHelper.ts +++ b/code/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/code/extensions/notebook-renderers/src/index.ts b/code/extensions/notebook-renderers/src/index.ts index dfff75d617e..09d4129e817 100644 --- a/code/extensions/notebook-renderers/src/index.ts +++ b/code/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/code/extensions/notebook-renderers/src/test/linkify.test.ts b/code/extensions/notebook-renderers/src/test/linkify.test.ts index cae8f569423..d24145a2b62 100644 --- a/code/extensions/notebook-renderers/src/test/linkify.test.ts +++ b/code/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/code/extensions/notebook-renderers/src/test/notebookRenderer.test.ts b/code/extensions/notebook-renderers/src/test/notebookRenderer.test.ts index 9dc8f6c845e..a193ce38d72 100644 --- a/code/extensions/notebook-renderers/src/test/notebookRenderer.test.ts +++ b/code/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/code/extensions/notebook-renderers/tsconfig.json b/code/extensions/notebook-renderers/tsconfig.json index 3472d5bbaa7..07c5ef470f5 100644 --- a/code/extensions/notebook-renderers/tsconfig.json +++ b/code/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/code/extensions/npm/package-lock.json b/code/extensions/npm/package-lock.json index 352ee31ae9f..694e98b5e12 100644 --- a/code/extensions/npm/package-lock.json +++ b/code/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/code/extensions/npm/src/tasks.ts b/code/extensions/npm/src/tasks.ts index 19a45488c07..ba833705cb4 100644 --- a/code/extensions/npm/src/tasks.ts +++ b/code/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/code/extensions/npm/tsconfig.json b/code/extensions/npm/tsconfig.json index ec12eb547b3..0c84fcc94e3 100644 --- a/code/extensions/npm/tsconfig.json +++ b/code/extensions/npm/tsconfig.json @@ -4,6 +4,9 @@ "outDir": "./out", "types": [ "node" + ], + "typeRoots": [ + "./node_modules/@types" ] }, "include": [ diff --git a/code/extensions/package-lock.json b/code/extensions/package-lock.json index f0f606506e1..9b3de520619 100644 --- a/code/extensions/package-lock.json +++ b/code/extensions/package-lock.json @@ -10,11 +10,10 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { - "typescript": "^5.9.2" + "typescript": "^5.9.3" }, "devDependencies": { - "@parcel/watcher": "2.5.1", - "crypto": "1.0.1", + "@vscode/watcher": "bpasero/watcher#8ecffb4a57df24ac3e6946aa095b9b1f14f8bba9", "esbuild": "0.25.0", "vscode-grammar-updater": "^1.1.0" } @@ -444,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" @@ -460,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" @@ -798,16 +509,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": { @@ -948,9 +656,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/code/extensions/package.json b/code/extensions/package.json index c1b6e35789c..9db9d47371e 100644 --- a/code/extensions/package.json +++ b/code/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", "crypto": "1.0.1" diff --git a/code/extensions/php-language-features/tsconfig.json b/code/extensions/php-language-features/tsconfig.json index 7234fdfeb97..29a69e99d98 100644 --- a/code/extensions/php-language-features/tsconfig.json +++ b/code/extensions/php-language-features/tsconfig.json @@ -4,7 +4,10 @@ "outDir": "./out", "types": [ "node" - ] + ], + "typeRoots": [ + "./node_modules/@types" + ], }, "include": [ "src/**/*", diff --git a/code/extensions/php/language-configuration.json b/code/extensions/php/language-configuration.json index 4d9a3238d40..fca0a338042 100644 --- a/code/extensions/php/language-configuration.json +++ b/code/extensions/php/language-configuration.json @@ -93,20 +93,16 @@ ] ], "indentationRules": { - "increaseIndentPattern": "({(?!.*}).*|\\(|\\[|((else(\\s)?)?if|else|for(each)?|while|switch|case).*:)\\s*((/[/*].*|)?$|\\?>)", - "decreaseIndentPattern": "^(.*\\*\\/)?\\s*((\\})|(\\)+[;,])|(\\]\\)*[;,])|\\b(else:)|\\b((end(if|for(each)?|while|switch));))", + "increaseIndentPattern": "((else\\s?)?if|else|for(each)?|while|switch|case).*:\\s*((/[/*].*|)?$|\\?>)", + "decreaseIndentPattern": "^(.*\\*\\/)?\\s*(\\b(else:)|\\bend(if|for(each)?|while|switch);)", // e.g. * ...| or */| or *-----*/| - "unIndentedLinePattern": { - "pattern": "^(\\t|[ ])*[ ]\\*[^/]*\\*/\\s*$|^(\\t|[ ])*[ ]\\*/\\s*$|^(\\t|[ ])*\\*([ ]([^\\*]|\\*(?!/))*)?$" - }, - "indentNextLinePattern": { - "pattern": "^\\s*(((if|else ?if|while|for|foreach)\\s*\\(.*\\)\\s*)|else\\s*)$" - } + "unIndentedLinePattern": "^(\\t|[ ])*[ ]\\*[^/]*\\*/\\s*$|^(\\t|[ ])*[ ]\\*/\\s*$|^(\\t|[ ])*\\*([ ]([^\\*]|\\*(?!/))*)?$", + "indentNextLinePattern": "^\\s*(((if|else ?if|while|for|foreach)\\s*\\(.*\\)\\s*)|else\\s*)$" }, "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/code/extensions/prompt-basics/package.json b/code/extensions/prompt-basics/package.json index 91ea0c081b0..f1d4ee98b29 100644 --- a/code/extensions/prompt-basics/package.json +++ b/code/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/code/extensions/references-view/package.json b/code/extensions/references-view/package.json index 62c9e29e0c6..5f2714589c7 100644 --- a/code/extensions/references-view/package.json +++ b/code/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/code/extensions/references-view/tsconfig.json b/code/extensions/references-view/tsconfig.json index f0f7c00adf5..796a159a61c 100644 --- a/code/extensions/references-view/tsconfig.json +++ b/code/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/code/extensions/search-result/package-lock.json b/code/extensions/search-result/package-lock.json index 4fbe8b97ef8..f85d9e265e0 100644 --- a/code/extensions/search-result/package-lock.json +++ b/code/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/code/extensions/search-result/package.json b/code/extensions/search-result/package.json index 6582db3e782..1119636313f 100644 --- a/code/extensions/search-result/package.json +++ b/code/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/code/extensions/search-result/tsconfig.json b/code/extensions/search-result/tsconfig.json index f0f7c00adf5..796a159a61c 100644 --- a/code/extensions/search-result/tsconfig.json +++ b/code/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/code/extensions/shared.webpack.config.mjs b/code/extensions/shared.webpack.config.mjs index f54499dc227..12b1ea522a4 100644 --- a/code/extensions/shared.webpack.config.mjs +++ b/code/extensions/shared.webpack.config.mjs @@ -42,17 +42,21 @@ function withNodeDefaults(/**@type WebpackConfig & { context: string }*/extConfi 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(import.meta.dirname, 'mangle-loader.js'), - options: { - configFile: path.join(extConfig.context, 'tsconfig.json') + 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: { @@ -135,12 +139,13 @@ function withBrowserDefaults(/**@type WebpackConfig & { context: string }*/extCo // ...(additionalOptions ? {} : { configFile: additionalOptions.configFile }), } }, - { - loader: path.resolve(import.meta.dirname, 'mangle-loader.js'), - options: { - configFile: path.join(extConfig.context, additionalOptions?.configFile ?? 'tsconfig.json') - }, - }, + // 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$/, diff --git a/code/extensions/shellscript/package.json b/code/extensions/shellscript/package.json index ab9be7b29ad..9cad54150bb 100644 --- a/code/extensions/shellscript/package.json +++ b/code/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/code/extensions/simple-browser/package-lock.json b/code/extensions/simple-browser/package-lock.json index c6d9b23636a..8aa3894ba1e 100644 --- a/code/extensions/simple-browser/package-lock.json +++ b/code/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/code/extensions/simple-browser/package.json b/code/extensions/simple-browser/package.json index 789de38deb3..79802e73668 100644 --- a/code/extensions/simple-browser/package.json +++ b/code/extensions/simple-browser/package.json @@ -57,10 +57,10 @@ ] }, "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-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/code/extensions/simple-browser/preview-src/tsconfig.json b/code/extensions/simple-browser/preview-src/tsconfig.json index 72282fb0c7d..e8e5336a66b 100644 --- a/code/extensions/simple-browser/preview-src/tsconfig.json +++ b/code/extensions/simple-browser/preview-src/tsconfig.json @@ -7,6 +7,9 @@ "ES2024", "DOM", "DOM.Iterable" + ], + "typeRoots": [ + "../node_modules/@types" ] } } diff --git a/code/extensions/simple-browser/src/extension.ts b/code/extensions/simple-browser/src/extension.ts index 927167a851d..885afe28712 100644 --- a/code/extensions/simple-browser/src/extension.ts +++ b/code/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/code/extensions/simple-browser/src/simpleBrowserView.ts b/code/extensions/simple-browser/src/simpleBrowserView.ts index 5725dcf4f9b..56c5aff5c8a 100644 --- a/code/extensions/simple-browser/src/simpleBrowserView.ts +++ b/code/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/code/extensions/simple-browser/src/uuid.ts b/code/extensions/simple-browser/src/uuid.ts new file mode 100644 index 00000000000..ca420b3b6af --- /dev/null +++ b/code/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/code/extensions/simple-browser/tsconfig.json b/code/extensions/simple-browser/tsconfig.json index bd370826678..43ed762ce7d 100644 --- a/code/extensions/simple-browser/tsconfig.json +++ b/code/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/code/extensions/swift/cgmanifest.json b/code/extensions/swift/cgmanifest.json index a9cdafc3b03..ecd2705da2a 100644 --- a/code/extensions/swift/cgmanifest.json +++ b/code/extensions/swift/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "jtbandes/swift-tmlanguage", "repositoryUrl": "https://github.com/jtbandes/swift-tmlanguage", - "commitHash": "0897d8939a82ddcf6533e9f318e5942b1265416b" + "commitHash": "45ac01d47c6d63402570c2c36bcfbadbd1c7bca6" } }, "license": "MIT" diff --git a/code/extensions/swift/syntaxes/swift.tmLanguage.json b/code/extensions/swift/syntaxes/swift.tmLanguage.json index 1d6b18c44ed..a8bbe5d00b4 100644 --- a/code/extensions/swift/syntaxes/swift.tmLanguage.json +++ b/code/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/0897d8939a82ddcf6533e9f318e5942b1265416b", + "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" }, @@ -1159,8 +1166,8 @@ }, "patterns": [ { - "match": "\\bsending\\b", - "name": "storage.modifier.swift" + "name": "storage.modifier.swift", + "match": "\\bsending\\b" }, { "include": "#declarations-available-types" @@ -1754,8 +1761,8 @@ "end": "(?=[,)])", "patterns": [ { - "match": "\\bsending\\b", - "name": "storage.modifier.swift" + "name": "storage.modifier.swift", + "match": "\\bsending\\b" }, { "include": "#declarations-available-types" @@ -2834,6 +2841,10 @@ "name": "keyword.control.loop.swift", "match": "(? = { + 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/code/extensions/terminal-suggest/src/completions/code.ts b/code/extensions/terminal-suggest/src/completions/code.ts index 99e1371b181..35cfbe28c58 100644 --- a/code/extensions/terminal-suggest/src/completions/code.ts +++ b/code/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/code/extensions/terminal-suggest/src/completions/copilot.ts b/code/extensions/terminal-suggest/src/completions/copilot.ts new file mode 100644 index 00000000000..6457479e708 --- /dev/null +++ b/code/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/code/extensions/terminal-suggest/src/completions/gh.ts b/code/extensions/terminal-suggest/src/completions/gh.ts index 0c06cc35d7f..ea163beeb7f 100644 --- a/code/extensions/terminal-suggest/src/completions/gh.ts +++ b/code/extensions/terminal-suggest/src/completions/gh.ts @@ -50,7 +50,7 @@ const postProcessRemoteBranches: Fig.Generator["postProcess"] = (out) => { return { name, description: "Branch", - icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.Branch}`, + icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmBranch}`, priority: 75, }; }); diff --git a/code/extensions/terminal-suggest/src/completions/git.ts b/code/extensions/terminal-suggest/src/completions/git.ts index 13c7739407a..68cc6fda2d7 100644 --- a/code/extensions/terminal-suggest/src/completions/git.ts +++ b/code/extensions/terminal-suggest/src/completions/git.ts @@ -74,46 +74,68 @@ const postProcessBranches = const seen = new Set(); return output .split("\n") - .filter((line) => !line.trim().startsWith("HEAD")) + .filter((line) => line.trim() && !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; + // 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(); } - // Current branch - return { - name: branch.replace("*", "").trim(), - description: "Current branch", - priority: 100, - icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.Branch}` - }; - } else if (parts[0] === "+") { - // Branch checked out in another worktree. - name = branch.replace("+", "").trim(); } + + let description = "Branch"; + + if (insertWithoutRemotes && name.startsWith("remotes/")) { + name = name.slice(name.indexOf("/", 8) + 1); + description = "Remote branch"; + } + + const space = name.indexOf(" "); + if (space !== -1) { + name = name.slice(0, space); + } + + return { + name, + description, + icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmBranch}`, + priority: 75, + }; } - 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(); + + const description = `${timeAgo} • ${author} • ${hash} • ${subject}`; + const priority = 75; if (insertWithoutRemotes && name.startsWith("remotes/")) { name = name.slice(name.indexOf("/", 8) + 1); - description = "Remote branch"; - } - - const space = name.indexOf(" "); - if (space !== -1) { - name = name.slice(0, space); } return { name, description, - icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.Branch}`, - priority: 75, + icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmBranch}`, + priority, }; }) .filter((suggestion) => { @@ -128,6 +150,15 @@ const postProcessBranches = }); }; +// 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 = { // Commit history commits: { @@ -148,7 +179,7 @@ export const gitGenerators = { return lines.map((line) => { return { name: line.substring(0, hashLength), - icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.Commit}`, + icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmCommit}`, description: line.substring(descriptionStart), }; }); @@ -194,7 +225,7 @@ export const gitGenerators = { return output.split("\n").map((line) => { return { name: line.substring(0, 7), - icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.Commit}`, + icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmCommit}`, description: line.substring(7), }; }); @@ -217,7 +248,7 @@ export const gitGenerators = { // account for conventional commit messages name: file.split(":").slice(2).join(":"), insertValue: file.split(":")[0], - icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.Stash}`, + icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmStash}`, }; }); }, @@ -252,26 +283,20 @@ export const gitGenerators = { // All branches remoteLocalBranches: { script: [ - "git", - "--no-optional-locks", - "branch", - "-a", - "--no-color", - "--sort=-committerdate", + ...gitBranchForEachRefArgs, + "refs/heads/", + "refs/remotes/", ], postProcess: postProcessBranches({ insertWithoutRemotes: true }), - } satisfies Fig.Generator, + }, localBranches: { script: [ - "git", - "--no-optional-locks", - "branch", - "--no-color", - "--sort=-committerdate", + ...gitBranchForEachRefArgs, + "refs/heads/", ], postProcess: postProcessBranches({ insertWithoutRemotes: true }), - } satisfies Fig.Generator, + }, // custom generator to display local branches by default or // remote branches if '-r' flag is used. See branch -d for use @@ -329,7 +354,7 @@ export const gitGenerators = { return Object.keys(remoteURLs).map((remote) => { return { name: remote, - icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.Remote}`, + icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmRemote}`, description: "Remote", }; }); @@ -347,7 +372,7 @@ export const gitGenerators = { postProcess: function (output) { return output.split("\n").map((tag) => ({ name: tag, - icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.Tag}` + icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmTag}` })); }, } satisfies Fig.Generator, @@ -6331,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: [ { @@ -6450,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, @@ -6459,7 +6982,7 @@ const completionSpec: Fig.Spec = { }, { name: "rename", - description: "Removes given remote [name]", + description: "Renames the given remote", args: [ { name: "old remote", @@ -8117,7 +8640,7 @@ const completionSpec: Fig.Spec = { { name: "-", description: "Switch to the last used branch", - icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.Branch}` + icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmBranch}` }, { name: "--", @@ -9283,7 +9806,7 @@ const completionSpec: Fig.Spec = { { name: "-", description: "Switch to the last used branch", - icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.Branch}` + icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmBranch}` }, ], }, diff --git a/code/extensions/terminal-suggest/src/completions/npm.ts b/code/extensions/terminal-suggest/src/completions/npm.ts new file mode 100644 index 00000000000..f031d467aff --- /dev/null +++ b/code/extensions/terminal-suggest/src/completions/npm.ts @@ -0,0 +1,1625 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +function uninstallSubcommand(named: string | string[]): Fig.Subcommand { + return { + name: named, + description: 'Uninstall a package', + args: { + name: 'package', + generators: dependenciesGenerator, + filterStrategy: 'fuzzy', + isVariadic: true, + }, + options: npmUninstallOptions, + }; +} + +const atsInStr = (s: string) => (s.match(/@/g) || []).length; + +export const createNpmSearchHandler = + (keywords?: string[]) => + async ( + context: string[], + executeShellCommand: Fig.ExecuteCommandFunction, + shellContext: Fig.ShellContext + ): Promise => { + const searchTerm = context[context.length - 1]; + if (searchTerm === '') { + return []; + } + // Add optional keyword parameter + const keywordParameter = + keywords && keywords.length > 0 ? `+keywords:${keywords.join(',')}` : ''; + + const queryPackagesUrl = keywordParameter + ? `https://api.npms.io/v2/search?size=20&q=${searchTerm}${keywordParameter}` + : `https://api.npms.io/v2/search/suggestions?q=${searchTerm}&size=20`; + + // Query the API with the package name + const queryPackages = [ + '-s', + '-H', + 'Accept: application/json', + queryPackagesUrl, + ]; + // We need to remove the '@' at the end of the searchTerm before querying versions + const queryVersions = [ + '-s', + '-H', + 'Accept: application/vnd.npm.install-v1+json', + `https://registry.npmjs.org/${searchTerm.slice(0, -1)}`, + ]; + // If the end of our token is '@', then we want to generate version suggestions + // Otherwise, we want packages + const out = (query: string) => + executeShellCommand({ + command: 'curl', + args: query[query.length - 1] === '@' ? queryVersions : queryPackages, + }); + // If our token starts with '@', then a 2nd '@' tells us we want + // versions. + // Otherwise, '@' anywhere else in the string will indicate the same. + const shouldGetVersion = searchTerm.startsWith('@') + ? atsInStr(searchTerm) > 1 + : searchTerm.includes('@'); + + try { + const data = JSON.parse((await out(searchTerm)).stdout); + if (shouldGetVersion) { + // create dist tags suggestions + const versions = Object.entries(data['dist-tags'] || {}).map( + ([key, value]) => ({ + name: key, + description: value, + }) + ) as Fig.Suggestion[]; + // create versions + versions.push( + ...Object.keys(data.versions) + .map((version) => ({ name: version })) + .reverse() + ); + return versions; + } + + const results = keywordParameter ? data.results : data; + return results.map( + (item: { package: { name: string; description: string } }) => ({ + name: item.package.name, + description: item.package.description, + }) + ) as Fig.Suggestion[]; + } catch (error) { + console.error({ error }); + return []; + } + }; + +// GENERATORS +export const npmSearchGenerator: Fig.Generator = { + trigger: (newToken, oldToken) => { + // If the package name starts with '@', we want to trigger when + // the 2nd '@' is typed because we'll need to generate version + // suggetsions + // e.g. @typescript-eslint/types + if (oldToken.startsWith('@')) { + return !(atsInStr(oldToken) > 1 && atsInStr(newToken) > 1); + } + + // If the package name doesn't start with '@', then trigger when + // we see the first '@' so we can generate version suggestions + return !(oldToken.includes('@') && newToken.includes('@')); + }, + getQueryTerm: '@', + cache: { + ttl: 1000 * 60 * 60 * 24 * 2, // 2 days + }, + custom: createNpmSearchHandler(), +}; + +const workspaceGenerator: Fig.Generator = { + // script: "cat $(npm prefix)/package.json", + custom: async (tokens, executeShellCommand) => { + const { stdout: npmPrefix } = await executeShellCommand({ + command: 'npm', + args: ['prefix'], + }); + + const { stdout: out } = await executeShellCommand({ + command: 'cat', + args: [`${npmPrefix}/package.json`], + }); + + const suggestions: Fig.Suggestion[] = []; + try { + if (out.trim() === '') { + return suggestions; + } + + const packageContent = JSON.parse(out); + const workspaces = packageContent['workspaces']; + + if (workspaces) { + for (const workspace of workspaces) { + suggestions.push({ + name: workspace, + description: 'Workspaces', + }); + } + } + } catch (e) { + console.log(e); + } + return suggestions; + }, +}; + +/** Generator that lists package.json dependencies */ +export const dependenciesGenerator: Fig.Generator = { + trigger: (newToken) => newToken === '-g' || newToken === '--global', + custom: async function (tokens, executeShellCommand) { + if (!tokens.includes('-g') && !tokens.includes('--global')) { + const { stdout: npmPrefix } = await executeShellCommand({ + command: 'npm', + args: ['prefix'], + }); + const { stdout: out } = await executeShellCommand({ + command: 'cat', + args: [`${npmPrefix}/package.json`], + }); + const packageContent = JSON.parse(out); + const dependencies = packageContent['dependencies'] ?? {}; + const devDependencies = packageContent['devDependencies']; + const optionalDependencies = packageContent['optionalDependencies'] ?? {}; + Object.assign(dependencies, devDependencies, optionalDependencies); + + return Object.keys(dependencies) + .filter((pkgName) => { + const isListed = tokens.some((current) => current === pkgName); + return !isListed; + }) + .map((pkgName) => ({ + name: pkgName, + description: dependencies[pkgName] + ? 'dependency' + : optionalDependencies[pkgName] + ? 'optionalDependency' + : 'devDependency', + })); + } else { + const { stdout } = await executeShellCommand({ + command: 'bash', + args: ['-c', 'ls -1 `npm root -g`'], + }); + return stdout.split('\n').map((name) => ({ + name, + description: 'Global dependency', + })); + } + }, +}; + +/** Generator that lists package.json scripts (with the respect to the `fig` field) */ +export const npmScriptsGenerator: Fig.Generator = { + cache: { + strategy: 'stale-while-revalidate', + cacheByDirectory: true, + }, + script: [ + 'bash', + '-c', + 'until [[ -f package.json ]] || [[ $PWD = \' / \' ]]; do cd ..; done; cat package.json', + ], + postProcess: function (out, [npmClient]) { + if (out.trim() === '') { + return []; + } + + try { + const packageContent = JSON.parse(out); + const scripts = packageContent['scripts']; + const figCompletions = packageContent['fig'] || {}; + + if (scripts) { + return Object.entries(scripts).map(([scriptName, scriptContents]) => { + const icon = + npmClient === 'yarn' + ? 'fig://icon?type=yarn' + : 'fig://icon?type=npm'; + const customScripts: Fig.Suggestion = figCompletions[scriptName]; + return { + name: scriptName, + icon, + description: scriptContents as string, + priority: 51, + /** + * If there are custom definitions for the scripts + * we want to override the default values + * */ + ...customScripts, + }; + }); + } + } catch (e) { + console.error(e); + } + + return []; + }, +}; + +const globalOption: Fig.Option = { + name: ['-g', '--global'], + description: + 'Operates in \'global\' mode, so that packages are installed into the prefix folder instead of the current working directory', +}; + +const jsonOption: Fig.Option = { + name: '--json', + description: 'Show output in json format', +}; + +const omitOption: Fig.Option = { + name: '--omit', + description: 'Dependency types to omit from the installation tree on disk', + args: { + name: 'Package type', + default: 'dev', + suggestions: ['dev', 'optional', 'peer'], + }, + isRepeatable: 3, +}; + +const parseableOption: Fig.Option = { + name: ['-p', '--parseable'], + description: + 'Output parseable results from commands that write to standard output', +}; + +const longOption: Fig.Option = { + name: ['-l', '--long'], + description: 'Show extended information', +}; + +const workSpaceOptions: Fig.Option[] = [ + { + name: ['-w', '--workspace'], + description: + 'Enable running a command in the context of the configured workspaces of the current project', + args: { + name: 'workspace', + generators: workspaceGenerator, + isVariadic: true, + }, + }, + { + name: ['-ws', '--workspaces'], + description: + 'Enable running a command in the context of all the configured workspaces', + }, +]; + +const npmUninstallOptions: Fig.Option[] = [ + { + name: ['-S', '--save'], + description: 'Package will be removed from your dependencies', + }, + { + name: ['-D', '--save-dev'], + description: 'Package will appear in your `devDependencies`', + }, + { + name: ['-O', '--save-optional'], + description: 'Package will appear in your `optionalDependencies`', + }, + { + name: '--no-save', + description: 'Prevents saving to `dependencies`', + }, + { + name: '-g', + description: 'Uninstall global package', + }, + ...workSpaceOptions, +]; + +const npmListOptions: Fig.Option[] = [ + { + name: ['-a', '-all'], + description: 'Show all outdated or installed packages', + }, + jsonOption, + longOption, + parseableOption, + { + name: '--depth', + description: 'The depth to go when recursing packages', + args: { name: 'depth' }, + }, + { + name: '--link', + description: 'Limits output to only those packages that are linked', + }, + { + name: '--package-lock-only', + description: + 'Current operation will only use the package-lock.json, ignoring node_modules', + }, + { + name: '--no-unicode', + description: 'Uses unicode characters in the tree output', + }, + globalOption, + omitOption, + ...workSpaceOptions, +]; + +const registryOption: Fig.Option = { + name: '--registry', + description: 'The base URL of the npm registry', + args: { name: 'registry' }, +}; + +const verboseOption: Fig.Option = { + name: '--verbose', + description: 'Show extra information', + args: { name: 'verbose' }, +}; + +const otpOption: Fig.Option = { + name: '--otp', + description: 'One-time password from a two-factor authenticator', + args: { name: 'otp' }, +}; + +const ignoreScriptsOption: Fig.Option = { + name: '--ignore-scripts', + description: + 'If true, npm does not run scripts specified in package.json files', +}; + +const scriptShellOption: Fig.Option = { + name: '--script-shell', + description: + 'The shell to use for scripts run with the npm exec, npm run and npm init commands', + args: { name: 'script-shell' }, +}; + +const dryRunOption: Fig.Option = { + name: '--dry-run', + description: + 'Indicates that you don\'t want npm to make any changes and that it should only report what it would have done', +}; + +const completionSpec: Fig.Spec = { + name: 'npm', + parserDirectives: { + flagsArePosixNoncompliant: true, + }, + description: 'Node package manager', + subcommands: [ + { + name: ['install', 'i', 'add'], + description: 'Install a package and its dependencies', + args: { + name: 'package', + isOptional: true, + generators: npmSearchGenerator, + debounce: true, + isVariadic: true, + }, + options: [ + { + name: ['-P', '--save-prod'], + description: + 'Package will appear in your `dependencies`. This is the default unless `-D` or `-O` are present', + }, + { + name: ['-D', '--save-dev'], + description: 'Package will appear in your `devDependencies`', + }, + { + name: ['-O', '--save-optional'], + description: 'Package will appear in your `optionalDependencies`', + }, + { + name: '--no-save', + description: 'Prevents saving to `dependencies`', + }, + { + name: ['-E', '--save-exact'], + description: + 'Saved dependencies will be configured with an exact version rather than using npm\'s default semver range operator', + }, + { + name: ['-B', '--save-bundle'], + description: + 'Saved dependencies will also be added to your bundleDependencies list', + }, + globalOption, + { + name: '--global-style', + description: + 'Causes npm to install the package into your local node_modules folder with the same layout it uses with the global node_modules folder', + }, + { + name: '--legacy-bundling', + description: + 'Causes npm to install the package such that versions of npm prior to 1.4, such as the one included with node 0.8, can install the package', + }, + { + name: '--legacy-peer-deps', + description: + 'Bypass peerDependency auto-installation. Emulate install behavior of NPM v4 through v6', + }, + { + name: '--package-lock-only', + description: 'Only update the `package-lock.json`, instead of checking `node_modules` and downloading dependencies.', + }, + { + name: '--strict-peer-deps', + description: + 'If set to true, and --legacy-peer-deps is not set, then any conflicting peerDependencies will be treated as an install failure', + }, + { + name: '--no-package-lock', + description: 'Ignores package-lock.json files when installing', + }, + registryOption, + verboseOption, + omitOption, + ignoreScriptsOption, + { + name: '--no-audit', + description: + 'Submit audit reports alongside the current npm command to the default registry and all registries configured for scopes', + }, + { + name: '--no-bin-links', + description: + 'Tells npm to not create symlinks (or .cmd shims on Windows) for package executables', + }, + { + name: '--no-fund', + description: + 'Hides the message at the end of each npm install acknowledging the number of dependencies looking for funding', + }, + dryRunOption, + ...workSpaceOptions, + ], + }, + { + name: ['run', 'run-script'], + description: 'Run arbitrary package scripts', + options: [ + ...workSpaceOptions, + { + name: '--if-present', + description: + 'Npm will not exit with an error code when run-script is invoked for a script that isn\'t defined in the scripts section of package.json', + }, + { + name: '--silent', + description: '', + }, + ignoreScriptsOption, + scriptShellOption, + { + name: '--', + args: { + name: 'args', + isVariadic: true, + // TODO: load the spec based on the runned script (see yarn spec `yarnScriptParsedDirectives`) + }, + }, + ], + args: { + name: 'script', + description: 'Script to run from your package.json', + filterStrategy: 'fuzzy', + generators: npmScriptsGenerator, + }, + }, + { + name: 'init', + description: 'Trigger the initialization', + options: [ + { + name: ['-y', '--yes'], + description: + 'Automatically answer \'yes\' to any prompts that npm might print on the command line', + }, + { + name: '-w', + description: + 'Create the folders and boilerplate expected while also adding a reference to your project workspaces property', + args: { name: 'dir' }, + }, + ], + }, + { name: 'access', description: 'Set access controls on private packages' }, + { + name: ['adduser', 'login'], + description: 'Add a registry user account', + options: [ + registryOption, + { + name: '--scope', + description: + 'Associate an operation with a scope for a scoped registry', + args: { + name: 'scope', + description: 'Scope name', + }, + }, + ], + }, + { + name: 'audit', + description: 'Run a security audit', + subcommands: [ + { + name: 'fix', + description: + 'If the fix argument is provided, then remediations will be applied to the package tree', + options: [ + dryRunOption, + { + name: ['-f', '--force'], + description: + 'Removes various protections against unfortunate side effects, common mistakes, unnecessary performance degradation, and malicious input', + isDangerous: true, + }, + ...workSpaceOptions, + ], + }, + ], + options: [ + ...workSpaceOptions, + { + name: '--audit-level', + description: + 'The minimum level of vulnerability for npm audit to exit with a non-zero exit code', + args: { + name: 'audit', + suggestions: [ + 'info', + 'low', + 'moderate', + 'high', + 'critical', + 'none', + ], + }, + }, + { + name: '--package-lock-only', + description: + 'Current operation will only use the package-lock.json, ignoring node_modules', + }, + jsonOption, + omitOption, + ], + }, + { + name: 'bin', + description: 'Print the folder where npm will install executables', + options: [globalOption], + }, + { + name: ['bugs', 'issues'], + description: 'Report bugs for a package in a web browser', + args: { + name: 'package', + isOptional: true, + generators: npmSearchGenerator, + debounce: true, + isVariadic: true, + }, + options: [ + { + name: '--no-browser', + description: 'Display in command line instead of browser', + exclusiveOn: ['--browser'], + }, + { + name: '--browser', + description: + 'The browser that is called by the npm bugs command to open websites', + args: { name: 'browser' }, + exclusiveOn: ['--no-browser'], + }, + registryOption, + ], + }, + { + name: 'cache', + description: 'Manipulates packages cache', + subcommands: [ + { + name: 'add', + description: 'Add the specified packages to the local cache', + }, + { + name: 'clean', + description: 'Delete all data out of the cache folder', + }, + { + name: 'verify', + description: + 'Verify the contents of the cache folder, garbage collecting any unneeded data, and verifying the integrity of the cache index and all cached data', + }, + ], + options: [ + { + name: '--cache', + args: { name: 'cache' }, + description: 'The location of npm\'s cache directory', + }, + ], + }, + { + name: ['ci', 'clean-install', 'install-clean'], + description: 'Install a project with a clean slate', + options: [ + { + name: '--audit', + description: + 'When \'true\' submit audit reports alongside the current npm command to the default registry and all registries configured for scopes', + args: { + name: 'audit', + suggestions: ['true', 'false'], + }, + exclusiveOn: ['--no-audit'], + }, + { + name: '--no-audit', + description: + 'Do not submit audit reports alongside the current npm command', + exclusiveOn: ['--audit'], + }, + ignoreScriptsOption, + scriptShellOption, + verboseOption, + registryOption, + ], + }, + { + name: 'cit', + description: 'Install a project with a clean slate and run tests', + }, + { + name: 'clean-install-test', + description: 'Install a project with a clean slate and run tests', + }, + { name: 'completion', description: 'Tab completion for npm' }, + { + name: ['config', 'c'], + description: 'Manage the npm configuration files', + subcommands: [ + { + name: 'set', + description: 'Sets the config key to the value', + args: [{ name: 'key' }, { name: 'value' }], + options: [ + { name: ['-g', '--global'], description: 'Sets it globally' }, + ], + }, + { + name: 'get', + description: 'Echo the config value to stdout', + args: { name: 'key' }, + }, + { + name: 'list', + description: 'Show all the config settings', + options: [ + { name: '-g', description: 'Lists globally installed packages' }, + { name: '-l', description: 'Also shows defaults' }, + jsonOption, + ], + }, + { + name: 'delete', + description: 'Deletes the key from all configuration files', + args: { name: 'key' }, + }, + { + name: 'edit', + description: 'Opens the config file in an editor', + options: [ + { name: '--global', description: 'Edits the global config' }, + ], + }, + ], + }, + { name: 'create', description: 'Create a package.json file' }, + { + name: ['dedupe', 'ddp'], + description: 'Reduce duplication in the package tree', + }, + { + name: 'deprecate', + description: 'Deprecate a version of a package', + options: [registryOption], + }, + { name: 'dist-tag', description: 'Modify package distribution tags' }, + { + name: ['docs', 'home'], + description: 'Open documentation for a package in a web browser', + args: { + name: 'package', + isOptional: true, + generators: npmSearchGenerator, + debounce: true, + isVariadic: true, + }, + options: [ + ...workSpaceOptions, + registryOption, + { + name: '--no-browser', + description: 'Display in command line instead of browser', + exclusiveOn: ['--browser'], + }, + { + name: '--browser', + description: + 'The browser that is called by the npm docs command to open websites', + args: { name: 'browser' }, + exclusiveOn: ['--no-browser'], + }, + ], + }, + { + name: 'doctor', + description: 'Check your npm environment', + options: [registryOption], + }, + { + name: 'edit', + description: 'Edit an installed package', + options: [ + { + name: '--editor', + description: 'The command to run for npm edit or npm config edit', + }, + ], + }, + { + name: ['explain', 'why'], + description: 'Explain installed packages', + args: { + name: 'package-spec', + description: 'Package name or path to folder within node_modules', + isVariadic: true, + filterStrategy: 'fuzzy', + generators: dependenciesGenerator, + }, + options: [jsonOption, ...workSpaceOptions], + }, + { + name: 'explore', + description: 'Browse an installed package', + args: { + name: 'package', + filterStrategy: 'fuzzy', + generators: dependenciesGenerator, + }, + }, + { name: 'fund', description: 'Retrieve funding information' }, + { name: 'get', description: 'Echo the config value to stdout' }, + { + name: 'help', + description: 'Get help on npm', + args: { + name: 'term', + isVariadic: true, + description: 'Terms to search for', + }, + options: [ + { + name: '--viewer', + description: 'The program to use to view help content', + args: { + name: 'viewer', + }, + }, + ], + }, + { + name: 'help-search', + description: 'Search npm help documentation', + args: { + name: 'text', + description: 'Text to search for', + }, + options: [longOption], + }, + { name: 'hook', description: 'Manage registry hooks' }, + { + name: 'install-ci-test', + description: 'Install a project with a clean slate and run tests', + }, + { name: 'install-test', description: 'Install package(s) and run tests' }, + { name: 'it', description: 'Install package(s) and run tests' }, + { + name: 'link', + description: 'Symlink a package folder', + args: { name: 'path', template: 'filepaths' }, + }, + { name: 'ln', description: 'Symlink a package folder' }, + { + name: 'logout', + description: 'Log out of the registry', + options: [ + registryOption, + { + name: '--scope', + description: + 'Associate an operation with a scope for a scoped registry', + args: { + name: 'scope', + description: 'Scope name', + }, + }, + ], + }, + { + name: ['ls', 'list'], + description: 'List installed packages', + options: npmListOptions, + args: { name: '[@scope]/pkg', isVariadic: true }, + }, + { + name: 'org', + description: 'Manage orgs', + subcommands: [ + { + name: 'set', + description: 'Add a user to an org or manage roles', + args: [ + { + name: 'orgname', + description: 'Organization name', + }, + { + name: 'username', + description: 'User name', + }, + { + name: 'role', + isOptional: true, + suggestions: ['developer', 'admin', 'owner'], + }, + ], + options: [registryOption, otpOption], + }, + { + name: 'rm', + description: 'Remove a user from an org', + args: [ + { + name: 'orgname', + description: 'Organization name', + }, + { + name: 'username', + description: 'User name', + }, + ], + options: [registryOption, otpOption], + }, + { + name: 'ls', + description: + 'List users in an org or see what roles a particular user has in an org', + args: [ + { + name: 'orgname', + description: 'Organization name', + }, + { + name: 'username', + description: 'User name', + isOptional: true, + }, + ], + options: [registryOption, otpOption, jsonOption, parseableOption], + }, + ], + }, + { + name: 'outdated', + description: 'Check for outdated packages', + args: { + name: '[<@scope>/]', + isVariadic: true, + isOptional: true, + }, + options: [ + { + name: ['-a', '-all'], + description: 'Show all outdated or installed packages', + }, + jsonOption, + longOption, + parseableOption, + { + name: '-g', + description: 'Checks globally', + }, + ...workSpaceOptions, + ], + }, + { + name: ['owner', 'author'], + description: 'Manage package owners', + subcommands: [ + { + name: 'ls', + description: + 'List all the users who have access to modify a package and push new versions. Handy when you need to know who to bug for help', + args: { name: '[@scope/]pkg' }, + options: [registryOption], + }, + { + name: 'add', + description: + 'Add a new user as a maintainer of a package. This user is enabled to modify metadata, publish new versions, and add other owners', + args: [{ name: 'user' }, { name: '[@scope/]pkg' }], + options: [registryOption, otpOption], + }, + { + name: 'rm', + description: + 'Remove a user from the package owner list. This immediately revokes their privileges', + args: [{ name: 'user' }, { name: '[@scope/]pkg' }], + options: [registryOption, otpOption], + }, + ], + }, + { + name: 'pack', + description: 'Create a tarball from a package', + args: { + name: '[<@scope>/]', + }, + options: [ + jsonOption, + dryRunOption, + ...workSpaceOptions, + { + name: '--pack-destination', + description: 'Directory in which npm pack will save tarballs', + args: { + name: 'pack-destination', + template: ['folders'], + }, + }, + ], + }, + { + name: 'ping', + description: 'Ping npm registry', + options: [registryOption], + }, + { + name: 'pkg', + description: 'Manages your package.json', + subcommands: [ + { + name: 'get', + description: + 'Retrieves a value key, defined in your package.json file. It is possible to get multiple values and values for child fields', + args: { + name: 'field', + description: + 'Name of the field to get. You can view child fields by separating them with a period', + isVariadic: true, + }, + options: [jsonOption, ...workSpaceOptions], + }, + { + name: 'set', + description: + 'Sets a value in your package.json based on the field value. It is possible to set multiple values and values for child fields', + args: { + // Format is =. How to achieve this? + name: 'field', + description: + 'Name of the field to set. You can set child fields by separating them with a period', + isVariadic: true, + }, + options: [ + jsonOption, + ...workSpaceOptions, + { + name: ['-f', '--force'], + description: + 'Removes various protections against unfortunate side effects, common mistakes, unnecessary performance degradation, and malicious input. Allow clobbering existing values in npm pkg', + isDangerous: true, + }, + ], + }, + { + name: 'delete', + description: 'Deletes a key from your package.json', + args: { + name: 'key', + description: + 'Name of the key to delete. You can delete child fields by separating them with a period', + isVariadic: true, + }, + options: [ + ...workSpaceOptions, + { + name: ['-f', '--force'], + description: + 'Removes various protections against unfortunate side effects, common mistakes, unnecessary performance degradation, and malicious input. Allow clobbering existing values in npm pkg', + isDangerous: true, + }, + ], + }, + ], + }, + { + name: 'prefix', + description: 'Display prefix', + options: [ + { + name: ['-g', '--global'], + description: 'Print the global prefix to standard out', + }, + ], + }, + { + name: 'profile', + description: 'Change settings on your registry profile', + subcommands: [ + { + name: 'get', + description: + 'Display all of the properties of your profile, or one or more specific properties', + args: { + name: 'property', + isOptional: true, + description: 'Property name', + }, + options: [registryOption, jsonOption, parseableOption, otpOption], + }, + { + name: 'set', + description: 'Set the value of a profile property', + args: [ + { + name: 'property', + description: 'Property name', + suggestions: [ + 'email', + 'fullname', + 'homepage', + 'freenode', + 'twitter', + 'github', + ], + }, + { + name: 'value', + description: 'Property value', + }, + ], + options: [registryOption, jsonOption, parseableOption, otpOption], + subcommands: [ + { + name: 'password', + description: + 'Change your password. This is interactive, you\'ll be prompted for your current password and a new password', + }, + ], + }, + { + name: 'enable-2fa', + description: 'Enables two-factor authentication', + args: { + name: 'mode', + description: + 'Mode for two-factor authentication. Defaults to auth-and-writes mode', + isOptional: true, + suggestions: [ + { + name: 'auth-only', + description: + 'Require an OTP when logging in or making changes to your account\'s authentication', + }, + { + name: 'auth-and-writes', + description: + 'Requires an OTP at all the times auth-only does, and also requires one when publishing a module, setting the latest dist-tag, or changing access via npm access and npm owner', + }, + ], + }, + options: [registryOption, otpOption], + }, + { + name: 'disable-2fa', + description: 'Disables two-factor authentication', + options: [registryOption, otpOption], + }, + ], + }, + { + name: 'prune', + description: 'Remove extraneous packages', + args: { + name: '[<@scope>/]', + isOptional: true, + }, + options: [ + omitOption, + dryRunOption, + jsonOption, + { + name: '--production', + description: 'Remove the packages specified in your devDependencies', + }, + ...workSpaceOptions, + ], + }, + { + name: 'publish', + description: 'Publish a package', + args: { + name: 'tarball|folder', + isOptional: true, + description: + 'A url or file path to a gzipped tar archive containing a single folder with a package.json file inside | A folder containing a package.json file', + template: ['folders'], + }, + options: [ + { + name: '--tag', + description: 'Registers the published package with the given tag', + args: { name: 'tag' }, + }, + ...workSpaceOptions, + { + name: '--access', + description: + 'Sets scoped package to be publicly viewable if set to \'public\'', + args: { + default: 'restricted', + suggestions: ['restricted', 'public'], + }, + }, + dryRunOption, + otpOption, + ], + }, + { + name: ['rebuild', 'rb'], + description: 'Rebuild a package', + args: { + name: '[<@scope>/][@]', + }, + options: [ + globalOption, + ...workSpaceOptions, + ignoreScriptsOption, + { + name: '--no-bin-links', + description: + 'Tells npm to not create symlinks (or .cmd shims on Windows) for package executables', + }, + ], + }, + { + name: 'repo', + description: 'Open package repository page in the browser', + args: { + name: 'package', + isOptional: true, + generators: npmSearchGenerator, + debounce: true, + isVariadic: true, + }, + options: [ + ...workSpaceOptions, + { + name: '--no-browser', + description: 'Display in command line instead of browser', + exclusiveOn: ['--browser'], + }, + { + name: '--browser', + description: + 'The browser that is called by the npm repo command to open websites', + args: { name: 'browser' }, + exclusiveOn: ['--no-browser'], + }, + ], + }, + { + name: 'restart', + description: 'Restart a package', + options: [ + ignoreScriptsOption, + scriptShellOption, + { + name: '--', + args: { + name: 'arg', + description: 'Arguments to be passed to the restart script', + }, + }, + ], + }, + { + name: 'root', + description: 'Display npm root', + options: [ + { + name: ['-g', '--global'], + description: + 'Print the effective global node_modules folder to standard out', + }, + ], + }, + { + name: ['search', 's', 'se', 'find'], + description: 'Search for packages', + args: { + name: 'search terms', + isVariadic: true, + }, + options: [ + longOption, + jsonOption, + { + name: '--color', + description: 'Show colors', + args: { + name: 'always', + suggestions: ['always'], + description: 'Always show colors', + }, + exclusiveOn: ['--no-color'], + }, + { + name: '--no-color', + description: 'Do not show colors', + exclusiveOn: ['--color'], + }, + parseableOption, + { + name: '--no-description', + description: 'Do not show descriptions', + }, + { + name: '--searchopts', + description: + 'Space-separated options that are always passed to search', + args: { + name: 'searchopts', + }, + }, + { + name: '--searchexclude', + description: + 'Space-separated options that limit the results from search', + args: { + name: 'searchexclude', + }, + }, + registryOption, + { + name: '--prefer-online', + description: + 'If true, staleness checks for cached data will be forced, making the CLI look for updates immediately even for fresh package data', + exclusiveOn: ['--prefer-offline', '--offline'], + }, + { + name: '--prefer-offline', + description: + 'If true, staleness checks for cached data will be bypassed, but missing data will be requested from the server', + exclusiveOn: ['--prefer-online', '--offline'], + }, + { + name: '--offline', + description: + 'Force offline mode: no network requests will be done during install', + exclusiveOn: ['--prefer-online', '--prefer-offline'], + }, + ], + }, + { name: 'set', description: 'Sets the config key to the value' }, + { + name: 'set-script', + description: 'Set tasks in the scripts section of package.json', + args: [ + { + name: 'script', + description: + 'Name of the task to be added to the scripts section of package.json', + }, + { + name: 'command', + description: 'Command to run when script is called', + }, + ], + options: workSpaceOptions, + }, + { + name: 'shrinkwrap', + description: 'Lock down dependency versions for publication', + }, + { + name: 'star', + description: 'Mark your favorite packages', + args: { + name: 'pkg', + description: 'Package to mark as favorite', + }, + options: [ + registryOption, + { + name: '--no-unicode', + description: 'Do not use unicode characters in the tree output', + }, + ], + }, + { + name: 'stars', + description: 'View packages marked as favorites', + args: { + name: 'user', + isOptional: true, + description: 'View packages marked as favorites by ', + }, + options: [registryOption], + }, + { + name: 'start', + description: 'Start a package', + options: [ + ignoreScriptsOption, + scriptShellOption, + { + name: '--', + args: { + name: 'arg', + description: 'Arguments to be passed to the start script', + }, + }, + ], + }, + { + name: 'stop', + description: 'Stop a package', + options: [ + ignoreScriptsOption, + scriptShellOption, + { + name: '--', + args: { + name: 'arg', + description: 'Arguments to be passed to the stop script', + }, + }, + ], + }, + { + name: 'team', + description: 'Manage organization teams and team memberships', + subcommands: [ + { + name: 'create', + args: { name: 'scope:team' }, + options: [registryOption, otpOption], + }, + { + name: 'destroy', + args: { name: 'scope:team' }, + options: [registryOption, otpOption], + }, + { + name: 'add', + args: [{ name: 'scope:team' }, { name: 'user' }], + options: [registryOption, otpOption], + }, + { + name: 'rm', + args: [{ name: 'scope:team' }, { name: 'user' }], + options: [registryOption, otpOption], + }, + { + name: 'ls', + args: { name: 'scope|scope:team' }, + options: [registryOption, jsonOption, parseableOption], + }, + ], + }, + { + name: ['test', 'tst', 't'], + description: 'Test a package', + options: [ignoreScriptsOption, scriptShellOption], + }, + { + name: 'token', + description: 'Manage your authentication tokens', + subcommands: [ + { + name: 'list', + description: 'Shows a table of all active authentication tokens', + options: [jsonOption, parseableOption], + }, + { + name: 'create', + description: 'Create a new authentication token', + options: [ + { + name: '--read-only', + description: + 'This is used to mark a token as unable to publish when configuring limited access tokens with the npm token create command', + }, + { + name: '--cidr', + description: + 'This is a list of CIDR address to be used when configuring limited access tokens with the npm token create command', + isRepeatable: true, + args: { + name: 'cidr', + }, + }, + ], + }, + { + name: 'revoke', + description: + 'Immediately removes an authentication token from the registry. You will no longer be able to use it', + args: { name: 'idtoken' }, + }, + ], + options: [registryOption, otpOption], + }, + uninstallSubcommand('uninstall'), + uninstallSubcommand(['r', 'rm']), + uninstallSubcommand('un'), + uninstallSubcommand('remove'), + uninstallSubcommand('unlink'), + { + name: 'unpublish', + description: 'Remove a package from the registry', + args: { + name: '[<@scope>/][@]', + }, + options: [ + dryRunOption, + { + name: ['-f', '--force'], + description: + 'Allow unpublishing all versions of a published package. Removes various protections against unfortunate side effects, common mistakes, unnecessary performance degradation, and malicious input', + isDangerous: true, + }, + ...workSpaceOptions, + ], + }, + { + name: 'unstar', + description: 'Remove an item from your favorite packages', + args: { + name: 'pkg', + description: 'Package to unmark as favorite', + }, + options: [ + registryOption, + otpOption, + { + name: '--no-unicode', + description: 'Do not use unicode characters in the tree output', + }, + ], + }, + { + name: ['update', 'upgrade', 'up'], + description: 'Update a package', + options: [ + { name: '-g', description: 'Update global package' }, + { + name: '--global-style', + description: + 'Causes npm to install the package into your local node_modules folder with the same layout it uses with the global node_modules folder', + }, + { + name: '--legacy-bundling', + description: + 'Causes npm to install the package such that versions of npm prior to 1.4, such as the one included with node 0.8, can install the package', + }, + { + name: '--strict-peer-deps', + description: + 'If set to true, and --legacy-peer-deps is not set, then any conflicting peerDependencies will be treated as an install failure', + }, + { + name: '--no-package-lock', + description: 'Ignores package-lock.json files when installing', + }, + omitOption, + ignoreScriptsOption, + { + name: '--no-audit', + description: + 'Submit audit reports alongside the current npm command to the default registry and all registries configured for scopes', + }, + { + name: '--no-bin-links', + description: + 'Tells npm to not create symlinks (or .cmd shims on Windows) for package executables', + }, + { + name: '--no-fund', + description: + 'Hides the message at the end of each npm install acknowledging the number of dependencies looking for funding', + }, + { + name: '--save', + description: + 'Update the semver values of direct dependencies in your project package.json', + }, + dryRunOption, + ...workSpaceOptions, + ], + }, + { + name: 'version', + description: 'Bump a package version', + options: [ + ...workSpaceOptions, + jsonOption, + { + name: '--allow-same-version', + description: + 'Prevents throwing an error when npm version is used to set the new version to the same value as the current version', + }, + { + name: '--no-commit-hooks', + description: + 'Do not run git commit hooks when using the npm version command', + }, + { + name: '--no-git-tag-version', + description: + 'Do not tag the commit when using the npm version command', + }, + { + name: '--preid', + description: + 'The \'prerelease identifier\' to use as a prefix for the \'prerelease\' part of a semver. Like the rc in 1.2.0-rc.8', + args: { + name: 'prerelease-id', + }, + }, + { + name: '--sign-git-tag', + description: + 'If set to true, then the npm version command will tag the version using -s to add a signature', + }, + ], + }, + { + name: ['view', 'v', 'info', 'show'], + description: 'View registry info', + options: [...workSpaceOptions, jsonOption], + }, + { + name: 'whoami', + description: 'Display npm username', + options: [registryOption], + }, + ], +}; + +export default completionSpec; diff --git a/code/extensions/terminal-suggest/src/completions/pnpm.ts b/code/extensions/terminal-suggest/src/completions/pnpm.ts new file mode 100644 index 00000000000..12b71e358e1 --- /dev/null +++ b/code/extensions/terminal-suggest/src/completions/pnpm.ts @@ -0,0 +1,1031 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// GENERATORS + +import { npmScriptsGenerator, npmSearchGenerator } from './npm'; +import { dependenciesGenerator, nodeClis } from './yarn'; + +const filterMessages = (out: string): string => { + return out.startsWith('warning:') || out.startsWith('error:') + ? out.split('\n').slice(1).join('\n') + : out; +}; + +const searchBranches: Fig.Generator = { + script: ['git', 'branch', '--no-color'], + postProcess: function (out) { + const output = filterMessages(out); + + if (output.startsWith('fatal:')) { + return []; + } + + return output.split('\n').map((elm) => { + let name = elm.trim(); + const parts = elm.match(/\S+/g); + if (parts && parts.length > 1) { + if (parts[0] === '*') { + // Current branch. + return { + name: elm.replace('*', '').trim(), + description: 'Current branch', + }; + } else if (parts[0] === '+') { + // Branch checked out in another worktree. + name = elm.replace('+', '').trim(); + } + } + + return { + name, + description: 'Branch', + icon: 'fig://icon?type=git', + }; + }); + }, +}; + +const generatorInstalledPackages: Fig.Generator = { + script: ['pnpm', 'ls'], + postProcess: function (out) { + /** + * out + * @example + * ``` + * Legend: production dependency, optional only, dev only + * + * /xxxx/xxxx/ (PRIVATE) + * + * dependencies: + * lodash 4.17.21 + * foo link:packages/foo + * + * devDependencies: + * typescript 4.7.4 + * ``` + */ + if (out.includes('ERR_PNPM')) { + return []; + } + + const output = out + .split('\n') + .slice(3) + // remove empty lines, '*dependencies:' lines, local workspace packages (eg: 'foo':'workspace:*') + .filter( + (item) => + !!item && + !item.toLowerCase().includes('dependencies') && + !item.includes('link:') + ) + .map((item) => item.replace(/\s/, '@')); // typescript 4.7.4 -> typescript@4.7.4 + + return output.map((pkg) => { + return { + name: pkg, + icon: 'fig://icon?type=package', + }; + }); + }, +}; + +const FILTER_OPTION: Fig.Option = { + name: '--filter', + args: { + template: 'filepaths', + name: 'Filepath / Package', + description: + 'To only select packages under the specified directory, you may specify any absolute path, typically in POSIX format', + }, + description: `Filtering allows you to restrict commands to specific subsets of packages. +pnpm supports a rich selector syntax for picking packages by name or by relation. +More details: https://pnpm.io/filtering`, +}; + +/** Options that being appended for `pnpm i` and `add` */ +const INSTALL_BASE_OPTIONS: Fig.Option[] = [ + { + name: '--offline', + description: + 'If true, pnpm will use only packages already available in the store. If a package won\'t be found locally, the installation will fail', + }, + { + name: '--prefer-offline', + description: + 'If true, staleness checks for cached data will be bypassed, but missing data will be requested from the server. To force full offline mode, use --offline', + }, + { + name: '--ignore-scripts', + description: + 'Do not execute any scripts defined in the project package.json and its dependencies', + }, + { + name: '--reporter', + description: `Allows you to choose the reporter that will log debug info to the terminal about the installation progress`, + args: { + name: 'Reporter Type', + suggestions: ['silent', 'default', 'append-only', 'ndjson'], + }, + }, +]; + +/** Base options for pnpm i when run without any arguments */ +const INSTALL_OPTIONS: Fig.Option[] = [ + { + name: ['-P', '--save-prod'], + description: `Pnpm will not install any package listed in devDependencies if the NODE_ENV environment variable is set to production. +Use this flag to instruct pnpm to ignore NODE_ENV and take its production status from this flag instead`, + }, + { + name: ['-D', '--save-dev'], + description: + 'Only devDependencies are installed regardless of the NODE_ENV', + }, + { + name: '--no-optional', + description: 'OptionalDependencies are not installed', + }, + { + name: '--lockfile-only', + description: + 'When used, only updates pnpm-lock.yaml and package.json instead of checking node_modules and downloading dependencies', + }, + { + name: '--frozen-lockfile', + description: + 'If true, pnpm doesn\'t generate a lockfile and fails to install if the lockfile is out of sync with the manifest / an update is needed or no lockfile is present', + }, + { + name: '--use-store-server', + description: + 'Starts a store server in the background. The store server will keep running after installation is done. To stop the store server, run pnpm server stop', + }, + { + name: '--shamefully-hoist', + description: + 'Creates a flat node_modules structure, similar to that of npm or yarn. WARNING: This is highly discouraged', + }, +]; + +/** Base options for pnpm add */ +const INSTALL_PACKAGE_OPTIONS: Fig.Option[] = [ + { + name: ['-P', '--save-prod'], + description: 'Install the specified packages as regular dependencies', + }, + { + name: ['-D', '--save-dev'], + description: 'Install the specified packages as devDependencies', + }, + { + name: ['-O', '--save-optional'], + description: 'Install the specified packages as optionalDependencies', + }, + { + name: '--no-save', + description: 'Prevents saving to `dependencies`', + }, + { + name: ['-E', '--save-exact'], + description: + 'Saved dependencies will be configured with an exact version rather than using pnpm\'s default semver range operator', + }, + { + name: '--save-peer', + description: + 'Using --save-peer will add one or more packages to peerDependencies and install them as dev dependencies', + }, + { + name: ['--ignore-workspace-root-check', '-W#'], + description: `Adding a new dependency to the root workspace package fails, unless the --ignore-workspace-root-check or -W flag is used. +For instance, pnpm add debug -W`, + }, + { + name: ['--global', '-g'], + description: `Install a package globally`, + }, + { + name: '--workspace', + description: `Only adds the new dependency if it is found in the workspace`, + }, + FILTER_OPTION, +]; + +// SUBCOMMANDS +const SUBCOMMANDS_MANAGE_DEPENDENCIES: Fig.Subcommand[] = [ + { + name: 'add', + description: `Installs a package and any packages that it depends on. By default, any new package is installed as a production dependency`, + args: { + name: 'package', + generators: npmSearchGenerator, + debounce: true, + isVariadic: true, + }, + options: [...INSTALL_BASE_OPTIONS, ...INSTALL_PACKAGE_OPTIONS], + }, + { + name: ['install', 'i'], + description: `Pnpm install is used to install all dependencies for a project. +In a CI environment, installation fails if a lockfile is present but needs an update. +Inside a workspace, pnpm install installs all dependencies in all the projects. +If you want to disable this behavior, set the recursive-install setting to false`, + async generateSpec(tokens) { + // `pnpm i` with args is an `pnpm add` alias + const hasArgs = + tokens.filter((token) => token.trim() !== '' && !token.startsWith('-')) + .length > 2; + + return { + name: 'install', + options: [ + ...INSTALL_BASE_OPTIONS, + ...(hasArgs ? INSTALL_PACKAGE_OPTIONS : INSTALL_OPTIONS), + ], + }; + }, + args: { + name: 'package', + isOptional: true, + generators: npmSearchGenerator, + debounce: true, + isVariadic: true, + }, + }, + { + name: ['install-test', 'it'], + description: + 'Runs pnpm install followed immediately by pnpm test. It takes exactly the same arguments as pnpm install', + options: [...INSTALL_BASE_OPTIONS, ...INSTALL_OPTIONS], + }, + { + name: ['update', 'upgrade', 'up'], + description: `Pnpm update updates packages to their latest version based on the specified range. +When used without arguments, updates all dependencies. You can use patterns to update specific dependencies`, + args: { + name: 'Package', + isOptional: true, + filterStrategy: 'fuzzy', + generators: dependenciesGenerator, + isVariadic: true, + }, + options: [ + { + name: ['--recursive', '-r'], + description: + 'Concurrently runs update in all subdirectories with a package.json (excluding node_modules)', + }, + { + name: ['--latest', '-L'], + description: + 'Ignores the version range specified in package.json. Instead, the version specified by the latest tag will be used (potentially upgrading the packages across major versions)', + }, + { + name: '--global', + description: 'Update global packages', + }, + { + name: ['-P', '--save-prod'], + description: `Only update packages in dependencies and optionalDependencies`, + }, + { + name: ['-D', '--save-dev'], + description: 'Only update packages in devDependencies', + }, + { + name: '--no-optional', + description: 'Don\'t update packages in optionalDependencies', + }, + { + name: ['--interactive', '-i'], + description: + 'Show outdated dependencies and select which ones to update', + }, + { + name: '--workspace', + description: `Tries to link all packages from the workspace. Versions are updated to match the versions of packages inside the workspace. +If specific packages are updated, the command will fail if any of the updated dependencies are not found inside the workspace. For instance, the following command fails if express is not a workspace package: pnpm up -r --workspace express`, + }, + FILTER_OPTION, + ], + }, + { + name: ['remove', 'rm', 'uninstall', 'un'], + description: `Removes packages from node_modules and from the project's package.json`, + args: { + name: 'Package', + filterStrategy: 'fuzzy', + generators: dependenciesGenerator, + isVariadic: true, + }, + options: [ + { + name: ['--recursive', '-r'], + description: `When used inside a workspace, removes a dependency (or dependencies) from every workspace package. +When used not inside a workspace, removes a dependency (or dependencies) from every package found in subdirectories`, + }, + { + name: '--global', + description: 'Remove a global package', + }, + { + name: ['-P', '--save-prod'], + description: `Only remove the dependency from dependencies`, + }, + { + name: ['-D', '--save-dev'], + description: 'Only remove the dependency from devDependencies', + }, + { + name: ['--save-optional', '-O'], + description: 'Only remove the dependency from optionalDependencies', + }, + FILTER_OPTION, + ], + }, + { + name: ['link', 'ln'], + description: `Makes the current local package accessible system-wide, or in another location`, + args: [ + { + name: 'Package', + filterStrategy: 'fuzzy', + generators: dependenciesGenerator, + isVariadic: true, + }, + { template: 'filepaths' }, + ], + options: [ + { + name: ['--dir', '-C'], + description: `Changes the link location to `, + }, + { + name: '--global', + description: + 'Links the specified package () from global node_modules to the node_nodules of package from where this command was executed or specified via --dir option', + }, + ], + }, + { + name: 'unlink', + description: `Unlinks a system-wide package (inverse of pnpm link). +If called without arguments, all linked dependencies will be unlinked. +This is similar to yarn unlink, except pnpm re-installs the dependency after removing the external link`, + args: [ + { + name: 'Package', + filterStrategy: 'fuzzy', + generators: dependenciesGenerator, + isVariadic: true, + }, + { template: 'filepaths' }, + ], + options: [ + { + name: ['--recursive', '-r'], + description: `Unlink in every package found in subdirectories or in every workspace package, when executed inside a workspace`, + }, + FILTER_OPTION, + ], + }, + { + name: 'import', + description: + 'Pnpm import generates a pnpm-lock.yaml from an npm package-lock.json (or npm-shrinkwrap.json) file', + }, + { + name: ['rebuild', 'rb'], + description: `Rebuild a package`, + args: [ + { + name: 'Package', + filterStrategy: 'fuzzy', + generators: dependenciesGenerator, + isVariadic: true, + }, + { template: 'filepaths' }, + ], + options: [ + { + name: ['--recursive', '-r'], + description: `This command runs the pnpm rebuild command in every package of the monorepo`, + }, + FILTER_OPTION, + ], + }, + { + name: 'prune', + description: `Removes unnecessary packages`, + options: [ + { + name: '--prod', + description: `Remove the packages specified in devDependencies`, + }, + { + name: '--no-optional', + description: `Remove the packages specified in optionalDependencies`, + }, + ], + }, + { + name: 'fetch', + description: `EXPERIMENTAL FEATURE: Fetch packages from a lockfile into virtual store, package manifest is ignored: https://pnpm.io/cli/fetch`, + options: [ + { + name: '--prod', + description: `Development packages will not be fetched`, + }, + { + name: '--dev', + description: `Only development packages will be fetched`, + }, + ], + }, + { + name: 'patch', + description: `This command will cause a package to be extracted in a temporary directory intended to be editable at will`, + args: { + name: 'package', + generators: generatorInstalledPackages, + }, + options: [ + { + name: '--edit-dir', + description: `The package that needs to be patched will be extracted to this directory`, + }, + ], + }, + { + name: 'patch-commit', + args: { + name: 'dir', + }, + description: `Generate a patch out of a directory`, + }, + { + name: 'patch-remove', + args: { + name: 'package', + isVariadic: true, + // TODO: would be nice to have a generator of all patched packages + }, + }, +]; + +const SUBCOMMANDS_RUN_SCRIPTS: Fig.Subcommand[] = [ + { + name: ['run', 'run-script'], + description: 'Runs a script defined in the package\'s manifest file', + args: { + name: 'Scripts', + filterStrategy: 'fuzzy', + generators: npmScriptsGenerator, + isVariadic: true, + }, + options: [ + { + name: ['-r', '--recursive'], + description: `This runs an arbitrary command from each package's 'scripts' object. If a package doesn't have the command, it is skipped. If none of the packages have the command, the command fails`, + }, + { + name: '--if-present', + description: + 'You can use the --if-present flag to avoid exiting with a non-zero exit code when the script is undefined. This lets you run potentially undefined scripts without breaking the execution chain', + }, + { + name: '--parallel', + description: + 'Completely disregard concurrency and topological sorting, running a given script immediately in all matching packages with prefixed streaming output. This is the preferred flag for long-running processes over many packages, for instance, a lengthy build process', + }, + { + name: '--stream', + description: + 'Stream output from child processes immediately, prefixed with the originating package directory. This allows output from different packages to be interleaved', + }, + FILTER_OPTION, + ], + }, + { + name: 'exec', + description: `Execute a shell command in scope of a project. +node_modules/.bin is added to the PATH, so pnpm exec allows executing commands of dependencies`, + args: { + name: 'Scripts', + filterStrategy: 'fuzzy', + generators: dependenciesGenerator, + isVariadic: true, + }, + options: [ + { + name: ['-r', '--recursive'], + description: `Execute the shell command in every project of the workspace. +The name of the current package is available through the environment variable PNPM_PACKAGE_NAME (supported from pnpm v2.22.0 onwards)`, + }, + { + name: '--parallel', + description: + 'Completely disregard concurrency and topological sorting, running a given script immediately in all matching packages with prefixed streaming output. This is the preferred flag for long-running processes over many packages, for instance, a lengthy build process', + }, + FILTER_OPTION, + ], + }, + { + name: ['test', 't', 'tst'], + description: `Runs an arbitrary command specified in the package's test property of its scripts object. +The intended usage of the property is to specify a command that runs unit or integration testing for your program`, + }, + { + name: 'start', + description: `Runs an arbitrary command specified in the package's start property of its scripts object. If no start property is specified on the scripts object, it will attempt to run node server.js as a default, failing if neither are present. +The intended usage of the property is to specify a command that starts your program`, + }, +]; + +const SUBCOMMANDS_REVIEW_DEPS: Fig.Subcommand[] = [ + { + name: 'audit', + description: `Checks for known security issues with the installed packages. +If security issues are found, try to update your dependencies via pnpm update. +If a simple update does not fix all the issues, use overrides to force versions that are not vulnerable. +For instance, if lodash@<2.1.0 is vulnerable, use overrides to force lodash@^2.1.0. +Details at: https://pnpm.io/cli/audit`, + options: [ + { + name: '--audit-level', + description: `Only print advisories with severity greater than or equal to `, + args: { + name: 'Audit Level', + default: 'low', + suggestions: ['low', 'moderate', 'high', 'critical'], + }, + }, + { + name: '--fix', + description: + 'Add overrides to the package.json file in order to force non-vulnerable versions of the dependencies', + }, + { + name: '--json', + description: `Output audit report in JSON format`, + }, + { + name: ['--dev', '-D'], + description: `Only audit dev dependencies`, + }, + { + name: ['--prod', '-P'], + description: `Only audit production dependencies`, + }, + { + name: '--no-optional', + description: `Don't audit optionalDependencies`, + }, + { + name: '--ignore-registry-errors', + description: `If the registry responds with a non-200 status code, the process should exit with 0. So the process will fail only if the registry actually successfully responds with found vulnerabilities`, + }, + ], + }, + { + name: ['list', 'ls'], + description: `This command will output all the versions of packages that are installed, as well as their dependencies, in a tree-structure. +Positional arguments are name-pattern@version-range identifiers, which will limit the results to only the packages named. For example, pnpm list 'babel-*' 'eslint-*' semver@5`, + options: [ + { + name: ['--recursive', '-r'], + description: `Perform command on every package in subdirectories or on every workspace package, when executed inside a workspace`, + }, + { + name: '--json', + description: `Log output in JSON format`, + }, + { + name: '--long', + description: `Show extended information`, + }, + { + name: '--parseable', + description: `Outputs package directories in a parseable format instead of their tree view`, + }, + { + name: '--global', + description: `List packages in the global install directory instead of in the current project`, + }, + { + name: '--depth', + description: `Max display depth of the dependency tree. +pnpm ls --depth 0 will list direct dependencies only. pnpm ls --depth -1 will list projects only. Useful inside a workspace when used with the -r option`, + args: { name: 'number' }, + }, + { + name: ['--dev', '-D'], + description: `Only list dev dependencies`, + }, + { + name: ['--prod', '-P'], + description: `Only list production dependencies`, + }, + { + name: '--no-optional', + description: `Don't list optionalDependencies`, + }, + FILTER_OPTION, + ], + }, + { + name: 'outdated', + description: `Checks for outdated packages. The check can be limited to a subset of the installed packages by providing arguments (patterns are supported)`, + options: [ + { + name: ['--recursive', '-r'], + description: `Check for outdated dependencies in every package found in subdirectories, or in every workspace package when executed inside a workspace`, + }, + { + name: '--long', + description: `Print details`, + }, + { + name: '--global', + description: `List outdated global packages`, + }, + { + name: '--no-table', + description: `Prints the outdated dependencies in a list format instead of the default table. Good for small consoles`, + }, + { + name: '--compatible', + description: `Prints only versions that satisfy specifications in package.json`, + }, + { + name: ['--dev', '-D'], + description: `Only list dev dependencies`, + }, + { + name: ['--prod', '-P'], + description: `Only list production dependencies`, + }, + { + name: '--no-optional', + description: `Doesn't check optionalDependencies`, + }, + ], + }, + { + name: 'why', + description: `Shows all packages that depend on the specified package`, + args: { + name: 'Scripts', + filterStrategy: 'fuzzy', + generators: dependenciesGenerator, + isVariadic: true, + }, + options: [ + { + name: ['--recursive', '-r'], + description: `Show the dependency tree for the specified package on every package in subdirectories or on every workspace package when executed inside a workspace`, + }, + { + name: '--json', + description: `Log output in JSON format`, + }, + { + name: '--long', + description: `Show verbose output`, + }, + { + name: '--parseable', + description: `Show parseable output instead of tree view`, + }, + { + name: '--global', + description: `List packages in the global install directory instead of in the current project`, + }, + { + name: ['--dev', '-D'], + description: `Only display the dependency tree for packages in devDependencies`, + }, + { + name: ['--prod', '-P'], + description: `Only display the dependency tree for packages in dependencies`, + }, + FILTER_OPTION, + ], + }, +]; + +const SUBCOMMANDS_MISC: Fig.Subcommand[] = [ + { + name: 'publish', + description: `Publishes a package to the registry. +When publishing a package inside a workspace, the LICENSE file from the root of the workspace is packed with the package (unless the package has a license of its own). +You may override some fields before publish, using the publishConfig field in package.json. You also can use the publishConfig.directory to customize the published subdirectory (usually using third party build tools). +When running this command recursively (pnpm -r publish), pnpm will publish all the packages that have versions not yet published to the registry`, + args: { + name: 'Branch', + generators: searchBranches, + }, + options: [ + { + name: '--tag', + description: `Publishes the package with the given tag. By default, pnpm publish updates the latest tag`, + args: { + name: '', + }, + }, + { + name: '--dry-run', + description: `Does everything a publish would do except actually publishing to the registry`, + }, + { + name: '--ignore-scripts', + description: `Ignores any publish related lifecycle scripts (prepublishOnly, postpublish, and the like)`, + }, + { + name: '--no-git-checks', + description: `Don't check if current branch is your publish branch, clean, and up-to-date`, + }, + { + name: '--access', + description: `Tells the registry whether the published package should be public or restricted`, + args: { + name: 'Type', + suggestions: ['public', 'private'], + }, + }, + { + name: '--force', + description: `Try to publish packages even if their current version is already found in the registry`, + }, + { + name: '--report-summary', + description: `Save the list of published packages to pnpm-publish-summary.json. Useful when some other tooling is used to report the list of published packages`, + }, + FILTER_OPTION, + ], + }, + { + name: ['recursive', 'm', 'multi', '-r'], + description: `Runs a pnpm command recursively on all subdirectories in the package or every available workspace`, + options: [ + { + name: '--link-workspace-packages', + description: `Link locally available packages in workspaces of a monorepo into node_modules instead of re-downloading them from the registry. This emulates functionality similar to yarn workspaces. +When this is set to deep, local packages can also be linked to subdependencies. +Be advised that it is encouraged instead to use npmrc for this setting, to enforce the same behaviour in all environments. This option exists solely so you may override that if necessary`, + args: { + name: 'bool or `deep`', + suggestions: ['dee['], + }, + }, + { + name: '--workspace-concurrency', + description: `Set the maximum number of tasks to run simultaneously. For unlimited concurrency use Infinity`, + args: { name: '' }, + }, + { + name: '--bail', + description: `Stops when a task throws an error`, + }, + { + name: '--no-bail', + description: `Don't stop when a task throws an error`, + }, + { + name: '--sort', + description: `Packages are sorted topologically (dependencies before dependents)`, + }, + { + name: '--no-sort', + description: `Disable packages sorting`, + }, + { + name: '--reverse', + description: `The order of packages is reversed`, + }, + FILTER_OPTION, + ], + }, + { + name: 'server', + description: `Manage a store server`, + subcommands: [ + { + name: 'start', + description: + 'Starts a server that performs all interactions with the store. Other commands will delegate any store-related tasks to this server', + options: [ + { + name: '--background', + description: `Runs the server in the background, similar to daemonizing on UNIX systems`, + }, + { + name: '--network-concurrency', + description: `The maximum number of network requests to process simultaneously`, + args: { name: 'number' }, + }, + { + name: '--protocol', + description: `The communication protocol used by the server. When this is set to auto, IPC is used on all systems except for Windows, which uses TCP`, + args: { + name: 'Type', + suggestions: ['auto', 'tcp', 'ipc'], + }, + }, + { + name: '--port', + description: `The port number to use when TCP is used for communication. If a port is specified and the protocol is set to auto, regardless of system type, the protocol is automatically set to use TCP`, + args: { name: 'port number' }, + }, + { + name: '--store-dir', + description: `The directory to use for the content addressable store`, + args: { name: 'Path', template: 'filepaths' }, + }, + { + name: '--lock', + description: `Set to make the package store immutable to external processes while the server is running or not`, + }, + { + name: '--no-lock', + description: `Set to make the package store mutable to external processes while the server is running or not`, + }, + { + name: '--ignore-stop-requests', + description: `Prevents you from stopping the server using pnpm server stop`, + }, + { + name: '--ignore-upload-requests', + description: `Prevents creating a new side effect cache during install`, + }, + ], + }, + { + name: 'stop', + description: 'Stops the store server', + }, + { + name: 'status', + description: 'Prints information about the running server', + }, + ], + }, + { + name: 'store', + description: 'Managing the package store', + subcommands: [ + { + name: 'status', + description: `Checks for modified packages in the store. +Returns exit code 0 if the content of the package is the same as it was at the time of unpacking`, + }, + { + name: 'add', + description: `Functionally equivalent to pnpm add, +except this adds new packages to the store directly without modifying any projects or files outside of the store`, + }, + { + name: 'prune', + description: `Removes orphan packages from the store. +Pruning the store will save disk space, however may slow down future installations involving pruned packages. +Ultimately, it is a safe operation, however not recommended if you have orphaned packages from a package you intend to reinstall. +Please read the FAQ for more information on unreferenced packages and best practices. +Please note that this is prohibited when a store server is running`, + }, + { + name: 'path', + description: `Returns the path to the active store directory`, + }, + ], + }, + { + name: 'init', + description: + 'Creates a basic package.json file in the current directory, if it doesn\'t exist already', + }, + { + name: 'doctor', + description: 'Checks for known common issues with pnpm configuration', + }, +]; + +const subcommands = [ + ...SUBCOMMANDS_MANAGE_DEPENDENCIES, + ...SUBCOMMANDS_REVIEW_DEPS, + ...SUBCOMMANDS_RUN_SCRIPTS, + ...SUBCOMMANDS_MISC, +]; + +const recursiveSubcommandsNames = [ + 'add', + 'exec', + 'install', + 'list', + 'outdated', + 'publish', + 'rebuild', + 'remove', + 'run', + 'test', + 'unlink', + 'update', + 'why', +]; + +const recursiveSubcommands = subcommands.filter((subcommand) => { + if (Array.isArray(subcommand.name)) { + return subcommand.name.some((name) => + recursiveSubcommandsNames.includes(name) + ); + } + return recursiveSubcommandsNames.includes(subcommand.name); +}); + +// RECURSIVE SUBCOMMAND INDEX +SUBCOMMANDS_MISC[1].subcommands = recursiveSubcommands; + +// common options +const COMMON_OPTIONS: Fig.Option[] = [ + { + name: ['-C', '--dir'], + args: { + name: 'path', + template: 'folders', + }, + isPersistent: true, + description: + 'Run as if pnpm was started in instead of the current working directory', + }, + { + name: ['-w', '--workspace-root'], + args: { + name: 'workspace', + }, + isPersistent: true, + description: + 'Run as if pnpm was started in the root of the instead of the current working directory', + }, + { + name: ['-h', '--help'], + isPersistent: true, + description: 'Output usage information', + }, + { + name: ['-v', '--version'], + description: 'Show pnpm\'s version', + }, +]; + +// SPEC +const completionSpec: Fig.Spec = { + name: 'pnpm', + description: 'Fast, disk space efficient package manager', + args: { + name: 'Scripts', + filterStrategy: 'fuzzy', + generators: npmScriptsGenerator, + isVariadic: true, + }, + filterStrategy: 'fuzzy', + generateSpec: async (tokens, executeShellCommand) => { + const { script, postProcess } = dependenciesGenerator as Fig.Generator & { + script: string[]; + }; + + if (postProcess === undefined) { + return undefined; + } + + const packages = postProcess( + ( + await executeShellCommand({ + command: script[0], + args: script.slice(1), + }) + ).stdout, + tokens + ) + ?.filter((e) => e !== null) + .map(({ name }) => name as string); + + const subcommands = packages + ?.filter((name) => nodeClis.has(name)) + .map((name) => ({ + name, + loadSpec: name, + icon: 'fig://icon?type=package', + })); + + return { + name: 'pnpm', + subcommands, + }; + }, + subcommands, + options: COMMON_OPTIONS, +}; + +export default completionSpec; diff --git a/code/extensions/terminal-suggest/src/completions/upstream/env.ts b/code/extensions/terminal-suggest/src/completions/upstream/env.ts index f7b4eb0337a..fa215dbcf05 100644 --- a/code/extensions/terminal-suggest/src/completions/upstream/env.ts +++ b/code/extensions/terminal-suggest/src/completions/upstream/env.ts @@ -1,4 +1,4 @@ -const enviromentVariables: Fig.Generator = { +const environmentVariables: Fig.Generator = { custom: async (_tokens, _executeCommand, generatorContext) => { return Object.values(generatorContext.environmentVariables).map( (envVar) => ({ @@ -31,7 +31,7 @@ const completionSpec: Fig.Spec = { description: "Remove variable from the environment", args: { name: "name", - generators: enviromentVariables, + generators: environmentVariables, }, }, { diff --git a/code/extensions/terminal-suggest/src/completions/upstream/npm.ts b/code/extensions/terminal-suggest/src/completions/upstream/npm.ts deleted file mode 100644 index aa142e05661..00000000000 --- a/code/extensions/terminal-suggest/src/completions/upstream/npm.ts +++ /dev/null @@ -1,1610 +0,0 @@ -function uninstallSubcommand(named: string | string[]): Fig.Subcommand { - return { - name: named, - description: "Uninstall a package", - args: { - name: "package", - generators: dependenciesGenerator, - filterStrategy: "fuzzy", - isVariadic: true, - }, - options: npmUninstallOptions, - }; -} - -const atsInStr = (s: string) => (s.match(/@/g) || []).length; - -export const createNpmSearchHandler = - (keywords?: string[]) => - async ( - context: string[], - executeShellCommand: Fig.ExecuteCommandFunction, - shellContext: Fig.ShellContext - ): Promise => { - const searchTerm = context[context.length - 1]; - if (searchTerm === "") { - return []; - } - // Add optional keyword parameter - const keywordParameter = - keywords && keywords.length > 0 ? `+keywords:${keywords.join(",")}` : ""; - - const queryPackagesUrl = keywordParameter - ? `https://api.npms.io/v2/search?size=20&q=${searchTerm}${keywordParameter}` - : `https://api.npms.io/v2/search/suggestions?q=${searchTerm}&size=20`; - - // Query the API with the package name - const queryPackages = [ - "-s", - "-H", - "Accept: application/json", - queryPackagesUrl, - ]; - // We need to remove the '@' at the end of the searchTerm before querying versions - const queryVersions = [ - "-s", - "-H", - "Accept: application/vnd.npm.install-v1+json", - `https://registry.npmjs.org/${searchTerm.slice(0, -1)}`, - ]; - // If the end of our token is '@', then we want to generate version suggestions - // Otherwise, we want packages - const out = (query: string) => - executeShellCommand({ - command: "curl", - args: query[query.length - 1] === "@" ? queryVersions : queryPackages, - }); - // If our token starts with '@', then a 2nd '@' tells us we want - // versions. - // Otherwise, '@' anywhere else in the string will indicate the same. - const shouldGetVersion = searchTerm.startsWith("@") - ? atsInStr(searchTerm) > 1 - : searchTerm.includes("@"); - - try { - const data = JSON.parse((await out(searchTerm)).stdout); - if (shouldGetVersion) { - // create dist tags suggestions - const versions = Object.entries(data["dist-tags"] || {}).map( - ([key, value]) => ({ - name: key, - description: value, - }) - ) as Fig.Suggestion[]; - // create versions - versions.push( - ...Object.keys(data.versions) - .map((version) => ({ name: version }) as Fig.Suggestion) - .reverse() - ); - return versions; - } - - const results = keywordParameter ? data.results : data; - return results.map( - (item: { package: { name: string; description: string } }) => ({ - name: item.package.name, - description: item.package.description, - }) - ) as Fig.Suggestion[]; - } catch (error) { - console.error({ error }); - return []; - } - }; - -// GENERATORS -export const npmSearchGenerator: Fig.Generator = { - trigger: (newToken, oldToken) => { - // If the package name starts with '@', we want to trigger when - // the 2nd '@' is typed because we'll need to generate version - // suggetsions - // e.g. @typescript-eslint/types - if (oldToken.startsWith("@")) { - return !(atsInStr(oldToken) > 1 && atsInStr(newToken) > 1); - } - - // If the package name doesn't start with '@', then trigger when - // we see the first '@' so we can generate version suggestions - return !(oldToken.includes("@") && newToken.includes("@")); - }, - getQueryTerm: "@", - cache: { - ttl: 1000 * 60 * 60 * 24 * 2, // 2 days - }, - custom: createNpmSearchHandler(), -}; - -const workspaceGenerator: Fig.Generator = { - // script: "cat $(npm prefix)/package.json", - custom: async (tokens, executeShellCommand) => { - const { stdout: npmPrefix } = await executeShellCommand({ - command: "npm", - // eslint-disable-next-line @withfig/fig-linter/no-useless-arrays - args: ["prefix"], - }); - - const { stdout: out } = await executeShellCommand({ - command: "cat", - // eslint-disable-next-line @withfig/fig-linter/no-useless-arrays - args: [`${npmPrefix}/package.json`], - }); - - const suggestions: Fig.Suggestion[] = []; - try { - if (out.trim() == "") { - return suggestions; - } - - const packageContent = JSON.parse(out); - const workspaces = packageContent["workspaces"]; - - if (workspaces) { - for (const workspace of workspaces) { - suggestions.push({ - name: workspace, - description: "Workspaces", - }); - } - } - } catch (e) { - console.log(e); - } - return suggestions; - }, -}; - -/** Generator that lists package.json dependencies */ -export const dependenciesGenerator: Fig.Generator = { - trigger: (newToken) => newToken === "-g" || newToken === "--global", - custom: async function (tokens, executeShellCommand) { - if (!tokens.includes("-g") && !tokens.includes("--global")) { - const { stdout: npmPrefix } = await executeShellCommand({ - command: "npm", - // eslint-disable-next-line @withfig/fig-linter/no-useless-arrays - args: ["prefix"], - }); - const { stdout: out } = await executeShellCommand({ - command: "cat", - // eslint-disable-next-line @withfig/fig-linter/no-useless-arrays - args: [`${npmPrefix}/package.json`], - }); - const packageContent = JSON.parse(out); - const dependencies = packageContent["dependencies"] ?? {}; - const devDependencies = packageContent["devDependencies"]; - const optionalDependencies = packageContent["optionalDependencies"] ?? {}; - Object.assign(dependencies, devDependencies, optionalDependencies); - - return Object.keys(dependencies) - .filter((pkgName) => { - const isListed = tokens.some((current) => current === pkgName); - return !isListed; - }) - .map((pkgName) => ({ - name: pkgName, - icon: "📦", - description: dependencies[pkgName] - ? "dependency" - : optionalDependencies[pkgName] - ? "optionalDependency" - : "devDependency", - })); - } else { - const { stdout } = await executeShellCommand({ - command: "bash", - args: ["-c", "ls -1 `npm root -g`"], - }); - return stdout.split("\n").map((name) => ({ - name, - icon: "📦", - description: "Global dependency", - })); - } - }, -}; - -/** Generator that lists package.json scripts (with the respect to the `fig` field) */ -export const npmScriptsGenerator: Fig.Generator = { - cache: { - strategy: "stale-while-revalidate", - cacheByDirectory: true, - }, - script: [ - "bash", - "-c", - "until [[ -f package.json ]] || [[ $PWD = '/' ]]; do cd ..; done; cat package.json", - ], - postProcess: function (out, [npmClient]) { - if (out.trim() == "") { - return []; - } - - try { - const packageContent = JSON.parse(out); - const scripts = packageContent["scripts"]; - const figCompletions = packageContent["fig"] || {}; - - if (scripts) { - return Object.entries(scripts).map(([scriptName, scriptContents]) => { - const icon = - npmClient === "yarn" - ? "fig://icon?type=yarn" - : "fig://icon?type=npm"; - const customScripts: Fig.Suggestion = figCompletions[scriptName]; - return { - name: scriptName, - icon, - description: scriptContents as string, - priority: 51, - /** - * If there are custom definitions for the scripts - * we want to override the default values - * */ - ...customScripts, - }; - }); - } - } catch (e) { - console.error(e); - } - - return []; - }, -}; - -const globalOption: Fig.Option = { - name: ["-g", "--global"], - description: - "Operates in 'global' mode, so that packages are installed into the prefix folder instead of the current working directory", -}; - -const jsonOption: Fig.Option = { - name: "--json", - description: "Show output in json format", -}; - -const omitOption: Fig.Option = { - name: "--omit", - description: "Dependency types to omit from the installation tree on disk", - args: { - name: "Package type", - default: "dev", - suggestions: ["dev", "optional", "peer"], - }, - isRepeatable: 3, -}; - -const parseableOption: Fig.Option = { - name: ["-p", "--parseable"], - description: - "Output parseable results from commands that write to standard output", -}; - -const longOption: Fig.Option = { - name: ["-l", "--long"], - description: "Show extended information", -}; - -const workSpaceOptions: Fig.Option[] = [ - { - name: ["-w", "--workspace"], - description: - "Enable running a command in the context of the configured workspaces of the current project", - args: { - name: "workspace", - generators: workspaceGenerator, - isVariadic: true, - }, - }, - { - name: ["-ws", "--workspaces"], - description: - "Enable running a command in the context of all the configured workspaces", - }, -]; - -const npmUninstallOptions: Fig.Option[] = [ - { - name: ["-S", "--save"], - description: "Package will be removed from your dependencies", - }, - { - name: ["-D", "--save-dev"], - description: "Package will appear in your `devDependencies`", - }, - { - name: ["-O", "--save-optional"], - description: "Package will appear in your `optionalDependencies`", - }, - { - name: "--no-save", - description: "Prevents saving to `dependencies`", - }, - { - name: "-g", - description: "Uninstall global package", - }, - ...workSpaceOptions, -]; - -const npmListOptions: Fig.Option[] = [ - { - name: ["-a", "-all"], - description: "Show all outdated or installed packages", - }, - jsonOption, - longOption, - parseableOption, - { - name: "--depth", - description: "The depth to go when recursing packages", - args: { name: "depth" }, - }, - { - name: "--link", - description: "Limits output to only those packages that are linked", - }, - { - name: "--package-lock-only", - description: - "Current operation will only use the package-lock.json, ignoring node_modules", - }, - { - name: "--no-unicode", - description: "Uses unicode characters in the tree output", - }, - globalOption, - omitOption, - ...workSpaceOptions, -]; - -const registryOption: Fig.Option = { - name: "--registry", - description: "The base URL of the npm registry", - args: { name: "registry" }, -}; - -const verboseOption: Fig.Option = { - name: "--verbose", - description: "Show extra information", - args: { name: "verbose" }, -}; - -const otpOption: Fig.Option = { - name: "--otp", - description: "One-time password from a two-factor authenticator", - args: { name: "otp" }, -}; - -const ignoreScriptsOption: Fig.Option = { - name: "--ignore-scripts", - description: - "If true, npm does not run scripts specified in package.json files", -}; - -const scriptShellOption: Fig.Option = { - name: "--script-shell", - description: - "The shell to use for scripts run with the npm exec, npm run and npm init commands", - args: { name: "script-shell" }, -}; - -const dryRunOption: Fig.Option = { - name: "--dry-run", - description: - "Indicates that you don't want npm to make any changes and that it should only report what it would have done", -}; - -const completionSpec: Fig.Spec = { - name: "npm", - parserDirectives: { - flagsArePosixNoncompliant: true, - }, - description: "Node package manager", - subcommands: [ - { - name: ["install", "i", "add"], - description: "Install a package and its dependencies", - args: { - name: "package", - isOptional: true, - generators: npmSearchGenerator, - debounce: true, - isVariadic: true, - }, - options: [ - { - name: ["-P", "--save-prod"], - description: - "Package will appear in your `dependencies`. This is the default unless `-D` or `-O` are present", - }, - { - name: ["-D", "--save-dev"], - description: "Package will appear in your `devDependencies`", - }, - { - name: ["-O", "--save-optional"], - description: "Package will appear in your `optionalDependencies`", - }, - { - name: "--no-save", - description: "Prevents saving to `dependencies`", - }, - { - name: ["-E", "--save-exact"], - description: - "Saved dependencies will be configured with an exact version rather than using npm's default semver range operator", - }, - { - name: ["-B", "--save-bundle"], - description: - "Saved dependencies will also be added to your bundleDependencies list", - }, - globalOption, - { - name: "--global-style", - description: - "Causes npm to install the package into your local node_modules folder with the same layout it uses with the global node_modules folder", - }, - { - name: "--legacy-bundling", - description: - "Causes npm to install the package such that versions of npm prior to 1.4, such as the one included with node 0.8, can install the package", - }, - { - name: "--legacy-peer-deps", - description: - "Bypass peerDependency auto-installation. Emulate install behavior of NPM v4 through v6", - }, - { - name: "--strict-peer-deps", - description: - "If set to true, and --legacy-peer-deps is not set, then any conflicting peerDependencies will be treated as an install failure", - }, - { - name: "--no-package-lock", - description: "Ignores package-lock.json files when installing", - }, - registryOption, - verboseOption, - omitOption, - ignoreScriptsOption, - { - name: "--no-audit", - description: - "Submit audit reports alongside the current npm command to the default registry and all registries configured for scopes", - }, - { - name: "--no-bin-links", - description: - "Tells npm to not create symlinks (or .cmd shims on Windows) for package executables", - }, - { - name: "--no-fund", - description: - "Hides the message at the end of each npm install acknowledging the number of dependencies looking for funding", - }, - dryRunOption, - ...workSpaceOptions, - ], - }, - { - name: ["run", "run-script"], - description: "Run arbitrary package scripts", - options: [ - ...workSpaceOptions, - { - name: "--if-present", - description: - "Npm will not exit with an error code when run-script is invoked for a script that isn't defined in the scripts section of package.json", - }, - { - name: "--silent", - description: "", - }, - ignoreScriptsOption, - scriptShellOption, - { - name: "--", - args: { - name: "args", - isVariadic: true, - // TODO: load the spec based on the runned script (see yarn spec `yarnScriptParsedDirectives`) - }, - }, - ], - args: { - name: "script", - description: "Script to run from your package.json", - filterStrategy: "fuzzy", - generators: npmScriptsGenerator, - }, - }, - { - name: "init", - description: "Trigger the initialization", - options: [ - { - name: ["-y", "--yes"], - description: - "Automatically answer 'yes' to any prompts that npm might print on the command line", - }, - { - name: "-w", - description: - "Create the folders and boilerplate expected while also adding a reference to your project workspaces property", - args: { name: "dir" }, - }, - ], - }, - { name: "access", description: "Set access controls on private packages" }, - { - name: ["adduser", "login"], - description: "Add a registry user account", - options: [ - registryOption, - { - name: "--scope", - description: - "Associate an operation with a scope for a scoped registry", - args: { - name: "scope", - description: "Scope name", - }, - }, - ], - }, - { - name: "audit", - description: "Run a security audit", - subcommands: [ - { - name: "fix", - description: - "If the fix argument is provided, then remediations will be applied to the package tree", - options: [ - dryRunOption, - { - name: ["-f", "--force"], - description: - "Removes various protections against unfortunate side effects, common mistakes, unnecessary performance degradation, and malicious input", - isDangerous: true, - }, - ...workSpaceOptions, - ], - }, - ], - options: [ - ...workSpaceOptions, - { - name: "--audit-level", - description: - "The minimum level of vulnerability for npm audit to exit with a non-zero exit code", - args: { - name: "audit", - suggestions: [ - "info", - "low", - "moderate", - "high", - "critical", - "none", - ], - }, - }, - { - name: "--package-lock-only", - description: - "Current operation will only use the package-lock.json, ignoring node_modules", - }, - jsonOption, - omitOption, - ], - }, - { - name: "bin", - description: "Print the folder where npm will install executables", - options: [globalOption], - }, - { - name: ["bugs", "issues"], - description: "Report bugs for a package in a web browser", - args: { - name: "package", - isOptional: true, - generators: npmSearchGenerator, - debounce: true, - isVariadic: true, - }, - options: [ - { - name: "--no-browser", - description: "Display in command line instead of browser", - exclusiveOn: ["--browser"], - }, - { - name: "--browser", - description: - "The browser that is called by the npm bugs command to open websites", - args: { name: "browser" }, - exclusiveOn: ["--no-browser"], - }, - registryOption, - ], - }, - { - name: "cache", - description: "Manipulates packages cache", - subcommands: [ - { - name: "add", - description: "Add the specified packages to the local cache", - }, - { - name: "clean", - description: "Delete all data out of the cache folder", - }, - { - name: "verify", - description: - "Verify the contents of the cache folder, garbage collecting any unneeded data, and verifying the integrity of the cache index and all cached data", - }, - ], - options: [ - { - name: "--cache", - args: { name: "cache" }, - description: "The location of npm's cache directory", - }, - ], - }, - { - name: ["ci", "clean-install", "install-clean"], - description: "Install a project with a clean slate", - options: [ - { - name: "--audit", - description: - 'When "true" submit audit reports alongside the current npm command to the default registry and all registries configured for scopes', - args: { - name: "audit", - suggestions: ["true", "false"], - }, - exclusiveOn: ["--no-audit"], - }, - { - name: "--no-audit", - description: - "Do not submit audit reports alongside the current npm command", - exclusiveOn: ["--audit"], - }, - ignoreScriptsOption, - scriptShellOption, - verboseOption, - registryOption, - ], - }, - { - name: "cit", - description: "Install a project with a clean slate and run tests", - }, - { - name: "clean-install-test", - description: "Install a project with a clean slate and run tests", - }, - { name: "completion", description: "Tab completion for npm" }, - { - name: ["config", "c"], - description: "Manage the npm configuration files", - subcommands: [ - { - name: "set", - description: "Sets the config key to the value", - args: [{ name: "key" }, { name: "value" }], - options: [ - { name: ["-g", "--global"], description: "Sets it globally" }, - ], - }, - { - name: "get", - description: "Echo the config value to stdout", - args: { name: "key" }, - }, - { - name: "list", - description: "Show all the config settings", - options: [ - { name: "-g", description: "Lists globally installed packages" }, - { name: "-l", description: "Also shows defaults" }, - jsonOption, - ], - }, - { - name: "delete", - description: "Deletes the key from all configuration files", - args: { name: "key" }, - }, - { - name: "edit", - description: "Opens the config file in an editor", - options: [ - { name: "--global", description: "Edits the global config" }, - ], - }, - ], - }, - { name: "create", description: "Create a package.json file" }, - { - name: ["dedupe", "ddp"], - description: "Reduce duplication in the package tree", - }, - { - name: "deprecate", - description: "Deprecate a version of a package", - options: [registryOption], - }, - { name: "dist-tag", description: "Modify package distribution tags" }, - { - name: ["docs", "home"], - description: "Open documentation for a package in a web browser", - args: { - name: "package", - isOptional: true, - generators: npmSearchGenerator, - debounce: true, - isVariadic: true, - }, - options: [ - ...workSpaceOptions, - registryOption, - { - name: "--no-browser", - description: "Display in command line instead of browser", - exclusiveOn: ["--browser"], - }, - { - name: "--browser", - description: - "The browser that is called by the npm docs command to open websites", - args: { name: "browser" }, - exclusiveOn: ["--no-browser"], - }, - ], - }, - { - name: "doctor", - description: "Check your npm environment", - options: [registryOption], - }, - { - name: "edit", - description: "Edit an installed package", - options: [ - { - name: "--editor", - description: "The command to run for npm edit or npm config edit", - }, - ], - }, - { - name: "explore", - description: "Browse an installed package", - args: { - name: "package", - filterStrategy: "fuzzy", - generators: dependenciesGenerator, - }, - }, - { name: "fund", description: "Retrieve funding information" }, - { name: "get", description: "Echo the config value to stdout" }, - { - name: "help", - description: "Get help on npm", - args: { - name: "term", - isVariadic: true, - description: "Terms to search for", - }, - options: [ - { - name: "--viewer", - description: "The program to use to view help content", - args: { - name: "viewer", - }, - }, - ], - }, - { - name: "help-search", - description: "Search npm help documentation", - args: { - name: "text", - description: "Text to search for", - }, - options: [longOption], - }, - { name: "hook", description: "Manage registry hooks" }, - { - name: "install-ci-test", - description: "Install a project with a clean slate and run tests", - }, - { name: "install-test", description: "Install package(s) and run tests" }, - { name: "it", description: "Install package(s) and run tests" }, - { - name: "link", - description: "Symlink a package folder", - args: { name: "path", template: "filepaths" }, - }, - { name: "ln", description: "Symlink a package folder" }, - { - name: "logout", - description: "Log out of the registry", - options: [ - registryOption, - { - name: "--scope", - description: - "Associate an operation with a scope for a scoped registry", - args: { - name: "scope", - description: "Scope name", - }, - }, - ], - }, - { - name: ["ls", "list"], - description: "List installed packages", - options: npmListOptions, - args: { name: "[@scope]/pkg", isVariadic: true }, - }, - { - name: "org", - description: "Manage orgs", - subcommands: [ - { - name: "set", - description: "Add a user to an org or manage roles", - args: [ - { - name: "orgname", - description: "Organization name", - }, - { - name: "username", - description: "User name", - }, - { - name: "role", - isOptional: true, - suggestions: ["developer", "admin", "owner"], - }, - ], - options: [registryOption, otpOption], - }, - { - name: "rm", - description: "Remove a user from an org", - args: [ - { - name: "orgname", - description: "Organization name", - }, - { - name: "username", - description: "User name", - }, - ], - options: [registryOption, otpOption], - }, - { - name: "ls", - description: - "List users in an org or see what roles a particular user has in an org", - args: [ - { - name: "orgname", - description: "Organization name", - }, - { - name: "username", - description: "User name", - isOptional: true, - }, - ], - options: [registryOption, otpOption, jsonOption, parseableOption], - }, - ], - }, - { - name: "outdated", - description: "Check for outdated packages", - args: { - name: "[<@scope>/]", - isVariadic: true, - isOptional: true, - }, - options: [ - { - name: ["-a", "-all"], - description: "Show all outdated or installed packages", - }, - jsonOption, - longOption, - parseableOption, - { - name: "-g", - description: "Checks globally", - }, - ...workSpaceOptions, - ], - }, - { - name: ["owner", "author"], - description: "Manage package owners", - subcommands: [ - { - name: "ls", - description: - "List all the users who have access to modify a package and push new versions. Handy when you need to know who to bug for help", - args: { name: "[@scope/]pkg" }, - options: [registryOption], - }, - { - name: "add", - description: - "Add a new user as a maintainer of a package. This user is enabled to modify metadata, publish new versions, and add other owners", - args: [{ name: "user" }, { name: "[@scope/]pkg" }], - options: [registryOption, otpOption], - }, - { - name: "rm", - description: - "Remove a user from the package owner list. This immediately revokes their privileges", - args: [{ name: "user" }, { name: "[@scope/]pkg" }], - options: [registryOption, otpOption], - }, - ], - }, - { - name: "pack", - description: "Create a tarball from a package", - args: { - name: "[<@scope>/]", - }, - options: [ - jsonOption, - dryRunOption, - ...workSpaceOptions, - { - name: "--pack-destination", - description: "Directory in which npm pack will save tarballs", - args: { - name: "pack-destination", - template: ["folders"], - }, - }, - ], - }, - { - name: "ping", - description: "Ping npm registry", - options: [registryOption], - }, - { - name: "pkg", - description: "Manages your package.json", - subcommands: [ - { - name: "get", - description: - "Retrieves a value key, defined in your package.json file. It is possible to get multiple values and values for child fields", - args: { - name: "field", - description: - "Name of the field to get. You can view child fields by separating them with a period", - isVariadic: true, - }, - options: [jsonOption, ...workSpaceOptions], - }, - { - name: "set", - description: - "Sets a value in your package.json based on the field value. It is possible to set multiple values and values for child fields", - args: { - // Format is =. How to achieve this? - name: "field", - description: - "Name of the field to set. You can set child fields by separating them with a period", - isVariadic: true, - }, - options: [ - jsonOption, - ...workSpaceOptions, - { - name: ["-f", "--force"], - description: - "Removes various protections against unfortunate side effects, common mistakes, unnecessary performance degradation, and malicious input. Allow clobbering existing values in npm pkg", - isDangerous: true, - }, - ], - }, - { - name: "delete", - description: "Deletes a key from your package.json", - args: { - name: "key", - description: - "Name of the key to delete. You can delete child fields by separating them with a period", - isVariadic: true, - }, - options: [ - ...workSpaceOptions, - { - name: ["-f", "--force"], - description: - "Removes various protections against unfortunate side effects, common mistakes, unnecessary performance degradation, and malicious input. Allow clobbering existing values in npm pkg", - isDangerous: true, - }, - ], - }, - ], - }, - { - name: "prefix", - description: "Display prefix", - options: [ - { - name: ["-g", "--global"], - description: "Print the global prefix to standard out", - }, - ], - }, - { - name: "profile", - description: "Change settings on your registry profile", - subcommands: [ - { - name: "get", - description: - "Display all of the properties of your profile, or one or more specific properties", - args: { - name: "property", - isOptional: true, - description: "Property name", - }, - options: [registryOption, jsonOption, parseableOption, otpOption], - }, - { - name: "set", - description: "Set the value of a profile property", - args: [ - { - name: "property", - description: "Property name", - suggestions: [ - "email", - "fullname", - "homepage", - "freenode", - "twitter", - "github", - ], - }, - { - name: "value", - description: "Property value", - }, - ], - options: [registryOption, jsonOption, parseableOption, otpOption], - subcommands: [ - { - name: "password", - description: - "Change your password. This is interactive, you'll be prompted for your current password and a new password", - }, - ], - }, - { - name: "enable-2fa", - description: "Enables two-factor authentication", - args: { - name: "mode", - description: - "Mode for two-factor authentication. Defaults to auth-and-writes mode", - isOptional: true, - suggestions: [ - { - name: "auth-only", - description: - "Require an OTP when logging in or making changes to your account's authentication", - }, - { - name: "auth-and-writes", - description: - "Requires an OTP at all the times auth-only does, and also requires one when publishing a module, setting the latest dist-tag, or changing access via npm access and npm owner", - }, - ], - }, - options: [registryOption, otpOption], - }, - { - name: "disable-2fa", - description: "Disables two-factor authentication", - options: [registryOption, otpOption], - }, - ], - }, - { - name: "prune", - description: "Remove extraneous packages", - args: { - name: "[<@scope>/]", - isOptional: true, - }, - options: [ - omitOption, - dryRunOption, - jsonOption, - { - name: "--production", - description: "Remove the packages specified in your devDependencies", - }, - ...workSpaceOptions, - ], - }, - { - name: "publish", - description: "Publish a package", - args: { - name: "tarball|folder", - isOptional: true, - description: - "A url or file path to a gzipped tar archive containing a single folder with a package.json file inside | A folder containing a package.json file", - template: ["folders"], - }, - options: [ - { - name: "--tag", - description: "Registers the published package with the given tag", - args: { name: "tag" }, - }, - ...workSpaceOptions, - { - name: "--access", - description: - "Sets scoped package to be publicly viewable if set to 'public'", - args: { - default: "restricted", - suggestions: ["restricted", "public"], - }, - }, - dryRunOption, - otpOption, - ], - }, - { - name: ["rebuild", "rb"], - description: "Rebuild a package", - args: { - name: "[<@scope>/][@]", - }, - options: [ - globalOption, - ...workSpaceOptions, - ignoreScriptsOption, - { - name: "--no-bin-links", - description: - "Tells npm to not create symlinks (or .cmd shims on Windows) for package executables", - }, - ], - }, - { - name: "repo", - description: "Open package repository page in the browser", - args: { - name: "package", - isOptional: true, - generators: npmSearchGenerator, - debounce: true, - isVariadic: true, - }, - options: [ - ...workSpaceOptions, - { - name: "--no-browser", - description: "Display in command line instead of browser", - exclusiveOn: ["--browser"], - }, - { - name: "--browser", - description: - "The browser that is called by the npm repo command to open websites", - args: { name: "browser" }, - exclusiveOn: ["--no-browser"], - }, - ], - }, - { - name: "restart", - description: "Restart a package", - options: [ - ignoreScriptsOption, - scriptShellOption, - { - name: "--", - args: { - name: "arg", - description: "Arguments to be passed to the restart script", - }, - }, - ], - }, - { - name: "root", - description: "Display npm root", - options: [ - { - name: ["-g", "--global"], - description: - "Print the effective global node_modules folder to standard out", - }, - ], - }, - { - name: ["search", "s", "se", "find"], - description: "Search for packages", - args: { - name: "search terms", - isVariadic: true, - }, - options: [ - longOption, - jsonOption, - { - name: "--color", - description: "Show colors", - args: { - name: "always", - suggestions: ["always"], - description: "Always show colors", - }, - exclusiveOn: ["--no-color"], - }, - { - name: "--no-color", - description: "Do not show colors", - exclusiveOn: ["--color"], - }, - parseableOption, - { - name: "--no-description", - description: "Do not show descriptions", - }, - { - name: "--searchopts", - description: - "Space-separated options that are always passed to search", - args: { - name: "searchopts", - }, - }, - { - name: "--searchexclude", - description: - "Space-separated options that limit the results from search", - args: { - name: "searchexclude", - }, - }, - registryOption, - { - name: "--prefer-online", - description: - "If true, staleness checks for cached data will be forced, making the CLI look for updates immediately even for fresh package data", - exclusiveOn: ["--prefer-offline", "--offline"], - }, - { - name: "--prefer-offline", - description: - "If true, staleness checks for cached data will be bypassed, but missing data will be requested from the server", - exclusiveOn: ["--prefer-online", "--offline"], - }, - { - name: "--offline", - description: - "Force offline mode: no network requests will be done during install", - exclusiveOn: ["--prefer-online", "--prefer-offline"], - }, - ], - }, - { name: "set", description: "Sets the config key to the value" }, - { - name: "set-script", - description: "Set tasks in the scripts section of package.json", - args: [ - { - name: "script", - description: - "Name of the task to be added to the scripts section of package.json", - }, - { - name: "command", - description: "Command to run when script is called", - }, - ], - options: workSpaceOptions, - }, - { - name: "shrinkwrap", - description: "Lock down dependency versions for publication", - }, - { - name: "star", - description: "Mark your favorite packages", - args: { - name: "pkg", - description: "Package to mark as favorite", - }, - options: [ - registryOption, - { - name: "--no-unicode", - description: "Do not use unicode characters in the tree output", - }, - ], - }, - { - name: "stars", - description: "View packages marked as favorites", - args: { - name: "user", - isOptional: true, - description: "View packages marked as favorites by ", - }, - options: [registryOption], - }, - { - name: "start", - description: "Start a package", - options: [ - ignoreScriptsOption, - scriptShellOption, - { - name: "--", - args: { - name: "arg", - description: "Arguments to be passed to the start script", - }, - }, - ], - }, - { - name: "stop", - description: "Stop a package", - options: [ - ignoreScriptsOption, - scriptShellOption, - { - name: "--", - args: { - name: "arg", - description: "Arguments to be passed to the stop script", - }, - }, - ], - }, - { - name: "team", - description: "Manage organization teams and team memberships", - subcommands: [ - { - name: "create", - args: { name: "scope:team" }, - options: [registryOption, otpOption], - }, - { - name: "destroy", - args: { name: "scope:team" }, - options: [registryOption, otpOption], - }, - { - name: "add", - args: [{ name: "scope:team" }, { name: "user" }], - options: [registryOption, otpOption], - }, - { - name: "rm", - args: [{ name: "scope:team" }, { name: "user" }], - options: [registryOption, otpOption], - }, - { - name: "ls", - args: { name: "scope|scope:team" }, - options: [registryOption, jsonOption, parseableOption], - }, - ], - }, - { - name: ["test", "tst", "t"], - description: "Test a package", - options: [ignoreScriptsOption, scriptShellOption], - }, - { - name: "token", - description: "Manage your authentication tokens", - subcommands: [ - { - name: "list", - description: "Shows a table of all active authentication tokens", - options: [jsonOption, parseableOption], - }, - { - name: "create", - description: "Create a new authentication token", - options: [ - { - name: "--read-only", - description: - "This is used to mark a token as unable to publish when configuring limited access tokens with the npm token create command", - }, - { - name: "--cidr", - description: - "This is a list of CIDR address to be used when configuring limited access tokens with the npm token create command", - isRepeatable: true, - args: { - name: "cidr", - }, - }, - ], - }, - { - name: "revoke", - description: - "Immediately removes an authentication token from the registry. You will no longer be able to use it", - args: { name: "idtoken" }, - }, - ], - options: [registryOption, otpOption], - }, - uninstallSubcommand("uninstall"), - uninstallSubcommand(["r", "rm"]), - uninstallSubcommand("un"), - uninstallSubcommand("remove"), - uninstallSubcommand("unlink"), - { - name: "unpublish", - description: "Remove a package from the registry", - args: { - name: "[<@scope>/][@]", - }, - options: [ - dryRunOption, - { - name: ["-f", "--force"], - description: - "Allow unpublishing all versions of a published package. Removes various protections against unfortunate side effects, common mistakes, unnecessary performance degradation, and malicious input", - isDangerous: true, - }, - ...workSpaceOptions, - ], - }, - { - name: "unstar", - description: "Remove an item from your favorite packages", - args: { - name: "pkg", - description: "Package to unmark as favorite", - }, - options: [ - registryOption, - otpOption, - { - name: "--no-unicode", - description: "Do not use unicode characters in the tree output", - }, - ], - }, - { - name: ["update", "upgrade", "up"], - description: "Update a package", - options: [ - { name: "-g", description: "Update global package" }, - { - name: "--global-style", - description: - "Causes npm to install the package into your local node_modules folder with the same layout it uses with the global node_modules folder", - }, - { - name: "--legacy-bundling", - description: - "Causes npm to install the package such that versions of npm prior to 1.4, such as the one included with node 0.8, can install the package", - }, - { - name: "--strict-peer-deps", - description: - "If set to true, and --legacy-peer-deps is not set, then any conflicting peerDependencies will be treated as an install failure", - }, - { - name: "--no-package-lock", - description: "Ignores package-lock.json files when installing", - }, - omitOption, - ignoreScriptsOption, - { - name: "--no-audit", - description: - "Submit audit reports alongside the current npm command to the default registry and all registries configured for scopes", - }, - { - name: "--no-bin-links", - description: - "Tells npm to not create symlinks (or .cmd shims on Windows) for package executables", - }, - { - name: "--no-fund", - description: - "Hides the message at the end of each npm install acknowledging the number of dependencies looking for funding", - }, - { - name: "--save", - description: - "Update the semver values of direct dependencies in your project package.json", - }, - dryRunOption, - ...workSpaceOptions, - ], - }, - { - name: "version", - description: "Bump a package version", - options: [ - ...workSpaceOptions, - jsonOption, - { - name: "--allow-same-version", - description: - "Prevents throwing an error when npm version is used to set the new version to the same value as the current version", - }, - { - name: "--no-commit-hooks", - description: - "Do not run git commit hooks when using the npm version command", - }, - { - name: "--no-git-tag-version", - description: - "Do not tag the commit when using the npm version command", - }, - { - name: "--preid", - description: - 'The "prerelease identifier" to use as a prefix for the "prerelease" part of a semver. Like the rc in 1.2.0-rc.8', - args: { - name: "prerelease-id", - }, - }, - { - name: "--sign-git-tag", - description: - "If set to true, then the npm version command will tag the version using -s to add a signature", - }, - ], - }, - { - name: ["view", "v", "info", "show"], - description: "View registry info", - options: [...workSpaceOptions, jsonOption], - }, - { - name: "whoami", - description: "Display npm username", - options: [registryOption], - }, - ], -}; - -export default completionSpec; diff --git a/code/extensions/terminal-suggest/src/completions/upstream/pnpm.ts b/code/extensions/terminal-suggest/src/completions/upstream/pnpm.ts deleted file mode 100644 index 9ce7c798208..00000000000 --- a/code/extensions/terminal-suggest/src/completions/upstream/pnpm.ts +++ /dev/null @@ -1,1027 +0,0 @@ -// GENERATORS - -import { npmScriptsGenerator, npmSearchGenerator } from "./npm"; -import { dependenciesGenerator, nodeClis } from "./yarn"; - -const filterMessages = (out: string): string => { - return out.startsWith("warning:") || out.startsWith("error:") - ? out.split("\n").slice(1).join("\n") - : out; -}; - -const searchBranches: Fig.Generator = { - script: ["git", "branch", "--no-color"], - postProcess: function (out) { - const output = filterMessages(out); - - if (output.startsWith("fatal:")) { - return []; - } - - return output.split("\n").map((elm) => { - let name = elm.trim(); - const parts = elm.match(/\S+/g); - if (parts && parts.length > 1) { - if (parts[0] == "*") { - // Current branch. - return { - name: elm.replace("*", "").trim(), - description: "Current branch", - icon: "⭐️", - }; - } else if (parts[0] == "+") { - // Branch checked out in another worktree. - name = elm.replace("+", "").trim(); - } - } - - return { - name, - description: "Branch", - icon: "fig://icon?type=git", - }; - }); - }, -}; - -const generatorInstalledPackages: Fig.Generator = { - script: ["pnpm", "ls"], - postProcess: function (out) { - /** - * out - * @example - * ``` - * Legend: production dependency, optional only, dev only - * - * /xxxx/xxxx/ (PRIVATE) - * - * dependencies: - * lodash 4.17.21 - * foo link:packages/foo - * - * devDependencies: - * typescript 4.7.4 - * ``` - */ - if (out.includes("ERR_PNPM")) { - return []; - } - - const output = out - .split("\n") - .slice(3) - // remove empty lines, "*dependencies:" lines, local workspace packages (eg: "foo":"workspace:*") - .filter( - (item) => - !!item && - !item.toLowerCase().includes("dependencies") && - !item.includes("link:") - ) - .map((item) => item.replace(/\s/, "@")); // typescript 4.7.4 -> typescript@4.7.4 - - return output.map((pkg) => { - return { - name: pkg, - icon: "fig://icon?type=package", - }; - }); - }, -}; - -const FILTER_OPTION: Fig.Option = { - name: "--filter", - args: { - template: "filepaths", - name: "Filepath / Package", - description: - "To only select packages under the specified directory, you may specify any absolute path, typically in POSIX format", - }, - description: `Filtering allows you to restrict commands to specific subsets of packages. -pnpm supports a rich selector syntax for picking packages by name or by relation. -More details: https://pnpm.io/filtering`, -}; - -/** Options that being appended for `pnpm i` and `add` */ -const INSTALL_BASE_OPTIONS: Fig.Option[] = [ - { - name: "--offline", - description: - "If true, pnpm will use only packages already available in the store. If a package won't be found locally, the installation will fail", - }, - { - name: "--prefer-offline", - description: - "If true, staleness checks for cached data will be bypassed, but missing data will be requested from the server. To force full offline mode, use --offline", - }, - { - name: "--ignore-scripts", - description: - "Do not execute any scripts defined in the project package.json and its dependencies", - }, - { - name: "--reporter", - description: `Allows you to choose the reporter that will log debug info to the terminal about the installation progress`, - args: { - name: "Reporter Type", - suggestions: ["silent", "default", "append-only", "ndjson"], - }, - }, -]; - -/** Base options for pnpm i when run without any arguments */ -const INSTALL_OPTIONS: Fig.Option[] = [ - { - name: ["-P", "--save-prod"], - description: `Pnpm will not install any package listed in devDependencies if the NODE_ENV environment variable is set to production. -Use this flag to instruct pnpm to ignore NODE_ENV and take its production status from this flag instead`, - }, - { - name: ["-D", "--save-dev"], - description: - "Only devDependencies are installed regardless of the NODE_ENV", - }, - { - name: "--no-optional", - description: "OptionalDependencies are not installed", - }, - { - name: "--lockfile-only", - description: - "When used, only updates pnpm-lock.yaml and package.json instead of checking node_modules and downloading dependencies", - }, - { - name: "--frozen-lockfile", - description: - "If true, pnpm doesn't generate a lockfile and fails to install if the lockfile is out of sync with the manifest / an update is needed or no lockfile is present", - }, - { - name: "--use-store-server", - description: - "Starts a store server in the background. The store server will keep running after installation is done. To stop the store server, run pnpm server stop", - }, - { - name: "--shamefully-hoist", - description: - "Creates a flat node_modules structure, similar to that of npm or yarn. WARNING: This is highly discouraged", - }, -]; - -/** Base options for pnpm add */ -const INSTALL_PACKAGE_OPTIONS: Fig.Option[] = [ - { - name: ["-P", "--save-prod"], - description: "Install the specified packages as regular dependencies", - }, - { - name: ["-D", "--save-dev"], - description: "Install the specified packages as devDependencies", - }, - { - name: ["-O", "--save-optional"], - description: "Install the specified packages as optionalDependencies", - }, - { - name: "--no-save", - description: "Prevents saving to `dependencies`", - }, - { - name: ["-E", "--save-exact"], - description: - "Saved dependencies will be configured with an exact version rather than using pnpm's default semver range operator", - }, - { - name: "--save-peer", - description: - "Using --save-peer will add one or more packages to peerDependencies and install them as dev dependencies", - }, - { - name: ["--ignore-workspace-root-check", "-W#"], - description: `Adding a new dependency to the root workspace package fails, unless the --ignore-workspace-root-check or -W flag is used. -For instance, pnpm add debug -W`, - }, - { - name: ["--global", "-g"], - description: `Install a package globally`, - }, - { - name: "--workspace", - description: `Only adds the new dependency if it is found in the workspace`, - }, - FILTER_OPTION, -]; - -// SUBCOMMANDS -const SUBCOMMANDS_MANAGE_DEPENDENCIES: Fig.Subcommand[] = [ - { - name: "add", - description: `Installs a package and any packages that it depends on. By default, any new package is installed as a production dependency`, - args: { - name: "package", - generators: npmSearchGenerator, - debounce: true, - isVariadic: true, - }, - options: [...INSTALL_BASE_OPTIONS, ...INSTALL_PACKAGE_OPTIONS], - }, - { - name: ["install", "i"], - description: `Pnpm install is used to install all dependencies for a project. -In a CI environment, installation fails if a lockfile is present but needs an update. -Inside a workspace, pnpm install installs all dependencies in all the projects. -If you want to disable this behavior, set the recursive-install setting to false`, - async generateSpec(tokens) { - // `pnpm i` with args is an `pnpm add` alias - const hasArgs = - tokens.filter((token) => token.trim() !== "" && !token.startsWith("-")) - .length > 2; - - return { - name: "install", - options: [ - ...INSTALL_BASE_OPTIONS, - ...(hasArgs ? INSTALL_PACKAGE_OPTIONS : INSTALL_OPTIONS), - ], - }; - }, - args: { - name: "package", - isOptional: true, - generators: npmSearchGenerator, - debounce: true, - isVariadic: true, - }, - }, - { - name: ["install-test", "it"], - description: - "Runs pnpm install followed immediately by pnpm test. It takes exactly the same arguments as pnpm install", - options: [...INSTALL_BASE_OPTIONS, ...INSTALL_OPTIONS], - }, - { - name: ["update", "upgrade", "up"], - description: `Pnpm update updates packages to their latest version based on the specified range. -When used without arguments, updates all dependencies. You can use patterns to update specific dependencies`, - args: { - name: "Package", - isOptional: true, - filterStrategy: "fuzzy", - generators: dependenciesGenerator, - isVariadic: true, - }, - options: [ - { - name: ["--recursive", "-r"], - description: - "Concurrently runs update in all subdirectories with a package.json (excluding node_modules)", - }, - { - name: ["--latest", "-L"], - description: - "Ignores the version range specified in package.json. Instead, the version specified by the latest tag will be used (potentially upgrading the packages across major versions)", - }, - { - name: "--global", - description: "Update global packages", - }, - { - name: ["-P", "--save-prod"], - description: `Only update packages in dependencies and optionalDependencies`, - }, - { - name: ["-D", "--save-dev"], - description: "Only update packages in devDependencies", - }, - { - name: "--no-optional", - description: "Don't update packages in optionalDependencies", - }, - { - name: ["--interactive", "-i"], - description: - "Show outdated dependencies and select which ones to update", - }, - { - name: "--workspace", - description: `Tries to link all packages from the workspace. Versions are updated to match the versions of packages inside the workspace. -If specific packages are updated, the command will fail if any of the updated dependencies are not found inside the workspace. For instance, the following command fails if express is not a workspace package: pnpm up -r --workspace express`, - }, - FILTER_OPTION, - ], - }, - { - name: ["remove", "rm", "uninstall", "un"], - description: `Removes packages from node_modules and from the project's package.json`, - args: { - name: "Package", - filterStrategy: "fuzzy", - generators: dependenciesGenerator, - isVariadic: true, - }, - options: [ - { - name: ["--recursive", "-r"], - description: `When used inside a workspace, removes a dependency (or dependencies) from every workspace package. -When used not inside a workspace, removes a dependency (or dependencies) from every package found in subdirectories`, - }, - { - name: "--global", - description: "Remove a global package", - }, - { - name: ["-P", "--save-prod"], - description: `Only remove the dependency from dependencies`, - }, - { - name: ["-D", "--save-dev"], - description: "Only remove the dependency from devDependencies", - }, - { - name: ["--save-optional", "-O"], - description: "Only remove the dependency from optionalDependencies", - }, - FILTER_OPTION, - ], - }, - { - name: ["link", "ln"], - description: `Makes the current local package accessible system-wide, or in another location`, - args: [ - { - name: "Package", - filterStrategy: "fuzzy", - generators: dependenciesGenerator, - isVariadic: true, - }, - { template: "filepaths" }, - ], - options: [ - { - name: ["--dir", "-C"], - description: `Changes the link location to `, - }, - { - name: "--global", - description: - "Links the specified package () from global node_modules to the node_nodules of package from where this command was executed or specified via --dir option", - }, - ], - }, - { - name: "unlink", - description: `Unlinks a system-wide package (inverse of pnpm link). -If called without arguments, all linked dependencies will be unlinked. -This is similar to yarn unlink, except pnpm re-installs the dependency after removing the external link`, - args: [ - { - name: "Package", - filterStrategy: "fuzzy", - generators: dependenciesGenerator, - isVariadic: true, - }, - { template: "filepaths" }, - ], - options: [ - { - name: ["--recursive", "-r"], - description: `Unlink in every package found in subdirectories or in every workspace package, when executed inside a workspace`, - }, - FILTER_OPTION, - ], - }, - { - name: "import", - description: - "Pnpm import generates a pnpm-lock.yaml from an npm package-lock.json (or npm-shrinkwrap.json) file", - }, - { - name: ["rebuild", "rb"], - description: `Rebuild a package`, - args: [ - { - name: "Package", - filterStrategy: "fuzzy", - generators: dependenciesGenerator, - isVariadic: true, - }, - { template: "filepaths" }, - ], - options: [ - { - name: ["--recursive", "-r"], - description: `This command runs the pnpm rebuild command in every package of the monorepo`, - }, - FILTER_OPTION, - ], - }, - { - name: "prune", - description: `Removes unnecessary packages`, - options: [ - { - name: "--prod", - description: `Remove the packages specified in devDependencies`, - }, - { - name: "--no-optional", - description: `Remove the packages specified in optionalDependencies`, - }, - ], - }, - { - name: "fetch", - description: `EXPERIMENTAL FEATURE: Fetch packages from a lockfile into virtual store, package manifest is ignored: https://pnpm.io/cli/fetch`, - options: [ - { - name: "--prod", - description: `Development packages will not be fetched`, - }, - { - name: "--dev", - description: `Only development packages will be fetched`, - }, - ], - }, - { - name: "patch", - description: `This command will cause a package to be extracted in a temporary directory intended to be editable at will`, - args: { - name: "package", - generators: generatorInstalledPackages, - }, - options: [ - { - name: "--edit-dir", - description: `The package that needs to be patched will be extracted to this directory`, - }, - ], - }, - { - name: "patch-commit", - args: { - name: "dir", - }, - description: `Generate a patch out of a directory`, - }, - { - name: "patch-remove", - args: { - name: "package", - isVariadic: true, - // TODO: would be nice to have a generator of all patched packages - }, - }, -]; - -const SUBCOMMANDS_RUN_SCRIPTS: Fig.Subcommand[] = [ - { - name: ["run", "run-script"], - description: "Runs a script defined in the package's manifest file", - args: { - name: "Scripts", - filterStrategy: "fuzzy", - generators: npmScriptsGenerator, - isVariadic: true, - }, - options: [ - { - name: ["-r", "--recursive"], - description: `This runs an arbitrary command from each package's "scripts" object. If a package doesn't have the command, it is skipped. If none of the packages have the command, the command fails`, - }, - { - name: "--if-present", - description: - "You can use the --if-present flag to avoid exiting with a non-zero exit code when the script is undefined. This lets you run potentially undefined scripts without breaking the execution chain", - }, - { - name: "--parallel", - description: - "Completely disregard concurrency and topological sorting, running a given script immediately in all matching packages with prefixed streaming output. This is the preferred flag for long-running processes over many packages, for instance, a lengthy build process", - }, - { - name: "--stream", - description: - "Stream output from child processes immediately, prefixed with the originating package directory. This allows output from different packages to be interleaved", - }, - FILTER_OPTION, - ], - }, - { - name: "exec", - description: `Execute a shell command in scope of a project. -node_modules/.bin is added to the PATH, so pnpm exec allows executing commands of dependencies`, - args: { - name: "Scripts", - filterStrategy: "fuzzy", - generators: dependenciesGenerator, - isVariadic: true, - }, - options: [ - { - name: ["-r", "--recursive"], - description: `Execute the shell command in every project of the workspace. -The name of the current package is available through the environment variable PNPM_PACKAGE_NAME (supported from pnpm v2.22.0 onwards)`, - }, - { - name: "--parallel", - description: - "Completely disregard concurrency and topological sorting, running a given script immediately in all matching packages with prefixed streaming output. This is the preferred flag for long-running processes over many packages, for instance, a lengthy build process", - }, - FILTER_OPTION, - ], - }, - { - name: ["test", "t", "tst"], - description: `Runs an arbitrary command specified in the package's test property of its scripts object. -The intended usage of the property is to specify a command that runs unit or integration testing for your program`, - }, - { - name: "start", - description: `Runs an arbitrary command specified in the package's start property of its scripts object. If no start property is specified on the scripts object, it will attempt to run node server.js as a default, failing if neither are present. -The intended usage of the property is to specify a command that starts your program`, - }, -]; - -const SUBCOMMANDS_REVIEW_DEPS: Fig.Subcommand[] = [ - { - name: "audit", - description: `Checks for known security issues with the installed packages. -If security issues are found, try to update your dependencies via pnpm update. -If a simple update does not fix all the issues, use overrides to force versions that are not vulnerable. -For instance, if lodash@<2.1.0 is vulnerable, use overrides to force lodash@^2.1.0. -Details at: https://pnpm.io/cli/audit`, - options: [ - { - name: "--audit-level", - description: `Only print advisories with severity greater than or equal to `, - args: { - name: "Audit Level", - default: "low", - suggestions: ["low", "moderate", "high", "critical"], - }, - }, - { - name: "--fix", - description: - "Add overrides to the package.json file in order to force non-vulnerable versions of the dependencies", - }, - { - name: "--json", - description: `Output audit report in JSON format`, - }, - { - name: ["--dev", "-D"], - description: `Only audit dev dependencies`, - }, - { - name: ["--prod", "-P"], - description: `Only audit production dependencies`, - }, - { - name: "--no-optional", - description: `Don't audit optionalDependencies`, - }, - { - name: "--ignore-registry-errors", - description: `If the registry responds with a non-200 status code, the process should exit with 0. So the process will fail only if the registry actually successfully responds with found vulnerabilities`, - }, - ], - }, - { - name: ["list", "ls"], - description: `This command will output all the versions of packages that are installed, as well as their dependencies, in a tree-structure. -Positional arguments are name-pattern@version-range identifiers, which will limit the results to only the packages named. For example, pnpm list "babel-*" "eslint-*" semver@5`, - options: [ - { - name: ["--recursive", "-r"], - description: `Perform command on every package in subdirectories or on every workspace package, when executed inside a workspace`, - }, - { - name: "--json", - description: `Log output in JSON format`, - }, - { - name: "--long", - description: `Show extended information`, - }, - { - name: "--parseable", - description: `Outputs package directories in a parseable format instead of their tree view`, - }, - { - name: "--global", - description: `List packages in the global install directory instead of in the current project`, - }, - { - name: "--depth", - description: `Max display depth of the dependency tree. -pnpm ls --depth 0 will list direct dependencies only. pnpm ls --depth -1 will list projects only. Useful inside a workspace when used with the -r option`, - args: { name: "number" }, - }, - { - name: ["--dev", "-D"], - description: `Only list dev dependencies`, - }, - { - name: ["--prod", "-P"], - description: `Only list production dependencies`, - }, - { - name: "--no-optional", - description: `Don't list optionalDependencies`, - }, - FILTER_OPTION, - ], - }, - { - name: "outdated", - description: `Checks for outdated packages. The check can be limited to a subset of the installed packages by providing arguments (patterns are supported)`, - options: [ - { - name: ["--recursive", "-r"], - description: `Check for outdated dependencies in every package found in subdirectories, or in every workspace package when executed inside a workspace`, - }, - { - name: "--long", - description: `Print details`, - }, - { - name: "--global", - description: `List outdated global packages`, - }, - { - name: "--no-table", - description: `Prints the outdated dependencies in a list format instead of the default table. Good for small consoles`, - }, - { - name: "--compatible", - description: `Prints only versions that satisfy specifications in package.json`, - }, - { - name: ["--dev", "-D"], - description: `Only list dev dependencies`, - }, - { - name: ["--prod", "-P"], - description: `Only list production dependencies`, - }, - { - name: "--no-optional", - description: `Doesn't check optionalDependencies`, - }, - ], - }, - { - name: "why", - description: `Shows all packages that depend on the specified package`, - args: { - name: "Scripts", - filterStrategy: "fuzzy", - generators: dependenciesGenerator, - isVariadic: true, - }, - options: [ - { - name: ["--recursive", "-r"], - description: `Show the dependency tree for the specified package on every package in subdirectories or on every workspace package when executed inside a workspace`, - }, - { - name: "--json", - description: `Log output in JSON format`, - }, - { - name: "--long", - description: `Show verbose output`, - }, - { - name: "--parseable", - description: `Show parseable output instead of tree view`, - }, - { - name: "--global", - description: `List packages in the global install directory instead of in the current project`, - }, - { - name: ["--dev", "-D"], - description: `Only display the dependency tree for packages in devDependencies`, - }, - { - name: ["--prod", "-P"], - description: `Only display the dependency tree for packages in dependencies`, - }, - FILTER_OPTION, - ], - }, -]; - -const SUBCOMMANDS_MISC: Fig.Subcommand[] = [ - { - name: "publish", - description: `Publishes a package to the registry. -When publishing a package inside a workspace, the LICENSE file from the root of the workspace is packed with the package (unless the package has a license of its own). -You may override some fields before publish, using the publishConfig field in package.json. You also can use the publishConfig.directory to customize the published subdirectory (usually using third party build tools). -When running this command recursively (pnpm -r publish), pnpm will publish all the packages that have versions not yet published to the registry`, - args: { - name: "Branch", - generators: searchBranches, - }, - options: [ - { - name: "--tag", - description: `Publishes the package with the given tag. By default, pnpm publish updates the latest tag`, - args: { - name: "", - }, - }, - { - name: "--dry-run", - description: `Does everything a publish would do except actually publishing to the registry`, - }, - { - name: "--ignore-scripts", - description: `Ignores any publish related lifecycle scripts (prepublishOnly, postpublish, and the like)`, - }, - { - name: "--no-git-checks", - description: `Don't check if current branch is your publish branch, clean, and up-to-date`, - }, - { - name: "--access", - description: `Tells the registry whether the published package should be public or restricted`, - args: { - name: "Type", - suggestions: ["public", "private"], - }, - }, - { - name: "--force", - description: `Try to publish packages even if their current version is already found in the registry`, - }, - { - name: "--report-summary", - description: `Save the list of published packages to pnpm-publish-summary.json. Useful when some other tooling is used to report the list of published packages`, - }, - FILTER_OPTION, - ], - }, - { - name: ["recursive", "m", "multi", "-r"], - description: `Runs a pnpm command recursively on all subdirectories in the package or every available workspace`, - options: [ - { - name: "--link-workspace-packages", - description: `Link locally available packages in workspaces of a monorepo into node_modules instead of re-downloading them from the registry. This emulates functionality similar to yarn workspaces. -When this is set to deep, local packages can also be linked to subdependencies. -Be advised that it is encouraged instead to use npmrc for this setting, to enforce the same behaviour in all environments. This option exists solely so you may override that if necessary`, - args: { - name: "bool or `deep`", - suggestions: ["dee["], - }, - }, - { - name: "--workspace-concurrency", - description: `Set the maximum number of tasks to run simultaneously. For unlimited concurrency use Infinity`, - args: { name: "" }, - }, - { - name: "--bail", - description: `Stops when a task throws an error`, - }, - { - name: "--no-bail", - description: `Don't stop when a task throws an error`, - }, - { - name: "--sort", - description: `Packages are sorted topologically (dependencies before dependents)`, - }, - { - name: "--no-sort", - description: `Disable packages sorting`, - }, - { - name: "--reverse", - description: `The order of packages is reversed`, - }, - FILTER_OPTION, - ], - }, - { - name: "server", - description: `Manage a store server`, - subcommands: [ - { - name: "start", - description: - "Starts a server that performs all interactions with the store. Other commands will delegate any store-related tasks to this server", - options: [ - { - name: "--background", - description: `Runs the server in the background, similar to daemonizing on UNIX systems`, - }, - { - name: "--network-concurrency", - description: `The maximum number of network requests to process simultaneously`, - args: { name: "number" }, - }, - { - name: "--protocol", - description: `The communication protocol used by the server. When this is set to auto, IPC is used on all systems except for Windows, which uses TCP`, - args: { - name: "Type", - suggestions: ["auto", "tcp", "ipc"], - }, - }, - { - name: "--port", - description: `The port number to use when TCP is used for communication. If a port is specified and the protocol is set to auto, regardless of system type, the protocol is automatically set to use TCP`, - args: { name: "port number" }, - }, - { - name: "--store-dir", - description: `The directory to use for the content addressable store`, - args: { name: "Path", template: "filepaths" }, - }, - { - name: "--lock", - description: `Set to make the package store immutable to external processes while the server is running or not`, - }, - { - name: "--no-lock", - description: `Set to make the package store mutable to external processes while the server is running or not`, - }, - { - name: "--ignore-stop-requests", - description: `Prevents you from stopping the server using pnpm server stop`, - }, - { - name: "--ignore-upload-requests", - description: `Prevents creating a new side effect cache during install`, - }, - ], - }, - { - name: "stop", - description: "Stops the store server", - }, - { - name: "status", - description: "Prints information about the running server", - }, - ], - }, - { - name: "store", - description: "Managing the package store", - subcommands: [ - { - name: "status", - description: `Checks for modified packages in the store. -Returns exit code 0 if the content of the package is the same as it was at the time of unpacking`, - }, - { - name: "add", - description: `Functionally equivalent to pnpm add, -except this adds new packages to the store directly without modifying any projects or files outside of the store`, - }, - { - name: "prune", - description: `Removes orphan packages from the store. -Pruning the store will save disk space, however may slow down future installations involving pruned packages. -Ultimately, it is a safe operation, however not recommended if you have orphaned packages from a package you intend to reinstall. -Please read the FAQ for more information on unreferenced packages and best practices. -Please note that this is prohibited when a store server is running`, - }, - { - name: "path", - description: `Returns the path to the active store directory`, - }, - ], - }, - { - name: "init", - description: - "Creates a basic package.json file in the current directory, if it doesn't exist already", - }, - { - name: "doctor", - description: "Checks for known common issues with pnpm configuration", - }, -]; - -const subcommands = [ - ...SUBCOMMANDS_MANAGE_DEPENDENCIES, - ...SUBCOMMANDS_REVIEW_DEPS, - ...SUBCOMMANDS_RUN_SCRIPTS, - ...SUBCOMMANDS_MISC, -]; - -const recursiveSubcommandsNames = [ - "add", - "exec", - "install", - "list", - "outdated", - "publish", - "rebuild", - "remove", - "run", - "test", - "unlink", - "update", - "why", -]; - -const recursiveSubcommands = subcommands.filter((subcommand) => { - if (Array.isArray(subcommand.name)) { - return subcommand.name.some((name) => - recursiveSubcommandsNames.includes(name) - ); - } - return recursiveSubcommandsNames.includes(subcommand.name); -}); - -// RECURSIVE SUBCOMMAND INDEX -SUBCOMMANDS_MISC[1].subcommands = recursiveSubcommands; - -// common options -const COMMON_OPTIONS: Fig.Option[] = [ - { - name: ["-C", "--dir"], - args: { - name: "path", - template: "folders", - }, - isPersistent: true, - description: - "Run as if pnpm was started in instead of the current working directory", - }, - { - name: ["-w", "--workspace-root"], - args: { - name: "workspace", - }, - isPersistent: true, - description: - "Run as if pnpm was started in the root of the instead of the current working directory", - }, - { - name: ["-h", "--help"], - isPersistent: true, - description: "Output usage information", - }, - { - name: ["-v", "--version"], - description: "Show pnpm's version", - }, -]; - -// SPEC -const completionSpec: Fig.Spec = { - name: "pnpm", - description: "Fast, disk space efficient package manager", - args: { - name: "Scripts", - filterStrategy: "fuzzy", - generators: npmScriptsGenerator, - isVariadic: true, - }, - filterStrategy: "fuzzy", - generateSpec: async (tokens, executeShellCommand) => { - const { script, postProcess } = dependenciesGenerator as Fig.Generator & { - script: string[]; - }; - - if (postProcess === undefined) { - return undefined; - } - - const packages = postProcess( - ( - await executeShellCommand({ - command: script[0], - args: script.slice(1), - }) - ).stdout, - tokens - ) - ?.filter((e) => e !== null) - .map(({ name }) => name as string); - - const subcommands = packages - ?.filter((name) => nodeClis.has(name)) - .map((name) => ({ - name, - loadSpec: name, - icon: "fig://icon?type=package", - })); - - return { - name: "pnpm", - subcommands, - } as Fig.Spec; - }, - subcommands, - options: COMMON_OPTIONS, -}; - -export default completionSpec; diff --git a/code/extensions/terminal-suggest/src/completions/upstream/yarn.ts b/code/extensions/terminal-suggest/src/completions/upstream/yarn.ts deleted file mode 100644 index 04c573a151b..00000000000 --- a/code/extensions/terminal-suggest/src/completions/upstream/yarn.ts +++ /dev/null @@ -1,1674 +0,0 @@ -import { npmScriptsGenerator, npmSearchGenerator } from "./npm"; - -export const yarnScriptParserDirectives: Fig.Arg["parserDirectives"] = { - alias: async (token, executeShellCommand) => { - const npmPrefix = await executeShellCommand({ - command: "npm", - // eslint-disable-next-line @withfig/fig-linter/no-useless-arrays - args: ["prefix"], - }); - if (npmPrefix.status !== 0) { - throw new Error("npm prefix command failed"); - } - const packageJson = await executeShellCommand({ - command: "cat", - // eslint-disable-next-line @withfig/fig-linter/no-useless-arrays - args: [`${npmPrefix.stdout.trim()}/package.json`], - }); - const script: string = JSON.parse(packageJson.stdout).scripts?.[token]; - if (!script) { - throw new Error(`Script not found: '${token}'`); - } - return script; - }, -}; - -export const nodeClis = new Set([ - "vue", - "vite", - "nuxt", - "react-native", - "degit", - "expo", - "jest", - "next", - "electron", - "prisma", - "eslint", - "prettier", - "tsc", - "typeorm", - "babel", - "remotion", - "autocomplete-tools", - "redwood", - "rw", - "create-completion-spec", - "publish-spec-to-team", - "capacitor", - "cap", -]); - -// generate global package list from global package.json file -const getGlobalPackagesGenerator: Fig.Generator = { - custom: async (tokens, executeCommand, generatorContext) => { - const { stdout: yarnGlobalDir } = await executeCommand({ - command: "yarn", - args: ["global", "dir"], - }); - - const { stdout } = await executeCommand({ - command: "cat", - // eslint-disable-next-line @withfig/fig-linter/no-useless-arrays - args: [`${yarnGlobalDir.trim()}/package.json`], - }); - - if (stdout.trim() == "") return []; - - try { - const packageContent = JSON.parse(stdout); - const dependencyScripts = packageContent["dependencies"] || {}; - const devDependencyScripts = packageContent["devDependencies"] || {}; - const dependencies = [ - ...Object.keys(dependencyScripts), - ...Object.keys(devDependencyScripts), - ]; - - const filteredDependencies = dependencies.filter( - (dependency) => !tokens.includes(dependency) - ); - - return filteredDependencies.map((dependencyName) => ({ - name: dependencyName, - icon: "📦", - })); - } catch (e) {} - - return []; - }, -}; - -// generate package list of direct and indirect dependencies -const allDependenciesGenerator: Fig.Generator = { - script: ["yarn", "list", "--depth=0", "--json"], - postProcess: (out) => { - if (out.trim() == "") return []; - - try { - const packageContent = JSON.parse(out); - const dependencies = packageContent.data.trees; - return dependencies.map((dependency: { name: string }) => ({ - name: dependency.name.split("@")[0], - icon: "📦", - })); - } catch (e) {} - return []; - }, -}; - -const configList: Fig.Generator = { - script: ["yarn", "config", "list"], - postProcess: function (out) { - if (out.trim() == "") { - return []; - } - - try { - const startIndex = out.indexOf("{"); - const endIndex = out.indexOf("}"); - let output = out.substring(startIndex, endIndex + 1); - // TODO: fix hacky code - // reason: JSON parse was not working without double quotes - output = output - .replace(/\'/gi, '"') - .replace("lastUpdateCheck", '"lastUpdateCheck"') - .replace("registry", '"lastUpdateCheck"'); - const configObject = JSON.parse(output); - if (configObject) { - return Object.keys(configObject).map((key) => ({ name: key })); - } - } catch (e) {} - - return []; - }, -}; - -export const dependenciesGenerator: Fig.Generator = { - script: [ - "bash", - "-c", - "until [[ -f package.json ]] || [[ $PWD = '/' ]]; do cd ..; done; cat package.json", - ], - postProcess: function (out, context = []) { - if (out.trim() === "") { - return []; - } - - try { - const packageContent = JSON.parse(out); - const dependencies = packageContent["dependencies"] ?? {}; - const devDependencies = packageContent["devDependencies"]; - const optionalDependencies = packageContent["optionalDependencies"] ?? {}; - Object.assign(dependencies, devDependencies, optionalDependencies); - - return Object.keys(dependencies) - .filter((pkgName) => { - const isListed = context.some((current) => current === pkgName); - return !isListed; - }) - .map((pkgName) => ({ - name: pkgName, - icon: "📦", - description: dependencies[pkgName] - ? "dependency" - : optionalDependencies[pkgName] - ? "optionalDependency" - : "devDependency", - })); - } catch (e) { - console.error(e); - return []; - } - }, -}; - -const commonOptions: Fig.Option[] = [ - { name: ["-s", "--silent"], description: "Skip Yarn console logs" }, - { - name: "--no-default-rc", - description: - "Prevent Yarn from automatically detecting yarnrc and npmrc files", - }, - { - name: "--use-yarnrc", - description: - "Specifies a yarnrc file that Yarn should use (.yarnrc only, not .npmrc) (default: )", - args: { name: "path", template: "filepaths" }, - }, - { - name: "--verbose", - description: "Output verbose messages on internal operations", - }, - { - name: "--offline", - description: - "Trigger an error if any required dependencies are not available in local cache", - }, - { - name: "--prefer-offline", - description: - "Use network only if dependencies are not available in local cache", - }, - { - name: ["--enable-pnp", "--pnp"], - description: "Enable the Plug'n'Play installation", - }, - { - name: "--json", - description: "Format Yarn log messages as lines of JSON", - }, - { - name: "--ignore-scripts", - description: "Don't run lifecycle scripts", - }, - { name: "--har", description: "Save HAR output of network traffic" }, - { name: "--ignore-platform", description: "Ignore platform checks" }, - { name: "--ignore-engines", description: "Ignore engines check" }, - { - name: "--ignore-optional", - description: "Ignore optional dependencies", - }, - { - name: "--force", - description: - "Install and build packages even if they were built before, overwrite lockfile", - }, - { - name: "--skip-integrity-check", - description: "Run install without checking if node_modules is installed", - }, - { - name: "--check-files", - description: "Install will verify file tree of packages for consistency", - }, - { - name: "--no-bin-links", - description: "Don't generate bin links when setting up packages", - }, - { name: "--flat", description: "Only allow one version of a package" }, - { - name: ["--prod", "--production"], - description: - "Instruct Yarn to ignore NODE_ENV and take its production-or-not status from this flag instead", - }, - { - name: "--no-lockfile", - description: "Don't read or generate a lockfile", - }, - { name: "--pure-lockfile", description: "Don't generate a lockfile" }, - { - name: "--frozen-lockfile", - description: "Don't generate a lockfile and fail if an update is needed", - }, - { - name: "--update-checksums", - description: "Update package checksums from current repository", - }, - { - name: "--link-duplicates", - description: "Create hardlinks to the repeated modules in node_modules", - }, - { - name: "--link-folder", - description: "Specify a custom folder to store global links", - args: { name: "path", template: "folders" }, - }, - { - name: "--global-folder", - description: "Specify a custom folder to store global packages", - args: { name: "path", template: "folders" }, - }, - { - name: "--modules-folder", - description: - "Rather than installing modules into the node_modules folder relative to the cwd, output them here", - args: { name: "path", template: "folders" }, - }, - { - name: "--preferred-cache-folder", - description: "Specify a custom folder to store the yarn cache if possible", - args: { name: "path", template: "folders" }, - }, - { - name: "--cache-folder", - description: - "Specify a custom folder that must be used to store the yarn cache", - args: { name: "path", template: "folders" }, - }, - { - name: "--mutex", - description: "Use a mutex to ensure only one yarn instance is executing", - args: { name: "type[:specifier]" }, - }, - { - name: "--emoji", - description: "Enables emoji in output", - args: { - default: "true", - suggestions: ["true", "false"], - }, - }, - { - name: "--cwd", - description: "Working directory to use", - args: { name: "cwd", template: "folders" }, - }, - { - name: ["--proxy", "--https-proxy"], - description: "", - args: { name: "host" }, - }, - { - name: "--registry", - description: "Override configuration registry", - args: { name: "url" }, - }, - { name: "--no-progress", description: "Disable progress bar" }, - { - name: "--network-concurrency", - description: "Maximum number of concurrent network requests", - args: { name: "number" }, - }, - { - name: "--network-timeout", - description: "TCP timeout for network requests", - args: { name: "milliseconds" }, - }, - { - name: "--non-interactive", - description: "Do not show interactive prompts", - }, - { - name: "--scripts-prepend-node-path", - description: "Prepend the node executable dir to the PATH in scripts", - }, - { - name: "--no-node-version-check", - description: - "Do not warn when using a potentially unsupported Node version", - }, - { - name: "--focus", - description: - "Focus on a single workspace by installing remote copies of its sibling workspaces", - }, - { - name: "--otp", - description: "One-time password for two factor authentication", - args: { name: "otpcode" }, - }, -]; - -export const createCLIsGenerator: Fig.Generator = { - script: function (context) { - if (context[context.length - 1] === "") return undefined; - const searchTerm = "create-" + context[context.length - 1]; - return [ - "curl", - "-s", - "-H", - "Accept: application/json", - `https://api.npms.io/v2/search?q=${searchTerm}&size=20`, - ]; - }, - cache: { - ttl: 100 * 24 * 60 * 60 * 3, // 3 days - }, - postProcess: function (out) { - try { - return JSON.parse(out).results.map( - (item: { package: { name: string; description: string } }) => - ({ - name: item.package.name.substring(7), - description: item.package.description, - }) as Fig.Suggestion - ) as Fig.Suggestion[]; - } catch (e) { - return []; - } - }, -}; - -const completionSpec: Fig.Spec = { - name: "yarn", - description: "Manage packages and run scripts", - generateSpec: async (tokens, executeShellCommand) => { - const binaries = ( - await executeShellCommand({ - command: "bash", - args: [ - "-c", - `until [[ -d node_modules/ ]] || [[ $PWD = '/' ]]; do cd ..; done; ls -1 node_modules/.bin/`, - ], - }) - ).stdout.split("\n"); - - const subcommands = binaries - .filter((name) => nodeClis.has(name)) - .map((name) => ({ - name: name, - loadSpec: name === "rw" ? "redwood" : name, - icon: "fig://icon?type=package", - })); - - return { - name: "yarn", - subcommands, - } as Fig.Spec; - }, - args: { - generators: npmScriptsGenerator, - filterStrategy: "fuzzy", - parserDirectives: yarnScriptParserDirectives, - isOptional: true, - isCommand: true, - }, - options: [ - { - name: "--disable-pnp", - description: "Disable the Plug'n'Play installation", - }, - { - name: "--emoji", - description: "Enable emoji in output (default: true)", - args: { - name: "bool", - suggestions: [{ name: "true" }, { name: "false" }], - }, - }, - { - name: ["--enable-pnp", "--pnp"], - description: "Enable the Plug'n'Play installation", - }, - { - name: "--flat", - description: "Only allow one version of a package", - }, - { - name: "--focus", - description: - "Focus on a single workspace by installing remote copies of its sibling workspaces", - }, - { - name: "--force", - description: - "Install and build packages even if they were built before, overwrite lockfile", - }, - { - name: "--frozen-lockfile", - description: "Don't generate a lockfile and fail if an update is needed", - }, - { - name: "--global-folder", - description: "Specify a custom folder to store global packages", - args: { - template: "folders", - }, - }, - { - name: "--har", - description: "Save HAR output of network traffic", - }, - { - name: "--https-proxy", - description: "", - args: { - name: "path", - suggestions: [{ name: "https://" }], - }, - }, - { - name: "--ignore-engines", - description: "Ignore engines check", - }, - { - name: "--ignore-optional", - description: "Ignore optional dependencies", - }, - { - name: "--ignore-platform", - description: "Ignore platform checks", - }, - { - name: "--ignore-scripts", - description: "Don't run lifecycle scripts", - }, - { - name: "--json", - description: - "Format Yarn log messages as lines of JSON (see jsonlines.org)", - }, - { - name: "--link-duplicates", - description: "Create hardlinks to the repeated modules in node_modules", - }, - { - name: "--link-folder", - description: "Specify a custom folder to store global links", - args: { - template: "folders", - }, - }, - { - name: "--modules-folder", - description: - "Rather than installing modules into the node_modules folder relative to the cwd, output them here", - args: { - template: "folders", - }, - }, - { - name: "--mutex", - description: "Use a mutex to ensure only one yarn instance is executing", - args: [ - { - name: "type", - suggestions: [{ name: ":" }], - }, - { - name: "specifier", - suggestions: [{ name: ":" }], - }, - ], - }, - { - name: "--network-concurrency", - description: "Maximum number of concurrent network requests", - args: { - name: "number", - }, - }, - { - name: "--network-timeout", - description: "TCP timeout for network requests", - args: { - name: "milliseconds", - }, - }, - { - name: "--no-bin-links", - description: "Don't generate bin links when setting up packages", - }, - { - name: "--no-default-rc", - description: - "Prevent Yarn from automatically detecting yarnrc and npmrc files", - }, - { - name: "--no-lockfile", - description: "Don't read or generate a lockfile", - }, - { - name: "--non-interactive", - description: "Do not show interactive prompts", - }, - { - name: "--no-node-version-check", - description: - "Do not warn when using a potentially unsupported Node version", - }, - { - name: "--no-progress", - description: "Disable progress bar", - }, - { - name: "--offline", - description: - "Trigger an error if any required dependencies are not available in local cache", - }, - { - name: "--otp", - description: "One-time password for two factor authentication", - args: { - name: "otpcode", - }, - }, - { - name: "--prefer-offline", - description: - "Use network only if dependencies are not available in local cache", - }, - { - name: "--preferred-cache-folder", - description: - "Specify a custom folder to store the yarn cache if possible", - args: { - template: "folders", - }, - }, - { - name: ["--prod", "--production"], - description: "", - args: {}, - }, - { - name: "--proxy", - description: "", - args: { - name: "host", - }, - }, - { - name: "--pure-lockfile", - description: "Don't generate a lockfile", - }, - { - name: "--registry", - description: "Override configuration registry", - args: { - name: "url", - }, - }, - { - name: ["-s", "--silent"], - description: - "Skip Yarn console logs, other types of logs (script output) will be printed", - }, - { - name: "--scripts-prepend-node-path", - description: "Prepend the node executable dir to the PATH in scripts", - args: { - suggestions: [{ name: "true" }, { name: "false" }], - }, - }, - { - name: "--skip-integrity-check", - description: "Run install without checking if node_modules is installed", - }, - { - name: "--strict-semver", - description: "", - }, - ...commonOptions, - { - name: ["-v", "--version"], - description: "Output the version number", - }, - { - name: ["-h", "--help"], - description: "Output usage information", - }, - ], - subcommands: [ - { - name: "add", - description: "Installs a package and any packages that it depends on", - args: { - name: "package", - generators: npmSearchGenerator, - debounce: true, - isVariadic: true, - }, - options: [ - ...commonOptions, - { - name: ["-W", "--ignore-workspace-root-check"], - description: "Required to run yarn add inside a workspace root", - }, - { - name: ["-D", "--dev"], - description: "Save package to your `devDependencies`", - }, - { - name: ["-P", "--peer"], - description: "Save package to your `peerDependencies`", - }, - { - name: ["-O", "--optional"], - description: "Save package to your `optionalDependencies`", - }, - { - name: ["-E", "--exact"], - description: "Install exact version", - dependsOn: ["--latest"], - }, - { - name: ["-T", "--tilde"], - description: - "Install most recent release with the same minor version", - }, - { - name: ["-A", "--audit"], - description: "Run vulnerability audit on installed packages", - }, - { - name: ["-h", "--help"], - description: "Output usage information", - }, - ], - }, - { - name: "audit", - description: - "Perform a vulnerability audit against the installed packages", - options: [ - { - name: "--summary", - description: "Only print the summary", - }, - { - name: "--groups", - description: - "Only audit dependencies from listed groups. Default: devDependencies, dependencies, optionalDependencies", - args: { - name: "group_name", - isVariadic: true, - }, - }, - { - name: "--level", - description: - "Only print advisories with severity greater than or equal to one of the following: info|low|moderate|high|critical. Default: info", - args: { - name: "severity", - suggestions: [ - { name: "info" }, - { name: "low" }, - { name: "moderate" }, - { name: "high" }, - { name: "critical" }, - ], - }, - }, - { - name: ["-h", "--help"], - description: "Output usage information", - }, - ], - }, - { - name: "autoclean", - description: - "Cleans and removes unnecessary files from package dependencies", - options: [ - { - name: ["-h", "--help"], - description: "Output usage information", - }, - { - name: ["-i", "--init"], - description: - "Creates the .yarnclean file if it does not exist, and adds the default entries", - }, - { - name: ["-f", "--force"], - description: "If a .yarnclean file exists, run the clean process", - }, - ], - }, - { - name: "bin", - description: "Displays the location of the yarn bin folder", - options: [ - { - name: ["-h", "--help"], - description: "Output usage information", - }, - ], - }, - { - name: "cache", - description: "Yarn cache list will print out every cached package", - options: [ - ...commonOptions, - { - name: ["-h", "--help"], - description: "Output usage information", - }, - ], - subcommands: [ - { - name: "clean", - description: "Clear global cache", - }, - { - name: "dir", - description: "Print yarn’s global cache path", - }, - { - name: "list", - description: "Print out every cached package", - options: [ - { - name: "--pattern", - description: "Filter cached packages by pattern", - args: { - name: "pattern", - }, - }, - ], - }, - ], - }, - { - name: "config", - description: "Configure yarn", - options: [ - { - name: ["-h", "--help"], - description: "Output usage information", - }, - ], - subcommands: [ - { - name: "set", - description: "Sets the config key to a certain value", - options: [ - { - name: ["-g", "--global"], - description: "Set global config", - }, - ], - }, - { - name: "get", - description: "Print the value for a given key", - args: { - generators: configList, - }, - }, - { - name: "delete", - description: "Deletes a given key from the config", - args: { - generators: configList, - }, - }, - { - name: "list", - description: "Displays the current configuration", - }, - ], - }, - { - name: "create", - description: "Creates new projects from any create-* starter kits", - args: { - name: "cli", - generators: createCLIsGenerator, - loadSpec: async (token) => ({ - name: "create-" + token, - type: "global", - }), - isCommand: true, - }, - options: [ - ...commonOptions, - { - name: ["-h", "--help"], - description: "Output usage information", - }, - ], - }, - { - name: "exec", - description: "", - options: [ - { - name: ["-h", "--help"], - description: "Output usage information", - }, - ], - }, - { - name: "generate-lock-entry", - description: "Generates a lock file entry", - options: [ - { - name: "--use-manifest", - description: - "Specify which manifest file to use for generating lock entry", - args: { - template: "filepaths", - }, - }, - { - name: "--resolved", - description: "Generate from <*.tgz>#", - args: { - template: "filepaths", - }, - }, - { - name: ["-h", "--help"], - description: "Output usage information", - }, - ], - }, - { - name: "global", - description: "Manage yarn globally", - subcommands: [ - { - name: "add", - description: "Install globally packages on your operating system", - args: { - name: "package", - generators: npmSearchGenerator, - debounce: true, - isVariadic: true, - }, - }, - { - name: "bin", - description: "Displays the location of the yarn global bin folder", - }, - { - name: "dir", - description: - "Displays the location of the global installation folder", - }, - { - name: "ls", - description: "List globally installed packages (deprecated)", - }, - { - name: "list", - description: "List globally installed packages", - }, - { - name: "remove", - description: "Remove globally installed packages", - args: { - name: "package", - filterStrategy: "fuzzy", - generators: getGlobalPackagesGenerator, - isVariadic: true, - }, - options: [ - ...commonOptions, - { - name: ["-W", "--ignore-workspace-root-check"], - description: - "Required to run yarn remove inside a workspace root", - }, - { - name: ["-h", "--help"], - description: "Output usage information", - }, - ], - }, - { - name: "upgrade", - description: "Upgrade globally installed packages", - options: [ - ...commonOptions, - { - name: ["-S", "--scope"], - description: "Upgrade packages under the specified scope", - args: { name: "scope" }, - }, - { - name: ["-L", "--latest"], - description: "List the latest version of packages", - }, - { - name: ["-E", "--exact"], - description: - "Install exact version. Only used when --latest is specified", - dependsOn: ["--latest"], - }, - { - name: ["-P", "--pattern"], - description: "Upgrade packages that match pattern", - args: { name: "pattern" }, - }, - { - name: ["-T", "--tilde"], - description: - "Install most recent release with the same minor version. Only used when --latest is specified", - }, - { - name: ["-C", "--caret"], - description: - "Install most recent release with the same major version. Only used when --latest is specified", - dependsOn: ["--latest"], - }, - { - name: ["-A", "--audit"], - description: "Run vulnerability audit on installed packages", - }, - { name: ["-h", "--help"], description: "Output usage information" }, - ], - }, - { - name: "upgrade-interactive", - description: - "Display the outdated packages before performing any upgrade", - options: [ - { - name: "--latest", - description: "Use the version tagged latest in the registry", - }, - ], - }, - ], - options: [ - ...commonOptions, - { - name: "--prefix", - description: "Bin prefix to use to install binaries", - args: { - name: "prefix", - }, - }, - { - name: "--latest", - description: "Bin prefix to use to install binaries", - }, - { - name: ["-h", "--help"], - description: "Output usage information", - }, - ], - }, - { - name: "help", - description: "Output usage information", - }, - { - name: "import", - description: "Generates yarn.lock from an npm package-lock.json file", - }, - { - name: "info", - description: "Show information about a package", - }, - { - name: "init", - description: "Interactively creates or updates a package.json file", - options: [ - ...commonOptions, - { - name: ["-y", "--yes"], - description: "Use default options", - }, - { - name: ["-p", "--private"], - description: "Use default options and private true", - }, - { - name: ["-i", "--install"], - description: "Install a specific Yarn release", - args: { - name: "version", - }, - }, - { - name: "-2", - description: "Generates the project using Yarn 2", - }, - { - name: ["-h", "--help"], - description: "Output usage information", - }, - ], - }, - { - name: "install", - description: "Install all the dependencies listed within package.json", - options: [ - ...commonOptions, - { - name: ["-A", "--audit"], - description: "Run vulnerability audit on installed packages", - }, - { - name: ["-h", "--help"], - description: "Output usage information", - }, - ], - }, - { - name: "licenses", - description: "", - subcommands: [ - { - name: "list", - description: "List licenses for installed packages", - }, - { - name: "generate-disclaimer", - description: "List of licenses from all the packages", - }, - ], - }, - { - name: "link", - description: "Symlink a package folder during development", - args: { - isOptional: true, - name: "package", - }, - options: [ - ...commonOptions, - { - name: ["-h", "--help"], - description: "Output usage information", - }, - ], - }, - { - name: "list", - description: "Lists all dependencies for the current working directory", - options: [ - { - name: "--depth", - description: "Restrict the depth of the dependencies", - }, - { - name: "--pattern", - description: "Filter the list of dependencies by the pattern", - }, - ], - }, - { - name: "login", - description: "Store registry username and email", - }, - { - name: "logout", - description: "Clear registry username and email", - }, - { - name: "node", - description: "", - }, - { - name: "outdated", - description: "Checks for outdated package dependencies", - options: [ - ...commonOptions, - { - name: ["-h", "--help"], - description: "Output usage information", - }, - ], - }, - { - name: "owner", - description: "Manage package owners", - subcommands: [ - { - name: "list", - description: "Lists all of the owners of a package", - args: { - name: "package", - }, - }, - { - name: "add", - description: "Adds the user as an owner of the package", - args: { - name: "package", - }, - }, - { - name: "remove", - description: "Removes the user as an owner of the package", - args: [ - { - name: "user", - }, - { - name: "package", - }, - ], - }, - ], - }, - { - name: "pack", - description: "Creates a compressed gzip archive of package dependencies", - options: [ - { - name: "--filename", - description: - "Creates a compressed gzip archive of package dependencies and names the file filename", - }, - ], - }, - { - name: "policies", - description: "Defines project-wide policies for your project", - subcommands: [ - { - name: "set-version", - description: "Will download the latest stable release", - options: [ - { - name: "--rc", - description: "Download the latest rc release", - }, - ], - }, - ], - }, - { - name: "publish", - description: "Publishes a package to the npm registry", - args: { name: "Tarball or Folder", template: "folders" }, - options: [ - ...commonOptions, - { name: ["-h", "--help"], description: "Output usage information" }, - { - name: "--major", - description: "Auto-increment major version number", - }, - { - name: "--minor", - description: "Auto-increment minor version number", - }, - { - name: "--patch", - description: "Auto-increment patch version number", - }, - { - name: "--premajor", - description: "Auto-increment premajor version number", - }, - { - name: "--preminor", - description: "Auto-increment preminor version number", - }, - { - name: "--prepatch", - description: "Auto-increment prepatch version number", - }, - { - name: "--prerelease", - description: "Auto-increment prerelease version number", - }, - { - name: "--preid", - description: "Add a custom identifier to the prerelease", - args: { name: "preid" }, - }, - { - name: "--message", - description: "Message", - args: { name: "message" }, - }, - { name: "--no-git-tag-version", description: "No git tag version" }, - { - name: "--no-commit-hooks", - description: "Bypass git hooks when committing new version", - }, - { name: "--access", description: "Access", args: { name: "access" } }, - { name: "--tag", description: "Tag", args: { name: "tag" } }, - ], - }, - { - name: "remove", - description: "Remove installed package", - args: { - filterStrategy: "fuzzy", - generators: dependenciesGenerator, - isVariadic: true, - }, - options: [ - ...commonOptions, - { - name: ["-W", "--ignore-workspace-root-check"], - description: "Required to run yarn remove inside a workspace root", - }, - { - name: ["-h", "--help"], - description: "Output usage information", - }, - ], - }, - { - name: "run", - description: "Runs a defined package script", - options: [ - ...commonOptions, - { name: ["-h", "--help"], description: "Output usage information" }, - ], - args: [ - { - name: "script", - description: "Script to run from your package.json", - generators: npmScriptsGenerator, - filterStrategy: "fuzzy", - parserDirectives: yarnScriptParserDirectives, - isCommand: true, - }, - { - name: "env", - suggestions: ["env"], - description: "Lists environment variables available to scripts", - isOptional: true, - }, - ], - }, - { - name: "tag", - description: "Add, remove, or list tags on a package", - }, - { - name: "team", - description: "Maintain team memberships", - subcommands: [ - { - name: "create", - description: "Create a new team", - args: { - name: "", - }, - }, - { - name: "destroy", - description: "Destroys an existing team", - args: { - name: "", - }, - }, - { - name: "add", - description: "Add a user to an existing team", - args: [ - { - name: "", - }, - { - name: "", - }, - ], - }, - { - name: "remove", - description: "Remove a user from a team they belong to", - args: { - name: " ", - }, - }, - { - name: "list", - description: - "If performed on an organization name, will return a list of existing teams under that organization. If performed on a team, it will instead return a list of all users belonging to that particular team", - args: { - name: "|", - }, - }, - ], - }, - { - name: "unlink", - description: "Unlink a previously created symlink for a package", - }, - { - name: "unplug", - description: "", - }, - { - name: "upgrade", - description: - "Upgrades packages to their latest version based on the specified range", - args: { - name: "package", - generators: dependenciesGenerator, - filterStrategy: "fuzzy", - isVariadic: true, - isOptional: true, - }, - options: [ - ...commonOptions, - { - name: ["-S", "--scope"], - description: "Upgrade packages under the specified scope", - args: { name: "scope" }, - }, - { - name: ["-L", "--latest"], - description: "List the latest version of packages", - }, - { - name: ["-E", "--exact"], - description: - "Install exact version. Only used when --latest is specified", - dependsOn: ["--latest"], - }, - { - name: ["-P", "--pattern"], - description: "Upgrade packages that match pattern", - args: { name: "pattern" }, - }, - { - name: ["-T", "--tilde"], - description: - "Install most recent release with the same minor version. Only used when --latest is specified", - }, - { - name: ["-C", "--caret"], - description: - "Install most recent release with the same major version. Only used when --latest is specified", - dependsOn: ["--latest"], - }, - { - name: ["-A", "--audit"], - description: "Run vulnerability audit on installed packages", - }, - { name: ["-h", "--help"], description: "Output usage information" }, - ], - }, - { - name: "upgrade-interactive", - description: "Upgrades packages in interactive mode", - options: [ - { - name: "--latest", - description: "Use the version tagged latest in the registry", - }, - ], - }, - { - name: "version", - description: "Update version of your package", - options: [ - ...commonOptions, - { name: ["-h", "--help"], description: "Output usage information" }, - { - name: "--new-version", - description: "New version", - args: { name: "version" }, - }, - { - name: "--major", - description: "Auto-increment major version number", - }, - { - name: "--minor", - description: "Auto-increment minor version number", - }, - { - name: "--patch", - description: "Auto-increment patch version number", - }, - { - name: "--premajor", - description: "Auto-increment premajor version number", - }, - { - name: "--preminor", - description: "Auto-increment preminor version number", - }, - { - name: "--prepatch", - description: "Auto-increment prepatch version number", - }, - { - name: "--prerelease", - description: "Auto-increment prerelease version number", - }, - { - name: "--preid", - description: "Add a custom identifier to the prerelease", - args: { name: "preid" }, - }, - { - name: "--message", - description: "Message", - args: { name: "message" }, - }, - { name: "--no-git-tag-version", description: "No git tag version" }, - { - name: "--no-commit-hooks", - description: "Bypass git hooks when committing new version", - }, - { name: "--access", description: "Access", args: { name: "access" } }, - { name: "--tag", description: "Tag", args: { name: "tag" } }, - ], - }, - { - name: "versions", - description: - "Displays version information of the currently installed Yarn, Node.js, and its dependencies", - }, - { - name: "why", - description: "Show information about why a package is installed", - args: { - name: "package", - filterStrategy: "fuzzy", - generators: allDependenciesGenerator, - }, - options: [ - ...commonOptions, - { - name: ["-h", "--help"], - description: "Output usage information", - }, - { - name: "--peers", - description: - "Print the peer dependencies that match the specified name", - }, - { - name: ["-R", "--recursive"], - description: - "List, for each workspace, what are all the paths that lead to the dependency", - }, - ], - }, - { - name: "workspace", - description: "Manage workspace", - filterStrategy: "fuzzy", - generateSpec: async (_tokens, executeShellCommand) => { - const version = ( - await executeShellCommand({ - command: "yarn", - // eslint-disable-next-line @withfig/fig-linter/no-useless-arrays - args: ["--version"], - }) - ).stdout; - const isYarnV1 = version.startsWith("1."); - - const getWorkspacesDefinitionsV1 = async () => { - const { stdout } = await executeShellCommand({ - command: "yarn", - args: ["workspaces", "info"], - }); - - const startJson = stdout.indexOf("{"); - const endJson = stdout.lastIndexOf("}"); - - return Object.entries( - JSON.parse(stdout.slice(startJson, endJson + 1)) as Record< - string, - { location: string } - > - ).map(([name, { location }]) => ({ - name, - location, - })); - }; - - // For yarn >= 2.0.0 - const getWorkspacesDefinitionsVOther = async () => { - // yarn workspaces list --json - const out = ( - await executeShellCommand({ - command: "yarn", - args: ["workspaces", "list", "--json"], - }) - ).stdout; - return out.split("\n").map((line) => JSON.parse(line.trim())); - }; - - try { - const workspacesDefinitions = isYarnV1 - ? // transform Yarn V1 output to array of workspaces like Yarn V2 - await getWorkspacesDefinitionsV1() - : // in yarn v>=2.0.0, workspaces definitions are a list of JSON lines - await getWorkspacesDefinitionsVOther(); - - const subcommands: Fig.Subcommand[] = workspacesDefinitions.map( - ({ name, location }: { name: string; location: string }) => ({ - name, - description: "Workspaces", - args: { - name: "script", - generators: { - cache: { - strategy: "stale-while-revalidate", - ttl: 60_000, // 60s - }, - script: ["cat", `${location}/package.json`], - postProcess: function (out: string) { - if (out.trim() == "") { - return []; - } - try { - const packageContent = JSON.parse(out); - const scripts = packageContent["scripts"]; - if (scripts) { - return Object.keys(scripts).map((script) => ({ - name: script, - })); - } - } catch (e) {} - return []; - }, - }, - }, - }) - ); - - return { - name: "workspace", - subcommands, - }; - } catch (e) { - console.error(e); - } - return { name: "workspaces" }; - }, - }, - { - name: "workspaces", - description: "Show information about your workspaces", - options: [ - { - name: "subcommand", - description: "", - args: { - suggestions: [{ name: "info" }, { name: "run" }], - }, - }, - { - name: "flags", - description: "", - }, - ], - }, - { - name: "set", - description: "Set global Yarn options", - subcommands: [ - { - name: "resolution", - description: "Enforce a package resolution", - args: [ - { - name: "descriptor", - description: - "A descriptor for the package, in the form of 'lodash@npm:^1.2.3'", - }, - { - name: "resolution", - description: "The version of the package to resolve", - }, - ], - options: [ - { - name: ["-s", "--save"], - description: - "Persist the resolution inside the top-level manifest", - }, - ], - }, - { - name: "version", - description: "Lock the Yarn version used by the project", - args: { - name: "version", - description: - "Use the specified version, which can also be a Yarn 2 build (e.g 2.0.0-rc.30) or a Yarn 1 build (e.g 1.22.1)", - template: "filepaths", - suggestions: [ - { - name: "from-sources", - insertValue: "from sources", - }, - "latest", - "canary", - "classic", - "self", - ], - }, - options: [ - { - name: "--only-if-needed", - description: - "Only lock the Yarn version if it isn't already locked", - }, - ], - }, - ], - }, - ], -}; - -export default completionSpec; diff --git a/code/extensions/terminal-suggest/src/completions/yarn.ts b/code/extensions/terminal-suggest/src/completions/yarn.ts new file mode 100644 index 00000000000..7b0750ba2b1 --- /dev/null +++ b/code/extensions/terminal-suggest/src/completions/yarn.ts @@ -0,0 +1,1677 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { npmScriptsGenerator, npmSearchGenerator } from './npm'; + +export const yarnScriptParserDirectives: Fig.Arg['parserDirectives'] = { + alias: async (token, executeShellCommand) => { + const npmPrefix = await executeShellCommand({ + command: 'npm', + args: ['prefix'], + }); + if (npmPrefix.status !== 0) { + throw new Error('npm prefix command failed'); + } + const packageJson = await executeShellCommand({ + command: 'cat', + args: [`${npmPrefix.stdout.trim()}/package.json`], + }); + const script: string = JSON.parse(packageJson.stdout).scripts?.[token]; + if (!script) { + throw new Error(`Script not found: '${token}'`); + } + return script; + }, +}; + +export const nodeClis = new Set([ + 'vue', + 'vite', + 'nuxt', + 'react-native', + 'degit', + 'expo', + 'jest', + 'next', + 'electron', + 'prisma', + 'eslint', + 'prettier', + 'tsc', + 'typeorm', + 'babel', + 'remotion', + 'autocomplete-tools', + 'redwood', + 'rw', + 'create-completion-spec', + 'publish-spec-to-team', + 'capacitor', + 'cap', +]); + +// generate global package list from global package.json file +const getGlobalPackagesGenerator: Fig.Generator = { + custom: async (tokens, executeCommand, generatorContext) => { + const { stdout: yarnGlobalDir } = await executeCommand({ + command: 'yarn', + args: ['global', 'dir'], + }); + + const { stdout } = await executeCommand({ + command: 'cat', + args: [`${yarnGlobalDir.trim()}/package.json`], + }); + + if (stdout.trim() === '') { + return []; + } + + try { + const packageContent = JSON.parse(stdout); + const dependencyScripts = packageContent['dependencies'] || {}; + const devDependencyScripts = packageContent['devDependencies'] || {}; + const dependencies = [ + ...Object.keys(dependencyScripts), + ...Object.keys(devDependencyScripts), + ]; + + const filteredDependencies = dependencies.filter( + (dependency) => !tokens.includes(dependency) + ); + + return filteredDependencies.map((dependencyName) => ({ + name: dependencyName, + })); + } catch (e) { } + + return []; + }, +}; + +// generate package list of direct and indirect dependencies +const allDependenciesGenerator: Fig.Generator = { + script: ['yarn', 'list', '--depth=0', '--json'], + postProcess: (out) => { + if (out.trim() === '') { + return []; + } + + try { + const packageContent = JSON.parse(out); + const dependencies = packageContent.data.trees; + return dependencies.map((dependency: { name: string }) => ({ + name: dependency.name.split('@')[0], + })); + } catch (e) { } + return []; + }, +}; + +const configList: Fig.Generator = { + script: ['yarn', 'config', 'list'], + postProcess: function (out) { + if (out.trim() === '') { + return []; + } + + try { + const startIndex = out.indexOf('{'); + const endIndex = out.indexOf('}'); + let output = out.substring(startIndex, endIndex + 1); + // TODO: fix hacky code + // reason: JSON parse was not working without double quotes + output = output + .replace(/\'/gi, '\'') + .replace('lastUpdateCheck', '\'lastUpdateCheck\'') + .replace('registry', '\'lastUpdateCheck\''); + const configObject = JSON.parse(output); + if (configObject) { + return Object.keys(configObject).map((key) => ({ name: key })); + } + } catch (e) { } + + return []; + }, +}; + +export const dependenciesGenerator: Fig.Generator = { + script: [ + 'bash', + '-c', + 'until [[ -f package.json ]] || [[ $PWD = \' / \' ]]; do cd ..; done; cat package.json', + ], + postProcess: function (out, context = []) { + if (out.trim() === '') { + return []; + } + + try { + const packageContent = JSON.parse(out); + const dependencies = packageContent['dependencies'] ?? {}; + const devDependencies = packageContent['devDependencies']; + const optionalDependencies = packageContent['optionalDependencies'] ?? {}; + Object.assign(dependencies, devDependencies, optionalDependencies); + + return Object.keys(dependencies) + .filter((pkgName) => { + const isListed = context.some((current) => current === pkgName); + return !isListed; + }) + .map((pkgName) => ({ + name: pkgName, + description: dependencies[pkgName] + ? 'dependency' + : optionalDependencies[pkgName] + ? 'optionalDependency' + : 'devDependency', + })); + } catch (e) { + console.error(e); + return []; + } + }, +}; + +const commonOptions: Fig.Option[] = [ + { name: ['-s', '--silent'], description: 'Skip Yarn console logs' }, + { + name: '--no-default-rc', + description: + 'Prevent Yarn from automatically detecting yarnrc and npmrc files', + }, + { + name: '--use-yarnrc', + description: + 'Specifies a yarnrc file that Yarn should use (.yarnrc only, not .npmrc) (default: )', + args: { name: 'path', template: 'filepaths' }, + }, + { + name: '--verbose', + description: 'Output verbose messages on internal operations', + }, + { + name: '--offline', + description: + 'Trigger an error if any required dependencies are not available in local cache', + }, + { + name: '--prefer-offline', + description: + 'Use network only if dependencies are not available in local cache', + }, + { + name: ['--enable-pnp', '--pnp'], + description: 'Enable the Plug\'n\'Play installation', + }, + { + name: '--json', + description: 'Format Yarn log messages as lines of JSON', + }, + { + name: '--ignore-scripts', + description: 'Don\'t run lifecycle scripts', + }, + { name: '--har', description: 'Save HAR output of network traffic' }, + { name: '--ignore-platform', description: 'Ignore platform checks' }, + { name: '--ignore-engines', description: 'Ignore engines check' }, + { + name: '--ignore-optional', + description: 'Ignore optional dependencies', + }, + { + name: '--force', + description: + 'Install and build packages even if they were built before, overwrite lockfile', + }, + { + name: '--skip-integrity-check', + description: 'Run install without checking if node_modules is installed', + }, + { + name: '--check-files', + description: 'Install will verify file tree of packages for consistency', + }, + { + name: '--no-bin-links', + description: 'Don\'t generate bin links when setting up packages', + }, + { name: '--flat', description: 'Only allow one version of a package' }, + { + name: ['--prod', '--production'], + description: + 'Instruct Yarn to ignore NODE_ENV and take its production-or-not status from this flag instead', + }, + { + name: '--no-lockfile', + description: 'Don\'t read or generate a lockfile', + }, + { + name: '--pure-lockfile', description: 'Don\'t generate a lockfile' + }, + { + name: '--frozen-lockfile', + description: 'Don\'t generate a lockfile and fail if an update is needed', + }, + { + name: '--update-checksums', + description: 'Update package checksums from current repository', + }, + { + name: '--link-duplicates', + description: 'Create hardlinks to the repeated modules in node_modules', + }, + { + name: '--link-folder', + description: 'Specify a custom folder to store global links', + args: { name: 'path', template: 'folders' }, + }, + { + name: '--global-folder', + description: 'Specify a custom folder to store global packages', + args: { name: 'path', template: 'folders' }, + }, + { + name: '--modules-folder', + description: + 'Rather than installing modules into the node_modules folder relative to the cwd, output them here', + args: { name: 'path', template: 'folders' }, + }, + { + name: '--preferred-cache-folder', + description: 'Specify a custom folder to store the yarn cache if possible', + args: { name: 'path', template: 'folders' }, + }, + { + name: '--cache-folder', + description: + 'Specify a custom folder that must be used to store the yarn cache', + args: { name: 'path', template: 'folders' }, + }, + { + name: '--mutex', + description: 'Use a mutex to ensure only one yarn instance is executing', + args: { name: 'type[:specifier]' }, + }, + { + name: '--emoji', + description: 'Enables emoji in output', + args: { + default: 'true', + suggestions: ['true', 'false'], + }, + }, + { + name: '--cwd', + description: 'Working directory to use', + args: { name: 'cwd', template: 'folders' }, + }, + { + name: ['--proxy', '--https-proxy'], + description: '', + args: { name: 'host' }, + }, + { + name: '--registry', + description: 'Override configuration registry', + args: { name: 'url' }, + }, + { name: '--no-progress', description: 'Disable progress bar' }, + { + name: '--network-concurrency', + description: 'Maximum number of concurrent network requests', + args: { name: 'number' }, + }, + { + name: '--network-timeout', + description: 'TCP timeout for network requests', + args: { name: 'milliseconds' }, + }, + { + name: '--non-interactive', + description: 'Do not show interactive prompts', + }, + { + name: '--scripts-prepend-node-path', + description: 'Prepend the node executable dir to the PATH in scripts', + }, + { + name: '--no-node-version-check', + description: + 'Do not warn when using a potentially unsupported Node version', + }, + { + name: '--focus', + description: + 'Focus on a single workspace by installing remote copies of its sibling workspaces', + }, + { + name: '--otp', + description: 'One-time password for two factor authentication', + args: { name: 'otpcode' }, + }, +]; + +export const createCLIsGenerator: Fig.Generator = { + script: function (context) { + if (context[context.length - 1] === '') { + return undefined; + } + const searchTerm = 'create-' + context[context.length - 1]; + return [ + 'curl', + '-s', + '-H', + 'Accept: application/json', + `https://api.npms.io/v2/search?q=${searchTerm}&size=20`, + ]; + }, + cache: { + ttl: 100 * 24 * 60 * 60 * 3, // 3 days + }, + postProcess: function (out) { + try { + return JSON.parse(out).results.map((item: { package: { name: string; description: string } }) => ({ + name: item.package.name.substring(7), + description: item.package.description, + })) as Fig.Suggestion[]; + } catch (e) { + return []; + } + }, +}; + +const completionSpec: Fig.Spec = { + name: 'yarn', + description: 'Manage packages and run scripts', + generateSpec: async (tokens, executeShellCommand) => { + const binaries = ( + await executeShellCommand({ + command: 'bash', + args: [ + '-c', + `until [[ -d node_modules/ ]] || [[ $PWD = '/' ]]; do cd ..; done; ls -1 node_modules/.bin/`, + ], + }) + ).stdout.split('\n'); + + const subcommands = binaries + .filter((name) => nodeClis.has(name)) + .map((name) => ({ + name: name, + loadSpec: name === 'rw' ? 'redwood' : name, + icon: 'fig://icon?type=package', + })); + + return { + name: 'yarn', + subcommands, + }; + }, + args: { + generators: npmScriptsGenerator, + filterStrategy: 'fuzzy', + parserDirectives: yarnScriptParserDirectives, + isOptional: true, + isCommand: true, + }, + options: [ + { + name: '--disable-pnp', + description: 'Disable the Plug\'n\'Play installation', + }, + { + name: '--emoji', + description: 'Enable emoji in output (default: true)', + args: { + name: 'bool', + suggestions: [{ name: 'true' }, { name: 'false' }], + }, + }, + { + name: ['--enable-pnp', '--pnp'], + description: 'Enable the Plug\'n\'Play installation', + }, + { + name: '--flat', + description: 'Only allow one version of a package', + }, + { + name: '--focus', + description: + 'Focus on a single workspace by installing remote copies of its sibling workspaces', + }, + { + name: '--force', + description: + 'Install and build packages even if they were built before, overwrite lockfile', + }, + { + name: '--frozen-lockfile', + description: 'Don\'t generate a lockfile and fail if an update is needed', + }, + { + name: '--global-folder', + description: 'Specify a custom folder to store global packages', + args: { + template: 'folders', + }, + }, + { + name: '--har', + description: 'Save HAR output of network traffic', + }, + { + name: '--https-proxy', + description: '', + args: { + name: 'path', + suggestions: [{ name: 'https://' }], + }, + }, + { + name: '--ignore-engines', + description: 'Ignore engines check', + }, + { + name: '--ignore-optional', + description: 'Ignore optional dependencies', + }, + { + name: '--ignore-platform', + description: 'Ignore platform checks', + }, + { + name: '--ignore-scripts', + description: 'Don\'t run lifecycle scripts', + }, + { + name: '--json', + description: + 'Format Yarn log messages as lines of JSON (see jsonlines.org)', + }, + { + name: '--link-duplicates', + description: 'Create hardlinks to the repeated modules in node_modules', + }, + { + name: '--link-folder', + description: 'Specify a custom folder to store global links', + args: { + template: 'folders', + }, + }, + { + name: '--modules-folder', + description: + 'Rather than installing modules into the node_modules folder relative to the cwd, output them here', + args: { + template: 'folders', + }, + }, + { + name: '--mutex', + description: 'Use a mutex to ensure only one yarn instance is executing', + args: [ + { + name: 'type', + suggestions: [{ name: ':' }], + }, + { + name: 'specifier', + suggestions: [{ name: ':' }], + }, + ], + }, + { + name: '--network-concurrency', + description: 'Maximum number of concurrent network requests', + args: { + name: 'number', + }, + }, + { + name: '--network-timeout', + description: 'TCP timeout for network requests', + args: { + name: 'milliseconds', + }, + }, + { + name: '--no-bin-links', + description: 'Don\'t generate bin links when setting up packages', + }, + { + name: '--no-default-rc', + description: + 'Prevent Yarn from automatically detecting yarnrc and npmrc files', + }, + { + name: '--no-lockfile', + description: 'Don\'t read or generate a lockfile', + }, + { + name: '--non-interactive', + description: 'Do not show interactive prompts', + }, + { + name: '--no-node-version-check', + description: + 'Do not warn when using a potentially unsupported Node version', + }, + { + name: '--no-progress', + description: 'Disable progress bar', + }, + { + name: '--offline', + description: + 'Trigger an error if any required dependencies are not available in local cache', + }, + { + name: '--otp', + description: 'One-time password for two factor authentication', + args: { + name: 'otpcode', + }, + }, + { + name: '--prefer-offline', + description: + 'Use network only if dependencies are not available in local cache', + }, + { + name: '--preferred-cache-folder', + description: + 'Specify a custom folder to store the yarn cache if possible', + args: { + template: 'folders', + }, + }, + { + name: ['--prod', '--production'], + description: '', + args: {}, + }, + { + name: '--proxy', + description: '', + args: { + name: 'host', + }, + }, + { + name: '--pure-lockfile', + description: 'Don\'t generate a lockfile', + }, + { + name: '--registry', + description: 'Override configuration registry', + args: { + name: 'url', + }, + }, + { + name: ['-s', '--silent'], + description: + 'Skip Yarn console logs, other types of logs (script output) will be printed', + }, + { + name: '--scripts-prepend-node-path', + description: 'Prepend the node executable dir to the PATH in scripts', + args: { + suggestions: [{ name: 'true' }, { name: 'false' }], + }, + }, + { + name: '--skip-integrity-check', + description: 'Run install without checking if node_modules is installed', + }, + { + name: '--strict-semver', + description: '', + }, + ...commonOptions, + { + name: ['-v', '--version'], + description: 'Output the version number', + }, + { + name: ['-h', '--help'], + description: 'Output usage information', + }, + ], + subcommands: [ + { + name: 'add', + description: 'Installs a package and any packages that it depends on', + args: { + name: 'package', + generators: npmSearchGenerator, + debounce: true, + isVariadic: true, + }, + options: [ + ...commonOptions, + { + name: ['-W', '--ignore-workspace-root-check'], + description: 'Required to run yarn add inside a workspace root', + }, + { + name: ['-D', '--dev'], + description: 'Save package to your `devDependencies`', + }, + { + name: ['-P', '--peer'], + description: 'Save package to your `peerDependencies`', + }, + { + name: ['-O', '--optional'], + description: 'Save package to your `optionalDependencies`', + }, + { + name: ['-E', '--exact'], + description: 'Install exact version', + dependsOn: ['--latest'], + }, + { + name: ['-T', '--tilde'], + description: + 'Install most recent release with the same minor version', + }, + { + name: ['-A', '--audit'], + description: 'Run vulnerability audit on installed packages', + }, + { + name: ['-h', '--help'], + description: 'Output usage information', + }, + ], + }, + { + name: 'audit', + description: + 'Perform a vulnerability audit against the installed packages', + options: [ + { + name: '--summary', + description: 'Only print the summary', + }, + { + name: '--groups', + description: + 'Only audit dependencies from listed groups. Default: devDependencies, dependencies, optionalDependencies', + args: { + name: 'group_name', + isVariadic: true, + }, + }, + { + name: '--level', + description: + 'Only print advisories with severity greater than or equal to one of the following: info|low|moderate|high|critical. Default: info', + args: { + name: 'severity', + suggestions: [ + { name: 'info' }, + { name: 'low' }, + { name: 'moderate' }, + { name: 'high' }, + { name: 'critical' }, + ], + }, + }, + { + name: ['-h', '--help'], + description: 'Output usage information', + }, + ], + }, + { + name: 'autoclean', + description: + 'Cleans and removes unnecessary files from package dependencies', + options: [ + { + name: ['-h', '--help'], + description: 'Output usage information', + }, + { + name: ['-i', '--init'], + description: + 'Creates the .yarnclean file if it does not exist, and adds the default entries', + }, + { + name: ['-f', '--force'], + description: 'If a .yarnclean file exists, run the clean process', + }, + ], + }, + { + name: 'bin', + description: 'Displays the location of the yarn bin folder', + options: [ + { + name: ['-h', '--help'], + description: 'Output usage information', + }, + ], + }, + { + name: 'cache', + description: 'Yarn cache list will print out every cached package', + options: [ + ...commonOptions, + { + name: ['-h', '--help'], + description: 'Output usage information', + }, + ], + subcommands: [ + { + name: 'clean', + description: 'Clear global cache', + }, + { + name: 'dir', + description: 'Print yarn\'s global cache path', + }, + { + name: 'list', + description: 'Print out every cached package', + options: [ + { + name: '--pattern', + description: 'Filter cached packages by pattern', + args: { + name: 'pattern', + }, + }, + ], + }, + ], + }, + { + name: 'config', + description: 'Configure yarn', + options: [ + { + name: ['-h', '--help'], + description: 'Output usage information', + }, + ], + subcommands: [ + { + name: 'set', + description: 'Sets the config key to a certain value', + options: [ + { + name: ['-g', '--global'], + description: 'Set global config', + }, + ], + }, + { + name: 'get', + description: 'Print the value for a given key', + args: { + generators: configList, + }, + }, + { + name: 'delete', + description: 'Deletes a given key from the config', + args: { + generators: configList, + }, + }, + { + name: 'list', + description: 'Displays the current configuration', + }, + ], + }, + { + name: 'create', + description: 'Creates new projects from any create-* starter kits', + args: { + name: 'cli', + generators: createCLIsGenerator, + loadSpec: async (token) => ({ + name: 'create-' + token, + type: 'global', + }), + isCommand: true, + }, + options: [ + ...commonOptions, + { + name: ['-h', '--help'], + description: 'Output usage information', + }, + ], + }, + { + name: 'exec', + description: '', + options: [ + { + name: ['-h', '--help'], + description: 'Output usage information', + }, + ], + }, + { + name: 'generate-lock-entry', + description: 'Generates a lock file entry', + options: [ + { + name: '--use-manifest', + description: + 'Specify which manifest file to use for generating lock entry', + args: { + template: 'filepaths', + }, + }, + { + name: '--resolved', + description: 'Generate from <*.tgz>#', + args: { + template: 'filepaths', + }, + }, + { + name: ['-h', '--help'], + description: 'Output usage information', + }, + ], + }, + { + name: 'global', + description: 'Manage yarn globally', + subcommands: [ + { + name: 'add', + description: 'Install globally packages on your operating system', + args: { + name: 'package', + generators: npmSearchGenerator, + debounce: true, + isVariadic: true, + }, + }, + { + name: 'bin', + description: 'Displays the location of the yarn global bin folder', + }, + { + name: 'dir', + description: + 'Displays the location of the global installation folder', + }, + { + name: 'ls', + description: 'List globally installed packages (deprecated)', + }, + { + name: 'list', + description: 'List globally installed packages', + }, + { + name: 'remove', + description: 'Remove globally installed packages', + args: { + name: 'package', + filterStrategy: 'fuzzy', + generators: getGlobalPackagesGenerator, + isVariadic: true, + }, + options: [ + ...commonOptions, + { + name: ['-W', '--ignore-workspace-root-check'], + description: + 'Required to run yarn remove inside a workspace root', + }, + { + name: ['-h', '--help'], + description: 'Output usage information', + }, + ], + }, + { + name: 'upgrade', + description: 'Upgrade globally installed packages', + options: [ + ...commonOptions, + { + name: ['-S', '--scope'], + description: 'Upgrade packages under the specified scope', + args: { name: 'scope' }, + }, + { + name: ['-L', '--latest'], + description: 'List the latest version of packages', + }, + { + name: ['-E', '--exact'], + description: + 'Install exact version. Only used when --latest is specified', + dependsOn: ['--latest'], + }, + { + name: ['-P', '--pattern'], + description: 'Upgrade packages that match pattern', + args: { name: 'pattern' }, + }, + { + name: ['-T', '--tilde'], + description: + 'Install most recent release with the same minor version. Only used when --latest is specified', + }, + { + name: ['-C', '--caret'], + description: + 'Install most recent release with the same major version. Only used when --latest is specified', + dependsOn: ['--latest'], + }, + { + name: ['-A', '--audit'], + description: 'Run vulnerability audit on installed packages', + }, + { name: ['-h', '--help'], description: 'Output usage information' }, + ], + }, + { + name: 'upgrade-interactive', + description: + 'Display the outdated packages before performing any upgrade', + options: [ + { + name: '--latest', + description: 'Use the version tagged latest in the registry', + }, + ], + }, + ], + options: [ + ...commonOptions, + { + name: '--prefix', + description: 'Bin prefix to use to install binaries', + args: { + name: 'prefix', + }, + }, + { + name: '--latest', + description: 'Bin prefix to use to install binaries', + }, + { + name: ['-h', '--help'], + description: 'Output usage information', + }, + ], + }, + { + name: 'help', + description: 'Output usage information', + }, + { + name: 'import', + description: 'Generates yarn.lock from an npm package-lock.json file', + }, + { + name: 'info', + description: 'Show information about a package', + }, + { + name: 'init', + description: 'Interactively creates or updates a package.json file', + options: [ + ...commonOptions, + { + name: ['-y', '--yes'], + description: 'Use default options', + }, + { + name: ['-p', '--private'], + description: 'Use default options and private true', + }, + { + name: ['-i', '--install'], + description: 'Install a specific Yarn release', + args: { + name: 'version', + }, + }, + { + name: '-2', + description: 'Generates the project using Yarn 2', + }, + { + name: ['-h', '--help'], + description: 'Output usage information', + }, + ], + }, + { + name: 'install', + description: 'Install all the dependencies listed within package.json', + options: [ + ...commonOptions, + { + name: ['-A', '--audit'], + description: 'Run vulnerability audit on installed packages', + }, + { + name: ['-h', '--help'], + description: 'Output usage information', + }, + ], + }, + { + name: 'licenses', + description: '', + subcommands: [ + { + name: 'list', + description: 'List licenses for installed packages', + }, + { + name: 'generate-disclaimer', + description: 'List of licenses from all the packages', + }, + ], + }, + { + name: 'link', + description: 'Symlink a package folder during development', + args: { + isOptional: true, + name: 'package', + }, + options: [ + ...commonOptions, + { + name: ['-h', '--help'], + description: 'Output usage information', + }, + ], + }, + { + name: 'list', + description: 'Lists all dependencies for the current working directory', + options: [ + { + name: '--depth', + description: 'Restrict the depth of the dependencies', + }, + { + name: '--pattern', + description: 'Filter the list of dependencies by the pattern', + }, + ], + }, + { + name: 'login', + description: 'Store registry username and email', + }, + { + name: 'logout', + description: 'Clear registry username and email', + }, + { + name: 'node', + description: '', + }, + { + name: 'outdated', + description: 'Checks for outdated package dependencies', + options: [ + ...commonOptions, + { + name: ['-h', '--help'], + description: 'Output usage information', + }, + ], + }, + { + name: 'owner', + description: 'Manage package owners', + subcommands: [ + { + name: 'list', + description: 'Lists all of the owners of a package', + args: { + name: 'package', + }, + }, + { + name: 'add', + description: 'Adds the user as an owner of the package', + args: { + name: 'package', + }, + }, + { + name: 'remove', + description: 'Removes the user as an owner of the package', + args: [ + { + name: 'user', + }, + { + name: 'package', + }, + ], + }, + ], + }, + { + name: 'pack', + description: 'Creates a compressed gzip archive of package dependencies', + options: [ + { + name: '--filename', + description: + 'Creates a compressed gzip archive of package dependencies and names the file filename', + }, + ], + }, + { + name: 'policies', + description: 'Defines project-wide policies for your project', + subcommands: [ + { + name: 'set-version', + description: 'Will download the latest stable release', + options: [ + { + name: '--rc', + description: 'Download the latest rc release', + }, + ], + }, + ], + }, + { + name: 'publish', + description: 'Publishes a package to the npm registry', + args: { name: 'Tarball or Folder', template: 'folders' }, + options: [ + ...commonOptions, + { name: ['-h', '--help'], description: 'Output usage information' }, + { + name: '--major', + description: 'Auto-increment major version number', + }, + { + name: '--minor', + description: 'Auto-increment minor version number', + }, + { + name: '--patch', + description: 'Auto-increment patch version number', + }, + { + name: '--premajor', + description: 'Auto-increment premajor version number', + }, + { + name: '--preminor', + description: 'Auto-increment preminor version number', + }, + { + name: '--prepatch', + description: 'Auto-increment prepatch version number', + }, + { + name: '--prerelease', + description: 'Auto-increment prerelease version number', + }, + { + name: '--preid', + description: 'Add a custom identifier to the prerelease', + args: { name: 'preid' }, + }, + { + name: '--message', + description: 'Message', + args: { name: 'message' }, + }, + { name: '--no-git-tag-version', description: 'No git tag version' }, + { + name: '--no-commit-hooks', + description: 'Bypass git hooks when committing new version', + }, + { name: '--access', description: 'Access', args: { name: 'access' } }, + { name: '--tag', description: 'Tag', args: { name: 'tag' } }, + ], + }, + { + name: 'remove', + description: 'Remove installed package', + args: { + filterStrategy: 'fuzzy', + generators: dependenciesGenerator, + isVariadic: true, + }, + options: [ + ...commonOptions, + { + name: ['-W', '--ignore-workspace-root-check'], + description: 'Required to run yarn remove inside a workspace root', + }, + { + name: ['-h', '--help'], + description: 'Output usage information', + }, + ], + }, + { + name: 'run', + description: 'Runs a defined package script', + options: [ + ...commonOptions, + { name: ['-h', '--help'], description: 'Output usage information' }, + ], + args: [ + { + name: 'script', + description: 'Script to run from your package.json', + generators: npmScriptsGenerator, + filterStrategy: 'fuzzy', + parserDirectives: yarnScriptParserDirectives, + isCommand: true, + }, + { + name: 'env', + suggestions: ['env'], + description: 'Lists environment variables available to scripts', + isOptional: true, + }, + ], + }, + { + name: 'tag', + description: 'Add, remove, or list tags on a package', + }, + { + name: 'team', + description: 'Maintain team memberships', + subcommands: [ + { + name: 'create', + description: 'Create a new team', + args: { + name: '', + }, + }, + { + name: 'destroy', + description: 'Destroys an existing team', + args: { + name: '', + }, + }, + { + name: 'add', + description: 'Add a user to an existing team', + args: [ + { + name: '', + }, + { + name: '', + }, + ], + }, + { + name: 'remove', + description: 'Remove a user from a team they belong to', + args: { + name: ' ', + }, + }, + { + name: 'list', + description: + 'If performed on an organization name, will return a list of existing teams under that organization. If performed on a team, it will instead return a list of all users belonging to that particular team', + args: { + name: '|', + }, + }, + ], + }, + { + name: 'unlink', + description: 'Unlink a previously created symlink for a package', + }, + { + name: 'unplug', + description: '', + }, + { + name: 'upgrade', + description: + 'Upgrades packages to their latest version based on the specified range', + args: { + name: 'package', + generators: dependenciesGenerator, + filterStrategy: 'fuzzy', + isVariadic: true, + isOptional: true, + }, + options: [ + ...commonOptions, + { + name: ['-S', '--scope'], + description: 'Upgrade packages under the specified scope', + args: { name: 'scope' }, + }, + { + name: ['-L', '--latest'], + description: 'List the latest version of packages', + }, + { + name: ['-E', '--exact'], + description: + 'Install exact version. Only used when --latest is specified', + dependsOn: ['--latest'], + }, + { + name: ['-P', '--pattern'], + description: 'Upgrade packages that match pattern', + args: { name: 'pattern' }, + }, + { + name: ['-T', '--tilde'], + description: + 'Install most recent release with the same minor version. Only used when --latest is specified', + }, + { + name: ['-C', '--caret'], + description: + 'Install most recent release with the same major version. Only used when --latest is specified', + dependsOn: ['--latest'], + }, + { + name: ['-A', '--audit'], + description: 'Run vulnerability audit on installed packages', + }, + { name: ['-h', '--help'], description: 'Output usage information' }, + ], + }, + { + name: 'upgrade-interactive', + description: 'Upgrades packages in interactive mode', + options: [ + { + name: '--latest', + description: 'Use the version tagged latest in the registry', + }, + ], + }, + { + name: 'version', + description: 'Update version of your package', + options: [ + ...commonOptions, + { name: ['-h', '--help'], description: 'Output usage information' }, + { + name: '--new-version', + description: 'New version', + args: { name: 'version' }, + }, + { + name: '--major', + description: 'Auto-increment major version number', + }, + { + name: '--minor', + description: 'Auto-increment minor version number', + }, + { + name: '--patch', + description: 'Auto-increment patch version number', + }, + { + name: '--premajor', + description: 'Auto-increment premajor version number', + }, + { + name: '--preminor', + description: 'Auto-increment preminor version number', + }, + { + name: '--prepatch', + description: 'Auto-increment prepatch version number', + }, + { + name: '--prerelease', + description: 'Auto-increment prerelease version number', + }, + { + name: '--preid', + description: 'Add a custom identifier to the prerelease', + args: { name: 'preid' }, + }, + { + name: '--message', + description: 'Message', + args: { name: 'message' }, + }, + { name: '--no-git-tag-version', description: 'No git tag version' }, + { + name: '--no-commit-hooks', + description: 'Bypass git hooks when committing new version', + }, + { name: '--access', description: 'Access', args: { name: 'access' } }, + { name: '--tag', description: 'Tag', args: { name: 'tag' } }, + ], + }, + { + name: 'versions', + description: + 'Displays version information of the currently installed Yarn, Node.js, and its dependencies', + }, + { + name: 'why', + description: 'Show information about why a package is installed', + args: { + name: 'package', + filterStrategy: 'fuzzy', + generators: allDependenciesGenerator, + }, + options: [ + ...commonOptions, + { + name: ['-h', '--help'], + description: 'Output usage information', + }, + { + name: '--peers', + description: + 'Print the peer dependencies that match the specified name', + }, + { + name: ['-R', '--recursive'], + description: + 'List, for each workspace, what are all the paths that lead to the dependency', + }, + ], + }, + { + name: 'workspace', + description: 'Manage workspace', + filterStrategy: 'fuzzy', + generateSpec: async (_tokens, executeShellCommand) => { + const version = ( + await executeShellCommand({ + command: 'yarn', + args: ['--version'], + }) + ).stdout; + const isYarnV1 = version.startsWith('1.'); + + const getWorkspacesDefinitionsV1 = async () => { + const { stdout } = await executeShellCommand({ + command: 'yarn', + args: ['workspaces', 'info'], + }); + + const startJson = stdout.indexOf('{'); + const endJson = stdout.lastIndexOf('}'); + + return Object.entries( + JSON.parse(stdout.slice(startJson, endJson + 1)) as Record< + string, + { location: string } + > + ).map(([name, { location }]) => ({ + name, + location, + })); + }; + + // For yarn >= 2.0.0 + const getWorkspacesDefinitionsVOther = async () => { + // yarn workspaces list --json + const out = ( + await executeShellCommand({ + command: 'yarn', + args: ['workspaces', 'list', '--json'], + }) + ).stdout; + return out.split('\n').map((line) => JSON.parse(line.trim())); + }; + + try { + const workspacesDefinitions = isYarnV1 + ? // transform Yarn V1 output to array of workspaces like Yarn V2 + await getWorkspacesDefinitionsV1() + : // in yarn v>=2.0.0, workspaces definitions are a list of JSON lines + await getWorkspacesDefinitionsVOther(); + + const subcommands: Fig.Subcommand[] = workspacesDefinitions.map( + ({ name, location }: { name: string; location: string }) => ({ + name, + description: 'Workspaces', + args: { + name: 'script', + generators: { + cache: { + strategy: 'stale-while-revalidate', + ttl: 60_000, // 60s + }, + script: ['cat', `${location}/package.json`], + postProcess: function (out: string) { + if (out.trim() === '') { + return []; + } + try { + const packageContent = JSON.parse(out); + const scripts = packageContent['scripts']; + if (scripts) { + return Object.keys(scripts).map((script) => ({ + name: script, + })); + } + } catch (e) { } + return []; + }, + }, + }, + }) + ); + + return { + name: 'workspace', + subcommands, + }; + } catch (e) { + console.error(e); + } + return { name: 'workspaces' }; + }, + }, + { + name: 'workspaces', + description: 'Show information about your workspaces', + options: [ + { + name: 'subcommand', + description: '', + args: { + suggestions: [{ name: 'info' }, { name: 'run' }], + }, + }, + { + name: 'flags', + description: '', + }, + ], + }, + { + name: 'set', + description: 'Set global Yarn options', + subcommands: [ + { + name: 'resolution', + description: 'Enforce a package resolution', + args: [ + { + name: 'descriptor', + description: + 'A descriptor for the package, in the form of \'lodash@npm:^ 1.2.3\'', + }, + { + name: 'resolution', + description: 'The version of the package to resolve', + }, + ], + options: [ + { + name: ['-s', '--save'], + description: + 'Persist the resolution inside the top-level manifest', + }, + ], + }, + { + name: 'version', + description: 'Lock the Yarn version used by the project', + args: { + name: 'version', + description: + 'Use the specified version, which can also be a Yarn 2 build (e.g 2.0.0-rc.30) or a Yarn 1 build (e.g 1.22.1)', + template: 'filepaths', + suggestions: [ + { + name: 'from-sources', + insertValue: 'from sources', + }, + 'latest', + 'canary', + 'classic', + 'self', + ], + }, + options: [ + { + name: '--only-if-needed', + description: + 'Only lock the Yarn version if it isn\'t already locked', + }, + ], + }, + ], + }, + ], +}; + +export default completionSpec; diff --git a/code/extensions/terminal-suggest/src/constants.ts b/code/extensions/terminal-suggest/src/constants.ts index 7d877e73961..db376c2c3b4 100644 --- a/code/extensions/terminal-suggest/src/constants.ts +++ b/code/extensions/terminal-suggest/src/constants.ts @@ -113,10 +113,7 @@ export const upstreamSpecs = [ // JavaScript / TypeScript 'node', - 'npm', 'nvm', - 'pnpm', - 'yarn', 'yo', // Python diff --git a/code/extensions/terminal-suggest/src/env/pathExecutableCache.ts b/code/extensions/terminal-suggest/src/env/pathExecutableCache.ts index 6576bb59894..8fae425393b 100644 --- a/code/extensions/terminal-suggest/src/env/pathExecutableCache.ts +++ b/code/extensions/terminal-suggest/src/env/pathExecutableCache.ts @@ -5,13 +5,11 @@ import * as fs from 'fs/promises'; import * as vscode from 'vscode'; -import { isExecutable } from '../helpers/executable'; +import { isExecutable, WindowsExecutableExtensionsCache } from '../helpers/executable'; import { osIsWindows } from '../helpers/os'; import type { ICompletionResource } from '../types'; import { getFriendlyResourcePath } from '../helpers/uri'; import { SettingsIds } from '../constants'; -import * as filesystem from 'fs'; -import * as path from 'path'; import { TerminalShellType } from '../terminalSuggestMain'; const isWindows = osIsWindows(); @@ -24,7 +22,7 @@ export interface IExecutablesInPath { export class PathExecutableCache implements vscode.Disposable { private _disposables: vscode.Disposable[] = []; - private _cachedWindowsExeExtensions: { [key: string]: boolean | undefined } | undefined; + private readonly _windowsExecutableExtensionsCache: WindowsExecutableExtensionsCache | undefined; private _cachedExes: Map | undefined> = new Map(); private _inProgressRequest: { @@ -35,10 +33,10 @@ export class PathExecutableCache implements vscode.Disposable { constructor() { if (isWindows) { - this._cachedWindowsExeExtensions = vscode.workspace.getConfiguration(SettingsIds.SuggestPrefix).get(SettingsIds.CachedWindowsExecutableExtensionsSuffixOnly); + this._windowsExecutableExtensionsCache = new WindowsExecutableExtensionsCache(this._getConfiguredWindowsExecutableExtensions()); this._disposables.push(vscode.workspace.onDidChangeConfiguration(e => { if (e.affectsConfiguration(SettingsIds.CachedWindowsExecutableExtensions)) { - this._cachedWindowsExeExtensions = vscode.workspace.getConfiguration(SettingsIds.SuggestPrefix).get(SettingsIds.CachedWindowsExecutableExtensionsSuffixOnly); + this._windowsExecutableExtensionsCache?.update(this._getConfiguredWindowsExecutableExtensions()); this._cachedExes.clear(); } })); @@ -161,6 +159,7 @@ export class PathExecutableCache implements vscode.Disposable { const result = new Set(); const fileResource = vscode.Uri.file(path); const files = await vscode.workspace.fs.readDirectory(fileResource); + const windowsExecutableExtensions = this._windowsExecutableExtensionsCache?.getExtensions(); await Promise.all( files.map(([file, fileType]) => (async () => { let kind: vscode.TerminalCompletionItemKind | undefined; @@ -177,7 +176,7 @@ export class PathExecutableCache implements vscode.Disposable { if (lstat.isSymbolicLink()) { try { const symlinkRealPath = await fs.realpath(resource.fsPath); - const isExec = await isExecutable(symlinkRealPath, this._cachedWindowsExeExtensions); + const isExec = await isExecutable(symlinkRealPath, windowsExecutableExtensions); if (!isExec) { return; } @@ -199,7 +198,7 @@ export class PathExecutableCache implements vscode.Disposable { return; } - const isExec = kind === vscode.TerminalCompletionItemKind.Method || await isExecutable(formattedPath, this._cachedWindowsExeExtensions); + const isExec = kind === vscode.TerminalCompletionItemKind.Method || await isExecutable(resource.fsPath, windowsExecutableExtensions); if (!isExec) { return; } @@ -218,47 +217,9 @@ export class PathExecutableCache implements vscode.Disposable { return undefined; } } -} - -export async function watchPathDirectories(context: vscode.ExtensionContext, env: ITerminalEnvironment, pathExecutableCache: PathExecutableCache | undefined): Promise { - const pathDirectories = new Set(); - - const envPath = env.PATH; - if (envPath) { - envPath.split(path.delimiter).forEach(p => pathDirectories.add(p)); - } - - const activeWatchers = new Set(); - - // Watch each directory - for (const dir of pathDirectories) { - try { - if (activeWatchers.has(dir)) { - // Skip if already watching or directory doesn't exist - continue; - } - - const stat = await fs.stat(dir); - if (!stat.isDirectory()) { - continue; - } - const watcher = filesystem.watch(dir, { persistent: false }, () => { - if (pathExecutableCache) { - // Refresh cache when directory contents change - pathExecutableCache.refresh(dir); - } - }); - - activeWatchers.add(dir); - - context.subscriptions.push(new vscode.Disposable(() => { - try { - watcher.close(); - activeWatchers.delete(dir); - } catch { } { } - })); - } catch { } + private _getConfiguredWindowsExecutableExtensions(): { [key: string]: boolean | undefined } | undefined { + return vscode.workspace.getConfiguration(SettingsIds.SuggestPrefix).get(SettingsIds.CachedWindowsExecutableExtensionsSuffixOnly); } } diff --git a/code/extensions/terminal-suggest/src/fig/figInterface.ts b/code/extensions/terminal-suggest/src/fig/figInterface.ts index 35a8e12747b..0f1fcc20d6d 100644 --- a/code/extensions/terminal-suggest/src/fig/figInterface.ts +++ b/code/extensions/terminal-suggest/src/fig/figInterface.ts @@ -20,8 +20,8 @@ import { asArray, availableSpecs } from '../terminalSuggestMain'; import { IFigExecuteExternals } from './execute'; export interface IFigSpecSuggestionsResult { - filesRequested: boolean; - foldersRequested: boolean; + showFiles: boolean; + showDirectories: boolean; fileExtensions?: string[]; hasCurrentArg: boolean; items: vscode.TerminalCompletionItem[]; @@ -29,7 +29,7 @@ export interface IFigSpecSuggestionsResult { export async function getFigSuggestions( specs: Fig.Spec[], - terminalContext: { commandLine: string; cursorPosition: number }, + terminalContext: { commandLine: string; cursorIndex: number }, availableCommands: ICompletionResource[], currentCommandAndArgString: string, tokenType: TokenType, @@ -40,8 +40,8 @@ export async function getFigSuggestions( token?: vscode.CancellationToken, ): Promise { const result: IFigSpecSuggestionsResult = { - filesRequested: false, - foldersRequested: false, + showFiles: false, + showDirectories: false, hasCurrentArg: false, items: [], }; @@ -75,7 +75,7 @@ export async function getFigSuggestions( if (availableCommand.kind !== vscode.TerminalCompletionItemKind.Alias) { const description = getFixSuggestionDescription(spec); result.items.push(createCompletionItem( - terminalContext.cursorPosition, + terminalContext.cursorIndex, currentCommandAndArgString, { label: { label: specLabel, description }, @@ -106,8 +106,8 @@ export async function getFigSuggestions( const completionItemResult = await getFigSpecSuggestions(actualSpec, terminalContext, currentCommandAndArgString, shellIntegrationCwd, env, name, executeExternals, token); result.hasCurrentArg ||= !!completionItemResult?.hasCurrentArg; if (completionItemResult) { - result.filesRequested ||= completionItemResult.filesRequested; - result.foldersRequested ||= completionItemResult.foldersRequested; + result.showFiles ||= completionItemResult.showFiles; + result.showDirectories ||= completionItemResult.showDirectories; result.fileExtensions ||= completionItemResult.fileExtensions; if (completionItemResult.items) { result.items = result.items.concat(completionItemResult.items); @@ -120,7 +120,7 @@ export async function getFigSuggestions( async function getFigSpecSuggestions( spec: Fig.Spec, - terminalContext: { commandLine: string; cursorPosition: number }, + terminalContext: { commandLine: string; cursorIndex: number }, prefix: string, shellIntegrationCwd: vscode.Uri | undefined, env: Record, @@ -128,11 +128,11 @@ async function getFigSpecSuggestions( executeExternals: IFigExecuteExternals, token?: vscode.CancellationToken, ): Promise { - let filesRequested = false; - let foldersRequested = false; + let showFiles = false; + let showDirectories = false; let fileExtensions: string[] | undefined; - const command = getCommand(terminalContext.commandLine, {}, terminalContext.cursorPosition); + const command = getCommand(terminalContext.commandLine, {}, terminalContext.cursorIndex); if (!command || !shellIntegrationCwd) { return; } @@ -153,14 +153,14 @@ async function getFigSpecSuggestions( } if (completionItemResult) { - filesRequested = completionItemResult.filesRequested; - foldersRequested = completionItemResult.foldersRequested; + showFiles = completionItemResult.showFiles; + showDirectories = completionItemResult.showDirectories; fileExtensions = completionItemResult.fileExtensions; } return { - filesRequested, - foldersRequested, + showFiles: showFiles, + showDirectories: showDirectories, fileExtensions, hasCurrentArg: !!parsedArguments.currentArg, items, @@ -173,14 +173,14 @@ export async function collectCompletionItemResult( command: Command, parsedArguments: ArgumentParserResult, prefix: string, - terminalContext: { commandLine: string; cursorPosition: number }, + terminalContext: { commandLine: string; cursorIndex: number }, shellIntegrationCwd: vscode.Uri | undefined, env: Record, items: vscode.TerminalCompletionItem[], executeExternals: IFigExecuteExternals -): Promise<{ filesRequested: boolean; foldersRequested: boolean; fileExtensions: string[] | undefined } | undefined> { - let filesRequested = false; - let foldersRequested = false; +): Promise<{ showFiles: boolean; showDirectories: boolean; fileExtensions: string[] | undefined } | undefined> { + let showFiles = false; + let showDirectories = false; let fileExtensions: string[] | undefined; const addSuggestions = async (specArgs: SpecArg[] | Record | undefined, kind: vscode.TerminalCompletionItemKind, parsedArguments?: ArgumentParserResult) => { @@ -188,7 +188,7 @@ export async function collectCompletionItemResult( const generators = parsedArguments.currentArg.generators; const initialFigState: FigState = { buffer: terminalContext.commandLine, - cursorLocation: terminalContext.cursorPosition, + cursorLocation: terminalContext.cursorIndex, cwd: shellIntegrationCwd?.fsPath ?? null, processUserIsIn: null, sshContextString: null, @@ -222,12 +222,12 @@ export async function collectCompletionItemResult( for (const generatorResult of generatorResults) { for (const item of (await generatorResult?.request) ?? []) { if (item.type === 'file') { - filesRequested = true; - foldersRequested = true; + showFiles = true; + showDirectories = true; fileExtensions = item._internal?.fileExtensions as string[] | undefined; } if (item.type === 'folder') { - foldersRequested = true; + showDirectories = true; } if (!item.name) { @@ -239,7 +239,7 @@ export async function collectCompletionItemResult( } for (const label of suggestionLabels) { items.push(createCompletionItem( - terminalContext.cursorPosition, + terminalContext.cursorIndex, prefix, { label }, item.displayName, @@ -256,16 +256,16 @@ export async function collectCompletionItemResult( const templates = Array.isArray(generator.template) ? generator.template : [generator.template]; for (const template of templates) { if (template === 'filepaths') { - filesRequested = true; + showFiles = true; } else if (template === 'folders') { - foldersRequested = true; + showDirectories = true; } } } } } if (!specArgs) { - return { filesRequested, foldersRequested }; + return { showFiles, showDirectories }; } const flagsToExclude = kind === vscode.TerminalCompletionItemKind.Flag ? parsedArguments?.passedOptions.map(option => option.name).flat() : undefined; @@ -304,7 +304,7 @@ export async function collectCompletionItemResult( items.push( createCompletionItem( - terminalContext.cursorPosition, + terminalContext.cursorIndex, prefix, { label: detail ? { label, detail } : label @@ -341,9 +341,10 @@ export async function collectCompletionItemResult( } if (parsedArguments.suggestionFlags & SuggestionFlag.Options) { await addSuggestions(parsedArguments.completionObj.options, vscode.TerminalCompletionItemKind.Flag, parsedArguments); + await addSuggestions(parsedArguments.completionObj.persistentOptions, vscode.TerminalCompletionItemKind.Flag, parsedArguments); } - return { filesRequested, foldersRequested, fileExtensions }; + return { showFiles, showDirectories, fileExtensions }; } function convertEnvRecordToArray(env: Record): EnvironmentVariable[] { @@ -372,11 +373,11 @@ export function getFigSuggestionLabel(spec: Fig.Spec | Fig.Arg | Fig.Suggestion function convertIconToKind(icon: string | undefined): vscode.TerminalCompletionItemKind | undefined { switch (icon) { - case 'vscode://icon?type=10': return vscode.TerminalCompletionItemKind.Commit; - case 'vscode://icon?type=11': return vscode.TerminalCompletionItemKind.Branch; - case 'vscode://icon?type=12': return vscode.TerminalCompletionItemKind.Tag; - case 'vscode://icon?type=13': return vscode.TerminalCompletionItemKind.Stash; - case 'vscode://icon?type=14': return vscode.TerminalCompletionItemKind.Remote; + case 'vscode://icon?type=10': return vscode.TerminalCompletionItemKind.ScmCommit; + case 'vscode://icon?type=11': return vscode.TerminalCompletionItemKind.ScmBranch; + case 'vscode://icon?type=12': return vscode.TerminalCompletionItemKind.ScmTag; + case 'vscode://icon?type=13': return vscode.TerminalCompletionItemKind.ScmStash; + case 'vscode://icon?type=14': return vscode.TerminalCompletionItemKind.ScmRemote; case 'vscode://icon?type=15': return vscode.TerminalCompletionItemKind.PullRequest; case 'vscode://icon?type=16': return vscode.TerminalCompletionItemKind.PullRequestDone; default: return undefined; diff --git a/code/extensions/terminal-suggest/src/fig/shell-parser/test/command.test.ts b/code/extensions/terminal-suggest/src/fig/shell-parser/test/command.test.ts index cc050512374..46d22dd6533 100644 --- a/code/extensions/terminal-suggest/src/fig/shell-parser/test/command.test.ts +++ b/code/extensions/terminal-suggest/src/fig/shell-parser/test/command.test.ts @@ -4,35 +4,35 @@ *--------------------------------------------------------------------------------------------*/ import { deepStrictEqual } from 'node:assert'; -import { getCommand, Command } from "../command"; +import { getCommand, Command } from '../command'; -suite("fig/shell-parser/ getCommand", () => { +suite('fig/shell-parser/ getCommand', () => { const aliases = { - woman: "man", - quote: "'q'", - g: "git", + woman: 'man', + quote: `'q'`, + g: 'git', }; const getTokenText = (command: Command | null) => command?.tokens.map((token) => token.text) ?? []; - test("works without matching aliases", () => { - deepStrictEqual(getTokenText(getCommand("git co ", {})), ["git", "co", ""]); - deepStrictEqual(getTokenText(getCommand("git co ", aliases)), ["git", "co", ""]); - deepStrictEqual(getTokenText(getCommand("woman ", {})), ["woman", ""]); - deepStrictEqual(getTokenText(getCommand("another string ", aliases)), [ - "another", - "string", - "", + test('works without matching aliases', () => { + deepStrictEqual(getTokenText(getCommand('git co ', {})), ['git', 'co', '']); + deepStrictEqual(getTokenText(getCommand('git co ', aliases)), ['git', 'co', '']); + deepStrictEqual(getTokenText(getCommand('woman ', {})), ['woman', '']); + deepStrictEqual(getTokenText(getCommand('another string ', aliases)), [ + 'another', + 'string', + '', ]); }); - test("works with regular aliases", () => { + test('works with regular aliases', () => { // Don't change a single token. - deepStrictEqual(getTokenText(getCommand("woman", aliases)), ["woman"]); + deepStrictEqual(getTokenText(getCommand('woman', aliases)), ['woman']); // Change first token if length > 1. - deepStrictEqual(getTokenText(getCommand("woman ", aliases)), ["man", ""]); + deepStrictEqual(getTokenText(getCommand('woman ', aliases)), ['man', '']); // Don't change later tokens. - deepStrictEqual(getTokenText(getCommand("man woman ", aliases)), ["man", "woman", ""]); + deepStrictEqual(getTokenText(getCommand('man woman ', aliases)), ['man', 'woman', '']); // Handle quotes - deepStrictEqual(getTokenText(getCommand("quote ", aliases)), ["q", ""]); + deepStrictEqual(getTokenText(getCommand('quote ', aliases)), ['q', '']); }); }); diff --git a/code/extensions/terminal-suggest/src/helpers/completionItem.ts b/code/extensions/terminal-suggest/src/helpers/completionItem.ts index 7cb174223c1..b5dba9a2874 100644 --- a/code/extensions/terminal-suggest/src/helpers/completionItem.ts +++ b/code/extensions/terminal-suggest/src/helpers/completionItem.ts @@ -13,8 +13,7 @@ export function createCompletionItem(cursorPosition: number, currentCommandStrin label: commandResource.label, detail: detail ?? commandResource.detail ?? '', documentation, - replacementIndex: cursorPosition - lastWord.length, - replacementLength: lastWord.length, + replacementRange: [cursorPosition - lastWord.length, cursorPosition], kind: kind ?? commandResource.kind ?? vscode.TerminalCompletionItemKind.Method }; } diff --git a/code/extensions/terminal-suggest/src/helpers/executable.ts b/code/extensions/terminal-suggest/src/helpers/executable.ts index 00f56f09cbd..7cd854c8ba4 100644 --- a/code/extensions/terminal-suggest/src/helpers/executable.ts +++ b/code/extensions/terminal-suggest/src/helpers/executable.ts @@ -6,10 +6,10 @@ import { osIsWindows } from './os'; import * as fs from 'fs/promises'; -export function isExecutable(filePath: string, configuredWindowsExecutableExtensions?: { [key: string]: boolean | undefined } | undefined): Promise | boolean { +export function isExecutable(filePath: string, windowsExecutableExtensions?: Set): Promise | boolean { if (osIsWindows()) { - const resolvedWindowsExecutableExtensions = resolveWindowsExecutableExtensions(configuredWindowsExecutableExtensions); - return resolvedWindowsExecutableExtensions.find(ext => filePath.endsWith(ext)) !== undefined; + const extensions = windowsExecutableExtensions ?? defaultWindowsExecutableExtensionsSet; + return hasWindowsExecutableExtension(filePath, extensions); } return isExecutableUnix(filePath); } @@ -25,22 +25,6 @@ export async function isExecutableUnix(filePath: string): Promise { } } - -function resolveWindowsExecutableExtensions(configuredWindowsExecutableExtensions?: { [key: string]: boolean | undefined }): string[] { - const resolvedWindowsExecutableExtensions: string[] = windowsDefaultExecutableExtensions; - const excluded = new Set(); - if (configuredWindowsExecutableExtensions) { - for (const [key, value] of Object.entries(configuredWindowsExecutableExtensions)) { - if (value === true) { - resolvedWindowsExecutableExtensions.push(key); - } else { - excluded.add(key); - } - } - } - return Array.from(new Set(resolvedWindowsExecutableExtensions)).filter(ext => !excluded.has(ext)); -} - export const windowsDefaultExecutableExtensions: string[] = [ '.exe', // Executable file '.bat', // Batch file @@ -59,3 +43,65 @@ export const windowsDefaultExecutableExtensions: string[] = [ '.pl', // Perl script (requires Perl interpreter) '.sh', // Shell script (via WSL or third-party tools) ]; + +const defaultWindowsExecutableExtensionsSet = new Set(); +for (const ext of windowsDefaultExecutableExtensions) { + defaultWindowsExecutableExtensionsSet.add(ext); +} + +export class WindowsExecutableExtensionsCache { + private _rawConfig: { [key: string]: boolean | undefined } | undefined; + private _cachedExtensions: Set | undefined; + + constructor(rawConfig?: { [key: string]: boolean | undefined }) { + this._rawConfig = rawConfig; + } + + update(rawConfig: { [key: string]: boolean | undefined } | undefined): void { + this._rawConfig = rawConfig; + this._cachedExtensions = undefined; + } + + getExtensions(): Set { + if (!this._cachedExtensions) { + this._cachedExtensions = resolveWindowsExecutableExtensions(this._rawConfig); + } + return this._cachedExtensions; + } +} + +function hasWindowsExecutableExtension(filePath: string, extensions: Set): boolean { + const fileName = filePath.slice(Math.max(filePath.lastIndexOf('\\'), filePath.lastIndexOf('/')) + 1); + for (const ext of extensions) { + if (fileName.endsWith(ext)) { + return true; + } + } + return false; +} + +function resolveWindowsExecutableExtensions(configuredWindowsExecutableExtensions?: { [key: string]: boolean | undefined }): Set { + const extensions = new Set(); + const configured = configuredWindowsExecutableExtensions ?? {}; + const excluded = new Set(); + + for (const [ext, value] of Object.entries(configured)) { + if (value !== true) { + excluded.add(ext); + } + } + + for (const ext of windowsDefaultExecutableExtensions) { + if (!excluded.has(ext)) { + extensions.add(ext); + } + } + + for (const [ext, value] of Object.entries(configured)) { + if (value === true) { + extensions.add(ext); + } + } + + return extensions; +} diff --git a/code/extensions/terminal-suggest/src/shell/pwsh.ts b/code/extensions/terminal-suggest/src/shell/pwsh.ts index 8926f72e516..5896309978f 100644 --- a/code/extensions/terminal-suggest/src/shell/pwsh.ts +++ b/code/extensions/terminal-suggest/src/shell/pwsh.ts @@ -66,33 +66,38 @@ async function getAliases(options: ExecOptionsWithStringEncoding, existingComman console.error('Error parsing output:', e); return []; } - return (json as any[]).map(e => { - // Aliases sometimes use the same Name and DisplayName, show them as methods in this case. - const isAlias = e.Name !== e.DisplayName; - const detailParts: string[] = []; - if (e.Definition) { - detailParts.push(e.Definition); - } - if (e.ModuleName && e.Version) { - detailParts.push(`${e.ModuleName} v${e.Version}`); - } - let definitionCommand = undefined; - if (e.Definition) { - let definitionIndex = e.Definition.indexOf(' '); - if (definitionIndex === -1) { - definitionIndex = e.Definition.length; - definitionCommand = e.Definition.substring(0, definitionIndex); + if (!Array.isArray(json)) { + return []; + } + return (json as unknown[]) + .filter(isPwshGetCommandEntry) + .map(e => { + // Aliases sometimes use the same Name and DisplayName, show them as methods in this case. + const isAlias = e.Name !== e.DisplayName; + const detailParts: string[] = []; + if (e.Definition) { + detailParts.push(e.Definition); } - } - return { - label: e.Name, - detail: detailParts.join('\n\n'), - kind: (isAlias - ? vscode.TerminalCompletionItemKind.Alias - : vscode.TerminalCompletionItemKind.Method), - definitionCommand, - }; - }); + if (e.ModuleName && e.Version) { + detailParts.push(`${e.ModuleName} v${e.Version}`); + } + let definitionCommand = undefined; + if (e.Definition) { + let definitionIndex = e.Definition.indexOf(' '); + if (definitionIndex === -1) { + definitionIndex = e.Definition.length; + definitionCommand = e.Definition.substring(0, definitionIndex); + } + } + return { + label: e.Name, + detail: detailParts.join('\n\n'), + kind: (isAlias + ? vscode.TerminalCompletionItemKind.Alias + : vscode.TerminalCompletionItemKind.Method), + definitionCommand, + }; + }); } async function getCommands(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise { @@ -100,15 +105,19 @@ async function getCommands(options: ExecOptionsWithStringEncoding, existingComma ...options, maxBuffer: 1024 * 1024 * 100 // This is a lot of content, increase buffer size }); - let json: any; + let json: unknown; try { json = JSON.parse(output); } catch (e) { console.error('Error parsing pwsh output:', e); return []; } + if (!Array.isArray(json)) { + return []; + } return ( - (json as any[]) + (json as unknown[]) + .filter(isPwshGetCommandEntry) .filter(e => e.CommandType !== PwshCommandType.Alias) .map(e => { const detailParts: string[] = []; @@ -126,3 +135,39 @@ async function getCommands(options: ExecOptionsWithStringEncoding, existingComma }) ); } + +interface IPwshGetCommandEntry { + Name: string; + CommandType: PwshCommandType; + DisplayName?: string | null; + Definition?: string | null; + ModuleName?: string | null; + Version?: string | null; +} + +function isPwshGetCommandEntry(entry: unknown): entry is IPwshGetCommandEntry { + return ( + isObject(entry) && + 'Name' in entry && typeof entry.Name === 'string' && + 'CommandType' in entry && typeof entry.CommandType === 'number' && + (!('DisplayName' in entry) || typeof entry.DisplayName === 'string' || entry.DisplayName === null) && + (!('Definition' in entry) || typeof entry.Definition === 'string' || entry.Definition === null) && + (!('ModuleName' in entry) || typeof entry.ModuleName === 'string' || entry.ModuleName === null) && + (!('Version' in entry) || typeof entry.Version === 'string' || entry.Version === null) + ); +} + +/** + * @returns whether the provided parameter is of type `object` but **not** + * `null`, an `array`, a `regexp`, nor a `date`. + */ +export function isObject(obj: unknown): obj is Object { + // The method can't do a type cast since there are type (like strings) which + // are subclasses of any put not positvely matched by the function. Hence type + // narrowing results in wrong results. + return typeof obj === 'object' + && obj !== null + && !Array.isArray(obj) + && !(obj instanceof RegExp) + && !(obj instanceof Date); +} diff --git a/code/extensions/terminal-suggest/src/terminalSuggestMain.ts b/code/extensions/terminal-suggest/src/terminalSuggestMain.ts index f105a135e29..95654ffe418 100644 --- a/code/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/code/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -4,18 +4,25 @@ *--------------------------------------------------------------------------------------------*/ import { ExecOptionsWithStringEncoding } from 'child_process'; +import * as fs from 'fs'; +import { basename, delimiter } from 'path'; import * as vscode from 'vscode'; +import azdSpec from './completions/azd'; import cdSpec from './completions/cd'; import codeCompletionSpec from './completions/code'; import codeInsidersCompletionSpec from './completions/code-insiders'; import codeTunnelCompletionSpec from './completions/code-tunnel'; import codeTunnelInsidersCompletionSpec from './completions/code-tunnel-insiders'; +import copilotSpec from './completions/copilot'; import gitCompletionSpec from './completions/git'; import ghCompletionSpec from './completions/gh'; +import npmCompletionSpec from './completions/npm'; import npxCompletionSpec from './completions/npx'; +import pnpmCompletionSpec from './completions/pnpm'; import setLocationSpec from './completions/set-location'; +import yarnCompletionSpec from './completions/yarn'; import { upstreamSpecs } from './constants'; -import { ITerminalEnvironment, PathExecutableCache, watchPathDirectories } from './env/pathExecutableCache'; +import { ITerminalEnvironment, PathExecutableCache } from './env/pathExecutableCache'; import { executeCommand, executeCommandTimeout, IFigExecuteExternals } from './fig/execute'; import { getFigSuggestions } from './fig/figInterface'; import { createCompletionItem } from './helpers/completionItem'; @@ -28,8 +35,6 @@ import { getPwshGlobals } from './shell/pwsh'; import { getZshGlobals } from './shell/zsh'; import { defaultShellTypeResetChars, getTokenType, shellTypeResetChars, TokenType } from './tokens'; import type { ICompletionResource } from './types'; -import { basename } from 'path'; - export const enum TerminalShellType { Bash = 'bash', Fish = 'fish', @@ -58,15 +63,20 @@ function getCacheKey(machineId: string, remoteAuthority: string | undefined, she } export const availableSpecs: Fig.Spec[] = [ + azdSpec, cdSpec, codeInsidersCompletionSpec, codeCompletionSpec, codeTunnelCompletionSpec, codeTunnelInsidersCompletionSpec, + copilotSpec, gitCompletionSpec, ghCompletionSpec, + npmCompletionSpec, npxCompletionSpec, + pnpmCompletionSpec, setLocationSpec, + yarnCompletionSpec, ]; for (const spec of upstreamSpecs) { availableSpecs.push(require(`./completions/upstream/${spec}`).default); @@ -251,7 +261,6 @@ export async function activate(context: vscode.ExtensionContext) { const remoteAuthority = vscode.env.remoteName; context.subscriptions.push(vscode.window.registerTerminalCompletionProvider({ - id: 'terminal-suggest', async provideTerminalCompletions(terminal: vscode.Terminal, terminalContext: vscode.TerminalCompletionContext, token: vscode.CancellationToken): Promise { currentTerminalEnv = terminal.shellIntegration?.env?.value ?? process.env; if (token.isCancellationRequested) { @@ -275,7 +284,7 @@ export async function activate(context: vscode.ExtensionContext) { } // Order is important here, add shell globals first so they are prioritized over path commands const commands = [...shellGlobals, ...commandsInPath.completionResources]; - const currentCommandString = getCurrentCommandAndArgs(terminalContext.commandLine, terminalContext.cursorPosition, terminalShellType); + const currentCommandString = getCurrentCommandAndArgs(terminalContext.commandLine, terminalContext.cursorIndex, terminalShellType); const pathSeparator = isWindows ? '\\' : '/'; const tokenType = getTokenType(terminalContext, terminalShellType); const result = await Promise.race([ @@ -305,26 +314,76 @@ export async function activate(context: vscode.ExtensionContext) { } } - - if (terminal.shellIntegration?.cwd && (result.filesRequested || result.foldersRequested)) { + const cwd = result.cwd ?? terminal.shellIntegration?.cwd; + if (cwd && (result.showFiles || result.showDirectories)) { + const globPattern = createFileGlobPattern(result.fileExtensions); return new vscode.TerminalCompletionList(result.items, { - filesRequested: result.filesRequested, - foldersRequested: result.foldersRequested, - fileExtensions: result.fileExtensions, - cwd: result.cwd ?? terminal.shellIntegration.cwd, - env: terminal.shellIntegration?.env?.value, + showFiles: result.showFiles, + showDirectories: result.showDirectories, + globPattern, + cwd, }); } return result.items; } }, '/', '\\')); - await watchPathDirectories(context, currentTerminalEnv, pathExecutableCache); + watchPathDirectories(context, currentTerminalEnv, pathExecutableCache); context.subscriptions.push(vscode.commands.registerCommand('terminal.integrated.suggest.clearCachedGlobals', () => { cachedGlobals.clear(); })); } +async function watchPathDirectories(context: vscode.ExtensionContext, env: ITerminalEnvironment, pathExecutableCache: PathExecutableCache | undefined): Promise { + const pathDirectories = new Set(); + + const envPath = env.PATH; + if (envPath) { + envPath.split(delimiter).forEach(p => pathDirectories.add(p)); + } + + const activeWatchers = new Set(); + + let debounceTimer: NodeJS.Timeout | undefined; // debounce in case many file events fire at once + function handleChange() { + if (debounceTimer) { + clearTimeout(debounceTimer); + } + debounceTimer = setTimeout(() => { + pathExecutableCache?.refresh(); + debounceTimer = undefined; + }, 300); + } + + // Watch each directory + for (const dir of pathDirectories) { + if (activeWatchers.has(dir)) { + // Skip if already watching this directory + continue; + } + + try { + const stat = await fs.promises.stat(dir); + if (!stat.isDirectory()) { + continue; + } + } catch { + // File not found + continue; + } + + const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(vscode.Uri.file(dir), '*')); + context.subscriptions.push( + watcher, + watcher.onDidCreate(() => handleChange()), + watcher.onDidChange(() => handleChange()), + watcher.onDidDelete(() => handleChange()) + ); + + activeWatchers.add(dir); + } +} + /** * Adjusts the current working directory based on a given current command string if it is a folder. * @param currentCommandString - The current command string, which might contain a folder path prefix. @@ -371,7 +430,7 @@ export async function resolveCwdFromCurrentCommandString(currentCommandString: s // Retrurns the string that represents the current command and its arguments up to the cursor position. // Uses shell specific separators to determine the current command and its arguments. -export function getCurrentCommandAndArgs(commandLine: string, cursorPosition: number, shellType: TerminalShellType | undefined): string { +export function getCurrentCommandAndArgs(commandLine: string, cursorIndex: number, shellType: TerminalShellType | undefined): string { // Return an empty string if the command line is empty after trimming if (commandLine.trim() === '') { @@ -379,12 +438,12 @@ export function getCurrentCommandAndArgs(commandLine: string, cursorPosition: nu } // Check if cursor is not at the end and there's non-whitespace after the cursor - if (cursorPosition < commandLine.length && /\S/.test(commandLine[cursorPosition])) { + if (cursorIndex < commandLine.length && /\S/.test(commandLine[cursorIndex])) { return ''; } // Extract the part of the line up to the cursor position - const beforeCursor = commandLine.slice(0, cursorPosition); + const beforeCursor = commandLine.slice(0, cursorIndex); const resetChars = shellType ? shellTypeResetChars.get(shellType) ?? defaultShellTypeResetChars : defaultShellTypeResetChars; // Find the last reset character before the cursor @@ -420,10 +479,10 @@ export async function getCompletionItemsFromSpecs( name: string, token?: vscode.CancellationToken, executeExternals?: IFigExecuteExternals, -): Promise<{ items: vscode.TerminalCompletionItem[]; filesRequested: boolean; foldersRequested: boolean; fileExtensions?: string[]; cwd?: vscode.Uri }> { +): Promise<{ items: vscode.TerminalCompletionItem[]; showFiles: boolean; showDirectories: boolean; fileExtensions?: string[]; cwd?: vscode.Uri }> { let items: vscode.TerminalCompletionItem[] = []; - let filesRequested = false; - let foldersRequested = false; + let showFiles = false; + let showDirectories = false; let hasCurrentArg = false; let fileExtensions: string[] | undefined; @@ -456,8 +515,8 @@ export async function getCompletionItemsFromSpecs( const result = await getFigSuggestions(specs, terminalContext, availableCommands, currentCommandString, tokenType, shellIntegrationCwd, env, name, executeExternalsWithFallback, token); if (result) { hasCurrentArg ||= result.hasCurrentArg; - filesRequested ||= result.filesRequested; - foldersRequested ||= result.foldersRequested; + showFiles ||= result.showFiles; + showDirectories ||= result.showDirectories; fileExtensions = result.fileExtensions; if (result.items) { items = items.concat(result.items); @@ -473,7 +532,7 @@ export async function getCompletionItemsFromSpecs( const labelWithoutExtension = isWindows ? commandTextLabel.replace(/\.[^ ]+$/, '') : commandTextLabel; if (!labels.has(labelWithoutExtension)) { items.push(createCompletionItem( - terminalContext.cursorPosition, + terminalContext.cursorIndex, currentCommandString, command, command.detail, @@ -492,23 +551,19 @@ export async function getCompletionItemsFromSpecs( existingItem.detail ??= command.detail; } } - filesRequested = true; - foldersRequested = true; - } - // For arguments when no fig suggestions are found these are fallback suggestions - else if (!items.length && !filesRequested && !foldersRequested && !hasCurrentArg) { - if (terminalContext.allowFallbackCompletions) { - filesRequested = true; - foldersRequested = true; - } + showFiles = true; + showDirectories = true; + } else if (!items.length && !showFiles && !showDirectories && !hasCurrentArg) { + showFiles = true; + showDirectories = true; } let cwd: vscode.Uri | undefined; - if (shellIntegrationCwd && (filesRequested || foldersRequested)) { + if (shellIntegrationCwd && (showFiles || showDirectories)) { cwd = await resolveCwdFromCurrentCommandString(currentCommandString, shellIntegrationCwd); } - return { items, filesRequested, foldersRequested, fileExtensions, cwd }; + return { items, showFiles, showDirectories, fileExtensions, cwd }; } function getEnvAsRecord(shellIntegrationEnv: ITerminalEnvironment): Record { @@ -565,3 +620,13 @@ export function sanitizeProcessEnvironment(env: Record, ...prese }); } +function createFileGlobPattern(fileExtensions?: string[]): string | undefined { + if (!fileExtensions || fileExtensions.length === 0) { + return undefined; + } + const exts = fileExtensions.map(ext => ext.startsWith('.') ? ext.slice(1) : ext); + if (exts.length === 1) { + return `**/*.${exts[0]}`; + } + return `**/*.{${exts.join(',')}}`; +} diff --git a/code/extensions/terminal-suggest/src/test/completions/cd.test.ts b/code/extensions/terminal-suggest/src/test/completions/cd.test.ts index 82d8c08d8a7..a40d5ee2103 100644 --- a/code/extensions/terminal-suggest/src/test/completions/cd.test.ts +++ b/code/extensions/terminal-suggest/src/test/completions/cd.test.ts @@ -8,7 +8,7 @@ import cdSpec from '../../completions/cd'; import { testPaths, type ISuiteSpec } from '../helpers'; const expectedCompletions = ['-']; -const cdExpectedCompletions = [{ label: 'cd', description: (cdSpec as any).description }]; +const cdExpectedCompletions = [{ label: 'cd', description: (cdSpec as Fig.Subcommand).description }]; export const cdTestSuiteSpec: ISuiteSpec = { name: 'cd', completionSpecs: cdSpec, diff --git a/code/extensions/terminal-suggest/src/test/completions/code.test.ts b/code/extensions/terminal-suggest/src/test/completions/code.test.ts index 9022c0c5a3e..316a46e4f57 100644 --- a/code/extensions/terminal-suggest/src/test/completions/code.test.ts +++ b/code/extensions/terminal-suggest/src/test/completions/code.test.ts @@ -53,6 +53,7 @@ export const codeSpecOptionsAndSubcommands = [ '--status', '--sync ', '--telemetry', + '--transient', '--uninstall-extension ', '--update-extensions', '--user-data-dir ', @@ -76,7 +77,7 @@ export function createCodeTestSpecs(executable: string): ITestSpec[] { const typingTests: ITestSpec[] = []; for (let i = 1; i < executable.length; i++) { - const expectedCompletions = [{ label: executable, description: executable === codeCompletionSpec.name ? (codeCompletionSpec as any).description : (codeInsidersCompletionSpec as any).description }]; + const expectedCompletions = [{ label: executable, description: executable === codeCompletionSpec.name ? (codeCompletionSpec as Fig.Subcommand).description : (codeInsidersCompletionSpec as Fig.Subcommand).description }]; const input = `${executable.slice(0, i)}|`; typingTests.push({ input, expectedCompletions, expectedResourceRequests: input.endsWith(' ') ? undefined : { type: 'both', cwd: testPaths.cwd } }); } @@ -167,6 +168,7 @@ export function createCodeTunnelTestSpecs(executable: string): ITestSpec[] { '--status', '--sync ', '--telemetry', + '--transient', '--uninstall-extension ', '--update-extensions', '--use-version []', @@ -263,7 +265,7 @@ export function createCodeTunnelTestSpecs(executable: string): ITestSpec[] { const typingTests: ITestSpec[] = []; for (let i = 1; i < executable.length; i++) { - const expectedCompletions = [{ label: executable, description: executable === codeCompletionSpec.name || executable === codeTunnelCompletionSpec.name ? (codeCompletionSpec as any).description : (codeInsidersCompletionSpec as any).description }]; + const expectedCompletions = [{ label: executable, description: executable === codeCompletionSpec.name || executable === codeTunnelCompletionSpec.name ? (codeCompletionSpec as Fig.Subcommand).description : (codeInsidersCompletionSpec as Fig.Subcommand).description }]; const input = `${executable.slice(0, i)}|`; typingTests.push({ input, expectedCompletions, expectedResourceRequests: input.endsWith(' ') ? undefined : { type: 'both', cwd: testPaths.cwd } }); } diff --git a/code/extensions/terminal-suggest/src/test/completions/git-branch.test.ts b/code/extensions/terminal-suggest/src/test/completions/git-branch.test.ts new file mode 100644 index 00000000000..20d140c3e7c --- /dev/null +++ b/code/extensions/terminal-suggest/src/test/completions/git-branch.test.ts @@ -0,0 +1,89 @@ +/*--------------------------------------------------------------------------------------------- + * 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 'mocha'; +import * as vscode from 'vscode'; +import { gitGenerators } from '../../completions/git'; + +suite('Git Branch Completions', () => { + test('postProcessBranches should parse git for-each-ref output with commit details', () => { + const input = `main|John Doe|abc1234|Fix response codeblock in debug view|2 days ago +feature/test|Jane Smith|def5678|Add new feature|1 week ago`; + + const result = gitGenerators.localBranches.postProcess!(input, []); + + assert.ok(result); + assert.strictEqual(result.length, 2); + assert.ok(result[0]); + assert.strictEqual(result[0].name, 'main'); + assert.strictEqual(result[0].description, '2 days ago • John Doe • abc1234 • Fix response codeblock in debug view'); + assert.strictEqual(result[0].icon, `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmBranch}`); + + assert.ok(result[1]); + assert.strictEqual(result[1].name, 'feature/test'); + assert.strictEqual(result[1].description, '1 week ago • Jane Smith • def5678 • Add new feature'); + assert.strictEqual(result[1].icon, `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmBranch}`); + }); + + test('postProcessBranches should handle remote branches', () => { + const input = `remotes/origin/main|John Doe|abc1234|Fix bug|2 days ago +remotes/origin/feature|Jane Smith|def5678|Add feature|1 week ago`; + + const result = gitGenerators.remoteLocalBranches.postProcess!(input, []); + + assert.ok(result); + assert.strictEqual(result.length, 2); + assert.ok(result[0]); + assert.strictEqual(result[0].name, 'main'); + assert.strictEqual(result[0].description, '2 days ago • John Doe • abc1234 • Fix bug'); + + assert.ok(result[1]); + assert.strictEqual(result[1].name, 'feature'); + assert.strictEqual(result[1].description, '1 week ago • Jane Smith • def5678 • Add feature'); + }); + + test('postProcessBranches should filter out HEAD branches', () => { + const input = `main|John Doe|abc1234|Fix bug|2 days ago +HEAD -> main|John Doe|abc1234|Fix bug|2 days ago`; + + const result = gitGenerators.localBranches.postProcess!(input, []); + + assert.ok(result); + assert.strictEqual(result.length, 1); + assert.ok(result[0]); + assert.strictEqual(result[0].name, 'main'); + }); + + test('postProcessBranches should handle empty input', () => { + const input = ''; + + const result = gitGenerators.localBranches.postProcess!(input, []); + + assert.ok(result); + assert.strictEqual(result.length, 0); + }); + + test('postProcessBranches should handle git error output', () => { + const input = 'fatal: not a git repository'; + + const result = gitGenerators.localBranches.postProcess!(input, []); + + assert.ok(result); + assert.strictEqual(result.length, 0); + }); + + test('postProcessBranches should deduplicate branches', () => { + const input = `main|John Doe|abc1234|Fix bug|2 days ago +main|John Doe|abc1234|Fix bug|2 days ago`; + + const result = gitGenerators.localBranches.postProcess!(input, []); + + assert.ok(result); + assert.strictEqual(result.length, 1); + assert.ok(result[0]); + assert.strictEqual(result[0].name, 'main'); + }); +}); diff --git a/code/extensions/terminal-suggest/src/test/completions/upstream/echo.test.ts b/code/extensions/terminal-suggest/src/test/completions/upstream/echo.test.ts index 1792d4f90e9..5ba2a611bc0 100644 --- a/code/extensions/terminal-suggest/src/test/completions/upstream/echo.test.ts +++ b/code/extensions/terminal-suggest/src/test/completions/upstream/echo.test.ts @@ -12,7 +12,7 @@ const allOptions = [ '-e', '-n', ]; -const echoExpectedCompletions = [{ label: 'echo', description: (echoSpec as any).description }]; +const echoExpectedCompletions = [{ label: 'echo', description: (echoSpec as Fig.Subcommand).description }]; export const echoTestSuiteSpec: ISuiteSpec = { name: 'echo', completionSpecs: echoSpec, diff --git a/code/extensions/terminal-suggest/src/test/completions/upstream/git.test.ts b/code/extensions/terminal-suggest/src/test/completions/upstream/git.test.ts index e8c0450fea0..b8a0a5fe6a7 100644 --- a/code/extensions/terminal-suggest/src/test/completions/upstream/git.test.ts +++ b/code/extensions/terminal-suggest/src/test/completions/upstream/git.test.ts @@ -11,7 +11,7 @@ import gitSpec from '../../../completions/git'; // const gitCommitArgs = ['--', '--all', '--allow-empty', '--allow-empty-message', '--amend', '--author', '--branch', '--cleanup', '--date', '--dry-run', '--edit', '--file', '--fixup', '--gpg-sign', '--include', '--long', '--message', '--no-edit', '--no-gpg-sign', '--no-post-rewrite', '--no-signoff', '--no-status', '--no-verify', '--null', '--only', '--patch', '--pathspec-file-nul', '--pathspec-from-file', '--porcelain', '--quiet', '--reedit-message', '--reset-author', '--reuse-message', '--short', '--signoff', '--squash', '--status', '--template', '--untracked-files', '--verbose', '-C', '-F', '-S', '-a', '-am', '-c', '-e', '-i', '-m', '-n', '-o', '-p', '-q', '-s', '-t', '-u', '-v', '-z']; // const gitMergeArgs = ['-', '--abort', '--allow-unrelated-histories', '--autostash', '--cleanup', '--commit', '--continue', '--edit', '--ff', '--ff-only', '--file', '--gpg-sign', '--log', '--no-autostash', '--no-commit', '--no-edit', '--no-ff', '--no-gpg-sign', '--no-log', '--no-overwrite-ignore', '--no-progress', '--no-rerere-autoupdate', '--no-signoff', '--no-squash', '--no-stat', '--no-summary', '--no-verify', '--no-verify-signatures', '--overwrite-ignore', '--progress', '--quiet', '--quit', '--rerere-autoupdate', '--signoff', '--squash', '--stat', '--strategy', '--strategy-option', '--summary', '--verbose', '--verify-signatures', '-F', '-S', '-X', '-e', '-m', '-n', '-q', '-s']; // const gitAddArgs = ['--', '--all', '--chmod', '--dry-run', '--edit', '--force', '--ignore-errors', '--ignore-missing', '--ignore-removal', '--intent-to-add', '--interactive', '--no-all', '--no-ignore-removal', '--no-warn-embedded-repo', '--patch', '--pathspec-file-nul', '--pathspec-from-file', '--refresh', '--renormalize', '--update', '--verbose', '-A', '-N', '-e', '-f', '-i', '-n', '-p', '-u', '-v']; -const expectedCompletions = [{ label: 'git', description: (gitSpec as any).description }]; +const expectedCompletions = [{ label: 'git', description: (gitSpec as Fig.Subcommand).description }]; export const gitTestSuiteSpec: ISuiteSpec = { name: 'git', diff --git a/code/extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts b/code/extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts index aab14e3d5c4..e08b755e60a 100644 --- a/code/extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts +++ b/code/extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts @@ -51,7 +51,7 @@ const allOptions = [ '-w', '-x', ]; -const expectedCompletions = [{ label: 'ls', description: (lsSpec as any).description }]; +const expectedCompletions = [{ label: 'ls', description: (lsSpec as Fig.Subcommand).description }]; export const lsTestSuiteSpec: ISuiteSpec = { name: 'ls', completionSpecs: lsSpec, diff --git a/code/extensions/terminal-suggest/src/test/completions/upstream/mkdir.test.ts b/code/extensions/terminal-suggest/src/test/completions/upstream/mkdir.test.ts index 2a5fe836b81..5c227f01e67 100644 --- a/code/extensions/terminal-suggest/src/test/completions/upstream/mkdir.test.ts +++ b/code/extensions/terminal-suggest/src/test/completions/upstream/mkdir.test.ts @@ -19,7 +19,7 @@ const allOptions = [ '-p', '-v', ]; -const expectedCompletions = [{ label: 'mkdir', description: (mkdirSpec as any).description }]; +const expectedCompletions = [{ label: 'mkdir', description: (mkdirSpec as Fig.Subcommand).description }]; export const mkdirTestSuiteSpec: ISuiteSpec = { name: 'mkdir', completionSpecs: mkdirSpec, diff --git a/code/extensions/terminal-suggest/src/test/completions/upstream/rm.test.ts b/code/extensions/terminal-suggest/src/test/completions/upstream/rm.test.ts index 564ccf570e1..0ca04b26733 100644 --- a/code/extensions/terminal-suggest/src/test/completions/upstream/rm.test.ts +++ b/code/extensions/terminal-suggest/src/test/completions/upstream/rm.test.ts @@ -16,7 +16,7 @@ const allOptions = [ '-r', '-v', ]; -const expectedCompletions = [{ label: 'rm', description: (rmSpec as any).description }]; +const expectedCompletions = [{ label: 'rm', description: (rmSpec as Fig.Subcommand).description }]; export const rmTestSuiteSpec: ISuiteSpec = { name: 'rm', completionSpecs: rmSpec, diff --git a/code/extensions/terminal-suggest/src/test/completions/upstream/rmdir.test.ts b/code/extensions/terminal-suggest/src/test/completions/upstream/rmdir.test.ts index e026851d187..c252a7f4aa8 100644 --- a/code/extensions/terminal-suggest/src/test/completions/upstream/rmdir.test.ts +++ b/code/extensions/terminal-suggest/src/test/completions/upstream/rmdir.test.ts @@ -10,7 +10,7 @@ import rmdirSpec from '../../../completions/upstream/rmdir'; const allOptions = [ '-p', ]; -const expectedCompletions = [{ label: 'rmdir', description: (rmdirSpec as any).description }]; +const expectedCompletions = [{ label: 'rmdir', description: (rmdirSpec as Fig.Subcommand).description }]; export const rmdirTestSuiteSpec: ISuiteSpec = { name: 'rmdir', diff --git a/code/extensions/terminal-suggest/src/test/completions/upstream/touch.test.ts b/code/extensions/terminal-suggest/src/test/completions/upstream/touch.test.ts index dd03b062154..1435822f49c 100644 --- a/code/extensions/terminal-suggest/src/test/completions/upstream/touch.test.ts +++ b/code/extensions/terminal-suggest/src/test/completions/upstream/touch.test.ts @@ -17,7 +17,7 @@ const allOptions = [ '-r ', '-t ', ]; -const expectedCompletions = [{ label: 'touch', description: (touchSpec as any).description }]; +const expectedCompletions = [{ label: 'touch', description: (touchSpec as Fig.Subcommand).description }]; export const touchTestSuiteSpec: ISuiteSpec = { name: 'touch', diff --git a/code/extensions/terminal-suggest/src/test/env/pathExecutableCache.test.ts b/code/extensions/terminal-suggest/src/test/env/pathExecutableCache.test.ts index 42b28d6d1dd..83e006a391a 100644 --- a/code/extensions/terminal-suggest/src/test/env/pathExecutableCache.test.ts +++ b/code/extensions/terminal-suggest/src/test/env/pathExecutableCache.test.ts @@ -7,6 +7,7 @@ import 'mocha'; import { deepStrictEqual, strictEqual } from 'node:assert'; import type { MarkdownString } from 'vscode'; import { PathExecutableCache } from '../../env/pathExecutableCache'; +import { WindowsExecutableExtensionsCache, windowsDefaultExecutableExtensions } from '../../helpers/executable'; suite('PathExecutableCache', () => { test('cache should return empty for empty PATH', async () => { @@ -67,4 +68,43 @@ suite('PathExecutableCache', () => { strictEqual(symlinkDoc, `${symlinkPath} -> ${realPath}`); }); } + + if (process.platform === 'win32') { + suite('WindowsExecutableExtensionsCache', () => { + test('returns default extensions when not configured', () => { + const cache = new WindowsExecutableExtensionsCache(); + const extensions = cache.getExtensions(); + + for (const ext of windowsDefaultExecutableExtensions) { + strictEqual(extensions.has(ext), true, `expected default extension ${ext}`); + } + }); + + test('honors configured additions and removals', () => { + const cache = new WindowsExecutableExtensionsCache({ + '.added': true, + '.bat': false + }); + + const extensions = cache.getExtensions(); + strictEqual(extensions.has('.added'), true); + strictEqual(extensions.has('.bat'), false); + strictEqual(extensions.has('.exe'), true); + }); + + test('recomputes only after update is called', () => { + const cache = new WindowsExecutableExtensionsCache({ '.one': true }); + + const first = cache.getExtensions(); + const second = cache.getExtensions(); + strictEqual(first, second, 'expected cached set to be reused'); + + cache.update({ '.two': true }); + const third = cache.getExtensions(); + strictEqual(third.has('.two'), true); + strictEqual(third.has('.one'), false); + strictEqual(third === first, false, 'expected cache to recompute after update'); + }); + }); + } }); diff --git a/code/extensions/terminal-suggest/src/test/fig.test.ts b/code/extensions/terminal-suggest/src/test/fig.test.ts index b8144219e88..c3aeee4fcf1 100644 --- a/code/extensions/terminal-suggest/src/test/fig.test.ts +++ b/code/extensions/terminal-suggest/src/test/fig.test.ts @@ -183,6 +183,56 @@ export const figGenericTestSuites: ISuiteSpec[] = [ { input: 'foo b|', expectedCompletions: ['b', 'foo'] }, { input: 'foo c|', expectedCompletions: ['c', 'foo'] }, ] + }, + { + name: 'Fig persistent options', + completionSpecs: [ + { + name: 'foo', + description: 'Foo', + options: [ + { name: '--help', description: 'Show help', isPersistent: true }, + { name: '--docs', description: 'Show docs' }, + { name: '--version', description: 'Version info', isPersistent: false } + ], + subcommands: [ + { + name: 'bar', + description: 'Bar subcommand', + options: [ + { name: '--local', description: 'Local option' } + ] + }, + { + name: 'baz', + description: 'Baz subcommand', + options: [ + { name: '--another', description: 'Another option' } + ], + subcommands: [ + { + name: 'nested', + description: 'Nested subcommand' + } + ] + } + ] + } + ], + availableCommands: 'foo', + testSpecs: [ + // Top-level should show all options including persistent + { input: 'foo |', expectedCompletions: ['--help', '--docs', '--version', 'bar', 'baz'] }, + // First-level subcommand should only inherit persistent options (not --docs or --version) + { input: 'foo bar |', expectedCompletions: ['--help', '--local'] }, + // Another first-level subcommand should also inherit only persistent options + { input: 'foo baz |', expectedCompletions: ['--help', '--another', 'nested'] }, + // Nested subcommand should inherit persistent options from top level + { input: 'foo baz nested |', expectedCompletions: ['--help'] }, + // Persistent options should be available even after using local options + { input: 'foo bar --local |', expectedCompletions: ['--help'] }, + ] } ]; + diff --git a/code/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts b/code/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts index 5f3fb96a72c..793cc9a634b 100644 --- a/code/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts +++ b/code/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts @@ -91,11 +91,11 @@ suite('Terminal Suggest', () => { } test(`'${testSpec.input}' -> ${expectedString}`, async () => { const commandLine = testSpec.input.split('|')[0]; - const cursorPosition = testSpec.input.indexOf('|'); - const currentCommandString = getCurrentCommandAndArgs(commandLine, cursorPosition, undefined); - const filesRequested = testSpec.expectedResourceRequests?.type === 'files' || testSpec.expectedResourceRequests?.type === 'both'; - const foldersRequested = testSpec.expectedResourceRequests?.type === 'folders' || testSpec.expectedResourceRequests?.type === 'both'; - const terminalContext = { commandLine, cursorPosition, allowFallbackCompletions: true }; + const cursorIndex = testSpec.input.indexOf('|'); + const currentCommandString = getCurrentCommandAndArgs(commandLine, cursorIndex, undefined); + const showFiles = testSpec.expectedResourceRequests?.type === 'files' || testSpec.expectedResourceRequests?.type === 'both'; + const showDirectories = testSpec.expectedResourceRequests?.type === 'folders' || testSpec.expectedResourceRequests?.type === 'both'; + const terminalContext = { commandLine, cursorIndex }; const result = await getCompletionItemsFromSpecs( completionSpecs, terminalContext, @@ -118,8 +118,8 @@ suite('Terminal Suggest', () => { }).sort(), (testSpec.expectedCompletions ?? []).sort() ); - strictEqual(result.filesRequested, filesRequested, 'Files requested different than expected, got: ' + result.filesRequested); - strictEqual(result.foldersRequested, foldersRequested, 'Folders requested different than expected, got: ' + result.foldersRequested); + strictEqual(result.showFiles, showFiles, 'Show files different than expected, got: ' + result.showFiles); + strictEqual(result.showDirectories, showDirectories, 'Show directories different than expected, got: ' + result.showDirectories); if (testSpec.expectedResourceRequests?.cwd) { strictEqual(result.cwd?.fsPath, testSpec.expectedResourceRequests.cwd.fsPath, 'Non matching cwd'); } diff --git a/code/extensions/terminal-suggest/src/test/tokens.test.ts b/code/extensions/terminal-suggest/src/test/tokens.test.ts index fb3ea702bab..dc583a28b08 100644 --- a/code/extensions/terminal-suggest/src/test/tokens.test.ts +++ b/code/extensions/terminal-suggest/src/test/tokens.test.ts @@ -10,91 +10,91 @@ import { TerminalShellType } from '../terminalSuggestMain'; suite('Terminal Suggest', () => { test('simple command', () => { - strictEqual(getTokenType({ commandLine: 'echo', cursorPosition: 'echo'.length }, undefined), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'echo', cursorIndex: 'echo'.length }, undefined), TokenType.Command); }); test('simple argument', () => { - strictEqual(getTokenType({ commandLine: 'echo hello', cursorPosition: 'echo hello'.length }, undefined), TokenType.Argument); + strictEqual(getTokenType({ commandLine: 'echo hello', cursorIndex: 'echo hello'.length }, undefined), TokenType.Argument); }); test('simple command, cursor mid text', () => { - strictEqual(getTokenType({ commandLine: 'echo hello', cursorPosition: 'echo'.length }, undefined), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'echo hello', cursorIndex: 'echo'.length }, undefined), TokenType.Command); }); test('simple argument, cursor mid text', () => { - strictEqual(getTokenType({ commandLine: 'echo hello', cursorPosition: 'echo hel'.length }, undefined), TokenType.Argument); + strictEqual(getTokenType({ commandLine: 'echo hello', cursorIndex: 'echo hel'.length }, undefined), TokenType.Argument); }); suite('reset to command', () => { test('|', () => { - strictEqual(getTokenType({ commandLine: 'echo hello | ', cursorPosition: 'echo hello | '.length }, undefined), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'echo hello | ', cursorIndex: 'echo hello | '.length }, undefined), TokenType.Command); }); test(';', () => { - strictEqual(getTokenType({ commandLine: 'echo hello; ', cursorPosition: 'echo hello; '.length }, undefined), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'echo hello; ', cursorIndex: 'echo hello; '.length }, undefined), TokenType.Command); }); test('&&', () => { - strictEqual(getTokenType({ commandLine: 'echo hello && ', cursorPosition: 'echo hello && '.length }, undefined), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'echo hello && ', cursorIndex: 'echo hello && '.length }, undefined), TokenType.Command); }); test('||', () => { - strictEqual(getTokenType({ commandLine: 'echo hello || ', cursorPosition: 'echo hello || '.length }, undefined), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'echo hello || ', cursorIndex: 'echo hello || '.length }, undefined), TokenType.Command); }); }); suite('pwsh', () => { test('simple command', () => { - strictEqual(getTokenType({ commandLine: 'Write-Host', cursorPosition: 'Write-Host'.length }, TerminalShellType.PowerShell), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'Write-Host', cursorIndex: 'Write-Host'.length }, TerminalShellType.PowerShell), TokenType.Command); }); test('simple argument', () => { - strictEqual(getTokenType({ commandLine: 'Write-Host hello', cursorPosition: 'Write-Host hello'.length }, TerminalShellType.PowerShell), TokenType.Argument); + strictEqual(getTokenType({ commandLine: 'Write-Host hello', cursorIndex: 'Write-Host hello'.length }, TerminalShellType.PowerShell), TokenType.Argument); }); test('reset char', () => { - strictEqual(getTokenType({ commandLine: `Write-Host hello -and `, cursorPosition: `Write-Host hello -and `.length }, TerminalShellType.PowerShell), TokenType.Command); + strictEqual(getTokenType({ commandLine: `Write-Host hello -and `, cursorIndex: `Write-Host hello -and `.length }, TerminalShellType.PowerShell), TokenType.Command); }); test('arguments after reset char', () => { - strictEqual(getTokenType({ commandLine: `Write-Host hello -and $true `, cursorPosition: `Write-Host hello -and $true `.length }, TerminalShellType.PowerShell), TokenType.Argument); + strictEqual(getTokenType({ commandLine: `Write-Host hello -and $true `, cursorIndex: `Write-Host hello -and $true `.length }, TerminalShellType.PowerShell), TokenType.Argument); }); test('; reset char', () => { - strictEqual(getTokenType({ commandLine: `Write-Host hello; `, cursorPosition: `Write-Host hello; `.length }, TerminalShellType.PowerShell), TokenType.Command); + strictEqual(getTokenType({ commandLine: `Write-Host hello; `, cursorIndex: `Write-Host hello; `.length }, TerminalShellType.PowerShell), TokenType.Command); }); suite('multiple commands on the line', () => { test('multiple commands, cursor at end', () => { - strictEqual(getTokenType({ commandLine: 'echo hello && echo world', cursorPosition: 'echo hello && ech'.length }, undefined), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'echo hello && echo world', cursorIndex: 'echo hello && ech'.length }, undefined), TokenType.Command); // Bash - strictEqual(getTokenType({ commandLine: 'echo hello && echo world', cursorPosition: 'echo hello && ech'.length }, TerminalShellType.Bash), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'echo hello && echo world', cursorIndex: 'echo hello && ech'.length }, TerminalShellType.Bash), TokenType.Command); // Zsh - strictEqual(getTokenType({ commandLine: 'echo hello && echo world', cursorPosition: 'echo hello && ech'.length }, TerminalShellType.Zsh), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'echo hello && echo world', cursorIndex: 'echo hello && ech'.length }, TerminalShellType.Zsh), TokenType.Command); // Fish (use ';' as separator) - strictEqual(getTokenType({ commandLine: 'echo hello; echo world', cursorPosition: 'echo hello; ech'.length }, TerminalShellType.Fish), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'echo hello; echo world', cursorIndex: 'echo hello; ech'.length }, TerminalShellType.Fish), TokenType.Command); // PowerShell (use ';' as separator) - strictEqual(getTokenType({ commandLine: 'echo hello; echo world', cursorPosition: 'echo hello; ech'.length }, TerminalShellType.PowerShell), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'echo hello; echo world', cursorIndex: 'echo hello; ech'.length }, TerminalShellType.PowerShell), TokenType.Command); }); test('multiple commands, cursor mid text', () => { - strictEqual(getTokenType({ commandLine: 'echo hello && echo world', cursorPosition: 'echo hello && echo w'.length }, undefined), TokenType.Argument); + strictEqual(getTokenType({ commandLine: 'echo hello && echo world', cursorIndex: 'echo hello && echo w'.length }, undefined), TokenType.Argument); // Bash - strictEqual(getTokenType({ commandLine: 'echo hello && echo world', cursorPosition: 'echo hello && echo w'.length }, TerminalShellType.Bash), TokenType.Argument); + strictEqual(getTokenType({ commandLine: 'echo hello && echo world', cursorIndex: 'echo hello && echo w'.length }, TerminalShellType.Bash), TokenType.Argument); // Zsh - strictEqual(getTokenType({ commandLine: 'echo hello && echo world', cursorPosition: 'echo hello && echo w'.length }, TerminalShellType.Zsh), TokenType.Argument); + strictEqual(getTokenType({ commandLine: 'echo hello && echo world', cursorIndex: 'echo hello && echo w'.length }, TerminalShellType.Zsh), TokenType.Argument); // Fish (use ';' as separator) - strictEqual(getTokenType({ commandLine: 'echo hello; echo world', cursorPosition: 'echo hello; echo w'.length }, TerminalShellType.Fish), TokenType.Argument); + strictEqual(getTokenType({ commandLine: 'echo hello; echo world', cursorIndex: 'echo hello; echo w'.length }, TerminalShellType.Fish), TokenType.Argument); // PowerShell (use ';' as separator) - strictEqual(getTokenType({ commandLine: 'echo hello; echo world', cursorPosition: 'echo hello; echo w'.length }, TerminalShellType.PowerShell), TokenType.Argument); + strictEqual(getTokenType({ commandLine: 'echo hello; echo world', cursorIndex: 'echo hello; echo w'.length }, TerminalShellType.PowerShell), TokenType.Argument); }); test('multiple commands, cursor at end with reset char', () => { - strictEqual(getTokenType({ commandLine: 'echo hello && echo world; ', cursorPosition: 'echo hello && echo world; '.length }, undefined), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'echo hello && echo world; ', cursorIndex: 'echo hello && echo world; '.length }, undefined), TokenType.Command); // Bash - strictEqual(getTokenType({ commandLine: 'echo hello && echo world; ', cursorPosition: 'echo hello && echo world; '.length }, TerminalShellType.Bash), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'echo hello && echo world; ', cursorIndex: 'echo hello && echo world; '.length }, TerminalShellType.Bash), TokenType.Command); // Zsh - strictEqual(getTokenType({ commandLine: 'echo hello && echo world; ', cursorPosition: 'echo hello && echo world; '.length }, TerminalShellType.Zsh), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'echo hello && echo world; ', cursorIndex: 'echo hello && echo world; '.length }, TerminalShellType.Zsh), TokenType.Command); // Fish (use ';' as separator) - strictEqual(getTokenType({ commandLine: 'echo hello; echo world; ', cursorPosition: 'echo hello; echo world; '.length }, TerminalShellType.Fish), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'echo hello; echo world; ', cursorIndex: 'echo hello; echo world; '.length }, TerminalShellType.Fish), TokenType.Command); // PowerShell (use ';' as separator) - strictEqual(getTokenType({ commandLine: 'echo hello; echo world; ', cursorPosition: 'echo hello; echo world; '.length }, TerminalShellType.PowerShell), TokenType.Command); + strictEqual(getTokenType({ commandLine: 'echo hello; echo world; ', cursorIndex: 'echo hello; echo world; '.length }, TerminalShellType.PowerShell), TokenType.Command); }); test('multiple commands, cursor mid text with reset char', () => { - strictEqual(getTokenType({ commandLine: 'echo hello && echo world; ', cursorPosition: 'echo hello && echo worl'.length }, undefined), TokenType.Argument); + strictEqual(getTokenType({ commandLine: 'echo hello && echo world; ', cursorIndex: 'echo hello && echo worl'.length }, undefined), TokenType.Argument); // Bash - strictEqual(getTokenType({ commandLine: 'echo hello && echo world; ', cursorPosition: 'echo hello && echo worl'.length }, TerminalShellType.Bash), TokenType.Argument); + strictEqual(getTokenType({ commandLine: 'echo hello && echo world; ', cursorIndex: 'echo hello && echo worl'.length }, TerminalShellType.Bash), TokenType.Argument); // Zsh - strictEqual(getTokenType({ commandLine: 'echo hello && echo world; ', cursorPosition: 'echo hello && echo worl'.length }, TerminalShellType.Zsh), TokenType.Argument); + strictEqual(getTokenType({ commandLine: 'echo hello && echo world; ', cursorIndex: 'echo hello && echo worl'.length }, TerminalShellType.Zsh), TokenType.Argument); // Fish (use ';' as separator) - strictEqual(getTokenType({ commandLine: 'echo hello; echo world; ', cursorPosition: 'echo hello; echo worl'.length }, TerminalShellType.Fish), TokenType.Argument); + strictEqual(getTokenType({ commandLine: 'echo hello; echo world; ', cursorIndex: 'echo hello; echo worl'.length }, TerminalShellType.Fish), TokenType.Argument); // PowerShell (use ';' as separator) - strictEqual(getTokenType({ commandLine: 'echo hello; echo world; ', cursorPosition: 'echo hello; echo worl'.length }, TerminalShellType.PowerShell), TokenType.Argument); + strictEqual(getTokenType({ commandLine: 'echo hello; echo world; ', cursorIndex: 'echo hello; echo worl'.length }, TerminalShellType.PowerShell), TokenType.Argument); }); }); }); diff --git a/code/extensions/terminal-suggest/src/tokens.ts b/code/extensions/terminal-suggest/src/tokens.ts index cadab15ba64..11a23f7ad43 100644 --- a/code/extensions/terminal-suggest/src/tokens.ts +++ b/code/extensions/terminal-suggest/src/tokens.ts @@ -19,9 +19,9 @@ export const shellTypeResetChars = new Map([ export const defaultShellTypeResetChars = shellTypeResetChars.get(TerminalShellType.Bash)!; -export function getTokenType(ctx: { commandLine: string; cursorPosition: number }, shellType: TerminalShellType | undefined): TokenType { +export function getTokenType(ctx: { commandLine: string; cursorIndex: number }, shellType: TerminalShellType | undefined): TokenType { const commandLine = ctx.commandLine; - const cursorPosition = ctx.cursorPosition; + const cursorPosition = ctx.cursorIndex; const commandResetChars = shellType === undefined ? defaultShellTypeResetChars : shellTypeResetChars.get(shellType) ?? defaultShellTypeResetChars; // Check for reset char before the current word diff --git a/code/extensions/terminal-suggest/tsconfig.json b/code/extensions/terminal-suggest/tsconfig.json index f1439c32819..1265a62536f 100644 --- a/code/extensions/terminal-suggest/tsconfig.json +++ b/code/extensions/terminal-suggest/tsconfig.json @@ -2,10 +2,13 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", - "esModuleInterop": true, "types": [ "node" ], + "typeRoots": [ + "./node_modules/@types" + ], + "esModuleInterop": true, // Needed to suppress warnings in upstream completions "noImplicitReturns": false, "noUnusedParameters": false diff --git a/code/extensions/theme-defaults/themes/dark_modern.json b/code/extensions/theme-defaults/themes/dark_modern.json index 27738936b27..51e0f371c27 100644 --- a/code/extensions/theme-defaults/themes/dark_modern.json +++ b/code/extensions/theme-defaults/themes/dark_modern.json @@ -88,6 +88,8 @@ "sideBarTitle.foreground": "#CCCCCC", "statusBar.background": "#181818", "statusBar.border": "#2B2B2B", + "statusBarItem.hoverBackground": "#F1F1F133", + "statusBarItem.hoverForeground": "#FFFFFF", "statusBar.debuggingBackground": "#0078D4", "statusBar.debuggingForeground": "#FFFFFF", "statusBar.focusBorder": "#0078D4", diff --git a/code/extensions/theme-defaults/themes/light_modern.json b/code/extensions/theme-defaults/themes/light_modern.json index 2251c4bac30..1576ef7e5c1 100644 --- a/code/extensions/theme-defaults/themes/light_modern.json +++ b/code/extensions/theme-defaults/themes/light_modern.json @@ -104,7 +104,8 @@ "statusBar.background": "#F8F8F8", "statusBar.foreground": "#3B3B3B", "statusBar.border": "#E5E5E5", - "statusBarItem.hoverBackground": "#B8B8B850", + "statusBarItem.hoverBackground": "#1F1F1F11", + "statusBarItem.hoverForeground": "#000000", "statusBarItem.compactHoverBackground": "#CCCCCC", "statusBar.debuggingBackground": "#FD716C", "statusBar.debuggingForeground": "#000000", diff --git a/code/extensions/theme-seti/cgmanifest.json b/code/extensions/theme-seti/cgmanifest.json index 6d2b5fb51c7..0a12c2c7baa 100644 --- a/code/extensions/theme-seti/cgmanifest.json +++ b/code/extensions/theme-seti/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "seti-ui", "repositoryUrl": "https://github.com/jesseweed/seti-ui", - "commitHash": "1cac4f30f93cc898103c62dde41823a09b0d7b74" + "commitHash": "2d6c5e68b4ded73c92dac291845ee44e1182d511" } }, "version": "0.1.0" diff --git a/code/extensions/theme-seti/icons/seti.woff b/code/extensions/theme-seti/icons/seti.woff index edc69c7cf89..6a8a3da7cf0 100644 Binary files a/code/extensions/theme-seti/icons/seti.woff and b/code/extensions/theme-seti/icons/seti.woff differ diff --git a/code/extensions/theme-seti/icons/vs-seti-icon-theme.json b/code/extensions/theme-seti/icons/vs-seti-icon-theme.json index 444f22780b4..f7c55c96d9e 100644 --- a/code/extensions/theme-seti/icons/vs-seti-icon-theme.json +++ b/code/extensions/theme-seti/icons/vs-seti-icon-theme.json @@ -1784,7 +1784,6 @@ "stylelintrc.js": "_stylelint", "stylelintignore": "_stylelint_1", "direnv": "_config", - "env": "_config", "static": "_config", "slugignore": "_config", "tmp": "_clock_1", @@ -1912,6 +1911,7 @@ "css": "_css", "dart": "_dart", "dockerfile": "_docker", + "dotenv": "_config", "ignore": "_git", "fsharp": "_f-sharp", "git-commit": "_git", @@ -2210,7 +2210,6 @@ "stylelintrc.js": "_stylelint_light", "stylelintignore": "_stylelint_1_light", "direnv": "_config_light", - "env": "_config_light", "static": "_config_light", "slugignore": "_config_light", "tmp": "_clock_1_light", @@ -2235,6 +2234,7 @@ "css": "_css_light", "dart": "_dart_light", "dockerfile": "_docker_light", + "dotenv": "_config_light", "ignore": "_git_light", "fsharp": "_f-sharp_light", "git-commit": "_git_light", @@ -2402,5 +2402,5 @@ "npm-debug.log": "_npm_ignored_light" } }, - "version": "https://github.com/jesseweed/seti-ui/commit/1cac4f30f93cc898103c62dde41823a09b0d7b74" + "version": "https://github.com/jesseweed/seti-ui/commit/2d6c5e68b4ded73c92dac291845ee44e1182d511" } \ No newline at end of file diff --git a/code/extensions/tunnel-forwarding/tsconfig.json b/code/extensions/tunnel-forwarding/tsconfig.json index 4769a9faec8..a65d85d716d 100644 --- a/code/extensions/tunnel-forwarding/tsconfig.json +++ b/code/extensions/tunnel-forwarding/tsconfig.json @@ -2,7 +2,9 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", - "downlevelIteration": true, + "typeRoots": [ + "./node_modules/@types" + ], "types": [ "node" ] diff --git a/code/extensions/typescript-basics/language-configuration.json b/code/extensions/typescript-basics/language-configuration.json index 336daa91339..563d392beeb 100644 --- a/code/extensions/typescript-basics/language-configuration.json +++ b/code/extensions/typescript-basics/language-configuration.json @@ -26,6 +26,10 @@ ] ], "autoClosingPairs": [ + { + "open": "${", + "close": "}" + }, { "open": "{", "close": "}" @@ -70,6 +74,14 @@ } ], "surroundingPairs": [ + [ + "${", + "}" + ], + [ + "$", + "" + ], [ "{", "}" diff --git a/code/extensions/typescript-language-features/package.json b/code/extensions/typescript-language-features/package.json index b48ba9ea67a..b4706482fb6 100644 --- a/code/extensions/typescript-language-features/package.json +++ b/code/extensions/typescript-language-features/package.json @@ -53,7 +53,7 @@ "@types/semver": "^5.5.0" }, "scripts": { - "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:typescript-language-features", + "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.mjs compile-extension:typescript-language-features", "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" }, @@ -218,6 +218,12 @@ "description": "%typescript.implementationsCodeLens.showOnInterfaceMethods%", "scope": "window" }, + "typescript.implementationsCodeLens.showOnAllClassMethods": { + "type": "boolean", + "default": false, + "description": "%typescript.implementationsCodeLens.showOnAllClassMethods%", + "scope": "window" + }, "typescript.reportStyleChecksAsWarnings": { "type": "boolean", "default": true, @@ -1632,7 +1638,7 @@ }, { "command": "typescript.goToProjectConfig", - "when": "editorLangId == typescriptreact" + "when": "editorLangId == typescriptreact && typescript.isManagedFile" }, { "command": "javascript.goToProjectConfig", @@ -1682,7 +1688,7 @@ "editor/context": [ { "command": "typescript.goToSourceDefinition", - "when": "tsSupportsSourceDefinition && (resourceLangId == typescript || resourceLangId == typescriptreact || resourceLangId == javascript || resourceLangId == javascriptreact)", + "when": "!config.typescript.experimental.useTsgo && tsSupportsSourceDefinition && (resourceLangId == typescript || resourceLangId == typescriptreact || resourceLangId == javascript || resourceLangId == javascriptreact)", "group": "navigation@1.41" } ], @@ -1778,6 +1784,27 @@ ], "pattern": "$tsc" }, + { + "name": "tsgo-watch", + "label": "%typescript.problemMatchers.tsgo-watch.label%", + "owner": "typescript", + "source": "ts", + "applyTo": "closedDocuments", + "fileLocation": [ + "relative", + "${cwd}" + ], + "pattern": "$tsc", + "background": { + "activeOnStart": true, + "beginsPattern": { + "regexp": "^build starting at .*$" + }, + "endsPattern": { + "regexp": "^build finished in .*$" + } + } + }, { "name": "tsc-watch", "label": "%typescript.problemMatchers.tscWatch.label%", diff --git a/code/extensions/typescript-language-features/package.nls.json b/code/extensions/typescript-language-features/package.nls.json index ceb221b137b..fb28b2a4eb2 100644 --- a/code/extensions/typescript-language-features/package.nls.json +++ b/code/extensions/typescript-language-features/package.nls.json @@ -56,6 +56,7 @@ "typescript.referencesCodeLens.showOnAllFunctions": "Enable/disable references CodeLens on all functions in TypeScript files.", "typescript.implementationsCodeLens.enabled": "Enable/disable implementations CodeLens. This CodeLens shows the implementers of an interface.", "typescript.implementationsCodeLens.showOnInterfaceMethods": "Enable/disable implementations CodeLens on interface methods.", + "typescript.implementationsCodeLens.showOnAllClassMethods": "Enable/disable showing implementations CodeLens above all class methods instead of only on abstract methods.", "typescript.openTsServerLog.title": "Open TS Server log", "typescript.restartTsServer": "Restart TS Server", "typescript.selectTypeScriptVersion.title": "Select TypeScript Version...", @@ -69,6 +70,7 @@ "typescript.tsc.autoDetect.build": "Only create single run compile tasks.", "typescript.tsc.autoDetect.watch": "Only create compile and watch tasks.", "typescript.problemMatchers.tsc.label": "TypeScript problems", + "typescript.problemMatchers.tsgo-watch.label": "TypeScript problems (watch mode)", "typescript.problemMatchers.tscWatch.label": "TypeScript problems (watch mode)", "configuration.suggest.paths": "Enable/disable suggestions for paths in import statements and require calls.", "configuration.tsserver.useSeparateSyntaxServer": "Enable/disable spawning a separate TypeScript server that can more quickly respond to syntax related operations, such as calculating folding or computing document symbols.", diff --git a/code/extensions/typescript-language-features/src/commands/commandManager.ts b/code/extensions/typescript-language-features/src/commands/commandManager.ts index 92b5158560c..1fdec800012 100644 --- a/code/extensions/typescript-language-features/src/commands/commandManager.ts +++ b/code/extensions/typescript-language-features/src/commands/commandManager.ts @@ -8,7 +8,7 @@ import * as vscode from 'vscode'; export interface Command { readonly id: string; - execute(...args: any[]): void | any; + execute(...args: unknown[]): void | unknown; } export class CommandManager { diff --git a/code/extensions/typescript-language-features/src/commands/configurePlugin.ts b/code/extensions/typescript-language-features/src/commands/configurePlugin.ts index 356738294ad..5a6a4478506 100644 --- a/code/extensions/typescript-language-features/src/commands/configurePlugin.ts +++ b/code/extensions/typescript-language-features/src/commands/configurePlugin.ts @@ -13,7 +13,7 @@ export class ConfigurePluginCommand implements Command { private readonly pluginManager: PluginManager, ) { } - public execute(pluginId: string, configuration: any) { + public execute(pluginId: string, configuration: unknown) { this.pluginManager.setConfiguration(pluginId, configuration); } } diff --git a/code/extensions/typescript-language-features/src/commands/tsserverRequests.ts b/code/extensions/typescript-language-features/src/commands/tsserverRequests.ts index 7c925024b96..77c07d658af 100644 --- a/code/extensions/typescript-language-features/src/commands/tsserverRequests.ts +++ b/code/extensions/typescript-language-features/src/commands/tsserverRequests.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { TypeScriptRequests } from '../typescriptService'; +import { ExecConfig, TypeScriptRequests } from '../typescriptService'; import TypeScriptServiceClientHost from '../typeScriptServiceClientHost'; import { nulToken } from '../utils/cancellation'; import { Lazy } from '../utils/lazy'; @@ -14,7 +14,7 @@ function isCancellationToken(value: any): value is vscode.CancellationToken { return value && typeof value.isCancellationRequested === 'boolean' && typeof value.onCancellationRequested === 'function'; } -interface RequestArgs { +export interface RequestArgs { readonly file?: unknown; readonly $traceId?: unknown; } @@ -26,7 +26,7 @@ export class TSServerRequestCommand implements Command { private readonly lazyClientHost: Lazy ) { } - public async execute(command: keyof TypeScriptRequests, args?: any, config?: any, token?: vscode.CancellationToken): Promise { + public async execute(command: keyof TypeScriptRequests, args?: unknown, config?: ExecConfig, token?: vscode.CancellationToken): Promise { if (!isCancellationToken(token)) { token = nulToken; } @@ -35,7 +35,7 @@ export class TSServerRequestCommand implements Command { const hasFile = requestArgs.file instanceof vscode.Uri; const hasTraceId = typeof requestArgs.$traceId === 'string'; if (hasFile || hasTraceId) { - const newArgs = { ...args }; + const newArgs = { file: undefined as string | undefined, ...args }; if (hasFile) { const client = this.lazyClientHost.value.serviceClient; newArgs.file = client.toOpenTsFilePath(requestArgs.file); diff --git a/code/extensions/typescript-language-features/src/commands/useTsgo.ts b/code/extensions/typescript-language-features/src/commands/useTsgo.ts index aedf28e54b0..d537cff3da6 100644 --- a/code/extensions/typescript-language-features/src/commands/useTsgo.ts +++ b/code/extensions/typescript-language-features/src/commands/useTsgo.ts @@ -6,6 +6,8 @@ import * as vscode from 'vscode'; import { Command } from './commandManager'; +export const tsNativeExtensionId = 'typescriptteam.native-preview'; + export class EnableTsgoCommand implements Command { public readonly id = 'typescript.experimental.enableTsgo'; @@ -27,7 +29,7 @@ export class DisableTsgoCommand implements Command { * @param enable Whether to enable or disable TypeScript Go */ async function updateTsgoSetting(enable: boolean): Promise { - const tsgoExtension = vscode.extensions.getExtension('typescript.typescript-lsp'); + const tsgoExtension = vscode.extensions.getExtension(tsNativeExtensionId); // Error if the TypeScript Go extension is not installed with a button to open the GitHub repo if (!tsgoExtension) { const selection = await vscode.window.showErrorMessage( @@ -68,7 +70,5 @@ async function updateTsgoSetting(enable: boolean): Promise { } } - // Update the setting, restart the extension host, and enable the TypeScript Go extension await tsConfig.update('experimental.useTsgo', enable, target); - await vscode.commands.executeCommand('workbench.action.restartExtensionHost'); } diff --git a/code/extensions/typescript-language-features/src/extension.ts b/code/extensions/typescript-language-features/src/extension.ts index 29f809bd653..6f51fe1b597 100644 --- a/code/extensions/typescript-language-features/src/extension.ts +++ b/code/extensions/typescript-language-features/src/extension.ts @@ -8,7 +8,7 @@ import * as fs from 'fs'; import * as vscode from 'vscode'; import { Api, getExtensionApi } from './api'; import { CommandManager } from './commands/commandManager'; -import { DisableTsgoCommand } from './commands/useTsgo'; +import { DisableTsgoCommand, tsNativeExtensionId } from './commands/useTsgo'; import { registerBaseCommands } from './commands/index'; import { ElectronServiceConfigurationProvider } from './configuration/configuration.electron'; import { ExperimentationTelemetryReporter, IExperimentationTelemetryReporter } from './experimentTelemetryReporter'; @@ -25,27 +25,12 @@ import { onCaseInsensitiveFileSystem } from './utils/fs.electron'; import { Lazy } from './utils/lazy'; import { getPackageInfo } from './utils/packageInfo'; import * as temp from './utils/temp.electron'; +import { conditionalRegistration, requireGlobalConfiguration, requireHasVsCodeExtension } from './languageFeatures/util/dependentRegistration'; +import { DisposableStore } from './utils/dispose'; export function activate( context: vscode.ExtensionContext ): Api { - const commandManager = new CommandManager(); - context.subscriptions.push(commandManager); - - // Disable extension if using the experimental TypeScript Go extension - const config = vscode.workspace.getConfiguration('typescript'); - const useTsgo = config.get('experimental.useTsgo', false); - - if (useTsgo) { - commandManager.register(new DisableTsgoCommand()); - // Return a no-op API when disabled - return { - getAPI() { - return undefined; - } - }; - } - const pluginManager = new PluginManager(); context.subscriptions.push(pluginManager); @@ -55,9 +40,6 @@ export function activate( const logDirectoryProvider = new NodeLogDirectoryProvider(context); const versionProvider = new DiskTypeScriptVersionProvider(); - const activeJsTsEditorTracker = new ActiveJsTsEditorTracker(); - context.subscriptions.push(activeJsTsEditorTracker); - let experimentTelemetryReporter: IExperimentationTelemetryReporter | undefined; const packageInfo = getPackageInfo(context); if (packageInfo) { @@ -71,34 +53,58 @@ export function activate( new ExperimentationService(experimentTelemetryReporter, id, version, context.globalState); } - const logger = new Logger(); - - const lazyClientHost = createLazyClientHost(context, onCaseInsensitiveFileSystem(), { - pluginManager, - commandManager, - logDirectoryProvider, - cancellerFactory: nodeRequestCancellerFactory, - versionProvider, - processFactory: new ElectronServiceProcessFactory(), - activeJsTsEditorTracker, - serviceConfigurationProvider: new ElectronServiceConfigurationProvider(), - experimentTelemetryReporter, - logger, - }, item => { - onCompletionAccepted.fire(item); - }); - - registerBaseCommands(commandManager, lazyClientHost, pluginManager, activeJsTsEditorTracker); - - import('./task/taskProvider').then(module => { - context.subscriptions.push(module.register(new Lazy(() => lazyClientHost.value.serviceClient))); - }); - + // Register features that work in both TSGO and non-TSGO modes import('./languageFeatures/tsconfig').then(module => { context.subscriptions.push(module.register()); }); - context.subscriptions.push(lazilyActivateClient(lazyClientHost, pluginManager, activeJsTsEditorTracker)); + // Conditionally register features based on whether TSGO is enabled + context.subscriptions.push(conditionalRegistration([ + requireGlobalConfiguration('typescript', 'experimental.useTsgo'), + requireHasVsCodeExtension(tsNativeExtensionId), + ], () => { + // TSGO. Only register a small set of features that don't use TS Server + const disposables = new DisposableStore(); + + const commandManager = disposables.add(new CommandManager()); + commandManager.register(new DisableTsgoCommand()); + + return disposables; + }, () => { + // Normal registration path + const disposables = new DisposableStore(); + + const commandManager = disposables.add(new CommandManager()); + const activeJsTsEditorTracker = disposables.add(new ActiveJsTsEditorTracker()); + + const lazyClientHost = createLazyClientHost(context, onCaseInsensitiveFileSystem(), { + pluginManager, + commandManager, + logDirectoryProvider, + cancellerFactory: nodeRequestCancellerFactory, + versionProvider, + processFactory: new ElectronServiceProcessFactory(), + activeJsTsEditorTracker, + serviceConfigurationProvider: new ElectronServiceConfigurationProvider(), + experimentTelemetryReporter, + logger: new Logger(), + }, item => { + onCompletionAccepted.fire(item); + }).map(clientHost => { + return disposables.add(clientHost); + }); + + // Register features + registerBaseCommands(commandManager, lazyClientHost, pluginManager, activeJsTsEditorTracker); + + import('./task/taskProvider').then(module => { + disposables.add(module.register(new Lazy(() => lazyClientHost.value.serviceClient))); + }); + + disposables.add(lazilyActivateClient(lazyClientHost, pluginManager, activeJsTsEditorTracker)); + + return disposables; + },)); return getExtensionApi(onCompletionAccepted.event, pluginManager); } diff --git a/code/extensions/typescript-language-features/src/languageFeatures/codeLens/implementationsCodeLens.ts b/code/extensions/typescript-language-features/src/languageFeatures/codeLens/implementationsCodeLens.ts index 6088292f8d0..d012a6ab9d9 100644 --- a/code/extensions/typescript-language-features/src/languageFeatures/codeLens/implementationsCodeLens.ts +++ b/code/extensions/typescript-language-features/src/languageFeatures/codeLens/implementationsCodeLens.ts @@ -25,7 +25,8 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip super(client, _cachedResponse); this._register( vscode.workspace.onDidChangeConfiguration(evt => { - if (evt.affectsConfiguration(`${language.id}.implementationsCodeLens.showOnInterfaceMethods`)) { + if (evt.affectsConfiguration(`${language.id}.implementationsCodeLens.showOnInterfaceMethods`) || + evt.affectsConfiguration(`${language.id}.implementationsCodeLens.showOnAllClassMethods`)) { this.changeEmitter.fire(); } }) @@ -87,23 +88,48 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip item: Proto.NavigationTree, parent: Proto.NavigationTree | undefined ): vscode.Range | undefined { - if (item.kind === PConst.Kind.method && parent && parent.kind === PConst.Kind.interface && vscode.workspace.getConfiguration(this.language.id).get('implementationsCodeLens.showOnInterfaceMethods')) { + const cfg = vscode.workspace.getConfiguration(this.language.id); + + // Always show on interfaces + if (item.kind === PConst.Kind.interface) { return getSymbolRange(document, item); } - switch (item.kind) { - case PConst.Kind.interface: - return getSymbolRange(document, item); - - case PConst.Kind.class: - case PConst.Kind.method: - case PConst.Kind.memberVariable: - case PConst.Kind.memberGetAccessor: - case PConst.Kind.memberSetAccessor: - if (item.kindModifiers.match(/\babstract\b/g)) { - return getSymbolRange(document, item); - } - break; + + // Always show on abstract classes/properties + if ( + (item.kind === PConst.Kind.class || + item.kind === PConst.Kind.method || + item.kind === PConst.Kind.memberVariable || + item.kind === PConst.Kind.memberGetAccessor || + item.kind === PConst.Kind.memberSetAccessor) && + /\babstract\b/.test(item.kindModifiers ?? '') + ) { + return getSymbolRange(document, item); + } + + // If configured, show on interface methods + if ( + item.kind === PConst.Kind.method && + parent?.kind === PConst.Kind.interface && + cfg.get('implementationsCodeLens.showOnInterfaceMethods', false) + ) { + return getSymbolRange(document, item); + } + + + // If configured, show on all class methods + if ( + item.kind === PConst.Kind.method && + parent?.kind === PConst.Kind.class && + cfg.get('implementationsCodeLens.showOnAllClassMethods', false) + ) { + // But not private ones as these can never be overridden + if (/\bprivate\b/.test(item.kindModifiers ?? '')) { + return undefined; + } + return getSymbolRange(document, item); } + return undefined; } } diff --git a/code/extensions/typescript-language-features/src/languageFeatures/completions.ts b/code/extensions/typescript-language-features/src/languageFeatures/completions.ts index 749e74b4048..7ac732d3f86 100644 --- a/code/extensions/typescript-language-features/src/languageFeatures/completions.ts +++ b/code/extensions/typescript-language-features/src/languageFeatures/completions.ts @@ -56,7 +56,7 @@ class MyCompletionItem extends vscode.CompletionItem { public readonly document: vscode.TextDocument, public readonly tsEntry: Proto.CompletionEntry, private readonly completionContext: CompletionContext, - public readonly metadata: any | undefined, + public readonly metadata: unknown | undefined, client: ITypeScriptServiceClient, defaultCommitCharacters: readonly string[] | undefined, ) { @@ -777,7 +777,7 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider< dotAccessorContext = { range, text }; } } - const isIncomplete = !!response.body.isIncomplete || (response.metadata as any)?.isIncomplete; + const isIncomplete = !!response.body.isIncomplete || !!(response.metadata as Record)?.isIncomplete; const entries = response.body.entries; const metadata = response.metadata; const defaultCommitCharacters = Object.freeze(response.body.defaultCommitCharacters); diff --git a/code/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts b/code/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts index d86f64637c6..632baf01ff9 100644 --- a/code/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts +++ b/code/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts @@ -285,7 +285,7 @@ export class DiagnosticsManager extends Disposable { private readonly _diagnostics: ResourceMap; private readonly _settings = new DiagnosticSettings(); private readonly _currentDiagnostics: vscode.DiagnosticCollection; - private readonly _pendingUpdates: ResourceMap; + private readonly _pendingUpdates: ResourceMap; private readonly _updateDelay = 50; diff --git a/code/extensions/typescript-language-features/src/languageFeatures/fileReferences.ts b/code/extensions/typescript-language-features/src/languageFeatures/fileReferences.ts index 3a475ac257b..c9d47d6ebff 100644 --- a/code/extensions/typescript-language-features/src/languageFeatures/fileReferences.ts +++ b/code/extensions/typescript-language-features/src/languageFeatures/fileReferences.ts @@ -79,11 +79,14 @@ export function register( client: ITypeScriptServiceClient, commandManager: CommandManager ) { - function updateContext() { - vscode.commands.executeCommand('setContext', FileReferencesCommand.context, client.apiVersion.gte(FileReferencesCommand.minVersion)); + function updateContext(overrideValue?: boolean) { + vscode.commands.executeCommand('setContext', FileReferencesCommand.context, overrideValue ?? client.apiVersion.gte(FileReferencesCommand.minVersion)); } updateContext(); commandManager.register(new FileReferencesCommand(client)); - return client.onTsServerStarted(() => updateContext()); + return vscode.Disposable.from( + client.onTsServerStarted(() => updateContext()), + new vscode.Disposable(() => updateContext(false)), + ); } diff --git a/code/extensions/typescript-language-features/src/languageFeatures/organizeImports.ts b/code/extensions/typescript-language-features/src/languageFeatures/organizeImports.ts index 82199438563..25c3a92c2c2 100644 --- a/code/extensions/typescript-language-features/src/languageFeatures/organizeImports.ts +++ b/code/extensions/typescript-language-features/src/languageFeatures/organizeImports.ts @@ -57,7 +57,7 @@ class DidOrganizeImportsCommand implements Command { private readonly telemetryReporter: TelemetryReporter, ) { } - public async execute(): Promise { + public async execute(): Promise { /* __GDPR__ "organizeImports.execute" : { "owner": "mjbvz", diff --git a/code/extensions/typescript-language-features/src/languageFeatures/quickFix.ts b/code/extensions/typescript-language-features/src/languageFeatures/quickFix.ts index e32727befbd..c0c89670d1d 100644 --- a/code/extensions/typescript-language-features/src/languageFeatures/quickFix.ts +++ b/code/extensions/typescript-language-features/src/languageFeatures/quickFix.ts @@ -366,22 +366,19 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider change.textChanges.map(textChange => textChange.newText).join('')).join(''); - title = 'Add meaningful parameter name with Copilot'; - message = `Rename the parameter ${newText} with a more meaningful name.`; + title = vscode.l10n.t('Add meaningful parameter name with AI'); + message = vscode.l10n.t(`Rename the parameter {0} with a more meaningful name.`, newText); expand = { kind: 'navtree-function', pos: diagnostic.range.start diff --git a/code/extensions/typescript-language-features/src/languageFeatures/signatureHelp.ts b/code/extensions/typescript-language-features/src/languageFeatures/signatureHelp.ts index 62aeba68b55..7bb56c8cd91 100644 --- a/code/extensions/typescript-language-features/src/languageFeatures/signatureHelp.ts +++ b/code/extensions/typescript-language-features/src/languageFeatures/signatureHelp.ts @@ -105,9 +105,9 @@ function toTsTriggerReason(context: vscode.SignatureHelpContext): Proto.Signatur case vscode.SignatureHelpTriggerKind.TriggerCharacter: if (context.triggerCharacter) { if (context.isRetrigger) { - return { kind: 'retrigger', triggerCharacter: context.triggerCharacter as any }; + return { kind: 'retrigger', triggerCharacter: context.triggerCharacter as Proto.SignatureHelpRetriggerCharacter }; } else { - return { kind: 'characterTyped', triggerCharacter: context.triggerCharacter as any }; + return { kind: 'characterTyped', triggerCharacter: context.triggerCharacter as Proto.SignatureHelpTriggerCharacter }; } } else { return { kind: 'invoked' }; diff --git a/code/extensions/typescript-language-features/src/languageFeatures/sourceDefinition.ts b/code/extensions/typescript-language-features/src/languageFeatures/sourceDefinition.ts index 301f8607a1e..f3798a0a53f 100644 --- a/code/extensions/typescript-language-features/src/languageFeatures/sourceDefinition.ts +++ b/code/extensions/typescript-language-features/src/languageFeatures/sourceDefinition.ts @@ -80,12 +80,15 @@ class SourceDefinitionCommand implements Command { export function register( client: ITypeScriptServiceClient, commandManager: CommandManager -) { - function updateContext() { - vscode.commands.executeCommand('setContext', SourceDefinitionCommand.context, client.apiVersion.gte(SourceDefinitionCommand.minVersion)); +): vscode.Disposable { + function updateContext(overrideValue?: boolean) { + vscode.commands.executeCommand('setContext', SourceDefinitionCommand.context, overrideValue ?? client.apiVersion.gte(SourceDefinitionCommand.minVersion)); } updateContext(); commandManager.register(new SourceDefinitionCommand(client)); - return client.onTsServerStarted(() => updateContext()); + return vscode.Disposable.from( + client.onTsServerStarted(() => updateContext()), + new vscode.Disposable(() => updateContext(false)), + ); } diff --git a/code/extensions/typescript-language-features/src/languageFeatures/util/dependentRegistration.ts b/code/extensions/typescript-language-features/src/languageFeatures/util/dependentRegistration.ts index e234acd1ab4..8371b470997 100644 --- a/code/extensions/typescript-language-features/src/languageFeatures/util/dependentRegistration.ts +++ b/code/extensions/typescript-language-features/src/languageFeatures/util/dependentRegistration.ts @@ -34,11 +34,15 @@ export class Condition extends Disposable { } class ConditionalRegistration { - private registration: vscode.Disposable | undefined = undefined; + private state?: { + readonly enabled: boolean; + readonly registration: vscode.Disposable | undefined; + }; public constructor( private readonly conditions: readonly Condition[], - private readonly doRegister: () => vscode.Disposable + private readonly doRegister: () => vscode.Disposable, + private readonly elseDoRegister?: () => vscode.Disposable ) { for (const condition of conditions) { condition.onDidChange(() => this.update()); @@ -47,17 +51,22 @@ class ConditionalRegistration { } public dispose() { - this.registration?.dispose(); - this.registration = undefined; + this.state?.registration?.dispose(); + this.state = undefined; } private update() { const enabled = this.conditions.every(condition => condition.value); if (enabled) { - this.registration ??= this.doRegister(); + if (!this.state?.enabled) { + this.state?.registration?.dispose(); + this.state = { enabled: true, registration: this.doRegister() }; + } } else { - this.registration?.dispose(); - this.registration = undefined; + if (this.state?.enabled || !this.state) { + this.state?.registration?.dispose(); + this.state = { enabled: false, registration: this.elseDoRegister?.() }; + } } } } @@ -65,8 +74,9 @@ class ConditionalRegistration { export function conditionalRegistration( conditions: readonly Condition[], doRegister: () => vscode.Disposable, + elseDoRegister?: () => vscode.Disposable ): vscode.Disposable { - return new ConditionalRegistration(conditions, doRegister); + return new ConditionalRegistration(conditions, doRegister, elseDoRegister); } export function requireMinVersion( @@ -101,3 +111,15 @@ export function requireSomeCapability( client.onDidChangeCapabilities ); } + +export function requireHasVsCodeExtension( + extensionId: string +) { + return new Condition( + () => { + return !!vscode.extensions.getExtension(extensionId); + }, + vscode.extensions.onDidChange + ); +} + diff --git a/code/extensions/typescript-language-features/src/languageFeatures/util/textRendering.ts b/code/extensions/typescript-language-features/src/languageFeatures/util/textRendering.ts index f44ac0c4f40..ccb9e0fe3ee 100644 --- a/code/extensions/typescript-language-features/src/languageFeatures/util/textRendering.ts +++ b/code/extensions/typescript-language-features/src/languageFeatures/util/textRendering.ts @@ -163,7 +163,7 @@ function convertLinkTags( const command = `command:${OpenJsDocLinkCommand.id}?${encodeURIComponent(JSON.stringify([args]))}`; const linkText = currentLink.text ? currentLink.text : escapeMarkdownSyntaxTokensForCode(currentLink.name ?? ''); - out.push(`[${currentLink.linkcode ? '`' + linkText + '`' : linkText}](${command})`); + out.push(`[${currentLink.linkcode ? '`' + linkText + '`' : linkText}](${command} "${vscode.l10n.t('Open symbol link')}")`); } else { const text = currentLink.text ?? currentLink.name; if (text) { diff --git a/code/extensions/typescript-language-features/src/lazyClientHost.ts b/code/extensions/typescript-language-features/src/lazyClientHost.ts index 3fc8a37cd17..86a4f71d963 100644 --- a/code/extensions/typescript-language-features/src/lazyClientHost.ts +++ b/code/extensions/typescript-language-features/src/lazyClientHost.ts @@ -38,16 +38,12 @@ export function createLazyClientHost( onCompletionAccepted: (item: vscode.CompletionItem) => void, ): Lazy { return new Lazy(() => { - const clientHost = new TypeScriptServiceClientHost( + return new TypeScriptServiceClientHost( standardLanguageDescriptions, context, onCaseInsensitiveFileSystem, services, onCompletionAccepted); - - context.subscriptions.push(clientHost); - - return clientHost; }); } @@ -90,7 +86,9 @@ export function lazilyActivateClient( }, undefined, disposables); } - return vscode.Disposable.from(...disposables); + return new vscode.Disposable(() => { + disposables.forEach(d => d.dispose()); + }); } function isSupportedDocument( diff --git a/code/extensions/typescript-language-features/src/logging/logger.ts b/code/extensions/typescript-language-features/src/logging/logger.ts index 1d33a75a30b..98b4e6fb813 100644 --- a/code/extensions/typescript-language-features/src/logging/logger.ts +++ b/code/extensions/typescript-language-features/src/logging/logger.ts @@ -16,17 +16,17 @@ export class Logger { return this.output.value.logLevel; } - public info(message: string, ...args: any[]): void { + public info(message: string, ...args: unknown[]): void { this.output.value.info(message, ...args); } - public trace(message: string, ...args: any[]): void { + public trace(message: string, ...args: unknown[]): void { this.output.value.trace(message, ...args); } - public error(message: string, data?: any): void { + public error(message: string, data?: unknown): void { // See https://github.com/microsoft/TypeScript/issues/10496 - if (data && data.message === 'No content available.') { + if (data && (data as { message?: string }).message === 'No content available.') { return; } this.output.value.error(message, ...(data ? [data] : [])); diff --git a/code/extensions/typescript-language-features/src/logging/tracer.ts b/code/extensions/typescript-language-features/src/logging/tracer.ts index e273181075d..0aa9a937cd1 100644 --- a/code/extensions/typescript-language-features/src/logging/tracer.ts +++ b/code/extensions/typescript-language-features/src/logging/tracer.ts @@ -32,7 +32,7 @@ export default class Tracer extends Disposable { } } - public traceRequestCompleted(serverId: string, command: string, request_seq: number, meta: RequestExecutionMetadata): any { + public traceRequestCompleted(serverId: string, command: string, request_seq: number, meta: RequestExecutionMetadata): void { if (this.logger.logLevel === vscode.LogLevel.Trace) { this.trace(serverId, `Async response received: ${command} (${request_seq}). Request took ${Date.now() - meta.queuingStartTime} ms.`); } diff --git a/code/extensions/typescript-language-features/src/test/smoke/implementationsCodeLens.test.ts b/code/extensions/typescript-language-features/src/test/smoke/implementationsCodeLens.test.ts new file mode 100644 index 00000000000..463f9a202fe --- /dev/null +++ b/code/extensions/typescript-language-features/src/test/smoke/implementationsCodeLens.test.ts @@ -0,0 +1,169 @@ +/*--------------------------------------------------------------------------------------------- + * 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 vscode from 'vscode'; +import { disposeAll } from '../../utils/dispose'; +import { joinLines, withRandomFileEditor } from '../testUtils'; +import { updateConfig, VsCodeConfiguration } from './referencesCodeLens.test'; + +const Config = { + referencesCodeLens: 'typescript.referencesCodeLens.enabled', + implementationsCodeLens: 'typescript.implementationsCodeLens.enabled', + showOnAllClassMethods: 'typescript.implementationsCodeLens.showOnAllClassMethods', +}; + +function getCodeLenses(doc: vscode.TextDocument) { + return vscode.commands.executeCommand('vscode.executeCodeLensProvider', doc.uri); +} + +suite('TypeScript Implementations CodeLens', () => { + const configDefaults = Object.freeze({ + [Config.referencesCodeLens]: false, + [Config.implementationsCodeLens]: true, + [Config.showOnAllClassMethods]: false, + }); + + const _disposables: vscode.Disposable[] = []; + let oldConfig: { [key: string]: any } = {}; + + setup(async () => { + // the tests assume that typescript features are registered + await vscode.extensions.getExtension('vscode.typescript-language-features')!.activate(); + + // Save off config and apply defaults + oldConfig = await updateConfig(configDefaults); + }); + + teardown(async () => { + disposeAll(_disposables); + + // Restore config + await updateConfig(oldConfig); + + return vscode.commands.executeCommand('workbench.action.closeAllEditors'); + }); + + test('Should show on interfaces and abstract classes', async () => { + await withRandomFileEditor( + joinLines( + 'interface IFoo {}', + 'class Foo implements IFoo {}', + 'abstract class AbstractBase {}', + 'class Concrete extends AbstractBase {}' + ), + 'ts', + async (_editor: vscode.TextEditor, doc: vscode.TextDocument) => { + const lenses = await getCodeLenses(doc); + assert.strictEqual(lenses?.length, 2); + + assert.strictEqual(lenses?.[0].range.start.line, 0, 'Expected interface IFoo to have a CodeLens'); + assert.strictEqual(lenses?.[1].range.start.line, 2, 'Expected abstract class AbstractBase to have a CodeLens'); + }, + ); + }); + + test('Should show on abstract methods, properties, and getters', async () => { + await withRandomFileEditor( + joinLines( + 'abstract class Base {', + ' abstract method(): void;', + ' abstract property: string;', + ' abstract get getter(): number;', + '}', + 'class Derived extends Base {', + ' method() {}', + ' property = "test";', + ' get getter() { return 42; }', + '}', + ), + 'ts', + async (_editor: vscode.TextEditor, doc: vscode.TextDocument) => { + const lenses = await getCodeLenses(doc); + assert.strictEqual(lenses?.length, 4); + + assert.strictEqual(lenses?.[0].range.start.line, 0, 'Expected abstract class to have a CodeLens'); + assert.strictEqual(lenses?.[1].range.start.line, 1, 'Expected abstract method to have a CodeLens'); + assert.strictEqual(lenses?.[2].range.start.line, 2, 'Expected abstract property to have a CodeLens'); + assert.strictEqual(lenses?.[3].range.start.line, 3, 'Expected abstract getter to have a CodeLens'); + }, + ); + }); + + test('Should not show implementations on methods by default', async () => { + await withRandomFileEditor( + joinLines( + 'abstract class A {', + ' foo() {}', + '}', + 'class B extends A {', + ' foo() {}', + '}', + ), + 'ts', + async (_editor: vscode.TextEditor, doc: vscode.TextDocument) => { + const lenses = await getCodeLenses(doc); + assert.strictEqual(lenses?.length, 1); + }, + ); + }); + + test('should show on all methods when showOnAllClassMethods is enabled', async () => { + await updateConfig({ + [Config.showOnAllClassMethods]: true + }); + + await withRandomFileEditor( + joinLines( + 'abstract class A {', + ' foo() {}', + '}', + 'class B extends A {', + ' foo() {}', + '}', + ), + 'ts', + async (_editor: vscode.TextEditor, doc: vscode.TextDocument) => { + const lenses = await getCodeLenses(doc); + assert.strictEqual(lenses?.length, 3); + + assert.strictEqual(lenses?.[0].range.start.line, 0, 'Expected class A to have a CodeLens'); + assert.strictEqual(lenses?.[1].range.start.line, 1, 'Expected method A.foo to have a CodeLens'); + assert.strictEqual(lenses?.[2].range.start.line, 4, 'Expected method B.foo to have a CodeLens'); + }, + ); + }); + + test('should not show on private methods when showOnAllClassMethods is enabled', async () => { + await updateConfig({ + [Config.showOnAllClassMethods]: true + }); + + await withRandomFileEditor( + joinLines( + 'abstract class A {', + ' public foo() {}', + ' private bar() {}', + ' protected baz() {}', + '}', + 'class B extends A {', + ' public foo() {}', + ' protected baz() {}', + '}', + ), + 'ts', + async (_editor: vscode.TextEditor, doc: vscode.TextDocument) => { + const lenses = await getCodeLenses(doc); + assert.strictEqual(lenses?.length, 5); + + assert.strictEqual(lenses?.[0].range.start.line, 0, 'Expected class A to have a CodeLens'); + assert.strictEqual(lenses?.[1].range.start.line, 1, 'Expected method A.foo to have a CodeLens'); + assert.strictEqual(lenses?.[2].range.start.line, 3, 'Expected method A.baz to have a CodeLens'); + assert.strictEqual(lenses?.[3].range.start.line, 6, 'Expected method B.foo to have a CodeLens'); + assert.strictEqual(lenses?.[4].range.start.line, 7, 'Expected method B.baz to have a CodeLens'); + }, + ); + }); +}); diff --git a/code/extensions/typescript-language-features/src/test/smoke/referencesCodeLens.test.ts b/code/extensions/typescript-language-features/src/test/smoke/referencesCodeLens.test.ts index f6259e0d276..7e068249a2e 100644 --- a/code/extensions/typescript-language-features/src/test/smoke/referencesCodeLens.test.ts +++ b/code/extensions/typescript-language-features/src/test/smoke/referencesCodeLens.test.ts @@ -10,9 +10,9 @@ import { createTestEditor, wait } from '../../test/testUtils'; import { disposeAll } from '../../utils/dispose'; -type VsCodeConfiguration = { [key: string]: any }; +export type VsCodeConfiguration = { [key: string]: any }; -async function updateConfig(newConfig: VsCodeConfiguration): Promise { +export async function updateConfig(newConfig: VsCodeConfiguration): Promise { const oldConfig: VsCodeConfiguration = {}; const config = vscode.workspace.getConfiguration(undefined); for (const configKey of Object.keys(newConfig)) { diff --git a/code/extensions/typescript-language-features/src/test/unit/functionCallSnippet.test.ts b/code/extensions/typescript-language-features/src/test/unit/functionCallSnippet.test.ts index 00f006a05a4..f0f1874a639 100644 --- a/code/extensions/typescript-language-features/src/test/unit/functionCallSnippet.test.ts +++ b/code/extensions/typescript-language-features/src/test/unit/functionCallSnippet.test.ts @@ -139,7 +139,7 @@ suite('typescript function call snippets', () => { assert.strictEqual( snippetForFunctionCall( { label: 'foobar', }, - [{ "text": "function", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "foobar", "kind": "functionName" }, { "text": "(", "kind": "punctuation" }, { "text": "alpha", "kind": "parameterName" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "string", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "beta", "kind": "parameterName" }, { "text": "?", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "|", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "undefined", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "gamma", "kind": "parameterName" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "string", "kind": "keyword" }, { "text": ")", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "void", "kind": "keyword" }] + [{ 'text': 'function', 'kind': 'keyword' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'foobar', 'kind': 'functionName' }, { 'text': '(', 'kind': 'punctuation' }, { 'text': 'alpha', 'kind': 'parameterName' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'string', 'kind': 'keyword' }, { 'text': ',', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'beta', 'kind': 'parameterName' }, { 'text': '?', 'kind': 'punctuation' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'number', 'kind': 'keyword' }, { 'text': ' ', 'kind': 'space' }, { 'text': '|', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'undefined', 'kind': 'keyword' }, { 'text': ',', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'gamma', 'kind': 'parameterName' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'string', 'kind': 'keyword' }, { 'text': ')', 'kind': 'punctuation' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'void', 'kind': 'keyword' }] ).snippet.value, 'foobar(${1:alpha}, ${2:beta}, ${3:gamma}$4)$0'); }); @@ -148,7 +148,7 @@ suite('typescript function call snippets', () => { assert.strictEqual( snippetForFunctionCall( { label: 'foobar', }, - [{ "text": "function", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "foobar", "kind": "functionName" }, { "text": "(", "kind": "punctuation" }, { "text": "alpha", "kind": "parameterName" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "string", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "beta", "kind": "parameterName" }, { "text": "?", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "|", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "undefined", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "gamma", "kind": "parameterName" }, { "text": "?", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "|", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "undefined", "kind": "keyword" }, { "text": ")", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "void", "kind": "keyword" }] + [{ 'text': 'function', 'kind': 'keyword' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'foobar', 'kind': 'functionName' }, { 'text': '(', 'kind': 'punctuation' }, { 'text': 'alpha', 'kind': 'parameterName' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'string', 'kind': 'keyword' }, { 'text': ',', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'beta', 'kind': 'parameterName' }, { 'text': '?', 'kind': 'punctuation' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'number', 'kind': 'keyword' }, { 'text': ' ', 'kind': 'space' }, { 'text': '|', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'undefined', 'kind': 'keyword' }, { 'text': ',', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'gamma', 'kind': 'parameterName' }, { 'text': '?', 'kind': 'punctuation' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'number', 'kind': 'keyword' }, { 'text': ' ', 'kind': 'space' }, { 'text': '|', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'undefined', 'kind': 'keyword' }, { 'text': ')', 'kind': 'punctuation' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'void', 'kind': 'keyword' }] ).snippet.value, 'foobar(${1:alpha}$2)$0'); }); @@ -158,9 +158,9 @@ suite('typescript function call snippets', () => { assert.strictEqual( snippetForFunctionCall( { label: 'foobar', }, - [{ "text": "function", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "foobar", "kind": "functionName" }, { "text": "(", "kind": "punctuation" }, { "text": "a", "kind": "parameterName" }, { "text": "?", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "|", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "undefined", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "b", "kind": "parameterName" }, { "text": "?", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "|", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "undefined", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "c", "kind": "parameterName" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "string", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, - { "text": "d", "kind": "parameterName" }, { "text": "?", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "|", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "undefined", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "e", "kind": "parameterName" }, { "text": "?", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "|", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "undefined", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "f", "kind": "parameterName" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "string", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, - { "text": "g", "kind": "parameterName" }, { "text": "?", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "|", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "undefined", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "h", "kind": "parameterName" }, { "text": "?", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "|", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "undefined", "kind": "keyword" }, { "text": ")", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "void", "kind": "keyword" }] + [{ 'text': 'function', 'kind': 'keyword' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'foobar', 'kind': 'functionName' }, { 'text': '(', 'kind': 'punctuation' }, { 'text': 'a', 'kind': 'parameterName' }, { 'text': '?', 'kind': 'punctuation' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'number', 'kind': 'keyword' }, { 'text': ' ', 'kind': 'space' }, { 'text': '|', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'undefined', 'kind': 'keyword' }, { 'text': ',', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'b', 'kind': 'parameterName' }, { 'text': '?', 'kind': 'punctuation' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'number', 'kind': 'keyword' }, { 'text': ' ', 'kind': 'space' }, { 'text': '|', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'undefined', 'kind': 'keyword' }, { 'text': ',', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'c', 'kind': 'parameterName' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'string', 'kind': 'keyword' }, { 'text': ',', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, + { 'text': 'd', 'kind': 'parameterName' }, { 'text': '?', 'kind': 'punctuation' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'number', 'kind': 'keyword' }, { 'text': ' ', 'kind': 'space' }, { 'text': '|', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'undefined', 'kind': 'keyword' }, { 'text': ',', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'e', 'kind': 'parameterName' }, { 'text': '?', 'kind': 'punctuation' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'number', 'kind': 'keyword' }, { 'text': ' ', 'kind': 'space' }, { 'text': '|', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'undefined', 'kind': 'keyword' }, { 'text': ',', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'f', 'kind': 'parameterName' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'string', 'kind': 'keyword' }, { 'text': ',', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, + { 'text': 'g', 'kind': 'parameterName' }, { 'text': '?', 'kind': 'punctuation' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'number', 'kind': 'keyword' }, { 'text': ' ', 'kind': 'space' }, { 'text': '|', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'undefined', 'kind': 'keyword' }, { 'text': ',', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'h', 'kind': 'parameterName' }, { 'text': '?', 'kind': 'punctuation' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'number', 'kind': 'keyword' }, { 'text': ' ', 'kind': 'space' }, { 'text': '|', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'undefined', 'kind': 'keyword' }, { 'text': ')', 'kind': 'punctuation' }, { 'text': ':', 'kind': 'punctuation' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'void', 'kind': 'keyword' }] ).snippet.value, 'foobar(${1:a}, ${2:b}, ${3:c}, ${4:d}, ${5:e}, ${6:f}$7)$0'); }); diff --git a/code/extensions/typescript-language-features/src/test/unit/textRendering.test.ts b/code/extensions/typescript-language-features/src/test/unit/textRendering.test.ts index 278bf9de412..7bbbb0e7835 100644 --- a/code/extensions/typescript-language-features/src/test/unit/textRendering.test.ts +++ b/code/extensions/typescript-language-features/src/test/unit/textRendering.test.ts @@ -29,7 +29,7 @@ suite('typescript.previewer', () => { assert.strictEqual( documentationToMarkdown( // 'x {@link http://www.example.com/foo} y {@link https://api.jquery.com/bind/#bind-eventType-eventData-handler} z', - [{ "text": "x ", "kind": "text" }, { "text": "{@link ", "kind": "link" }, { "text": "http://www.example.com/foo", "kind": "linkText" }, { "text": "}", "kind": "link" }, { "text": " y ", "kind": "text" }, { "text": "{@link ", "kind": "link" }, { "text": "https://api.jquery.com/bind/#bind-eventType-eventData-handler", "kind": "linkText" }, { "text": "}", "kind": "link" }, { "text": " z", "kind": "text" }], + [{ 'text': 'x ', 'kind': 'text' }, { 'text': '{@link ', 'kind': 'link' }, { 'text': 'http://www.example.com/foo', 'kind': 'linkText' }, { 'text': '}', 'kind': 'link' }, { 'text': ' y ', 'kind': 'text' }, { 'text': '{@link ', 'kind': 'link' }, { 'text': 'https://api.jquery.com/bind/#bind-eventType-eventData-handler', 'kind': 'linkText' }, { 'text': '}', 'kind': 'link' }, { 'text': ' z', 'kind': 'text' }], [], noopToResource, undefined ).value, @@ -40,7 +40,7 @@ suite('typescript.previewer', () => { assert.strictEqual( documentationToMarkdown( // 'x {@link http://www.example.com/foo abc xyz} y {@link http://www.example.com/bar|b a z} z', - [{ "text": "x ", "kind": "text" }, { "text": "{@link ", "kind": "link" }, { "text": "http://www.example.com/foo abc xyz", "kind": "linkText" }, { "text": "}", "kind": "link" }, { "text": " y ", "kind": "text" }, { "text": "{@link ", "kind": "link" }, { "text": "http://www.example.com/bar b a z", "kind": "linkText" }, { "text": "}", "kind": "link" }, { "text": " z", "kind": "text" }], + [{ 'text': 'x ', 'kind': 'text' }, { 'text': '{@link ', 'kind': 'link' }, { 'text': 'http://www.example.com/foo abc xyz', 'kind': 'linkText' }, { 'text': '}', 'kind': 'link' }, { 'text': ' y ', 'kind': 'text' }, { 'text': '{@link ', 'kind': 'link' }, { 'text': 'http://www.example.com/bar b a z', 'kind': 'linkText' }, { 'text': '}', 'kind': 'link' }, { 'text': ' z', 'kind': 'text' }], [], noopToResource, undefined ).value, @@ -51,7 +51,7 @@ suite('typescript.previewer', () => { assert.strictEqual( documentationToMarkdown( // 'x {@linkcode http://www.example.com/foo} y {@linkplain http://www.example.com/bar} z', - [{ "text": "x ", "kind": "text" }, { "text": "{@linkcode ", "kind": "link" }, { "text": "http://www.example.com/foo", "kind": "linkText" }, { "text": "}", "kind": "link" }, { "text": " y ", "kind": "text" }, { "text": "{@linkplain ", "kind": "link" }, { "text": "http://www.example.com/bar", "kind": "linkText" }, { "text": "}", "kind": "link" }, { "text": " z", "kind": "text" }], + [{ 'text': 'x ', 'kind': 'text' }, { 'text': '{@linkcode ', 'kind': 'link' }, { 'text': 'http://www.example.com/foo', 'kind': 'linkText' }, { 'text': '}', 'kind': 'link' }, { 'text': ' y ', 'kind': 'text' }, { 'text': '{@linkplain ', 'kind': 'link' }, { 'text': 'http://www.example.com/bar', 'kind': 'linkText' }, { 'text': '}', 'kind': 'link' }, { 'text': ' z', 'kind': 'text' }], [], noopToResource, undefined ).value, @@ -64,7 +64,7 @@ suite('typescript.previewer', () => { { name: 'param', // a x {@link http://www.example.com/foo abc xyz} y {@link http://www.example.com/bar|b a z} z - text: [{ "text": "a", "kind": "parameterName" }, { "text": " ", "kind": "space" }, { "text": "x ", "kind": "text" }, { "text": "{@link ", "kind": "link" }, { "text": "http://www.example.com/foo abc xyz", "kind": "linkText" }, { "text": "}", "kind": "link" }, { "text": " y ", "kind": "text" }, { "text": "{@link ", "kind": "link" }, { "text": "http://www.example.com/bar b a z", "kind": "linkText" }, { "text": "}", "kind": "link" }, { "text": " z", "kind": "text" }], + text: [{ 'text': 'a', 'kind': 'parameterName' }, { 'text': ' ', 'kind': 'space' }, { 'text': 'x ', 'kind': 'text' }, { 'text': '{@link ', 'kind': 'link' }, { 'text': 'http://www.example.com/foo abc xyz', 'kind': 'linkText' }, { 'text': '}', 'kind': 'link' }, { 'text': ' y ', 'kind': 'text' }, { 'text': '{@link ', 'kind': 'link' }, { 'text': 'http://www.example.com/bar b a z', 'kind': 'linkText' }, { 'text': '}', 'kind': 'link' }, { 'text': ' z', 'kind': 'text' }], } ], noopToResource), '*@param* `a` — x [abc xyz](http://www.example.com/foo) y [b a z](http://www.example.com/bar) z'); @@ -133,23 +133,23 @@ suite('typescript.previewer', () => { assert.strictEqual( tagsToMarkdown([ { - "name": "example", - "text": [ + 'name': 'example', + 'text': [ { - "text": "1 + 1 ", - "kind": "text" + 'text': '1 + 1 ', + 'kind': 'text' }, { - "text": "{@link ", - "kind": "link" + 'text': '{@link ', + 'kind': 'link' }, { - "text": "foo", - "kind": "linkName" + 'text': 'foo', + 'kind': 'linkName' }, { - "text": "}", - "kind": "link" + 'text': '}', + 'kind': 'link' } ] } @@ -160,41 +160,41 @@ suite('typescript.previewer', () => { test('Should render @linkcode symbol name as code', () => { assert.strictEqual( asPlainTextWithLinks([ - { "text": "a ", "kind": "text" }, - { "text": "{@linkcode ", "kind": "link" }, + { 'text': 'a ', 'kind': 'text' }, + { 'text': '{@linkcode ', 'kind': 'link' }, { - "text": "dog", - "kind": "linkName", - "target": { - "file": "/path/file.ts", - "start": { "line": 7, "offset": 5 }, - "end": { "line": 7, "offset": 13 } + 'text': 'dog', + 'kind': 'linkName', + 'target': { + 'file': '/path/file.ts', + 'start': { 'line': 7, 'offset': 5 }, + 'end': { 'line': 7, 'offset': 13 } } } as SymbolDisplayPart, - { "text": "}", "kind": "link" }, - { "text": " b", "kind": "text" } + { 'text': '}', 'kind': 'link' }, + { 'text': ' b', 'kind': 'text' } ], noopToResource), - 'a [`dog`](command:_typescript.openJsDocLink?%5B%7B%22file%22%3A%7B%22path%22%3A%22%2Fpath%2Ffile.ts%22%2C%22scheme%22%3A%22file%22%7D%2C%22position%22%3A%7B%22line%22%3A6%2C%22character%22%3A4%7D%7D%5D) b'); + 'a [`dog`](command:_typescript.openJsDocLink?%5B%7B%22file%22%3A%7B%22path%22%3A%22%2Fpath%2Ffile.ts%22%2C%22scheme%22%3A%22file%22%7D%2C%22position%22%3A%7B%22line%22%3A6%2C%22character%22%3A4%7D%7D%5D "Open symbol link") b'); }); test('Should render @linkcode text as code', () => { assert.strictEqual( asPlainTextWithLinks([ - { "text": "a ", "kind": "text" }, - { "text": "{@linkcode ", "kind": "link" }, + { 'text': 'a ', 'kind': 'text' }, + { 'text': '{@linkcode ', 'kind': 'link' }, { - "text": "dog", - "kind": "linkName", - "target": { - "file": "/path/file.ts", - "start": { "line": 7, "offset": 5 }, - "end": { "line": 7, "offset": 13 } + 'text': 'dog', + 'kind': 'linkName', + 'target': { + 'file': '/path/file.ts', + 'start': { 'line': 7, 'offset': 5 }, + 'end': { 'line': 7, 'offset': 13 } } } as SymbolDisplayPart, - { "text": "husky", "kind": "linkText" }, - { "text": "}", "kind": "link" }, - { "text": " b", "kind": "text" } + { 'text': 'husky', 'kind': 'linkText' }, + { 'text': '}', 'kind': 'link' }, + { 'text': ' b', 'kind': 'text' } ], noopToResource), - 'a [`husky`](command:_typescript.openJsDocLink?%5B%7B%22file%22%3A%7B%22path%22%3A%22%2Fpath%2Ffile.ts%22%2C%22scheme%22%3A%22file%22%7D%2C%22position%22%3A%7B%22line%22%3A6%2C%22character%22%3A4%7D%7D%5D) b'); + 'a [`husky`](command:_typescript.openJsDocLink?%5B%7B%22file%22%3A%7B%22path%22%3A%22%2Fpath%2Ffile.ts%22%2C%22scheme%22%3A%22file%22%7D%2C%22position%22%3A%7B%22line%22%3A6%2C%22character%22%3A4%7D%7D%5D "Open symbol link") b'); }); }); diff --git a/code/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts b/code/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts index 7b47be8d32f..04e068916ed 100644 --- a/code/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts +++ b/code/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts @@ -168,7 +168,7 @@ class SyncedBuffer { ) { } public open(): void { - const args: Proto.OpenRequestArgs = { + const args: Proto.OpenRequestArgs & { plugins?: string[] } = { file: this.filepath, fileContent: this.document.getText(), projectRootPath: this.getProjectRootPath(this.document.uri), @@ -183,7 +183,7 @@ class SyncedBuffer { .filter(x => x.languages.indexOf(this.document.languageId) >= 0); if (tsPluginsForDocument.length) { - (args as any).plugins = tsPluginsForDocument.map(plugin => plugin.name); + args.plugins = tsPluginsForDocument.map(plugin => plugin.name); } this.synchronizer.open(this.resource, args); @@ -349,15 +349,15 @@ class GetErrRequest { } } - private areProjectDiagnosticsEnabled() { + private areProjectDiagnosticsEnabled(): boolean { return this.client.configuration.enableProjectDiagnostics && this.client.capabilities.has(ClientCapability.Semantic); } - private areRegionDiagnosticsEnabled() { + private areRegionDiagnosticsEnabled(): boolean { return this.client.configuration.enableRegionDiagnostics && this.client.apiVersion.gte(API.v560); } - public cancel(): any { + public cancel(): void { if (!this._done) { this._token.cancel(); } diff --git a/code/extensions/typescript-language-features/src/tsServer/plugins.ts b/code/extensions/typescript-language-features/src/tsServer/plugins.ts index 6036e4c968b..a4f56f03e4e 100644 --- a/code/extensions/typescript-language-features/src/tsServer/plugins.ts +++ b/code/extensions/typescript-language-features/src/tsServer/plugins.ts @@ -26,7 +26,7 @@ namespace TypeScriptServerPlugin { } export class PluginManager extends Disposable { - private readonly _pluginConfigurations = new Map(); + private readonly _pluginConfigurations = new Map(); private _plugins?: Map>; @@ -54,15 +54,15 @@ export class PluginManager extends Disposable { private readonly _onDidUpdatePlugins = this._register(new vscode.EventEmitter()); public readonly onDidChangePlugins = this._onDidUpdatePlugins.event; - private readonly _onDidUpdateConfig = this._register(new vscode.EventEmitter<{ pluginId: string; config: {} }>()); + private readonly _onDidUpdateConfig = this._register(new vscode.EventEmitter<{ pluginId: string; config: unknown }>()); public readonly onDidUpdateConfig = this._onDidUpdateConfig.event; - public setConfiguration(pluginId: string, config: {}) { + public setConfiguration(pluginId: string, config: unknown) { this._pluginConfigurations.set(pluginId, config); this._onDidUpdateConfig.fire({ pluginId, config }); } - public configurations(): IterableIterator<[string, {}]> { + public configurations(): IterableIterator<[string, unknown]> { return this._pluginConfigurations.entries(); } diff --git a/code/extensions/typescript-language-features/src/tsServer/requestQueue.ts b/code/extensions/typescript-language-features/src/tsServer/requestQueue.ts index 204e386c1bc..aa461128098 100644 --- a/code/extensions/typescript-language-features/src/tsServer/requestQueue.ts +++ b/code/extensions/typescript-language-features/src/tsServer/requestQueue.ts @@ -86,7 +86,7 @@ export class RequestQueue { return false; } - public createRequest(command: string, args: any): Proto.Request { + public createRequest(command: string, args: unknown): Proto.Request { return { seq: this.sequenceNumber++, type: 'request', diff --git a/code/extensions/typescript-language-features/src/tsServer/server.ts b/code/extensions/typescript-language-features/src/tsServer/server.ts index 3374adc39e5..58b94ff46bf 100644 --- a/code/extensions/typescript-language-features/src/tsServer/server.ts +++ b/code/extensions/typescript-language-features/src/tsServer/server.ts @@ -5,6 +5,7 @@ import { Cancellation } from '@vscode/sync-api-common/lib/common/messageCancellation'; import * as vscode from 'vscode'; +import { RequestArgs } from '../commands/tsserverRequests'; import { TypeScriptServiceConfiguration } from '../configuration/configuration'; import { TelemetryReporter } from '../logging/telemetry'; import Tracer from '../logging/tracer'; @@ -15,11 +16,11 @@ import { ServerResponse, ServerType, TypeScriptRequests } from '../typescriptSer import { Disposable } from '../utils/dispose'; import { isWebAndHasSharedArrayBuffers } from '../utils/platform'; import { OngoingRequestCanceller } from './cancellation'; +import { NodeVersionManager } from './nodeManager'; import type * as Proto from './protocol/protocol'; import { EventName } from './protocol/protocol.const'; import { TypeScriptVersionManager } from './versionManager'; import { TypeScriptVersion } from './versionProvider'; -import { NodeVersionManager } from './nodeManager'; export enum ExecutionTarget { Semantic, @@ -38,7 +39,7 @@ export type TsServerLog = export interface ITypeScriptServer { readonly onEvent: vscode.Event; readonly onExit: vscode.Event; - readonly onError: vscode.Event; + readonly onError: vscode.Event; readonly tsServerLog: TsServerLog | undefined; @@ -48,7 +49,7 @@ export interface ITypeScriptServer { * @return A list of all execute requests. If there are multiple entries, the first item is the primary * request while the rest are secondary ones. */ - executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean; token?: vscode.CancellationToken; expectsResult: boolean; lowPriority?: boolean; executionTarget?: ExecutionTarget }): Array> | undefined>; + executeImpl(command: keyof TypeScriptRequests, args: unknown, executeInfo: { isAsync: boolean; token?: vscode.CancellationToken; expectsResult: boolean; lowPriority?: boolean; executionTarget?: ExecutionTarget }): Array> | undefined>; dispose(): void; } @@ -124,7 +125,7 @@ export class SingleTsServer extends Disposable implements ITypeScriptServer { private readonly _onExit = this._register(new vscode.EventEmitter()); public readonly onExit = this._onExit.event; - private readonly _onError = this._register(new vscode.EventEmitter()); + private readonly _onError = this._register(new vscode.EventEmitter()); public readonly onError = this._onError.event; public get tsServerLog() { return this._tsServerLog; } @@ -283,7 +284,8 @@ export class SingleTsServer extends Disposable implements ITypeScriptServer { } this._requestQueue.enqueue(requestInfo); - if (args && typeof (args as any).$traceId === 'string') { + const traceId = (args as RequestArgs | undefined)?.$traceId; + if (args && typeof traceId === 'string') { const queueLength = this._requestQueue.length - 1; const pendingResponses = this._pendingResponses.size; const data: { command: string; queueLength: number; pendingResponses: number; queuedCommands?: string[]; pendingCommands?: string[] } = { @@ -298,7 +300,7 @@ export class SingleTsServer extends Disposable implements ITypeScriptServer { data.pendingCommands = this.getPendingCommands(); } - this._telemetryReporter.logTraceEvent('TSServer.enqueueRequest', (args as any).$traceId, JSON.stringify(data)); + this._telemetryReporter.logTraceEvent('TSServer.enqueueRequest', traceId, JSON.stringify(data)); } this.sendNextRequests(); @@ -404,7 +406,7 @@ class RequestRouter { public execute( command: keyof TypeScriptRequests, - args: any, + args: unknown, executeInfo: ExecuteInfo, ): Array> | undefined> { if (RequestRouter.sharedCommands.has(command) && typeof executeInfo.executionTarget === 'undefined') { @@ -526,7 +528,7 @@ export class GetErrRoutingTsServer extends Disposable implements ITypeScriptServ private readonly _onExit = this._register(new vscode.EventEmitter()); public readonly onExit = this._onExit.event; - private readonly _onError = this._register(new vscode.EventEmitter()); + private readonly _onError = this._register(new vscode.EventEmitter()); public readonly onError = this._onError.event; public get tsServerLog() { return this.mainServer.tsServerLog; } @@ -536,7 +538,7 @@ export class GetErrRoutingTsServer extends Disposable implements ITypeScriptServ this.mainServer.kill(); } - public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean; token?: vscode.CancellationToken; expectsResult: boolean; lowPriority?: boolean; executionTarget?: ExecutionTarget }): Array> | undefined> { + public executeImpl(command: keyof TypeScriptRequests, args: unknown, executeInfo: { isAsync: boolean; token?: vscode.CancellationToken; expectsResult: boolean; lowPriority?: boolean; executionTarget?: ExecutionTarget }): Array> | undefined> { return this.router.execute(command, args, executeInfo); } } @@ -664,10 +666,10 @@ export class SyntaxRoutingTsServer extends Disposable implements ITypeScriptServ private readonly _onEvent = this._register(new vscode.EventEmitter()); public readonly onEvent = this._onEvent.event; - private readonly _onExit = this._register(new vscode.EventEmitter()); + private readonly _onExit = this._register(new vscode.EventEmitter()); public readonly onExit = this._onExit.event; - private readonly _onError = this._register(new vscode.EventEmitter()); + private readonly _onError = this._register(new vscode.EventEmitter()); public readonly onError = this._onError.event; public get tsServerLog() { return this.semanticServer.tsServerLog; } @@ -677,7 +679,7 @@ export class SyntaxRoutingTsServer extends Disposable implements ITypeScriptServ this.semanticServer.kill(); } - public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean; token?: vscode.CancellationToken; expectsResult: boolean; lowPriority?: boolean; executionTarget?: ExecutionTarget }): Array> | undefined> { + public executeImpl(command: keyof TypeScriptRequests, args: unknown, executeInfo: { isAsync: boolean; token?: vscode.CancellationToken; expectsResult: boolean; lowPriority?: boolean; executionTarget?: ExecutionTarget }): Array> | undefined> { return this.router.execute(command, args, executeInfo); } } diff --git a/code/extensions/typescript-language-features/src/tsServer/versionManager.ts b/code/extensions/typescript-language-features/src/tsServer/versionManager.ts index 43a2413e383..dcfee493f43 100644 --- a/code/extensions/typescript-language-features/src/tsServer/versionManager.ts +++ b/code/extensions/typescript-language-features/src/tsServer/versionManager.ts @@ -5,6 +5,7 @@ import * as vscode from 'vscode'; import { TypeScriptServiceConfiguration } from '../configuration/configuration'; +import { tsNativeExtensionId } from '../commands/useTsgo'; import { setImmediate } from '../utils/async'; import { Disposable } from '../utils/dispose'; import { ITypeScriptVersionProvider, TypeScriptVersion } from './versionProvider'; @@ -77,16 +78,26 @@ export class TypeScriptVersionManager extends Disposable { } public async promptUserForVersion(): Promise { - const selected = await vscode.window.showQuickPick([ + const nativePreviewItem = this.getNativePreviewPickItem(); + const items: QuickPickItem[] = [ this.getBundledPickItem(), ...this.getLocalPickItems(), + ]; + + if (nativePreviewItem) { + items.push(nativePreviewItem); + } + + items.push( { kind: vscode.QuickPickItemKind.Separator, label: '', run: () => { /* noop */ }, }, LearnMorePickItem, - ], { + ); + + const selected = await vscode.window.showQuickPick(items, { placeHolder: vscode.l10n.t("Select the TypeScript version used for JavaScript and TypeScript language features"), }); @@ -129,6 +140,24 @@ export class TypeScriptVersionManager extends Disposable { }); } + private getNativePreviewPickItem(): QuickPickItem | undefined { + const nativePreviewExtension = vscode.extensions.getExtension(tsNativeExtensionId); + if (!nativePreviewExtension) { + return undefined; + } + + const tsConfig = vscode.workspace.getConfiguration('typescript'); + const isUsingTsgo = tsConfig.get('experimental.useTsgo', false); + + return { + label: (isUsingTsgo ? '• ' : '') + vscode.l10n.t("Use TypeScript Native Preview (Experimental)"), + description: nativePreviewExtension.packageJSON.version, + run: async () => { + await vscode.commands.executeCommand('typescript.native-preview.enable'); + }, + }; + } + private async promptUseWorkspaceTsdk(): Promise { const workspaceVersion = this.versionProvider.localVersion; diff --git a/code/extensions/typescript-language-features/src/tsServer/versionProvider.electron.ts b/code/extensions/typescript-language-features/src/tsServer/versionProvider.electron.ts index 239519e6f6a..12cb1ccadb4 100644 --- a/code/extensions/typescript-language-features/src/tsServer/versionProvider.electron.ts +++ b/code/extensions/typescript-language-features/src/tsServer/versionProvider.electron.ts @@ -183,7 +183,7 @@ export class DiskTypeScriptVersionProvider implements ITypeScriptVersionProvider } const contents = fs.readFileSync(fileName).toString(); - let desc: any = null; + let desc: any; try { desc = JSON.parse(contents); } catch (err) { diff --git a/code/extensions/typescript-language-features/src/typescriptServiceClient.ts b/code/extensions/typescript-language-features/src/typescriptServiceClient.ts index 4201d6da29b..207698de92f 100644 --- a/code/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/code/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -108,7 +108,6 @@ interface WatchEvent { export default class TypeScriptServiceClient extends Disposable implements ITypeScriptServiceClient { - private readonly _onReady?: { promise: Promise; resolve: () => void; reject: () => void }; private _configuration: TypeScriptServiceConfiguration; private readonly pluginPathsProvider: TypeScriptPluginPathsProvider; @@ -236,7 +235,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType return this.apiVersion.fullVersionString; }); - this.diagnosticsManager = new DiagnosticsManager('typescript', this._configuration, this.telemetryReporter, onCaseInsensitiveFileSystem); + this.diagnosticsManager = this._register(new DiagnosticsManager('typescript', this._configuration, this.telemetryReporter, onCaseInsensitiveFileSystem)); this.typescriptServerSpawner = new TypeScriptServerSpawner(this.versionProvider, this._versionManager, this._nodeVersionManager, this.logDirectoryProvider, this.pluginPathsProvider, this.logger, this.telemetryReporter, this.tracer, this.processFactory); this._register(this.pluginManager.onDidUpdateConfig(update => { @@ -445,12 +444,17 @@ export default class TypeScriptServiceClient extends Disposable implements IType typeScriptVersionSource: version.source, }); - handle.onError((err: Error) => { + handle.onError((err: unknown) => { if (this.token !== mytoken) { // this is coming from an old process return; } + if (!(err instanceof Error)) { + this.logger.error('TSServer got unknown error type:', err); + return; + } + if (err) { vscode.window.showErrorMessage(vscode.l10n.t("TypeScript language server exited with error. Error message is: {0}", err.message || err.name)); } @@ -632,6 +636,10 @@ export default class TypeScriptServiceClient extends Disposable implements IType this.serverState = ServerState.None; + if (this.isDisposed) { + return; + } + if (restart) { const diff = Date.now() - this.lastStart; this.numberRestarts++; @@ -848,7 +856,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType return vscode.workspace.getWorkspaceFolder(resource)?.uri; } - public execute(command: keyof TypeScriptRequests, args: any, token: vscode.CancellationToken, config?: ExecConfig): Promise> { + public execute(command: keyof TypeScriptRequests, args: unknown, token: vscode.CancellationToken, config?: ExecConfig): Promise> { let executions: Array> | undefined> | undefined; if (config?.cancelOnResourceChange) { @@ -904,7 +912,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType return executions[0]!; } - public executeWithoutWaitingForResponse(command: keyof TypeScriptRequests, args: any): void { + public executeWithoutWaitingForResponse(command: keyof TypeScriptRequests, args: unknown): void { this.executeImpl(command, args, { isAsync: false, token: undefined, @@ -920,7 +928,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType })[0]!; } - private executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean; token?: vscode.CancellationToken; expectsResult: boolean; lowPriority?: boolean; requireSemantic?: boolean }): Array> | undefined> { + private executeImpl(command: keyof TypeScriptRequests, args: unknown, executeInfo: { isAsync: boolean; token?: vscode.CancellationToken; expectsResult: boolean; lowPriority?: boolean; requireSemantic?: boolean }): Array> | undefined> { const serverState = this.serverState; if (serverState.type === ServerState.Type.Running) { this.bufferSyncSupport.beforeCommand(command); @@ -1227,7 +1235,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType this.telemetryReporter.logTelemetry(telemetryData.telemetryEventName, properties); } - private configurePlugin(pluginName: string, configuration: {}): any { + private configurePlugin(pluginName: string, configuration: unknown): void { this.executeWithoutWaitingForResponse('configurePlugin', { pluginName, configuration }); } } diff --git a/code/extensions/typescript-language-features/src/ui/managedFileContext.ts b/code/extensions/typescript-language-features/src/ui/managedFileContext.ts index 1da4588a334..c66b90c9e82 100644 --- a/code/extensions/typescript-language-features/src/ui/managedFileContext.ts +++ b/code/extensions/typescript-language-features/src/ui/managedFileContext.ts @@ -18,13 +18,20 @@ export default class ManagedFileContextManager extends Disposable { private isInManagedFileContext: boolean = false; - public constructor(activeJsTsEditorTracker: ActiveJsTsEditorTracker) { + constructor(activeJsTsEditorTracker: ActiveJsTsEditorTracker) { super(); activeJsTsEditorTracker.onDidChangeActiveJsTsEditor(this.onDidChangeActiveTextEditor, this, this._disposables); this.onDidChangeActiveTextEditor(activeJsTsEditorTracker.activeJsTsEditor); } + override dispose() { + // Clear the context + this.updateContext(false); + + super.dispose(); + } + private onDidChangeActiveTextEditor(editor?: vscode.TextEditor): void { if (editor) { this.updateContext(this.isManagedFile(editor)); diff --git a/code/extensions/typescript-language-features/src/utils/async.ts b/code/extensions/typescript-language-features/src/utils/async.ts index 0d3b8a74f2c..fa5a04e3aa1 100644 --- a/code/extensions/typescript-language-features/src/utils/async.ts +++ b/code/extensions/typescript-language-features/src/utils/async.ts @@ -61,7 +61,7 @@ export class Delayer { } } -export function setImmediate(callback: (...args: any[]) => void, ...args: any[]): Disposable { +export function setImmediate(callback: (...args: unknown[]) => void, ...args: unknown[]): Disposable { if (global.setImmediate) { const handle = global.setImmediate(callback, ...args); return { dispose: () => global.clearImmediate(handle) }; diff --git a/code/extensions/typescript-language-features/src/utils/lazy.ts b/code/extensions/typescript-language-features/src/utils/lazy.ts index 7114ece99b1..be1d0530b88 100644 --- a/code/extensions/typescript-language-features/src/utils/lazy.ts +++ b/code/extensions/typescript-language-features/src/utils/lazy.ts @@ -44,4 +44,8 @@ export class Lazy { * Get the wrapped value without forcing evaluation. */ get rawValue(): T | undefined { return this._value; } + + map(fn: (value: T) => R): Lazy { + return new Lazy(() => fn(this.value)); + } } diff --git a/code/extensions/typescript-language-features/src/utils/platform.ts b/code/extensions/typescript-language-features/src/utils/platform.ts index f97d93e0b57..bb4e7cdbfd9 100644 --- a/code/extensions/typescript-language-features/src/utils/platform.ts +++ b/code/extensions/typescript-language-features/src/utils/platform.ts @@ -10,7 +10,7 @@ export function isWeb(): boolean { } export function isWebAndHasSharedArrayBuffers(): boolean { - return isWeb() && (globalThis as any)['crossOriginIsolated']; + return isWeb() && !!(globalThis as Record)['crossOriginIsolated']; } export function supportsReadableByteStreams(): boolean { diff --git a/code/extensions/typescript-language-features/tsconfig.json b/code/extensions/typescript-language-features/tsconfig.json index c34a9ffb7bf..7300af07c43 100644 --- a/code/extensions/typescript-language-features/tsconfig.json +++ b/code/extensions/typescript-language-features/tsconfig.json @@ -2,6 +2,9 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", + "typeRoots": [ + "./node_modules/@types" + ], "esModuleInterop": true, "types": [ "node" diff --git a/code/extensions/typescript-language-features/web/src/serverHost.ts b/code/extensions/typescript-language-features/web/src/serverHost.ts index d746501682a..fdc617868b5 100644 --- a/code/extensions/typescript-language-features/web/src/serverHost.ts +++ b/code/extensions/typescript-language-features/web/src/serverHost.ts @@ -13,6 +13,31 @@ import { PathMapper, looksLikeNodeModules, mapUri } from './pathMapper'; import { findArgument, hasArgument } from './util/args'; import { URI } from 'vscode-uri'; +type TsModule = typeof ts; + +interface TsInternals extends TsModule { + combinePaths(path: string, ...paths: (string | undefined)[]): string; + + matchFiles( + path: string, + extensions: readonly string[] | undefined, + excludes: readonly string[] | undefined, + includes: readonly string[] | undefined, + useCaseSensitiveFileNames: boolean, + currentDirectory: string, + depth: number | undefined, + getFileSystemEntries: (path: string) => { files: readonly string[]; directories: readonly string[] }, + realpath: (path: string) => string + ): string[]; + + generateDjb2Hash(data: string): string; + + memoize: (callback: () => T) => () => T; + ensureTrailingDirectorySeparator: (path: string) => string; + getDirectoryPath: (path: string) => string; + directorySeparator: string; +} + type ServerHostWithImport = ts.server.ServerHost & { importPlugin(root: string, moduleName: string): Promise }; function createServerHost( @@ -29,26 +54,16 @@ function createServerHost( const fs = apiClient?.vscode.workspace.fileSystem; // Internals - const combinePaths: (path: string, ...paths: (string | undefined)[]) => string = (ts as any).combinePaths; + const combinePaths = (ts as TsInternals).combinePaths; const byteOrderMarkIndicator = '\uFEFF'; - const matchFiles: ( - path: string, - extensions: readonly string[] | undefined, - excludes: readonly string[] | undefined, - includes: readonly string[] | undefined, - useCaseSensitiveFileNames: boolean, - currentDirectory: string, - depth: number | undefined, - getFileSystemEntries: (path: string) => { files: readonly string[]; directories: readonly string[] }, - realpath: (path: string) => string - ) => string[] = (ts as any).matchFiles; - const generateDjb2Hash = (ts as any).generateDjb2Hash; + const matchFiles = (ts as TsInternals).matchFiles; + const generateDjb2Hash = (ts as TsInternals).generateDjb2Hash; // Legacy web - const memoize: (callback: () => T) => () => T = (ts as any).memoize; - const ensureTrailingDirectorySeparator: (path: string) => string = (ts as any).ensureTrailingDirectorySeparator; - const getDirectoryPath: (path: string) => string = (ts as any).getDirectoryPath; - const directorySeparator: string = (ts as any).directorySeparator; + const memoize = (ts as TsInternals).memoize; + const ensureTrailingDirectorySeparator = (ts as TsInternals).ensureTrailingDirectorySeparator; + const getDirectoryPath = (ts as TsInternals).getDirectoryPath; + const directorySeparator = (ts as TsInternals).directorySeparator; const executingFilePath = findArgument(args, '--executingFilePath') || location + ''; const getExecutingDirectoryPath = memoize(() => memoize(() => ensureTrailingDirectorySeparator(getDirectoryPath(executingFilePath)))); const getWebPath = (path: string) => path.startsWith(directorySeparator) ? path.replace(directorySeparator, getExecutingDirectoryPath()) : undefined; @@ -59,16 +74,16 @@ function createServerHost( return { watchFile: watchManager.watchFile.bind(watchManager), watchDirectory: watchManager.watchDirectory.bind(watchManager), - setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): any { + setTimeout(callback: (...args: unknown[]) => void, ms: number, ...args: unknown[]): unknown { return setTimeout(callback, ms, ...args); }, clearTimeout(timeoutId: any): void { clearTimeout(timeoutId); }, - setImmediate(callback: (...args: any[]) => void, ...args: any[]): any { + setImmediate(callback: (...args: unknown[]) => void, ...args: unknown[]): unknown { return this.setTimeout(callback, 0, ...args); }, - clearImmediate(timeoutId: any): void { + clearImmediate(timeoutId: unknown): void { this.clearTimeout(timeoutId); }, importPlugin: async (root, moduleName) => { diff --git a/code/extensions/typescript-language-features/web/src/webServer.ts b/code/extensions/typescript-language-features/web/src/webServer.ts index 7bb1de3393f..5a964231519 100644 --- a/code/extensions/typescript-language-features/web/src/webServer.ts +++ b/code/extensions/typescript-language-features/web/src/webServer.ts @@ -13,7 +13,13 @@ import { createSys } from './serverHost'; import { findArgument, findArgumentStringArray, hasArgument, parseServerMode } from './util/args'; import { StartSessionOptions, startWorkerSession } from './workerSession'; -const setSys: (s: ts.System) => void = (ts as any).setSys; +type TsModule = typeof ts; + +interface TsInternals extends TsModule { + setSys(sys: ts.System): void; +} + +const setSys: (s: ts.System) => void = (ts as TsInternals).setSys; async function initializeSession( args: readonly string[], diff --git a/code/extensions/typescript-language-features/web/src/workerSession.ts b/code/extensions/typescript-language-features/web/src/workerSession.ts index 6ae517ccc32..c12bfd465ea 100644 --- a/code/extensions/typescript-language-features/web/src/workerSession.ts +++ b/code/extensions/typescript-language-features/web/src/workerSession.ts @@ -22,6 +22,12 @@ export interface StartSessionOptions { readonly disableAutomaticTypingAcquisition: boolean; } +type ServerModule = typeof ts.server; + +interface TsServerInternals extends ServerModule { + indent(str: string): string; +} + export function startWorkerSession( ts: typeof import('typescript/lib/tsserverlibrary'), host: ts.server.ServerHost, @@ -31,7 +37,7 @@ export function startWorkerSession( pathMapper: PathMapper, logger: Logger, ): void { - const indent: (str: string) => string = (ts as any).server.indent; + const indent = (ts.server as TsServerInternals).indent; const worker = new class WorkerSession extends ts.server.Session<{}> { diff --git a/code/extensions/vb/language-configuration.json b/code/extensions/vb/language-configuration.json index 53f537617c5..e5f718360f4 100644 --- a/code/extensions/vb/language-configuration.json +++ b/code/extensions/vb/language-configuration.json @@ -25,5 +25,40 @@ "start": "^\\s*#Region\\b", "end": "^\\s*#End Region\\b" } - } + }, + "indentationRules": { + "decreaseIndentPattern": { + "pattern": "^\\s*((End\\s+(If|Sub|Function|Class|Module|Enum|Structure|Interface|Namespace|With|Select|Try|While|For|Property|Get|Set|SyncLock|Using|AddHandler|RaiseEvent|RemoveHandler|Event|Operator))|Else|ElseIf|Case|Catch|Finally|Loop|Next|Wend|Until)\\b", + "flags": "i" + }, + "increaseIndentPattern": { + "pattern": "^\\s*((If|ElseIf).*Then(?!\\s+(End\\s+If))\\s*(('|REM).*)?$)|\\b(Else|While|For|Do|Select\\s+Case|Case|Sub|Function|Class|Module|Enum|Structure|Interface|Namespace|With|Try|Catch|Finally|SyncLock|Using|Property|Get|Set|AddHandler|RaiseEvent|RemoveHandler|Event|Operator)\\b(?!.*\\bEnd\\s+(If|Sub|Function|Class|Module|Enum|Structure|Interface|Namespace|With|Select|Try|While|For|Property|Get|Set|SyncLock|Using|AddHandler|RaiseEvent|RemoveHandler|Event|Operator)\\b).*(('|REM).*)?$", + "flags": "i" + } + }, + "onEnterRules": [ + // Prevent indent after End statements, block terminators (Else, ElseIf, Loop, Next, etc.) + { + "beforeText": { "pattern": "^\\s*((End\\s+(If|Sub|Function|Class|Module|Enum|Structure|Interface|Namespace|With|Select|Try|While|For|Property|Get|Set|SyncLock|Using|AddHandler|RaiseEvent|RemoveHandler|Event|Operator))|Else|ElseIf|Loop|Next|Wend|Until)\\b.*$", "flags": "i" }, + "action": { + "indent": "none" + } + }, + // Prevent indent when pressing Enter on a blank line after End statements or block terminators + { + "beforeText": "^\\s*$", + "previousLineText": { "pattern": "^\\s*((End\\s+(If|Sub|Function|Class|Module|Enum|Structure|Interface|Namespace|With|Select|Try|While|For|Property|Get|Set|SyncLock|Using|AddHandler|RaiseEvent|RemoveHandler|Event|Operator))|Else|ElseIf|Loop|Next|Wend|Until)\\b.*$", "flags": "i" }, + "action": { + "indent": "none" + } + }, + // Prevent indent after lines ending with closing parenthesis (e.g., function calls, method invocations) + { + "beforeText": { "pattern": "^[^'\"]*\\)\\s*('.*)?$", "flags": "i" }, + "afterText": "^(?!\\s*\\))", + "action": { + "indent": "none" + } + } + ] } diff --git a/code/extensions/vscode-api-tests/package-lock.json b/code/extensions/vscode-api-tests/package-lock.json index 777b1eaad7f..644c4fdd017 100644 --- a/code/extensions/vscode-api-tests/package-lock.json +++ b/code/extensions/vscode-api-tests/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.1", "license": "MIT", "devDependencies": { - "@types/mocha": "^9.1.1", + "@types/mocha": "^10.0.10", "@types/node": "22.x", "@types/node-forge": "^1.3.11", "node-forge": "^1.3.2", @@ -20,10 +20,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", @@ -157,9 +158,9 @@ "dev": true }, "node_modules/node-forge": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz", - "integrity": "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.2.tgz", + "integrity": "sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw==", "dev": true, "license": "(BSD-3-Clause OR GPL-2.0)", "engines": { diff --git a/code/extensions/vscode-api-tests/package.json b/code/extensions/vscode-api-tests/package.json index f1f3add6bf0..3f586a847ea 100644 --- a/code/extensions/vscode-api-tests/package.json +++ b/code/extensions/vscode-api-tests/package.json @@ -32,6 +32,7 @@ "notebookMessaging", "notebookMime", "portsAttributes", + "quickInputButtonLocation", "quickPickSortByLabel", "resolvers", "scmActionButton", @@ -50,7 +51,9 @@ "treeViewReveal", "tunnels", "workspaceTrust", - "inlineCompletionsAdditions" + "inlineCompletionsAdditions", + "devDeviceId", + "languageModelProxy" ], "private": true, "activationEvents": [], @@ -79,7 +82,9 @@ } ], "modes": [ - "agent", "ask", "edit" + "agent", + "ask", + "edit" ] }, { @@ -90,15 +95,15 @@ } ], "languageModelTools": [ - { - "name": "requires_confirmation_tool", - "toolReferenceName": "requires_confirmation_tool", - "displayName": "Requires Confirmation Tool", - "modelDescription": "A noop tool to trigger confirmation.", - "canBeReferencedInPrompt": true, - "icon": "$(files)", - "inputSchema": {} - } + { + "name": "requires_confirmation_tool", + "toolReferenceName": "requires_confirmation_tool", + "displayName": "Requires Confirmation Tool", + "modelDescription": "A noop tool to trigger confirmation.", + "canBeReferencedInPrompt": true, + "icon": "$(files)", + "inputSchema": {} + } ], "configuration": { "type": "object", @@ -262,10 +267,10 @@ }, "scripts": { "compile": "node ./node_modules/vscode/bin/compile -watch -p ./", - "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-api-tests ./tsconfig.json" + "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.mjs compile-extension:vscode-api-tests ./tsconfig.json" }, "devDependencies": { - "@types/mocha": "^9.1.1", + "@types/mocha": "^10.0.10", "@types/node": "22.x", "@types/node-forge": "^1.3.11", "node-forge": "^1.3.2", diff --git a/code/extensions/vscode-api-tests/src/extension.ts b/code/extensions/vscode-api-tests/src/extension.ts index 79223b12361..c49f62a38c2 100644 --- a/code/extensions/vscode-api-tests/src/extension.ts +++ b/code/extensions/vscode-api-tests/src/extension.ts @@ -5,7 +5,11 @@ import * as vscode from 'vscode'; +declare global { + var testExtensionContext: vscode.ExtensionContext; +} + export function activate(_context: vscode.ExtensionContext) { // Set context as a global as some tests depend on it - (global as any).testExtensionContext = _context; + global.testExtensionContext = _context; } diff --git a/code/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts b/code/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts index d9327193dda..ff5b49d9b69 100644 --- a/code/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts +++ b/code/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts @@ -4,8 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import * as fs from 'fs'; +import { join } from 'path'; import 'mocha'; -import { ChatContext, ChatRequest, ChatRequestTurn, ChatRequestTurn2, ChatResult, Disposable, Event, EventEmitter, chat, commands, lm } from 'vscode'; +import { ChatContext, ChatRequest, ChatRequestTurn, ChatRequestTurn2, ChatResult, Disposable, env, Event, EventEmitter, chat, commands, lm, UIKind } from 'vscode'; import { DeferredPromise, asPromise, assertNoRpc, closeAllEditors, delay, disposeAll } from '../utils'; suite('chat', () => { @@ -178,7 +180,12 @@ suite('chat', () => { await commands.executeCommand('workbench.action.chat.newChat'); const result = await commands.executeCommand('workbench.action.chat.open', { query: 'hello', blockOnResponse: true }); - assert.strictEqual((result as any).errorDetails.code, 'rate_limited'); + type PartialChatAgentResult = { + errorDetails: { + code: string; + }; + }; + assert.strictEqual((result).errorDetails.code, 'rate_limited'); }); test('title provider is called for first request', async () => { @@ -209,4 +216,28 @@ suite('chat', () => { // Title provider was not called again assert.strictEqual(calls, 1); }); + + test('can access node-pty module', async function () { + // Required for copilot cli in chat extension. + if (env.uiKind === UIKind.Web) { + this.skip(); + } + const nodePtyModules = [ + join(env.appRoot, 'node_modules.asar', 'node-pty'), + join(env.appRoot, 'node_modules', 'node-pty') + ]; + + for (const modulePath of nodePtyModules) { + // try to stat and require module + try { + await fs.promises.stat(modulePath); + const nodePty = require(modulePath); + assert.ok(nodePty, `Successfully required node-pty from ${modulePath}`); + return; + } catch (err) { + // failed to require, try next + } + } + assert.fail('Failed to find and require node-pty module'); + }); }); diff --git a/code/extensions/vscode-api-tests/src/singlefolder-tests/configuration.test.ts b/code/extensions/vscode-api-tests/src/singlefolder-tests/configuration.test.ts index 51d4147774e..0884e1fa95f 100644 --- a/code/extensions/vscode-api-tests/src/singlefolder-tests/configuration.test.ts +++ b/code/extensions/vscode-api-tests/src/singlefolder-tests/configuration.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import 'mocha'; import * as vscode from 'vscode'; -import { assertNoRpc } from '../utils'; +import { assertNoRpc, Mutable } from '../utils'; suite('vscode API - configuration', () => { @@ -30,7 +30,7 @@ suite('vscode API - configuration', () => { assert.strictEqual(config['config0'], true); assert.strictEqual(config['config4'], ''); - assert.throws(() => (config)['config4'] = 'valuevalue'); + assert.throws(() => (config as Mutable)['config4'] = 'valuevalue'); assert.ok(config.has('nested.config1')); assert.strictEqual(config.get('nested.config1'), 42); @@ -44,6 +44,6 @@ suite('vscode API - configuration', () => { assert.ok(config.has('get')); assert.strictEqual(config.get('get'), 'get-prop'); assert.deepStrictEqual(config['get'], config.get); - assert.throws(() => config['get'] = 'get-prop'); + assert.throws(() => (config as Mutable)['get'] = 'get-prop' as unknown as typeof config.get); }); }); diff --git a/code/extensions/vscode-api-tests/src/singlefolder-tests/debug.test.ts b/code/extensions/vscode-api-tests/src/singlefolder-tests/debug.test.ts index 28f66a0f104..18ae50d3af6 100644 --- a/code/extensions/vscode-api-tests/src/singlefolder-tests/debug.test.ts +++ b/code/extensions/vscode-api-tests/src/singlefolder-tests/debug.test.ts @@ -63,7 +63,7 @@ suite('vscode API - debug', function () { assert.strictEqual(functionBreakpoint.functionName, 'func'); }); - test('start debugging', async function () { + test.skip('start debugging', async function () { // Flaky: https://github.com/microsoft/vscode/issues/242033 let stoppedEvents = 0; let variablesReceived: () => void; let initializedReceived: () => void; diff --git a/code/extensions/vscode-api-tests/src/singlefolder-tests/documentPaste.test.ts b/code/extensions/vscode-api-tests/src/singlefolder-tests/documentPaste.test.ts index b4212bb6103..39179d0d1e3 100644 --- a/code/extensions/vscode-api-tests/src/singlefolder-tests/documentPaste.test.ts +++ b/code/extensions/vscode-api-tests/src/singlefolder-tests/documentPaste.test.ts @@ -208,7 +208,7 @@ suite.skip('vscode API - Copy Paste', function () { }); function reverseString(str: string) { - return str.split("").reverse().join(""); + return str.split('').reverse().join(''); } function getNextDocumentText(disposables: vscode.Disposable[], doc: vscode.TextDocument): Promise { diff --git a/code/extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts b/code/extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts index 1bd22369152..ce8e68e0c1e 100644 --- a/code/extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts +++ b/code/extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { commands, env, Position, Range, Selection, SnippetString, TextDocument, TextEditor, TextEditorCursorStyle, TextEditorLineNumbersStyle, Uri, window, workspace } from 'vscode'; +import { env, Position, Range, Selection, SnippetString, TextDocument, TextEditor, TextEditorCursorStyle, TextEditorLineNumbersStyle, Uri, window, workspace } from 'vscode'; import { assertNoRpc, closeAllEditors, createRandomFile, deleteFile } from '../utils'; suite('vscode API - editors', () => { @@ -167,53 +167,6 @@ suite('vscode API - editors', () => { }); }); - function executeReplace(editor: TextEditor, range: Range, text: string, undoStopBefore: boolean, undoStopAfter: boolean): Thenable { - return editor.edit((builder) => { - builder.replace(range, text); - }, { undoStopBefore: undoStopBefore, undoStopAfter: undoStopAfter }); - } - - test.skip('TextEditor.edit can control undo/redo stack 1', () => { - return withRandomFileEditor('Hello world!', async (editor, doc) => { - const applied1 = await executeReplace(editor, new Range(0, 0, 0, 1), 'h', false, false); - assert.ok(applied1); - assert.strictEqual(doc.getText(), 'hello world!'); - assert.ok(doc.isDirty); - - const applied2 = await executeReplace(editor, new Range(0, 1, 0, 5), 'ELLO', false, false); - assert.ok(applied2); - assert.strictEqual(doc.getText(), 'hELLO world!'); - assert.ok(doc.isDirty); - - await commands.executeCommand('undo'); - if (doc.getText() === 'hello world!') { - // see https://github.com/microsoft/vscode/issues/109131 - // it looks like an undo stop was inserted in between these two edits - // it is unclear why this happens, but it can happen for a multitude of reasons - await commands.executeCommand('undo'); - } - assert.strictEqual(doc.getText(), 'Hello world!'); - }); - }); - - test.skip('TextEditor.edit can control undo/redo stack 2', () => { - return withRandomFileEditor('Hello world!', (editor, doc) => { - return executeReplace(editor, new Range(0, 0, 0, 1), 'h', false, false).then(applied => { - assert.ok(applied); - assert.strictEqual(doc.getText(), 'hello world!'); - assert.ok(doc.isDirty); - return executeReplace(editor, new Range(0, 1, 0, 5), 'ELLO', true, false); - }).then(applied => { - assert.ok(applied); - assert.strictEqual(doc.getText(), 'hELLO world!'); - assert.ok(doc.isDirty); - return commands.executeCommand('undo'); - }).then(_ => { - assert.strictEqual(doc.getText(), 'hello world!'); - }); - }); - }); - test('issue #16573: Extension API: insertSpaces and tabSize are undefined', () => { return withRandomFileEditor('Hello world!\n\tHello world!', (editor, _doc) => { diff --git a/code/extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts b/code/extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts index d02e5a44442..124c6a5030a 100644 --- a/code/extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts +++ b/code/extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { env, ExtensionKind, extensions, UIKind, Uri } from 'vscode'; -import { assertNoRpc } from '../utils'; +import { assertNoRpc, Mutable } from '../utils'; suite('vscode API - env', () => { @@ -21,12 +21,12 @@ suite('vscode API - env', () => { }); test('env is readonly', function () { - assert.throws(() => (env as any).language = '234'); - assert.throws(() => (env as any).appRoot = '234'); - assert.throws(() => (env as any).appName = '234'); - assert.throws(() => (env as any).machineId = '234'); - assert.throws(() => (env as any).sessionId = '234'); - assert.throws(() => (env as any).shell = '234'); + assert.throws(() => (env as Mutable).language = '234'); + assert.throws(() => (env as Mutable).appRoot = '234'); + assert.throws(() => (env as Mutable).appName = '234'); + assert.throws(() => (env as Mutable).machineId = '234'); + assert.throws(() => (env as Mutable).sessionId = '234'); + assert.throws(() => (env as Mutable).shell = '234'); }); test('env.remoteName', function () { diff --git a/code/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts b/code/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts index 55a17cd3ce0..03a1d3833ae 100644 --- a/code/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts +++ b/code/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import 'mocha'; import * as vscode from 'vscode'; -import { asPromise, disposeAll } from '../utils'; +import { asPromise, disposeAll, poll } from '../utils'; import { Kernel, saveAllFilesAndCloseAll } from './notebook.api.test'; export type INativeInteractiveWindow = { notebookUri: vscode.Uri; inputUri: vscode.Uri; notebookEditor: vscode.NotebookEditor }; @@ -108,7 +108,10 @@ async function addCellAndRun(code: string, notebook: vscode.NotebookDocument) { } }); - test('Interactive window has the correct kernel', async () => { + // https://github.com/microsoft/vscode/issues/266229 + test.skip('Interactive window has the correct kernel', async function () { + // Extend timeout a bit as kernel association can be async & occasionally slow on CI + this.timeout(20000); assert.ok(vscode.workspace.workspaceFolders); await createInteractiveWindow(defaultKernel); @@ -118,11 +121,15 @@ async function addCellAndRun(code: string, notebook: vscode.NotebookDocument) { const { notebookEditor } = await createInteractiveWindow(secondKernel); assert.ok(notebookEditor); - // Verify the kernel is the secondary one + // Run a cell to ensure the kernel is actually exercised await addCellAndRun(`print`, notebookEditor.notebook); + await poll( + () => Promise.resolve(secondKernel.associatedNotebooks.has(notebookEditor.notebook.uri.toString())), + v => v, + 'Secondary kernel was not set as the kernel for the interactive window' + ); assert.strictEqual(secondKernel.associatedNotebooks.has(notebookEditor.notebook.uri.toString()), true, `Secondary kernel was not set as the kernel for the interactive window`); - }); }); diff --git a/code/extensions/vscode-api-tests/src/singlefolder-tests/ipynb.test.ts b/code/extensions/vscode-api-tests/src/singlefolder-tests/ipynb.test.ts index dafd4df1e68..5cfdf8fe8f2 100644 --- a/code/extensions/vscode-api-tests/src/singlefolder-tests/ipynb.test.ts +++ b/code/extensions/vscode-api-tests/src/singlefolder-tests/ipynb.test.ts @@ -9,24 +9,24 @@ import * as vscode from 'vscode'; import { assertNoRpc, closeAllEditors, createRandomFile } from '../utils'; const ipynbContent = JSON.stringify({ - "cells": [ + 'cells': [ { - "cell_type": "markdown", - "source": ["## Header"], - "metadata": {} + 'cell_type': 'markdown', + 'source': ['## Header'], + 'metadata': {} }, { - "cell_type": "code", - "execution_count": 2, - "source": ["print('hello 1')\n", "print('hello 2')"], - "outputs": [ + 'cell_type': 'code', + 'execution_count': 2, + 'source': [`print('hello 1')\n`, `print('hello 2')`], + 'outputs': [ { - "output_type": "stream", - "name": "stdout", - "text": ["hello 1\n", "hello 2\n"] + 'output_type': 'stream', + 'name': 'stdout', + 'text': ['hello 1\n', 'hello 2\n'] } ], - "metadata": {} + 'metadata': {} } ] }); diff --git a/code/extensions/vscode-api-tests/src/singlefolder-tests/notebook.kernel.test.ts b/code/extensions/vscode-api-tests/src/singlefolder-tests/notebook.kernel.test.ts index 7cc5e40a100..805c9446086 100644 --- a/code/extensions/vscode-api-tests/src/singlefolder-tests/notebook.kernel.test.ts +++ b/code/extensions/vscode-api-tests/src/singlefolder-tests/notebook.kernel.test.ts @@ -14,9 +14,7 @@ async function createRandomNotebookFile() { } async function openRandomNotebookDocument() { - console.log('Creating a random notebook file'); const uri = await createRandomNotebookFile(); - console.log('Created a random notebook file'); return vscode.workspace.openNotebookDocument(uri); } @@ -121,7 +119,6 @@ const apiTestSerializer: vscode.NotebookSerializer = { } ] }; - console.log('Returning NotebookData in deserializeNotebook'); return dto; } }; diff --git a/code/extensions/vscode-api-tests/src/singlefolder-tests/proxy.test.ts b/code/extensions/vscode-api-tests/src/singlefolder-tests/proxy.test.ts index 7845e3113cc..c0b4f154260 100644 --- a/code/extensions/vscode-api-tests/src/singlefolder-tests/proxy.test.ts +++ b/code/extensions/vscode-api-tests/src/singlefolder-tests/proxy.test.ts @@ -13,6 +13,12 @@ import * as vscode from 'vscode'; import { Straightforward, Middleware, RequestContext, ConnectContext, isRequest, isConnect } from 'straightforward'; import assert from 'assert'; +declare module 'https' { + interface Agent { + testCertificates?: string[]; + } +} + (vscode.env.uiKind === vscode.UIKind.Web ? suite.skip : suite)('vscode API - network proxy support', () => { teardown(async function () { @@ -56,7 +62,7 @@ import assert from 'assert'; }); // Using https.globalAgent because it is shared with proxyResolver.ts and mutable. - (https.globalAgent as any).testCertificates = [certPEM]; + https.globalAgent.testCertificates = [certPEM]; resetCaches(); try { @@ -72,7 +78,7 @@ import assert from 'assert'; .on('error', reject); }); } finally { - delete (https.globalAgent as any).testCertificates; + delete https.globalAgent.testCertificates; resetCaches(); server.close(); } diff --git a/code/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts b/code/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts index 4f8331c286f..485e3d85f34 100644 --- a/code/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts +++ b/code/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { commands, Disposable, QuickPick, QuickPickItem, window } from 'vscode'; +import { commands, Disposable, QuickPick, QuickPickItem, window, workspace } from 'vscode'; import { assertNoRpc, closeAllEditors } from '../utils'; interface QuickPickExpected { @@ -248,6 +248,71 @@ suite('vscode API - quick input', function () { quickPick.hide(); await waitForHide(quickPick); }); + + test('createQuickPick, match item by label derived from resourceUri', function (_done) { + let done = (err?: any) => { + done = () => { }; + _done(err); + }; + + const quickPick = createQuickPick({ + events: ['active', 'selection', 'accept', 'hide'], + activeItems: [['']], + selectionItems: [['']], + acceptedItems: { + active: [['']], + selection: [['']], + dispose: [true] + }, + }, (err?: any) => done(err)); + + const baseUri = workspace!.workspaceFolders![0].uri; + quickPick.items = [ + { label: 'a1', resourceUri: baseUri.with({ path: baseUri.path + '/test1.txt' }) }, + { label: '', resourceUri: baseUri.with({ path: baseUri.path + '/test2.txt' }) }, + { label: 'a3', resourceUri: baseUri.with({ path: baseUri.path + '/test3.txt' }) } + ]; + quickPick.value = 'test2.txt'; + quickPick.show(); + + (async () => { + await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem'); + })() + .catch(err => done(err)); + }); + + test('createQuickPick, match item by description derived from resourceUri', function (_done) { + let done = (err?: any) => { + done = () => { }; + _done(err); + }; + + const quickPick = createQuickPick({ + events: ['active', 'selection', 'accept', 'hide'], + activeItems: [['a2']], + selectionItems: [['a2']], + acceptedItems: { + active: [['a2']], + selection: [['a2']], + dispose: [true] + }, + }, (err?: any) => done(err)); + + const baseUri = workspace!.workspaceFolders![0].uri; + quickPick.items = [ + { label: 'a1', resourceUri: baseUri.with({ path: baseUri.path + '/test1.txt' }) }, + { label: 'a2', resourceUri: baseUri.with({ path: baseUri.path + '/test2.txt' }) }, + { label: 'a3', resourceUri: baseUri.with({ path: baseUri.path + '/test3.txt' }) } + ]; + quickPick.matchOnDescription = true; + quickPick.value = 'test2.txt'; + quickPick.show(); + + (async () => { + await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem'); + })() + .catch(err => done(err)); + }); }); function createQuickPick(expected: QuickPickExpected, done: (err?: any) => void, record = false) { diff --git a/code/extensions/vscode-api-tests/src/singlefolder-tests/state.test.ts b/code/extensions/vscode-api-tests/src/singlefolder-tests/state.test.ts index cbe58948d86..54e638d5f3b 100644 --- a/code/extensions/vscode-api-tests/src/singlefolder-tests/state.test.ts +++ b/code/extensions/vscode-api-tests/src/singlefolder-tests/state.test.ts @@ -13,7 +13,7 @@ suite('vscode API - globalState / workspaceState', () => { suiteSetup(async () => { // Trigger extension activation and grab the context as some tests depend on it await extensions.getExtension('vscode.vscode-api-tests')?.activate(); - extensionContext = (global as any).testExtensionContext; + extensionContext = global.testExtensionContext; }); test('state basics', async () => { diff --git a/code/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts b/code/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts index 9e666879933..de82ba5410d 100644 --- a/code/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts +++ b/code/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts @@ -15,7 +15,7 @@ import { assertNoRpc, poll } from '../utils'; suiteSetup(async () => { // Trigger extension activation and grab the context as some tests depend on it await extensions.getExtension('vscode.vscode-api-tests')?.activate(); - extensionContext = (global as any).testExtensionContext; + extensionContext = global.testExtensionContext; const config = workspace.getConfiguration('terminal.integrated'); // Disable conpty in integration tests because of https://github.com/microsoft/vscode/issues/76548 @@ -27,7 +27,7 @@ import { assertNoRpc, poll } from '../utils'; // Disable env var relaunch for tests to prevent terminals relaunching themselves await config.update('environmentChangesRelaunch', false, ConfigurationTarget.Global); // Disable local echo in case it causes any problems in remote tests - await config.update('localEchoEnabled', "off", ConfigurationTarget.Global); + await config.update('localEchoEnabled', 'off', ConfigurationTarget.Global); await config.update('shellIntegration.enabled', false); }); @@ -926,16 +926,16 @@ import { assertNoRpc, poll } from '../utils'; applyAtProcessCreation: true, applyAtShellIntegration: false }; - deepStrictEqual(collection.get('A'), { value: '~a2~', type: EnvironmentVariableMutatorType.Replace, options: defaultOptions }); - deepStrictEqual(collection.get('B'), { value: '~b2~', type: EnvironmentVariableMutatorType.Append, options: defaultOptions }); - deepStrictEqual(collection.get('C'), { value: '~c2~', type: EnvironmentVariableMutatorType.Prepend, options: defaultOptions }); + deepStrictEqual(collection.get('A'), { value: '~a2~', type: EnvironmentVariableMutatorType.Replace, options: defaultOptions, variable: 'A' }); + deepStrictEqual(collection.get('B'), { value: '~b2~', type: EnvironmentVariableMutatorType.Append, options: defaultOptions, variable: 'B' }); + deepStrictEqual(collection.get('C'), { value: '~c2~', type: EnvironmentVariableMutatorType.Prepend, options: defaultOptions, variable: 'C' }); // Verify forEach const entries: [string, EnvironmentVariableMutator][] = []; collection.forEach((v, m) => entries.push([v, m])); deepStrictEqual(entries, [ - ['A', { value: '~a2~', type: EnvironmentVariableMutatorType.Replace, options: defaultOptions }], - ['B', { value: '~b2~', type: EnvironmentVariableMutatorType.Append, options: defaultOptions }], - ['C', { value: '~c2~', type: EnvironmentVariableMutatorType.Prepend, options: defaultOptions }] + ['A', { value: '~a2~', type: EnvironmentVariableMutatorType.Replace, options: defaultOptions, variable: 'A' }], + ['B', { value: '~b2~', type: EnvironmentVariableMutatorType.Append, options: defaultOptions, variable: 'B' }], + ['C', { value: '~c2~', type: EnvironmentVariableMutatorType.Prepend, options: defaultOptions, variable: 'C' }] ]); }); @@ -956,17 +956,17 @@ import { assertNoRpc, poll } from '../utils'; applyAtShellIntegration: false }; const expectedScopedCollection = collection.getScoped(scope); - deepStrictEqual(expectedScopedCollection.get('A'), { value: 'scoped~a2~', type: EnvironmentVariableMutatorType.Replace, options: defaultOptions }); - deepStrictEqual(expectedScopedCollection.get('B'), { value: 'scoped~b2~', type: EnvironmentVariableMutatorType.Append, options: defaultOptions }); - deepStrictEqual(expectedScopedCollection.get('C'), { value: 'scoped~c2~', type: EnvironmentVariableMutatorType.Prepend, options: defaultOptions }); + deepStrictEqual(expectedScopedCollection.get('A'), { value: 'scoped~a2~', type: EnvironmentVariableMutatorType.Replace, options: defaultOptions, variable: 'A' }); + deepStrictEqual(expectedScopedCollection.get('B'), { value: 'scoped~b2~', type: EnvironmentVariableMutatorType.Append, options: defaultOptions, variable: 'B' }); + deepStrictEqual(expectedScopedCollection.get('C'), { value: 'scoped~c2~', type: EnvironmentVariableMutatorType.Prepend, options: defaultOptions, variable: 'C' }); // Verify forEach const entries: [string, EnvironmentVariableMutator][] = []; expectedScopedCollection.forEach((v, m) => entries.push([v, m])); deepStrictEqual(entries.map(v => v[1]), [ - { value: 'scoped~a2~', type: EnvironmentVariableMutatorType.Replace, options: defaultOptions }, - { value: 'scoped~b2~', type: EnvironmentVariableMutatorType.Append, options: defaultOptions }, - { value: 'scoped~c2~', type: EnvironmentVariableMutatorType.Prepend, options: defaultOptions } + { value: 'scoped~a2~', type: EnvironmentVariableMutatorType.Replace, options: defaultOptions, variable: 'A' }, + { value: 'scoped~b2~', type: EnvironmentVariableMutatorType.Append, options: defaultOptions, variable: 'B' }, + { value: 'scoped~c2~', type: EnvironmentVariableMutatorType.Prepend, options: defaultOptions, variable: 'C' } ]); deepStrictEqual(entries.map(v => v[0]), ['A', 'B', 'C']); }); diff --git a/code/extensions/vscode-api-tests/src/singlefolder-tests/tree.test.ts b/code/extensions/vscode-api-tests/src/singlefolder-tests/tree.test.ts new file mode 100644 index 00000000000..5382fb5777e --- /dev/null +++ b/code/extensions/vscode-api-tests/src/singlefolder-tests/tree.test.ts @@ -0,0 +1,245 @@ +/*--------------------------------------------------------------------------------------------- + * 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 'mocha'; +import * as vscode from 'vscode'; +import { asPromise, assertNoRpc, disposeAll, delay, DeferredPromise } from '../utils'; + +suite('vscode API - tree', () => { + + const disposables: vscode.Disposable[] = []; + + teardown(() => { + disposeAll(disposables); + disposables.length = 0; + assertNoRpc(); + }); + + test('TreeView - element already registered', async function () { + this.timeout(60_000); + + type TreeElement = { readonly kind: 'leaf' }; + + class QuickRefreshTreeDataProvider implements vscode.TreeDataProvider { + private readonly changeEmitter = new vscode.EventEmitter(); + private readonly requestEmitter = new vscode.EventEmitter(); + private readonly pendingRequests: DeferredPromise[] = []; + private readonly element: TreeElement = { kind: 'leaf' }; + + readonly onDidChangeTreeData = this.changeEmitter.event; + + getChildren(element?: TreeElement): Thenable { + if (!element) { + const deferred = new DeferredPromise(); + this.pendingRequests.push(deferred); + this.requestEmitter.fire(this.pendingRequests.length); + return deferred.p; + } + return Promise.resolve([]); + } + + getTreeItem(): vscode.TreeItem { + const item = new vscode.TreeItem('duplicate', vscode.TreeItemCollapsibleState.None); + item.id = 'dup'; + return item; + } + + getParent(): TreeElement | undefined { + return undefined; + } + + async waitForRequestCount(count: number): Promise { + while (this.pendingRequests.length < count) { + await asPromise(this.requestEmitter.event); + } + } + + async resolveNextRequest(): Promise { + const next = this.pendingRequests.shift(); + if (!next) { + return; + } + await next.complete([this.element]); + } + + dispose(): void { + this.changeEmitter.dispose(); + this.requestEmitter.dispose(); + while (this.pendingRequests.length) { + this.pendingRequests.shift()!.complete([]); + } + } + + getElement(): TreeElement { + return this.element; + } + } + + const provider = new QuickRefreshTreeDataProvider(); + disposables.push(provider); + + const treeView = vscode.window.createTreeView('test.treeId', { treeDataProvider: provider }); + disposables.push(treeView); + + const revealFirst = (treeView.reveal(provider.getElement(), { expand: true }) + .then(() => ({ error: undefined as Error | undefined })) as Promise<{ error: Error | undefined }>) + .catch(error => ({ error })); + const revealSecond = (treeView.reveal(provider.getElement(), { expand: true }) + .then(() => ({ error: undefined as Error | undefined })) as Promise<{ error: Error | undefined }>) + .catch(error => ({ error })); + + await provider.waitForRequestCount(2); + + await provider.resolveNextRequest(); + await delay(0); + await provider.resolveNextRequest(); + + const [firstResult, secondResult] = await Promise.all([revealFirst, revealSecond]); + const error = firstResult.error ?? secondResult.error; + if (error && /Element with id .+ is already registered/.test(error.message)) { + assert.fail(error.message); + } + }); + + test('TreeView - element already registered after refresh', async function () { + this.timeout(60_000); + + type ParentElement = { readonly kind: 'parent' }; + type ChildElement = { readonly kind: 'leaf'; readonly version: number }; + type TreeElement = ParentElement | ChildElement; + + class ParentRefreshTreeDataProvider implements vscode.TreeDataProvider { + private readonly changeEmitter = new vscode.EventEmitter(); + private readonly rootRequestEmitter = new vscode.EventEmitter(); + private readonly childRequestEmitter = new vscode.EventEmitter(); + private readonly rootRequests: DeferredPromise[] = []; + private readonly childRequests: DeferredPromise[] = []; + private readonly parentElement: ParentElement = { kind: 'parent' }; + private childVersion = 0; + private currentChild: ChildElement = { kind: 'leaf', version: 0 }; + + readonly onDidChangeTreeData = this.changeEmitter.event; + + getChildren(element?: TreeElement): Thenable { + if (!element) { + const deferred = new DeferredPromise(); + this.rootRequests.push(deferred); + this.rootRequestEmitter.fire(this.rootRequests.length); + return deferred.p; + } + if (element.kind === 'parent') { + const deferred = new DeferredPromise(); + this.childRequests.push(deferred); + this.childRequestEmitter.fire(this.childRequests.length); + return deferred.p; + } + return Promise.resolve([]); + } + + getTreeItem(element: TreeElement): vscode.TreeItem { + if (element.kind === 'parent') { + const item = new vscode.TreeItem('parent', vscode.TreeItemCollapsibleState.Collapsed); + item.id = 'parent'; + return item; + } + const item = new vscode.TreeItem('duplicate', vscode.TreeItemCollapsibleState.None); + item.id = 'dup'; + return item; + } + + getParent(element: TreeElement): TreeElement | undefined { + if (element.kind === 'leaf') { + return this.parentElement; + } + return undefined; + } + + getCurrentChild(): ChildElement { + return this.currentChild; + } + + replaceChild(): ChildElement { + this.childVersion++; + this.currentChild = { kind: 'leaf', version: this.childVersion }; + return this.currentChild; + } + + async waitForRootRequestCount(count: number): Promise { + while (this.rootRequests.length < count) { + await asPromise(this.rootRequestEmitter.event); + } + } + + async waitForChildRequestCount(count: number): Promise { + while (this.childRequests.length < count) { + await asPromise(this.childRequestEmitter.event); + } + } + + async resolveNextRootRequest(elements?: TreeElement[]): Promise { + const next = this.rootRequests.shift(); + if (!next) { + return; + } + await next.complete(elements ?? [this.parentElement]); + } + + async resolveChildRequestAt(index: number, elements?: TreeElement[]): Promise { + const request = this.childRequests[index]; + if (!request) { + return; + } + this.childRequests.splice(index, 1); + await request.complete(elements ?? [this.currentChild]); + } + + dispose(): void { + this.changeEmitter.dispose(); + this.rootRequestEmitter.dispose(); + this.childRequestEmitter.dispose(); + while (this.rootRequests.length) { + this.rootRequests.shift()!.complete([]); + } + while (this.childRequests.length) { + this.childRequests.shift()!.complete([]); + } + } + } + + const provider = new ParentRefreshTreeDataProvider(); + disposables.push(provider); + + const treeView = vscode.window.createTreeView('test.treeRefresh', { treeDataProvider: provider }); + disposables.push(treeView); + + const initialChild = provider.getCurrentChild(); + const firstReveal = (treeView.reveal(initialChild, { expand: true }) + .then(() => ({ error: undefined as Error | undefined })) as Promise<{ error: Error | undefined }>) + .catch(error => ({ error })); + + await provider.waitForRootRequestCount(1); + await provider.resolveNextRootRequest(); + + await provider.waitForChildRequestCount(1); + const staleChild = provider.getCurrentChild(); + const refreshedChild = provider.replaceChild(); + const secondReveal = (treeView.reveal(refreshedChild, { expand: true }) + .then(() => ({ error: undefined as Error | undefined })) as Promise<{ error: Error | undefined }>) + .catch(error => ({ error })); + + await provider.waitForChildRequestCount(2); + + await provider.resolveChildRequestAt(1, [refreshedChild]); + await delay(0); + await provider.resolveChildRequestAt(0, [staleChild]); + + const [firstResult, secondResult] = await Promise.all([firstReveal, secondReveal]); + const error = firstResult.error ?? secondResult.error; + if (error && /Element with id .+ is already registered/.test(error.message)) { + assert.fail(error.message); + } + }); +}); diff --git a/code/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/code/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index 66e2fba9561..30d08c25b98 100644 --- a/code/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/code/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -8,7 +8,7 @@ import * as fs from 'fs'; import { basename, join, posix } from 'path'; import * as vscode from 'vscode'; import { TestFS } from '../memfs'; -import { assertNoRpc, closeAllEditors, createRandomFile, delay, deleteFile, disposeAll, pathEquals, revertAllDirty, rndName, testFs, withLogDisabled } from '../utils'; +import { assertNoRpc, closeAllEditors, createRandomFile, delay, deleteFile, disposeAll, Mutable, pathEquals, revertAllDirty, rndName, testFs, withLogDisabled } from '../utils'; suite('vscode API - workspace', () => { @@ -41,12 +41,13 @@ suite('vscode API - workspace', () => { test('textDocuments', () => { assert.ok(Array.isArray(vscode.workspace.textDocuments)); - assert.throws(() => (vscode.workspace).textDocuments = null); + assert.throws(() => (vscode.workspace as Mutable).textDocuments = null as unknown as vscode.TextDocument[]); }); test('rootPath', () => { assert.ok(pathEquals(vscode.workspace.rootPath!, join(__dirname, '../../testWorkspace'))); - assert.throws(() => (vscode.workspace as any).rootPath = 'farboo'); + + assert.throws(() => (vscode.workspace as Mutable).rootPath = 'farboo'); }); test('workspaceFile', () => { @@ -458,7 +459,7 @@ suite('vscode API - workspace', () => { const registration = vscode.workspace.registerTextDocumentContentProvider('foo', { provideTextDocumentContent(_uri) { - return 123; + return 123 as unknown as string; } }); return vscode.workspace.openTextDocument(vscode.Uri.parse('foo://auth/path')).then(() => { @@ -1148,12 +1149,12 @@ suite('vscode API - workspace', () => { const we = new vscode.WorkspaceEdit(); we.insert(file, new vscode.Position(0, 5), '2'); we.renameFile(file, newFile); - await vscode.workspace.applyEdit(we); + assert.ok(await vscode.workspace.applyEdit(we)); } // show the new document { - const document = await vscode.workspace.openTextDocument(newFile); + const document = await vscode.workspace.openTextDocument(newFile); // FAILS here await vscode.window.showTextDocument(document); assert.strictEqual(document.getText(), 'hello2'); assert.strictEqual(document.isDirty, true); @@ -1178,40 +1179,6 @@ suite('vscode API - workspace', () => { }); - test.skip('issue #110141 - TextEdit.setEndOfLine applies an edit and invalidates redo stack even when no change is made', async () => { - const file = await createRandomFile('hello\nworld'); - - const document = await vscode.workspace.openTextDocument(file); - await vscode.window.showTextDocument(document); - - // apply edit - { - const we = new vscode.WorkspaceEdit(); - we.insert(file, new vscode.Position(0, 5), '2'); - await vscode.workspace.applyEdit(we); - } - - // check the document - { - assert.strictEqual(document.getText(), 'hello2\nworld'); - assert.strictEqual(document.isDirty, true); - } - - // apply no-op edit - { - const we = new vscode.WorkspaceEdit(); - we.set(file, [vscode.TextEdit.setEndOfLine(vscode.EndOfLine.LF)]); - await vscode.workspace.applyEdit(we); - } - - // undo - { - await vscode.commands.executeCommand('undo'); - assert.strictEqual(document.getText(), 'hello\nworld'); - assert.strictEqual(document.isDirty, false); - } - }); - test('SnippetString in WorkspaceEdit', async function (): Promise { const file = await createRandomFile('hello\nworld'); diff --git a/code/extensions/vscode-api-tests/src/utils.ts b/code/extensions/vscode-api-tests/src/utils.ts index 4bf02420715..7e3e4106ab5 100644 --- a/code/extensions/vscode-api-tests/src/utils.ts +++ b/code/extensions/vscode-api-tests/src/utils.ts @@ -250,3 +250,7 @@ export class DeferredPromise { }); } } + +export type Mutable = { + -readonly [P in keyof T]: T[P]; +}; diff --git a/code/extensions/vscode-api-tests/tsconfig.json b/code/extensions/vscode-api-tests/tsconfig.json index 3ef85d919ec..6cfdb070b4e 100644 --- a/code/extensions/vscode-api-tests/tsconfig.json +++ b/code/extensions/vscode-api-tests/tsconfig.json @@ -2,9 +2,10 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", - "types": [ - "node" - ] + "typeRoots": [ + "./node_modules/@types" + ], + "skipLibCheck": true }, "include": [ "src/**/*", diff --git a/code/extensions/vscode-colorize-perf-tests/package.json b/code/extensions/vscode-colorize-perf-tests/package.json index fb7ce45f613..2ac38c1a995 100644 --- a/code/extensions/vscode-colorize-perf-tests/package.json +++ b/code/extensions/vscode-colorize-perf-tests/package.json @@ -14,7 +14,7 @@ }, "icon": "media/icon.png", "scripts": { - "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-colorize-perf-tests ./tsconfig.json", + "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.mjs compile-extension:vscode-colorize-perf-tests ./tsconfig.json", "watch": "gulp watch-extension:vscode-colorize-perf-tests", "compile": "gulp compile-extension:vscode-colorize-perf-tests" }, diff --git a/code/extensions/vscode-colorize-perf-tests/src/colorizer.test.ts b/code/extensions/vscode-colorize-perf-tests/src/colorizer.test.ts index 7e1df20ca29..2076a96d6b3 100644 --- a/code/extensions/vscode-colorize-perf-tests/src/colorizer.test.ts +++ b/code/extensions/vscode-colorize-perf-tests/src/colorizer.test.ts @@ -131,7 +131,7 @@ suite('Tokenization Performance', () => { suiteSetup(async function () { originalSettingValue = workspace.getConfiguration('editor').get('experimental.preferTreeSitter'); - await workspace.getConfiguration('editor').update('experimental.preferTreeSitter', ["typescript"], ConfigurationTarget.Global); + await workspace.getConfiguration('editor').update('experimental.preferTreeSitter', ['typescript'], ConfigurationTarget.Global); }); suiteTeardown(async function () { await workspace.getConfiguration('editor').update('experimental.preferTreeSitter', originalSettingValue, ConfigurationTarget.Global); diff --git a/code/extensions/vscode-colorize-perf-tests/tsconfig.json b/code/extensions/vscode-colorize-perf-tests/tsconfig.json index 7234fdfeb97..20f72eae51d 100644 --- a/code/extensions/vscode-colorize-perf-tests/tsconfig.json +++ b/code/extensions/vscode-colorize-perf-tests/tsconfig.json @@ -2,6 +2,9 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", + "typeRoots": [ + "./node_modules/@types" + ], "types": [ "node" ] diff --git a/code/extensions/vscode-colorize-tests/package.json b/code/extensions/vscode-colorize-tests/package.json index 49592763745..1abff3d9862 100644 --- a/code/extensions/vscode-colorize-tests/package.json +++ b/code/extensions/vscode-colorize-tests/package.json @@ -14,7 +14,7 @@ }, "icon": "media/icon.png", "scripts": { - "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-colorize-tests ./tsconfig.json", + "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.mjs compile-extension:vscode-colorize-tests ./tsconfig.json", "watch": "gulp watch-extension:vscode-colorize-tests", "compile": "gulp compile-extension:vscode-colorize-tests" }, diff --git a/code/extensions/vscode-colorize-tests/test/colorize-fixtures/test.env b/code/extensions/vscode-colorize-tests/test/colorize-fixtures/test.env new file mode 100644 index 00000000000..b2972ab3751 --- /dev/null +++ b/code/extensions/vscode-colorize-tests/test/colorize-fixtures/test.env @@ -0,0 +1,3 @@ +# dev +HELLO=123 +GOODBYE=456 diff --git a/code/extensions/vscode-colorize-tests/test/colorize-results/test_env.json b/code/extensions/vscode-colorize-tests/test/colorize-results/test_env.json new file mode 100644 index 00000000000..a0eb219fea5 --- /dev/null +++ b/code/extensions/vscode-colorize-tests/test/colorize-results/test_env.json @@ -0,0 +1,100 @@ +[ + { + "c": "# dev", + "t": "source.dotenv comment.line.dotenv", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668", + "dark_modern": "comment: #6A9955", + "hc_light": "comment: #515151", + "light_modern": "comment: #008000" + } + }, + { + "c": "HELLO", + "t": "source.dotenv variable.key.dotenv", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" + } + }, + { + "c": "=", + "t": "source.dotenv keyword.operator.assignment.dotenv", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4", + "dark_modern": "keyword.operator: #D4D4D4", + "hc_light": "keyword.operator: #000000", + "light_modern": "keyword.operator: #000000" + } + }, + { + "c": "123", + "t": "source.dotenv property.value.dotenv", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "GOODBYE", + "t": "source.dotenv variable.key.dotenv", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" + } + }, + { + "c": "=", + "t": "source.dotenv keyword.operator.assignment.dotenv", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4", + "dark_modern": "keyword.operator: #D4D4D4", + "hc_light": "keyword.operator: #000000", + "light_modern": "keyword.operator: #000000" + } + }, + { + "c": "456", + "t": "source.dotenv property.value.dotenv", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + } +] \ No newline at end of file diff --git a/code/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test_css.json b/code/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test_css.json index 73b86795d8a..98adba8bf54 100644 --- a/code/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test_css.json +++ b/code/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test_css.json @@ -125,6 +125,20 @@ "light_modern": "string: #A31515" } }, + { + "c": "mystyle.css", + "t": "string.quoted.double.css", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "dark_modern": "string: #CE9178", + "hc_light": "string: #0F4A85", + "light_modern": "string: #A31515" + } + }, { "c": "\"", "t": "string.quoted.double.css", @@ -209,6 +223,20 @@ "light_modern": "string: #A31515" } }, + { + "c": "mystyle.css", + "t": "string.quoted.double.css", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "dark_modern": "string: #CE9178", + "hc_light": "string: #0F4A85", + "light_modern": "string: #A31515" + } + }, { "c": "\"", "t": "string.quoted.double.css", @@ -307,6 +335,20 @@ "light_modern": "string: #A31515" } }, + { + "c": "bluish.css", + "t": "string.quoted.double.css", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "dark_modern": "string: #CE9178", + "hc_light": "string: #0F4A85", + "light_modern": "string: #A31515" + } + }, { "c": "\"", "t": "string.quoted.double.css", @@ -3387,6 +3429,20 @@ "light_modern": "string: #A31515" } }, + { + "c": "", + "t": "string.quoted.single.css", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "dark_modern": "string: #CE9178", + "hc_light": "string: #0F4A85", + "light_modern": "string: #A31515" + } + }, { "c": "'", "t": "string.quoted.single.css", @@ -7307,6 +7363,20 @@ "light_modern": "string: #A31515" } }, + { + "c": "#B3AE94", + "t": "string.quoted.single.css", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "dark_modern": "string: #CE9178", + "hc_light": "string: #0F4A85", + "light_modern": "string: #A31515" + } + }, { "c": "'", "t": "string.quoted.single.css", @@ -8413,6 +8483,20 @@ "light_modern": "string: #A31515" } }, + { + "c": "codicon-", + "t": "meta.selector.css string.quoted.single.css", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "dark_modern": "string: #CE9178", + "hc_light": "string: #0F4A85", + "light_modern": "string: #A31515" + } + }, { "c": "'", "t": "meta.selector.css string.quoted.single.css", diff --git a/code/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test_env.json b/code/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test_env.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/code/extensions/vscode-colorize-tests/test/colorize-tree-sitter-results/test_env.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/code/extensions/vscode-colorize-tests/tsconfig.json b/code/extensions/vscode-colorize-tests/tsconfig.json index 7234fdfeb97..20f72eae51d 100644 --- a/code/extensions/vscode-colorize-tests/tsconfig.json +++ b/code/extensions/vscode-colorize-tests/tsconfig.json @@ -2,6 +2,9 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", + "typeRoots": [ + "./node_modules/@types" + ], "types": [ "node" ] diff --git a/code/extensions/vscode-test-resolver/package.json b/code/extensions/vscode-test-resolver/package.json index c96c1d5894f..0990d7c5036 100644 --- a/code/extensions/vscode-test-resolver/package.json +++ b/code/extensions/vscode-test-resolver/package.json @@ -18,7 +18,7 @@ ], "scripts": { "compile": "node ./node_modules/vscode/bin/compile -watch -p ./", - "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-test-resolver" + "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.mjs compile-extension:vscode-test-resolver" }, "activationEvents": [ "onResolveRemoteAuthority:test", diff --git a/code/extensions/vscode-test-resolver/tsconfig.json b/code/extensions/vscode-test-resolver/tsconfig.json index d1c0f9d9f50..087654f228a 100644 --- a/code/extensions/vscode-test-resolver/tsconfig.json +++ b/code/extensions/vscode-test-resolver/tsconfig.json @@ -2,6 +2,9 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", + "typeRoots": [ + "./node_modules/@types" + ], "types": [ "node" ], diff --git a/code/gulpfile.js b/code/gulpfile.js deleted file mode 100644 index 4dce0234239..00000000000 --- a/code/gulpfile.js +++ /dev/null @@ -1,9 +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 { createRequire } from 'node:module'; - -const require = createRequire(import.meta.url); -require('./build/gulpfile'); diff --git a/code/gulpfile.mjs b/code/gulpfile.mjs new file mode 100644 index 00000000000..5acdbee578a --- /dev/null +++ b/code/gulpfile.mjs @@ -0,0 +1,5 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import './build/gulpfile.ts'; diff --git a/code/package-lock.json b/code/package-lock.json index 2d54381e1a8..61d5e44beae 100644 --- a/code/package-lock.json +++ b/code/package-lock.json @@ -1,42 +1,42 @@ { "name": "che-code", - "version": "1.104.3", + "version": "1.108.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "che-code", - "version": "1.104.3", + "version": "1.108.0", "hasInstallScript": true, "license": "MIT", "dependencies": { "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", - "@parcel/watcher": "2.5.1", "@types/semver": "^7.5.8", "@vscode/deviceid": "^0.1.1", - "@vscode/iconv-lite-umd": "0.7.0", + "@vscode/iconv-lite-umd": "0.7.1", "@vscode/policy-watcher": "^1.3.2", - "@vscode/proxy-agent": "^0.34.0", + "@vscode/proxy-agent": "^0.36.0", "@vscode/ripgrep": "^1.15.13", "@vscode/spdlog": "^0.15.2", - "@vscode/sqlite3": "5.1.8-vscode", + "@vscode/sqlite3": "5.1.10-vscode", "@vscode/sudo-prompt": "9.3.1", - "@vscode/tree-sitter-wasm": "^0.1.4", + "@vscode/tree-sitter-wasm": "^0.3.0", "@vscode/vscode-languagedetection": "1.0.21", + "@vscode/watcher": "bpasero/watcher#8ecffb4a57df24ac3e6946aa095b9b1f14f8bba9", "@vscode/windows-mutex": "^0.5.0", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.2.0-beta.101", - "@xterm/addon-image": "^0.9.0-beta.118", - "@xterm/addon-ligatures": "^0.10.0-beta.118", - "@xterm/addon-progress": "^0.2.0-beta.24", - "@xterm/addon-search": "^0.16.0-beta.118", - "@xterm/addon-serialize": "^0.14.0-beta.118", - "@xterm/addon-unicode11": "^0.9.0-beta.118", - "@xterm/addon-webgl": "^0.19.0-beta.118", - "@xterm/headless": "^5.6.0-beta.118", - "@xterm/xterm": "^5.6.0-beta.118", + "@xterm/addon-clipboard": "^0.3.0-beta.91", + "@xterm/addon-image": "^0.10.0-beta.91", + "@xterm/addon-ligatures": "^0.11.0-beta.91", + "@xterm/addon-progress": "^0.3.0-beta.91", + "@xterm/addon-search": "^0.17.0-beta.91", + "@xterm/addon-serialize": "^0.15.0-beta.91", + "@xterm/addon-unicode11": "^0.10.0-beta.91", + "@xterm/addon-webgl": "^0.20.0-beta.90", + "@xterm/headless": "^6.1.0-beta.91", + "@xterm/xterm": "^6.1.0-beta.91", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "js-yaml": "^4.1.0", @@ -44,17 +44,17 @@ "katex": "^0.16.22", "kerberos": "2.1.1", "minimist": "^1.2.8", - "native-is-elevated": "0.7.0", + "native-is-elevated": "0.8.0", "native-keymap": "^3.3.5", "native-watchdog": "^1.4.1", - "node-pty": "^1.1.0-beta33", + "node-pty": "^1.1.0-beta43", "open": "^10.1.2", - "tas-client-umd": "0.2.0", + "tas-client": "0.3.1", + "undici": "^7.9.0", "v8-inspect-profiler": "^0.1.1", "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", - "vscode-textmate": "9.2.0", - "ws": "8.2.3", + "vscode-textmate": "^9.3.0", "yauzl": "^3.0.0", "yazl": "^2.4.3" }, @@ -62,7 +62,7 @@ "che-code": "out/vs/server/main.js" }, "devDependencies": { - "@playwright/test": "^1.53.2", + "@playwright/test": "^1.56.1", "@stylistic/eslint-plugin-ts": "^2.8.0", "@types/cookie": "^0.3.3", "@types/debug": "^4.1.5", @@ -74,29 +74,29 @@ "@types/minimatch": "^3.0.5", "@types/minimist": "^1.2.1", "@types/mocha": "^10.0.10", - "@types/node": "22.x", + "@types/node": "^22.18.10", "@types/sinon": "^10.0.2", "@types/sinon-test": "^2.4.2", - "@types/trusted-types": "^1.0.6", + "@types/trusted-types": "^2.0.7", "@types/vscode-notebook-renderer": "^1.72.0", "@types/webpack": "^5.28.5", - "@types/wicg-file-system-access": "^2020.9.6", + "@types/wicg-file-system-access": "^2023.10.7", "@types/windows-foreground-love": "^0.3.0", "@types/winreg": "^1.2.30", "@types/ws": "8.2.0", "@types/yauzl": "^2.10.0", "@types/yazl": "^2.4.2", - "@typescript-eslint/utils": "^8.36.0", + "@typescript-eslint/utils": "^8.45.0", "@typescript/native-preview": "^7.0.0-dev.20250812.1", "@vscode/gulp-electron": "^1.38.2", "@vscode/l10n-dev": "0.0.18", "@vscode/telemetry-extractor": "^1.10.2", "@vscode/test-cli": "^0.0.6", "@vscode/test-electron": "^2.4.0", - "@vscode/test-web": "^0.0.62", + "@vscode/test-web": "^0.0.76", "@vscode/v8-heap-parser": "^0.1.0", "@vscode/vscode-perf": "^0.0.19", - "@webgpu/types": "^0.1.44", + "@webgpu/types": "^0.1.66", "ansi-colors": "^3.2.3", "asar": "^3.0.3", "chromium-pickle-js": "^0.2.0", @@ -105,8 +105,8 @@ "css-loader": "^6.9.1", "debounce": "^1.0.0", "deemon": "^1.13.6", - "electron": "37.3.1", - "eslint": "^9.11.1", + "electron": "39.2.7", + "eslint": "^9.36.0", "eslint-formatter-compact": "^8.40.0", "eslint-plugin-header": "3.1.1", "eslint-plugin-jsdoc": "^50.3.1", @@ -143,7 +143,7 @@ "mocha": "^10.8.2", "mocha-junit-reporter": "^2.2.1", "mocha-multi-reporters": "^1.5.1", - "npm-run-all": "^4.1.5", + "npm-run-all2": "^8.0.4", "os-browserify": "^0.3.0", "p-all": "^1.0.0", "path-browserify": "^1.0.1", @@ -156,18 +156,17 @@ "source-map-support": "^0.3.2", "style-loader": "^3.3.2", "ts-loader": "^9.5.1", - "ts-node": "^10.9.1", "tsec": "0.2.7", "tslib": "^2.6.3", - "typescript": "^6.0.0-dev.20250827", - "typescript-eslint": "^8.39.0", + "typescript": "^6.0.0-dev.20251110", + "typescript-eslint": "^8.45.0", "util": "^0.12.4", "webpack": "^5.94.0", "webpack-cli": "^5.1.4", "webpack-stream": "^7.0.0", "xml2js": "^0.5.0", "yaserver": "^0.4.0", - "zx": "^8.7.0" + "zx": "^8.8.5" }, "optionalDependencies": { "windows-foreground-love": "0.5.0" @@ -734,28 +733,6 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz", @@ -851,9 +828,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", - "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "dev": true, "license": "MIT", "dependencies": { @@ -870,21 +847,23 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", - "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/config-array": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", - "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.4", + "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -892,20 +871,35 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/core": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.6.0.tgz", - "integrity": "sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==", + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/eslintrc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", - "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -925,50 +919,42 @@ } }, "node_modules/@eslint/js": { - "version": "9.11.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.11.1.tgz", - "integrity": "sha512-/qu+TWz8WwPWc7/HcIJKi+c+MOm46GdVaSlTTQcaqaL53+GsoA6MxWp5PtTx48qbSP7ylM1Kn7nhvkugfJvRSA==", + "version": "9.36.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.36.0.tgz", + "integrity": "sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==", "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, "node_modules/@eslint/object-schema": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", - "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", - "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.12.0", + "@eslint/core": "^0.15.2", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", - "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/@gulp-sourcemaps/identity-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-2.0.1.tgz", @@ -1076,6 +1062,30 @@ "xtend": "~4.0.1" } }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -1090,10 +1100,11 @@ } }, "node_modules/@humanwhocodes/retry": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", - "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=18.18" }, @@ -1107,6 +1118,7 @@ "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", @@ -1120,10 +1132,11 @@ } }, "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "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" }, @@ -1132,10 +1145,11 @@ } }, "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "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" }, @@ -1147,13 +1161,15 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true + "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", @@ -1167,10 +1183,11 @@ } }, "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "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" }, @@ -1186,6 +1203,7 @@ "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", @@ -1290,18 +1308,19 @@ } }, "node_modules/@koa/router": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/@koa/router/-/router-13.1.0.tgz", - "integrity": "sha512-mNVu1nvkpSd8Q8gMebGbCkDWJ51ODetrFvLKYusej+V0ByD4btqHYnPIzTBLXnQMVUlm/oxVwqmWBY3zQfZilw==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@koa/router/-/router-14.0.0.tgz", + "integrity": "sha512-LBSu5K0qAaaQcXX/0WIB9PGDevyCxxpnc1uq13vV/CgObaVxuis5hKl3Eboq/8gcb6ebnkAStW9NB/Em2eYyFA==", "dev": true, "license": "MIT", "dependencies": { + "debug": "^4.4.1", "http-errors": "^2.0.0", "koa-compose": "^4.1.0", - "path-to-regexp": "^6.3.0" + "path-to-regexp": "^8.2.0" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@malept/cross-spawn-promise": { @@ -1579,318 +1598,12 @@ "node": ">=8.0.0" } }, - "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==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "detect-libc": "^1.0.3", - "is-glob": "^4.0.3", - "micromatch": "^4.0.5", - "node-addon-api": "^7.0.0" - }, - "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" - ], - "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" - ], - "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" - ], - "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" - ], - "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" - ], - "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" - ], - "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" - ], - "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" - ], - "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" - ], - "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" - ], - "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" - ], - "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" - ], - "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" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher/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==", - "license": "Apache-2.0", - "bin": { - "detect-libc": "bin/detect-libc.js" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, + "license": "MIT", "optional": true, "engines": { "node": ">=14" @@ -1909,27 +1622,27 @@ } }, "node_modules/@playwright/browser-chromium": { - "version": "1.47.2", - "resolved": "https://registry.npmjs.org/@playwright/browser-chromium/-/browser-chromium-1.47.2.tgz", - "integrity": "sha512-tsk9bLcGzIu4k4xI2ixlwDrdJhMqCalUCsSj7TRI8VuvK7cLiJIa5SR0dprKbX+wkku/JMR4EN6g9DMHvfna+Q==", + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/@playwright/browser-chromium/-/browser-chromium-1.56.1.tgz", + "integrity": "sha512-n4xzZpOn4qOtZJylpIn8co2QDoWczfJ068sEeky3EE5Vvy+lHX2J3WAcC4MbXzcpfoBee1lJm8JtXuLZ9HBCBA==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.47.2" + "playwright-core": "1.56.1" }, "engines": { "node": ">=18" } }, "node_modules/@playwright/test": { - "version": "1.53.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.2.tgz", - "integrity": "sha512-tEB2U5z74ebBeyfGNZ3Jfg29AnW+5HlWhvHtb/Mqco9pFdZU1ZLNdVb2UtB5CvmiilNr2ZfVH/qMmAROG/XTzw==", + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.1.tgz", + "integrity": "sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.53.2" + "playwright": "1.56.1" }, "bin": { "playwright": "cli.js" @@ -2102,30 +1815,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", - "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", - "dev": true - }, "node_modules/@types/cacheable-request": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", @@ -2292,13 +1981,13 @@ "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/node-fetch": { @@ -2367,10 +2056,11 @@ "dev": true }, "node_modules/@types/trusted-types": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-1.0.6.tgz", - "integrity": "sha512-230RC8sFeHoT6sSUlRO6a8cAnclO06eeiq1QDfiv2FGCLWFvvERWgwIQD4FWqD9A69BN7Lzee4OXwoMVnnsWDw==", - "dev": true + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true, + "license": "MIT" }, "node_modules/@types/tunnel": { "version": "0.0.3", @@ -2409,10 +2099,11 @@ } }, "node_modules/@types/wicg-file-system-access": { - "version": "2020.9.6", - "resolved": "https://registry.npmjs.org/@types/wicg-file-system-access/-/wicg-file-system-access-2020.9.6.tgz", - "integrity": "sha512-6hogE75Hl2Ov/jgp8ZhDaGmIF/q3J07GtXf8nCJCwKTHq7971po5+DId7grft09zG7plBwpF6ZU0yx9Du4/e1A==", - "dev": true + "version": "2023.10.7", + "resolved": "https://registry.npmjs.org/@types/wicg-file-system-access/-/wicg-file-system-access-2023.10.7.tgz", + "integrity": "sha512-g49ijasEJvCd7ifmAY2D0wdEtt1xRjBbA33PJTiv8mKBr7DoMsPeISoJ8oQOTopSRi+FBWPpPW5ouDj2QPKtGA==", + "dev": true, + "license": "MIT" }, "node_modules/@types/windows-foreground-love": { "version": "0.3.0", @@ -2455,17 +2146,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.0.tgz", - "integrity": "sha512-bhEz6OZeUR+O/6yx9Jk6ohX6H9JSFTaiY0v9/PuKT3oGK0rn0jNplLmyFUGV+a9gfYnVNwGDwS/UkLIuXNb2Rw==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.45.0.tgz", + "integrity": "sha512-HC3y9CVuevvWCl/oyZuI47dOeDF9ztdMEfMH8/DW/Mhwa9cCLnK1oD7JoTVGW/u7kFzNZUKUoyJEqkaJh5y3Wg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.39.0", - "@typescript-eslint/type-utils": "8.39.0", - "@typescript-eslint/utils": "8.39.0", - "@typescript-eslint/visitor-keys": "8.39.0", + "@typescript-eslint/scope-manager": "8.45.0", + "@typescript-eslint/type-utils": "8.45.0", + "@typescript-eslint/utils": "8.45.0", + "@typescript-eslint/visitor-keys": "8.45.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -2479,7 +2170,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.39.0", + "@typescript-eslint/parser": "^8.45.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } @@ -2495,16 +2186,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.39.0.tgz", - "integrity": "sha512-g3WpVQHngx0aLXn6kfIYCZxM6rRJlWzEkVpqEFLT3SgEDsp9cpCbxxgwnE504q4H+ruSDh/VGS6nqZIDynP+vg==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.45.0.tgz", + "integrity": "sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.39.0", - "@typescript-eslint/types": "8.39.0", - "@typescript-eslint/typescript-estree": "8.39.0", - "@typescript-eslint/visitor-keys": "8.39.0", + "@typescript-eslint/scope-manager": "8.45.0", + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/typescript-estree": "8.45.0", + "@typescript-eslint/visitor-keys": "8.45.0", "debug": "^4.3.4" }, "engines": { @@ -2520,14 +2211,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.39.0.tgz", - "integrity": "sha512-CTzJqaSq30V/Z2Og9jogzZt8lJRR5TKlAdXmWgdu4hgcC9Kww5flQ+xFvMxIBWVNdxJO7OifgdOK4PokMIWPew==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.45.0.tgz", + "integrity": "sha512-3pcVHwMG/iA8afdGLMuTibGR7pDsn9RjDev6CCB+naRsSYs2pns5QbinF4Xqw6YC/Sj3lMrm/Im0eMfaa61WUg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.39.0", - "@typescript-eslint/types": "^8.39.0", + "@typescript-eslint/tsconfig-utils": "^8.45.0", + "@typescript-eslint/types": "^8.45.0", "debug": "^4.3.4" }, "engines": { @@ -2542,14 +2233,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.39.0.tgz", - "integrity": "sha512-8QOzff9UKxOh6npZQ/4FQu4mjdOCGSdO3p44ww0hk8Vu+IGbg0tB/H1LcTARRDzGCC8pDGbh2rissBuuoPgH8A==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.45.0.tgz", + "integrity": "sha512-clmm8XSNj/1dGvJeO6VGH7EUSeA0FMs+5au/u3lrA3KfG8iJ4u8ym9/j2tTEoacAffdW1TVUzXO30W1JTJS7dA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.39.0", - "@typescript-eslint/visitor-keys": "8.39.0" + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/visitor-keys": "8.45.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2560,9 +2251,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.39.0.tgz", - "integrity": "sha512-Fd3/QjmFV2sKmvv3Mrj8r6N8CryYiCS8Wdb/6/rgOXAWGcFuc+VkQuG28uk/4kVNVZBQuuDHEDUpo/pQ32zsIQ==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.45.0.tgz", + "integrity": "sha512-aFdr+c37sc+jqNMGhH+ajxPXwjv9UtFZk79k8pLoJ6p4y0snmYpPA52GuWHgt2ZF4gRRW6odsEj41uZLojDt5w==", "dev": true, "license": "MIT", "engines": { @@ -2577,15 +2268,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.39.0.tgz", - "integrity": "sha512-6B3z0c1DXVT2vYA9+z9axjtc09rqKUPRmijD5m9iv8iQpHBRYRMBcgxSiKTZKm6FwWw1/cI4v6em35OsKCiN5Q==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.45.0.tgz", + "integrity": "sha512-bpjepLlHceKgyMEPglAeULX1vixJDgaKocp0RVJ5u4wLJIMNuKtUXIczpJCPcn2waII0yuvks/5m5/h3ZQKs0A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.39.0", - "@typescript-eslint/typescript-estree": "8.39.0", - "@typescript-eslint/utils": "8.39.0", + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/typescript-estree": "8.45.0", + "@typescript-eslint/utils": "8.45.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -2602,9 +2293,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.39.0.tgz", - "integrity": "sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.45.0.tgz", + "integrity": "sha512-WugXLuOIq67BMgQInIxxnsSyRLFxdkJEJu8r4ngLR56q/4Q5LrbfkFRH27vMTjxEK8Pyz7QfzuZe/G15qQnVRA==", "dev": true, "license": "MIT", "engines": { @@ -2616,16 +2307,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.39.0.tgz", - "integrity": "sha512-ndWdiflRMvfIgQRpckQQLiB5qAKQ7w++V4LlCHwp62eym1HLB/kw7D9f2e8ytONls/jt89TEasgvb+VwnRprsw==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.45.0.tgz", + "integrity": "sha512-GfE1NfVbLam6XQ0LcERKwdTTPlLvHvXXhOeUGC1OXi4eQBoyy1iVsW+uzJ/J9jtCz6/7GCQ9MtrQ0fml/jWCnA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.39.0", - "@typescript-eslint/tsconfig-utils": "8.39.0", - "@typescript-eslint/types": "8.39.0", - "@typescript-eslint/visitor-keys": "8.39.0", + "@typescript-eslint/project-service": "8.45.0", + "@typescript-eslint/tsconfig-utils": "8.45.0", + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/visitor-keys": "8.45.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -2671,16 +2362,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.39.0.tgz", - "integrity": "sha512-4GVSvNA0Vx1Ktwvf4sFE+exxJ3QGUorQG1/A5mRfRNZtkBT2xrA/BCO2H0eALx/PnvCS6/vmYwRdDA41EoffkQ==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.45.0.tgz", + "integrity": "sha512-bxi1ht+tLYg4+XV2knz/F7RVhU0k6VrSMc9sb8DQ6fyCTrGQLHfo7lDtN0QJjZjKkLA2ThrKuCdHEvLReqtIGg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.39.0", - "@typescript-eslint/types": "8.39.0", - "@typescript-eslint/typescript-estree": "8.39.0" + "@typescript-eslint/scope-manager": "8.45.0", + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/typescript-estree": "8.45.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2695,13 +2386,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.39.0.tgz", - "integrity": "sha512-ldgiJ+VAhQCfIjeOgu8Kj5nSxds0ktPOSO9p4+0VDH2R2pLvQraaM5Oen2d7NxzMCm+Sn/vJT+mv2H5u6b/3fA==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.45.0.tgz", + "integrity": "sha512-qsaFBA3e09MIDAGFUrTk+dzqtfv1XPVz8t8d1f0ybTzrCY7BKiMC5cjrl1O/P7UmHsNyW90EYSkU/ZWpmXelag==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.39.0", + "@typescript-eslint/types": "8.45.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -2726,31 +2417,28 @@ } }, "node_modules/@typescript/native-preview": { - "version": "7.0.0-dev.20250827.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview/-/native-preview-7.0.0-dev.20250827.1.tgz", - "integrity": "sha512-AdSaD57/ZXM4UEyuzfqt4daiGeN2y8CFogwAKNjyQV6muQWi5QCmuLNkEbQk7Teg33lCeycrHCTjz9aPgbrJIw==", + "version": "7.0.0-dev.20251110.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview/-/native-preview-7.0.0-dev.20251110.1.tgz", + "integrity": "sha512-yzCDN6wUV1kibefOTwxw1MdeIgaJOgN5/a06cMyUlEDcXBriV4O2v+yeXY8c3yzUaVVVO8CKtHPbCMwro4j1Dw==", "dev": true, "license": "Apache-2.0", "bin": { "tsgo": "bin/tsgo.js" }, - "engines": { - "node": ">=20.6.0" - }, "optionalDependencies": { - "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20250827.1", - "@typescript/native-preview-darwin-x64": "7.0.0-dev.20250827.1", - "@typescript/native-preview-linux-arm": "7.0.0-dev.20250827.1", - "@typescript/native-preview-linux-arm64": "7.0.0-dev.20250827.1", - "@typescript/native-preview-linux-x64": "7.0.0-dev.20250827.1", - "@typescript/native-preview-win32-arm64": "7.0.0-dev.20250827.1", - "@typescript/native-preview-win32-x64": "7.0.0-dev.20250827.1" + "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20251110.1", + "@typescript/native-preview-darwin-x64": "7.0.0-dev.20251110.1", + "@typescript/native-preview-linux-arm": "7.0.0-dev.20251110.1", + "@typescript/native-preview-linux-arm64": "7.0.0-dev.20251110.1", + "@typescript/native-preview-linux-x64": "7.0.0-dev.20251110.1", + "@typescript/native-preview-win32-arm64": "7.0.0-dev.20251110.1", + "@typescript/native-preview-win32-x64": "7.0.0-dev.20251110.1" } }, "node_modules/@typescript/native-preview-darwin-arm64": { - "version": "7.0.0-dev.20250827.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-arm64/-/native-preview-darwin-arm64-7.0.0-dev.20250827.1.tgz", - "integrity": "sha512-lTgoQK7wkbkb/gxNn6ZcdpBtEh+tBgXJQbl7feMP/BdpNvFJNFKW6DN+1wlXAJj47GYfMvmCN+vk571a9oO4cw==", + "version": "7.0.0-dev.20251110.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-arm64/-/native-preview-darwin-arm64-7.0.0-dev.20251110.1.tgz", + "integrity": "sha512-x3DskzZCgk5qA7BCcCC/8XuZiycvZk5reeqkNTuDYeWyF1ZCKa8WWZRbW5LaunaOtXV6UsAPRCqRC8Wx34mMCg==", "cpu": [ "arm64" ], @@ -2759,15 +2447,12 @@ "optional": true, "os": [ "darwin" - ], - "engines": { - "node": ">=20.6.0" - } + ] }, "node_modules/@typescript/native-preview-darwin-x64": { - "version": "7.0.0-dev.20250827.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-x64/-/native-preview-darwin-x64-7.0.0-dev.20250827.1.tgz", - "integrity": "sha512-xPyU3x81DdhAiuh+cqLYK2733+mQuRqMaHCrdwL8s1dy+swjqnwLq9e685Kj00QndOBXm0y21EQ32uH1okv7Ig==", + "version": "7.0.0-dev.20251110.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-x64/-/native-preview-darwin-x64-7.0.0-dev.20251110.1.tgz", + "integrity": "sha512-tuS4akGtsPs+RTiVXEXOT41+as23DXCOhzeOEtYYVdhWVuMBYLHksdTx5PGoQrCc4SfETp5jDwhyqUaVYLDGcA==", "cpu": [ "x64" ], @@ -2776,15 +2461,12 @@ "optional": true, "os": [ "darwin" - ], - "engines": { - "node": ">=20.6.0" - } + ] }, "node_modules/@typescript/native-preview-linux-arm": { - "version": "7.0.0-dev.20250827.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm/-/native-preview-linux-arm-7.0.0-dev.20250827.1.tgz", - "integrity": "sha512-vvkrXO9HvkWMzKlvdM3y9oV3TEupfD/IWttFuTiKBa2oUSxAo9sptHYkPi43756f1jrVmagCn5PsFWJNO4TA5Q==", + "version": "7.0.0-dev.20251110.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm/-/native-preview-linux-arm-7.0.0-dev.20251110.1.tgz", + "integrity": "sha512-I9zOzHXFqIQIcTcf2Sx9EF6gLOKXUCMo5gsjoQm4/R22+19+TMLeAs7Q1aTvd8CX8kFCtpI1eeyNzIf76rxELA==", "cpu": [ "arm" ], @@ -2793,15 +2475,12 @@ "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">=20.6.0" - } + ] }, "node_modules/@typescript/native-preview-linux-arm64": { - "version": "7.0.0-dev.20250827.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm64/-/native-preview-linux-arm64-7.0.0-dev.20250827.1.tgz", - "integrity": "sha512-pUKOi7wwNtRCBY/mGtSRAf/6DwOz+5XbFeifWuiFCxPvFEJrbUaF6naAh2d1bJVOCCwiZCCBGMRy708eoclvBw==", + "version": "7.0.0-dev.20251110.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm64/-/native-preview-linux-arm64-7.0.0-dev.20251110.1.tgz", + "integrity": "sha512-IvSeQ1iw4uvBZ8+XrO9z80J9KfbkbTzfXliPHUsjZqEtpOJTf/Mv7xzMbv4mN4xOEGVUyBG47p846oW2HknogA==", "cpu": [ "arm64" ], @@ -2810,15 +2489,12 @@ "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">=20.6.0" - } + ] }, "node_modules/@typescript/native-preview-linux-x64": { - "version": "7.0.0-dev.20250827.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-x64/-/native-preview-linux-x64-7.0.0-dev.20250827.1.tgz", - "integrity": "sha512-W4VRgMd+JdiC6+9S8ai/PqV5dwRfv/U77Es7yfWCR2RsKBloZQXQzwN9bbxErbo3LlGznw/7QZWBJRrMrQuwVg==", + "version": "7.0.0-dev.20251110.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-x64/-/native-preview-linux-x64-7.0.0-dev.20251110.1.tgz", + "integrity": "sha512-OWy32tgpP70rSRvmQZ6OgJpuv1pi4mQdng00eF3tfHheHluX3mvqqe86H0FOv5B9PuxlGwOZSUot1XHWadhAWg==", "cpu": [ "x64" ], @@ -2827,15 +2503,12 @@ "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">=20.6.0" - } + ] }, "node_modules/@typescript/native-preview-win32-arm64": { - "version": "7.0.0-dev.20250827.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-arm64/-/native-preview-win32-arm64-7.0.0-dev.20250827.1.tgz", - "integrity": "sha512-vfdlEP+zAHYvPZnyz2HB1FTTjPxYLYl1A30JrEBpod7rqPZU04rah3ykcI6+7BRPehClHBCeoTMC7Bn0hi/CeA==", + "version": "7.0.0-dev.20251110.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-arm64/-/native-preview-win32-arm64-7.0.0-dev.20251110.1.tgz", + "integrity": "sha512-u/Bo0gIcQCv/4MDnV5f2FZR1dEdN2jk3MfkmJLKGG1zwbak4MY7sWNzvSRJHihwK2SxtcJEHus4tKb2ra2Rhig==", "cpu": [ "arm64" ], @@ -2844,15 +2517,12 @@ "optional": true, "os": [ "win32" - ], - "engines": { - "node": ">=20.6.0" - } + ] }, "node_modules/@typescript/native-preview-win32-x64": { - "version": "7.0.0-dev.20250827.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-x64/-/native-preview-win32-x64-7.0.0-dev.20250827.1.tgz", - "integrity": "sha512-fuU+XxEbUzqsMEO41HK+oUd5vu4mkP7zi0UIs1HOl9N0h548GaC5MVHTk7ErrvNiRSY8ffNiCxhiy32YjpJD1Q==", + "version": "7.0.0-dev.20251110.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-x64/-/native-preview-win32-x64-7.0.0-dev.20251110.1.tgz", + "integrity": "sha512-1CysgwFRuNjR0bBYv6RI3fbXtAwzD5OlbxqOQFhf2lUulMZRIkP1w4eCChSndLVCTfnUEt5Bnmn1JEUauIE+kQ==", "cpu": [ "x64" ], @@ -2861,10 +2531,7 @@ "optional": true, "os": [ "win32" - ], - "engines": { - "node": ">=20.6.0" - } + ] }, "node_modules/@vscode/deviceid": { "version": "0.1.1", @@ -3166,9 +2833,10 @@ } }, "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==" + "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==", + "license": "MIT" }, "node_modules/@vscode/l10n-dev": { "version": "0.0.18", @@ -3199,30 +2867,30 @@ } }, "node_modules/@vscode/l10n-dev/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": ">=12" + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@vscode/l10n-dev/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "license": "ISC", "dependencies": { @@ -3260,10 +2928,20 @@ "node": ">=4.0.0" } }, + "node_modules/@vscode/l10n-dev/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/@vscode/policy-watcher": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@vscode/policy-watcher/-/policy-watcher-1.3.2.tgz", - "integrity": "sha512-fmNPYysU2ioH99uCaBPiRblEZSnir5cTmc7w91hAxAoYoGpHt2PZPxT5eIOn7FGmPOsjLdQcd6fduFJGYVD4Mw==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@vscode/policy-watcher/-/policy-watcher-1.3.5.tgz", + "integrity": "sha512-k1n9gaDBjyVRy5yJLABbZCnyFwgQ8OA4sR3vXmXnmB+mO9JA0nsl/XOXQfVCoLasBu3UHCOfAnDWGn2sRzCR+A==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -3281,9 +2959,9 @@ } }, "node_modules/@vscode/proxy-agent": { - "version": "0.34.0", - "resolved": "https://registry.npmjs.org/@vscode/proxy-agent/-/proxy-agent-0.34.0.tgz", - "integrity": "sha512-LrX5mb+0vgvGQ/1jLvpsd4tUzlCVYNjvu+vvPx+yV2AvyXXnRQj/Qom1Fiavw9Mfmxw3+AHfzZ73tXwTMCfEdQ==", + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/@vscode/proxy-agent/-/proxy-agent-0.36.0.tgz", + "integrity": "sha512-W4mls/+zErqTYcKC41utdmoYnBWZRH1dRF9U4cBAyKU5EhcnWfVsPBvUnXXw1CffI3djmMWnu9JrF/Ynw7lkcg==", "license": "MIT", "dependencies": { "@tootallnate/once": "^3.0.0", @@ -3294,6 +2972,9 @@ "socks-proxy-agent": "^8.0.1", "undici": "^7.2.0" }, + "engines": { + "node": ">=22.15.0" + }, "optionalDependencies": { "@vscode/windows-ca-certs": "^0.3.1" } @@ -3320,9 +3001,9 @@ } }, "node_modules/@vscode/spdlog": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@vscode/spdlog/-/spdlog-0.15.2.tgz", - "integrity": "sha512-8RQ7JEs81x5IFONYGtFhYtaF2a3IPtNtgMdp+MFLxTDokJQBAVittx0//EN38BYhlzeVqEPgusRsOA8Yulaysg==", + "version": "0.15.4", + "resolved": "https://registry.npmjs.org/@vscode/spdlog/-/spdlog-0.15.4.tgz", + "integrity": "sha512-NmFasVWjn/6BjHMAjqalsbG2srQCt8yfC0EczP5wzNQFawv74rhvuarhWi44x3St9LB8bZBxrpbT7igPaTJwcw==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -3332,9 +3013,9 @@ } }, "node_modules/@vscode/sqlite3": { - "version": "5.1.8-vscode", - "resolved": "https://registry.npmjs.org/@vscode/sqlite3/-/sqlite3-5.1.8-vscode.tgz", - "integrity": "sha512-9Ku18yZej1kxS7mh6dhCWxkCof043HljcLIdq+RRJr65QdOeAqPOUJ2i6qXRL63l1Kd72uXV/zLA2SBwhfgiOw==", + "version": "5.1.10-vscode", + "resolved": "https://registry.npmjs.org/@vscode/sqlite3/-/sqlite3-5.1.10-vscode.tgz", + "integrity": "sha512-sCJozBr1jItK4eCtbibX3Vi8BXfNyDsPCplojm89OuydoSxwP+Z3gSgzsTXWD5qYyXpTvVaT3LtHLoH2Byv8oA==", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { @@ -3401,192 +3082,102 @@ } }, "node_modules/@vscode/test-cli/node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", - "dev": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vscode/test-cli/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vscode/test-electron": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.4.0.tgz", - "integrity": "sha512-yojuDFEjohx6Jb+x949JRNtSn6Wk2FAh4MldLE3ck9cfvCqzwxF32QsNy1T9Oe4oT+ZfFcg0uPUCajJzOmPlTA==", - "dev": true, - "dependencies": { - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.4", - "jszip": "^3.10.1", - "ora": "^7.0.1", - "semver": "^7.6.2" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@vscode/test-web": { - "version": "0.0.62", - "resolved": "https://registry.npmjs.org/@vscode/test-web/-/test-web-0.0.62.tgz", - "integrity": "sha512-Ypug5PvhPOPFbuHVilai7t23tm3Wm5geIpC2DB09Gy9o0jZCduramiSdPf+YN7yhkFy1usFYtN3Eaks1XoBrOQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@koa/cors": "^5.0.0", - "@koa/router": "^13.1.0", - "@playwright/browser-chromium": "^1.47.2", - "glob": "^11.0.0", - "gunzip-maybe": "^1.4.2", - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.5", - "koa": "^2.15.3", - "koa-morgan": "^1.0.1", - "koa-mount": "^4.0.0", - "koa-static": "^5.0.0", - "minimist": "^1.2.8", - "playwright": "^1.47.2", - "tar-fs": "^3.0.6", - "vscode-uri": "^3.0.8" - }, - "bin": { - "vscode-test-web": "out/server/index.js" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@vscode/test-web/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==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@vscode/test-web/node_modules/glob": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", - "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, + "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^4.0.1", - "minimatch": "^10.0.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" + "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vscode/test-web/node_modules/jackspeak": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz", - "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==", - "dev": true, - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": "20 || >=22" - }, "funding": { "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/@vscode/test-web/node_modules/lru-cache": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.1.tgz", - "integrity": "sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==", - "dev": true, - "engines": { - "node": "20 || >=22" } }, - "node_modules/@vscode/test-web/node_modules/minimatch": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", - "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "node_modules/@vscode/test-cli/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": "20 || >=22" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@vscode/test-web/node_modules/minipass": { + "node_modules/@vscode/test-cli/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/@vscode/test-web/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==", + "node_modules/@vscode/test-electron": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.4.0.tgz", + "integrity": "sha512-yojuDFEjohx6Jb+x949JRNtSn6Wk2FAh4MldLE3ck9cfvCqzwxF32QsNy1T9Oe4oT+ZfFcg0uPUCajJzOmPlTA==", "dev": true, "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.4", + "jszip": "^3.10.1", + "ora": "^7.0.1", + "semver": "^7.6.2" }, "engines": { - "node": "20 || >=22" + "node": ">=16" + } + }, + "node_modules/@vscode/test-web": { + "version": "0.0.76", + "resolved": "https://registry.npmjs.org/@vscode/test-web/-/test-web-0.0.76.tgz", + "integrity": "sha512-hB+GKNmxnaTKemNOOBUcqYsIa5a0uuccCRnNIdCMS+I3RhVlyCtLBl29ZN/RAB2+M+ujjI8L8qL6GLCPqNFIBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@koa/cors": "^5.0.0", + "@koa/router": "^14.0.0", + "@playwright/browser-chromium": "^1.56.1", + "gunzip-maybe": "^1.4.2", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "koa": "^3.1.1", + "koa-morgan": "^1.0.1", + "koa-mount": "^4.2.0", + "koa-static": "^5.0.0", + "minimist": "^1.2.8", + "playwright": "^1.56.1", + "tar-fs": "^3.1.1", + "tinyglobby": "^0.2.15", + "vscode-uri": "^3.1.0" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "bin": { + "vscode-test-web": "out/server/index.js" + }, + "engines": { + "node": ">=20" } }, "node_modules/@vscode/tree-sitter-wasm": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@vscode/tree-sitter-wasm/-/tree-sitter-wasm-0.1.4.tgz", - "integrity": "sha512-kQVVg/CamCYDM+/XYCZuNTQyixjZd8ts/Gf84UzjEY0eRnbg6kiy5I9z2/2i3XdqwhI87iG07rkMR2KwhqcSbA==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@vscode/tree-sitter-wasm/-/tree-sitter-wasm-0.3.0.tgz", + "integrity": "sha512-4kjB1jgLyG9VimGfyJb1F8/GFdrx55atsBCH/9r2D/iZHAUDCvZ5zhWXB7sRQ2z2WkkuNYm/0pgQtUm1jhdf7A==", "license": "MIT" }, "node_modules/@vscode/v8-heap-parser": { @@ -3623,6 +3214,26 @@ "node": ">= 16" } }, + "node_modules/@vscode/watcher": { + "version": "2.5.1-vscode", + "resolved": "git+ssh://git@github.com/bpasero/watcher.git#8ecffb4a57df24ac3e6946aa095b9b1f14f8bba9", + "integrity": "sha512-7F4REbtMh5JAtdPpBCyPq7yLgcqnZV5L+uzuT4IDaZUyCKvIqi9gDiNPyoKpvCtrw6funLmrAncFHHWoDI+S4g==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/@vscode/windows-ca-certs": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@vscode/windows-ca-certs/-/windows-ca-certs-0.3.3.tgz", @@ -3648,29 +3259,32 @@ } }, "node_modules/@vscode/windows-mutex": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@vscode/windows-mutex/-/windows-mutex-0.5.0.tgz", - "integrity": "sha512-iD29L9AUscpn07aAvhP2QuhrXzuKc1iQpPF6u7ybtvRbR+o+RotfbuKqqF1RDlDDrJZkL+3AZTy4D01U4nEe5A==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@vscode/windows-mutex/-/windows-mutex-0.5.2.tgz", + "integrity": "sha512-O9CNYVl2GmFVbiHiz7tyFrKIdXVs3qf8HnyWlfxyuMaKzXd1L35jSTNCC1oAVwr8F0O2P4o3C/jOSIXulUCJ7w==", "hasInstallScript": true, + "license": "MIT", "dependencies": { "bindings": "^1.5.0", "node-addon-api": "7.1.0" } }, "node_modules/@vscode/windows-process-tree": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@vscode/windows-process-tree/-/windows-process-tree-0.6.0.tgz", - "integrity": "sha512-7/DjBKKUtlmKNiAet2GRbdvfjgMKmfBeWVClIgONv8aqxGnaKca5N85eIDxh6rLMy2hKvFqIIsqgxs1Q26TWwg==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@vscode/windows-process-tree/-/windows-process-tree-0.6.2.tgz", + "integrity": "sha512-uzyUuQ93m7K1jSPrB/72m4IspOyeGpvvghNwFCay/McZ+y4Hk2BnLdZPb6EJ8HLRa3GwCvYjH/MQZzcnLOVnaQ==", "hasInstallScript": true, + "license": "MIT", "dependencies": { "node-addon-api": "7.1.0" } }, "node_modules/@vscode/windows-registry": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@vscode/windows-registry/-/windows-registry-1.1.0.tgz", - "integrity": "sha512-5AZzuWJpGscyiMOed0IuyEwt6iKmV5Us7zuwCDCFYMIq7tsvooO9BUiciywsvuthGz6UG4LSpeDeCxvgMVhnIw==", - "hasInstallScript": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@vscode/windows-registry/-/windows-registry-1.1.2.tgz", + "integrity": "sha512-/eDRmGNe6g11wHckOyiVLvK/mEE5UBZFeoRlBosIL343LDrSKUL5JDAcFeAZqOXnlTtZ3UZtj5yezKiAz99NcA==", + "hasInstallScript": true, + "license": "MIT" }, "node_modules/@webassemblyjs/ast": { "version": "1.14.1", @@ -3834,10 +3448,11 @@ } }, "node_modules/@webgpu/types": { - "version": "0.1.44", - "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.44.tgz", - "integrity": "sha512-JDpYJN5E/asw84LTYhKyvPpxGnD+bAKPtpW9Ilurf7cZpxaTbxkQcGwOd7jgB9BPBrTYQ+32ufo4HiuomTjHNQ==", - "dev": true + "version": "0.1.66", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.66.tgz", + "integrity": "sha512-YA2hLrwLpDsRueNDXIMqN9NTzD6bCDkuXbOSe0heS+f8YE8usA6Gbv1prj81pzVHrbaAma7zObnIC+I6/sXJgA==", + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/@webpack-cli/configtest": { "version": "2.1.1", @@ -3883,99 +3498,133 @@ } } }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.11", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz", + "integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/@xterm/addon-clipboard": { - "version": "0.2.0-beta.101", - "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.101.tgz", - "integrity": "sha512-Q20+bySNgX9gGb8NYidHah/TzoCyOm/0+Orea9+jwCHADN/dnqb0vzT0UIvxZckFFW4twZnjNps19rszWoGC0A==", + "version": "0.3.0-beta.91", + "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.3.0-beta.91.tgz", + "integrity": "sha512-jVOjr6yu/zFS64rMTbgsAToqp++ZYam2dLE8dudKEIPcjV1Oxv6krvhdmhISGqTAU+ZhzphhccpXfovdTh+UYQ==", "license": "MIT", "dependencies": { "js-base64": "^3.7.5" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^6.1.0-beta.91" } }, "node_modules/@xterm/addon-image": { - "version": "0.9.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.118.tgz", - "integrity": "sha512-PsFEO3VsP2PdZ3TunvJuQfgPX/xdDxBzbUJD5Lh74piY1vNfpUb4R3kypRwgKxn/HYH0+/mIzwmrlMyz8i57vQ==", + "version": "0.10.0-beta.91", + "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.10.0-beta.91.tgz", + "integrity": "sha512-WwZI4UxfEGbHIuTN/EsRoDYo29Dv5l8YRqNPmS+ejvbBj9aNfPly4rVSnWlrMjZw7RsxQcPPi93PFALLY4GvQw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^6.1.0-beta.91" } }, "node_modules/@xterm/addon-ligatures": { - "version": "0.10.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.118.tgz", - "integrity": "sha512-ry8jZoyTky0TRr9uayaHXhkwwin7HFjO/J5aB/77iZbs+ES0QuZZKRWJCyk/07/RNlj3NjfxDq4ImJgUD34oGQ==", + "version": "0.11.0-beta.91", + "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.11.0-beta.91.tgz", + "integrity": "sha512-+ph1zxVDIvXyjWzNpbK+Y7bKOWttEag6EgpREXQELB8kgIM6F12rmmdY8sfueD3k03cuLeO8/si/s1IzVH89ig==", "license": "MIT", "dependencies": { - "font-finder": "^1.1.0", - "font-ligatures": "^1.4.1" + "lru-cache": "^6.0.0", + "opentype.js": "^0.8.0" }, "engines": { "node": ">8.0.0" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^6.1.0-beta.91" + } + }, + "node_modules/@xterm/addon-ligatures/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" } }, + "node_modules/@xterm/addon-ligatures/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/@xterm/addon-progress": { - "version": "0.2.0-beta.24", - "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.24.tgz", - "integrity": "sha512-ZDrgQQOtNuDYIPIbQCmPu+MdIEx3aob3jSxHu7j/SMIvBezXMGINFVk0g8ph9ARXldAydld2dUXyr9KdDtTHSA==", + "version": "0.3.0-beta.91", + "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.3.0-beta.91.tgz", + "integrity": "sha512-88osAt/Hg4qf96p0rRlcoFBbeaU+/XubA5bfmXvhl2FoLA8hzeRSNTpr2LWSV7R1N0SICVS0ewKhPGevcoX4Qg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^6.1.0-beta.91" } }, "node_modules/@xterm/addon-search": { - "version": "0.16.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.118.tgz", - "integrity": "sha512-xZqu+0ip8wEFyGqLglqrWgjywcoc6EHGoqF2o94Qpjn/qlWYDFdbwQNLwVqF/awAxfOIvyqz7w6Z68QIbPTxWA==", + "version": "0.17.0-beta.91", + "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.17.0-beta.91.tgz", + "integrity": "sha512-pu9QD60WWAL33jCvLxf+IaIQwm4DOgfBJnYfckJZ5A3Zbj8CvaMAfFR3R7CYallBv/z5rxZ/rDRj3GEheXHVgg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^6.1.0-beta.91" } }, "node_modules/@xterm/addon-serialize": { - "version": "0.14.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.118.tgz", - "integrity": "sha512-gcyP1Vv6GAsNgE0QfXs73XyZn1V7xxETnzjBVqTevhea580CBeE6bueI+L1WBk/ztg7PLF3W30jtQoyDgqyB7A==", + "version": "0.15.0-beta.91", + "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.15.0-beta.91.tgz", + "integrity": "sha512-zpIH9p49pLE7OYJUKwOs13AqoOfhr8vf0c8cimbfX8eibdrQU+ohWYLRPIDXHu+SgIPIzal4syVdqethaJN0yg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^6.1.0-beta.91" } }, "node_modules/@xterm/addon-unicode11": { - "version": "0.9.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.118.tgz", - "integrity": "sha512-IjGBKjiopPk6hrplrd4FkOvx/94DJ6tJOXQtAyUUhpSHvQmrqEz+pWyx+K3u91OyRlsqgpdVCUfI1T9fG6V2Pg==", + "version": "0.10.0-beta.91", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.10.0-beta.91.tgz", + "integrity": "sha512-FZ/onuRqgkJSHLekhdswJGb4pvXvUmpmXSp0eIFF7mWJK4HMUqkYAanlsUsuc+1CtrghA583KNXCv37W9Irc4A==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^6.1.0-beta.91" } }, "node_modules/@xterm/addon-webgl": { - "version": "0.19.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.118.tgz", - "integrity": "sha512-l68STep6P7dCapAuzJGLKjmkDtIo6eNxRE3K9CXn2ni5epVO2e4dR5Pu0CRuY1BDHARqmFq5lU/ImaCOIWAn2g==", + "version": "0.20.0-beta.90", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.20.0-beta.90.tgz", + "integrity": "sha512-eeESmpszISob0ehoAxP0gYSUKYjnMYlpQacMQpK8Lczo4QB5LbS3D7C++NNEtOFhnfrscSjkGW+0YZoAgSfN6g==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^6.1.0-beta.91" } }, "node_modules/@xterm/headless": { - "version": "5.6.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.6.0-beta.118.tgz", - "integrity": "sha512-VUIlj6NvEmhi4PISiPhBAhPhEPcoSBYG39E3nuVHp2wvo02Z+2lig8WppVuwBMDk1EfBSo/MYa1YqPxVSqYNUQ==", - "license": "MIT" + "version": "6.1.0-beta.91", + "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-6.1.0-beta.91.tgz", + "integrity": "sha512-8OfmAJ6FASbge0g7mXNrM1olHGCCOS0+NUx18pU94w1l7Rw7xqU43aHe29PIPv8MY3+taO3Bpa3Tw1g6fiCEUg==", + "license": "MIT", + "workspaces": [ + "addons/*" + ] }, "node_modules/@xterm/xterm": { - "version": "5.6.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.118.tgz", - "integrity": "sha512-uwqiQP4VH1uFl99wey7gjPRHOHd/ovot/Dh6YJNQk95aT3UjWunb/yHNQpkMD6X4i5Ysv97rn0GrO5DR0eLPXw==", - "license": "MIT" + "version": "6.1.0-beta.91", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.1.0-beta.91.tgz", + "integrity": "sha512-sXd6uKhMB3cycSyW/I8eVGglZ9+rGDLDTtm9fkiXDPcA1N/oMiR0iUKGstv2VOCT/40af3+//kYgl8IwOqPbxw==", + "license": "MIT", + "workspaces": [ + "addons/*" + ] }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", @@ -3998,13 +3647,14 @@ "dev": true }, "node_modules/accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "dev": true, + "license": "MIT", "dependencies": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, "engines": { "node": ">= 0.6" @@ -4045,22 +3695,11 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "dependencies": { - "debug": "^4.3.4" - }, + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", "engines": { "node": ">= 14" } @@ -4263,12 +3902,6 @@ "node": ">=14" } }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -4939,17 +4572,24 @@ "node": ">=14.14.0" } }, - "node_modules/cache-content-type": { + "node_modules/cache-base": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", - "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", "dev": true, "dependencies": { - "mime-types": "^2.1.18", - "ylru": "^1.2.0" + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" }, "engines": { - "node": ">= 6.0.0" + "node": ">=0.10.0" } }, "node_modules/cacheable-lookup": { @@ -5017,6 +4657,7 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -5357,16 +4998,6 @@ "util-deprecate": "~1.0.1" } }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, "node_modules/code-block-writer": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-12.0.0.tgz", @@ -5534,6 +5165,7 @@ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "dev": true, + "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" }, @@ -5559,13 +5191,15 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/content-type": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -5593,6 +5227,7 @@ "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz", "integrity": "sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==", "dev": true, + "license": "MIT", "dependencies": { "depd": "~2.0.0", "keygrip": "~1.1.0" @@ -5684,12 +5319,6 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", "dev": true }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -5960,8 +5589,9 @@ "node_modules/deep-equal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", - "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU= sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==", - "dev": true + "integrity": "sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==", + "dev": true, + "license": "MIT" }, "node_modules/deep-extend": { "version": "0.6.0", @@ -6106,8 +5736,9 @@ "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "dev": true + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true, + "license": "MIT" }, "node_modules/depd": { "version": "2.0.0", @@ -6123,6 +5754,7 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" @@ -6147,9 +5779,10 @@ } }, "node_modules/detect-libc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", - "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "license": "Apache-2.0", "engines": { "node": ">=8" } @@ -6332,9 +5965,9 @@ "dev": true }, "node_modules/electron": { - "version": "37.3.1", - "resolved": "https://registry.npmjs.org/electron/-/electron-37.3.1.tgz", - "integrity": "sha512-7DhktRLqhe6OJh/Bo75bTI0puUYEmIwSzMinocgO63mx3MVjtIn2tYMzLmAleNIlud2htkjpsMG2zT4PiTCloA==", + "version": "39.2.7", + "resolved": "https://registry.npmjs.org/electron/-/electron-39.2.7.tgz", + "integrity": "sha512-KU0uFS6LSTh4aOIC3miolcbizOFP7N1M46VTYVfqIgFiuA2ilfNaOHLDS9tCMvwwHRowAsvqBrh9NgMXcTOHCQ==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -6373,10 +6006,11 @@ } }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -6445,32 +6079,6 @@ "is-arrayish": "^0.2.1" } }, - "node_modules/es-abstract": { - "version": "1.18.0-next.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", - "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", - "dev": true, - "dependencies": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-negative-zero": "^2.0.0", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -6525,23 +6133,6 @@ "node": ">= 0.4" } }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/es5-ext": { "version": "0.10.63", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.63.tgz", @@ -6612,7 +6203,8 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/escape-string-regexp": { "version": "4.0.0", @@ -6627,31 +6219,33 @@ } }, "node_modules/eslint": { - "version": "9.11.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.11.1.tgz", - "integrity": "sha512-MobhYKIoAO1s1e4VUrgx1l1Sk2JBR/Gqjjgw8+mfgoLE2xwsHur4gdfTxyTgShrhvdVFTaJSgMiQBl1jv/AWxg==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.11.0", - "@eslint/config-array": "^0.18.0", - "@eslint/core": "^0.6.0", - "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.11.1", - "@eslint/plugin-kit": "^0.2.0", + "version": "9.36.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz", + "integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.36.0", + "@eslint/plugin-kit": "^0.3.5", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.3.0", - "@nodelib/fs.walk": "^1.2.8", + "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.0.2", - "eslint-visitor-keys": "^4.0.0", - "espree": "^10.1.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -6661,14 +6255,11 @@ "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" @@ -6742,10 +6333,11 @@ } }, "node_modules/eslint-scope": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.1.0.tgz", - "integrity": "sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -6770,10 +6362,11 @@ } }, "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", - "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -6815,14 +6408,15 @@ "dev": true }, "node_modules/espree": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.2.0.tgz", - "integrity": "sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.12.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.1.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6832,10 +6426,11 @@ } }, "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", - "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -7098,6 +6693,24 @@ "pend": "~1.2.0" } }, + "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/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -7303,51 +6916,6 @@ "util-deprecate": "~1.0.1" } }, - "node_modules/font-finder": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/font-finder/-/font-finder-1.1.0.tgz", - "integrity": "sha512-wpCL2uIbi6GurJbU7ZlQ3nGd61Ho+dSU6U83/xJT5UPFfN35EeCW/rOtS+5k+IuEZu2SYmHzDIPL9eA5tSYRAw==", - "license": "MIT", - "dependencies": { - "get-system-fonts": "^2.0.0", - "promise-stream-reader": "^1.0.1" - }, - "engines": { - "node": ">8.0.0" - } - }, - "node_modules/font-ligatures": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/font-ligatures/-/font-ligatures-1.4.1.tgz", - "integrity": "sha512-7W6zlfyhvCqShZ5ReUWqmSd9vBaUudW0Hxis+tqUjtHhsPU+L3Grf8mcZAtCiXHTzorhwdRTId2WeH/88gdFkw==", - "license": "MIT", - "dependencies": { - "font-finder": "^1.0.3", - "lru-cache": "^6.0.0", - "opentype.js": "^0.8.0" - }, - "engines": { - "node": ">8.0.0" - } - }, - "node_modules/font-ligatures/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/font-ligatures/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" - }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -7424,8 +6992,9 @@ "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -7667,15 +7236,6 @@ "once": "^1.3.1" } }, - "node_modules/get-system-fonts": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-system-fonts/-/get-system-fonts-2.0.2.tgz", - "integrity": "sha512-zzlgaYnHMIEgHRrfC7x0Qp0Ylhw/sHpM6MHXeVBTYIsvGf5GpbnClB+Q6rAPdn+0gd2oZZIo6Tj3EaWrt4VhDQ==", - "license": "MIT", - "engines": { - "node": ">8.0.0" - } - }, "node_modules/get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", @@ -8023,6 +7583,7 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -9504,15 +9065,6 @@ "node": ">=0.10.0" } }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0= sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "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", @@ -9604,6 +9156,7 @@ "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz", "integrity": "sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==", "dev": true, + "license": "MIT", "dependencies": { "deep-equal": "~1.0.1", "http-errors": "~1.8.0" @@ -9615,8 +9168,9 @@ "node_modules/http-assert/node_modules/depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -9626,6 +9180,7 @@ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", "dev": true, + "license": "MIT", "dependencies": { "depd": "~1.1.2", "inherits": "2.0.4", @@ -9640,8 +9195,9 @@ "node_modules/http-assert/node_modules/statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -9653,20 +9209,24 @@ "dev": true }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "dev": true, "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/http-proxy-agent": { @@ -9695,11 +9255,12 @@ } }, "node_modules/https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", "dependencies": { - "agent-base": "^7.0.2", + "agent-base": "^7.1.2", "debug": "4" }, "engines": { @@ -9839,10 +9400,11 @@ "dev": true }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -10031,16 +9593,26 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "deprecated": "Please upgrade to v1.0.1", "dev": true, - "engines": { - "node": ">= 0.4" + "dependencies": { + "kind-of": "^6.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" } }, "node_modules/is-deflate": { @@ -10176,18 +9748,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -10196,15 +9756,6 @@ "node": ">=0.12.0" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", @@ -10229,21 +9780,6 @@ "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", "dev": true }, - "node_modules/is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-relative": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", @@ -10265,21 +9801,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-typed-array": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", @@ -10480,16 +10001,14 @@ } }, "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, - "engines": { - "node": ">=14" - }, "funding": { "url": "https://github.com/sponsors/isaacs" }, @@ -10602,9 +10121,11 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "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" }, @@ -10653,12 +10174,6 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -10680,8 +10195,9 @@ "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", "dev": true, + "license": "ISC", "optional": true }, "node_modules/json5": { @@ -10795,7 +10311,9 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", "dev": true, + "license": "MIT", "dependencies": { "tsscmp": "1.0.6" }, @@ -10822,58 +10340,41 @@ } }, "node_modules/koa": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/koa/-/koa-2.16.1.tgz", - "integrity": "sha512-umfX9d3iuSxTQP4pnzLOz0HKnPg0FaUUIKcye2lOiz3KPu1Y3M3xlz76dISdFPQs37P9eJz1wUpcTS6KDPn9fA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/koa/-/koa-3.1.1.tgz", + "integrity": "sha512-KDDuvpfqSK0ZKEO2gCPedNjl5wYpfj+HNiuVRlbhd1A88S3M0ySkdf2V/EJ4NWt5dwh5PXCdcenrKK2IQJAxsg==", "dev": true, "license": "MIT", "dependencies": { - "accepts": "^1.3.5", - "cache-content-type": "^1.0.0", - "content-disposition": "~0.5.2", - "content-type": "^1.0.4", - "cookies": "~0.9.0", - "debug": "^4.3.2", + "accepts": "^1.3.8", + "content-disposition": "~0.5.4", + "content-type": "^1.0.5", + "cookies": "~0.9.1", "delegates": "^1.0.0", - "depd": "^2.0.0", - "destroy": "^1.0.4", - "encodeurl": "^1.0.2", + "destroy": "^1.2.0", + "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "fresh": "~0.5.2", - "http-assert": "^1.3.0", - "http-errors": "^1.6.3", - "is-generator-function": "^1.0.7", + "http-assert": "^1.5.0", + "http-errors": "^2.0.0", "koa-compose": "^4.1.0", - "koa-convert": "^2.0.0", - "on-finished": "^2.3.0", - "only": "~0.0.2", - "parseurl": "^1.3.2", - "statuses": "^1.5.0", - "type-is": "^1.6.16", + "mime-types": "^3.0.1", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1", + "type-is": "^2.0.1", "vary": "^1.1.2" }, "engines": { - "node": "^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4" + "node": ">= 18" } }, "node_modules/koa-compose": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==", - "dev": true - }, - "node_modules/koa-convert": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-2.0.0.tgz", - "integrity": "sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==", "dev": true, - "dependencies": { - "co": "^4.6.0", - "koa-compose": "^4.1.0" - }, - "engines": { - "node": ">= 10" - } + "license": "MIT" }, "node_modules/koa-morgan": { "version": "1.0.1", @@ -10885,10 +10386,11 @@ } }, "node_modules/koa-mount": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/koa-mount/-/koa-mount-4.0.0.tgz", - "integrity": "sha512-rm71jaA/P+6HeCpoRhmCv8KVBIi0tfGuO/dMKicbQnQW/YJntJ6MnnspkodoA4QstMVEZArsCphmd0bJEtoMjQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/koa-mount/-/koa-mount-4.2.0.tgz", + "integrity": "sha512-2iHQc7vbA9qLeVq5gKAYh3m5DOMMlMfIKjW/REPAS18Mf63daCJHHVXY9nbu7ivrnYn5PiPC4CE523Tf5qvjeQ==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^4.0.1", "koa-compose": "^4.1.0" @@ -10967,38 +10469,31 @@ "ms": "^2.1.1" } }, - "node_modules/koa/node_modules/http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", - "dev": true, - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/koa/node_modules/http-errors/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "node_modules/koa/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } }, - "node_modules/koa/node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "node_modules/koa/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/last-run": { @@ -11124,19 +10619,13 @@ "node": ">=0.10.0" } }, - "node_modules/load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs= sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "node_modules/linkify-it": { + "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, "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=4" + "uc.micro": "^2.0.0" } }, "node_modules/loader-runner": { @@ -11278,12 +10767,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, "node_modules/make-iterator": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", @@ -11405,12 +10888,13 @@ "dev": true }, "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/memoizee": { @@ -11946,9 +11430,9 @@ "optional": true }, "node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { @@ -11970,16 +11454,18 @@ "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" }, "node_modules/native-is-elevated": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/native-is-elevated/-/native-is-elevated-0.7.0.tgz", - "integrity": "sha512-tp8hUqK7vexBiyIWKMvmRxdG6kqUtO+3eay9iB0i16NYgvCqE5wMe1Y0guHilpkmRgvVXEWNW4et1+qqcwpLBA==", - "hasInstallScript": true + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/native-is-elevated/-/native-is-elevated-0.8.0.tgz", + "integrity": "sha512-utx846s63JTqN2DcsLSAd0YpwOMcBezBzN55gSyVJX2kZAsvqOt6+ypdyogNqjSnzd7NvOCEvzMRq+AB2ekVxQ==", + "hasInstallScript": true, + "license": "MIT" }, "node_modules/native-keymap": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/native-keymap/-/native-keymap-3.3.5.tgz", - "integrity": "sha512-7XDOLPNX1FnUFC/cX3cioBz2M+dO212ai9DuwpfKFzkPu3xTmEzOm5xewOMLXE4V9YoRhNPxvq1H2YpPWDgSsg==", - "hasInstallScript": true + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/native-keymap/-/native-keymap-3.3.7.tgz", + "integrity": "sha512-07n5kF0L9ERC9pilqEFucnhs1XG4WttbHAMWhhOSqQYXhB8mMNTSCzP4psTaVgDSp6si2HbIPhTIHuxSia6NPQ==", + "hasInstallScript": true, + "license": "MIT" }, "node_modules/native-watchdog": { "version": "1.4.2", @@ -11994,10 +11480,11 @@ "dev": true }, "node_modules/negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -12083,9 +11570,9 @@ } }, "node_modules/node-pty": { - "version": "1.1.0-beta33", - "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.1.0-beta33.tgz", - "integrity": "sha512-+BN2bT/KqO+fmCHnpFS99VMVJr7VUBCUa2VIBEw0oEvszkR7ri0kwD1lF91OeQToUJ2dXKA8j6scPjbO4eRWOQ==", + "version": "1.1.0-beta43", + "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.1.0-beta43.tgz", + "integrity": "sha512-CYyIQogRs97Rfjo0WKyku8V56Bm4WyWUijrbWDs5LJ+ZmsUW2gqbVAEpD+1gtA7dEZ6v1A08GzfqsDuIl/eRqw==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -12166,91 +11653,106 @@ "node": ">= 0.10" } }, - "node_modules/npm-run-all": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", - "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "node_modules/npm-normalize-package-bin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", + "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-run-all2": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/npm-run-all2/-/npm-run-all2-8.0.4.tgz", + "integrity": "sha512-wdbB5My48XKp2ZfJUlhnLVihzeuA1hgBnqB2J9ahV77wLS+/YAJAlN8I+X3DIFIPZ3m5L7nplmlbhNiFDmXRDA==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^3.2.1", - "chalk": "^2.4.1", - "cross-spawn": "^6.0.5", + "ansi-styles": "^6.2.1", + "cross-spawn": "^7.0.6", "memorystream": "^0.3.1", - "minimatch": "^3.0.4", - "pidtree": "^0.3.0", - "read-pkg": "^3.0.0", - "shell-quote": "^1.6.1", - "string.prototype.padend": "^3.0.0" + "picomatch": "^4.0.2", + "pidtree": "^0.6.0", + "read-package-json-fast": "^4.0.0", + "shell-quote": "^1.7.3", + "which": "^5.0.0" }, "bin": { "npm-run-all": "bin/npm-run-all/index.js", + "npm-run-all2": "bin/npm-run-all/index.js", "run-p": "bin/run-p/index.js", "run-s": "bin/run-s/index.js" }, "engines": { - "node": ">= 4" + "node": "^20.5.0 || >=22.0.0", + "npm": ">= 10" } }, - "node_modules/npm-run-all/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/npm-run-all2/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, - "dependencies": { - "color-convert": "^1.9.0" - }, + "license": "MIT", "engines": { - "node": ">=4" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/npm-run-all/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/npm-run-all2/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, + "license": "ISC", "engines": { - "node": ">=4" + "node": ">=16" } }, - "node_modules/npm-run-all/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "node_modules/npm-run-all2/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, - "dependencies": { - "color-name": "1.1.3" + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/npm-run-all/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/npm-run-all/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "node_modules/npm-run-all2/node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, "engines": { - "node": ">=0.8.0" + "node": ">=0.10" } }, - "node_modules/npm-run-all/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/npm-run-all2/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, + "license": "ISC", "dependencies": { - "has-flag": "^3.0.0" + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" }, "engines": { - "node": ">=4" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/nth-check": { @@ -12283,17 +11785,91 @@ "node": ">=0.10.0" } }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "node_modules/object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw= sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", + "dev": true, + "dependencies": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "deprecated": "Please upgrade to v0.1.7", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "deprecated": "Please upgrade to v0.1.5", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-descriptor/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" + "dependencies": { + "is-buffer": "^1.1.5" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=0.10.0" } }, "node_modules/object-keys": { @@ -12381,6 +11957,7 @@ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dev": true, + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -12421,12 +11998,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/only": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", - "integrity": "sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q= sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==", - "dev": true - }, "node_modules/open": { "version": "10.1.2", "resolved": "https://registry.npmjs.org/open/-/open-10.1.2.tgz", @@ -12705,10 +12276,11 @@ } }, "node_modules/package-json-from-dist": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", - "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", - "dev": true + "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/pako": { "version": "0.2.9", @@ -12721,6 +12293,7 @@ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -12755,19 +12328,6 @@ "node": ">= 18" } }, - "node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", - "dev": true, - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/parse-node-version": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", @@ -12791,6 +12351,7 @@ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -12887,11 +12448,15 @@ } }, "node_modules/path-to-regexp": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", - "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", "dev": true, - "license": "MIT" + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } }, "node_modules/path-type": { "version": "4.0.0", @@ -12970,27 +12535,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pidtree": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", - "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", - "dev": true, - "bin": { - "pidtree": "bin/pidtree.js" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", @@ -13077,13 +12621,13 @@ } }, "node_modules/playwright": { - "version": "1.53.2", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.2.tgz", - "integrity": "sha512-6K/qQxVFuVQhRQhFsVZ9fGeatxirtrpPgxzBYWyZLEXJzqYwuL4fuNmfOfD5et1tJE4GScKyPNeLhZeRwuTU3A==", + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz", + "integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.53.2" + "playwright-core": "1.56.1" }, "bin": { "playwright": "cli.js" @@ -13096,22 +12640,9 @@ } }, "node_modules/playwright-core": { - "version": "1.47.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.47.2.tgz", - "integrity": "sha512-3JvMfF+9LJfe16l7AbSmU555PaTl2tPyQsVInqm3id16pdDfvZ8TTZ/pyzmkbDrZTQefyzU7AIHlZqQnxpqHVQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright/node_modules/playwright-core": { - "version": "1.53.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.2.tgz", - "integrity": "sha512-ox/OytMy+2w1jcYEYlOo1Hhp8hZkLCximMTUTMBXjGUA1KoFfiSZ+DU+3a739jsPY0yoKH2TFy9S2fsJas8yAw==", + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz", + "integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -13122,25 +12653,18 @@ } }, "node_modules/plist": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/plist/-/plist-3.0.5.tgz", - "integrity": "sha512-83vX4eYdQp3vP9SxuYgEM/G/pJQqLUz/V/xzPrzruLs7fz7jxGQ1msZ/mg1nwZxUSuOp4sb+/bEIbRrbzZRxDA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", + "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==", "dev": true, + "license": "MIT", "dependencies": { + "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.5.1", - "xmlbuilder": "^9.0.7" + "xmlbuilder": "^15.1.1" }, "engines": { - "node": ">=6" - } - }, - "node_modules/plist/node_modules/xmlbuilder": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", - "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= sha512-7YXTQc3P2l9+0rjaUbLwMKRhtmwg1M1eDf6nag7urC7pIPYLD9W/jmzQ4ptRSUbodw5S0jfoGTflLemQibSpeQ==", - "dev": true, - "engines": { - "node": ">=4.0" + "node": ">=10.4.0" } }, "node_modules/plugin-error": { @@ -13171,9 +12695,9 @@ } }, "node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "funding": [ { @@ -13191,7 +12715,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.8", + "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -13386,15 +12910,6 @@ "node": ">=0.4.0" } }, - "node_modules/promise-stream-reader": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-stream-reader/-/promise-stream-reader-1.0.1.tgz", - "integrity": "sha512-Tnxit5trUjBAqqZCGWwjyxhmgMN4hGrtpW3Oc/tRI4bpm/O2+ej72BB08l6JBnGQgVDGCLvHFGjGgQS6vzhwXg==", - "license": "MIT", - "engines": { - "node": ">8.0.0" - } - }, "node_modules/proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", @@ -13547,18 +13062,28 @@ "integrity": "sha512-JkXJ0IrUcdupLoIx6gE4YcFaMVSGtu7kQf4NJoDJUnfBZGuATmJ2Yal2v55KTltp+WV8dGr7A0RtOzx6jmtM6Q==", "dev": true }, - "node_modules/read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k= sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", + "node_modules/read-package-json-fast": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-4.0.0.tgz", + "integrity": "sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==", "dev": true, + "license": "ISC", "dependencies": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" + "json-parse-even-better-errors": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" }, "engines": { - "node": ">=4" + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/read-package-json-fast/node_modules/json-parse-even-better-errors": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz", + "integrity": "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/read-pkg-up": { @@ -13676,18 +13201,6 @@ "node": ">=0.10.0" } }, - "node_modules/read-pkg/node_modules/path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "dependencies": { - "pify": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -13933,6 +13446,7 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -14639,9 +14153,9 @@ } }, "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "dev": true, "license": "MIT", "engines": { @@ -14806,6 +14320,7 @@ "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", @@ -14819,7 +14334,8 @@ "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 + "dev": true, + "license": "MIT" }, "node_modules/string-width/node_modules/ansi-regex": { "version": "6.0.1", @@ -14848,49 +14364,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/string.prototype.padend": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.1.tgz", - "integrity": "sha512-eCzTASPnoCr5Ht+Vn1YXgm8SB015hHKgEIMu9Nr9bQmLhRBxKRfmzSj/IQsxDFc8JInJDDFA0qXwK+xxI7wDkg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz", - "integrity": "sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz", - "integrity": "sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -14909,6 +14382,7 @@ "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" }, @@ -14916,15 +14390,6 @@ "node": ">=8" } }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/strip-bom-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", @@ -15201,10 +14666,14 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, - "node_modules/tas-client-umd": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/tas-client-umd/-/tas-client-umd-0.2.0.tgz", - "integrity": "sha512-oezN7mJVm5qZDVEby7OzxCLKUpUN5of0rY4dvOWaDF2JZBlGpd3BXceFN8B53qlTaIkVSzP65aAMT0Vc+/N25Q==" + "node_modules/tas-client": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/tas-client/-/tas-client-0.3.1.tgz", + "integrity": "sha512-Mn4+4t/KXEf8aIENeI1TkzpKIImzmG+FjPZ2dlaoGNFgxJqBE/pp3MT7nc2032EG4aS73E4OEcr2WiNaWW8mdA==", + "license": "MIT", + "engines": { + "node": ">=22" + } }, "node_modules/teex": { "version": "1.0.1", @@ -15343,12 +14812,6 @@ "b4a": "^1.6.4" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, "node_modules/textextensions": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-1.0.2.tgz", @@ -15431,6 +14894,36 @@ "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", "license": "MIT" }, + "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/tinyglobby/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/to-absolute-glob": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", @@ -15601,63 +15094,12 @@ "code-block-writer": "^12.0.0" } }, - "node_modules/ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", - "dev": true, - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/ts-node/node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/tsec": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/tsec/-/tsec-0.2.7.tgz", "integrity": "sha512-Pj9DuBBWLEo8p7QsbrEdXzW/u6QJBcib0ZGOTXkeSDx+PLXFY7hwyZE9Tfhp3TA3LQNpYouyT0WmzXRyUW4otQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "glob": "^7.1.1", "minimatch": "^3.0.3" @@ -15672,16 +15114,17 @@ } }, "node_modules/tsec/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" }, @@ -15703,6 +15146,7 @@ "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.6.x" } @@ -15768,18 +15212,47 @@ } }, "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "dev": true, + "license": "MIT", "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" }, "engines": { "node": ">= 0.6" } }, + "node_modules/type-is/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -15787,9 +15260,9 @@ "dev": true }, "node_modules/typescript": { - "version": "6.0.0-dev.20250827", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.0-dev.20250827.tgz", - "integrity": "sha512-TNrtA9r9AMgInuUMAV5hVTQWClBfvG+HWhIanl7ZO8GWSwjzL9mRgauvwzMfXpdwoAR1h+XC5zSj9WbbGGOtxg==", + "version": "6.0.0-dev.20251110", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.0-dev.20251110.tgz", + "integrity": "sha512-tHG+EJXTSaUCMbTNApOuVE3WmgOmEqUwQiAXnmwsF/sVKhPFHQA0+S1hml0Ro8kpayvD0d9AX5iC2S2s+TIQxQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -15801,16 +15274,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.39.0.tgz", - "integrity": "sha512-lH8FvtdtzcHJCkMOKnN73LIn6SLTpoojgJqDAxPm1jCR14eWSGPX8ul/gggBdPMk/d5+u9V854vTYQ8T5jF/1Q==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.45.0.tgz", + "integrity": "sha512-qzDmZw/Z5beNLUrXfd0HIW6MzIaAV5WNDxmMs9/3ojGOpYavofgNAAD/nC6tGV2PczIi0iw8vot2eAe/sBn7zg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.39.0", - "@typescript-eslint/parser": "8.39.0", - "@typescript-eslint/typescript-estree": "8.39.0", - "@typescript-eslint/utils": "8.39.0" + "@typescript-eslint/eslint-plugin": "8.45.0", + "@typescript-eslint/parser": "8.45.0", + "@typescript-eslint/typescript-estree": "8.45.0", + "@typescript-eslint/utils": "8.45.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -15888,9 +15361,9 @@ } }, "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" }, @@ -16018,12 +15491,6 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true - }, "node_modules/v8-inspect-profiler": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/v8-inspect-profiler/-/v8-inspect-profiler-0.1.1.tgz", @@ -16260,16 +15727,17 @@ } }, "node_modules/vscode-textmate": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-9.2.0.tgz", - "integrity": "sha512-rkvG4SraZQaPSN/5XjwKswdU0OP9MF28QjrYzUBbhb8QyG3ljB1Ky996m++jiI7KdiAP2CkBiQZd9pqEDTClqA==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-9.3.0.tgz", + "integrity": "sha512-zHiZZOdb9xqj5/X1C4a29sbgT2HngdWxPLSl3PyHRQF+5visI4uNM020OHiLJjsMxUssyk/pGVAg/9LCIobrVg==", "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==", - "dev": true + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" }, "node_modules/watchpack": { "version": "2.4.1", @@ -16633,6 +16101,7 @@ "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", @@ -16649,13 +16118,15 @@ "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 + "dev": true, + "license": "MIT" }, "node_modules/wrap-ansi-cjs/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", @@ -16740,6 +16211,16 @@ "node": ">=4.0" } }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -16864,24 +16345,6 @@ "buffer-crc32": "~0.2.3" } }, - "node_modules/ylru": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.2.1.tgz", - "integrity": "sha512-faQrqNMzcPCHGVC2aaOINk13K+aaBDUPjGWl0teOXywElLjyVAB6Oe2jj62jHYtwsU49jXhScYbvPENK+6zAvQ==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -16895,9 +16358,9 @@ } }, "node_modules/zx": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/zx/-/zx-8.7.0.tgz", - "integrity": "sha512-pArftqj5JV/er8p+czFZwF+k6SbCldl7kcfCR+rIiDIh3gUsLB0F3Xh05diP8PzToZ39D/GWeFoVFimjHQkbAg==", + "version": "8.8.5", + "resolved": "https://registry.npmjs.org/zx/-/zx-8.8.5.tgz", + "integrity": "sha512-SNgDF5L0gfN7FwVOdEFguY3orU5AkfFZm9B5YSHog/UDHv+lvmd82ZAsOenOkQixigwH2+yyH198AwNdKhj+RA==", "dev": true, "license": "Apache-2.0", "bin": { diff --git a/code/package.json b/code/package.json index a380bea30ac..a4002640e65 100644 --- a/code/package.json +++ b/code/package.json @@ -1,7 +1,7 @@ { "name": "che-code", - "version": "1.104.3", - "distro": "099a1d02c3537e7498ea6457d247a9954d33c7f0", + "version": "1.108.0", + "distro": "e5c6724cbb9375a8edee07cc94fa99aa31c4c680", "author": { "name": "Microsoft Corporation" }, @@ -15,111 +15,114 @@ "test-browser-no-install": "node test/unit/browser/index.js", "test-node": "mocha test/unit/node/index.js --delay --ui=tdd --timeout=5000 --exit", "test-extension": "vscode-test", - "preinstall": "node build/npm/preinstall.js", - "postinstall": "node build/npm/postinstall.js", - "compile": "node ./node_modules/gulp/bin/gulp.js compile", + "test-build-scripts": "cd build && npm run test", + "preinstall": "node build/npm/preinstall.ts", + "postinstall": "node build/npm/postinstall.ts", + "compile": "npm run gulp compile", "compile-check-ts-native": "tsgo --project ./src/tsconfig.json --noEmit --skipLibCheck", - "watch": "npm-run-all -lp watch-client watch-extensions", + "watch": "npm-run-all2 -lp watch-client watch-extensions", "watchd": "deemon npm run watch", "watch-webd": "deemon npm run watch-web", "kill-watchd": "deemon --kill npm run watch", "kill-watch-webd": "deemon --kill npm run watch-web", "restart-watchd": "deemon --restart npm run watch", "restart-watch-webd": "deemon --restart npm run watch-web", - "watch-client": "node --max-old-space-size=8192 ./node_modules/gulp/bin/gulp.js watch-client", + "watch-client": "npm run gulp watch-client", "watch-clientd": "deemon npm run watch-client", "kill-watch-clientd": "deemon --kill npm run watch-client", - "watch-extensions": "node --max-old-space-size=8192 ./node_modules/gulp/bin/gulp.js watch-extensions watch-extension-media", + "watch-extensions": "npm run gulp watch-extensions watch-extension-media", "watch-extensionsd": "deemon npm run watch-extensions", "kill-watch-extensionsd": "deemon --kill npm run watch-extensions", - "precommit": "node build/hygiene.js", + "precommit": "node build/hygiene.ts", "gulp": "node --max-old-space-size=8192 ./node_modules/gulp/bin/gulp.js", - "electron": "node build/lib/electron", + "electron": "node build/lib/electron.ts", "7z": "7z", - "update-grammars": "node build/npm/update-all-grammars.mjs", - "update-localization-extension": "node build/npm/update-localization-extension.js", - "smoketest": "node build/lib/preLaunch.js && cd test/smoke && npm run compile && node test/index.js", + "update-grammars": "node build/npm/update-all-grammars.ts", + "update-localization-extension": "node build/npm/update-localization-extension.ts", + "mixin-telemetry-docs": "node build/npm/mixin-telemetry-docs.ts", + "smoketest": "node build/lib/preLaunch.ts && cd test/smoke && npm run compile && node test/index.js", "smoketest-no-compile": "cd test/smoke && node test/index.js", - "download-builtin-extensions": "node build/lib/builtInExtensions.js", - "download-builtin-extensions-cg": "node build/lib/builtInExtensionsCG.js", - "monaco-compile-check": "tsc -p src/tsconfig.monaco.json --noEmit", + "download-builtin-extensions": "node build/lib/builtInExtensions.ts", + "download-builtin-extensions-cg": "node build/lib/builtInExtensionsCG.ts", + "monaco-compile-check": "tsgo --project src/tsconfig.monaco.json --noEmit", "tsec-compile-check": "node node_modules/tsec/bin/tsec -p src/tsconfig.tsec.json", "vscode-dts-compile-check": "tsgo --project src/tsconfig.vscode-dts.json && tsgo --project src/tsconfig.vscode-proposed-dts.json", - "valid-layers-check": "node build/checker/layersChecker.js && tsgo --project build/checker/tsconfig.browser.json && tsgo --project build/checker/tsconfig.worker.json && tsgo --project build/checker/tsconfig.node.json && tsgo --project build/checker/tsconfig.electron-browser.json && tsgo --project build/checker/tsconfig.electron-main.json && tsgo --project build/checker/tsconfig.electron-utility.json", - "define-class-fields-check": "node build/lib/propertyInitOrderChecker.js && tsc -p src/tsconfig.defineClassFields.json", - "update-distro": "node build/npm/update-distro.mjs", + "valid-layers-check": "node build/checker/layersChecker.ts && tsgo --project build/checker/tsconfig.browser.json && tsgo --project build/checker/tsconfig.worker.json && tsgo --project build/checker/tsconfig.node.json && tsgo --project build/checker/tsconfig.electron-browser.json && tsgo --project build/checker/tsconfig.electron-main.json && tsgo --project build/checker/tsconfig.electron-utility.json", + "define-class-fields-check": "node build/lib/propertyInitOrderChecker.ts && tsgo --project src/tsconfig.defineClassFields.json", + "update-distro": "node build/npm/update-distro.ts", "web": "echo 'npm run web' is replaced by './scripts/code-server' or './scripts/code-web'", - "compile-cli": "gulp compile-cli", - "compile-web": "node ./node_modules/gulp/bin/gulp.js compile-web", - "watch-web": "node ./node_modules/gulp/bin/gulp.js watch-web", - "watch-cli": "node ./node_modules/gulp/bin/gulp.js watch-cli", - "eslint": "node build/eslint", - "stylelint": "node build/stylelint", + "compile-cli": "npm run gulp compile-cli", + "compile-web": "npm run gulp compile-web", + "watch-web": "npm run gulp watch-web", + "watch-cli": "npm run gulp watch-cli", + "eslint": "node build/eslint.ts", + "stylelint": "node build/stylelint.ts", "playwright-install": "npm exec playwright install", - "compile-build": "node ./node_modules/gulp/bin/gulp.js compile-build-with-mangling", - "compile-extensions-build": "node ./node_modules/gulp/bin/gulp.js compile-extensions-build", - "minify-vscode": "node ./node_modules/gulp/bin/gulp.js minify-vscode", - "minify-vscode-reh": "node ./node_modules/gulp/bin/gulp.js minify-vscode-reh", - "minify-vscode-reh-web": "node ./node_modules/gulp/bin/gulp.js minify-vscode-reh-web", - "hygiene": "node ./node_modules/gulp/bin/gulp.js hygiene", - "core-ci": "node ./node_modules/gulp/bin/gulp.js core-ci", - "core-ci-pr": "node ./node_modules/gulp/bin/gulp.js core-ci-pr", - "extensions-ci": "node ./node_modules/gulp/bin/gulp.js extensions-ci", - "extensions-ci-pr": "node ./node_modules/gulp/bin/gulp.js extensions-ci-pr", + "compile-build": "npm run gulp compile-build-with-mangling", + "compile-extensions-build": "npm run gulp compile-extensions-build", + "minify-vscode": "npm run gulp minify-vscode", + "minify-vscode-reh": "npm run gulp minify-vscode-reh", + "minify-vscode-reh-web": "npm run gulp minify-vscode-reh-web", + "hygiene": "npm run gulp hygiene", + "core-ci": "npm run gulp core-ci", + "core-ci-pr": "npm run gulp core-ci-pr", + "extensions-ci": "npm run gulp extensions-ci", + "extensions-ci-pr": "npm run gulp extensions-ci-pr", "perf": "node scripts/code-perf.js", - "update-build-ts-version": "npm install -D typescript@next && npm install -D @typescript/native-preview && (cd build && npm run compile)" + "update-build-ts-version": "npm install -D typescript@next && npm install -D @typescript/native-preview && (cd build && npm run typecheck)" }, "dependencies": { "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", - "@parcel/watcher": "2.5.1", "@types/semver": "^7.5.8", "@vscode/deviceid": "^0.1.1", - "@vscode/iconv-lite-umd": "0.7.0", + "@vscode/iconv-lite-umd": "0.7.1", "@vscode/policy-watcher": "^1.3.2", - "@vscode/proxy-agent": "^0.34.0", + "@vscode/proxy-agent": "^0.36.0", "@vscode/ripgrep": "^1.15.13", "@vscode/spdlog": "^0.15.2", - "@vscode/sqlite3": "5.1.8-vscode", + "@vscode/sqlite3": "5.1.10-vscode", "@vscode/sudo-prompt": "9.3.1", - "@vscode/tree-sitter-wasm": "^0.1.4", + "@vscode/tree-sitter-wasm": "^0.3.0", "@vscode/vscode-languagedetection": "1.0.21", + "@vscode/watcher": "bpasero/watcher#8ecffb4a57df24ac3e6946aa095b9b1f14f8bba9", "@vscode/windows-mutex": "^0.5.0", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.2.0-beta.101", - "@xterm/addon-image": "^0.9.0-beta.118", - "@xterm/addon-ligatures": "^0.10.0-beta.118", - "@xterm/addon-progress": "^0.2.0-beta.24", - "@xterm/addon-search": "^0.16.0-beta.118", - "@xterm/addon-serialize": "^0.14.0-beta.118", - "@xterm/addon-unicode11": "^0.9.0-beta.118", - "@xterm/addon-webgl": "^0.19.0-beta.118", - "@xterm/headless": "^5.6.0-beta.118", - "@xterm/xterm": "^5.6.0-beta.118", + "@xterm/addon-clipboard": "^0.3.0-beta.91", + "@xterm/addon-image": "^0.10.0-beta.91", + "@xterm/addon-ligatures": "^0.11.0-beta.91", + "@xterm/addon-progress": "^0.3.0-beta.91", + "@xterm/addon-search": "^0.17.0-beta.91", + "@xterm/addon-serialize": "^0.15.0-beta.91", + "@xterm/addon-unicode11": "^0.10.0-beta.91", + "@xterm/addon-webgl": "^0.20.0-beta.90", + "@xterm/headless": "^6.1.0-beta.91", + "@xterm/xterm": "^6.1.0-beta.91", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "jschardet": "3.1.4", "katex": "^0.16.22", "kerberos": "2.1.1", "minimist": "^1.2.8", - "native-is-elevated": "0.7.0", + "native-is-elevated": "0.8.0", "native-keymap": "^3.3.5", "native-watchdog": "^1.4.1", - "node-pty": "^1.1.0-beta33", + "node-pty": "^1.1.0-beta43", "open": "^10.1.2", - "tas-client-umd": "0.2.0", + "tas-client": "0.3.1", + "undici": "^7.9.0", "v8-inspect-profiler": "^0.1.1", "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", - "vscode-textmate": "9.2.0", + "vscode-textmate": "^9.3.0", "yauzl": "^3.0.0", "yazl": "^2.4.3", "ws": "8.2.3", "js-yaml": "^4.1.0" }, "devDependencies": { - "@playwright/test": "^1.53.2", + "@playwright/test": "^1.56.1", "@stylistic/eslint-plugin-ts": "^2.8.0", "@types/cookie": "^0.3.3", "@types/debug": "^4.1.5", @@ -130,28 +133,28 @@ "@types/minimist": "^1.2.1", "@types/minimatch": "^3.0.5", "@types/mocha": "^10.0.10", - "@types/node": "22.x", + "@types/node": "^22.18.10", "@types/sinon": "^10.0.2", "@types/sinon-test": "^2.4.2", - "@types/trusted-types": "^1.0.6", + "@types/trusted-types": "^2.0.7", "@types/vscode-notebook-renderer": "^1.72.0", "@types/webpack": "^5.28.5", - "@types/wicg-file-system-access": "^2020.9.6", + "@types/wicg-file-system-access": "^2023.10.7", "@types/windows-foreground-love": "^0.3.0", "@types/winreg": "^1.2.30", "@types/yauzl": "^2.10.0", "@types/yazl": "^2.4.2", - "@typescript-eslint/utils": "^8.36.0", + "@typescript-eslint/utils": "^8.45.0", "@typescript/native-preview": "^7.0.0-dev.20250812.1", "@vscode/gulp-electron": "^1.38.2", "@vscode/l10n-dev": "0.0.18", "@vscode/telemetry-extractor": "^1.10.2", "@vscode/test-cli": "^0.0.6", "@vscode/test-electron": "^2.4.0", - "@vscode/test-web": "^0.0.62", + "@vscode/test-web": "^0.0.76", "@vscode/v8-heap-parser": "^0.1.0", "@vscode/vscode-perf": "^0.0.19", - "@webgpu/types": "^0.1.44", + "@webgpu/types": "^0.1.66", "ansi-colors": "^3.2.3", "asar": "^3.0.3", "chromium-pickle-js": "^0.2.0", @@ -160,8 +163,8 @@ "css-loader": "^6.9.1", "debounce": "^1.0.0", "deemon": "^1.13.6", - "electron": "37.3.1", - "eslint": "^9.11.1", + "electron": "39.2.7", + "eslint": "^9.36.0", "eslint-formatter-compact": "^8.40.0", "eslint-plugin-header": "3.1.1", "eslint-plugin-jsdoc": "^50.3.1", @@ -198,7 +201,7 @@ "mocha": "^10.8.2", "mocha-junit-reporter": "^2.2.1", "mocha-multi-reporters": "^1.5.1", - "npm-run-all": "^4.1.5", + "npm-run-all2": "^8.0.4", "os-browserify": "^0.3.0", "p-all": "^1.0.0", "path-browserify": "^1.0.1", @@ -211,11 +214,10 @@ "source-map-support": "^0.3.2", "style-loader": "^3.3.2", "ts-loader": "^9.5.1", - "ts-node": "^10.9.1", "tsec": "0.2.7", "tslib": "^2.6.3", - "typescript": "^6.0.0-dev.20250827", - "typescript-eslint": "^8.39.0", + "typescript": "^6.0.0-dev.20251110", + "typescript-eslint": "^8.45.0", "util": "^0.12.4", "webpack": "^5.94.0", "webpack-cli": "^5.1.4", @@ -224,7 +226,7 @@ "yaserver": "^0.4.0", "@types/ws": "8.2.0", "@types/js-yaml": "^4.0.5", - "zx": "^8.7.0" + "zx": "^8.8.5" }, "overrides": { "node-gyp-build": "4.8.1", diff --git a/code/product.json b/code/product.json index 3854f6186c5..d2ac99be64c 100644 --- a/code/product.json +++ b/code/product.json @@ -53,8 +53,8 @@ }, { "name": "ms-vscode.js-debug", - "version": "1.104.0", - "sha256": "856db934294bd8b78769dd91c86904c7e35e356bb05b223a9e4d8eb38cb17ae3", + "version": "1.105.0", + "sha256": "0c45b90342e8aafd4ff2963b4006de64208ca58c2fd01fea7a710fe61dcfd12a", "repo": "https://github.com/microsoft/vscode-js-debug", "metadata": { "id": "25629058-ddac-4e17-abba-74678e126c5d", @@ -99,6 +99,61 @@ "terminalNameChangeEvent", "testObserver", "textSearchProvider" + ], + "github.copilot-chat": [ + "activeComment", + "aiRelatedInformation", + "aiSettingsSearch", + "aiTextSearchProvider", + "authLearnMore", + "chatBinaryReferenceData", + "chatEditing", + "chatParticipantAdditions", + "chatParticipantPrivate", + "chatProvider", + "chatReadonlyPromptReference", + "chatReferenceBinaryData", + "chatReferenceDiagnostic", + "chatSessionsProvider", + "chatStatusItem", + "codeActionAI", + "commentReveal", + "contribCommentThreadAdditionalMenu", + "contribCommentsViewThreadMenus", + "contribDebugCreateConfiguration", + "contribEditorContentMenu", + "contribLanguageModelToolSets", + "contribSourceControlInputBoxMenu", + "dataChannels", + "defaultChatParticipant", + "devDeviceId", + "documentFiltersExclusive", + "embeddings", + "extensionsAny", + "findFiles2", + "findTextInFiles", + "findTextInFiles2", + "inlineCompletionsAdditions", + "interactive", + "languageModelCapabilities", + "languageModelSystem", + "languageModelThinkingPart", + "languageModelToolResultAudience", + "mappedEditsProvider", + "newSymbolNamesProvider", + "readonlyMessage", + "resolvers", + "scmInputBoxValueProvider", + "taskExecutionTerminal", + "taskProblemMatcherStatus", + "terminalDataWriteEvent", + "terminalExecuteCommandEvent", + "terminalQuickFixProvider", + "terminalSelection", + "testObserver", + "textDocumentChangeReason", + "textSearchProvider", + "textSearchProvider2" ] }, "sendASmile": { @@ -108,5 +163,73 @@ "extensionsGallery": { "serviceUrl": "https://open-vsx.org/vscode/gallery", "itemUrl": "https://open-vsx.org/vscode/item" + }, + "defaultChatAgent": { + "extensionId": "GitHub.copilot", + "chatExtensionId": "GitHub.copilot-chat", + "documentationUrl": "https://aka.ms/github-copilot-overview", + "termsStatementUrl": "https://aka.ms/github-copilot-terms-statement", + "privacyStatementUrl": "https://aka.ms/github-copilot-privacy-statement", + "skusDocumentationUrl": "https://aka.ms/github-copilot-plans", + "publicCodeMatchesUrl": "https://aka.ms/github-copilot-match-public-code", + "manageSettingsUrl": "https://aka.ms/github-copilot-settings", + "managePlanUrl": "https://aka.ms/github-copilot-manage-plan", + "manageOverageUrl": "https://aka.ms/github-copilot-manage-overage", + "upgradePlanUrl": "https://aka.ms/github-copilot-upgrade-plan", + "signUpUrl": "https://aka.ms/github-sign-up", + "provider": { + "default": { + "id": "github", + "name": "GitHub" + }, + "enterprise": { + "id": "github-enterprise", + "name": "GHE.com" + }, + "google": { + "id": "google", + "name": "Google" + }, + "apple": { + "id": "apple", + "name": "Apple" + } + }, + "providerUriSetting": "github-enterprise.uri", + "providerScopes": [ + [ + "read:user", + "user:email", + "repo", + "workflow" + ], + [ + "user:email" + ], + [ + "read:user" + ] + ], + "entitlementUrl": "https://api.github.com/copilot_internal/user", + "entitlementSignupLimitedUrl": "https://api.github.com/copilot_internal/subscribe_limited_user", + "chatQuotaExceededContext": "github.copilot.chat.quotaExceeded", + "completionsQuotaExceededContext": "github.copilot.completions.quotaExceeded", + "walkthroughCommand": "github.copilot.open.walkthrough", + "completionsMenuCommand": "github.copilot.toggleStatusMenu", + "completionsRefreshTokenCommand": "github.copilot.signIn", + "chatRefreshTokenCommand": "github.copilot.refreshToken", + "generateCommitMessageCommand": "github.copilot.git.generateCommitMessage", + "resolveMergeConflictsCommand": "github.copilot.git.resolveMergeConflicts", + "completionsAdvancedSetting": "github.copilot.advanced", + "completionsEnablementSetting": "github.copilot.enable", + "nextEditSuggestionsSetting": "github.copilot.nextEditSuggestions.enabled" + }, + "trustedExtensionAuthAccess": { + "github": [ + "GitHub.copilot-chat" + ], + "github-enterprise": [ + "GitHub.copilot-chat" + ] } } diff --git a/code/remote/.npmrc b/code/remote/.npmrc index 0d4f2364f8f..326fb9fd0f6 100644 --- a/code/remote/.npmrc +++ b/code/remote/.npmrc @@ -1,6 +1,6 @@ disturl="https://nodejs.org/dist" -target="22.18.0" -ms_build_id="354241" +target="22.21.1" +ms_build_id="374314" runtime="node" build_from_source="true" legacy-peer-deps="true" diff --git a/code/remote/package-lock.json b/code/remote/package-lock.json index ddd0b958583..dfb17fef20f 100644 --- a/code/remote/package-lock.json +++ b/code/remote/package-lock.json @@ -11,26 +11,26 @@ "@kubernetes/client-node": "^1.4.0", "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", - "@parcel/watcher": "2.5.1", "@vscode/deviceid": "^0.1.1", - "@vscode/iconv-lite-umd": "0.7.0", - "@vscode/proxy-agent": "^0.34.0", + "@vscode/iconv-lite-umd": "0.7.1", + "@vscode/proxy-agent": "^0.36.0", "@vscode/ripgrep": "^1.15.13", "@vscode/spdlog": "^0.15.2", - "@vscode/tree-sitter-wasm": "^0.1.4", + "@vscode/tree-sitter-wasm": "^0.3.0", "@vscode/vscode-languagedetection": "1.0.21", + "@vscode/watcher": "bpasero/watcher#8ecffb4a57df24ac3e6946aa095b9b1f14f8bba9", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.2.0-beta.101", - "@xterm/addon-image": "^0.9.0-beta.118", - "@xterm/addon-ligatures": "^0.10.0-beta.118", - "@xterm/addon-progress": "^0.2.0-beta.24", - "@xterm/addon-search": "^0.16.0-beta.118", - "@xterm/addon-serialize": "^0.14.0-beta.118", - "@xterm/addon-unicode11": "^0.9.0-beta.118", - "@xterm/addon-webgl": "^0.19.0-beta.118", - "@xterm/headless": "^5.6.0-beta.118", - "@xterm/xterm": "^5.6.0-beta.118", + "@xterm/addon-clipboard": "^0.3.0-beta.91", + "@xterm/addon-image": "^0.10.0-beta.91", + "@xterm/addon-ligatures": "^0.11.0-beta.91", + "@xterm/addon-progress": "^0.3.0-beta.91", + "@xterm/addon-search": "^0.17.0-beta.91", + "@xterm/addon-serialize": "^0.15.0-beta.91", + "@xterm/addon-unicode11": "^0.10.0-beta.91", + "@xterm/addon-webgl": "^0.20.0-beta.90", + "@xterm/headless": "^6.1.0-beta.91", + "@xterm/xterm": "^6.1.0-beta.91", "cookie": "^0.7.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", @@ -40,12 +40,11 @@ "kerberos": "2.1.1", "minimist": "^1.2.8", "native-watchdog": "^1.4.1", - "node-pty": "^1.1.0-beta33", - "tas-client-umd": "0.2.0", + "node-pty": "^1.1.0-beta43", + "tas-client": "0.3.1", "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", - "vscode-textmate": "9.2.0", - "ws": "8.2.3", + "vscode-textmate": "^9.3.0", "yauzl": "^3.0.0", "yazl": "^2.4.3" } @@ -186,313 +185,6 @@ "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.9.tgz", "integrity": "sha512-n1VPsljTSkthsAFYdiWfC+DKzK2WwcRp83Y1YAqdX552BstvsDjft9YXppjUzp11BPsapDoO1LDgrDB0XVsfNQ==" }, - "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==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "detect-libc": "^1.0.3", - "is-glob": "^4.0.3", - "micromatch": "^4.0.5", - "node-addon-api": "^7.0.0" - }, - "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" - ], - "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" - ], - "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" - ], - "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" - ], - "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" - ], - "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" - ], - "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" - ], - "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" - ], - "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" - ], - "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" - ], - "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" - ], - "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" - ], - "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" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher/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==", - "license": "Apache-2.0", - "bin": { - "detect-libc": "bin/detect-libc.js" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/@tootallnate/once": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-3.0.0.tgz", @@ -546,14 +238,15 @@ } }, "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==" + "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==", + "license": "MIT" }, "node_modules/@vscode/proxy-agent": { - "version": "0.34.0", - "resolved": "https://registry.npmjs.org/@vscode/proxy-agent/-/proxy-agent-0.34.0.tgz", - "integrity": "sha512-LrX5mb+0vgvGQ/1jLvpsd4tUzlCVYNjvu+vvPx+yV2AvyXXnRQj/Qom1Fiavw9Mfmxw3+AHfzZ73tXwTMCfEdQ==", + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/@vscode/proxy-agent/-/proxy-agent-0.36.0.tgz", + "integrity": "sha512-W4mls/+zErqTYcKC41utdmoYnBWZRH1dRF9U4cBAyKU5EhcnWfVsPBvUnXXw1CffI3djmMWnu9JrF/Ynw7lkcg==", "license": "MIT", "dependencies": { "@tootallnate/once": "^3.0.0", @@ -564,6 +257,9 @@ "socks-proxy-agent": "^8.0.1", "undici": "^7.2.0" }, + "engines": { + "node": ">=22.15.0" + }, "optionalDependencies": { "@vscode/windows-ca-certs": "^0.3.1" } @@ -590,9 +286,9 @@ } }, "node_modules/@vscode/spdlog": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@vscode/spdlog/-/spdlog-0.15.2.tgz", - "integrity": "sha512-8RQ7JEs81x5IFONYGtFhYtaF2a3IPtNtgMdp+MFLxTDokJQBAVittx0//EN38BYhlzeVqEPgusRsOA8Yulaysg==", + "version": "0.15.4", + "resolved": "https://registry.npmjs.org/@vscode/spdlog/-/spdlog-0.15.4.tgz", + "integrity": "sha512-NmFasVWjn/6BjHMAjqalsbG2srQCt8yfC0EczP5wzNQFawv74rhvuarhWi44x3St9LB8bZBxrpbT7igPaTJwcw==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -602,9 +298,9 @@ } }, "node_modules/@vscode/tree-sitter-wasm": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@vscode/tree-sitter-wasm/-/tree-sitter-wasm-0.1.4.tgz", - "integrity": "sha512-kQVVg/CamCYDM+/XYCZuNTQyixjZd8ts/Gf84UzjEY0eRnbg6kiy5I9z2/2i3XdqwhI87iG07rkMR2KwhqcSbA==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@vscode/tree-sitter-wasm/-/tree-sitter-wasm-0.3.0.tgz", + "integrity": "sha512-4kjB1jgLyG9VimGfyJb1F8/GFdrx55atsBCH/9r2D/iZHAUDCvZ5zhWXB7sRQ2z2WkkuNYm/0pgQtUm1jhdf7A==", "license": "MIT" }, "node_modules/@vscode/vscode-languagedetection": { @@ -615,6 +311,26 @@ "vscode-languagedetection": "cli/index.js" } }, + "node_modules/@vscode/watcher": { + "version": "2.5.1-vscode", + "resolved": "git+ssh://git@github.com/bpasero/watcher.git#8ecffb4a57df24ac3e6946aa095b9b1f14f8bba9", + "integrity": "sha512-7F4REbtMh5JAtdPpBCyPq7yLgcqnZV5L+uzuT4IDaZUyCKvIqi9gDiNPyoKpvCtrw6funLmrAncFHHWoDI+S4g==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/@vscode/windows-ca-certs": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@vscode/windows-ca-certs/-/windows-ca-certs-0.3.3.tgz", @@ -640,113 +356,121 @@ } }, "node_modules/@vscode/windows-process-tree": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@vscode/windows-process-tree/-/windows-process-tree-0.6.0.tgz", - "integrity": "sha512-7/DjBKKUtlmKNiAet2GRbdvfjgMKmfBeWVClIgONv8aqxGnaKca5N85eIDxh6rLMy2hKvFqIIsqgxs1Q26TWwg==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@vscode/windows-process-tree/-/windows-process-tree-0.6.2.tgz", + "integrity": "sha512-uzyUuQ93m7K1jSPrB/72m4IspOyeGpvvghNwFCay/McZ+y4Hk2BnLdZPb6EJ8HLRa3GwCvYjH/MQZzcnLOVnaQ==", "hasInstallScript": true, + "license": "MIT", "dependencies": { "node-addon-api": "7.1.0" } }, "node_modules/@vscode/windows-registry": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@vscode/windows-registry/-/windows-registry-1.1.0.tgz", - "integrity": "sha512-5AZzuWJpGscyiMOed0IuyEwt6iKmV5Us7zuwCDCFYMIq7tsvooO9BUiciywsvuthGz6UG4LSpeDeCxvgMVhnIw==", - "hasInstallScript": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@vscode/windows-registry/-/windows-registry-1.1.2.tgz", + "integrity": "sha512-/eDRmGNe6g11wHckOyiVLvK/mEE5UBZFeoRlBosIL343LDrSKUL5JDAcFeAZqOXnlTtZ3UZtj5yezKiAz99NcA==", + "hasInstallScript": true, + "license": "MIT" }, "node_modules/@xterm/addon-clipboard": { - "version": "0.2.0-beta.101", - "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.101.tgz", - "integrity": "sha512-Q20+bySNgX9gGb8NYidHah/TzoCyOm/0+Orea9+jwCHADN/dnqb0vzT0UIvxZckFFW4twZnjNps19rszWoGC0A==", + "version": "0.3.0-beta.91", + "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.3.0-beta.91.tgz", + "integrity": "sha512-jVOjr6yu/zFS64rMTbgsAToqp++ZYam2dLE8dudKEIPcjV1Oxv6krvhdmhISGqTAU+ZhzphhccpXfovdTh+UYQ==", "license": "MIT", "dependencies": { "js-base64": "^3.7.5" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^6.1.0-beta.91" } }, "node_modules/@xterm/addon-image": { - "version": "0.9.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.118.tgz", - "integrity": "sha512-PsFEO3VsP2PdZ3TunvJuQfgPX/xdDxBzbUJD5Lh74piY1vNfpUb4R3kypRwgKxn/HYH0+/mIzwmrlMyz8i57vQ==", + "version": "0.10.0-beta.91", + "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.10.0-beta.91.tgz", + "integrity": "sha512-WwZI4UxfEGbHIuTN/EsRoDYo29Dv5l8YRqNPmS+ejvbBj9aNfPly4rVSnWlrMjZw7RsxQcPPi93PFALLY4GvQw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^6.1.0-beta.91" } }, "node_modules/@xterm/addon-ligatures": { - "version": "0.10.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.118.tgz", - "integrity": "sha512-ry8jZoyTky0TRr9uayaHXhkwwin7HFjO/J5aB/77iZbs+ES0QuZZKRWJCyk/07/RNlj3NjfxDq4ImJgUD34oGQ==", + "version": "0.11.0-beta.91", + "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.11.0-beta.91.tgz", + "integrity": "sha512-+ph1zxVDIvXyjWzNpbK+Y7bKOWttEag6EgpREXQELB8kgIM6F12rmmdY8sfueD3k03cuLeO8/si/s1IzVH89ig==", "license": "MIT", "dependencies": { - "font-finder": "^1.1.0", - "font-ligatures": "^1.4.1" + "lru-cache": "^6.0.0", + "opentype.js": "^0.8.0" }, "engines": { "node": ">8.0.0" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^6.1.0-beta.91" } }, "node_modules/@xterm/addon-progress": { - "version": "0.2.0-beta.24", - "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.24.tgz", - "integrity": "sha512-ZDrgQQOtNuDYIPIbQCmPu+MdIEx3aob3jSxHu7j/SMIvBezXMGINFVk0g8ph9ARXldAydld2dUXyr9KdDtTHSA==", + "version": "0.3.0-beta.91", + "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.3.0-beta.91.tgz", + "integrity": "sha512-88osAt/Hg4qf96p0rRlcoFBbeaU+/XubA5bfmXvhl2FoLA8hzeRSNTpr2LWSV7R1N0SICVS0ewKhPGevcoX4Qg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^6.1.0-beta.91" } }, "node_modules/@xterm/addon-search": { - "version": "0.16.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.118.tgz", - "integrity": "sha512-xZqu+0ip8wEFyGqLglqrWgjywcoc6EHGoqF2o94Qpjn/qlWYDFdbwQNLwVqF/awAxfOIvyqz7w6Z68QIbPTxWA==", + "version": "0.17.0-beta.91", + "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.17.0-beta.91.tgz", + "integrity": "sha512-pu9QD60WWAL33jCvLxf+IaIQwm4DOgfBJnYfckJZ5A3Zbj8CvaMAfFR3R7CYallBv/z5rxZ/rDRj3GEheXHVgg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^6.1.0-beta.91" } }, "node_modules/@xterm/addon-serialize": { - "version": "0.14.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.118.tgz", - "integrity": "sha512-gcyP1Vv6GAsNgE0QfXs73XyZn1V7xxETnzjBVqTevhea580CBeE6bueI+L1WBk/ztg7PLF3W30jtQoyDgqyB7A==", + "version": "0.15.0-beta.91", + "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.15.0-beta.91.tgz", + "integrity": "sha512-zpIH9p49pLE7OYJUKwOs13AqoOfhr8vf0c8cimbfX8eibdrQU+ohWYLRPIDXHu+SgIPIzal4syVdqethaJN0yg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^6.1.0-beta.91" } }, "node_modules/@xterm/addon-unicode11": { - "version": "0.9.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.118.tgz", - "integrity": "sha512-IjGBKjiopPk6hrplrd4FkOvx/94DJ6tJOXQtAyUUhpSHvQmrqEz+pWyx+K3u91OyRlsqgpdVCUfI1T9fG6V2Pg==", + "version": "0.10.0-beta.91", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.10.0-beta.91.tgz", + "integrity": "sha512-FZ/onuRqgkJSHLekhdswJGb4pvXvUmpmXSp0eIFF7mWJK4HMUqkYAanlsUsuc+1CtrghA583KNXCv37W9Irc4A==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^6.1.0-beta.91" } }, "node_modules/@xterm/addon-webgl": { - "version": "0.19.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.118.tgz", - "integrity": "sha512-l68STep6P7dCapAuzJGLKjmkDtIo6eNxRE3K9CXn2ni5epVO2e4dR5Pu0CRuY1BDHARqmFq5lU/ImaCOIWAn2g==", + "version": "0.20.0-beta.90", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.20.0-beta.90.tgz", + "integrity": "sha512-eeESmpszISob0ehoAxP0gYSUKYjnMYlpQacMQpK8Lczo4QB5LbS3D7C++NNEtOFhnfrscSjkGW+0YZoAgSfN6g==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^6.1.0-beta.91" } }, "node_modules/@xterm/headless": { - "version": "5.6.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.6.0-beta.118.tgz", - "integrity": "sha512-VUIlj6NvEmhi4PISiPhBAhPhEPcoSBYG39E3nuVHp2wvo02Z+2lig8WppVuwBMDk1EfBSo/MYa1YqPxVSqYNUQ==", - "license": "MIT" + "version": "6.1.0-beta.91", + "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-6.1.0-beta.91.tgz", + "integrity": "sha512-8OfmAJ6FASbge0g7mXNrM1olHGCCOS0+NUx18pU94w1l7Rw7xqU43aHe29PIPv8MY3+taO3Bpa3Tw1g6fiCEUg==", + "license": "MIT", + "workspaces": [ + "addons/*" + ] }, "node_modules/@xterm/xterm": { - "version": "5.6.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.118.tgz", - "integrity": "sha512-uwqiQP4VH1uFl99wey7gjPRHOHd/ovot/Dh6YJNQk95aT3UjWunb/yHNQpkMD6X4i5Ysv97rn0GrO5DR0eLPXw==", - "license": "MIT" + "version": "6.1.0-beta.91", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.1.0-beta.91.tgz", + "integrity": "sha512-sXd6uKhMB3cycSyW/I8eVGglZ9+rGDLDTtm9fkiXDPcA1N/oMiR0iUKGstv2VOCT/40af3+//kYgl8IwOqPbxw==", + "license": "MIT", + "workspaces": [ + "addons/*" + ] }, "node_modules/agent-base": { "version": "7.1.1", @@ -1051,9 +775,10 @@ } }, "node_modules/detect-libc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", - "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "license": "Apache-2.0", "engines": { "node": ">=8" } @@ -1172,49 +897,6 @@ "node": ">=8" } }, - "node_modules/font-finder": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/font-finder/-/font-finder-1.1.0.tgz", - "integrity": "sha512-wpCL2uIbi6GurJbU7ZlQ3nGd61Ho+dSU6U83/xJT5UPFfN35EeCW/rOtS+5k+IuEZu2SYmHzDIPL9eA5tSYRAw==", - "license": "MIT", - "dependencies": { - "get-system-fonts": "^2.0.0", - "promise-stream-reader": "^1.0.1" - }, - "engines": { - "node": ">8.0.0" - } - }, - "node_modules/font-ligatures": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/font-ligatures/-/font-ligatures-1.4.1.tgz", - "integrity": "sha512-7W6zlfyhvCqShZ5ReUWqmSd9vBaUudW0Hxis+tqUjtHhsPU+L3Grf8mcZAtCiXHTzorhwdRTId2WeH/88gdFkw==", - "license": "MIT", - "dependencies": { - "font-finder": "^1.0.3", - "lru-cache": "^6.0.0", - "opentype.js": "^0.8.0" - }, - "engines": { - "node": ">8.0.0" - } - }, - "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "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", @@ -1233,61 +915,6 @@ "node": ">=14.14" } }, - "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==", - "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==", - "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==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-system-fonts": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-system-fonts/-/get-system-fonts-2.0.2.tgz", - "integrity": "sha512-zzlgaYnHMIEgHRrfC7x0Qp0Ylhw/sHpM6MHXeVBTYIsvGf5GpbnClB+Q6rAPdn+0gd2oZZIo6Tj3EaWrt4VhDQ==", - "license": "MIT", - "engines": { - "node": ">8.0.0" - } - }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -1713,9 +1340,9 @@ } }, "node_modules/node-pty": { - "version": "1.1.0-beta33", - "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.1.0-beta33.tgz", - "integrity": "sha512-+BN2bT/KqO+fmCHnpFS99VMVJr7VUBCUa2VIBEw0oEvszkR7ri0kwD1lF91OeQToUJ2dXKA8j6scPjbO4eRWOQ==", + "version": "1.1.0-beta43", + "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.1.0-beta43.tgz", + "integrity": "sha512-CYyIQogRs97Rfjo0WKyku8V56Bm4WyWUijrbWDs5LJ+ZmsUW2gqbVAEpD+1gtA7dEZ6v1A08GzfqsDuIl/eRqw==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -1805,15 +1432,6 @@ "node": ">=10" } }, - "node_modules/promise-stream-reader": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-stream-reader/-/promise-stream-reader-1.0.1.tgz", - "integrity": "sha512-Tnxit5trUjBAqqZCGWwjyxhmgMN4hGrtpW3Oc/tRI4bpm/O2+ej72BB08l6JBnGQgVDGCLvHFGjGgQS6vzhwXg==", - "license": "MIT", - "engines": { - "node": ">8.0.0" - } - }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -2040,10 +1658,14 @@ "node": ">=6" } }, - "node_modules/tas-client-umd": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/tas-client-umd/-/tas-client-umd-0.2.0.tgz", - "integrity": "sha512-oezN7mJVm5qZDVEby7OzxCLKUpUN5of0rY4dvOWaDF2JZBlGpd3BXceFN8B53qlTaIkVSzP65aAMT0Vc+/N25Q==" + "node_modules/tas-client": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/tas-client/-/tas-client-0.3.1.tgz", + "integrity": "sha512-Mn4+4t/KXEf8aIENeI1TkzpKIImzmG+FjPZ2dlaoGNFgxJqBE/pp3MT7nc2032EG4aS73E4OEcr2WiNaWW8mdA==", + "license": "MIT", + "engines": { + "node": ">=22" + } }, "node_modules/text-decoder": { "version": "1.2.3", @@ -2145,9 +1767,9 @@ } }, "node_modules/vscode-textmate": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-9.2.0.tgz", - "integrity": "sha512-rkvG4SraZQaPSN/5XjwKswdU0OP9MF28QjrYzUBbhb8QyG3ljB1Ky996m++jiI7KdiAP2CkBiQZd9pqEDTClqA==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-9.3.0.tgz", + "integrity": "sha512-zHiZZOdb9xqj5/X1C4a29sbgT2HngdWxPLSl3PyHRQF+5visI4uNM020OHiLJjsMxUssyk/pGVAg/9LCIobrVg==", "license": "MIT" }, "node_modules/webidl-conversions": { diff --git a/code/remote/package.json b/code/remote/package.json index 84e1c82a1ce..3d997915b6a 100644 --- a/code/remote/package.json +++ b/code/remote/package.json @@ -6,26 +6,26 @@ "@kubernetes/client-node": "^1.4.0", "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", - "@parcel/watcher": "2.5.1", "@vscode/deviceid": "^0.1.1", - "@vscode/iconv-lite-umd": "0.7.0", - "@vscode/proxy-agent": "^0.34.0", + "@vscode/iconv-lite-umd": "0.7.1", + "@vscode/proxy-agent": "^0.36.0", "@vscode/ripgrep": "^1.15.13", "@vscode/spdlog": "^0.15.2", - "@vscode/tree-sitter-wasm": "^0.1.4", + "@vscode/tree-sitter-wasm": "^0.3.0", "@vscode/vscode-languagedetection": "1.0.21", + "@vscode/watcher": "bpasero/watcher#8ecffb4a57df24ac3e6946aa095b9b1f14f8bba9", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.2.0-beta.101", - "@xterm/addon-image": "^0.9.0-beta.118", - "@xterm/addon-ligatures": "^0.10.0-beta.118", - "@xterm/addon-progress": "^0.2.0-beta.24", - "@xterm/addon-search": "^0.16.0-beta.118", - "@xterm/addon-serialize": "^0.14.0-beta.118", - "@xterm/addon-unicode11": "^0.9.0-beta.118", - "@xterm/addon-webgl": "^0.19.0-beta.118", - "@xterm/headless": "^5.6.0-beta.118", - "@xterm/xterm": "^5.6.0-beta.118", + "@xterm/addon-clipboard": "^0.3.0-beta.91", + "@xterm/addon-image": "^0.10.0-beta.91", + "@xterm/addon-ligatures": "^0.11.0-beta.91", + "@xterm/addon-progress": "^0.3.0-beta.91", + "@xterm/addon-search": "^0.17.0-beta.91", + "@xterm/addon-serialize": "^0.15.0-beta.91", + "@xterm/addon-unicode11": "^0.10.0-beta.91", + "@xterm/addon-webgl": "^0.20.0-beta.90", + "@xterm/headless": "^6.1.0-beta.91", + "@xterm/xterm": "^6.1.0-beta.91", "cookie": "^0.7.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", @@ -34,11 +34,11 @@ "kerberos": "2.1.1", "minimist": "^1.2.8", "native-watchdog": "^1.4.1", - "node-pty": "^1.1.0-beta33", - "tas-client-umd": "0.2.0", + "node-pty": "^1.1.0-beta43", + "tas-client": "0.3.1", "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", - "vscode-textmate": "9.2.0", + "vscode-textmate": "^9.3.0", "yauzl": "^3.0.0", "yazl": "^2.4.3", "ws": "8.2.3", diff --git a/code/remote/web/package-lock.json b/code/remote/web/package-lock.json index 19ca9f496ab..8456cf6f41c 100644 --- a/code/remote/web/package-lock.json +++ b/code/remote/web/package-lock.json @@ -10,23 +10,23 @@ "dependencies": { "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", - "@vscode/iconv-lite-umd": "0.7.0", - "@vscode/tree-sitter-wasm": "^0.1.4", + "@vscode/iconv-lite-umd": "0.7.1", + "@vscode/tree-sitter-wasm": "^0.3.0", "@vscode/vscode-languagedetection": "1.0.21", - "@xterm/addon-clipboard": "^0.2.0-beta.101", - "@xterm/addon-image": "^0.9.0-beta.118", - "@xterm/addon-ligatures": "^0.10.0-beta.118", - "@xterm/addon-progress": "^0.2.0-beta.24", - "@xterm/addon-search": "^0.16.0-beta.118", - "@xterm/addon-serialize": "^0.14.0-beta.118", - "@xterm/addon-unicode11": "^0.9.0-beta.118", - "@xterm/addon-webgl": "^0.19.0-beta.118", - "@xterm/xterm": "^5.6.0-beta.118", + "@xterm/addon-clipboard": "^0.3.0-beta.91", + "@xterm/addon-image": "^0.10.0-beta.91", + "@xterm/addon-ligatures": "^0.11.0-beta.91", + "@xterm/addon-progress": "^0.3.0-beta.91", + "@xterm/addon-search": "^0.17.0-beta.91", + "@xterm/addon-serialize": "^0.15.0-beta.91", + "@xterm/addon-unicode11": "^0.10.0-beta.91", + "@xterm/addon-webgl": "^0.20.0-beta.90", + "@xterm/xterm": "^6.1.0-beta.91", "jschardet": "3.1.4", "katex": "^0.16.22", - "tas-client-umd": "0.2.0", + "tas-client": "0.3.1", "vscode-oniguruma": "1.7.0", - "vscode-textmate": "9.2.0" + "vscode-textmate": "^9.3.0" } }, "node_modules/@microsoft/1ds-core-js": { @@ -72,14 +72,15 @@ "integrity": "sha512-n1VPsljTSkthsAFYdiWfC+DKzK2WwcRp83Y1YAqdX552BstvsDjft9YXppjUzp11BPsapDoO1LDgrDB0XVsfNQ==" }, "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==" + "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==", + "license": "MIT" }, "node_modules/@vscode/tree-sitter-wasm": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@vscode/tree-sitter-wasm/-/tree-sitter-wasm-0.1.4.tgz", - "integrity": "sha512-kQVVg/CamCYDM+/XYCZuNTQyixjZd8ts/Gf84UzjEY0eRnbg6kiy5I9z2/2i3XdqwhI87iG07rkMR2KwhqcSbA==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@vscode/tree-sitter-wasm/-/tree-sitter-wasm-0.3.0.tgz", + "integrity": "sha512-4kjB1jgLyG9VimGfyJb1F8/GFdrx55atsBCH/9r2D/iZHAUDCvZ5zhWXB7sRQ2z2WkkuNYm/0pgQtUm1jhdf7A==", "license": "MIT" }, "node_modules/@vscode/vscode-languagedetection": { @@ -91,92 +92,95 @@ } }, "node_modules/@xterm/addon-clipboard": { - "version": "0.2.0-beta.101", - "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.101.tgz", - "integrity": "sha512-Q20+bySNgX9gGb8NYidHah/TzoCyOm/0+Orea9+jwCHADN/dnqb0vzT0UIvxZckFFW4twZnjNps19rszWoGC0A==", + "version": "0.3.0-beta.91", + "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.3.0-beta.91.tgz", + "integrity": "sha512-jVOjr6yu/zFS64rMTbgsAToqp++ZYam2dLE8dudKEIPcjV1Oxv6krvhdmhISGqTAU+ZhzphhccpXfovdTh+UYQ==", "license": "MIT", "dependencies": { "js-base64": "^3.7.5" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^6.1.0-beta.91" } }, "node_modules/@xterm/addon-image": { - "version": "0.9.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.118.tgz", - "integrity": "sha512-PsFEO3VsP2PdZ3TunvJuQfgPX/xdDxBzbUJD5Lh74piY1vNfpUb4R3kypRwgKxn/HYH0+/mIzwmrlMyz8i57vQ==", + "version": "0.10.0-beta.91", + "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.10.0-beta.91.tgz", + "integrity": "sha512-WwZI4UxfEGbHIuTN/EsRoDYo29Dv5l8YRqNPmS+ejvbBj9aNfPly4rVSnWlrMjZw7RsxQcPPi93PFALLY4GvQw==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^6.1.0-beta.91" } }, "node_modules/@xterm/addon-ligatures": { - "version": "0.10.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.118.tgz", - "integrity": "sha512-ry8jZoyTky0TRr9uayaHXhkwwin7HFjO/J5aB/77iZbs+ES0QuZZKRWJCyk/07/RNlj3NjfxDq4ImJgUD34oGQ==", + "version": "0.11.0-beta.91", + "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.11.0-beta.91.tgz", + "integrity": "sha512-+ph1zxVDIvXyjWzNpbK+Y7bKOWttEag6EgpREXQELB8kgIM6F12rmmdY8sfueD3k03cuLeO8/si/s1IzVH89ig==", "license": "MIT", "dependencies": { - "font-finder": "^1.1.0", - "font-ligatures": "^1.4.1" + "lru-cache": "^6.0.0", + "opentype.js": "^0.8.0" }, "engines": { "node": ">8.0.0" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^6.1.0-beta.91" } }, "node_modules/@xterm/addon-progress": { - "version": "0.2.0-beta.24", - "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.2.0-beta.24.tgz", - "integrity": "sha512-ZDrgQQOtNuDYIPIbQCmPu+MdIEx3aob3jSxHu7j/SMIvBezXMGINFVk0g8ph9ARXldAydld2dUXyr9KdDtTHSA==", + "version": "0.3.0-beta.91", + "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.3.0-beta.91.tgz", + "integrity": "sha512-88osAt/Hg4qf96p0rRlcoFBbeaU+/XubA5bfmXvhl2FoLA8hzeRSNTpr2LWSV7R1N0SICVS0ewKhPGevcoX4Qg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^6.1.0-beta.91" } }, "node_modules/@xterm/addon-search": { - "version": "0.16.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.118.tgz", - "integrity": "sha512-xZqu+0ip8wEFyGqLglqrWgjywcoc6EHGoqF2o94Qpjn/qlWYDFdbwQNLwVqF/awAxfOIvyqz7w6Z68QIbPTxWA==", + "version": "0.17.0-beta.91", + "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.17.0-beta.91.tgz", + "integrity": "sha512-pu9QD60WWAL33jCvLxf+IaIQwm4DOgfBJnYfckJZ5A3Zbj8CvaMAfFR3R7CYallBv/z5rxZ/rDRj3GEheXHVgg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^6.1.0-beta.91" } }, "node_modules/@xterm/addon-serialize": { - "version": "0.14.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.118.tgz", - "integrity": "sha512-gcyP1Vv6GAsNgE0QfXs73XyZn1V7xxETnzjBVqTevhea580CBeE6bueI+L1WBk/ztg7PLF3W30jtQoyDgqyB7A==", + "version": "0.15.0-beta.91", + "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.15.0-beta.91.tgz", + "integrity": "sha512-zpIH9p49pLE7OYJUKwOs13AqoOfhr8vf0c8cimbfX8eibdrQU+ohWYLRPIDXHu+SgIPIzal4syVdqethaJN0yg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^6.1.0-beta.91" } }, "node_modules/@xterm/addon-unicode11": { - "version": "0.9.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.118.tgz", - "integrity": "sha512-IjGBKjiopPk6hrplrd4FkOvx/94DJ6tJOXQtAyUUhpSHvQmrqEz+pWyx+K3u91OyRlsqgpdVCUfI1T9fG6V2Pg==", + "version": "0.10.0-beta.91", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.10.0-beta.91.tgz", + "integrity": "sha512-FZ/onuRqgkJSHLekhdswJGb4pvXvUmpmXSp0eIFF7mWJK4HMUqkYAanlsUsuc+1CtrghA583KNXCv37W9Irc4A==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^6.1.0-beta.91" } }, "node_modules/@xterm/addon-webgl": { - "version": "0.19.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.118.tgz", - "integrity": "sha512-l68STep6P7dCapAuzJGLKjmkDtIo6eNxRE3K9CXn2ni5epVO2e4dR5Pu0CRuY1BDHARqmFq5lU/ImaCOIWAn2g==", + "version": "0.20.0-beta.90", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.20.0-beta.90.tgz", + "integrity": "sha512-eeESmpszISob0ehoAxP0gYSUKYjnMYlpQacMQpK8Lczo4QB5LbS3D7C++NNEtOFhnfrscSjkGW+0YZoAgSfN6g==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.118" + "@xterm/xterm": "^6.1.0-beta.91" } }, "node_modules/@xterm/xterm": { - "version": "5.6.0-beta.118", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.118.tgz", - "integrity": "sha512-uwqiQP4VH1uFl99wey7gjPRHOHd/ovot/Dh6YJNQk95aT3UjWunb/yHNQpkMD6X4i5Ysv97rn0GrO5DR0eLPXw==", - "license": "MIT" + "version": "6.1.0-beta.91", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.1.0-beta.91.tgz", + "integrity": "sha512-sXd6uKhMB3cycSyW/I8eVGglZ9+rGDLDTtm9fkiXDPcA1N/oMiR0iUKGstv2VOCT/40af3+//kYgl8IwOqPbxw==", + "license": "MIT", + "workspaces": [ + "addons/*" + ] }, "node_modules/commander": { "version": "8.3.0", @@ -187,42 +191,6 @@ "node": ">= 12" } }, - "node_modules/font-finder": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/font-finder/-/font-finder-1.1.0.tgz", - "integrity": "sha512-wpCL2uIbi6GurJbU7ZlQ3nGd61Ho+dSU6U83/xJT5UPFfN35EeCW/rOtS+5k+IuEZu2SYmHzDIPL9eA5tSYRAw==", - "license": "MIT", - "dependencies": { - "get-system-fonts": "^2.0.0", - "promise-stream-reader": "^1.0.1" - }, - "engines": { - "node": ">8.0.0" - } - }, - "node_modules/font-ligatures": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/font-ligatures/-/font-ligatures-1.4.1.tgz", - "integrity": "sha512-7W6zlfyhvCqShZ5ReUWqmSd9vBaUudW0Hxis+tqUjtHhsPU+L3Grf8mcZAtCiXHTzorhwdRTId2WeH/88gdFkw==", - "license": "MIT", - "dependencies": { - "font-finder": "^1.0.3", - "lru-cache": "^6.0.0", - "opentype.js": "^0.8.0" - }, - "engines": { - "node": ">8.0.0" - } - }, - "node_modules/get-system-fonts": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-system-fonts/-/get-system-fonts-2.0.2.tgz", - "integrity": "sha512-zzlgaYnHMIEgHRrfC7x0Qp0Ylhw/sHpM6MHXeVBTYIsvGf5GpbnClB+Q6rAPdn+0gd2oZZIo6Tj3EaWrt4VhDQ==", - "license": "MIT", - "engines": { - "node": ">8.0.0" - } - }, "node_modules/js-base64": { "version": "3.7.7", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", @@ -277,20 +245,15 @@ "ot": "bin/ot" } }, - "node_modules/promise-stream-reader": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-stream-reader/-/promise-stream-reader-1.0.1.tgz", - "integrity": "sha512-Tnxit5trUjBAqqZCGWwjyxhmgMN4hGrtpW3Oc/tRI4bpm/O2+ej72BB08l6JBnGQgVDGCLvHFGjGgQS6vzhwXg==", + "node_modules/tas-client": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/tas-client/-/tas-client-0.3.1.tgz", + "integrity": "sha512-Mn4+4t/KXEf8aIENeI1TkzpKIImzmG+FjPZ2dlaoGNFgxJqBE/pp3MT7nc2032EG4aS73E4OEcr2WiNaWW8mdA==", "license": "MIT", "engines": { - "node": ">8.0.0" + "node": ">=22" } }, - "node_modules/tas-client-umd": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/tas-client-umd/-/tas-client-umd-0.2.0.tgz", - "integrity": "sha512-oezN7mJVm5qZDVEby7OzxCLKUpUN5of0rY4dvOWaDF2JZBlGpd3BXceFN8B53qlTaIkVSzP65aAMT0Vc+/N25Q==" - }, "node_modules/tiny-inflate": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", @@ -303,9 +266,9 @@ "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==" }, "node_modules/vscode-textmate": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-9.2.0.tgz", - "integrity": "sha512-rkvG4SraZQaPSN/5XjwKswdU0OP9MF28QjrYzUBbhb8QyG3ljB1Ky996m++jiI7KdiAP2CkBiQZd9pqEDTClqA==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-9.3.0.tgz", + "integrity": "sha512-zHiZZOdb9xqj5/X1C4a29sbgT2HngdWxPLSl3PyHRQF+5visI4uNM020OHiLJjsMxUssyk/pGVAg/9LCIobrVg==", "license": "MIT" }, "node_modules/yallist": { diff --git a/code/remote/web/package.json b/code/remote/web/package.json index e1198458b15..00ff7f51b19 100644 --- a/code/remote/web/package.json +++ b/code/remote/web/package.json @@ -5,22 +5,22 @@ "dependencies": { "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", - "@vscode/iconv-lite-umd": "0.7.0", - "@vscode/tree-sitter-wasm": "^0.1.4", + "@vscode/iconv-lite-umd": "0.7.1", + "@vscode/tree-sitter-wasm": "^0.3.0", "@vscode/vscode-languagedetection": "1.0.21", - "@xterm/addon-clipboard": "^0.2.0-beta.101", - "@xterm/addon-image": "^0.9.0-beta.118", - "@xterm/addon-ligatures": "^0.10.0-beta.118", - "@xterm/addon-progress": "^0.2.0-beta.24", - "@xterm/addon-search": "^0.16.0-beta.118", - "@xterm/addon-serialize": "^0.14.0-beta.118", - "@xterm/addon-unicode11": "^0.9.0-beta.118", - "@xterm/addon-webgl": "^0.19.0-beta.118", - "@xterm/xterm": "^5.6.0-beta.118", + "@xterm/addon-clipboard": "^0.3.0-beta.91", + "@xterm/addon-image": "^0.10.0-beta.91", + "@xterm/addon-ligatures": "^0.11.0-beta.91", + "@xterm/addon-progress": "^0.3.0-beta.91", + "@xterm/addon-search": "^0.17.0-beta.91", + "@xterm/addon-serialize": "^0.15.0-beta.91", + "@xterm/addon-unicode11": "^0.10.0-beta.91", + "@xterm/addon-webgl": "^0.20.0-beta.90", + "@xterm/xterm": "^6.1.0-beta.91", "jschardet": "3.1.4", "katex": "^0.16.22", - "tas-client-umd": "0.2.0", + "tas-client": "0.3.1", "vscode-oniguruma": "1.7.0", - "vscode-textmate": "9.2.0" + "vscode-textmate": "^9.3.0" } } diff --git a/code/resources/linux/snap/electron-launch b/code/resources/linux/snap/electron-launch index bbd8e76588e..57d025ccef1 100755 --- a/code/resources/linux/snap/electron-launch +++ b/code/resources/linux/snap/electron-launch @@ -276,4 +276,4 @@ fi wait_for_async_execs -exec "$@" +exec "$@" --ozone-platform=x11 diff --git a/code/resources/win32/insider/bin/code.cmd b/code/resources/win32/insider/bin/code.cmd new file mode 100644 index 00000000000..1298c72ee0e --- /dev/null +++ b/code/resources/win32/insider/bin/code.cmd @@ -0,0 +1,7 @@ +@echo off +setlocal +set VSCODE_DEV= +set ELECTRON_RUN_AS_NODE=1 +"%~dp0..\@@NAME@@.exe" "%~dp0..\@@VERSIONFOLDER@@\resources\app\out\cli.js" %* +IF %ERRORLEVEL% NEQ 0 EXIT /b %ERRORLEVEL% +endlocal diff --git a/code/resources/win32/insider/bin/code.sh b/code/resources/win32/insider/bin/code.sh new file mode 100644 index 00000000000..2443d965ca7 --- /dev/null +++ b/code/resources/win32/insider/bin/code.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env sh +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +if [ "$VSCODE_WSL_DEBUG_INFO" = true ]; then + set -x +fi + +COMMIT="@@COMMIT@@" +APP_NAME="@@APPNAME@@" +QUALITY="@@QUALITY@@" +NAME="@@NAME@@" +SERVERDATAFOLDER="@@SERVERDATAFOLDER@@" +VERSIONFOLDER="@@VERSIONFOLDER@@" +VSCODE_PATH="$(dirname "$(dirname "$(realpath "$0")")")" +ELECTRON="$VSCODE_PATH/$NAME.exe" + +IN_WSL=false +if [ -n "$WSL_DISTRO_NAME" ]; then + # $WSL_DISTRO_NAME is available since WSL builds 18362, also for WSL2 + IN_WSL=true +else + WSL_BUILD=$(uname -r | sed -E 's/^[0-9.]+-([0-9]+)-Microsoft.*|.*/\1/') + if [ -n "$WSL_BUILD" ]; then + if [ "$WSL_BUILD" -ge 17063 ]; then + # WSLPATH is available since WSL build 17046 + # WSLENV is available since WSL build 17063 + IN_WSL=true + else + # If running under older WSL, don't pass cli.js to Electron as + # environment vars cannot be transferred from WSL to Windows + # See: https://github.com/microsoft/BashOnWindows/issues/1363 + # https://github.com/microsoft/BashOnWindows/issues/1494 + "$ELECTRON" "$@" + exit $? + fi + fi +fi +if [ $IN_WSL = true ]; then + + export WSLENV="ELECTRON_RUN_AS_NODE/w:$WSLENV" + CLI=$(wslpath -m "$VSCODE_PATH/$VERSIONFOLDER/resources/app/out/cli.js") + + # use the Remote WSL extension if installed + WSL_EXT_ID="ms-vscode-remote.remote-wsl" + + ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" --locate-extension $WSL_EXT_ID >/tmp/remote-wsl-loc.txt 2>/dev/null { const reader = request.body.getReader(); @@ -332,6 +333,7 @@ function makeLoaderJsHotReloadable(loaderJsCode: string, fileChangesUrl: URL): s const moduleIdStr = trimEnd(relativePath, '.js'); const requireFn: any = globalThis.require; + // eslint-disable-next-line local/code-no-any-casts const moduleManager = (requireFn as any).moduleManager; if (!moduleManager) { console.log(debugSessionName, 'ignoring js change, as moduleManager is not available', relativePath); @@ -347,6 +349,7 @@ function makeLoaderJsHotReloadable(loaderJsCode: string, fileChangesUrl: URL): s } // Check if we can reload + // eslint-disable-next-line local/code-no-any-casts const g = globalThis as any; // A frozen copy of the previous exports diff --git a/code/scripts/test-integration.bat b/code/scripts/test-integration.bat index 50c14ff8d3f..2be5bfef0a0 100644 --- a/code/scripts/test-integration.bat +++ b/code/scripts/test-integration.bat @@ -80,6 +80,11 @@ mkdir %GITWORKSPACE% call "%INTEGRATION_TEST_ELECTRON_PATH%" %GITWORKSPACE% --extensionDevelopmentPath=%~dp0\..\extensions\git --extensionTestsPath=%~dp0\..\extensions\git\out\test %API_TESTS_EXTRA_ARGS% if %errorlevel% neq 0 exit /b %errorlevel% +echo. +echo ### Git Base tests +call npm run test-extension -- -l git-base +if %errorlevel% neq 0 exit /b %errorlevel% + echo. echo ### Ipynb tests call npm run test-extension -- -l ipynb diff --git a/code/scripts/test-integration.sh b/code/scripts/test-integration.sh index 3e26bb17a17..e3c391004f8 100755 --- a/code/scripts/test-integration.sh +++ b/code/scripts/test-integration.sh @@ -97,6 +97,12 @@ echo "$INTEGRATION_TEST_ELECTRON_PATH" $(mktemp -d 2>/dev/null) --extensionDevelopmentPath=$ROOT/extensions/git --extensionTestsPath=$ROOT/extensions/git/out/test $API_TESTS_EXTRA_ARGS kill_app +echo +echo "### Git Base tests" +echo +npm run test-extension -- -l git-base +kill_app + echo echo "### Ipynb tests" echo diff --git a/code/scripts/xterm-update.js b/code/scripts/xterm-update.js index 35c2084f794..1a36e6ac41a 100644 --- a/code/scripts/xterm-update.js +++ b/code/scripts/xterm-update.js @@ -23,8 +23,8 @@ const backendOnlyModuleNames = [ ]; const vscodeDir = process.argv.length >= 3 ? process.argv[2] : process.cwd(); -if (path.basename(vscodeDir) !== 'vscode') { - console.error('The cwd is not named "vscode"'); +if (!path.basename(vscodeDir).match(/.*vscode.*/)) { + console.error('The cwd is not "vscode" root'); return; } diff --git a/code/src/bootstrap-fork.ts b/code/src/bootstrap-fork.ts index a92290a213d..b87e855ba85 100644 --- a/code/src/bootstrap-fork.ts +++ b/code/src/bootstrap-fork.ts @@ -123,7 +123,7 @@ function pipeLoggingToParent(): void { Object.defineProperty(stream, 'write', { set: () => { }, - get: () => (chunk: string | Buffer | Uint8Array, encoding: BufferEncoding | undefined, callback: ((err?: Error | undefined) => void) | undefined) => { + get: () => (chunk: string | Buffer | Uint8Array, encoding: BufferEncoding | undefined, callback: ((err?: Error | null) => void) | undefined) => { buf += chunk.toString(encoding); const eol = buf.length > MAX_STREAM_BUFFER_LENGTH ? buf.length : buf.lastIndexOf('\n'); if (eol !== -1) { @@ -184,9 +184,9 @@ function configureCrashReporter(): void { const crashReporterProcessType = process.env['VSCODE_CRASH_REPORTER_PROCESS_TYPE']; if (crashReporterProcessType) { try { - //@ts-ignore + //@ts-expect-error if (process['crashReporter'] && typeof process['crashReporter'].addExtraParameter === 'function' /* Electron only */) { - //@ts-ignore + //@ts-expect-error process['crashReporter'].addExtraParameter('processType', crashReporterProcessType); } } catch (error) { diff --git a/code/src/bootstrap-import.ts b/code/src/bootstrap-import.ts index 9d222a003eb..3bd5c73a0af 100644 --- a/code/src/bootstrap-import.ts +++ b/code/src/bootstrap-import.ts @@ -47,7 +47,7 @@ export async function initialize(injectPath: string): Promise { console.log(`[bootstrap-import] Initialized node_modules redirector for: ${injectPath}`); } -export async function resolve(specifier: string | number, context: any, nextResolve: (arg0: any, arg1: any) => any) { +export async function resolve(specifier: string | number, context: unknown, nextResolve: (arg0: unknown, arg1: unknown) => unknown) { const newSpecifier = _specifierToUrl[specifier]; if (newSpecifier !== undefined) { diff --git a/code/src/bootstrap-node.ts b/code/src/bootstrap-node.ts index 30e1bd41c90..837dcc91424 100644 --- a/code/src/bootstrap-node.ts +++ b/code/src/bootstrap-node.ts @@ -83,7 +83,7 @@ export function removeGlobalNodeJsModuleLookupPaths(): void { const originalResolveLookupPaths = Module._resolveLookupPaths; - Module._resolveLookupPaths = function (moduleName: string, parent: any): string[] { + Module._resolveLookupPaths = function (moduleName: string, parent: unknown): string[] { const paths = originalResolveLookupPaths(moduleName, parent); if (Array.isArray(paths)) { let commonSuffixLength = 0; @@ -142,6 +142,11 @@ export function configurePortable(product: Partial): { po return path.dirname(path.dirname(path.dirname(appRoot))); } + // appRoot = ..\Microsoft VS Code Insiders\\resources\app + if (process.platform === 'win32' && product.quality === 'insider') { + return path.dirname(path.dirname(path.dirname(appRoot))); + } + return path.dirname(path.dirname(appRoot)); } diff --git a/code/src/main.ts b/code/src/main.ts index deba4c449cf..ec2e45c31d2 100644 --- a/code/src/main.ts +++ b/code/src/main.ts @@ -88,7 +88,7 @@ perf.mark('code/didStartCrashReporter'); // to ensure that no 'logs' folder is created on disk at a // location outside of the portable directory // (https://github.com/microsoft/vscode/issues/56651) -if (portable && portable.isPortable) { +if (portable.isPortable) { app.setAppLogsPath(path.join(userDataPath, 'logs')); } @@ -320,18 +320,18 @@ function configureCommandlineSwitchesSync(cliArgs: NativeParsedArgs) { }); // Following features are enabled from the runtime: + // `NetAdapterMaxBufSizeFeature` - Specify the max buffer size for NetToMojoPendingBuffer, refs https://github.com/microsoft/vscode/issues/268800 // `DocumentPolicyIncludeJSCallStacksInCrashReports` - https://www.electronjs.org/docs/latest/api/web-frame-main#framecollectjavascriptcallstack-experimental // `EarlyEstablishGpuChannel` - Refs https://issues.chromium.org/issues/40208065 // `EstablishGpuChannelAsync` - Refs https://issues.chromium.org/issues/40208065 const featuresToEnable = - `DocumentPolicyIncludeJSCallStacksInCrashReports,EarlyEstablishGpuChannel,EstablishGpuChannelAsync,${app.commandLine.getSwitchValue('enable-features')}`; + `NetAdapterMaxBufSizeFeature:NetAdapterMaxBufSize/8192,DocumentPolicyIncludeJSCallStacksInCrashReports,EarlyEstablishGpuChannel,EstablishGpuChannelAsync,${app.commandLine.getSwitchValue('enable-features')}`; app.commandLine.appendSwitch('enable-features', featuresToEnable); // Following features are disabled from the runtime: // `CalculateNativeWinOcclusion` - Disable native window occlusion tracker (https://groups.google.com/a/chromium.org/g/embedder-dev/c/ZF3uHHyWLKw/m/VDN2hDXMAAAJ) - // `FontationsLinuxSystemFonts` - Revert to FreeType for system fonts on Linux Refs https://github.com/microsoft/vscode/issues/260391 const featuresToDisable = - `CalculateNativeWinOcclusion,FontationsLinuxSystemFonts,${app.commandLine.getSwitchValue('disable-features')}`; + `CalculateNativeWinOcclusion,${app.commandLine.getSwitchValue('disable-features')}`; app.commandLine.appendSwitch('disable-features', featuresToDisable); // Blink features to configure. @@ -352,6 +352,10 @@ function configureCommandlineSwitchesSync(cliArgs: NativeParsedArgs) { // Runtime sets the default version to 3, refs https://github.com/electron/electron/pull/44426 app.commandLine.appendSwitch('xdg-portal-required-version', '4'); + // Increase the maximum number of active WebGL contexts as each terminal may + // use up to 2 + app.commandLine.appendSwitch('max-active-webgl-contexts', '32'); + return argvConfig; } @@ -528,7 +532,8 @@ function configureCrashReporter(): void { productName: process.env['VSCODE_DEV'] ? `${productName} Dev` : productName, submitURL, uploadToServer, - compress: true + compress: true, + ignoreSystemCrashHandler: true }); } @@ -582,7 +587,7 @@ function registerListeners(): void { * the app-ready event. We listen very early for open-file and remember this upon startup as path to open. */ const macOpenFiles: string[] = []; - (globalThis as any)['macOpenFiles'] = macOpenFiles; + (globalThis as { macOpenFiles?: string[] }).macOpenFiles = macOpenFiles; app.on('open-file', function (event, path) { macOpenFiles.push(path); }); @@ -602,7 +607,7 @@ function registerListeners(): void { app.on('open-url', onOpenUrl); }); - (globalThis as any)['getOpenUrls'] = function () { + (globalThis as { getOpenUrls?: () => string[] }).getOpenUrls = function () { app.removeListener('open-url', onOpenUrl); return openUrls; diff --git a/code/src/server-main.ts b/code/src/server-main.ts index b18adcd8fa7..046347d1103 100644 --- a/code/src/server-main.ts +++ b/code/src/server-main.ts @@ -21,7 +21,7 @@ import { INLSConfiguration } from './vs/nls.js'; import { IServerAPI } from './vs/server/node/remoteExtensionHostAgentServer.js'; perf.mark('code/server/start'); -(globalThis as any).vscodeServerStartTime = performance.now(); +(globalThis as { vscodeServerStartTime?: number }).vscodeServerStartTime = performance.now(); // Do a quick parse to determine if a server or the cli needs to be started const parsedArgs = minimist(process.argv.slice(2), { @@ -105,7 +105,7 @@ if (shouldSpawnCli) { perf.mark('code/server/firstWebSocket'); } const remoteExtensionHostAgentServer = await getRemoteExtensionHostAgentServer(); - // @ts-ignore + // @ts-expect-error return remoteExtensionHostAgentServer.handleUpgrade(req, socket); }); server.on('error', async (err) => { @@ -144,7 +144,7 @@ if (shouldSpawnCli) { console.log(output); perf.mark('code/server/started'); - (globalThis as any).vscodeServerListenTime = performance.now(); + (globalThis as { vscodeServerListenTime?: number }).vscodeServerListenTime = performance.now(); await getRemoteExtensionHostAgentServer(); }); @@ -158,7 +158,7 @@ if (shouldSpawnCli) { }); } -function sanitizeStringArg(val: any): string | undefined { +function sanitizeStringArg(val: unknown): string | undefined { if (Array.isArray(val)) { // if an argument is passed multiple times, minimist creates an array val = val.pop(); // take the last item } diff --git a/code/src/tsconfig.base.json b/code/src/tsconfig.base.json index 732da287a10..b1c66907abf 100644 --- a/code/src/tsconfig.base.json +++ b/code/src/tsconfig.base.json @@ -20,7 +20,6 @@ "DOM", "DOM.Iterable", "WebWorker.ImportScripts" - ], - "allowSyntheticDefaultImports": true + ] } } diff --git a/code/src/tsconfig.monaco.json b/code/src/tsconfig.monaco.json index 95952c1c2ee..6293f59ba2b 100644 --- a/code/src/tsconfig.monaco.json +++ b/code/src/tsconfig.monaco.json @@ -7,14 +7,14 @@ "trusted-types", "wicg-file-system-access" ], - "paths": {}, - "module": "amd", - "moduleResolution": "node", + "module": "nodenext", + "moduleResolution": "nodenext", "removeComments": false, "preserveConstEnums": true, "target": "ES2022", "sourceMap": false, - "declaration": true + "declaration": true, + "skipLibCheck": true }, "include": [ "typings/css.d.ts", diff --git a/code/src/tsec.exemptions.json b/code/src/tsec.exemptions.json index f913df5e7da..83691e2de5a 100644 --- a/code/src/tsec.exemptions.json +++ b/code/src/tsec.exemptions.json @@ -18,7 +18,7 @@ "vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts" ], "ban-worker-calls": [ - "vs/base/browser/webWorkerFactory.ts", + "vs/platform/webWorker/browser/webWorkerServiceImpl.ts", "vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts" ], "ban-worker-importscripts": [ diff --git a/code/src/typings/vscode-globals-product.d.ts b/code/src/typings/vscode-globals-product.d.ts index 2cd632e77a0..ab169bd82d0 100644 --- a/code/src/typings/vscode-globals-product.d.ts +++ b/code/src/typings/vscode-globals-product.d.ts @@ -27,6 +27,20 @@ declare global { */ var _VSCODE_PACKAGE_JSON: Record; + /** + * Used to disable CSS import map loading during development. Needed + * when a bundler is used that loads the css directly. + * @deprecated Avoid using this variable. + */ + var _VSCODE_DISABLE_CSS_IMPORT_MAP: boolean | undefined; + + /** + * If this variable is set, and the source code references another module + * via import, the (relative) module should be referenced (instead of the + * JS module in the out folder). + * @deprecated Avoid using this variable. + */ + var _VSCODE_USE_RELATIVE_IMPORTS: boolean | undefined; } // fake export to make global work diff --git a/code/src/typings/vscode-globals-ttp.d.ts b/code/src/typings/vscode-globals-ttp.d.ts index b91080ec741..b79cf938c68 100644 --- a/code/src/typings/vscode-globals-ttp.d.ts +++ b/code/src/typings/vscode-globals-ttp.d.ts @@ -7,10 +7,9 @@ declare global { - var _VSCODE_WEB_PACKAGE_TTP: Pick, 'name' | 'createScriptURL'> | undefined; + var _VSCODE_WEB_PACKAGE_TTP: Pick | undefined; } // fake export to make global work -export { } +export { }; + diff --git a/code/src/vs/amdX.ts b/code/src/vs/amdX.ts index b2f9fd3fa91..374d4f19faf 100644 --- a/code/src/vs/amdX.ts +++ b/code/src/vs/amdX.ts @@ -38,9 +38,7 @@ class AMDModuleImporter { private readonly _defineCalls: DefineCall[] = []; private _state = AMDModuleImporterState.Uninitialized; - private _amdPolicy: Pick, 'name' | 'createScriptURL'> | undefined; + private _amdPolicy: Pick | undefined; constructor() { } diff --git a/code/src/vs/base/browser/browser.ts b/code/src/vs/base/browser/browser.ts index cc3651edc55..b6e9ec09fff 100644 --- a/code/src/vs/base/browser/browser.ts +++ b/code/src/vs/base/browser/browser.ts @@ -131,11 +131,32 @@ export function isStandalone(): boolean { // e.g. visible is true even in fullscreen mode where the controls are hidden // See docs at https://developer.mozilla.org/en-US/docs/Web/API/WindowControlsOverlay/visible export function isWCOEnabled(): boolean { - return (navigator as any)?.windowControlsOverlay?.visible; + return !!(navigator as Navigator & { windowControlsOverlay?: { visible: boolean } })?.windowControlsOverlay?.visible; } // Returns the bounding rect of the titlebar area if it is supported and defined // See docs at https://developer.mozilla.org/en-US/docs/Web/API/WindowControlsOverlay/getTitlebarAreaRect export function getWCOTitlebarAreaRect(targetWindow: Window): DOMRect | undefined { - return (targetWindow.navigator as any)?.windowControlsOverlay?.getTitlebarAreaRect(); + return (targetWindow.navigator as Navigator & { windowControlsOverlay?: { getTitlebarAreaRect: () => DOMRect } })?.windowControlsOverlay?.getTitlebarAreaRect(); +} + +export interface IMonacoEnvironment { + + createTrustedTypesPolicy?( + policyName: string, + policyOptions?: Options, + ): undefined | Pick>; + + getWorker?(moduleId: string, label: string): Worker | Promise; + + getWorkerUrl?(moduleId: string, label: string): string; + + globalAPI?: boolean; + +} +interface IGlobalWithMonacoEnvironment { + MonacoEnvironment?: IMonacoEnvironment; +} +export function getMonacoEnvironment(): IMonacoEnvironment | undefined { + return (globalThis as IGlobalWithMonacoEnvironment).MonacoEnvironment; } diff --git a/code/src/vs/base/browser/canIUse.ts b/code/src/vs/base/browser/canIUse.ts index 895d3eb28a7..d7c129abb27 100644 --- a/code/src/vs/base/browser/canIUse.ts +++ b/code/src/vs/base/browser/canIUse.ts @@ -33,7 +33,7 @@ export const BrowserFeatures = { return KeyboardSupport.Always; } - if ((navigator).keyboard || browser.isSafari) { + if ((navigator as Navigator & { keyboard?: unknown }).keyboard || browser.isSafari) { return KeyboardSupport.FullScreen; } diff --git a/code/src/vs/base/browser/deviceAccess.ts b/code/src/vs/base/browser/deviceAccess.ts index 17cb1beb028..793825e98fe 100644 --- a/code/src/vs/base/browser/deviceAccess.ts +++ b/code/src/vs/base/browser/deviceAccess.ts @@ -5,6 +5,27 @@ // https://wicg.github.io/webusb/ +interface UsbDevice { + readonly deviceClass: number; + readonly deviceProtocol: number; + readonly deviceSubclass: number; + readonly deviceVersionMajor: number; + readonly deviceVersionMinor: number; + readonly deviceVersionSubminor: number; + readonly manufacturerName?: string; + readonly productId: number; + readonly productName?: string; + readonly serialNumber?: string; + readonly usbVersionMajor: number; + readonly usbVersionMinor: number; + readonly usbVersionSubminor: number; + readonly vendorId: number; +} + +interface USB { + requestDevice(options: { filters: unknown[] }): Promise; +} + export interface UsbDeviceData { readonly deviceClass: number; readonly deviceProtocol: number; @@ -23,7 +44,7 @@ export interface UsbDeviceData { } export async function requestUsbDevice(options?: { filters?: unknown[] }): Promise { - const usb = (navigator as any).usb; + const usb = (navigator as Navigator & { usb?: USB }).usb; if (!usb) { return undefined; } @@ -53,13 +74,26 @@ export async function requestUsbDevice(options?: { filters?: unknown[] }): Promi // https://wicg.github.io/serial/ +interface SerialPortInfo { + readonly usbVendorId?: number | undefined; + readonly usbProductId?: number | undefined; +} + +interface SerialPort { + getInfo(): SerialPortInfo; +} + +interface Serial { + requestPort(options: { filters: unknown[] }): Promise; +} + export interface SerialPortData { readonly usbVendorId?: number | undefined; readonly usbProductId?: number | undefined; } export async function requestSerialPort(options?: { filters?: unknown[] }): Promise { - const serial = (navigator as any).serial; + const serial = (navigator as Navigator & { serial?: Serial }).serial; if (!serial) { return undefined; } @@ -78,6 +112,18 @@ export async function requestSerialPort(options?: { filters?: unknown[] }): Prom // https://wicg.github.io/webhid/ +interface HidDevice { + readonly opened: boolean; + readonly vendorId: number; + readonly productId: number; + readonly productName: string; + readonly collections: []; +} + +interface HID { + requestDevice(options: { filters: unknown[] }): Promise; +} + export interface HidDeviceData { readonly opened: boolean; readonly vendorId: number; @@ -87,7 +133,7 @@ export interface HidDeviceData { } export async function requestHidDevice(options?: { filters?: unknown[] }): Promise { - const hid = (navigator as any).hid; + const hid = (navigator as Navigator & { hid?: HID }).hid; if (!hid) { return undefined; } diff --git a/code/src/vs/base/browser/dom.ts b/code/src/vs/base/browser/dom.ts index f5948f40879..0e792265805 100644 --- a/code/src/vs/base/browser/dom.ts +++ b/code/src/vs/base/browser/dom.ts @@ -5,7 +5,7 @@ import * as browser from './browser.js'; import { BrowserFeatures } from './canIUse.js'; -import { IKeyboardEvent, StandardKeyboardEvent } from './keyboardEvent.js'; +import { hasModifierKeys, IKeyboardEvent, StandardKeyboardEvent } from './keyboardEvent.js'; import { IMouseEvent, StandardMouseEvent } from './mouseEvent.js'; import { AbstractIdleValue, IntervalTimer, TimeoutTimer, _runWhenIdle, IdleDeadline } from '../common/async.js'; import { BugIndicatingError, onUnexpectedError } from '../common/errors.js'; @@ -18,7 +18,7 @@ import { URI } from '../common/uri.js'; import { hash } from '../common/hash.js'; import { CodeWindow, ensureCodeWindow, mainWindow } from './window.js'; import { isPointWithinTriangle } from '../common/numbers.js'; -import { IObservable, derived, derivedOpts, IReader, observableValue } from '../common/observable.js'; +import { IObservable, derived, derivedOpts, IReader, observableValue, isObservable } from '../common/observable.js'; export interface IRegisteredCodeWindow { readonly window: CodeWindow; @@ -166,15 +166,15 @@ export function addDisposableListener(node: EventTarget, type: string, handler: } export interface IAddStandardDisposableListenerSignature { - (node: HTMLElement, type: 'click', handler: (event: IMouseEvent) => void, useCapture?: boolean): IDisposable; - (node: HTMLElement, type: 'mousedown', handler: (event: IMouseEvent) => void, useCapture?: boolean): IDisposable; - (node: HTMLElement, type: 'keydown', handler: (event: IKeyboardEvent) => void, useCapture?: boolean): IDisposable; - (node: HTMLElement, type: 'keypress', handler: (event: IKeyboardEvent) => void, useCapture?: boolean): IDisposable; - (node: HTMLElement, type: 'keyup', handler: (event: IKeyboardEvent) => void, useCapture?: boolean): IDisposable; - (node: HTMLElement, type: 'pointerdown', handler: (event: PointerEvent) => void, useCapture?: boolean): IDisposable; - (node: HTMLElement, type: 'pointermove', handler: (event: PointerEvent) => void, useCapture?: boolean): IDisposable; - (node: HTMLElement, type: 'pointerup', handler: (event: PointerEvent) => void, useCapture?: boolean): IDisposable; - (node: HTMLElement, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable; + (node: HTMLElement | Element | Document, type: 'click', handler: (event: IMouseEvent) => void, useCapture?: boolean): IDisposable; + (node: HTMLElement | Element | Document, type: 'mousedown', handler: (event: IMouseEvent) => void, useCapture?: boolean): IDisposable; + (node: HTMLElement | Element | Document, type: 'keydown', handler: (event: IKeyboardEvent) => void, useCapture?: boolean): IDisposable; + (node: HTMLElement | Element | Document, type: 'keypress', handler: (event: IKeyboardEvent) => void, useCapture?: boolean): IDisposable; + (node: HTMLElement | Element | Document, type: 'keyup', handler: (event: IKeyboardEvent) => void, useCapture?: boolean): IDisposable; + (node: HTMLElement | Element | Document, type: 'pointerdown', handler: (event: PointerEvent) => void, useCapture?: boolean): IDisposable; + (node: HTMLElement | Element | Document, type: 'pointermove', handler: (event: PointerEvent) => void, useCapture?: boolean): IDisposable; + (node: HTMLElement | Element | Document, type: 'pointerup', handler: (event: PointerEvent) => void, useCapture?: boolean): IDisposable; + (node: HTMLElement | Element | Document, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable; } function _wrapAsStandardMouseEvent(targetWindow: Window, handler: (e: IMouseEvent) => void): (e: MouseEvent) => void { return function (e: MouseEvent) { @@ -186,7 +186,7 @@ function _wrapAsStandardKeyboardEvent(handler: (e: IKeyboardEvent) => void): (e: return handler(new StandardKeyboardEvent(e)); }; } -export const addStandardDisposableListener: IAddStandardDisposableListenerSignature = function addStandardDisposableListener(node: HTMLElement, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable { +export const addStandardDisposableListener: IAddStandardDisposableListenerSignature = function addStandardDisposableListener(node: HTMLElement | Element | Document, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable { let wrapHandler = handler; if (type === 'click' || type === 'mousedown' || type === 'contextmenu') { @@ -421,13 +421,13 @@ export interface IEventMerger { } const MINIMUM_TIME_MS = 8; -const DEFAULT_EVENT_MERGER: IEventMerger = function (lastEvent: Event | null, currentEvent: Event) { +function DEFAULT_EVENT_MERGER(_lastEvent: unknown, currentEvent: T) { return currentEvent; -}; +} class TimeoutThrottledDomListener extends Disposable { - constructor(node: any, type: string, handler: (event: R) => void, eventMerger: IEventMerger = DEFAULT_EVENT_MERGER, minimumTimeMs: number = MINIMUM_TIME_MS) { + constructor(node: Node, type: string, handler: (event: R) => void, eventMerger: IEventMerger = DEFAULT_EVENT_MERGER as IEventMerger, minimumTimeMs: number = MINIMUM_TIME_MS) { super(); let lastEvent: R | null = null; @@ -715,6 +715,7 @@ export function getDomNodeZoomLevel(domNode: HTMLElement): number { let testElement: HTMLElement | null = domNode; let zoom = 1.0; do { + // eslint-disable-next-line local/code-no-any-casts const elementZoomLevel = (getComputedStyle(testElement) as any).zoom; if (elementZoomLevel !== null && elementZoomLevel !== undefined && elementZoomLevel !== '1') { zoom *= elementZoomLevel; @@ -798,6 +799,7 @@ export function setParentFlowTo(fromChildElement: HTMLElement, toParentElement: function getParentFlowToElement(node: HTMLElement): HTMLElement | null { const flowToParentId = node.dataset[parentFlowToDataKey]; if (typeof flowToParentId === 'string') { + // eslint-disable-next-line no-restricted-syntax return node.ownerDocument.getElementById(flowToParentId); } return null; @@ -994,14 +996,14 @@ export const sharedMutationObserver = new class { }; export function createMetaElement(container: HTMLElement = mainWindow.document.head): HTMLMetaElement { - return createHeadElement('meta', container) as HTMLMetaElement; + return createHeadElement('meta', container); } export function createLinkElement(container: HTMLElement = mainWindow.document.head): HTMLLinkElement { - return createHeadElement('link', container) as HTMLLinkElement; + return createHeadElement('link', container); } -function createHeadElement(tagName: string, container: HTMLElement = mainWindow.document.head): HTMLElement { +function createHeadElement(tagName: K, container: HTMLElement = mainWindow.document.head): HTMLElementTagNameMap[K] { const element = document.createElement(tagName); container.appendChild(element); return element; @@ -1266,7 +1268,7 @@ export function append(parent: HTMLElement, ...children: (T | st export function append(parent: HTMLElement, ...children: (T | string)[]): T | void { parent.append(...children); if (children.length === 1 && typeof children[0] !== 'string') { - return children[0]; + return children[0]; } } @@ -1320,6 +1322,7 @@ function _$(namespace: Namespace, description: string, attrs? } if (/^on\w+$/.test(name)) { + // eslint-disable-next-line local/code-no-any-casts (result)[name] = value; } else if (name === 'selected') { if (value) { @@ -1334,7 +1337,7 @@ function _$(namespace: Namespace, description: string, attrs? result.append(...children); - return result as T; + return result; } export function $(description: string, attrs?: { [key: string]: any }, ...children: Array): T { @@ -1514,6 +1517,7 @@ export function windowOpenWithSuccess(url: string, noOpener = true): boolean { if (newTab) { if (noOpener) { // see `windowOpenNoOpener` for details on why this is important + // eslint-disable-next-line local/code-no-any-casts (newTab as any).opener = null; } newTab.location.href = url; @@ -1592,6 +1596,10 @@ export interface INotification extends IDisposable { readonly onClick: event.Event; } +function sanitizeNotificationText(text: string): string { + return text.replace(/`/g, '\''); // convert backticks to single quotes +} + export async function triggerNotification(message: string, options?: { detail?: string; sticky?: boolean }): Promise { const permission = await Notification.requestPermission(); if (permission !== 'granted') { @@ -1600,9 +1608,9 @@ export async function triggerNotification(message: string, options?: { detail?: const disposables = new DisposableStore(); - const notification = new Notification(message, { - body: options?.detail, - requireInteraction: options?.sticky + const notification = new Notification(sanitizeNotificationText(message), { + body: options?.detail ? sanitizeNotificationText(options.detail) : undefined, + requireInteraction: options?.sticky, }); const onClick = new event.Emitter(); @@ -1649,6 +1657,7 @@ export interface IDetectedFullscreen { export function detectFullscreen(targetWindow: Window): IDetectedFullscreen | null { // Browser fullscreen: use DOM APIs to detect + // eslint-disable-next-line local/code-no-any-casts if (targetWindow.document.fullscreenElement || (targetWindow.document).webkitFullscreenElement || (targetWindow.document).webkitIsFullScreen) { return { mode: DetectedFullscreenMode.DOCUMENT, guess: false }; } @@ -1805,7 +1814,7 @@ export class ModifierKeyEmitter extends event.Emitter { } get isModifierPressed(): boolean { - return this._keyStatus.altKey || this._keyStatus.ctrlKey || this._keyStatus.metaKey || this._keyStatus.shiftKey; + return hasModifierKeys(this._keyStatus); } /** @@ -2001,6 +2010,7 @@ export function h(tag: string, ...args: [] | [attributes: { $: string } & Partia attributes = {}; children = args[0]; } else { + // eslint-disable-next-line local/code-no-any-casts attributes = args[0] as any || {}; children = args[1]; } @@ -2103,6 +2113,7 @@ export function svgElem(tag: string, ...args: [] | [attributes: { $: string } & attributes = {}; children = args[0]; } else { + // eslint-disable-next-line local/code-no-any-casts attributes = args[0] as any || {}; children = args[1]; } @@ -2114,6 +2125,7 @@ export function svgElem(tag: string, ...args: [] | [attributes: { $: string } & } const tagName = match.groups['tag'] || 'div'; + // eslint-disable-next-line local/code-no-any-casts const el = document.createElementNS('http://www.w3.org/2000/svg', tagName) as any as HTMLElement; if (match.groups['id']) { @@ -2276,11 +2288,13 @@ export namespace n { const obsRef = attributes.obsRef; delete attributes.obsRef; + // eslint-disable-next-line local/code-no-any-casts return new ObserverNodeWithElement(tag as any, ref, obsRef, elementNs, className, attributes, children); }; } function node, TKey extends keyof TMap>(tag: TKey, elementNs: string | undefined = undefined): DomCreateFn { + // eslint-disable-next-line local/code-no-any-casts const f = nodeNs(elementNs) as any; return (attributes, children) => { return f(tag, attributes, children); @@ -2308,6 +2322,7 @@ export namespace n { return value; } }); + // eslint-disable-next-line local/code-no-any-casts return result as any; } } @@ -2419,12 +2434,14 @@ export abstract class ObserverNode { /** @description set.tabIndex */ + // eslint-disable-next-line local/code-no-any-casts this._element.tabIndex = value.read(reader) as any; })); } else { this._element.tabIndex = value; } } else if (key.startsWith('on')) { + // eslint-disable-next-line local/code-no-any-casts (this._element as any)[key] = value; } else { if (isObservable(value)) { @@ -2490,7 +2507,43 @@ export abstract class ObserverNode | undefined = undefined; + + get isHovered(): IObservable { + if (!this._isHovered) { + const hovered = observableValue('hovered', false); + this._element.addEventListener('mouseenter', (_e) => hovered.set(true, undefined)); + this._element.addEventListener('mouseleave', (_e) => hovered.set(false, undefined)); + this._isHovered = hovered; + } + return this._isHovered; + } + + private _didMouseMoveDuringHover: IObservable | undefined = undefined; + + get didMouseMoveDuringHover(): IObservable { + if (!this._didMouseMoveDuringHover) { + let _hovering = false; + const hovered = observableValue('didMouseMoveDuringHover', false); + this._element.addEventListener('mouseenter', (_e) => { + _hovering = true; + }); + this._element.addEventListener('mousemove', (_e) => { + if (_hovering) { + hovered.set(true, undefined); + } + }); + this._element.addEventListener('mouseleave', (_e) => { + _hovering = false; + hovered.set(false, undefined); + }); + this._didMouseMoveDuringHover = hovered; + } + return this._didMouseMoveDuringHover; + } } + function setClassName(domNode: HTMLOrSVGElement, className: string) { if (isSVGElement(domNode)) { domNode.setAttribute('class', className); @@ -2498,6 +2551,7 @@ function setClassName(domNode: HTMLOrSVGElement, className: string) { domNode.className = className; } } + function resolve(value: ValueOrList, reader: IReader | undefined, cb: (val: T) => void): void { if (isObservable(value)) { cb(value.read(reader)); @@ -2509,6 +2563,7 @@ function resolve(value: ValueOrList, reader: IReader | undefined, cb: (val } return; } + // eslint-disable-next-line local/code-no-any-casts cb(value as any); } function getClassName(className: ValueOrList | undefined, reader: IReader | undefined): string { @@ -2564,41 +2619,6 @@ export class ObserverNodeWithElement | undefined = undefined; - - get isHovered(): IObservable { - if (!this._isHovered) { - const hovered = observableValue('hovered', false); - this._element.addEventListener('mouseenter', (_e) => hovered.set(true, undefined)); - this._element.addEventListener('mouseleave', (_e) => hovered.set(false, undefined)); - this._isHovered = hovered; - } - return this._isHovered; - } - - private _didMouseMoveDuringHover: IObservable | undefined = undefined; - - get didMouseMoveDuringHover(): IObservable { - if (!this._didMouseMoveDuringHover) { - let _hovering = false; - const hovered = observableValue('didMouseMoveDuringHover', false); - this._element.addEventListener('mouseenter', (_e) => { - _hovering = true; - }); - this._element.addEventListener('mousemove', (_e) => { - if (_hovering) { - hovered.set(true, undefined); - } - }); - this._element.addEventListener('mouseleave', (_e) => { - _hovering = false; - hovered.set(false, undefined); - }); - this._didMouseMoveDuringHover = hovered; - } - return this._didMouseMoveDuringHover; - } } function setOrRemoveAttribute(element: HTMLOrSVGElement, key: string, value: unknown) { if (value === null || value === undefined) { @@ -2608,9 +2628,6 @@ function setOrRemoveAttribute(element: HTMLOrSVGElement, key: string, value: unk } } -function isObservable(obj: unknown): obj is IObservable { - return !!obj && (>obj).read !== undefined && (>obj).reportChanges !== undefined; -} type ElementAttributeKeys = Partial<{ [K in keyof T]: T[K] extends Function ? never : T[K] extends object ? ElementAttributeKeys : Value; }>; diff --git a/code/src/vs/base/browser/domSanitize.ts b/code/src/vs/base/browser/domSanitize.ts index 22941738e8b..942bf6e8315 100644 --- a/code/src/vs/base/browser/domSanitize.ts +++ b/code/src/vs/base/browser/domSanitize.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DisposableStore, IDisposable, toDisposable } from '../common/lifecycle.js'; import { Schemas } from '../common/network.js'; -import dompurify from './dompurify/dompurify.js'; - +import { reset } from './dom.js'; +// eslint-disable-next-line no-restricted-imports +import dompurify, * as DomPurifyTypes from './dompurify/dompurify.js'; /** * List of safe, non-input html tags. @@ -54,6 +54,7 @@ export const basicMarkupHtmlTags = Object.freeze([ 'rp', 'rt', 'ruby', + 's', 'samp', 'small', 'small', @@ -102,46 +103,52 @@ export const defaultAllowedAttrs = Object.freeze([ ]); -type UponSanitizeElementCb = (currentNode: Element, data: dompurify.SanitizeElementHookEvent, config: dompurify.Config) => void; -type UponSanitizeAttributeCb = (currentNode: Element, data: dompurify.SanitizeAttributeHookEvent, config: dompurify.Config) => void; +const fakeRelativeUrlProtocol = 'vscode-relative-path'; -function addDompurifyHook(hook: 'uponSanitizeElement', cb: UponSanitizeElementCb): IDisposable; -function addDompurifyHook(hook: 'uponSanitizeAttribute', cb: UponSanitizeAttributeCb): IDisposable; -function addDompurifyHook(hook: 'uponSanitizeElement' | 'uponSanitizeAttribute', cb: any): IDisposable { - dompurify.addHook(hook, cb); - return toDisposable(() => dompurify.removeHook(hook)); +interface AllowedLinksConfig { + readonly override: readonly string[] | '*'; + readonly allowRelativePaths: boolean; } -/** - * Hooks dompurify using `afterSanitizeAttributes` to check that all `href` and `src` - * attributes are valid. - */ -function hookDomPurifyHrefAndSrcSanitizer(allowedLinkProtocols: readonly string[] | '*', allowedMediaProtocols: readonly string[] | '*'): IDisposable { - // https://github.com/cure53/DOMPurify/blob/main/demos/hooks-scheme-allowlist.html - // build an anchor to map URLs to - const anchor = document.createElement('a'); - - function validateLink(value: string, allowedProtocols: readonly string[] | '*'): boolean { - if (allowedProtocols === '*') { - return true; // allow all protocols +function validateLink(value: string, allowedProtocols: AllowedLinksConfig): boolean { + if (allowedProtocols.override === '*') { + return true; // allow all protocols + } + + try { + const url = new URL(value, fakeRelativeUrlProtocol + '://'); + if (allowedProtocols.override.includes(url.protocol.replace(/:$/, ''))) { + return true; + } + + if (allowedProtocols.allowRelativePaths + && url.protocol === fakeRelativeUrlProtocol + ':' + && !value.trim().toLowerCase().startsWith(fakeRelativeUrlProtocol) + ) { + return true; } - anchor.href = value; - return allowedProtocols.includes(anchor.protocol.replace(/:$/, '')); + return false; + } catch (e) { + return false; } +} +/** + * Hooks dompurify using `afterSanitizeAttributes` to check that all `href` and `src` + * attributes are valid. + */ +function hookDomPurifyHrefAndSrcSanitizer(allowedLinkProtocols: AllowedLinksConfig, allowedMediaProtocols: AllowedLinksConfig) { dompurify.addHook('afterSanitizeAttributes', (node) => { // check all href/src attributes for validity for (const attr of ['href', 'src']) { if (node.hasAttribute(attr)) { const attrValue = node.getAttribute(attr) as string; if (attr === 'href') { - if (!attrValue.startsWith('#') && !validateLink(attrValue, allowedLinkProtocols)) { node.removeAttribute(attr); } - - } else {// 'src' + } else { // 'src' if (!validateLink(attrValue, allowedMediaProtocols)) { node.removeAttribute(attr); } @@ -149,8 +156,6 @@ function hookDomPurifyHrefAndSrcSanitizer(allowedLinkProtocols: readonly string[ } } }); - - return toDisposable(() => dompurify.removeHook('afterSanitizeAttributes')); } /** @@ -165,6 +170,7 @@ export interface SanitizeAttributeRule { shouldKeep: SanitizeAttributePredicate; } + export interface DomSanitizerConfig { /** * Configured the allowed html tags. @@ -189,6 +195,11 @@ export interface DomSanitizerConfig { readonly override?: readonly string[] | '*'; }; + /** + * If set, allows relative paths for links. + */ + readonly allowRelativeLinkPaths?: boolean; + /** * List of allowed protocols for `src` attributes. */ @@ -196,6 +207,11 @@ export interface DomSanitizerConfig { readonly override?: readonly string[] | '*'; }; + /** + * If set, allows relative paths for media (images, videos, etc). + */ + readonly allowRelativeMediaPaths?: boolean; + /** * If set, replaces unsupported tags with their plaintext representation instead of removing them. * @@ -207,12 +223,9 @@ export interface DomSanitizerConfig { const defaultDomPurifyConfig = Object.freeze({ ALLOWED_TAGS: [...basicMarkupHtmlTags], ALLOWED_ATTR: [...defaultAllowedAttrs], - RETURN_DOM: false, - RETURN_DOM_FRAGMENT: false, - RETURN_TRUSTED_TYPE: true, // We sanitize the src/href attributes later if needed ALLOW_UNKNOWN_PROTOCOLS: true, -} satisfies dompurify.Config); +} satisfies DomPurifyTypes.Config); /** * Sanitizes an html string. @@ -223,9 +236,14 @@ const defaultDomPurifyConfig = Object.freeze({ * @returns A sanitized string of html. */ export function sanitizeHtml(untrusted: string, config?: DomSanitizerConfig): TrustedHTML { - const store = new DisposableStore(); + return doSanitizeHtml(untrusted, config, 'trusted'); +} + +function doSanitizeHtml(untrusted: string, config: DomSanitizerConfig | undefined, outputType: 'dom'): DocumentFragment; +function doSanitizeHtml(untrusted: string, config: DomSanitizerConfig | undefined, outputType: 'trusted'): TrustedHTML; +function doSanitizeHtml(untrusted: string, config: DomSanitizerConfig | undefined, outputType: 'dom' | 'trusted'): TrustedHTML | DocumentFragment { try { - const resolvedConfig: dompurify.Config = { ...defaultDomPurifyConfig }; + const resolvedConfig: DomPurifyTypes.Config = { ...defaultDomPurifyConfig }; if (config?.allowedTags) { if (config.allowedTags.override) { @@ -272,16 +290,22 @@ export function sanitizeHtml(untrusted: string, config?: DomSanitizerConfig): Tr resolvedConfig.ALLOWED_ATTR = Array.from(allowedAttrNames); - store.add(hookDomPurifyHrefAndSrcSanitizer( - config?.allowedLinkProtocols?.override ?? [Schemas.http, Schemas.https], - config?.allowedMediaProtocols?.override ?? [Schemas.http, Schemas.https])); + hookDomPurifyHrefAndSrcSanitizer( + { + override: config?.allowedLinkProtocols?.override ?? [Schemas.http, Schemas.https], + allowRelativePaths: config?.allowRelativeLinkPaths ?? false + }, + { + override: config?.allowedMediaProtocols?.override ?? [Schemas.http, Schemas.https], + allowRelativePaths: config?.allowRelativeMediaPaths ?? false + }); if (config?.replaceWithPlaintext) { - store.add(addDompurifyHook('uponSanitizeElement', replaceWithPlainTextHook)); + dompurify.addHook('uponSanitizeElement', replaceWithPlainTextHook); } if (allowedAttrPredicates.size) { - store.add(addDompurifyHook('uponSanitizeAttribute', (node, e) => { + dompurify.addHook('uponSanitizeAttribute', (node, e) => { const predicate = allowedAttrPredicates.get(e.attrName); if (predicate) { const result = predicate.shouldKeep(node, e); @@ -294,44 +318,57 @@ export function sanitizeHtml(untrusted: string, config?: DomSanitizerConfig): Tr } else { e.keepAttr = allowedAttrNames.has(e.attrName); } - })); + }); } - return dompurify.sanitize(untrusted, { - ...resolvedConfig, - RETURN_TRUSTED_TYPE: true - }); + if (outputType === 'dom') { + return dompurify.sanitize(untrusted, { + ...resolvedConfig, + RETURN_DOM_FRAGMENT: true + }); + } else { + return dompurify.sanitize(untrusted, { + ...resolvedConfig, + RETURN_TRUSTED_TYPE: true + }) as unknown as TrustedHTML; // Cast from lib TrustedHTML to global TrustedHTML + } } finally { - store.dispose(); + dompurify.removeAllHooks(); } } const selfClosingTags = ['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr']; -function replaceWithPlainTextHook(element: Element, data: dompurify.SanitizeElementHookEvent, _config: dompurify.Config) { +const replaceWithPlainTextHook: DomPurifyTypes.UponSanitizeElementHook = (node, data, _config) => { if (!data.allowedTags[data.tagName] && data.tagName !== 'body') { - const replacement = convertTagToPlaintext(element); - if (element.nodeType === Node.COMMENT_NODE) { - // Workaround for https://github.com/cure53/DOMPurify/issues/1005 - // The comment will be deleted in the next phase. However if we try to remove it now, it will cause - // an exception. Instead we insert the text node before the comment. - element.parentElement?.insertBefore(replacement, element); - } else { - element.parentElement?.replaceChild(replacement, element); + const replacement = convertTagToPlaintext(node); + if (replacement) { + if (node.nodeType === Node.COMMENT_NODE) { + // Workaround for https://github.com/cure53/DOMPurify/issues/1005 + // The comment will be deleted in the next phase. However if we try to remove it now, it will cause + // an exception. Instead we insert the text node before the comment. + node.parentElement?.insertBefore(replacement, node); + } else { + node.parentElement?.replaceChild(replacement, node); + } } } -} +}; + +export function convertTagToPlaintext(node: Node): DocumentFragment | undefined { + if (!node.ownerDocument) { + return; + } -export function convertTagToPlaintext(element: Element): DocumentFragment { let startTagText: string; let endTagText: string | undefined; - if (element.nodeType === Node.COMMENT_NODE) { - startTagText = ``; - } else { - const tagName = element.tagName.toLowerCase(); + if (node.nodeType === Node.COMMENT_NODE) { + startTagText = ``; + } else if (node instanceof Element) { + const tagName = node.tagName.toLowerCase(); const isSelfClosing = selfClosingTags.includes(tagName); - const attrString = element.attributes.length ? - ' ' + Array.from(element.attributes) + const attrString = node.attributes.length ? + ' ' + Array.from(node.attributes) .map(attr => `${attr.name}="${attr.value}"`) .join(' ') : ''; @@ -339,16 +376,18 @@ export function convertTagToPlaintext(element: Element): DocumentFragment { if (!isSelfClosing) { endTagText = ``; } + } else { + return; } const fragment = document.createDocumentFragment(); - const textNode = element.ownerDocument.createTextNode(startTagText); + const textNode = node.ownerDocument.createTextNode(startTagText); fragment.appendChild(textNode); - while (element.firstChild) { - fragment.appendChild(element.firstChild); + while (node.firstChild) { + fragment.appendChild(node.firstChild); } - const endTagTextNode = endTagText ? element.ownerDocument.createTextNode(endTagText) : undefined; + const endTagTextNode = endTagText ? node.ownerDocument.createTextNode(endTagText) : undefined; if (endTagTextNode) { fragment.appendChild(endTagTextNode); } @@ -360,5 +399,6 @@ export function convertTagToPlaintext(element: Element): DocumentFragment { * Sanitizes the given `value` and reset the given `node` with it. */ export function safeSetInnerHtml(node: HTMLElement, untrusted: string, config?: DomSanitizerConfig): void { - node.innerHTML = sanitizeHtml(untrusted, config) as any; + const fragment = doSanitizeHtml(untrusted, config, 'dom'); + reset(node, fragment); } diff --git a/code/src/vs/base/browser/domStylesheets.ts b/code/src/vs/base/browser/domStylesheets.ts index 13ddb7b545a..1e34173680e 100644 --- a/code/src/vs/base/browser/domStylesheets.ts +++ b/code/src/vs/base/browser/domStylesheets.ts @@ -5,6 +5,7 @@ import { DisposableStore, toDisposable, IDisposable } from '../common/lifecycle.js'; import { autorun, IObservable } from '../common/observable.js'; +import { isFirefox } from './browser.js'; import { getWindows, sharedMutationObserver } from './dom.js'; import { mainWindow } from './window.js'; @@ -62,6 +63,9 @@ export function createStyleSheet(container: HTMLElement = mainWindow.document.he if (container === mainWindow.document.head) { const globalStylesheetClones = new Set(); globalStylesheets.set(style, globalStylesheetClones); + if (disposableStore) { + disposableStore.add(toDisposable(() => globalStylesheets.delete(style))); + } for (const { window: targetWindow, disposables } of getWindows()) { if (targetWindow === mainWindow) { @@ -97,7 +101,7 @@ function cloneGlobalStyleSheet(globalStylesheet: HTMLStyleElement, globalStylesh clone.sheet?.insertRule(rule.cssText, clone.sheet?.cssRules.length); } - disposables.add(sharedMutationObserver.observe(globalStylesheet, disposables, { childList: true })(() => { + disposables.add(sharedMutationObserver.observe(globalStylesheet, disposables, { childList: true, subtree: isFirefox, characterData: isFirefox })(() => { clone.textContent = globalStylesheet.textContent; })); diff --git a/code/src/vs/base/browser/dompurify/cgmanifest.json b/code/src/vs/base/browser/dompurify/cgmanifest.json index 0038d843f74..21da5642470 100644 --- a/code/src/vs/base/browser/dompurify/cgmanifest.json +++ b/code/src/vs/base/browser/dompurify/cgmanifest.json @@ -6,11 +6,12 @@ "git": { "name": "dompurify", "repositoryUrl": "https://github.com/cure53/DOMPurify", - "commitHash": "15f54ed66d99a824d8d3030f711ff82af68ad191" + "commitHash": "eaa0bdb26a1d0164af587d9059b98269008faece", + "tag": "3.2.7" } }, "license": "Apache 2.0", - "version": "3.1.7" + "version": "3.2.7" } ], "version": 1 diff --git a/code/src/vs/base/browser/dompurify/dompurify.d.ts b/code/src/vs/base/browser/dompurify/dompurify.d.ts index 2c5b6396d8a..f6a8b19745d 100644 --- a/code/src/vs/base/browser/dompurify/dompurify.d.ts +++ b/code/src/vs/base/browser/dompurify/dompurify.d.ts @@ -1,146 +1,439 @@ -// Type definitions for DOM Purify 3.0 -// Project: https://github.com/cure53/DOMPurify -// Definitions by: Dave Taylor https://github.com/davetayls -// Samira Bazuzi -// FlowCrypt -// Exigerr -// Piotr Błażejewicz -// Nicholas Ellul -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped -// Minimum TypeScript Version: 4.5 +/*! @license DOMPurify 3.2.7 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.7/LICENSE */ -export default DOMPurify; +import type { TrustedTypePolicy, TrustedHTML, TrustedTypesWindow } from 'trusted-types/lib/index.d.ts'; -declare const DOMPurify: createDOMPurifyI; - -type WindowLike = Pick< - typeof globalThis, - | 'NodeFilter' - | 'Node' - | 'Element' - | 'HTMLTemplateElement' - | 'DocumentFragment' - | 'HTMLFormElement' - | 'DOMParser' - | 'NamedNodeMap' ->; - -interface createDOMPurifyI extends DOMPurify.DOMPurifyI { - (window?: Window | WindowLike): DOMPurify.DOMPurifyI; -} - -declare namespace DOMPurify { - interface DOMPurifyI { - sanitize(source: string | Node): string; - sanitize(source: string | Node, config: Config & { RETURN_TRUSTED_TYPE: true }): TrustedHTML; - sanitize( - source: string | Node, - config: Config & { RETURN_DOM_FRAGMENT?: false | undefined; RETURN_DOM?: false | undefined }, - ): string; - sanitize(source: string | Node, config: Config & { RETURN_DOM_FRAGMENT: true }): DocumentFragment; - sanitize(source: string | Node, config: Config & { RETURN_DOM: true }): HTMLElement; - sanitize(source: string | Node, config: Config): string | HTMLElement | DocumentFragment; - - addHook( - hook: 'uponSanitizeElement', - cb: (currentNode: Element, data: SanitizeElementHookEvent, config: Config) => void, - ): void; - addHook( - hook: 'uponSanitizeAttribute', - cb: (currentNode: Element, data: SanitizeAttributeHookEvent, config: Config) => void, - ): void; - addHook(hook: HookName, cb: (currentNode: Element, data: HookEvent, config: Config) => void): void; - - setConfig(cfg: Config): void; - clearConfig(): void; - isValidAttribute(tag: string, attr: string, value: string): boolean; - - removeHook(entryPoint: HookName): void; - removeHooks(entryPoint: HookName): void; - removeAllHooks(): void; - - version: string; - removed: any[]; - isSupported: boolean; - } - - interface Config { - ADD_ATTR?: string[] | undefined; - ADD_DATA_URI_TAGS?: string[] | undefined; - ADD_TAGS?: string[] | undefined; - ADD_URI_SAFE_ATTR?: string[] | undefined; - ALLOW_ARIA_ATTR?: boolean | undefined; - ALLOW_DATA_ATTR?: boolean | undefined; - ALLOW_UNKNOWN_PROTOCOLS?: boolean | undefined; - ALLOW_SELF_CLOSE_IN_ATTR?: boolean | undefined; - ALLOWED_ATTR?: string[] | undefined; - ALLOWED_TAGS?: string[] | undefined; - ALLOWED_NAMESPACES?: string[] | undefined; - ALLOWED_URI_REGEXP?: RegExp | undefined; - FORBID_ATTR?: string[] | undefined; - FORBID_CONTENTS?: string[] | undefined; - FORBID_TAGS?: string[] | undefined; - FORCE_BODY?: boolean | undefined; - IN_PLACE?: boolean | undefined; - KEEP_CONTENT?: boolean | undefined; +/** + * Configuration to control DOMPurify behavior. + */ +interface Config { + /** + * Extend the existing array of allowed attributes. + */ + ADD_ATTR?: string[] | undefined; + /** + * Extend the existing array of elements that can use Data URIs. + */ + ADD_DATA_URI_TAGS?: string[] | undefined; + /** + * Extend the existing array of allowed tags. + */ + ADD_TAGS?: string[] | undefined; + /** + * Extend the existing array of elements that are safe for URI-like values (be careful, XSS risk). + */ + ADD_URI_SAFE_ATTR?: string[] | undefined; + /** + * Allow ARIA attributes, leave other safe HTML as is (default is true). + */ + ALLOW_ARIA_ATTR?: boolean | undefined; + /** + * Allow HTML5 data attributes, leave other safe HTML as is (default is true). + */ + ALLOW_DATA_ATTR?: boolean | undefined; + /** + * Allow external protocol handlers in URL attributes (default is false, be careful, XSS risk). + * By default only `http`, `https`, `ftp`, `ftps`, `tel`, `mailto`, `callto`, `sms`, `cid` and `xmpp` are allowed. + */ + ALLOW_UNKNOWN_PROTOCOLS?: boolean | undefined; + /** + * Decide if self-closing tags in attributes are allowed. + * Usually removed due to a mXSS issue in jQuery 3.0. + */ + ALLOW_SELF_CLOSE_IN_ATTR?: boolean | undefined; + /** + * Allow only specific attributes. + */ + ALLOWED_ATTR?: string[] | undefined; + /** + * Allow only specific elements. + */ + ALLOWED_TAGS?: string[] | undefined; + /** + * Allow only specific namespaces. Defaults to: + * - `http://www.w3.org/1999/xhtml` + * - `http://www.w3.org/2000/svg` + * - `http://www.w3.org/1998/Math/MathML` + */ + ALLOWED_NAMESPACES?: string[] | undefined; + /** + * Allow specific protocols handlers in URL attributes via regex (be careful, XSS risk). + * Default RegExp: + * ``` + * /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i; + * ``` + */ + ALLOWED_URI_REGEXP?: RegExp | undefined; + /** + * Define how custom elements are handled. + */ + CUSTOM_ELEMENT_HANDLING?: { /** - * change the default namespace from HTML to something different + * Regular expression or function to match to allowed elements. + * Default is null (disallow any custom elements). */ - NAMESPACE?: string | undefined; - PARSER_MEDIA_TYPE?: string | undefined; - RETURN_DOM_FRAGMENT?: boolean | undefined; + tagNameCheck?: RegExp | ((tagName: string) => boolean) | null | undefined; /** - * This defaults to `true` starting DOMPurify 2.2.0. Note that setting it to `false` - * might cause XSS from attacks hidden in closed shadowroots in case the browser - * supports Declarative Shadow: DOM https://web.dev/declarative-shadow-dom/ + * Regular expression or function to match to allowed attributes. + * Default is null (disallow any attributes not on the allow list). */ - RETURN_DOM_IMPORT?: boolean | undefined; - RETURN_DOM?: boolean | undefined; - RETURN_TRUSTED_TYPE?: boolean | undefined; - SAFE_FOR_TEMPLATES?: boolean | undefined; - SANITIZE_DOM?: boolean | undefined; - /** @default false */ - SANITIZE_NAMED_PROPS?: boolean | undefined; - USE_PROFILES?: - | false - | { - mathMl?: boolean | undefined; - svg?: boolean | undefined; - svgFilters?: boolean | undefined; - html?: boolean | undefined; - } - | undefined; - WHOLE_DOCUMENT?: boolean | undefined; - CUSTOM_ELEMENT_HANDLING?: { - tagNameCheck?: RegExp | ((tagName: string) => boolean) | null | undefined; - attributeNameCheck?: RegExp | ((lcName: string) => boolean) | null | undefined; - allowCustomizedBuiltInElements?: boolean | undefined; - }; - } - - type HookName = - | 'beforeSanitizeElements' - | 'uponSanitizeElement' - | 'afterSanitizeElements' - | 'beforeSanitizeAttributes' - | 'uponSanitizeAttribute' - | 'afterSanitizeAttributes' - | 'beforeSanitizeShadowDOM' - | 'uponSanitizeShadowNode' - | 'afterSanitizeShadowDOM'; - - type HookEvent = SanitizeElementHookEvent | SanitizeAttributeHookEvent | null; + attributeNameCheck?: RegExp | ((attributeName: string, tagName?: string) => boolean) | null | undefined; + /** + * Allow custom elements derived from built-ins if they pass `tagNameCheck`. Default is false. + */ + allowCustomizedBuiltInElements?: boolean | undefined; + }; + /** + * Add attributes to block-list. + */ + FORBID_ATTR?: string[] | undefined; + /** + * Add child elements to be removed when their parent is removed. + */ + FORBID_CONTENTS?: string[] | undefined; + /** + * Add elements to block-list. + */ + FORBID_TAGS?: string[] | undefined; + /** + * Glue elements like style, script or others to `document.body` and prevent unintuitive browser behavior in several edge-cases (default is false). + */ + FORCE_BODY?: boolean | undefined; + /** + * Map of non-standard HTML element names to support. Map to true to enable support. For example: + * + * ``` + * HTML_INTEGRATION_POINTS: { foreignobject: true } + * ``` + */ + HTML_INTEGRATION_POINTS?: Record | undefined; + /** + * Sanitize a node "in place", which is much faster depending on how you use DOMPurify. + */ + IN_PLACE?: boolean | undefined; + /** + * Keep an element's content when the element is removed (default is true). + */ + KEEP_CONTENT?: boolean | undefined; + /** + * Map of MathML element names to support. Map to true to enable support. For example: + * + * ``` + * MATHML_TEXT_INTEGRATION_POINTS: { mtext: true } + * ``` + */ + MATHML_TEXT_INTEGRATION_POINTS?: Record | undefined; + /** + * Change the default namespace from HTML to something different. + */ + NAMESPACE?: string | undefined; + /** + * Change the parser type so sanitized data is treated as XML and not as HTML, which is the default. + */ + PARSER_MEDIA_TYPE?: DOMParserSupportedType | undefined; + /** + * Return a DOM `DocumentFragment` instead of an HTML string (default is false). + */ + RETURN_DOM_FRAGMENT?: boolean | undefined; + /** + * Return a DOM `HTMLBodyElement` instead of an HTML string (default is false). + */ + RETURN_DOM?: boolean | undefined; + /** + * Return a TrustedHTML object instead of a string if possible. + */ + RETURN_TRUSTED_TYPE?: boolean | undefined; + /** + * Strip `{{ ... }}`, `${ ... }` and `<% ... %>` to make output safe for template systems. + * Be careful please, this mode is not recommended for production usage. + * Allowing template parsing in user-controlled HTML is not advised at all. + * Only use this mode if there is really no alternative. + */ + SAFE_FOR_TEMPLATES?: boolean | undefined; + /** + * Change how e.g. comments containing risky HTML characters are treated. + * Be very careful, this setting should only be set to `false` if you really only handle + * HTML and nothing else, no SVG, MathML or the like. + * Otherwise, changing from `true` to `false` will lead to XSS in this or some other way. + */ + SAFE_FOR_XML?: boolean | undefined; + /** + * Use DOM Clobbering protection on output (default is true, handle with care, minor XSS risks here). + */ + SANITIZE_DOM?: boolean | undefined; + /** + * Enforce strict DOM Clobbering protection via namespace isolation (default is false). + * When enabled, isolates the namespace of named properties (i.e., `id` and `name` attributes) + * from JS variables by prefixing them with the string `user-content-` + */ + SANITIZE_NAMED_PROPS?: boolean | undefined; + /** + * Supplied policy must define `createHTML` and `createScriptURL`. + */ + TRUSTED_TYPES_POLICY?: TrustedTypePolicy | undefined; + /** + * Controls categories of allowed elements. + * + * Note that the `USE_PROFILES` setting will override the `ALLOWED_TAGS` setting + * so don't use them together. + */ + USE_PROFILES?: false | UseProfilesConfig | undefined; + /** + * Return entire document including tags (default is false). + */ + WHOLE_DOCUMENT?: boolean | undefined; +} +/** + * Defines categories of allowed elements. + */ +interface UseProfilesConfig { + /** + * Allow all safe MathML elements. + */ + mathMl?: boolean | undefined; + /** + * Allow all safe SVG elements. + */ + svg?: boolean | undefined; + /** + * Allow all save SVG Filters. + */ + svgFilters?: boolean | undefined; + /** + * Allow all safe HTML elements. + */ + html?: boolean | undefined; +} - interface SanitizeElementHookEvent { - tagName: string; - allowedTags: { [key: string]: boolean }; - } +declare const _default: DOMPurify; - interface SanitizeAttributeHookEvent { - attrName: string; - attrValue: string; - keepAttr: boolean; - allowedAttributes: { [key: string]: boolean }; - forceKeepAttr?: boolean | undefined; - } +interface DOMPurify { + /** + * Creates a DOMPurify instance using the given window-like object. Defaults to `window`. + */ + (root?: WindowLike): DOMPurify; + /** + * Version label, exposed for easier checks + * if DOMPurify is up to date or not + */ + version: string; + /** + * Array of elements that DOMPurify removed during sanitation. + * Empty if nothing was removed. + */ + removed: Array; + /** + * Expose whether this browser supports running the full DOMPurify. + */ + isSupported: boolean; + /** + * Set the configuration once. + * + * @param cfg configuration object + */ + setConfig(cfg?: Config): void; + /** + * Removes the configuration. + */ + clearConfig(): void; + /** + * Provides core sanitation functionality. + * + * @param dirty string or DOM node + * @param cfg object + * @returns Sanitized TrustedHTML. + */ + sanitize(dirty: string | Node, cfg: Config & { + RETURN_TRUSTED_TYPE: true; + }): TrustedHTML; + /** + * Provides core sanitation functionality. + * + * @param dirty DOM node + * @param cfg object + * @returns Sanitized DOM node. + */ + sanitize(dirty: Node, cfg: Config & { + IN_PLACE: true; + }): Node; + /** + * Provides core sanitation functionality. + * + * @param dirty string or DOM node + * @param cfg object + * @returns Sanitized DOM node. + */ + sanitize(dirty: string | Node, cfg: Config & { + RETURN_DOM: true; + }): Node; + /** + * Provides core sanitation functionality. + * + * @param dirty string or DOM node + * @param cfg object + * @returns Sanitized document fragment. + */ + sanitize(dirty: string | Node, cfg: Config & { + RETURN_DOM_FRAGMENT: true; + }): DocumentFragment; + /** + * Provides core sanitation functionality. + * + * @param dirty string or DOM node + * @param cfg object + * @returns Sanitized string. + */ + sanitize(dirty: string | Node, cfg?: Config): string; + /** + * Checks if an attribute value is valid. + * Uses last set config, if any. Otherwise, uses config defaults. + * + * @param tag Tag name of containing element. + * @param attr Attribute name. + * @param value Attribute value. + * @returns Returns true if `value` is valid. Otherwise, returns false. + */ + isValidAttribute(tag: string, attr: string, value: string): boolean; + /** + * Adds a DOMPurify hook. + * + * @param entryPoint entry point for the hook to add + * @param hookFunction function to execute + */ + addHook(entryPoint: BasicHookName, hookFunction: NodeHook): void; + /** + * Adds a DOMPurify hook. + * + * @param entryPoint entry point for the hook to add + * @param hookFunction function to execute + */ + addHook(entryPoint: ElementHookName, hookFunction: ElementHook): void; + /** + * Adds a DOMPurify hook. + * + * @param entryPoint entry point for the hook to add + * @param hookFunction function to execute + */ + addHook(entryPoint: DocumentFragmentHookName, hookFunction: DocumentFragmentHook): void; + /** + * Adds a DOMPurify hook. + * + * @param entryPoint entry point for the hook to add + * @param hookFunction function to execute + */ + addHook(entryPoint: 'uponSanitizeElement', hookFunction: UponSanitizeElementHook): void; + /** + * Adds a DOMPurify hook. + * + * @param entryPoint entry point for the hook to add + * @param hookFunction function to execute + */ + addHook(entryPoint: 'uponSanitizeAttribute', hookFunction: UponSanitizeAttributeHook): void; + /** + * Remove a DOMPurify hook at a given entryPoint + * (pops it from the stack of hooks if hook not specified) + * + * @param entryPoint entry point for the hook to remove + * @param hookFunction optional specific hook to remove + * @returns removed hook + */ + removeHook(entryPoint: BasicHookName, hookFunction?: NodeHook): NodeHook | undefined; + /** + * Remove a DOMPurify hook at a given entryPoint + * (pops it from the stack of hooks if hook not specified) + * + * @param entryPoint entry point for the hook to remove + * @param hookFunction optional specific hook to remove + * @returns removed hook + */ + removeHook(entryPoint: ElementHookName, hookFunction?: ElementHook): ElementHook | undefined; + /** + * Remove a DOMPurify hook at a given entryPoint + * (pops it from the stack of hooks if hook not specified) + * + * @param entryPoint entry point for the hook to remove + * @param hookFunction optional specific hook to remove + * @returns removed hook + */ + removeHook(entryPoint: DocumentFragmentHookName, hookFunction?: DocumentFragmentHook): DocumentFragmentHook | undefined; + /** + * Remove a DOMPurify hook at a given entryPoint + * (pops it from the stack of hooks if hook not specified) + * + * @param entryPoint entry point for the hook to remove + * @param hookFunction optional specific hook to remove + * @returns removed hook + */ + removeHook(entryPoint: 'uponSanitizeElement', hookFunction?: UponSanitizeElementHook): UponSanitizeElementHook | undefined; + /** + * Remove a DOMPurify hook at a given entryPoint + * (pops it from the stack of hooks if hook not specified) + * + * @param entryPoint entry point for the hook to remove + * @param hookFunction optional specific hook to remove + * @returns removed hook + */ + removeHook(entryPoint: 'uponSanitizeAttribute', hookFunction?: UponSanitizeAttributeHook): UponSanitizeAttributeHook | undefined; + /** + * Removes all DOMPurify hooks at a given entryPoint + * + * @param entryPoint entry point for the hooks to remove + */ + removeHooks(entryPoint: HookName): void; + /** + * Removes all DOMPurify hooks. + */ + removeAllHooks(): void; } +/** + * An element removed by DOMPurify. + */ +interface RemovedElement { + /** + * The element that was removed. + */ + element: Node; +} +/** + * An element removed by DOMPurify. + */ +interface RemovedAttribute { + /** + * The attribute that was removed. + */ + attribute: Attr | null; + /** + * The element that the attribute was removed. + */ + from: Node; +} +type BasicHookName = 'beforeSanitizeElements' | 'afterSanitizeElements' | 'uponSanitizeShadowNode'; +type ElementHookName = 'beforeSanitizeAttributes' | 'afterSanitizeAttributes'; +type DocumentFragmentHookName = 'beforeSanitizeShadowDOM' | 'afterSanitizeShadowDOM'; +type UponSanitizeElementHookName = 'uponSanitizeElement'; +type UponSanitizeAttributeHookName = 'uponSanitizeAttribute'; +type HookName = BasicHookName | ElementHookName | DocumentFragmentHookName | UponSanitizeElementHookName | UponSanitizeAttributeHookName; +type NodeHook = (this: DOMPurify, currentNode: Node, hookEvent: null, config: Config) => void; +type ElementHook = (this: DOMPurify, currentNode: Element, hookEvent: null, config: Config) => void; +type DocumentFragmentHook = (this: DOMPurify, currentNode: DocumentFragment, hookEvent: null, config: Config) => void; +type UponSanitizeElementHook = (this: DOMPurify, currentNode: Node, hookEvent: UponSanitizeElementHookEvent, config: Config) => void; +type UponSanitizeAttributeHook = (this: DOMPurify, currentNode: Element, hookEvent: UponSanitizeAttributeHookEvent, config: Config) => void; +interface UponSanitizeElementHookEvent { + tagName: string; + allowedTags: Record; +} +interface UponSanitizeAttributeHookEvent { + attrName: string; + attrValue: string; + keepAttr: boolean; + allowedAttributes: Record; + forceKeepAttr: boolean | undefined; +} +/** + * A `Window`-like object containing the properties and types that DOMPurify requires. + */ +type WindowLike = Pick & { + document?: Document; + MozNamedAttrMap?: typeof window.NamedNodeMap; +} & Pick; + +export { type Config, type DOMPurify, type DocumentFragmentHook, type ElementHook, type HookName, type NodeHook, type RemovedAttribute, type RemovedElement, type UponSanitizeAttributeHook, type UponSanitizeAttributeHookEvent, type UponSanitizeElementHook, type UponSanitizeElementHookEvent, type WindowLike, _default as default }; diff --git a/code/src/vs/base/browser/dompurify/dompurify.js b/code/src/vs/base/browser/dompurify/dompurify.js index af102433b9c..e3ad75a5cc7 100644 --- a/code/src/vs/base/browser/dompurify/dompurify.js +++ b/code/src/vs/base/browser/dompurify/dompurify.js @@ -1,4 +1,4 @@ -/*! @license DOMPurify 3.1.7 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.1.7/LICENSE */ +/*! @license DOMPurify 3.2.7 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.7/LICENSE */ const { entries, @@ -27,18 +27,26 @@ if (!seal) { }; } if (!apply) { - apply = function apply(fun, thisValue, args) { - return fun.apply(thisValue, args); + apply = function apply(func, thisArg) { + for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { + args[_key - 2] = arguments[_key]; + } + return func.apply(thisArg, args); }; } if (!construct) { - construct = function construct(Func, args) { + construct = function construct(Func) { + for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { + args[_key2 - 1] = arguments[_key2]; + } return new Func(...args); }; } const arrayForEach = unapply(Array.prototype.forEach); +const arrayLastIndexOf = unapply(Array.prototype.lastIndexOf); const arrayPop = unapply(Array.prototype.pop); const arrayPush = unapply(Array.prototype.push); +const arraySplice = unapply(Array.prototype.splice); const stringToLowerCase = unapply(String.prototype.toLowerCase); const stringToString = unapply(String.prototype.toString); const stringMatch = unapply(String.prototype.match); @@ -48,44 +56,44 @@ const stringTrim = unapply(String.prototype.trim); const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty); const regExpTest = unapply(RegExp.prototype.test); const typeErrorCreate = unconstruct(TypeError); - /** * Creates a new function that calls the given function with a specified thisArg and arguments. * - * @param {Function} func - The function to be wrapped and called. - * @returns {Function} A new function that calls the given function with a specified thisArg and arguments. + * @param func - The function to be wrapped and called. + * @returns A new function that calls the given function with a specified thisArg and arguments. */ function unapply(func) { return function (thisArg) { - for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { - args[_key - 1] = arguments[_key]; + if (thisArg instanceof RegExp) { + thisArg.lastIndex = 0; + } + for (var _len3 = arguments.length, args = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) { + args[_key3 - 1] = arguments[_key3]; } return apply(func, thisArg, args); }; } - /** * Creates a new function that constructs an instance of the given constructor function with the provided arguments. * - * @param {Function} func - The constructor function to be wrapped and called. - * @returns {Function} A new function that constructs an instance of the given constructor function with the provided arguments. + * @param func - The constructor function to be wrapped and called. + * @returns A new function that constructs an instance of the given constructor function with the provided arguments. */ -function unconstruct(func) { +function unconstruct(Func) { return function () { - for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { - args[_key2] = arguments[_key2]; + for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { + args[_key4] = arguments[_key4]; } - return construct(func, args); + return construct(Func, args); }; } - /** * Add properties to a lookup table * - * @param {Object} set - The set to which elements will be added. - * @param {Array} array - The array containing elements to be added to the set. - * @param {Function} transformCaseFunc - An optional function to transform the case of each element before adding to the set. - * @returns {Object} The modified set with added elements. + * @param set - The set to which elements will be added. + * @param array - The array containing elements to be added to the set. + * @param transformCaseFunc - An optional function to transform the case of each element before adding to the set. + * @returns The modified set with added elements. */ function addToSet(set, array) { let transformCaseFunc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : stringToLowerCase; @@ -112,12 +120,11 @@ function addToSet(set, array) { } return set; } - /** * Clean up an array to harden against CSPP * - * @param {Array} array - The array to be cleaned. - * @returns {Array} The cleaned version of the array + * @param array - The array to be cleaned. + * @returns The cleaned version of the array */ function cleanArray(array) { for (let index = 0; index < array.length; index++) { @@ -128,12 +135,11 @@ function cleanArray(array) { } return array; } - /** * Shallow clone an object * - * @param {Object} object - The object to be cloned. - * @returns {Object} A new object that copies the original. + * @param object - The object to be cloned. + * @returns A new object that copies the original. */ function clone(object) { const newObject = create(null); @@ -151,13 +157,12 @@ function clone(object) { } return newObject; } - /** * This method automatically checks if the prop is function or getter and behaves accordingly. * - * @param {Object} object - The object to look up the getter function in its prototype chain. - * @param {String} prop - The property name for which to find the getter function. - * @returns {Function} The getter function found in the prototype chain or a fallback function. + * @param object - The object to look up the getter function in its prototype chain. + * @param prop - The property name for which to find the getter function. + * @returns The getter function found in the prototype chain or a fallback function. */ function lookupGetter(object, prop) { while (object !== null) { @@ -178,25 +183,21 @@ function lookupGetter(object, prop) { return fallbackValue; } -const html$1 = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'select', 'shadow', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']); - -// SVG -const svg$1 = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']); +const html$1 = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'search', 'section', 'select', 'shadow', 'slot', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']); +const svg$1 = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'enterkeyhint', 'exportparts', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'inputmode', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'part', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'slot', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']); const svgFilters = freeze(['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence']); - // List of SVG elements that are disallowed by default. // We still need to know them so that we can do namespace // checks properly in case one wants to add them to // allow-list. const svgDisallowed = freeze(['animate', 'color-profile', 'cursor', 'discard', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'foreignobject', 'hatch', 'hatchpath', 'mesh', 'meshgradient', 'meshpatch', 'meshrow', 'missing-glyph', 'script', 'set', 'solidcolor', 'unknown', 'use']); const mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr', 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot', 'mrow', 'ms', 'mspace', 'msqrt', 'mstyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', 'munderover', 'mprescripts']); - // Similarly to SVG, we want to know all MathML elements, // even those that we disallow by default. const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']); const text = freeze(['#text']); -const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns', 'slot']); +const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'exportparts', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inert', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'part', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'slot', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns', 'slot']); const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'amplitude', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'exponent', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'slope', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'tablevalues', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']); const mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnsalign', 'columnlines', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lspace', 'lquote', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']); const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']); @@ -204,10 +205,10 @@ const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:x // eslint-disable-next-line unicorn/better-regex const MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode const ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm); -const TMPLIT_EXPR = seal(/\${[\w\W]*}/gm); -const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]/); // eslint-disable-line no-useless-escape +const TMPLIT_EXPR = seal(/\$\{[\w\W]*/gm); // eslint-disable-line unicorn/better-regex +const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]+$/); // eslint-disable-line no-useless-escape const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape -const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape +const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape ); const IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i); const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex @@ -217,18 +218,19 @@ const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i); var EXPRESSIONS = /*#__PURE__*/Object.freeze({ __proto__: null, - MUSTACHE_EXPR: MUSTACHE_EXPR, - ERB_EXPR: ERB_EXPR, - TMPLIT_EXPR: TMPLIT_EXPR, - DATA_ATTR: DATA_ATTR, ARIA_ATTR: ARIA_ATTR, - IS_ALLOWED_URI: IS_ALLOWED_URI, - IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA, ATTR_WHITESPACE: ATTR_WHITESPACE, + CUSTOM_ELEMENT: CUSTOM_ELEMENT, + DATA_ATTR: DATA_ATTR, DOCTYPE_NAME: DOCTYPE_NAME, - CUSTOM_ELEMENT: CUSTOM_ELEMENT + ERB_EXPR: ERB_EXPR, + IS_ALLOWED_URI: IS_ALLOWED_URI, + IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA, + MUSTACHE_EXPR: MUSTACHE_EXPR, + TMPLIT_EXPR: TMPLIT_EXPR }); +/* eslint-disable @typescript-eslint/indent */ // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType const NODE_TYPE = { element: 1, @@ -249,20 +251,18 @@ const NODE_TYPE = { const getGlobal = function getGlobal() { return typeof window === 'undefined' ? null : window; }; - /** * Creates a no-op policy for internal use only. * Don't export this function outside this module! - * @param {TrustedTypePolicyFactory} trustedTypes The policy factory. - * @param {HTMLScriptElement} purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix). - * @return {TrustedTypePolicy} The policy created (or null, if Trusted Types + * @param trustedTypes The policy factory. + * @param purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix). + * @return The policy created (or null, if Trusted Types * are not supported or creating the policy failed). */ const _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, purifyHostElement) { if (typeof trustedTypes !== 'object' || typeof trustedTypes.createPolicy !== 'function') { return null; } - // Allow the callers to control the unique policy name // by adding a data-tt-policy-suffix to the script element with the DOMPurify. // Policy creation with duplicate names throws in Trusted Types. @@ -289,22 +289,25 @@ const _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedType return null; } }; +const _createHooksMap = function _createHooksMap() { + return { + afterSanitizeAttributes: [], + afterSanitizeElements: [], + afterSanitizeShadowDOM: [], + beforeSanitizeAttributes: [], + beforeSanitizeElements: [], + beforeSanitizeShadowDOM: [], + uponSanitizeAttribute: [], + uponSanitizeElement: [], + uponSanitizeShadowNode: [] + }; +}; function createDOMPurify() { let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal(); const DOMPurify = root => createDOMPurify(root); - - /** - * Version label, exposed for easier checks - * if DOMPurify is up to date or not - */ - DOMPurify.version = '3.1.7'; - - /** - * Array of elements that DOMPurify removed during sanitation. - * Empty if nothing was removed. - */ + DOMPurify.version = '3.2.7'; DOMPurify.removed = []; - if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document) { + if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) { // Not running in a browser, provide a factory function // so that you can pass your own Window DOMPurify.isSupported = false; @@ -332,7 +335,6 @@ function createDOMPurify() { const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling'); const getChildNodes = lookupGetter(ElementPrototype, 'childNodes'); const getParentNode = lookupGetter(ElementPrototype, 'parentNode'); - // As per issue #47, the web-components registry is inherited by a // new document created via createHTMLDocument. As per the spec // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries) @@ -356,8 +358,7 @@ function createDOMPurify() { const { importNode } = originalDocument; - let hooks = {}; - + let hooks = _createHooksMap(); /** * Expose whether this browser supports running the full DOMPurify. */ @@ -375,22 +376,18 @@ function createDOMPurify() { let { IS_ALLOWED_URI: IS_ALLOWED_URI$1 } = EXPRESSIONS; - /** * We consider the elements and attributes below to be safe. Ideally * don't add any new ones but feel free to remove unwanted ones. */ - /* allowed element names */ let ALLOWED_TAGS = null; const DEFAULT_ALLOWED_TAGS = addToSet({}, [...html$1, ...svg$1, ...svgFilters, ...mathMl$1, ...text]); - /* Allowed attribute names */ let ALLOWED_ATTR = null; const DEFAULT_ALLOWED_ATTR = addToSet({}, [...html, ...svg, ...mathMl, ...xml]); - /* - * Configure how DOMPUrify should handle custom elements and their attributes as well as customized built-in elements. + * Configure how DOMPurify should handle custom elements and their attributes as well as customized built-in elements. * @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements) * @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list) * @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`. @@ -415,65 +412,49 @@ function createDOMPurify() { value: false } })); - /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */ let FORBID_TAGS = null; - /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */ let FORBID_ATTR = null; - /* Decide if ARIA attributes are okay */ let ALLOW_ARIA_ATTR = true; - /* Decide if custom data attributes are okay */ let ALLOW_DATA_ATTR = true; - /* Decide if unknown protocols are okay */ let ALLOW_UNKNOWN_PROTOCOLS = false; - /* Decide if self-closing tags in attributes are allowed. * Usually removed due to a mXSS issue in jQuery 3.0 */ let ALLOW_SELF_CLOSE_IN_ATTR = true; - /* Output should be safe for common template engines. * This means, DOMPurify removes data attributes, mustaches and ERB */ let SAFE_FOR_TEMPLATES = false; - /* Output should be safe even for XML used within HTML and alike. * This means, DOMPurify removes comments when containing risky content. */ let SAFE_FOR_XML = true; - /* Decide if document with ... should be returned */ let WHOLE_DOCUMENT = false; - /* Track whether config is already set on this instance of DOMPurify. */ let SET_CONFIG = false; - /* Decide if all elements (e.g. style, script) must be children of * document.body. By default, browsers might move them to document.head */ let FORCE_BODY = false; - /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html * string (or a TrustedHTML object if Trusted Types are supported). * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead */ let RETURN_DOM = false; - /* Decide if a DOM `DocumentFragment` should be returned, instead of a html * string (or a TrustedHTML object if Trusted Types are supported) */ let RETURN_DOM_FRAGMENT = false; - /* Try to return a Trusted Type object instead of a string, return a string in * case Trusted Types are not supported */ let RETURN_TRUSTED_TYPE = false; - /* Output should be free from DOM clobbering attacks? * This sanitizes markups named with colliding, clobberable built-in DOM APIs. */ let SANITIZE_DOM = true; - /* Achieve full DOM Clobbering protection by isolating the namespace of named * properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules. * @@ -489,25 +470,19 @@ function createDOMPurify() { */ let SANITIZE_NAMED_PROPS = false; const SANITIZE_NAMED_PROPS_PREFIX = 'user-content-'; - /* Keep element content when removing element? */ let KEEP_CONTENT = true; - /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead * of importing it into a new Document and returning a sanitized copy */ let IN_PLACE = false; - /* Allow usage of profiles like html, svg and mathMl */ let USE_PROFILES = {}; - /* Tags to ignore content of when KEEP_CONTENT is true */ let FORBID_CONTENTS = null; const DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']); - /* Tags that are safe for data: URIs */ let DATA_URI_TAGS = null; const DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']); - /* Attributes safe for values like "javascript:" */ let URI_SAFE_ATTRIBUTES = null; const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']); @@ -517,32 +492,33 @@ function createDOMPurify() { /* Document namespace */ let NAMESPACE = HTML_NAMESPACE; let IS_EMPTY_INPUT = false; - /* Allowed XHTML+XML namespaces */ let ALLOWED_NAMESPACES = null; const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString); - + let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']); + let HTML_INTEGRATION_POINTS = addToSet({}, ['annotation-xml']); + // Certain elements are allowed in both SVG and HTML + // namespace. We need to specify them explicitly + // so that they don't get erroneously deleted from + // HTML namespace. + const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']); /* Parsing of strict XHTML documents */ let PARSER_MEDIA_TYPE = null; const SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html']; const DEFAULT_PARSER_MEDIA_TYPE = 'text/html'; let transformCaseFunc = null; - /* Keep a reference to config to pass to hooks */ let CONFIG = null; - /* Ideally, do not touch anything below this line */ /* ______________________________________________ */ - const formElement = document.createElement('form'); const isRegexOrFunction = function isRegexOrFunction(testValue) { return testValue instanceof RegExp || testValue instanceof Function; }; - /** * _parseConfig * - * @param {Object} cfg optional config literal + * @param cfg optional config literal */ // eslint-disable-next-line complexity const _parseConfig = function _parseConfig() { @@ -550,42 +526,26 @@ function createDOMPurify() { if (CONFIG && CONFIG === cfg) { return; } - /* Shield configuration object from tampering */ if (!cfg || typeof cfg !== 'object') { cfg = {}; } - /* Shield configuration object from prototype pollution */ cfg = clone(cfg); PARSER_MEDIA_TYPE = // eslint-disable-next-line unicorn/prefer-includes SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? DEFAULT_PARSER_MEDIA_TYPE : cfg.PARSER_MEDIA_TYPE; - // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is. transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase; - /* Set configuration parameters */ ALLOWED_TAGS = objectHasOwnProperty(cfg, 'ALLOWED_TAGS') ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS; ALLOWED_ATTR = objectHasOwnProperty(cfg, 'ALLOWED_ATTR') ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR; ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, 'ALLOWED_NAMESPACES') ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES; - URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), - // eslint-disable-line indent - cfg.ADD_URI_SAFE_ATTR, - // eslint-disable-line indent - transformCaseFunc // eslint-disable-line indent - ) // eslint-disable-line indent - : DEFAULT_URI_SAFE_ATTRIBUTES; - DATA_URI_TAGS = objectHasOwnProperty(cfg, 'ADD_DATA_URI_TAGS') ? addToSet(clone(DEFAULT_DATA_URI_TAGS), - // eslint-disable-line indent - cfg.ADD_DATA_URI_TAGS, - // eslint-disable-line indent - transformCaseFunc // eslint-disable-line indent - ) // eslint-disable-line indent - : DEFAULT_DATA_URI_TAGS; + URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR, transformCaseFunc) : DEFAULT_URI_SAFE_ATTRIBUTES; + DATA_URI_TAGS = objectHasOwnProperty(cfg, 'ADD_DATA_URI_TAGS') ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS, transformCaseFunc) : DEFAULT_DATA_URI_TAGS; FORBID_CONTENTS = objectHasOwnProperty(cfg, 'FORBID_CONTENTS') ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS; - FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : {}; - FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : {}; + FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : clone({}); + FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : clone({}); USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES : false; ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true @@ -604,6 +564,8 @@ function createDOMPurify() { IN_PLACE = cfg.IN_PLACE || false; // Default false IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI; NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE; + MATHML_TEXT_INTEGRATION_POINTS = cfg.MATHML_TEXT_INTEGRATION_POINTS || MATHML_TEXT_INTEGRATION_POINTS; + HTML_INTEGRATION_POINTS = cfg.HTML_INTEGRATION_POINTS || HTML_INTEGRATION_POINTS; CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {}; if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) { CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck; @@ -620,7 +582,6 @@ function createDOMPurify() { if (RETURN_DOM_FRAGMENT) { RETURN_DOM = true; } - /* Parse profile info */ if (USE_PROFILES) { ALLOWED_TAGS = addToSet({}, text); @@ -645,7 +606,6 @@ function createDOMPurify() { addToSet(ALLOWED_ATTR, xml); } } - /* Merge configuration parameters */ if (cfg.ADD_TAGS) { if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) { @@ -668,17 +628,14 @@ function createDOMPurify() { } addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc); } - /* Add #text in case KEEP_CONTENT is set to true */ if (KEEP_CONTENT) { ALLOWED_TAGS['#text'] = true; } - /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */ if (WHOLE_DOCUMENT) { addToSet(ALLOWED_TAGS, ['html', 'head', 'body']); } - /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */ if (ALLOWED_TAGS.table) { addToSet(ALLOWED_TAGS, ['tbody']); @@ -691,10 +648,8 @@ function createDOMPurify() { if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') { throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.'); } - // Overwrite existing TrustedTypes policy. trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY; - // Sign local variables required by `sanitize`. emptyHTML = trustedTypesPolicy.createHTML(''); } else { @@ -702,13 +657,11 @@ function createDOMPurify() { if (trustedTypesPolicy === undefined) { trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript); } - // If creating the internal policy succeeded sign internal variables. if (trustedTypesPolicy !== null && typeof emptyHTML === 'string') { emptyHTML = trustedTypesPolicy.createHTML(''); } } - // Prevent further manipulation of configuration. // Not available in IE8, Safari 5, etc. if (freeze) { @@ -716,30 +669,19 @@ function createDOMPurify() { } CONFIG = cfg; }; - const MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']); - const HTML_INTEGRATION_POINTS = addToSet({}, ['annotation-xml']); - - // Certain elements are allowed in both SVG and HTML - // namespace. We need to specify them explicitly - // so that they don't get erroneously deleted from - // HTML namespace. - const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']); - /* Keep track of all possible SVG and MathML tags * so that we can perform the namespace checks * correctly. */ const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]); const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]); - /** - * @param {Element} element a DOM element whose namespace is being checked - * @returns {boolean} Return false if the element has a + * @param element a DOM element whose namespace is being checked + * @returns Return false if the element has a * namespace that a spec-compliant parser would never * return. Return true otherwise. */ const _checkValidNamespace = function _checkValidNamespace(element) { let parent = getParentNode(element); - // In JSDOM, if we're inside shadow DOM, then parentNode // can be null. We just simulate parent in this case. if (!parent || !parent.tagName) { @@ -760,14 +702,12 @@ function createDOMPurify() { if (parent.namespaceURI === HTML_NAMESPACE) { return tagName === 'svg'; } - // The only way to switch from MathML to SVG is via` // svg if parent is either or MathML // text integration points. if (parent.namespaceURI === MATHML_NAMESPACE) { return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]); } - // We only allow elements that are defined in SVG // spec. All others are disallowed in SVG namespace. return Boolean(ALL_SVG_TAGS[tagName]); @@ -779,13 +719,11 @@ function createDOMPurify() { if (parent.namespaceURI === HTML_NAMESPACE) { return tagName === 'math'; } - // The only way to switch from SVG to MathML is via // and HTML integration points if (parent.namespaceURI === SVG_NAMESPACE) { return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName]; } - // We only allow elements that are defined in MathML // spec. All others are disallowed in MathML namespace. return Boolean(ALL_MATHML_TAGS[tagName]); @@ -800,28 +738,24 @@ function createDOMPurify() { if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) { return false; } - // We disallow tags that are specific for MathML // or SVG and should never appear in HTML namespace return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]); } - // For XHTML and XML documents that support custom namespaces if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) { return true; } - // The code should never reach this place (this means // that the element somehow got namespace that is not // HTML, SVG, MathML or allowed via ALLOWED_NAMESPACES). // Return false just in case. return false; }; - /** * _forceRemove * - * @param {Node} node a DOM node + * @param node a DOM node */ const _forceRemove = function _forceRemove(node) { arrayPush(DOMPurify.removed, { @@ -834,46 +768,43 @@ function createDOMPurify() { remove(node); } }; - /** * _removeAttribute * - * @param {String} name an Attribute name - * @param {Node} node a DOM node + * @param name an Attribute name + * @param element a DOM node */ - const _removeAttribute = function _removeAttribute(name, node) { + const _removeAttribute = function _removeAttribute(name, element) { try { arrayPush(DOMPurify.removed, { - attribute: node.getAttributeNode(name), - from: node + attribute: element.getAttributeNode(name), + from: element }); } catch (_) { arrayPush(DOMPurify.removed, { attribute: null, - from: node + from: element }); } - node.removeAttribute(name); - - // We void attribute values for unremovable "is"" attributes - if (name === 'is' && !ALLOWED_ATTR[name]) { + element.removeAttribute(name); + // We void attribute values for unremovable "is" attributes + if (name === 'is') { if (RETURN_DOM || RETURN_DOM_FRAGMENT) { try { - _forceRemove(node); + _forceRemove(element); } catch (_) {} } else { try { - node.setAttribute(name, ''); + element.setAttribute(name, ''); } catch (_) {} } } }; - /** * _initDocument * - * @param {String} dirty a string of dirty markup - * @return {Document} a DOM, filled with the dirty markup + * @param dirty - a string of dirty markup + * @return a DOM, filled with the dirty markup */ const _initDocument = function _initDocument(dirty) { /* Create a HTML document */ @@ -900,7 +831,6 @@ function createDOMPurify() { doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE); } catch (_) {} } - /* Use createHTMLDocument in case DOMParser is not available */ if (!doc || !doc.documentElement) { doc = implementation.createDocument(NAMESPACE, 'template', null); @@ -914,112 +844,86 @@ function createDOMPurify() { if (dirty && leadingWhitespace) { body.insertBefore(document.createTextNode(leadingWhitespace), body.childNodes[0] || null); } - /* Work on whole document or just its body */ if (NAMESPACE === HTML_NAMESPACE) { return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0]; } return WHOLE_DOCUMENT ? doc.documentElement : body; }; - /** * Creates a NodeIterator object that you can use to traverse filtered lists of nodes or elements in a document. * - * @param {Node} root The root element or node to start traversing on. - * @return {NodeIterator} The created NodeIterator + * @param root The root element or node to start traversing on. + * @return The created NodeIterator */ const _createNodeIterator = function _createNodeIterator(root) { return createNodeIterator.call(root.ownerDocument || root, root, // eslint-disable-next-line no-bitwise NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null); }; - /** * _isClobbered * - * @param {Node} elm element to check for clobbering attacks - * @return {Boolean} true if clobbered, false if safe + * @param element element to check for clobbering attacks + * @return true if clobbered, false if safe */ - const _isClobbered = function _isClobbered(elm) { - return elm instanceof HTMLFormElement && (typeof elm.nodeName !== 'string' || typeof elm.textContent !== 'string' || typeof elm.removeChild !== 'function' || !(elm.attributes instanceof NamedNodeMap) || typeof elm.removeAttribute !== 'function' || typeof elm.setAttribute !== 'function' || typeof elm.namespaceURI !== 'string' || typeof elm.insertBefore !== 'function' || typeof elm.hasChildNodes !== 'function'); + const _isClobbered = function _isClobbered(element) { + return element instanceof HTMLFormElement && (typeof element.nodeName !== 'string' || typeof element.textContent !== 'string' || typeof element.removeChild !== 'function' || !(element.attributes instanceof NamedNodeMap) || typeof element.removeAttribute !== 'function' || typeof element.setAttribute !== 'function' || typeof element.namespaceURI !== 'string' || typeof element.insertBefore !== 'function' || typeof element.hasChildNodes !== 'function'); }; - /** * Checks whether the given object is a DOM node. * - * @param {Node} object object to check whether it's a DOM node - * @return {Boolean} true is object is a DOM node + * @param value object to check whether it's a DOM node + * @return true is object is a DOM node */ - const _isNode = function _isNode(object) { - return typeof Node === 'function' && object instanceof Node; + const _isNode = function _isNode(value) { + return typeof Node === 'function' && value instanceof Node; }; - - /** - * _executeHook - * Execute user configurable hooks - * - * @param {String} entryPoint Name of the hook's entry point - * @param {Node} currentNode node to work on with the hook - * @param {Object} data additional hook parameters - */ - const _executeHook = function _executeHook(entryPoint, currentNode, data) { - if (!hooks[entryPoint]) { - return; - } - arrayForEach(hooks[entryPoint], hook => { + function _executeHooks(hooks, currentNode, data) { + arrayForEach(hooks, hook => { hook.call(DOMPurify, currentNode, data, CONFIG); }); - }; - + } /** * _sanitizeElements * * @protect nodeName * @protect textContent * @protect removeChild - * - * @param {Node} currentNode to check for permission to exist - * @return {Boolean} true if node was killed, false if left alive + * @param currentNode to check for permission to exist + * @return true if node was killed, false if left alive */ const _sanitizeElements = function _sanitizeElements(currentNode) { let content = null; - /* Execute a hook if present */ - _executeHook('beforeSanitizeElements', currentNode, null); - + _executeHooks(hooks.beforeSanitizeElements, currentNode, null); /* Check if element is clobbered or can clobber */ if (_isClobbered(currentNode)) { _forceRemove(currentNode); return true; } - /* Now let's check the element's type and name */ const tagName = transformCaseFunc(currentNode.nodeName); - /* Execute a hook if present */ - _executeHook('uponSanitizeElement', currentNode, { + _executeHooks(hooks.uponSanitizeElement, currentNode, { tagName, allowedTags: ALLOWED_TAGS }); - /* Detect mXSS attempts abusing namespace confusion */ - if (currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w]/g, currentNode.innerHTML) && regExpTest(/<[/\w]/g, currentNode.textContent)) { + if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w!]/g, currentNode.innerHTML) && regExpTest(/<[/\w!]/g, currentNode.textContent)) { _forceRemove(currentNode); return true; } - /* Remove any occurrence of processing instructions */ if (currentNode.nodeType === NODE_TYPE.progressingInstruction) { _forceRemove(currentNode); return true; } - /* Remove any kind of possibly harmful comments */ if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(/<[/\w]/g, currentNode.data)) { _forceRemove(currentNode); return true; } - /* Remove element if anything forbids its presence */ if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) { /* Check if we have a custom element to handle */ @@ -1031,7 +935,6 @@ function createDOMPurify() { return false; } } - /* Keep content except for bad-listed elements */ if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) { const parentNode = getParentNode(currentNode) || currentNode.parentNode; @@ -1048,19 +951,16 @@ function createDOMPurify() { _forceRemove(currentNode); return true; } - /* Check whether element has a valid namespace */ if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) { _forceRemove(currentNode); return true; } - /* Make sure that older browsers don't get fallback-tag mXSS */ if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(/<\/no(script|embed|frames)/i, currentNode.innerHTML)) { _forceRemove(currentNode); return true; } - /* Sanitize element content to be template-safe */ if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) { /* Get the element's text content */ @@ -1075,19 +975,17 @@ function createDOMPurify() { currentNode.textContent = content; } } - /* Execute a hook if present */ - _executeHook('afterSanitizeElements', currentNode, null); + _executeHooks(hooks.afterSanitizeElements, currentNode, null); return false; }; - /** * _isValidAttribute * - * @param {string} lcTag Lowercase tag name of containing element. - * @param {string} lcName Lowercase attribute name. - * @param {string} value Attribute value. - * @return {Boolean} Returns true if `value` is valid, otherwise false. + * @param lcTag Lowercase tag name of containing element. + * @param lcName Lowercase attribute name. + * @param value Attribute value. + * @return Returns true if `value` is valid, otherwise false. */ // eslint-disable-next-line complexity const _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) { @@ -1095,7 +993,6 @@ function createDOMPurify() { if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) { return false; } - /* Allow valid data-* attributes: At least one character after "-" (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes) XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804) @@ -1105,7 +1002,7 @@ function createDOMPurify() { // First condition does a very basic check if a) it's basically a valid custom element tagname AND // b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck // and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck - _isBasicCustomElement(lcTag) && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(lcTag)) && (CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName) || CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName)) || + _isBasicCustomElement(lcTag) && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(lcTag)) && (CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName) || CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName, lcTag)) || // Alternative, second condition checks if it's an `is`-attribute, AND // the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck lcName === 'is' && CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, value) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(value))) ; else { @@ -1117,19 +1014,17 @@ function createDOMPurify() { } else ; return true; }; - /** * _isBasicCustomElement * checks if at least one dash is included in tagName, and it's not the first char * for more sophisticated checking see https://github.com/sindresorhus/validate-element-name * - * @param {string} tagName name of the tag of the node to sanitize - * @returns {boolean} Returns true if the tag name meets the basic criteria for a custom element, otherwise false. + * @param tagName name of the tag of the node to sanitize + * @returns Returns true if the tag name meets the basic criteria for a custom element, otherwise false. */ const _isBasicCustomElement = function _isBasicCustomElement(tagName) { return tagName !== 'annotation-xml' && stringMatch(tagName, CUSTOM_ELEMENT); }; - /** * _sanitizeAttributes * @@ -1138,27 +1033,26 @@ function createDOMPurify() { * @protect removeAttribute * @protect setAttribute * - * @param {Node} currentNode to sanitize + * @param currentNode to sanitize */ const _sanitizeAttributes = function _sanitizeAttributes(currentNode) { /* Execute a hook if present */ - _executeHook('beforeSanitizeAttributes', currentNode, null); + _executeHooks(hooks.beforeSanitizeAttributes, currentNode, null); const { attributes } = currentNode; - /* Check if we have attributes; if not we might have a text node */ - if (!attributes) { + if (!attributes || _isClobbered(currentNode)) { return; } const hookEvent = { attrName: '', attrValue: '', keepAttr: true, - allowedAttributes: ALLOWED_ATTR + allowedAttributes: ALLOWED_ATTR, + forceKeepAttr: undefined }; let l = attributes.length; - /* Go backwards over all attributes; safely remove bad ones */ while (l--) { const attr = attributes[l]; @@ -1168,65 +1062,60 @@ function createDOMPurify() { value: attrValue } = attr; const lcName = transformCaseFunc(name); - let value = name === 'value' ? attrValue : stringTrim(attrValue); - + const initValue = attrValue; + let value = name === 'value' ? initValue : stringTrim(initValue); /* Execute a hook if present */ hookEvent.attrName = lcName; hookEvent.attrValue = value; hookEvent.keepAttr = true; hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set - _executeHook('uponSanitizeAttribute', currentNode, hookEvent); + _executeHooks(hooks.uponSanitizeAttribute, currentNode, hookEvent); value = hookEvent.attrValue; - + /* Full DOM Clobbering protection via namespace isolation, + * Prefix id and name attributes with `user-content-` + */ + if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) { + // Remove the attribute with this value + _removeAttribute(name, currentNode); + // Prefix the value and later re-create the attribute with the sanitized value + value = SANITIZE_NAMED_PROPS_PREFIX + value; + } + /* Work around a security issue with comments inside attributes */ + if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|title|textarea)/i, value)) { + _removeAttribute(name, currentNode); + continue; + } + /* Make sure we cannot easily use animated hrefs, even if animations are allowed */ + if (lcName === 'attributename' && stringMatch(value, 'href')) { + _removeAttribute(name, currentNode); + continue; + } /* Did the hooks approve of the attribute? */ if (hookEvent.forceKeepAttr) { continue; } - - /* Remove attribute */ - _removeAttribute(name, currentNode); - /* Did the hooks approve of the attribute? */ if (!hookEvent.keepAttr) { + _removeAttribute(name, currentNode); continue; } - /* Work around a security issue in jQuery 3.0 */ if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\/>/i, value)) { _removeAttribute(name, currentNode); continue; } - /* Sanitize attribute content to be template-safe */ if (SAFE_FOR_TEMPLATES) { arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => { value = stringReplace(value, expr, ' '); }); } - /* Is `value` valid for this attribute? */ const lcTag = transformCaseFunc(currentNode.nodeName); if (!_isValidAttribute(lcTag, lcName, value)) { - continue; - } - - /* Full DOM Clobbering protection via namespace isolation, - * Prefix id and name attributes with `user-content-` - */ - if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) { - // Remove the attribute with this value - _removeAttribute(name, currentNode); - - // Prefix the value and later re-create the attribute with the sanitized value - value = SANITIZE_NAMED_PROPS_PREFIX + value; - } - - /* Work around a security issue with comments inside attributes */ - if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|title)/i, value)) { _removeAttribute(name, currentNode); continue; } - /* Handle attributes that require Trusted Types */ if (trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes.getAttributeType === 'function') { if (namespaceURI) ; else { @@ -1244,67 +1133,53 @@ function createDOMPurify() { } } } - /* Handle invalid data-* attribute set by try-catching it */ - try { - if (namespaceURI) { - currentNode.setAttributeNS(namespaceURI, name, value); - } else { - /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */ - currentNode.setAttribute(name, value); - } - if (_isClobbered(currentNode)) { - _forceRemove(currentNode); - } else { - arrayPop(DOMPurify.removed); + if (value !== initValue) { + try { + if (namespaceURI) { + currentNode.setAttributeNS(namespaceURI, name, value); + } else { + /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */ + currentNode.setAttribute(name, value); + } + if (_isClobbered(currentNode)) { + _forceRemove(currentNode); + } else { + arrayPop(DOMPurify.removed); + } + } catch (_) { + _removeAttribute(name, currentNode); } - } catch (_) {} + } } - /* Execute a hook if present */ - _executeHook('afterSanitizeAttributes', currentNode, null); + _executeHooks(hooks.afterSanitizeAttributes, currentNode, null); }; - /** * _sanitizeShadowDOM * - * @param {DocumentFragment} fragment to iterate over recursively + * @param fragment to iterate over recursively */ const _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) { let shadowNode = null; const shadowIterator = _createNodeIterator(fragment); - /* Execute a hook if present */ - _executeHook('beforeSanitizeShadowDOM', fragment, null); + _executeHooks(hooks.beforeSanitizeShadowDOM, fragment, null); while (shadowNode = shadowIterator.nextNode()) { /* Execute a hook if present */ - _executeHook('uponSanitizeShadowNode', shadowNode, null); - + _executeHooks(hooks.uponSanitizeShadowNode, shadowNode, null); /* Sanitize tags and elements */ - if (_sanitizeElements(shadowNode)) { - continue; - } - + _sanitizeElements(shadowNode); + /* Check attributes next */ + _sanitizeAttributes(shadowNode); /* Deep shadow DOM detected */ if (shadowNode.content instanceof DocumentFragment) { _sanitizeShadowDOM(shadowNode.content); } - - /* Check attributes, sanitize if necessary */ - _sanitizeAttributes(shadowNode); } - /* Execute a hook if present */ - _executeHook('afterSanitizeShadowDOM', fragment, null); + _executeHooks(hooks.afterSanitizeShadowDOM, fragment, null); }; - - /** - * Sanitize - * Public method providing core sanitation functionality - * - * @param {String|Node} dirty string or DOM node - * @param {Object} cfg object - */ // eslint-disable-next-line complexity DOMPurify.sanitize = function (dirty) { let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; @@ -1319,7 +1194,6 @@ function createDOMPurify() { if (IS_EMPTY_INPUT) { dirty = ''; } - /* Stringify, in case dirty is an object */ if (typeof dirty !== 'string' && !_isNode(dirty)) { if (typeof dirty.toString === 'function') { @@ -1331,20 +1205,16 @@ function createDOMPurify() { throw typeErrorCreate('toString is not a function'); } } - /* Return dirty HTML if DOMPurify cannot run */ if (!DOMPurify.isSupported) { return dirty; } - /* Assign config vars */ if (!SET_CONFIG) { _parseConfig(cfg); } - /* Clean up removed elements */ DOMPurify.removed = []; - /* Check if dirty is correctly typed for IN_PLACE */ if (typeof dirty === 'string') { IN_PLACE = false; @@ -1378,45 +1248,34 @@ function createDOMPurify() { dirty.indexOf('<') === -1) { return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty; } - /* Initialize the document to work on */ body = _initDocument(dirty); - /* Check we have a DOM node from the data */ if (!body) { return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : ''; } } - /* Remove first element node (ours) if FORCE_BODY is set */ if (body && FORCE_BODY) { _forceRemove(body.firstChild); } - /* Get node iterator */ const nodeIterator = _createNodeIterator(IN_PLACE ? dirty : body); - /* Now start iterating over the created document */ while (currentNode = nodeIterator.nextNode()) { /* Sanitize tags and elements */ - if (_sanitizeElements(currentNode)) { - continue; - } - + _sanitizeElements(currentNode); + /* Check attributes next */ + _sanitizeAttributes(currentNode); /* Shadow DOM detected, sanitize it */ if (currentNode.content instanceof DocumentFragment) { _sanitizeShadowDOM(currentNode.content); } - - /* Check attributes, sanitize if necessary */ - _sanitizeAttributes(currentNode); } - /* If we sanitized `dirty` in-place, return it. */ if (IN_PLACE) { return dirty; } - /* Return sanitized string or DOM */ if (RETURN_DOM) { if (RETURN_DOM_FRAGMENT) { @@ -1441,12 +1300,10 @@ function createDOMPurify() { return returnNode; } let serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML; - /* Serialize doctype if allowed */ if (WHOLE_DOCUMENT && ALLOWED_TAGS['!doctype'] && body.ownerDocument && body.ownerDocument.doctype && body.ownerDocument.doctype.name && regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)) { serializedHTML = '\n' + serializedHTML; } - /* Sanitize final string template-safe */ if (SAFE_FOR_TEMPLATES) { arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => { @@ -1455,39 +1312,15 @@ function createDOMPurify() { } return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML; }; - - /** - * Public method to set the configuration once - * setConfig - * - * @param {Object} cfg configuration object - */ DOMPurify.setConfig = function () { let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; _parseConfig(cfg); SET_CONFIG = true; }; - - /** - * Public method to remove the configuration - * clearConfig - * - */ DOMPurify.clearConfig = function () { CONFIG = null; SET_CONFIG = false; }; - - /** - * Public method to check if an attribute value is valid. - * Uses last set config, if any. Otherwise, uses config defaults. - * isValidAttribute - * - * @param {String} tag Tag name of containing element. - * @param {String} attr Attribute name. - * @param {String} value Attribute value. - * @return {Boolean} Returns true if `value` is valid. Otherwise, returns false. - */ DOMPurify.isValidAttribute = function (tag, attr, value) { /* Initialize shared config vars if necessary. */ if (!CONFIG) { @@ -1497,58 +1330,27 @@ function createDOMPurify() { const lcName = transformCaseFunc(attr); return _isValidAttribute(lcTag, lcName, value); }; - - /** - * AddHook - * Public method to add DOMPurify hooks - * - * @param {String} entryPoint entry point for the hook to add - * @param {Function} hookFunction function to execute - */ DOMPurify.addHook = function (entryPoint, hookFunction) { if (typeof hookFunction !== 'function') { return; } - hooks[entryPoint] = hooks[entryPoint] || []; arrayPush(hooks[entryPoint], hookFunction); }; - - /** - * RemoveHook - * Public method to remove a DOMPurify hook at a given entryPoint - * (pops it from the stack of hooks if more are present) - * - * @param {String} entryPoint entry point for the hook to remove - * @return {Function} removed(popped) hook - */ - DOMPurify.removeHook = function (entryPoint) { - if (hooks[entryPoint]) { - return arrayPop(hooks[entryPoint]); + DOMPurify.removeHook = function (entryPoint, hookFunction) { + if (hookFunction !== undefined) { + const index = arrayLastIndexOf(hooks[entryPoint], hookFunction); + return index === -1 ? undefined : arraySplice(hooks[entryPoint], index, 1)[0]; } + return arrayPop(hooks[entryPoint]); }; - - /** - * RemoveHooks - * Public method to remove all DOMPurify hooks at a given entryPoint - * - * @param {String} entryPoint entry point for the hooks to remove - */ DOMPurify.removeHooks = function (entryPoint) { - if (hooks[entryPoint]) { - hooks[entryPoint] = []; - } + hooks[entryPoint] = []; }; - - /** - * RemoveAllHooks - * Public method to remove all DOMPurify hooks - */ DOMPurify.removeAllHooks = function () { - hooks = {}; + hooks = _createHooksMap(); }; return DOMPurify; } var purify = createDOMPurify(); export { purify as default }; -//# sourceMappingURL=purify.es.mjs.map \ No newline at end of file diff --git a/code/src/vs/base/browser/fastDomNode.ts b/code/src/vs/base/browser/fastDomNode.ts index 9d58aaba9c3..218a68c4fec 100644 --- a/code/src/vs/base/browser/fastDomNode.ts +++ b/code/src/vs/base/browser/fastDomNode.ts @@ -291,7 +291,7 @@ export class FastDomNode { return; } this._contain = contain; - (this.domNode.style).contain = this._contain; + this.domNode.style.contain = this._contain; } public setAttribute(name: string, value: string): void { diff --git a/code/src/vs/base/browser/history.ts b/code/src/vs/base/browser/history.ts index c5e9d036604..a7e8972ea2e 100644 --- a/code/src/vs/base/browser/history.ts +++ b/code/src/vs/base/browser/history.ts @@ -13,8 +13,8 @@ export interface IHistoryNavigationWidget { showNextValue(): void; - onDidFocus: Event; + readonly onDidFocus: Event; - onDidBlur: Event; + readonly onDidBlur: Event; } diff --git a/code/src/vs/base/browser/keyboardEvent.ts b/code/src/vs/base/browser/keyboardEvent.ts index 4e98a3b12d8..6b675d06535 100644 --- a/code/src/vs/base/browser/keyboardEvent.ts +++ b/code/src/vs/base/browser/keyboardEvent.ts @@ -4,12 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as browser from './browser.js'; -import { EVENT_KEY_CODE_MAP, KeyCode, KeyCodeUtils, KeyMod } from '../common/keyCodes.js'; +import { EVENT_KEY_CODE_MAP, isModifierKey, KeyCode, KeyCodeUtils, KeyMod } from '../common/keyCodes.js'; import { KeyCodeChord } from '../common/keybindings.js'; import * as platform from '../common/platform.js'; - - function extractKeyCode(e: KeyboardEvent): KeyCode { if (e.charCode) { // "keypress" events mostly @@ -114,6 +112,15 @@ export function printStandardKeyboardEvent(e: StandardKeyboardEvent): string { return `modifiers: [${modifiers.join(',')}], code: ${e.code}, keyCode: ${e.keyCode} ('${KeyCodeUtils.toString(e.keyCode)}')`; } +export function hasModifierKeys(keyStatus: { + readonly ctrlKey: boolean; + readonly shiftKey: boolean; + readonly altKey: boolean; + readonly metaKey: boolean; +}): boolean { + return keyStatus.ctrlKey || keyStatus.shiftKey || keyStatus.altKey || keyStatus.metaKey; +} + export class StandardKeyboardEvent implements IKeyboardEvent { readonly _standardKeyboardEventBrand = true; @@ -181,7 +188,7 @@ export class StandardKeyboardEvent implements IKeyboardEvent { private _computeKeybinding(): number { let key = KeyCode.Unknown; - if (this.keyCode !== KeyCode.Ctrl && this.keyCode !== KeyCode.Shift && this.keyCode !== KeyCode.Alt && this.keyCode !== KeyCode.Meta) { + if (!isModifierKey(this.keyCode)) { key = this.keyCode; } @@ -205,7 +212,7 @@ export class StandardKeyboardEvent implements IKeyboardEvent { private _computeKeyCodeChord(): KeyCodeChord { let key = KeyCode.Unknown; - if (this.keyCode !== KeyCode.Ctrl && this.keyCode !== KeyCode.Shift && this.keyCode !== KeyCode.Alt && this.keyCode !== KeyCode.Meta) { + if (!isModifierKey(this.keyCode)) { key = this.keyCode; } return new KeyCodeChord(this.ctrlKey, this.shiftKey, this.altKey, this.metaKey, key); diff --git a/code/src/vs/base/browser/markdownRenderer.ts b/code/src/vs/base/browser/markdownRenderer.ts index 71af7a09743..e3f20d96726 100644 --- a/code/src/vs/base/browser/markdownRenderer.ts +++ b/code/src/vs/base/browser/markdownRenderer.ts @@ -8,21 +8,21 @@ import { escapeDoubleQuotes, IMarkdownString, MarkdownStringTrustedOptions, pars import { markdownEscapeEscapedIcons } from '../common/iconLabels.js'; import { defaultGenerator } from '../common/idGenerator.js'; import { KeyCode } from '../common/keyCodes.js'; -import { Lazy } from '../common/lazy.js'; -import { DisposableStore } from '../common/lifecycle.js'; +import { DisposableStore, IDisposable } from '../common/lifecycle.js'; import * as marked from '../common/marked/marked.js'; import { parse } from '../common/marshalling.js'; import { FileAccess, Schemas } from '../common/network.js'; import { cloneAndChange } from '../common/objects.js'; -import { dirname, resolvePath } from '../common/resources.js'; +import { basename as pathBasename } from '../common/path.js'; +import { basename, dirname, resolvePath } from '../common/resources.js'; import { escape } from '../common/strings.js'; -import { URI } from '../common/uri.js'; +import { URI, UriComponents } from '../common/uri.js'; import * as DOM from './dom.js'; import * as domSanitize from './domSanitize.js'; import { convertTagToPlaintext } from './domSanitize.js'; import { StandardKeyboardEvent } from './keyboardEvent.js'; import { StandardMouseEvent } from './mouseEvent.js'; -import { renderLabelWithIcons } from './ui/iconLabel/iconLabels.js'; +import { renderIcon, renderLabelWithIcons } from './ui/iconLabel/iconLabels.js'; export type MarkdownActionHandler = (linkContent: string, mdStr: IMarkdownString) => void; @@ -115,13 +115,73 @@ const defaultMarkedRenderers = Object.freeze({ }, }); +/** + * Blockquote renderer that processes GitHub-style alert syntax. + * Transforms blockquotes like "> [!NOTE]" into structured alert markup with icons. + * + * Based on GitHub's alert syntax: https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts + */ +function createAlertBlockquoteRenderer(fallbackRenderer: (this: marked.Renderer, token: marked.Tokens.Blockquote) => string) { + return function (this: marked.Renderer, token: marked.Tokens.Blockquote): string { + const { tokens } = token; + // Check if this blockquote starts with alert syntax [!TYPE] + const firstToken = tokens[0]; + if (firstToken?.type !== 'paragraph') { + return fallbackRenderer.call(this, token); + } + + const paragraphTokens = firstToken.tokens; + if (!paragraphTokens || paragraphTokens.length === 0) { + return fallbackRenderer.call(this, token); + } + + const firstTextToken = paragraphTokens[0]; + if (firstTextToken?.type !== 'text') { + return fallbackRenderer.call(this, token); + } + + const pattern = /^\s*\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*?\n*/i; + const match = firstTextToken.raw.match(pattern); + if (!match) { + return fallbackRenderer.call(this, token); + } + + // Remove the alert marker from the token + firstTextToken.raw = firstTextToken.raw.replace(pattern, ''); + firstTextToken.text = firstTextToken.text.replace(pattern, ''); + + const alertIcons: Record = { + 'note': 'info', + 'tip': 'light-bulb', + 'important': 'comment', + 'warning': 'alert', + 'caution': 'stop' + }; + + const type = match[1]; + const typeCapitalized = type.charAt(0).toUpperCase() + type.slice(1).toLowerCase(); + const severity = type.toLowerCase(); + const iconHtml = renderIcon({ id: alertIcons[severity] }).outerHTML; + + // Render the remaining content + const content = this.parser.parse(tokens); + + // Return alert markup with icon and severity (skipping the first 3 characters: `

`) + return `

${iconHtml}${typeCapitalized}${content.substring(3)}

\n`; + }; +} + +export interface IRenderedMarkdown extends IDisposable { + readonly element: HTMLElement; +} + /** * Low-level way create a html element from a markdown string. * * **Note** that for most cases you should be using {@link import('../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js').MarkdownRenderer MarkdownRenderer} * which comes with support for pretty code block rendering and which uses the default way of handling links. */ -export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRenderOptions = {}, target?: HTMLElement): { element: HTMLElement; dispose: () => void } { +export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRenderOptions = {}, target?: HTMLElement): IRenderedMarkdown { const disposables = new DisposableStore(); let isDisposed = false; @@ -151,7 +211,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende } const renderedContent = document.createElement('div'); - const sanitizerConfig = getDomSanitizerConfig(markdown.isTrusted ?? false, options.sanitizerConfig ?? {}); + const sanitizerConfig = getDomSanitizerConfig(markdown, options.sanitizerConfig ?? {}); domSanitize.safeSetInnerHtml(renderedContent, renderedMarkdown, sanitizerConfig); // Rewrite links and images before potentially inserting them into the real dom @@ -160,7 +220,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende let outElement: HTMLElement; if (target) { outElement = target; - DOM.reset(target, ...renderedContent.children); + DOM.reset(target, ...renderedContent.childNodes); } else { outElement = renderedContent; } @@ -171,6 +231,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende return; } const renderedElements = new Map(tuples); + // eslint-disable-next-line no-restricted-syntax const placeholderElements = outElement.querySelectorAll(`div[data-code]`); for (const placeholderElement of placeholderElements) { const renderedElement = renderedElements.get(placeholderElement.dataset['code'] ?? ''); @@ -182,6 +243,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende }); } else if (syncCodeBlocks.length > 0) { const renderedElements = new Map(syncCodeBlocks); + // eslint-disable-next-line no-restricted-syntax const placeholderElements = outElement.querySelectorAll(`div[data-code]`); for (const placeholderElement of placeholderElements) { const renderedElement = renderedElements.get(placeholderElement.dataset['code'] ?? ''); @@ -193,6 +255,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende // Signal size changes for image tags if (options.asyncRenderCallback) { + // eslint-disable-next-line no-restricted-syntax for (const img of outElement.getElementsByTagName('img')) { const listener = disposables.add(DOM.addDisposableListener(img, 'load', () => { listener.dispose(); @@ -223,13 +286,18 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende } // Remove/disable inputs + // eslint-disable-next-line no-restricted-syntax for (const input of [...outElement.getElementsByTagName('input')]) { if (input.attributes.getNamedItem('type')?.value === 'checkbox') { input.setAttribute('disabled', ''); } else { if (options.sanitizerConfig?.replaceWithPlaintext) { const replacement = convertTagToPlaintext(input); - input.parentElement?.replaceChild(replacement, input); + if (replacement) { + input.parentElement?.replaceChild(replacement, input); + } else { + input.remove(); + } } else { input.remove(); } @@ -246,6 +314,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende } function rewriteRenderedLinks(markdown: IMarkdownString, options: MarkdownRenderOptions, root: HTMLElement) { + // eslint-disable-next-line no-restricted-syntax for (const el of root.querySelectorAll('img, audio, video, source')) { const src = el.getAttribute('src'); // Get the raw 'src' attribute value as text, not the resolved 'src' if (src) { @@ -267,6 +336,7 @@ function rewriteRenderedLinks(markdown: IMarkdownString, options: MarkdownRender } } + // eslint-disable-next-line no-restricted-syntax for (const el of root.querySelectorAll('a')) { const href = el.getAttribute('href'); // Get the raw 'href' attribute value as text, not the resolved 'href' el.setAttribute('href', ''); // Clear out href. We use the `data-href` for handling clicks instead @@ -292,6 +362,10 @@ function createMarkdownRenderer(marked: marked.Marked, options: MarkdownRenderOp renderer.link = defaultMarkedRenderers.link; renderer.paragraph = defaultMarkedRenderers.paragraph; + if (markdown.supportAlertSyntax) { + renderer.blockquote = createAlertBlockquoteRenderer(renderer.blockquote); + } + // Will collect [id, renderedElement] tuples const codeBlocks: Promise<[string, HTMLElement]>[] = []; const syncCodeBlocks: [string, HTMLElement][] = []; @@ -361,6 +435,7 @@ function activateLink(mdStr: IMarkdownString, options: MarkdownRenderOptions, ev onUnexpectedError(err); } finally { event.preventDefault(); + event.stopPropagation(); } } @@ -437,12 +512,17 @@ function resolveWithBaseUri(baseUri: URI, href: string): string { } } +type MdStrConfig = { + readonly isTrusted?: boolean | MarkdownStringTrustedOptions; + readonly baseUri?: UriComponents; +}; + function sanitizeRenderedMarkdown( renderedMarkdown: string, - isTrusted: boolean | MarkdownStringTrustedOptions, + originalMdStrConfig: MdStrConfig, options: MarkdownSanitizerConfig = {}, ): TrustedHTML { - const sanitizerConfig = getDomSanitizerConfig(isTrusted, options); + const sanitizerConfig = getDomSanitizerConfig(originalMdStrConfig, options); return domSanitize.sanitizeHtml(renderedMarkdown, sanitizerConfig); } @@ -480,6 +560,7 @@ export const allowedMarkdownHtmlAttributes = Object.freeze unescapeInfo.get(m) ?? m) .trim(); @@ -654,15 +750,22 @@ function createPlainTextRenderer(): marked.Renderer { }; return renderer; } -const plainTextRenderer = new Lazy(createPlainTextRenderer); -const plainTextWithCodeBlocksRenderer = new Lazy(() => { - const renderer = createPlainTextRenderer(); - renderer.code = ({ text }: marked.Tokens.Code): string => { - return `\n\`\`\`\n${escape(text)}\n\`\`\`\n`; - }; - return renderer; -}); +const codeBlockFences = ({ text }: marked.Tokens.Code): string => { + return `\n\`\`\`\n${escape(text)}\n\`\`\`\n`; +}; + +const linkFormatter = ({ text, href }: marked.Tokens.Link): string => { + try { + if (href) { + const uri = URI.parse(href); + return text.trim() || basename(uri); + } + } catch (e) { + return text.trim() || pathBasename(href); + } + return text; +}; function mergeRawTokenText(tokens: marked.Token[]): string { let mergedTokenText = ''; @@ -935,7 +1038,7 @@ function completeWithString(tokens: marked.Token[] | marked.Token, closingString // If it was completed correctly, this should be a single token. // Expecting either a Paragraph or a List const trimmedRawText = shouldTrim ? mergedRawText.trimEnd() : mergedRawText; - return marked.lexer(trimmedRawText + closingString)[0] as marked.Token; + return marked.lexer(trimmedRawText + closingString)[0]; } function completeTable(tokens: marked.Token[]): marked.Token[] | undefined { @@ -977,4 +1080,3 @@ function completeTable(tokens: marked.Token[]): marked.Token[] | undefined { return undefined; } - diff --git a/code/src/vs/base/browser/mouseEvent.ts b/code/src/vs/base/browser/mouseEvent.ts index 3cad09d7dcc..2f8c99ff327 100644 --- a/code/src/vs/base/browser/mouseEvent.ts +++ b/code/src/vs/base/browser/mouseEvent.ts @@ -97,6 +97,7 @@ export class DragMouseEvent extends StandardMouseEvent { constructor(targetWindow: Window, e: MouseEvent) { super(targetWindow, e); + // eslint-disable-next-line local/code-no-any-casts this.dataTransfer = (e).dataTransfer; } } @@ -134,6 +135,7 @@ export class StandardWheelEvent { constructor(e: IMouseWheelEvent | null, deltaX: number = 0, deltaY: number = 0) { this.browserEvent = e || null; + // eslint-disable-next-line local/code-no-any-casts this.target = e ? (e.target || (e).targetNode || e.srcElement) : null; this.deltaY = deltaY; @@ -150,7 +152,9 @@ export class StandardWheelEvent { if (e) { // Old (deprecated) wheel events + // eslint-disable-next-line local/code-no-any-casts const e1 = e; + // eslint-disable-next-line local/code-no-any-casts const e2 = e; const devicePixelRatio = e.view?.devicePixelRatio || 1; diff --git a/code/src/vs/base/browser/pixelRatio.ts b/code/src/vs/base/browser/pixelRatio.ts index 7ff456e5aa3..d2d93b66f30 100644 --- a/code/src/vs/base/browser/pixelRatio.ts +++ b/code/src/vs/base/browser/pixelRatio.ts @@ -7,6 +7,14 @@ import { getWindowId, onDidUnregisterWindow } from './dom.js'; import { Emitter, Event } from '../common/event.js'; import { Disposable, markAsSingleton } from '../common/lifecycle.js'; +type BackingStoreContext = CanvasRenderingContext2D & { + webkitBackingStorePixelRatio?: number; + mozBackingStorePixelRatio?: number; + msBackingStorePixelRatio?: number; + oBackingStorePixelRatio?: number; + backingStorePixelRatio?: number; +}; + /** * See https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio#monitoring_screen_resolution_or_zoom_level_changes */ @@ -67,13 +75,13 @@ class PixelRatioMonitorImpl extends Disposable implements IPixelRatioMonitor { } private _getPixelRatio(targetWindow: Window): number { - const ctx: any = document.createElement('canvas').getContext('2d'); + const ctx = document.createElement('canvas').getContext('2d') as BackingStoreContext | null; const dpr = targetWindow.devicePixelRatio || 1; - const bsr = ctx.webkitBackingStorePixelRatio || - ctx.mozBackingStorePixelRatio || - ctx.msBackingStorePixelRatio || - ctx.oBackingStorePixelRatio || - ctx.backingStorePixelRatio || 1; + const bsr = ctx?.webkitBackingStorePixelRatio || + ctx?.mozBackingStorePixelRatio || + ctx?.msBackingStorePixelRatio || + ctx?.oBackingStorePixelRatio || + ctx?.backingStorePixelRatio || 1; return dpr / bsr; } } diff --git a/code/src/vs/base/browser/trustedTypes.ts b/code/src/vs/base/browser/trustedTypes.ts index 60e915362cb..310ce79f0f9 100644 --- a/code/src/vs/base/browser/trustedTypes.ts +++ b/code/src/vs/base/browser/trustedTypes.ts @@ -4,19 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import { onUnexpectedError } from '../common/errors.js'; +import { getMonacoEnvironment } from './browser.js'; + +type TrustedTypePolicyOptions = import('trusted-types/lib/index.d.ts').TrustedTypePolicyOptions; export function createTrustedTypesPolicy( policyName: string, policyOptions?: Options, -): undefined | Pick, 'name' | Extract> { +): undefined | Pick> { - interface IMonacoEnvironment { - createTrustedTypesPolicy( - policyName: string, - policyOptions?: Options, - ): undefined | Pick, 'name' | Extract>; - } - const monacoEnvironment: IMonacoEnvironment | undefined = (globalThis as any).MonacoEnvironment; + const monacoEnvironment = getMonacoEnvironment(); if (monacoEnvironment?.createTrustedTypesPolicy) { try { @@ -27,6 +24,7 @@ export function createTrustedTypesPolicy { if (!this._element.classList.contains('disabled')) { - this.updateBackground(true); + this.updateStyles(true); } })); this._register(addDisposableListener(this._element, EventType.MOUSE_OUT, e => { - this.updateBackground(false); // restore standard styles + this.updateStyles(false); // restore standard styles })); // Also set hover background when button is focused for feedback this.focusTracker = this._register(trackFocus(this._element)); - this._register(this.focusTracker.onDidFocus(() => { if (this.enabled) { this.updateBackground(true); } })); - this._register(this.focusTracker.onDidBlur(() => { if (this.enabled) { this.updateBackground(false); } })); + this._register(this.focusTracker.onDidFocus(() => { if (this.enabled) { this.updateStyles(true); } })); + this._register(this.focusTracker.onDidBlur(() => { if (this.enabled) { this.updateStyles(false); } })); } public override dispose(): void { @@ -218,16 +218,19 @@ export class Button extends Disposable implements IButton { return elements; } - private updateBackground(hover: boolean): void { + private updateStyles(hover: boolean): void { let background; + let foreground; if (this.options.secondary) { background = hover ? this.options.buttonSecondaryHoverBackground : this.options.buttonSecondaryBackground; + foreground = this.options.buttonSecondaryForeground; } else { background = hover ? this.options.buttonHoverBackground : this.options.buttonBackground; + foreground = this.options.buttonForeground; } - if (background) { - this._element.style.backgroundColor = background; - } + + this._element.style.backgroundColor = background || ''; + this._element.style.color = foreground || ''; } get element(): HTMLElement { @@ -251,6 +254,7 @@ export class Button extends Disposable implements IButton { rendered.dispose(); // Don't include outer `

` + // eslint-disable-next-line no-restricted-syntax const root = rendered.element.querySelector('p')?.innerHTML; if (root) { safeSetInnerHtml(labelElement, root, buttonSanitizerConfig); @@ -326,6 +330,12 @@ export class Button extends Disposable implements IButton { return !this._element.classList.contains('disabled'); } + set secondary(value: boolean) { + this._element.classList.toggle('secondary', value); + (this.options as { secondary?: boolean }).secondary = value; + this.updateStyles(false); + } + set checked(value: boolean) { if (value) { this._element.classList.add('checked'); @@ -371,7 +381,7 @@ export interface IButtonWithDropdownOptions extends IButtonOptions { export class ButtonWithDropdown extends Disposable implements IButton { readonly primaryButton: Button; - private readonly action: Action; + private readonly action: IAction; readonly dropdownButton: Button; private readonly separatorContainer: HTMLDivElement; private readonly separator: HTMLDivElement; @@ -393,7 +403,7 @@ export class ButtonWithDropdown extends Disposable implements IButton { this.primaryButton = this._register(new Button(this.element, options)); this._register(this.primaryButton.onDidClick(e => this._onDidClick.fire(e))); - this.action = this._register(new Action('primaryAction', renderAsPlaintext(this.primaryButton.label), undefined, true, async () => this._onDidClick.fire(undefined))); + this.action = toAction({ id: 'primaryAction', label: renderAsPlaintext(this.primaryButton.label), run: async () => this._onDidClick.fire(undefined) }); this.separatorContainer = document.createElement('div'); this.separatorContainer.classList.add('monaco-button-dropdown-separator'); @@ -621,6 +631,8 @@ export class ButtonWithIcon extends Button { public get labelElement() { return this._mdlabelElement; } + public get iconElement() { return this._iconElement; } + constructor(container: HTMLElement, options: IButtonOptions) { super(container, options); @@ -652,6 +664,7 @@ export class ButtonWithIcon extends Button { const rendered = renderMarkdown(value, undefined, document.createElement('span')); rendered.dispose(); + // eslint-disable-next-line no-restricted-syntax const root = rendered.element.querySelector('p')?.innerHTML; if (root) { safeSetInnerHtml(this._mdlabelElement, root, buttonSanitizerConfig); diff --git a/code/src/vs/base/browser/ui/codicons/codicon/codicon-modifiers.css b/code/src/vs/base/browser/ui/codicons/codicon/codicon-modifiers.css index 9666216f6ae..71b1dd3ef41 100644 --- a/code/src/vs/base/browser/ui/codicons/codicon/codicon-modifiers.css +++ b/code/src/vs/base/browser/ui/codicons/codicon/codicon-modifiers.css @@ -16,7 +16,9 @@ .codicon-sync.codicon-modifier-spin, .codicon-loading.codicon-modifier-spin, .codicon-gear.codicon-modifier-spin, -.codicon-notebook-state-executing.codicon-modifier-spin { +.codicon-notebook-state-executing.codicon-modifier-spin, +.codicon-loading, +.codicon-tree-item-loading::before { /* Use steps to throttle FPS to reduce CPU usage */ animation: codicon-spin 1.5s steps(30) infinite; } @@ -24,10 +26,3 @@ .codicon-modifier-disabled { opacity: 0.4; } - -/* custom speed & easing for loading icon */ -.codicon-loading, -.codicon-tree-item-loading::before { - animation-duration: 1s !important; - animation-timing-function: cubic-bezier(0.53, 0.21, 0.29, 0.67) !important; -} diff --git a/code/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/code/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 9076b3a707f..e7e46096e12 100644 Binary files a/code/src/vs/base/browser/ui/codicons/codicon/codicon.ttf and b/code/src/vs/base/browser/ui/codicons/codicon/codicon.ttf differ diff --git a/code/src/vs/base/browser/ui/countBadge/countBadge.css b/code/src/vs/base/browser/ui/countBadge/countBadge.css index eb0c0837ee9..9fb4d0bee48 100644 --- a/code/src/vs/base/browser/ui/countBadge/countBadge.css +++ b/code/src/vs/base/browser/ui/countBadge/countBadge.css @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ .monaco-count-badge { - padding: 3px 6px; + padding: 3px 5px; border-radius: 11px; font-size: 11px; min-width: 18px; diff --git a/code/src/vs/base/browser/ui/dialog/dialog.ts b/code/src/vs/base/browser/ui/dialog/dialog.ts index b569359a06c..6c972e7866b 100644 --- a/code/src/vs/base/browser/ui/dialog/dialog.ts +++ b/code/src/vs/base/browser/ui/dialog/dialog.ts @@ -136,6 +136,7 @@ export class Dialog extends Disposable { const customFooter = this.footerContainer.appendChild($('#monaco-dialog-footer.dialog-footer')); this.options.renderFooter(customFooter); + // eslint-disable-next-line no-restricted-syntax for (const el of this.footerContainer.querySelectorAll('a')) { el.tabIndex = 0; } @@ -177,6 +178,7 @@ export class Dialog extends Disposable { const customBody = this.messageContainer.appendChild($('#monaco-dialog-message-body.dialog-message-body')); this.options.renderBody(customBody); + // eslint-disable-next-line no-restricted-syntax for (const el of this.messageContainer.querySelectorAll('a')) { el.tabIndex = 0; } @@ -378,6 +380,7 @@ export class Dialog extends Disposable { let focusedIndex = -1; if (this.messageContainer) { + // eslint-disable-next-line no-restricted-syntax const links = this.messageContainer.querySelectorAll('a'); for (const link of links) { focusableElements.push(link); @@ -422,6 +425,7 @@ export class Dialog extends Disposable { } if (this.footerContainer) { + // eslint-disable-next-line no-restricted-syntax const links = this.footerContainer.querySelectorAll('a'); for (const link of links) { focusableElements.push(link); @@ -562,6 +566,7 @@ export class Dialog extends Disposable { this.element.style.border = border; if (linkFgColor) { + // eslint-disable-next-line no-restricted-syntax for (const el of [...this.messageContainer.getElementsByTagName('a'), ...this.footerContainer?.getElementsByTagName('a') ?? []]) { el.style.color = linkFgColor; } diff --git a/code/src/vs/base/browser/ui/dropdown/dropdown.ts b/code/src/vs/base/browser/ui/dropdown/dropdown.ts index 4bb319918f3..9476e374949 100644 --- a/code/src/vs/base/browser/ui/dropdown/dropdown.ts +++ b/code/src/vs/base/browser/ui/dropdown/dropdown.ts @@ -61,9 +61,8 @@ export class BaseDropdown extends ActionRunner { for (const event of [EventType.MOUSE_DOWN, GestureEventType.Tap]) { this._register(addDisposableListener(this._label, event, e => { - if (isMouseEvent(e) && (e.detail > 1 || e.button !== 0)) { + if (isMouseEvent(e) && e.button !== 0) { // prevent right click trigger to allow separate context menu (https://github.com/microsoft/vscode/issues/151064) - // prevent multiple clicks to open multiple context menus (https://github.com/microsoft/vscode/issues/41363) return; } diff --git a/code/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts b/code/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts index 1842d6444d4..6b3610b67c1 100644 --- a/code/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts +++ b/code/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts @@ -73,6 +73,7 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { const labelRenderer: ILabelRenderer = (el: HTMLElement): IDisposable | null => { this.element = append(el, $('a.action-label')); + this.setAriaLabelAttributes(this.element); return this.renderLabel(this.element); }; diff --git a/code/src/vs/base/browser/ui/findinput/findInput.ts b/code/src/vs/base/browser/ui/findinput/findInput.ts index 2182b0bd2cb..2518cf4250d 100644 --- a/code/src/vs/base/browser/ui/findinput/findInput.ts +++ b/code/src/vs/base/browser/ui/findinput/findInput.ts @@ -13,11 +13,13 @@ import { HistoryInputBox, IInputBoxStyles, IInputValidator, IMessage as InputBox import { Widget } from '../widget.js'; import { Emitter, Event } from '../../../common/event.js'; import { KeyCode } from '../../../common/keyCodes.js'; +import { IAction } from '../../../common/actions.js'; +import type { IActionViewItemProvider } from '../actionbar/actionbar.js'; import './findInput.css'; import * as nls from '../../../../nls.js'; import { DisposableStore, MutableDisposable } from '../../../common/lifecycle.js'; -import { createInstantHoverDelegate } from '../hover/hoverDelegateFactory.js'; import { IHistory } from '../../../common/history.js'; +import type { IHoverLifecycleOptions } from '../hover/hover.js'; export interface IFindInputOptions { @@ -34,10 +36,13 @@ export interface IFindInputOptions { readonly appendWholeWordsLabel?: string; readonly appendRegexLabel?: string; readonly additionalToggles?: Toggle[]; + readonly actions?: ReadonlyArray; + readonly actionViewItemProvider?: IActionViewItemProvider; readonly showHistoryHint?: () => boolean; readonly toggleStyles: IToggleStyles; readonly inputBoxStyles: IInputBoxStyles; readonly history?: IHistory; + readonly hoverLifecycleOptions?: IHoverLifecycleOptions; } const NLS_DEFAULT_LABEL = nls.localize('defaultLabel', "input"); @@ -111,16 +116,17 @@ export class FindInput extends Widget { flexibleWidth, flexibleMaxHeight, inputBoxStyles: options.inputBoxStyles, - history: options.history + history: options.history, + actions: options.actions, + actionViewItemProvider: options.actionViewItemProvider })); - const hoverDelegate = this._register(createInstantHoverDelegate()); - if (this.showCommonFindToggles) { + const hoverLifecycleOptions: IHoverLifecycleOptions = options?.hoverLifecycleOptions || { groupId: 'find-input' }; this.regex = this._register(new RegexToggle({ appendTitle: appendRegexLabel, isChecked: false, - hoverDelegate, + hoverLifecycleOptions, ...options.toggleStyles })); this._register(this.regex.onChange(viaKeyboard => { @@ -137,7 +143,7 @@ export class FindInput extends Widget { this.wholeWords = this._register(new WholeWordsToggle({ appendTitle: appendWholeWordsLabel, isChecked: false, - hoverDelegate, + hoverLifecycleOptions, ...options.toggleStyles })); this._register(this.wholeWords.onChange(viaKeyboard => { @@ -151,7 +157,7 @@ export class FindInput extends Widget { this.caseSensitive = this._register(new CaseSensitiveToggle({ appendTitle: appendCaseSensitiveLabel, isChecked: false, - hoverDelegate, + hoverLifecycleOptions, ...options.toggleStyles })); this._register(this.caseSensitive.onChange(viaKeyboard => { @@ -307,6 +313,10 @@ export class FindInput extends Widget { this.updateInputBoxPadding(); } + public setActions(actions: ReadonlyArray | undefined, actionViewItemProvider?: IActionViewItemProvider): void { + this.inputBox.setActions(actions, actionViewItemProvider); + } + private updateInputBoxPadding(controlsHidden = false) { if (controlsHidden) { this.inputBox.paddingRight = 0; diff --git a/code/src/vs/base/browser/ui/findinput/findInputToggles.ts b/code/src/vs/base/browser/ui/findinput/findInputToggles.ts index c13fcafe5f8..b2eec09e331 100644 --- a/code/src/vs/base/browser/ui/findinput/findInputToggles.ts +++ b/code/src/vs/base/browser/ui/findinput/findInputToggles.ts @@ -3,11 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getDefaultHoverDelegate } from '../hover/hoverDelegateFactory.js'; -import { IHoverDelegate } from '../hover/hoverDelegate.js'; import { Toggle } from '../toggle/toggle.js'; import { Codicon } from '../../../common/codicons.js'; import * as nls from '../../../../nls.js'; +import { type IHoverLifecycleOptions } from '../hover/hover.js'; export interface IFindInputToggleOpts { readonly appendTitle: string; @@ -15,7 +14,7 @@ export interface IFindInputToggleOpts { readonly inputActiveOptionBorder: string | undefined; readonly inputActiveOptionForeground: string | undefined; readonly inputActiveOptionBackground: string | undefined; - readonly hoverDelegate?: IHoverDelegate; + readonly hoverLifecycleOptions?: IHoverLifecycleOptions; } const NLS_CASE_SENSITIVE_TOGGLE_LABEL = nls.localize('caseDescription', "Match Case"); @@ -28,7 +27,7 @@ export class CaseSensitiveToggle extends Toggle { icon: Codicon.caseSensitive, title: NLS_CASE_SENSITIVE_TOGGLE_LABEL + opts.appendTitle, isChecked: opts.isChecked, - hoverDelegate: opts.hoverDelegate ?? getDefaultHoverDelegate('element'), + hoverLifecycleOptions: opts.hoverLifecycleOptions, inputActiveOptionBorder: opts.inputActiveOptionBorder, inputActiveOptionForeground: opts.inputActiveOptionForeground, inputActiveOptionBackground: opts.inputActiveOptionBackground @@ -42,7 +41,7 @@ export class WholeWordsToggle extends Toggle { icon: Codicon.wholeWord, title: NLS_WHOLE_WORD_TOGGLE_LABEL + opts.appendTitle, isChecked: opts.isChecked, - hoverDelegate: opts.hoverDelegate ?? getDefaultHoverDelegate('element'), + hoverLifecycleOptions: opts.hoverLifecycleOptions, inputActiveOptionBorder: opts.inputActiveOptionBorder, inputActiveOptionForeground: opts.inputActiveOptionForeground, inputActiveOptionBackground: opts.inputActiveOptionBackground @@ -56,7 +55,7 @@ export class RegexToggle extends Toggle { icon: Codicon.regex, title: NLS_REGEX_TOGGLE_LABEL + opts.appendTitle, isChecked: opts.isChecked, - hoverDelegate: opts.hoverDelegate ?? getDefaultHoverDelegate('element'), + hoverLifecycleOptions: opts.hoverLifecycleOptions, inputActiveOptionBorder: opts.inputActiveOptionBorder, inputActiveOptionForeground: opts.inputActiveOptionForeground, inputActiveOptionBackground: opts.inputActiveOptionBackground diff --git a/code/src/vs/base/browser/ui/findinput/replaceInput.ts b/code/src/vs/base/browser/ui/findinput/replaceInput.ts index 018a26fc1bd..10c5b47b653 100644 --- a/code/src/vs/base/browser/ui/findinput/replaceInput.ts +++ b/code/src/vs/base/browser/ui/findinput/replaceInput.ts @@ -16,8 +16,8 @@ import { Emitter, Event } from '../../../common/event.js'; import { KeyCode } from '../../../common/keyCodes.js'; import './findInput.css'; import * as nls from '../../../../nls.js'; -import { getDefaultHoverDelegate } from '../hover/hoverDelegateFactory.js'; import { IHistory } from '../../../common/history.js'; +import { type IHoverLifecycleOptions } from '../hover/hover.js'; export interface IReplaceInputOptions { @@ -28,6 +28,7 @@ export interface IReplaceInputOptions { readonly flexibleHeight?: boolean; readonly flexibleWidth?: boolean; readonly flexibleMaxHeight?: number; + readonly hoverLifecycleOptions?: IHoverLifecycleOptions; readonly appendPreserveCaseLabel?: string; readonly history?: IHistory; @@ -46,7 +47,7 @@ class PreserveCaseToggle extends Toggle { icon: Codicon.preserveCase, title: NLS_PRESERVE_CASE_LABEL + opts.appendTitle, isChecked: opts.isChecked, - hoverDelegate: opts.hoverDelegate ?? getDefaultHoverDelegate('element'), + hoverLifecycleOptions: opts.hoverLifecycleOptions, inputActiveOptionBorder: opts.inputActiveOptionBorder, inputActiveOptionForeground: opts.inputActiveOptionForeground, inputActiveOptionBackground: opts.inputActiveOptionBackground, @@ -120,6 +121,7 @@ export class ReplaceInput extends Widget { this.preserveCase = this._register(new PreserveCaseToggle({ appendTitle: appendPreserveCaseLabel, isChecked: false, + hoverLifecycleOptions: options.hoverLifecycleOptions, ...options.toggleStyles })); this._register(this.preserveCase.onChange(viaKeyboard => { diff --git a/code/src/vs/base/browser/ui/grid/grid.ts b/code/src/vs/base/browser/ui/grid/grid.ts index 882c2e980fb..60b2666cf26 100644 --- a/code/src/vs/base/browser/ui/grid/grid.ts +++ b/code/src/vs/base/browser/ui/grid/grid.ts @@ -63,6 +63,7 @@ export interface GridBranchNode { export type GridNode = GridLeafNode | GridBranchNode; export function isGridBranchNode(node: GridNode): node is GridBranchNode { + // eslint-disable-next-line local/code-no-any-casts return !!(node as any).children; } @@ -869,7 +870,9 @@ function isGridBranchNodeDescriptor(nodeDescriptor: GridNodeDescriptor): n } export function sanitizeGridNodeDescriptor(nodeDescriptor: GridNodeDescriptor, rootNode: boolean): void { + // eslint-disable-next-line local/code-no-any-casts if (!rootNode && (nodeDescriptor as any).groups && (nodeDescriptor as any).groups.length <= 1) { + // eslint-disable-next-line local/code-no-any-casts (nodeDescriptor as any).groups = undefined; } diff --git a/code/src/vs/base/browser/ui/grid/gridview.ts b/code/src/vs/base/browser/ui/grid/gridview.ts index bf3d737867f..46821d6fcb8 100644 --- a/code/src/vs/base/browser/ui/grid/gridview.ts +++ b/code/src/vs/base/browser/ui/grid/gridview.ts @@ -193,6 +193,7 @@ export interface GridBranchNode { export type GridNode = GridLeafNode | GridBranchNode; export function isGridBranchNode(node: GridNode): node is GridBranchNode { + // eslint-disable-next-line local/code-no-any-casts return !!(node as any).children; } @@ -1716,7 +1717,7 @@ export class GridView implements IDisposable { const height = json.height; const result = new GridView(options); - result._deserialize(json.root as ISerializedBranchNode, orientation, deserializer, height); + result._deserialize(json.root, orientation, deserializer, height); return result; } @@ -1728,7 +1729,7 @@ export class GridView implements IDisposable { private _deserializeNode(node: ISerializedNode, orientation: Orientation, deserializer: IViewDeserializer, orthogonalSize: number): Node { let result: Node; if (node.type === 'branch') { - const serializedChildren = node.data as ISerializedNode[]; + const serializedChildren = node.data; const children = serializedChildren.map(serializedChild => { return { node: this._deserializeNode(serializedChild, orthogonal(orientation), deserializer, node.size), diff --git a/code/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts b/code/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts index 0c6cb1f1fa7..006a832f11e 100644 --- a/code/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts +++ b/code/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts @@ -22,12 +22,6 @@ export interface IHighlight { } export interface IHighlightedLabelOptions { - - /** - * Whether the label supports rendering icons. - */ - readonly supportIcons?: boolean; - readonly hoverDelegate?: IHoverDelegate; } @@ -41,7 +35,6 @@ export class HighlightedLabel extends Disposable { private text: string = ''; private title: string = ''; private highlights: readonly IHighlight[] = []; - private supportIcons: boolean; private didEverRender: boolean = false; private customHover: IManagedHover | undefined; @@ -53,7 +46,6 @@ export class HighlightedLabel extends Disposable { constructor(container: HTMLElement, private readonly options?: IHighlightedLabelOptions) { super(); - this.supportIcons = options?.supportIcons ?? false; this.domNode = dom.append(container, dom.$('span.monaco-highlighted-label')); } @@ -73,7 +65,7 @@ export class HighlightedLabel extends Disposable { * @param escapeNewLines Whether to escape new lines. * @returns */ - set(text: string | undefined, highlights: readonly IHighlight[] = [], title: string = '', escapeNewLines?: boolean) { + set(text: string | undefined, highlights: readonly IHighlight[] = [], title: string = '', escapeNewLines?: boolean, supportIcons?: boolean) { if (!text) { text = ''; } @@ -90,10 +82,10 @@ export class HighlightedLabel extends Disposable { this.text = text; this.title = title; this.highlights = highlights; - this.render(); + this.render(supportIcons); } - private render(): void { + private render(supportIcons?: boolean): void { const children: Array = []; let pos = 0; @@ -105,7 +97,7 @@ export class HighlightedLabel extends Disposable { if (pos < highlight.start) { const substring = this.text.substring(pos, highlight.start); - if (this.supportIcons) { + if (supportIcons) { children.push(...renderLabelWithIcons(substring)); } else { children.push(substring); @@ -114,7 +106,7 @@ export class HighlightedLabel extends Disposable { } const substring = this.text.substring(pos, highlight.end); - const element = dom.$('span.highlight', undefined, ...this.supportIcons ? renderLabelWithIcons(substring) : [substring]); + const element = dom.$('span.highlight', undefined, ...supportIcons ? renderLabelWithIcons(substring) : [substring]); if (highlight.extraClasses) { element.classList.add(...highlight.extraClasses); @@ -126,7 +118,7 @@ export class HighlightedLabel extends Disposable { if (pos < this.text.length) { const substring = this.text.substring(pos,); - if (this.supportIcons) { + if (supportIcons) { children.push(...renderLabelWithIcons(substring)); } else { children.push(substring); diff --git a/code/src/vs/base/browser/ui/hover/hover.ts b/code/src/vs/base/browser/ui/hover/hover.ts index 6c682c3c083..50510f6e9a6 100644 --- a/code/src/vs/base/browser/ui/hover/hover.ts +++ b/code/src/vs/base/browser/ui/hover/hover.ts @@ -148,6 +148,17 @@ export interface IHoverWidget extends IDisposable { readonly isDisposed: boolean; } +export const enum HoverStyle { + /** + * The hover is anchored below the element with a pointer above it pointing at the target. + */ + Pointer = 1, + /** + * The hover is anchored to the bottom right of the cursor's location. + */ + Mouse = 2, +} + export interface IHoverOptions { /** * The content to display in the primary section of the hover. The type of text determines the @@ -205,6 +216,11 @@ export interface IHoverOptions { */ trapFocus?: boolean; + /** + * The style of the hover, this sets default values of {@link position} and {@link appearance}: + */ + style?: HoverStyle; + /** * Options that defines where the hover is positioned. */ diff --git a/code/src/vs/base/browser/ui/hover/hoverWidget.css b/code/src/vs/base/browser/ui/hover/hoverWidget.css index 11f1fdccc7c..85379221cf2 100644 --- a/code/src/vs/base/browser/ui/hover/hoverWidget.css +++ b/code/src/vs/base/browser/ui/hover/hoverWidget.css @@ -181,19 +181,6 @@ color: var(--vscode-textLink-activeForeground); } -/** - * Spans in markdown hovers need a margin-bottom to avoid looking cramped: - * https://github.com/microsoft/vscode/issues/101496 - - * This was later refined to only apply when the last child of a rendered markdown block (before the - * border or a `hr`) uses background color: - * https://github.com/microsoft/vscode/issues/228136 - */ -.monaco-hover .markdown-hover .hover-contents:not(.code-hover-contents):not(.html-hover-contents) p:last-child [style*="background-color"] { - margin-bottom: 4px; - display: inline-block; -} - /** * Add a slight margin to try vertically align codicons with any text * https://github.com/microsoft/vscode/issues/221359 diff --git a/code/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/code/src/vs/base/browser/ui/iconLabel/iconLabel.ts index b3eedce65c8..468667aabc0 100644 --- a/code/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/code/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -16,6 +16,7 @@ import { getDefaultHoverDelegate } from '../hover/hoverDelegateFactory.js'; import type { IManagedHoverTooltipMarkdownString } from '../hover/hover.js'; import { getBaseLayerHoverDelegate } from '../hover/hoverDelegate2.js'; import { URI } from '../../../common/uri.js'; +import { ThemeIcon } from '../../../common/themables.js'; export interface IIconLabelCreationOptions { readonly supportHighlights?: boolean; @@ -31,6 +32,7 @@ export interface IIconLabelValueOptions { suffix?: string; hideIcon?: boolean; extraClasses?: readonly string[]; + bold?: boolean; italic?: boolean; strikethrough?: boolean; matches?: readonly IMatch[]; @@ -39,7 +41,8 @@ export interface IIconLabelValueOptions { disabledCommand?: boolean; readonly separator?: string; readonly domId?: string; - iconPath?: URI; + iconPath?: URI | ThemeIcon; + supportIcons?: boolean; } class FastLabelNode { @@ -136,6 +139,10 @@ export class IconLabel extends Disposable { labelClasses.push(...options.extraClasses); } + if (options.bold) { + labelClasses.push('bold'); + } + if (options.italic) { labelClasses.push('italic'); } @@ -156,6 +163,7 @@ export class IconLabel extends Disposable { } } + // eslint-disable-next-line no-restricted-syntax const existingIconNode = this.domNode.element.querySelector('.monaco-icon-label-iconpath'); if (options?.iconPath) { let iconNode; @@ -165,7 +173,13 @@ export class IconLabel extends Disposable { } else { iconNode = existingIconNode; } - iconNode.style.backgroundImage = css.asCSSUrl(options?.iconPath); + if (ThemeIcon.isThemeIcon(options.iconPath)) { + const iconClass = ThemeIcon.asClassName(options.iconPath); + iconNode.className = `monaco-icon-label-iconpath ${iconClass}`; + iconNode.style.backgroundImage = ''; + } else { + iconNode.style.backgroundImage = css.asCSSUrl(options?.iconPath); + } iconNode.style.backgroundRepeat = 'no-repeat'; iconNode.style.backgroundPosition = 'center'; iconNode.style.backgroundSize = 'contain'; @@ -185,7 +199,8 @@ export class IconLabel extends Disposable { if (description || this.descriptionNode) { const descriptionNode = this.getOrCreateDescriptionNode(); if (descriptionNode instanceof HighlightedLabel) { - descriptionNode.set(description || '', options ? options.descriptionMatches : undefined, undefined, options?.labelEscapeNewLines); + const supportIcons = options?.supportIcons ?? this.creationOptions?.supportIcons; + descriptionNode.set(description || '', options ? options.descriptionMatches : undefined, undefined, options?.labelEscapeNewLines, supportIcons); this.setupHover(descriptionNode.element, options?.descriptionTitle); } else { descriptionNode.textContent = description && options?.labelEscapeNewLines ? HighlightedLabel.escapeNewLines(description, []) : (description || ''); @@ -247,7 +262,7 @@ export class IconLabel extends Disposable { if (!this.descriptionNode) { const descriptionContainer = this._register(new FastLabelNode(dom.append(this.labelContainer, dom.$('span.monaco-icon-description-container')))); if (this.creationOptions?.supportDescriptionHighlights) { - this.descriptionNode = this._register(new HighlightedLabel(dom.append(descriptionContainer.element, dom.$('span.label-description')), { supportIcons: !!this.creationOptions.supportIcons })); + this.descriptionNode = this._register(new HighlightedLabel(dom.append(descriptionContainer.element, dom.$('span.label-description')))); } else { this.descriptionNode = this._register(new FastLabelNode(dom.append(descriptionContainer.element, dom.$('span.label-description')))); } @@ -338,14 +353,17 @@ class LabelWithHighlights extends Disposable { this.label = label; this.options = options; + // Determine supportIcons: use option if provided, otherwise use constructor value + const supportIcons = options?.supportIcons ?? this.supportIcons; + if (typeof label === 'string') { if (!this.singleLabel) { this.container.textContent = ''; this.container.classList.remove('multiple'); - this.singleLabel = this._register(new HighlightedLabel(dom.append(this.container, dom.$('a.label-name', { id: options?.domId })), { supportIcons: this.supportIcons })); + this.singleLabel = this._register(new HighlightedLabel(dom.append(this.container, dom.$('a.label-name', { id: options?.domId })))); } - this.singleLabel.set(label, options?.matches, undefined, options?.labelEscapeNewLines); + this.singleLabel.set(label, options?.matches, undefined, options?.labelEscapeNewLines, supportIcons); } else { this.container.textContent = ''; this.container.classList.add('multiple'); @@ -360,8 +378,8 @@ class LabelWithHighlights extends Disposable { const id = options?.domId && `${options?.domId}_${i}`; const name = dom.$('a.label-name', { id, 'data-icon-label-count': label.length, 'data-icon-label-index': i, 'role': 'treeitem' }); - const highlightedLabel = this._register(new HighlightedLabel(dom.append(this.container, name), { supportIcons: this.supportIcons })); - highlightedLabel.set(l, m, undefined, options?.labelEscapeNewLines); + const highlightedLabel = this._register(new HighlightedLabel(dom.append(this.container, name))); + highlightedLabel.set(l, m, undefined, options?.labelEscapeNewLines, supportIcons); if (i < label.length - 1) { dom.append(name, dom.$('span.label-separator', undefined, separator)); diff --git a/code/src/vs/base/browser/ui/iconLabel/iconlabel.css b/code/src/vs/base/browser/ui/iconLabel/iconlabel.css index ed9278d7087..d3dfd9a586b 100644 --- a/code/src/vs/base/browser/ui/iconLabel/iconlabel.css +++ b/code/src/vs/base/browser/ui/iconLabel/iconlabel.css @@ -33,9 +33,8 @@ .monaco-icon-label-iconpath { width: 16px; - height: 16px; - padding-left: 2px; - margin-top: 2px; + height: 22px; + margin-right: 6px; display: flex; } @@ -79,6 +78,11 @@ opacity: .95; } +.monaco-icon-label.bold > .monaco-icon-label-container > .monaco-icon-name-container > .label-name, +.monaco-icon-label.bold > .monaco-icon-label-container > .monaco-icon-description-container > .label-description { + font-weight: bold; +} + .monaco-icon-label.italic > .monaco-icon-label-container > .monaco-icon-name-container > .label-name, .monaco-icon-label.italic > .monaco-icon-label-container > .monaco-icon-description-container > .label-description { font-style: italic; diff --git a/code/src/vs/base/browser/ui/inputbox/inputBox.ts b/code/src/vs/base/browser/ui/inputbox/inputBox.ts index 44af8e92aee..3438890dd23 100644 --- a/code/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/code/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -8,7 +8,7 @@ import * as cssJs from '../../cssValue.js'; import { DomEmitter } from '../../event.js'; import { renderFormattedText, renderText } from '../../formattedTextRenderer.js'; import { IHistoryNavigationWidget } from '../../history.js'; -import { ActionBar } from '../actionbar/actionbar.js'; +import { ActionBar, IActionViewItemProvider } from '../actionbar/actionbar.js'; import * as aria from '../aria/aria.js'; import { AnchorAlignment, IContextViewProvider } from '../contextview/contextview.js'; import { getBaseLayerHoverDelegate } from '../hover/hoverDelegate2.js'; @@ -37,6 +37,7 @@ export interface IInputOptions { readonly flexibleWidth?: boolean; readonly flexibleMaxHeight?: number; readonly actions?: ReadonlyArray; + readonly actionViewItemProvider?: IActionViewItemProvider; readonly inputBoxStyles: IInputBoxStyles; readonly history?: IHistory; } @@ -206,13 +207,29 @@ export class InputBox extends Widget { // Support actions if (this.options.actions) { - this.actionbar = this._register(new ActionBar(this.element)); + this.actionbar = this._register(new ActionBar(this.element, { + actionViewItemProvider: this.options.actionViewItemProvider + })); this.actionbar.push(this.options.actions, { icon: true, label: false }); } this.applyStyles(); } + public setActions(actions: ReadonlyArray | undefined, actionViewItemProvider?: IActionViewItemProvider): void { + if (this.actionbar) { + this.actionbar.clear(); + if (actions) { + this.actionbar.push(actions, { icon: true, label: false }); + } + } else if (actions) { + this.actionbar = this._register(new ActionBar(this.element, { + actionViewItemProvider: actionViewItemProvider ?? this.options.actionViewItemProvider + })); + this.actionbar.push(actions, { icon: true, label: false }); + } + } + protected onBlur(): void { this._hideMessage(); if (this.options.showPlaceholderOnFocus) { @@ -536,6 +553,12 @@ export class InputBox extends Widget { this.state = 'idle'; } + private layoutMessage(): void { + if (this.state === 'open' && this.contextViewProvider) { + this.contextViewProvider.layout(); + } + } + private onValueChange(): void { this._onDidChange.fire(this.value); @@ -586,6 +609,7 @@ export class InputBox extends Widget { public layout(): void { if (!this.mirror) { + this.layoutMessage(); return; } @@ -597,6 +621,8 @@ export class InputBox extends Widget { this.input.style.height = this.cachedHeight + 'px'; this._onDidHeightChange.fire(this.cachedContentHeight); } + + this.layoutMessage(); } public insertAtCursor(text: string): void { diff --git a/code/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.css b/code/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.css index 7f203773f5e..b528d495fb9 100644 --- a/code/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.css +++ b/code/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.css @@ -10,11 +10,13 @@ } .monaco-keybinding > .monaco-keybinding-key { - display: inline-block; + display: inline-flex; + align-items: center; border-style: solid; border-width: 1px; border-radius: 3px; - vertical-align: middle; + justify-content: center; + min-width: 12px; font-size: 11px; padding: 3px 5px; margin: 0 2px; diff --git a/code/src/vs/base/browser/ui/list/list.css b/code/src/vs/base/browser/ui/list/list.css index 672f03e42f0..512d74df27b 100644 --- a/code/src/vs/base/browser/ui/list/list.css +++ b/code/src/vs/base/browser/ui/list/list.css @@ -8,6 +8,7 @@ height: 100%; width: 100%; white-space: nowrap; + overflow: hidden; } .monaco-list.mouse-support { diff --git a/code/src/vs/base/browser/ui/list/listPaging.ts b/code/src/vs/base/browser/ui/list/listPaging.ts index cb486cfaaba..04c2b8de2ef 100644 --- a/code/src/vs/base/browser/ui/list/listPaging.ts +++ b/code/src/vs/base/browser/ui/list/listPaging.ts @@ -6,7 +6,7 @@ import { range } from '../../../common/arrays.js'; import { CancellationTokenSource } from '../../../common/cancellation.js'; import { Event } from '../../../common/event.js'; -import { Disposable, IDisposable } from '../../../common/lifecycle.js'; +import { Disposable, DisposableStore, IDisposable } from '../../../common/lifecycle.js'; import { IPagedModel } from '../../../common/paging.js'; import { ScrollbarVisibility } from '../../../common/scrollable.js'; import './list.css'; @@ -122,8 +122,9 @@ function fromPagedListOptions(modelProvider: () => IPagedModel, options: I export class PagedList implements IDisposable { - private list: List; + private readonly list: List; private _model!: IPagedModel; + private readonly modelDisposables = new DisposableStore(); constructor( user: string, @@ -202,8 +203,10 @@ export class PagedList implements IDisposable { } set model(model: IPagedModel) { + this.modelDisposables.clear(); this._model = model; this.list.splice(0, this.list.length, range(model.length)); + this.modelDisposables.add(model.onDidIncrementLength(newLength => this.list.splice(this.list.length, 0, range(this.list.length, newLength)))); } get length(): number { @@ -296,5 +299,6 @@ export class PagedList implements IDisposable { dispose(): void { this.list.dispose(); + this.modelDisposables.dispose(); } } diff --git a/code/src/vs/base/browser/ui/list/listView.ts b/code/src/vs/base/browser/ui/list/listView.ts index 7fc6ea246b6..6f2a2349d5b 100644 --- a/code/src/vs/base/browser/ui/list/listView.ts +++ b/code/src/vs/base/browser/ui/list/listView.ts @@ -58,11 +58,13 @@ export const enum ListViewTargetSector { BOTTOM = 3 // [75%-100%) } +export type CheckBoxAccessibleState = boolean | 'mixed'; + export interface IListViewAccessibilityProvider { getSetSize?(element: T, index: number, listLength: number): number; getPosInSet?(element: T, index: number): number; getRole?(element: T): AriaRole | undefined; - isChecked?(element: T): boolean | IValueWithChangeEvent | undefined; + isChecked?(element: T): CheckBoxAccessibleState | IValueWithChangeEvent | undefined; } export interface IListViewOptionsUpdate { @@ -197,7 +199,7 @@ class ListViewAccessibilityProvider implements Required number; readonly getPosInSet: (element: T, index: number) => number; readonly getRole: (element: T) => AriaRole | undefined; - readonly isChecked: (element: T) => boolean | IValueWithChangeEvent | undefined; + readonly isChecked: (element: T) => CheckBoxAccessibleState | IValueWithChangeEvent | undefined; constructor(accessibilityProvider?: IListViewAccessibilityProvider) { if (accessibilityProvider?.getSetSize) { @@ -959,11 +961,12 @@ export class ListView implements IListView { item.row.domNode.setAttribute('role', role); const checked = this.accessibilityProvider.isChecked(item.element); + const toAriaState = (value: CheckBoxAccessibleState) => value === 'mixed' ? 'mixed' : String(!!value); - if (typeof checked === 'boolean') { - item.row.domNode.setAttribute('aria-checked', String(!!checked)); + if (typeof checked === 'boolean' || checked === 'mixed') { + item.row.domNode.setAttribute('aria-checked', toAriaState(checked)); } else if (checked) { - const update = (checked: boolean) => item.row!.domNode.setAttribute('aria-checked', String(!!checked)); + const update = (value: CheckBoxAccessibleState) => item.row!.domNode.setAttribute('aria-checked', toAriaState(value)); update(checked.value); item.checkedDisposable = checked.onDidChange(() => update(checked.value)); } @@ -1115,9 +1118,9 @@ export class ListView implements IListView { @memoize get onMouseOver(): Event> { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'mouseover')).event, e => this.toMouseEvent(e), this.disposables); } @memoize get onMouseMove(): Event> { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'mousemove')).event, e => this.toMouseEvent(e), this.disposables); } @memoize get onMouseOut(): Event> { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'mouseout')).event, e => this.toMouseEvent(e), this.disposables); } - @memoize get onContextMenu(): Event | IListGestureEvent> { return Event.any | IListGestureEvent>(Event.map(this.disposables.add(new DomEmitter(this.domNode, 'contextmenu')).event, e => this.toMouseEvent(e), this.disposables), Event.map(this.disposables.add(new DomEmitter(this.domNode, TouchEventType.Contextmenu)).event as Event, e => this.toGestureEvent(e), this.disposables)); } + @memoize get onContextMenu(): Event | IListGestureEvent> { return Event.any | IListGestureEvent>(Event.map(this.disposables.add(new DomEmitter(this.domNode, 'contextmenu')).event, e => this.toMouseEvent(e), this.disposables), Event.map(this.disposables.add(new DomEmitter(this.domNode, TouchEventType.Contextmenu)).event, e => this.toGestureEvent(e), this.disposables)); } @memoize get onTouchStart(): Event> { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'touchstart')).event, e => this.toTouchEvent(e), this.disposables); } - @memoize get onTap(): Event> { return Event.map(this.disposables.add(new DomEmitter(this.rowsContainer, TouchEventType.Tap)).event, e => this.toGestureEvent(e as GestureEvent), this.disposables); } + @memoize get onTap(): Event> { return Event.map(this.disposables.add(new DomEmitter(this.rowsContainer, TouchEventType.Tap)).event, e => this.toGestureEvent(e), this.disposables); } private toMouseEvent(browserEvent: MouseEvent): IListMouseEvent { const index = this.getItemIndexFromEventTarget(browserEvent.target || null); @@ -1363,7 +1366,7 @@ export class ListView implements IListView { } for (const index of feedback) { - const item = this.items[index]!; + const item = this.items[index]; item.dropTarget = true; item.row?.domNode.classList.add(dragOverEffectPosition); @@ -1371,7 +1374,7 @@ export class ListView implements IListView { this.currentDragFeedbackDisposable = toDisposable(() => { for (const index of feedback) { - const item = this.items[index]!; + const item = this.items[index]; item.dropTarget = false; item.row?.domNode.classList.remove(dragOverEffectPosition); @@ -1633,8 +1636,12 @@ export class ListView implements IListView { if (item.row) { item.row.domNode.style.height = ''; item.size = item.row.domNode.offsetHeight; - if (item.size === 0 && !isAncestor(item.row.domNode, getWindow(item.row.domNode).document.body)) { - console.warn('Measuring item node that is not in DOM! Add ListView to the DOM before measuring row height!', new Error().stack); + if (item.size === 0) { + if (!isAncestor(item.row.domNode, getWindow(item.row.domNode).document.body)) { + console.warn('Measuring item node that is not in DOM! Add ListView to the DOM before measuring row height!', new Error().stack); + } else { + console.warn('Measured item node at 0px- ensure that ListView is not display:none before measuring row height!', new Error().stack); + } } item.lastDynamicHeightWidth = this.renderWidth; return item.size - size; diff --git a/code/src/vs/base/browser/ui/list/listWidget.ts b/code/src/vs/base/browser/ui/list/listWidget.ts index f47064064c3..c637ba43c88 100644 --- a/code/src/vs/base/browser/ui/list/listWidget.ts +++ b/code/src/vs/base/browser/ui/list/listWidget.ts @@ -631,6 +631,7 @@ class DOMFocusController implements IDisposable { return; } + // eslint-disable-next-line no-restricted-syntax const tabIndexElement = focusedDomElement.querySelector('[tabIndex]'); if (!tabIndexElement || !(isHTMLElement(tabIndexElement)) || tabIndexElement.tabIndex === -1) { @@ -859,7 +860,7 @@ export interface IListAccessibilityProvider extends IListViewAccessibilityPro getWidgetAriaLabel(): string | IObservable; getWidgetRole?(): AriaRole; getAriaLevel?(element: T): number | undefined; - onDidChangeActiveDescendant?: Event; + readonly onDidChangeActiveDescendant?: Event; getActiveDescendantId?(element: T): string | undefined; } @@ -954,7 +955,7 @@ export class DefaultStyleController implements IStyleController { content.push(` .monaco-drag-image${suffix}, .monaco-list${suffix}:focus .monaco-list-row.focused, - .monaco-workbench.context-menu-visible .monaco-list${suffix}.last-focused .monaco-list-row.focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; } + .context-menu-visible .monaco-list${suffix}.last-focused .monaco-list-row.focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; } `); } @@ -1002,13 +1003,13 @@ export class DefaultStyleController implements IStyleController { content.push(` .monaco-table > .monaco-split-view2, .monaco-table > .monaco-split-view2 .monaco-sash.vertical::before, - .monaco-workbench:not(.reduce-motion) .monaco-table:hover > .monaco-split-view2, - .monaco-workbench:not(.reduce-motion) .monaco-table:hover > .monaco-split-view2 .monaco-sash.vertical::before { + .monaco-enable-motion .monaco-table:hover > .monaco-split-view2, + .monaco-enable-motion .monaco-table:hover > .monaco-split-view2 .monaco-sash.vertical::before { border-color: ${styles.tableColumnsBorder}; } - .monaco-workbench:not(.reduce-motion) .monaco-table > .monaco-split-view2, - .monaco-workbench:not(.reduce-motion) .monaco-table > .monaco-split-view2 .monaco-sash.vertical::before { + .monaco-enable-motion .monaco-table > .monaco-split-view2, + .monaco-enable-motion .monaco-table > .monaco-split-view2 .monaco-sash.vertical::before { border-color: transparent; } `); diff --git a/code/src/vs/base/browser/ui/list/rowCache.ts b/code/src/vs/base/browser/ui/list/rowCache.ts index 4ec97d40923..b1d60d1fd45 100644 --- a/code/src/vs/base/browser/ui/list/rowCache.ts +++ b/code/src/vs/base/browser/ui/list/rowCache.ts @@ -33,10 +33,7 @@ export class RowCache implements IDisposable { let isStale = false; if (result) { - isStale = this.transactionNodesPendingRemoval.has(result.domNode); - if (isStale) { - this.transactionNodesPendingRemoval.delete(result.domNode); - } + isStale = this.transactionNodesPendingRemoval.delete(result.domNode); } else { const domNode = $('.monaco-list-row'); const renderer = this.getRenderer(templateId); diff --git a/code/src/vs/base/browser/ui/menu/menu.ts b/code/src/vs/base/browser/ui/menu/menu.ts index 2ad32aa6744..f48c4488073 100644 --- a/code/src/vs/base/browser/ui/menu/menu.ts +++ b/code/src/vs/base/browser/ui/menu/menu.ts @@ -140,9 +140,9 @@ export class Menu extends ActionBar { if (options.enableMnemonics) { this._register(addDisposableListener(menuElement, EventType.KEY_DOWN, (e) => { const key = e.key.toLocaleLowerCase(); - if (this.mnemonics.has(key)) { + const actions = this.mnemonics.get(key); + if (actions !== undefined) { EventHelper.stop(e, true); - const actions = this.mnemonics.get(key)!; if (actions.length === 1) { if (actions[0] instanceof SubmenuMenuActionViewItem && actions[0].container) { @@ -398,14 +398,12 @@ export class Menu extends ActionBar { if (options.enableMnemonics) { const mnemonic = menuActionViewItem.getMnemonic(); if (mnemonic && menuActionViewItem.isEnabled()) { - let actionViewItems: BaseMenuActionViewItem[] = []; - if (this.mnemonics.has(mnemonic)) { - actionViewItems = this.mnemonics.get(mnemonic)!; + const actionViewItems = this.mnemonics.get(mnemonic); + if (actionViewItems !== undefined) { + actionViewItems.push(menuActionViewItem); + } else { + this.mnemonics.set(mnemonic, [menuActionViewItem]); } - - actionViewItems.push(menuActionViewItem); - - this.mnemonics.set(mnemonic, actionViewItems); } } @@ -423,14 +421,12 @@ export class Menu extends ActionBar { if (options.enableMnemonics) { const mnemonic = menuActionViewItem.getMnemonic(); if (mnemonic && menuActionViewItem.isEnabled()) { - let actionViewItems: BaseMenuActionViewItem[] = []; - if (this.mnemonics.has(mnemonic)) { - actionViewItems = this.mnemonics.get(mnemonic)!; + const actionViewItems = this.mnemonics.get(mnemonic); + if (actionViewItems !== undefined) { + actionViewItems.push(menuActionViewItem); + } else { + this.mnemonics.set(mnemonic, [menuActionViewItem]); } - - actionViewItems.push(menuActionViewItem); - - this.mnemonics.set(mnemonic, actionViewItems); } } @@ -903,6 +899,8 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { this.submenuContainer.style.position = 'fixed'; this.submenuContainer.style.top = '0'; this.submenuContainer.style.left = '0'; + // Fix to #263546, for submenu of treeView view/item/context z-index issue - ensure submenu appears above other elements + this.submenuContainer.style.zIndex = '1'; this.parentData.submenu = new Menu(this.submenuContainer, this.submenuActions.length ? this.submenuActions : [new EmptySubmenuAction()], this.submenuOptions, this.menuStyle); diff --git a/code/src/vs/base/browser/ui/menu/menubar.css b/code/src/vs/base/browser/ui/menu/menubar.css index 45eebe98553..17fbe89af15 100644 --- a/code/src/vs/base/browser/ui/menu/menubar.css +++ b/code/src/vs/base/browser/ui/menu/menubar.css @@ -33,7 +33,7 @@ outline: 0 !important; } -.monaco-workbench .menubar:not(.compact) > .menubar-menu-button:focus .menubar-menu-title { +.menubar:not(.compact) > .menubar-menu-button:focus .menubar-menu-title { outline-width: 1px; outline-style: solid; outline-offset: -1px; diff --git a/code/src/vs/base/browser/ui/menu/menubar.ts b/code/src/vs/base/browser/ui/menu/menubar.ts index 607dc187a74..29240b5c9a4 100644 --- a/code/src/vs/base/browser/ui/menu/menubar.ts +++ b/code/src/vs/base/browser/ui/menu/menubar.ts @@ -118,7 +118,7 @@ export class MenuBar extends Disposable { this._register(DOM.ModifierKeyEmitter.getInstance().event(this.onModifierKeyToggled, this)); this._register(DOM.addDisposableListener(this.container, DOM.EventType.KEY_DOWN, (e) => { - const event = new StandardKeyboardEvent(e as KeyboardEvent); + const event = new StandardKeyboardEvent(e); let eventHandled = true; const key = !!e.key ? e.key.toLocaleLowerCase() : ''; @@ -157,7 +157,7 @@ export class MenuBar extends Disposable { })); this._register(DOM.addDisposableListener(this.container, DOM.EventType.FOCUS_IN, (e) => { - const event = e as FocusEvent; + const event = e; if (event.relatedTarget) { if (!this.container.contains(event.relatedTarget as HTMLElement)) { @@ -167,7 +167,7 @@ export class MenuBar extends Disposable { })); this._register(DOM.addDisposableListener(this.container, DOM.EventType.FOCUS_OUT, (e) => { - const event = e as FocusEvent; + const event = e; // We are losing focus and there is no related target, e.g. webview case if (!event.relatedTarget) { @@ -228,7 +228,7 @@ export class MenuBar extends Disposable { this.updateLabels(titleElement, buttonElement, menuBarMenu.label); this._register(DOM.addDisposableListener(buttonElement, DOM.EventType.KEY_UP, (e) => { - const event = new StandardKeyboardEvent(e as KeyboardEvent); + const event = new StandardKeyboardEvent(e); let eventHandled = true; if ((event.equals(KeyCode.DownArrow) || event.equals(KeyCode.Enter)) && !this.isOpen) { @@ -324,7 +324,7 @@ export class MenuBar extends Disposable { buttonElement.style.visibility = 'hidden'; this._register(DOM.addDisposableListener(buttonElement, DOM.EventType.KEY_UP, (e) => { - const event = new StandardKeyboardEvent(e as KeyboardEvent); + const event = new StandardKeyboardEvent(e); let eventHandled = true; const triggerKeys = [KeyCode.Enter]; diff --git a/code/src/vs/base/browser/ui/progressbar/progressAccessibilitySignal.ts b/code/src/vs/base/browser/ui/progressbar/progressAccessibilitySignal.ts index db5d0b0cee3..5419c8819c6 100644 --- a/code/src/vs/base/browser/ui/progressbar/progressAccessibilitySignal.ts +++ b/code/src/vs/base/browser/ui/progressbar/progressAccessibilitySignal.ts @@ -14,10 +14,10 @@ const nullScopedAccessibilityProgressSignalFactory = () => ({ }); let progressAccessibilitySignalSchedulerFactory: (msDelayTime: number, msLoopTime?: number) => IScopedAccessibilityProgressSignalDelegate = nullScopedAccessibilityProgressSignalFactory; -export function setProgressAcccessibilitySignalScheduler(progressAccessibilitySignalScheduler: (msDelayTime: number, msLoopTime?: number) => IScopedAccessibilityProgressSignalDelegate) { +export function setProgressAccessibilitySignalScheduler(progressAccessibilitySignalScheduler: (msDelayTime: number, msLoopTime?: number) => IScopedAccessibilityProgressSignalDelegate) { progressAccessibilitySignalSchedulerFactory = progressAccessibilitySignalScheduler; } -export function getProgressAcccessibilitySignalScheduler(msDelayTime: number, msLoopTime?: number): IScopedAccessibilityProgressSignalDelegate { +export function getProgressAccessibilitySignalScheduler(msDelayTime: number, msLoopTime?: number): IScopedAccessibilityProgressSignalDelegate { return progressAccessibilitySignalSchedulerFactory(msDelayTime, msLoopTime); } diff --git a/code/src/vs/base/browser/ui/progressbar/progressbar.ts b/code/src/vs/base/browser/ui/progressbar/progressbar.ts index 9b5b2c7c2ba..a369fb81871 100644 --- a/code/src/vs/base/browser/ui/progressbar/progressbar.ts +++ b/code/src/vs/base/browser/ui/progressbar/progressbar.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { hide, show } from '../../dom.js'; -import { getProgressAcccessibilitySignalScheduler } from './progressAccessibilitySignal.js'; +import { getProgressAccessibilitySignalScheduler } from './progressAccessibilitySignal.js'; import { RunOnceScheduler } from '../../../common/async.js'; import { Disposable, IDisposable, MutableDisposable } from '../../../common/lifecycle.js'; import { isNumber } from '../../../common/types.js'; @@ -177,7 +177,7 @@ export class ProgressBar extends Disposable { } /** - * Tells the progress bar the total amount of work that has been completed. + * Tells the progress bar the total amount of work (0 to 100) that has been completed. */ setWorked(value: number): ProgressBar { value = Math.max(1, Number(value)); @@ -206,7 +206,7 @@ export class ProgressBar extends Disposable { show(delay?: number): void { this.showDelayedScheduler.cancel(); - this.progressSignal.value = getProgressAcccessibilitySignalScheduler(ProgressBar.PROGRESS_SIGNAL_DEFAULT_DELAY); + this.progressSignal.value = getProgressAccessibilitySignalScheduler(ProgressBar.PROGRESS_SIGNAL_DEFAULT_DELAY); if (typeof delay === 'number') { this.showDelayedScheduler.schedule(delay); diff --git a/code/src/vs/base/browser/ui/sash/sash.css b/code/src/vs/base/browser/ui/sash/sash.css index 42b0f425787..52419ac7d45 100644 --- a/code/src/vs/base/browser/ui/sash/sash.css +++ b/code/src/vs/base/browser/ui/sash/sash.css @@ -111,7 +111,7 @@ background: transparent; } -.monaco-workbench:not(.reduce-motion) .monaco-sash:before { +.monaco-enable-motion .monaco-sash:before { transition: background-color 0.1s ease-out; } diff --git a/code/src/vs/base/browser/ui/sash/sash.ts b/code/src/vs/base/browser/ui/sash/sash.ts index acd80856a72..6f2fcb52276 100644 --- a/code/src/vs/base/browser/ui/sash/sash.ts +++ b/code/src/vs/base/browser/ui/sash/sash.ts @@ -488,17 +488,21 @@ export class Sash extends Disposable { let isMultisashResize = false; + // eslint-disable-next-line local/code-no-any-casts if (!(event as any).__orthogonalSashEvent) { const orthogonalSash = this.getOrthogonalSash(event); if (orthogonalSash) { isMultisashResize = true; + // eslint-disable-next-line local/code-no-any-casts (event as any).__orthogonalSashEvent = true; orthogonalSash.onPointerStart(event, new OrthogonalPointerEventFactory(pointerEventFactory)); } } + // eslint-disable-next-line local/code-no-any-casts if (this.linkedSash && !(event as any).__linkedSashEvent) { + // eslint-disable-next-line local/code-no-any-casts (event as any).__linkedSashEvent = true; this.linkedSash.onPointerStart(event, new OrthogonalPointerEventFactory(pointerEventFactory)); } @@ -507,6 +511,7 @@ export class Sash extends Disposable { return; } + // eslint-disable-next-line no-restricted-syntax const iframes = this.el.ownerDocument.getElementsByTagName('iframe'); for (const iframe of iframes) { iframe.classList.add(PointerEventsDisabledCssClass); // disable mouse events on iframes as long as we drag the sash diff --git a/code/src/vs/base/browser/ui/scrollbar/media/scrollbars.css b/code/src/vs/base/browser/ui/scrollbar/media/scrollbars.css index 84c17370894..be500bc2e56 100644 --- a/code/src/vs/base/browser/ui/scrollbar/media/scrollbars.css +++ b/code/src/vs/base/browser/ui/scrollbar/media/scrollbars.css @@ -59,6 +59,10 @@ box-shadow: var(--vscode-scrollbar-shadow) 6px 0 6px -6px inset; } +.monaco-scrollable-element > .scrollbar { + background: var(--vscode-scrollbar-background); +} + .monaco-scrollable-element > .scrollbar > .slider { background: var(--vscode-scrollbarSlider-background); } diff --git a/code/src/vs/base/browser/ui/scrollbar/scrollableElement.ts b/code/src/vs/base/browser/ui/scrollbar/scrollableElement.ts index 33c60da52f6..12753112b43 100644 --- a/code/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +++ b/code/src/vs/base/browser/ui/scrollbar/scrollableElement.ts @@ -495,7 +495,7 @@ export abstract class AbstractScrollableElement extends Widget { // Check that we are scrolling towards a location which is valid desiredScrollPosition = this._scrollable.validateScrollPosition(desiredScrollPosition); - if (this._options.inertialScroll && (deltaX || deltaY)) { + if (this._options.inertialScroll && (deltaX || deltaY) && !classifier.isPhysicalMouseWheel()) { let startPeriodic = false; // Only start periodic if it's not running if (this._inertialSpeed.X === 0 && this._inertialSpeed.Y === 0) { diff --git a/code/src/vs/base/browser/ui/selectBox/selectBox.ts b/code/src/vs/base/browser/ui/selectBox/selectBox.ts index 1e023ae4e4e..335c2c9c09b 100644 --- a/code/src/vs/base/browser/ui/selectBox/selectBox.ts +++ b/code/src/vs/base/browser/ui/selectBox/selectBox.ts @@ -53,6 +53,11 @@ export interface ISelectOptionItem { isDisabled?: boolean; } +export const SeparatorSelectOption: Readonly = Object.freeze({ + text: '\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500', + isDisabled: true, +}); + export interface ISelectBoxStyles extends IListStyles { readonly selectBackground: string | undefined; readonly selectListBackground: string | undefined; diff --git a/code/src/vs/base/browser/ui/selectBox/selectBoxCustom.css b/code/src/vs/base/browser/ui/selectBox/selectBoxCustom.css index 292cd2dd1e1..4d2fb516f20 100644 --- a/code/src/vs/base/browser/ui/selectBox/selectBoxCustom.css +++ b/code/src/vs/base/browser/ui/selectBox/selectBoxCustom.css @@ -3,21 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -/* Use custom CSS vars to expose padding into parent select for padding calculation */ -.monaco-select-box-dropdown-padding { - --dropdown-padding-top: 1px; - --dropdown-padding-bottom: 1px; -} - -.hc-black .monaco-select-box-dropdown-padding, -.hc-light .monaco-select-box-dropdown-padding { - --dropdown-padding-top: 3px; - --dropdown-padding-bottom: 4px; -} - .monaco-select-box-dropdown-container { display: none; - box-sizing: border-box; + box-sizing: border-box; + border-radius: 5px; + box-shadow: 0 2px 8px var(--vscode-widget-shadow); } .monaco-select-box-dropdown-container > .select-box-details-pane > .select-box-description-markdown * { @@ -41,33 +31,23 @@ text-align: left; width: 1px; overflow: hidden; - border-bottom-left-radius: 3px; - border-bottom-right-radius: 3px; } .monaco-select-box-dropdown-container > .select-box-dropdown-list-container { flex: 0 0 auto; align-self: flex-start; - padding-top: var(--dropdown-padding-top); - padding-bottom: var(--dropdown-padding-bottom); - padding-left: 1px; - padding-right: 1px; width: 100%; overflow: hidden; - box-sizing: border-box; + box-sizing: border-box; } .monaco-select-box-dropdown-container > .select-box-details-pane { - padding: 5px; -} - -.hc-black .monaco-select-box-dropdown-container > .select-box-dropdown-list-container { - padding-top: var(--dropdown-padding-top); - padding-bottom: var(--dropdown-padding-bottom); + padding: 5px 6px; } .monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row { cursor: pointer; + padding-left: 2px; } .monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row > .option-text { @@ -100,12 +80,12 @@ /* https://webaim.org/techniques/css/invisiblecontent/ */ .monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row > .visually-hidden { - position: absolute; - left: -10000px; - top: auto; - width: 1px; - height: 1px; - overflow: hidden; + position: absolute; + left: -10000px; + top: auto; + width: 1px; + height: 1px; + overflow: hidden; } .monaco-select-box-dropdown-container > .select-box-dropdown-container-width-control { diff --git a/code/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/code/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index c6a1c8a3dbe..f6c2ff1cb4f 100644 --- a/code/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/code/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -7,7 +7,7 @@ import { localize } from '../../../../nls.js'; import * as arrays from '../../../common/arrays.js'; import { Emitter, Event } from '../../../common/event.js'; import { KeyCode, KeyCodeUtils } from '../../../common/keyCodes.js'; -import { Disposable, IDisposable } from '../../../common/lifecycle.js'; +import { Disposable, DisposableStore, IDisposable } from '../../../common/lifecycle.js'; import { isMacintosh } from '../../../common/platform.js'; import { ScrollbarVisibility } from '../../../common/scrollable.js'; import * as cssJs from '../../cssValue.js'; @@ -15,7 +15,7 @@ import * as dom from '../../dom.js'; import * as domStylesheetsJs from '../../domStylesheets.js'; import { DomEmitter } from '../../event.js'; import { StandardKeyboardEvent } from '../../keyboardEvent.js'; -import { MarkdownActionHandler, renderMarkdown } from '../../markdownRenderer.js'; +import { IRenderedMarkdown, MarkdownActionHandler, renderMarkdown } from '../../markdownRenderer.js'; import { AnchorPosition, IContextViewProvider } from '../contextview/contextview.js'; import type { IManagedHover } from '../hover/hover.js'; import { getBaseLayerHoverDelegate } from '../hover/hoverDelegate2.js'; @@ -103,6 +103,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi private _dropDownPosition!: AnchorPosition; private _hasDetails: boolean = false; private selectionDetailsPane!: HTMLElement; + private readonly _selectionDetailsDisposables = this._register(new DisposableStore()); private _skipLayout: boolean = false; private _cachedMaxDetailsHeight?: number; private _hover?: IManagedHover; @@ -124,9 +125,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi } this.selectElement = document.createElement('select'); - - // Use custom CSS vars for padding calculation - this.selectElement.className = 'monaco-select-box monaco-select-box-dropdown-padding'; + this.selectElement.className = 'monaco-select-box'; if (typeof this.selectBoxOptions.ariaLabel === 'string') { this.selectElement.setAttribute('aria-label', this.selectBoxOptions.ariaLabel); @@ -175,8 +174,6 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi // SetUp ContextView container to hold select Dropdown this.contextViewProvider = contextViewProvider; this.selectDropDownContainer = dom.$('.monaco-select-box-dropdown-container'); - // Use custom CSS vars for padding calculation (shared with parent select) - this.selectDropDownContainer.classList.add('monaco-select-box-dropdown-padding'); // Setup container for select option details this.selectionDetailsPane = dom.append(this.selectDropDownContainer, $('.select-box-details-pane')); @@ -471,7 +468,6 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi }, onHide: () => { this.selectDropDownContainer.classList.remove('visible'); - this.selectElement.classList.remove('synthetic-focus'); }, anchorPosition: this._dropDownPosition }, this.selectBoxOptions.optionsAsChildren ? this.container : undefined); @@ -486,7 +482,6 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi layout: () => this.layoutSelectDropDown(), onHide: () => { this.selectDropDownContainer.classList.remove('visible'); - this.selectElement.classList.remove('synthetic-focus'); }, anchorPosition: this._dropDownPosition }, this.selectBoxOptions.optionsAsChildren ? this.container : undefined); @@ -558,15 +553,13 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi const window = dom.getWindow(this.selectElement); const selectPosition = dom.getDomNodePagePosition(this.selectElement); - const styles = dom.getWindow(this.selectElement).getComputedStyle(this.selectElement); - const verticalPadding = parseFloat(styles.getPropertyValue('--dropdown-padding-top')) + parseFloat(styles.getPropertyValue('--dropdown-padding-bottom')); const maxSelectDropDownHeightBelow = (window.innerHeight - selectPosition.top - selectPosition.height - (this.selectBoxOptions.minBottomMargin || 0)); const maxSelectDropDownHeightAbove = (selectPosition.top - SelectBoxList.DEFAULT_DROPDOWN_MINIMUM_TOP_MARGIN); // Determine optimal width - min(longest option), opt(parent select, excluding margins), max(ContextView controlled) const selectWidth = this.selectElement.offsetWidth; const selectMinWidth = this.setWidthControlElement(this.widthControlElement); - const selectOptimalWidth = Math.max(selectMinWidth, Math.round(selectWidth)).toString() + 'px'; + const selectOptimalWidth = `${Math.max(selectMinWidth, Math.round(selectWidth))}px`; this.selectDropDownContainer.style.width = selectOptimalWidth; @@ -580,9 +573,9 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi } const maxDetailsPaneHeight = this._hasDetails ? this._cachedMaxDetailsHeight! : 0; - const minRequiredDropDownHeight = listHeight + verticalPadding + maxDetailsPaneHeight; - const maxVisibleOptionsBelow = ((Math.floor((maxSelectDropDownHeightBelow - verticalPadding - maxDetailsPaneHeight) / this.getHeight()))); - const maxVisibleOptionsAbove = ((Math.floor((maxSelectDropDownHeightAbove - verticalPadding - maxDetailsPaneHeight) / this.getHeight()))); + const minRequiredDropDownHeight = listHeight + maxDetailsPaneHeight; + const maxVisibleOptionsBelow = ((Math.floor((maxSelectDropDownHeightBelow - maxDetailsPaneHeight) / this.getHeight()))); + const maxVisibleOptionsAbove = ((Math.floor((maxSelectDropDownHeightAbove - maxDetailsPaneHeight) / this.getHeight()))); // If we are only doing pre-layout check/adjust position only // Calculate vertical space available, flip up if insufficient @@ -672,20 +665,16 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi if (this._hasDetails) { // Leave the selectDropDownContainer to size itself according to children (list + details) - #57447 - this.selectList.getHTMLElement().style.height = (listHeight + verticalPadding) + 'px'; + this.selectList.getHTMLElement().style.height = `${listHeight}px`; this.selectDropDownContainer.style.height = ''; } else { - this.selectDropDownContainer.style.height = (listHeight + verticalPadding) + 'px'; + this.selectDropDownContainer.style.height = `${listHeight}px`; } this.updateDetail(this.selected); this.selectDropDownContainer.style.width = selectOptimalWidth; - - // Maintain focus outline on parent select as well as list container - tabindex for focus this.selectDropDownListContainer.setAttribute('tabindex', '0'); - this.selectElement.classList.add('synthetic-focus'); - this.selectDropDownContainer.classList.add('synthetic-focus'); return true; } else { @@ -712,7 +701,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi }); - container.textContent = this.options[longest].text + (!!this.options[longest].decoratorRight ? (this.options[longest].decoratorRight + ' ') : ''); + container.textContent = this.options[longest].text + (!!this.options[longest].decoratorRight ? `${this.options[longest].decoratorRight} ` : ''); elementWidth = dom.getTotalWidth(container); } @@ -868,7 +857,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi } - private renderDescriptionMarkdown(text: string, actionHandler?: MarkdownActionHandler): HTMLElement { + private renderDescriptionMarkdown(text: string, actionHandler?: MarkdownActionHandler): IRenderedMarkdown { const cleanRenderedMarkdown = (element: Node) => { for (let i = 0; i < element.childNodes.length; i++) { const child = element.childNodes.item(i); @@ -887,7 +876,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi rendered.element.classList.add('select-box-description-markdown'); cleanRenderedMarkdown(rendered.element); - return rendered.element; + return rendered; } // List Focus Change - passive - update details pane with newly focused element's data @@ -901,7 +890,10 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi } private updateDetail(selectedIndex: number): void { + // Reset + this._selectionDetailsDisposables.clear(); this.selectionDetailsPane.textContent = ''; + const option = this.options[selectedIndex]; const description = option?.description ?? ''; const descriptionIsMarkdown = option?.descriptionIsMarkdown ?? false; @@ -909,7 +901,8 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi if (description) { if (descriptionIsMarkdown) { const actionHandler = option.descriptionMarkdownActionHandler; - this.selectionDetailsPane.appendChild(this.renderDescriptionMarkdown(description, actionHandler)); + const result = this._selectionDetailsDisposables.add(this.renderDescriptionMarkdown(description, actionHandler)); + this.selectionDetailsPane.appendChild(result.element); } else { this.selectionDetailsPane.textContent = description; } diff --git a/code/src/vs/base/browser/ui/splitview/paneview.css b/code/src/vs/base/browser/ui/splitview/paneview.css index 64587284592..7bb4282a227 100644 --- a/code/src/vs/base/browser/ui/splitview/paneview.css +++ b/code/src/vs/base/browser/ui/splitview/paneview.css @@ -116,7 +116,7 @@ transition-timing-function: ease-out; } -.reduce-motion .monaco-pane-view .split-view-view { +.monaco-reduce-motion .monaco-pane-view .split-view-view { transition-duration: 0s !important; } diff --git a/code/src/vs/base/browser/ui/splitview/paneview.ts b/code/src/vs/base/browser/ui/splitview/paneview.ts index 42d2234a131..fb2e1f406ae 100644 --- a/code/src/vs/base/browser/ui/splitview/paneview.ts +++ b/code/src/vs/base/browser/ui/splitview/paneview.ts @@ -657,6 +657,7 @@ export class PaneView extends Disposable { } private getPaneHeaderElements(): HTMLElement[] { + // eslint-disable-next-line no-restricted-syntax return [...this.element.querySelectorAll('.pane-header')] as HTMLElement[]; } diff --git a/code/src/vs/base/browser/ui/table/table.css b/code/src/vs/base/browser/ui/table/table.css index 2266437d7bc..03dbcb31566 100644 --- a/code/src/vs/base/browser/ui/table/table.css +++ b/code/src/vs/base/browser/ui/table/table.css @@ -51,7 +51,7 @@ border-left: 1px solid transparent; } -.monaco-workbench:not(.reduce-motion) .monaco-table > .monaco-split-view2, -.monaco-workbench:not(.reduce-motion) .monaco-table > .monaco-split-view2 .monaco-sash.vertical::before { +.monaco-enable-motion .monaco-table > .monaco-split-view2, +.monaco-enable-motion .monaco-table > .monaco-split-view2 .monaco-sash.vertical::before { transition: border-color 0.2s ease-out; } diff --git a/code/src/vs/base/browser/ui/toggle/toggle.ts b/code/src/vs/base/browser/ui/toggle/toggle.ts index 34e08dba3f1..f310bff8965 100644 --- a/code/src/vs/base/browser/ui/toggle/toggle.ts +++ b/code/src/vs/base/browser/ui/toggle/toggle.ts @@ -6,25 +6,26 @@ import { IAction } from '../../../common/actions.js'; import { Codicon } from '../../../common/codicons.js'; import { Emitter, Event } from '../../../common/event.js'; +import { IMarkdownString, isMarkdownString } from '../../../common/htmlContent.js'; +import { getCodiconAriaLabel, stripIcons } from '../../../common/iconLabels.js'; import { KeyCode } from '../../../common/keyCodes.js'; import { ThemeIcon } from '../../../common/themables.js'; -import { $, addDisposableListener, EventType, isActiveElement } from '../../dom.js'; +import { $, addDisposableListener, EventType, isActiveElement, isHTMLElement } from '../../dom.js'; import { IKeyboardEvent } from '../../keyboardEvent.js'; import { BaseActionViewItem, IActionViewItemOptions } from '../actionbar/actionViewItems.js'; -import type { IManagedHover } from '../hover/hover.js'; -import { IHoverDelegate } from '../hover/hoverDelegate.js'; +import { IActionViewItemProvider } from '../actionbar/actionbar.js'; +import { HoverStyle, IHoverLifecycleOptions } from '../hover/hover.js'; import { getBaseLayerHoverDelegate } from '../hover/hoverDelegate2.js'; -import { getDefaultHoverDelegate } from '../hover/hoverDelegateFactory.js'; import { Widget } from '../widget.js'; import './toggle.css'; export interface IToggleOpts extends IToggleStyles { readonly actionClassName?: string; readonly icon?: ThemeIcon; - readonly title: string; + readonly title: string | IMarkdownString | HTMLElement; readonly isChecked: boolean; readonly notFocusable?: boolean; - readonly hoverDelegate?: IHoverDelegate; + readonly hoverLifecycleOptions?: IHoverLifecycleOptions; } export interface IToggleStyles { @@ -40,7 +41,7 @@ export interface ICheckboxStyles { readonly checkboxDisabledBackground: string | undefined; readonly checkboxDisabledForeground: string | undefined; readonly size?: number; - readonly hoverDelegate?: IHoverDelegate; + readonly hoverLifecycleOptions?: IHoverLifecycleOptions; } export const unthemedToggleStyles = { @@ -66,7 +67,6 @@ export class ToggleActionViewItem extends BaseActionViewItem { inputActiveOptionBackground: options.toggleStyles?.inputActiveOptionBackground, inputActiveOptionBorder: options.toggleStyles?.inputActiveOptionBorder, inputActiveOptionForeground: options.toggleStyles?.inputActiveOptionForeground, - hoverDelegate: options.hoverDelegate })); this._register(this.toggle.onChange(() => { this._action.checked = !!this.toggle && this.toggle.checked; @@ -128,16 +128,17 @@ export class Toggle extends Widget { get onKeyDown(): Event { return this._onKeyDown.event; } private readonly _opts: IToggleOpts; + private _title: string | IMarkdownString | HTMLElement; private _icon: ThemeIcon | undefined; readonly domNode: HTMLElement; private _checked: boolean; - private _hover: IManagedHover; constructor(opts: IToggleOpts) { super(); this._opts = opts; + this._title = this._opts.title; this._checked = this._opts.isChecked; const classes = ['monaco-custom-toggle']; @@ -153,15 +154,18 @@ export class Toggle extends Widget { } this.domNode = document.createElement('div'); - this._hover = this._register(getBaseLayerHoverDelegate().setupManagedHover(opts.hoverDelegate ?? getDefaultHoverDelegate('mouse'), this.domNode, this._opts.title)); + this._register(getBaseLayerHoverDelegate().setupDelayedHover(this.domNode, () => ({ + content: !isMarkdownString(this._title) && !isHTMLElement(this._title) ? stripIcons(this._title) : this._title, + style: HoverStyle.Pointer, + }), this._opts.hoverLifecycleOptions)); this.domNode.classList.add(...classes); if (!this._opts.notFocusable) { this.domNode.tabIndex = 0; } this.domNode.setAttribute('role', 'checkbox'); this.domNode.setAttribute('aria-checked', String(this._checked)); - this.domNode.setAttribute('aria-label', this._opts.title); + this.setTitle(this._opts.title); this.applyStyles(); this.onclick(this.domNode, (ev) => { @@ -244,9 +248,12 @@ export class Toggle extends Widget { this.domNode.classList.add('disabled'); } - setTitle(newTitle: string): void { - this._hover.update(newTitle); - this.domNode.setAttribute('aria-label', newTitle); + setTitle(newTitle: string | IMarkdownString | HTMLElement): void { + this._title = newTitle; + + const ariaLabel = typeof newTitle === 'string' ? newTitle : isMarkdownString(newTitle) ? newTitle.value : newTitle.textContent; + + this.domNode.setAttribute('aria-label', getCodiconAriaLabel(ariaLabel)); } set visible(visible: boolean) { @@ -316,7 +323,7 @@ abstract class BaseCheckbox extends Widget { export class Checkbox extends BaseCheckbox { constructor(title: string, isChecked: boolean, styles: ICheckboxStyles) { - const toggle = new Toggle({ title, isChecked, icon: Codicon.check, actionClassName: BaseCheckbox.CLASS_NAME, hoverDelegate: styles.hoverDelegate, ...unthemedToggleStyles }); + const toggle = new Toggle({ title, isChecked, icon: Codicon.check, actionClassName: BaseCheckbox.CLASS_NAME, hoverLifecycleOptions: styles.hoverLifecycleOptions, ...unthemedToggleStyles }); super(toggle, toggle.domNode, styles); this._register(toggle); @@ -348,7 +355,7 @@ export class Checkbox extends BaseCheckbox { export class TriStateCheckbox extends BaseCheckbox { constructor( title: string, - private _state: boolean | 'partial', + private _state: boolean | 'mixed', styles: ICheckboxStyles ) { let icon: ThemeIcon | undefined; @@ -356,7 +363,7 @@ export class TriStateCheckbox extends BaseCheckbox { case true: icon = Codicon.check; break; - case 'partial': + case 'mixed': icon = Codicon.dash; break; case false: @@ -368,7 +375,7 @@ export class TriStateCheckbox extends BaseCheckbox { isChecked: _state === true, icon, actionClassName: Checkbox.CLASS_NAME, - hoverDelegate: styles.hoverDelegate, + hoverLifecycleOptions: styles.hoverLifecycleOptions, ...unthemedToggleStyles }); super( @@ -385,11 +392,11 @@ export class TriStateCheckbox extends BaseCheckbox { })); } - get checked(): boolean | 'partial' { + get checked(): boolean | 'mixed' { return this._state; } - set checked(newState: boolean | 'partial') { + set checked(newState: boolean | 'mixed') { if (this._state !== newState) { this._state = newState; this.checkbox.checked = newState === true; @@ -402,7 +409,7 @@ export class TriStateCheckbox extends BaseCheckbox { case true: this.checkbox.setIcon(Codicon.check); break; - case 'partial': + case 'mixed': this.checkbox.setIcon(Codicon.dash); break; case false: @@ -495,3 +502,21 @@ export class CheckboxActionViewItem extends BaseActionViewItem { } } + +/** + * Creates an action view item provider that renders toggles for actions with a checked state + * and falls back to default button rendering for regular actions. + * + * @param toggleStyles - Optional styles to apply to toggle items + * @returns An IActionViewItemProvider that can be used with ActionBar + */ +export function createToggleActionViewItemProvider(toggleStyles?: IToggleStyles): IActionViewItemProvider { + return (action: IAction, options: IActionViewItemOptions) => { + // Only render as a toggle if the action has a checked property + if (action.checked !== undefined) { + return new ToggleActionViewItem(null, action, { ...options, toggleStyles }); + } + // Return undefined to fall back to default button rendering + return undefined; + }; +} diff --git a/code/src/vs/base/browser/ui/toolbar/toolbar.css b/code/src/vs/base/browser/ui/toolbar/toolbar.css index 8182fbd9c29..4c4c684755e 100644 --- a/code/src/vs/base/browser/ui/toolbar/toolbar.css +++ b/code/src/vs/base/browser/ui/toolbar/toolbar.css @@ -11,3 +11,10 @@ display: inline-block; padding: 0; } + +.monaco-toolbar.responsive { + .monaco-action-bar > .actions-container > .action-item { + flex-shrink: 1; + min-width: 20px; + } +} diff --git a/code/src/vs/base/browser/ui/toolbar/toolbar.ts b/code/src/vs/base/browser/ui/toolbar/toolbar.ts index 64b0c137d70..5146046fe99 100644 --- a/code/src/vs/base/browser/ui/toolbar/toolbar.ts +++ b/code/src/vs/base/browser/ui/toolbar/toolbar.ts @@ -7,17 +7,19 @@ import { IContextMenuProvider } from '../../contextmenu.js'; import { ActionBar, ActionsOrientation, IActionViewItemProvider } from '../actionbar/actionbar.js'; import { AnchorAlignment } from '../contextview/contextview.js'; import { DropdownMenuActionViewItem } from '../dropdown/dropdownActionViewItem.js'; -import { Action, IAction, IActionRunner, SubmenuAction } from '../../../common/actions.js'; +import { Action, IAction, IActionRunner, Separator, SubmenuAction } from '../../../common/actions.js'; import { Codicon } from '../../../common/codicons.js'; import { ThemeIcon } from '../../../common/themables.js'; import { EventMultiplexer } from '../../../common/event.js'; import { ResolvedKeybinding } from '../../../common/keybindings.js'; -import { Disposable, DisposableStore } from '../../../common/lifecycle.js'; +import { Disposable, DisposableStore, toDisposable } from '../../../common/lifecycle.js'; import './toolbar.css'; import * as nls from '../../../../nls.js'; import { IHoverDelegate } from '../hover/hoverDelegate.js'; import { createInstantHoverDelegate } from '../hover/hoverDelegateFactory.js'; +const ACTION_MIN_WIDTH = 24; /* 20px codicon + 4px left padding*/ + export interface IToolBarOptions { orientation?: ActionsOrientation; actionViewItemProvider?: IActionViewItemProvider; @@ -31,6 +33,7 @@ export interface IToolBarOptions { allowContextMenu?: boolean; skipTelemetry?: boolean; hoverDelegate?: IHoverDelegate; + trailingSeparator?: boolean; /** * If true, toggled primary items are highlighted with a background color. @@ -46,6 +49,13 @@ export interface IToolBarOptions { * Render action with label (default: `false`) */ label?: boolean; + + /** + * Controls the responsive behavior of the primary group of the toolbar. + * - `enabled`: Whether the responsive behavior is enabled. + * - `minItems`: The minimum number of items that should always be visible. + */ + responsiveBehavior?: { enabled: boolean; minItems?: number }; } /** @@ -62,9 +72,12 @@ export class ToolBar extends Disposable { private _onDidChangeDropdownVisibility = this._register(new EventMultiplexer()); get onDidChangeDropdownVisibility() { return this._onDidChangeDropdownVisibility.event; } + private originalPrimaryActions: ReadonlyArray = []; + private originalSecondaryActions: ReadonlyArray = []; + private hiddenActions: { action: IAction; size: number }[] = []; private readonly disposables = this._register(new DisposableStore()); - constructor(container: HTMLElement, contextMenuProvider: IContextMenuProvider, options: IToolBarOptions = { orientation: ActionsOrientation.HORIZONTAL }) { + constructor(private readonly container: HTMLElement, contextMenuProvider: IContextMenuProvider, options: IToolBarOptions = { orientation: ActionsOrientation.HORIZONTAL }) { super(); options.hoverDelegate = options.hoverDelegate ?? this._register(createInstantHoverDelegate()); @@ -87,7 +100,7 @@ export class ToolBar extends Disposable { if (action.id === ToggleMenuAction.ID) { this.toggleMenuActionViewItem = new DropdownMenuActionViewItem( action, - (action).menuActions, + { getActions: () => this.toggleMenuAction.menuActions }, contextMenuProvider, { actionViewItemProvider: this.options.actionViewItemProvider, @@ -141,6 +154,17 @@ export class ToolBar extends Disposable { return undefined; } })); + + // Responsive support + if (this.options.responsiveBehavior?.enabled) { + this.element.classList.add('responsive'); + + const observer = new ResizeObserver(() => { + this.updateActions(this.element.getBoundingClientRect().width); + }); + observer.observe(this.element); + this._store.add(toDisposable(() => observer.disconnect())); + } } set actionRunner(actionRunner: IActionRunner) { @@ -194,6 +218,10 @@ export class ToolBar extends Disposable { setActions(primaryActions: ReadonlyArray, secondaryActions?: ReadonlyArray): void { this.clear(); + // Store primary and secondary actions as rendered initially + this.originalPrimaryActions = primaryActions ? primaryActions.slice(0) : []; + this.originalSecondaryActions = secondaryActions ? secondaryActions.slice(0) : []; + const primaryActionsToSet = primaryActions ? primaryActions.slice(0) : []; // Inject additional action to open secondary actions if present @@ -203,9 +231,40 @@ export class ToolBar extends Disposable { primaryActionsToSet.push(this.toggleMenuAction); } + if (primaryActionsToSet.length > 0 && this.options.trailingSeparator) { + primaryActionsToSet.push(new Separator()); + } + primaryActionsToSet.forEach(action => { this.actionBar.push(action, { icon: this.options.icon ?? true, label: this.options.label ?? false, keybinding: this.getKeybindingLabel(action) }); }); + + if (this.options.responsiveBehavior?.enabled) { + // Reset hidden actions + this.hiddenActions.length = 0; + + // Set the minimum width + if (this.options.responsiveBehavior?.minItems !== undefined) { + let itemCount = this.options.responsiveBehavior.minItems; + + // Account for overflow menu + if ( + this.originalSecondaryActions.length > 0 || + itemCount < this.originalPrimaryActions.length + ) { + itemCount += 1; + } + + this.container.style.minWidth = `${itemCount * ACTION_MIN_WIDTH}px`; + this.element.style.minWidth = `${itemCount * ACTION_MIN_WIDTH}px`; + } else { + this.container.style.minWidth = `${ACTION_MIN_WIDTH}px`; + this.element.style.minWidth = `${ACTION_MIN_WIDTH}px`; + } + + // Update toolbar actions to fit with container width + this.updateActions(this.element.getBoundingClientRect().width); + } } isEmpty(): boolean { @@ -218,6 +277,99 @@ export class ToolBar extends Disposable { return key?.getLabel() ?? undefined; } + private updateActions(containerWidth: number) { + // Actions bar is empty + if (this.actionBar.isEmpty()) { + return; + } + + // Ensure that the container width respects the minimum width of the + // element which is set based on the `responsiveBehavior.minItems` option + containerWidth = Math.max(containerWidth, parseInt(this.element.style.minWidth)); + + // Each action is assumed to have a minimum width so that actions with a label + // can shrink to the action's minimum width. We do this so that action visibility + // takes precedence over the action label. + const actionBarWidth = () => this.actionBar.length() * ACTION_MIN_WIDTH; + + // Action bar fits and there are no hidden actions to show + if (actionBarWidth() <= containerWidth && this.hiddenActions.length === 0) { + return; + } + + if (actionBarWidth() > containerWidth) { + // Check for max items limit + if (this.options.responsiveBehavior?.minItems !== undefined) { + const primaryActionsCount = this.actionBar.hasAction(this.toggleMenuAction) + ? this.actionBar.length() - 1 + : this.actionBar.length(); + + if (primaryActionsCount <= this.options.responsiveBehavior.minItems) { + return; + } + } + + // Hide actions from the right + while (actionBarWidth() > containerWidth && this.actionBar.length() > 0) { + const index = this.originalPrimaryActions.length - this.hiddenActions.length - 1; + if (index < 0) { + break; + } + + // Store the action and its size + const size = Math.min(ACTION_MIN_WIDTH, this.getItemWidth(index)); + const action = this.originalPrimaryActions[index]; + this.hiddenActions.unshift({ action, size }); + + // Remove the action + this.actionBar.pull(index); + + // There are no secondary actions, but we have actions that we need to hide so we + // create the overflow menu. This will ensure that another primary action will be + // removed making space for the overflow menu. + if (this.originalSecondaryActions.length === 0 && this.hiddenActions.length === 1) { + this.actionBar.push(this.toggleMenuAction, { + icon: this.options.icon ?? true, + label: this.options.label ?? false, + keybinding: this.getKeybindingLabel(this.toggleMenuAction), + }); + } + } + } else { + // Show actions from the top of the toggle menu + while (this.hiddenActions.length > 0) { + const entry = this.hiddenActions.shift()!; + if (actionBarWidth() + entry.size > containerWidth) { + // Not enough space to show the action + this.hiddenActions.unshift(entry); + break; + } + + // Add the action + this.actionBar.push(entry.action, { + icon: this.options.icon ?? true, + label: this.options.label ?? false, + keybinding: this.getKeybindingLabel(entry.action), + index: this.originalPrimaryActions.length - this.hiddenActions.length - 1 + }); + + // There are no secondary actions, and there is only one hidden item left so we + // remove the overflow menu making space for the last hidden action to be shown. + if (this.originalSecondaryActions.length === 0 && this.hiddenActions.length === 1) { + this.toggleMenuAction.menuActions = []; + this.actionBar.pull(this.actionBar.length() - 1); + } + } + } + + // Update overflow menu + const hiddenActions = this.hiddenActions.map(entry => entry.action); + if (this.originalSecondaryActions.length > 0 || hiddenActions.length > 0) { + const secondaryActions = this.originalSecondaryActions.slice(0); + this.toggleMenuAction.menuActions = Separator.join(hiddenActions, secondaryActions); + } + } + private clear(): void { this.submenuActionViewItems = []; this.disposables.clear(); diff --git a/code/src/vs/base/browser/ui/tree/abstractTree.ts b/code/src/vs/base/browser/ui/tree/abstractTree.ts index 3c398d9215f..10c3bbfd304 100644 --- a/code/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/code/src/vs/base/browser/ui/tree/abstractTree.ts @@ -33,11 +33,10 @@ import { clamp } from '../../../common/numbers.js'; import { ScrollEvent } from '../../../common/scrollable.js'; import './media/tree.css'; import { localize } from '../../../../nls.js'; -import { IHoverDelegate } from '../hover/hoverDelegate.js'; -import { createInstantHoverDelegate } from '../hover/hoverDelegateFactory.js'; import { autorun, constObservable } from '../../../common/observable.js'; import { alert } from '../aria/aria.js'; import { IMouseWheelEvent } from '../../mouseEvent.js'; +import { type IHoverLifecycleOptions } from '../hover/hover.js'; class TreeElementsDragAndDropData extends ElementsDragAndDropData { @@ -168,10 +167,12 @@ function asListOptions(modelProvider: () => ITreeModel | IListTouchEvent); }, isSelectionRangeChangeEvent(e) { - return options.multipleSelectionController!.isSelectionRangeChangeEvent({ ...e, element: e.element } as any); + // eslint-disable-next-line local/code-no-dangerous-type-assertions + return options.multipleSelectionController!.isSelectionRangeChangeEvent({ ...e, element: e.element } as IListMouseEvent | IListTouchEvent); } }, accessibilityProvider: options.accessibilityProvider && { @@ -303,11 +304,12 @@ export enum RenderIndentGuides { Always = 'always' } -interface ITreeRendererOptions { +interface ITreeRendererOptions { readonly indent?: number; readonly renderIndentGuides?: RenderIndentGuides; // TODO@joao replace this with collapsible: boolean | 'ondemand' readonly hideTwistiesOfChildlessElements?: boolean; + readonly twistieAdditionalCssClass?: (element: T) => string | undefined; } interface Collection { @@ -342,6 +344,7 @@ export class TreeRenderer implements IListR private renderedNodes = new Map, ITreeListTemplateData>(); private indent: number = TreeRenderer.DefaultIndent; private hideTwistiesOfChildlessElements: boolean = false; + private twistieAdditionalCssClass?: (element: T) => string | undefined; private shouldRenderIndentGuides: boolean = false; private activeIndentNodes = new Set>(); @@ -355,7 +358,7 @@ export class TreeRenderer implements IListR onDidChangeCollapseState: Event>, private readonly activeNodes: Collection>, private readonly renderedIndentGuides: SetMap, HTMLDivElement>, - options: ITreeRendererOptions = {} + options: ITreeRendererOptions = {} ) { this.templateId = renderer.templateId; this.updateOptions(options); @@ -364,7 +367,7 @@ export class TreeRenderer implements IListR renderer.onDidChangeTwistieState?.(this.onDidChangeTwistieState, this, this.disposables); } - updateOptions(options: ITreeRendererOptions = {}): void { + updateOptions(options: ITreeRendererOptions = {}): void { if (typeof options.indent !== 'undefined') { const indent = clamp(options.indent, 0, 40); @@ -403,6 +406,10 @@ export class TreeRenderer implements IListR if (typeof options.hideTwistiesOfChildlessElements !== 'undefined') { this.hideTwistiesOfChildlessElements = options.hideTwistiesOfChildlessElements; } + + if (typeof options.twistieAdditionalCssClass !== 'undefined') { + this.twistieAdditionalCssClass = options.twistieAdditionalCssClass; + } } renderTemplate(container: HTMLElement): ITreeListTemplateData { @@ -461,6 +468,7 @@ export class TreeRenderer implements IListR } private renderTreeElement(node: ITreeNode, templateData: ITreeListTemplateData): void { + templateData.twistie.className = templateData.twistie.classList.item(0)!; templateData.twistie.style.paddingLeft = `${templateData.indentSize}px`; templateData.indent.style.width = `${templateData.indentSize + this.indent - 16}px`; @@ -489,6 +497,14 @@ export class TreeRenderer implements IListR templateData.twistie.classList.remove('collapsible', 'collapsed'); } + // Additional twistie class + if (this.twistieAdditionalCssClass) { + const additionalClass = this.twistieAdditionalCssClass(node.element); + if (additionalClass) { + templateData.twistie.classList.add(additionalClass); + } + } + this._renderIndentGuides(node, templateData); } @@ -708,7 +724,7 @@ class TreeFindToggle extends Toggle { readonly id: string; - constructor(contribution: ITreeFindToggleContribution, opts: IToggleStyles, hoverDelegate?: IHoverDelegate) { + constructor(contribution: ITreeFindToggleContribution, opts: IToggleStyles, hoverLifecycleOptions?: IHoverLifecycleOptions) { super({ icon: contribution.icon, title: contribution.title, @@ -716,7 +732,7 @@ class TreeFindToggle extends Toggle { inputActiveOptionBorder: opts.inputActiveOptionBorder, inputActiveOptionForeground: opts.inputActiveOptionForeground, inputActiveOptionBackground: opts.inputActiveOptionBackground, - hoverDelegate, + hoverLifecycleOptions, }); this.id = contribution.id; @@ -819,7 +835,7 @@ class FindWidget extends Disposable { constructor( container: HTMLElement, - private tree: AbstractTree, + private tree: AbstractTree, contextViewProvider: IContextViewProvider, placeholder: string, toggleContributions: ITreeFindToggleContribution[] = [], @@ -840,8 +856,9 @@ class FindWidget extends Disposable { this.elements.root.style.boxShadow = `0 0 8px 2px ${styles.listFilterWidgetShadow}`; } - const toggleHoverDelegate = this._register(createInstantHoverDelegate()); - this.toggles = toggleContributions.map(contribution => this._register(new TreeFindToggle(contribution, styles.toggleStyles, toggleHoverDelegate))); + // const toggleHoverDelegate = this._register(createInstantHoverDelegate()); + const hoverLifecycleOptions: IHoverLifecycleOptions = { groupId: 'abstract-tree' }; + this.toggles = toggleContributions.map(contribution => this._register(new TreeFindToggle(contribution, styles.toggleStyles, hoverLifecycleOptions))); this.onDidToggleChange = Event.any(...this.toggles.map(toggle => Event.map(toggle.onChange, () => ({ id: toggle.id, isChecked: toggle.checked })))); const history = options?.history || []; @@ -852,7 +869,8 @@ class FindWidget extends Disposable { showCommonFindToggles: false, inputBoxStyles: styles.inputBoxStyles, toggleStyles: styles.toggleStyles, - history: new Set(history) + history: new Set(history), + hoverLifecycleOptions, })); this.actionbar = this._register(new ActionBar(this.elements.actionbar)); @@ -985,7 +1003,7 @@ export abstract class AbstractFindController implements IDisposa protected readonly disposables = new DisposableStore(); constructor( - protected tree: AbstractTree, + protected tree: AbstractTree, protected filter: IFindFilter, protected readonly contextViewProvider: IContextViewProvider, protected readonly options: IAbstractFindControllerOptions = {} @@ -1127,7 +1145,7 @@ export class FindController extends AbstractFindController, + tree: AbstractTree, protected override filter: FindFilter, contextViewProvider: IContextViewProvider, options: IFindControllerOptions = {} @@ -1167,7 +1185,7 @@ export class FindController extends AbstractFindController this.filter.reset())); } - updateOptions(optionsUpdate: IAbstractTreeOptionsUpdate = {}): void { + updateOptions(optionsUpdate: IAbstractTreeOptionsUpdate = {}): void { if (optionsUpdate.defaultFindMode !== undefined) { this.mode = optionsUpdate.defaultFindMode; } @@ -1181,7 +1199,7 @@ export class FindController extends AbstractFindController !FuzzyScore.isDefault(node.filterData as any as FuzzyScore)); + this.tree.focusNext(0, true, undefined, (node) => !FuzzyScore.isDefault(node.filterData as unknown as FuzzyScore)); } const focus = this.tree.getFocus(); @@ -1206,7 +1224,7 @@ export class FindController extends AbstractFindController extends Disposable { private readonly tree: AbstractTree, private readonly model: ITreeModel, private readonly view: List>, - renderers: TreeRenderer[], + renderers: TreeRenderer[], private readonly treeDelegate: IListVirtualDelegate>, options: IAbstractTreeOptions = {}, ) { @@ -1615,7 +1633,7 @@ class StickyScrollController extends Disposable { return this._widget.focusedLast(); } - updateOptions(optionsUpdate: IAbstractTreeOptionsUpdate = {}): void { + updateOptions(optionsUpdate: IAbstractTreeOptionsUpdate = {}): void { if (optionsUpdate.paddingTop !== undefined) { this.paddingTop = optionsUpdate.paddingTop; } @@ -1629,7 +1647,7 @@ class StickyScrollController extends Disposable { } } - validateStickySettings(options: IAbstractTreeOptionsUpdate): { stickyScrollMaxItemCount: number } { + validateStickySettings(options: IAbstractTreeOptionsUpdate): { stickyScrollMaxItemCount: number } { let stickyScrollMaxItemCount = 7; if (typeof options.stickyScrollMaxItemCount === 'number') { stickyScrollMaxItemCount = Math.max(options.stickyScrollMaxItemCount, 1); @@ -1654,7 +1672,7 @@ class StickyScrollWidget implements IDisposable { container: HTMLElement, private readonly view: List>, private readonly tree: AbstractTree, - private readonly treeRenderers: TreeRenderer[], + private readonly treeRenderers: TreeRenderer[], private readonly treeDelegate: IListVirtualDelegate>, private readonly accessibilityProvider: IListAccessibilityProvider | undefined, ) { @@ -2140,7 +2158,7 @@ class StickyScrollFocus extends Disposable { } } -function asTreeMouseEvent(event: IListMouseEvent>): ITreeMouseEvent { +function asTreeMouseEvent(event: IListMouseEvent>): ITreeMouseEvent { let target: TreeMouseEventTarget = TreeMouseEventTarget.Unknown; if (hasParentWithClass(event.browserEvent.target as HTMLElement, 'monaco-tl-twistie', 'monaco-tl-row')) { @@ -2158,7 +2176,7 @@ function asTreeMouseEvent(event: IListMouseEvent>): ITreeMo }; } -function asTreeContextMenuEvent(event: IListContextMenuEvent>): ITreeContextMenuEvent { +function asTreeContextMenuEvent(event: IListContextMenuEvent>): ITreeContextMenuEvent { const isStickyScroll = isStickyScrollContainer(event.browserEvent.target as HTMLElement); return { @@ -2169,7 +2187,7 @@ function asTreeContextMenuEvent(event: IListContextMenuEvent extends ITreeRendererOptions { readonly multipleSelectionSupport?: boolean; readonly typeNavigationEnabled?: boolean; readonly typeNavigationMode?: TypeNavigationMode; @@ -2182,13 +2200,13 @@ export interface IAbstractTreeOptionsUpdate extends ITreeRendererOptions { readonly mouseWheelScrollSensitivity?: number; readonly fastScrollSensitivity?: number; readonly expandOnDoubleClick?: boolean; - readonly expandOnlyOnTwistieClick?: boolean | ((e: any) => boolean); // e is T + readonly expandOnlyOnTwistieClick?: boolean | ((e: T) => boolean); readonly enableStickyScroll?: boolean; readonly stickyScrollMaxItemCount?: number; readonly paddingTop?: number; } -export interface IAbstractTreeOptions extends IAbstractTreeOptionsUpdate, IListOptions { +export interface IAbstractTreeOptions extends IAbstractTreeOptionsUpdate, IListOptions { readonly contextViewProvider?: IContextViewProvider; readonly collapseByDefault?: boolean; // defaults to false readonly allowNonCollapsibleParents?: boolean; // defaults to false @@ -2198,7 +2216,7 @@ export interface IAbstractTreeOptions extends IAbstractTr readonly findWidgetEnabled?: boolean; readonly findWidgetStyles?: IFindWidgetStyles; readonly defaultFindVisibility?: TreeVisibility | ((e: T) => TreeVisibility); - readonly stickyScrollDelegate?: IStickyScrollDelegate; + readonly stickyScrollDelegate?: IStickyScrollDelegate; readonly disableExpandOnSpacebar?: boolean; // defaults to false } @@ -2213,14 +2231,14 @@ function dfs(node: ITreeNode, fn: (node: ITreeNo */ class Trait { - private nodes: ITreeNode[] = []; + private nodes: ITreeNode[] = []; private elements: T[] | undefined; private readonly _onDidChange = new Emitter>(); readonly onDidChange = this._onDidChange.event; - private _nodeSet: Set> | undefined; - private get nodeSet(): Set> { + private _nodeSet: Set> | undefined; + private get nodeSet(): Set> { if (!this._nodeSet) { this._nodeSet = this.createNodeSet(); } @@ -2229,19 +2247,20 @@ class Trait { } constructor( - private getFirstViewElementWithTrait: () => ITreeNode | undefined, + private getFirstViewElementWithTrait: () => ITreeNode | undefined, private identityProvider?: IIdentityProvider ) { } - set(nodes: ITreeNode[], browserEvent?: UIEvent): void { - if (!(browserEvent as any)?.__forceEvent && equals(this.nodes, nodes)) { + set(nodes: ITreeNode[], browserEvent?: UIEvent): void { + const event = browserEvent as UIEvent & { __forceEvent?: boolean }; + if (!(event?.__forceEvent) && equals(this.nodes, nodes)) { return; } this._set(nodes, false, browserEvent); } - private _set(nodes: ITreeNode[], silent: boolean, browserEvent?: UIEvent): void { + private _set(nodes: ITreeNode[], silent: boolean, browserEvent?: UIEvent): void { this.nodes = [...nodes]; this.elements = undefined; this._nodeSet = undefined; @@ -2260,32 +2279,32 @@ class Trait { return [...this.elements]; } - getNodes(): readonly ITreeNode[] { + getNodes(): readonly ITreeNode[] { return this.nodes; } - has(node: ITreeNode): boolean { + has(node: ITreeNode): boolean { return this.nodeSet.has(node); } - onDidModelSplice({ insertedNodes, deletedNodes }: ITreeModelSpliceEvent): void { + onDidModelSplice({ insertedNodes, deletedNodes }: ITreeModelSpliceEvent): void { if (!this.identityProvider) { const set = this.createNodeSet(); - const visit = (node: ITreeNode) => set.delete(node); + const visit = (node: ITreeNode) => set.delete(node); deletedNodes.forEach(node => dfs(node, visit)); this.set([...set.values()]); return; } const deletedNodesIdSet = new Set(); - const deletedNodesVisitor = (node: ITreeNode) => deletedNodesIdSet.add(this.identityProvider!.getId(node.element).toString()); + const deletedNodesVisitor = (node: ITreeNode) => deletedNodesIdSet.add(this.identityProvider!.getId(node.element).toString()); deletedNodes.forEach(node => dfs(node, deletedNodesVisitor)); - const insertedNodesMap = new Map>(); - const insertedNodesVisitor = (node: ITreeNode) => insertedNodesMap.set(this.identityProvider!.getId(node.element).toString(), node); + const insertedNodesMap = new Map>(); + const insertedNodesVisitor = (node: ITreeNode) => insertedNodesMap.set(this.identityProvider!.getId(node.element).toString(), node); insertedNodes.forEach(node => dfs(node, insertedNodesVisitor)); - const nodes: ITreeNode[] = []; + const nodes: ITreeNode[] = []; for (const node of this.nodes) { const id = this.identityProvider.getId(node.element).toString(); @@ -2313,8 +2332,8 @@ class Trait { this._set(nodes, true); } - private createNodeSet(): Set> { - const set = new Set>(); + private createNodeSet(): Set> { + const set = new Set>(); for (const node of this.nodes) { set.add(node); @@ -2467,7 +2486,7 @@ class TreeNodeList extends List> user: string, container: HTMLElement, virtualDelegate: IListVirtualDelegate>, - renderers: IListRenderer[], + renderers: IListRenderer, unknown>[], private focusTrait: Trait, private selectionTrait: Trait, private anchorTrait: Trait, @@ -2555,7 +2574,7 @@ export const enum AbstractTreePart { export abstract class AbstractTree implements IDisposable { protected view: TreeNodeList; - private renderers: TreeRenderer[]; + private renderers: TreeRenderer[]; protected model: ITreeModel; private treeDelegate: ComposedTreeDelegate>; private focus: Trait; @@ -2578,6 +2597,7 @@ export abstract class AbstractTree implements IDisposable get onMouseClick(): Event> { return Event.map(this.view.onMouseClick, asTreeMouseEvent); } get onMouseDblClick(): Event> { return Event.filter(Event.map(this.view.onMouseDblClick, asTreeMouseEvent), e => e.target !== TreeMouseEventTarget.Filter); } + get onMouseMiddleClick(): Event> { return Event.filter(Event.map(this.view.onMouseMiddleClick, asTreeMouseEvent), e => e.target !== TreeMouseEventTarget.Filter); } get onMouseOver(): Event> { return Event.map(this.view.onMouseOver, asTreeMouseEvent); } get onMouseOut(): Event> { return Event.map(this.view.onMouseOut, asTreeMouseEvent); } get onContextMenu(): Event> { return Event.any(Event.filter(Event.map(this.view.onContextMenu, asTreeContextMenuEvent), e => !e.isStickyScroll), this.stickyScrollController?.onContextMenu ?? Event.None); } @@ -2627,7 +2647,7 @@ export abstract class AbstractTree implements IDisposable private readonly _user: string, container: HTMLElement, delegate: IListVirtualDelegate, - renderers: ITreeRenderer[], + renderers: ITreeRenderer[], private _options: IAbstractTreeOptions = {} ) { if (_options.keyboardNavigationLabelProvider && (_options.findWidgetEnabled ?? true)) { @@ -2641,7 +2661,7 @@ export abstract class AbstractTree implements IDisposable const activeNodes = this.disposables.add(new EventCollection(this.onDidChangeActiveNodesRelay.event)); const renderedIndentGuides = new SetMap, HTMLDivElement>(); - this.renderers = renderers.map(r => new TreeRenderer(r, this.model, this.onDidChangeCollapseStateRelay.event, activeNodes, renderedIndentGuides, _options)); + this.renderers = renderers.map(r => new TreeRenderer(r, this.model, this.onDidChangeCollapseStateRelay.event, activeNodes, renderedIndentGuides, _options)); for (const r of this.renderers) { this.disposables.add(r); } @@ -2690,7 +2710,7 @@ export abstract class AbstractTree implements IDisposable this.getHTMLElement().classList.toggle('always', this._options.renderIndentGuides === RenderIndentGuides.Always); } - updateOptions(optionsUpdate: IAbstractTreeOptionsUpdate = {}): void { + updateOptions(optionsUpdate: IAbstractTreeOptionsUpdate = {}): void { this._options = { ...this._options, ...optionsUpdate }; for (const renderer of this.renderers) { @@ -2710,7 +2730,7 @@ export abstract class AbstractTree implements IDisposable return this._options; } - private updateStickyScroll(optionsUpdate: IAbstractTreeOptionsUpdate) { + private updateStickyScroll(optionsUpdate: IAbstractTreeOptionsUpdate) { if (!this.stickyScrollController && this._options.enableStickyScroll) { this.stickyScrollController = new StickyScrollController(this, this.model, this.view, this.renderers, this.treeDelegate, this._options); this.onDidChangeStickyScrollFocused = this.stickyScrollController.onDidChangeHasFocus; @@ -2870,10 +2890,10 @@ export abstract class AbstractTree implements IDisposable content.push(`.monaco-list${suffix}.sticky-scroll-focused .monaco-scrollable-element .monaco-tree-sticky-container:focus .monaco-list-row.focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; }`); content.push(`.monaco-list${suffix}:not(.sticky-scroll-focused) .monaco-scrollable-element .monaco-tree-sticky-container .monaco-list-row.focused { outline: inherit; }`); - content.push(`.monaco-workbench.context-menu-visible .monaco-list${suffix}.last-focused.sticky-scroll-focused .monaco-scrollable-element .monaco-tree-sticky-container .monaco-list-row.passive-focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; }`); + content.push(`.context-menu-visible .monaco-list${suffix}.last-focused.sticky-scroll-focused .monaco-scrollable-element .monaco-tree-sticky-container .monaco-list-row.passive-focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; }`); - content.push(`.monaco-workbench.context-menu-visible .monaco-list${suffix}.last-focused.sticky-scroll-focused .monaco-list-rows .monaco-list-row.focused { outline: inherit; }`); - content.push(`.monaco-workbench.context-menu-visible .monaco-list${suffix}.last-focused:not(.sticky-scroll-focused) .monaco-tree-sticky-container .monaco-list-rows .monaco-list-row.focused { outline: inherit; }`); + content.push(`.context-menu-visible .monaco-list${suffix}.last-focused.sticky-scroll-focused .monaco-list-rows .monaco-list-row.focused { outline: inherit; }`); + content.push(`.context-menu-visible .monaco-list${suffix}.last-focused:not(.sticky-scroll-focused) .monaco-tree-sticky-container .monaco-list-rows .monaco-list-row.focused { outline: inherit; }`); } this.styleElement.textContent = content.join('\n'); @@ -3198,16 +3218,16 @@ export abstract class AbstractTree implements IDisposable // a nice to have UI feature. const activeNodesEmitter = this.modelDisposables.add(new Emitter[]>()); const activeNodesDebounce = this.modelDisposables.add(new Delayer(0)); - this.modelDisposables.add(Event.any(onDidModelSplice, this.focus.onDidChange, this.selection.onDidChange)(() => { + this.modelDisposables.add(Event.any(onDidModelSplice, this.focus.onDidChange, this.selection.onDidChange)(() => { activeNodesDebounce.trigger(() => { const set = new Set>(); for (const node of this.focus.getNodes()) { - set.add(node); + set.add(node as ITreeNode); } for (const node of this.selection.getNodes()) { - set.add(node); + set.add(node as ITreeNode); } activeNodesEmitter.fire([...set.values()]); @@ -3237,12 +3257,12 @@ export abstract class AbstractTree implements IDisposable } } -interface ITreeNavigatorView, TFilterData> { +interface ITreeNavigatorView { readonly length: number; element(index: number): ITreeNode; } -class TreeNavigator, TFilterData, TRef> implements ITreeNavigator { +class TreeNavigator implements ITreeNavigator { private index: number; diff --git a/code/src/vs/base/browser/ui/tree/asyncDataTree.ts b/code/src/vs/base/browser/ui/tree/asyncDataTree.ts index 5b29605dce5..6112f2bfba3 100644 --- a/code/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/code/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { IDragAndDropData } from '../../dnd.js'; -import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListDragAndDrop, IListDragOverReaction, IListVirtualDelegate } from '../list/list.js'; +import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListDragAndDrop, IListDragOverReaction, IListMouseEvent, IListTouchEvent, IListVirtualDelegate } from '../list/list.js'; import { ElementsDragAndDropData, ListViewTargetSector } from '../list/listView.js'; import { IListStyles } from '../list/listWidget.js'; -import { ComposedTreeDelegate, TreeFindMode as TreeFindMode, IAbstractTreeOptions, IAbstractTreeOptionsUpdate, TreeFindMatchType, AbstractTreePart, LabelFuzzyScore, FindFilter, FindController, ITreeFindToggleChangeEvent, IFindControllerOptions } from './abstractTree.js'; +import { ComposedTreeDelegate, TreeFindMode as TreeFindMode, IAbstractTreeOptions, IAbstractTreeOptionsUpdate, TreeFindMatchType, AbstractTreePart, LabelFuzzyScore, FindFilter, FindController, ITreeFindToggleChangeEvent, IFindControllerOptions, IStickyScrollDelegate, AbstractTree } from './abstractTree.js'; import { ICompressedTreeElement, ICompressedTreeNode } from './compressedObjectTreeModel.js'; import { getVisibleState, isFilterResult } from './indexTreeModel.js'; import { CompressibleObjectTree, ICompressibleKeyboardNavigationLabelProvider, ICompressibleObjectTreeOptions, ICompressibleTreeRenderer, IObjectTreeOptions, IObjectTreeSetChildrenOptions, ObjectTree } from './objectTree.js'; @@ -27,7 +27,7 @@ import { FuzzyScore } from '../../../common/filters.js'; import { insertInto, splice } from '../../../common/arrays.js'; import { localize } from '../../../../nls.js'; -interface IAsyncDataTreeNode { +export interface IAsyncDataTreeNode { element: TInput | T; readonly parent: IAsyncDataTreeNode | null; readonly children: IAsyncDataTreeNode[]; @@ -302,7 +302,7 @@ class AsyncFindController extends FindController, TFilterData>, ) { - super(tree as any, filter, contextViewProvider, options); + super(tree as unknown as AbstractTree, filter, contextViewProvider, options); // Always make sure to end the session before disposing this.disposables.add(toDisposable(async () => { if (this.activeSession) { @@ -413,7 +413,7 @@ class AsyncFindController extends FindController(options?: IAsyncDataTreeOpt dnd: options.dnd && new AsyncDataTreeNodeListDragAndDrop(options.dnd), multipleSelectionController: options.multipleSelectionController && { isSelectionSingleChangeEvent(e) { - return options.multipleSelectionController!.isSelectionSingleChangeEvent({ ...e, element: e.element } as any); + // eslint-disable-next-line local/code-no-dangerous-type-assertions + return options.multipleSelectionController!.isSelectionSingleChangeEvent({ ...e, element: e.element } as IListMouseEvent | IListTouchEvent); }, isSelectionRangeChangeEvent(e) { - return options.multipleSelectionController!.isSelectionRangeChangeEvent({ ...e, element: e.element } as any); + // eslint-disable-next-line local/code-no-dangerous-type-assertions + return options.multipleSelectionController!.isSelectionRangeChangeEvent({ ...e, element: e.element } as IListMouseEvent | IListTouchEvent); } }, accessibilityProvider: options.accessibilityProvider && { @@ -473,10 +475,13 @@ function asObjectTreeOptions(options?: IAsyncDataTreeOpt sorter: undefined, expandOnlyOnTwistieClick: typeof options.expandOnlyOnTwistieClick === 'undefined' ? undefined : ( typeof options.expandOnlyOnTwistieClick !== 'function' ? options.expandOnlyOnTwistieClick : ( - e => (options.expandOnlyOnTwistieClick as ((e: T) => boolean))(e.element as T) + ((e: IAsyncDataTreeNode) => (options.expandOnlyOnTwistieClick as ((e: T) => boolean))(e.element as T)) as ((e: unknown) => boolean) ) ), - defaultFindVisibility: e => { + twistieAdditionalCssClass: typeof options.twistieAdditionalCssClass === 'undefined' ? undefined : ( + ((e: IAsyncDataTreeNode) => (options.twistieAdditionalCssClass as ((e: T) => string | undefined))(e.element as T)) as ((e: unknown) => string | undefined) + ), + defaultFindVisibility: (e: IAsyncDataTreeNode) => { if (e.hasChildren && e.stale) { return TreeVisibility.Visible; } else if (typeof options.defaultFindVisibility === 'number') { @@ -486,14 +491,14 @@ function asObjectTreeOptions(options?: IAsyncDataTreeOpt } else { return (options.defaultFindVisibility as ((e: T) => TreeVisibility))(e.element as T); } - } + }, + stickyScrollDelegate: options.stickyScrollDelegate as IStickyScrollDelegate, TFilterData> | undefined }; } - -export interface IAsyncDataTreeOptionsUpdate extends IAbstractTreeOptionsUpdate { } +export interface IAsyncDataTreeOptionsUpdate extends IAbstractTreeOptionsUpdate { } export interface IAsyncDataTreeUpdateChildrenOptions extends IObjectTreeSetChildrenOptions { } -export interface IAsyncDataTreeOptions extends IAsyncDataTreeOptionsUpdate, Pick, Exclude, 'collapseByDefault'>> { +export interface IAsyncDataTreeOptions extends IAsyncDataTreeOptionsUpdate, Pick, Exclude, 'collapseByDefault'>> { readonly collapseByDefault?: { (e: T): boolean }; readonly identityProvider?: IIdentityProvider; readonly sorter?: ITreeSorter; @@ -562,7 +567,7 @@ export class AsyncDataTree implements IDisposable get onDidChangeModel(): Event { return this.tree.onDidChangeModel; } get onDidChangeCollapseState(): Event | null, TFilterData>> { return this.tree.onDidChangeCollapseState; } - get onDidUpdateOptions(): Event { return this.tree.onDidUpdateOptions; } + get onDidUpdateOptions(): Event>> { return this.tree.onDidUpdateOptions; } private focusNavigationFilter: ((node: ITreeNode | null, TFilterData>) => boolean) | undefined; @@ -592,7 +597,7 @@ export class AsyncDataTree implements IDisposable protected user: string, container: HTMLElement, delegate: IListVirtualDelegate, - renderers: ITreeRenderer[], + renderers: ITreeRenderer[], private dataSource: IAsyncDataSource, options: IAsyncDataTreeOptions = {} ) { @@ -638,9 +643,9 @@ export class AsyncDataTree implements IDisposable this.findController = this.disposables.add(new AsyncFindController(this.tree, options.findProvider!, findFilter!, this.tree.options.contextViewProvider!, findOptions)); this.focusNavigationFilter = node => this.findController!.shouldFocusWhenNavigating(node); - this.onDidChangeFindOpenState = this.findController!.onDidChangeOpenState; - this.onDidChangeFindMode = this.findController!.onDidChangeMode; - this.onDidChangeFindMatchType = this.findController!.onDidChangeMatchType; + this.onDidChangeFindOpenState = this.findController.onDidChangeOpenState; + this.onDidChangeFindMode = this.findController.onDidChangeMode; + this.onDidChangeFindMatchType = this.findController.onDidChangeMatchType; } else { this.onDidChangeFindOpenState = this.tree.onDidChangeFindOpenState; this.onDidChangeFindMode = this.tree.onDidChangeFindMode; @@ -652,7 +657,7 @@ export class AsyncDataTree implements IDisposable user: string, container: HTMLElement, delegate: IListVirtualDelegate, - renderers: ITreeRenderer[], + renderers: ITreeRenderer[], options: IAsyncDataTreeOptions ): ObjectTree, TFilterData> { const objectTreeDelegate = new ComposedTreeDelegate>(delegate); @@ -662,7 +667,7 @@ export class AsyncDataTree implements IDisposable return new ObjectTree(user, container, objectTreeDelegate, objectTreeRenderers, objectTreeOptions); } - updateOptions(optionsUpdate: IAsyncDataTreeOptionsUpdate = {}): void { + updateOptions(optionsUpdate: IAsyncDataTreeOptionsUpdate | null> = {}): void { if (this.findController) { if (optionsUpdate.defaultFindMode !== undefined) { this.findController.mode = optionsUpdate.defaultFindMode; @@ -1179,7 +1184,7 @@ export class AsyncDataTree implements IDisposable } } - private _onDidChangeCollapseState({ node, deep }: ICollapseStateChangeEvent | null, any>): void { + private _onDidChangeCollapseState({ node, deep }: ICollapseStateChangeEvent | null, TFilterData>): void { if (node.element === null) { return; } @@ -1477,7 +1482,8 @@ function asCompressibleObjectTreeOptions(options?: IComp getCompressedNodeKeyboardNavigationLabel(els) { return options.keyboardNavigationLabelProvider!.getCompressedNodeKeyboardNavigationLabel(els.map(e => e.element as T)); } - } + }, + stickyScrollDelegate: objectTreeOptions.stickyScrollDelegate as IStickyScrollDelegate, TFilterData> | undefined }; } @@ -1486,7 +1492,7 @@ export interface ICompressibleAsyncDataTreeOptions extend readonly keyboardNavigationLabelProvider?: ICompressibleKeyboardNavigationLabelProvider; } -export interface ICompressibleAsyncDataTreeOptionsUpdate extends IAsyncDataTreeOptionsUpdate { +export interface ICompressibleAsyncDataTreeOptionsUpdate extends IAsyncDataTreeOptionsUpdate { readonly compressionEnabled?: boolean; } @@ -1501,7 +1507,7 @@ export class CompressibleAsyncDataTree extends As container: HTMLElement, virtualDelegate: IListVirtualDelegate, private compressionDelegate: ITreeCompressionDelegate, - renderers: ICompressibleTreeRenderer[], + renderers: ICompressibleTreeRenderer[], dataSource: IAsyncDataSource, options: ICompressibleAsyncDataTreeOptions = {} ) { @@ -1518,7 +1524,7 @@ export class CompressibleAsyncDataTree extends As user: string, container: HTMLElement, delegate: IListVirtualDelegate, - renderers: ICompressibleTreeRenderer[], + renderers: ICompressibleTreeRenderer[], options: ICompressibleAsyncDataTreeOptions ): ObjectTree, TFilterData> { const objectTreeDelegate = new ComposedTreeDelegate>(delegate); diff --git a/code/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts b/code/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts index f8057a418c0..e4adc832676 100644 --- a/code/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts +++ b/code/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts @@ -117,7 +117,7 @@ const wrapIdentityProvider = (base: IIdentityProvider): IIdentityProvider< }); // Exported only for test reasons, do not use directly -export class CompressedObjectTreeModel, TFilterData extends NonNullable = void> implements ITreeModel | null, TFilterData, T | null> { +export class CompressedObjectTreeModel implements ITreeModel | null, TFilterData, T | null> { readonly rootRef = null; @@ -351,7 +351,7 @@ export class CompressedObjectTreeModel, TFilterData e // Compressible Object Tree export type ElementMapper = (elements: T[]) => T; -export const DefaultElementMapper: ElementMapper = elements => elements[elements.length - 1]; +export const DefaultElementMapper: ElementMapper = elements => elements[elements.length - 1]; export type CompressedNodeUnwrapper = (node: ICompressedTreeNode) => T; type CompressedNodeWeakMapper = WeakMapper | null, TFilterData>, ITreeNode>; @@ -405,7 +405,7 @@ export interface ICompressibleObjectTreeModelOptions extends IOb readonly elementMapper?: ElementMapper; } -export class CompressibleObjectTreeModel, TFilterData extends NonNullable = void> implements IObjectTreeModel { +export class CompressibleObjectTreeModel implements IObjectTreeModel { readonly rootRef = null; @@ -443,7 +443,7 @@ export class CompressibleObjectTreeModel, TFilterData user: string, options: ICompressibleObjectTreeModelOptions = {} ) { - this.elementMapper = options.elementMapper || DefaultElementMapper; + this.elementMapper = options.elementMapper || (DefaultElementMapper as ElementMapper); const compressedNodeUnwrapper: CompressedNodeUnwrapper = node => this.elementMapper(node.elements); this.nodeMapper = new WeakMapper(node => new CompressedTreeNodeWrapper(compressedNodeUnwrapper, node)); @@ -478,11 +478,11 @@ export class CompressibleObjectTreeModel, TFilterData return this.model.getListRenderCount(location); } - getNode(location?: T | null | undefined): ITreeNode { + getNode(location?: T | null | undefined): ITreeNode { return this.nodeMapper.map(this.model.getNode(location)); } - getNodeLocation(node: ITreeNode): T | null { + getNodeLocation(node: ITreeNode): T | null { return node.element; } diff --git a/code/src/vs/base/browser/ui/tree/dataTree.ts b/code/src/vs/base/browser/ui/tree/dataTree.ts index 09eec7634ba..ff3fd98af78 100644 --- a/code/src/vs/base/browser/ui/tree/dataTree.ts +++ b/code/src/vs/base/browser/ui/tree/dataTree.ts @@ -25,7 +25,7 @@ export class DataTree extends AbstractTree, - renderers: ITreeRenderer[], + renderers: ITreeRenderer[], private dataSource: IDataSource, options: IDataTreeOptions = {} ) { @@ -167,7 +167,7 @@ export class DataTree extends AbstractTree): ITreeModel { + protected createModel(user: string, options: IDataTreeOptions): ITreeModel { return new ObjectTreeModel(user, options); } } diff --git a/code/src/vs/base/browser/ui/tree/indexTree.ts b/code/src/vs/base/browser/ui/tree/indexTree.ts index cc43faca89e..f3e2bb30c75 100644 --- a/code/src/vs/base/browser/ui/tree/indexTree.ts +++ b/code/src/vs/base/browser/ui/tree/indexTree.ts @@ -20,7 +20,7 @@ export class IndexTree extends AbstractTree, - renderers: ITreeRenderer[], + renderers: ITreeRenderer[], private rootElement: T, options: IIndexTreeOptions = {} ) { diff --git a/code/src/vs/base/browser/ui/tree/indexTreeModel.ts b/code/src/vs/base/browser/ui/tree/indexTreeModel.ts index f0a9d56150e..a9a2a8ae65b 100644 --- a/code/src/vs/base/browser/ui/tree/indexTreeModel.ts +++ b/code/src/vs/base/browser/ui/tree/indexTreeModel.ts @@ -86,10 +86,10 @@ interface CollapsedStateUpdate { type CollapseStateUpdate = CollapsibleStateUpdate | CollapsedStateUpdate; function isCollapsibleStateUpdate(update: CollapseStateUpdate): update is CollapsibleStateUpdate { - return typeof (update as any).collapsible === 'boolean'; + return 'collapsible' in update; } -export class IndexTreeModel, TFilterData = void> implements ITreeModel { +export class IndexTreeModel, TFilterData = void> implements ITreeModel { readonly rootRef = []; diff --git a/code/src/vs/base/browser/ui/tree/media/tree.css b/code/src/vs/base/browser/ui/tree/media/tree.css index 7bd2f13dbaf..b5330b90041 100644 --- a/code/src/vs/base/browser/ui/tree/media/tree.css +++ b/code/src/vs/base/browser/ui/tree/media/tree.css @@ -33,7 +33,7 @@ opacity: 0; } -.monaco-workbench:not(.reduce-motion) .monaco-tl-indent > .indent-guide { +.monaco-enable-motion .monaco-tl-indent > .indent-guide { transition: opacity 0.1s linear; } @@ -86,7 +86,7 @@ border-bottom-right-radius: 4px; } -.monaco-workbench:not(.reduce-motion) .monaco-tree-type-filter { +.monaco-enable-motion .monaco-tree-type-filter { transition: top 0.3s; } diff --git a/code/src/vs/base/browser/ui/tree/objectTree.ts b/code/src/vs/base/browser/ui/tree/objectTree.ts index f554eca9a88..d9100592a4b 100644 --- a/code/src/vs/base/browser/ui/tree/objectTree.ts +++ b/code/src/vs/base/browser/ui/tree/objectTree.ts @@ -35,7 +35,7 @@ export interface IObjectTreeSetChildrenOptions { readonly diffIdentityProvider?: IIdentityProvider; } -export class ObjectTree, TFilterData = void> extends AbstractTree { +export class ObjectTree extends AbstractTree { protected declare model: IObjectTreeModel; @@ -45,7 +45,7 @@ export class ObjectTree, TFilterData = void> extends protected readonly user: string, container: HTMLElement, delegate: IListVirtualDelegate, - renderers: ITreeRenderer[], + renderers: ITreeRenderer[], options: IObjectTreeOptions = {} ) { super(user, container, delegate, renderers, options as IObjectTreeOptions); @@ -81,7 +81,7 @@ export class ObjectTree, TFilterData = void> extends return this.model.has(element); } - protected createModel(user: string, options: IObjectTreeOptions): ITreeModel { + protected createModel(user: string, options: IObjectTreeOptions): ITreeModel { return new ObjectTreeModel(user, options); } } @@ -100,7 +100,7 @@ interface CompressibleTemplateData { readonly data: TTemplateData; } -class CompressibleRenderer, TFilterData, TTemplateData> implements ITreeRenderer> { +class CompressibleRenderer implements ITreeRenderer> { readonly templateId: string; readonly onDidChangeTwistieState: Event | undefined; @@ -271,11 +271,11 @@ function asObjectTreeOptions(compressedTreeNodeProvider: () => I }; } -export interface ICompressibleObjectTreeOptionsUpdate extends IAbstractTreeOptionsUpdate { +export interface ICompressibleObjectTreeOptionsUpdate extends IAbstractTreeOptionsUpdate { readonly compressionEnabled?: boolean; } -export class CompressibleObjectTree, TFilterData = void> extends ObjectTree implements ICompressedTreeNodeProvider { +export class CompressibleObjectTree extends ObjectTree implements ICompressedTreeNodeProvider { protected declare model: CompressibleObjectTreeModel; @@ -283,12 +283,12 @@ export class CompressibleObjectTree, TFilterData = vo user: string, container: HTMLElement, delegate: IListVirtualDelegate, - renderers: ICompressibleTreeRenderer[], + renderers: ICompressibleTreeRenderer[], options: ICompressibleObjectTreeOptions = {} ) { const compressedTreeNodeProvider = () => this; const stickyScrollDelegate = new CompressibleStickyScrollDelegate(() => this.model); - const compressibleRenderers = renderers.map(r => new CompressibleRenderer(compressedTreeNodeProvider, stickyScrollDelegate, r)); + const compressibleRenderers = renderers.map(r => new CompressibleRenderer(compressedTreeNodeProvider, stickyScrollDelegate, r)); super(user, container, delegate, compressibleRenderers, { ...asObjectTreeOptions(compressedTreeNodeProvider, options), stickyScrollDelegate }); } @@ -297,11 +297,11 @@ export class CompressibleObjectTree, TFilterData = vo this.model.setChildren(element, children, options); } - protected override createModel(user: string, options: ICompressibleObjectTreeOptions): ITreeModel { + protected override createModel(user: string, options: ICompressibleObjectTreeOptions): ITreeModel { return new CompressibleObjectTreeModel(user, options); } - override updateOptions(optionsUpdate: ICompressibleObjectTreeOptionsUpdate = {}): void { + override updateOptions(optionsUpdate: ICompressibleObjectTreeOptionsUpdate = {}): void { super.updateOptions(optionsUpdate); if (typeof optionsUpdate.compressionEnabled !== 'undefined') { diff --git a/code/src/vs/base/browser/ui/tree/objectTreeModel.ts b/code/src/vs/base/browser/ui/tree/objectTreeModel.ts index 57bec495e51..39bb3412e0c 100644 --- a/code/src/vs/base/browser/ui/tree/objectTreeModel.ts +++ b/code/src/vs/base/browser/ui/tree/objectTreeModel.ts @@ -11,7 +11,7 @@ import { Iterable } from '../../../common/iterator.js'; export type ITreeNodeCallback = (node: ITreeNode) => void; -export interface IObjectTreeModel, TFilterData extends NonNullable = void> extends ITreeModel { +export interface IObjectTreeModel extends ITreeModel { setChildren(element: T | null, children: Iterable> | undefined, options?: IObjectTreeModelSetChildrenOptions): void; resort(element?: T | null, recursive?: boolean): void; } @@ -24,7 +24,7 @@ export interface IObjectTreeModelOptions extends IIndexTreeModel readonly identityProvider?: IIdentityProvider; } -export class ObjectTreeModel, TFilterData extends NonNullable = void> implements IObjectTreeModel { +export class ObjectTreeModel implements IObjectTreeModel { readonly rootRef = null; diff --git a/code/src/vs/base/browser/ui/tree/tree.ts b/code/src/vs/base/browser/ui/tree/tree.ts index 2ef6d7ceab2..fe579cfa9f8 100644 --- a/code/src/vs/base/browser/ui/tree/tree.ts +++ b/code/src/vs/base/browser/ui/tree/tree.ts @@ -142,8 +142,8 @@ export interface ITreeModel { getListIndex(location: TRef): number; getListRenderCount(location: TRef): number; - getNode(location?: TRef): ITreeNode; - getNodeLocation(node: ITreeNode): TRef; + getNode(location?: TRef): ITreeNode; + getNodeLocation(node: ITreeNode): TRef; getParentNodeLocation(location: TRef): TRef | undefined; getFirstElementChild(location: TRef): T | undefined; @@ -167,7 +167,7 @@ export interface ITreeRenderer exte renderElement(element: ITreeNode, index: number, templateData: TTemplateData, details?: ITreeElementRenderDetails): void; disposeElement?(element: ITreeNode, index: number, templateData: TTemplateData, details?: ITreeElementRenderDetails): void; renderTwistie?(element: T, twistieElement: HTMLElement): boolean; - onDidChangeTwistieState?: Event; + readonly onDidChangeTwistieState?: Event; } export interface ITreeEvent { diff --git a/code/src/vs/base/browser/webWorkerFactory.ts b/code/src/vs/base/browser/webWorkerFactory.ts deleted file mode 100644 index d55ef7601cc..00000000000 --- a/code/src/vs/base/browser/webWorkerFactory.ts +++ /dev/null @@ -1,205 +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 { createTrustedTypesPolicy } from './trustedTypes.js'; -import { onUnexpectedError } from '../common/errors.js'; -import { COI } from '../common/network.js'; -import { URI } from '../common/uri.js'; -import { IWebWorker, IWebWorkerClient, Message, WebWorkerClient } from '../common/worker/webWorker.js'; -import { Disposable, toDisposable } from '../common/lifecycle.js'; -import { coalesce } from '../common/arrays.js'; -import { getNLSLanguage, getNLSMessages } from '../../nls.js'; -import { Emitter } from '../common/event.js'; - -// Reuse the trusted types policy defined from worker bootstrap -// when available. -// Refs https://github.com/microsoft/vscode/issues/222193 -let ttPolicy: ReturnType; -if (typeof self === 'object' && self.constructor && self.constructor.name === 'DedicatedWorkerGlobalScope' && (globalThis as any).workerttPolicy !== undefined) { - ttPolicy = (globalThis as any).workerttPolicy; -} else { - ttPolicy = createTrustedTypesPolicy('defaultWorkerFactory', { createScriptURL: value => value }); -} - -export function createBlobWorker(blobUrl: string, options?: WorkerOptions): Worker { - if (!blobUrl.startsWith('blob:')) { - throw new URIError('Not a blob-url: ' + blobUrl); - } - return new Worker(ttPolicy ? ttPolicy.createScriptURL(blobUrl) as unknown as string : blobUrl, { ...options, type: 'module' }); -} - -function getWorker(descriptor: IWebWorkerDescriptor, id: number): Worker | Promise { - const label = descriptor.label || 'anonymous' + id; - - // Option for hosts to overwrite the worker script (used in the standalone editor) - interface IMonacoEnvironment { - getWorker?(moduleId: string, label: string): Worker | Promise; - getWorkerUrl?(moduleId: string, label: string): string; - } - const monacoEnvironment: IMonacoEnvironment | undefined = (globalThis as any).MonacoEnvironment; - if (monacoEnvironment) { - if (typeof monacoEnvironment.getWorker === 'function') { - return monacoEnvironment.getWorker('workerMain.js', label); - } - if (typeof monacoEnvironment.getWorkerUrl === 'function') { - const workerUrl = monacoEnvironment.getWorkerUrl('workerMain.js', label); - return new Worker(ttPolicy ? ttPolicy.createScriptURL(workerUrl) as unknown as string : workerUrl, { name: label, type: 'module' }); - } - } - - const esmWorkerLocation = descriptor.esmModuleLocation; - if (esmWorkerLocation) { - const workerUrl = getWorkerBootstrapUrl(label, esmWorkerLocation.toString(true)); - const worker = new Worker(ttPolicy ? ttPolicy.createScriptURL(workerUrl) as unknown as string : workerUrl, { name: label, type: 'module' }); - return whenESMWorkerReady(worker); - } - - throw new Error(`You must define a function MonacoEnvironment.getWorkerUrl or MonacoEnvironment.getWorker`); -} - -function getWorkerBootstrapUrl(label: string, workerScriptUrl: string): string { - if (/^((http:)|(https:)|(file:))/.test(workerScriptUrl) && workerScriptUrl.substring(0, globalThis.origin.length) !== globalThis.origin) { - // this is the cross-origin case - // i.e. the webpage is running at a different origin than where the scripts are loaded from - } else { - const start = workerScriptUrl.lastIndexOf('?'); - const end = workerScriptUrl.lastIndexOf('#', start); - const params = start > 0 - ? new URLSearchParams(workerScriptUrl.substring(start + 1, ~end ? end : undefined)) - : new URLSearchParams(); - - COI.addSearchParam(params, true, true); - const search = params.toString(); - if (!search) { - workerScriptUrl = `${workerScriptUrl}#${label}`; - } else { - workerScriptUrl = `${workerScriptUrl}?${params.toString()}#${label}`; - } - } - - // In below blob code, we are using JSON.stringify to ensure the passed - // in values are not breaking our script. The values may contain string - // terminating characters (such as ' or "). - const blob = new Blob([coalesce([ - `/*${label}*/`, - `globalThis._VSCODE_NLS_MESSAGES = ${JSON.stringify(getNLSMessages())};`, - `globalThis._VSCODE_NLS_LANGUAGE = ${JSON.stringify(getNLSLanguage())};`, - `globalThis._VSCODE_FILE_ROOT = ${JSON.stringify(globalThis._VSCODE_FILE_ROOT)};`, - `const ttPolicy = globalThis.trustedTypes?.createPolicy('defaultWorkerFactory', { createScriptURL: value => value });`, - `globalThis.workerttPolicy = ttPolicy;`, - `await import(ttPolicy?.createScriptURL(${JSON.stringify(workerScriptUrl)}) ?? ${JSON.stringify(workerScriptUrl)});`, - `globalThis.postMessage({ type: 'vscode-worker-ready' });`, - `/*${label}*/` - ]).join('')], { type: 'application/javascript' }); - return URL.createObjectURL(blob); -} - -function whenESMWorkerReady(worker: Worker): Promise { - return new Promise((resolve, reject) => { - worker.onmessage = function (e) { - if (e.data.type === 'vscode-worker-ready') { - worker.onmessage = null; - resolve(worker); - } - }; - worker.onerror = reject; - }); -} - -function isPromiseLike(obj: unknown): obj is PromiseLike { - return !!obj && typeof (obj as PromiseLike).then === 'function'; -} - -/** - * A worker that uses HTML5 web workers so that is has - * its own global scope and its own thread. - */ -class WebWorker extends Disposable implements IWebWorker { - - private static LAST_WORKER_ID = 0; - - private readonly id: number; - private worker: Promise | null; - - private readonly _onMessage = this._register(new Emitter()); - public readonly onMessage = this._onMessage.event; - - private readonly _onError = this._register(new Emitter()); - public readonly onError = this._onError.event; - - constructor(descriptorOrWorker: IWebWorkerDescriptor | Worker | Promise) { - super(); - this.id = ++WebWorker.LAST_WORKER_ID; - const workerOrPromise = ( - descriptorOrWorker instanceof Worker - ? descriptorOrWorker : - 'then' in descriptorOrWorker ? descriptorOrWorker - : getWorker(descriptorOrWorker, this.id) - ); - if (isPromiseLike(workerOrPromise)) { - this.worker = workerOrPromise; - } else { - this.worker = Promise.resolve(workerOrPromise); - } - this.postMessage('-please-ignore-', []); // TODO: Eliminate this extra message - const errorHandler = (ev: ErrorEvent) => { - this._onError.fire(ev); - }; - this.worker.then((w) => { - w.onmessage = (ev) => { - this._onMessage.fire(ev.data); - }; - w.onmessageerror = (ev) => { - this._onError.fire(ev); - }; - if (typeof w.addEventListener === 'function') { - w.addEventListener('error', errorHandler); - } - }); - this._register(toDisposable(() => { - this.worker?.then(w => { - w.onmessage = null; - w.onmessageerror = null; - w.removeEventListener('error', errorHandler); - w.terminate(); - }); - this.worker = null; - })); - } - - public getId(): number { - return this.id; - } - - public postMessage(message: unknown, transfer: Transferable[]): void { - this.worker?.then(w => { - try { - w.postMessage(message, transfer); - } catch (err) { - onUnexpectedError(err); - onUnexpectedError(new Error(`FAILED to post message to worker`, { cause: err })); - } - }); - } -} - -export interface IWebWorkerDescriptor { - readonly esmModuleLocation: URI | undefined; - readonly label: string | undefined; -} - -export class WebWorkerDescriptor implements IWebWorkerDescriptor { - constructor( - public readonly esmModuleLocation: URI, - public readonly label: string | undefined, - ) { } -} - -export function createWebWorker(esmModuleLocation: URI, label: string | undefined): IWebWorkerClient; -export function createWebWorker(workerDescriptor: IWebWorkerDescriptor | Worker | Promise): IWebWorkerClient; -export function createWebWorker(arg0: URI | IWebWorkerDescriptor | Worker | Promise, arg1?: string | undefined): IWebWorkerClient { - const workerDescriptorOrWorker = (URI.isUri(arg0) ? new WebWorkerDescriptor(arg0, arg1) : arg0); - return new WebWorkerClient(new WebWorker(workerDescriptorOrWorker)); -} diff --git a/code/src/vs/base/common/actions.ts b/code/src/vs/base/common/actions.ts index 0970509e98c..6d3e3f2b3db 100644 --- a/code/src/vs/base/common/actions.ts +++ b/code/src/vs/base/common/actions.ts @@ -52,6 +52,11 @@ export interface IActionChangeEvent { readonly checked?: boolean; } +/** + * A concrete implementation of {@link IAction}. + * + * Note that in most cases you should use the lighter-weight {@linkcode toAction} function instead. + */ export class Action extends Disposable implements IAction { protected _onDidChange = this._register(new Emitter()); @@ -223,7 +228,7 @@ export class Separator implements IAction { readonly tooltip: string = ''; readonly class: string = 'separator'; readonly enabled: boolean = false; - readonly checked: boolean = false; + readonly checked: undefined = undefined; async run() { } } diff --git a/code/src/vs/base/common/arrays.ts b/code/src/vs/base/common/arrays.ts index 2f16dbccda4..a7b52b435bd 100644 --- a/code/src/vs/base/common/arrays.ts +++ b/code/src/vs/base/common/arrays.ts @@ -109,7 +109,16 @@ export function binarySearch2(length: number, compareToKey: (index: number) => n type Compare = (a: T, b: T) => number; - +/** + * Finds the nth smallest element in the array using quickselect algorithm. + * The data does not need to be sorted. + * + * @param nth The zero-based index of the element to find (0 = smallest, 1 = second smallest, etc.) + * @param data The unsorted array + * @param compare A comparator function that defines the sort order + * @returns The nth smallest element + * @throws TypeError if nth is >= data.length + */ export function quickSelect(nth: number, data: T[], compare: Compare): T { nth = nth | 0; @@ -193,8 +202,8 @@ export function forEachWithNeighbors(arr: T[], f: (before: T | undefined, ele } } -export function concatArrays(...arrays: TArr): TArr[number][number][] { - return ([] as any[]).concat(...arrays); +export function concatArrays(...arrays: T): T[number][number][] { + return [].concat(...arrays); } interface IMutableSplice extends ISplice { @@ -393,7 +402,7 @@ export function distinct(array: ReadonlyArray, keyFn: (value: T) => unknow const seen = new Set(); return array.filter(element => { - const key = keyFn!(element); + const key = keyFn(element); if (seen.has(key)) { return false; } @@ -563,6 +572,22 @@ export function mapArrayOrNot(items: T | T[], fn: (_: T) => U): U | U[] { fn(items); } +export function mapFilter(array: ReadonlyArray, fn: (t: T) => U | undefined): U[] { + const result: U[] = []; + for (const item of array) { + const mapped = fn(item); + if (mapped !== undefined) { + result.push(mapped); + } + } + return result; +} + +export function withoutDuplicates(array: ReadonlyArray): T[] { + const s = new Set(array); + return Array.from(s); +} + export function asArray(x: T | T[]): T[]; export function asArray(x: T | readonly T[]): readonly T[]; export function asArray(x: T | T[]): T[] { diff --git a/code/src/vs/base/common/arraysFind.ts b/code/src/vs/base/common/arraysFind.ts index ea75386c4a9..8ed34987f63 100644 --- a/code/src/vs/base/common/arraysFind.ts +++ b/code/src/vs/base/common/arraysFind.ts @@ -5,9 +5,9 @@ import { Comparator } from './arrays.js'; -export function findLast(array: readonly T[], predicate: (item: T) => item is R, fromIndex?: number): R | undefined; -export function findLast(array: readonly T[], predicate: (item: T) => unknown, fromIndex?: number): T | undefined; -export function findLast(array: readonly T[], predicate: (item: T) => unknown, fromIndex = array.length - 1): T | undefined { +export function findLast(array: readonly T[], predicate: (item: T, index: number) => item is R, fromIndex?: number): R | undefined; +export function findLast(array: readonly T[], predicate: (item: T, index: number) => unknown, fromIndex?: number): T | undefined; +export function findLast(array: readonly T[], predicate: (item: T, index: number) => unknown, fromIndex = array.length - 1): T | undefined { const idx = findLastIdx(array, predicate, fromIndex); if (idx === -1) { return undefined; @@ -15,11 +15,33 @@ export function findLast(array: readonly T[], predicate: (item: T) => unknown return array[idx]; } -export function findLastIdx(array: readonly T[], predicate: (item: T) => unknown, fromIndex = array.length - 1): number { +export function findLastIdx(array: readonly T[], predicate: (item: T, index: number) => unknown, fromIndex = array.length - 1): number { for (let i = fromIndex; i >= 0; i--) { const element = array[i]; - if (predicate(element)) { + if (predicate(element, i)) { + return i; + } + } + + return -1; +} + +export function findFirst(array: readonly T[], predicate: (item: T, index: number) => item is R, fromIndex?: number): R | undefined; +export function findFirst(array: readonly T[], predicate: (item: T, index: number) => unknown, fromIndex?: number): T | undefined; +export function findFirst(array: readonly T[], predicate: (item: T, index: number) => unknown, fromIndex = 0): T | undefined { + const idx = findFirstIdx(array, predicate, fromIndex); + if (idx === -1) { + return undefined; + } + return array[idx]; +} + +export function findFirstIdx(array: readonly T[], predicate: (item: T, index: number) => unknown, fromIndex = 0): number { + for (let i = fromIndex; i < array.length; i++) { + const element = array[i]; + + if (predicate(element, i)) { return i; } } diff --git a/code/src/vs/base/common/assert.ts b/code/src/vs/base/common/assert.ts index 860c3e816d5..b8f47aefd66 100644 --- a/code/src/vs/base/common/assert.ts +++ b/code/src/vs/base/common/assert.ts @@ -29,6 +29,10 @@ export function assertNever(value: never, message = 'Unreachable'): never { throw new Error(message); } +export function softAssertNever(value: never): void { + // no-op +} + /** * Asserts that a condition is `truthy`. * diff --git a/code/src/vs/base/common/async.ts b/code/src/vs/base/common/async.ts index 9f2962017f8..3dcfa0c5130 100644 --- a/code/src/vs/base/common/async.ts +++ b/code/src/vs/base/common/async.ts @@ -191,6 +191,10 @@ export interface ITask { (): T; } +export interface ICancellableTask { + (token: CancellationToken): T; +} + /** * A helper to prevent accumulation of sequential async tasks. * @@ -221,18 +225,19 @@ export class Throttler implements IDisposable { private activePromise: Promise | null; private queuedPromise: Promise | null; - private queuedPromiseFactory: ITask> | null; - - private isDisposed = false; + private queuedPromiseFactory: ICancellableTask> | null; + private cancellationTokenSource: CancellationTokenSource; constructor() { this.activePromise = null; this.queuedPromise = null; this.queuedPromiseFactory = null; + + this.cancellationTokenSource = new CancellationTokenSource(); } - queue(promiseFactory: ITask>): Promise { - if (this.isDisposed) { + queue(promiseFactory: ICancellableTask>): Promise { + if (this.cancellationTokenSource.token.isCancellationRequested) { return Promise.reject(new Error('Throttler is disposed')); } @@ -243,7 +248,7 @@ export class Throttler implements IDisposable { const onComplete = () => { this.queuedPromise = null; - if (this.isDisposed) { + if (this.cancellationTokenSource.token.isCancellationRequested) { return; } @@ -263,7 +268,7 @@ export class Throttler implements IDisposable { }); } - this.activePromise = promiseFactory(); + this.activePromise = promiseFactory(this.cancellationTokenSource.token); return new Promise((resolve, reject) => { this.activePromise!.then((result: T) => { @@ -277,7 +282,7 @@ export class Throttler implements IDisposable { } dispose(): void { - this.isDisposed = true; + this.cancellationTokenSource.cancel(); } } @@ -308,6 +313,10 @@ export class SequencerByKey { return newPromise; } + peek(key: TKey): Promise | undefined { + return this.promiseMap.get(key) || undefined; + } + keys(): IterableIterator { return this.promiseMap.keys(); } @@ -458,7 +467,7 @@ export class ThrottledDelayer { this.throttler = new Throttler(); } - trigger(promiseFactory: ITask>, delay?: number): Promise { + trigger(promiseFactory: ICancellableTask>, delay?: number): Promise { return this.delayer.trigger(() => this.throttler.queue(promiseFactory), delay) as unknown as Promise; } @@ -919,6 +928,12 @@ export class ResourceQueue implements IDisposable { export type Task = () => (Promise | T); +/** + * Wrap a type in an optional promise. This can be useful to avoid the runtime + * overhead of creating a promise. + */ +export type MaybePromise = Promise | T; + /** * Processes tasks in the order they were scheduled. */ @@ -1642,8 +1657,8 @@ export class TaskSequentializer { this._queued = { run, promise, - promiseResolve: promiseResolve!, - promiseReject: promiseReject! + promiseResolve, + promiseReject }; } @@ -1715,6 +1730,12 @@ const enum DeferredOutcome { */ export class DeferredPromise { + public static fromPromise(promise: Promise): DeferredPromise { + const deferred = new DeferredPromise(); + deferred.settleWith(promise); + return deferred; + } + private completeCallback!: ValueCallback; private errorCallback!: (err: unknown) => void; private outcome?: { outcome: DeferredOutcome.Rejected; value: unknown } | { outcome: DeferredOutcome.Resolved; value: T }; @@ -1745,6 +1766,10 @@ export class DeferredPromise { } public complete(value: T) { + if (this.isSettled) { + return Promise.resolve(); + } + return new Promise(resolve => { this.completeCallback(value); this.outcome = { outcome: DeferredOutcome.Resolved, value }; @@ -1753,6 +1778,10 @@ export class DeferredPromise { } public error(err: unknown) { + if (this.isSettled) { + return Promise.resolve(); + } + return new Promise(resolve => { this.errorCallback(err); this.outcome = { outcome: DeferredOutcome.Rejected, value: err }; @@ -2401,6 +2430,42 @@ export class AsyncIterableProducer implements AsyncIterable { }); } + public static tee(iterable: AsyncIterable): [AsyncIterableProducer, AsyncIterableProducer] { + let emitter1: AsyncIterableEmitter | undefined; + let emitter2: AsyncIterableEmitter | undefined; + + const defer = new DeferredPromise(); + + const start = async () => { + if (!emitter1 || !emitter2) { + return; // not yet ready + } + try { + for await (const item of iterable) { + emitter1.emitOne(item); + emitter2.emitOne(item); + } + } catch (err) { + emitter1.reject(err); + emitter2.reject(err); + } finally { + defer.complete(); + } + }; + + const p1 = new AsyncIterableProducer(async (emitter) => { + emitter1 = emitter; + start(); + return defer.p; + }); + const p2 = new AsyncIterableProducer(async (emitter) => { + emitter2 = emitter; + start(); + return defer.p; + }); + return [p1, p2]; + } + public map(mapFn: (item: T) => R): AsyncIterableProducer { return AsyncIterableProducer.map(this, mapFn); } diff --git a/code/src/vs/base/common/buffer.ts b/code/src/vs/base/common/buffer.ts index 831357fec08..244fb8e1c0d 100644 --- a/code/src/vs/base/common/buffer.ts +++ b/code/src/vs/base/common/buffer.ts @@ -8,7 +8,7 @@ import * as streams from './stream.js'; interface NodeBuffer { allocUnsafe(size: number): Uint8Array; - isBuffer(obj: any): obj is NodeBuffer; + isBuffer(obj: unknown): obj is NodeBuffer; from(arrayBuffer: ArrayBufferLike, byteOffset?: number, length?: number): Uint8Array; from(data: string): Uint8Array; } @@ -127,7 +127,7 @@ export class VSBuffer { return this.buffer.toString(); } else { if (!textDecoder) { - textDecoder = new TextDecoder(); + textDecoder = new TextDecoder(undefined, { ignoreBOM: true }); } return textDecoder.decode(this.buffer); } diff --git a/code/src/vs/base/common/cache.ts b/code/src/vs/base/common/cache.ts index 9bdb47acd26..2e4e17c6295 100644 --- a/code/src/vs/base/common/cache.ts +++ b/code/src/vs/base/common/cache.ts @@ -118,3 +118,36 @@ export class CachedFunction { return value; } } + +/** + * Uses an unbounded cache to memoize the results of the given function. +*/ +export class WeakCachedFunction { + private readonly _map = new WeakMap(); + + private readonly _fn: (arg: TArg) => TComputed; + private readonly _computeKey: (arg: TArg) => unknown; + + constructor(fn: (arg: TArg) => TComputed); + constructor(options: ICacheOptions, fn: (arg: TArg) => TComputed); + constructor(arg1: ICacheOptions | ((arg: TArg) => TComputed), arg2?: (arg: TArg) => TComputed) { + if (typeof arg1 === 'function') { + this._fn = arg1; + this._computeKey = identity; + } else { + this._fn = arg2!; + this._computeKey = arg1.getCacheKey; + } + } + + public get(arg: TArg): TComputed { + const key = this._computeKey(arg) as WeakKey; + if (this._map.has(key)) { + return this._map.get(key)!; + } + + const value = this._fn(arg); + this._map.set(key, value); + return value; + } +} diff --git a/code/src/vs/base/common/cancellation.ts b/code/src/vs/base/common/cancellation.ts index c5b8133ae6c..e04c9277b85 100644 --- a/code/src/vs/base/common/cancellation.ts +++ b/code/src/vs/base/common/cancellation.ts @@ -21,10 +21,10 @@ export interface CancellationToken { * * @event */ - readonly onCancellationRequested: (listener: (e: any) => any, thisArgs?: any, disposables?: IDisposable[]) => IDisposable; + readonly onCancellationRequested: (listener: (e: void) => unknown, thisArgs?: unknown, disposables?: IDisposable[]) => IDisposable; } -const shortcutEvent: Event = Object.freeze(function (callback, context?): IDisposable { +const shortcutEvent: Event = Object.freeze(function (callback, context?): IDisposable { const handle = setTimeout(callback.bind(context), 0); return { dispose() { clearTimeout(handle); } }; }); @@ -60,7 +60,7 @@ export namespace CancellationToken { class MutableToken implements CancellationToken { private _isCancelled: boolean = false; - private _emitter: Emitter | null = null; + private _emitter: Emitter | null = null; public cancel() { if (!this._isCancelled) { @@ -76,12 +76,12 @@ class MutableToken implements CancellationToken { return this._isCancelled; } - get onCancellationRequested(): Event { + get onCancellationRequested(): Event { if (this._isCancelled) { return shortcutEvent; } if (!this._emitter) { - this._emitter = new Emitter(); + this._emitter = new Emitter(); } return this._emitter.event; } @@ -146,3 +146,61 @@ export function cancelOnDispose(store: DisposableStore): CancellationToken { store.add({ dispose() { source.cancel(); } }); return source.token; } + +/** + * A pool that aggregates multiple cancellation tokens. The pool's own token + * (accessible via `pool.token`) is cancelled only after every token added + * to the pool has been cancelled. Adding tokens after the pool token has + * been cancelled has no effect. + */ +export class CancellationTokenPool { + + private readonly _source = new CancellationTokenSource(); + private readonly _listeners = new DisposableStore(); + + private _total: number = 0; + private _cancelled: number = 0; + private _isDone: boolean = false; + + get token(): CancellationToken { + return this._source.token; + } + + /** + * Add a token to the pool. If the token is already cancelled it is counted + * immediately. Tokens added after the pool token has been cancelled are ignored. + */ + add(token: CancellationToken): void { + if (this._isDone) { + return; + } + + this._total++; + + if (token.isCancellationRequested) { + this._cancelled++; + this._check(); + return; + } + + const d = token.onCancellationRequested(() => { + d.dispose(); + this._cancelled++; + this._check(); + }); + this._listeners.add(d); + } + + private _check(): void { + if (!this._isDone && this._total > 0 && this._total === this._cancelled) { + this._isDone = true; + this._listeners.dispose(); + this._source.cancel(); + } + } + + dispose(): void { + this._listeners.dispose(); + this._source.dispose(); + } +} diff --git a/code/src/vs/base/common/codiconsLibrary.ts b/code/src/vs/base/common/codiconsLibrary.ts index b709d6206a1..29037850602 100644 --- a/code/src/vs/base/common/codiconsLibrary.ts +++ b/code/src/vs/base/common/codiconsLibrary.ts @@ -31,9 +31,6 @@ export const codiconsLibrary = { personFollow: register('person-follow', 0xea67), personOutline: register('person-outline', 0xea67), personFilled: register('person-filled', 0xea67), - gitBranch: register('git-branch', 0xea68), - gitBranchCreate: register('git-branch-create', 0xea68), - gitBranchDelete: register('git-branch-delete', 0xea68), sourceControl: register('source-control', 0xea68), mirror: register('mirror', 0xea69), mirrorPublic: register('mirror-public', 0xea69), @@ -82,7 +79,6 @@ export const codiconsLibrary = { vm: register('vm', 0xea7a), deviceDesktop: register('device-desktop', 0xea7a), file: register('file', 0xea7b), - fileText: register('file-text', 0xea7b), more: register('more', 0xea7c), ellipsis: register('ellipsis', 0xea7c), kebabHorizontal: register('kebab-horizontal', 0xea7c), @@ -264,6 +260,7 @@ export const codiconsLibrary = { italic: register('italic', 0xeb0d), jersey: register('jersey', 0xeb0e), json: register('json', 0xeb0f), + bracket: register('bracket', 0xeb0f), kebabVertical: register('kebab-vertical', 0xeb10), key: register('key', 0xeb11), law: register('law', 0xeb12), @@ -490,7 +487,6 @@ export const codiconsLibrary = { graphLine: register('graph-line', 0xebe2), graphScatter: register('graph-scatter', 0xebe3), pieChart: register('pie-chart', 0xebe4), - bracket: register('bracket', 0xeb0f), bracketDot: register('bracket-dot', 0xebe5), bracketError: register('bracket-error', 0xebe6), lockSmall: register('lock-small', 0xebe7), @@ -613,4 +609,45 @@ export const codiconsLibrary = { commentDiscussionSparkle: register('comment-discussion-sparkle', 0xec54), chatSparkleWarning: register('chat-sparkle-warning', 0xec55), chatSparkleError: register('chat-sparkle-error', 0xec56), + collection: register('collection', 0xec57), + newCollection: register('new-collection', 0xec58), + thinking: register('thinking', 0xec59), + build: register('build', 0xec5a), + commentDiscussionQuote: register('comment-discussion-quote', 0xec5b), + cursor: register('cursor', 0xec5c), + eraser: register('eraser', 0xec5d), + fileText: register('file-text', 0xec5e), + quotes: register('quotes', 0xec60), + rename: register('rename', 0xec61), + runWithDeps: register('run-with-deps', 0xec62), + debugConnected: register('debug-connected', 0xec63), + strikethrough: register('strikethrough', 0xec64), + openInProduct: register('open-in-product', 0xec65), + indexZero: register('index-zero', 0xec66), + agent: register('agent', 0xec67), + editCode: register('edit-code', 0xec68), + repoSelected: register('repo-selected', 0xec69), + skip: register('skip', 0xec6a), + mergeInto: register('merge-into', 0xec6b), + gitBranchChanges: register('git-branch-changes', 0xec6c), + gitBranchStagedChanges: register('git-branch-staged-changes', 0xec6d), + gitBranchConflicts: register('git-branch-conflicts', 0xec6e), + gitBranch: register('git-branch', 0xec6f), + gitBranchCreate: register('git-branch-create', 0xec6f), + gitBranchDelete: register('git-branch-delete', 0xec6f), + searchLarge: register('search-large', 0xec70), + terminalGitBash: register('terminal-git-bash', 0xec71), + windowActive: register('window-active', 0xec72), + forward: register('forward', 0xec73), + download: register('download', 0xec74), + clockface: register('clockface', 0xec75), + unarchive: register('unarchive', 0xec76), + sessionInProgress: register('session-in-progress', 0xec77), + collectionSmall: register('collection-small', 0xec78), + vmSmall: register('vm-small', 0xec79), + cloudSmall: register('cloud-small', 0xec7a), + addSmall: register('add-small', 0xec7b), + removeSmall: register('remove-small', 0xec7c), + worktreeSmall: register('worktree-small', 0xec7d), + worktree: register('worktree', 0xec7e), } as const; diff --git a/code/src/vs/base/common/collections.ts b/code/src/vs/base/common/collections.ts index 81a8bfae1cb..f64ad848bf4 100644 --- a/code/src/vs/base/common/collections.ts +++ b/code/src/vs/base/common/collections.ts @@ -19,8 +19,8 @@ export type INumberDictionary = Record; * Groups the collection into a dictionary based on the provided * group function. */ -export function groupBy(data: V[], groupFn: (element: V) => K): Record { - const result: Record = Object.create(null); +export function groupBy(data: readonly V[], groupFn: (element: V) => K): Partial> { + const result: Partial> = Object.create(null); for (const element of data) { const key = groupFn(element); let target = result[key]; @@ -96,7 +96,7 @@ export function intersection(setA: Set, setB: Iterable): Set { } export class SetWithKey implements Set { - private _map = new Map(); + private _map = new Map(); constructor(values: T[], private toKey: (t: T) => unknown) { for (const value of values) { @@ -142,7 +142,7 @@ export class SetWithKey implements Set { this._map.clear(); } - forEach(callbackfn: (value: T, value2: T, set: Set) => void, thisArg?: any): void { + forEach(callbackfn: (value: T, value2: T, set: Set) => void, thisArg?: unknown): void { this._map.forEach(entry => callbackfn.call(thisArg, entry, entry, this)); } diff --git a/code/src/vs/base/common/console.ts b/code/src/vs/base/common/console.ts index f4790644524..0a82cbb6a22 100644 --- a/code/src/vs/base/common/console.ts +++ b/code/src/vs/base/common/console.ts @@ -131,9 +131,11 @@ export function log(entry: IRemoteConsoleLog, label: string): void { } // Log it + // eslint-disable-next-line local/code-no-any-casts if (typeof (console as any)[entry.severity] !== 'function') { throw new Error('Unknown console method'); } + // eslint-disable-next-line local/code-no-any-casts (console as any)[entry.severity].apply(console, consoleArgs); } diff --git a/code/src/vs/base/common/controlFlow.ts b/code/src/vs/base/common/controlFlow.ts index eead0f45a93..8fc6b19d3b1 100644 --- a/code/src/vs/base/common/controlFlow.ts +++ b/code/src/vs/base/common/controlFlow.ts @@ -53,8 +53,8 @@ export class ReentrancyBarrier { return this._isOccupied; } - public makeExclusiveOrSkip(fn: TFunction): TFunction { - return ((...args: any[]) => { + public makeExclusiveOrSkip(fn: (...args: TArgs) => void): (...args: TArgs) => void { + return ((...args: TArgs) => { if (this._isOccupied) { return; } @@ -64,6 +64,6 @@ export class ReentrancyBarrier { } finally { this._isOccupied = false; } - }) as any; + }); } } diff --git a/code/src/vs/base/common/decorators.ts b/code/src/vs/base/common/decorators.ts index 0f02b3ede2c..7510ffcec1f 100644 --- a/code/src/vs/base/common/decorators.ts +++ b/code/src/vs/base/common/decorators.ts @@ -54,6 +54,7 @@ export function memoize(_target: Object, key: string, descriptor: PropertyDescri value: fn.apply(this, args) }); } + // eslint-disable-next-line local/code-no-any-casts return (this as any)[memoizeKey]; }; } diff --git a/code/src/vs/base/common/decorators/cancelPreviousCalls.ts b/code/src/vs/base/common/decorators/cancelPreviousCalls.ts index 7e901a24ca4..50a4ca8d819 100644 --- a/code/src/vs/base/common/decorators/cancelPreviousCalls.ts +++ b/code/src/vs/base/common/decorators/cancelPreviousCalls.ts @@ -87,7 +87,7 @@ type TWithOptionalCancellationToken = TFunction exte export function cancelPreviousCalls< TObject extends Disposable, TArgs extends unknown[], - TReturn extends unknown, + TReturn, >( _proto: TObject, methodName: string, diff --git a/code/src/vs/base/common/defaultAccount.ts b/code/src/vs/base/common/defaultAccount.ts index 0310d1e50a2..dd412d4de0b 100644 --- a/code/src/vs/base/common/defaultAccount.ts +++ b/code/src/vs/base/common/defaultAccount.ts @@ -7,6 +7,7 @@ export interface IDefaultAccount { readonly sessionId: string; readonly enterprise: boolean; readonly access_type_sku?: string; + readonly copilot_plan?: string; readonly assigned_date?: string; readonly can_signup_for_limited?: boolean; readonly chat_enabled?: boolean; diff --git a/code/src/vs/base/common/equals.ts b/code/src/vs/base/common/equals.ts index df2db9256f1..495d43d066b 100644 --- a/code/src/vs/base/common/equals.ts +++ b/code/src/vs/base/common/equals.ts @@ -5,59 +5,46 @@ import * as arrays from './arrays.js'; +/* + * Each function in this file which offers an equality comparison, has an accompanying + * `*C` variant which returns an EqualityComparer function. + * + * The `*C` variant allows for easier composition of equality comparers and improved type-inference. +*/ + + +/** Represents a function that decides if two values are equal. */ export type EqualityComparer = (a: T, b: T) => boolean; +export interface IEquatable { + equals(other: T): boolean; +} + /** * Compares two items for equality using strict equality. */ -export const strictEquals: EqualityComparer = (a, b) => a === b; - -/** - * Checks if the items of two arrays are equal. - * By default, strict equality is used to compare elements, but a custom equality comparer can be provided. - */ -export function itemsEquals(itemEquals: EqualityComparer = strictEquals): EqualityComparer { - return (a, b) => arrays.equals(a, b, itemEquals); +export function strictEquals(a: T, b: T): boolean { + return a === b; } -/** - * Two items are considered equal, if their stringified representations are equal. -*/ -export function jsonStringifyEquals(): EqualityComparer { - return (a, b) => JSON.stringify(a) === JSON.stringify(b); +export function strictEqualsC(): EqualityComparer { + return (a, b) => a === b; } /** - * Uses `item.equals(other)` to determine equality. + * Checks if the items of two arrays are equal. + * By default, strict equality is used to compare elements, but a custom equality comparer can be provided. */ -export function itemEquals(): EqualityComparer { - return (a, b) => a.equals(b); +export function arrayEquals(a: readonly T[], b: readonly T[], itemEquals?: EqualityComparer): boolean { + return arrays.equals(a, b, itemEquals ?? strictEquals); } /** - * Checks if two items are both null or undefined, or are equal according to the provided equality comparer. -*/ -export function equalsIfDefined(v1: T | undefined | null, v2: T | undefined | null, equals: EqualityComparer): boolean; -/** - * Returns an equality comparer that checks if two items are both null or undefined, or are equal according to the provided equality comparer. -*/ -export function equalsIfDefined(equals: EqualityComparer): EqualityComparer; -export function equalsIfDefined(equalsOrV1: EqualityComparer | T, v2?: T | undefined | null, equals?: EqualityComparer): EqualityComparer | boolean { - if (equals !== undefined) { - const v1 = equalsOrV1 as T | undefined; - if (v1 === undefined || v1 === null || v2 === undefined || v2 === null) { - return v2 === v1; - } - return equals(v1, v2); - } else { - const equals = equalsOrV1 as EqualityComparer; - return (v1, v2) => { - if (v1 === undefined || v1 === null || v2 === undefined || v2 === null) { - return v2 === v1; - } - return equals(v1, v2); - }; - } + * Checks if the items of two arrays are equal. + * By default, strict equality is used to compare elements, but a custom equality comparer can be provided. + */ +export function arrayEqualsC(itemEquals?: EqualityComparer): EqualityComparer { + return (a, b) => arrays.equals(a, b, itemEquals ?? strictEquals); } /** @@ -108,6 +95,10 @@ export function structuralEquals(a: T, b: T): boolean { return false; } +export function structuralEqualsC(): EqualityComparer { + return (a, b) => structuralEquals(a, b); +} + /** * `getStructuralKey(a) === getStructuralKey(b) <=> structuralEquals(a, b)` * (assuming that a and b are not cyclic structures and nothing extends globalThis Array). @@ -144,3 +135,72 @@ function toNormalizedJsonStructure(t: unknown): unknown { } return t; } + + +/** + * Two items are considered equal, if their stringified representations are equal. +*/ +export function jsonStringifyEquals(a: T, b: T): boolean { + return JSON.stringify(a) === JSON.stringify(b); +} + +/** + * Two items are considered equal, if their stringified representations are equal. +*/ +export function jsonStringifyEqualsC(): EqualityComparer { + return (a, b) => JSON.stringify(a) === JSON.stringify(b); +} + +/** + * Uses `item.equals(other)` to determine equality. + */ +export function thisEqualsC>(): EqualityComparer { + return (a, b) => a.equals(b); +} + +/** + * Checks if two items are both null or undefined, or are equal according to the provided equality comparer. +*/ +export function equalsIfDefined(v1: T | undefined | null, v2: T | undefined | null, equals: EqualityComparer): boolean { + if (v1 === undefined || v1 === null || v2 === undefined || v2 === null) { + return v2 === v1; + } + return equals(v1, v2); +} + +/** + * Returns an equality comparer that checks if two items are both null or undefined, or are equal according to the provided equality comparer. +*/ +export function equalsIfDefinedC(equals: EqualityComparer): EqualityComparer { + return (v1, v2) => { + if (v1 === undefined || v1 === null || v2 === undefined || v2 === null) { + return v2 === v1; + } + return equals(v1, v2); + }; +} + +/** + * Each function in this file which offers an equality comparison, has an accompanying + * `*C` variant which returns an EqualityComparer function. + * + * The `*C` variant allows for easier composition of equality comparers and improved type-inference. +*/ +export namespace equals { + export const strict = strictEquals; + export const strictC = strictEqualsC; + + export const array = arrayEquals; + export const arrayC = arrayEqualsC; + + export const structural = structuralEquals; + export const structuralC = structuralEqualsC; + + export const jsonStringify = jsonStringifyEquals; + export const jsonStringifyC = jsonStringifyEqualsC; + + export const thisC = thisEqualsC; + + export const ifDefined = equalsIfDefined; + export const ifDefinedC = equalsIfDefinedC; +} diff --git a/code/src/vs/base/common/errors.ts b/code/src/vs/base/common/errors.ts index dc7f9cbbdfa..69645c4c574 100644 --- a/code/src/vs/base/common/errors.ts +++ b/code/src/vs/base/common/errors.ts @@ -139,6 +139,7 @@ export function transformErrorForSerialization(error: any): any; export function transformErrorForSerialization(error: any): any { if (error instanceof Error) { const { name, message, cause } = error; + // eslint-disable-next-line local/code-no-any-casts const stack: string = (error).stacktrace || (error).stack; return { $isError: true, diff --git a/code/src/vs/base/common/event.ts b/code/src/vs/base/common/event.ts index 5a33d03fcf1..d52779616d0 100644 --- a/code/src/vs/base/common/event.ts +++ b/code/src/vs/base/common/event.ts @@ -70,10 +70,13 @@ export namespace Event { * returned event causes this utility to leak a listener on the original event. * * @param event The event source for the new event. + * @param flushOnListenerRemove Whether to fire all debounced events when a listener is removed. If this is not + * specified, some events could go missing. Use this if it's important that all events are processed, even if the + * listener gets disposed before the debounced event fires. * @param disposable A disposable store to add the new EventEmitter to. */ - export function defer(event: Event, disposable?: DisposableStore): Event { - return debounce(event, () => void 0, 0, undefined, true, undefined, disposable); + export function defer(event: Event, flushOnListenerRemove?: boolean, disposable?: DisposableStore): Event { + return debounce(event, () => void 0, 0, undefined, flushOnListenerRemove ?? true, undefined, disposable); } /** @@ -324,15 +327,22 @@ export namespace Event { * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the * returned event causes this utility to leak a listener on the original event. + * + * @param event The event source for the new event. + * @param delay The number of milliseconds to debounce. + * @param flushOnListenerRemove Whether to fire all debounced events when a listener is removed. If this is not + * specified, some events could go missing. Use this if it's important that all events are processed, even if the + * listener gets disposed before the debounced event fires. + * @param disposable A disposable store to add the new EventEmitter to. */ - export function accumulate(event: Event, delay: number | typeof MicrotaskDelay = 0, disposable?: DisposableStore): Event { + export function accumulate(event: Event, delay: number | typeof MicrotaskDelay = 0, flushOnListenerRemove?: boolean, disposable?: DisposableStore): Event { return Event.debounce(event, (last, e) => { if (!last) { return [e]; } last.push(e); return last; - }, delay, undefined, true, undefined, disposable); + }, delay, undefined, flushOnListenerRemove ?? true, undefined, disposable); } /** @@ -601,13 +611,22 @@ export namespace Event { */ export function toPromise(event: Event, disposables?: IDisposable[] | DisposableStore): CancelablePromise { let cancelRef: () => void; - const promise = new Promise((resolve, reject) => { - const listener = once(event)(resolve, null, disposables); + let listener: IDisposable; + const promise = new Promise((resolve) => { + listener = once(event)(resolve); + addToDisposables(listener, disposables); + // not resolved, matching the behavior of a normal disposal - cancelRef = () => listener.dispose(); + cancelRef = () => { + disposeAndRemove(listener, disposables); + }; }) as CancelablePromise; promise.cancel = cancelRef!; + if (disposables) { + promise.finally(() => disposeAndRemove(listener, disposables)); + } + return promise; } @@ -746,11 +765,7 @@ export namespace Event { } }; - if (disposables instanceof DisposableStore) { - disposables.add(disposable); - } else if (Array.isArray(disposables)) { - disposables.push(disposable); - } + addToDisposables(disposable, disposables); return disposable; }; @@ -886,7 +901,7 @@ class LeakageMonitor { const [topStack, topCount] = this.getMostFrequentStack()!; const message = `[${this.name}] potential listener LEAK detected, having ${listenerCount} listeners already. MOST frequent listener (${topCount}):`; console.warn(message); - console.warn(topStack!); + console.warn(topStack); const error = new ListenerLeakError(message, topStack); this._errorHandler(error); @@ -1129,11 +1144,7 @@ export class Emitter { removeMonitor?.(); this._removeListener(contained); }); - if (disposables instanceof DisposableStore) { - disposables.add(result); - } else if (Array.isArray(disposables)) { - disposables.push(result); - } + addToDisposables(result, disposables); return result; }; @@ -1778,3 +1789,24 @@ export function trackSetChanges(getData: () => ReadonlySet, onDidChangeDat store.add(map); return store; } + + +function addToDisposables(result: IDisposable, disposables: DisposableStore | IDisposable[] | undefined) { + if (disposables instanceof DisposableStore) { + disposables.add(result); + } else if (Array.isArray(disposables)) { + disposables.push(result); + } +} + +function disposeAndRemove(result: IDisposable, disposables: DisposableStore | IDisposable[] | undefined) { + if (disposables instanceof DisposableStore) { + disposables.delete(result); + } else if (Array.isArray(disposables)) { + const index = disposables.indexOf(result); + if (index !== -1) { + disposables.splice(index, 1); + } + } + result.dispose(); +} diff --git a/code/src/vs/base/common/extpath.ts b/code/src/vs/base/common/extpath.ts index bf8b0905ae6..2e48d19de23 100644 --- a/code/src/vs/base/common/extpath.ts +++ b/code/src/vs/base/common/extpath.ts @@ -359,14 +359,14 @@ export interface IPathWithLineAndColumn { export function parseLineAndColumnAware(rawPath: string): IPathWithLineAndColumn { const segments = rawPath.split(':'); // C:\file.txt:: - let path: string | undefined = undefined; - let line: number | undefined = undefined; - let column: number | undefined = undefined; + let path: string | undefined; + let line: number | undefined; + let column: number | undefined; for (const segment of segments) { const segmentAsNumber = Number(segment); if (!isNumber(segmentAsNumber)) { - path = !!path ? [path, segment].join(':') : segment; // a colon can well be part of a path (e.g. C:\...) + path = path ? [path, segment].join(':') : segment; // a colon can well be part of a path (e.g. C:\...) } else if (line === undefined) { line = segmentAsNumber; } else if (column === undefined) { diff --git a/code/src/vs/base/common/filters.ts b/code/src/vs/base/common/filters.ts index aa0b036ac76..fd159b40ab4 100644 --- a/code/src/vs/base/common/filters.ts +++ b/code/src/vs/base/common/filters.ts @@ -6,6 +6,7 @@ import { CharCode } from './charCode.js'; import { LRUCache } from './map.js'; import { getKoreanAltChars } from './naturalLanguage/korean.js'; +import { tryNormalizeToBase } from './normalization.js'; import * as strings from './strings.js'; export interface IFilter { @@ -65,6 +66,10 @@ function _matchesPrefix(ignoreCase: boolean, word: string, wordToMatchAgainst: s // Contiguous Substring export function matchesContiguousSubString(word: string, wordToMatchAgainst: string): IMatch[] | null { + if (word.length > wordToMatchAgainst.length) { + return null; + } + const index = wordToMatchAgainst.toLowerCase().indexOf(word.toLowerCase()); if (index === -1) { return null; @@ -73,9 +78,28 @@ export function matchesContiguousSubString(word: string, wordToMatchAgainst: str return [{ start: index, end: index + word.length }]; } +export function matchesBaseContiguousSubString(word: string, wordToMatchAgainst: string): IMatch[] | null { + if (word.length > wordToMatchAgainst.length) { + return null; + } + + word = tryNormalizeToBase(word); + wordToMatchAgainst = tryNormalizeToBase(wordToMatchAgainst); + const index = wordToMatchAgainst.indexOf(word); + if (index === -1) { + return null; + } + + return [{ start: index, end: index + word.length }]; +} + // Substring export function matchesSubString(word: string, wordToMatchAgainst: string): IMatch[] | null { + if (word.length > wordToMatchAgainst.length) { + return null; + } + return _matchesSubString(word.toLowerCase(), wordToMatchAgainst.toLowerCase(), 0, 0); } @@ -121,7 +145,7 @@ function isWhitespace(code: number): boolean { } const wordSeparators = new Set(); -// These are chosen as natural word separators based on writen text. +// These are chosen as natural word separators based on written text. // It is a subset of the word separators used by the monaco editor. '()[]{}<>`\'"-/;:,.?!' .split('') @@ -319,8 +343,8 @@ export function matchesWords(word: string, target: string, contiguous: boolean = let result: IMatch[] | null = null; let targetIndex = 0; - word = word.toLowerCase(); - target = target.toLowerCase(); + word = tryNormalizeToBase(word); + target = tryNormalizeToBase(target); while (targetIndex < target.length) { result = _matchesWords(word, target, 0, targetIndex, contiguous); if (result !== null) { diff --git a/code/src/vs/base/common/fuzzyScorer.ts b/code/src/vs/base/common/fuzzyScorer.ts index dfb09cc716b..65f94a78032 100644 --- a/code/src/vs/base/common/fuzzyScorer.ts +++ b/code/src/vs/base/common/fuzzyScorer.ts @@ -9,7 +9,7 @@ import { createMatches as createFuzzyMatches, fuzzyScore, IMatch, isUpper, match import { hash } from './hash.js'; import { sep } from './path.js'; import { isLinux, isWindows } from './platform.js'; -import { equalsIgnoreCase, stripWildcards } from './strings.js'; +import { equalsIgnoreCase } from './strings.js'; //#region Fuzzy scorer @@ -322,7 +322,7 @@ function doScoreFuzzy2Multiple(target: string, query: IPreparedQueryPiece[], pat } function doScoreFuzzy2Single(target: string, query: IPreparedQueryPiece, patternStart: number, wordStart: number): FuzzyScore2 { - const score = fuzzyScore(query.original, query.originalLowercase, patternStart, target, target.toLowerCase(), wordStart, { firstMatchCanBeWeak: true, boostFullMatch: true }); + const score = fuzzyScore(query.normalized, query.normalizedLowercase, patternStart, target, target.toLowerCase(), wordStart, { firstMatchCanBeWeak: true, boostFullMatch: true }); if (!score) { return NO_SCORE2; } @@ -683,25 +683,25 @@ export function compareItemsByFuzzyScore(itemA: T, itemB: T, query: IPrepared } function computeLabelAndDescriptionMatchDistance(item: T, score: IItemScore, accessor: IItemAccessor): number { - let matchStart: number = -1; - let matchEnd: number = -1; + let matchStart = -1; + let matchEnd = -1; // If we have description matches, the start is first of description match - if (score.descriptionMatch && score.descriptionMatch.length) { + if (score.descriptionMatch?.length) { matchStart = score.descriptionMatch[0].start; } // Otherwise, the start is the first label match - else if (score.labelMatch && score.labelMatch.length) { + else if (score.labelMatch?.length) { matchStart = score.labelMatch[0].start; } // If we have label match, the end is the last label match // If we had a description match, we add the length of the description // as offset to the end to indicate this. - if (score.labelMatch && score.labelMatch.length) { + if (score.labelMatch?.length) { matchEnd = score.labelMatch[score.labelMatch.length - 1].end; - if (score.descriptionMatch && score.descriptionMatch.length) { + if (score.descriptionMatch?.length) { const itemDescription = accessor.getItemDescription(item); if (itemDescription) { matchEnd += itemDescription.length; @@ -710,7 +710,7 @@ function computeLabelAndDescriptionMatchDistance(item: T, score: IItemScore, } // If we have just a description match, the end is the last description match - else if (score.descriptionMatch && score.descriptionMatch.length) { + else if (score.descriptionMatch?.length) { matchEnd = score.descriptionMatch[score.descriptionMatch.length - 1].end; } @@ -718,15 +718,15 @@ function computeLabelAndDescriptionMatchDistance(item: T, score: IItemScore, } function compareByMatchLength(matchesA?: IMatch[], matchesB?: IMatch[]): number { - if ((!matchesA && !matchesB) || ((!matchesA || !matchesA.length) && (!matchesB || !matchesB.length))) { + if ((!matchesA && !matchesB) || ((!matchesA?.length) && (!matchesB?.length))) { return 0; // make sure to not cause bad comparing when matches are not provided } - if (!matchesB || !matchesB.length) { + if (!matchesB?.length) { return -1; } - if (!matchesA || !matchesA.length) { + if (!matchesA?.length) { return 1; } @@ -811,7 +811,7 @@ export interface IPreparedQueryPiece { /** * In addition to the normalized path, will have - * whitespace and wildcards removed. + * whitespace, wildcards, quotes, ellipsis, and trailing hash characters removed. */ normalized: string; normalizedLowercase: string; @@ -900,8 +900,13 @@ function normalizeQuery(original: string): { pathNormalized: string; normalized: pathNormalized = original.replace(/\\/g, sep); // Help macOS/Linux users to search for paths when using backslash } - // we remove quotes here because quotes are used for exact match search - const normalized = stripWildcards(pathNormalized).replace(/\s|"/g, ''); + // remove certain characters that help find better results: + // - quotes: are used for exact match search + // - wildcards: are used for fuzzy matching + // - whitespace: are used to separate queries + // - ellipsis: sometimes used to indicate any path segments + // - trailing hash: used by some language servers (e.g. rust-analyzer) as query modifiers + const normalized = pathNormalized.replace(/[\*\u2026\s"]/g, '').replace(/(?<=.)#$/, ''); return { pathNormalized, diff --git a/code/src/vs/base/common/glob.ts b/code/src/vs/base/common/glob.ts index 276f076788d..6c2b7680f8b 100644 --- a/code/src/vs/base/common/glob.ts +++ b/code/src/vs/base/common/glob.ts @@ -10,7 +10,7 @@ import { isEqualOrParent } from './extpath.js'; import { LRUCache } from './map.js'; import { basename, extname, posix, sep } from './path.js'; import { isLinux } from './platform.js'; -import { escapeRegExpCharacters, ltrim } from './strings.js'; +import { endsWithIgnoreCase, equalsIgnoreCase, escapeRegExpCharacters, ltrim } from './strings.js'; export interface IRelativePattern { @@ -270,7 +270,7 @@ export type ParsedPattern = (path: string, basename?: string) => boolean; // iff `hasSibling` returns a `Promise`. export type ParsedExpression = (path: string, basename?: string, hasSibling?: (name: string) => boolean | Promise) => string | null | Promise /* the matching pattern */; -interface IGlobOptions { +export interface IGlobOptions { /** * Simplify patterns for use as exclusion filters during @@ -278,6 +278,17 @@ interface IGlobOptions { * outside of a tree traversal. */ trimForExclusions?: boolean; + + /** + * Whether glob pattern matching should be case insensitive. + */ + ignoreCase?: boolean; +} + +interface IGlobOptionsInternal extends IGlobOptions { + equals: (a: string, b: string) => boolean; + endsWith: (str: string, candidate: string) => boolean; + isEqualOrParent: (base: string, candidate: string) => boolean; } interface ParsedStringPattern { @@ -339,45 +350,55 @@ function parsePattern(arg1: string | IRelativePattern, options: IGlobOptions): P // Whitespace trimming pattern = pattern.trim(); + const ignoreCase = options.ignoreCase ?? false; + const internalOptions = { + ...options, + equals: ignoreCase ? equalsIgnoreCase : (a: string, b: string) => a === b, + endsWith: ignoreCase ? endsWithIgnoreCase : (str: string, candidate: string) => str.endsWith(candidate), + // TODO: the '!isLinux' part below is to keep current behavior unchanged, but it should probably be removed + // in favor of passing correct options from the caller. + isEqualOrParent: (base: string, candidate: string) => isEqualOrParent(base, candidate, !isLinux || ignoreCase) + }; + // Check cache - const patternKey = `${pattern}_${!!options.trimForExclusions}`; + const patternKey = `${ignoreCase ? pattern.toLowerCase() : pattern}_${!!options.trimForExclusions}_${ignoreCase}`; let parsedPattern = CACHE.get(patternKey); if (parsedPattern) { - return wrapRelativePattern(parsedPattern, arg1); + return wrapRelativePattern(parsedPattern, arg1, internalOptions); } // Check for Trivials let match: RegExpExecArray | null; if (T1.test(pattern)) { - parsedPattern = trivia1(pattern.substr(4), pattern); // common pattern: **/*.txt just need endsWith check - } else if (match = T2.exec(trimForExclusions(pattern, options))) { // common pattern: **/some.txt just need basename check - parsedPattern = trivia2(match[1], pattern); + parsedPattern = trivia1(pattern.substring(4), pattern, internalOptions); // common pattern: **/*.txt just need endsWith check + } else if (match = T2.exec(trimForExclusions(pattern, internalOptions))) { // common pattern: **/some.txt just need basename check + parsedPattern = trivia2(match[1], pattern, internalOptions); } else if ((options.trimForExclusions ? T3_2 : T3).test(pattern)) { // repetition of common patterns (see above) {**/*.txt,**/*.png} - parsedPattern = trivia3(pattern, options); - } else if (match = T4.exec(trimForExclusions(pattern, options))) { // common pattern: **/something/else just need endsWith check - parsedPattern = trivia4and5(match[1].substr(1), pattern, true); - } else if (match = T5.exec(trimForExclusions(pattern, options))) { // common pattern: something/else just need equals check - parsedPattern = trivia4and5(match[1], pattern, false); + parsedPattern = trivia3(pattern, internalOptions); + } else if (match = T4.exec(trimForExclusions(pattern, internalOptions))) { // common pattern: **/something/else just need endsWith check + parsedPattern = trivia4and5(match[1].substring(1), pattern, true, internalOptions); + } else if (match = T5.exec(trimForExclusions(pattern, internalOptions))) { // common pattern: something/else just need equals check + parsedPattern = trivia4and5(match[1], pattern, false, internalOptions); } // Otherwise convert to pattern else { - parsedPattern = toRegExp(pattern); + parsedPattern = toRegExp(pattern, internalOptions); } // Cache CACHE.set(patternKey, parsedPattern); - return wrapRelativePattern(parsedPattern, arg1); + return wrapRelativePattern(parsedPattern, arg1, internalOptions); } -function wrapRelativePattern(parsedPattern: ParsedStringPattern, arg2: string | IRelativePattern): ParsedStringPattern { +function wrapRelativePattern(parsedPattern: ParsedStringPattern, arg2: string | IRelativePattern, options: IGlobOptionsInternal): ParsedStringPattern { if (typeof arg2 === 'string') { return parsedPattern; } const wrappedPattern: ParsedStringPattern = function (path, basename) { - if (!isEqualOrParent(path, arg2.base, !isLinux)) { + if (!options.isEqualOrParent(path, arg2.base)) { // skip glob matching if `base` is not a parent of `path` return null; } @@ -390,7 +411,7 @@ function wrapRelativePattern(parsedPattern: ParsedStringPattern, arg2: string | // for the fact that `base` might end in a path separator // (https://github.com/microsoft/vscode/issues/162498) - return parsedPattern(ltrim(path.substr(arg2.base.length), sep), basename); + return parsedPattern(ltrim(path.substring(arg2.base.length), sep), basename); }; // Make sure to preserve associated metadata @@ -403,18 +424,18 @@ function wrapRelativePattern(parsedPattern: ParsedStringPattern, arg2: string | } function trimForExclusions(pattern: string, options: IGlobOptions): string { - return options.trimForExclusions && pattern.endsWith('/**') ? pattern.substr(0, pattern.length - 2) : pattern; // dropping **, tailing / is dropped later + return options.trimForExclusions && pattern.endsWith('/**') ? pattern.substring(0, pattern.length - 2) : pattern; // dropping **, tailing / is dropped later } // common pattern: **/*.txt just need endsWith check -function trivia1(base: string, pattern: string): ParsedStringPattern { +function trivia1(base: string, pattern: string, options: IGlobOptionsInternal): ParsedStringPattern { return function (path: string, basename?: string) { - return typeof path === 'string' && path.endsWith(base) ? pattern : null; + return typeof path === 'string' && options.endsWith(path, base) ? pattern : null; }; } // common pattern: **/some.txt just need basename check -function trivia2(base: string, pattern: string): ParsedStringPattern { +function trivia2(base: string, pattern: string, options: IGlobOptionsInternal): ParsedStringPattern { const slashBase = `/${base}`; const backslashBase = `\\${base}`; @@ -424,10 +445,10 @@ function trivia2(base: string, pattern: string): ParsedStringPattern { } if (basename) { - return basename === base ? pattern : null; + return options.equals(basename, base) ? pattern : null; } - return path === base || path.endsWith(slashBase) || path.endsWith(backslashBase) ? pattern : null; + return options.equals(path, base) || options.endsWith(path, slashBase) || options.endsWith(path, backslashBase) ? pattern : null; }; const basenames = [base]; @@ -439,7 +460,7 @@ function trivia2(base: string, pattern: string): ParsedStringPattern { } // repetition of common patterns (see above) {**/*.txt,**/*.png} -function trivia3(pattern: string, options: IGlobOptions): ParsedStringPattern { +function trivia3(pattern: string, options: IGlobOptionsInternal): ParsedStringPattern { const parsedPatterns = aggregateBasenameMatches(pattern.slice(1, -1) .split(',') .map(pattern => parsePattern(pattern, options)) @@ -478,7 +499,7 @@ function trivia3(pattern: string, options: IGlobOptions): ParsedStringPattern { } // common patterns: **/something/else just need endsWith check, something/else just needs and equals check -function trivia4and5(targetPath: string, pattern: string, matchPathEnds: boolean): ParsedStringPattern { +function trivia4and5(targetPath: string, pattern: string, matchPathEnds: boolean, options: IGlobOptionsInternal): ParsedStringPattern { const usingPosixSep = sep === posix.sep; const nativePath = usingPosixSep ? targetPath : targetPath.replace(ALL_FORWARD_SLASHES, sep); const nativePathEnd = sep + nativePath; @@ -487,11 +508,14 @@ function trivia4and5(targetPath: string, pattern: string, matchPathEnds: boolean let parsedPattern: ParsedStringPattern; if (matchPathEnds) { parsedPattern = function (path: string, basename?: string) { - return typeof path === 'string' && ((path === nativePath || path.endsWith(nativePathEnd)) || !usingPosixSep && (path === targetPath || path.endsWith(targetPathEnd))) ? pattern : null; + return typeof path === 'string' && ( + (options.equals(path, nativePath) || options.endsWith(path, nativePathEnd)) || + !usingPosixSep && (options.equals(path, targetPath) || options.endsWith(path, targetPathEnd)) + ) ? pattern : null; }; } else { parsedPattern = function (path: string, basename?: string) { - return typeof path === 'string' && (path === nativePath || (!usingPosixSep && path === targetPath)) ? pattern : null; + return typeof path === 'string' && (options.equals(path, nativePath) || (!usingPosixSep && options.equals(path, targetPath))) ? pattern : null; }; } @@ -500,15 +524,15 @@ function trivia4and5(targetPath: string, pattern: string, matchPathEnds: boolean return parsedPattern; } -function toRegExp(pattern: string): ParsedStringPattern { +function toRegExp(pattern: string, options: IGlobOptions): ParsedStringPattern { try { - const regExp = new RegExp(`^${parseRegExp(pattern)}$`); + const regExp = new RegExp(`^${parseRegExp(pattern)}$`, options.ignoreCase ? 'i' : undefined); return function (path: string) { regExp.lastIndex = 0; // reset RegExp to its initial state to reuse it! return typeof path === 'string' && regExp.test(path) ? pattern : null; }; - } catch (error) { + } catch { return NULL; } } @@ -522,14 +546,14 @@ function toRegExp(pattern: string): ParsedStringPattern { * * `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …) * * `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`) */ -export function match(pattern: string | IRelativePattern, path: string): boolean; -export function match(expression: IExpression, path: string, hasSibling?: (name: string) => boolean): string /* the matching pattern */; -export function match(arg1: string | IExpression | IRelativePattern, path: string, hasSibling?: (name: string) => boolean): boolean | string | null | Promise { +export function match(pattern: string | IRelativePattern, path: string, options?: IGlobOptions): boolean; +export function match(expression: IExpression, path: string, options?: IGlobOptions): boolean; +export function match(arg1: string | IExpression | IRelativePattern, path: string, options?: IGlobOptions): boolean { if (!arg1 || typeof path !== 'string') { return false; } - return parse(arg1)(path, undefined, hasSibling); + return parse(arg1, options)(path) as boolean; } /** @@ -572,7 +596,7 @@ export function parse(arg1: string | IExpression | IRelativePattern, options: IG } // Glob with Expression - return parsedExpression(arg1, options); + return parsedExpression(arg1, options); } export function isRelativePattern(obj: unknown): obj is IRelativePattern { @@ -672,7 +696,7 @@ function parsedExpression(expression: IExpression, options: IGlobOptions): Parse } if (!name) { - name = base.substr(0, base.length - extname(path).length); + name = base.substring(0, base.length - extname(path).length); } } @@ -805,7 +829,7 @@ function aggregateBasenameMatches(parsedPatterns: Array | undefined, patternsB: Array | undefined): boolean { return equals(patternsA, patternsB, (a, b) => { if (typeof a === 'string' && typeof b === 'string') { diff --git a/code/src/vs/base/common/hash.ts b/code/src/vs/base/common/hash.ts index 324f057007c..a9a1747a6f9 100644 --- a/code/src/vs/base/common/hash.ts +++ b/code/src/vs/base/common/hash.ts @@ -56,16 +56,16 @@ export function stringHash(s: string, hashVal: number) { return hashVal; } -function arrayHash(arr: any[], initialHashVal: number): number { +function arrayHash(arr: unknown[], initialHashVal: number): number { initialHashVal = numberHash(104579, initialHashVal); - return arr.reduce((hashVal, item) => doHash(item, hashVal), initialHashVal); + return arr.reduce((hashVal, item) => doHash(item, hashVal), initialHashVal); } -function objectHash(obj: any, initialHashVal: number): number { +function objectHash(obj: object, initialHashVal: number): number { initialHashVal = numberHash(181387, initialHashVal); return Object.keys(obj).sort().reduce((hashVal, key) => { hashVal = stringHash(key, hashVal); - return doHash(obj[key], hashVal); + return doHash((obj as Record)[key], hashVal); }, initialHashVal); } diff --git a/code/src/vs/base/common/history.ts b/code/src/vs/base/common/history.ts index 3c23e8e3a29..d75120336f6 100644 --- a/code/src/vs/base/common/history.ts +++ b/code/src/vs/base/common/history.ts @@ -13,9 +13,9 @@ export interface IHistory { add(t: T): this; has(t: T): boolean; clear(): void; - forEach(callbackfn: (value: T, value2: T, set: Set) => void, thisArg?: any): void; + forEach(callbackfn: (value: T, value2: T, set: Set) => void, thisArg?: unknown): void; replace?(t: T[]): void; - onDidChange?: Event; + readonly onDidChange?: Event; } export class HistoryNavigator implements INavigator { diff --git a/code/src/vs/base/common/hotReload.ts b/code/src/vs/base/common/hotReload.ts index 0bd35312a4f..f7fc624c5aa 100644 --- a/code/src/vs/base/common/hotReload.ts +++ b/code/src/vs/base/common/hotReload.ts @@ -5,9 +5,14 @@ import { IDisposable } from './lifecycle.js'; +let _isHotReloadEnabled = false; + +export function enableHotReload() { + _isHotReloadEnabled = true; +} + export function isHotReloadEnabled(): boolean { - // return env && !!env['VSCODE_DEV_DEBUG']; - return false; // TODO@hediet investigate how to get hot reload + return _isHotReloadEnabled; } export function registerHotReloadHandler(handler: HotReloadHandler): IDisposable { if (!isHotReloadEnabled()) { @@ -94,12 +99,14 @@ if (isHotReloadEnabled()) { if (oldExportedItem) { for (const prop of Object.getOwnPropertyNames(exportedItem.prototype)) { const descriptor = Object.getOwnPropertyDescriptor(exportedItem.prototype, prop)!; + // eslint-disable-next-line local/code-no-any-casts const oldDescriptor = Object.getOwnPropertyDescriptor((oldExportedItem as any).prototype, prop); if (descriptor?.value?.toString() !== oldDescriptor?.value?.toString()) { console.log(`[hot-reload] Patching prototype method '${key}.${prop}'`); } + // eslint-disable-next-line local/code-no-any-casts Object.defineProperty((oldExportedItem as any).prototype, prop, descriptor); } newExports[key] = oldExportedItem; diff --git a/code/src/vs/base/common/hotReloadHelpers.ts b/code/src/vs/base/common/hotReloadHelpers.ts index f4f7cf11eae..0a4a9eb33e4 100644 --- a/code/src/vs/base/common/hotReloadHelpers.ts +++ b/code/src/vs/base/common/hotReloadHelpers.ts @@ -36,6 +36,7 @@ export function createHotClass(clazz: T): IObservable { return constObservable(clazz); } + // eslint-disable-next-line local/code-no-any-casts const id = (clazz as any).name; let existing = classes.get(id); diff --git a/code/src/vs/base/common/htmlContent.ts b/code/src/vs/base/common/htmlContent.ts index 99ddaa059c7..070279f045a 100644 --- a/code/src/vs/base/common/htmlContent.ts +++ b/code/src/vs/base/common/htmlContent.ts @@ -19,6 +19,8 @@ export interface IMarkdownString { readonly isTrusted?: boolean | MarkdownStringTrustedOptions; readonly supportThemeIcons?: boolean; readonly supportHtml?: boolean; + /** @internal */ + readonly supportAlertSyntax?: boolean; readonly baseUri?: UriComponents; uris?: { [href: string]: UriComponents }; } @@ -34,6 +36,7 @@ export class MarkdownString implements IMarkdownString { public isTrusted?: boolean | MarkdownStringTrustedOptions; public supportThemeIcons?: boolean; public supportHtml?: boolean; + public supportAlertSyntax?: boolean; public baseUri?: URI; public uris?: { [href: string]: UriComponents } | undefined; @@ -46,7 +49,7 @@ export class MarkdownString implements IMarkdownString { constructor( value: string = '', - isTrustedOrOptions: boolean | { isTrusted?: boolean | MarkdownStringTrustedOptions; supportThemeIcons?: boolean; supportHtml?: boolean } = false, + isTrustedOrOptions: boolean | { isTrusted?: boolean | MarkdownStringTrustedOptions; supportThemeIcons?: boolean; supportHtml?: boolean; supportAlertSyntax?: boolean } = false, ) { this.value = value; if (typeof this.value !== 'string') { @@ -57,11 +60,13 @@ export class MarkdownString implements IMarkdownString { this.isTrusted = isTrustedOrOptions; this.supportThemeIcons = false; this.supportHtml = false; + this.supportAlertSyntax = false; } else { this.isTrusted = isTrustedOrOptions.isTrusted ?? undefined; this.supportThemeIcons = isTrustedOrOptions.supportThemeIcons ?? false; this.supportHtml = isTrustedOrOptions.supportHtml ?? false; + this.supportAlertSyntax = isTrustedOrOptions.supportAlertSyntax ?? false; } } @@ -124,7 +129,8 @@ export function isMarkdownString(thing: unknown): thing is IMarkdownString { } else if (thing && typeof thing === 'object') { return typeof (thing).value === 'string' && (typeof (thing).isTrusted === 'boolean' || typeof (thing).isTrusted === 'object' || (thing).isTrusted === undefined) - && (typeof (thing).supportThemeIcons === 'boolean' || (thing).supportThemeIcons === undefined); + && (typeof (thing).supportThemeIcons === 'boolean' || (thing).supportThemeIcons === undefined) + && (typeof (thing).supportAlertSyntax === 'boolean' || (thing).supportAlertSyntax === undefined); } return false; } @@ -139,6 +145,7 @@ export function markdownStringEqual(a: IMarkdownString, b: IMarkdownString): boo && a.isTrusted === b.isTrusted && a.supportThemeIcons === b.supportThemeIcons && a.supportHtml === b.supportHtml + && a.supportAlertSyntax === b.supportAlertSyntax && (a.baseUri === b.baseUri || !!a.baseUri && !!b.baseUri && isEqual(URI.from(a.baseUri), URI.from(b.baseUri))); } } @@ -199,12 +206,19 @@ export function parseHrefAndDimensions(href: string): { href: string; dimensions return { href, dimensions }; } -export function markdownCommandLink(command: { title: string; id: string; arguments?: unknown[] }, escapeTokens = true): string { - const uri = URI.from({ - scheme: Schemas.command, - path: command.id, - query: command.arguments?.length ? encodeURIComponent(JSON.stringify(command.arguments)) : undefined, - }).toString(); +export function createMarkdownLink(text: string, href: string, title?: string, escapeTokens = true): string { + return `[${escapeTokens ? escapeMarkdownSyntaxTokens(text) : text}](${href}${title ? ` "${escapeMarkdownSyntaxTokens(title)}"` : ''})`; +} - return `[${escapeTokens ? escapeMarkdownSyntaxTokens(command.title) : command.title}](${uri})`; +export function createMarkdownCommandLink(command: { title: string; id: string; arguments?: unknown[]; tooltip?: string }, escapeTokens = true): string { + const uri = createCommandUri(command.id, ...(command.arguments || [])).toString(); + return createMarkdownLink(command.title, uri, command.tooltip, escapeTokens); +} + +export function createCommandUri(commandId: string, ...commandArgs: unknown[]): URI { + return URI.from({ + scheme: Schemas.command, + path: commandId, + query: commandArgs.length ? encodeURIComponent(JSON.stringify(commandArgs)) : undefined, + }); } diff --git a/code/src/vs/base/common/iterator.ts b/code/src/vs/base/common/iterator.ts index 59d92ca3994..1e8c84d9af9 100644 --- a/code/src/vs/base/common/iterator.ts +++ b/code/src/vs/base/common/iterator.ts @@ -7,13 +7,13 @@ import { isIterable } from './types.js'; export namespace Iterable { - export function is(thing: unknown): thing is Iterable { + export function is(thing: unknown): thing is Iterable { return !!thing && typeof thing === 'object' && typeof (thing as Iterable)[Symbol.iterator] === 'function'; } - const _empty: Iterable = Object.freeze([]); - export function empty(): Iterable { - return _empty; + const _empty: Iterable = Object.freeze([]); + export function empty(): readonly never[] { + return _empty as readonly never[]; } export function* single(element: T): Iterable { @@ -29,7 +29,7 @@ export namespace Iterable { } export function from(iterable: Iterable | undefined | null): Iterable { - return iterable || _empty; + return iterable ?? (_empty as Iterable); } export function* reverse(array: ReadonlyArray): Iterable { diff --git a/code/src/vs/base/common/json.ts b/code/src/vs/base/common/json.ts index 8623e87503b..d7ac069d3d1 100644 --- a/code/src/vs/base/common/json.ts +++ b/code/src/vs/base/common/json.ts @@ -850,7 +850,7 @@ export function parse(text: string, errors: ParseError[] = [], options: ParseOpt function onValue(value: unknown) { if (Array.isArray(currentParent)) { - (currentParent).push(value); + currentParent.push(value); } else if (currentProperty !== null) { currentParent[currentProperty] = value; } @@ -980,7 +980,7 @@ export function findNodeAtLocation(root: Node, path: JSONPath): Node | undefined return undefined; } } else { - const index = segment; + const index = segment; if (node.type !== 'array' || index < 0 || !Array.isArray(node.children) || index >= node.children.length) { return undefined; } diff --git a/code/src/vs/base/common/jsonSchema.ts b/code/src/vs/base/common/jsonSchema.ts index 6932da1b2d6..ac98a0ddd5f 100644 --- a/code/src/vs/base/common/jsonSchema.ts +++ b/code/src/vs/base/common/jsonSchema.ts @@ -102,31 +102,83 @@ export interface IJSONSchemaSnippet { /** * Converts a basic JSON schema to a TypeScript type. - * - * TODO: only supports basic schemas. Doesn't support all JSON schema features. */ -export type SchemaToType = T extends { type: 'string' } - ? string - : T extends { type: 'number' } - ? number - : T extends { type: 'boolean' } - ? boolean - : T extends { type: 'null' } - ? null - // Object +export type TypeFromJsonSchema = + // enum + T extends { enum: infer EnumValues } + ? UnionOf + + // Object with list of required properties. + // Values are required or optional based on `required` list. + : T extends { type: 'object'; properties: infer P; required: infer RequiredList } + ? { + [K in keyof P]: IsRequired extends true ? TypeFromJsonSchema : TypeFromJsonSchema | undefined; + } & AdditionalPropertiesType + + // Object with no required properties. + // All values are optional : T extends { type: 'object'; properties: infer P } - ? { [K in keyof P]: SchemaToType } + ? { [K in keyof P]: TypeFromJsonSchema | undefined } & AdditionalPropertiesType + // Array - : T extends { type: 'array'; items: infer I } - ? Array> - // OneOf + : T extends { type: 'array'; items: infer Items } + ? Items extends [...infer R] + // If items is an array, we treat it like a tuple + ? { [K in keyof R]: TypeFromJsonSchema } + : Array> + + // oneOf / anyof + // These are handled the same way as they both represent a union type. + // However at the validation level, they have different semantics. : T extends { oneOf: infer I } ? MapSchemaToType + : T extends { anyOf: infer I } + ? MapSchemaToType + + // Primitive types + : T extends { type: infer Type } + // Basic type + ? Type extends 'string' | 'number' | 'integer' | 'boolean' | 'null' + ? SchemaPrimitiveTypeNameToType + // Union of primitive types + : Type extends [...infer R] + ? UnionOf<{ [K in keyof R]: SchemaPrimitiveTypeNameToType }> + : never + // Fallthrough : never; +type SchemaPrimitiveTypeNameToType = + T extends 'string' ? string : + T extends 'number' | 'integer' ? number : + T extends 'boolean' ? boolean : + T extends 'null' ? null : + never; + +type UnionOf = + T extends [infer First, ...infer Rest] + ? First | UnionOf + : never; + +type IsRequired = + RequiredList extends [] + ? false + + : RequiredList extends [K, ...infer _] + ? true + + : RequiredList extends [infer _, ...infer R] + ? IsRequired + + : false; + +type AdditionalPropertiesType = + Schema extends { additionalProperties: infer AP } + ? AP extends false ? {} : { [key: string]: TypeFromJsonSchema } + : {}; + type MapSchemaToType = T extends [infer First, ...infer Rest] - ? SchemaToType | MapSchemaToType + ? TypeFromJsonSchema | MapSchemaToType : never; interface Equals { schemas: IJSONSchema[]; id?: string } diff --git a/code/src/vs/base/common/keyCodes.ts b/code/src/vs/base/common/keyCodes.ts index 9f1fd59fddc..8824bd526e3 100644 --- a/code/src/vs/base/common/keyCodes.ts +++ b/code/src/vs/base/common/keyCodes.ts @@ -738,14 +738,7 @@ for (let i = 0; i <= KeyCode.MAX_VALUE; i++) { scanCodeLowerCaseStrToInt[scanCodeStr.toLowerCase()] = scanCode; if (immutable) { IMMUTABLE_CODE_TO_KEY_CODE[scanCode] = keyCode; - if ( - (keyCode !== KeyCode.Unknown) - && (keyCode !== KeyCode.Enter) - && (keyCode !== KeyCode.Ctrl) - && (keyCode !== KeyCode.Shift) - && (keyCode !== KeyCode.Alt) - && (keyCode !== KeyCode.Meta) - ) { + if ((keyCode !== KeyCode.Unknown) && (keyCode !== KeyCode.Enter) && !isModifierKey(keyCode)) { IMMUTABLE_KEY_CODE_TO_CODE[keyCode] = scanCode; } } @@ -828,3 +821,12 @@ export function KeyChord(firstPart: number, secondPart: number): number { const chordPart = ((secondPart & 0x0000FFFF) << 16) >>> 0; return (firstPart | chordPart) >>> 0; } + +export function isModifierKey(keyCode: KeyCode): boolean { + return ( + keyCode === KeyCode.Ctrl + || keyCode === KeyCode.Shift + || keyCode === KeyCode.Alt + || keyCode === KeyCode.Meta + ); +} diff --git a/code/src/vs/base/common/lifecycle.ts b/code/src/vs/base/common/lifecycle.ts index 90264b8371e..c6ecaec1793 100644 --- a/code/src/vs/base/common/lifecycle.ts +++ b/code/src/vs/base/common/lifecycle.ts @@ -5,7 +5,8 @@ import { compareBy, numberComparator } from './arrays.js'; import { groupBy } from './collections.js'; -import { SetMap } from './map.js'; +import { SetMap, ResourceMap } from './map.js'; +import { URI } from './uri.js'; import { createSingleCallFunction } from './functional.js'; import { Iterable } from './iterator.js'; import { BugIndicatingError, onUnexpectedError } from './errors.js'; @@ -206,7 +207,9 @@ export class DisposableTracker implements IDisposableTracker { const continuations = groupBy([...prevStarts].map(d => getStackTracePath(d)[i]), v => v); delete continuations[stackTracePath[i]]; for (const [cont, set] of Object.entries(continuations)) { - stackTraceFormattedLines.unshift(` - stacktraces of ${set.length} other leaks continue with ${cont}`); + if (set) { + stackTraceFormattedLines.unshift(` - stacktraces of ${set.length} other leaks continue with ${cont}`); + } } stackTraceFormattedLines.unshift(line); @@ -233,6 +236,7 @@ if (TRACK_DISPOSABLES) { trackDisposable(x: IDisposable): void { const stack = new Error('Potentially leaked disposable').stack!; setTimeout(() => { + // eslint-disable-next-line local/code-no-any-casts if (!(x as any)[__is_disposable_tracked__]) { console.log(stack); } @@ -242,6 +246,7 @@ if (TRACK_DISPOSABLES) { setParent(child: IDisposable, parent: IDisposable | null): void { if (child && child !== Disposable.None) { try { + // eslint-disable-next-line local/code-no-any-casts (child as any)[__is_disposable_tracked__] = true; } catch { // noop @@ -252,6 +257,7 @@ if (TRACK_DISPOSABLES) { markAsDisposed(disposable: IDisposable): void { if (disposable && disposable !== Disposable.None) { try { + // eslint-disable-next-line local/code-no-any-casts (disposable as any)[__is_disposable_tracked__] = true; } catch { // noop @@ -310,7 +316,8 @@ export interface IDisposable { /** * Check if `thing` is {@link IDisposable disposable}. */ -export function isDisposable(thing: E): thing is E & IDisposable { +export function isDisposable(thing: E): thing is E & IDisposable { + // eslint-disable-next-line local/code-no-any-casts return typeof thing === 'object' && thing !== null && typeof (thing).dispose === 'function' && (thing).dispose.length === 0; } @@ -367,19 +374,36 @@ export function combinedDisposable(...disposables: IDisposable[]): IDisposable { return parent; } +class FunctionDisposable implements IDisposable { + private _isDisposed: boolean; + private readonly _fn: () => void; + + constructor(fn: () => void) { + this._isDisposed = false; + this._fn = fn; + trackDisposable(this); + } + + dispose() { + if (this._isDisposed) { + return; + } + if (!this._fn) { + throw new Error(`Unbound disposable context: Need to use an arrow function to preserve the value of this`); + } + this._isDisposed = true; + markAsDisposed(this); + this._fn(); + } +} + /** * Turn a function that implements dispose into an {@link IDisposable}. * * @param fn Clean up function, guaranteed to be called only **once**. */ export function toDisposable(fn: () => void): IDisposable { - const self = trackDisposable({ - dispose: createSingleCallFunction(() => { - markAsDisposed(self); - fn(); - }) - }); - return self; + return new FunctionDisposable(fn); } /** @@ -482,8 +506,7 @@ export class DisposableStore implements IDisposable { if (!o) { return; } - if (this._toDispose.has(o)) { - this._toDispose.delete(o); + if (this._toDispose.delete(o)) { setParentOfDisposable(o, null); } } @@ -547,10 +570,25 @@ export class MutableDisposable implements IDisposable { trackDisposable(this); } + /** + * Get the currently held disposable value, or `undefined` if this MutableDisposable has been disposed + */ get value(): T | undefined { return this._isDisposed ? undefined : this._value; } + /** + * Set a new disposable value. + * + * Behaviour: + * - If the MutableDisposable has been disposed, the setter is a no-op. + * - If the new value is strictly equal to the current value, the setter is a no-op. + * - Otherwise the previous value (if any) is disposed and the new value is stored. + * + * Related helpers: + * - clear() resets the value to `undefined` (and disposes the previous value). + * - clearAndLeak() returns the old value without disposing it and removes its parent. + */ set value(value: T | undefined) { if (this._isDisposed || value === this._value) { return; @@ -649,7 +687,7 @@ export abstract class ReferenceCollection { private readonly references: Map = new Map(); - acquire(key: string, ...args: any[]): IReference { + acquire(key: string, ...args: unknown[]): IReference { let reference = this.references.get(key); if (!reference) { @@ -670,7 +708,7 @@ export abstract class ReferenceCollection { return { object, dispose }; } - protected abstract createReferencedObject(key: string, ...args: any[]): T; + protected abstract createReferencedObject(key: string, ...args: unknown[]): T; protected abstract destroyReferencedObject(key: string, object: T): void; } @@ -718,10 +756,11 @@ export function disposeOnReturn(fn: (store: DisposableStore) => void): void { */ export class DisposableMap implements IDisposable { - private readonly _store = new Map(); + private readonly _store: Map; private _isDisposed = false; - constructor() { + constructor(store: Map = new Map()) { + this._store = store; trackDisposable(this); } @@ -841,3 +880,9 @@ export function thenRegisterOrDispose(promise: Promise return disposable; }); } + +export class DisposableResourceMap extends DisposableMap { + constructor() { + super(new ResourceMap()); + } +} diff --git a/code/src/vs/base/common/linkedList.ts b/code/src/vs/base/common/linkedList.ts index 42a1c2aad94..b436c611717 100644 --- a/code/src/vs/base/common/linkedList.ts +++ b/code/src/vs/base/common/linkedList.ts @@ -5,11 +5,11 @@ class Node { - static readonly Undefined = new Node(undefined); + static readonly Undefined = new Node(undefined); element: E; - next: Node; - prev: Node; + next: Node | typeof Node.Undefined; + prev: Node | typeof Node.Undefined; constructor(element: E) { this.element = element; @@ -20,8 +20,8 @@ class Node { export class LinkedList { - private _first: Node = Node.Undefined; - private _last: Node = Node.Undefined; + private _first: Node | typeof Node.Undefined = Node.Undefined; + private _last: Node | typeof Node.Undefined = Node.Undefined; private _size: number = 0; get size(): number { @@ -91,7 +91,7 @@ export class LinkedList { } else { const res = this._first.element; this._remove(this._first); - return res; + return res as E; } } @@ -101,11 +101,20 @@ export class LinkedList { } else { const res = this._last.element; this._remove(this._last); - return res; + return res as E; } } - private _remove(node: Node): void { + peek(): E | undefined { + if (this._last === Node.Undefined) { + return undefined; + } else { + const res = this._last.element; + return res as E; + } + } + + private _remove(node: Node | typeof Node.Undefined): void { if (node.prev !== Node.Undefined && node.next !== Node.Undefined) { // middle const anchor = node.prev; @@ -135,7 +144,7 @@ export class LinkedList { *[Symbol.iterator](): Iterator { let node = this._first; while (node !== Node.Undefined) { - yield node.element; + yield node.element as E; node = node.next; } } diff --git a/code/src/vs/base/common/map.ts b/code/src/vs/base/common/map.ts index c8f9de67964..622923e4450 100644 --- a/code/src/vs/base/common/map.ts +++ b/code/src/vs/base/common/map.ts @@ -116,12 +116,12 @@ export class ResourceMap implements Map { return this.map.delete(this.toKey(resource)); } - forEach(clb: (value: T, key: URI, map: Map) => void, thisArg?: any): void { + forEach(clb: (value: T, key: URI, map: Map) => void, thisArg?: object): void { if (typeof thisArg !== 'undefined') { clb = clb.bind(thisArg); } for (const [_, entry] of this.map) { - clb(entry.value, entry.uri, this); + clb(entry.value, entry.uri, this); } } @@ -185,7 +185,7 @@ export class ResourceSet implements Set { return this._map.delete(value); } - forEach(callbackfn: (value: URI, value2: URI, set: Set) => void, thisArg?: any): void { + forEach(callbackfn: (value: URI, value2: URI, set: Set) => void, thisArg?: unknown): void { this._map.forEach((_value, key) => callbackfn.call(thisArg, key, key, this)); } @@ -340,7 +340,7 @@ export 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) { @@ -789,7 +789,7 @@ export class BidirectionalMap { return true; } - forEach(callbackfn: (value: V, key: K, map: BidirectionalMap) => void, thisArg?: any): void { + forEach(callbackfn: (value: V, key: K, map: BidirectionalMap) => void, thisArg?: unknown): void { this._m1.forEach((value, key) => { callbackfn.call(thisArg, value, key, this); }); @@ -894,10 +894,12 @@ export class NKeyMap { public set(value: TValue, ...keys: [...TKeys]): void { let currentMap = this._data; for (let i = 0; i < keys.length - 1; i++) { - if (!currentMap.has(keys[i])) { - currentMap.set(keys[i], new Map()); + let nextMap = currentMap.get(keys[i]); + if (nextMap === undefined) { + nextMap = new Map(); + currentMap.set(keys[i], nextMap); } - currentMap = currentMap.get(keys[i]); + currentMap = nextMap; } currentMap.set(keys[keys.length - 1], value); } @@ -905,10 +907,11 @@ export class NKeyMap { public get(...keys: [...TKeys]): TValue | undefined { let currentMap = this._data; for (let i = 0; i < keys.length - 1; i++) { - if (!currentMap.has(keys[i])) { + const nextMap = currentMap.get(keys[i]); + if (nextMap === undefined) { return undefined; } - currentMap = currentMap.get(keys[i]); + currentMap = nextMap; } return currentMap.get(keys[keys.length - 1]); } diff --git a/code/src/vs/base/common/marked/marked.js b/code/src/vs/base/common/marked/marked.js index b7b6ecccd16..ea5462500bf 100644 --- a/code/src/vs/base/common/marked/marked.js +++ b/code/src/vs/base/common/marked/marked.js @@ -2479,4 +2479,3 @@ const parser = _Parser.parse; const lexer = _Lexer.lex; export { _Hooks as Hooks, _Lexer as Lexer, Marked, _Parser as Parser, _Renderer as Renderer, _TextRenderer as TextRenderer, _Tokenizer as Tokenizer, _defaults as defaults, _getDefaults as getDefaults, lexer, marked, options, parse, parseInline, parser, setOptions, use, walkTokens }; -//# sourceMappingURL=marked.esm.js.map diff --git a/code/src/vs/base/common/marshalling.ts b/code/src/vs/base/common/marshalling.ts index 1fd5c7fc2dd..678e047c1b7 100644 --- a/code/src/vs/base/common/marshalling.ts +++ b/code/src/vs/base/common/marshalling.ts @@ -50,8 +50,11 @@ export function revive(obj: any, depth = 0): Revived { if (typeof obj === 'object') { switch ((obj).$mid) { + // eslint-disable-next-line local/code-no-any-casts case MarshalledId.Uri: return URI.revive(obj); + // eslint-disable-next-line local/code-no-any-casts case MarshalledId.Regexp: return new RegExp(obj.source, obj.flags); + // eslint-disable-next-line local/code-no-any-casts case MarshalledId.Date: return new Date(obj.source); } @@ -59,6 +62,7 @@ export function revive(obj: any, depth = 0): Revived { obj instanceof VSBuffer || obj instanceof Uint8Array ) { + // eslint-disable-next-line local/code-no-any-casts return obj; } diff --git a/code/src/vs/base/common/marshallingIds.ts b/code/src/vs/base/common/marshallingIds.ts index 4400c6246f3..730fbd61533 100644 --- a/code/src/vs/base/common/marshallingIds.ts +++ b/code/src/vs/base/common/marshallingIds.ts @@ -28,6 +28,6 @@ export const enum MarshalledId { LanguageModelThinkingPart, LanguageModelPromptTsxPart, LanguageModelDataPart, - ChatSessionContext, + AgentSessionContext, ChatResponsePullRequestPart, } diff --git a/code/src/vs/base/common/network.ts b/code/src/vs/base/common/network.ts index 3ba15ba200c..d1b3f52429e 100644 --- a/code/src/vs/base/common/network.ts +++ b/code/src/vs/base/common/network.ts @@ -87,8 +87,8 @@ export namespace Schemas { /** Scheme used for the chat input part */ export const vscodeChatInput = 'chatSessionInput'; - /** Scheme for chat session content */ - export const vscodeChatSession = 'vscode-chat-session'; + /** Scheme used for local chat session content */ + export const vscodeLocalChatSession = 'vscode-chat-session'; /** * Scheme used internally for webviews that aren't linked to a resource (i.e. not custom editors) @@ -151,6 +151,11 @@ export namespace Schemas { */ export const chatEditingSnapshotScheme = 'chat-editing-snapshot-text-model'; export const chatEditingModel = 'chat-editing-text-model'; + + /** + * Used for rendering multidiffs in copilot agent sessions + */ + export const copilotPr = 'copilot-pr'; } export function matchesScheme(target: URI | string, scheme: string): boolean { @@ -409,7 +414,7 @@ export namespace COI { * isn't enabled the current context */ export function addSearchParam(urlOrSearch: URLSearchParams | Record, coop: boolean, coep: boolean): void { - if (!(globalThis).crossOriginIsolated) { + if (!(globalThis as typeof globalThis & { crossOriginIsolated?: boolean }).crossOriginIsolated) { // depends on the current context being COI return; } @@ -417,7 +422,7 @@ export namespace COI { if (urlOrSearch instanceof URLSearchParams) { urlOrSearch.set(coiSearchParamName, value); } else { - (>urlOrSearch)[coiSearchParamName] = value; + urlOrSearch[coiSearchParamName] = value; } } } diff --git a/code/src/vs/base/common/normalization.ts b/code/src/vs/base/common/normalization.ts index 1d4fbef7f72..8e426391512 100644 --- a/code/src/vs/base/common/normalization.ts +++ b/code/src/vs/base/common/normalization.ts @@ -39,11 +39,25 @@ function normalize(str: string, form: string, normalizedCache: LRUCache string = (function () { - // transform into NFD form and remove accents - // see: https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript/37511463#37511463 - const regex = /[\u0300-\u036f]/g; - return function (str: string) { - return normalizeNFD(str).replace(regex, ''); +/** + * Attempts to normalize the string to Unicode base format (NFD -> remove accents -> lower case). + * When original string contains accent characters directly, only lower casing will be performed. + * This is done so as to keep the string length the same and not affect indices. + * + * @see https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript/37511463#37511463 + */ +export const tryNormalizeToBase: (str: string) => string = function () { + const cache = new LRUCache(10000); // bounded to 10000 elements + const accentsRegex = /[\u0300-\u036f]/g; + return function (str: string): string { + const cached = cache.get(str); + if (cached) { + return cached; + } + + const noAccents = normalizeNFD(str).replace(accentsRegex, ''); + const result = (noAccents.length === str.length ? noAccents : str).toLowerCase(); + cache.set(str, result); + return result; }; -})(); +}(); diff --git a/code/src/vs/base/common/numbers.ts b/code/src/vs/base/common/numbers.ts index 89c9f183e6d..326a9b2f2c3 100644 --- a/code/src/vs/base/common/numbers.ts +++ b/code/src/vs/base/common/numbers.ts @@ -99,65 +99,6 @@ export function isPointWithinTriangle( return u >= 0 && v >= 0 && u + v < 1; } -/** - * Function to get a (pseudo)random integer from a provided `max`...[`min`] range. - * Both `min` and `max` values are inclusive. The `min` value is optional (defaults to `0`). - * - * @throws in the next cases: - * - if provided `min` or `max` is not a number - * - if provided `min` or `max` is not finite - * - if provided `min` is larger than `max` value - * - * ## Examples - * - * Specifying a `max` value only uses `0` as the `min` value by default: - * - * ```typescript - * // get a random integer between 0 and 10 - * const randomInt = randomInt(10); - * - * assert( - * randomInt >= 0, - * 'Should be greater than or equal to 0.', - * ); - * - * assert( - * randomInt <= 10, - * 'Should be less than or equal to 10.', - * ); - * ``` - * * Specifying both `max` and `min` values: - * - * ```typescript - * // get a random integer between 5 and 8 - * const randomInt = randomInt(8, 5); - * - * assert( - * randomInt >= 5, - * 'Should be greater than or equal to 5.', - * ); - * - * assert( - * randomInt <= 8, - * 'Should be less than or equal to 8.', - * ); - * ``` - */ -export function randomInt(max: number, min: number = 0): number { - assert(!isNaN(min), '"min" param is not a number.'); - assert(!isNaN(max), '"max" param is not a number.'); - - assert(isFinite(max), '"max" param is not finite.'); - assert(isFinite(min), '"min" param is not finite.'); - - assert(max > min, `"max"(${max}) param should be greater than "min"(${min}).`); - - const delta = max - min; - const randomFloat = delta * Math.random(); - - return Math.round(min + randomFloat); -} - export function randomChance(p: number): boolean { assert(p >= 0 && p <= 1, 'p must be between 0 and 1'); return Math.random() < p; diff --git a/code/src/vs/base/common/oauth.ts b/code/src/vs/base/common/oauth.ts index c4d5a755edd..ea5a3cbbe35 100644 --- a/code/src/vs/base/common/oauth.ts +++ b/code/src/vs/base/common/oauth.ts @@ -257,6 +257,115 @@ export interface IAuthorizationServerMetadata { * OPTIONAL. JSON array containing a list of PKCE code challenge methods supported. */ code_challenge_methods_supported?: string[]; + + /** + * OPTIONAL. Boolean flag indicating whether the authorization server supports the + * client_id_metadata document. + * ref https://datatracker.ietf.org/doc/html/draft-parecki-oauth-client-id-metadata-document-03 + */ + client_id_metadata_document_supported?: boolean; +} + +/** + * Request for the dynamic client registration endpoint. + * @see https://datatracker.ietf.org/doc/html/rfc7591#section-2 + */ +export interface IAuthorizationDynamicClientRegistrationRequest { + /** + * OPTIONAL. Array of redirection URI strings for use in redirect-based flows + * such as the authorization code and implicit flows. + */ + redirect_uris?: string[]; + + /** + * OPTIONAL. String indicator of the requested authentication method for the token endpoint. + * Values: "none", "client_secret_post", "client_secret_basic". + * Default is "client_secret_basic". + */ + token_endpoint_auth_method?: string; + + /** + * OPTIONAL. Array of OAuth 2.0 grant type strings that the client can use at the token endpoint. + * Default is ["authorization_code"]. + */ + grant_types?: string[]; + + /** + * OPTIONAL. Array of the OAuth 2.0 response type strings that the client can use at the authorization endpoint. + * Default is ["code"]. + */ + response_types?: string[]; + + /** + * OPTIONAL. Human-readable string name of the client to be presented to the end-user during authorization. + */ + client_name?: string; + + /** + * OPTIONAL. URL string of a web page providing information about the client. + */ + client_uri?: string; + + /** + * OPTIONAL. URL string that references a logo for the client. + */ + logo_uri?: string; + + /** + * OPTIONAL. String containing a space-separated list of scope values that the client can use when requesting access tokens. + */ + scope?: string; + + /** + * OPTIONAL. Array of strings representing ways to contact people responsible for this client, typically email addresses. + */ + contacts?: string[]; + + /** + * OPTIONAL. URL string that points to a human-readable terms of service document for the client. + */ + tos_uri?: string; + + /** + * OPTIONAL. URL string that points to a human-readable privacy policy document. + */ + policy_uri?: string; + + /** + * OPTIONAL. URL string referencing the client's JSON Web Key (JWK) Set document. + */ + jwks_uri?: string; + + /** + * OPTIONAL. Client's JSON Web Key Set document value. + */ + jwks?: object; + + /** + * OPTIONAL. A unique identifier string assigned by the client developer or software publisher. + */ + software_id?: string; + + /** + * OPTIONAL. A version identifier string for the client software. + */ + software_version?: string; + + /** + * OPTIONAL. A software statement containing client metadata values about the client software as claims. + */ + software_statement?: string; + + /** + * OPTIONAL. Application type. Usually "native" for OAuth clients. + * https://openid.net/specs/openid-connect-registration-1_0.html + */ + application_type?: 'native' | 'web' | string; + + /** + * OPTIONAL. Additional metadata fields as defined by extensions. + */ + [key: string]: unknown; } /** @@ -749,35 +858,35 @@ export async function fetchDynamicRegistration(serverMetadata: IAuthorizationSer if (!serverMetadata.registration_endpoint) { throw new Error('Server does not support dynamic registration'); } + + const requestBody: IAuthorizationDynamicClientRegistrationRequest = { + client_name: clientName, + client_uri: 'https://code.visualstudio.com', + grant_types: serverMetadata.grant_types_supported + ? serverMetadata.grant_types_supported.filter(gt => grantTypesSupported.includes(gt)) + : grantTypesSupported, + response_types: ['code'], + redirect_uris: [ + 'https://insiders.vscode.dev/redirect', + 'https://vscode.dev/redirect', + 'http://127.0.0.1/', + // Added these for any server that might do + // only exact match on the redirect URI even + // though the spec says it should not care + // about the port. + `http://127.0.0.1:${DEFAULT_AUTH_FLOW_PORT}/` + ], + scope: scopes?.join(AUTH_SCOPE_SEPARATOR), + token_endpoint_auth_method: 'none', + application_type: 'native' + }; + const response = await fetch(serverMetadata.registration_endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - client_name: clientName, - client_uri: 'https://code.visualstudio.com', - grant_types: serverMetadata.grant_types_supported - ? serverMetadata.grant_types_supported.filter(gt => grantTypesSupported.includes(gt)) - : grantTypesSupported, - response_types: ['code'], - redirect_uris: [ - 'https://insiders.vscode.dev/redirect', - 'https://vscode.dev/redirect', - 'http://localhost/', - 'http://127.0.0.1/', - // Added these for any server that might do - // only exact match on the redirect URI even - // though the spec says it should not care - // about the port. - `http://localhost:${DEFAULT_AUTH_FLOW_PORT}/`, - `http://127.0.0.1:${DEFAULT_AUTH_FLOW_PORT}/` - ], - scope: scopes?.join(AUTH_SCOPE_SEPARATOR), - token_endpoint_auth_method: 'none', - // https://openid.net/specs/openid-connect-registration-1_0.html - application_type: 'native' - }) + body: JSON.stringify(requestBody) }); if (!response.ok) { @@ -938,17 +1047,25 @@ export function getClaimsFromJWT(token: string): IAuthorizationJWTClaims { * Checks if two scope lists are equivalent, regardless of order. * This is useful for comparing OAuth scopes where the order should not matter. * - * @param scopes1 First list of scopes to compare - * @param scopes2 Second list of scopes to compare + * @param scopes1 First list of scopes to compare (can be undefined) + * @param scopes2 Second list of scopes to compare (can be undefined) * @returns true if the scope lists contain the same scopes (order-independent), false otherwise * * @example * ```typescript * scopesMatch(['read', 'write'], ['write', 'read']) // Returns: true * scopesMatch(['read'], ['write']) // Returns: false + * scopesMatch(undefined, undefined) // Returns: true + * scopesMatch(['read'], undefined) // Returns: false * ``` */ -export function scopesMatch(scopes1: readonly string[], scopes2: readonly string[]): boolean { +export function scopesMatch(scopes1: readonly string[] | undefined, scopes2: readonly string[] | undefined): boolean { + if (scopes1 === scopes2) { + return true; + } + if (!scopes1 || !scopes2) { + return false; + } if (scopes1.length !== scopes2.length) { return false; } @@ -959,3 +1076,258 @@ export function scopesMatch(scopes1: readonly string[], scopes2: readonly string return sortedScopes1.every((scope, index) => scope === sortedScopes2[index]); } + +interface CommonResponse { + status: number; + statusText: string; + json(): Promise; + text(): Promise; +} + +interface IFetcher { + (input: string, init: { method: string; headers: Record }): Promise; +} + +export interface IFetchResourceMetadataOptions { + /** + * Headers to include only when the resource metadata URL has the same origin as the target resource + */ + sameOriginHeaders?: Record; + /** + * Optional custom fetch implementation (defaults to global fetch) + */ + fetch?: IFetcher; +} + +/** + * Fetches and validates OAuth 2.0 protected resource metadata from the given URL. + * + * @param targetResource The target resource URL to compare origins with (e.g., the MCP server URL) + * @param resourceMetadataUrl Optional URL to fetch the resource metadata from. If not provided, will try well-known URIs. + * @param options Configuration options for the fetch operation + * @returns Promise that resolves to an object containing the validated resource metadata and any errors encountered during discovery + * @throws Error if the fetch fails, returns non-200 status, or the response is invalid on all attempted URLs + */ +export async function fetchResourceMetadata( + targetResource: string, + resourceMetadataUrl: string | undefined, + options: IFetchResourceMetadataOptions = {} +): Promise<{ metadata: IAuthorizationProtectedResourceMetadata; discoveryUrl: string; errors: Error[] }> { + const { + sameOriginHeaders = {}, + fetch: fetchImpl = fetch + } = options; + + const targetResourceUrlObj = new URL(targetResource); + + const fetchPrm = async (prmUrl: string, validateUrl: string) => { + // Determine if we should include same-origin headers + let headers: Record = { + 'Accept': 'application/json' + }; + + const resourceMetadataUrlObj = new URL(prmUrl); + if (resourceMetadataUrlObj.origin === targetResourceUrlObj.origin) { + headers = { + ...headers, + ...sameOriginHeaders + }; + } + + const response = await fetchImpl(prmUrl, { method: 'GET', headers }); + if (response.status !== 200) { + let errorText: string; + try { + errorText = await response.text(); + } catch { + errorText = response.statusText; + } + throw new Error(`Failed to fetch resource metadata from ${prmUrl}: ${response.status} ${errorText}`); + } + + const body = await response.json(); + if (isAuthorizationProtectedResourceMetadata(body)) { + // Validate that the resource matches the target resource + // Use URL constructor for normalization - it handles hostname case and trailing slashes + const prmValue = new URL(body.resource).toString(); + const expectedResource = new URL(validateUrl).toString(); + if (prmValue !== expectedResource) { + throw new Error(`Protected Resource Metadata 'resource' property value "${prmValue}" does not match expected value "${expectedResource}" for URL ${prmUrl}. Per RFC 9728, these MUST match. See https://datatracker.ietf.org/doc/html/rfc9728#PRConfigurationValidation`); + } + return body; + } else { + throw new Error(`Invalid resource metadata from ${prmUrl}. Expected to follow shape of https://datatracker.ietf.org/doc/html/rfc9728#name-protected-resource-metadata (Hints: is scopes_supported an array? Is resource a string?). Current payload: ${JSON.stringify(body)}`); + } + }; + + const errors: Error[] = []; + if (resourceMetadataUrl) { + try { + const metadata = await fetchPrm(resourceMetadataUrl, targetResource); + return { metadata, discoveryUrl: resourceMetadataUrl, errors }; + } catch (e) { + errors.push(e instanceof Error ? e : new Error(String(e))); + } + } + + // Try well-known URIs starting with path-appended, then root + const hasPathComponent = targetResourceUrlObj.pathname !== '/'; + const rootUrl = `${targetResourceUrlObj.origin}${AUTH_PROTECTED_RESOURCE_METADATA_DISCOVERY_PATH}`; + + if (hasPathComponent) { + const pathAppendedUrl = `${rootUrl}${targetResourceUrlObj.pathname}`; + try { + const metadata = await fetchPrm(pathAppendedUrl, targetResource); + return { metadata, discoveryUrl: pathAppendedUrl, errors }; + } catch (e) { + errors.push(e instanceof Error ? e : new Error(String(e))); + } + } + + // Finally, try root discovery + try { + const metadata = await fetchPrm(rootUrl, targetResourceUrlObj.origin); + return { metadata, discoveryUrl: rootUrl, errors }; + } catch (e) { + errors.push(e instanceof Error ? e : new Error(String(e))); + } + + // If we've tried all methods and none worked, throw the error(s) + if (errors.length === 1) { + throw errors[0]; + } else { + throw new AggregateError(errors, 'Failed to fetch resource metadata from all attempted URLs'); + } +} + +export interface IFetchAuthorizationServerMetadataOptions { + /** + * Headers to include in the requests + */ + additionalHeaders?: Record; + /** + * Optional custom fetch implementation (defaults to global fetch) + */ + fetch?: IFetcher; +} + +/** Helper to try parsing the response as authorization server metadata */ +async function tryParseAuthServerMetadata(response: CommonResponse): Promise { + if (response.status !== 200) { + return undefined; + } + try { + const body = await response.json(); + if (isAuthorizationServerMetadata(body)) { + return body; + } + } catch { + // Failed to parse as JSON or not valid metadata + } + return undefined; +} + +/** Helper to get error text from response */ +async function getErrText(res: CommonResponse): Promise { + try { + return await res.text(); + } catch { + return res.statusText; + } +} + +/** + * Fetches and validates OAuth 2.0 authorization server metadata from the given authorization server URL. + * + * This function tries multiple discovery endpoints in the following order: + * 1. OAuth 2.0 Authorization Server Metadata with path insertion (RFC 8414) + * 2. OpenID Connect Discovery with path insertion + * 3. OpenID Connect Discovery with path addition + * + * Path insertion: For issuer URLs with path components (e.g., https://example.com/tenant), + * the well-known path is inserted after the origin and before the path: + * https://example.com/.well-known/oauth-authorization-server/tenant + * + * Path addition: The well-known path is simply appended to the existing path: + * https://example.com/tenant/.well-known/openid-configuration + * + * @param authorizationServer The authorization server URL (issuer identifier) + * @param options Configuration options for the fetch operation + * @returns Promise that resolves to the validated authorization server metadata + * @throws Error if all discovery attempts fail or the response is invalid + * + * @see https://datatracker.ietf.org/doc/html/rfc8414#section-3 + */ +export async function fetchAuthorizationServerMetadata( + authorizationServer: string, + options: IFetchAuthorizationServerMetadataOptions = {} +): Promise<{ metadata: IAuthorizationServerMetadata; discoveryUrl: string; errors: Error[] }> { + const { + additionalHeaders = {}, + fetch: fetchImpl = fetch + } = options; + + const authorizationServerUrl = new URL(authorizationServer); + const extraPath = authorizationServerUrl.pathname === '/' ? '' : authorizationServerUrl.pathname; + + const errors: Error[] = []; + + const doFetch = async (url: string): Promise => { + try { + const rawResponse = await fetchImpl(url, { + method: 'GET', + headers: { + ...additionalHeaders, + 'Accept': 'application/json' + } + }); + const metadata = await tryParseAuthServerMetadata(rawResponse); + if (metadata) { + return metadata; + } + // No metadata found, collect error from response + errors.push(new Error(`Failed to fetch authorization server metadata from ${url}: ${rawResponse.status} ${await getErrText(rawResponse)}`)); + return undefined; + } catch (e) { + // Collect error from fetch failure + errors.push(e instanceof Error ? e : new Error(String(e))); + return undefined; + } + }; + + // For the oauth server metadata discovery path, we _INSERT_ + // the well known path after the origin and before the path. + // https://datatracker.ietf.org/doc/html/rfc8414#section-3 + const pathToFetch = new URL(AUTH_SERVER_METADATA_DISCOVERY_PATH, authorizationServer).toString() + extraPath; + let metadata = await doFetch(pathToFetch); + if (metadata) { + return { metadata, discoveryUrl: pathToFetch, errors }; + } + + // Try fetching the OpenID Connect Discovery with path insertion. + // For issuer URLs with path components, this inserts the well-known path + // after the origin and before the path. + const openidPathInsertionUrl = new URL(OPENID_CONNECT_DISCOVERY_PATH, authorizationServer).toString() + extraPath; + metadata = await doFetch(openidPathInsertionUrl); + if (metadata) { + return { metadata, discoveryUrl: openidPathInsertionUrl, errors }; + } + + // Try fetching the other discovery URL. For the openid metadata discovery + // path, we _ADD_ the well known path after the existing path. + // https://datatracker.ietf.org/doc/html/rfc8414#section-3 + const openidPathAdditionUrl = authorizationServer.endsWith('/') + ? authorizationServer + OPENID_CONNECT_DISCOVERY_PATH.substring(1) // Remove leading slash if authServer ends with slash + : authorizationServer + OPENID_CONNECT_DISCOVERY_PATH; + metadata = await doFetch(openidPathAdditionUrl); + if (metadata) { + return { metadata, discoveryUrl: openidPathAdditionUrl, errors }; + } + + // If we've tried all URLs and none worked, throw the error(s) + if (errors.length === 1) { + throw errors[0]; + } else { + throw new AggregateError(errors, 'Failed to fetch authorization server metadata from all attempted URLs'); + } +} diff --git a/code/src/vs/base/common/objects.ts b/code/src/vs/base/common/objects.ts index cd976d77377..e8e60b1ea92 100644 --- a/code/src/vs/base/common/objects.ts +++ b/code/src/vs/base/common/objects.ts @@ -69,10 +69,10 @@ function _cloneAndChange(obj: any, changer: (orig: any) => any, seen: Set): throw new Error('Cannot clone recursive data-structure'); } seen.add(obj); - const r2 = {}; + const r2: Record = {}; for (const i2 in obj) { if (_hasOwnProperty.call(obj, i2)) { - (r2 as any)[i2] = _cloneAndChange(obj[i2], changer, seen); + r2[i2] = _cloneAndChange(obj[i2], changer, seen); } } seen.delete(obj); diff --git a/code/src/vs/base/common/observableInternal/changeTracker.ts b/code/src/vs/base/common/observableInternal/changeTracker.ts index b29fb62ce40..11ee5d7edf9 100644 --- a/code/src/vs/base/common/observableInternal/changeTracker.ts +++ b/code/src/vs/base/common/observableInternal/changeTracker.ts @@ -31,6 +31,7 @@ export function recordChanges { return { createChangeSummary: (_previousChangeSummary) => { + // eslint-disable-next-line local/code-no-any-casts return { changes: [], } as any; @@ -38,6 +39,7 @@ export function recordChanges { + // eslint-disable-next-line local/code-no-any-casts return { changes: [], } as any; @@ -74,6 +77,7 @@ export function recordChangesLazy)[key] === value) { return key; } } diff --git a/code/src/vs/base/common/observableInternal/experimental/reducer.ts b/code/src/vs/base/common/observableInternal/experimental/reducer.ts index 573ef3d07dd..032666f2e99 100644 --- a/code/src/vs/base/common/observableInternal/experimental/reducer.ts +++ b/code/src/vs/base/common/observableInternal/experimental/reducer.ts @@ -36,6 +36,7 @@ export interface IReducerOptions { * Additionally, a reducer can report how that state changed. */ export function observableReducer(owner: DebugOwner, options: IReducerOptions): SimplifyObservableWithChange { + // eslint-disable-next-line local/code-no-any-casts return observableReducerSettable(owner, options) as any; } diff --git a/code/src/vs/base/common/observableInternal/experimental/time.ts b/code/src/vs/base/common/observableInternal/experimental/time.ts new file mode 100644 index 00000000000..af167bb8667 --- /dev/null +++ b/code/src/vs/base/common/observableInternal/experimental/time.ts @@ -0,0 +1,118 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from '../../lifecycle.js'; +import { IObservable } from '../base.js'; +import { DisposableStore, IDisposable, toDisposable } from '../commonFacade/deps.js'; +import { observableValue } from '../observables/observableValue.js'; +import { autorun } from '../reactions/autorun.js'; + +/** Measures the total time an observable had the value "true". */ +export class TotalTrueTimeObservable extends Disposable { + private _totalTime = 0; + private _startTime: number | undefined = undefined; + + constructor( + private readonly value: IObservable, + ) { + super(); + this._register(autorun(reader => { + const isTrue = this.value.read(reader); + if (isTrue) { + this._startTime = Date.now(); + } else { + if (this._startTime !== undefined) { + const delta = Date.now() - this._startTime; + this._totalTime += delta; + this._startTime = undefined; + } + } + })); + } + + /** + * Reports the total time the observable has been true in milliseconds. + * E.g. `true` for 100ms, then `false` for 50ms, then `true` for 200ms results in 300ms. + */ + public totalTimeMs(): number { + if (this._startTime !== undefined) { + return this._totalTime + (Date.now() - this._startTime); + } + return this._totalTime; + } + + /** + * Runs the callback when the total time the observable has been true increased by the given delta in milliseconds. + */ + public fireWhenTimeIncreasedBy(deltaTimeMs: number, callback: () => void): IDisposable { + const store = new DisposableStore(); + let accumulatedTime = 0; + let startTime: number | undefined = undefined; + + store.add(autorun(reader => { + const isTrue = this.value.read(reader); + + if (isTrue) { + startTime = Date.now(); + const remainingTime = deltaTimeMs - accumulatedTime; + + if (remainingTime <= 0) { + callback(); + store.dispose(); + return; + } + + const handle = setTimeout(() => { + accumulatedTime += (Date.now() - startTime!); + startTime = undefined; + callback(); + store.dispose(); + }, remainingTime); + + reader.store.add(toDisposable(() => { + clearTimeout(handle); + if (startTime !== undefined) { + accumulatedTime += (Date.now() - startTime); + startTime = undefined; + } + })); + } + })); + + return store; + } +} + +/** + * Returns an observable that is true when the input observable was true within the last `timeMs` milliseconds. + */ +export function wasTrueRecently(obs: IObservable, timeMs: number, store: DisposableStore): IObservable { + const result = observableValue('wasTrueRecently', false); + let timeout: ReturnType | undefined; + + store.add(autorun(reader => { + const value = obs.read(reader); + if (value) { + result.set(true, undefined); + if (timeout !== undefined) { + clearTimeout(timeout); + timeout = undefined; + } + } else { + timeout = setTimeout(() => { + result.set(false, undefined); + timeout = undefined; + }, timeMs); + } + })); + + store.add(toDisposable(() => { + if (timeout !== undefined) { + clearTimeout(timeout); + } + })); + + return result; +} diff --git a/code/src/vs/base/common/observableInternal/index.ts b/code/src/vs/base/common/observableInternal/index.ts index 50540a9af91..af010d118c3 100644 --- a/code/src/vs/base/common/observableInternal/index.ts +++ b/code/src/vs/base/common/observableInternal/index.ts @@ -14,10 +14,11 @@ export { type IDerivedReader } from './observables/derivedImpl.js'; export { ObservableLazy, ObservableLazyPromise, ObservablePromise, PromiseResult, } from './utils/promise.js'; export { derivedWithCancellationToken, waitForState } from './utils/utilsCancellation.js'; export { - debouncedObservableDeprecated, debouncedObservable, derivedObservableWithCache, + debouncedObservable, debouncedObservable2, derivedObservableWithCache, derivedObservableWithWritableCache, keepObserved, mapObservableArrayCached, observableFromPromise, recomputeInitiallyAndOnChange, signalFromObservable, wasEventTriggeredRecently, + isObservable, } from './utils/utils.js'; export { type DebugOwner } from './debugName.js'; export { type IChangeContext, type IChangeTracker, recordChanges, recordChangesLazy } from './changeTracker.js'; @@ -40,8 +41,10 @@ import { addLogger, setLogObservableFn } from './logging/logging.js'; import { ConsoleObservableLogger, logObservableToConsole } from './logging/consoleObservableLogger.js'; import { DevToolsLogger } from './logging/debugger/devToolsLogger.js'; import { env } from '../process.js'; +import { _setDebugGetObservableGraph } from './observables/baseObservable.js'; +import { debugGetObservableGraph } from './logging/debugGetDependencyGraph.js'; - +_setDebugGetObservableGraph(debugGetObservableGraph); setLogObservableFn(logObservableToConsole); // Remove "//" in the next line to enable logging diff --git a/code/src/vs/base/common/observableInternal/logging/consoleObservableLogger.ts b/code/src/vs/base/common/observableInternal/logging/consoleObservableLogger.ts index f8ca91b86ff..7450139cc56 100644 --- a/code/src/vs/base/common/observableInternal/logging/consoleObservableLogger.ts +++ b/code/src/vs/base/common/observableInternal/logging/consoleObservableLogger.ts @@ -77,6 +77,7 @@ export class ConsoleObservableLogger implements IObservableLogger { const debugTrackUpdating = false; if (debugTrackUpdating) { const updating: IObservable[] = []; + // eslint-disable-next-line local/code-no-any-casts (derived as any).__debugUpdating = updating; const existingBeginUpdate = derived.beginUpdate; diff --git a/code/src/vs/base/common/observableInternal/logging/debugGetDependencyGraph.ts b/code/src/vs/base/common/observableInternal/logging/debugGetDependencyGraph.ts new file mode 100644 index 00000000000..9a13ba8840f --- /dev/null +++ b/code/src/vs/base/common/observableInternal/logging/debugGetDependencyGraph.ts @@ -0,0 +1,159 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IObservable, IObserver } from '../base.js'; +import { Derived } from '../observables/derivedImpl.js'; +import { FromEventObservable } from '../observables/observableFromEvent.js'; +import { ObservableValue } from '../observables/observableValue.js'; +import { AutorunObserver } from '../reactions/autorunImpl.js'; +import { formatValue } from './consoleObservableLogger.js'; + +interface IOptions { + type: 'dependencies' | 'observers'; + debugNamePostProcessor?: (name: string) => string; +} + +export function debugGetObservableGraph(obs: IObservable | IObserver, options: IOptions): string { + const debugNamePostProcessor = options?.debugNamePostProcessor ?? ((str: string) => str); + const info = Info.from(obs, debugNamePostProcessor); + if (!info) { + return ''; + } + + const alreadyListed = new Set | IObserver>(); + + if (options.type === 'observers') { + return formatObservableInfoWithObservers(info, 0, alreadyListed, options).trim(); + } else { + return formatObservableInfoWithDependencies(info, 0, alreadyListed, options).trim(); + } +} + +function formatObservableInfoWithDependencies(info: Info, indentLevel: number, alreadyListed: Set | IObserver>, options: IOptions): string { + const indent = '\t\t'.repeat(indentLevel); + const lines: string[] = []; + + const isAlreadyListed = alreadyListed.has(info.sourceObj); + if (isAlreadyListed) { + lines.push(`${indent}* ${info.type} ${info.name} (already listed)`); + return lines.join('\n'); + } + + alreadyListed.add(info.sourceObj); + + lines.push(`${indent}* ${info.type} ${info.name}:`); + lines.push(`${indent} value: ${formatValue(info.value, 50)}`); + lines.push(`${indent} state: ${info.state}`); + + if (info.dependencies.length > 0) { + lines.push(`${indent} dependencies:`); + for (const dep of info.dependencies) { + const info = Info.from(dep, options.debugNamePostProcessor ?? (name => name)) ?? Info.unknown(dep); + lines.push(formatObservableInfoWithDependencies(info, indentLevel + 1, alreadyListed, options)); + } + } + + return lines.join('\n'); +} + +function formatObservableInfoWithObservers(info: Info, indentLevel: number, alreadyListed: Set | IObserver>, options: IOptions): string { + const indent = '\t\t'.repeat(indentLevel); + const lines: string[] = []; + + const isAlreadyListed = alreadyListed.has(info.sourceObj); + if (isAlreadyListed) { + lines.push(`${indent}* ${info.type} ${info.name} (already listed)`); + return lines.join('\n'); + } + + alreadyListed.add(info.sourceObj); + + lines.push(`${indent}* ${info.type} ${info.name}:`); + lines.push(`${indent} value: ${formatValue(info.value, 50)}`); + lines.push(`${indent} state: ${info.state}`); + + if (info.observers.length > 0) { + lines.push(`${indent} observers:`); + for (const observer of info.observers) { + const info = Info.from(observer, options.debugNamePostProcessor ?? (name => name)) ?? Info.unknown(observer); + lines.push(formatObservableInfoWithObservers(info, indentLevel + 1, alreadyListed, options)); + } + } + + return lines.join('\n'); +} + +class Info { + public static from(obs: IObservable | IObserver, debugNamePostProcessor: (name: string) => string): Info | undefined { + if (obs instanceof AutorunObserver) { + const state = obs.debugGetState(); + return new Info( + obs, + debugNamePostProcessor(obs.debugName), + 'autorun', + undefined, + state.stateStr, + Array.from(state.dependencies), + [] + ); + } else if (obs instanceof Derived) { + const state = obs.debugGetState(); + return new Info( + obs, + debugNamePostProcessor(obs.debugName), + 'derived', + state.value, + state.stateStr, + Array.from(state.dependencies), + Array.from(obs.debugGetObservers()) + ); + } else if (obs instanceof ObservableValue) { + const state = obs.debugGetState(); + return new Info( + obs, + debugNamePostProcessor(obs.debugName), + 'observableValue', + state.value, + 'upToDate', + [], + Array.from(obs.debugGetObservers()) + ); + } else if (obs instanceof FromEventObservable) { + const state = obs.debugGetState(); + return new Info( + obs, + debugNamePostProcessor(obs.debugName), + 'fromEvent', + state.value, + state.hasValue ? 'upToDate' : 'initial', + [], + Array.from(obs.debugGetObservers()) + ); + } + return undefined; + } + + public static unknown(obs: IObservable | IObserver): Info { + return new Info( + obs, + '(unknown)', + 'unknown', + undefined, + 'unknown', + [], + [] + ); + } + + constructor( + public readonly sourceObj: IObservable | IObserver, + public readonly name: string, + public readonly type: string, + public readonly value: any, + public readonly state: string, + public readonly dependencies: (IObservable | IObserver)[], + public readonly observers: (IObservable | IObserver)[], + ) { } +} diff --git a/code/src/vs/base/common/observableInternal/logging/debugger/debuggerRpc.ts b/code/src/vs/base/common/observableInternal/logging/debugger/debuggerRpc.ts index 3da4cbd4765..332be8e6c4a 100644 --- a/code/src/vs/base/common/observableInternal/logging/debugger/debuggerRpc.ts +++ b/code/src/vs/base/common/observableInternal/logging/debugger/debuggerRpc.ts @@ -9,6 +9,7 @@ export function registerDebugChannel( channelId: T['channelId'], createClient: () => T['client'], ): SimpleTypedRpcConnection> { + // eslint-disable-next-line local/code-no-any-casts const g = globalThis as any as GlobalObj; let queuedNotifications: unknown[] = []; diff --git a/code/src/vs/base/common/observableInternal/logging/debugger/rpc.ts b/code/src/vs/base/common/observableInternal/logging/debugger/rpc.ts index 6c53100e379..d19da1fe159 100644 --- a/code/src/vs/base/common/observableInternal/logging/debugger/rpc.ts +++ b/code/src/vs/base/common/observableInternal/logging/debugger/rpc.ts @@ -91,6 +91,7 @@ export class SimpleTypedRpcConnection { } }); + // eslint-disable-next-line local/code-no-any-casts this.api = { notifications: notifications, requests: requests } as any; } } diff --git a/code/src/vs/base/common/observableInternal/map.ts b/code/src/vs/base/common/observableInternal/map.ts index 1db8c9ebb26..5cd028db280 100644 --- a/code/src/vs/base/common/observableInternal/map.ts +++ b/code/src/vs/base/common/observableInternal/map.ts @@ -51,7 +51,7 @@ export class ObservableMap implements Map { } } - forEach(callbackfn: (value: V, key: K, map: Map) => void, thisArg?: any): void { + forEach(callbackfn: (value: V, key: K, map: Map) => void, thisArg?: unknown): void { this._data.forEach((value, key, _map) => { callbackfn.call(thisArg, value, key, this); }); diff --git a/code/src/vs/base/common/observableInternal/observables/baseObservable.ts b/code/src/vs/base/common/observableInternal/observables/baseObservable.ts index f279d10f78d..4903e1a6e57 100644 --- a/code/src/vs/base/common/observableInternal/observables/baseObservable.ts +++ b/code/src/vs/base/common/observableInternal/observables/baseObservable.ts @@ -7,6 +7,7 @@ import { IObservableWithChange, IObserver, IReader, IObservable } from '../base. import { DisposableStore } from '../commonFacade/deps.js'; import { DebugLocation } from '../debugLocation.js'; import { DebugOwner, getFunctionName } from '../debugName.js'; +import { debugGetObservableGraph } from '../logging/debugGetDependencyGraph.js'; import { getLogger, logObservable } from '../logging/logging.js'; import type { keepObserved, recomputeInitiallyAndOnChange } from '../utils/utils.js'; import { derivedOpts } from './derived.js'; @@ -30,6 +31,11 @@ export function _setKeepObserved(keepObserved: typeof _keepObserved) { _keepObserved = keepObserved; } +let _debugGetObservableGraph: typeof debugGetObservableGraph; +export function _setDebugGetObservableGraph(debugGetObservableGraph: typeof _debugGetObservableGraph) { + _debugGetObservableGraph = debugGetObservableGraph; +} + export abstract class ConvenientObservable implements IObservableWithChange { get TChange(): TChange { return null!; } @@ -121,6 +127,23 @@ export abstract class ConvenientObservable implements IObservableWit protected get debugValue() { return this.get(); } + + get debug(): DebugHelper { + return new DebugHelper(this); + } +} + +class DebugHelper { + constructor(public readonly observable: IObservableWithChange) { + } + + getDependencyGraph(): string { + return _debugGetObservableGraph(this.observable, { type: 'dependencies' }); + } + + getObserverGraph(): string { + return _debugGetObservableGraph(this.observable, { type: 'observers' }); + } } export abstract class BaseObservable extends ConvenientObservable { diff --git a/code/src/vs/base/common/observableInternal/observables/derived.ts b/code/src/vs/base/common/observableInternal/observables/derived.ts index f59e75890f9..bb8f9ce5578 100644 --- a/code/src/vs/base/common/observableInternal/observables/derived.ts +++ b/code/src/vs/base/common/observableInternal/observables/derived.ts @@ -17,8 +17,8 @@ import { IDerivedReader, Derived, DerivedWithSetter } from './derivedImpl.js'; * * {@link computeFn} should start with a JS Doc using `@description` to name the derived. */ -export function derived(computeFn: (reader: IDerivedReader) => T): IObservableWithChange; -export function derived(owner: DebugOwner, computeFn: (reader: IDerivedReader) => T): IObservableWithChange; +export function derived(computeFn: (reader: IDerivedReader, debugLocation?: DebugLocation) => T): IObservableWithChange; +export function derived(owner: DebugOwner, computeFn: (reader: IDerivedReader) => T, debugLocation?: DebugLocation): IObservableWithChange; export function derived( computeFnOrOwner: ((reader: IDerivedReader) => T) | DebugOwner, computeFn?: ((reader: IDerivedReader) => T) | undefined, @@ -35,7 +35,9 @@ export function derived( ); } return new Derived( + // eslint-disable-next-line local/code-no-any-casts new DebugNameData(undefined, undefined, computeFnOrOwner as any), + // eslint-disable-next-line local/code-no-any-casts computeFnOrOwner as any, undefined, undefined, @@ -119,10 +121,12 @@ export function derivedWithStore(computeFnOrOwner: ((reader: IReader, store: let computeFn: (reader: IReader, store: DisposableStore) => T; let owner: DebugOwner; if (computeFnOrUndefined === undefined) { + // eslint-disable-next-line local/code-no-any-casts computeFn = computeFnOrOwner as any; owner = undefined; } else { owner = computeFnOrOwner; + // eslint-disable-next-line local/code-no-any-casts computeFn = computeFnOrUndefined as any; } @@ -153,10 +157,12 @@ export function derivedDisposable(computeFnOr let computeFn: (reader: IReader) => T; let owner: DebugOwner; if (computeFnOrUndefined === undefined) { + // eslint-disable-next-line local/code-no-any-casts computeFn = computeFnOrOwner as any; owner = undefined; } else { owner = computeFnOrOwner; + // eslint-disable-next-line local/code-no-any-casts computeFn = computeFnOrUndefined as any; } diff --git a/code/src/vs/base/common/observableInternal/observables/derivedImpl.ts b/code/src/vs/base/common/observableInternal/observables/derivedImpl.ts index 3b1505da9ff..96e37ce40c7 100644 --- a/code/src/vs/base/common/observableInternal/observables/derivedImpl.ts +++ b/code/src/vs/base/common/observableInternal/observables/derivedImpl.ts @@ -40,6 +40,16 @@ export const enum DerivedState { upToDate = 3, } +function derivedStateToString(state: DerivedState): string { + switch (state) { + case DerivedState.initial: return 'initial'; + case DerivedState.dependenciesMightHaveChanged: return 'dependenciesMightHaveChanged'; + case DerivedState.stale: return 'stale'; + case DerivedState.upToDate: return 'upToDate'; + default: return ''; + } +} + export class Derived extends BaseObservable implements IDerivedReader, IObserver { private _state = DerivedState.initial; private _value: T | undefined = undefined; @@ -300,6 +310,7 @@ export class Derived extends BaseObserv shouldReact = this._changeTracker ? this._changeTracker.handleChange({ changedObservable: observable, change, + // eslint-disable-next-line local/code-no-any-casts didChange: (o): this is any => o === observable as any, }, this._changeSummary!) : true; } catch (e) { @@ -370,9 +381,7 @@ export class Derived extends BaseObserv super.addObserver(observer); if (shouldCallBeginUpdate) { - if (this._removedObserverToCallEndUpdateOn && this._removedObserverToCallEndUpdateOn.has(observer)) { - this._removedObserverToCallEndUpdateOn.delete(observer); - } else { + if (!this._removedObserverToCallEndUpdateOn?.delete(observer)) { observer.beginUpdate(this); } } @@ -391,6 +400,7 @@ export class Derived extends BaseObserv public debugGetState() { return { state: this._state, + stateStr: derivedStateToString(this._state), updateCount: this._updateCount, isComputing: this._isComputing, dependencies: this._dependencies, @@ -399,6 +409,7 @@ export class Derived extends BaseObserv } public debugSetValue(newValue: unknown) { + // eslint-disable-next-line local/code-no-any-casts this._value = newValue as any; } diff --git a/code/src/vs/base/common/observableInternal/observables/observableFromEvent.ts b/code/src/vs/base/common/observableInternal/observables/observableFromEvent.ts index b8b2a05860a..009387b39e3 100644 --- a/code/src/vs/base/common/observableInternal/observables/observableFromEvent.ts +++ b/code/src/vs/base/common/observableInternal/observables/observableFromEvent.ts @@ -48,6 +48,7 @@ export function observableFromEvent(...args: export function observableFromEventOpts( options: IDebugNameData & { equalsFn?: EqualityComparer; + getTransaction?: () => ITransaction | undefined; }, event: Event, getValue: (args: TArgs | undefined) => T, @@ -56,7 +57,10 @@ export function observableFromEventOpts( return new FromEventObservable( new DebugNameData(options.owner, options.debugName, options.debugReferenceFn ?? getValue), event, - getValue, () => FromEventObservable.globalTransaction, options.equalsFn ?? strictEquals, debugLocation + getValue, + () => options.getTransaction?.() ?? FromEventObservable.globalTransaction, + options.equalsFn ?? strictEquals, + debugLocation ); } @@ -147,9 +151,14 @@ export class FromEventObservable extends BaseObservable { } } - public debugSetValue(value: unknown) { + public debugSetValue(value: unknown): void { + // eslint-disable-next-line local/code-no-any-casts this._value = value as any; } + + public debugGetState() { + return { value: this._value, hasValue: this._hasValue }; + } } export namespace observableFromEvent { diff --git a/code/src/vs/base/common/observableInternal/reactions/autorunImpl.ts b/code/src/vs/base/common/observableInternal/reactions/autorunImpl.ts index 16f48bfa2f8..046bccfa54b 100644 --- a/code/src/vs/base/common/observableInternal/reactions/autorunImpl.ts +++ b/code/src/vs/base/common/observableInternal/reactions/autorunImpl.ts @@ -24,6 +24,15 @@ export const enum AutorunState { upToDate = 3, } +function autorunStateToString(state: AutorunState): string { + switch (state) { + case AutorunState.dependenciesMightHaveChanged: return 'dependenciesMightHaveChanged'; + case AutorunState.stale: return 'stale'; + case AutorunState.upToDate: return 'upToDate'; + default: return ''; + } +} + export class AutorunObserver implements IObserver, IReaderWithStore, IDisposable { private _state = AutorunState.stale; private _updateCount = 0; @@ -32,6 +41,7 @@ export class AutorunObserver implements IObserver, IReader private _dependenciesToBeRemoved = new Set>(); private _changeSummary: TChangeSummary | undefined; private _isRunning = false; + private _iteration = 0; public get debugName(): string { return this._debugNameData.getDebugName(this) ?? '(anonymous)'; @@ -127,6 +137,7 @@ export class AutorunObserver implements IObserver, IReader // IObserver implementation public beginUpdate(_observable: IObservable): void { if (this._state === AutorunState.upToDate) { + this._checkIterations(); this._state = AutorunState.dependenciesMightHaveChanged; } this._updateCount++; @@ -135,7 +146,11 @@ export class AutorunObserver implements IObserver, IReader public endUpdate(_observable: IObservable): void { try { if (this._updateCount === 1) { + this._iteration = 1; do { + if (this._checkIterations()) { + return; + } if (this._state === AutorunState.dependenciesMightHaveChanged) { this._state = AutorunState.upToDate; for (const d of this._dependencies) { @@ -147,6 +162,7 @@ export class AutorunObserver implements IObserver, IReader } } + this._iteration++; if (this._state !== AutorunState.upToDate) { this._run(); // Warning: indirect external call! } @@ -161,6 +177,7 @@ export class AutorunObserver implements IObserver, IReader public handlePossibleChange(observable: IObservable): void { if (this._state === AutorunState.upToDate && this._isDependency(observable)) { + this._checkIterations(); this._state = AutorunState.dependenciesMightHaveChanged; } } @@ -173,9 +190,11 @@ export class AutorunObserver implements IObserver, IReader const shouldReact = this._changeTracker ? this._changeTracker.handleChange({ changedObservable: observable, change, + // eslint-disable-next-line local/code-no-any-casts didChange: (o): this is any => o === observable as any, }, this._changeSummary!) : true; if (shouldReact) { + this._checkIterations(); this._state = AutorunState.stale; } } catch (e) { @@ -241,6 +260,7 @@ export class AutorunObserver implements IObserver, IReader updateCount: this._updateCount, dependencies: this._dependencies, state: this._state, + stateStr: autorunStateToString(this._state), }; } @@ -251,4 +271,12 @@ export class AutorunObserver implements IObserver, IReader this._state = AutorunState.stale; } } + + private _checkIterations(): boolean { + if (this._iteration > 100) { + onBugIndicatingError(new BugIndicatingError(`Autorun '${this.debugName}' is stuck in an infinite update loop.`)); + return true; + } + return false; + } } diff --git a/code/src/vs/base/common/observableInternal/set.ts b/code/src/vs/base/common/observableInternal/set.ts index 294cdc73ca4..a966df1bb4d 100644 --- a/code/src/vs/base/common/observableInternal/set.ts +++ b/code/src/vs/base/common/observableInternal/set.ts @@ -48,6 +48,7 @@ export class ObservableSet implements Set { forEach(callbackfn: (value: T, value2: T, set: Set) => void, thisArg?: any): void { this._data.forEach((value, value2, _set) => { + // eslint-disable-next-line local/code-no-any-casts callbackfn.call(thisArg, value, value2, this as any); }); } diff --git a/code/src/vs/base/common/observableInternal/utils/promise.ts b/code/src/vs/base/common/observableInternal/utils/promise.ts index e36c6be95e1..a6493858f6c 100644 --- a/code/src/vs/base/common/observableInternal/utils/promise.ts +++ b/code/src/vs/base/common/observableInternal/utils/promise.ts @@ -41,6 +41,10 @@ export class ObservablePromise { return new ObservablePromise(fn()); } + public static resolved(value: T): ObservablePromise { + return new ObservablePromise(Promise.resolve(value)); + } + private readonly _value = observableValue | undefined>(this, undefined); /** diff --git a/code/src/vs/base/common/observableInternal/utils/utils.ts b/code/src/vs/base/common/observableInternal/utils/utils.ts index ed35204c7e3..a7dda800004 100644 --- a/code/src/vs/base/common/observableInternal/utils/utils.ts +++ b/code/src/vs/base/common/observableInternal/utils/utils.ts @@ -5,7 +5,6 @@ import { autorun } from '../reactions/autorun.js'; import { IObservable, IObservableWithChange, IObserver, IReader, ITransaction } from '../base.js'; -import { transaction } from '../transaction.js'; import { observableValue } from '../observables/observableValue.js'; import { DebugOwner } from '../debugName.js'; import { DisposableStore, Event, IDisposable, toDisposable } from '../commonFacade/deps.js'; @@ -13,6 +12,7 @@ import { derived, derivedOpts } from '../observables/derived.js'; import { observableFromEvent } from '../observables/observableFromEvent.js'; import { observableSignal } from '../observables/observableSignal.js'; import { _setKeepObserved, _setRecomputeInitiallyAndOnChange } from '../observables/baseObservable.js'; +import { DebugLocation } from '../debugLocation.js'; export function observableFromPromise(promise: Promise): IObservable<{ value?: T }> { const observable = observableValue<{ value?: T }>('promiseValue', {}); @@ -31,42 +31,16 @@ export function signalFromObservable(owner: DebugOwner | undefined, observabl }); } -/** - * @deprecated Use `debouncedObservable` instead. - */ -export function debouncedObservableDeprecated(observable: IObservable, debounceMs: number, disposableStore: DisposableStore): IObservable { - const debouncedObservable = observableValue('debounced', undefined); - - let timeout: Timeout | undefined = undefined; - - disposableStore.add(autorun(reader => { - /** @description debounce */ - const value = observable.read(reader); - - if (timeout) { - clearTimeout(timeout); - } - timeout = setTimeout(() => { - transaction(tx => { - debouncedObservable.set(value, tx); - }); - }, debounceMs); - - })); - - return debouncedObservable; -} - /** * Creates an observable that debounces the input observable. */ -export function debouncedObservable(observable: IObservable, debounceMs: number): IObservable { +export function debouncedObservable(observable: IObservable, debounceMs: number | ((lastValue: T | undefined, newValue: T) => number), debugLocation = DebugLocation.ofCaller()): IObservable { let hasValue = false; let lastValue: T | undefined; let timeout: Timeout | undefined = undefined; - return observableFromEvent(cb => { + return observableFromEvent(undefined, cb => { const d = autorun(reader => { const value = observable.read(reader); @@ -77,10 +51,16 @@ export function debouncedObservable(observable: IObservable, debounceMs: n if (timeout) { clearTimeout(timeout); } + const debounceDuration = typeof debounceMs === 'number' ? debounceMs : debounceMs(lastValue, value); + if (debounceDuration === 0) { + lastValue = value; + cb(); + return; + } timeout = setTimeout(() => { lastValue = value; cb(); - }, debounceMs); + }, debounceDuration); } }); return { @@ -96,7 +76,48 @@ export function debouncedObservable(observable: IObservable, debounceMs: n } else { return observable.get(); } - }); + }, debugLocation); +} + +/** + * Creates an observable that debounces the input observable. + */ +export function debouncedObservable2(observable: IObservable, debounceMs: number | ((currentValue: T | undefined, newValue: T) => number), debugLocation = DebugLocation.ofCaller()): IObservable { + const s = observableSignal('handleTimeout'); + + let currentValue: T | undefined = undefined; + let timeout: Timeout | undefined = undefined; + + const d = derivedOpts({ + owner: undefined, + onLastObserverRemoved: () => { + currentValue = undefined; + } + }, reader => { + const val = observable.read(reader); + s.read(reader); + + if (val !== currentValue) { + const debounceDuration = typeof debounceMs === 'number' ? debounceMs : debounceMs(currentValue, val); + + if (debounceDuration === 0) { + currentValue = val; + return val; + } + + if (timeout) { + clearTimeout(timeout); + } + timeout = setTimeout(() => { + currentValue = val; + s.trigger(undefined); + }, debounceDuration); + } + + return currentValue!; + }, debugLocation); + + return d; } export function wasEventTriggeredRecently(event: Event, timeoutMs: number, disposableStore: DisposableStore): IObservable { @@ -225,7 +246,8 @@ export function mapObservableArrayCached(owner: DebugOwne m = new ArrayMap(map); } }, (reader) => { - m.setItems(items.read(reader)); + const i = items.read(reader); + m.setItems(i); return m.getItems(); }); return self; @@ -277,3 +299,7 @@ class ArrayMap implements IDisposable { return this._items; } } + +export function isObservable(obj: unknown): obj is IObservable { + return !!obj && (>obj).read !== undefined && (>obj).reportChanges !== undefined; +} diff --git a/code/src/vs/base/common/observableInternal/utils/utilsCancellation.ts b/code/src/vs/base/common/observableInternal/utils/utilsCancellation.ts index 9f0d0783172..40cc45bed73 100644 --- a/code/src/vs/base/common/observableInternal/utils/utilsCancellation.ts +++ b/code/src/vs/base/common/observableInternal/utils/utilsCancellation.ts @@ -75,10 +75,12 @@ export function derivedWithCancellationToken(computeFnOrOwner: ((reader: IRea let computeFn: (reader: IReader, store: CancellationToken) => T; let owner: DebugOwner; if (computeFnOrUndefined === undefined) { + // eslint-disable-next-line local/code-no-any-casts computeFn = computeFnOrOwner as any; owner = undefined; } else { owner = computeFnOrOwner; + // eslint-disable-next-line local/code-no-any-casts computeFn = computeFnOrUndefined as any; } diff --git a/code/src/vs/base/common/paging.ts b/code/src/vs/base/common/paging.ts index 463852a8cd7..74123dd25b5 100644 --- a/code/src/vs/base/common/paging.ts +++ b/code/src/vs/base/common/paging.ts @@ -6,6 +6,7 @@ import { range } from './arrays.js'; import { CancellationToken, CancellationTokenSource } from './cancellation.js'; import { CancellationError } from './errors.js'; +import { Event, Emitter } from './event.js'; /** * A Pager is a stateless abstraction over a paged collection. @@ -17,6 +18,16 @@ export interface IPager { getPage(pageIndex: number, cancellationToken: CancellationToken): Promise; } +export interface IIterativePage { + readonly items: T[]; + readonly hasMore: boolean; +} + +export interface IIterativePager { + readonly firstPage: IIterativePage; + getNextPage(cancellationToken: CancellationToken): Promise>; +} + export interface IPageIterator { elements: T[]; total: number; @@ -46,7 +57,8 @@ function createPage(elements?: T[]): IPage { * A PagedModel is a stateful model over an abstracted paged collection. */ export interface IPagedModel { - length: number; + readonly length: number; + readonly onDidIncrementLength: Event; isResolved(index: number): boolean; get(index: number): T; resolve(index: number, cancellationToken: CancellationToken): Promise; @@ -69,6 +81,7 @@ export class PagedModel implements IPagedModel { private pages: IPage[] = []; get length(): number { return this.pager.total; } + readonly onDidIncrementLength = Event.None; constructor(arg: IPager | T[]) { this.pager = Array.isArray(arg) ? singlePagePager(arg) : arg; @@ -147,8 +160,9 @@ export class PagedModel implements IPagedModel { export class DelayedPagedModel implements IPagedModel { get length(): number { return this.model.length; } + get onDidIncrementLength() { return this.model.onDidIncrementLength; } - constructor(private model: IPagedModel, private timeout: number = 500) { } + constructor(private readonly model: IPagedModel, private timeout: number = 500) { } isResolved(index: number): boolean { return this.model.isResolved(index); @@ -244,9 +258,7 @@ export class PageIteratorPager implements IPager { } return this.cachedPages[pageIndex]; } finally { - if (this.pendingRequests.has(pageIndex)) { - this.pendingRequests.delete(pageIndex); - } + this.pendingRequests.delete(pageIndex); } } @@ -265,6 +277,107 @@ export class PageIteratorPager implements IPager { } } +export class IterativePagedModel implements IPagedModel { + + private items: T[] = []; + private _hasNextPage = true; + private readonly _onDidIncrementLength = new Emitter(); + private loadingPromise: Promise | null = null; + + private readonly pager: IIterativePager; + + constructor(pager: IIterativePager) { + this.pager = pager; + this.items = [...pager.firstPage.items]; + this._hasNextPage = pager.firstPage.hasMore; + } + + get onDidIncrementLength(): Event { + return this._onDidIncrementLength.event; + } + + /** + * Returns actual length + 1 if there are more pages (sentinel approach) + */ + get length(): number { + return this.items.length + (this._hasNextPage ? 1 : 0); + } + + /** + * Sentinel item is never resolved - it triggers loading + */ + isResolved(index: number): boolean { + if (index === this.items.length && this._hasNextPage) { + return false; // This will trigger resolve() call + } + return index < this.items.length; + } + + get(index: number): T { + if (index < this.items.length) { + return this.items[index]; + } + throw new Error('Item not resolved yet'); + } + + /** + * When sentinel item is accessed, load next page + */ + async resolve(index: number, cancellationToken: CancellationToken): Promise { + if (cancellationToken.isCancellationRequested) { + return Promise.reject(new CancellationError()); + } + + // If trying to resolve the sentinel item, load next page + if (index === this.items.length && this._hasNextPage) { + await this.loadNextPage(cancellationToken); + } + + // After loading, the requested index should now be valid + if (index < this.items.length) { + return this.items[index]; + } + + throw new Error('Index out of bounds'); + } + + private async loadNextPage(cancellationToken: CancellationToken): Promise { + if (!this._hasNextPage) { + return; + } + + // If already loading, return the cached promise + if (this.loadingPromise) { + await this.loadingPromise; + return; + } + + const pagePromise = this.pager.getNextPage(cancellationToken); + + this.loadingPromise = pagePromise + .then(page => { + this.items.push(...page.items); + this._hasNextPage = page.hasMore; + + // Clear the loading promise before firing the event + // so that event handlers can trigger loading the next page if needed + this.loadingPromise = null; + + // Fire length update event + this._onDidIncrementLength.fire(this.length); + }, err => { + this.loadingPromise = null; + throw err; + }); + + await this.loadingPromise; + } + + dispose(): void { + this._onDidIncrementLength.dispose(); + } +} + /** * Similar to array.map, `mapPager` lets you map the elements of an * abstract paged collection to another type. diff --git a/code/src/vs/base/common/platform.ts b/code/src/vs/base/common/platform.ts index cf76f7ce0e1..3013e09489b 100644 --- a/code/src/vs/base/common/platform.ts +++ b/code/src/vs/base/common/platform.ts @@ -275,6 +275,6 @@ export const isSafari = !!(!isChrome && (userAgent && userAgent.indexOf('Safari' export const isEdge = !!(userAgent && userAgent.indexOf('Edg/') >= 0); export const isAndroid = !!(userAgent && userAgent.indexOf('Android') >= 0); -export function isBigSurOrNewer(osVersion: string): boolean { - return parseFloat(osVersion) >= 20; +export function isTahoeOrNewer(osVersion: string): boolean { + return parseFloat(osVersion) >= 25; } diff --git a/code/src/vs/base/common/policy.ts b/code/src/vs/base/common/policy.ts index 1e97392d5e2..8141b0f9b5d 100644 --- a/code/src/vs/base/common/policy.ts +++ b/code/src/vs/base/common/policy.ts @@ -3,9 +3,57 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from '../../nls.js'; import { IDefaultAccount } from './defaultAccount.js'; +/** + * System-wide policy file path for Linux systems. + */ +export const LINUX_SYSTEM_POLICY_FILE_PATH = '/etc/vscode/policy.json'; + export type PolicyName = string; +export type LocalizedValue = { + key: string; + value: string; +}; + +export enum PolicyCategory { + Extensions = 'Extensions', + IntegratedTerminal = 'IntegratedTerminal', + InteractiveSession = 'InteractiveSession', + Telemetry = 'Telemetry', + Update = 'Update', +} + +export const PolicyCategoryData: { + [key in PolicyCategory]: { name: LocalizedValue } +} = { + [PolicyCategory.Extensions]: { + name: { + key: 'extensionsConfigurationTitle', value: localize('extensionsConfigurationTitle', "Extensions"), + } + }, + [PolicyCategory.IntegratedTerminal]: { + name: { + key: 'terminalIntegratedConfigurationTitle', value: localize('terminalIntegratedConfigurationTitle', "Integrated Terminal"), + } + }, + [PolicyCategory.InteractiveSession]: { + name: { + key: 'interactiveSessionConfigurationTitle', value: localize('interactiveSessionConfigurationTitle', "Chat"), + } + }, + [PolicyCategory.Telemetry]: { + name: { + key: 'telemetryConfigurationTitle', value: localize('telemetryConfigurationTitle', "Telemetry"), + } + }, + [PolicyCategory.Update]: { + name: { + key: 'updateConfigurationTitle', value: localize('updateConfigurationTitle', "Update"), + } + } +}; export interface IPolicy { @@ -14,15 +62,27 @@ export interface IPolicy { */ readonly name: PolicyName; + /** + * The policy category. + */ + readonly category: PolicyCategory; + /** * The Code version in which this policy was introduced. */ readonly minimumVersion: `${number}.${number}`; /** - * The policy description (optional). + * Localization info for the policy. + * + * IMPORTANT: the key values for these must be unique to avoid collisions, as during the export time the module information is not available. */ - readonly description?: string; + readonly localization: { + /** The localization key or key value pair. If only a key is provided, the default value will fallback to the parent configuration's description property. */ + description: LocalizedValue; + /** List of localization key or key value pair. If only a key is provided, the default value will fallback to the parent configuration's enumDescriptions property. */ + enumDescriptions?: LocalizedValue[]; + }; /** * The value that an ACCOUNT-based feature will use when its corresponding policy is active. diff --git a/code/src/vs/base/common/process.ts b/code/src/vs/base/common/process.ts index af67fda5669..b5ac53c0c07 100644 --- a/code/src/vs/base/common/process.ts +++ b/code/src/vs/base/common/process.ts @@ -9,7 +9,7 @@ let safeProcess: Omit & { arch: string | undefined }; declare const process: INodeProcess; // Native sandbox environment -const vscodeGlobal = (globalThis as any).vscode; +const vscodeGlobal = (globalThis as { vscode?: { process?: INodeProcess } }).vscode; if (typeof vscodeGlobal !== 'undefined' && typeof vscodeGlobal.process !== 'undefined') { const sandboxProcess: INodeProcess = vscodeGlobal.process; safeProcess = { diff --git a/code/src/vs/base/common/product.ts b/code/src/vs/base/common/product.ts index f97b4110ec5..c6f54817d60 100644 --- a/code/src/vs/base/common/product.ts +++ b/code/src/vs/base/common/product.ts @@ -43,6 +43,7 @@ export interface IChatSessionRecommendation { readonly displayName: string; readonly name: string; readonly description: string; + readonly postInstallCommand?: string; } export type ConfigurationSyncStore = { @@ -107,7 +108,6 @@ export interface IProductConfiguration { readonly extensionsGallery?: { readonly serviceUrl: string; readonly controlUrl: string; - readonly mcpUrl: string; readonly extensionUrlTemplate: string; readonly resourceUrlTemplate: string; readonly nlsBaseUrl: string; @@ -116,6 +116,12 @@ export interface IProductConfiguration { readonly mcpGallery?: { readonly serviceUrl: string; + readonly itemWebUrl: string; + readonly publisherUrl: string; + readonly supportUrl: string; + readonly privacyPolicyUrl: string; + readonly termsOfServiceUrl: string; + readonly reportUrl: string; }; readonly extensionPublisherOrgs?: readonly string[]; @@ -209,12 +215,14 @@ export interface IProductConfiguration { readonly id: string; readonly enterpriseProviderId: string; readonly enterpriseProviderConfig: string; - readonly scopes: string[]; + readonly enterpriseProviderUriSetting: string; + readonly scopes: string[][]; }; readonly tokenEntitlementUrl: string; readonly chatEntitlementUrl: string; readonly mcpRegistryDataUrl: string; }; + readonly authClientIdMetadataUrl?: string; readonly 'configurationSync.store'?: ConfigurationSyncStore; @@ -226,7 +234,7 @@ export interface IProductConfiguration { readonly commonlyUsedSettings?: string[]; readonly aiGeneratedWorkspaceTrust?: IAiGeneratedWorkspaceTrust; - readonly defaultChatAgent?: IDefaultChatAgent; + readonly defaultChatAgent: IDefaultChatAgent; readonly chatParticipantRegistry?: string; readonly chatSessionRecommendations?: IChatSessionRecommendation[]; readonly emergencyAlertUrl?: string; @@ -375,6 +383,7 @@ export interface IDefaultChatAgent { readonly completionsRefreshTokenCommand: string; readonly chatRefreshTokenCommand: string; readonly generateCommitMessageCommand: string; + readonly resolveMergeConflictsCommand: string; readonly completionsAdvancedSetting: string; readonly completionsEnablementSetting: string; diff --git a/code/src/vs/base/common/resourceTree.ts b/code/src/vs/base/common/resourceTree.ts index c1a1c951bb2..5328c30b448 100644 --- a/code/src/vs/base/common/resourceTree.ts +++ b/code/src/vs/base/common/resourceTree.ts @@ -75,7 +75,7 @@ function collect(node: IResourceNode, result: T[]): T[] { return result; } -export class ResourceTree, C> { +export class ResourceTree, C> { readonly root: Node; diff --git a/code/src/vs/base/common/skipList.ts b/code/src/vs/base/common/skipList.ts deleted file mode 100644 index ed3fd7e5a0a..00000000000 --- a/code/src/vs/base/common/skipList.ts +++ /dev/null @@ -1,204 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - - -class Node { - readonly forward: Node[]; - constructor(readonly level: number, readonly key: K, public value: V) { - this.forward = []; - } -} - -const NIL: undefined = undefined; - -interface Comparator { - (a: K, b: K): number; -} - -export class SkipList implements Map { - - readonly [Symbol.toStringTag] = 'SkipList'; - - private _maxLevel: number; - private _level: number = 0; - private _header: Node; - private _size: number = 0; - - /** - * - * @param capacity Capacity at which the list performs best - */ - constructor( - readonly comparator: (a: K, b: K) => number, - capacity: number = 2 ** 16 - ) { - this._maxLevel = Math.max(1, Math.log2(capacity) | 0); - this._header = new Node(this._maxLevel, NIL, NIL); - } - - get size(): number { - return this._size; - } - - clear(): void { - this._header = new Node(this._maxLevel, NIL, NIL); - this._size = 0; - } - - has(key: K): boolean { - return Boolean(SkipList._search(this, key, this.comparator)); - } - - get(key: K): V | undefined { - return SkipList._search(this, key, this.comparator)?.value; - } - - set(key: K, value: V): this { - if (SkipList._insert(this, key, value, this.comparator)) { - this._size += 1; - } - return this; - } - - delete(key: K): boolean { - const didDelete = SkipList._delete(this, key, this.comparator); - if (didDelete) { - this._size -= 1; - } - return didDelete; - } - - // --- iteration - - forEach(callbackfn: (value: V, key: K, map: Map) => void, thisArg?: any): void { - let node = this._header.forward[0]; - while (node) { - callbackfn.call(thisArg, node.value, node.key, this); - node = node.forward[0]; - } - } - - [Symbol.iterator](): IterableIterator<[K, V]> { - return this.entries(); - } - - *entries(): IterableIterator<[K, V]> { - let node = this._header.forward[0]; - while (node) { - yield [node.key, node.value]; - node = node.forward[0]; - } - } - - *keys(): IterableIterator { - let node = this._header.forward[0]; - while (node) { - yield node.key; - node = node.forward[0]; - } - } - - *values(): IterableIterator { - let node = this._header.forward[0]; - while (node) { - yield node.value; - node = node.forward[0]; - } - } - - toString(): string { - // debug string... - let result = '[SkipList]:'; - let node = this._header.forward[0]; - while (node) { - result += `node(${node.key}, ${node.value}, lvl:${node.level})`; - node = node.forward[0]; - } - return result; - } - - // from https://www.epaperpress.com/sortsearch/download/skiplist.pdf - - private static _search(list: SkipList, searchKey: K, comparator: Comparator) { - let x = list._header; - for (let i = list._level - 1; i >= 0; i--) { - while (x.forward[i] && comparator(x.forward[i].key, searchKey) < 0) { - x = x.forward[i]; - } - } - x = x.forward[0]; - if (x && comparator(x.key, searchKey) === 0) { - return x; - } - return undefined; - } - - private static _insert(list: SkipList, searchKey: K, value: V, comparator: Comparator) { - const update: Node[] = []; - let x = list._header; - for (let i = list._level - 1; i >= 0; i--) { - while (x.forward[i] && comparator(x.forward[i].key, searchKey) < 0) { - x = x.forward[i]; - } - update[i] = x; - } - x = x.forward[0]; - if (x && comparator(x.key, searchKey) === 0) { - // update - x.value = value; - return false; - } else { - // insert - const lvl = SkipList._randomLevel(list); - if (lvl > list._level) { - for (let i = list._level; i < lvl; i++) { - update[i] = list._header; - } - list._level = lvl; - } - x = new Node(lvl, searchKey, value); - for (let i = 0; i < lvl; i++) { - x.forward[i] = update[i].forward[i]; - update[i].forward[i] = x; - } - return true; - } - } - - private static _randomLevel(list: SkipList, p: number = 0.5): number { - let lvl = 1; - while (Math.random() < p && lvl < list._maxLevel) { - lvl += 1; - } - return lvl; - } - - private static _delete(list: SkipList, searchKey: K, comparator: Comparator) { - const update: Node[] = []; - let x = list._header; - for (let i = list._level - 1; i >= 0; i--) { - while (x.forward[i] && comparator(x.forward[i].key, searchKey) < 0) { - x = x.forward[i]; - } - update[i] = x; - } - x = x.forward[0]; - if (!x || comparator(x.key, searchKey) !== 0) { - // not found - return false; - } - for (let i = 0; i < list._level; i++) { - if (update[i].forward[i] !== x) { - break; - } - update[i].forward[i] = x.forward[i]; - } - while (list._level > 0 && list._header.forward[list._level - 1] === NIL) { - list._level -= 1; - } - return true; - } - -} diff --git a/code/src/vs/base/common/stream.ts b/code/src/vs/base/common/stream.ts index 990c8f1c6bc..ebc0ef5d23b 100644 --- a/code/src/vs/base/common/stream.ts +++ b/code/src/vs/base/common/stream.ts @@ -331,14 +331,14 @@ class WriteableStreamImpl implements WriteableStream { on(event: 'data', callback: (data: T) => void): void; on(event: 'error', callback: (err: Error) => void): void; on(event: 'end', callback: () => void): void; - on(event: 'data' | 'error' | 'end', callback: (arg0?: any) => void): void { + on(event: 'data' | 'error' | 'end', callback: ((data: T) => void) | ((err: Error) => void) | (() => void)): void { if (this.state.destroyed) { return; } switch (event) { case 'data': - this.listeners.data.push(callback); + this.listeners.data.push(callback as (data: T) => void); // switch into flowing mode as soon as the first 'data' // listener is added and we are not yet in flowing mode @@ -347,7 +347,7 @@ class WriteableStreamImpl implements WriteableStream { break; case 'end': - this.listeners.end.push(callback); + this.listeners.end.push(callback as () => void); // emit 'end' event directly if we are flowing // and the end has already been reached @@ -360,7 +360,7 @@ class WriteableStreamImpl implements WriteableStream { break; case 'error': - this.listeners.error.push(callback); + this.listeners.error.push(callback as (err: Error) => void); // emit buffered 'error' events unless done already // now that we know that we have at least one listener diff --git a/code/src/vs/base/common/strings.ts b/code/src/vs/base/common/strings.ts index a9957139b7b..e31c45120fb 100644 --- a/code/src/vs/base/common/strings.ts +++ b/code/src/vs/base/common/strings.ts @@ -23,6 +23,7 @@ const _formatRegexp = /{(\d+)}/g; * @param value string to which formatting is applied * @param args replacements for {n}-entries */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function format(value: string, ...args: any[]): string { if (args.length === 0) { return value; @@ -142,14 +143,16 @@ export function ltrim(haystack: string, needle: string): string { } const needleLen = needle.length; - if (needleLen === 0 || haystack.length === 0) { - return haystack; - } - let offset = 0; - - while (haystack.indexOf(needle, offset) === offset) { - offset = offset + needleLen; + if (needleLen === 1) { + const ch = needle.charCodeAt(0); + while (offset < haystack.length && haystack.charCodeAt(offset) === ch) { + offset++; + } + } else { + while (haystack.startsWith(needle, offset)) { + offset += needleLen; + } } return haystack.substring(offset); } @@ -167,22 +170,18 @@ export function rtrim(haystack: string, needle: string): string { const needleLen = needle.length, haystackLen = haystack.length; - if (needleLen === 0 || haystackLen === 0) { - return haystack; + if (needleLen === 1) { + let end = haystackLen; + const ch = needle.charCodeAt(0); + while (end > 0 && haystack.charCodeAt(end - 1) === ch) { + end--; + } + return haystack.substring(0, end); } - let offset = haystackLen, - idx = -1; - - while (true) { - idx = haystack.lastIndexOf(needle, offset - 1); - if (idx === -1 || idx + needleLen !== offset) { - break; - } - if (idx === 0) { - return ''; - } - offset = idx; + let offset = haystackLen; + while (offset > 0 && haystack.endsWith(needle, offset)) { + offset -= needleLen; } return haystack.substring(0, offset); @@ -192,10 +191,6 @@ export function convertSimple2RegExpPattern(pattern: string): string { return pattern.replace(/[\-\\\{\}\+\?\|\^\$\.\,\[\]\(\)\#\s]/g, '\\$&').replace(/[\*]/g, '.*'); } -export function stripWildcards(pattern: string): string { - return pattern.replace(/\*/g, ''); -} - export interface RegExpOptions { matchCase?: boolean; wholeWord?: boolean; @@ -326,7 +321,7 @@ export function getIndentationLength(str: string): number { * Function that works identically to String.prototype.replace, except, the * replace function is allowed to be async and return a Promise. */ -export function replaceAsync(str: string, search: RegExp, replacer: (match: string, ...args: any[]) => Promise): Promise { +export function replaceAsync(str: string, search: RegExp, replacer: (match: string, ...args: unknown[]) => Promise): Promise { const parts: (string | Promise)[] = []; let last = 0; @@ -442,13 +437,19 @@ export function equalsIgnoreCase(a: string, b: string): boolean { return a.length === b.length && compareSubstringIgnoreCase(a, b) === 0; } +export function equals(a: string | undefined, b: string | undefined, ignoreCase?: boolean): boolean { + return a === b || (!!ignoreCase && a !== undefined && b !== undefined && equalsIgnoreCase(a, b)); +} + export function startsWithIgnoreCase(str: string, candidate: string): boolean { - const candidateLength = candidate.length; - if (candidate.length > str.length) { - return false; - } + const len = candidate.length; + return len <= str.length && compareSubstringIgnoreCase(str, candidate, 0, len) === 0; +} - return compareSubstringIgnoreCase(str, candidate, 0, candidateLength) === 0; +export function endsWithIgnoreCase(str: string, candidate: string): boolean { + const len = str.length; + const start = len - candidate.length; + return start >= 0 && compareSubstringIgnoreCase(str, candidate, start, len) === 0; } /** @@ -729,12 +730,14 @@ export function isFullWidthCharacter(charCode: number): boolean { // FF00 - FFEF Halfwidth and Fullwidth Forms // [https://en.wikipedia.org/wiki/Halfwidth_and_fullwidth_forms] // of which FF01 - FF5E fullwidth ASCII of 21 to 7E + // and FFE0 - FFE6 fullwidth symbol variants // [IGNORE] and FF65 - FFDC halfwidth of Katakana and Hangul // [IGNORE] FFF0 - FFFF Specials return ( (charCode >= 0x2E80 && charCode <= 0xD7AF) || (charCode >= 0xF900 && charCode <= 0xFAFF) || (charCode >= 0xFF01 && charCode <= 0xFF5E) + || (charCode >= 0xFFE0 && charCode <= 0xFFE6) ); } @@ -1194,10 +1197,9 @@ export class AmbiguousCharacters { ); }); - private static readonly cache = new LRUCachedFunction< - string[], - AmbiguousCharacters - >({ getCacheKey: JSON.stringify }, (locales) => { + private static readonly cache = new LRUCachedFunction((localesStr) => { + const locales = localesStr.split(','); + function arrayToMap(arr: number[]): Map { const result = new Map(); for (let i = 0; i < arr.length; i += 2) { @@ -1236,7 +1238,7 @@ export class AmbiguousCharacters { const data = this.ambiguousCharacterData.value; let filteredLocales = locales.filter( - (l) => !l.startsWith('_') && l in data + (l) => !l.startsWith('_') && Object.hasOwn(data, l) ); if (filteredLocales.length === 0) { filteredLocales = ['_default']; @@ -1254,8 +1256,8 @@ export class AmbiguousCharacters { return new AmbiguousCharacters(map); }); - public static getInstance(locales: Set): AmbiguousCharacters { - return AmbiguousCharacters.cache.get(Array.from(locales)); + public static getInstance(locales: Iterable): AmbiguousCharacters { + return AmbiguousCharacters.cache.get(Array.from(locales).join(',')); } private static _locales = new Lazy(() => diff --git a/code/src/vs/base/common/ternarySearchTree.ts b/code/src/vs/base/common/ternarySearchTree.ts index fef1f8e1cf0..d184ed1765e 100644 --- a/code/src/vs/base/common/ternarySearchTree.ts +++ b/code/src/vs/base/common/ternarySearchTree.ts @@ -258,7 +258,7 @@ abstract class Undef { } static unwrap(value: V | typeof Undef.Val): V | undefined { - return value === Undef.Val ? undefined : value as V; + return value === Undef.Val ? undefined : value; } } @@ -781,7 +781,7 @@ export class TernarySearchTree { // for debug/testing _isBalanced(): boolean { - const nodeIsBalanced = (node: TernarySearchTreeNode | undefined): boolean => { + const nodeIsBalanced = (node: TernarySearchTreeNode | undefined): boolean => { if (!node) { return true; } diff --git a/code/src/vs/base/common/themables.ts b/code/src/vs/base/common/themables.ts index 6b2551bebb2..dcc1369e593 100644 --- a/code/src/vs/base/common/themables.ts +++ b/code/src/vs/base/common/themables.ts @@ -101,4 +101,17 @@ export namespace ThemeIcon { return ti1.id === ti2.id && ti1.color?.id === ti2.color?.id; } + /** + * Returns whether specified icon is defined and has 'file' ID. + */ + export function isFile(icon: ThemeIcon | undefined): boolean { + return icon?.id === Codicon.file.id; + } + + /** + * Returns whether specified icon is defined and has 'folder' ID. + */ + export function isFolder(icon: ThemeIcon | undefined): boolean { + return icon?.id === Codicon.folder.id; + } } diff --git a/code/src/vs/base/common/types.ts b/code/src/vs/base/common/types.ts index b08d15edc6c..154d8199690 100644 --- a/code/src/vs/base/common/types.ts +++ b/code/src/vs/base/common/types.ts @@ -16,7 +16,14 @@ export function isString(str: unknown): str is string { * @returns whether the provided parameter is a JavaScript Array and each element in the array is a string. */ export function isStringArray(value: unknown): value is string[] { - return Array.isArray(value) && (value).every(elem => isString(elem)); + return isArrayOf(value, isString); +} + +/** + * @returns whether the provided parameter is a JavaScript Array and each element in the array satisfies the provided type guard. + */ +export function isArrayOf(value: unknown, check: (item: unknown) => item is T): value is T[] { + return Array.isArray(value) && value.every(check); } /** @@ -55,6 +62,7 @@ export function isNumber(obj: unknown): obj is number { * @returns whether the provided parameter is an Iterable, casting to the given generic */ export function isIterable(obj: unknown): obj is Iterable { + // eslint-disable-next-line local/code-no-any-casts return !!obj && typeof (obj as any)[Symbol.iterator] === 'function'; } @@ -62,6 +70,7 @@ export function isIterable(obj: unknown): obj is Iterable { * @returns whether the provided parameter is an Iterable, casting to the given generic */ export function isAsyncIterable(obj: unknown): obj is AsyncIterable { + // eslint-disable-next-line local/code-no-any-casts return !!obj && typeof (obj as any)[Symbol.asyncIterator] === 'function'; } @@ -263,6 +272,7 @@ export function validateConstraint(arg: unknown, constraint: TypeConstraint | un } catch { // ignore } + // eslint-disable-next-line local/code-no-any-casts if (!isUndefinedOrNull(arg) && (arg as any).constructor === constraint) { return; } @@ -316,11 +326,35 @@ export type Mutable = { -readonly [P in keyof T]: T[P] }; +/** + * A type that adds readonly to all properties of T, recursively. + */ +export type DeepImmutable = T extends (infer U)[] + ? ReadonlyArray> + : T extends ReadonlyArray + ? ReadonlyArray> + : T extends Map + ? ReadonlyMap> + : T extends Set + ? ReadonlySet> + : T extends object + ? { + readonly [K in keyof T]: DeepImmutable; + } + : T; + /** * A single object or an array of the objects. */ export type SingleOrMany = T | T[]; +/** + * Given a `type X = { foo?: string }` checking that an object `satisfies X` + * will ensure each property was explicitly defined, ensuring no properties + * are omitted or forgotten. + */ +export type WithDefinedProps = { [K in keyof Required]: T[K] }; + /** * A type that recursively makes all properties of `T` required @@ -341,3 +375,36 @@ export type DeepPartial = { * Represents a type that is a partial version of a given type `T`, except a subset. */ export type PartialExcept = Partial> & Pick; + + +type KeysOfUnionType = T extends T ? keyof T : never; +type FilterType = T extends TTest ? T : never; +type MakeOptionalAndTrue = { [K in keyof T]?: true }; + +/** + * 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): x is FilterType & keyof TKeys]: unknown }> { + for (const k in key) { + if (!(k in x)) { + return false; + } + } + return true; +} diff --git a/code/src/vs/base/common/uriIpc.ts b/code/src/vs/base/common/uriIpc.ts index 8a8c18055a8..67bf4c3428c 100644 --- a/code/src/vs/base/common/uriIpc.ts +++ b/code/src/vs/base/common/uriIpc.ts @@ -30,7 +30,7 @@ export interface IRawURITransformer { } function toJSON(uri: URI): UriComponents { - return uri.toJSON(); + return uri.toJSON(); } export class URITransformer implements IURITransformer { diff --git a/code/src/vs/workbench/api/node/uriTransformer.ts b/code/src/vs/base/common/uriTransformer.ts similarity index 97% rename from code/src/vs/workbench/api/node/uriTransformer.ts rename to code/src/vs/base/common/uriTransformer.ts index 00bf30130df..40451e04db8 100644 --- a/code/src/vs/workbench/api/node/uriTransformer.ts +++ b/code/src/vs/base/common/uriTransformer.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { UriParts, IRawURITransformer, URITransformer, IURITransformer } from '../../../base/common/uriIpc.js'; +import { UriParts, IRawURITransformer, URITransformer, IURITransformer } from './uriIpc.js'; /** * ``` diff --git a/code/src/vs/base/common/validation.ts b/code/src/vs/base/common/validation.ts new file mode 100644 index 00000000000..e94e451a00b --- /dev/null +++ b/code/src/vs/base/common/validation.ts @@ -0,0 +1,396 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { mapFilter } from './arrays.js'; +import { IJSONSchema } from './jsonSchema.js'; + +export interface IValidator { + validate(content: unknown): { content: T; error: undefined } | { content: undefined; error: ValidationError }; + + getJSONSchema(): IJSONSchema; +} + +export abstract class ValidatorBase implements IValidator { + abstract validate(content: unknown): { content: T; error: undefined } | { content: undefined; error: ValidationError }; + + abstract getJSONSchema(): IJSONSchema; + + validateOrThrow(content: unknown): T { + const result = this.validate(content); + if (result.error) { + throw new Error(result.error.message); + } + return result.content; + } +} + +export type ValidatorType = T extends IValidator ? U : never; + +export interface ValidationError { + message: string; +} + +type TypeOfMap = { + string: string; + number: number; + boolean: boolean; + object: object; + null: null; +}; + +class TypeofValidator extends ValidatorBase { + constructor(private readonly type: TKey) { + super(); + } + + validate(content: unknown): { content: TypeOfMap[TKey]; error: undefined } | { content: undefined; error: ValidationError } { + if (typeof content !== this.type) { + return { content: undefined, error: { message: `Expected ${this.type}, but got ${typeof content}` } }; + } + + return { content: content as TypeOfMap[TKey], error: undefined }; + } + + getJSONSchema(): IJSONSchema { + return { type: this.type }; + } +} + +const vStringValidator = new TypeofValidator('string'); +export function vString(): ValidatorBase { return vStringValidator; } + +const vNumberValidator = new TypeofValidator('number'); +export function vNumber(): ValidatorBase { return vNumberValidator; } + +const vBooleanValidator = new TypeofValidator('boolean'); +export function vBoolean(): ValidatorBase { return vBooleanValidator; } + +const vObjAnyValidator = new TypeofValidator('object'); +export function vObjAny(): ValidatorBase { return vObjAnyValidator; } + + +class UncheckedValidator extends ValidatorBase { + validate(content: unknown): { content: T; error: undefined } { + return { content: content as T, error: undefined }; + } + + getJSONSchema(): IJSONSchema { + return {}; + } +} + +export function vUnchecked(): ValidatorBase { + return new UncheckedValidator(); +} + +class UndefinedValidator extends ValidatorBase { + validate(content: unknown): { content: undefined; error: undefined } | { content: undefined; error: ValidationError } { + if (content !== undefined) { + return { content: undefined, error: { message: `Expected undefined, but got ${typeof content}` } }; + } + + return { content: undefined, error: undefined }; + } + + getJSONSchema(): IJSONSchema { + return {}; + } +} + +export function vUndefined(): ValidatorBase { + return new UndefinedValidator(); +} + +export function vUnknown(): ValidatorBase { + return vUnchecked(); +} + +export type ObjectProperties = Record; + +export class Optional> { + constructor(public readonly validator: T) { } +} + +export function vOptionalProp(validator: IValidator): Optional> { + return new Optional(validator); +} + +type ExtractOptionalKeys = { + [K in keyof T]: T[K] extends Optional> ? K : never; +}[keyof T]; + +type ExtractRequiredKeys = { + [K in keyof T]: T[K] extends Optional> ? never : K; +}[keyof T]; + +export type vObjType | Optional>>> = { + [K in ExtractRequiredKeys]: T[K] extends IValidator ? U : never; +} & { + [K in ExtractOptionalKeys]?: T[K] extends Optional> ? U : never; +}; + +class ObjValidator | Optional>>> extends ValidatorBase> { + constructor(private readonly properties: T) { + super(); + } + + validate(content: unknown): { content: vObjType; error: undefined } | { content: undefined; error: ValidationError } { + if (typeof content !== 'object' || content === null) { + return { content: undefined, error: { message: 'Expected object' } }; + } + + // eslint-disable-next-line local/code-no-dangerous-type-assertions + const result: vObjType = {} as vObjType; + + for (const key in this.properties) { + const prop = this.properties[key]; + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + const fieldValue = (content as any)[key]; + + const isOptional = prop instanceof Optional; + const validator: IValidator = isOptional ? prop.validator : prop; + + if (isOptional && fieldValue === undefined) { + // Optional field not provided, skip validation + continue; + } + + const { content: value, error } = validator.validate(fieldValue); + if (error) { + return { content: undefined, error: { message: `Error in property '${key}': ${error.message}` } }; + } + + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + (result as any)[key] = value; + } + + return { content: result, error: undefined }; + } + + getJSONSchema(): IJSONSchema { + const requiredFields: string[] = []; + const schemaProperties: Record = {}; + + for (const [key, prop] of Object.entries(this.properties)) { + const isOptional = prop instanceof Optional; + const validator: IValidator = isOptional ? prop.validator : prop; + schemaProperties[key] = validator.getJSONSchema(); + if (!isOptional) { + requiredFields.push(key); + } + } + + const schema: IJSONSchema = { + type: 'object', + properties: schemaProperties, + ...(requiredFields.length > 0 ? { required: requiredFields } : {}) + }; + + return schema; + } +} + +export function vObj | Optional>>>(properties: T): ValidatorBase> { + return new ObjValidator(properties); +} + +class ArrayValidator extends ValidatorBase { + constructor(private readonly validator: IValidator) { + super(); + } + + validate(content: unknown): { content: T[]; error: undefined } | { content: undefined; error: ValidationError } { + if (!Array.isArray(content)) { + return { content: undefined, error: { message: 'Expected array' } }; + } + + const result: T[] = []; + for (let i = 0; i < content.length; i++) { + const { content: value, error } = this.validator.validate(content[i]); + if (error) { + return { content: undefined, error: { message: `Error in element ${i}: ${error.message}` } }; + } + + result.push(value); + } + + return { content: result, error: undefined }; + } + + getJSONSchema(): IJSONSchema { + return { + type: 'array', + items: this.validator.getJSONSchema(), + }; + } +} + +export function vArray(validator: IValidator): ValidatorBase { + return new ArrayValidator(validator); +} + +type vTupleType[]> = { [K in keyof T]: ValidatorType }; + +class TupleValidator[]> extends ValidatorBase> { + constructor(private readonly validators: T) { + super(); + } + + validate(content: unknown): { content: vTupleType; error: undefined } | { content: undefined; error: ValidationError } { + if (!Array.isArray(content)) { + return { content: undefined, error: { message: 'Expected array' } }; + } + + if (content.length !== this.validators.length) { + return { content: undefined, error: { message: `Expected tuple of length ${this.validators.length}, but got ${content.length}` } }; + } + + const result = [] as vTupleType; + for (let i = 0; i < this.validators.length; i++) { + const validator = this.validators[i]; + const { content: value, error } = validator.validate(content[i]); + if (error) { + return { content: undefined, error: { message: `Error in element ${i}: ${error.message}` } }; + } + result.push(value); + } + + return { content: result, error: undefined }; + } + + getJSONSchema(): IJSONSchema { + return { + type: 'array', + items: this.validators.map(validator => validator.getJSONSchema()), + }; + } +} + +export function vTuple[]>(...validators: T): ValidatorBase> { + return new TupleValidator(validators); +} + +class UnionValidator[]> extends ValidatorBase> { + constructor(private readonly validators: T) { + super(); + } + + validate(content: unknown): { content: ValidatorType; error: undefined } | { content: undefined; error: ValidationError } { + let lastError: ValidationError | undefined; + for (const validator of this.validators) { + const { content: value, error } = validator.validate(content); + if (!error) { + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + return { content: value as any, error: undefined }; + } + + lastError = error; + } + + return { content: undefined, error: lastError! }; + } + + getJSONSchema(): IJSONSchema { + return { + oneOf: mapFilter(this.validators, validator => { + if (validator instanceof UndefinedValidator) { + return undefined; + } + return validator.getJSONSchema(); + }), + }; + } +} + +export function vUnion[]>(...validators: T): ValidatorBase> { + return new UnionValidator(validators); +} + +class EnumValidator extends ValidatorBase { + constructor(private readonly values: T) { + super(); + } + + validate(content: unknown): { content: T[number]; error: undefined } | { content: undefined; error: ValidationError } { + if (this.values.indexOf(content as string) === -1) { + return { content: undefined, error: { message: `Expected one of: ${this.values.join(', ')}` } }; + } + + return { content: content as T[number], error: undefined }; + } + + getJSONSchema(): IJSONSchema { + return { + enum: this.values, + }; + } +} + +export function vEnum(...values: T): ValidatorBase { + return new EnumValidator(values); +} + +class LiteralValidator extends ValidatorBase { + constructor(private readonly value: T) { + super(); + } + + validate(content: unknown): { content: T; error: undefined } | { content: undefined; error: ValidationError } { + if (content !== this.value) { + return { content: undefined, error: { message: `Expected: ${this.value}` } }; + } + + return { content: content as T, error: undefined }; + } + + getJSONSchema(): IJSONSchema { + return { + const: this.value, + }; + } +} + +export function vLiteral(value: T): ValidatorBase { + return new LiteralValidator(value); +} + +class LazyValidator extends ValidatorBase { + constructor(private readonly fn: () => IValidator) { + super(); + } + + validate(content: unknown): { content: T; error: undefined } | { content: undefined; error: ValidationError } { + return this.fn().validate(content); + } + + getJSONSchema(): IJSONSchema { + return this.fn().getJSONSchema(); + } +} + +export function vLazy(fn: () => IValidator): ValidatorBase { + return new LazyValidator(fn); +} + +class UseRefSchemaValidator extends ValidatorBase { + constructor( + private readonly _ref: string, + private readonly _validator: IValidator + ) { + super(); + } + + validate(content: unknown): { content: T; error: undefined } | { content: undefined; error: ValidationError } { + return this._validator.validate(content); + } + + getJSONSchema(): IJSONSchema { + return { $ref: this._ref }; + } +} + +export function vWithJsonSchemaRef(ref: string, validator: IValidator): ValidatorBase { + return new UseRefSchemaValidator(ref, validator); +} diff --git a/code/src/vs/base/common/verifier.ts b/code/src/vs/base/common/verifier.ts index a2576a3c1ca..fda24540ebc 100644 --- a/code/src/vs/base/common/verifier.ts +++ b/code/src/vs/base/common/verifier.ts @@ -79,6 +79,7 @@ export function verifyObject(verifiers: { [K in keyof T]: IVer for (const key in verifiers) { if (Object.hasOwnProperty.call(verifiers, key)) { const verifier = verifiers[key]; + // eslint-disable-next-line local/code-no-any-casts result[key] = verifier.verify((value as any)[key]); } } diff --git a/code/src/vs/base/common/worker/webWorker.ts b/code/src/vs/base/common/worker/webWorker.ts index 999f490c590..0ad11b838aa 100644 --- a/code/src/vs/base/common/worker/webWorker.ts +++ b/code/src/vs/base/common/worker/webWorker.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { CharCode } from '../charCode.js'; -import { onUnexpectedError, transformErrorForSerialization } from '../errors.js'; +import { onUnexpectedError, SerializedError, transformErrorForSerialization } from '../errors.js'; import { Emitter, Event } from '../event.js'; import { Disposable, IDisposable } from '../lifecycle.js'; import { isWeb } from '../platform.js'; @@ -15,13 +15,13 @@ const INITIALIZE = '$initialize'; export interface IWebWorker extends IDisposable { getId(): number; - onMessage: Event; - onError: Event; + readonly onMessage: Event; + readonly onError: Event; postMessage(message: Message, transfer: ArrayBuffer[]): void; } let webWorkerWarningLogged = false; -export function logOnceWebWorkerWarning(err: any): void { +export function logOnceWebWorkerWarning(err: unknown): void { if (!isWeb) { // running tests return; @@ -30,7 +30,7 @@ export function logOnceWebWorkerWarning(err: any): void { webWorkerWarningLogged = true; console.warn('Could not create web worker(s). Falling back to loading web worker code in main thread, which might cause UI freezes. Please see https://github.com/microsoft/monaco-editor#faq'); } - console.warn(err.message); + console.warn((err as Error).message); } const enum MessageType { @@ -47,7 +47,7 @@ class RequestMessage { public readonly req: string, public readonly channel: string, public readonly method: string, - public readonly args: any[] + public readonly args: unknown[] ) { } } class ReplyMessage { @@ -55,8 +55,8 @@ class ReplyMessage { constructor( public readonly vsWorker: number, public readonly seq: string, - public readonly res: any, - public readonly err: any + public readonly res: unknown, + public readonly err: unknown | SerializedError ) { } } class SubscribeEventMessage { @@ -66,7 +66,7 @@ class SubscribeEventMessage { public readonly req: string, public readonly channel: string, public readonly eventName: string, - public readonly arg: any + public readonly arg: unknown ) { } } class EventMessage { @@ -74,7 +74,7 @@ class EventMessage { constructor( public readonly vsWorker: number, public readonly req: string, - public readonly event: any + public readonly event: unknown ) { } } class UnsubscribeEventMessage { @@ -87,14 +87,14 @@ class UnsubscribeEventMessage { export type Message = RequestMessage | ReplyMessage | SubscribeEventMessage | EventMessage | UnsubscribeEventMessage; interface IMessageReply { - resolve: (value?: any) => void; - reject: (error?: any) => void; + resolve: (value?: unknown) => void; + reject: (error?: unknown) => void; } interface IMessageHandler { - sendMessage(msg: any, transfer?: ArrayBuffer[]): void; - handleMessage(channel: string, method: string, args: any[]): Promise; - handleEvent(channel: string, eventName: string, arg: any): Event; + sendMessage(msg: unknown, transfer?: ArrayBuffer[]): void; + handleMessage(channel: string, method: string, args: unknown[]): Promise; + handleEvent(channel: string, eventName: string, arg: unknown): Event; } class WebWorkerProtocol { @@ -102,7 +102,7 @@ class WebWorkerProtocol { private _workerId: number; private _lastSentReq: number; private _pendingReplies: { [req: string]: IMessageReply }; - private _pendingEmitters: Map>; + private _pendingEmitters: Map>; private _pendingEvents: Map; private _handler: IMessageHandler; @@ -111,7 +111,7 @@ class WebWorkerProtocol { this._handler = handler; this._lastSentReq = 0; this._pendingReplies = Object.create(null); - this._pendingEmitters = new Map>(); + this._pendingEmitters = new Map>(); this._pendingEvents = new Map(); } @@ -119,9 +119,9 @@ class WebWorkerProtocol { this._workerId = workerId; } - public sendMessage(channel: string, method: string, args: any[]): Promise { + public async sendMessage(channel: string, method: string, args: unknown[]): Promise { const req = String(++this._lastSentReq); - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { this._pendingReplies[req] = { resolve: resolve, reject: reject @@ -130,9 +130,9 @@ class WebWorkerProtocol { }); } - public listen(channel: string, eventName: string, arg: any): Event { + public listen(channel: string, eventName: string, arg: unknown): Event { let req: string | null = null; - const emitter = new Emitter({ + const emitter = new Emitter({ onWillAddFirstListener: () => { req = String(++this._lastSentReq); this._pendingEmitters.set(req, emitter); @@ -147,28 +147,28 @@ class WebWorkerProtocol { return emitter.event; } - public handleMessage(message: Message): void { - if (!message || !message.vsWorker) { + public handleMessage(message: unknown): void { + if (!message || !(message as Message).vsWorker) { return; } - if (this._workerId !== -1 && message.vsWorker !== this._workerId) { + if (this._workerId !== -1 && (message as Message).vsWorker !== this._workerId) { return; } - this._handleMessage(message); + this._handleMessage(message as Message); } public createProxyToRemoteChannel(channel: string, sendMessageBarrier?: () => Promise): T { const handler = { - get: (target: any, name: PropertyKey) => { + get: (target: Record, name: PropertyKey) => { if (typeof name === 'string' && !target[name]) { if (propertyIsDynamicEvent(name)) { // onDynamic... - target[name] = (arg: any): Event => { + target[name] = (arg: unknown): Event => { return this.listen(channel, name, arg); }; } else if (propertyIsEvent(name)) { // on... target[name] = this.listen(channel, name, undefined); } else if (name.charCodeAt(0) === CharCode.DollarSign) { // $... - target[name] = async (...myArgs: any[]) => { + target[name] = async (...myArgs: unknown[]) => { await sendMessageBarrier?.(); return this.sendMessage(channel, name, myArgs); }; @@ -206,11 +206,12 @@ class WebWorkerProtocol { if (replyMessage.err) { let err = replyMessage.err; - if (replyMessage.err.$isError) { - err = new Error(); - err.name = replyMessage.err.name; - err.message = replyMessage.err.message; - err.stack = replyMessage.err.stack; + if ((replyMessage.err as SerializedError).$isError) { + const newErr = new Error(); + newErr.name = (replyMessage.err as SerializedError).name; + newErr.message = (replyMessage.err as SerializedError).message; + newErr.stack = (replyMessage.err as SerializedError).stack; + err = newErr; } reply.reject(err); return; @@ -242,19 +243,21 @@ class WebWorkerProtocol { } private _handleEventMessage(msg: EventMessage): void { - if (!this._pendingEmitters.has(msg.req)) { + const emitter = this._pendingEmitters.get(msg.req); + if (emitter === undefined) { console.warn('Got event for unknown req'); return; } - this._pendingEmitters.get(msg.req)!.fire(msg.event); + emitter.fire(msg.event); } private _handleUnsubscribeEventMessage(msg: UnsubscribeEventMessage): void { - if (!this._pendingEvents.has(msg.req)) { + const event = this._pendingEvents.get(msg.req); + if (event === undefined) { console.warn('Got unsubscribe for unknown req'); return; } - this._pendingEvents.get(msg.req)!.dispose(); + event.dispose(); this._pendingEvents.delete(msg.req); } @@ -262,8 +265,9 @@ class WebWorkerProtocol { const transfer: ArrayBuffer[] = []; if (msg.type === MessageType.Request) { for (let i = 0; i < msg.args.length; i++) { - if (msg.args[i] instanceof ArrayBuffer) { - transfer.push(msg.args[i]); + const arg = msg.args[i]; + if (arg instanceof ArrayBuffer) { + transfer.push(arg); } } } else if (msg.type === MessageType.Reply) { @@ -325,13 +329,13 @@ export class WebWorkerClient extends Disposable implements IWe })); this._protocol = new WebWorkerProtocol({ - sendMessage: (msg: any, transfer: ArrayBuffer[]): void => { + sendMessage: (msg: Message, transfer: ArrayBuffer[]): void => { this._worker.postMessage(msg, transfer); }, - handleMessage: (channel: string, method: string, args: any[]): Promise => { + handleMessage: (channel: string, method: string, args: unknown[]): Promise => { return this._handleMessage(channel, method, args); }, - handleEvent: (channel: string, eventName: string, arg: any): Event => { + handleEvent: (channel: string, eventName: string, arg: unknown): Event => { return this._handleEvent(channel, eventName, arg); } }); @@ -340,7 +344,7 @@ export class WebWorkerClient extends Disposable implements IWe // Send initialize message this._onModuleLoaded = this._protocol.sendMessage(DEFAULT_CHANNEL, INITIALIZE, [ this._worker.getId(), - ]); + ]).then(() => { }); this.proxy = this._protocol.createProxyToRemoteChannel(DEFAULT_CHANNEL, async () => { await this._onModuleLoaded; }); this._onModuleLoaded.catch((e) => { @@ -348,40 +352,46 @@ export class WebWorkerClient extends Disposable implements IWe }); } - private _handleMessage(channelName: string, method: string, args: any[]): Promise { + private _handleMessage(channelName: string, method: string, args: unknown[]): Promise { const channel: object | undefined = this._localChannels.get(channelName); if (!channel) { return Promise.reject(new Error(`Missing channel ${channelName} on main thread`)); } - if (typeof (channel as any)[method] !== 'function') { + + const fn = (channel as Record)[method]; + if (typeof fn !== 'function') { return Promise.reject(new Error(`Missing method ${method} on main thread channel ${channelName}`)); } try { - return Promise.resolve((channel as any)[method].apply(channel, args)); + return Promise.resolve(fn.apply(channel, args)); } catch (e) { return Promise.reject(e); } } - private _handleEvent(channelName: string, eventName: string, arg: any): Event { + private _handleEvent(channelName: string, eventName: string, arg: unknown): Event { const channel: object | undefined = this._localChannels.get(channelName); if (!channel) { throw new Error(`Missing channel ${channelName} on main thread`); } if (propertyIsDynamicEvent(eventName)) { - const event = (channel as any)[eventName].call(channel, arg); + const fn = (channel as Record)[eventName]; + if (typeof fn !== 'function') { + throw new Error(`Missing dynamic event ${eventName} on main thread channel ${channelName}.`); + } + const event = fn.call(channel, arg); if (typeof event !== 'function') { throw new Error(`Missing dynamic event ${eventName} on main thread channel ${channelName}.`); } return event; } if (propertyIsEvent(eventName)) { - const event = (channel as any)[eventName]; + const event = (channel as Record)[eventName]; if (typeof event !== 'function') { throw new Error(`Missing event ${eventName} on main thread channel ${channelName}.`); } - return event; + return event as Event; } throw new Error(`Malformed event name ${eventName}`); } @@ -391,14 +401,15 @@ export class WebWorkerClient extends Disposable implements IWe } public getChannel(channel: string): Proxied { - if (!this._remoteChannels.has(channel)) { - const inst = this._protocol.createProxyToRemoteChannel(channel, async () => { await this._onModuleLoaded; }); + let inst = this._remoteChannels.get(channel); + if (inst === undefined) { + inst = this._protocol.createProxyToRemoteChannel(channel, async () => { await this._onModuleLoaded; }); this._remoteChannels.set(channel, inst); } - return this._remoteChannels.get(channel) as Proxied; + return inst as Proxied; } - private _onError(message: string, error?: any): void { + private _onError(message: string, error?: unknown): void { console.error(message); console.info(error); } @@ -415,7 +426,8 @@ function propertyIsDynamicEvent(name: string): boolean { } export interface IWebWorkerServerRequestHandler { - _requestHandlerBrand: any; + _requestHandlerBrand: void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any [prop: string]: any; } @@ -435,20 +447,20 @@ export class WebWorkerServer implement constructor(postMessage: (msg: Message, transfer?: ArrayBuffer[]) => void, requestHandlerFactory: IWebWorkerServerRequestHandlerFactory) { this._protocol = new WebWorkerProtocol({ - sendMessage: (msg: any, transfer: ArrayBuffer[]): void => { + sendMessage: (msg: Message, transfer: ArrayBuffer[]): void => { postMessage(msg, transfer); }, - handleMessage: (channel: string, method: string, args: any[]): Promise => this._handleMessage(channel, method, args), - handleEvent: (channel: string, eventName: string, arg: any): Event => this._handleEvent(channel, eventName, arg) + handleMessage: (channel: string, method: string, args: unknown[]): Promise => this._handleMessage(channel, method, args), + handleEvent: (channel: string, eventName: string, arg: unknown): Event => this._handleEvent(channel, eventName, arg) }); this.requestHandler = requestHandlerFactory(this); } - public onmessage(msg: any): void { + public onmessage(msg: unknown): void { this._protocol.handleMessage(msg); } - private _handleMessage(channel: string, method: string, args: any[]): Promise { + private _handleMessage(channel: string, method: string, args: unknown[]): Promise { if (channel === DEFAULT_CHANNEL && method === INITIALIZE) { return this.initialize(args[0]); } @@ -457,35 +469,42 @@ export class WebWorkerServer implement if (!requestHandler) { return Promise.reject(new Error(`Missing channel ${channel} on worker thread`)); } - if (typeof (requestHandler as any)[method] !== 'function') { + + const fn = (requestHandler as Record)[method]; + if (typeof fn !== 'function') { return Promise.reject(new Error(`Missing method ${method} on worker thread channel ${channel}`)); } try { - return Promise.resolve((requestHandler as any)[method].apply(requestHandler, args)); + return Promise.resolve(fn.apply(requestHandler, args)); } catch (e) { return Promise.reject(e); } } - private _handleEvent(channel: string, eventName: string, arg: any): Event { + private _handleEvent(channel: string, eventName: string, arg: unknown): Event { const requestHandler: object | null | undefined = (channel === DEFAULT_CHANNEL ? this.requestHandler : this._localChannels.get(channel)); if (!requestHandler) { throw new Error(`Missing channel ${channel} on worker thread`); } if (propertyIsDynamicEvent(eventName)) { - const event = (requestHandler as any)[eventName].call(requestHandler, arg); + const fn = (requestHandler as Record)[eventName]; + if (typeof fn !== 'function') { + throw new Error(`Missing dynamic event ${eventName} on request handler.`); + } + + const event = fn.call(requestHandler, arg); if (typeof event !== 'function') { throw new Error(`Missing dynamic event ${eventName} on request handler.`); } return event; } if (propertyIsEvent(eventName)) { - const event = (requestHandler as any)[eventName]; + const event = (requestHandler as Record)[eventName]; if (typeof event !== 'function') { throw new Error(`Missing event ${eventName} on request handler.`); } - return event; + return event as Event; } throw new Error(`Malformed event name ${eventName}`); } @@ -495,11 +514,12 @@ export class WebWorkerServer implement } public getChannel(channel: string): Proxied { - if (!this._remoteChannels.has(channel)) { - const inst = this._protocol.createProxyToRemoteChannel(channel); + let inst = this._remoteChannels.get(channel); + if (inst === undefined) { + inst = this._protocol.createProxyToRemoteChannel(channel); this._remoteChannels.set(channel, inst); } - return this._remoteChannels.get(channel) as Proxied; + return inst as Proxied; } private async initialize(workerId: number): Promise { diff --git a/code/src/vs/base/common/yaml.ts b/code/src/vs/base/common/yaml.ts new file mode 100644 index 00000000000..6f2e801d696 --- /dev/null +++ b/code/src/vs/base/common/yaml.ts @@ -0,0 +1,892 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * Parses a simplified YAML-like input from a single string. + * Supports objects, arrays, primitive types (string, number, boolean, null). + * Tracks positions for error reporting and node locations. + * + * Limitations: + * - No multi-line strings or block literals + * - No anchors or references + * - No complex types (dates, binary) + * - No special handling for escape sequences in strings + * - Indentation must be consistent (spaces only, no tabs) + * + * Notes: + * - New line separators can be either "\n" or "\r\n". The input string is split into lines internally. + * + * @param input A string containing the YAML-like input + * @param errors Array to collect parsing errors + * @param options Parsing options + * @returns The parsed representation (ObjectNode, ArrayNode, or primitive node) + */ +export function parse(input: string, errors: YamlParseError[] = [], options: ParseOptions = {}): YamlNode | undefined { + // Normalize both LF and CRLF by splitting on either; CR characters are not retained as part of line text. + // This keeps the existing line/character based lexer logic intact. + const lines = input.length === 0 ? [] : input.split(/\r\n|\n/); + const parser = new YamlParser(lines, errors, options); + return parser.parse(); +} + +export interface YamlParseError { + readonly message: string; + readonly start: Position; + readonly end: Position; + readonly code: string; +} + +export interface ParseOptions { + readonly allowDuplicateKeys?: boolean; +} + +export interface Position { + readonly line: number; + readonly character: number; +} + +export interface YamlStringNode { + readonly type: 'string'; + readonly value: string; + readonly start: Position; + readonly end: Position; +} + +export interface YamlNumberNode { + readonly type: 'number'; + readonly value: number; + readonly start: Position; + readonly end: Position; +} + +export interface YamlBooleanNode { + readonly type: 'boolean'; + readonly value: boolean; + readonly start: Position; + readonly end: Position; +} + +export interface YamlNullNode { + readonly type: 'null'; + readonly value: null; + readonly start: Position; + readonly end: Position; +} + +export interface YamlObjectNode { + readonly type: 'object'; + readonly properties: { key: YamlStringNode; value: YamlNode }[]; + readonly start: Position; + readonly end: Position; +} + +export interface YamlArrayNode { + readonly type: 'array'; + readonly items: YamlNode[]; + readonly start: Position; + readonly end: Position; +} + +export type YamlNode = YamlStringNode | YamlNumberNode | YamlBooleanNode | YamlNullNode | YamlObjectNode | YamlArrayNode; + +// Helper functions for position and node creation +function createPosition(line: number, character: number): Position { + return { line, character }; +} + +// Specialized node creation functions using a more concise approach +function createStringNode(value: string, start: Position, end: Position): YamlStringNode { + return { type: 'string', value, start, end }; +} + +function createNumberNode(value: number, start: Position, end: Position): YamlNumberNode { + return { type: 'number', value, start, end }; +} + +function createBooleanNode(value: boolean, start: Position, end: Position): YamlBooleanNode { + return { type: 'boolean', value, start, end }; +} + +function createNullNode(start: Position, end: Position): YamlNullNode { + return { type: 'null', value: null, start, end }; +} + +function createObjectNode(properties: { key: YamlStringNode; value: YamlNode }[], start: Position, end: Position): YamlObjectNode { + return { type: 'object', start, end, properties }; +} + +function createArrayNode(items: YamlNode[], start: Position, end: Position): YamlArrayNode { + return { type: 'array', start, end, items }; +} + +// Utility functions for parsing +function isWhitespace(char: string): boolean { + return char === ' ' || char === '\t'; +} + +// Simplified number validation using regex +function isValidNumber(value: string): boolean { + return /^-?\d*\.?\d+$/.test(value); +} + +// Lexer/Tokenizer for YAML content +class YamlLexer { + private lines: string[]; + private currentLine: number = 0; + private currentChar: number = 0; + + constructor(lines: string[]) { + this.lines = lines; + } + + getCurrentPosition(): Position { + return createPosition(this.currentLine, this.currentChar); + } + + getCurrentLineNumber(): number { + return this.currentLine; + } + + getCurrentCharNumber(): number { + return this.currentChar; + } + + getCurrentLineText(): string { + return this.currentLine < this.lines.length ? this.lines[this.currentLine] : ''; + } + + savePosition(): { line: number; char: number } { + return { line: this.currentLine, char: this.currentChar }; + } + + restorePosition(pos: { line: number; char: number }): void { + this.currentLine = pos.line; + this.currentChar = pos.char; + } + + isAtEnd(): boolean { + return this.currentLine >= this.lines.length; + } + + getCurrentChar(): string { + if (this.isAtEnd() || this.currentChar >= this.lines[this.currentLine].length) { + return ''; + } + return this.lines[this.currentLine][this.currentChar]; + } + + peek(offset: number = 1): string { + const newChar = this.currentChar + offset; + if (this.currentLine >= this.lines.length || newChar >= this.lines[this.currentLine].length) { + return ''; + } + return this.lines[this.currentLine][newChar]; + } + + advance(): string { + const char = this.getCurrentChar(); + if (this.currentChar >= this.lines[this.currentLine].length && this.currentLine < this.lines.length - 1) { + this.currentLine++; + this.currentChar = 0; + } else { + this.currentChar++; + } + return char; + } + + advanceLine(): void { + this.currentLine++; + this.currentChar = 0; + } + + skipWhitespace(): void { + while (!this.isAtEnd() && this.currentChar < this.lines[this.currentLine].length && isWhitespace(this.getCurrentChar())) { + this.advance(); + } + } + + skipToEndOfLine(): void { + this.currentChar = this.lines[this.currentLine].length; + } + + getIndentation(): number { + if (this.isAtEnd()) { + return 0; + } + let indent = 0; + for (let i = 0; i < this.lines[this.currentLine].length; i++) { + if (this.lines[this.currentLine][i] === ' ') { + indent++; + } else if (this.lines[this.currentLine][i] === '\t') { + indent += 4; // Treat tab as 4 spaces + } else { + break; + } + } + return indent; + } + + moveToNextNonEmptyLine(): void { + while (this.currentLine < this.lines.length) { + // First check current line from current position + if (this.currentChar < this.lines[this.currentLine].length) { + const remainingLine = this.lines[this.currentLine].substring(this.currentChar).trim(); + if (remainingLine.length > 0 && !remainingLine.startsWith('#')) { + this.skipWhitespace(); + return; + } + } + + // Move to next line and check from beginning + this.currentLine++; + this.currentChar = 0; + + if (this.currentLine < this.lines.length) { + const line = this.lines[this.currentLine].trim(); + if (line.length > 0 && !line.startsWith('#')) { + this.skipWhitespace(); + return; + } + } + } + } +} + +// Parser class for handling YAML parsing +class YamlParser { + private lexer: YamlLexer; + private errors: YamlParseError[]; + private options: ParseOptions; + // Track nesting level of flow (inline) collections '[' ']' '{' '}' + private flowLevel: number = 0; + + constructor(lines: string[], errors: YamlParseError[], options: ParseOptions) { + this.lexer = new YamlLexer(lines); + this.errors = errors; + this.options = options; + } + + addError(message: string, code: string, start: Position, end: Position): void { + this.errors.push({ message, code, start, end }); + } + + parseValue(expectedIndent?: number): YamlNode { + this.lexer.skipWhitespace(); + + if (this.lexer.isAtEnd()) { + const pos = this.lexer.getCurrentPosition(); + return createStringNode('', pos, pos); + } + + const char = this.lexer.getCurrentChar(); + + // Handle quoted strings + if (char === '"' || char === `'`) { + return this.parseQuotedString(char); + } + + // Handle inline arrays + if (char === '[') { + return this.parseInlineArray(); + } + + // Handle inline objects + if (char === '{') { + return this.parseInlineObject(); + } + + // Handle unquoted values + return this.parseUnquotedValue(); + } + + parseQuotedString(quote: string): YamlNode { + const start = this.lexer.getCurrentPosition(); + this.lexer.advance(); // Skip opening quote + + let value = ''; + while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '' && this.lexer.getCurrentChar() !== quote) { + value += this.lexer.advance(); + } + + if (this.lexer.getCurrentChar() === quote) { + this.lexer.advance(); // Skip closing quote + } + + const end = this.lexer.getCurrentPosition(); + return createStringNode(value, start, end); + } + + parseUnquotedValue(): YamlNode { + const start = this.lexer.getCurrentPosition(); + let value = ''; + let endPos = start; + + // Helper function to check for value terminators + const isTerminator = (char: string): boolean => { + if (char === '#') { return true; } + // Comma, ']' and '}' only terminate inside flow collections + if (this.flowLevel > 0 && (char === ',' || char === ']' || char === '}')) { return true; } + return false; + }; + + // Handle opening quote that might not be closed + const firstChar = this.lexer.getCurrentChar(); + if (firstChar === '"' || firstChar === `'`) { + value += this.lexer.advance(); + endPos = this.lexer.getCurrentPosition(); + while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '') { + const char = this.lexer.getCurrentChar(); + if (char === firstChar || isTerminator(char)) { + break; + } + value += this.lexer.advance(); + endPos = this.lexer.getCurrentPosition(); + } + } else { + while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '') { + const char = this.lexer.getCurrentChar(); + if (isTerminator(char)) { + break; + } + value += this.lexer.advance(); + endPos = this.lexer.getCurrentPosition(); + } + } + const trimmed = value.trimEnd(); + const diff = value.length - trimmed.length; + if (diff) { + endPos = createPosition(start.line, endPos.character - diff); + } + const finalValue = (firstChar === '"' || firstChar === `'`) ? trimmed.substring(1) : trimmed; + return this.createValueNode(finalValue, start, endPos); + } + + private createValueNode(value: string, start: Position, end: Position): YamlNode { + if (value === '') { + return createStringNode('', start, start); + } + + // Boolean values + if (value === 'true') { + return createBooleanNode(true, start, end); + } + if (value === 'false') { + return createBooleanNode(false, start, end); + } + + // Null values + if (value === 'null' || value === '~') { + return createNullNode(start, end); + } + + // Number values + const numberValue = Number(value); + if (!isNaN(numberValue) && isFinite(numberValue) && isValidNumber(value)) { + return createNumberNode(numberValue, start, end); + } + + // Default to string + return createStringNode(value, start, end); + } + + parseInlineArray(): YamlArrayNode { + const start = this.lexer.getCurrentPosition(); + this.lexer.advance(); // Skip '[' + this.flowLevel++; + + const items: YamlNode[] = []; + + while (!this.lexer.isAtEnd()) { + this.lexer.skipWhitespace(); + + // Handle end of array + if (this.lexer.getCurrentChar() === ']') { + this.lexer.advance(); + break; + } + + // Handle end of line - continue to next line for multi-line arrays + if (this.lexer.getCurrentChar() === '') { + this.lexer.advanceLine(); + continue; + } + + // Handle comments - comments should terminate the array parsing + if (this.lexer.getCurrentChar() === '#') { + // Skip the rest of the line (comment) + this.lexer.skipToEndOfLine(); + this.lexer.advanceLine(); + continue; + } + + // Save position before parsing to detect if we're making progress + const positionBefore = this.lexer.savePosition(); + + // Parse array item + const item = this.parseValue(); + // Skip implicit empty items that arise from a leading comma at the beginning of a new line + // (e.g. a line starting with ",foo" after a comment). A legitimate empty string element + // would have quotes and thus a non-zero span. We only filter zero-length spans. + if (!(item.type === 'string' && item.value === '' && item.start.line === item.end.line && item.start.character === item.end.character)) { + items.push(item); + } + + // Check if we made progress - if not, we're likely stuck + const positionAfter = this.lexer.savePosition(); + if (positionBefore.line === positionAfter.line && positionBefore.char === positionAfter.char) { + // No progress made, advance at least one character to prevent infinite loop + if (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '') { + this.lexer.advance(); + } else { + break; + } + } + + this.lexer.skipWhitespace(); + + // Handle comma separator + if (this.lexer.getCurrentChar() === ',') { + this.lexer.advance(); + } + } + + const end = this.lexer.getCurrentPosition(); + this.flowLevel--; + return createArrayNode(items, start, end); + } + + parseInlineObject(): YamlObjectNode { + const start = this.lexer.getCurrentPosition(); + this.lexer.advance(); // Skip '{' + this.flowLevel++; + + const properties: { key: YamlStringNode; value: YamlNode }[] = []; + + while (!this.lexer.isAtEnd()) { + this.lexer.skipWhitespace(); + + // Handle end of object + if (this.lexer.getCurrentChar() === '}') { + this.lexer.advance(); + break; + } + + // Handle comments - comments should terminate the object parsing + if (this.lexer.getCurrentChar() === '#') { + // Skip the rest of the line (comment) + this.lexer.skipToEndOfLine(); + this.lexer.advanceLine(); + continue; + } + + // Save position before parsing to detect if we're making progress + const positionBefore = this.lexer.savePosition(); + + // Parse key - read until colon + const keyStart = this.lexer.getCurrentPosition(); + let keyValue = ''; + + // Handle quoted keys + if (this.lexer.getCurrentChar() === '"' || this.lexer.getCurrentChar() === `'`) { + const quote = this.lexer.getCurrentChar(); + this.lexer.advance(); // Skip opening quote + + while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '' && this.lexer.getCurrentChar() !== quote) { + keyValue += this.lexer.advance(); + } + + if (this.lexer.getCurrentChar() === quote) { + this.lexer.advance(); // Skip closing quote + } + } else { + // Handle unquoted keys - read until colon + while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '' && this.lexer.getCurrentChar() !== ':') { + keyValue += this.lexer.advance(); + } + } + + keyValue = keyValue.trim(); + const keyEnd = this.lexer.getCurrentPosition(); + const key = createStringNode(keyValue, keyStart, keyEnd); + + this.lexer.skipWhitespace(); + + // Expect colon + if (this.lexer.getCurrentChar() === ':') { + this.lexer.advance(); + } + + this.lexer.skipWhitespace(); + + // Parse value + const value = this.parseValue(); + + properties.push({ key, value }); + + // Check if we made progress - if not, we're likely stuck + const positionAfter = this.lexer.savePosition(); + if (positionBefore.line === positionAfter.line && positionBefore.char === positionAfter.char) { + // No progress made, advance at least one character to prevent infinite loop + if (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '') { + this.lexer.advance(); + } else { + break; + } + } + + this.lexer.skipWhitespace(); + + // Handle comma separator + if (this.lexer.getCurrentChar() === ',') { + this.lexer.advance(); + } + } + + const end = this.lexer.getCurrentPosition(); + this.flowLevel--; + return createObjectNode(properties, start, end); + } + + parseBlockArray(baseIndent: number): YamlArrayNode { + const start = this.lexer.getCurrentPosition(); + const items: YamlNode[] = []; + + while (!this.lexer.isAtEnd()) { + this.lexer.moveToNextNonEmptyLine(); + + if (this.lexer.isAtEnd()) { + break; + } + + const currentIndent = this.lexer.getIndentation(); + + // If indentation is less than expected, we're done with this array + if (currentIndent < baseIndent) { + break; + } + + this.lexer.skipWhitespace(); + + // Check for array item marker + if (this.lexer.getCurrentChar() === '-') { + this.lexer.advance(); // Skip '-' + this.lexer.skipWhitespace(); + + const itemStart = this.lexer.getCurrentPosition(); + + // Check if this is a nested structure + if (this.lexer.getCurrentChar() === '' || this.lexer.getCurrentChar() === '#') { + // Empty item - check if next lines form a nested structure + this.lexer.advanceLine(); + + if (!this.lexer.isAtEnd()) { + const nextIndent = this.lexer.getIndentation(); + + if (nextIndent > currentIndent) { + // Check if the next line starts with a dash (nested array) or has properties (nested object) + this.lexer.skipWhitespace(); + if (this.lexer.getCurrentChar() === '-') { + // It's a nested array + const nestedArray = this.parseBlockArray(nextIndent); + items.push(nestedArray); + } else { + // Check if it looks like an object property (has a colon) + const currentLine = this.lexer.getCurrentLineText(); + const currentPos = this.lexer.getCurrentCharNumber(); + const remainingLine = currentLine.substring(currentPos); + + if (remainingLine.includes(':') && !remainingLine.trim().startsWith('#')) { + // It's a nested object + const nestedObject = this.parseBlockObject(nextIndent, this.lexer.getCurrentCharNumber()); + items.push(nestedObject); + } else { + // Not a nested structure, create empty string + items.push(createStringNode('', itemStart, itemStart)); + } + } + } else { + // No nested content, empty item + items.push(createStringNode('', itemStart, itemStart)); + } + } else { + // End of input, empty item + items.push(createStringNode('', itemStart, itemStart)); + } + } else { + // Parse the item value + // Check if this is a multi-line object by looking for a colon and checking next lines + const currentLine = this.lexer.getCurrentLineText(); + const currentPos = this.lexer.getCurrentCharNumber(); + const remainingLine = currentLine.substring(currentPos); + + // Check if there's a colon on this line (indicating object properties) + const hasColon = remainingLine.includes(':'); + + if (hasColon) { + // Any line with a colon should be treated as an object + // Parse as an object with the current item's indentation as the base + const item = this.parseBlockObject(itemStart.character, itemStart.character); + items.push(item); + } else { + // No colon, parse as regular value + const item = this.parseValue(); + items.push(item); + + // Skip to end of line + while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '' && this.lexer.getCurrentChar() !== '#') { + this.lexer.advance(); + } + this.lexer.advanceLine(); + } + } + } else { + // No dash found at expected indent level, break + break; + } + } + + // Calculate end position based on the last item + let end = start; + if (items.length > 0) { + const lastItem = items[items.length - 1]; + end = lastItem.end; + } else { + // If no items, end is right after the start + end = createPosition(start.line, start.character + 1); + } + + return createArrayNode(items, start, end); + } + + parseBlockObject(baseIndent: number, baseCharPosition?: number): YamlObjectNode { + const start = this.lexer.getCurrentPosition(); + const properties: { key: YamlStringNode; value: YamlNode }[] = []; + const localKeysSeen = new Set(); + + // For parsing from current position (inline object parsing) + const fromCurrentPosition = baseCharPosition !== undefined; + let firstIteration = true; + + while (!this.lexer.isAtEnd()) { + if (!firstIteration || !fromCurrentPosition) { + this.lexer.moveToNextNonEmptyLine(); + } + firstIteration = false; + + if (this.lexer.isAtEnd()) { + break; + } + + const currentIndent = this.lexer.getIndentation(); + + if (fromCurrentPosition) { + // For current position parsing, check character position alignment + this.lexer.skipWhitespace(); + const currentCharPosition = this.lexer.getCurrentCharNumber(); + + if (currentCharPosition < baseCharPosition) { + break; + } + } else { + // For normal block parsing, check indentation level + if (currentIndent < baseIndent) { + break; + } + + // Check for incorrect indentation + if (currentIndent > baseIndent) { + const lineStart = createPosition(this.lexer.getCurrentLineNumber(), 0); + const lineEnd = createPosition(this.lexer.getCurrentLineNumber(), this.lexer.getCurrentLineText().length); + this.addError('Unexpected indentation', 'indentation', lineStart, lineEnd); + + // Try to recover by treating it as a property anyway + this.lexer.skipWhitespace(); + } else { + this.lexer.skipWhitespace(); + } + } + + // Parse key + const keyStart = this.lexer.getCurrentPosition(); + let keyValue = ''; + + while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '' && this.lexer.getCurrentChar() !== ':') { + keyValue += this.lexer.advance(); + } + + keyValue = keyValue.trim(); + const keyEnd = this.lexer.getCurrentPosition(); + const key = createStringNode(keyValue, keyStart, keyEnd); + + // Check for duplicate keys + if (!this.options.allowDuplicateKeys && localKeysSeen.has(keyValue)) { + this.addError(`Duplicate key '${keyValue}'`, 'duplicateKey', keyStart, keyEnd); + } + localKeysSeen.add(keyValue); + + // Expect colon + if (this.lexer.getCurrentChar() === ':') { + this.lexer.advance(); + } + + this.lexer.skipWhitespace(); + + // Determine if value is on same line or next line(s) + let value: YamlNode; + const valueStart = this.lexer.getCurrentPosition(); + + if (this.lexer.getCurrentChar() === '' || this.lexer.getCurrentChar() === '#') { + // Value is on next line(s) or empty + this.lexer.advanceLine(); + + // Check next line for nested content + if (!this.lexer.isAtEnd()) { + const nextIndent = this.lexer.getIndentation(); + + if (nextIndent > currentIndent) { + // Nested content - determine if it's an object, array, or just a scalar value + this.lexer.skipWhitespace(); + + if (this.lexer.getCurrentChar() === '-') { + value = this.parseBlockArray(nextIndent); + } else { + // Check if this looks like an object property (has a colon) + const currentLine = this.lexer.getCurrentLineText(); + const currentPos = this.lexer.getCurrentCharNumber(); + const remainingLine = currentLine.substring(currentPos); + + if (remainingLine.includes(':') && !remainingLine.trim().startsWith('#')) { + // It's a nested object + value = this.parseBlockObject(nextIndent); + } else { + // It's just a scalar value on the next line + value = this.parseValue(); + } + } + } else if (!fromCurrentPosition && nextIndent === currentIndent) { + // Same indentation level - check if it's an array item + this.lexer.skipWhitespace(); + + if (this.lexer.getCurrentChar() === '-') { + value = this.parseBlockArray(currentIndent); + } else { + value = createStringNode('', valueStart, valueStart); + } + } else { + value = createStringNode('', valueStart, valueStart); + } + } else { + value = createStringNode('', valueStart, valueStart); + } + } else { + // Value is on the same line + value = this.parseValue(); + + // Skip any remaining content on this line (comments, etc.) + while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '' && this.lexer.getCurrentChar() !== '#') { + if (isWhitespace(this.lexer.getCurrentChar())) { + this.lexer.advance(); + } else { + break; + } + } + + // Skip to end of line if we hit a comment + if (this.lexer.getCurrentChar() === '#') { + this.lexer.skipToEndOfLine(); + } + + // Move to next line for next iteration + if (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() === '') { + this.lexer.advanceLine(); + } + } + + properties.push({ key, value }); + } + + // Calculate the end position based on the last property + let end = start; + if (properties.length > 0) { + const lastProperty = properties[properties.length - 1]; + end = lastProperty.value.end; + } + + return createObjectNode(properties, start, end); + } + + parse(): YamlNode | undefined { + if (this.lexer.isAtEnd()) { + return undefined; + } + + this.lexer.moveToNextNonEmptyLine(); + + if (this.lexer.isAtEnd()) { + return undefined; + } + + // Determine the root structure type + this.lexer.skipWhitespace(); + + if (this.lexer.getCurrentChar() === '-') { + // Check if this is an array item or a negative number + // Look at the character after the dash + const nextChar = this.lexer.peek(); + if (nextChar === ' ' || nextChar === '\t' || nextChar === '' || nextChar === '#') { + // It's an array item (dash followed by whitespace/end/comment) + return this.parseBlockArray(0); + } else { + // It's likely a negative number or other value, treat as single value + return this.parseValue(); + } + } else if (this.lexer.getCurrentChar() === '[') { + // Root is an inline array + return this.parseInlineArray(); + } else if (this.lexer.getCurrentChar() === '{') { + // Root is an inline object + return this.parseInlineObject(); + } else { + // Check if this looks like a key-value pair by looking for a colon + // For single values, there shouldn't be a colon + const currentLine = this.lexer.getCurrentLineText(); + const currentPos = this.lexer.getCurrentCharNumber(); + const remainingLine = currentLine.substring(currentPos); + + // Check if there's a colon that's not inside quotes + let hasColon = false; + let inQuotes = false; + let quoteChar = ''; + + for (let i = 0; i < remainingLine.length; i++) { + const char = remainingLine[i]; + + if (!inQuotes && (char === '"' || char === `'`)) { + inQuotes = true; + quoteChar = char; + } else if (inQuotes && char === quoteChar) { + inQuotes = false; + quoteChar = ''; + } else if (!inQuotes && char === ':') { + hasColon = true; + break; + } else if (!inQuotes && char === '#') { + // Comment starts, stop looking + break; + } + } + + if (hasColon) { + // Root is an object + return this.parseBlockObject(0); + } else { + // Root is a single value + return this.parseValue(); + } + } + } +} + + diff --git a/code/src/vs/base/node/id.ts b/code/src/vs/base/node/id.ts index c74fcf4ae61..b37534ac0bc 100644 --- a/code/src/vs/base/node/id.ts +++ b/code/src/vs/base/node/id.ts @@ -78,7 +78,7 @@ export const virtualMachineHint: { value(): number } = new class { }; let machineId: Promise; -export async function getMachineId(errorLogger: (error: any) => void): Promise { +export async function getMachineId(errorLogger: (error: Error) => void): Promise { if (!machineId) { machineId = (async () => { const id = await getMacMachineId(errorLogger); @@ -90,7 +90,7 @@ export async function getMachineId(errorLogger: (error: any) => void): Promise void): Promise { +async function getMacMachineId(errorLogger: (error: Error) => void): Promise { try { const crypto = await import('crypto'); const macAddress = getMac(); @@ -102,7 +102,7 @@ async function getMacMachineId(errorLogger: (error: any) => void): Promise void): Promise { +export async function getSqmMachineId(errorLogger: (error: Error) => void): Promise { if (isWindows) { const Registry = await import('@vscode/windows-registry'); try { @@ -115,7 +115,7 @@ export async function getSqmMachineId(errorLogger: (error: any) => void): Promis return ''; } -export async function getDevDeviceId(errorLogger: (error: any) => void): Promise { +export async function getDevDeviceId(errorLogger: (error: Error) => void): Promise { try { const deviceIdPackage = await import('@vscode/deviceid'); const id = await deviceIdPackage.getDeviceId(); diff --git a/code/src/vs/base/node/osDisplayProtocolInfo.ts b/code/src/vs/base/node/osDisplayProtocolInfo.ts index 2dbc302e02a..41ed6b7eb0a 100644 --- a/code/src/vs/base/node/osDisplayProtocolInfo.ts +++ b/code/src/vs/base/node/osDisplayProtocolInfo.ts @@ -18,7 +18,7 @@ const enum DisplayProtocolType { Unknown = 'unknown' } -export async function getDisplayProtocol(errorLogger: (error: any) => void): Promise { +export async function getDisplayProtocol(errorLogger: (error: string | Error) => void): Promise { const xdgSessionType = env[XDG_SESSION_TYPE]; if (xdgSessionType) { diff --git a/code/src/vs/base/node/osReleaseInfo.ts b/code/src/vs/base/node/osReleaseInfo.ts index 8c34493531f..890bc254e16 100644 --- a/code/src/vs/base/node/osReleaseInfo.ts +++ b/code/src/vs/base/node/osReleaseInfo.ts @@ -13,7 +13,7 @@ type ReleaseInfo = { version_id?: string; }; -export async function getOSReleaseInfo(errorLogger: (error: any) => void): Promise { +export async function getOSReleaseInfo(errorLogger: (error: string | Error) => void): Promise { if (Platform.isMacintosh || Platform.isWindows) { return; } diff --git a/code/src/vs/base/node/pfs.ts b/code/src/vs/base/node/pfs.ts index 3a0839940f8..55251cf572a 100644 --- a/code/src/vs/base/node/pfs.ts +++ b/code/src/vs/base/node/pfs.ts @@ -72,7 +72,7 @@ async function rimrafMove(path: string, moveToPath = randomPath(tmpdir())): Prom } // Delete but do not return as promise - rimrafUnlink(moveToPath).catch(error => {/* ignore */ }); + rimrafUnlink(moveToPath).catch(() => {/* ignore */ }); } catch (error) { if (error.code !== 'ENOENT') { throw error; @@ -107,13 +107,13 @@ async function readdir(path: string, options?: { withFileTypes: true }): Promise try { return await doReaddir(path, options); } catch (error) { - // TODO@bpasero workaround for #252361 that should be removed - // once the upstream issue in node.js is resolved. Adds a trailing - // dot to a root drive letter path (G:\ => G:\.) as a workaround. + // Workaround for #252361 that should be removed once the upstream issue + // in node.js is resolved. Adds a trailing dot to a root drive letter path + // (G:\ => G:\.) as a workaround. if (error.code === 'ENOENT' && isWindows && isRootOrDriveLetter(path)) { try { return await doReaddir(`${path}.`, options); - } catch (e) { + } catch { // ignore } } @@ -268,7 +268,7 @@ export namespace SymlinkSupport { if (!lstats.isSymbolicLink()) { return { stat: lstats }; } - } catch (error) { + } catch { /* ignore - use stat() instead */ } @@ -324,7 +324,7 @@ export namespace SymlinkSupport { const { stat, symbolicLink } = await SymlinkSupport.stat(path); return stat.isFile() && symbolicLink?.dangling !== true; - } catch (error) { + } catch { // Ignore, path might not exist } @@ -346,7 +346,7 @@ export namespace SymlinkSupport { const { stat, symbolicLink } = await SymlinkSupport.stat(path); return stat.isDirectory() && symbolicLink?.dangling !== true; - } catch (error) { + } catch { // Ignore, path might not exist } @@ -542,7 +542,7 @@ async function renameWithRetry(source: string, target: string, startTime: number if (!stat.isFile()) { abortRetry = true; // if target is not a file, EPERM error may be raised and we should not attempt to retry } - } catch (error) { + } catch { // Ignore } @@ -601,7 +601,7 @@ async function doCopy(source: string, target: string, payload: ICopyPayload): Pr if (payload.options.preserveSymlinks) { try { return await doCopySymlink(source, target, payload); - } catch (error) { + } catch { // in any case of an error fallback to normal copy via dereferencing } } @@ -713,7 +713,7 @@ export async function realcase(path: string, token?: CancellationToken): Promise } } } - } catch (error) { + } catch { // silently ignore error } @@ -727,7 +727,7 @@ async function realpath(path: string): Promise { // drives to be resolved to their target on Windows // https://github.com/microsoft/vscode/issues/118562 return await promisify(fs.realpath)(path); - } catch (error) { + } catch { // We hit an error calling fs.realpath(). Since fs.realpath() is doing some path normalization // we now do a similar normalization and then try again if we can access the path with read @@ -748,7 +748,7 @@ async function realpath(path: string): Promise { export function realpathSync(path: string): string { try { return fs.realpathSync(path); - } catch (error) { + } catch { // We hit an error calling fs.realpathSync(). Since fs.realpathSync() is doing some path normalization // we now do a similar normalization and then try again if we can access the path with read diff --git a/code/src/vs/base/node/ports.ts b/code/src/vs/base/node/ports.ts index a237f433ea0..1bd338695e0 100644 --- a/code/src/vs/base/node/ports.ts +++ b/code/src/vs/base/node/ports.ts @@ -151,6 +151,10 @@ export function isPortFree(port: number, timeout: number): Promise { return findFreePortFaster(port, 0, timeout).then(port => port !== 0); } +interface ServerError { + code?: string; +} + /** * Uses listen instead of connect. Is faster, but if there is another listener on 0.0.0.0 then this will take 127.0.0.1 from that listener. */ @@ -178,8 +182,8 @@ export function findFreePortFaster(startPort: number, giveUpAfter: number, timeo server.on('listening', () => { doResolve(startPort, resolve); }); - server.on('error', err => { - if (err && ((err).code === 'EADDRINUSE' || (err).code === 'EACCES') && (countTried < giveUpAfter)) { + server.on('error', (err: ServerError) => { + if (err && (err.code === 'EADDRINUSE' || err.code === 'EACCES') && (countTried < giveUpAfter)) { startPort++; countTried++; server.listen(startPort, hostname); diff --git a/code/src/vs/base/node/processes.ts b/code/src/vs/base/node/processes.ts index ca1b94e5590..ed474e121e1 100644 --- a/code/src/vs/base/node/processes.ts +++ b/code/src/vs/base/node/processes.ts @@ -21,7 +21,7 @@ export type ErrorCallback = (error?: any) => void; export type ProgressCallback = (progress: T) => void; -export function getWindowsShell(env = processCommon.env as Platform.IProcessEnvironment): string { +export function getWindowsShell(env = processCommon.env): string { return env['comspec'] || 'cmd.exe'; } @@ -83,7 +83,7 @@ async function fileExistsDefault(path: string): Promise { return false; } -export async function findExecutable(command: string, cwd?: string, paths?: string[], env: Platform.IProcessEnvironment = processCommon.env as Platform.IProcessEnvironment, fileExists: (path: string) => Promise = fileExistsDefault): Promise { +export async function findExecutable(command: string, cwd?: string, paths?: string[], env: Platform.IProcessEnvironment = processCommon.env, fileExists: (path: string) => Promise = fileExistsDefault): Promise { // If we have an absolute path then we take it. if (path.isAbsolute(command)) { return await fileExists(command) ? command : undefined; diff --git a/code/src/vs/base/node/ps.ts b/code/src/vs/base/node/ps.ts index 6079d798d8c..88c3db0ef29 100644 --- a/code/src/vs/base/node/ps.ts +++ b/code/src/vs/base/node/ps.ts @@ -9,19 +9,17 @@ import { FileAccess } from '../common/network.js'; import { ProcessItem } from '../common/processes.js'; import { isWindows } from '../common/platform.js'; -export function listProcesses(rootPid: number): Promise { +export const JS_FILENAME_PATTERN = /[a-zA-Z-]+\.js\b/g; +export function listProcesses(rootPid: number): Promise { return new Promise((resolve, reject) => { - let rootItem: ProcessItem | undefined; const map = new Map(); const totalMemory = totalmem(); function addToTree(pid: number, ppid: number, cmd: string, load: number, mem: number) { - const parent = map.get(ppid); if (pid === rootPid || parent) { - const item: ProcessItem = { name: findName(cmd), cmd, @@ -49,7 +47,6 @@ export function listProcesses(rootPid: number): Promise { } function findName(cmd: string): string { - const UTILITY_NETWORK_HINT = /--utility-sub-type=network/i; const WINDOWS_CRASH_REPORTER = /--crashes-directory/i; const WINPTY = /\\pipe\\winpty-control/i; @@ -88,19 +85,17 @@ export function listProcesses(rootPid: number): Promise { return matches[1]; } - // find all xxxx.js - const JS = /[a-zA-Z-]+\.js/g; - let result = ''; - do { - matches = JS.exec(cmd); - if (matches) { - result += matches + ' '; - } - } while (matches); + if (cmd.indexOf('node ') < 0 && cmd.indexOf('node.exe') < 0) { + let result = ''; // find all xyz.js + do { + matches = JS_FILENAME_PATTERN.exec(cmd); + if (matches) { + result += matches + ' '; + } + } while (matches); - if (result) { - if (cmd.indexOf('node ') < 0 && cmd.indexOf('node.exe') < 0) { - return `electron-nodejs (${result})`; + if (result) { + return `electron-nodejs (${result.trim()})`; } } @@ -108,7 +103,6 @@ export function listProcesses(rootPid: number): Promise { } if (process.platform === 'win32') { - const cleanUNCPrefix = (value: string): string => { if (value.indexOf('\\\\?\\') === 0) { return value.substring(4); @@ -167,8 +161,12 @@ export function listProcesses(rootPid: number): Promise { }); }, windowsProcessTree.ProcessDataFlag.CommandLine | windowsProcessTree.ProcessDataFlag.Memory); }); - } else { // OS X & Linux + } + + // OS X & Linux + else { function calculateLinuxCpuUsage() { + // Flatten rootItem to get a list of all VSCode processes let processes = [rootItem]; const pids: number[] = []; diff --git a/code/src/vs/base/node/unc.ts b/code/src/vs/base/node/unc.ts index e51fed72d1a..4076e601262 100644 --- a/code/src/vs/base/node/unc.ts +++ b/code/src/vs/base/node/unc.ts @@ -12,12 +12,12 @@ export function getUNCHostAllowlist(): string[] { return []; } -function processUNCHostAllowlist(): Set { +function processUNCHostAllowlist(): Set | undefined { // The property `process.uncHostAllowlist` is not available in official node.js // releases, only in our own builds, so we have to probe for availability - return (process as any).uncHostAllowlist; + return (process as unknown as { uncHostAllowlist?: Set }).uncHostAllowlist; } export function addUNCHostToAllowlist(allowedHost: string | string[]): void { @@ -90,7 +90,7 @@ export function disableUNCAccessRestrictions(): void { return; } - (process as any).restrictUNCAccess = false; + (process as unknown as { restrictUNCAccess?: boolean }).restrictUNCAccess = false; } export function isUNCAccessRestrictionsDisabled(): boolean { @@ -98,5 +98,5 @@ export function isUNCAccessRestrictionsDisabled(): boolean { return true; } - return (process as any).restrictUNCAccess === false; + return (process as unknown as { restrictUNCAccess?: boolean }).restrictUNCAccess === false; } diff --git a/code/src/vs/base/parts/contextmenu/electron-browser/contextmenu.ts b/code/src/vs/base/parts/contextmenu/electron-browser/contextmenu.ts index 3eab2e24b9d..7b1971d0aa7 100644 --- a/code/src/vs/base/parts/contextmenu/electron-browser/contextmenu.ts +++ b/code/src/vs/base/parts/contextmenu/electron-browser/contextmenu.ts @@ -13,13 +13,16 @@ export function popup(items: IContextMenuItem[], options?: IPopupOptions, onHide const contextMenuId = contextMenuIdPool++; const onClickChannel = `vscode:onContextMenu${contextMenuId}`; - const onClickChannelHandler = (event: unknown, itemId: number, context: IContextMenuEvent) => { + const onClickChannelHandler = (_event: unknown, ...args: unknown[]) => { + const itemId = args[0] as number; + const context = args[1] as IContextMenuEvent; const item = processedItems[itemId]; item.click?.(context); }; ipcRenderer.once(onClickChannel, onClickChannelHandler); - ipcRenderer.once(CONTEXT_MENU_CLOSE_CHANNEL, (event: unknown, closedContextMenuId: number) => { + ipcRenderer.once(CONTEXT_MENU_CLOSE_CHANNEL, (_event: unknown, ...args: unknown[]) => { + const closedContextMenuId = args[0] as number; if (closedContextMenuId !== contextMenuId) { return; } diff --git a/code/src/vs/base/parts/ipc/common/ipc.ts b/code/src/vs/base/parts/ipc/common/ipc.ts index 5e0764d9849..b22e9fd9e42 100644 --- a/code/src/vs/base/parts/ipc/common/ipc.ts +++ b/code/src/vs/base/parts/ipc/common/ipc.ts @@ -98,7 +98,7 @@ interface IHandler { export interface IMessagePassingProtocol { send(buffer: VSBuffer): void; - onMessage: Event; + readonly onMessage: Event; /** * Wait for the write buffer (if applicable) to become empty. */ @@ -334,7 +334,7 @@ export class ChannelServer implements IChannelServer(); - constructor(private protocol: IMessagePassingProtocol, private ctx: TContext, private logger: IIPCLogger | null = null, private timeoutDelay: number = 1000) { + constructor(private protocol: IMessagePassingProtocol, private ctx: TContext, private logger: IIPCLogger | null = null, private timeoutDelay = 1000) { this.protocolListener = this.protocol.onMessage(msg => this.onRawMessage(msg)); this.sendResponse({ type: ResponseType.Initialize }); } @@ -532,11 +532,11 @@ export interface IIPCLogger { export class ChannelClient implements IChannelClient, IDisposable { - private isDisposed: boolean = false; + private isDisposed = false; private state: State = State.Uninitialized; private activeRequests = new Set(); private handlers = new Map(); - private lastRequestId: number = 0; + private lastRequestId = 0; private protocolListener: IDisposable | null; private logger: IIPCLogger | null; @@ -596,7 +596,7 @@ export class ChannelClient implements IChannelClient, IDisposable { case ResponseType.PromiseError: { this.handlers.delete(id); const error = new Error(response.data.message); - (error).stack = Array.isArray(response.data.stack) ? response.data.stack.join('\n') : response.data.stack; + error.stack = Array.isArray(response.data.stack) ? response.data.stack.join('\n') : response.data.stack; error.name = response.data.name; e(error); break; @@ -682,6 +682,7 @@ export class ChannelClient implements IChannelClient, IDisposable { this.activeRequests.delete(emitter); this.sendRequest({ id, type: RequestType.EventDispose }); } + this.handlers.delete(id); } }); @@ -784,7 +785,7 @@ export class ChannelClient implements IChannelClient, IDisposable { export interface ClientConnectionEvent { protocol: IMessagePassingProtocol; - onDidClientDisconnect: Event; + readonly onDidClientDisconnect: Event; } interface Connection extends Client { @@ -995,7 +996,7 @@ export class IPCClient implements IChannelClient, IChannelSer } getChannel(channelName: string): T { - return this.channelClient.getChannel(channelName) as T; + return this.channelClient.getChannel(channelName); } registerChannel(channelName: string, channel: IServerChannel): void { @@ -1106,7 +1107,7 @@ export namespace ProxyChannel { export function fromService(service: unknown, disposables: DisposableStore, options?: ICreateServiceChannelOptions): IServerChannel { const handler = service as { [key: string]: unknown }; - const disableMarshalling = options && options.disableMarshalling; + const disableMarshalling = options?.disableMarshalling; // Buffer any event that should be supported by // iterating over all property keys and finding them @@ -1183,7 +1184,7 @@ export namespace ProxyChannel { } export function toService(channel: IChannel, options?: ICreateProxyServiceOptions): T { - const disableMarshalling = options && options.disableMarshalling; + const disableMarshalling = options?.disableMarshalling; return new Proxy({}, { get(_target: T, propKey: PropertyKey) { diff --git a/code/src/vs/base/parts/ipc/electron-main/ipcMain.ts b/code/src/vs/base/parts/ipc/electron-main/ipcMain.ts index 0f8985406a6..0137b8924eb 100644 --- a/code/src/vs/base/parts/ipc/electron-main/ipcMain.ts +++ b/code/src/vs/base/parts/ipc/electron-main/ipcMain.ts @@ -104,7 +104,7 @@ class ValidatedIpcMain implements Event.NodeEventEmitter { } private validateEvent(channel: string, event: electron.IpcMainEvent | electron.IpcMainInvokeEvent): boolean { - if (!channel || !channel.startsWith('vscode:')) { + if (!channel?.startsWith('vscode:')) { onUnexpectedError(`Refused to handle ipcMain event for channel '${channel}' because the channel is unknown.`); return false; // unexpected channel } @@ -128,6 +128,12 @@ class ValidatedIpcMain implements Event.NodeEventEmitter { return false; // unexpected URL } + if (process.env.VSCODE_DEV) { + if (url === process.env.DEV_WINDOW_SRC && (host === 'localhost' || host.startsWith('localhost:'))) { + return true; // development support where the window is served from localhost + } + } + if (host !== VSCODE_AUTHORITY) { onUnexpectedError(`Refused to handle ipcMain event for channel '${channel}' because of a bad origin of '${host}'.`); return false; // unexpected sender diff --git a/code/src/vs/base/parts/ipc/node/ipc.cp.ts b/code/src/vs/base/parts/ipc/node/ipc.cp.ts index 2c5ef506bf1..73630126fbc 100644 --- a/code/src/vs/base/parts/ipc/node/ipc.cp.ts +++ b/code/src/vs/base/parts/ipc/node/ipc.cp.ts @@ -93,7 +93,7 @@ export class Client implements IChannelClient, IDisposable { readonly onDidProcessExit = this._onDidProcessExit.event; constructor(private modulePath: string, private options: IIPCOptions) { - const timeout = options && options.timeout ? options.timeout : 60000; + const timeout = options.timeout || 60000; this.disposeDelayer = new Delayer(timeout); this.child = null; this._client = null; @@ -174,24 +174,24 @@ export class Client implements IChannelClient, IDisposable { private get client(): IPCClient { if (!this._client) { - const args = this.options && this.options.args ? this.options.args : []; + const args = this.options.args || []; const forkOpts: ForkOptions = Object.create(null); forkOpts.env = { ...deepClone(process.env), 'VSCODE_PARENT_PID': String(process.pid) }; - if (this.options && this.options.env) { + if (this.options.env) { forkOpts.env = { ...forkOpts.env, ...this.options.env }; } - if (this.options && this.options.freshExecArgv) { + if (this.options.freshExecArgv) { forkOpts.execArgv = []; } - if (this.options && typeof this.options.debug === 'number') { + if (typeof this.options.debug === 'number') { forkOpts.execArgv = ['--nolazy', '--inspect=' + this.options.debug]; } - if (this.options && typeof this.options.debugBrk === 'number') { + if (typeof this.options.debugBrk === 'number') { forkOpts.execArgv = ['--nolazy', '--inspect-brk=' + this.options.debugBrk]; } @@ -221,7 +221,7 @@ export class Client implements IChannelClient, IDisposable { }); const sender = this.options.useQueue ? createQueuedSender(this.child) : this.child; - const send = (r: VSBuffer) => this.child && this.child.connected && sender.send((r.buffer).toString('base64')); + const send = (r: VSBuffer) => this.child?.connected && sender.send((r.buffer).toString('base64')); const onMessage = onMessageEmitter.event; const protocol = { send, onMessage }; diff --git a/code/src/vs/base/parts/ipc/node/ipc.mp.ts b/code/src/vs/base/parts/ipc/node/ipc.mp.ts index df9cd52c58a..ade456373c0 100644 --- a/code/src/vs/base/parts/ipc/node/ipc.mp.ts +++ b/code/src/vs/base/parts/ipc/node/ipc.mp.ts @@ -20,7 +20,7 @@ class Protocol implements IMessagePassingProtocol { constructor(private port: MessagePortMain) { this.onMessage = Event.fromNodeEventEmitter(this.port, 'message', (e: MessageEvent) => { if (e.data) { - return VSBuffer.wrap(e.data); + return VSBuffer.wrap(e.data as Uint8Array); } return VSBuffer.alloc(0); }); diff --git a/code/src/vs/base/parts/ipc/node/ipc.net.ts b/code/src/vs/base/parts/ipc/node/ipc.net.ts index 82da99e5bd6..9a508286e23 100644 --- a/code/src/vs/base/parts/ipc/node/ipc.net.ts +++ b/code/src/vs/base/parts/ipc/node/ipc.net.ts @@ -22,12 +22,14 @@ export function upgradeToISocket(req: http.IncomingMessage, socket: Socket, { debugLabel, skipWebSocketFrames = false, disableWebSocketCompression = false, + enableMessageSplitting = true, }: { debugLabel: string; skipWebSocketFrames?: boolean; disableWebSocketCompression?: boolean; + enableMessageSplitting?: boolean; }): NodeSocket | WebSocketNodeSocket | undefined { - if (req.headers['upgrade'] === undefined || req.headers['upgrade'].toLowerCase() !== 'websocket') { + if (req.headers.upgrade === undefined || req.headers.upgrade.toLowerCase() !== 'websocket') { socket.end('HTTP/1.1 400 Bad Request'); return; } @@ -78,7 +80,7 @@ export function upgradeToISocket(req: http.IncomingMessage, socket: Socket, { if (skipWebSocketFrames) { return new NodeSocket(socket, debugLabel); } else { - return new WebSocketNodeSocket(new NodeSocket(socket, debugLabel), permessageDeflate, null, true); + return new WebSocketNodeSocket(new NodeSocket(socket, debugLabel), permessageDeflate, null, true, enableMessageSplitting); } } @@ -95,20 +97,20 @@ export class NodeSocket implements ISocket { public readonly debugLabel: string; public readonly socket: Socket; - private readonly _errorListener: (err: any) => void; + private readonly _errorListener: (err: NodeJS.ErrnoException) => void; private readonly _closeListener: (hadError: boolean) => void; private readonly _endListener: () => void; private _canWrite = true; - public traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void { + public traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | unknown): void { SocketDiagnostics.traceSocketEvent(this.socket, this.debugLabel, type, data); } - constructor(socket: Socket, debugLabel: string = '') { + constructor(socket: Socket, debugLabel = '') { this.debugLabel = debugLabel; this.socket = socket; this.traceSocketEvent(SocketDiagnosticsEventType.Created, { type: 'NodeSocket' }); - this._errorListener = (err: any) => { + this._errorListener = (err: NodeJS.ErrnoException) => { this.traceSocketEvent(SocketDiagnosticsEventType.Error, { code: err?.code, message: err?.message }); if (err) { if (err.code === 'EPIPE') { @@ -198,7 +200,7 @@ export class NodeSocket implements ISocket { // > accept and buffer chunk even if it has not been allowed to drain. try { this.traceSocketEvent(SocketDiagnosticsEventType.Write, buffer); - this.socket.write(buffer.buffer, (err: any) => { + this.socket.write(buffer.buffer, (err: NodeJS.ErrnoException | null | undefined) => { if (err) { if (err.code === 'EPIPE') { // An EPIPE exception at the wrong time can lead to a renderer process crash @@ -277,7 +279,7 @@ const enum ReadState { } interface ISocketTracer { - traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void; + traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | unknown): void; } interface FrameOptions { @@ -295,7 +297,8 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT private readonly _incomingData: ChunkStream; private readonly _onData = this._register(new Emitter()); private readonly _onClose = this._register(new Emitter()); - private _isEnded: boolean = false; + private readonly _maxSocketMessageLength: number; + private _isEnded = false; private readonly _state = { state: ReadState.PeekHeader, @@ -315,7 +318,7 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT return this._flowManager.recordedInflateBytes; } - public traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void { + public traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | unknown): void { this.socket.traceSocketEvent(type, data); } @@ -331,9 +334,10 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT * @param inflateBytes "Seed" zlib inflate with these bytes. * @param recordInflateBytes Record all bytes sent to inflate */ - constructor(socket: NodeSocket, permessageDeflate: boolean, inflateBytes: VSBuffer | null, recordInflateBytes: boolean) { + constructor(socket: NodeSocket, permessageDeflate: boolean, inflateBytes: VSBuffer | null, recordInflateBytes: boolean, enableMessageSplitting = true) { super(); this.socket = socket; + this._maxSocketMessageLength = enableMessageSplitting ? Constants.MaxWebSocketMessageLength : Infinity; this.traceSocketEvent(SocketDiagnosticsEventType.Created, { type: 'WebSocketNodeSocket', permessageDeflate, inflateBytesLength: inflateBytes?.byteLength || 0, recordInflateBytes }); this._flowManager = this._register(new WebSocketFlowManager( this, @@ -404,8 +408,8 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT let start = 0; while (start < buffer.byteLength) { - this._flowManager.writeMessage(buffer.slice(start, Math.min(start + Constants.MaxWebSocketMessageLength, buffer.byteLength)), { compressed: true, opcode: 0x02 /* Binary frame */ }); - start += Constants.MaxWebSocketMessageLength; + this._flowManager.writeMessage(buffer.slice(start, Math.min(start + this._maxSocketMessageLength, buffer.byteLength)), { compressed: true, opcode: 0x02 /* Binary frame */ }); + start += this._maxSocketMessageLength; } } @@ -726,8 +730,8 @@ class ZlibInflateStream extends Disposable { ) { super(); this._zlibInflate = createInflateRaw(options); - this._zlibInflate.on('error', (err) => { - this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateError, { message: err?.message, code: (err)?.code }); + this._zlibInflate.on('error', (err: Error) => { + this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateError, { message: err?.message, code: (err as NodeJS.ErrnoException)?.code }); this._onError.fire(err); }); this._zlibInflate.on('data', (data: Buffer) => { @@ -779,8 +783,8 @@ class ZlibDeflateStream extends Disposable { this._zlibDeflate = createDeflateRaw({ windowBits: 15 }); - this._zlibDeflate.on('error', (err) => { - this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibDeflateError, { message: err?.message, code: (err)?.code }); + this._zlibDeflate.on('error', (err: Error) => { + this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibDeflateError, { message: err?.message, code: (err as NodeJS.ErrnoException)?.code }); this._onError.fire(err); }); this._zlibDeflate.on('data', (data: Buffer) => { @@ -837,7 +841,7 @@ function unmask(buffer: VSBuffer, mask: number): void { // Read this before there's any chance it is overwritten // Related to https://github.com/microsoft/vscode/issues/30624 -export const XDG_RUNTIME_DIR = process.env['XDG_RUNTIME_DIR']; +export const XDG_RUNTIME_DIR = process.env['XDG_RUNTIME_DIR']; const safeIpcPathLengths: { [platform: number]: number } = { [Platform.Linux]: 107, @@ -930,28 +934,35 @@ export class Server extends IPCServer { export function serve(port: number): Promise; export function serve(namedPipe: string): Promise; -export function serve(hook: any): Promise { - return new Promise((c, e) => { +export function serve(hook: number | string): Promise { + return new Promise((resolve, reject) => { const server = createServer(); - server.on('error', e); + server.on('error', reject); server.listen(hook, () => { - server.removeListener('error', e); - c(new Server(server)); + server.removeListener('error', reject); + resolve(new Server(server)); }); }); } export function connect(options: { host: string; port: number }, clientId: string): Promise; -export function connect(port: number, clientId: string): Promise; export function connect(namedPipe: string, clientId: string): Promise; -export function connect(hook: any, clientId: string): Promise { - return new Promise((c, e) => { - const socket = createConnection(hook, () => { - socket.removeListener('error', e); - c(Client.fromSocket(new NodeSocket(socket, `ipc-client${clientId}`), clientId)); - }); +export function connect(hook: { host: string; port: number } | string, clientId: string): Promise { + return new Promise((resolve, reject) => { + let socket: Socket; + + const callbackHandler = () => { + socket.removeListener('error', reject); + resolve(Client.fromSocket(new NodeSocket(socket, `ipc-client${clientId}`), clientId)); + }; + + if (typeof hook === 'string') { + socket = createConnection(hook, callbackHandler); + } else { + socket = createConnection(hook, callbackHandler); + } - socket.once('error', e); + socket.once('error', reject); }); } diff --git a/code/src/vs/base/parts/ipc/test/common/ipc.test.ts b/code/src/vs/base/parts/ipc/test/common/ipc.test.ts index 1d5029273d8..8e937c51f34 100644 --- a/code/src/vs/base/parts/ipc/test/common/ipc.test.ts +++ b/code/src/vs/base/parts/ipc/test/common/ipc.test.ts @@ -108,7 +108,7 @@ interface ITestService { marshall(uri: URI): Promise; context(): Promise; - onPong: Event; + readonly onPong: Event; } class TestService implements ITestService { diff --git a/code/src/vs/base/parts/ipc/test/node/ipc.net.test.ts b/code/src/vs/base/parts/ipc/test/node/ipc.net.test.ts index d13c8a9dce5..a1bc9a5749a 100644 --- a/code/src/vs/base/parts/ipc/test/node/ipc.net.test.ts +++ b/code/src/vs/base/parts/ipc/test/node/ipc.net.test.ts @@ -84,10 +84,12 @@ class Ether { private _ba: Buffer[]; public get a(): Socket { + // eslint-disable-next-line local/code-no-any-casts return this._a; } public get b(): Socket { + // eslint-disable-next-line local/code-no-any-casts return this._b; } @@ -647,6 +649,7 @@ suite('WebSocketNodeSocket', () => { async function testReading(frames: number[][], permessageDeflate: boolean): Promise { const disposables = new DisposableStore(); const socket = new FakeNodeSocket(); + // eslint-disable-next-line local/code-no-any-casts const webSocket = disposables.add(new WebSocketNodeSocket(socket, permessageDeflate, null, false)); const barrier = new Barrier(); @@ -779,6 +782,7 @@ suite('WebSocketNodeSocket', () => { const disposables = new DisposableStore(); const socket = new FakeNodeSocket(); + // eslint-disable-next-line local/code-no-any-casts const webSocket = disposables.add(new WebSocketNodeSocket(socket, false, null, false)); let receivedData: string = ''; diff --git a/code/src/vs/base/parts/ipc/test/node/testService.ts b/code/src/vs/base/parts/ipc/test/node/testService.ts index 99b01eee2d6..099c4ea6ab9 100644 --- a/code/src/vs/base/parts/ipc/test/node/testService.ts +++ b/code/src/vs/base/parts/ipc/test/node/testService.ts @@ -12,7 +12,7 @@ export interface IMarcoPoloEvent { } export interface ITestService { - onMarco: Event; + readonly onMarco: Event; marco(): Promise; pong(ping: string): Promise<{ incoming: string; outgoing: string }>; cancelMe(): Promise; @@ -21,7 +21,7 @@ export interface ITestService { export class TestService implements ITestService { private readonly _onMarco = new Emitter(); - onMarco: Event = this._onMarco.event; + readonly onMarco: Event = this._onMarco.event; marco(): Promise { this._onMarco.fire({ answer: 'polo' }); diff --git a/code/src/vs/base/parts/request/test/electron-main/request.test.ts b/code/src/vs/base/parts/request/test/electron-main/request.test.ts index 0eea7d9a2fb..e653570c5fc 100644 --- a/code/src/vs/base/parts/request/test/electron-main/request.test.ts +++ b/code/src/vs/base/parts/request/test/electron-main/request.test.ts @@ -10,6 +10,7 @@ import { CancellationToken, CancellationTokenSource } from '../../../../common/c import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../test/common/utils.js'; import { request } from '../../common/requestImpl.js'; import { streamToBuffer } from '../../../../common/buffer.js'; +import { runWithFakedTimers } from '../../../../test/common/timeTravelScheduler.js'; suite('Request', () => { @@ -83,32 +84,36 @@ suite('Request', () => { }); test('timeout', async () => { - try { - await request({ - type: 'GET', - url: `http://127.0.0.1:${port}/noreply`, - timeout: 123, - }, CancellationToken.None); - assert.fail('Should fail with timeout'); - } catch (err) { - assert.strictEqual(err.message, 'Fetch timeout: 123ms'); - } + return runWithFakedTimers({}, async () => { + try { + await request({ + type: 'GET', + url: `http://127.0.0.1:${port}/noreply`, + timeout: 123, + }, CancellationToken.None); + assert.fail('Should fail with timeout'); + } catch (err) { + assert.strictEqual(err.message, 'Fetch timeout: 123ms'); + } + }); }); test('cancel', async () => { - try { - const source = new CancellationTokenSource(); - const res = request({ - type: 'GET', - url: `http://127.0.0.1:${port}/noreply`, - }, source.token); - await new Promise(resolve => setTimeout(resolve, 100)); - source.cancel(); - await res; - assert.fail('Should fail with cancellation'); - } catch (err) { - assert.strictEqual(err.message, 'Canceled'); - } + return runWithFakedTimers({}, async () => { + try { + const source = new CancellationTokenSource(); + const res = request({ + type: 'GET', + url: `http://127.0.0.1:${port}/noreply`, + }, source.token); + await new Promise(resolve => setTimeout(resolve, 100)); + source.cancel(); + await res; + assert.fail('Should fail with cancellation'); + } catch (err) { + assert.strictEqual(err.message, 'Canceled'); + } + }); }); ensureNoDisposablesAreLeakedInTestSuite(); diff --git a/code/src/vs/base/parts/sandbox/electron-browser/electronTypes.ts b/code/src/vs/base/parts/sandbox/electron-browser/electronTypes.ts index cbf98ff13c5..fee511ba913 100644 --- a/code/src/vs/base/parts/sandbox/electron-browser/electronTypes.ts +++ b/code/src/vs/base/parts/sandbox/electron-browser/electronTypes.ts @@ -66,17 +66,17 @@ export interface IpcRenderer { * returned by `invoke` will reject. However, the `Error` object in the renderer * process will not be the same as the one thrown in the main process. */ - invoke(channel: string, ...args: any[]): Promise; + invoke(channel: string, ...args: unknown[]): Promise; /** * Listens to `channel`, when a new message arrives `listener` would be called with * `listener(event, args...)`. */ - on(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): this; + on(channel: string, listener: (event: IpcRendererEvent, ...args: unknown[]) => void): this; /** * Adds a one time `listener` function for the event. This `listener` is invoked * only the next time a message is sent to `channel`, after which it is removed. */ - once(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): this; + once(channel: string, listener: (event: IpcRendererEvent, ...args: unknown[]) => void): this; // Note: API with `Transferable` intentionally commented out because you // cannot transfer these when `contextIsolation: true`. // /** @@ -92,12 +92,12 @@ export interface IpcRenderer { // * For more information on using `MessagePort` and `MessageChannel`, see the MDN // * documentation. // */ - // postMessage(channel: string, message: any, transfer?: MessagePort[]): void; + // postMessage(channel: string, message: unknown, transfer?: MessagePort[]): void; /** * Removes the specified `listener` from the listener array for the specified * `channel`. */ - removeListener(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): this; + removeListener(channel: string, listener: (event: IpcRendererEvent, ...args: unknown[]) => void): this; /** * Send an asynchronous message to the main process via `channel`, along with * arguments. Arguments will be serialized with the Structured Clone Algorithm, @@ -122,7 +122,7 @@ export interface IpcRenderer { * If you want to receive a single response from the main process, like the result * of a method call, consider using `ipcRenderer.invoke`. */ - send(channel: string, ...args: any[]): void; + send(channel: string, ...args: unknown[]): void; } export interface WebFrame { diff --git a/code/src/vs/base/parts/sandbox/electron-browser/globals.ts b/code/src/vs/base/parts/sandbox/electron-browser/globals.ts index cd1e5387864..bd43ae40457 100644 --- a/code/src/vs/base/parts/sandbox/electron-browser/globals.ts +++ b/code/src/vs/base/parts/sandbox/electron-browser/globals.ts @@ -115,7 +115,18 @@ export interface ISandboxContext { resolveConfiguration(): Promise; } -const vscodeGlobal = (globalThis as any).vscode; +interface ISandboxGlobal { + vscode: { + readonly ipcRenderer: IpcRenderer; + readonly ipcMessagePort: IpcMessagePort; + readonly webFrame: WebFrame; + readonly process: ISandboxNodeProcess; + readonly context: ISandboxContext; + readonly webUtils: WebUtils; + }; +} + +const vscodeGlobal = (globalThis as unknown as ISandboxGlobal).vscode; export const ipcRenderer: IpcRenderer = vscodeGlobal.ipcRenderer; export const ipcMessagePort: IpcMessagePort = vscodeGlobal.ipcMessagePort; export const webFrame: WebFrame = vscodeGlobal.webFrame; diff --git a/code/src/vs/base/parts/sandbox/electron-browser/preload-aux.ts b/code/src/vs/base/parts/sandbox/electron-browser/preload-aux.ts index 83b7e91fc6f..0299893e6f9 100644 --- a/code/src/vs/base/parts/sandbox/electron-browser/preload-aux.ts +++ b/code/src/vs/base/parts/sandbox/electron-browser/preload-aux.ts @@ -8,7 +8,7 @@ const { ipcRenderer, webFrame, contextBridge } = require('electron'); function validateIPC(channel: string): true | never { - if (!channel || !channel.startsWith('vscode:')) { + if (!channel?.startsWith('vscode:')) { throw new Error(`Unsupported event IPC channel '${channel}'`); } @@ -23,13 +23,13 @@ */ ipcRenderer: { - send(channel: string, ...args: any[]): void { + send(channel: string, ...args: unknown[]): void { if (validateIPC(channel)) { ipcRenderer.send(channel, ...args); } }, - invoke(channel: string, ...args: any[]): Promise { + invoke(channel: string, ...args: unknown[]): Promise { validateIPC(channel); return ipcRenderer.invoke(channel, ...args); diff --git a/code/src/vs/base/parts/sandbox/electron-browser/preload.ts b/code/src/vs/base/parts/sandbox/electron-browser/preload.ts index 7ff6c24d2c7..b3b9a17a6ff 100644 --- a/code/src/vs/base/parts/sandbox/electron-browser/preload.ts +++ b/code/src/vs/base/parts/sandbox/electron-browser/preload.ts @@ -14,7 +14,7 @@ //#region Utilities function validateIPC(channel: string): true | never { - if (!channel || !channel.startsWith('vscode:')) { + if (!channel?.startsWith('vscode:')) { throw new Error(`Unsupported event IPC channel '${channel}'`); } @@ -109,19 +109,19 @@ ipcRenderer: { - send(channel: string, ...args: any[]): void { + send(channel: string, ...args: unknown[]): void { if (validateIPC(channel)) { ipcRenderer.send(channel, ...args); } }, - invoke(channel: string, ...args: any[]): Promise { + invoke(channel: string, ...args: unknown[]): Promise { validateIPC(channel); return ipcRenderer.invoke(channel, ...args); }, - on(channel: string, listener: (event: Electron.IpcRendererEvent, ...args: any[]) => void) { + on(channel: string, listener: (event: Electron.IpcRendererEvent, ...args: unknown[]) => void) { validateIPC(channel); ipcRenderer.on(channel, listener); @@ -129,7 +129,7 @@ return this; }, - once(channel: string, listener: (event: Electron.IpcRendererEvent, ...args: any[]) => void) { + once(channel: string, listener: (event: Electron.IpcRendererEvent, ...args: unknown[]) => void) { validateIPC(channel); ipcRenderer.once(channel, listener); @@ -137,7 +137,7 @@ return this; }, - removeListener(channel: string, listener: (event: Electron.IpcRendererEvent, ...args: any[]) => void) { + removeListener(channel: string, listener: (event: Electron.IpcRendererEvent, ...args: unknown[]) => void) { validateIPC(channel); ipcRenderer.removeListener(channel, listener); @@ -215,7 +215,7 @@ return process.getProcessMemoryInfo(); }, - on(type: string, callback: (...args: any[]) => void): void { + on(type: string, callback: (...args: unknown[]) => void): void { process.on(type, callback); } }, @@ -246,16 +246,10 @@ } }; - // Use `contextBridge` APIs to expose globals to VSCode - // only if context isolation is enabled, otherwise just - // add to the DOM global. - if (process.contextIsolated) { - try { - contextBridge.exposeInMainWorld('vscode', globals); - } catch (error) { - console.error(error); - } - } else { - (window as any).vscode = globals; + try { + // Use `contextBridge` APIs to expose globals to VSCode + contextBridge.exposeInMainWorld('vscode', globals); + } catch (error) { + console.error(error); } }()); diff --git a/code/src/vs/base/parts/sandbox/node/electronTypes.ts b/code/src/vs/base/parts/sandbox/node/electronTypes.ts index 37629888c19..4ed90e5b4d0 100644 --- a/code/src/vs/base/parts/sandbox/node/electronTypes.ts +++ b/code/src/vs/base/parts/sandbox/node/electronTypes.ts @@ -31,7 +31,7 @@ export interface MessagePortMain extends NodeJS.EventEmitter { * Sends a message from the port, and optionally, transfers ownership of objects to * other browsing contexts. */ - postMessage(message: any, transfer?: MessagePortMain[]): void; + postMessage(message: unknown, transfer?: MessagePortMain[]): void; /** * Starts the sending of messages queued on the port. Messages will be queued until * this method is called. @@ -40,7 +40,7 @@ export interface MessagePortMain extends NodeJS.EventEmitter { } export interface MessageEvent { - data: any; + data: unknown; ports: MessagePortMain[]; } @@ -60,7 +60,7 @@ export interface ParentPort extends NodeJS.EventEmitter { /** * Sends a message from the process to its parent. */ - postMessage(message: any): void; + postMessage(message: unknown): void; } export interface UtilityNodeJSProcess extends NodeJS.Process { diff --git a/code/src/vs/base/parts/storage/common/storage.ts b/code/src/vs/base/parts/storage/common/storage.ts index 9a3dab6dc70..38ed1e08c93 100644 --- a/code/src/vs/base/parts/storage/common/storage.ts +++ b/code/src/vs/base/parts/storage/common/storage.ts @@ -349,7 +349,7 @@ export class Storage extends Disposable implements IStorage { // the DB is not healthy. try { await this.doFlush(0 /* as soon as possible */); - } catch (error) { + } catch { // Ignore } diff --git a/code/src/vs/base/parts/storage/node/storage.ts b/code/src/vs/base/parts/storage/node/storage.ts index 081d940bbdc..edd322f692b 100644 --- a/code/src/vs/base/parts/storage/node/storage.ts +++ b/code/src/vs/base/parts/storage/node/storage.ts @@ -118,7 +118,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { } // DELETE - if (toDelete && toDelete.size) { + if (toDelete?.size) { const keysChunks: (string[])[] = []; keysChunks.push([]); // seed with initial empty chunk @@ -245,7 +245,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { const connection = await this.whenConnected; const row = await this.get(connection, full ? 'PRAGMA integrity_check' : 'PRAGMA quick_check'); - const integrity = full ? (row as any)['integrity_check'] : (row as any)['quick_check']; + const integrity = full ? (row as { integrity_check: string }).integrity_check : (row as { quick_check: string }).quick_check; if (connection.isErroneous) { return `${integrity} (last error: ${connection.lastError})`; @@ -258,7 +258,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { return integrity; } - private async connect(path: string, retryOnBusy: boolean = true): Promise { + private async connect(path: string, retryOnBusy = true): Promise { this.logger.trace(`[storage ${this.name}] open(${path}, retryOnBusy: ${retryOnBusy})`); try { @@ -291,7 +291,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { await fs.promises.unlink(path); try { await Promises.rename(this.toBackupPath(path), path, false /* no retry */); - } catch (error) { + } catch { // ignore } diff --git a/code/src/vs/base/parts/storage/test/node/storage.integrationTest.ts b/code/src/vs/base/parts/storage/test/node/storage.integrationTest.ts index 994417ddc00..4db56c00c77 100644 --- a/code/src/vs/base/parts/storage/test/node/storage.integrationTest.ts +++ b/code/src/vs/base/parts/storage/test/node/storage.integrationTest.ts @@ -446,7 +446,7 @@ flakySuite('SQLite Storage Library', function () { const corruptDBPath = join(testdir, 'broken.db'); await Promises.writeFile(corruptDBPath, 'This is a broken DB'); - let expectedError: any; + let expectedError: Error | string | undefined = undefined; await testDBBasics(corruptDBPath, error => { expectedError = error; }); diff --git a/code/src/vs/base/test/browser/actionbar.test.ts b/code/src/vs/base/test/browser/actionbar.test.ts index 7ec25b1bf11..60f9902bc3f 100644 --- a/code/src/vs/base/test/browser/actionbar.test.ts +++ b/code/src/vs/base/test/browser/actionbar.test.ts @@ -7,6 +7,8 @@ import assert from 'assert'; import { ActionBar, prepareActions } from '../../browser/ui/actionbar/actionbar.js'; import { Action, Separator } from '../../common/actions.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../common/utils.js'; +import { createToggleActionViewItemProvider, ToggleActionViewItem, unthemedToggleStyles } from '../../browser/ui/toggle/toggle.js'; +import { ActionViewItem } from '../../browser/ui/actionbar/actionViewItems.js'; suite('Actionbar', () => { @@ -60,4 +62,110 @@ suite('Actionbar', () => { actionbar.clear(); assert.strictEqual(actionbar.hasAction(a1), false); }); + + suite('ToggleActionViewItemProvider', () => { + + test('renders toggle for actions with checked state', function () { + const container = document.createElement('div'); + const provider = createToggleActionViewItemProvider(unthemedToggleStyles); + const actionbar = store.add(new ActionBar(container, { + actionViewItemProvider: provider + })); + + const toggleAction = store.add(new Action('toggle', 'Toggle', undefined, true, undefined)); + toggleAction.checked = true; + + actionbar.push(toggleAction); + + // Verify that the action was rendered as a toggle + assert.strictEqual(actionbar.viewItems.length, 1); + assert(actionbar.viewItems[0] instanceof ToggleActionViewItem, 'Action with checked state should render as ToggleActionViewItem'); + }); + + test('renders button for actions without checked state', function () { + const container = document.createElement('div'); + const provider = createToggleActionViewItemProvider(unthemedToggleStyles); + const actionbar = store.add(new ActionBar(container, { + actionViewItemProvider: provider + })); + + const buttonAction = store.add(new Action('button', 'Button')); + + actionbar.push(buttonAction); + + // Verify that the action was rendered as a regular button (ActionViewItem) + assert.strictEqual(actionbar.viewItems.length, 1); + assert(actionbar.viewItems[0] instanceof ActionViewItem, 'Action without checked state should render as ActionViewItem'); + assert(!(actionbar.viewItems[0] instanceof ToggleActionViewItem), 'Action without checked state should not render as ToggleActionViewItem'); + }); + + test('handles mixed actions (toggles and buttons)', function () { + const container = document.createElement('div'); + const provider = createToggleActionViewItemProvider(unthemedToggleStyles); + const actionbar = store.add(new ActionBar(container, { + actionViewItemProvider: provider + })); + + const toggleAction = store.add(new Action('toggle', 'Toggle')); + toggleAction.checked = false; + const buttonAction = store.add(new Action('button', 'Button')); + + actionbar.push([toggleAction, buttonAction]); + + // Verify that we have both types of items + assert.strictEqual(actionbar.viewItems.length, 2); + assert(actionbar.viewItems[0] instanceof ToggleActionViewItem, 'First action should be a toggle'); + assert(actionbar.viewItems[1] instanceof ActionViewItem, 'Second action should be a button'); + assert(!(actionbar.viewItems[1] instanceof ToggleActionViewItem), 'Second action should not be a toggle'); + }); + + test('toggle state changes when action checked changes', function () { + const container = document.createElement('div'); + const provider = createToggleActionViewItemProvider(unthemedToggleStyles); + const actionbar = store.add(new ActionBar(container, { + actionViewItemProvider: provider + })); + + const toggleAction = store.add(new Action('toggle', 'Toggle')); + toggleAction.checked = false; + + actionbar.push(toggleAction); + + // Verify the toggle view item was created + const toggleViewItem = actionbar.viewItems[0] as ToggleActionViewItem; + assert(toggleViewItem instanceof ToggleActionViewItem, 'Toggle view item should exist'); + + // Change the action's checked state + toggleAction.checked = true; + // The view item should reflect the updated checked state + assert.strictEqual(toggleAction.checked, true, 'Toggle action should update checked state'); + }); + + test('quick input button with toggle property creates action with checked state', async function () { + const { quickInputButtonToAction } = await import('../../../platform/quickinput/browser/quickInputUtils.js'); + + // Create a button with toggle property + const toggleButton = { + iconClass: 'test-icon', + tooltip: 'Toggle Button', + toggle: { checked: true } + }; + + const action = quickInputButtonToAction(toggleButton, 'test-id', () => { }); + + // Verify the action has checked property set + assert.strictEqual(action.checked, true, 'Action should have checked property set to true'); + + // Create a button without toggle property + const regularButton = { + iconClass: 'test-icon', + tooltip: 'Regular Button' + }; + + const regularAction = quickInputButtonToAction(regularButton, 'test-id-2', () => { }); + + // Verify the action doesn't have checked property + assert.strictEqual(regularAction.checked, undefined, 'Regular action should not have checked property'); + }); + }); }); diff --git a/code/src/vs/base/test/browser/dom.test.ts b/code/src/vs/base/test/browser/dom.test.ts index 345efd026e8..a7975897761 100644 --- a/code/src/vs/base/test/browser/dom.test.ts +++ b/code/src/vs/base/test/browser/dom.test.ts @@ -404,7 +404,7 @@ suite('dom', () => { suite('SafeTriangle', () => { const fakeElement = (left: number, right: number, top: number, bottom: number): HTMLElement => { - return { getBoundingClientRect: () => ({ left, right, top, bottom }) } as any; + return { getBoundingClientRect: () => ({ left, right, top, bottom }) } as HTMLElement; }; test('works', () => { diff --git a/code/src/vs/base/test/browser/domSanitize.test.ts b/code/src/vs/base/test/browser/domSanitize.test.ts index 437d42d7e75..0bcf386848f 100644 --- a/code/src/vs/base/test/browser/domSanitize.test.ts +++ b/code/src/vs/base/test/browser/domSanitize.test.ts @@ -99,9 +99,8 @@ suite('DomSanitize', () => { test('allows safe protocols for href', () => { const html = 'safe link'; const result = sanitizeHtml(html); - const str = result.toString(); - assert.ok(str.includes('href="https://example.com"')); + assert.ok(result.toString().includes('href="https://example.com"')); }); test('allows fragment links', () => { @@ -126,9 +125,22 @@ suite('DomSanitize', () => { const result = sanitizeHtml(html, { allowedMediaProtocols: { override: [Schemas.data] } }); - const str = result.toString(); - assert.ok(str.includes('src="data:image/png;base64,')); + assert.ok(result.toString().includes('src="data:image/png;base64,')); + }); + + test('Removes relative paths for img src by default', () => { + const html = ''; + const result = sanitizeHtml(html); + assert.strictEqual(result.toString(), ''); + }); + + test('Can allow relative paths for image', () => { + const html = ''; + const result = sanitizeHtml(html, { + allowRelativeMediaPaths: true, + }); + assert.strictEqual(result.toString(), ''); }); test('Supports dynamic attribute sanitization', () => { @@ -145,8 +157,7 @@ suite('DomSanitize', () => { ] } }); - const str = result.toString(); - assert.strictEqual(str, '
text1
text2
'); + assert.strictEqual(result.toString(), '
text1
text2
'); }); test('Supports changing attributes in dynamic sanitization', () => { diff --git a/code/src/vs/base/test/browser/highlightedLabel.test.ts b/code/src/vs/base/test/browser/highlightedLabel.test.ts index 27ca48e4906..66906743633 100644 --- a/code/src/vs/base/test/browser/highlightedLabel.test.ts +++ b/code/src/vs/base/test/browser/highlightedLabel.test.ts @@ -11,7 +11,7 @@ suite('HighlightedLabel', () => { let label: HighlightedLabel; setup(() => { - label = new HighlightedLabel(document.createElement('div'), { supportIcons: true }); + label = new HighlightedLabel(document.createElement('div')); }); test('empty label', function () { diff --git a/code/src/vs/base/test/browser/markdownRenderer.test.ts b/code/src/vs/base/test/browser/markdownRenderer.test.ts index f1174905b02..5a02b84c7dc 100644 --- a/code/src/vs/base/test/browser/markdownRenderer.test.ts +++ b/code/src/vs/base/test/browser/markdownRenderer.test.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +/* eslint-disable no-restricted-syntax */ + import assert from 'assert'; import { fillInIncompleteTokens, renderMarkdown, renderAsPlaintext } from '../../browser/markdownRenderer.js'; import { IMarkdownString, MarkdownString } from '../../common/htmlContent.js'; @@ -166,7 +168,7 @@ suite('MarkdownRenderer', () => { mds.appendMarkdown(`[$(zap)-link](#link)`); const result: HTMLElement = store.add(renderMarkdown(mds)).element; - assert.strictEqual(result.innerHTML, `

-link

`); + assert.strictEqual(result.innerHTML, `

-link

`); }); test('render icon in table', () => { @@ -186,7 +188,7 @@ suite('MarkdownRenderer', () => { --link +-link `); @@ -220,6 +222,36 @@ suite('MarkdownRenderer', () => { }); }); + suite('Alerts', () => { + test('Should render alert with data-severity attribute and icon', () => { + const markdown = new MarkdownString('> [!NOTE]\n> This is a note alert', { supportAlertSyntax: true }); + const result = store.add(renderMarkdown(markdown)).element; + + const blockquote = result.querySelector('blockquote[data-severity="note"]'); + assert.ok(blockquote, 'Should have blockquote with data-severity="note"'); + assert.ok(result.innerHTML.includes('This is a note alert'), 'Should contain alert text'); + assert.ok(result.innerHTML.includes('codicon-info'), 'Should contain info icon'); + }); + + test('Should render regular blockquote when supportAlertSyntax is disabled', () => { + const markdown = new MarkdownString('> [!NOTE]\n> This should be a regular blockquote'); + const result = store.add(renderMarkdown(markdown)).element; + + const blockquote = result.querySelector('blockquote'); + assert.ok(blockquote, 'Should have blockquote'); + assert.strictEqual(blockquote?.getAttribute('data-severity'), null, 'Should not have data-severity attribute'); + assert.ok(result.innerHTML.includes('[!NOTE]'), 'Should contain literal [!NOTE] text'); + }); + + test('Should not transform blockquotes without alert syntax', () => { + const markdown = new MarkdownString('> This is a regular blockquote', { supportAlertSyntax: true }); + const result = store.add(renderMarkdown(markdown)).element; + + const blockquote = result.querySelector('blockquote'); + assert.strictEqual(blockquote?.getAttribute('data-severity'), null, 'Should not have data-severity attribute'); + }); + }); + test('npm Hover Run Script not working #90855', function () { const md: IMarkdownString = JSON.parse('{"value":"[Run Script](command:npm.runScriptFromHover?%7B%22documentUri%22%3A%7B%22%24mid%22%3A1%2C%22fsPath%22%3A%22c%3A%5C%5CUsers%5C%5Cjrieken%5C%5CCode%5C%5C_sample%5C%5Cfoo%5C%5Cpackage.json%22%2C%22_sep%22%3A1%2C%22external%22%3A%22file%3A%2F%2F%2Fc%253A%2FUsers%2Fjrieken%2FCode%2F_sample%2Ffoo%2Fpackage.json%22%2C%22path%22%3A%22%2Fc%3A%2FUsers%2Fjrieken%2FCode%2F_sample%2Ffoo%2Fpackage.json%22%2C%22scheme%22%3A%22file%22%7D%2C%22script%22%3A%22echo%22%7D \\"Run the script as a task\\")","supportThemeIcons":false,"isTrusted":true,"uris":{"__uri_e49443":{"$mid":1,"fsPath":"c:\\\\Users\\\\jrieken\\\\Code\\\\_sample\\\\foo\\\\package.json","_sep":1,"external":"file:///c%3A/Users/jrieken/Code/_sample/foo/package.json","path":"/c:/Users/jrieken/Code/_sample/foo/package.json","scheme":"file"},"command:npm.runScriptFromHover?%7B%22documentUri%22%3A%7B%22%24mid%22%3A1%2C%22fsPath%22%3A%22c%3A%5C%5CUsers%5C%5Cjrieken%5C%5CCode%5C%5C_sample%5C%5Cfoo%5C%5Cpackage.json%22%2C%22_sep%22%3A1%2C%22external%22%3A%22file%3A%2F%2F%2Fc%253A%2FUsers%2Fjrieken%2FCode%2F_sample%2Ffoo%2Fpackage.json%22%2C%22path%22%3A%22%2Fc%3A%2FUsers%2Fjrieken%2FCode%2F_sample%2Ffoo%2Fpackage.json%22%2C%22scheme%22%3A%22file%22%7D%2C%22script%22%3A%22echo%22%7D":{"$mid":1,"path":"npm.runScriptFromHover","scheme":"command","query":"{\\"documentUri\\":\\"__uri_e49443\\",\\"script\\":\\"echo\\"}"}}}'); @@ -253,7 +285,28 @@ suite('MarkdownRenderer', () => { }); const result: HTMLElement = store.add(renderMarkdown(md)).element; - assert.strictEqual(result.innerHTML, `

command1 command2

`); + assert.strictEqual(result.innerHTML, `

command1 command2

`); + }); + + test('Should remove relative links if there is no base url', () => { + const md = new MarkdownString(`[text](./foo) bar`, { + isTrusted: true, + supportHtml: true, + }); + + const result = store.add(renderMarkdown(md)).element; + assert.strictEqual(result.innerHTML, `

text bar

`); + }); + + test('Should support relative links if baseurl is set', () => { + const md = new MarkdownString(`[text](./foo) bar `, { + isTrusted: true, + supportHtml: true, + }); + md.baseUri = URI.parse('https://example.com/path/'); + + const result = store.add(renderMarkdown(md)).element; + assert.strictEqual(result.innerHTML, `

text bar

`); }); suite('PlaintextMarkdownRender', () => { diff --git a/code/src/vs/base/test/browser/ui/splitview/splitview.test.ts b/code/src/vs/base/test/browser/ui/splitview/splitview.test.ts index 76d7639b04c..e470e11a3f1 100644 --- a/code/src/vs/base/test/browser/ui/splitview/splitview.test.ts +++ b/code/src/vs/base/test/browser/ui/splitview/splitview.test.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +/* eslint-disable no-restricted-syntax */ + import assert from 'assert'; import { Sash, SashState } from '../../../../browser/ui/sash/sash.js'; import { IView, LayoutPriority, Sizing, SplitView } from '../../../../browser/ui/splitview/splitview.js'; diff --git a/code/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts b/code/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts index 4eb5e2b7e28..25840582aab 100644 --- a/code/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts +++ b/code/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +/* eslint-disable no-restricted-syntax */ + import assert from 'assert'; import { IIdentityProvider, IListVirtualDelegate } from '../../../../browser/ui/list/list.js'; import { AsyncDataTree, CompressibleAsyncDataTree, ITreeCompressionDelegate } from '../../../../browser/ui/tree/asyncDataTree.js'; diff --git a/code/src/vs/base/test/browser/ui/tree/objectTree.test.ts b/code/src/vs/base/test/browser/ui/tree/objectTree.test.ts index aa11fbe6036..b3813240e60 100644 --- a/code/src/vs/base/test/browser/ui/tree/objectTree.test.ts +++ b/code/src/vs/base/test/browser/ui/tree/objectTree.test.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +/* eslint-disable no-restricted-syntax */ + import assert from 'assert'; import { IIdentityProvider, IListVirtualDelegate } from '../../../../browser/ui/list/list.js'; import { ICompressedTreeNode } from '../../../../browser/ui/tree/compressedObjectTreeModel.js'; diff --git a/code/src/vs/base/test/common/async.test.ts b/code/src/vs/base/test/common/async.test.ts index aacd7918ef0..c011d89aa7c 100644 --- a/code/src/vs/base/test/common/async.test.ts +++ b/code/src/vs/base/test/common/async.test.ts @@ -5,7 +5,7 @@ import assert from 'assert'; import * as async from '../../common/async.js'; -import * as MicrotaskDelay from "../../common/symbols.js"; +import * as MicrotaskDelay from '../../common/symbols.js'; import { CancellationToken, CancellationTokenSource } from '../../common/cancellation.js'; import { isCancellationError } from '../../common/errors.js'; import { Event } from '../../common/event.js'; @@ -1210,6 +1210,22 @@ suite('Async', () => { assert.strictEqual((await deferred.p.catch(e => e)).name, 'Canceled'); assert.strictEqual(deferred.isRejected, true); }); + + test('retains the original settled value', async () => { + const deferred = new async.DeferredPromise(); + assert.strictEqual(deferred.isResolved, false); + assert.strictEqual(deferred.value, undefined); + + deferred.complete(42); + assert.strictEqual(await deferred.p, 42); + assert.strictEqual(deferred.value, 42); + assert.strictEqual(deferred.isResolved, true); + + deferred.complete(-1); + assert.strictEqual(await deferred.p, 42); + assert.strictEqual(deferred.value, 42); + assert.strictEqual(deferred.isResolved, true); + }); }); suite('Promises.settled', () => { @@ -2165,6 +2181,137 @@ suite('Async', () => { { id: 3, name: 'third' } ]); }); + + test('tee - both iterators receive all values', async () => { + // TODO: Implementation bug - executors don't await start(), causing producers to finalize early + async function* sourceGenerator() { + yield 1; + yield 2; + yield 3; + yield 4; + yield 5; + } + + const [iter1, iter2] = async.AsyncIterableProducer.tee(sourceGenerator()); + + const result1: number[] = []; + const result2: number[] = []; + + // Consume both iterables concurrently + await Promise.all([ + (async () => { + for await (const item of iter1) { + result1.push(item); + } + })(), + (async () => { + for await (const item of iter2) { + result2.push(item); + } + })() + ]); + + assert.deepStrictEqual(result1, [1, 2, 3, 4, 5]); + assert.deepStrictEqual(result2, [1, 2, 3, 4, 5]); + }); + + test('tee - sequential consumption', async () => { + // TODO: Implementation bug - executors don't await start(), causing producers to finalize early + const source = new async.AsyncIterableProducer(emitter => { + emitter.emitMany([1, 2, 3]); + }); + + const [iter1, iter2] = async.AsyncIterableProducer.tee(source); + + // Consume first iterator completely + const result1: number[] = []; + for await (const item of iter1) { + result1.push(item); + } + + // Then consume second iterator + const result2: number[] = []; + for await (const item of iter2) { + result2.push(item); + } + + assert.deepStrictEqual(result1, [1, 2, 3]); + assert.deepStrictEqual(result2, [1, 2, 3]); + }); + + test.skip('tee - empty source', async () => { + // TODO: Implementation bug - executors don't await start(), causing producers to finalize early + const source = new async.AsyncIterableProducer(emitter => { + // Emit nothing + }); + + const [iter1, iter2] = async.AsyncIterableProducer.tee(source); + + const result1: number[] = []; + const result2: number[] = []; + + await Promise.all([ + (async () => { + for await (const item of iter1) { + result1.push(item); + } + })(), + (async () => { + for await (const item of iter2) { + result2.push(item); + } + })() + ]); + + assert.deepStrictEqual(result1, []); + assert.deepStrictEqual(result2, []); + }); + + test.skip('tee - handles errors in source', async () => { + // TODO: Implementation bug - executors don't await start(), causing producers to finalize early + const expectedError = new Error('source error'); + const source = new async.AsyncIterableProducer(async emitter => { + emitter.emitOne(1); + emitter.emitOne(2); + throw expectedError; + }); + + const [iter1, iter2] = async.AsyncIterableProducer.tee(source); + + let error1: Error | undefined; + let error2: Error | undefined; + const result1: number[] = []; + const result2: number[] = []; + + await Promise.all([ + (async () => { + try { + for await (const item of iter1) { + result1.push(item); + } + } catch (e) { + error1 = e as Error; + } + })(), + (async () => { + try { + for await (const item of iter2) { + result2.push(item); + } + } catch (e) { + error2 = e as Error; + } + })() + ]); + + // Both iterators should have received the same values before error + assert.deepStrictEqual(result1, [1, 2]); + assert.deepStrictEqual(result2, [1, 2]); + + // Both should have received the error + assert.strictEqual(error1, expectedError); + assert.strictEqual(error2, expectedError); + }); }); suite('AsyncReader', () => { @@ -2375,12 +2522,14 @@ suite('Async', () => { }); test('peekTimeout - timeout occurs', async () => { - const reader = new async.AsyncReader(createDelayedAsyncIterator([1, 2, 3], 50)); + return runWithFakedTimers({}, async () => { + const reader = new async.AsyncReader(createDelayedAsyncIterator([1, 2, 3], 50)); - const result = await reader.peekTimeout(10); - assert.strictEqual(result, undefined); + const result = await reader.peekTimeout(10); + assert.strictEqual(result, undefined); - await reader.consumeToEnd(); + await reader.consumeToEnd(); + }); }); test('peekTimeout - empty iterator', async () => { diff --git a/code/src/vs/base/test/common/buffer.test.ts b/code/src/vs/base/test/common/buffer.test.ts index 6666d901d81..9109d4cdb1b 100644 --- a/code/src/vs/base/test/common/buffer.test.ts +++ b/code/src/vs/base/test/common/buffer.test.ts @@ -19,6 +19,20 @@ suite('Buffer', () => { assert.deepStrictEqual(buffer.toString(), 'hi'); }); + test('issue #251527 - VSBuffer#toString preserves BOM character in filenames', () => { + // BOM character (U+FEFF) is a zero-width character that was being stripped + // when deserializing messages in the IPC layer. This test verifies that + // the BOM character is preserved when using VSBuffer.toString(). + const bomChar = '\uFEFF'; + const filename = `${bomChar}c.txt`; + const buffer = VSBuffer.fromString(filename); + const result = buffer.toString(); + + // Verify the BOM character is preserved + assert.strictEqual(result, filename); + assert.strictEqual(result.charCodeAt(0), 0xFEFF); + }); + test('bufferToReadable / readableToBuffer', () => { const content = 'Hello World'; const readable = bufferToReadable(VSBuffer.fromString(content)); @@ -498,6 +512,7 @@ suite('Buffer', () => { // Test invalid input assert.throws(() => { + // eslint-disable-next-line local/code-no-any-casts buff.set({} as any); }); }); diff --git a/code/src/vs/base/test/common/cancellation.test.ts b/code/src/vs/base/test/common/cancellation.test.ts index 38ed33f8d62..7e5ad1060fd 100644 --- a/code/src/vs/base/test/common/cancellation.test.ts +++ b/code/src/vs/base/test/common/cancellation.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { CancellationToken, CancellationTokenSource } from '../../common/cancellation.js'; +import { CancellationToken, CancellationTokenSource, CancellationTokenPool } from '../../common/cancellation.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from './utils.js'; suite('CancellationToken', function () { @@ -125,3 +125,155 @@ suite('CancellationToken', function () { parent.dispose(); }); }); + +suite('CancellationTokenPool', function () { + + const store = ensureNoDisposablesAreLeakedInTestSuite(); + + test('empty pool token is not cancelled', function () { + const pool = new CancellationTokenPool(); + store.add(pool); + + assert.strictEqual(pool.token.isCancellationRequested, false); + }); + + test('pool token cancels when all tokens are cancelled', function () { + const pool = new CancellationTokenPool(); + store.add(pool); + + const source1 = new CancellationTokenSource(); + const source2 = new CancellationTokenSource(); + const source3 = new CancellationTokenSource(); + + pool.add(source1.token); + pool.add(source2.token); + pool.add(source3.token); + + assert.strictEqual(pool.token.isCancellationRequested, false); + + source1.cancel(); + assert.strictEqual(pool.token.isCancellationRequested, false); + + source2.cancel(); + assert.strictEqual(pool.token.isCancellationRequested, false); + + source3.cancel(); + assert.strictEqual(pool.token.isCancellationRequested, true); + + source1.dispose(); + source2.dispose(); + source3.dispose(); + }); + + test('pool token fires cancellation event when all tokens are cancelled', function () { + return new Promise(resolve => { + const pool = new CancellationTokenPool(); + store.add(pool); + + const source1 = new CancellationTokenSource(); + const source2 = new CancellationTokenSource(); + + pool.add(source1.token); + pool.add(source2.token); + + store.add(pool.token.onCancellationRequested(() => resolve())); + + source1.cancel(); + source2.cancel(); + + source1.dispose(); + source2.dispose(); + }); + }); + + test('adding already cancelled token counts immediately', function () { + const pool = new CancellationTokenPool(); + store.add(pool); + + const source1 = new CancellationTokenSource(); + const source2 = new CancellationTokenSource(); + + source1.cancel(); // Cancel before adding to pool + + pool.add(source1.token); + assert.strictEqual(pool.token.isCancellationRequested, true); // 1 of 1 cancelled, so pool is cancelled + + pool.add(source2.token); // Adding after pool is done should have no effect + assert.strictEqual(pool.token.isCancellationRequested, true); + + source2.cancel(); // This should have no effect since pool is already done + assert.strictEqual(pool.token.isCancellationRequested, true); + + source1.dispose(); + source2.dispose(); + }); + + test('adding single already cancelled token cancels pool immediately', function () { + const pool = new CancellationTokenPool(); + store.add(pool); + + const source = new CancellationTokenSource(); + source.cancel(); + + pool.add(source.token); + assert.strictEqual(pool.token.isCancellationRequested, true); // 1 of 1 cancelled + + source.dispose(); + }); + + test('adding token after pool is done has no effect', function () { + const pool = new CancellationTokenPool(); + store.add(pool); + + const source1 = new CancellationTokenSource(); + const source2 = new CancellationTokenSource(); + + pool.add(source1.token); + source1.cancel(); // Pool should be done now + + assert.strictEqual(pool.token.isCancellationRequested, true); + + // Adding another token should have no effect + pool.add(source2.token); + source2.cancel(); + + assert.strictEqual(pool.token.isCancellationRequested, true); + + source1.dispose(); + source2.dispose(); + }); + + test('single token pool behaviour', function () { + const pool = new CancellationTokenPool(); + store.add(pool); + + const source = new CancellationTokenSource(); + pool.add(source.token); + + assert.strictEqual(pool.token.isCancellationRequested, false); + + source.cancel(); + assert.strictEqual(pool.token.isCancellationRequested, true); + + source.dispose(); + }); + + test('pool with only cancelled tokens', function () { + const pool = new CancellationTokenPool(); + store.add(pool); + + const source1 = new CancellationTokenSource(); + const source2 = new CancellationTokenSource(); + + source1.cancel(); + source2.cancel(); + + pool.add(source1.token); + pool.add(source2.token); + + assert.strictEqual(pool.token.isCancellationRequested, true); + + source1.dispose(); + source2.dispose(); + }); +}); diff --git a/code/src/vs/base/test/common/collections.test.ts b/code/src/vs/base/test/common/collections.test.ts index fbf8f6e77b3..5bd2206d538 100644 --- a/code/src/vs/base/test/common/collections.test.ts +++ b/code/src/vs/base/test/common/collections.test.ts @@ -24,12 +24,12 @@ suite('Collections', () => { const grouped = collections.groupBy(source, x => x.key); // Group 1 - assert.strictEqual(grouped[group1].length, 2); + assert.strictEqual(grouped[group1]?.length, 2); assert.strictEqual(grouped[group1][0].value, value1); assert.strictEqual(grouped[group1][1].value, value2); // Group 2 - assert.strictEqual(grouped[group2].length, 1); + assert.strictEqual(grouped[group2]?.length, 1); assert.strictEqual(grouped[group2][0].value, value3); }); diff --git a/code/src/vs/base/test/common/decorators.test.ts b/code/src/vs/base/test/common/decorators.test.ts index bac27cd772e..9100176007b 100644 --- a/code/src/vs/base/test/common/decorators.test.ts +++ b/code/src/vs/base/test/common/decorators.test.ts @@ -127,6 +127,7 @@ suite('Decorators', () => { assert.strictEqual(foo.answer, 42); try { + // eslint-disable-next-line local/code-no-any-casts (foo as any)['$memoize$answer'] = 1337; assert(false); } catch (e) { diff --git a/code/src/vs/base/test/common/envfile.test.ts b/code/src/vs/base/test/common/envfile.test.ts index 753a8c6b534..e8713e25856 100644 --- a/code/src/vs/base/test/common/envfile.test.ts +++ b/code/src/vs/base/test/common/envfile.test.ts @@ -93,14 +93,14 @@ suite('parseEnvFile', () => { assert.strictEqual(parsed.get('DOUBLE_QUOTES_SPACED'), ' double quotes '); assert.strictEqual(parsed.get('DOUBLE_QUOTES_INSIDE_SINGLE'), 'double "quotes" work inside single quotes'); assert.strictEqual(parsed.get('DOUBLE_QUOTES_WITH_NO_SPACE_BRACKET'), '{ port: $MONGOLAB_PORT}'); - assert.strictEqual(parsed.get('SINGLE_QUOTES_INSIDE_DOUBLE'), "single 'quotes' work inside double quotes"); + assert.strictEqual(parsed.get('SINGLE_QUOTES_INSIDE_DOUBLE'), `single 'quotes' work inside double quotes`); assert.strictEqual(parsed.get('BACKTICKS_INSIDE_SINGLE'), '`backticks` work inside single quotes'); assert.strictEqual(parsed.get('BACKTICKS_INSIDE_DOUBLE'), '`backticks` work inside double quotes'); assert.strictEqual(parsed.get('BACKTICKS'), 'backticks'); assert.strictEqual(parsed.get('BACKTICKS_SPACED'), ' backticks '); assert.strictEqual(parsed.get('DOUBLE_QUOTES_INSIDE_BACKTICKS'), 'double "quotes" work inside backticks'); - assert.strictEqual(parsed.get('SINGLE_QUOTES_INSIDE_BACKTICKS'), "single 'quotes' work inside backticks"); - assert.strictEqual(parsed.get('DOUBLE_AND_SINGLE_QUOTES_INSIDE_BACKTICKS'), "double \"quotes\" and single 'quotes' work inside backticks"); + assert.strictEqual(parsed.get('SINGLE_QUOTES_INSIDE_BACKTICKS'), `single 'quotes' work inside backticks`); + assert.strictEqual(parsed.get('DOUBLE_AND_SINGLE_QUOTES_INSIDE_BACKTICKS'), `double "quotes" and single 'quotes' work inside backticks`); assert.strictEqual(parsed.get('EXPAND_NEWLINES'), 'expand\nnew\nlines'); assert.strictEqual(parsed.get('DONT_EXPAND_UNQUOTED'), 'dontexpand\\nnewlines'); assert.strictEqual(parsed.get('DONT_EXPAND_SQUOTED'), 'dontexpand\\nnewlines'); diff --git a/code/src/vs/base/test/common/event.test.ts b/code/src/vs/base/test/common/event.test.ts index c081f420c1e..f79fa81cdff 100644 --- a/code/src/vs/base/test/common/event.test.ts +++ b/code/src/vs/base/test/common/event.test.ts @@ -34,7 +34,7 @@ namespace Samples { private readonly _onDidChange = new Emitter(); - onDidChange: Event = this._onDidChange.event; + readonly onDidChange: Event = this._onDidChange.event; setText(value: string) { //... @@ -464,6 +464,111 @@ suite('Event', function () { }); }); + suite('Event.toPromise', () => { + class DisposableStoreWithSize extends DisposableStore { + public size = 0; + public override add(o: T): T { + this.size++; + return super.add(o); + } + + public override delete(o: T): void { + this.size--; + return super.delete(o); + } + } + test('resolves on first event', async () => { + const emitter = ds.add(new Emitter()); + const promise = Event.toPromise(emitter.event); + + emitter.fire(42); + const result = await promise; + + assert.strictEqual(result, 42); + }); + + test('disposes listener after resolution', async () => { + const emitter = ds.add(new Emitter()); + const promise = Event.toPromise(emitter.event); + + emitter.fire(1); + await promise; + + // Listener should be disposed, firing again should not affect anything + emitter.fire(2); + assert.ok(true); // No errors + }); + + test('adds to DisposableStore', async () => { + const emitter = ds.add(new Emitter()); + const store = ds.add(new DisposableStoreWithSize()); + const promise = Event.toPromise(emitter.event, store); + + assert.strictEqual(store.size, 1); + + emitter.fire(42); + await promise; + + // Should be removed from store after resolution + assert.strictEqual(store.size, 0); + }); + + test('adds to disposables array', async () => { + const emitter = ds.add(new Emitter()); + const disposables: IDisposable[] = []; + const promise = Event.toPromise(emitter.event, disposables); + + assert.strictEqual(disposables.length, 1); + + emitter.fire(42); + await promise; + + // Should be removed from array after resolution + assert.strictEqual(disposables.length, 0); + }); + + test('cancel removes from DisposableStore', () => { + const emitter = ds.add(new Emitter()); + const store = ds.add(new DisposableStoreWithSize()); + const promise = Event.toPromise(emitter.event, store); + + assert.strictEqual(store.size, 1); + + promise.cancel(); + + // Should be removed from store after cancellation + assert.strictEqual(store.size, 0); + }); + + test('cancel removes from disposables array', () => { + const emitter = ds.add(new Emitter()); + const disposables: IDisposable[] = []; + const promise = Event.toPromise(emitter.event, disposables); + + assert.strictEqual(disposables.length, 1); + + promise.cancel(); + + // Should be removed from array after cancellation + assert.strictEqual(disposables.length, 0); + }); + + test('cancel does not resolve promise', async () => { + const emitter = ds.add(new Emitter()); + const promise = Event.toPromise(emitter.event); + + promise.cancel(); + emitter.fire(42); + + // Promise should not resolve after cancellation + let resolved = false; + promise.then(() => resolved = true); + + await timeout(10); + assert.strictEqual(resolved, false); + }); + }); + test('Microtask Emitter', (done) => { let count = 0; assert.strictEqual(count, 0); @@ -531,12 +636,6 @@ suite('Event', function () { // assert that all events are delivered in order assert.deepStrictEqual(listener2Events, ['e1', 'e2', 'e3']); }); - - test('Cannot read property \'_actual\' of undefined #142204', function () { - const e = ds.add(new Emitter()); - const dispo = e.event(() => { }); - dispo.dispose.call(undefined); // assert that disposable can be called with this - }); }); suite('AsyncEmitter', function () { diff --git a/code/src/vs/base/test/common/filters.perf.data.d.ts b/code/src/vs/base/test/common/filters.perf.data.d.ts index b2ef6866955..3393c4f5a02 100644 --- a/code/src/vs/base/test/common/filters.perf.data.d.ts +++ b/code/src/vs/base/test/common/filters.perf.data.d.ts @@ -2,4 +2,4 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -export const data: string[]; \ No newline at end of file +export declare const data: string[]; diff --git a/code/src/vs/base/test/common/filters.test.ts b/code/src/vs/base/test/common/filters.test.ts index ce70ad5f9c1..6aeaefd59fb 100644 --- a/code/src/vs/base/test/common/filters.test.ts +++ b/code/src/vs/base/test/common/filters.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { anyScore, createMatches, fuzzyScore, fuzzyScoreGraceful, fuzzyScoreGracefulAggressive, FuzzyScorer, IFilter, IMatch, matchesCamelCase, matchesContiguousSubString, matchesPrefix, matchesStrictPrefix, matchesSubString, matchesWords, or } from '../../common/filters.js'; +import { anyScore, createMatches, fuzzyScore, fuzzyScoreGraceful, fuzzyScoreGracefulAggressive, FuzzyScorer, IFilter, IMatch, matchesBaseContiguousSubString, matchesCamelCase, matchesContiguousSubString, matchesPrefix, matchesStrictPrefix, matchesSubString, matchesWords, or } from '../../common/filters.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from './utils.js'; function filterOk(filter: IFilter, word: string, wordToMatchAgainst: string, highlights?: { start: number; end: number }[]) { @@ -25,6 +25,7 @@ suite('Filters', () => { let filter: IFilter; let counters: number[]; const newFilter = function (i: number, r: boolean): IFilter { + // eslint-disable-next-line local/code-no-any-casts return function (): IMatch[] { counters[i]++; return r as any; }; }; @@ -157,6 +158,30 @@ suite('Filters', () => { ]); }); + test('matchesBaseContiguousSubString', () => { + filterOk(matchesBaseContiguousSubString, 'cela', 'cancelAnimationFrame()', [ + { start: 3, end: 7 } + ]); + filterOk(matchesBaseContiguousSubString, 'cafe', 'café', [ + { start: 0, end: 4 } + ]); + filterOk(matchesBaseContiguousSubString, 'cafe', 'caféBar', [ + { start: 0, end: 4 } + ]); + filterOk(matchesBaseContiguousSubString, 'resume', 'résumé', [ + { start: 0, end: 6 } + ]); + filterOk(matchesBaseContiguousSubString, 'naïve', 'naïve', [ + { start: 0, end: 5 } + ]); + filterOk(matchesBaseContiguousSubString, 'naive', 'naïve', [ + { start: 0, end: 5 } + ]); + filterOk(matchesBaseContiguousSubString, 'aeou', 'àéöü', [ + { start: 0, end: 4 } + ]); + }); + test('matchesSubString', () => { filterOk(matchesSubString, 'cmm', 'cancelAnimationFrame()', [ { start: 0, end: 1 }, diff --git a/code/src/vs/base/test/common/fuzzyScorer.test.ts b/code/src/vs/base/test/common/fuzzyScorer.test.ts index a0905fec5e9..f120298e22b 100644 --- a/code/src/vs/base/test/common/fuzzyScorer.test.ts +++ b/code/src/vs/base/test/common/fuzzyScorer.test.ts @@ -206,6 +206,18 @@ suite('Fuzzy Scorer', () => { assert.strictEqual(pathRes.descriptionMatch[0].start, 1); assert.strictEqual(pathRes.descriptionMatch[0].end, 4); + // Ellipsis Match + const ellipsisRes = scoreItem(resource, '…me/path/someFile123.txt', true, ResourceAccessor); + assert.ok(ellipsisRes.score); + assert.ok(pathRes.descriptionMatch); + assert.ok(pathRes.labelMatch); + assert.strictEqual(pathRes.labelMatch.length, 1); + assert.strictEqual(pathRes.labelMatch[0].start, 8); + assert.strictEqual(pathRes.labelMatch[0].end, 11); + assert.strictEqual(pathRes.descriptionMatch.length, 1); + assert.strictEqual(pathRes.descriptionMatch[0].start, 1); + assert.strictEqual(pathRes.descriptionMatch[0].end, 4); + // No Match const noRes = scoreItem(resource, '987', true, ResourceAccessor); assert.ok(!noRes.score); @@ -1128,6 +1140,11 @@ suite('Fuzzy Scorer', () => { test('prepareQuery', () => { assert.strictEqual(prepareQuery(' f*a ').normalized, 'fa'); + assert.strictEqual(prepareQuery(' f…a ').normalized, 'fa'); + assert.strictEqual(prepareQuery('main#').normalized, 'main'); + assert.strictEqual(prepareQuery('main#').original, 'main#'); + assert.strictEqual(prepareQuery('foo*').normalized, 'foo'); + assert.strictEqual(prepareQuery('foo*').original, 'foo*'); assert.strictEqual(prepareQuery('model Tester.ts').original, 'model Tester.ts'); assert.strictEqual(prepareQuery('model Tester.ts').originalLowercase, 'model Tester.ts'.toLowerCase()); assert.strictEqual(prepareQuery('model Tester.ts').normalized, 'modelTester.ts'); @@ -1282,5 +1299,59 @@ suite('Fuzzy Scorer', () => { assert.strictEqual(score[1][1], 8); }); + test('Workspace symbol search with special characters (#, *)', function () { + // Simulates the scenario from the issue where rust-analyzer uses # and * as query modifiers + // The original query (with special chars) should reach the language server + // but normalized query (without special chars) should be used for fuzzy matching + + // Test #: User types "main#", language server returns "main" symbol + let query = prepareQuery('main#'); + assert.strictEqual(query.original, 'main#'); // Sent to language server + assert.strictEqual(query.normalized, 'main'); // Used for fuzzy matching + let [score, matches] = _doScore2('main', 'main#'); + assert.ok(typeof score === 'number' && score > 0, 'Should match "main" symbol when query is "main#"'); + assert.ok(matches.length > 0); + + // Test *: User types "foo*", language server returns "foo" symbol + query = prepareQuery('foo*'); + assert.strictEqual(query.original, 'foo*'); // Sent to language server + assert.strictEqual(query.normalized, 'foo'); // Used for fuzzy matching + [score, matches] = _doScore2('foo', 'foo*'); + assert.ok(typeof score === 'number' && score > 0, 'Should match "foo" symbol when query is "foo*"'); + assert.ok(matches.length > 0); + + // Test both: User types "MyClass#*", should match "MyClass" + query = prepareQuery('MyClass#*'); + assert.strictEqual(query.original, 'MyClass#*'); + assert.strictEqual(query.normalized, 'MyClass'); + [score, matches] = _doScore2('MyClass', 'MyClass#*'); + assert.ok(typeof score === 'number' && score > 0, 'Should match "MyClass" symbol when query is "MyClass#*"'); + assert.ok(matches.length > 0); + + // Test fuzzy matching still works: User types "MC#", should match "MyClass" + query = prepareQuery('MC#'); + assert.strictEqual(query.original, 'MC#'); + assert.strictEqual(query.normalized, 'MC'); + [score, matches] = _doScore2('MyClass', 'MC#'); + assert.ok(typeof score === 'number' && score > 0, 'Should fuzzy match "MyClass" symbol when query is "MC#"'); + assert.ok(matches.length > 0); + + // Make sure leading # or # in the middle are not removed. + query = prepareQuery('#SpecialFunction'); + assert.strictEqual(query.original, '#SpecialFunction'); + assert.strictEqual(query.normalized, '#SpecialFunction'); + [score, matches] = _doScore2('#SpecialFunction', '#SpecialFunction'); + assert.ok(typeof score === 'number' && score > 0, 'Should match "#SpecialFunction" symbol when query is "#SpecialFunction"'); + assert.ok(matches.length > 0); + + // Make sure standalone # is not removed + query = prepareQuery('#'); + assert.strictEqual(query.original, '#'); + assert.strictEqual(query.normalized, '#', 'Standalone # should not be removed'); + [score, matches] = _doScore2('#', '#'); + assert.ok(typeof score === 'number' && score > 0, 'Should match "#" symbol when query is "#"'); + assert.ok(matches.length > 0); + }); + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/code/src/vs/base/test/common/glob.test.ts b/code/src/vs/base/test/common/glob.test.ts index 74fbdf6f65d..3021b924988 100644 --- a/code/src/vs/base/test/common/glob.test.ts +++ b/code/src/vs/base/test/common/glob.test.ts @@ -64,14 +64,14 @@ suite('Glob', () => { // console.profileEnd(); // }); - function assertGlobMatch(pattern: string | glob.IRelativePattern, input: string) { - assert(glob.match(pattern, input), `${JSON.stringify(pattern)} should match ${input}`); - assert(glob.match(pattern, nativeSep(input)), `${pattern} should match ${nativeSep(input)}`); + function assertGlobMatch(pattern: string | glob.IRelativePattern, input: string, ignoreCase?: boolean) { + assert(glob.match(pattern, input, { ignoreCase }), `${JSON.stringify(pattern)} should match ${input}`); + assert(glob.match(pattern, nativeSep(input), { ignoreCase }), `${pattern} should match ${nativeSep(input)}`); } - function assertNoGlobMatch(pattern: string | glob.IRelativePattern, input: string) { - assert(!glob.match(pattern, input), `${pattern} should not match ${input}`); - assert(!glob.match(pattern, nativeSep(input)), `${pattern} should not match ${nativeSep(input)}`); + function assertNoGlobMatch(pattern: string | glob.IRelativePattern, input: string, ignoreCase?: boolean) { + assert(!glob.match(pattern, input, { ignoreCase }), `${pattern} should not match ${input}`); + assert(!glob.match(pattern, nativeSep(input), { ignoreCase }), `${pattern} should not match ${nativeSep(input)}`); } test('simple', () => { @@ -480,10 +480,10 @@ suite('Glob', () => { } }; - assert.strictEqual('**/*.js', glob.match(expression, 'test.js', hasSibling)); - assert.strictEqual(glob.match(expression, 'test.js', () => false), null); - assert.strictEqual(glob.match(expression, 'test.js', name => name === 'te.ts'), null); - assert.strictEqual(glob.match(expression, 'test.js'), null); + assert.strictEqual('**/*.js', glob.parse(expression)('test.js', undefined, hasSibling)); + assert.strictEqual(glob.parse(expression)('test.js', undefined, () => false), null); + assert.strictEqual(glob.parse(expression)('test.js', undefined, name => name === 'te.ts'), null); + assert.strictEqual(glob.parse(expression)('test.js', undefined), null); expression = { '**/*.js': { @@ -491,18 +491,19 @@ suite('Glob', () => { } }; - assert.strictEqual(glob.match(expression, 'test.js', hasSibling), null); + assert.strictEqual(glob.parse(expression)('test.js', undefined, hasSibling), null); expression = { + // eslint-disable-next-line local/code-no-any-casts '**/*.js': { } as any }; - assert.strictEqual('**/*.js', glob.match(expression, 'test.js', hasSibling)); + assert.strictEqual('**/*.js', glob.parse(expression)('test.js', undefined, hasSibling)); expression = {}; - assert.strictEqual(glob.match(expression, 'test.js', hasSibling), null); + assert.strictEqual(glob.parse(expression)('test.js', undefined, hasSibling), null); }); test('expression support (multiple)', function () { @@ -514,14 +515,15 @@ suite('Glob', () => { '**/*.js': { when: '$(basename).ts' }, '**/*.as': true, '**/*.foo': false, + // eslint-disable-next-line local/code-no-any-casts '**/*.bananas': { bananas: true } as any }; - assert.strictEqual('**/*.js', glob.match(expression, 'test.js', hasSibling)); - assert.strictEqual('**/*.as', glob.match(expression, 'test.as', hasSibling)); - assert.strictEqual('**/*.bananas', glob.match(expression, 'test.bananas', hasSibling)); - assert.strictEqual('**/*.bananas', glob.match(expression, 'test.bananas')); - assert.strictEqual(glob.match(expression, 'test.foo', hasSibling), null); + assert.strictEqual('**/*.js', glob.parse(expression)('test.js', undefined, hasSibling)); + assert.strictEqual('**/*.as', glob.parse(expression)('test.as', undefined, hasSibling)); + assert.strictEqual('**/*.bananas', glob.parse(expression)('test.bananas', undefined, hasSibling)); + assert.strictEqual('**/*.bananas', glob.parse(expression)('test.bananas', undefined)); + assert.strictEqual(glob.parse(expression)('test.foo', undefined, hasSibling), null); }); test('brackets', () => { @@ -758,6 +760,7 @@ suite('Glob', () => { }); test('expression with other falsy value', function () { + // eslint-disable-next-line local/code-no-any-casts const expr = { '**/*.js': 0 } as any; assert.strictEqual(glob.match(expr, 'foo.js'), '**/*.js'); @@ -786,16 +789,16 @@ suite('Glob', () => { const siblings = ['foo.ts', 'foo.js', 'foo', 'bar']; const hasSibling = (name: string) => siblings.indexOf(name) !== -1; - assert.strictEqual(glob.match(expr, 'bar', hasSibling), '**/bar'); - assert.strictEqual(glob.match(expr, 'foo', hasSibling), null); - assert.strictEqual(glob.match(expr, 'foo/bar', hasSibling), '**/bar'); + assert.strictEqual(glob.parse(expr)('bar', undefined, hasSibling), '**/bar'); + assert.strictEqual(glob.parse(expr)('foo', undefined, hasSibling), null); + assert.strictEqual(glob.parse(expr)('foo/bar', undefined, hasSibling), '**/bar'); if (isWindows) { // backslash is a valid file name character on posix - assert.strictEqual(glob.match(expr, 'foo\\bar', hasSibling), '**/bar'); + assert.strictEqual(glob.parse(expr)('foo\\bar', undefined, hasSibling), '**/bar'); } - assert.strictEqual(glob.match(expr, 'foo/foo', hasSibling), null); - assert.strictEqual(glob.match(expr, 'foo.js', hasSibling), '**/*.js'); - assert.strictEqual(glob.match(expr, 'bar.js', hasSibling), null); + assert.strictEqual(glob.parse(expr)('foo/foo', undefined, hasSibling), null); + assert.strictEqual(glob.parse(expr)('foo.js', undefined, hasSibling), '**/*.js'); + assert.strictEqual(glob.parse(expr)('bar.js', undefined, hasSibling), null); }); test('expression with multipe basename globs', function () { @@ -1168,5 +1171,30 @@ suite('Glob', () => { assert.ok(glob.isEmptyPattern(glob.parse({ '**/*.js': false }))); }); + test('caseInsensitiveMatch', () => { + assertNoGlobMatch('PATH/FOO.js', 'path/foo.js'); + assertGlobMatch('PATH/FOO.js', 'path/foo.js', true); + // T1 + assertNoGlobMatch('**/*.JS', 'bar/foo.js'); + assertGlobMatch('**/*.JS', 'bar/foo.js', true); + // T2 + assertNoGlobMatch('**/package', 'bar/Package'); + assertGlobMatch('**/package', 'bar/Package', true); + // T3 + assertNoGlobMatch('{**/*.JS,**/*.TS}', 'bar/foo.ts'); + assertNoGlobMatch('{**/*.JS,**/*.TS}', 'bar/foo.js'); + assertGlobMatch('{**/*.JS,**/*.TS}', 'bar/foo.ts', true); + assertGlobMatch('{**/*.JS,**/*.TS}', 'bar/foo.js', true); + // T4 + assertNoGlobMatch('**/FOO/Bar', 'bar/foo/bar'); + assertGlobMatch('**/FOO/Bar', 'bar/foo/bar', true); + // T5 + assertNoGlobMatch('FOO/Bar', 'foo/bar'); + assertGlobMatch('FOO/Bar', 'foo/bar', true); + // Other + assertNoGlobMatch('some/*/Random/*/Path.FILE', 'some/very/random/unusual/path.file'); + assertGlobMatch('some/*/Random/*/Path.FILE', 'some/very/random/unusual/path.file', true); + }); + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/code/src/vs/base/test/common/iterativePaging.test.ts b/code/src/vs/base/test/common/iterativePaging.test.ts new file mode 100644 index 00000000000..395b928a99a --- /dev/null +++ b/code/src/vs/base/test/common/iterativePaging.test.ts @@ -0,0 +1,300 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { CancellationToken, CancellationTokenSource } from '../../common/cancellation.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from './utils.js'; +import { IterativePagedModel, IIterativePager, IIterativePage } from '../../common/paging.js'; + +function createTestPager(pageSize: number, maxPages: number): IIterativePager { + let currentPage = 0; + + const createPage = (page: number): IIterativePage => { + const start = page * pageSize; + const items: number[] = []; + for (let i = 0; i < pageSize; i++) { + items.push(start + i); + } + const hasMore = page + 1 < maxPages; + return { items, hasMore }; + }; + + return { + firstPage: createPage(currentPage++), + getNextPage: async (cancellationToken: CancellationToken): Promise> => { + if (currentPage >= maxPages) { + return { items: [], hasMore: false }; + } + return createPage(currentPage++); + } + }; +} + +suite('IterativePagedModel', () => { + + const store = ensureNoDisposablesAreLeakedInTestSuite(); + + test('initial state', () => { + const pager = createTestPager(10, 3); + const model = store.add(new IterativePagedModel(pager)); + + // Initially first page is loaded, so length should be 10 + 1 sentinel + assert.strictEqual(model.length, 11); + assert.strictEqual(model.isResolved(0), true); + assert.strictEqual(model.isResolved(9), true); + assert.strictEqual(model.isResolved(10), false); // sentinel + }); + + test('load first page via sentinel access', async () => { + const pager = createTestPager(10, 3); + const model = store.add(new IterativePagedModel(pager)); + + // Access an item in the first page (already loaded) + const item = await model.resolve(0, CancellationToken.None); + + assert.strictEqual(item, 0); + assert.strictEqual(model.length, 11); // 10 items + 1 sentinel + assert.strictEqual(model.isResolved(0), true); + assert.strictEqual(model.isResolved(9), true); + assert.strictEqual(model.isResolved(10), false); // sentinel + }); + + test('load multiple pages', async () => { + const pager = createTestPager(10, 3); + const model = store.add(new IterativePagedModel(pager)); + + // First page already loaded + assert.strictEqual(model.length, 11); + + // Load second page by accessing its sentinel + await model.resolve(10, CancellationToken.None); + assert.strictEqual(model.length, 21); // 20 items + 1 sentinel + assert.strictEqual(model.get(10), 10); // First item of second page + + // Load third (final) page + await model.resolve(20, CancellationToken.None); + assert.strictEqual(model.length, 30); // 30 items, no sentinel (no more pages) + }); + + test('onDidIncrementLength event fires correctly', async () => { + const pager = createTestPager(10, 3); + const model = store.add(new IterativePagedModel(pager)); + const lengths: number[] = []; + + store.add(model.onDidIncrementLength((length: number) => lengths.push(length))); + + // Load second page + await model.resolve(10, CancellationToken.None); + + assert.strictEqual(lengths.length, 1); + assert.strictEqual(lengths[0], 21); // 20 items + 1 sentinel + + // Load third page + await model.resolve(20, CancellationToken.None); + + assert.strictEqual(lengths.length, 2); + assert.strictEqual(lengths[1], 30); // 30 items, no sentinel + }); + + test('accessing regular items does not trigger loading', async () => { + const pager = createTestPager(10, 3); + const model = store.add(new IterativePagedModel(pager)); + + const initialLength = model.length; + + // Access items within the loaded range + assert.strictEqual(model.get(5), 5); + assert.strictEqual(model.isResolved(5), true); + + // Length should not change + assert.strictEqual(model.length, initialLength); + }); + + test('reaching end of data removes sentinel', async () => { + const pager = createTestPager(10, 3); + const model = store.add(new IterativePagedModel(pager)); + + // Load all pages + await model.resolve(10, CancellationToken.None); // Page 2 + await model.resolve(20, CancellationToken.None); // Page 3 (final) + + // After loading all data, there should be no more pages + assert.strictEqual(model.length, 30); // Exactly 30 items, no sentinel + + // Accessing resolved items should work + assert.strictEqual(model.isResolved(29), true); + assert.strictEqual(model.isResolved(30), false); + }); + + test('concurrent access to sentinel only loads once', async () => { + const pager = createTestPager(10, 3); + const model = store.add(new IterativePagedModel(pager)); + + // Access sentinel concurrently + const [item1, item2, item3] = await Promise.all([ + model.resolve(10, CancellationToken.None), + model.resolve(10, CancellationToken.None), + model.resolve(10, CancellationToken.None) + ]); + + // All should get the same item + assert.strictEqual(item1, 10); + assert.strictEqual(item2, 10); + assert.strictEqual(item3, 10); + assert.strictEqual(model.length, 21); // 20 items + 1 sentinel + }); + + test('empty pager with no items', () => { + const emptyPager: IIterativePager = { + firstPage: { items: [], hasMore: false }, + getNextPage: async () => ({ items: [], hasMore: false }) + }; + const model = store.add(new IterativePagedModel(emptyPager)); + + assert.strictEqual(model.length, 0); + assert.strictEqual(model.isResolved(0), false); + }); + + test('single page pager with no more pages', () => { + const singlePagePager: IIterativePager = { + firstPage: { items: [1, 2, 3], hasMore: false }, + getNextPage: async () => ({ items: [], hasMore: false }) + }; + const model = store.add(new IterativePagedModel(singlePagePager)); + + assert.strictEqual(model.length, 3); // No sentinel + assert.strictEqual(model.isResolved(0), true); + assert.strictEqual(model.isResolved(2), true); + assert.strictEqual(model.isResolved(3), false); + assert.strictEqual(model.get(0), 1); + assert.strictEqual(model.get(2), 3); + }); + + test('accessing item beyond loaded range throws', () => { + const pager = createTestPager(10, 3); + const model = store.add(new IterativePagedModel(pager)); + + // Try to access item beyond current length + assert.throws(() => model.get(15), /Item not resolved yet/); + }); + + test('resolving item beyond all pages throws', async () => { + const pager = createTestPager(10, 3); + const model = store.add(new IterativePagedModel(pager)); + + // Load all pages + await model.resolve(10, CancellationToken.None); + await model.resolve(20, CancellationToken.None); + + // Try to resolve beyond the last item + await assert.rejects( + async () => model.resolve(30, CancellationToken.None), + /Index out of bounds/ + ); + }); + + test('cancelled token during initial resolve', async () => { + const pager = createTestPager(10, 3); + const model = store.add(new IterativePagedModel(pager)); + + const cts = new CancellationTokenSource(); + cts.cancel(); + + await assert.rejects( + async () => model.resolve(0, cts.token), + /Canceled/ + ); + }); + + test('event fires for each page load', async () => { + const pager = createTestPager(5, 4); + const model = store.add(new IterativePagedModel(pager)); + const lengths: number[] = []; + + store.add(model.onDidIncrementLength((length: number) => lengths.push(length))); + + // Initially has first page (5 items + 1 sentinel = 6) + assert.strictEqual(model.length, 6); + + // Load page 2 + await model.resolve(5, CancellationToken.None); + assert.deepStrictEqual(lengths, [11]); // 10 items + 1 sentinel + + // Load page 3 + await model.resolve(10, CancellationToken.None); + assert.deepStrictEqual(lengths, [11, 16]); // 15 items + 1 sentinel + + // Load page 4 (final) + await model.resolve(15, CancellationToken.None); + assert.deepStrictEqual(lengths, [11, 16, 20]); // 20 items, no sentinel + }); + + test('sequential page loads work correctly', async () => { + const pager = createTestPager(5, 3); + const model = store.add(new IterativePagedModel(pager)); + + // Load pages sequentially + for (let page = 1; page < 3; page++) { + const sentinelIndex = page * 5; + await model.resolve(sentinelIndex, CancellationToken.None); + } + + // Verify all items are accessible + assert.strictEqual(model.length, 15); // 3 pages * 5 items, no sentinel + for (let i = 0; i < 15; i++) { + assert.strictEqual(model.get(i), i); + assert.strictEqual(model.isResolved(i), true); + } + }); + + test('accessing items after loading all pages', async () => { + const pager = createTestPager(10, 2); + const model = store.add(new IterativePagedModel(pager)); + + // Load second page + await model.resolve(10, CancellationToken.None); + + // No sentinel after loading all pages + assert.strictEqual(model.length, 20); + assert.strictEqual(model.isResolved(19), true); + assert.strictEqual(model.isResolved(20), false); + + // All items should be accessible + for (let i = 0; i < 20; i++) { + assert.strictEqual(model.get(i), i); + } + }); + + test('pager with varying page sizes', async () => { + let pageNum = 0; + const varyingPager: IIterativePager = { + firstPage: { items: ['a', 'b', 'c'], hasMore: true }, + getNextPage: async (): Promise> => { + pageNum++; + if (pageNum === 1) { + return { items: ['d', 'e'], hasMore: true }; + } else if (pageNum === 2) { + return { items: ['f', 'g', 'h', 'i'], hasMore: false }; + } + return { items: [], hasMore: false }; + } + }; + + const model = store.add(new IterativePagedModel(varyingPager)); + + assert.strictEqual(model.length, 4); // 3 items + 1 sentinel + + // Load second page (2 items) + await model.resolve(3, CancellationToken.None); + assert.strictEqual(model.length, 6); // 5 items + 1 sentinel + assert.strictEqual(model.get(3), 'd'); + + // Load third page (4 items) + await model.resolve(5, CancellationToken.None); + assert.strictEqual(model.length, 9); // 9 items, no sentinel + assert.strictEqual(model.get(5), 'f'); + assert.strictEqual(model.get(8), 'i'); + }); +}); diff --git a/code/src/vs/base/test/common/json.test.ts b/code/src/vs/base/test/common/json.test.ts index 91a2c976f48..ff75a79a73e 100644 --- a/code/src/vs/base/test/common/json.test.ts +++ b/code/src/vs/base/test/common/json.test.ts @@ -49,6 +49,7 @@ function assertTree(input: string, expected: any, expectedErrors: number[] = [], if (node.children) { for (const child of node.children) { assert.strictEqual(node, child.parent); + // eslint-disable-next-line local/code-no-any-casts delete (child).parent; // delete to avoid recursion in deep equal checkParent(child); } diff --git a/code/src/vs/base/test/common/jsonParse.test.ts b/code/src/vs/base/test/common/jsonParse.test.ts index 83e520fad22..4fab789adee 100644 --- a/code/src/vs/base/test/common/jsonParse.test.ts +++ b/code/src/vs/base/test/common/jsonParse.test.ts @@ -12,127 +12,127 @@ suite('JSON Parse', () => { test('Line comment', () => { const content: string = [ - "{", - " \"prop\": 10 // a comment", - "}", + '{', + ' "prop": 10 // a comment', + '}', ].join('\n'); const expected = [ - "{", - " \"prop\": 10 ", - "}", + '{', + ' "prop": 10 ', + '}', ].join('\n'); assert.deepEqual(parse(content), JSON.parse(expected)); }); test('Line comment - EOF', () => { const content: string = [ - "{", - "}", - "// a comment" + '{', + '}', + '// a comment' ].join('\n'); const expected = [ - "{", - "}", - "" + '{', + '}', + '' ].join('\n'); assert.deepEqual(parse(content), JSON.parse(expected)); }); test('Line comment - \\r\\n', () => { const content: string = [ - "{", - " \"prop\": 10 // a comment", - "}", + '{', + ' "prop": 10 // a comment', + '}', ].join('\r\n'); const expected = [ - "{", - " \"prop\": 10 ", - "}", + '{', + ' "prop": 10 ', + '}', ].join('\r\n'); assert.deepEqual(parse(content), JSON.parse(expected)); }); test('Line comment - EOF - \\r\\n', () => { const content: string = [ - "{", - "}", - "// a comment" + '{', + '}', + '// a comment' ].join('\r\n'); const expected = [ - "{", - "}", - "" + '{', + '}', + '' ].join('\r\n'); assert.deepEqual(parse(content), JSON.parse(expected)); }); test('Block comment - single line', () => { const content: string = [ - "{", - " /* before */\"prop\": 10/* after */", - "}", + '{', + ' /* before */"prop": 10/* after */', + '}', ].join('\n'); const expected = [ - "{", - " \"prop\": 10", - "}", + '{', + ' "prop": 10', + '}', ].join('\n'); assert.deepEqual(parse(content), JSON.parse(expected)); }); test('Block comment - multi line', () => { const content: string = [ - "{", - " /**", - " * Some comment", - " */", - " \"prop\": 10", - "}", + '{', + ' /**', + ' * Some comment', + ' */', + ' "prop": 10', + '}', ].join('\n'); const expected = [ - "{", - " ", - " \"prop\": 10", - "}", + '{', + ' ', + ' "prop": 10', + '}', ].join('\n'); assert.deepEqual(parse(content), JSON.parse(expected)); }); test('Block comment - shortest match', () => { - const content = "/* abc */ */"; - const expected = " */"; + const content = '/* abc */ */'; + const expected = ' */'; assert.strictEqual(stripComments(content), expected); }); test('No strings - double quote', () => { const content: string = [ - "{", - " \"/* */\": 10", - "}" + '{', + ' "/* */": 10', + '}' ].join('\n'); const expected: string = [ - "{", - " \"/* */\": 10", - "}" + '{', + ' "/* */": 10', + '}' ].join('\n'); assert.deepEqual(parse(content), JSON.parse(expected)); }); test('No strings - single quote', () => { const content: string = [ - "{", - " '/* */': 10", - "}" + '{', + ` '/* */': 10`, + '}' ].join('\n'); const expected: string = [ - "{", - " '/* */': 10", - "}" + '{', + ` '/* */': 10`, + '}' ].join('\n'); assert.strictEqual(stripComments(content), expected); }); test('Trailing comma in object', () => { const content: string = [ - "{", + '{', ` "a": 10,`, - "}" + '}' ].join('\n'); const expected: string = [ - "{", + '{', ` "a": 10`, - "}" + '}' ].join('\n'); assert.deepEqual(parse(content), JSON.parse(expected)); }); @@ -148,16 +148,16 @@ suite('JSON Parse', () => { test('Trailing comma', () => { const content: string = [ - "{", - " \"propA\": 10, // a comment", - " \"propB\": false, // a trailing comma", - "}", + '{', + ' "propA": 10, // a comment', + ' "propB": false, // a trailing comma', + '}', ].join('\n'); const expected = [ - "{", - " \"propA\": 10,", - " \"propB\": false", - "}", + '{', + ' "propA": 10,', + ' "propB": false', + '}', ].join('\n'); assert.deepEqual(parse(content), JSON.parse(expected)); }); @@ -186,9 +186,9 @@ suite('JSON Parse', () => { } `; assert.deepEqual(parse(content), { - "enable-crash-reporter": true, - "crash-reporter-id": "aaaaab31-7453-4506-97d0-93411b2c21c7", - "locale": "en" + 'enable-crash-reporter': true, + 'crash-reporter-id': 'aaaaab31-7453-4506-97d0-93411b2c21c7', + 'locale': 'en' }); }); }); diff --git a/code/src/vs/base/test/common/jsonSchema.test.ts b/code/src/vs/base/test/common/jsonSchema.test.ts index da678c56675..aad3607ad37 100644 --- a/code/src/vs/base/test/common/jsonSchema.test.ts +++ b/code/src/vs/base/test/common/jsonSchema.test.ts @@ -79,7 +79,7 @@ suite('JSON Schema', () => { } }, $defs: { - "_0": { + '_0': { type: 'object', properties: { c: { @@ -155,7 +155,7 @@ suite('JSON Schema', () => { } }, $defs: { - "_0": { + '_0': { type: 'object', properties: { b: { @@ -270,44 +270,44 @@ suite('JSON Schema', () => { }; const expected: IJSONSchema = { - "type": "object", - "properties": { - "a": { - "type": "object", - "oneOf": [ + 'type': 'object', + 'properties': { + 'a': { + 'type': 'object', + 'oneOf': [ { - "allOf": [ + 'allOf': [ { - "$ref": "#/$defs/_0" + '$ref': '#/$defs/_0' }, { - "$ref": "#/$defs/_1" + '$ref': '#/$defs/_1' } ] }, { - "allOf": [ + 'allOf': [ { - "$ref": "#/$defs/_0" + '$ref': '#/$defs/_0' }, { - "properties": { - "river": { - "type": "string" + 'properties': { + 'river': { + 'type': 'string' } } } ] }, { - "allOf": [ + 'allOf': [ { - "$ref": "#/$defs/_0" + '$ref': '#/$defs/_0' }, { - "properties": { - "mountain": { - "type": "string" + 'properties': { + 'mountain': { + 'type': 'string' } } } @@ -315,30 +315,30 @@ suite('JSON Schema', () => { } ] }, - "b": { - "type": "object", - "properties": { - "street": { - "$ref": "#/$defs/_1" + 'b': { + 'type': 'object', + 'properties': { + 'street': { + '$ref': '#/$defs/_1' } } } }, - "$defs": { - "_0": { - "properties": { - "name": { - "type": "string" + '$defs': { + '_0': { + 'properties': { + 'name': { + 'type': 'string' }, - "description": { - "type": "string" + 'description': { + 'type': 'string' } } }, - "_1": { - "properties": { - "street": { - "type": "string" + '_1': { + 'properties': { + 'street': { + 'type': 'string' } } } @@ -415,7 +415,7 @@ suite('JSON Schema', () => { } }, $defs: { - "_0": { + '_0': { type: 'object', properties: { b: { @@ -428,7 +428,7 @@ suite('JSON Schema', () => { } } }, - "_1": { + '_1': { type: 'object', properties: { d: { @@ -536,13 +536,13 @@ suite('JSON Schema', () => { } }, $defs: { - "_0": { + '_0': { type: 'array', items: { $ref: '#/$defs/_2' } }, - "_1": { + '_1': { type: 'object', properties: { b: { @@ -550,7 +550,7 @@ suite('JSON Schema', () => { } } }, - "_2": { + '_2': { type: 'object', properties: { c: { diff --git a/code/src/vs/base/test/common/mock.ts b/code/src/vs/base/test/common/mock.ts index 7a0d9fbefd7..0e7ba6901af 100644 --- a/code/src/vs/base/test/common/mock.ts +++ b/code/src/vs/base/test/common/mock.ts @@ -4,12 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { SinonStub, stub } from 'sinon'; +import { DeepPartial } from '../../common/types.js'; export interface Ctor { new(): T; } export function mock(): Ctor { + // eslint-disable-next-line local/code-no-any-casts return function () { } as any; } @@ -18,6 +20,7 @@ export type MockObject = { [K in keyof T]: K extends Exc // Creates an object object that returns sinon mocks for every property. Optionally // takes base properties. export const mockObject = () => = {}>(properties?: TP): MockObject => { + // eslint-disable-next-line local/code-no-any-casts return new Proxy({ ...properties } as any, { get(target, key) { if (!target.hasOwnProperty(key)) { @@ -32,3 +35,13 @@ export const mockObject = () => = {}>(p }, }); }; + +/** + * Shortcut for type-safe partials in mocks. A shortcut for `obj as Partial as T`. + */ +export function upcastPartial(partial: Partial): T { + return partial as T; +} +export function upcastDeepPartial(partial: DeepPartial): T { + return partial as T; +} diff --git a/code/src/vs/base/test/common/normalization.test.ts b/code/src/vs/base/test/common/normalization.test.ts index 8ef33d54cc4..651f2b4f6ac 100644 --- a/code/src/vs/base/test/common/normalization.test.ts +++ b/code/src/vs/base/test/common/normalization.test.ts @@ -4,64 +4,86 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { removeAccents } from '../../common/normalization.js'; +import { tryNormalizeToBase } from '../../common/normalization.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from './utils.js'; suite('Normalization', () => { ensureNoDisposablesAreLeakedInTestSuite(); - test('removeAccents', function () { - assert.strictEqual(removeAccents('joào'), 'joao'); - assert.strictEqual(removeAccents('joáo'), 'joao'); - assert.strictEqual(removeAccents('joâo'), 'joao'); - assert.strictEqual(removeAccents('joäo'), 'joao'); - // assert.strictEqual(strings.removeAccents('joæo'), 'joao'); // not an accent - assert.strictEqual(removeAccents('joão'), 'joao'); - assert.strictEqual(removeAccents('joåo'), 'joao'); - assert.strictEqual(removeAccents('joåo'), 'joao'); - assert.strictEqual(removeAccents('joāo'), 'joao'); + test('tryNormalizeToBase', function () { + assert.strictEqual(tryNormalizeToBase('joào'), 'joao'); + assert.strictEqual(tryNormalizeToBase('joáo'), 'joao'); + assert.strictEqual(tryNormalizeToBase('joâo'), 'joao'); + assert.strictEqual(tryNormalizeToBase('joäo'), 'joao'); + // assert.strictEqual(strings.tryNormalizeToBase('joæo'), 'joao'); // not an accent + assert.strictEqual(tryNormalizeToBase('joão'), 'joao'); + assert.strictEqual(tryNormalizeToBase('joåo'), 'joao'); + assert.strictEqual(tryNormalizeToBase('joåo'), 'joao'); + assert.strictEqual(tryNormalizeToBase('joāo'), 'joao'); - assert.strictEqual(removeAccents('fôo'), 'foo'); - assert.strictEqual(removeAccents('föo'), 'foo'); - assert.strictEqual(removeAccents('fòo'), 'foo'); - assert.strictEqual(removeAccents('fóo'), 'foo'); - // assert.strictEqual(strings.removeAccents('fœo'), 'foo'); - // assert.strictEqual(strings.removeAccents('føo'), 'foo'); - assert.strictEqual(removeAccents('fōo'), 'foo'); - assert.strictEqual(removeAccents('fõo'), 'foo'); + assert.strictEqual(tryNormalizeToBase('fôo'), 'foo'); + assert.strictEqual(tryNormalizeToBase('föo'), 'foo'); + assert.strictEqual(tryNormalizeToBase('fòo'), 'foo'); + assert.strictEqual(tryNormalizeToBase('fóo'), 'foo'); + // assert.strictEqual(strings.tryNormalizeToBase('fœo'), 'foo'); + // assert.strictEqual(strings.tryNormalizeToBase('føo'), 'foo'); + assert.strictEqual(tryNormalizeToBase('fōo'), 'foo'); + assert.strictEqual(tryNormalizeToBase('fõo'), 'foo'); - assert.strictEqual(removeAccents('andrè'), 'andre'); - assert.strictEqual(removeAccents('andré'), 'andre'); - assert.strictEqual(removeAccents('andrê'), 'andre'); - assert.strictEqual(removeAccents('andrë'), 'andre'); - assert.strictEqual(removeAccents('andrē'), 'andre'); - assert.strictEqual(removeAccents('andrė'), 'andre'); - assert.strictEqual(removeAccents('andrę'), 'andre'); + assert.strictEqual(tryNormalizeToBase('andrè'), 'andre'); + assert.strictEqual(tryNormalizeToBase('andré'), 'andre'); + assert.strictEqual(tryNormalizeToBase('andrê'), 'andre'); + assert.strictEqual(tryNormalizeToBase('andrë'), 'andre'); + assert.strictEqual(tryNormalizeToBase('andrē'), 'andre'); + assert.strictEqual(tryNormalizeToBase('andrė'), 'andre'); + assert.strictEqual(tryNormalizeToBase('andrę'), 'andre'); - assert.strictEqual(removeAccents('hvîc'), 'hvic'); - assert.strictEqual(removeAccents('hvïc'), 'hvic'); - assert.strictEqual(removeAccents('hvíc'), 'hvic'); - assert.strictEqual(removeAccents('hvīc'), 'hvic'); - assert.strictEqual(removeAccents('hvįc'), 'hvic'); - assert.strictEqual(removeAccents('hvìc'), 'hvic'); + assert.strictEqual(tryNormalizeToBase('hvîc'), 'hvic'); + assert.strictEqual(tryNormalizeToBase('hvïc'), 'hvic'); + assert.strictEqual(tryNormalizeToBase('hvíc'), 'hvic'); + assert.strictEqual(tryNormalizeToBase('hvīc'), 'hvic'); + assert.strictEqual(tryNormalizeToBase('hvįc'), 'hvic'); + assert.strictEqual(tryNormalizeToBase('hvìc'), 'hvic'); - assert.strictEqual(removeAccents('ûdo'), 'udo'); - assert.strictEqual(removeAccents('üdo'), 'udo'); - assert.strictEqual(removeAccents('ùdo'), 'udo'); - assert.strictEqual(removeAccents('údo'), 'udo'); - assert.strictEqual(removeAccents('ūdo'), 'udo'); + assert.strictEqual(tryNormalizeToBase('ûdo'), 'udo'); + assert.strictEqual(tryNormalizeToBase('üdo'), 'udo'); + assert.strictEqual(tryNormalizeToBase('ùdo'), 'udo'); + assert.strictEqual(tryNormalizeToBase('údo'), 'udo'); + assert.strictEqual(tryNormalizeToBase('ūdo'), 'udo'); - assert.strictEqual(removeAccents('heÿ'), 'hey'); + assert.strictEqual(tryNormalizeToBase('heÿ'), 'hey'); - // assert.strictEqual(strings.removeAccents('gruß'), 'grus'); - assert.strictEqual(removeAccents('gruś'), 'grus'); - assert.strictEqual(removeAccents('gruš'), 'grus'); + // assert.strictEqual(strings.tryNormalizeToBase('gruß'), 'grus'); + assert.strictEqual(tryNormalizeToBase('gruś'), 'grus'); + assert.strictEqual(tryNormalizeToBase('gruš'), 'grus'); - assert.strictEqual(removeAccents('çool'), 'cool'); - assert.strictEqual(removeAccents('ćool'), 'cool'); - assert.strictEqual(removeAccents('čool'), 'cool'); + assert.strictEqual(tryNormalizeToBase('çool'), 'cool'); + assert.strictEqual(tryNormalizeToBase('ćool'), 'cool'); + assert.strictEqual(tryNormalizeToBase('čool'), 'cool'); - assert.strictEqual(removeAccents('ñice'), 'nice'); - assert.strictEqual(removeAccents('ńice'), 'nice'); + assert.strictEqual(tryNormalizeToBase('ñice'), 'nice'); + assert.strictEqual(tryNormalizeToBase('ńice'), 'nice'); + + // Different cases + assert.strictEqual(tryNormalizeToBase('CAFÉ'), 'cafe'); + assert.strictEqual(tryNormalizeToBase('Café'), 'cafe'); + assert.strictEqual(tryNormalizeToBase('café'), 'cafe'); + assert.strictEqual(tryNormalizeToBase('JOÃO'), 'joao'); + assert.strictEqual(tryNormalizeToBase('João'), 'joao'); + + // Mixed cases with accents + assert.strictEqual(tryNormalizeToBase('CaFé'), 'cafe'); + assert.strictEqual(tryNormalizeToBase('JoÃo'), 'joao'); + assert.strictEqual(tryNormalizeToBase('AnDrÉ'), 'andre'); + + // Precomposed accents + assert.strictEqual(tryNormalizeToBase('\u00E9'), 'e'); + assert.strictEqual(tryNormalizeToBase('\u00E0'), 'a'); + assert.strictEqual(tryNormalizeToBase('caf\u00E9'), 'cafe'); + + // Base + combining accents - lower only + assert.strictEqual(tryNormalizeToBase('\u0065\u0301'), '\u0065\u0301'); + assert.strictEqual(tryNormalizeToBase('Ã\u0061\u0300'), 'ã\u0061\u0300'); + assert.strictEqual(tryNormalizeToBase('CaF\u0065\u0301'), 'caf\u0065\u0301'); }); }); diff --git a/code/src/vs/base/test/common/numbers.test.ts b/code/src/vs/base/test/common/numbers.test.ts index 94c07090585..e21844eea98 100644 --- a/code/src/vs/base/test/common/numbers.test.ts +++ b/code/src/vs/base/test/common/numbers.test.ts @@ -5,7 +5,7 @@ import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from './utils.js'; -import { isPointWithinTriangle, randomInt } from '../../common/numbers.js'; +import { isPointWithinTriangle } from '../../common/numbers.js'; suite('isPointWithinTriangle', () => { ensureNoDisposablesAreLeakedInTestSuite(); @@ -25,176 +25,3 @@ suite('isPointWithinTriangle', () => { assert.ok(result); }); }); - -suite('randomInt', () => { - ensureNoDisposablesAreLeakedInTestSuite(); - - /** - * Test helper that allows to run a test on the `randomInt()` - * utility with specified `max` and `min` values. - */ - const testRandomIntUtil = (max: number, min: number | undefined, testName: string) => { - suite(testName, () => { - let i = 0; - while (++i < 5) { - test(`should generate random boolean attempt#${i}`, async () => { - let iterations = 100; - while (iterations-- > 0) { - const int = randomInt(max, min); - - assert( - int <= max, - `Expected ${int} to be less than or equal to ${max}.` - ); - assert( - int >= (min ?? 0), - `Expected ${int} to be greater than or equal to ${min ?? 0}.`, - ); - } - }); - } - - test('should include min and max', async () => { - let iterations = 125; - const results = []; - while (iterations-- > 0) { - results.push(randomInt(max, min)); - } - - assert( - results.includes(max), - `Expected ${results} to include ${max}.`, - ); - assert( - results.includes(min ?? 0), - `Expected ${results} to include ${min ?? 0}.`, - ); - }); - }); - }; - - suite('positive numbers', () => { - testRandomIntUtil(4, 2, 'max: 4, min: 2'); - testRandomIntUtil(4, 0, 'max: 4, min: 0'); - testRandomIntUtil(4, undefined, 'max: 4, min: undefined'); - testRandomIntUtil(1, 0, 'max: 0, min: 0'); - }); - - suite('negative numbers', () => { - testRandomIntUtil(-2, -5, 'max: -2, min: -5'); - testRandomIntUtil(0, -5, 'max: 0, min: -5'); - testRandomIntUtil(0, -1, 'max: 0, min: -1'); - }); - - suite('split numbers', () => { - testRandomIntUtil(3, -1, 'max: 3, min: -1'); - testRandomIntUtil(2, -2, 'max: 2, min: -2'); - testRandomIntUtil(1, -3, 'max: 2, min: -2'); - }); - - suite('errors', () => { - test('should throw if "min" is == "max" #1', () => { - assert.throws(() => { - randomInt(200, 200); - }, `"max"(200) param should be greater than "min"(200)."`); - }); - - test('should throw if "min" is == "max" #2', () => { - assert.throws(() => { - randomInt(2, 2); - }, `"max"(2) param should be greater than "min"(2)."`); - }); - - test('should throw if "min" is == "max" #3', () => { - assert.throws(() => { - randomInt(0); - }, `"max"(0) param should be greater than "min"(0)."`); - }); - - test('should throw if "min" is > "max" #1', () => { - assert.throws(() => { - randomInt(2, 3); - }, `"max"(2) param should be greater than "min"(3)."`); - }); - - test('should throw if "min" is > "max" #2', () => { - assert.throws(() => { - randomInt(999, 2000); - }, `"max"(999) param should be greater than "min"(2000)."`); - }); - - test('should throw if "min" is > "max" #3', () => { - assert.throws(() => { - randomInt(0, 1); - }, `"max"(0) param should be greater than "min"(1)."`); - }); - - test('should throw if "min" is > "max" #4', () => { - assert.throws(() => { - randomInt(-5, 2); - }, `"max"(-5) param should be greater than "min"(2)."`); - }); - - test('should throw if "min" is > "max" #5', () => { - assert.throws(() => { - randomInt(-4, 0); - }, `"max"(-4) param should be greater than "min"(0)."`); - }); - - test('should throw if "min" is > "max" #6', () => { - assert.throws(() => { - randomInt(-4); - }, `"max"(-4) param should be greater than "min"(0)."`); - }); - - test('should throw if "max" is `NaN`', () => { - assert.throws(() => { - randomInt(NaN); - }, `"max" param is not a number."`); - }); - - test('should throw if "min" is `NaN`', () => { - assert.throws(() => { - randomInt(4, NaN); - }, `"min" param is not a number."`); - }); - - suite('infinite arguments', () => { - test('should throw if "max" is infinite [Infinity]', () => { - assert.throws(() => { - randomInt(Infinity); - }, `"max" param is not finite."`); - }); - - test('should throw if "max" is infinite [-Infinity]', () => { - assert.throws(() => { - randomInt(-Infinity); - }, `"max" param is not finite."`); - }); - - test('should throw if "max" is infinite [+Infinity]', () => { - assert.throws(() => { - randomInt(+Infinity); - }, `"max" param is not finite."`); - }); - - test('should throw if "min" is infinite [Infinity]', () => { - assert.throws(() => { - randomInt(Infinity, Infinity); - }, `"max" param is not finite."`); - }); - - test('should throw if "min" is infinite [-Infinity]', () => { - assert.throws(() => { - randomInt(Infinity, -Infinity); - }, `"max" param is not finite."`); - }); - - test('should throw if "min" is infinite [+Infinity]', () => { - assert.throws(() => { - randomInt(Infinity, +Infinity); - }, `"max" param is not finite."`); - }); - }); - }); -}); diff --git a/code/src/vs/base/test/common/oauth.test.ts b/code/src/vs/base/test/common/oauth.test.ts index 78de7c02649..c554a8b7bdd 100644 --- a/code/src/vs/base/test/common/oauth.test.ts +++ b/code/src/vs/base/test/common/oauth.test.ts @@ -17,6 +17,8 @@ import { isAuthorizationTokenResponse, parseWWWAuthenticateHeader, fetchDynamicRegistration, + fetchResourceMetadata, + fetchAuthorizationServerMetadata, scopesMatch, IAuthorizationJWTClaims, IAuthorizationServerMetadata, @@ -309,6 +311,18 @@ suite('OAuth', () => { const scopes2 = ['scope2', 'scope1', 'scope1']; assert.strictEqual(scopesMatch(scopes1, scopes2), true); }); + + test('scopesMatch should handle undefined values', () => { + assert.strictEqual(scopesMatch(undefined, undefined), true); + assert.strictEqual(scopesMatch(['read'], undefined), false); + assert.strictEqual(scopesMatch(undefined, ['write']), false); + }); + + test('scopesMatch should handle mixed undefined and empty arrays', () => { + assert.strictEqual(scopesMatch([], undefined), false); + assert.strictEqual(scopesMatch(undefined, []), false); + assert.strictEqual(scopesMatch([], []), true); + }); }); suite('Utility Functions', () => { @@ -473,9 +487,7 @@ suite('OAuth', () => { assert.deepStrictEqual(requestBody.redirect_uris, [ 'https://insiders.vscode.dev/redirect', 'https://vscode.dev/redirect', - 'http://localhost/', 'http://127.0.0.1/', - `http://localhost:${DEFAULT_AUTH_FLOW_PORT}/`, `http://127.0.0.1:${DEFAULT_AUTH_FLOW_PORT}/` ]); @@ -833,4 +845,1413 @@ suite('OAuth', () => { ); }); }); + + suite('fetchResourceMetadata', () => { + let sandbox: sinon.SinonSandbox; + let fetchStub: sinon.SinonStub; + + setup(() => { + sandbox = sinon.createSandbox(); + fetchStub = sandbox.stub(); + }); + + teardown(() => { + sandbox.restore(); + }); + + test('should successfully fetch and validate resource metadata', async () => { + const targetResource = 'https://example.com/api'; + const resourceMetadataUrl = 'https://example.com/.well-known/oauth-protected-resource'; + const expectedMetadata = { + resource: 'https://example.com/api', + scopes_supported: ['read', 'write'] + }; + + fetchStub.resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata) + }); + + const result = await fetchResourceMetadata( + targetResource, + resourceMetadataUrl, + { fetch: fetchStub } + ); + + assert.deepStrictEqual(result.metadata, expectedMetadata); + assert.strictEqual(result.discoveryUrl, resourceMetadataUrl); + assert.deepStrictEqual(result.errors, []); + assert.strictEqual(fetchStub.callCount, 1); + assert.strictEqual(fetchStub.firstCall.args[0], resourceMetadataUrl); + assert.strictEqual(fetchStub.firstCall.args[1].method, 'GET'); + assert.strictEqual(fetchStub.firstCall.args[1].headers['Accept'], 'application/json'); + }); + + test('should include same-origin headers when origins match', async () => { + const targetResource = 'https://example.com/api'; + const resourceMetadataUrl = 'https://example.com/.well-known/oauth-protected-resource'; + const sameOriginHeaders = { + 'X-Test-Header': 'test-value', + 'X-Custom-Header': 'value' + }; + const expectedMetadata = { + resource: 'https://example.com/api' + }; + + fetchStub.resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata) + }); + + const result = await fetchResourceMetadata( + targetResource, + resourceMetadataUrl, + { fetch: fetchStub, sameOriginHeaders } + ); + + assert.strictEqual(result.discoveryUrl, resourceMetadataUrl); + const headers = fetchStub.firstCall.args[1].headers; + assert.strictEqual(headers['Accept'], 'application/json'); + assert.strictEqual(headers['X-Test-Header'], 'test-value'); + assert.strictEqual(headers['X-Custom-Header'], 'value'); + }); + + test('should not include same-origin headers when origins differ', async () => { + const targetResource = 'https://example.com/api'; + const resourceMetadataUrl = 'https://other-domain.com/.well-known/oauth-protected-resource'; + const sameOriginHeaders = { + 'X-Test-Header': 'test-value' + }; + const expectedMetadata = { + resource: 'https://example.com/api' + }; + + fetchStub.resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata) + }); + + const result = await fetchResourceMetadata( + targetResource, + resourceMetadataUrl, + { fetch: fetchStub, sameOriginHeaders } + ); + + assert.strictEqual(result.discoveryUrl, resourceMetadataUrl); + const headers = fetchStub.firstCall.args[1].headers; + assert.strictEqual(headers['Accept'], 'application/json'); + assert.strictEqual(headers['X-Test-Header'], undefined); + }); + + test('should throw error when fetch returns non-200 status', async () => { + const targetResource = 'https://example.com/api'; + const resourceMetadataUrl = 'https://example.com/.well-known/oauth-protected-resource'; + + // Stub all possible URLs to return 404 for robust fallback testing + fetchStub.resolves({ + status: 404, + text: async () => 'Not Found' + }); + + await assert.rejects( + async () => fetchResourceMetadata(targetResource, resourceMetadataUrl, { fetch: fetchStub }), + (error: any) => { + // Should be AggregateError since all URLs fail + assert.ok(error instanceof AggregateError || /Failed to fetch resource metadata from.*404 Not Found/.test(error.message)); + return true; + } + ); + }); + + test('should handle error when response.text() throws', async () => { + const targetResource = 'https://example.com/api'; + const resourceMetadataUrl = 'https://example.com/.well-known/oauth-protected-resource'; + + // Stub all possible URLs to return 500 for robust fallback testing + fetchStub.resolves({ + status: 500, + statusText: 'Internal Server Error', + text: async () => { throw new Error('Cannot read response'); } + }); + + await assert.rejects( + async () => fetchResourceMetadata(targetResource, resourceMetadataUrl, { fetch: fetchStub }), + (error: any) => { + // Should be AggregateError since all URLs fail + assert.ok(error instanceof AggregateError || /Failed to fetch resource metadata from.*500 Internal Server Error/.test(error.message)); + return true; + } + ); + }); + + test('should throw error when resource property does not match target resource', async () => { + const targetResource = 'https://example.com/api'; + const resourceMetadataUrl = 'https://example.com/.well-known/oauth-protected-resource'; + const metadata = { + resource: 'https://different.com/api' + }; + + // Stub all possible URLs to return invalid metadata for robust fallback testing + fetchStub.resolves({ + status: 200, + json: async () => metadata, + text: async () => JSON.stringify(metadata) + }); + + await assert.rejects( + async () => fetchResourceMetadata(targetResource, resourceMetadataUrl, { fetch: fetchStub }), + (error: any) => { + // Should be AggregateError since all URLs fail validation + assert.ok(error instanceof AggregateError); + assert.ok(error.errors.some((e: Error) => /does not match expected value/.test(e.message))); + return true; + } + ); + }); + + test('should normalize URLs when comparing resource values', async () => { + const targetResource = 'https://EXAMPLE.COM/api'; + const resourceMetadataUrl = 'https://example.com/.well-known/oauth-protected-resource'; + const metadata = { + resource: 'https://example.com/api' + }; + + fetchStub.resolves({ + status: 200, + json: async () => metadata, + text: async () => JSON.stringify(metadata) + }); + + // URL normalization should handle hostname case differences + const result = await fetchResourceMetadata(targetResource, resourceMetadataUrl, { fetch: fetchStub }); + assert.deepStrictEqual(result.metadata, metadata); + assert.strictEqual(result.discoveryUrl, resourceMetadataUrl); + }); + + test('should throw error when response is not valid resource metadata', async () => { + const targetResource = 'https://example.com/api'; + const resourceMetadataUrl = 'https://example.com/.well-known/oauth-protected-resource'; + const invalidMetadata = { + // Missing required 'resource' property + scopes_supported: ['read', 'write'] + }; + + // Stub all possible URLs to return invalid metadata for robust fallback testing + fetchStub.resolves({ + status: 200, + json: async () => invalidMetadata, + text: async () => JSON.stringify(invalidMetadata) + }); + + await assert.rejects( + async () => fetchResourceMetadata(targetResource, resourceMetadataUrl, { fetch: fetchStub }), + (error: any) => { + // Should be AggregateError since all URLs return invalid metadata + assert.ok(error instanceof AggregateError || /Invalid resource metadata/.test(error.message)); + return true; + } + ); + }); + + test('should throw error when scopes_supported is not an array', async () => { + const targetResource = 'https://example.com/api'; + const resourceMetadataUrl = 'https://example.com/.well-known/oauth-protected-resource'; + const invalidMetadata = { + resource: 'https://example.com/api', + scopes_supported: 'not an array' + }; + + // Stub all possible URLs to return invalid metadata for robust fallback testing + fetchStub.resolves({ + status: 200, + json: async () => invalidMetadata, + text: async () => JSON.stringify(invalidMetadata) + }); + + await assert.rejects( + async () => fetchResourceMetadata(targetResource, resourceMetadataUrl, { fetch: fetchStub }), + (error: any) => { + // Should be AggregateError since all URLs return invalid metadata + assert.ok(error instanceof AggregateError || /Invalid resource metadata/.test(error.message)); + return true; + } + ); + }); + + test('should handle metadata with optional fields', async () => { + const targetResource = 'https://example.com/api'; + const resourceMetadataUrl = 'https://example.com/.well-known/oauth-protected-resource'; + const metadata = { + resource: 'https://example.com/api', + resource_name: 'Example API', + authorization_servers: ['https://auth.example.com'], + jwks_uri: 'https://example.com/jwks', + scopes_supported: ['read', 'write', 'admin'], + bearer_methods_supported: ['header', 'body'], + resource_documentation: 'https://example.com/docs' + }; + + fetchStub.resolves({ + status: 200, + json: async () => metadata, + text: async () => JSON.stringify(metadata) + }); + + const result = await fetchResourceMetadata(targetResource, resourceMetadataUrl, { fetch: fetchStub }); + assert.deepStrictEqual(result.metadata, metadata); + }); + + test('should use global fetch when custom fetch is not provided', async () => { + const targetResource = 'https://example.com/api'; + const resourceMetadataUrl = 'https://example.com/.well-known/oauth-protected-resource'; + const metadata = { + resource: 'https://example.com/api' + }; + + // eslint-disable-next-line local/code-no-any-casts + const globalFetchStub = sandbox.stub(globalThis, 'fetch').resolves({ + status: 200, + json: async () => metadata, + text: async () => JSON.stringify(metadata) + } as any); + + const result = await fetchResourceMetadata(targetResource, resourceMetadataUrl); + + assert.deepStrictEqual(result.metadata, metadata); + assert.strictEqual(result.discoveryUrl, resourceMetadataUrl); + assert.strictEqual(globalFetchStub.callCount, 1); + }); + + test('should handle same origin with different ports', async () => { + const targetResource = 'https://example.com:8080/api'; + const resourceMetadataUrl = 'https://example.com:9090/.well-known/oauth-protected-resource'; + const sameOriginHeaders = { + 'X-Test-Header': 'test-value' + }; + const metadata = { + resource: 'https://example.com:8080/api' + }; + + fetchStub.resolves({ + status: 200, + json: async () => metadata, + text: async () => JSON.stringify(metadata) + }); + + const result = await fetchResourceMetadata( + targetResource, + resourceMetadataUrl, + { fetch: fetchStub, sameOriginHeaders } + ); + + assert.strictEqual(result.discoveryUrl, resourceMetadataUrl); + // Different ports mean different origins + const headers = fetchStub.firstCall.args[1].headers; + assert.strictEqual(headers['X-Test-Header'], undefined); + }); + + test('should handle same origin with different protocols', async () => { + const targetResource = 'http://example.com/api'; + const resourceMetadataUrl = 'https://example.com/.well-known/oauth-protected-resource'; + const sameOriginHeaders = { + 'X-Test-Header': 'test-value' + }; + const metadata = { + resource: 'http://example.com/api' + }; + + fetchStub.resolves({ + status: 200, + json: async () => metadata, + text: async () => JSON.stringify(metadata) + }); + + const result = await fetchResourceMetadata( + targetResource, + resourceMetadataUrl, + { fetch: fetchStub, sameOriginHeaders } + ); + + assert.strictEqual(result.discoveryUrl, resourceMetadataUrl); + // Different protocols mean different origins + const headers = fetchStub.firstCall.args[1].headers; + assert.strictEqual(headers['X-Test-Header'], undefined); + }); + + test('should include error details in message with resource values', async () => { + const targetResource = 'https://example.com/api'; + const resourceMetadataUrl = 'https://example.com/.well-known/oauth-protected-resource'; + const metadata = { + resource: 'https://different.com/other' + }; + + // Stub all possible URLs to return invalid metadata for robust fallback testing + fetchStub.resolves({ + status: 200, + json: async () => metadata, + text: async () => JSON.stringify(metadata) + }); + + try { + await fetchResourceMetadata(targetResource, resourceMetadataUrl, { fetch: fetchStub }); + assert.fail('Should have thrown an error'); + } catch (error: any) { + // Should be AggregateError with validation errors + const errorMessage = error instanceof AggregateError ? error.errors.map((e: Error) => e.message).join(' ') : error.message; + assert.ok(/does not match expected value/.test(errorMessage), 'Error message should mention mismatch'); + assert.ok(/https:\/\/different\.com\/other/.test(errorMessage), 'Error message should include actual resource value'); + assert.ok(/https:\/\/example\.com\/api/.test(errorMessage), 'Error message should include expected resource value'); + } + }); + + test('should fallback to well-known URI with path when no resourceMetadataUrl provided', async () => { + const targetResource = 'https://example.com/api/v1'; + const expectedMetadata = { + resource: 'https://example.com/api/v1', + scopes_supported: ['read', 'write'] + }; + + fetchStub.resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata) + }); + + const result = await fetchResourceMetadata( + targetResource, + undefined, + { fetch: fetchStub } + ); + + assert.deepStrictEqual(result.metadata, expectedMetadata); + assert.strictEqual(result.discoveryUrl, 'https://example.com/.well-known/oauth-protected-resource/api/v1'); + assert.strictEqual(fetchStub.callCount, 1); + // Should try path-appended version first + assert.strictEqual(fetchStub.firstCall.args[0], 'https://example.com/.well-known/oauth-protected-resource/api/v1'); + }); + + test('should fallback to well-known URI at root when path version fails', async () => { + const targetResource = 'https://example.com/api/v1'; + const expectedMetadata = { + resource: 'https://example.com/', + scopes_supported: ['read', 'write'] + }; + + // First call fails, second succeeds + fetchStub.onFirstCall().resolves({ + status: 404, + text: async () => 'Not Found', + statusText: 'Not Found' + }); + + fetchStub.onSecondCall().resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata) + }); + + const result = await fetchResourceMetadata( + targetResource, + undefined, + { fetch: fetchStub } + ); + + assert.deepStrictEqual(result.metadata, expectedMetadata); + assert.strictEqual(result.discoveryUrl, 'https://example.com/.well-known/oauth-protected-resource'); + assert.strictEqual(result.errors.length, 1); + assert.strictEqual(fetchStub.callCount, 2); + // First attempt with path + assert.strictEqual(fetchStub.firstCall.args[0], 'https://example.com/.well-known/oauth-protected-resource/api/v1'); + // Second attempt at root + assert.strictEqual(fetchStub.secondCall.args[0], 'https://example.com/.well-known/oauth-protected-resource'); + }); + + test('should throw error when all well-known URIs fail', async () => { + const targetResource = 'https://example.com/api/v1'; + + fetchStub.resolves({ + status: 404, + text: async () => 'Not Found', + statusText: 'Not Found' + }); + + await assert.rejects( + async () => fetchResourceMetadata(targetResource, undefined, { fetch: fetchStub }), + (error: any) => { + assert.ok(error instanceof AggregateError, 'Should be an AggregateError'); + assert.strictEqual(error.errors.length, 2, 'Should contain 2 errors'); + assert.ok(/Failed to fetch resource metadata from.*\/api\/v1.*404/.test(error.errors[0].message), 'First error should mention /api/v1 and 404'); + assert.ok(/Failed to fetch resource metadata from.*\.well-known.*404/.test(error.errors[1].message), 'Second error should mention .well-known and 404'); + return true; + } + ); assert.strictEqual(fetchStub.callCount, 2); + }); + + test('should not append path when target resource is root', async () => { + const targetResource = 'https://example.com/'; + const expectedMetadata = { + resource: 'https://example.com/', + scopes_supported: ['read'] + }; + + fetchStub.resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata) + }); + + const result = await fetchResourceMetadata( + targetResource, + undefined, + { fetch: fetchStub } + ); + + assert.deepStrictEqual(result.metadata, expectedMetadata); + assert.strictEqual(result.discoveryUrl, 'https://example.com/.well-known/oauth-protected-resource'); + assert.strictEqual(fetchStub.callCount, 1); + // Both URLs should be the same when path is / + assert.strictEqual(fetchStub.firstCall.args[0], 'https://example.com/.well-known/oauth-protected-resource'); + }); + + test('should include same-origin headers when using well-known fallback', async () => { + const targetResource = 'https://example.com/api'; + const sameOriginHeaders = { + 'X-Test-Header': 'test-value', + 'X-Custom-Header': 'value' + }; + const expectedMetadata = { + resource: 'https://example.com/api' + }; + + fetchStub.resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata) + }); + + const result = await fetchResourceMetadata( + targetResource, + undefined, + { fetch: fetchStub, sameOriginHeaders } + ); + + assert.strictEqual(result.discoveryUrl, 'https://example.com/.well-known/oauth-protected-resource/api'); + const headers = fetchStub.firstCall.args[1].headers; + assert.strictEqual(headers['Accept'], 'application/json'); + assert.strictEqual(headers['X-Test-Header'], 'test-value'); + assert.strictEqual(headers['X-Custom-Header'], 'value'); + }); + + test('should handle fetchImpl throwing network error and continue to next URL', async () => { + const targetResource = 'https://example.com/api/v1'; + const expectedMetadata = { + resource: 'https://example.com/', + scopes_supported: ['read', 'write'] + }; + + // First call throws network error, second succeeds + fetchStub.onFirstCall().rejects(new Error('Network connection failed')); + + fetchStub.onSecondCall().resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata) + }); + + const result = await fetchResourceMetadata( + targetResource, + undefined, + { fetch: fetchStub } + ); + + assert.deepStrictEqual(result.metadata, expectedMetadata); + assert.strictEqual(result.discoveryUrl, 'https://example.com/.well-known/oauth-protected-resource'); + assert.strictEqual(result.errors.length, 1); + assert.ok(/Network connection failed/.test(result.errors[0].message)); + assert.strictEqual(fetchStub.callCount, 2); + // First attempt with path should have thrown error + assert.strictEqual(fetchStub.firstCall.args[0], 'https://example.com/.well-known/oauth-protected-resource/api/v1'); + // Second attempt at root should succeed + assert.strictEqual(fetchStub.secondCall.args[0], 'https://example.com/.well-known/oauth-protected-resource'); + }); + + test('should throw AggregateError when fetchImpl throws on all URLs', async () => { + const targetResource = 'https://example.com/api/v1'; + + // Both calls throw network errors + fetchStub.rejects(new Error('Network connection failed')); + + await assert.rejects( + async () => fetchResourceMetadata(targetResource, undefined, { fetch: fetchStub }), + (error: any) => { + assert.ok(error instanceof AggregateError, 'Should be an AggregateError'); + assert.strictEqual(error.errors.length, 2, 'Should contain 2 errors'); + assert.ok(/Network connection failed/.test(error.errors[0].message), 'First error should mention network failure'); + assert.ok(/Network connection failed/.test(error.errors[1].message), 'Second error should mention network failure'); + return true; + } + ); + + assert.strictEqual(fetchStub.callCount, 2); + }); + + test('should handle mix of fetch error and non-200 response', async () => { + const targetResource = 'https://example.com/api/v1'; + + // First call throws network error + fetchStub.onFirstCall().rejects(new Error('Connection timeout')); + + // Second call returns 404 + fetchStub.onSecondCall().resolves({ + status: 404, + text: async () => 'Not Found', + statusText: 'Not Found' + }); + + await assert.rejects( + async () => fetchResourceMetadata(targetResource, undefined, { fetch: fetchStub }), + (error: any) => { + assert.ok(error instanceof AggregateError, 'Should be an AggregateError'); + assert.strictEqual(error.errors.length, 2, 'Should contain 2 errors'); + assert.ok(/Connection timeout/.test(error.errors[0].message), 'First error should be network error'); + assert.ok(/Failed to fetch resource metadata.*404/.test(error.errors[1].message), 'Second error should be 404'); + return true; + } + ); + + assert.strictEqual(fetchStub.callCount, 2); + }); + + test('should accept root URL in PRM resource when using root discovery fallback (no trailing slash)', async () => { + const targetResource = 'https://example.com/api/v1'; + // Per RFC 9728: when metadata retrieved from root discovery URL, + // the resource value must match the root URL (where well-known was inserted) + const expectedMetadata = { + resource: 'https://example.com', + scopes_supported: ['read', 'write'] + }; + + // First call (path-appended) fails, second (root) succeeds + fetchStub.onFirstCall().resolves({ + status: 404, + text: async () => 'Not Found', + statusText: 'Not Found' + }); + + fetchStub.onSecondCall().resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata) + }); + + const result = await fetchResourceMetadata( + targetResource, + undefined, + { fetch: fetchStub } + ); + + assert.deepStrictEqual(result.metadata, expectedMetadata); + assert.strictEqual(fetchStub.callCount, 2); + }); + + test('should accept root URL in PRM resource when using root discovery fallback (with trailing slash)', async () => { + const targetResource = 'https://example.com/api/v1'; + // Test that trailing slash form is also accepted (URL normalization) + const expectedMetadata = { + resource: 'https://example.com/', + scopes_supported: ['read', 'write'] + }; + + // First call (path-appended) fails, second (root) succeeds + fetchStub.onFirstCall().resolves({ + status: 404, + text: async () => 'Not Found', + statusText: 'Not Found' + }); + + fetchStub.onSecondCall().resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata) + }); + + const result = await fetchResourceMetadata( + targetResource, + undefined, + { fetch: fetchStub } + ); + + assert.deepStrictEqual(result.metadata, expectedMetadata); + assert.strictEqual(fetchStub.callCount, 2); + }); + + test('should reject PRM with full path resource when using root discovery fallback', async () => { + const targetResource = 'https://example.com/api/v1'; + // This violates RFC 9728: root discovery PRM should have root URL, not full path + const invalidMetadata = { + resource: 'https://example.com/api/v1', + scopes_supported: ['read'] + }; + + // First call (path-appended) fails, second (root) returns invalid metadata + fetchStub.onFirstCall().resolves({ + status: 404, + text: async () => 'Not Found', + statusText: 'Not Found' + }); + + fetchStub.onSecondCall().resolves({ + status: 200, + json: async () => invalidMetadata, + text: async () => JSON.stringify(invalidMetadata) + }); + + await assert.rejects( + async () => fetchResourceMetadata(targetResource, undefined, { fetch: fetchStub }), + (error: any) => { + assert.ok(error instanceof AggregateError, 'Should be an AggregateError'); + assert.strictEqual(error.errors.length, 2); + // First error is 404 from path-appended attempt + assert.ok(/404/.test(error.errors[0].message)); + // Second error is validation failure from root attempt + assert.ok(/does not match expected value/.test(error.errors[1].message)); + // Check that validation was against root URL (origin) not full path + assert.ok(/https:\/\/example\.com\/api\/v1.*https:\/\/example\.com/.test(error.errors[1].message)); + return true; + } + ); + + assert.strictEqual(fetchStub.callCount, 2); + }); + + test('should reject PRM with root resource when using path-appended discovery', async () => { + const targetResource = 'https://example.com/api/v1'; + // This violates RFC 9728: path-appended discovery PRM should match full target URL + const invalidMetadata = { + resource: 'https://example.com/', + scopes_supported: ['read'] + }; + + // First attempt (path-appended) gets the wrong resource value + // It will fail validation and continue to second URL (root) + // Second attempt (root) will succeed because root expects root resource + fetchStub.resolves({ + status: 200, + json: async () => invalidMetadata, + text: async () => JSON.stringify(invalidMetadata) + }); + + // This should actually succeed on the second (root) attempt + const result = await fetchResourceMetadata(targetResource, undefined, { fetch: fetchStub }); + + assert.deepStrictEqual(result.metadata, invalidMetadata); + assert.strictEqual(result.discoveryUrl, 'https://example.com/.well-known/oauth-protected-resource'); + assert.strictEqual(result.errors.length, 1); + assert.strictEqual(fetchStub.callCount, 2); + // Verify both URLs were tried + assert.strictEqual(fetchStub.firstCall.args[0], 'https://example.com/.well-known/oauth-protected-resource/api/v1'); + assert.strictEqual(fetchStub.secondCall.args[0], 'https://example.com/.well-known/oauth-protected-resource'); + }); + + test('should validate against targetResource when resourceMetadataUrl is explicitly provided', async () => { + const targetResource = 'https://example.com/api/v1'; + const resourceMetadataUrl = 'https://example.com/.well-known/oauth-protected-resource'; + // When explicit URL provided (e.g., from WWW-Authenticate), must match targetResource + const validMetadata = { + resource: 'https://example.com/api/v1', + scopes_supported: ['read'] + }; + + fetchStub.resolves({ + status: 200, + json: async () => validMetadata, + text: async () => JSON.stringify(validMetadata) + }); + + const result = await fetchResourceMetadata( + targetResource, + resourceMetadataUrl, + { fetch: fetchStub } + ); + + assert.deepStrictEqual(result.metadata, validMetadata); + assert.strictEqual(result.discoveryUrl, resourceMetadataUrl); + assert.strictEqual(fetchStub.callCount, 1); + assert.strictEqual(fetchStub.firstCall.args[0], resourceMetadataUrl); + }); + + test('should fallback to root discovery when explicit resourceMetadataUrl validation fails', async () => { + const targetResource = 'https://example.com/api/v1'; + const resourceMetadataUrl = 'https://example.com/.well-known/oauth-protected-resource'; + const invalidMetadata = { + resource: 'https://example.com/', + scopes_supported: ['read'] + }; + + // Stub all URLs to return root resource metadata + // Explicit URL returns root (validation fails), path-appended fails, root succeeds + fetchStub.resolves({ + status: 200, + json: async () => invalidMetadata, + text: async () => JSON.stringify(invalidMetadata) + }); + + // Should succeed on root discovery fallback + const result = await fetchResourceMetadata(targetResource, resourceMetadataUrl, { fetch: fetchStub }); + assert.deepStrictEqual(result.metadata, invalidMetadata); + assert.strictEqual(result.discoveryUrl, 'https://example.com/.well-known/oauth-protected-resource'); + assert.ok(result.errors.length >= 1); + // Should have tried explicit URL, path-appended, then succeeded on root + assert.ok(fetchStub.callCount >= 2); + }); + + test('should handle fetchImpl throwing error with explicit resourceMetadataUrl', async () => { + const targetResource = 'https://example.com/api'; + const resourceMetadataUrl = 'https://example.com/.well-known/oauth-protected-resource'; + + // Stub all possible URLs to throw network error for robust fallback testing + fetchStub.rejects(new Error('DNS resolution failed')); + + await assert.rejects( + async () => fetchResourceMetadata(targetResource, resourceMetadataUrl, { fetch: fetchStub }), + (error: any) => { + // Should be AggregateError since all URLs fail + assert.ok(error instanceof AggregateError || /DNS resolution failed/.test(error.message)); + return true; + } + ); + + // Should have tried explicit URL and well-known discovery + assert.ok(fetchStub.callCount >= 2); + }); + }); + + suite('fetchAuthorizationServerMetadata', () => { + let sandbox: sinon.SinonSandbox; + let fetchStub: sinon.SinonStub; + + setup(() => { + sandbox = sinon.createSandbox(); + fetchStub = sandbox.stub(); + }); + + teardown(() => { + sandbox.restore(); + }); + + test('should successfully fetch metadata from OAuth discovery endpoint with path insertion', async () => { + const authorizationServer = 'https://auth.example.com/tenant'; + const expectedMetadata: IAuthorizationServerMetadata = { + issuer: 'https://auth.example.com/tenant', + authorization_endpoint: 'https://auth.example.com/tenant/authorize', + token_endpoint: 'https://auth.example.com/tenant/token', + response_types_supported: ['code'] + }; + + fetchStub.resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata), + statusText: 'OK' + }); + + const result = await fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }); + + assert.deepStrictEqual(result.metadata, expectedMetadata); + assert.strictEqual(result.discoveryUrl, 'https://auth.example.com/.well-known/oauth-authorization-server/tenant'); + assert.deepStrictEqual(result.errors, []); + assert.strictEqual(fetchStub.callCount, 1); + // Should try OAuth discovery with path insertion: https://auth.example.com/.well-known/oauth-authorization-server/tenant + assert.strictEqual(fetchStub.firstCall.args[0], 'https://auth.example.com/.well-known/oauth-authorization-server/tenant'); + assert.strictEqual(fetchStub.firstCall.args[1].method, 'GET'); + }); + + test('should fallback to OpenID Connect discovery with path insertion', async () => { + const authorizationServer = 'https://auth.example.com/tenant'; + const expectedMetadata: IAuthorizationServerMetadata = { + issuer: 'https://auth.example.com/tenant', + authorization_endpoint: 'https://auth.example.com/tenant/authorize', + token_endpoint: 'https://auth.example.com/tenant/token', + response_types_supported: ['code'] + }; + + // First call fails, second succeeds + fetchStub.onFirstCall().resolves({ + status: 404, + text: async () => 'Not Found', + statusText: 'Not Found', + json: async () => { throw new Error('Not JSON'); } + }); + + fetchStub.onSecondCall().resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata), + statusText: 'OK' + }); + + const result = await fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }); + + assert.deepStrictEqual(result.metadata, expectedMetadata); + assert.strictEqual(result.discoveryUrl, 'https://auth.example.com/.well-known/openid-configuration/tenant'); + assert.strictEqual(result.errors.length, 1); + assert.strictEqual(fetchStub.callCount, 2); + // First attempt: OAuth discovery + assert.strictEqual(fetchStub.firstCall.args[0], 'https://auth.example.com/.well-known/oauth-authorization-server/tenant'); + // Second attempt: OpenID Connect discovery with path insertion + assert.strictEqual(fetchStub.secondCall.args[0], 'https://auth.example.com/.well-known/openid-configuration/tenant'); + }); + + test('should fallback to OpenID Connect discovery with path addition', async () => { + const authorizationServer = 'https://auth.example.com/tenant'; + const expectedMetadata: IAuthorizationServerMetadata = { + issuer: 'https://auth.example.com/tenant', + authorization_endpoint: 'https://auth.example.com/tenant/authorize', + token_endpoint: 'https://auth.example.com/tenant/token', + response_types_supported: ['code'] + }; + + // First two calls fail, third succeeds + fetchStub.onFirstCall().resolves({ + status: 404, + text: async () => 'Not Found', + statusText: 'Not Found', + json: async () => { throw new Error('Not JSON'); } + }); + + fetchStub.onSecondCall().resolves({ + status: 404, + text: async () => 'Not Found', + statusText: 'Not Found', + json: async () => { throw new Error('Not JSON'); } + }); + + fetchStub.onThirdCall().resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata), + statusText: 'OK' + }); + + const result = await fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }); + + assert.deepStrictEqual(result.metadata, expectedMetadata); + assert.strictEqual(result.discoveryUrl, 'https://auth.example.com/tenant/.well-known/openid-configuration'); + assert.strictEqual(result.errors.length, 2); + assert.strictEqual(fetchStub.callCount, 3); + // First attempt: OAuth discovery + assert.strictEqual(fetchStub.firstCall.args[0], 'https://auth.example.com/.well-known/oauth-authorization-server/tenant'); + // Second attempt: OpenID Connect discovery with path insertion + assert.strictEqual(fetchStub.secondCall.args[0], 'https://auth.example.com/.well-known/openid-configuration/tenant'); + // Third attempt: OpenID Connect discovery with path addition + assert.strictEqual(fetchStub.thirdCall.args[0], 'https://auth.example.com/tenant/.well-known/openid-configuration'); + }); + + test('should handle authorization server at root without extra path', async () => { + const authorizationServer = 'https://auth.example.com'; + const expectedMetadata: IAuthorizationServerMetadata = { + issuer: 'https://auth.example.com/', + authorization_endpoint: 'https://auth.example.com/authorize', + token_endpoint: 'https://auth.example.com/token', + response_types_supported: ['code'] + }; + + fetchStub.resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata), + statusText: 'OK' + }); + + const result = await fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }); + + assert.deepStrictEqual(result.metadata, expectedMetadata); + assert.strictEqual(result.discoveryUrl, 'https://auth.example.com/.well-known/oauth-authorization-server'); + assert.deepStrictEqual(result.errors, []); + assert.strictEqual(fetchStub.callCount, 1); + // For root URLs, no extra path is added + assert.strictEqual(fetchStub.firstCall.args[0], 'https://auth.example.com/.well-known/oauth-authorization-server'); + }); + + test('should handle authorization server with trailing slash', async () => { + const authorizationServer = 'https://auth.example.com/tenant/'; + const expectedMetadata: IAuthorizationServerMetadata = { + issuer: 'https://auth.example.com/tenant/', + authorization_endpoint: 'https://auth.example.com/tenant/authorize', + token_endpoint: 'https://auth.example.com/tenant/token', + response_types_supported: ['code'] + }; + + fetchStub.resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata), + statusText: 'OK' + }); + + const result = await fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }); + + assert.deepStrictEqual(result.metadata, expectedMetadata); + assert.strictEqual(result.discoveryUrl, 'https://auth.example.com/.well-known/oauth-authorization-server/tenant/'); + assert.deepStrictEqual(result.errors, []); + assert.strictEqual(fetchStub.callCount, 1); + }); + + test('should include additional headers in all requests', async () => { + const authorizationServer = 'https://auth.example.com/tenant'; + const additionalHeaders = { + 'X-Custom-Header': 'custom-value', + 'Authorization': 'Bearer token123' + }; + const expectedMetadata: IAuthorizationServerMetadata = { + issuer: 'https://auth.example.com/tenant', + response_types_supported: ['code'] + }; + + fetchStub.resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata), + statusText: 'OK' + }); + + const result = await fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub, additionalHeaders }); + + assert.strictEqual(result.discoveryUrl, 'https://auth.example.com/.well-known/oauth-authorization-server/tenant'); + const headers = fetchStub.firstCall.args[1].headers; + assert.strictEqual(headers['X-Custom-Header'], 'custom-value'); + assert.strictEqual(headers['Authorization'], 'Bearer token123'); + assert.strictEqual(headers['Accept'], 'application/json'); + }); + test('should throw AggregateError when all discovery endpoints fail', async () => { + const authorizationServer = 'https://auth.example.com/tenant'; + + fetchStub.resolves({ + status: 404, + text: async () => 'Not Found', + statusText: 'Not Found', + json: async () => { throw new Error('Not JSON'); } + }); + + await assert.rejects( + async () => fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }), + (error: any) => { + assert.ok(error instanceof AggregateError, 'Should be an AggregateError'); + assert.strictEqual(error.errors.length, 3, 'Should contain 3 errors (one for each URL)'); + assert.strictEqual(error.message, 'Failed to fetch authorization server metadata from all attempted URLs'); + // Verify each error includes the URL it attempted + assert.ok(/oauth-authorization-server.*404/.test(error.errors[0].message), 'First error should mention OAuth discovery and 404'); + assert.ok(/openid-configuration.*404/.test(error.errors[1].message), 'Second error should mention OpenID path insertion and 404'); + assert.ok(/openid-configuration.*404/.test(error.errors[2].message), 'Third error should mention OpenID path addition and 404'); + return true; + } + ); + + // Should have tried all three endpoints + assert.strictEqual(fetchStub.callCount, 3); + }); + + test('should throw single error (not AggregateError) when only one URL is tried and fails', async () => { + const authorizationServer = 'https://auth.example.com'; + + // First attempt succeeds on second try, so only one error is collected for first URL + fetchStub.onFirstCall().resolves({ + status: 500, + text: async () => 'Internal Server Error', + statusText: 'Internal Server Error', + json: async () => { throw new Error('Not JSON'); } + }); + + const expectedMetadata: IAuthorizationServerMetadata = { + issuer: 'https://auth.example.com/', + response_types_supported: ['code'] + }; + + fetchStub.onSecondCall().resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata), + statusText: 'OK' + }); + + // Should succeed on second attempt + const result = await fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }); + assert.deepStrictEqual(result.metadata, expectedMetadata); + assert.strictEqual(result.errors.length, 1); + assert.strictEqual(fetchStub.callCount, 2); + }); + + test('should throw AggregateError when multiple URLs fail with mixed error types', async () => { + const authorizationServer = 'https://auth.example.com/tenant'; + + // First call: network error + fetchStub.onFirstCall().rejects(new Error('Connection timeout')); + + // Second call: 404 + fetchStub.onSecondCall().resolves({ + status: 404, + text: async () => 'Not Found', + statusText: 'Not Found', + json: async () => { throw new Error('Not JSON'); } + }); + + // Third call: 500 + fetchStub.onThirdCall().resolves({ + status: 500, + text: async () => 'Internal Server Error', + statusText: 'Internal Server Error', + json: async () => { throw new Error('Not JSON'); } + }); + + await assert.rejects( + async () => fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }), + (error: any) => { + assert.ok(error instanceof AggregateError, 'Should be an AggregateError'); + assert.strictEqual(error.errors.length, 3, 'Should contain 3 errors'); + // First error is network error + assert.ok(/Connection timeout/.test(error.errors[0].message), 'First error should be network error'); + // Second error is 404 + assert.ok(/404.*Not Found/.test(error.errors[1].message), 'Second error should be 404'); + // Third error is 500 + assert.ok(/500.*Internal Server Error/.test(error.errors[2].message), 'Third error should be 500'); + return true; + } + ); + + assert.strictEqual(fetchStub.callCount, 3); + }); + + test('should handle invalid JSON response', async () => { + const authorizationServer = 'https://auth.example.com'; + + fetchStub.resolves({ + status: 200, + json: async () => { throw new Error('Invalid JSON'); }, + text: async () => 'Invalid JSON', + statusText: 'OK' + }); + + await assert.rejects( + async () => fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }), + /Failed to fetch authorization server metadata/ + ); + }); + + test('should handle valid JSON but invalid metadata structure', async () => { + const authorizationServer = 'https://auth.example.com'; + const invalidMetadata = { + // Missing required 'issuer' field + authorization_endpoint: 'https://auth.example.com/authorize' + }; + + fetchStub.resolves({ + status: 200, + json: async () => invalidMetadata, + text: async () => JSON.stringify(invalidMetadata), + statusText: 'OK' + }); + + await assert.rejects( + async () => fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }), + /Failed to fetch authorization server metadata/ + ); + }); + + test('should use global fetch when custom fetch is not provided', async () => { + const authorizationServer = 'https://auth.example.com'; + const expectedMetadata: IAuthorizationServerMetadata = { + issuer: 'https://auth.example.com/', + response_types_supported: ['code'] + }; + + // eslint-disable-next-line local/code-no-any-casts + const globalFetchStub = sandbox.stub(globalThis, 'fetch').resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata), + statusText: 'OK' + } as any); + + const result = await fetchAuthorizationServerMetadata(authorizationServer); + + assert.deepStrictEqual(result.metadata, expectedMetadata); + assert.strictEqual(result.discoveryUrl, 'https://auth.example.com/.well-known/oauth-authorization-server'); + assert.deepStrictEqual(result.errors, []); + assert.strictEqual(globalFetchStub.callCount, 1); + }); + + test('should handle network fetch failure and continue to next endpoint', async () => { + const authorizationServer = 'https://auth.example.com'; + const expectedMetadata: IAuthorizationServerMetadata = { + issuer: 'https://auth.example.com/', + response_types_supported: ['code'] + }; + + // First call throws network error, second succeeds + fetchStub.onFirstCall().rejects(new Error('Network error')); + fetchStub.onSecondCall().resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata), + statusText: 'OK' + }); + + const result = await fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }); + + assert.deepStrictEqual(result.metadata, expectedMetadata); + assert.strictEqual(result.errors.length, 1); + assert.ok(/Network error/.test(result.errors[0].message)); + // Should have tried two endpoints + assert.strictEqual(fetchStub.callCount, 2); + }); + + test('should throw error when network fails on all endpoints', async () => { + const authorizationServer = 'https://auth.example.com'; + + fetchStub.rejects(new Error('Network error')); + + await assert.rejects( + async () => fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }), + (error: any) => { + assert.ok(error instanceof AggregateError, 'Should be an AggregateError'); + assert.strictEqual(error.errors.length, 3, 'Should contain 3 errors'); + assert.strictEqual(error.message, 'Failed to fetch authorization server metadata from all attempted URLs'); + // All errors should be network errors + assert.ok(/Network error/.test(error.errors[0].message), 'First error should be network error'); + assert.ok(/Network error/.test(error.errors[1].message), 'Second error should be network error'); + assert.ok(/Network error/.test(error.errors[2].message), 'Third error should be network error'); + return true; + } + ); + + // Should have tried all three endpoints + assert.strictEqual(fetchStub.callCount, 3); + }); + + test('should handle mix of network error and non-200 response', async () => { + const authorizationServer = 'https://auth.example.com/tenant'; + const expectedMetadata: IAuthorizationServerMetadata = { + issuer: 'https://auth.example.com/tenant', + response_types_supported: ['code'] + }; + + // First call throws network error + fetchStub.onFirstCall().rejects(new Error('Connection timeout')); + + // Second call returns 404 + fetchStub.onSecondCall().resolves({ + status: 404, + text: async () => 'Not Found', + statusText: 'Not Found', + json: async () => { throw new Error('Not JSON'); } + }); + + // Third call succeeds + fetchStub.onThirdCall().resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata), + statusText: 'OK' + }); + + const result = await fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }); + + assert.deepStrictEqual(result.metadata, expectedMetadata); + assert.strictEqual(result.errors.length, 2); + // Should have tried all three endpoints + assert.strictEqual(fetchStub.callCount, 3); + }); + + test('should handle response.text() failure in error case', async () => { + const authorizationServer = 'https://auth.example.com'; + + fetchStub.resolves({ + status: 500, + text: async () => { throw new Error('Cannot read text'); }, + statusText: 'Internal Server Error', + json: async () => { throw new Error('Cannot read json'); } + }); + + await assert.rejects( + async () => fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }), + (error: any) => { + assert.ok(error instanceof AggregateError, 'Should be an AggregateError'); + assert.strictEqual(error.errors.length, 3, 'Should contain 3 errors'); + // All errors should include status code and statusText (fallback when text() fails) + for (const err of error.errors) { + assert.ok(/500 Internal Server Error/.test(err.message), `Error should mention 500 and statusText: ${err.message}`); + } + return true; + } + ); + }); + + test('should correctly handle path addition with trailing slash', async () => { + const authorizationServer = 'https://auth.example.com/tenant/'; + const expectedMetadata: IAuthorizationServerMetadata = { + issuer: 'https://auth.example.com/tenant/', + response_types_supported: ['code'] + }; + + // First two calls fail, third succeeds + fetchStub.onFirstCall().resolves({ + status: 404, + text: async () => 'Not Found', + statusText: 'Not Found', + json: async () => { throw new Error('Not JSON'); } + }); + + fetchStub.onSecondCall().resolves({ + status: 404, + text: async () => 'Not Found', + statusText: 'Not Found', + json: async () => { throw new Error('Not JSON'); } + }); + + fetchStub.onThirdCall().resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata), + statusText: 'OK' + }); + + const result = await fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }); + + assert.deepStrictEqual(result.metadata, expectedMetadata); + assert.strictEqual(result.discoveryUrl, 'https://auth.example.com/tenant/.well-known/openid-configuration'); + assert.strictEqual(result.errors.length, 2); + assert.strictEqual(fetchStub.callCount, 3); + // Third attempt should correctly handle trailing slash (not double-slash) + assert.strictEqual(fetchStub.thirdCall.args[0], 'https://auth.example.com/tenant/.well-known/openid-configuration'); + }); + + test('should handle deeply nested paths', async () => { + const authorizationServer = 'https://auth.example.com/tenant/org/sub'; + const expectedMetadata: IAuthorizationServerMetadata = { + issuer: 'https://auth.example.com/tenant/org/sub', + response_types_supported: ['code'] + }; + + fetchStub.resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata), + statusText: 'OK' + }); + + const result = await fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }); + + assert.deepStrictEqual(result.metadata, expectedMetadata); + assert.strictEqual(result.discoveryUrl, 'https://auth.example.com/.well-known/oauth-authorization-server/tenant/org/sub'); + assert.deepStrictEqual(result.errors, []); + assert.strictEqual(fetchStub.callCount, 1); + // Should correctly insert well-known path with nested paths + assert.strictEqual(fetchStub.firstCall.args[0], 'https://auth.example.com/.well-known/oauth-authorization-server/tenant/org/sub'); + }); + + test('should handle 200 response with non-metadata JSON', async () => { + const authorizationServer = 'https://auth.example.com'; + const invalidResponse = { + error: 'not_supported', + message: 'Metadata not available' + }; + + fetchStub.resolves({ + status: 200, + json: async () => invalidResponse, + text: async () => JSON.stringify(invalidResponse), + statusText: 'OK' + }); + + await assert.rejects( + async () => fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }), + (error: any) => { + assert.ok(error instanceof AggregateError, 'Should be an AggregateError'); + assert.strictEqual(error.errors.length, 3, 'Should contain 3 errors'); + // All errors should indicate failed to fetch with status code + for (const err of error.errors) { + assert.ok(/Failed to fetch authorization server metadata from/.test(err.message), `Error should mention failed fetch: ${err.message}`); + } + return true; + } + ); + + // Should try all three endpoints + assert.strictEqual(fetchStub.callCount, 3); + }); + + test('should validate metadata according to isAuthorizationServerMetadata', async () => { + const authorizationServer = 'https://auth.example.com'; + // Valid metadata with all required fields + const validMetadata: IAuthorizationServerMetadata = { + issuer: 'https://auth.example.com/', + authorization_endpoint: 'https://auth.example.com/authorize', + token_endpoint: 'https://auth.example.com/token', + jwks_uri: 'https://auth.example.com/jwks', + registration_endpoint: 'https://auth.example.com/register', + response_types_supported: ['code', 'token'] + }; + + fetchStub.resolves({ + status: 200, + json: async () => validMetadata, + text: async () => JSON.stringify(validMetadata), + statusText: 'OK' + }); + + const result = await fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }); + + assert.deepStrictEqual(result.metadata, validMetadata); + assert.strictEqual(result.discoveryUrl, 'https://auth.example.com/.well-known/oauth-authorization-server'); + assert.deepStrictEqual(result.errors, []); + assert.strictEqual(fetchStub.callCount, 1); + }); + + test('should handle URLs with query parameters', async () => { + const authorizationServer = 'https://auth.example.com/tenant?version=v2'; + const expectedMetadata: IAuthorizationServerMetadata = { + issuer: 'https://auth.example.com/tenant?version=v2', + response_types_supported: ['code'] + }; + + fetchStub.resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata), + statusText: 'OK' + }); + + const result = await fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub }); + + assert.deepStrictEqual(result.metadata, expectedMetadata); + // Query parameters are not included in the discovery URL (only pathname is extracted) + assert.strictEqual(result.discoveryUrl, 'https://auth.example.com/.well-known/oauth-authorization-server/tenant'); + assert.deepStrictEqual(result.errors, []); + assert.strictEqual(fetchStub.callCount, 1); + }); + + test('should handle empty additionalHeaders', async () => { + const authorizationServer = 'https://auth.example.com'; + const expectedMetadata: IAuthorizationServerMetadata = { + issuer: 'https://auth.example.com/', + response_types_supported: ['code'] + }; + + fetchStub.resolves({ + status: 200, + json: async () => expectedMetadata, + text: async () => JSON.stringify(expectedMetadata), + statusText: 'OK' + }); + + const result = await fetchAuthorizationServerMetadata(authorizationServer, { fetch: fetchStub, additionalHeaders: {} }); + + assert.strictEqual(result.discoveryUrl, 'https://auth.example.com/.well-known/oauth-authorization-server'); + const headers = fetchStub.firstCall.args[1].headers; + assert.strictEqual(headers['Accept'], 'application/json'); + }); + }); }); diff --git a/code/src/vs/base/test/common/observables/debug.test.ts b/code/src/vs/base/test/common/observables/debug.test.ts new file mode 100644 index 00000000000..c046999ef59 --- /dev/null +++ b/code/src/vs/base/test/common/observables/debug.test.ts @@ -0,0 +1,57 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { observableValue, derived, autorun } from '../../../common/observable.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../utils.js'; +// eslint-disable-next-line local/code-no-deep-import-of-internal +import { debugGetObservableGraph } from '../../../common/observableInternal/logging/debugGetDependencyGraph.js'; + +suite('debug', () => { + const ds = ensureNoDisposablesAreLeakedInTestSuite(); + + test('debugGetDependencyGraph', () => { + const myObservable1 = observableValue('myObservable1', 0); + const myObservable2 = observableValue('myObservable2', 0); + + const myComputed1 = derived(reader => { + /** @description myComputed1 */ + const value1 = myObservable1.read(reader); + const value2 = myObservable2.read(reader); + const sum = value1 + value2; + return sum; + }); + + const myComputed2 = derived(reader => { + /** @description myComputed2 */ + const value1 = myComputed1.read(reader); + const value2 = myObservable1.read(reader); + const value3 = myObservable2.read(reader); + const sum = value1 + value2 + value3; + return sum; + }); + + const myComputed3 = derived(reader => { + /** @description myComputed3 */ + const value1 = myComputed2.read(reader); + const value2 = myObservable1.read(reader); + const value3 = myObservable2.read(reader); + const sum = value1 + value2 + value3; + return sum; + }); + + ds.add(autorun(reader => { + /** @description myAutorun */ + myComputed3.read(reader); + })); + + + let idx = 0; + assert.deepStrictEqual( + debugGetObservableGraph(myComputed3, { type: 'dependencies', debugNamePostProcessor: name => `name${++idx}` }), + '* derived name1:\n value: 0\n state: upToDate\n dependencies:\n\t\t* derived name2:\n\t\t value: 0\n\t\t state: upToDate\n\t\t dependencies:\n\t\t\t\t* derived name3:\n\t\t\t\t value: 0\n\t\t\t\t state: upToDate\n\t\t\t\t dependencies:\n\t\t\t\t\t\t* observableValue name4:\n\t\t\t\t\t\t value: 0\n\t\t\t\t\t\t state: upToDate\n\t\t\t\t\t\t* observableValue name5:\n\t\t\t\t\t\t value: 0\n\t\t\t\t\t\t state: upToDate\n\t\t\t\t* observableValue name6 (already listed)\n\t\t\t\t* observableValue name7 (already listed)\n\t\t* observableValue name8 (already listed)\n\t\t* observableValue name9 (already listed)', + ); + }); +}); diff --git a/code/src/vs/base/test/common/observable.test.ts b/code/src/vs/base/test/common/observables/observable.test.ts similarity index 84% rename from code/src/vs/base/test/common/observable.test.ts rename to code/src/vs/base/test/common/observables/observable.test.ts index 20e262ffff9..c1814dfe7b1 100644 --- a/code/src/vs/base/test/common/observable.test.ts +++ b/code/src/vs/base/test/common/observables/observable.test.ts @@ -4,15 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import { setUnexpectedErrorHandler } from '../../common/errors.js'; -import { Emitter, Event } from '../../common/event.js'; -import { DisposableStore, toDisposable } from '../../common/lifecycle.js'; -import { IDerivedReader, IObservableWithChange, autorun, autorunHandleChanges, autorunWithStoreHandleChanges, derived, derivedDisposable, IObservable, IObserver, ISettableObservable, ITransaction, keepObserved, observableFromEvent, observableSignal, observableValue, recordChanges, transaction, waitForState, derivedHandleChanges, runOnChange, DebugLocation } from '../../common/observable.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from './utils.js'; +import { setUnexpectedErrorHandler } from '../../../common/errors.js'; +import { Emitter, Event } from '../../../common/event.js'; +import { DisposableStore, toDisposable } from '../../../common/lifecycle.js'; +import { IDerivedReader, IObservableWithChange, autorun, autorunHandleChanges, autorunWithStoreHandleChanges, derived, derivedDisposable, IObservable, IObserver, ISettableObservable, ITransaction, keepObserved, observableFromEvent, observableSignal, observableValue, recordChanges, transaction, waitForState, derivedHandleChanges, runOnChange, DebugLocation } from '../../../common/observable.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../utils.js'; // eslint-disable-next-line local/code-no-deep-import-of-internal -import { observableReducer } from '../../common/observableInternal/experimental/reducer.js'; +import { observableReducer } from '../../../common/observableInternal/experimental/reducer.js'; // eslint-disable-next-line local/code-no-deep-import-of-internal -import { BaseObservable } from '../../common/observableInternal/observables/baseObservable.js'; +import { BaseObservable } from '../../../common/observableInternal/observables/baseObservable.js'; suite('observables', () => { const ds = ensureNoDisposablesAreLeakedInTestSuite(); @@ -97,22 +97,22 @@ suite('observables', () => { })); // autorun runs immediately assert.deepStrictEqual(log.getAndClearEntries(), [ - "myDerived.recompute: 0 + 0 = 0", - "myAutorun(myDerived: 0)", + 'myDerived.recompute: 0 + 0 = 0', + 'myAutorun(myDerived: 0)', ]); observable1.set(1, undefined); // and on changes... assert.deepStrictEqual(log.getAndClearEntries(), [ - "myDerived.recompute: 1 + 0 = 1", - "myAutorun(myDerived: 1)", + 'myDerived.recompute: 1 + 0 = 1', + 'myAutorun(myDerived: 1)', ]); observable2.set(1, undefined); // ... of any dependency. assert.deepStrictEqual(log.getAndClearEntries(), [ - "myDerived.recompute: 1 + 1 = 2", - "myAutorun(myDerived: 2)", + 'myDerived.recompute: 1 + 1 = 2', + 'myAutorun(myDerived: 2)', ]); // Now we change multiple observables in a transaction to batch process the effects. @@ -127,8 +127,8 @@ suite('observables', () => { // deriveds are only recomputed on demand. // (Note that you cannot see the intermediate value when `obs1 == 5` and `obs2 == 1`) assert.deepStrictEqual(log.getAndClearEntries(), [ - "myDerived.recompute: 5 + 5 = 10", - "myAutorun(myDerived: 10)", + 'myDerived.recompute: 5 + 5 = 10', + 'myAutorun(myDerived: 10)', ]); transaction((tx) => { @@ -139,7 +139,7 @@ suite('observables', () => { assert.deepStrictEqual(log.getAndClearEntries(), []); }); // Now the autorun didn't run again, because its dependency changed from 10 to 10 (= no change). - assert.deepStrictEqual(log.getAndClearEntries(), (["myDerived.recompute: 6 + 4 = 10"])); + assert.deepStrictEqual(log.getAndClearEntries(), (['myDerived.recompute: 6 + 4 = 10'])); }); test('read during transaction', () => { @@ -162,8 +162,8 @@ suite('observables', () => { })); // autorun runs immediately assert.deepStrictEqual(log.getAndClearEntries(), [ - "myDerived.recompute: 0 + 0 = 0", - "myAutorun(myDerived: 0)", + 'myDerived.recompute: 0 + 0 = 0', + 'myAutorun(myDerived: 0)', ]); transaction((tx) => { @@ -171,7 +171,7 @@ suite('observables', () => { assert.deepStrictEqual(log.getAndClearEntries(), []); myDerived.get(); // This forces a (sync) recomputation of the current value! - assert.deepStrictEqual(log.getAndClearEntries(), (["myDerived.recompute: -10 + 0 = -10"])); + assert.deepStrictEqual(log.getAndClearEntries(), (['myDerived.recompute: -10 + 0 = -10'])); // This means, that even in transactions you can assume that all values you can read with `get` and `read` are up-to-date. // Read these values just might cause additional (potentially unneeded) recomputations. @@ -180,8 +180,8 @@ suite('observables', () => { }); // This autorun runs again, because its dependency changed from 0 to -10 and then back to 0. assert.deepStrictEqual(log.getAndClearEntries(), [ - "myDerived.recompute: -10 + 10 = 0", - "myAutorun(myDerived: 0)", + 'myDerived.recompute: -10 + 10 = 0', + 'myAutorun(myDerived: 0)', ]); }); @@ -268,35 +268,35 @@ suite('observables', () => { log.log(`value: ${computedSum.get()}`); // Those deriveds are recomputed on demand, i.e. when someone reads them. assert.deepStrictEqual(log.getAndClearEntries(), [ - "recompute1: 2 % 3 = 2", - "recompute2: 2 * 2 = 4", - "recompute3: 2 * 3 = 6", - "recompute4: 4 + 6 = 10", - "value: 10", + 'recompute1: 2 % 3 = 2', + 'recompute2: 2 * 2 = 4', + 'recompute3: 2 * 3 = 6', + 'recompute4: 4 + 6 = 10', + 'value: 10', ]); log.log(`value: ${computedSum.get()}`); // ... and then cached again - assert.deepStrictEqual(log.getAndClearEntries(), (["value: 10"])); + assert.deepStrictEqual(log.getAndClearEntries(), (['value: 10'])); disposable.dispose(); // Don't forget to dispose the keepAlive to prevent memory leaks! log.log(`value: ${computedSum.get()}`); // Which disables the cache again assert.deepStrictEqual(log.getAndClearEntries(), [ - "recompute1: 2 % 3 = 2", - "recompute2: 2 * 2 = 4", - "recompute3: 2 * 3 = 6", - "recompute4: 4 + 6 = 10", - "value: 10", + 'recompute1: 2 % 3 = 2', + 'recompute2: 2 * 2 = 4', + 'recompute3: 2 * 3 = 6', + 'recompute4: 4 + 6 = 10', + 'value: 10', ]); log.log(`value: ${computedSum.get()}`); assert.deepStrictEqual(log.getAndClearEntries(), [ - "recompute1: 2 % 3 = 2", - "recompute2: 2 * 2 = 4", - "recompute3: 2 * 3 = 6", - "recompute4: 4 + 6 = 10", - "value: 10", + 'recompute1: 2 % 3 = 2', + 'recompute2: 2 * 2 = 4', + 'recompute3: 2 * 3 = 6', + 'recompute4: 4 + 6 = 10', + 'value: 10', ]); // Why don't we just always keep the cache alive? @@ -395,38 +395,38 @@ suite('observables', () => { log.log(`myAutorun.run(myComputed3: ${myComputed3.read(reader)})`); })); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myComputed1.recompute(myObservable1: 0 + myObservable2: 0 = 0)", - "myComputed2.recompute(myComputed1: 0 + myObservable1: 0 + myObservable2: 0 = 0)", - "myComputed3.recompute(myComputed2: 0 + myObservable1: 0 + myObservable2: 0 = 0)", - "myAutorun.run(myComputed3: 0)", + 'myComputed1.recompute(myObservable1: 0 + myObservable2: 0 = 0)', + 'myComputed2.recompute(myComputed1: 0 + myObservable1: 0 + myObservable2: 0 = 0)', + 'myComputed3.recompute(myComputed2: 0 + myObservable1: 0 + myObservable2: 0 = 0)', + 'myAutorun.run(myComputed3: 0)', ]); myObservable1.set(1, undefined); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myComputed1.recompute(myObservable1: 1 + myObservable2: 0 = 1)", - "myComputed2.recompute(myComputed1: 1 + myObservable1: 1 + myObservable2: 0 = 2)", - "myComputed3.recompute(myComputed2: 2 + myObservable1: 1 + myObservable2: 0 = 3)", - "myAutorun.run(myComputed3: 3)", + 'myComputed1.recompute(myObservable1: 1 + myObservable2: 0 = 1)', + 'myComputed2.recompute(myComputed1: 1 + myObservable1: 1 + myObservable2: 0 = 2)', + 'myComputed3.recompute(myComputed2: 2 + myObservable1: 1 + myObservable2: 0 = 3)', + 'myAutorun.run(myComputed3: 3)', ]); transaction((tx) => { myObservable1.set(2, tx); myComputed2.get(); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myComputed1.recompute(myObservable1: 2 + myObservable2: 0 = 2)", - "myComputed2.recompute(myComputed1: 2 + myObservable1: 2 + myObservable2: 0 = 4)", + 'myComputed1.recompute(myObservable1: 2 + myObservable2: 0 = 2)', + 'myComputed2.recompute(myComputed1: 2 + myObservable1: 2 + myObservable2: 0 = 4)', ]); myObservable1.set(3, tx); myComputed2.get(); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myComputed1.recompute(myObservable1: 3 + myObservable2: 0 = 3)", - "myComputed2.recompute(myComputed1: 3 + myObservable1: 3 + myObservable2: 0 = 6)", + 'myComputed1.recompute(myObservable1: 3 + myObservable2: 0 = 3)', + 'myComputed2.recompute(myComputed1: 3 + myObservable1: 3 + myObservable2: 0 = 6)', ]); }); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myComputed3.recompute(myComputed2: 6 + myObservable1: 3 + myObservable2: 0 = 9)", - "myAutorun.run(myComputed3: 9)", + 'myComputed3.recompute(myComputed2: 6 + myObservable1: 3 + myObservable2: 0 = 9)', + 'myAutorun.run(myComputed3: 9)', ]); }); @@ -482,22 +482,22 @@ suite('observables', () => { }); assert.deepStrictEqual(log.getAndClearEntries(), [ - "subscribed handler 0", - "compute value undefined", - "autorun, value: undefined", + 'subscribed handler 0', + 'compute value undefined', + 'autorun, value: undefined', ]); setValue(1); assert.deepStrictEqual(log.getAndClearEntries(), [ - "compute value 1", - "autorun, value: 1" + 'compute value 1', + 'autorun, value: 1' ]); autorunDisposable.dispose(); assert.deepStrictEqual(log.getAndClearEntries(), [ - "unsubscribed handler 0" + 'unsubscribed handler 0' ]); }); @@ -593,26 +593,26 @@ suite('observables', () => { log.log(`myAutorun: ${value}`); })); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myComputed.recompute", - "myObs1.firstObserverAdded", - "myObs1.get", - "myAutorun: 0", + 'myComputed.recompute', + 'myObs1.firstObserverAdded', + 'myObs1.get', + 'myAutorun: 0', ]); transaction(tx => { myObs1.set(1, tx); - assert.deepStrictEqual(log.getAndClearEntries(), (["myObs1.set (value 1)"])); + assert.deepStrictEqual(log.getAndClearEntries(), (['myObs1.set (value 1)'])); shouldReadObservable.set(false, tx); assert.deepStrictEqual(log.getAndClearEntries(), ([])); myComputed.get(); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myComputed.recompute", - "myObs1.lastObserverRemoved", + 'myComputed.recompute', + 'myObs1.lastObserverRemoved', ]); }); - assert.deepStrictEqual(log.getAndClearEntries(), (["myAutorun: 1"])); + assert.deepStrictEqual(log.getAndClearEntries(), (['myAutorun: 1'])); }); test('avoid recomputation of deriveds that are no longer read', () => { @@ -640,41 +640,41 @@ suite('observables', () => { } })); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObsShouldRead.firstObserverAdded", - "myObsShouldRead.get", - "myObs1.firstObserverAdded", - "myObs1.get", - "myComputed1(myObs1: 0): Computed 0", - "myAutorun(shouldRead: true, myComputed1: 0): run", + 'myObsShouldRead.firstObserverAdded', + 'myObsShouldRead.get', + 'myObs1.firstObserverAdded', + 'myObs1.get', + 'myComputed1(myObs1: 0): Computed 0', + 'myAutorun(shouldRead: true, myComputed1: 0): run', ]); transaction(tx => { myObsShouldRead.set(false, tx); myObs1.set(1, tx); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObsShouldRead.set (value false)", - "myObs1.set (value 1)", + 'myObsShouldRead.set (value false)', + 'myObs1.set (value 1)', ]); }); // myComputed1 should not be recomputed here, even though its dependency myObs1 changed! assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObsShouldRead.get", - "myAutorun(shouldRead: false): run", - "myObs1.lastObserverRemoved", + 'myObsShouldRead.get', + 'myAutorun(shouldRead: false): run', + 'myObs1.lastObserverRemoved', ]); transaction(tx => { myObsShouldRead.set(true, tx); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObsShouldRead.set (value true)", + 'myObsShouldRead.set (value true)', ]); }); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObsShouldRead.get", - "myObs1.firstObserverAdded", - "myObs1.get", - "myComputed1(myObs1: 1): Computed 1", - "myAutorun(shouldRead: true, myComputed1: 1): run", + 'myObsShouldRead.get', + 'myObs1.firstObserverAdded', + 'myObs1.get', + 'myComputed1(myObs1: 1): Computed 1', + 'myAutorun(shouldRead: true, myComputed1: 1): run', ]); }); @@ -715,8 +715,8 @@ suite('observables', () => { log.log(`myAutorun.run(myDerived: ${myDerived.read(reader)})`); })); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myDerived.read(myObservable: 0)", - "myAutorun.run(myDerived: 0)" + 'myDerived.read(myObservable: 0)', + 'myAutorun.run(myDerived: 0)' ]); transaction((tx) => { @@ -727,7 +727,7 @@ suite('observables', () => { assert.deepStrictEqual(log.getAndClearEntries(), []); }); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myDerived.read(myObservable: 0)" + 'myDerived.read(myObservable: 0)' ]); }); @@ -746,8 +746,8 @@ suite('observables', () => { log.log(`myAutorun.run(myDerived: ${myDerived.read(reader)})`); })); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myDerived.read(myObservable: 0)", - "myAutorun.run(myDerived: 0)" + 'myDerived.read(myObservable: 0)', + 'myAutorun.run(myDerived: 0)' ]); transaction((tx) => { @@ -756,15 +756,15 @@ suite('observables', () => { myDerived.get(); // This marks the auto-run as changed assert.deepStrictEqual(log.getAndClearEntries(), [ - "myDerived.read(myObservable: 2)" + 'myDerived.read(myObservable: 2)' ]); myObservable.set(0, tx); assert.deepStrictEqual(log.getAndClearEntries(), []); }); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myDerived.read(myObservable: 0)", - "myAutorun.run(myDerived: 0)" + 'myDerived.read(myObservable: 0)', + 'myAutorun.run(myDerived: 0)' ]); }); }); @@ -780,28 +780,28 @@ suite('observables', () => { /** @description autorun */ if (observable1.read(reader) >= 2) { assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable1.set (value 2)", - "myObservable1.get", + 'myObservable1.set (value 2)', + 'myObservable1.get', ]); myObservable2.read(reader); // First time this observable is read assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable2.firstObserverAdded", - "myObservable2.get", + 'myObservable2.firstObserverAdded', + 'myObservable2.get', ]); d.dispose(); // Disposing removes all observers assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable1.lastObserverRemoved", - "myObservable2.lastObserverRemoved", + 'myObservable1.lastObserverRemoved', + 'myObservable2.lastObserverRemoved', ]); myObservable3.read(reader); // This does not subscribe the observable, because the autorun is disposed assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable3.get", + 'myObservable3.get', ]); } }); @@ -882,28 +882,28 @@ suite('observables', () => { log.log(`myAutorun(myComputed: ${value})`); })); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable.firstObserverAdded", - "myObservable.get", - "myComputed(myObservable: 0): start computing", - "myComputed(myObservable: 0): finished computing", - "myAutorun(myComputed: 0)" + 'myObservable.firstObserverAdded', + 'myObservable.get', + 'myComputed(myObservable: 0): start computing', + 'myComputed(myObservable: 0): finished computing', + 'myAutorun(myComputed: 0)' ]); myObservable.set(1, undefined); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable.set (value 1)", - "myObservable.get", - "myComputed(myObservable: 1): start computing", - "myObservable.set (value 2)", - "myComputed(myObservable: 1): finished computing", - "myObservable.get", - "myComputed(myObservable: 2): start computing", - "myObservable.set (value 3)", - "myComputed(myObservable: 2): finished computing", - "myObservable.get", - "myComputed(myObservable: 3): start computing", - "myComputed(myObservable: 3): finished computing", - "myAutorun(myComputed: 3)", + 'myObservable.set (value 1)', + 'myObservable.get', + 'myComputed(myObservable: 1): start computing', + 'myObservable.set (value 2)', + 'myComputed(myObservable: 1): finished computing', + 'myObservable.get', + 'myComputed(myObservable: 2): start computing', + 'myObservable.set (value 3)', + 'myComputed(myObservable: 2): finished computing', + 'myObservable.get', + 'myComputed(myObservable: 3): start computing', + 'myComputed(myObservable: 3): finished computing', + 'myAutorun(myComputed: 3)', ]); }); @@ -921,30 +921,30 @@ suite('observables', () => { log.log(`myAutorun(myObservable: ${value}): end`); })); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable.firstObserverAdded", - "myObservable.get", - "myAutorun(myObservable: 0): start", - "myAutorun(myObservable: 0): end", + 'myObservable.firstObserverAdded', + 'myObservable.get', + 'myAutorun(myObservable: 0): start', + 'myAutorun(myObservable: 0): end', ]); myObservable.set(1, undefined); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable.set (value 1)", - "myObservable.get", - "myAutorun(myObservable: 1): start", - "myObservable.set (value 2)", - "myAutorun(myObservable: 1): end", - "myObservable.get", - "myAutorun(myObservable: 2): start", - "myObservable.set (value 3)", - "myAutorun(myObservable: 2): end", - "myObservable.get", - "myAutorun(myObservable: 3): start", - "myObservable.set (value 4)", - "myAutorun(myObservable: 3): end", - "myObservable.get", - "myAutorun(myObservable: 4): start", - "myAutorun(myObservable: 4): end", + 'myObservable.set (value 1)', + 'myObservable.get', + 'myAutorun(myObservable: 1): start', + 'myObservable.set (value 2)', + 'myAutorun(myObservable: 1): end', + 'myObservable.get', + 'myAutorun(myObservable: 2): start', + 'myObservable.set (value 3)', + 'myAutorun(myObservable: 2): end', + 'myObservable.get', + 'myAutorun(myObservable: 3): start', + 'myObservable.set (value 4)', + 'myAutorun(myObservable: 3): end', + 'myObservable.get', + 'myAutorun(myObservable: 4): start', + 'myAutorun(myObservable: 4): end', ]); }); @@ -972,36 +972,36 @@ suite('observables', () => { log.log(`myAutorun(myDerived2: ${value})`); })); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable.firstObserverAdded", - "myObservable.get", - "myDerived1(myObservable: 0): start computing", - "myDerived2(myDerived1: 0): start computing", - "myAutorun(myDerived2: 0)", + 'myObservable.firstObserverAdded', + 'myObservable.get', + 'myDerived1(myObservable: 0): start computing', + 'myDerived2(myDerived1: 0): start computing', + 'myAutorun(myDerived2: 0)', ]); transaction(tx => { myObservable.set(1, tx); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable.set (value 1)", + 'myObservable.set (value 1)', ]); myDerived2.get(); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable.get", - "myDerived1(myObservable: 1): start computing", - "myDerived2(myDerived1: 1): start computing", + 'myObservable.get', + 'myDerived1(myObservable: 1): start computing', + 'myDerived2(myDerived1: 1): start computing', ]); myObservable.set(2, tx); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable.set (value 2)", + 'myObservable.set (value 2)', ]); }); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable.get", - "myDerived1(myObservable: 2): start computing", - "myDerived2(myDerived1: 2): start computing", - "myAutorun(myDerived2: 2)", + 'myObservable.get', + 'myDerived1(myObservable: 2): start computing', + 'myDerived2(myDerived1: 2): start computing', + 'myAutorun(myDerived2: 2)', ]); }); @@ -1031,30 +1031,30 @@ suite('observables', () => { log.log(`myAutorun(myDerived3: ${val})`); })); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable1.firstObserverAdded", - "myObservable1.get", - "myObservable2.firstObserverAdded", - "myObservable2.get", - "myDerived2.computed(myObservable2: 0)", - "myDerived3.computed(myDerived1: 0, myDerived2: 0)", - "myAutorun(myDerived3: 0 + 0)", + 'myObservable1.firstObserverAdded', + 'myObservable1.get', + 'myObservable2.firstObserverAdded', + 'myObservable2.get', + 'myDerived2.computed(myObservable2: 0)', + 'myDerived3.computed(myDerived1: 0, myDerived2: 0)', + 'myAutorun(myDerived3: 0 + 0)', ]); transaction(tx => { myObservable1.set(1, tx); // Mark myDerived 3 as stale assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable1.set (value 1)", + 'myObservable1.set (value 1)', ]); myObservable2.set(10, tx); // This is a non-change. myDerived3 should not be marked as possibly-depedency-changed! assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable2.set (value 10)", + 'myObservable2.set (value 10)', ]); }); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable1.get", - "myObservable2.get", - "myDerived2.computed(myObservable2: 10)", + 'myObservable1.get', + 'myObservable2.get', + 'myDerived2.computed(myObservable2: 10)', 'myDerived3.computed(myDerived1: 1, myDerived2: 0)', 'myAutorun(myDerived3: 1 + 0)', ]); @@ -1146,11 +1146,11 @@ suite('observables', () => { i++; emitter.fire(2); - assert.deepStrictEqual(log.getAndClearEntries(), ["event fired 2"]); + assert.deepStrictEqual(log.getAndClearEntries(), ['event fired 2']); i++; emitter.fire(3); - assert.deepStrictEqual(log.getAndClearEntries(), ["event fired 3"]); + assert.deepStrictEqual(log.getAndClearEntries(), ['event fired 3']); d.dispose(); }); @@ -1347,17 +1347,17 @@ suite('observables', () => { }); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable.firstObserverAdded", - "myObservable.get", - "error: foobar" + 'myObservable.firstObserverAdded', + 'myObservable.get', + 'error: foobar' ]); myObservable.set(1, undefined); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable.set (value 1)", - "myObservable.get", - "error: foobar", + 'myObservable.set (value 1)', + 'myObservable.get', + 'error: foobar', ]); d.dispose(); @@ -1380,24 +1380,24 @@ suite('observables', () => { }); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable.firstObserverAdded", - "myObservable.get", + 'myObservable.firstObserverAdded', + 'myObservable.get', ]); myObservable.set(1, undefined); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable.set (value 1)", - "myObservable.get", - "error: foobar", + 'myObservable.set (value 1)', + 'myObservable.get', + 'error: foobar', ]); myObservable.set(2, undefined); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myObservable.set (value 2)", - "myObservable.get", - "error: foobar", + 'myObservable.set (value 2)', + 'myObservable.get', + 'error: foobar', ]); d.dispose(); @@ -1436,15 +1436,15 @@ suite('observables', () => { }); assert.deepStrictEqual(log.getAndClearEntries(), [ - "myDerived.computed start", - "myObservable.firstObserverAdded", - "myObservable.get", - "myObservable.set (value 1)", - "myDerived.computed end", - "myDerived.computed start", - "myObservable.get", - "myDerived.computed end", - "recomputeInitiallyAndOnChange, myDerived: 1", + 'myDerived.computed start', + 'myObservable.firstObserverAdded', + 'myObservable.get', + 'myObservable.set (value 1)', + 'myDerived.computed end', + 'myDerived.computed start', + 'myObservable.get', + 'myDerived.computed end', + 'recomputeInitiallyAndOnChange, myDerived: 1', ]); myDerived.get(); @@ -1521,12 +1521,12 @@ suite('observables', () => { }); assert.deepStrictEqual(log.getAndClearEntries(), ([ - "autorun start", - "d1.computed start", - "d2.computed start", - "Error: Cyclic deriveds are not supported yet!", - "d1.computed end", - "autorun end" + 'autorun start', + 'd1.computed start', + 'd2.computed start', + 'Error: Cyclic deriveds are not supported yet!', + 'd1.computed end', + 'autorun end' ])); disp.dispose(); @@ -1573,9 +1573,9 @@ suite('observables', () => { })); assert.deepStrictEqual(log.getAndClearEntries(), [ - "createInitial", + 'createInitial', 'update {"changes":[],"myObservable1":5,"myObservable2":9}', - "update -> 14", + 'update -> 14', 'autorun {"changes":[],"sum":14}', ]); @@ -1585,9 +1585,9 @@ suite('observables', () => { }); assert.deepStrictEqual(log.getAndClearEntries(), ([ - "update {\"changes\":[{\"key\":\"myObservable1\",\"change\":1},{\"key\":\"myObservable2\",\"change\":3}],\"myObservable1\":6,\"myObservable2\":12}", - "update -> 18", - "autorun {\"changes\":[{\"key\":\"sum\",\"change\":4}],\"sum\":18}" + 'update {"changes":[{"key":"myObservable1","change":1},{"key":"myObservable2","change":3}],"myObservable1":6,"myObservable2":12}', + 'update -> 18', + 'autorun {"changes":[{"key":"sum","change":4}],"sum":18}' ])); transaction(tx => { @@ -1598,18 +1598,18 @@ suite('observables', () => { }); assert.deepStrictEqual(log.getAndClearEntries(), ([ - "update {\"changes\":[{\"key\":\"myObservable1\",\"change\":1}],\"myObservable1\":7,\"myObservable2\":12}", - "update -> 19", - "sum.get() 19", - "update {\"changes\":[{\"key\":\"myObservable2\",\"change\":3}],\"myObservable1\":7,\"myObservable2\":15}", - "update -> 22", - "autorun {\"changes\":[{\"key\":\"sum\",\"change\":1}],\"sum\":22}" + 'update {"changes":[{"key":"myObservable1","change":1}],"myObservable1":7,"myObservable2":12}', + 'update -> 19', + 'sum.get() 19', + 'update {"changes":[{"key":"myObservable2","change":3}],"myObservable1":7,"myObservable2":15}', + 'update -> 22', + 'autorun {"changes":[{"key":"sum","change":1}],"sum":22}' ])); store.dispose(); assert.deepStrictEqual(log.getAndClearEntries(), ([ - "disposeFinal 22" + 'disposeFinal 22' ])); }); }); @@ -1633,22 +1633,22 @@ suite('observables', () => { }); assert.deepStrictEqual(log.getAndClearEntries(), ([ - "computed 0", - "a: 0" + 'computed 0', + 'a: 0' ])); observable1.set(1, undefined); assert.deepStrictEqual(log.getAndClearEntries(), ([ - "computed1: 0 disposed", - "computed 1", - "a: 1" + 'computed1: 0 disposed', + 'computed 1', + 'a: 1' ])); a.dispose(); assert.deepStrictEqual(log.getAndClearEntries(), ([ - "computed1: 1 disposed" + 'computed1: 1 disposed' ])); }); @@ -1670,22 +1670,22 @@ suite('observables', () => { }); assert.deepStrictEqual(log.getAndClearEntries(), ([ - "computed 0", - "a: 0" + 'computed 0', + 'a: 0' ])); observable1.set(1, undefined); assert.deepStrictEqual(log.getAndClearEntries(), ([ - "computed 1", - "computed1: 0 disposed", - "a: 1" + 'computed 1', + 'computed1: 0 disposed', + 'a: 1' ])); a.dispose(); assert.deepStrictEqual(log.getAndClearEntries(), ([ - "computed1: 1 disposed" + 'computed1: 1 disposed' ])); }); }); @@ -1724,7 +1724,7 @@ suite('observables', () => { }); assert.deepStrictEqual(log.getAndClearEntries(), ([ - "runOnChange [\"signal1: foo, signal2Derived: bar (derived)\"]" + 'runOnChange ["signal1: foo, signal2Derived: bar (derived)"]' ])); @@ -1733,7 +1733,7 @@ suite('observables', () => { }); assert.deepStrictEqual(log.getAndClearEntries(), ([ - "runOnChange [\"signal2Derived: baz (derived)\"]" + 'runOnChange ["signal2Derived: baz (derived)"]' ])); disp.dispose(); diff --git a/code/src/vs/base/test/common/skipList.test.ts b/code/src/vs/base/test/common/skipList.test.ts deleted file mode 100644 index d827e70c087..00000000000 --- a/code/src/vs/base/test/common/skipList.test.ts +++ /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. - *--------------------------------------------------------------------------------------------*/ - -import assert from 'assert'; -import { binarySearch } from '../../common/arrays.js'; -import { SkipList } from '../../common/skipList.js'; -import { StopWatch } from '../../common/stopwatch.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from './utils.js'; - - -suite('SkipList', function () { - - ensureNoDisposablesAreLeakedInTestSuite(); - - function assertValues(list: SkipList, expected: V[]) { - assert.strictEqual(list.size, expected.length); - assert.deepStrictEqual([...list.values()], expected); - - const valuesFromEntries = [...list.entries()].map(entry => entry[1]); - assert.deepStrictEqual(valuesFromEntries, expected); - - const valuesFromIter = [...list].map(entry => entry[1]); - assert.deepStrictEqual(valuesFromIter, expected); - - let i = 0; - list.forEach((value, _key, map) => { - assert.ok(map === list); - assert.deepStrictEqual(value, expected[i++]); - }); - } - - function assertKeys(list: SkipList, expected: K[]) { - assert.strictEqual(list.size, expected.length); - assert.deepStrictEqual([...list.keys()], expected); - - const keysFromEntries = [...list.entries()].map(entry => entry[0]); - assert.deepStrictEqual(keysFromEntries, expected); - - const keysFromIter = [...list].map(entry => entry[0]); - assert.deepStrictEqual(keysFromIter, expected); - - let i = 0; - list.forEach((_value, key, map) => { - assert.ok(map === list); - assert.deepStrictEqual(key, expected[i++]); - }); - } - - test('set/get/delete', function () { - const list = new SkipList((a, b) => a - b); - - assert.strictEqual(list.get(3), undefined); - list.set(3, 1); - assert.strictEqual(list.get(3), 1); - assertValues(list, [1]); - - list.set(3, 3); - assertValues(list, [3]); - - list.set(1, 1); - list.set(4, 4); - assert.strictEqual(list.get(3), 3); - assert.strictEqual(list.get(1), 1); - assert.strictEqual(list.get(4), 4); - assertValues(list, [1, 3, 4]); - - assert.strictEqual(list.delete(17), false); - - assert.strictEqual(list.delete(1), true); - assert.strictEqual(list.get(1), undefined); - assert.strictEqual(list.get(3), 3); - assert.strictEqual(list.get(4), 4); - - assertValues(list, [3, 4]); - }); - - test('Figure 3', function () { - const list = new SkipList((a, b) => a - b); - list.set(3, true); - list.set(6, true); - list.set(7, true); - list.set(9, true); - list.set(12, true); - list.set(19, true); - list.set(21, true); - list.set(25, true); - - assertKeys(list, [3, 6, 7, 9, 12, 19, 21, 25]); - - list.set(17, true); - assert.deepStrictEqual(list.size, 9); - assertKeys(list, [3, 6, 7, 9, 12, 17, 19, 21, 25]); - }); - - test('clear ( CPU pegged after some builds #194853)', function () { - const list = new SkipList((a, b) => a - b); - list.set(1, true); - list.set(2, true); - list.set(3, true); - assert.strictEqual(list.size, 3); - list.clear(); - assert.strictEqual(list.size, 0); - assert.strictEqual(list.get(1), undefined); - assert.strictEqual(list.get(2), undefined); - assert.strictEqual(list.get(3), undefined); - }); - - test('capacity max', function () { - const list = new SkipList((a, b) => a - b, 10); - list.set(1, true); - list.set(2, true); - list.set(3, true); - list.set(4, true); - list.set(5, true); - list.set(6, true); - list.set(7, true); - list.set(8, true); - list.set(9, true); - list.set(10, true); - list.set(11, true); - list.set(12, true); - - assertKeys(list, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); - }); - - const cmp = (a: number, b: number): number => { - if (a < b) { - return -1; - } else if (a > b) { - return 1; - } else { - return 0; - } - }; - - function insertArraySorted(array: number[], element: number) { - let idx = binarySearch(array, element, cmp); - if (idx >= 0) { - array[idx] = element; - } else { - idx = ~idx; - // array = array.slice(0, idx).concat(element, array.slice(idx)); - array.splice(idx, 0, element); - } - return array; - } - - function delArraySorted(array: number[], element: number) { - const idx = binarySearch(array, element, cmp); - if (idx >= 0) { - // array = array.slice(0, idx).concat(array.slice(idx)); - array.splice(idx, 1); - } - return array; - } - - - test.skip('perf', function () { - - // data - const max = 2 ** 16; - const values = new Set(); - for (let i = 0; i < max; i++) { - const value = Math.floor(Math.random() * max); - values.add(value); - } - console.log(values.size); - - // init - const list = new SkipList(cmp, max); - let sw = new StopWatch(); - values.forEach(value => list.set(value, true)); - sw.stop(); - console.log(`[LIST] ${list.size} elements after ${sw.elapsed()}ms`); - let array: number[] = []; - sw = new StopWatch(); - values.forEach(value => array = insertArraySorted(array, value)); - sw.stop(); - console.log(`[ARRAY] ${array.length} elements after ${sw.elapsed()}ms`); - - // get - sw = new StopWatch(); - const someValues = [...values].slice(0, values.size / 4); - someValues.forEach(key => { - const value = list.get(key); // find - console.assert(value, '[LIST] must have ' + key); - list.get(-key); // miss - }); - sw.stop(); - console.log(`[LIST] retrieve ${sw.elapsed()}ms (${(sw.elapsed() / (someValues.length * 2)).toPrecision(4)}ms/op)`); - sw = new StopWatch(); - someValues.forEach(key => { - const idx = binarySearch(array, key, cmp); // find - console.assert(idx >= 0, '[ARRAY] must have ' + key); - binarySearch(array, -key, cmp); // miss - }); - sw.stop(); - console.log(`[ARRAY] retrieve ${sw.elapsed()}ms (${(sw.elapsed() / (someValues.length * 2)).toPrecision(4)}ms/op)`); - - - // insert - sw = new StopWatch(); - someValues.forEach(key => { - list.set(-key, false); - }); - sw.stop(); - console.log(`[LIST] insert ${sw.elapsed()}ms (${(sw.elapsed() / someValues.length).toPrecision(4)}ms/op)`); - sw = new StopWatch(); - someValues.forEach(key => { - array = insertArraySorted(array, -key); - }); - sw.stop(); - console.log(`[ARRAY] insert ${sw.elapsed()}ms (${(sw.elapsed() / someValues.length).toPrecision(4)}ms/op)`); - - // delete - sw = new StopWatch(); - someValues.forEach(key => { - list.delete(key); // find - list.delete(-key); // miss - }); - sw.stop(); - console.log(`[LIST] delete ${sw.elapsed()}ms (${(sw.elapsed() / (someValues.length * 2)).toPrecision(4)}ms/op)`); - sw = new StopWatch(); - someValues.forEach(key => { - array = delArraySorted(array, key); // find - array = delArraySorted(array, -key); // miss - }); - sw.stop(); - console.log(`[ARRAY] delete ${sw.elapsed()}ms (${(sw.elapsed() / (someValues.length * 2)).toPrecision(4)}ms/op)`); - }); -}); diff --git a/code/src/vs/base/test/common/snapshot.ts b/code/src/vs/base/test/common/snapshot.ts index 9523d6ca3aa..6cef4010941 100644 --- a/code/src/vs/base/test/common/snapshot.ts +++ b/code/src/vs/base/test/common/snapshot.ts @@ -120,7 +120,9 @@ function formatValue(value: unknown, level = 0, seen: unknown[] = []): string { if (seen.includes(value)) { return '[Circular]'; } + // eslint-disable-next-line local/code-no-any-casts if (debugDescriptionSymbol in value && typeof (value as any)[debugDescriptionSymbol] === 'function') { + // eslint-disable-next-line local/code-no-any-casts return (value as any)[debugDescriptionSymbol](); } const oi = ' '.repeat(level); diff --git a/code/src/vs/base/test/common/strings.test.ts b/code/src/vs/base/test/common/strings.test.ts index 8b7656f4758..bb992038f19 100644 --- a/code/src/vs/base/test/common/strings.test.ts +++ b/code/src/vs/base/test/common/strings.test.ts @@ -19,7 +19,20 @@ suite('Strings', () => { assert(strings.equalsIgnoreCase('ÖL', 'Öl')); }); - test('beginsWithIgnoreCase', () => { + test('equals', () => { + assert(!strings.equals(undefined, 'abc')); + assert(!strings.equals('abc', undefined)); + assert(strings.equals(undefined, undefined)); + assert(strings.equals('', '')); + assert(strings.equals('a', 'a')); + assert(!strings.equals('abc', 'Abc')); + assert(strings.equals('abc', 'ABC', true)); + assert(!strings.equals('Höhenmeter', 'HÖhenmeter')); + assert(!strings.equals('ÖL', 'Öl')); + assert(strings.equals('ÖL', 'Öl', true)); + }); + + test('startsWithIgnoreCase', () => { assert(strings.startsWithIgnoreCase('', '')); assert(!strings.startsWithIgnoreCase('', '1')); assert(strings.startsWithIgnoreCase('1', '')); @@ -45,6 +58,34 @@ suite('Strings', () => { assert(!strings.startsWithIgnoreCase('alles klar', 'ö')); }); + test('endsWithIgnoreCase', () => { + assert(strings.endsWithIgnoreCase('', '')); + assert(!strings.endsWithIgnoreCase('', '1')); + assert(strings.endsWithIgnoreCase('1', '')); + + assert(!strings.endsWithIgnoreCase('abcd', 'abcde')); + + assert(strings.endsWithIgnoreCase('a', 'a')); + assert(strings.endsWithIgnoreCase('abc', 'Abc')); + assert(strings.endsWithIgnoreCase('abc', 'ABC')); + assert(strings.endsWithIgnoreCase('Höhenmeter', 'HÖhenmeter')); + assert(strings.endsWithIgnoreCase('ÖL', 'Öl')); + + assert(strings.endsWithIgnoreCase('alles klar', 'r')); + assert(strings.endsWithIgnoreCase('alles klar', 'R')); + assert(strings.endsWithIgnoreCase('alles klar', 's klar')); + assert(strings.endsWithIgnoreCase('alles klar', 'S klar')); + assert(strings.endsWithIgnoreCase('alles klar', 'S KLAR')); + assert(strings.endsWithIgnoreCase('alles klar', 'alles klar')); + assert(strings.endsWithIgnoreCase('alles klar', 'ALLES KLAR')); + + assert(!strings.endsWithIgnoreCase('alles klar', 'S KLAR ')); + assert(!strings.endsWithIgnoreCase('alles klar', ' S KLAR')); + assert(!strings.endsWithIgnoreCase('alles klar', 'S KLARö')); + assert(!strings.endsWithIgnoreCase('alles klar', ' ')); + assert(!strings.endsWithIgnoreCase('alles klar', 'ö')); + }); + test('compareIgnoreCase', () => { function assertCompareIgnoreCase(a: string, b: string, recurse = true): void { @@ -174,6 +215,11 @@ suite('Strings', () => { assert.strictEqual(strings.ltrim('///', '/'), ''); assert.strictEqual(strings.ltrim('', ''), ''); assert.strictEqual(strings.ltrim('', '/'), ''); + // Multi-character needle with consecutive repetitions + assert.strictEqual(strings.ltrim('---hello', '---'), 'hello'); + assert.strictEqual(strings.ltrim('------hello', '---'), 'hello'); + assert.strictEqual(strings.ltrim('---------hello', '---'), 'hello'); + assert.strictEqual(strings.ltrim('hello---', '---'), 'hello---'); }); test('rtrim', () => { @@ -187,6 +233,13 @@ suite('Strings', () => { assert.strictEqual(strings.rtrim('///', '/'), ''); assert.strictEqual(strings.rtrim('', ''), ''); assert.strictEqual(strings.rtrim('', '/'), ''); + // Multi-character needle with consecutive repetitions (bug fix) + assert.strictEqual(strings.rtrim('hello---', '---'), 'hello'); + assert.strictEqual(strings.rtrim('hello------', '---'), 'hello'); + assert.strictEqual(strings.rtrim('hello---------', '---'), 'hello'); + assert.strictEqual(strings.rtrim('---hello', '---'), '---hello'); + assert.strictEqual(strings.rtrim('hello world' + '---'.repeat(10), '---'), 'hello world'); + assert.strictEqual(strings.rtrim('path/to/file///', '//'), 'path/to/file/'); }); test('trim', () => { @@ -232,6 +285,24 @@ suite('Strings', () => { assert.strictEqual(strings.isEmojiImprecise(codePoint), true); }); + test('isFullWidthCharacter', () => { + // Fullwidth ASCII (FF01-FF5E) + assert.strictEqual(strings.isFullWidthCharacter('A'.charCodeAt(0)), true, 'A U+FF21 fullwidth A'); + assert.strictEqual(strings.isFullWidthCharacter('?'.charCodeAt(0)), true, '? U+FF1F fullwidth question mark'); + assert.strictEqual(strings.isFullWidthCharacter('#'.charCodeAt(0)), true, '# U+FF03 fullwidth number sign'); + assert.strictEqual(strings.isFullWidthCharacter('='.charCodeAt(0)), true, '= U+FF1D fullwidth equals sign'); + + // Hiragana (3040-309F) + assert.strictEqual(strings.isFullWidthCharacter('あ'.charCodeAt(0)), true, 'あ U+3042 hiragana'); + + // Fullwidth symbols (FFE0-FFE6) + assert.strictEqual(strings.isFullWidthCharacter('¥'.charCodeAt(0)), true, '¥ U+FFE5 fullwidth yen sign'); + + // Regular ASCII should not be full width + assert.strictEqual(strings.isFullWidthCharacter('A'.charCodeAt(0)), false, 'A regular ASCII'); + assert.strictEqual(strings.isFullWidthCharacter('?'.charCodeAt(0)), false, '? regular ASCII'); + }); + test('isBasicASCII', () => { function assertIsBasicASCII(str: string, expected: boolean): void { assert.strictEqual(strings.isBasicASCII(str), expected, str + ` (${str.charCodeAt(0)})`); diff --git a/code/src/vs/base/test/common/timeTravelScheduler.ts b/code/src/vs/base/test/common/timeTravelScheduler.ts index 7c5e397a48c..72f8d8985ce 100644 --- a/code/src/vs/base/test/common/timeTravelScheduler.ts +++ b/code/src/vs/base/test/common/timeTravelScheduler.ts @@ -38,15 +38,19 @@ const scheduledTaskComparator = tieBreakComparators( export class TimeTravelScheduler implements Scheduler { private taskCounter = 0; - private _now: TimeOffset = 0; + private _nowMs: TimeOffset = 0; private readonly queue: PriorityQueue = new SimplePriorityQueue([], scheduledTaskComparator); private readonly taskScheduledEmitter = new Emitter<{ task: ScheduledTask }>(); public readonly onTaskScheduled = this.taskScheduledEmitter.event; + constructor(startTimeMs: number) { + this._nowMs = startTimeMs; + } + schedule(task: ScheduledTask): IDisposable { - if (task.time < this._now) { - throw new Error(`Scheduled time (${task.time}) must be equal to or greater than the current time (${this._now}).`); + if (task.time < this._nowMs) { + throw new Error(`Scheduled time (${task.time}) must be equal to or greater than the current time (${this._nowMs}).`); } const extendedTask: ExtendedScheduledTask = { ...task, id: this.taskCounter++ }; this.queue.add(extendedTask); @@ -55,7 +59,7 @@ export class TimeTravelScheduler implements Scheduler { } get now(): TimeOffset { - return this._now; + return this._nowMs; } get hasScheduledTasks(): boolean { @@ -69,7 +73,7 @@ export class TimeTravelScheduler implements Scheduler { runNext(): ScheduledTask | undefined { const task = this.queue.removeMin(); if (task) { - this._now = task.time; + this._nowMs = task.time; task.run(); } @@ -164,26 +168,30 @@ export class AsyncSchedulerProcessor extends Disposable { } -export async function runWithFakedTimers(options: { useFakeTimers?: boolean; useSetImmediate?: boolean; maxTaskCount?: number }, fn: () => Promise): Promise { +export async function runWithFakedTimers(options: { startTime?: number; useFakeTimers?: boolean; useSetImmediate?: boolean; maxTaskCount?: number }, fn: () => Promise): Promise { const useFakeTimers = options.useFakeTimers === undefined ? true : options.useFakeTimers; if (!useFakeTimers) { return fn(); } - const scheduler = new TimeTravelScheduler(); + const scheduler = new TimeTravelScheduler(options.startTime ?? 0); const schedulerProcessor = new AsyncSchedulerProcessor(scheduler, { useSetImmediate: options.useSetImmediate, maxTaskCount: options.maxTaskCount }); const globalInstallDisposable = scheduler.installGlobally(); + let didThrow = true; let result: T; try { result = await fn(); + didThrow = false; } finally { globalInstallDisposable.dispose(); try { - // We process the remaining scheduled tasks. - // The global override is no longer active, so during this, no more tasks will be scheduled. - await schedulerProcessor.waitForEmptyQueue(); + if (!didThrow) { + // We process the remaining scheduled tasks. + // The global override is no longer active, so during this, no more tasks will be scheduled. + await schedulerProcessor.waitForEmptyQueue(); + } } finally { schedulerProcessor.dispose(); } @@ -265,6 +273,7 @@ function setInterval(scheduler: Scheduler, handler: TimerHandler, interval: numb } function overwriteGlobals(scheduler: Scheduler): IDisposable { + // eslint-disable-next-line local/code-no-any-casts globalThis.setTimeout = ((handler: TimerHandler, timeout?: number) => setTimeout(scheduler, handler, timeout)) as any; globalThis.clearTimeout = (timeoutId: any) => { if (typeof timeoutId === 'object' && timeoutId && 'dispose' in timeoutId) { @@ -274,6 +283,7 @@ function overwriteGlobals(scheduler: Scheduler): IDisposable { } }; + // eslint-disable-next-line local/code-no-any-casts globalThis.setInterval = ((handler: TimerHandler, timeout: number) => setInterval(scheduler, handler, timeout)) as any; globalThis.clearInterval = (timeoutId: any) => { if (typeof timeoutId === 'object' && timeoutId && 'dispose' in timeoutId) { @@ -306,11 +316,13 @@ function createDateClass(scheduler: Scheduler): DateConstructor { if (args.length === 0) { return new OriginalDate(scheduler.now); } + // eslint-disable-next-line local/code-no-any-casts return new (OriginalDate as any)(...args); } for (const prop in OriginalDate) { if (OriginalDate.hasOwnProperty(prop)) { + // eslint-disable-next-line local/code-no-any-casts (SchedulerDate as any)[prop] = (OriginalDate as any)[prop]; } } @@ -326,6 +338,7 @@ function createDateClass(scheduler: Scheduler): DateConstructor { SchedulerDate.UTC = OriginalDate.UTC; SchedulerDate.prototype.toUTCString = OriginalDate.prototype.toUTCString; + // eslint-disable-next-line local/code-no-any-casts return SchedulerDate as any; } diff --git a/code/src/vs/base/test/common/troubleshooting.ts b/code/src/vs/base/test/common/troubleshooting.ts index f794f7e2033..953fbd6972a 100644 --- a/code/src/vs/base/test/common/troubleshooting.ts +++ b/code/src/vs/base/test/common/troubleshooting.ts @@ -47,9 +47,11 @@ export function endTrackingDisposables(): void { } export function beginLoggingFS(withStacks: boolean = false): void { + // eslint-disable-next-line local/code-no-any-casts (self).beginLoggingFS?.(withStacks); } export function endLoggingFS(): void { + // eslint-disable-next-line local/code-no-any-casts (self).endLoggingFS?.(); } diff --git a/code/src/vs/base/test/common/types.test.ts b/code/src/vs/base/test/common/types.test.ts index 7195c458a57..f0812718722 100644 --- a/code/src/vs/base/test/common/types.test.ts +++ b/code/src/vs/base/test/common/types.test.ts @@ -104,6 +104,85 @@ suite('Types', () => { assert(types.isString('foo')); }); + test('isStringArray', () => { + assert(!types.isStringArray(undefined)); + assert(!types.isStringArray(null)); + assert(!types.isStringArray(5)); + assert(!types.isStringArray('foo')); + assert(!types.isStringArray(true)); + assert(!types.isStringArray({})); + assert(!types.isStringArray(/test/)); + assert(!types.isStringArray(new RegExp(''))); + assert(!types.isStringArray(new Date())); + assert(!types.isStringArray(assert)); + assert(!types.isStringArray(function foo() { /**/ })); + assert(!types.isStringArray({ foo: 'bar' })); + assert(!types.isStringArray([1, 2, 3])); + assert(!types.isStringArray([1, 2, '3'])); + assert(!types.isStringArray(['foo', 'bar', 5])); + assert(!types.isStringArray(['foo', null, 'bar'])); + assert(!types.isStringArray(['foo', undefined, 'bar'])); + + assert(types.isStringArray([])); + assert(types.isStringArray(['foo'])); + assert(types.isStringArray(['foo', 'bar'])); + assert(types.isStringArray(['foo', 'bar', 'baz'])); + }); + + test('isArrayOf', () => { + // Basic non-array values + assert(!types.isArrayOf(undefined, types.isString)); + assert(!types.isArrayOf(null, types.isString)); + assert(!types.isArrayOf(5, types.isString)); + assert(!types.isArrayOf('foo', types.isString)); + assert(!types.isArrayOf(true, types.isString)); + assert(!types.isArrayOf({}, types.isString)); + assert(!types.isArrayOf(/test/, types.isString)); + assert(!types.isArrayOf(new RegExp(''), types.isString)); + assert(!types.isArrayOf(new Date(), types.isString)); + assert(!types.isArrayOf(assert, types.isString)); + assert(!types.isArrayOf(function foo() { /**/ }, types.isString)); + assert(!types.isArrayOf({ foo: 'bar' }, types.isString)); + + // Arrays with wrong types + assert(!types.isArrayOf([1, 2, 3], types.isString)); + assert(!types.isArrayOf([1, 2, '3'], types.isString)); + assert(!types.isArrayOf(['foo', 'bar', 5], types.isString)); + assert(!types.isArrayOf(['foo', null, 'bar'], types.isString)); + assert(!types.isArrayOf(['foo', undefined, 'bar'], types.isString)); + + // Valid string arrays + assert(types.isArrayOf([], types.isString)); + assert(types.isArrayOf(['foo'], types.isString)); + assert(types.isArrayOf(['foo', 'bar'], types.isString)); + assert(types.isArrayOf(['foo', 'bar', 'baz'], types.isString)); + + // Valid number arrays + assert(types.isArrayOf([], types.isNumber)); + assert(types.isArrayOf([1], types.isNumber)); + assert(types.isArrayOf([1, 2, 3], types.isNumber)); + assert(!types.isArrayOf([1, 2, '3'], types.isNumber)); + + // Valid boolean arrays + assert(types.isArrayOf([], types.isBoolean)); + assert(types.isArrayOf([true], types.isBoolean)); + assert(types.isArrayOf([true, false, true], types.isBoolean)); + assert(!types.isArrayOf([true, 1, false], types.isBoolean)); + + // Valid function arrays + assert(types.isArrayOf([], types.isFunction)); + assert(types.isArrayOf([assert], types.isFunction)); + assert(types.isArrayOf([assert, function foo() { /**/ }], types.isFunction)); + assert(!types.isArrayOf([assert, 'foo'], types.isFunction)); + + // Custom type guard + const isEven = (n: unknown): n is number => types.isNumber(n) && n % 2 === 0; + assert(types.isArrayOf([], isEven)); + assert(types.isArrayOf([2, 4, 6], isEven)); + assert(!types.isArrayOf([2, 3, 4], isEven)); + assert(!types.isArrayOf([1, 3, 5], isEven)); + }); + test('isNumber', () => { assert(!types.isNumber(undefined)); assert(!types.isNumber(null)); @@ -826,4 +905,98 @@ suite('Types', () => { assert.throws(() => types.validateConstraints(['2'], [types.isNumber])); assert.throws(() => types.validateConstraints([1, 'test', true], [Number, String, Number])); }); + + suite('hasKey', () => { + test('should return true when object has specified key', () => { + type A = { a: string }; + type B = { b: number }; + const obj: A | B = { a: 'test' }; + + assert(types.hasKey(obj, { a: true })); + // After this check, TypeScript knows obj is type A + assert.strictEqual(obj.a, 'test'); + }); + + test('should return false when object does not have specified key', () => { + type A = { a: string }; + type B = { b: number }; + const obj: A | B = { b: 42 }; + + // @ts-expect-error + assert(!types.hasKey(obj, { a: true })); + }); + + test('should work with multiple keys', () => { + type A = { a: string; b: number }; + type B = { c: boolean }; + const obj: A | B = { a: 'test', b: 42 }; + + assert(types.hasKey(obj, { a: true, b: true })); + // After this check, TypeScript knows obj is type A + assert.strictEqual(obj.a, 'test'); + assert.strictEqual(obj.b, 42); + }); + + test('should return false if any key is missing', () => { + type A = { a: string; b: number }; + type B = { a: string }; + const obj: A | B = { a: 'test' }; + + assert(!types.hasKey(obj, { a: true, b: true })); + }); + + test('should work with empty key object', () => { + type A = { a: string }; + type B = { b: number }; + const obj: A | B = { a: 'test' }; + + // Empty key object should return true (all zero keys exist) + assert(types.hasKey(obj, {})); + }); + + test('should work with complex union types', () => { + type TypeA = { kind: 'a'; value: string }; + type TypeB = { kind: 'b'; count: number }; + type TypeC = { kind: 'c'; items: string[] }; + + const objA: TypeA | TypeB | TypeC = { kind: 'a', value: 'hello' }; + const objB: TypeA | TypeB | TypeC = { kind: 'b', count: 5 }; + + assert(types.hasKey(objA, { value: true })); + // @ts-expect-error + assert(!types.hasKey(objA, { count: true })); + // @ts-expect-error + assert(!types.hasKey(objA, { items: true })); + + // @ts-expect-error + assert(!types.hasKey(objB, { value: true })); + // @ts-expect-error + assert(types.hasKey(objB, { count: true })); + // @ts-expect-error + assert(!types.hasKey(objB, { items: true })); + }); + + test('should handle objects with optional properties', () => { + type A = { a: string; b?: number }; + type B = { c: boolean }; + const obj1: A | B = { a: 'test', b: 42 }; + const obj2: A | B = { a: 'test' }; + + assert(types.hasKey(obj1, { a: true })); + assert(types.hasKey(obj1, { b: true })); + + assert(types.hasKey(obj2, { a: true })); + assert(!types.hasKey(obj2, { b: true })); + }); + + test('should work with nested objects', () => { + type A = { data: { nested: string } }; + type B = { value: number }; + const obj: A | B = { data: { nested: 'test' } }; + + assert(types.hasKey(obj, { data: true })); + // @ts-expect-error + assert(!types.hasKey(obj, { value: true })); + }); + }); }); diff --git a/code/src/vs/base/test/common/uri.test.ts b/code/src/vs/base/test/common/uri.test.ts index 323789a764e..e2f22d32ef8 100644 --- a/code/src/vs/base/test/common/uri.test.ts +++ b/code/src/vs/base/test/common/uri.test.ts @@ -472,8 +472,8 @@ suite('URI', () => { }), true); assert.strictEqual(URI.isUri(1), false); - assert.strictEqual(URI.isUri("1"), false); - assert.strictEqual(URI.isUri("http://sample.com"), false); + assert.strictEqual(URI.isUri('1'), false); + assert.strictEqual(URI.isUri('http://sample.com'), false); assert.strictEqual(URI.isUri(null), false); assert.strictEqual(URI.isUri(undefined), false); }); @@ -487,7 +487,7 @@ suite('URI', () => { assert.strictEqual(isUriComponents(1), false); assert.strictEqual(isUriComponents(true), false); - assert.strictEqual(isUriComponents("true"), false); + assert.strictEqual(isUriComponents('true'), false); assert.strictEqual(isUriComponents({}), false); assert.strictEqual(isUriComponents({ scheme: '' }), true); // valid components but INVALID uri assert.strictEqual(isUriComponents({ scheme: 'fo' }), true); @@ -635,4 +635,15 @@ suite('URI', () => { assert.strictEqual(URI.parse('http://user@[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html').toString(), 'http://user@[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:80/index.html'); assert.strictEqual(URI.parse('http://us[er@[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html').toString(), 'http://us%5Ber@[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:80/index.html'); }); + + test('File paths containing apostrophes break URI parsing and cannot be opened #276075', function () { + if (isWindows) { + const filePath = 'C:\\Users\\Abd-al-Haseeb\'s_Dell\\Studio\\w3mage\\wp-content\\database.ht.sqlite'; + const uri = URI.file(filePath); + assert.strictEqual(uri.path, '/C:/Users/Abd-al-Haseeb\'s_Dell/Studio/w3mage/wp-content/database.ht.sqlite'); + assert.strictEqual(uri.fsPath, 'c:\\Users\\Abd-al-Haseeb\'s_Dell\\Studio\\w3mage\\wp-content\\database.ht.sqlite'); + } + }); + + }); diff --git a/code/src/vs/base/test/common/yaml.test.ts b/code/src/vs/base/test/common/yaml.test.ts new file mode 100644 index 00000000000..c6e3a53e7cc --- /dev/null +++ b/code/src/vs/base/test/common/yaml.test.ts @@ -0,0 +1,1174 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { deepStrictEqual, strictEqual, ok } from 'assert'; +import { parse, ParseOptions, YamlParseError, Position, YamlNode } from '../../common/yaml.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from './utils.js'; + + +function assertValidParse(input: string[], expected: YamlNode, expectedErrors: YamlParseError[], options?: ParseOptions): void { + const errors: YamlParseError[] = []; + const text = input.join('\n'); + const actual1 = parse(text, errors, options); + deepStrictEqual(actual1, expected); + deepStrictEqual(errors, expectedErrors); +} + +function pos(line: number, character: number): Position { + return { line, character }; +} + +suite('YAML Parser', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + + suite('scalars', () => { + + test('numbers', () => { + assertValidParse(['1'], { type: 'number', start: pos(0, 0), end: pos(0, 1), value: 1 }, []); + assertValidParse(['1.234'], { type: 'number', start: pos(0, 0), end: pos(0, 5), value: 1.234 }, []); + assertValidParse(['-42'], { type: 'number', start: pos(0, 0), end: pos(0, 3), value: -42 }, []); + }); + + test('boolean', () => { + assertValidParse(['true'], { type: 'boolean', start: pos(0, 0), end: pos(0, 4), value: true }, []); + assertValidParse(['false'], { type: 'boolean', start: pos(0, 0), end: pos(0, 5), value: false }, []); + }); + + test('null', () => { + assertValidParse(['null'], { type: 'null', start: pos(0, 0), end: pos(0, 4), value: null }, []); + assertValidParse(['~'], { type: 'null', start: pos(0, 0), end: pos(0, 1), value: null }, []); + }); + + test('string', () => { + assertValidParse(['A Developer'], { type: 'string', start: pos(0, 0), end: pos(0, 11), value: 'A Developer' }, []); + assertValidParse(['\'A Developer\''], { type: 'string', start: pos(0, 0), end: pos(0, 13), value: 'A Developer' }, []); + assertValidParse(['"A Developer"'], { type: 'string', start: pos(0, 0), end: pos(0, 13), value: 'A Developer' }, []); + assertValidParse(['*.js,*.ts'], { type: 'string', start: pos(0, 0), end: pos(0, 9), value: '*.js,*.ts' }, []); + }); + }); + + suite('objects', () => { + + test('simple properties', () => { + assertValidParse(['name: John Doe'], { + type: 'object', start: pos(0, 0), end: pos(0, 14), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 4), value: 'name' }, + value: { type: 'string', start: pos(0, 6), end: pos(0, 14), value: 'John Doe' } + } + ] + }, []); + assertValidParse(['age: 30'], { + type: 'object', start: pos(0, 0), end: pos(0, 7), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 3), value: 'age' }, + value: { type: 'number', start: pos(0, 5), end: pos(0, 7), value: 30 } + } + ] + }, []); + assertValidParse(['active: true'], { + type: 'object', start: pos(0, 0), end: pos(0, 12), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 6), value: 'active' }, + value: { type: 'boolean', start: pos(0, 8), end: pos(0, 12), value: true } + } + ] + }, []); + assertValidParse(['value: null'], { + type: 'object', start: pos(0, 0), end: pos(0, 11), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 5), value: 'value' }, + value: { type: 'null', start: pos(0, 7), end: pos(0, 11), value: null } + } + ] + }, []); + }); + + test('value on next line', () => { + assertValidParse( + [ + 'name:', + ' John Doe', + 'colors:', + ' [ Red, Green, Blue ]', + ], + { + type: 'object', start: pos(0, 0), end: pos(3, 22), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 4), value: 'name' }, + value: { type: 'string', start: pos(1, 2), end: pos(1, 10), value: 'John Doe' } + }, + { + key: { type: 'string', start: pos(2, 0), end: pos(2, 6), value: 'colors' }, + value: { + type: 'array', start: pos(3, 2), end: pos(3, 22), items: [ + { type: 'string', start: pos(3, 4), end: pos(3, 7), value: 'Red' }, + { type: 'string', start: pos(3, 9), end: pos(3, 14), value: 'Green' }, + { type: 'string', start: pos(3, 16), end: pos(3, 20), value: 'Blue' } + ] + } + } + ] + }, + [] + ); + }); + + test('multiple properties', () => { + assertValidParse( + [ + 'name: John Doe', + 'age: 30' + ], + { + type: 'object', start: pos(0, 0), end: pos(1, 7), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 4), value: 'name' }, + value: { type: 'string', start: pos(0, 6), end: pos(0, 14), value: 'John Doe' } + }, + { + key: { type: 'string', start: pos(1, 0), end: pos(1, 3), value: 'age' }, + value: { type: 'number', start: pos(1, 5), end: pos(1, 7), value: 30 } + } + ] + }, + [] + ); + }); + + test('nested object', () => { + assertValidParse( + [ + 'person:', + ' name: John Doe', + ' age: 30' + ], + { + type: 'object', start: pos(0, 0), end: pos(2, 9), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 6), value: 'person' }, + value: { + type: 'object', start: pos(1, 2), end: pos(2, 9), properties: [ + { + key: { type: 'string', start: pos(1, 2), end: pos(1, 6), value: 'name' }, + value: { type: 'string', start: pos(1, 8), end: pos(1, 16), value: 'John Doe' } + }, + { + key: { type: 'string', start: pos(2, 2), end: pos(2, 5), value: 'age' }, + value: { type: 'number', start: pos(2, 7), end: pos(2, 9), value: 30 } + } + ] + } + } + ] + + }, + [] + ); + }); + + + test('nested objects with address', () => { + assertValidParse( + [ + 'person:', + ' name: John Doe', + ' age: 30', + ' address:', + ' street: 123 Main St', + ' city: Example City' + ], + { + type: 'object', start: pos(0, 0), end: pos(5, 22), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 6), value: 'person' }, + value: { + type: 'object', start: pos(1, 2), end: pos(5, 22), + properties: [ + { + key: { type: 'string', start: pos(1, 2), end: pos(1, 6), value: 'name' }, + value: { type: 'string', start: pos(1, 8), end: pos(1, 16), value: 'John Doe' } + }, + { + key: { type: 'string', start: pos(2, 2), end: pos(2, 5), value: 'age' }, + value: { type: 'number', start: pos(2, 7), end: pos(2, 9), value: 30 } + }, + { + key: { type: 'string', start: pos(3, 2), end: pos(3, 9), value: 'address' }, + value: { + type: 'object', start: pos(4, 4), end: pos(5, 22), properties: [ + { + key: { type: 'string', start: pos(4, 4), end: pos(4, 10), value: 'street' }, + value: { type: 'string', start: pos(4, 12), end: pos(4, 23), value: '123 Main St' } + }, + { + key: { type: 'string', start: pos(5, 4), end: pos(5, 8), value: 'city' }, + value: { type: 'string', start: pos(5, 10), end: pos(5, 22), value: 'Example City' } + } + ] + } + } + ] + } + } + ] + }, + [] + ); + }); + + test('properties without space after colon', () => { + assertValidParse( + ['name:John'], + { + type: 'object', start: pos(0, 0), end: pos(0, 9), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 4), value: 'name' }, + value: { type: 'string', start: pos(0, 5), end: pos(0, 9), value: 'John' } + } + ] + }, + [] + ); + + // Test mixed: some properties with space, some without + assertValidParse( + [ + 'config:', + ' database:', + ' host:localhost', + ' port: 5432', + ' credentials:', + ' username:admin', + ' password: secret123' + ], + { + type: 'object', start: pos(0, 0), end: pos(6, 25), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 6), value: 'config' }, + value: { + type: 'object', start: pos(1, 2), end: pos(6, 25), properties: [ + { + key: { type: 'string', start: pos(1, 2), end: pos(1, 10), value: 'database' }, + value: { + type: 'object', start: pos(2, 4), end: pos(6, 25), properties: [ + { + key: { type: 'string', start: pos(2, 4), end: pos(2, 8), value: 'host' }, + value: { type: 'string', start: pos(2, 9), end: pos(2, 18), value: 'localhost' } + }, + { + key: { type: 'string', start: pos(3, 4), end: pos(3, 8), value: 'port' }, + value: { type: 'number', start: pos(3, 10), end: pos(3, 14), value: 5432 } + }, + { + key: { type: 'string', start: pos(4, 4), end: pos(4, 15), value: 'credentials' }, + value: { + type: 'object', start: pos(5, 6), end: pos(6, 25), properties: [ + { + key: { type: 'string', start: pos(5, 6), end: pos(5, 14), value: 'username' }, + value: { type: 'string', start: pos(5, 15), end: pos(5, 20), value: 'admin' } + }, + { + key: { type: 'string', start: pos(6, 6), end: pos(6, 14), value: 'password' }, + value: { type: 'string', start: pos(6, 16), end: pos(6, 25), value: 'secret123' } + } + ] + } + } + ] + } + } + ] + } + } + ] + }, + [] + ); + }); + + test('inline objects', () => { + assertValidParse( + ['{name: John, age: 30}'], + { + type: 'object', start: pos(0, 0), end: pos(0, 21), properties: [ + { + key: { type: 'string', start: pos(0, 1), end: pos(0, 5), value: 'name' }, + value: { type: 'string', start: pos(0, 7), end: pos(0, 11), value: 'John' } + }, + { + key: { type: 'string', start: pos(0, 13), end: pos(0, 16), value: 'age' }, + value: { type: 'number', start: pos(0, 18), end: pos(0, 20), value: 30 } + } + ] + }, + [] + ); + + // Test with different data types + assertValidParse( + ['{active: true, score: 85.5, role: null}'], + { + type: 'object', start: pos(0, 0), end: pos(0, 39), properties: [ + { + key: { type: 'string', start: pos(0, 1), end: pos(0, 7), value: 'active' }, + value: { type: 'boolean', start: pos(0, 9), end: pos(0, 13), value: true } + }, + { + key: { type: 'string', start: pos(0, 15), end: pos(0, 20), value: 'score' }, + value: { type: 'number', start: pos(0, 22), end: pos(0, 26), value: 85.5 } + }, + { + key: { type: 'string', start: pos(0, 28), end: pos(0, 32), value: 'role' }, + value: { type: 'null', start: pos(0, 34), end: pos(0, 38), value: null } + } + ] + }, + [] + ); + + // Test empty inline object + assertValidParse( + ['{}'], + { + type: 'object', start: pos(0, 0), end: pos(0, 2), properties: [] + }, + [] + ); + + // Test inline object with quoted keys and values + assertValidParse( + ['{"name": "John Doe", "age": 30}'], + { + type: 'object', start: pos(0, 0), end: pos(0, 31), properties: [ + { + key: { type: 'string', start: pos(0, 1), end: pos(0, 7), value: 'name' }, + value: { type: 'string', start: pos(0, 9), end: pos(0, 19), value: 'John Doe' } + }, + { + key: { type: 'string', start: pos(0, 21), end: pos(0, 26), value: 'age' }, + value: { type: 'number', start: pos(0, 28), end: pos(0, 30), value: 30 } + } + ] + }, + [] + ); + + // Test inline object without spaces + assertValidParse( + ['{name:John,age:30}'], + { + type: 'object', start: pos(0, 0), end: pos(0, 18), properties: [ + { + key: { type: 'string', start: pos(0, 1), end: pos(0, 5), value: 'name' }, + value: { type: 'string', start: pos(0, 6), end: pos(0, 10), value: 'John' } + }, + { + key: { type: 'string', start: pos(0, 11), end: pos(0, 14), value: 'age' }, + value: { type: 'number', start: pos(0, 15), end: pos(0, 17), value: 30 } + } + ] + }, + [] + ); + + // Test multi-line inline object with internal comment line between properties + assertValidParse( + ['{a:1, # comment about b', ' b:2, c:3}'], + { + type: 'object', start: pos(0, 0), end: pos(1, 10), properties: [ + { + key: { type: 'string', start: pos(0, 1), end: pos(0, 2), value: 'a' }, + value: { type: 'number', start: pos(0, 3), end: pos(0, 4), value: 1 } + }, + { + key: { type: 'string', start: pos(1, 1), end: pos(1, 2), value: 'b' }, + value: { type: 'number', start: pos(1, 3), end: pos(1, 4), value: 2 } + }, + { + key: { type: 'string', start: pos(1, 6), end: pos(1, 7), value: 'c' }, + value: { type: 'number', start: pos(1, 8), end: pos(1, 9), value: 3 } + } + ] + }, + [] + ); + }); + + test('special characters in values', () => { + // Test values with special characters + assertValidParse( + [`key: value with \t special chars`], + { + type: 'object', start: pos(0, 0), end: pos(0, 31), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 3), value: 'key' }, + value: { type: 'string', start: pos(0, 5), end: pos(0, 31), value: `value with \t special chars` } + } + ] + }, + [] + ); + }); + + test('various whitespace types', () => { + // Test different types of whitespace + assertValidParse( + [`key:\t \t \t value`], + { + type: 'object', start: pos(0, 0), end: pos(0, 15), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 3), value: 'key' }, + value: { type: 'string', start: pos(0, 10), end: pos(0, 15), value: 'value' } + } + ] + }, + [] + ); + }); + }); + + suite('arrays', () => { + + + test('arrays', () => { + assertValidParse( + [ + '- Boston Red Sox', + '- Detroit Tigers', + '- New York Yankees' + ], + { + type: 'array', start: pos(0, 0), end: pos(2, 18), items: [ + { type: 'string', start: pos(0, 2), end: pos(0, 16), value: 'Boston Red Sox' }, + { type: 'string', start: pos(1, 2), end: pos(1, 16), value: 'Detroit Tigers' }, + { type: 'string', start: pos(2, 2), end: pos(2, 18), value: 'New York Yankees' } + ] + + }, + [] + ); + }); + + + test('inline arrays', () => { + assertValidParse( + ['[Apple, Banana, Cherry]'], + { + type: 'array', start: pos(0, 0), end: pos(0, 23), items: [ + { type: 'string', start: pos(0, 1), end: pos(0, 6), value: 'Apple' }, + { type: 'string', start: pos(0, 8), end: pos(0, 14), value: 'Banana' }, + { type: 'string', start: pos(0, 16), end: pos(0, 22), value: 'Cherry' } + ] + + }, + [] + ); + }); + + test('inline array with internal comment line', () => { + assertValidParse( + ['[one # comment about two', ',two, three]'], + { + type: 'array', start: pos(0, 0), end: pos(1, 12), items: [ + { type: 'string', start: pos(0, 1), end: pos(0, 4), value: 'one' }, + { type: 'string', start: pos(1, 1), end: pos(1, 4), value: 'two' }, + { type: 'string', start: pos(1, 6), end: pos(1, 11), value: 'three' } + ] + }, + [] + ); + }); + + test('multi-line inline arrays', () => { + assertValidParse( + [ + '[', + ' geen, ', + ' yello, red]' + ], + { + type: 'array', start: pos(0, 0), end: pos(2, 15), items: [ + { type: 'string', start: pos(1, 4), end: pos(1, 8), value: 'geen' }, + { type: 'string', start: pos(2, 4), end: pos(2, 9), value: 'yello' }, + { type: 'string', start: pos(2, 11), end: pos(2, 14), value: 'red' } + ] + }, + [] + ); + }); + + test('arrays of arrays', () => { + assertValidParse( + [ + '-', + ' - Apple', + ' - Banana', + ' - Cherry' + ], + { + type: 'array', start: pos(0, 0), end: pos(3, 10), items: [ + { + type: 'array', start: pos(1, 2), end: pos(3, 10), items: [ + { type: 'string', start: pos(1, 4), end: pos(1, 9), value: 'Apple' }, + { type: 'string', start: pos(2, 4), end: pos(2, 10), value: 'Banana' }, + { type: 'string', start: pos(3, 4), end: pos(3, 10), value: 'Cherry' } + ] + } + ] + }, + [] + ); + }); + + test('inline arrays of inline arrays', () => { + assertValidParse( + [ + '[', + ' [ee], [ff, gg]', + ']', + ], + { + type: 'array', start: pos(0, 0), end: pos(2, 1), items: [ + { + type: 'array', start: pos(1, 2), end: pos(1, 6), items: [ + { type: 'string', start: pos(1, 3), end: pos(1, 5), value: 'ee' }, + ], + }, + { + type: 'array', start: pos(1, 8), end: pos(1, 16), items: [ + { type: 'string', start: pos(1, 9), end: pos(1, 11), value: 'ff' }, + { type: 'string', start: pos(1, 13), end: pos(1, 15), value: 'gg' }, + ], + } + ] + }, + [] + ); + }); + + test('object with array containing single object', () => { + assertValidParse( + [ + 'items:', + '- name: John', + ' age: 30' + ], + { + type: 'object', start: pos(0, 0), end: pos(2, 9), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 5), value: 'items' }, + value: { + type: 'array', start: pos(1, 0), end: pos(2, 9), items: [ + { + type: 'object', start: pos(1, 2), end: pos(2, 9), properties: [ + { + key: { type: 'string', start: pos(1, 2), end: pos(1, 6), value: 'name' }, + value: { type: 'string', start: pos(1, 8), end: pos(1, 12), value: 'John' } + }, + { + key: { type: 'string', start: pos(2, 2), end: pos(2, 5), value: 'age' }, + value: { type: 'number', start: pos(2, 7), end: pos(2, 9), value: 30 } + } + ] + } + ] + } + } + ] + }, + [] + ); + }); + + test('arrays of objects', () => { + assertValidParse( + [ + '-', + ' name: one', + '- name: two', + '-', + ' name: three' + ], + { + type: 'array', start: pos(0, 0), end: pos(4, 13), items: [ + { + type: 'object', start: pos(1, 2), end: pos(1, 11), properties: [ + { + key: { type: 'string', start: pos(1, 2), end: pos(1, 6), value: 'name' }, + value: { type: 'string', start: pos(1, 8), end: pos(1, 11), value: 'one' } + } + ] + }, + { + type: 'object', start: pos(2, 2), end: pos(2, 11), properties: [ + { + key: { type: 'string', start: pos(2, 2), end: pos(2, 6), value: 'name' }, + value: { type: 'string', start: pos(2, 8), end: pos(2, 11), value: 'two' } + } + ] + }, + { + type: 'object', start: pos(4, 2), end: pos(4, 13), properties: [ + { + key: { type: 'string', start: pos(4, 2), end: pos(4, 6), value: 'name' }, + value: { type: 'string', start: pos(4, 8), end: pos(4, 13), value: 'three' } + } + ] + } + ] + }, + [] + ); + }); + }); + + suite('complex structures', () => { + + test('array of objects', () => { + assertValidParse( + [ + 'products:', + ' - name: Laptop', + ' price: 999.99', + ' in_stock: true', + ' - name: Mouse', + ' price: 25.50', + ' in_stock: false' + ], + { + type: 'object', start: pos(0, 0), end: pos(6, 19), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 8), value: 'products' }, + value: { + type: 'array', start: pos(1, 2), end: pos(6, 19), items: [ + { + type: 'object', start: pos(1, 4), end: pos(3, 18), properties: [ + { + key: { type: 'string', start: pos(1, 4), end: pos(1, 8), value: 'name' }, + value: { type: 'string', start: pos(1, 10), end: pos(1, 16), value: 'Laptop' } + }, + { + key: { type: 'string', start: pos(2, 4), end: pos(2, 9), value: 'price' }, + value: { type: 'number', start: pos(2, 11), end: pos(2, 17), value: 999.99 } + }, + { + key: { type: 'string', start: pos(3, 4), end: pos(3, 12), value: 'in_stock' }, + value: { type: 'boolean', start: pos(3, 14), end: pos(3, 18), value: true } + } + ] + }, + { + type: 'object', start: pos(4, 4), end: pos(6, 19), properties: [ + { + key: { type: 'string', start: pos(4, 4), end: pos(4, 8), value: 'name' }, + value: { type: 'string', start: pos(4, 10), end: pos(4, 15), value: 'Mouse' } + }, + { + key: { type: 'string', start: pos(5, 4), end: pos(5, 9), value: 'price' }, + value: { type: 'number', start: pos(5, 11), end: pos(5, 16), value: 25.50 } + }, + { + key: { type: 'string', start: pos(6, 4), end: pos(6, 12), value: 'in_stock' }, + value: { type: 'boolean', start: pos(6, 14), end: pos(6, 19), value: false } + } + ] + } + ] + } + } + ] + }, + [] + ); + }); + + test('inline array mixed primitives', () => { + assertValidParse( + ['vals: [1, true, null, "str"]'], + { + type: 'object', start: pos(0, 0), end: pos(0, 28), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 4), value: 'vals' }, + value: { + type: 'array', start: pos(0, 6), end: pos(0, 28), items: [ + { type: 'number', start: pos(0, 7), end: pos(0, 8), value: 1 }, + { type: 'boolean', start: pos(0, 10), end: pos(0, 14), value: true }, + { type: 'null', start: pos(0, 16), end: pos(0, 20), value: null }, + { type: 'string', start: pos(0, 22), end: pos(0, 27), value: 'str' } + ] + } + } + ] + }, + [] + ); + }); + + test('mixed inline structures', () => { + assertValidParse( + ['config: {env: "prod", settings: [true, 42], debug: false}'], + { + type: 'object', start: pos(0, 0), end: pos(0, 57), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 6), value: 'config' }, + value: { + type: 'object', start: pos(0, 8), end: pos(0, 57), properties: [ + { + key: { type: 'string', start: pos(0, 9), end: pos(0, 12), value: 'env' }, + value: { type: 'string', start: pos(0, 14), end: pos(0, 20), value: 'prod' } + }, + { + key: { type: 'string', start: pos(0, 22), end: pos(0, 30), value: 'settings' }, + value: { + type: 'array', start: pos(0, 32), end: pos(0, 42), items: [ + { type: 'boolean', start: pos(0, 33), end: pos(0, 37), value: true }, + { type: 'number', start: pos(0, 39), end: pos(0, 41), value: 42 } + ] + } + }, + { + key: { type: 'string', start: pos(0, 44), end: pos(0, 49), value: 'debug' }, + value: { type: 'boolean', start: pos(0, 51), end: pos(0, 56), value: false } + } + ] + } + } + ] + }, + [] + ); + }); + + test('with comments', () => { + assertValidParse( + [ + `# This is a comment`, + 'name: John Doe # inline comment', + 'age: 30' + ], + { + type: 'object', start: pos(1, 0), end: pos(2, 7), properties: [ + { + key: { type: 'string', start: pos(1, 0), end: pos(1, 4), value: 'name' }, + value: { type: 'string', start: pos(1, 6), end: pos(1, 14), value: 'John Doe' } + }, + { + key: { type: 'string', start: pos(2, 0), end: pos(2, 3), value: 'age' }, + value: { type: 'number', start: pos(2, 5), end: pos(2, 7), value: 30 } + } + ] + }, + [] + ); + }); + }); + + suite('edge cases and error handling', () => { + + + // Edge cases + test('duplicate keys error', () => { + assertValidParse( + [ + 'key: 1', + 'key: 2' + ], + { + type: 'object', start: pos(0, 0), end: pos(1, 6), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 3), value: 'key' }, + value: { type: 'number', start: pos(0, 5), end: pos(0, 6), value: 1 } + }, + { + key: { type: 'string', start: pos(1, 0), end: pos(1, 3), value: 'key' }, + value: { type: 'number', start: pos(1, 5), end: pos(1, 6), value: 2 } + } + ] + }, + [ + { + message: 'Duplicate key \'key\'', + code: 'duplicateKey', + start: pos(1, 0), + end: pos(1, 3) + } + ] + ); + }); + + test('duplicate keys allowed with option', () => { + assertValidParse( + [ + 'key: 1', + 'key: 2' + ], + { + type: 'object', start: pos(0, 0), end: pos(1, 6), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 3), value: 'key' }, + value: { type: 'number', start: pos(0, 5), end: pos(0, 6), value: 1 } + }, + { + key: { type: 'string', start: pos(1, 0), end: pos(1, 3), value: 'key' }, + value: { type: 'number', start: pos(1, 5), end: pos(1, 6), value: 2 } + } + ] + }, + [], + { allowDuplicateKeys: true } + ); + }); + + test('unexpected indentation error with recovery', () => { + // Parser reports error but still captures the over-indented property. + assertValidParse( + [ + 'key: 1', + ' stray: value' + ], + { + type: 'object', start: pos(0, 0), end: pos(1, 16), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 3), value: 'key' }, + value: { type: 'number', start: pos(0, 5), end: pos(0, 6), value: 1 } + }, + { + key: { type: 'string', start: pos(1, 4), end: pos(1, 9), value: 'stray' }, + value: { type: 'string', start: pos(1, 11), end: pos(1, 16), value: 'value' } + } + ] + }, + [ + { + message: 'Unexpected indentation', + code: 'indentation', + start: pos(1, 0), + end: pos(1, 16) + } + ] + ); + }); + + test('empty values and inline empty array', () => { + assertValidParse( + [ + 'empty:', + 'array: []' + ], + { + type: 'object', start: pos(0, 0), end: pos(1, 9), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 5), value: 'empty' }, + value: { type: 'string', start: pos(0, 6), end: pos(0, 6), value: '' } + }, + { + key: { type: 'string', start: pos(1, 0), end: pos(1, 5), value: 'array' }, + value: { type: 'array', start: pos(1, 7), end: pos(1, 9), items: [] } + } + ] + }, + [] + ); + }); + + + + test('nested empty objects', () => { + // Parser should create nodes for both parent and child, with child having empty string value. + assertValidParse( + [ + 'parent:', + ' child:' + ], + { + type: 'object', start: pos(0, 0), end: pos(1, 8), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 6), value: 'parent' }, + value: { + type: 'object', start: pos(1, 2), end: pos(1, 8), properties: [ + { + key: { type: 'string', start: pos(1, 2), end: pos(1, 7), value: 'child' }, + value: { type: 'string', start: pos(1, 8), end: pos(1, 8), value: '' } + } + ] + } + } + ] + }, + [] + ); + }); + + test('empty object with only colons', () => { + // Test object with empty values + assertValidParse( + ['key1:', 'key2:', 'key3:'], + { + type: 'object', start: pos(0, 0), end: pos(2, 5), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 4), value: 'key1' }, + value: { type: 'string', start: pos(0, 5), end: pos(0, 5), value: '' } + }, + { + key: { type: 'string', start: pos(1, 0), end: pos(1, 4), value: 'key2' }, + value: { type: 'string', start: pos(1, 5), end: pos(1, 5), value: '' } + }, + { + key: { type: 'string', start: pos(2, 0), end: pos(2, 4), value: 'key3' }, + value: { type: 'string', start: pos(2, 5), end: pos(2, 5), value: '' } + } + ] + }, + [] + ); + }); + + test('large input performance', () => { + // Test that large inputs are handled efficiently + const input = Array.from({ length: 1000 }, (_, i) => `key${i}: value${i}`); + const expectedProperties = Array.from({ length: 1000 }, (_, i) => ({ + key: { type: 'string' as const, start: pos(i, 0), end: pos(i, `key${i}`.length), value: `key${i}` }, + value: { type: 'string' as const, start: pos(i, `key${i}: `.length), end: pos(i, `key${i}: value${i}`.length), value: `value${i}` } + })); + + const start = Date.now(); + assertValidParse( + input, + { + type: 'object', + start: pos(0, 0), + end: pos(999, 'key999: value999'.length), + properties: expectedProperties + }, + [] + ); + const duration = Date.now() - start; + + ok(duration < 100, `Parsing took ${duration}ms, expected < 100ms`); + }); + + test('deeply nested structure performance', () => { + // Test that deeply nested structures are handled efficiently + const lines = []; + for (let i = 0; i < 50; i++) { + const indent = ' '.repeat(i); + lines.push(`${indent}level${i}:`); + } + lines.push(' '.repeat(50) + 'deepValue: reached'); + + const start = Date.now(); + const errors: YamlParseError[] = []; + const result = parse(lines.join('\n'), errors); + const duration = Date.now() - start; + + ok(result); + strictEqual(result.type, 'object'); + strictEqual(errors.length, 0); + ok(duration < 100, `Parsing took ${duration}ms, expected < 100ms`); + }); + + test('malformed array with position issues', () => { + // Test malformed arrays that might cause position advancement issues + assertValidParse( + [ + 'key: [', + '', + '', + '', + '' + ], + { + type: 'object', start: pos(0, 0), end: pos(5, 0), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 3), value: 'key' }, + value: { type: 'array', start: pos(0, 5), end: pos(5, 0), items: [] } + } + ] + }, + [] + ); + }); + + test('self-referential like structure', () => { + // Test structures that might appear self-referential + assertValidParse( + [ + 'a:', + ' b:', + ' a:', + ' b:', + ' value: test' + ], + { + type: 'object', start: pos(0, 0), end: pos(4, 19), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 1), value: 'a' }, + value: { + type: 'object', start: pos(1, 2), end: pos(4, 19), properties: [ + { + key: { type: 'string', start: pos(1, 2), end: pos(1, 3), value: 'b' }, + value: { + type: 'object', start: pos(2, 4), end: pos(4, 19), properties: [ + { + key: { type: 'string', start: pos(2, 4), end: pos(2, 5), value: 'a' }, + value: { + type: 'object', start: pos(3, 6), end: pos(4, 19), properties: [ + { + key: { type: 'string', start: pos(3, 6), end: pos(3, 7), value: 'b' }, + value: { + type: 'object', start: pos(4, 8), end: pos(4, 19), properties: [ + { + key: { type: 'string', start: pos(4, 8), end: pos(4, 13), value: 'value' }, + value: { type: 'string', start: pos(4, 15), end: pos(4, 19), value: 'test' } + } + ] + } + } + ] + } + } + ] + } + } + ] + } + } + ] + }, + [] + ); + }); + + test('array with empty lines', () => { + // Test arrays spanning multiple lines with empty lines + assertValidParse( + ['arr: [', '', 'item1,', '', 'item2', '', ']'], + { + type: 'object', start: pos(0, 0), end: pos(6, 1), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 3), value: 'arr' }, + value: { + type: 'array', start: pos(0, 5), end: pos(6, 1), items: [ + { type: 'string', start: pos(2, 0), end: pos(2, 5), value: 'item1' }, + { type: 'string', start: pos(4, 0), end: pos(4, 5), value: 'item2' } + ] + } + } + ] + }, + [] + ); + }); + + test('whitespace advancement robustness', () => { + // Test that whitespace advancement works correctly + assertValidParse( + [`key: value`], + { + type: 'object', start: pos(0, 0), end: pos(0, 15), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 3), value: 'key' }, + value: { type: 'string', start: pos(0, 10), end: pos(0, 15), value: 'value' } + } + ] + }, + [] + ); + }); + + + test('missing end quote in string values', () => { + // Test unclosed double quote - parser treats it as bare string with quote included + assertValidParse( + ['name: "John'], + { + type: 'object', start: pos(0, 0), end: pos(0, 11), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 4), value: 'name' }, + value: { type: 'string', start: pos(0, 6), end: pos(0, 11), value: 'John' } + } + ] + }, + [] + ); + + // Test unclosed single quote - parser treats it as bare string with quote included + assertValidParse( + ['description: \'Hello world'], + { + type: 'object', start: pos(0, 0), end: pos(0, 25), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 11), value: 'description' }, + value: { type: 'string', start: pos(0, 13), end: pos(0, 25), value: 'Hello world' } + } + ] + }, + [] + ); + + // Test unclosed quote in multi-line context + assertValidParse( + [ + 'data: "incomplete', + 'next: value' + ], + { + type: 'object', start: pos(0, 0), end: pos(1, 11), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 4), value: 'data' }, + value: { type: 'string', start: pos(0, 6), end: pos(0, 17), value: 'incomplete' } + }, + { + key: { type: 'string', start: pos(1, 0), end: pos(1, 4), value: 'next' }, + value: { type: 'string', start: pos(1, 6), end: pos(1, 11), value: 'value' } + } + ] + }, + [] + ); + + // Test properly quoted strings for comparison + assertValidParse( + ['name: "John"'], + { + type: 'object', start: pos(0, 0), end: pos(0, 12), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 4), value: 'name' }, + value: { type: 'string', start: pos(0, 6), end: pos(0, 12), value: 'John' } + } + ] + }, + [] + ); + }); + + test('comment in inline array #269078', () => { + // Test malformed array with comment-like content - should not cause endless loop + assertValidParse( + [ + 'mode: agent', + 'tools: [#r' + ], + { + type: 'object', start: pos(0, 0), end: pos(2, 0), properties: [ + { + key: { type: 'string', start: pos(0, 0), end: pos(0, 4), value: 'mode' }, + value: { type: 'string', start: pos(0, 6), end: pos(0, 11), value: 'agent' } + }, + { + key: { type: 'string', start: pos(1, 0), end: pos(1, 5), value: 'tools' }, + value: { type: 'array', start: pos(1, 7), end: pos(2, 0), items: [] } + } + ] + }, + [] + ); + }); + + + }); + +}); diff --git a/code/src/vs/base/test/node/ps.test.ts b/code/src/vs/base/test/node/ps.test.ts new file mode 100644 index 00000000000..e3d103205e4 --- /dev/null +++ b/code/src/vs/base/test/node/ps.test.ts @@ -0,0 +1,64 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { deepStrictEqual } from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../common/utils.js'; +import { JS_FILENAME_PATTERN } from '../../node/ps.js'; + +suite('Process Utils', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + + suite('JS file regex', () => { + + function findJsFiles(cmd: string): string[] { + const matches: string[] = []; + let match; + while ((match = JS_FILENAME_PATTERN.exec(cmd)) !== null) { + matches.push(match[0]); + } + return matches; + } + + test('should match simple .js files', () => { + deepStrictEqual(findJsFiles('node bootstrap.js'), ['bootstrap.js']); + }); + + test('should match multiple .js files', () => { + deepStrictEqual(findJsFiles('node server.js --require helper.js'), ['server.js', 'helper.js']); + }); + + test('should match .js files with hyphens', () => { + deepStrictEqual(findJsFiles('node my-script.js'), ['my-script.js']); + }); + + test('should not match .json files', () => { + deepStrictEqual(findJsFiles('cat package.json'), []); + }); + + test('should not match .js prefix in .json extension (regression test for \\b fix)', () => { + // Without the \b word boundary, the regex would incorrectly match "package.js" from "package.json" + deepStrictEqual(findJsFiles('node --config tsconfig.json'), []); + deepStrictEqual(findJsFiles('eslint.json'), []); + }); + + test('should not match .jsx files', () => { + deepStrictEqual(findJsFiles('node component.jsx'), []); + }); + + test('should match .js but not .json in same command', () => { + deepStrictEqual(findJsFiles('node app.js --config settings.json'), ['app.js']); + }); + + test('should not match partial matches inside other extensions', () => { + deepStrictEqual(findJsFiles('file.jsmith'), []); + }); + + test('should match .js at end of command', () => { + deepStrictEqual(findJsFiles('/path/to/script.js'), ['script.js']); + }); + }); +}); + diff --git a/code/src/vs/code/browser/workbench/workbench.ts b/code/src/vs/code/browser/workbench/workbench.ts index 55a91d8d9ea..8784494a4a4 100644 --- a/code/src/vs/code/browser/workbench/workbench.ts +++ b/code/src/vs/code/browser/workbench/workbench.ts @@ -131,7 +131,7 @@ class ServerKeyedAESCrypto implements ISecretStorageCrypto { const keyData = new Uint8Array(AESConstants.KEY_LENGTH / 8); for (let i = 0; i < keyData.byteLength; i++) { - keyData[i] = clientKey[i]! ^ serverKey[i]!; + keyData[i] = clientKey[i] ^ serverKey[i]; } return mainWindow.crypto.subtle.importKey( @@ -223,6 +223,7 @@ export class LocalStorageSecretStorageProvider implements ISecretStorageProvider private loadAuthSessionFromElement(): Record { let authSessionInfo: (AuthenticationSessionInfo & { scopes: string[][] }) | undefined; + // eslint-disable-next-line no-restricted-syntax const authSessionElement = mainWindow.document.getElementById('vscode-workbench-auth-session'); const authSessionElementAttribute = authSessionElement ? authSessionElement.getAttribute('data-settings') : undefined; if (authSessionElementAttribute) { @@ -599,6 +600,7 @@ function readCookie(name: string): string | undefined { (function () { // Find config by checking for DOM + // eslint-disable-next-line no-restricted-syntax const configElement = mainWindow.document.getElementById('vscode-workbench-web-configuration'); const configElementAttribute = configElement ? configElement.getAttribute('data-settings') : undefined; if (!configElement || !configElementAttribute) { diff --git a/code/src/vs/code/electron-browser/workbench/workbench-dev.html b/code/src/vs/code/electron-browser/workbench/workbench-dev.html index 1121fc7c047..13ff778a58c 100644 --- a/code/src/vs/code/electron-browser/workbench/workbench-dev.html +++ b/code/src/vs/code/electron-browser/workbench/workbench-dev.html @@ -73,5 +73,5 @@ - + diff --git a/code/src/vs/code/electron-browser/workbench/workbench.ts b/code/src/vs/code/electron-browser/workbench/workbench.ts index 40ca898489b..da8713718c7 100644 --- a/code/src/vs/code/electron-browser/workbench/workbench.ts +++ b/code/src/vs/code/electron-browser/workbench/workbench.ts @@ -17,7 +17,7 @@ type IMainWindowSandboxGlobals = import('../../../base/parts/sandbox/electron-browser/globals.js').IMainWindowSandboxGlobals; type IDesktopMain = import('../../../workbench/electron-browser/desktop.main.js').IDesktopMain; - const preloadGlobals: IMainWindowSandboxGlobals = (window as any).vscode; // defined by preload.ts + const preloadGlobals = (window as unknown as { vscode: IMainWindowSandboxGlobals }).vscode; // defined by preload.ts const safeProcess = preloadGlobals.process; //#region Splash Screen Helpers @@ -126,7 +126,7 @@ titleDiv.style.left = '0'; titleDiv.style.top = '0'; titleDiv.style.backgroundColor = `${colorInfo.titleBarBackground}`; - (titleDiv.style as any)['-webkit-app-region'] = 'drag'; + (titleDiv.style as CSSStyleDeclaration & { '-webkit-app-region': string })['-webkit-app-region'] = 'drag'; splash.appendChild(titleDiv); if (colorInfo.titleBarBorder) { @@ -273,7 +273,7 @@ //#region Window Helpers - async function load(esModule: string, options: ILoadOptions): Promise> { + async function load(options: ILoadOptions): Promise> { // Window Configuration from Preload Script const configuration = await resolveWindowConfiguration(); @@ -296,8 +296,14 @@ // ESM Import try { - const result = await import(new URL(`${esModule}.js`, baseUrl).href); + let workbenchUrl: string; + if (!!safeProcess.env['VSCODE_DEV'] && globalThis._VSCODE_USE_RELATIVE_IMPORTS) { + workbenchUrl = '../../../workbench/workbench.desktop.main.js'; // for dev purposes only + } else { + workbenchUrl = new URL(`vs/workbench/workbench.desktop.main.js`, baseUrl).href; + } + const result = await import(workbenchUrl); if (developerDeveloperKeybindingsDisposable && removeDeveloperKeybindingsAfterLoad) { developerDeveloperKeybindingsDisposable(); } @@ -449,6 +455,10 @@ // DEV: a blob URL that loads the CSS via a dynamic @import-rule. // DEV --------------------------------------------------------------------------------------- + if (globalThis._VSCODE_DISABLE_CSS_IMPORT_MAP) { + return; // disabled in certain development setups + } + if (Array.isArray(configuration.cssModules) && configuration.cssModules.length > 0) { performance.mark('code/willAddCssLoader'); @@ -474,7 +484,7 @@ const importMapScript = document.createElement('script'); importMapScript.type = 'importmap'; importMapScript.setAttribute('nonce', '0c6a828f1297'); - // @ts-ignore + // @ts-expect-error importMapScript.textContent = ttp?.createScript(importMapSrc) ?? importMapSrc; window.document.head.appendChild(importMapScript); @@ -484,7 +494,7 @@ //#endregion - const { result, configuration } = await load('vs/workbench/workbench.desktop.main', + const { result, configuration } = await load( { configureDeveloperSettings: function (windowConfig) { return { diff --git a/code/src/vs/code/electron-main/app.ts b/code/src/vs/code/electron-main/app.ts index 9643512df76..43aba014586 100644 --- a/code/src/vs/code/electron-main/app.ts +++ b/code/src/vs/code/electron-main/app.ts @@ -545,7 +545,7 @@ export class CodeApplication extends Disposable { // See: https://github.com/microsoft/vscode/issues/35361#issuecomment-399794085 try { if (isMacintosh && this.configurationService.getValue('window.nativeTabs') === true && !systemPreferences.getUserDefault('NSUseImprovedLayoutPass', 'boolean')) { - systemPreferences.setUserDefault('NSUseImprovedLayoutPass', 'boolean', true as any); + systemPreferences.setUserDefault('NSUseImprovedLayoutPass', 'boolean', true); } } catch (error) { this.logService.error(error); @@ -690,7 +690,7 @@ export class CodeApplication extends Disposable { } // macOS: open-url events that were received before the app is ready - const protocolUrlsFromEvent = ((global).getOpenUrls() || []) as string[]; + const protocolUrlsFromEvent = ((global as { getOpenUrls?: () => string[] }).getOpenUrls?.() || []); if (protocolUrlsFromEvent.length > 0) { this.logService.trace(`app#resolveInitialProtocolUrls() protocol urls from macOS 'open-url' event:`, protocolUrlsFromEvent); } @@ -1297,7 +1297,7 @@ export class CodeApplication extends Disposable { } } - const macOpenFiles: string[] = (global).macOpenFiles; + const macOpenFiles: string[] = (global as { macOpenFiles?: string[] }).macOpenFiles ?? []; const hasCliArgs = args._.length; const hasFolderURIs = !!args['folder-uri']; const hasFileURIs = !!args['file-uri']; diff --git a/code/src/vs/code/electron-main/main.ts b/code/src/vs/code/electron-main/main.ts index 7e83508c821..8ea3d44a286 100644 --- a/code/src/vs/code/electron-main/main.ts +++ b/code/src/vs/code/electron-main/main.ts @@ -18,7 +18,7 @@ import { getPathLabel } from '../../base/common/labels.js'; import { Schemas } from '../../base/common/network.js'; import { basename, resolve } from '../../base/common/path.js'; import { mark } from '../../base/common/performance.js'; -import { IProcessEnvironment, isMacintosh, isWindows, OS } from '../../base/common/platform.js'; +import { IProcessEnvironment, isLinux, isMacintosh, isWindows, OS } from '../../base/common/platform.js'; import { cwd } from '../../base/common/process.js'; import { rtrim, trim } from '../../base/common/strings.js'; import { Promises as FSPromises } from '../../base/node/pfs.js'; @@ -73,6 +73,7 @@ import { SaveStrategy, StateService } from '../../platform/state/node/stateServi import { FileUserDataProvider } from '../../platform/userData/common/fileUserDataProvider.js'; import { addUNCHostToAllowlist, getUNCHost } from '../../base/node/unc.js'; import { ThemeMainService } from '../../platform/theme/electron-main/themeMainServiceImpl.js'; +import { LINUX_SYSTEM_POLICY_FILE_PATH } from '../../base/common/policy.js'; /** * The main VS Code entry point. @@ -143,6 +144,14 @@ class CodeMain { evt.join('instanceLockfile', promises.unlink(environmentMainService.mainLockfile).catch(() => { /* ignored */ })); }); + // Check if Inno Setup is running + const innoSetupActive = await this.checkInnoSetupMutex(productService); + if (innoSetupActive) { + const message = `${productService.nameShort} is currently being updated. Please wait for the update to complete before launching.`; + instantiationService.invokeFunction(this.quit, new Error(message)); + return; + } + return instantiationService.createInstance(CodeApplication, mainProcessNodeIpcServer, instanceEnvironment).startup(); }); } catch (error) { @@ -204,6 +213,8 @@ class CodeMain { policyService = disposables.add(new NativePolicyService(logService, productService.win32RegValueName)); } else if (isMacintosh && productService.darwinBundleIdentifier) { policyService = disposables.add(new NativePolicyService(logService, productService.darwinBundleIdentifier)); + } else if (isLinux) { + policyService = disposables.add(new FilePolicyService(URI.file(LINUX_SYSTEM_POLICY_FILE_PATH), fileService, logService)); } else if (environmentMainService.policyFile) { policyService = disposables.add(new FilePolicyService(environmentMainService.policyFile, fileService, logService)); } else { @@ -313,11 +324,6 @@ class CodeMain { throw error; } - // Since we are the second instance, we do not want to show the dock - if (isMacintosh) { - app.dock?.hide(); - } - // there's a running instance, let's connect to it let client: NodeIPCClient; try { @@ -417,11 +423,6 @@ class CodeMain { throw new ExpectedError('Terminating...'); } - // dock might be hidden at this case due to a retry - if (isMacintosh) { - app.dock?.show(); - } - // Set the VSCODE_PID variable here when we are sure we are the first // instance to startup. Otherwise we would wrongly overwrite the PID process.env['VSCODE_PID'] = String(process.pid); @@ -494,6 +495,21 @@ class CodeMain { lifecycleMainService.kill(exitCode); } + private async checkInnoSetupMutex(productService: IProductService): Promise { + if (!isWindows || !productService.win32MutexName || productService.quality !== 'insider') { + return false; + } + + try { + const readyMutexName = `${productService.win32MutexName}setup`; + const mutex = await import('@vscode/windows-mutex'); + return mutex.isActive(readyMutexName); + } catch (error) { + console.error('Failed to check Inno Setup mutex:', error); + return false; + } + } + //#region Command line arguments utilities private resolveArgs(): NativeParsedArgs { @@ -523,6 +539,9 @@ class CodeMain { } else if (args.chat['reuse-window']) { // Apply `--reuse-window` flag to the main arguments args['reuse-window'] = true; + } else if (args.chat['profile']) { + // Apply `--profile` flag to the main arguments + args['profile'] = args.chat['profile']; } else { // Unless we are started with specific instructions about // new windows or reusing existing ones, always take the diff --git a/code/src/vs/code/electron-utility/sharedProcess/contrib/defaultExtensionsInitializer.ts b/code/src/vs/code/electron-utility/sharedProcess/contrib/defaultExtensionsInitializer.ts index 0098f682199..04b79ab51fd 100644 --- a/code/src/vs/code/electron-utility/sharedProcess/contrib/defaultExtensionsInitializer.ts +++ b/code/src/vs/code/electron-utility/sharedProcess/contrib/defaultExtensionsInitializer.ts @@ -13,6 +13,7 @@ import { ILogService } from '../../../../platform/log/common/log.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { FileOperationResult, IFileService, IFileStat, toFileOperationResult } from '../../../../platform/files/common/files.js'; import { getErrorMessage } from '../../../../base/common/errors.js'; +import { IProductService } from '../../../../platform/product/common/productService.js'; const defaultExtensionsInitStatusKey = 'initializing-default-extensions'; @@ -23,6 +24,7 @@ export class DefaultExtensionsInitializer extends Disposable { @IStorageService storageService: IStorageService, @IFileService private readonly fileService: IFileService, @ILogService private readonly logService: ILogService, + @IProductService private readonly productService: IProductService, ) { super(); @@ -70,9 +72,15 @@ export class DefaultExtensionsInitializer extends Disposable { } private getDefaultExtensionVSIXsLocation(): URI { - // appRoot = C:\Users\\AppData\Local\Programs\Microsoft VS Code Insiders\resources\app - // extensionsPath = C:\Users\\AppData\Local\Programs\Microsoft VS Code Insiders\bootstrap\extensions - return URI.file(join(dirname(dirname(this.environmentService.appRoot)), 'bootstrap', 'extensions')); + if (this.productService.quality === 'insider') { + // appRoot = C:\Users\\AppData\Local\Programs\Microsoft VS Code Insiders\\resources\app + // extensionsPath = C:\Users\\AppData\Local\Programs\Microsoft VS Code Insiders\\bootstrap\extensions + return URI.file(join(dirname(dirname(dirname(this.environmentService.appRoot))), 'bootstrap', 'extensions')); + } else { + // appRoot = C:\Users\\AppData\Local\Programs\Microsoft VS Code Insiders\resources\app + // extensionsPath = C:\Users\\AppData\Local\Programs\Microsoft VS Code Insiders\bootstrap\extensions + return URI.file(join(dirname(dirname(this.environmentService.appRoot)), 'bootstrap', 'extensions')); + } } } diff --git a/code/src/vs/code/node/cli.ts b/code/src/vs/code/node/cli.ts index b31099ef0fb..b3bdca721dd 100644 --- a/code/src/vs/code/node/cli.ts +++ b/code/src/vs/code/node/cli.ts @@ -5,7 +5,7 @@ import { ChildProcess, spawn, SpawnOptions, StdioOptions } from 'child_process'; import { chmodSync, existsSync, readFileSync, statSync, truncateSync, unlinkSync } from 'fs'; -import { homedir, release, tmpdir } from 'os'; +import { homedir, tmpdir } from 'os'; import type { ProfilingSession, Target } from 'v8-inspect-profiler'; import { Event } from '../../base/common/event.js'; import { isAbsolute, resolve, join, dirname } from '../../base/common/path.js'; @@ -40,7 +40,7 @@ function shouldSpawnCliProcess(argv: NativeParsedArgs): boolean { || !!argv['telemetry']; } -export async function main(argv: string[]): Promise { +export async function main(argv: string[]): Promise { let args: NativeParsedArgs; try { @@ -183,9 +183,9 @@ export async function main(argv: string[]): Promise { try { // Check for readonly status and chmod if so if we are told so - let targetMode: number = 0; + let targetMode = 0; let restoreMode = false; - if (!!args['file-chmod']) { + if (args['file-chmod']) { targetMode = statSync(target).mode; if (!(targetMode & 0o200 /* File mode indicating writable by owner */)) { chmodSync(target, targetMode | 0o200); @@ -320,8 +320,6 @@ export async function main(argv: string[]): Promise { } } - const isMacOSBigSurOrNewer = isMacintosh && release() > '20.0.0'; - // If we are started with --wait create a random temporary file // and pass it over to the starting instance. We can use this file // to wait for it to be deleted to monitor that the edited file @@ -339,8 +337,8 @@ export async function main(argv: string[]): Promise { // - the launched process terminates (e.g. due to a crash) processCallbacks.push(async child => { let childExitPromise; - if (isMacOSBigSurOrNewer) { - // On Big Sur, we resolve the following promise only when the child, + if (isMacintosh) { + // On macOS, we resolve the following promise only when the child, // i.e. the open command, exited with a signal or error. Otherwise, we // wait for the marker file to be deleted or for the child to error. childExitPromise = new Promise(resolve => { @@ -482,15 +480,14 @@ export async function main(argv: string[]): Promise { } let child: ChildProcess; - if (!isMacOSBigSurOrNewer) { + if (!isMacintosh) { if (!args.verbose && args.status) { options['stdio'] = ['ignore', 'pipe', 'ignore']; // restore ability to see output when --status is used } - // We spawn process.execPath directly child = spawn(process.execPath, argv.slice(2), options); } else { - // On Big Sur, we spawn using the open command to obtain behavior + // On macOS, we spawn using the open command to obtain behavior // similar to if the app was launched from the dock // https://github.com/microsoft/vscode/issues/102975 @@ -567,7 +564,7 @@ export async function main(argv: string[]): Promise { child = spawn('open', spawnArgs, { ...options, env: {} }); } - return Promise.all(processCallbacks.map(callback => callback(child))); + await Promise.all(processCallbacks.map(callback => callback(child))); } } diff --git a/code/src/vs/code/node/cliProcessMain.ts b/code/src/vs/code/node/cliProcessMain.ts index 1ad692cfcc2..e0a2115ade8 100644 --- a/code/src/vs/code/node/cliProcessMain.ts +++ b/code/src/vs/code/node/cliProcessMain.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { setDefaultResultOrder } from 'dns'; import * as fs from 'fs'; import { hostname, release } from 'os'; import { raceTimeout } from '../../base/common/async.js'; @@ -11,7 +12,7 @@ import { isSigPipeError, onUnexpectedError, setUnexpectedErrorHandler } from '.. import { Disposable } from '../../base/common/lifecycle.js'; import { Schemas } from '../../base/common/network.js'; import { isAbsolute, join } from '../../base/common/path.js'; -import { isWindows, isMacintosh } from '../../base/common/platform.js'; +import { isWindows, isMacintosh, isLinux } from '../../base/common/platform.js'; import { cwd } from '../../base/common/process.js'; import { URI } from '../../base/common/uri.js'; import { IConfigurationService } from '../../platform/configuration/common/configuration.js'; @@ -75,6 +76,7 @@ import { McpGalleryService } from '../../platform/mcp/common/mcpGalleryService.j import { AllowedMcpServersService } from '../../platform/mcp/common/allowedMcpServersService.js'; import { IMcpGalleryManifestService } from '../../platform/mcp/common/mcpGalleryManifest.js'; import { McpGalleryManifestService } from '../../platform/mcp/common/mcpGalleryManifestService.js'; +import { LINUX_SYSTEM_POLICY_FILE_PATH } from '../../base/common/policy.js'; class CliMain extends Disposable { @@ -109,6 +111,10 @@ class CliMain extends Disposable { // Error handler this.registerErrorHandler(logService); + // DNS result order + // Refs https://github.com/microsoft/vscode/issues/264136 + setDefaultResultOrder('ipv4first'); + // Run based on argv await this.doRun(environmentService, fileService, userDataProfilesService, instantiationService); @@ -177,6 +183,8 @@ class CliMain extends Disposable { policyService = this._register(new NativePolicyService(logService, productService.win32RegValueName)); } else if (isMacintosh && productService.darwinBundleIdentifier) { policyService = this._register(new NativePolicyService(logService, productService.darwinBundleIdentifier)); + } else if (isLinux) { + policyService = this._register(new FilePolicyService(URI.file(LINUX_SYSTEM_POLICY_FILE_PATH), fileService, logService)); } else if (environmentService.policyFile) { policyService = this._register(new FilePolicyService(environmentService.policyFile, fileService, logService)); } else { @@ -242,7 +250,7 @@ class CliMain extends Disposable { const appenders: ITelemetryAppender[] = []; const isInternal = isInternalTelemetry(productService, configurationService); if (supportsTelemetry(productService, environmentService)) { - if (productService.aiConfig && productService.aiConfig.ariaKey) { + if (productService.aiConfig?.ariaKey) { appenders.push(new OneDataSystemAppender(requestService, isInternal, 'monacoworkbench', null, productService.aiConfig.ariaKey)); } diff --git a/code/src/vs/editor/browser/config/editorConfiguration.ts b/code/src/vs/editor/browser/config/editorConfiguration.ts index 8b936dd220c..8196bcd7174 100644 --- a/code/src/vs/editor/browser/config/editorConfiguration.ts +++ b/code/src/vs/editor/browser/config/editorConfiguration.ts @@ -16,6 +16,7 @@ import { TabFocus } from './tabFocus.js'; import { ComputeOptionsMemory, ConfigurationChangedEvent, EditorOption, editorOptionsRegistry, FindComputedEditorOptionValueById, IComputedEditorOptions, IEditorOptions, IEnvironmentalOptions } from '../../common/config/editorOptions.js'; import { EditorZoom } from '../../common/config/editorZoom.js'; import { BareFontInfo, FontInfo, IValidatedEditorOptions } from '../../common/config/fontInfo.js'; +import { createBareFontInfoFromValidatedSettings } from '../../common/config/fontInfoFromSettings.js'; import { IDimension } from '../../common/core/2d/dimension.js'; import { IEditorConfiguration } from '../../common/config/editorConfiguration.js'; import { AccessibilitySupport, IAccessibilityService } from '../../../platform/accessibility/common/accessibility.js'; @@ -114,7 +115,7 @@ export class EditorConfiguration extends Disposable implements IEditorConfigurat private _computeOptions(): ComputedEditorOptions { const partialEnv = this._readEnvConfiguration(); - const bareFontInfo = BareFontInfo.createFromValidatedSettings(this._validatedOptions, partialEnv.pixelRatio, this.isSimpleWidget); + const bareFontInfo = createBareFontInfoFromValidatedSettings(this._validatedOptions, partialEnv.pixelRatio, this.isSimpleWidget); const fontInfo = this._readFontInfo(bareFontInfo); const env: IEnvironmentalOptions = { memory: this._computeOptionsMemory, @@ -143,6 +144,7 @@ export class EditorConfiguration extends Disposable implements IEditorConfigurat outerHeight: this._containerObserver.getHeight(), emptySelectionClipboard: browser.isWebKit || browser.isFirefox, pixelRatio: PixelRatio.getInstance(getWindowById(this._targetWindowId, true).window).value, + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any editContextSupported: typeof (globalThis as any).EditContext === 'function', accessibilitySupport: ( this._accessibilityService.isScreenReaderOptimized() @@ -254,12 +256,12 @@ export interface IEnvConfiguration { } class ValidatedEditorOptions implements IValidatedEditorOptions { - private readonly _values: any[] = []; + private readonly _values: unknown[] = []; public _read(option: EditorOption): T { - return this._values[option]; + return this._values[option] as T; } public get(id: T): FindComputedEditorOptionValueById { - return this._values[id]; + return this._values[id] as FindComputedEditorOptionValueById; } public _write(option: EditorOption, value: T): void { this._values[option] = value; @@ -267,12 +269,12 @@ class ValidatedEditorOptions implements IValidatedEditorOptions { } export class ComputedEditorOptions implements IComputedEditorOptions { - private readonly _values: any[] = []; + private readonly _values: unknown[] = []; public _read(id: EditorOption): T { if (id >= this._values.length) { throw new Error('Cannot read uninitialized value'); } - return this._values[id]; + return this._values[id] as T; } public get(id: T): FindComputedEditorOptionValueById { return this._read(id); @@ -287,7 +289,7 @@ class EditorOptionsUtil { public static validateOptions(options: IEditorOptions): ValidatedEditorOptions { const result = new ValidatedEditorOptions(); for (const editorOption of editorOptionsRegistry) { - const value = (editorOption.name === '_never_' ? undefined : (options as any)[editorOption.name]); + const value = (editorOption.name === '_never_' ? undefined : (options as Record)[editorOption.name]); result._write(editorOption.id, editorOption.validate(value)); } return result; @@ -340,8 +342,8 @@ class EditorOptionsUtil { let changed = false; for (const editorOption of editorOptionsRegistry) { if (update.hasOwnProperty(editorOption.name)) { - const result = editorOption.applyUpdate((options as any)[editorOption.name], (update as any)[editorOption.name]); - (options as any)[editorOption.name] = result.newValue; + const result = editorOption.applyUpdate((options as Record)[editorOption.name], (update as Record)[editorOption.name]); + (options as Record)[editorOption.name] = result.newValue; changed = changed || result.didChange; } } diff --git a/code/src/vs/editor/browser/config/elementSizeObserver.ts b/code/src/vs/editor/browser/config/elementSizeObserver.ts index f6c6ac5e926..c1f886e2c35 100644 --- a/code/src/vs/editor/browser/config/elementSizeObserver.ts +++ b/code/src/vs/editor/browser/config/elementSizeObserver.ts @@ -47,10 +47,10 @@ export class ElementSizeObserver extends Disposable { // Otherwise we will postpone to the next animation frame. // We'll use `observeContentRect` to store the content rect we received. - let observedDimenstion: IDimension | null = null; + let observedDimension: IDimension | null = null; const observeNow = () => { - if (observedDimenstion) { - this.observe({ width: observedDimenstion.width, height: observedDimenstion.height }); + if (observedDimension) { + this.observe({ width: observedDimension.width, height: observedDimension.height }); } else { this.observe(); } @@ -76,9 +76,9 @@ export class ElementSizeObserver extends Disposable { this._resizeObserver = new ResizeObserver((entries) => { if (entries && entries[0] && entries[0].contentRect) { - observedDimenstion = { width: entries[0].contentRect.width, height: entries[0].contentRect.height }; + observedDimension = { width: entries[0].contentRect.width, height: entries[0].contentRect.height }; } else { - observedDimenstion = null; + observedDimension = null; } shouldObserve = true; update(); diff --git a/code/src/vs/editor/browser/config/fontMeasurements.ts b/code/src/vs/editor/browser/config/fontMeasurements.ts index d9a5cb897d9..759add4ccc8 100644 --- a/code/src/vs/editor/browser/config/fontMeasurements.ts +++ b/code/src/vs/editor/browser/config/fontMeasurements.ts @@ -271,7 +271,7 @@ class FontMeasurementsCache { this._values[itemId] = value; } - public remove(item: BareFontInfo): void { + public remove(item: FontInfo): void { const itemId = item.getId(); delete this._keys[itemId]; delete this._values[itemId]; diff --git a/code/src/vs/editor/browser/config/migrateOptions.ts b/code/src/vs/editor/browser/config/migrateOptions.ts index 3664d611ec4..5ecd03e14a0 100644 --- a/code/src/vs/editor/browser/config/migrateOptions.ts +++ b/code/src/vs/editor/browser/config/migrateOptions.ts @@ -6,11 +6,11 @@ import { IEditorOptions } from '../../common/config/editorOptions.js'; export interface ISettingsReader { - (key: string): any; + (key: string): unknown; } export interface ISettingsWriter { - (key: string, value: any): void; + (key: string, value: unknown): void; } export class EditorSettingMigration { @@ -19,46 +19,46 @@ export class EditorSettingMigration { constructor( public readonly key: string, - public readonly migrate: (value: any, read: ISettingsReader, write: ISettingsWriter) => void + public readonly migrate: (value: unknown, read: ISettingsReader, write: ISettingsWriter) => void ) { } - apply(options: any): void { + apply(options: unknown): void { const value = EditorSettingMigration._read(options, this.key); const read = (key: string) => EditorSettingMigration._read(options, key); - const write = (key: string, value: any) => EditorSettingMigration._write(options, key, value); + const write = (key: string, value: unknown) => EditorSettingMigration._write(options, key, value); this.migrate(value, read, write); } - private static _read(source: any, key: string): any { - if (typeof source === 'undefined') { + private static _read(source: unknown, key: string): unknown { + if (typeof source === 'undefined' || source === null) { return undefined; } const firstDotIndex = key.indexOf('.'); if (firstDotIndex >= 0) { const firstSegment = key.substring(0, firstDotIndex); - return this._read(source[firstSegment], key.substring(firstDotIndex + 1)); + return this._read((source as Record)[firstSegment], key.substring(firstDotIndex + 1)); } - return source[key]; + return (source as Record)[key]; } - private static _write(target: any, key: string, value: any): void { + private static _write(target: unknown, key: string, value: unknown): void { const firstDotIndex = key.indexOf('.'); if (firstDotIndex >= 0) { const firstSegment = key.substring(0, firstDotIndex); - target[firstSegment] = target[firstSegment] || {}; - this._write(target[firstSegment], key.substring(firstDotIndex + 1), value); + (target as Record)[firstSegment] = (target as Record)[firstSegment] || {}; + this._write((target as Record)[firstSegment], key.substring(firstDotIndex + 1), value); return; } - target[key] = value; + (target as Record)[key] = value; } } -function registerEditorSettingMigration(key: string, migrate: (value: any, read: ISettingsReader, write: ISettingsWriter) => void): void { +function registerEditorSettingMigration(key: string, migrate: (value: unknown, read: ISettingsReader, write: ISettingsWriter) => void): void { EditorSettingMigration.items.push(new EditorSettingMigration(key, migrate)); } -function registerSimpleEditorSettingMigration(key: string, values: [any, any][]): void { +function registerSimpleEditorSettingMigration(key: string, values: [unknown, unknown][]): void { registerEditorSettingMigration(key, (value, read, write) => { if (typeof value !== 'undefined') { for (const [oldValue, newValue] of values) { @@ -158,7 +158,7 @@ const suggestFilteredTypesMapping: Record = { registerEditorSettingMigration('suggest.filteredTypes', (value, read, write) => { if (value && typeof value === 'object') { for (const entry of Object.entries(suggestFilteredTypesMapping)) { - const v = value[entry[0]]; + const v = (value as Record)[entry[0]]; if (v === false) { if (typeof read(`suggest.${entry[1]}`) === 'undefined') { write(`suggest.${entry[1]}`, false); @@ -212,7 +212,7 @@ registerEditorSettingMigration('editor.experimentalEditContextEnabled', (value, registerEditorSettingMigration('codeActionsOnSave', (value, read, write) => { if (value && typeof value === 'object') { let toBeModified = false; - const newValue = {} as any; + const newValue: Record = {}; for (const entry of Object.entries(value)) { if (typeof entry[1] === 'boolean') { toBeModified = true; @@ -251,3 +251,10 @@ registerEditorSettingMigration('inlineSuggest.edits.codeShifting', (value, read, write('inlineSuggest.edits.allowCodeShifting', value ? 'always' : 'never'); } }); + +// Migrate Hover +registerEditorSettingMigration('hover.enabled', (value, read, write) => { + if (typeof value === 'boolean') { + write('hover.enabled', value ? 'on' : 'off'); + } +}); diff --git a/code/src/vs/editor/browser/config/tabFocus.ts b/code/src/vs/editor/browser/config/tabFocus.ts index 6d821bc2725..4cf0b237248 100644 --- a/code/src/vs/editor/browser/config/tabFocus.ts +++ b/code/src/vs/editor/browser/config/tabFocus.ts @@ -4,10 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from '../../../base/common/event.js'; +import { Disposable } from '../../../base/common/lifecycle.js'; -class TabFocusImpl { +class TabFocusImpl extends Disposable { private _tabFocus: boolean = false; - private readonly _onDidChangeTabFocus = new Emitter(); + private readonly _onDidChangeTabFocus = this._register(new Emitter()); public readonly onDidChangeTabFocus: Event = this._onDidChangeTabFocus.event; public getTabFocusMode(): boolean { diff --git a/code/src/vs/editor/browser/controller/dragScrolling.ts b/code/src/vs/editor/browser/controller/dragScrolling.ts index 830d4b5ec5d..bba8a03f777 100644 --- a/code/src/vs/editor/browser/controller/dragScrolling.ts +++ b/code/src/vs/editor/browser/controller/dragScrolling.ts @@ -134,6 +134,7 @@ export class TopBottomDragScrollingOperation extends DragScrollingOperation { const viewportData = this._context.viewLayout.getLinesViewportData(); const edgeLineNumber = (this._position.outsidePosition === 'above' ? viewportData.startLineNumber : viewportData.endLineNumber); + const cannotScrollAnymore = (this._position.outsidePosition === 'above' ? viewportData.startLineNumber === 1 : viewportData.endLineNumber === this._context.viewModel.getLineCount()); // First, try to find a position that matches the horizontal position of the mouse let mouseTarget: IMouseTarget; @@ -144,7 +145,7 @@ export class TopBottomDragScrollingOperation extends DragScrollingOperation { const relativePos = createCoordinatesRelativeToEditor(this._viewHelper.viewDomNode, editorPos, pos); mouseTarget = this._mouseTargetFactory.createMouseTarget(this._viewHelper.getLastRenderData(), editorPos, pos, relativePos, null); } - if (!mouseTarget.position || mouseTarget.position.lineNumber !== edgeLineNumber) { + if (!mouseTarget.position || mouseTarget.position.lineNumber !== edgeLineNumber || cannotScrollAnymore) { if (this._position.outsidePosition === 'above') { mouseTarget = MouseTarget.createOutsideEditor(this._position.mouseColumn, new Position(edgeLineNumber, 1), 'above', this._position.outsideDistance); } else { diff --git a/code/src/vs/editor/browser/controller/editContext/clipboardUtils.ts b/code/src/vs/editor/browser/controller/editContext/clipboardUtils.ts index 66c01da1d36..09ca8745315 100644 --- a/code/src/vs/editor/browser/controller/editContext/clipboardUtils.ts +++ b/code/src/vs/editor/browser/controller/editContext/clipboardUtils.ts @@ -6,8 +6,57 @@ import { IViewModel } from '../../../common/viewModel.js'; import { Range } from '../../../common/core/range.js'; import { isWindows } from '../../../../base/common/platform.js'; import { Mimes } from '../../../../base/common/mime.js'; +import { ViewContext } from '../../../common/viewModel/viewContext.js'; +import { ILogService, LogLevel } from '../../../../platform/log/common/log.js'; +import { EditorOption, IComputedEditorOptions } from '../../../common/config/editorOptions.js'; +import { generateUuid } from '../../../../base/common/uuid.js'; -export function getDataToCopy(viewModel: IViewModel, modelSelections: Range[], emptySelectionClipboard: boolean, copyWithSyntaxHighlighting: boolean): ClipboardDataToCopy { +export function ensureClipboardGetsEditorSelection(e: ClipboardEvent, context: ViewContext, logService: ILogService, isFirefox: boolean): void { + const viewModel = context.viewModel; + const options = context.configuration.options; + let id: string | undefined = undefined; + if (logService.getLevel() === LogLevel.Trace) { + id = generateUuid(); + } + + const { dataToCopy, storedMetadata } = generateDataToCopyAndStoreInMemory(viewModel, options, id, isFirefox); + + // !!!!! + // This is a workaround for what we think is an Electron bug where + // execCommand('copy') does not always work (it does not fire a clipboard event) + // !!!!! + // We signal that we have executed a copy command + CopyOptions.electronBugWorkaroundCopyEventHasFired = true; + + e.preventDefault(); + if (e.clipboardData) { + ClipboardEventUtils.setTextData(e.clipboardData, dataToCopy.text, dataToCopy.html, storedMetadata); + } + logService.trace('ensureClipboardGetsEditorSelection with id : ', id, ' with text.length: ', dataToCopy.text.length); +} + +export function generateDataToCopyAndStoreInMemory(viewModel: IViewModel, options: IComputedEditorOptions, id: string | undefined, isFirefox: boolean) { + const emptySelectionClipboard = options.get(EditorOption.emptySelectionClipboard); + const copyWithSyntaxHighlighting = options.get(EditorOption.copyWithSyntaxHighlighting); + const selections = viewModel.getCursorStates().map(cursorState => cursorState.modelState.selection); + const dataToCopy = getDataToCopy(viewModel, selections, emptySelectionClipboard, copyWithSyntaxHighlighting); + const storedMetadata: ClipboardStoredMetadata = { + version: 1, + id, + isFromEmptySelection: dataToCopy.isFromEmptySelection, + multicursorText: dataToCopy.multicursorText, + mode: dataToCopy.mode + }; + InMemoryClipboardMetadataManager.INSTANCE.set( + // When writing "LINE\r\n" to the clipboard and then pasting, + // Firefox pastes "LINE\n", so let's work around this quirk + (isFirefox ? dataToCopy.text.replace(/\r\n/g, '\n') : dataToCopy.text), + storedMetadata + ); + return { dataToCopy, storedMetadata }; +} + +function getDataToCopy(viewModel: IViewModel, modelSelections: Range[], emptySelectionClipboard: boolean, copyWithSyntaxHighlighting: boolean): ClipboardDataToCopy { const rawTextToCopy = viewModel.getPlainTextToCopy(modelSelections, emptySelectionClipboard, isWindows); const newLineCharacter = viewModel.model.getEOL(); @@ -79,7 +128,8 @@ export interface ClipboardStoredMetadata { } export const CopyOptions = { - forceCopyWithSyntaxHighlighting: false + forceCopyWithSyntaxHighlighting: false, + electronBugWorkaroundCopyEventHasFired: false }; interface InMemoryClipboardMetadata { diff --git a/code/src/vs/editor/browser/controller/editContext/native/debugEditContext.ts b/code/src/vs/editor/browser/controller/editContext/native/debugEditContext.ts index 5fa3d47cc1b..a04ad8ed1ca 100644 --- a/code/src/vs/editor/browser/controller/editContext/native/debugEditContext.ts +++ b/code/src/vs/editor/browser/controller/editContext/native/debugEditContext.ts @@ -88,7 +88,7 @@ export class DebugEditContext { private readonly _listenerMap = new Map(); - addEventListener(type: K, listener: (this: GlobalEventHandlers, ev: EditContextEventHandlersEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: GlobalEventHandlers, ev: EditContextEventHandlersEventMap[K]) => void, options?: boolean | AddEventListenerOptions): void; addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void { if (!listener) { return; } diff --git a/code/src/vs/editor/browser/controller/editContext/native/editContextFactory.ts b/code/src/vs/editor/browser/controller/editContext/native/editContextFactory.ts index 84b9cc52df3..51d258c9e13 100644 --- a/code/src/vs/editor/browser/controller/editContext/native/editContextFactory.ts +++ b/code/src/vs/editor/browser/controller/editContext/native/editContextFactory.ts @@ -10,6 +10,6 @@ export namespace EditContext { * Create an edit context. */ export function create(window: Window, options?: EditContextInit): EditContext { - return new (window as any).EditContext(options); + return new (window as unknown as { EditContext: new (options?: EditContextInit) => EditContext }).EditContext(options); } } diff --git a/code/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts b/code/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts index 5d10e6e9f8a..6334e8bdfe1 100644 --- a/code/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts +++ b/code/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts @@ -14,9 +14,9 @@ import { EditorOption } from '../../../../common/config/editorOptions.js'; import { EndOfLinePreference, IModelDeltaDecoration } from '../../../../common/model.js'; import { ViewConfigurationChangedEvent, ViewCursorStateChangedEvent, ViewDecorationsChangedEvent, ViewFlushedEvent, ViewLinesChangedEvent, ViewLinesDeletedEvent, ViewLinesInsertedEvent, ViewScrollChangedEvent, ViewZonesChangedEvent } from '../../../../common/viewEvents.js'; import { ViewContext } from '../../../../common/viewModel/viewContext.js'; -import { RestrictedRenderingContext, RenderingContext } from '../../../view/renderingContext.js'; +import { RestrictedRenderingContext, RenderingContext, HorizontalPosition } from '../../../view/renderingContext.js'; import { ViewController } from '../../../view/viewController.js'; -import { ClipboardEventUtils, ClipboardStoredMetadata, getDataToCopy, InMemoryClipboardMetadataManager } from '../clipboardUtils.js'; +import { ClipboardEventUtils, ensureClipboardGetsEditorSelection, InMemoryClipboardMetadataManager } from '../clipboardUtils.js'; import { AbstractEditContext } from '../editContext.js'; import { editContextAddDisposableListener, FocusTracker, ITypeData } from './nativeEditContextUtils.js'; import { ScreenReaderSupport } from './screenReaderSupport.js'; @@ -31,8 +31,8 @@ import { IEditorAriaOptions } from '../../../editorBrowser.js'; import { isHighSurrogate, isLowSurrogate } from '../../../../../base/common/strings.js'; import { IME } from '../../../../../base/common/ime.js'; import { OffsetRange } from '../../../../common/core/ranges/offsetRange.js'; -import { ILogService, LogLevel } from '../../../../../platform/log/common/log.js'; -import { generateUuid } from '../../../../../base/common/uuid.js'; +import { ILogService } from '../../../../../platform/log/common/log.js'; +import { inputLatency } from '../../../../../base/browser/performance.js'; // Corresponds to classes in nativeEditContext.css enum CompositionClassName { @@ -60,7 +60,7 @@ export class NativeEditContext extends AbstractEditContext { private _editContextPrimarySelection: Selection = new Selection(1, 1, 1, 1); // Overflow guard container - private _parent: HTMLElement | undefined; + private readonly _parent: HTMLElement; private _decorations: string[] = []; private _primarySelection: Selection = new Selection(1, 1, 1, 1); @@ -114,23 +114,27 @@ export class NativeEditContext extends AbstractEditContext { this._register(addDisposableListener(this.domNode.domNode, 'copy', (e) => { this.logService.trace('NativeEditContext#copy'); - this._ensureClipboardGetsEditorSelection(e); + ensureClipboardGetsEditorSelection(e, this._context, this.logService, isFirefox); })); this._register(addDisposableListener(this.domNode.domNode, 'cut', (e) => { this.logService.trace('NativeEditContext#cut'); // Pretend here we touched the text area, as the `cut` event will most likely // result in a `selectionchange` event which we want to ignore this._screenReaderSupport.onWillCut(); - this._ensureClipboardGetsEditorSelection(e); + ensureClipboardGetsEditorSelection(e, this._context, this.logService, isFirefox); this.logService.trace('NativeEditContext#cut (before viewController.cut)'); this._viewController.cut(); })); + this._register(addDisposableListener(this.domNode.domNode, 'selectionchange', () => { + inputLatency.onSelectionChange(); + })); this._register(addDisposableListener(this.domNode.domNode, 'keyup', (e) => this._onKeyUp(e))); this._register(addDisposableListener(this.domNode.domNode, 'keydown', async (e) => this._onKeyDown(e))); this._register(addDisposableListener(this._imeTextArea.domNode, 'keyup', (e) => this._onKeyUp(e))); this._register(addDisposableListener(this._imeTextArea.domNode, 'keydown', async (e) => this._onKeyDown(e))); this._register(addDisposableListener(this.domNode.domNode, 'beforeinput', async (e) => { + inputLatency.onBeforeInput(); if (e.inputType === 'insertParagraph' || e.inputType === 'insertLineBreak') { this._onType(this._viewController, { text: '\n', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }); } @@ -166,6 +170,7 @@ export class NativeEditContext extends AbstractEditContext { this._register(editContextAddDisposableListener(this._editContext, 'characterboundsupdate', (e) => this._updateCharacterBounds(e))); let highSurrogateCharacter: string | undefined; this._register(editContextAddDisposableListener(this._editContext, 'textupdate', (e) => { + inputLatency.onInput(); const text = e.text; if (text.length === 1) { const charCode = text.charCodeAt(0); @@ -241,9 +246,13 @@ export class NativeEditContext extends AbstractEditContext { return this._primarySelection.getPosition(); } - public prepareRender(ctx: RenderingContext): void { + public override prepareRender(ctx: RenderingContext): void { this._screenReaderSupport.prepareRender(ctx); - this._updateSelectionAndControlBounds(ctx); + this._updateSelectionAndControlBoundsData(ctx); + } + + public override onDidRender(): void { + this._updateSelectionAndControlBoundsAfterRender(); } public render(ctx: RestrictedRenderingContext): void { @@ -351,10 +360,12 @@ export class NativeEditContext extends AbstractEditContext { // --- Private methods --- private _onKeyUp(e: KeyboardEvent) { + inputLatency.onKeyUp(); this._viewController.emitKeyUp(new StandardKeyboardEvent(e)); } private _onKeyDown(e: KeyboardEvent) { + inputLatency.onKeyDown(); const standardKeyboardEvent = new StandardKeyboardEvent(e); // When the IME is visible, the keys, like arrow-left and arrow-right, should be used to navigate in the IME, and should not be propagated further if (standardKeyboardEvent.keyCode === KeyCode.KEY_IN_COMPOSITION) { @@ -483,26 +494,35 @@ export class NativeEditContext extends AbstractEditContext { this._decorations = this._context.viewModel.model.deltaDecorations(this._decorations, decorations); } - private _updateSelectionAndControlBounds(ctx: RenderingContext) { - if (!this._parent) { - return; + private _linesVisibleRanges: HorizontalPosition | null = null; + private _updateSelectionAndControlBoundsData(ctx: RenderingContext): void { + const viewSelection = this._context.viewModel.coordinatesConverter.convertModelRangeToViewRange(this._primarySelection); + if (this._primarySelection.isEmpty()) { + const linesVisibleRanges = ctx.visibleRangeForPosition(viewSelection.getStartPosition()); + this._linesVisibleRanges = linesVisibleRanges; + } else { + this._linesVisibleRanges = null; } + } + + private _updateSelectionAndControlBoundsAfterRender() { const options = this._context.configuration.options; const contentLeft = options.get(EditorOption.layoutInfo).contentLeft; - const parentBounds = this._parent.getBoundingClientRect(); + const viewSelection = this._context.viewModel.coordinatesConverter.convertModelRangeToViewRange(this._primarySelection); const verticalOffsetStart = this._context.viewLayout.getVerticalOffsetForLineNumber(viewSelection.startLineNumber); + const verticalOffsetEnd = this._context.viewLayout.getVerticalOffsetAfterLineNumber(viewSelection.endLineNumber); + // Make sure this doesn't force an extra layout (i.e. don't call it before rendering finished) + const parentBounds = this._parent.getBoundingClientRect(); const top = parentBounds.top + verticalOffsetStart - this._scrollTop; - const verticalOffsetEnd = this._context.viewLayout.getVerticalOffsetAfterLineNumber(viewSelection.endLineNumber); const height = verticalOffsetEnd - verticalOffsetStart; let left = parentBounds.left + contentLeft - this._scrollLeft; let width: number; if (this._primarySelection.isEmpty()) { - const linesVisibleRanges = ctx.visibleRangeForPosition(viewSelection.getStartPosition()); - if (linesVisibleRanges) { - left += linesVisibleRanges.left; + if (this._linesVisibleRanges) { + left += this._linesVisibleRanges.left; } width = 0; } else { @@ -515,9 +535,6 @@ export class NativeEditContext extends AbstractEditContext { } private _updateCharacterBounds(e: CharacterBoundsUpdateEvent): void { - if (!this._parent) { - return; - } const options = this._context.configuration.options; const typicalHalfWidthCharacterWidth = options.get(EditorOption.fontInfo).typicalHalfwidthCharacterWidth; const contentLeft = options.get(EditorOption.layoutInfo).contentLeft; @@ -551,34 +568,4 @@ export class NativeEditContext extends AbstractEditContext { } this._editContext.updateCharacterBounds(e.rangeStart, characterBounds); } - - private _ensureClipboardGetsEditorSelection(e: ClipboardEvent): void { - const options = this._context.configuration.options; - const emptySelectionClipboard = options.get(EditorOption.emptySelectionClipboard); - const copyWithSyntaxHighlighting = options.get(EditorOption.copyWithSyntaxHighlighting); - const selections = this._context.viewModel.getCursorStates().map(cursorState => cursorState.modelState.selection); - const dataToCopy = getDataToCopy(this._context.viewModel, selections, emptySelectionClipboard, copyWithSyntaxHighlighting); - let id = undefined; - if (this.logService.getLevel() === LogLevel.Trace) { - id = generateUuid(); - } - const storedMetadata: ClipboardStoredMetadata = { - version: 1, - id, - isFromEmptySelection: dataToCopy.isFromEmptySelection, - multicursorText: dataToCopy.multicursorText, - mode: dataToCopy.mode - }; - InMemoryClipboardMetadataManager.INSTANCE.set( - // When writing "LINE\r\n" to the clipboard and then pasting, - // Firefox pastes "LINE\n", so let's work around this quirk - (isFirefox ? dataToCopy.text.replace(/\r\n/g, '\n') : dataToCopy.text), - storedMetadata - ); - e.preventDefault(); - if (e.clipboardData) { - ClipboardEventUtils.setTextData(e.clipboardData, dataToCopy.text, dataToCopy.html, storedMetadata); - } - this.logService.trace('NativeEditContext#_ensureClipboardGetsEditorSelectios with id : ', id, ' with text.length: ', dataToCopy.text.length); - } } diff --git a/code/src/vs/editor/browser/controller/editContext/native/nativeEditContextUtils.ts b/code/src/vs/editor/browser/controller/editContext/native/nativeEditContextUtils.ts index b9504d9082a..86436a376ec 100644 --- a/code/src/vs/editor/browser/controller/editContext/native/nativeEditContextUtils.ts +++ b/code/src/vs/editor/browser/controller/editContext/native/nativeEditContextUtils.ts @@ -77,10 +77,12 @@ export class FocusTracker extends Disposable { } } -export function editContextAddDisposableListener(target: EventTarget, type: K, listener: (this: GlobalEventHandlers, ev: EditContextEventHandlersEventMap[K]) => any, options?: boolean | AddEventListenerOptions): IDisposable { +export function editContextAddDisposableListener(target: EventTarget, type: K, listener: (this: GlobalEventHandlers, ev: EditContextEventHandlersEventMap[K]) => void, options?: boolean | AddEventListenerOptions): IDisposable { + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any target.addEventListener(type, listener as any, options); return { dispose() { + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any target.removeEventListener(type, listener as any); } }; diff --git a/code/src/vs/editor/browser/controller/editContext/native/screenReaderContentRich.ts b/code/src/vs/editor/browser/controller/editContext/native/screenReaderContentRich.ts index a79bb3480dc..7a274e27f3b 100644 --- a/code/src/vs/editor/browser/controller/editContext/native/screenReaderContentRich.ts +++ b/code/src/vs/editor/browser/controller/editContext/native/screenReaderContentRich.ts @@ -33,7 +33,7 @@ export class RichScreenReaderContent extends Disposable implements IScreenReader private _accessibilityPageSize: number = 1; private _ignoreSelectionChangeTime: number = 0; - private _state: RichScreenReaderState = new RichScreenReaderState([]); + private _state: RichScreenReaderState = RichScreenReaderState.NULL; private _strategy: RichPagedScreenReaderStrategy = new RichPagedScreenReaderStrategy(); private _renderedLines: Map = new Map(); @@ -66,7 +66,7 @@ export class RichScreenReaderContent extends Disposable implements IScreenReader this._setSelectionOnScreenReaderContent(this._context, this._renderedLines, primarySelection); } } else { - this._state = new RichScreenReaderState([]); + this._state = RichScreenReaderState.NULL; this._setIgnoreSelectionChangeTime('setValue'); this._domNode.domNode.textContent = ''; } @@ -341,18 +341,32 @@ class LineInterval { class RichScreenReaderState { - constructor(public readonly intervals: LineInterval[]) { } + public readonly value: string; - equals(other: RichScreenReaderState): boolean { - if (this.intervals.length !== other.intervals.length) { - return false; - } - for (let i = 0; i < this.intervals.length; i++) { - if (this.intervals[i].startLine !== other.intervals[i].startLine || this.intervals[i].endLine !== other.intervals[i].endLine) { - return false; + constructor(model: ISimpleModel, public readonly intervals: LineInterval[]) { + let value = ''; + for (const interval of intervals) { + for (let lineNumber = interval.startLine; lineNumber <= interval.endLine; lineNumber++) { + value += model.getLineContent(lineNumber) + '\n'; } } - return true; + this.value = value; + } + + equals(other: RichScreenReaderState): boolean { + return this.value === other.value; + } + + static get NULL(): RichScreenReaderState { + const nullModel: ISimpleModel = { + getLineContent: () => '', + getLineCount: () => 1, + getLineMaxColumn: () => 1, + getValueInRange: () => '', + getValueLengthInRange: () => 0, + modifyPosition: (position, offset) => position + }; + return new RichScreenReaderState(nullModel, []); } } @@ -380,6 +394,6 @@ class RichPagedScreenReaderStrategy implements IPagedScreenReaderStrategy, viewController: ViewController, @@ -169,7 +169,6 @@ export class TextAreaEditContext extends AbstractEditContext { this._contentHeight = layoutInfo.height; this._fontInfo = options.get(EditorOption.fontInfo); this._emptySelectionClipboard = options.get(EditorOption.emptySelectionClipboard); - this._copyWithSyntaxHighlighting = options.get(EditorOption.copyWithSyntaxHighlighting); this._visibleTextArea = null; this._selections = [new Selection(1, 1, 1, 1)]; @@ -205,9 +204,7 @@ export class TextAreaEditContext extends AbstractEditContext { const simplePagedScreenReaderStrategy = new SimplePagedScreenReaderStrategy(); const textAreaInputHost: ITextAreaInputHost = { - getDataToCopy: (): ClipboardDataToCopy => { - return getDataToCopy(this._context.viewModel, this._modelSelections, this._emptySelectionClipboard, this._copyWithSyntaxHighlighting); - }, + context: this._context, getScreenReaderContent: (): TextAreaState => { if (this._accessibilitySupport === AccessibilitySupport.Disabled) { // We know for a fact that a screen reader is not attached @@ -445,6 +442,8 @@ export class TextAreaEditContext extends AbstractEditContext { this._register(IME.onDidChange(() => { this._ensureReadOnlyAttribute(); })); + + this._register(TextAreaEditContextRegistry.register(ownerID, this)); } public get domNode() { @@ -573,7 +572,6 @@ export class TextAreaEditContext extends AbstractEditContext { this._contentHeight = layoutInfo.height; this._fontInfo = options.get(EditorOption.fontInfo); this._emptySelectionClipboard = options.get(EditorOption.emptySelectionClipboard); - this._copyWithSyntaxHighlighting = options.get(EditorOption.copyWithSyntaxHighlighting); this.textArea.setAttribute('wrap', this._textAreaWrapping && !this._visibleTextArea ? 'on' : 'off'); const { tabSize } = this._context.viewModel.model.getOptions(); this.textArea.domNode.style.tabSize = `${tabSize * this._fontInfo.spaceWidth}px`; diff --git a/code/src/vs/editor/browser/controller/editContext/textArea/textAreaEditContextInput.ts b/code/src/vs/editor/browser/controller/editContext/textArea/textAreaEditContextInput.ts index 4e4cb103bc5..fa7ecddebff 100644 --- a/code/src/vs/editor/browser/controller/editContext/textArea/textAreaEditContextInput.ts +++ b/code/src/vs/editor/browser/controller/editContext/textArea/textAreaEditContextInput.ts @@ -17,10 +17,10 @@ import * as strings from '../../../../../base/common/strings.js'; import { Position } from '../../../../common/core/position.js'; import { Selection } from '../../../../common/core/selection.js'; import { IAccessibilityService } from '../../../../../platform/accessibility/common/accessibility.js'; -import { ILogService, LogLevel } from '../../../../../platform/log/common/log.js'; -import { ClipboardDataToCopy, ClipboardEventUtils, ClipboardStoredMetadata, InMemoryClipboardMetadataManager } from '../clipboardUtils.js'; +import { ILogService } from '../../../../../platform/log/common/log.js'; +import { ClipboardEventUtils, ClipboardStoredMetadata, ensureClipboardGetsEditorSelection, InMemoryClipboardMetadataManager } from '../clipboardUtils.js'; import { _debugComposition, ITextAreaWrapper, ITypeData, TextAreaState } from './textAreaEditContextState.js'; -import { generateUuid } from '../../../../../base/common/uuid.js'; +import { ViewContext } from '../../../../common/viewModel/viewContext.js'; export namespace TextAreaSyntethicEvents { export const Tap = '-monaco-textarea-synthetic-tap'; @@ -37,7 +37,7 @@ export interface IPasteData { } export interface ITextAreaInputHost { - getDataToCopy(): ClipboardDataToCopy; + readonly context: ViewContext | null; getScreenReaderContent(): TextAreaState; deduceModelPosition(viewAnchorPosition: Position, deltaOffset: number, lineFeedCnt: number): Position; } @@ -363,13 +363,17 @@ export class TextAreaInput extends Disposable { // result in a `selectionchange` event which we want to ignore this._textArea.setIgnoreSelectionChangeTime('received cut event'); - this._ensureClipboardGetsEditorSelection(e); + if (this._host.context) { + ensureClipboardGetsEditorSelection(e, this._host.context, this._logService, this._browser.isFirefox); + } this._asyncTriggerCut.schedule(); })); this._register(this._textArea.onCopy((e) => { this._logService.trace(`TextAreaInput#onCopy`, e); - this._ensureClipboardGetsEditorSelection(e); + if (this._host.context) { + ensureClipboardGetsEditorSelection(e, this._host.context, this._logService, this._browser.isFirefox); + } })); this._register(this._textArea.onPaste((e) => { @@ -608,33 +612,6 @@ export class TextAreaInput extends Disposable { } this._setAndWriteTextAreaState(reason, this._host.getScreenReaderContent()); } - - private _ensureClipboardGetsEditorSelection(e: ClipboardEvent): void { - const dataToCopy = this._host.getDataToCopy(); - let id = undefined; - if (this._logService.getLevel() === LogLevel.Trace) { - id = generateUuid(); - } - const storedMetadata: ClipboardStoredMetadata = { - version: 1, - id, - isFromEmptySelection: dataToCopy.isFromEmptySelection, - multicursorText: dataToCopy.multicursorText, - mode: dataToCopy.mode - }; - InMemoryClipboardMetadataManager.INSTANCE.set( - // When writing "LINE\r\n" to the clipboard and then pasting, - // Firefox pastes "LINE\n", so let's work around this quirk - (this._browser.isFirefox ? dataToCopy.text.replace(/\r\n/g, '\n') : dataToCopy.text), - storedMetadata - ); - - e.preventDefault(); - if (e.clipboardData) { - ClipboardEventUtils.setTextData(e.clipboardData, dataToCopy.text, dataToCopy.html, storedMetadata); - } - this._logService.trace('TextAreaEditContextInput#_ensureClipboardGetsEditorSelection with id : ', id, ' with text.length: ', dataToCopy.text.length); - } } export class TextAreaWrapper extends Disposable implements ICompleteTextAreaWrapper { diff --git a/code/src/vs/editor/browser/controller/editContext/textArea/textAreaEditContextRegistry.ts b/code/src/vs/editor/browser/controller/editContext/textArea/textAreaEditContextRegistry.ts new file mode 100644 index 00000000000..d52b4ee8f58 --- /dev/null +++ b/code/src/vs/editor/browser/controller/editContext/textArea/textAreaEditContextRegistry.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 { IDisposable } from '../../../../../base/common/lifecycle.js'; +import { TextAreaEditContext } from './textAreaEditContext.js'; + +class TextAreaEditContextRegistryImpl { + + private _textAreaEditContextMapping: Map = new Map(); + + register(ownerID: string, textAreaEditContext: TextAreaEditContext): IDisposable { + this._textAreaEditContextMapping.set(ownerID, textAreaEditContext); + return { + dispose: () => { + this._textAreaEditContextMapping.delete(ownerID); + } + }; + } + + get(ownerID: string): TextAreaEditContext | undefined { + return this._textAreaEditContextMapping.get(ownerID); + } +} + +export const TextAreaEditContextRegistry = new TextAreaEditContextRegistryImpl(); diff --git a/code/src/vs/editor/browser/controller/editContext/textArea/textAreaEditContextState.ts b/code/src/vs/editor/browser/controller/editContext/textArea/textAreaEditContextState.ts index c8778b2bf45..9556337f01a 100644 --- a/code/src/vs/editor/browser/controller/editContext/textArea/textAreaEditContextState.ts +++ b/code/src/vs/editor/browser/controller/editContext/textArea/textAreaEditContextState.ts @@ -6,6 +6,7 @@ import { commonPrefixLength, commonSuffixLength } from '../../../../../base/common/strings.js'; import { Position } from '../../../../common/core/position.js'; import { Range } from '../../../../common/core/range.js'; +import { SelectionDirection } from '../../../../common/core/selection.js'; import { ISimpleScreenReaderContentState } from '../screenReaderUtils.js'; export const _debugComposition = false; @@ -226,10 +227,23 @@ export class TextAreaState { } public static fromScreenReaderContentState(screenReaderContentState: ISimpleScreenReaderContentState) { + let selectionStart; + let selectionEnd; + const direction = screenReaderContentState.selection.getDirection(); + switch (direction) { + case SelectionDirection.LTR: + selectionStart = screenReaderContentState.selectionStart; + selectionEnd = screenReaderContentState.selectionEnd; + break; + case SelectionDirection.RTL: + selectionStart = screenReaderContentState.selectionEnd; + selectionEnd = screenReaderContentState.selectionStart; + break; + } return new TextAreaState( screenReaderContentState.value, - screenReaderContentState.selectionStart, - screenReaderContentState.selectionEnd, + selectionStart, + selectionEnd, screenReaderContentState.selection, screenReaderContentState.newlineCountBeforeSelection ); diff --git a/code/src/vs/editor/browser/controller/mouseHandler.ts b/code/src/vs/editor/browser/controller/mouseHandler.ts index cdfc280ec5e..4ad4ca9d817 100644 --- a/code/src/vs/editor/browser/controller/mouseHandler.ts +++ b/code/src/vs/editor/browser/controller/mouseHandler.ts @@ -23,6 +23,7 @@ import { NavigationCommandRevealType } from '../coreCommands.js'; import { MouseWheelClassifier } from '../../../base/browser/ui/scrollbar/scrollableElement.js'; import type { ViewLinesGpu } from '../viewParts/viewLinesGpu/viewLinesGpu.js'; import { TopBottomDragScrolling, LeftRightDragScrolling } from './dragScrolling.js'; +import { TextDirection } from '../../common/model.js'; export interface IPointerHandlerHelper { viewDomNode: HTMLElement; @@ -241,13 +242,14 @@ export class MouseHandler extends ViewEventHandler { } protected _createMouseTarget(e: EditorMouseEvent, testEventTarget: boolean): IMouseTarget { - let target = e.target; + let target: HTMLElement | null = e.target; if (!this.viewHelper.viewDomNode.contains(target)) { const shadowRoot = dom.getShadowRoot(this.viewHelper.viewDomNode); if (shadowRoot) { - target = (shadowRoot).elementsFromPoint(e.posx, e.posy).find( + const potentialTarget = shadowRoot.elementsFromPoint(e.posx, e.posy).find( (el: Element) => this.viewHelper.viewDomNode.contains(el) - ); + ) ?? null; + target = potentialTarget as HTMLElement; } } return this.mouseTargetFactory.createMouseTarget(this.viewHelper.getLastRenderData(), e.editorPos, e.pos, e.relativePos, testEventTarget ? target : null); @@ -339,7 +341,7 @@ export class MouseHandler extends ViewEventHandler { this._mouseDownOperation.start(t.type, e, pointerId); e.preventDefault(); } - } else if (targetIsWidget && this.viewHelper.shouldSuppressMouseDownOnWidget(t.detail)) { + } else if (targetIsWidget && this.viewHelper.shouldSuppressMouseDownOnWidget(t.detail)) { focus(); e.preventDefault(); } @@ -575,7 +577,8 @@ class MouseDownOperation extends Disposable { const xLeftBoundary = layoutInfo.contentLeft; if (e.relativePos.x <= xLeftBoundary) { const outsideDistance = xLeftBoundary - e.relativePos.x; - return MouseTarget.createOutsideEditor(mouseColumn, new Position(possibleLineNumber, 1), 'left', outsideDistance); + const isRtl = model.getTextDirection(possibleLineNumber) === TextDirection.RTL; + return MouseTarget.createOutsideEditor(mouseColumn, new Position(possibleLineNumber, isRtl ? model.getLineMaxColumn(possibleLineNumber) : 1), 'left', outsideDistance); } const contentRight = ( @@ -586,7 +589,8 @@ class MouseDownOperation extends Disposable { const xRightBoundary = contentRight; if (e.relativePos.x >= xRightBoundary) { const outsideDistance = e.relativePos.x - xRightBoundary; - return MouseTarget.createOutsideEditor(mouseColumn, new Position(possibleLineNumber, model.getLineMaxColumn(possibleLineNumber)), 'right', outsideDistance); + const isRtl = model.getTextDirection(possibleLineNumber) === TextDirection.RTL; + return MouseTarget.createOutsideEditor(mouseColumn, new Position(possibleLineNumber, isRtl ? 1 : model.getLineMaxColumn(possibleLineNumber)), 'right', outsideDistance); } return null; diff --git a/code/src/vs/editor/browser/controller/mouseTarget.ts b/code/src/vs/editor/browser/controller/mouseTarget.ts index db133ec98c6..8256f6b487c 100644 --- a/code/src/vs/editor/browser/controller/mouseTarget.ts +++ b/code/src/vs/editor/browser/controller/mouseTarget.ts @@ -150,7 +150,7 @@ export class MouseTarget { } public static toString(target: IMouseTarget): string { - return this._typeToString(target.type) + ': ' + target.position + ' - ' + target.range + ' - ' + JSON.stringify((target).detail); + return this._typeToString(target.type) + ': ' + target.position + ' - ' + target.range + ' - ' + JSON.stringify((target as unknown as Record).detail); } } @@ -448,7 +448,7 @@ class HitTestRequest extends BareHitTestRequest { } public override toString(): string { - return `pos(${this.pos.x},${this.pos.y}), editorPos(${this.editorPos.x},${this.editorPos.y}), relativePos(${this.relativePos.x},${this.relativePos.y}), mouseVerticalOffset: ${this.mouseVerticalOffset}, mouseContentHorizontalOffset: ${this.mouseContentHorizontalOffset}\n\ttarget: ${this.target ? (this.target).outerHTML : null}`; + return `pos(${this.pos.x},${this.pos.y}), editorPos(${this.editorPos.x},${this.editorPos.y}), relativePos(${this.relativePos.x},${this.relativePos.y}), mouseVerticalOffset: ${this.mouseVerticalOffset}, mouseContentHorizontalOffset: ${this.mouseContentHorizontalOffset}\n\ttarget: ${this.target ? this.target.outerHTML : null}`; } public get wouldBenefitFromHitTestTargetSwitch(): boolean { @@ -481,7 +481,8 @@ class HitTestRequest extends BareHitTestRequest { return MouseTarget.createMargin(type, this.target, this._getMouseColumn(position), position, range, detail); } public fulfillViewZone(type: MouseTargetType.GUTTER_VIEW_ZONE | MouseTargetType.CONTENT_VIEW_ZONE, position: Position, detail: IMouseTargetViewZoneData): IMouseTargetViewZone { - return MouseTarget.createViewZone(type, this.target, this._getMouseColumn(position), position, detail); + // Always return the usual mouse column for a view zone. + return MouseTarget.createViewZone(type, this.target, this._getMouseColumn(), position, detail); } public fulfillContentText(position: Position, range: EditorRange | null, detail: IMouseTargetContentTextData): IMouseTargetContentText { return MouseTarget.createContentText(this.target, this._getMouseColumn(position), position, range, detail); @@ -989,12 +990,15 @@ export class MouseTargetFactory { const shadowRoot = dom.getShadowRoot(ctx.viewDomNode); let range: Range; if (shadowRoot) { + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any if (typeof (shadowRoot).caretRangeFromPoint === 'undefined') { range = shadowCaretRangeFromPoint(shadowRoot, coords.clientX, coords.clientY); } else { + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any range = (shadowRoot).caretRangeFromPoint(coords.clientX, coords.clientY); } } else { + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any range = (ctx.viewDomNode.ownerDocument).caretRangeFromPoint(coords.clientX, coords.clientY); } @@ -1024,7 +1028,7 @@ export class MouseTargetFactory { const parent2ClassName = parent2 && parent2.nodeType === parent2.ELEMENT_NODE ? (parent2).className : null; if (parent2ClassName === ViewLine.CLASS_NAME) { - return HitTestResult.createFromDOMInfo(ctx, startContainer, (startContainer).textContent!.length); + return HitTestResult.createFromDOMInfo(ctx, startContainer, (startContainer).textContent.length); } else { return new UnknownHitTestResult(startContainer); } @@ -1037,6 +1041,7 @@ export class MouseTargetFactory { * Most probably Gecko */ private static _doHitTestWithCaretPositionFromPoint(ctx: HitTestContext, coords: ClientCoordinates): HitTestResult { + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any const hitResult: { offsetNode: Node; offset: number } = (ctx.viewDomNode.ownerDocument).caretPositionFromPoint(coords.clientX, coords.clientY); if (hitResult.offsetNode.nodeType === hitResult.offsetNode.TEXT_NODE) { @@ -1089,8 +1094,10 @@ export class MouseTargetFactory { public static doHitTest(ctx: HitTestContext, request: BareHitTestRequest): HitTestResult { let result: HitTestResult = new UnknownHitTestResult(); + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any if (typeof (ctx.viewDomNode.ownerDocument).caretRangeFromPoint === 'function') { result = this._doHitTestWithCaretRangeFromPoint(ctx, request); + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any } else if ((ctx.viewDomNode.ownerDocument).caretPositionFromPoint) { result = this._doHitTestWithCaretPositionFromPoint(ctx, request.pos.toClientCoordinates(dom.getWindow(ctx.viewDomNode))); } @@ -1110,6 +1117,7 @@ function shadowCaretRangeFromPoint(shadowRoot: ShadowRoot, x: number, y: number) const range = document.createRange(); // Get the element under the point + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any let el: HTMLElement | null = (shadowRoot).elementFromPoint(x, y); // When el is not null, it may be div.monaco-mouse-cursor-text Element, which has not childNodes, we don't need to handle it. if (el?.hasChildNodes()) { diff --git a/code/src/vs/editor/browser/controller/pointerHandler.ts b/code/src/vs/editor/browser/controller/pointerHandler.ts index 80fc4825a83..4eb4ca92b39 100644 --- a/code/src/vs/editor/browser/controller/pointerHandler.ts +++ b/code/src/vs/editor/browser/controller/pointerHandler.ts @@ -32,7 +32,7 @@ export class PointerEventHandler extends MouseHandler { this._lastPointerType = 'mouse'; - this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, 'pointerdown', (e: any) => { + this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, 'pointerdown', (e: PointerEvent) => { const pointerType = e.pointerType; if (pointerType === 'mouse') { this._lastPointerType = 'mouse'; @@ -54,7 +54,7 @@ export class PointerEventHandler extends MouseHandler { } private onTap(event: GestureEvent): void { - if (!event.initialTarget || !this.viewHelper.linesContentDomNode.contains(event.initialTarget)) { + if (!event.initialTarget || !this.viewHelper.linesContentDomNode.contains(event.initialTarget as HTMLElement)) { return; } @@ -94,7 +94,7 @@ export class PointerEventHandler extends MouseHandler { } protected override _onMouseDown(e: EditorMouseEvent, pointerId: number): void { - if ((e.browserEvent as any).pointerType === 'touch') { + if ((e.browserEvent as PointerEvent).pointerType === 'touch') { return; } diff --git a/code/src/vs/editor/browser/coreCommands.ts b/code/src/vs/editor/browser/coreCommands.ts index 0fcd8dd6997..9331eea48ff 100644 --- a/code/src/vs/editor/browser/coreCommands.ts +++ b/code/src/vs/editor/browser/coreCommands.ts @@ -31,6 +31,7 @@ import { IViewModel } from '../common/viewModel.js'; import { ISelection } from '../common/core/selection.js'; import { getActiveElement, isEditableElement } from '../../base/browser/dom.js'; import { EnterOperation } from '../common/cursor/cursorTypeEditOperations.js'; +import { TextEditorSelectionSource } from '../../platform/editor/common/editor.js'; const CORE_WEIGHT = KeybindingWeight.EditorCore; @@ -49,12 +50,12 @@ export abstract class CoreEditorCommand extends EditorCommand { export namespace EditorScroll_ { - const isEditorScrollArgs = function (arg: any): boolean { + const isEditorScrollArgs = function (arg: unknown): boolean { if (!types.isObject(arg)) { return false; } - const scrollArg: RawArguments = arg; + const scrollArg: RawArguments = arg as RawArguments; if (!types.isString(scrollArg.to)) { return false; @@ -235,12 +236,12 @@ export namespace EditorScroll_ { export namespace RevealLine_ { - const isRevealLineArgs = function (arg: any): boolean { + const isRevealLineArgs = function (arg: unknown): boolean { if (!types.isObject(arg)) { return false; } - const reveaLineArg: RawArguments = arg; + const reveaLineArg: RawArguments = arg as RawArguments; if (!types.isNumber(reveaLineArg.lineNumber) && !types.isString(reveaLineArg.lineNumber)) { return false; @@ -604,13 +605,16 @@ export namespace CoreNavigationCommands { } private _runCursorMove(viewModel: IViewModel, source: string | null | undefined, args: CursorMove_.ParsedArguments): void { + // If noHistory is true, use PROGRAMMATIC source to prevent adding to navigation history + const effectiveSource = args.noHistory ? TextEditorSelectionSource.PROGRAMMATIC : source; + viewModel.model.pushStackElement(); viewModel.setCursorStates( - source, + effectiveSource, CursorChangeReason.Explicit, CursorMoveImpl._move(viewModel, viewModel.getCursorStates(), args) ); - viewModel.revealAllCursors(source, true); + viewModel.revealAllCursors(effectiveSource, true); } private static _move(viewModel: IViewModel, cursors: CursorState[], args: CursorMove_.ParsedArguments): PartialCursorState[] | null { @@ -1319,8 +1323,7 @@ export namespace CoreNavigationCommands { EditorScroll_.Unit.WrappedLine, EditorScroll_.Unit.Page, EditorScroll_.Unit.HalfPage, - EditorScroll_.Unit.Editor, - EditorScroll_.Unit.Column + EditorScroll_.Unit.Editor ]; const horizontalDirections = [EditorScroll_.Direction.Left, EditorScroll_.Direction.Right]; const verticalDirections = [EditorScroll_.Direction.Up, EditorScroll_.Direction.Down]; @@ -1355,11 +1358,13 @@ export namespace CoreNavigationCommands { if (args.revealCursor) { // must ensure cursor is in new visible range const desiredVisibleViewRange = viewModel.getCompletelyVisibleViewRangeAtScrollTop(desiredScrollTop); + const paddedRange = viewModel.getViewRangeWithCursorPadding(desiredVisibleViewRange); + viewModel.setCursorStates( source, CursorChangeReason.Explicit, [ - CursorMoveCommands.findPositionInViewportIfOutside(viewModel, viewModel.getPrimaryCursorState(), desiredVisibleViewRange, args.select) + CursorMoveCommands.findPositionInViewportIfOutside(viewModel, viewModel.getPrimaryCursorState(), paddedRange, args.select) ] ); } diff --git a/code/src/vs/editor/browser/dataTransfer.ts b/code/src/vs/editor/browser/dataTransfer.ts index 014c3cdac21..10f1b3e0b9a 100644 --- a/code/src/vs/editor/browser/dataTransfer.ts +++ b/code/src/vs/editor/browser/dataTransfer.ts @@ -29,7 +29,7 @@ export function toVSDataTransfer(dataTransfer: DataTransfer): VSDataTransfer { function createFileDataTransferItemFromFile(file: File): IDataTransferItem { const path = getPathForFile(file); - const uri = path ? URI.parse(path!) : undefined; + const uri = path ? URI.parse(path) : undefined; return createFileDataTransferItem(file.name, uri, async () => { return new Uint8Array(await file.arrayBuffer()); }); diff --git a/code/src/vs/editor/browser/editorBrowser.ts b/code/src/vs/editor/browser/editorBrowser.ts index fdb8a1add80..f2ac74abf99 100644 --- a/code/src/vs/editor/browser/editorBrowser.ts +++ b/code/src/vs/editor/browser/editorBrowser.ts @@ -7,9 +7,12 @@ import { IKeyboardEvent } from '../../base/browser/keyboardEvent.js'; import { IMouseEvent, IMouseWheelEvent } from '../../base/browser/mouseEvent.js'; import { IBoundarySashes } from '../../base/browser/ui/sash/sash.js'; import { Event } from '../../base/common/event.js'; -import { IEditorConstructionOptions } from './config/editorConfiguration.js'; +import { MenuId } from '../../platform/actions/common/actions.js'; +import { IContextKeyService } from '../../platform/contextkey/common/contextkey.js'; +import { ServicesAccessor } from '../../platform/instantiation/common/instantiation.js'; import { ConfigurationChangedEvent, EditorLayoutInfo, EditorOption, FindComputedEditorOptionValueById, IComputedEditorOptions, IDiffEditorOptions, IEditorOptions, OverviewRulerPosition } from '../common/config/editorOptions.js'; import { IDimension } from '../common/core/2d/dimension.js'; +import { TextEdit } from '../common/core/edits/textEdit.js'; import { IPosition, Position } from '../common/core/position.js'; import { IRange, Range } from '../common/core/range.js'; import { Selection } from '../common/core/selection.js'; @@ -17,16 +20,13 @@ import { IWordAtPosition } from '../common/core/wordHelper.js'; import { ICursorPositionChangedEvent, ICursorSelectionChangedEvent } from '../common/cursorEvents.js'; import { IDiffComputationResult, ILineChange } from '../common/diff/legacyLinesDiffComputer.js'; import * as editorCommon from '../common/editorCommon.js'; -import { GlyphMarginLane, ICursorStateComputer, IIdentifiedSingleEditOperation, IModelDecoration, IModelDeltaDecoration, ITextModel, PositionAffinity } from '../common/model.js'; +import { GlyphMarginLane, ICursorStateComputer, IIdentifiedSingleEditOperation, IModelDecoration, IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel, PositionAffinity } from '../common/model.js'; import { InjectedText } from '../common/modelLineProjectionData.js'; +import { TextModelEditSource } from '../common/textModelEditSource.js'; import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelOptionsChangedEvent, IModelTokensChangedEvent, ModelFontChangedEvent, ModelLineHeightChangedEvent } from '../common/textModelEvents.js'; import { IEditorWhitespace, IViewModel } from '../common/viewModel.js'; import { OverviewRulerZone } from '../common/viewModel/overviewZoneManager.js'; -import { MenuId } from '../../platform/actions/common/actions.js'; -import { IContextKeyService } from '../../platform/contextkey/common/contextkey.js'; -import { ServicesAccessor } from '../../platform/instantiation/common/instantiation.js'; -import { TextEdit } from '../common/core/edits/textEdit.js'; -import { TextModelEditSource } from '../common/textModelEditSource.js'; +import { IEditorConstructionOptions } from './config/editorConfiguration.js'; /** * A view zone is a full horizontal rectangle that 'pushes' text down. @@ -178,6 +178,13 @@ export interface IContentWidget { * Render this content widget in a location where it could overflow the editor's view dom node. */ allowEditorOverflow?: boolean; + + /** + * If true, this widget doesn't have a visual representation. + * The element will have display set to 'none'. + */ + useDisplayNone?: boolean; + /** * Call preventDefault() on mousedown events that target the content widget. */ @@ -272,7 +279,7 @@ export interface IOverlayWidgetPosition { * When set, stacks with other overlay widgets with the same preference, * in an order determined by the ordinal value. */ - stackOridinal?: number; + stackOrdinal?: number; } /** * An overlay widgets renders on top of the text. @@ -281,7 +288,7 @@ export interface IOverlayWidget { /** * Event fired when the widget layout changes. */ - onDidLayout?: Event; + readonly onDidLayout?: Event; /** * Render this overlay widget in a location where it could overflow the editor's view dom node. */ @@ -825,6 +832,8 @@ export interface ICodeEditor extends editorCommon.IEditor { */ readonly onEndUpdate: Event; + readonly onDidChangeViewZones: Event; + /** * Saves current view state of the editor in a serializable object. */ @@ -898,14 +907,14 @@ export interface ICodeEditor extends editorCommon.IEditor { * @internal * @event */ - onDidChangeLineHeight: Event; + readonly onDidChangeLineHeight: Event; /** * An event emitted when the font of the editor has changed. * @internal * @event */ - onDidChangeFont: Event; + readonly onDidChangeFont: Event; /** * Get value of the current model attached to this editor. @@ -1012,6 +1021,11 @@ export interface ICodeEditor extends editorCommon.IEditor { */ executeCommands(source: string | null | undefined, commands: (editorCommon.ICommand | null)[]): void; + /** + * Scroll vertically or horizontally as necessary and reveal the current cursors. + */ + revealAllCursors(revealHorizontal: boolean, minimalReveal?: boolean): void; + /** * @internal */ @@ -1123,7 +1137,7 @@ export interface ICodeEditor extends editorCommon.IEditor { /** * @internal */ - getTelemetryData(): { [key: string]: any } | undefined; + getTelemetryData(): object | undefined; /** * Returns the editor's container dom node @@ -1189,6 +1203,8 @@ export interface ICodeEditor extends editorCommon.IEditor { */ getOffsetForColumn(lineNumber: number, column: number): number; + getWidthOfLine(lineNumber: number): number; + /** * Force an editor render now. */ @@ -1283,6 +1299,15 @@ export interface IActiveCodeEditor extends ICodeEditor { * Warning: the results of this method are inaccurate for positions that are outside the current editor viewport. */ getScrolledVisiblePosition(position: IPosition): { top: number; left: number; height: number }; + + /** + * Change the decorations. All decorations added through this changeAccessor + * will get the ownerId of the editor (meaning they will not show up in other + * editors). + * @see {@link ITextModel.changeDecorations} + * @internal + */ + changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): T; } /** @@ -1462,7 +1487,7 @@ export function getCodeEditor(thing: unknown): ICodeEditor | null { /** *@internal */ -export function getIEditor(thing: any): editorCommon.IEditor | null { +export function getIEditor(thing: unknown): editorCommon.IEditor | null { if (isCodeEditor(thing) || isDiffEditor(thing)) { return thing; } diff --git a/code/src/vs/editor/browser/editorDom.ts b/code/src/vs/editor/browser/editorDom.ts index c2d7f868d63..5e41bb11c15 100644 --- a/code/src/vs/editor/browser/editorDom.ts +++ b/code/src/vs/editor/browser/editorDom.ts @@ -242,7 +242,7 @@ export class GlobalEditorPointerMoveMonitor extends Disposable { // Add a <> keydown event listener that will cancel the monitoring // if something other than a modifier key is pressed - this._keydownListener = dom.addStandardDisposableListener(initialElement.ownerDocument, 'keydown', (e) => { + this._keydownListener = dom.addStandardDisposableListener(initialElement.ownerDocument, 'keydown', (e) => { const chord = e.toKeyCodeChord(); if (chord.isModifierKey()) { // Allow modifier keys @@ -381,8 +381,8 @@ class RefCountedCssRule { private getCssText(className: string, properties: CssProperties): string { let str = `.${className} {`; for (const prop in properties) { - const value = (properties as any)[prop] as string | ThemeColor; - let cssValue; + const value = (properties as Record)[prop] as string | ThemeColor; + let cssValue: unknown; if (typeof value === 'object') { cssValue = asCssVariable(value.id); } else { diff --git a/code/src/vs/editor/browser/editorExtensions.ts b/code/src/vs/editor/browser/editorExtensions.ts index 9d2d30b37cd..d5961dd4f1e 100644 --- a/code/src/vs/editor/browser/editorExtensions.ts +++ b/code/src/vs/editor/browser/editorExtensions.ts @@ -25,6 +25,7 @@ import { IDisposable } from '../../base/common/lifecycle.js'; import { KeyMod, KeyCode } from '../../base/common/keyCodes.js'; import { ILogService } from '../../platform/log/common/log.js'; import { getActiveElement } from '../../base/browser/dom.js'; +import { TriggerInlineEditCommandsRegistry } from './triggerInlineEditCommandsRegistry.js'; export type ServicesAccessor = InstantiationServicesAccessor; export type EditorContributionCtor = IConstructorSignature; @@ -82,7 +83,7 @@ export interface ICommandKeybindingsOptions extends IKeybindings { /** * the default keybinding arguments */ - args?: any; + args?: unknown; } export interface ICommandMenuOptions { menuId: MenuId; @@ -98,6 +99,7 @@ export interface ICommandOptions { kbOpts?: ICommandKeybindingsOptions | ICommandKeybindingsOptions[]; metadata?: ICommandMetadata; menuOpts?: ICommandMenuOptions | ICommandMenuOptions[]; + canTriggerInlineEdits?: boolean; } export abstract class Command { public readonly id: string; @@ -105,6 +107,7 @@ export abstract class Command { private readonly _kbOpts: ICommandKeybindingsOptions | ICommandKeybindingsOptions[] | undefined; private readonly _menuOpts: ICommandMenuOptions | ICommandMenuOptions[] | undefined; public readonly metadata: ICommandMetadata | undefined; + public readonly canTriggerInlineEdits: boolean | undefined; constructor(opts: ICommandOptions) { this.id = opts.id; @@ -112,6 +115,7 @@ export abstract class Command { this._kbOpts = opts.kbOpts; this._menuOpts = opts.menuOpts; this.metadata = opts.metadata; + this.canTriggerInlineEdits = opts.canTriggerInlineEdits; } public register(): void { @@ -155,6 +159,10 @@ export abstract class Command { handler: (accessor, args) => this.runCommand(accessor, args), metadata: this.metadata }); + + if (this.canTriggerInlineEdits) { + TriggerInlineEditCommandsRegistry.registerCommand(this.id); + } } private _registerMenuItem(item: ICommandMenuOptions): void { @@ -171,7 +179,7 @@ export abstract class Command { }); } - public abstract runCommand(accessor: ServicesAccessor, args: any): void | Promise; + public abstract runCommand(accessor: ServicesAccessor, args: unknown): void | Promise; } //#endregion Command @@ -214,7 +222,7 @@ export class MultiCommand extends Command { }; } - public runCommand(accessor: ServicesAccessor, args: any): void | Promise { + public runCommand(accessor: ServicesAccessor, args: unknown): void | Promise { const logService = accessor.get(ILogService); const contextKeyService = accessor.get(IContextKeyService); logService.trace(`Executing Command '${this.id}' which has ${this._implementations.length} bound.`); @@ -254,7 +262,7 @@ export class ProxyCommand extends Command { super(opts); } - public runCommand(accessor: ServicesAccessor, args: any): void | Promise { + public runCommand(accessor: ServicesAccessor, args: unknown): void | Promise { return this.command.runCommand(accessor, args); } } @@ -262,7 +270,7 @@ export class ProxyCommand extends Command { //#region EditorCommand export interface IContributionCommandOptions extends ICommandOptions { - handler: (controller: T, args: any) => void; + handler: (controller: T, args: unknown) => void; } export interface EditorControllerCommand { new(opts: IContributionCommandOptions): EditorCommand; @@ -274,7 +282,7 @@ export abstract class EditorCommand extends Command { */ public static bindToContribution(controllerGetter: (editor: ICodeEditor) => T | null): EditorControllerCommand { return class EditorControllerCommandImpl extends EditorCommand { - private readonly _callback: (controller: T, args: any) => void; + private readonly _callback: (controller: T, args: unknown) => void; constructor(opts: IContributionCommandOptions) { super(opts); @@ -282,7 +290,7 @@ export abstract class EditorCommand extends Command { this._callback = opts.handler; } - public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { + public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: unknown): void { const controller = controllerGetter(editor); if (controller) { this._callback(controller, args); @@ -291,11 +299,11 @@ export abstract class EditorCommand extends Command { }; } - public static runEditorCommand( + public static runEditorCommand( accessor: ServicesAccessor, - args: any, + args: T, precondition: ContextKeyExpression | undefined, - runner: (accessor: ServicesAccessor, editor: ICodeEditor, args: any) => void | Promise + runner: (accessor: ServicesAccessor, editor: ICodeEditor, args: T) => void | Promise ): void | Promise { const codeEditorService = accessor.get(ICodeEditorService); @@ -317,11 +325,11 @@ export abstract class EditorCommand extends Command { }); } - public runCommand(accessor: ServicesAccessor, args: any): void | Promise { + public runCommand(accessor: ServicesAccessor, args: unknown): void | Promise { return EditorCommand.runEditorCommand(accessor, args, this.precondition, (accessor, editor, args) => this.runEditorCommand(accessor, editor, args)); } - public abstract runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void | Promise; + public abstract runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: unknown): void | Promise; } //#endregion EditorCommand @@ -392,7 +400,7 @@ export abstract class EditorAction extends EditorCommand { } } - public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void | Promise { + public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: unknown): void | Promise { this.reportTelemetry(accessor, editor); return this.run(accessor, editor, args || {}); } @@ -411,10 +419,10 @@ export abstract class EditorAction extends EditorCommand { accessor.get(ITelemetryService).publicLog2('editorActionInvoked', { name: this.label, id: this.id }); } - public abstract run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void | Promise; + public abstract run(accessor: ServicesAccessor, editor: ICodeEditor, args: unknown): void | Promise; } -export type EditorActionImplementation = (accessor: ServicesAccessor, editor: ICodeEditor, args: any) => boolean | Promise; +export type EditorActionImplementation = (accessor: ServicesAccessor, editor: ICodeEditor, args: unknown) => boolean | Promise; export class MultiEditorAction extends EditorAction { @@ -438,7 +446,7 @@ export class MultiEditorAction extends EditorAction { }; } - public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void | Promise { + public run(accessor: ServicesAccessor, editor: ICodeEditor, args: unknown): void | Promise { for (const impl of this._implementations) { const result = impl[1](accessor, editor, args); if (result) { @@ -458,7 +466,7 @@ export class MultiEditorAction extends EditorAction { export abstract class EditorAction2 extends Action2 { - run(accessor: ServicesAccessor, ...args: any[]) { + run(accessor: ServicesAccessor, ...args: unknown[]) { // Find the editor with text focus or active const codeEditorService = accessor.get(ICodeEditorService); const editor = codeEditorService.getFocusedCodeEditor() || codeEditorService.getActiveCodeEditor(); @@ -479,7 +487,7 @@ export abstract class EditorAction2 extends Action2 { }); } - abstract runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]): any; + abstract runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: unknown[]): unknown; } //#endregion @@ -487,7 +495,7 @@ export abstract class EditorAction2 extends Action2 { // --- Registration of commands and actions -export function registerModelAndPositionCommand(id: string, handler: (accessor: ServicesAccessor, model: ITextModel, position: Position, ...args: any[]) => any) { +export function registerModelAndPositionCommand(id: string, handler: (accessor: ServicesAccessor, model: ITextModel, position: Position, ...args: unknown[]) => unknown) { CommandsRegistry.registerCommand(id, function (accessor, ...args) { const instaService = accessor.get(IInstantiationService); diff --git a/code/src/vs/editor/browser/gpu/css/decorationCssRuleExtractor.ts b/code/src/vs/editor/browser/gpu/css/decorationCssRuleExtractor.ts index 43dbd85c87a..91afd694b1f 100644 --- a/code/src/vs/editor/browser/gpu/css/decorationCssRuleExtractor.ts +++ b/code/src/vs/editor/browser/gpu/css/decorationCssRuleExtractor.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $, getActiveDocument } from '../../../../base/browser/dom.js'; +import { $, getActiveDocument, getActiveWindow } from '../../../../base/browser/dom.js'; import { Disposable, toDisposable } from '../../../../base/common/lifecycle.js'; import './media/decorationCssRuleExtractor.css'; @@ -81,4 +81,14 @@ export class DecorationCssRuleExtractor extends Disposable { return rules; } + + /** + * Resolves a CSS variable to its computed value using the container element. + */ + resolveCssVariable(canvas: HTMLCanvasElement, variableName: string): string { + canvas.appendChild(this._container); + const result = getActiveWindow().getComputedStyle(this._container).getPropertyValue(variableName).trim(); + canvas.removeChild(this._container); + return result; + } } diff --git a/code/src/vs/editor/browser/gpu/css/decorationStyleCache.ts b/code/src/vs/editor/browser/gpu/css/decorationStyleCache.ts index 1b1c07df163..9a2eea56e1a 100644 --- a/code/src/vs/editor/browser/gpu/css/decorationStyleCache.ts +++ b/code/src/vs/editor/browser/gpu/css/decorationStyleCache.ts @@ -18,6 +18,18 @@ export interface IDecorationStyleSet { * A number between 0 and 1 representing the opacity of the text. */ opacity: number | undefined; + /** + * Whether the text should be rendered with a strikethrough. + */ + strikethrough: boolean | undefined; + /** + * The thickness of the strikethrough line in pixels (CSS pixels, not device pixels). + */ + strikethroughThickness: number | undefined; + /** + * A 32-bit number representing the strikethrough color. + */ + strikethroughColor: number | undefined; } export interface IDecorationStyleCacheEntry extends IDecorationStyleSet { @@ -32,29 +44,49 @@ export class DecorationStyleCache { private _nextId = 1; private readonly _cacheById = new Map(); - private readonly _cacheByStyle = new NKeyMap(); + private readonly _cacheByStyle = new NKeyMap(); getOrCreateEntry( color: number | undefined, bold: boolean | undefined, - opacity: number | undefined + opacity: number | undefined, + strikethrough: boolean | undefined, + strikethroughThickness: number | undefined, + strikethroughColor: number | undefined ): number { - if (color === undefined && bold === undefined && opacity === undefined) { + if (color === undefined && bold === undefined && opacity === undefined && strikethrough === undefined && strikethroughThickness === undefined && strikethroughColor === undefined) { return 0; } - const result = this._cacheByStyle.get(color ?? 0, bold ? 1 : 0, opacity === undefined ? '' : opacity.toFixed(2)); + const result = this._cacheByStyle.get( + color ?? 0, + bold ? 1 : 0, + opacity === undefined ? '' : opacity.toFixed(2), + strikethrough ? 1 : 0, + strikethroughThickness === undefined ? '' : strikethroughThickness.toFixed(2), + strikethroughColor ?? 0 + ); if (result) { return result.id; } const id = this._nextId++; - const entry = { + const entry: IDecorationStyleCacheEntry = { id, color, bold, opacity, + strikethrough, + strikethroughThickness, + strikethroughColor, }; this._cacheById.set(id, entry); - this._cacheByStyle.set(entry, color ?? 0, bold ? 1 : 0, opacity === undefined ? '' : opacity.toFixed(2)); + this._cacheByStyle.set(entry, + color ?? 0, + bold ? 1 : 0, + opacity === undefined ? '' : opacity.toFixed(2), + strikethrough ? 1 : 0, + strikethroughThickness === undefined ? '' : strikethroughThickness.toFixed(2), + strikethroughColor ?? 0 + ); return id; } diff --git a/code/src/vs/editor/browser/gpu/gpuUtils.ts b/code/src/vs/editor/browser/gpu/gpuUtils.ts index 6ced420acc2..4647d8adc33 100644 --- a/code/src/vs/editor/browser/gpu/gpuUtils.ts +++ b/code/src/vs/editor/browser/gpu/gpuUtils.ts @@ -49,6 +49,7 @@ export function observeDevicePixelDimensions(element: HTMLElement, parentWindow: } }); try { + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any observer.observe(element, { box: ['device-pixel-content-box'] } as any); } catch { observer.disconnect(); diff --git a/code/src/vs/editor/browser/gpu/raster/glyphRasterizer.ts b/code/src/vs/editor/browser/gpu/raster/glyphRasterizer.ts index cc41c8dcaa4..7b74ec8830c 100644 --- a/code/src/vs/editor/browser/gpu/raster/glyphRasterizer.ts +++ b/code/src/vs/editor/browser/gpu/raster/glyphRasterizer.ts @@ -115,7 +115,7 @@ export class GlyphRasterizer extends Disposable implements IGlyphRasterizer { // The sub-pixel x offset is the fractional part of the x pixel coordinate of the cell, this // is used to improve the spacing between rendered characters. - const xSubPixelXOffset = (tokenMetadata & 0b1111) / 10; + const subPixelXOffset = (tokenMetadata & 0b1111) / 10; const bgId = TokenMetadata.getBackground(tokenMetadata); const bg = colorMap[bgId]; @@ -145,26 +145,54 @@ export class GlyphRasterizer extends Disposable implements IGlyphRasterizer { fontSb.appendString(`${devicePixelFontSize}px ${this.fontFamily}`); this._ctx.font = fontSb.build(); - // TODO: Support FontStyle.Strikethrough and FontStyle.Underline text decorations, these - // need to be drawn manually to the canvas. See xterm.js for "dodging" the text for - // underlines. + // TODO: Support FontStyle.Underline text decorations, these need to be drawn manually to + // the canvas. See xterm.js for "dodging" the text for underlines. const originX = devicePixelFontSize; const originY = devicePixelFontSize; + + // Apply text color if (decorationStyleSet?.color !== undefined) { this._ctx.fillStyle = `#${decorationStyleSet.color.toString(16).padStart(8, '0')}`; } else { this._ctx.fillStyle = colorMap[TokenMetadata.getForeground(tokenMetadata)]; } - this._ctx.textBaseline = 'top'; + // Apply opacity if (decorationStyleSet?.opacity !== undefined) { this._ctx.globalAlpha = decorationStyleSet.opacity; } - this._ctx.fillText(chars, originX + xSubPixelXOffset, originY); + // The glyph baseline is top, meaning it's drawn at the top-left of the + // cell. Add `TextMetrics.alphabeticBaseline` to the drawn position to + // get the alphabetic baseline. + this._ctx.textBaseline = 'top'; + + // Draw the text + this._ctx.fillText(chars, originX + subPixelXOffset, originY); + + // Draw strikethrough + if (decorationStyleSet?.strikethrough) { + // TODO: This position could be refined further by checking + // TextMetrics of lowercase letters. + // Position strikethrough at approximately the vertical center of + // lowercase letters. + const strikethroughY = Math.round(originY - this._textMetrics.alphabeticBaseline * 0.65); + const lineWidth = decorationStyleSet?.strikethroughThickness !== undefined + ? Math.round(decorationStyleSet.strikethroughThickness * this.devicePixelRatio) + : Math.max(1, Math.floor(devicePixelFontSize / 10)); + // Apply strikethrough color if specified + if (decorationStyleSet?.strikethroughColor !== undefined) { + this._ctx.fillStyle = `#${decorationStyleSet.strikethroughColor.toString(16).padStart(8, '0')}`; + } + // Intentionally do not apply the sub pixel x offset to + // strikethrough to ensure successive glyphs form a contiguous line. + this._ctx.fillRect(originX, strikethroughY - Math.floor(lineWidth / 2), Math.ceil(this._textMetrics.width), lineWidth); + } + this._ctx.restore(); + // Extract the image data and clear the background color const imageData = this._ctx.getImageData(0, 0, this._canvas.width, this._canvas.height); if (this._antiAliasing === 'subpixel') { const bgR = parseInt(bg.substring(1, 3), 16); @@ -173,7 +201,10 @@ export class GlyphRasterizer extends Disposable implements IGlyphRasterizer { this._clearColor(imageData, bgR, bgG, bgB); this._ctx.putImageData(imageData, 0, 0); } + + // Find the bounding box this._findGlyphBoundingBox(imageData, this._workGlyph.boundingBox); + // const offset = { // x: textMetrics.actualBoundingBoxLeft, // y: textMetrics.actualBoundingBoxAscent diff --git a/code/src/vs/editor/browser/gpu/renderStrategy/fullFileRenderStrategy.ts b/code/src/vs/editor/browser/gpu/renderStrategy/fullFileRenderStrategy.ts index 9588c78c49c..f0ae8bba97f 100644 --- a/code/src/vs/editor/browser/gpu/renderStrategy/fullFileRenderStrategy.ts +++ b/code/src/vs/editor/browser/gpu/renderStrategy/fullFileRenderStrategy.ts @@ -257,6 +257,9 @@ export class FullFileRenderStrategy extends BaseRenderStrategy { let decorationStyleSetBold: boolean | undefined; let decorationStyleSetColor: number | undefined; let decorationStyleSetOpacity: number | undefined; + let decorationStyleSetStrikethrough: boolean | undefined; + let decorationStyleSetStrikethroughThickness: number | undefined; + let decorationStyleSetStrikethroughColor: number | undefined; let lineData: ViewLineRenderingData; let decoration: InlineDecoration; @@ -375,6 +378,9 @@ export class FullFileRenderStrategy extends BaseRenderStrategy { decorationStyleSetColor = undefined; decorationStyleSetBold = undefined; decorationStyleSetOpacity = undefined; + decorationStyleSetStrikethrough = undefined; + decorationStyleSetStrikethroughThickness = undefined; + decorationStyleSetStrikethroughColor = undefined; // Apply supported inline decoration styles to the cell metadata for (decoration of lineData.inlineDecorations) { @@ -419,6 +425,36 @@ export class FullFileRenderStrategy extends BaseRenderStrategy { decorationStyleSetOpacity = parsedValue; break; } + case 'text-decoration': + case 'text-decoration-line': { + if (value === 'line-through') { + decorationStyleSetStrikethrough = true; + } + break; + } + case 'text-decoration-thickness': { + const match = value.match(/^(\d+(?:\.\d+)?)px$/); + if (match) { + decorationStyleSetStrikethroughThickness = parseFloat(match[1]); + } + break; + } + case 'text-decoration-color': { + let colorValue = value; + const varMatch = value.match(/^var\((--[^,]+),\s*(?:initial|inherit)\)$/); + if (varMatch) { + colorValue = ViewGpuContext.decorationCssRuleExtractor.resolveCssVariable(this._viewGpuContext.canvas.domNode, varMatch[1]); + } + const parsedColor = Color.Format.CSS.parse(colorValue); + if (parsedColor) { + decorationStyleSetStrikethroughColor = parsedColor.toNumber32Bit(); + } + break; + } + case 'text-decoration-style': { + // These are validated in canRender and use default behavior + break; + } default: throw new BugIndicatingError('Unexpected inline decoration style'); } } @@ -443,7 +479,7 @@ export class FullFileRenderStrategy extends BaseRenderStrategy { continue; } - const decorationStyleSetId = ViewGpuContext.decorationStyleCache.getOrCreateEntry(decorationStyleSetColor, decorationStyleSetBold, decorationStyleSetOpacity); + const decorationStyleSetId = ViewGpuContext.decorationStyleCache.getOrCreateEntry(decorationStyleSetColor, decorationStyleSetBold, decorationStyleSetOpacity, decorationStyleSetStrikethrough, decorationStyleSetStrikethroughThickness, decorationStyleSetStrikethroughColor); glyph = this._viewGpuContext.atlas.getGlyph(this.glyphRasterizer, chars, tokenMetadata, decorationStyleSetId, absoluteOffsetX); absoluteOffsetY = Math.round( diff --git a/code/src/vs/editor/browser/gpu/renderStrategy/viewportRenderStrategy.ts b/code/src/vs/editor/browser/gpu/renderStrategy/viewportRenderStrategy.ts index 9dbfa48a53f..446fef8cbbc 100644 --- a/code/src/vs/editor/browser/gpu/renderStrategy/viewportRenderStrategy.ts +++ b/code/src/vs/editor/browser/gpu/renderStrategy/viewportRenderStrategy.ts @@ -209,6 +209,9 @@ export class ViewportRenderStrategy extends BaseRenderStrategy { let decorationStyleSetBold: boolean | undefined; let decorationStyleSetColor: number | undefined; let decorationStyleSetOpacity: number | undefined; + let decorationStyleSetStrikethrough: boolean | undefined; + let decorationStyleSetStrikethroughThickness: number | undefined; + let decorationStyleSetStrikethroughColor: number | undefined; let lineData: ViewLineRenderingData; let decoration: InlineDecoration; @@ -278,6 +281,9 @@ export class ViewportRenderStrategy extends BaseRenderStrategy { decorationStyleSetColor = undefined; decorationStyleSetBold = undefined; decorationStyleSetOpacity = undefined; + decorationStyleSetStrikethrough = undefined; + decorationStyleSetStrikethroughThickness = undefined; + decorationStyleSetStrikethroughColor = undefined; // Apply supported inline decoration styles to the cell metadata for (decoration of lineData.inlineDecorations) { @@ -322,6 +328,36 @@ export class ViewportRenderStrategy extends BaseRenderStrategy { decorationStyleSetOpacity = parsedValue; break; } + case 'text-decoration': + case 'text-decoration-line': { + if (value === 'line-through') { + decorationStyleSetStrikethrough = true; + } + break; + } + case 'text-decoration-thickness': { + const match = value.match(/^(\d+(?:\.\d+)?)px$/); + if (match) { + decorationStyleSetStrikethroughThickness = parseFloat(match[1]); + } + break; + } + case 'text-decoration-color': { + let colorValue = value; + const varMatch = value.match(/^var\((--[^,]+),\s*(?:initial|inherit)\)$/); + if (varMatch) { + colorValue = ViewGpuContext.decorationCssRuleExtractor.resolveCssVariable(this._viewGpuContext.canvas.domNode, varMatch[1]); + } + const parsedColor = Color.Format.CSS.parse(colorValue); + if (parsedColor) { + decorationStyleSetStrikethroughColor = parsedColor.toNumber32Bit(); + } + break; + } + case 'text-decoration-style': { + // These are validated in canRender and use default behavior + break; + } default: throw new BugIndicatingError('Unexpected inline decoration style'); } } @@ -346,7 +382,7 @@ export class ViewportRenderStrategy extends BaseRenderStrategy { continue; } - const decorationStyleSetId = ViewGpuContext.decorationStyleCache.getOrCreateEntry(decorationStyleSetColor, decorationStyleSetBold, decorationStyleSetOpacity); + const decorationStyleSetId = ViewGpuContext.decorationStyleCache.getOrCreateEntry(decorationStyleSetColor, decorationStyleSetBold, decorationStyleSetOpacity, decorationStyleSetStrikethrough, decorationStyleSetStrikethroughThickness, decorationStyleSetStrikethroughColor); glyph = this._viewGpuContext.atlas.getGlyph(this.glyphRasterizer, chars, tokenMetadata, decorationStyleSetId, absoluteOffsetX); absoluteOffsetY = Math.round( diff --git a/code/src/vs/editor/browser/gpu/viewGpuContext.ts b/code/src/vs/editor/browser/gpu/viewGpuContext.ts index 4333d262ff6..e3f24380846 100644 --- a/code/src/vs/editor/browser/gpu/viewGpuContext.ts +++ b/code/src/vs/editor/browser/gpu/viewGpuContext.ts @@ -6,6 +6,7 @@ import * as nls from '../../../nls.js'; import { addDisposableListener, getActiveWindow } from '../../../base/browser/dom.js'; import { createFastDomNode, type FastDomNode } from '../../../base/browser/fastDomNode.js'; +import { Color } from '../../../base/common/color.js'; import { BugIndicatingError } from '../../../base/common/errors.js'; import { Disposable } from '../../../base/common/lifecycle.js'; import type { ViewportData } from '../../common/viewLayout/viewLinesViewportData.js'; @@ -223,6 +224,7 @@ export class ViewGpuContext extends Disposable { } for (const r of rule.style) { if (!supportsCssRule(r, rule.style)) { + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any problemRules.push(`${r}: ${rule.style[r as any]}`); return false; } @@ -254,6 +256,11 @@ const gpuSupportedDecorationCssRules = [ 'color', 'font-weight', 'opacity', + 'text-decoration', + 'text-decoration-color', + 'text-decoration-line', + 'text-decoration-style', + 'text-decoration-thickness', ]; function supportsCssRule(rule: string, style: CSSStyleDeclaration) { @@ -262,6 +269,31 @@ function supportsCssRule(rule: string, style: CSSStyleDeclaration) { } // Check for values that aren't supported switch (rule) { + case 'text-decoration': + case 'text-decoration-line': { + const value = style.getPropertyValue(rule); + // Only line-through is supported currently + return value === 'line-through'; + } + case 'text-decoration-color': { + const value = style.getPropertyValue(rule); + // Support var(--something, initial/inherit) which falls back to currentcolor + if (/^var\(--[^,]+,\s*(?:initial|inherit)\)$/.test(value)) { + return true; + } + // Support parsed color values + return Color.Format.CSS.parse(value) !== null; + } + case 'text-decoration-style': { + const value = style.getPropertyValue(rule); + // Only 'initial' (solid) is supported + return value === 'initial'; + } + case 'text-decoration-thickness': { + const value = style.getPropertyValue(rule); + // Only pixel values and 'initial' are supported + return value === 'initial' || /^\d+(\.\d+)?px$/.test(value); + } default: return true; } } diff --git a/code/src/vs/editor/browser/observableCodeEditor.ts b/code/src/vs/editor/browser/observableCodeEditor.ts index 5af51f2cf79..6c7f5cddaa0 100644 --- a/code/src/vs/editor/browser/observableCodeEditor.ts +++ b/code/src/vs/editor/browser/observableCodeEditor.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { equalsIfDefined, itemsEquals } from '../../base/common/equals.js'; +import { equalsIfDefinedC, arrayEqualsC } from '../../base/common/equals.js'; import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../base/common/lifecycle.js'; -import { DebugLocation, IObservable, IObservableWithChange, ITransaction, TransactionImpl, autorun, autorunOpts, derived, derivedOpts, derivedWithSetter, observableFromEvent, observableSignal, observableValue, observableValueOpts } from '../../base/common/observable.js'; +import { DebugLocation, IObservable, IObservableWithChange, IReader, ITransaction, TransactionImpl, autorun, autorunOpts, derived, derivedOpts, derivedWithSetter, observableFromEvent, observableFromEventOpts, observableSignal, observableSignalFromEvent, observableValue, observableValueOpts } from '../../base/common/observable.js'; import { EditorOption, FindComputedEditorOptionValueById } from '../common/config/editorOptions.js'; import { LineRange } from '../common/core/ranges/lineRange.js'; import { OffsetRange } from '../common/core/ranges/offsetRange.js'; @@ -74,19 +74,19 @@ export class ObservableCodeEditor extends Disposable { this._currentTransaction = undefined; this._model = observableValue(this, this.editor.getModel()); this.model = this._model; - this.isReadonly = observableFromEvent(this, this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.readOnly)); + this.isReadonly = observableFromEventOpts({ owner: this, getTransaction: () => this._currentTransaction }, this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.readOnly)); this._versionId = observableValueOpts({ owner: this, lazy: true }, this.editor.getModel()?.getVersionId() ?? null); this.versionId = this._versionId; this._selections = observableValueOpts( - { owner: this, equalsFn: equalsIfDefined(itemsEquals(Selection.selectionsEqual)), lazy: true }, + { owner: this, equalsFn: equalsIfDefinedC(arrayEqualsC(Selection.selectionsEqual)), lazy: true }, this.editor.getSelections() ?? null ); this.selections = this._selections; this.positions = derivedOpts( - { owner: this, equalsFn: equalsIfDefined(itemsEquals(Position.equals)) }, + { owner: this, equalsFn: equalsIfDefinedC(arrayEqualsC(Position.equals)) }, reader => this.selections.read(reader)?.map(s => s.getStartPosition()) ?? null ); - this.isFocused = observableFromEvent(this, e => { + this.isFocused = observableFromEventOpts({ owner: this, getTransaction: () => this._currentTransaction }, e => { const d1 = this.editor.onDidFocusEditorWidget(e); const d2 = this.editor.onDidBlurEditorWidget(e); return { @@ -96,7 +96,7 @@ export class ObservableCodeEditor extends Disposable { } }; }, () => this.editor.hasWidgetFocus()); - this.isTextFocused = observableFromEvent(this, e => { + this.isTextFocused = observableFromEventOpts({ owner: this, getTransaction: () => this._currentTransaction }, e => { const d1 = this.editor.onDidFocusEditorText(e); const d2 = this.editor.onDidBlurEditorText(e); return { @@ -106,7 +106,7 @@ export class ObservableCodeEditor extends Disposable { } }; }, () => this.editor.hasTextFocus()); - this.inComposition = observableFromEvent(this, e => { + this.inComposition = observableFromEventOpts({ owner: this, getTransaction: () => this._currentTransaction }, e => { const d1 = this.editor.onDidCompositionStart(() => { e(undefined); }); @@ -132,22 +132,25 @@ export class ObservableCodeEditor extends Disposable { } ); this.valueIsEmpty = derived(this, reader => { this.versionId.read(reader); return this.editor.getModel()?.getValueLength() === 0; }); - this.cursorSelection = derivedOpts({ owner: this, equalsFn: equalsIfDefined(Selection.selectionsEqual) }, reader => this.selections.read(reader)?.[0] ?? null); + this.cursorSelection = derivedOpts({ owner: this, equalsFn: equalsIfDefinedC(Selection.selectionsEqual) }, reader => this.selections.read(reader)?.[0] ?? null); this.cursorPosition = derivedOpts({ owner: this, equalsFn: Position.equals }, reader => this.selections.read(reader)?.[0]?.getPosition() ?? null); this.cursorLineNumber = derived(this, reader => this.cursorPosition.read(reader)?.lineNumber ?? null); this.onDidType = observableSignal(this); this.onDidPaste = observableSignal(this); - this.scrollTop = observableFromEvent(this.editor.onDidScrollChange, () => this.editor.getScrollTop()); - this.scrollLeft = observableFromEvent(this.editor.onDidScrollChange, () => this.editor.getScrollLeft()); - this.layoutInfo = observableFromEvent(this.editor.onDidLayoutChange, () => this.editor.getLayoutInfo()); + this.scrollTop = observableFromEventOpts({ owner: this, getTransaction: () => this._currentTransaction }, this.editor.onDidScrollChange, () => this.editor.getScrollTop()); + this.scrollLeft = observableFromEventOpts({ owner: this, getTransaction: () => this._currentTransaction }, this.editor.onDidScrollChange, () => this.editor.getScrollLeft()); + this.layoutInfo = observableFromEventOpts({ owner: this, getTransaction: () => this._currentTransaction }, this.editor.onDidLayoutChange, () => this.editor.getLayoutInfo()); this.layoutInfoContentLeft = this.layoutInfo.map(l => l.contentLeft); this.layoutInfoDecorationsLeft = this.layoutInfo.map(l => l.decorationsLeft); this.layoutInfoWidth = this.layoutInfo.map(l => l.width); this.layoutInfoHeight = this.layoutInfo.map(l => l.height); this.layoutInfoMinimap = this.layoutInfo.map(l => l.minimap); this.layoutInfoVerticalScrollbarWidth = this.layoutInfo.map(l => l.verticalScrollbarWidth); - this.contentWidth = observableFromEvent(this.editor.onDidContentSizeChange, () => this.editor.getContentWidth()); - this.contentHeight = observableFromEvent(this.editor.onDidContentSizeChange, () => this.editor.getContentHeight()); + this.contentWidth = observableFromEventOpts({ owner: this, getTransaction: () => this._currentTransaction }, this.editor.onDidContentSizeChange, () => this.editor.getContentWidth()); + this.contentHeight = observableFromEventOpts({ owner: this, getTransaction: () => this._currentTransaction }, this.editor.onDidContentSizeChange, () => this.editor.getContentHeight()); + this._onDidChangeViewZones = observableSignalFromEvent(this, this.editor.onDidChangeViewZones); + this._onDidHiddenAreasChanged = observableSignalFromEvent(this, this.editor.onDidChangeHiddenAreas); + this._onDidLineHeightChanged = observableSignalFromEvent(this, this.editor.onDidChangeLineHeight); this._widgetCounter = 0; this.openedPeekWidgets = observableValue(this, 0); @@ -211,6 +214,22 @@ export class ObservableCodeEditor extends Disposable { }); } + /** + * Batches the transactions started by observableFromEvent. + * + * If the callback causes the editor to fire an event that updates + * an observable value backed by observableFromEvent (such as scrollTop etc.), + * then all such updates will be part of the same transaction. + */ + public transaction(cb: (tx: ITransaction) => T): T { + this._beginUpdate(); + try { + return cb(this._currentTransaction!); + } finally { + this._endUpdate(); + } + } + public forceUpdate(): void; public forceUpdate(cb: (tx: ITransaction) => T): T; public forceUpdate(cb?: (tx: ITransaction) => T): T { @@ -364,9 +383,26 @@ export class ObservableCodeEditor extends Disposable { }); } + /** + * Uses an approximation if the exact position cannot be determined. + */ + getLeftOfPosition(position: Position, reader: IReader | undefined): number { + this.layoutInfo.read(reader); + this.value.read(reader); + + let offset = this.editor.getOffsetForColumn(position.lineNumber, position.column); + if (offset === -1) { + // approximation + const typicalHalfwidthCharacterWidth = this.editor.getOption(EditorOption.fontInfo).typicalHalfwidthCharacterWidth; + const approximation = position.column * typicalHalfwidthCharacterWidth; + offset = approximation; + } + return offset; + } + public observePosition(position: IObservable, store: DisposableStore): IObservable { let pos = position.get(); - const result = observableValueOpts({ owner: this, debugName: () => `topLeftOfPosition${pos?.toString()}`, equalsFn: equalsIfDefined(Point.equals) }, new Point(0, 0)); + const result = observableValueOpts({ owner: this, debugName: () => `topLeftOfPosition${pos?.toString()}`, equalsFn: equalsIfDefinedC(Point.equals) }, new Point(0, 0)); const contentWidgetId = `observablePositionWidget` + (this._widgetCounter++); const domNode = document.createElement('div'); const w: IContentWidget = { @@ -376,6 +412,7 @@ export class ObservableCodeEditor extends Disposable { }, getId: () => contentWidgetId, allowEditorOverflow: false, + useDisplayNone: true, afterRender: (position, coordinate) => { const model = this._model.get(); if (model && pos && pos.lineNumber > model.getLineCount()) { @@ -456,6 +493,37 @@ export class ObservableCodeEditor extends Disposable { }); } + private readonly _onDidChangeViewZones; + private readonly _onDidHiddenAreasChanged; + private readonly _onDidLineHeightChanged; + + /** + * Get the vertical position (top offset) for the line's bottom w.r.t. to the first line. + */ + observeTopForLineNumber(lineNumber: number): IObservable { + return derived(reader => { + this.layoutInfo.read(reader); + this._onDidChangeViewZones.read(reader); + this._onDidHiddenAreasChanged.read(reader); + this._onDidLineHeightChanged.read(reader); + this._versionId.read(reader); + return this.editor.getTopForLineNumber(lineNumber); + }); + } + + /** + * Get the vertical position (top offset) for the line's bottom w.r.t. to the first line. + */ + observeBottomForLineNumber(lineNumber: number): IObservable { + return derived(reader => { + this.layoutInfo.read(reader); + this._onDidChangeViewZones.read(reader); + this._onDidHiddenAreasChanged.read(reader); + this._onDidLineHeightChanged.read(reader); + this._versionId.read(reader); + return this.editor.getBottomForLineNumber(lineNumber); + }); + } } interface IObservableOverlayWidget { diff --git a/code/src/vs/editor/browser/services/abstractCodeEditorService.ts b/code/src/vs/editor/browser/services/abstractCodeEditorService.ts index 42538d39b71..abb76fe6ebf 100644 --- a/code/src/vs/editor/browser/services/abstractCodeEditorService.ts +++ b/code/src/vs/editor/browser/services/abstractCodeEditorService.ts @@ -211,22 +211,22 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC } private readonly _transientWatchers = this._register(new DisposableMap()); - private readonly _modelProperties = new Map>(); + private readonly _modelProperties = new Map>(); - public setModelProperty(resource: URI, key: string, value: any): void { + public setModelProperty(resource: URI, key: string, value: unknown): void { const key1 = resource.toString(); - let dest: Map; + let dest: Map; if (this._modelProperties.has(key1)) { dest = this._modelProperties.get(key1)!; } else { - dest = new Map(); + dest = new Map(); this._modelProperties.set(key1, dest); } dest.set(key, value); } - public getModelProperty(resource: URI, key: string): any { + public getModelProperty(resource: URI, key: string): unknown { const key1 = resource.toString(); if (this._modelProperties.has(key1)) { const innerMap = this._modelProperties.get(key1)!; @@ -235,7 +235,7 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC return undefined; } - public setTransientModelProperty(model: ITextModel, key: string, value: any): void { + public setTransientModelProperty(model: ITextModel, key: string, value: unknown): void { const uri = model.uri.toString(); let w = this._transientWatchers.get(uri); @@ -251,7 +251,7 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC } } - public getTransientModelProperty(model: ITextModel, key: string): any { + public getTransientModelProperty(model: ITextModel, key: string): unknown { const uri = model.uri.toString(); const watcher = this._transientWatchers.get(uri); @@ -262,7 +262,7 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC return watcher.get(key); } - public getTransientModelProperties(model: ITextModel): [string, any][] | undefined { + public getTransientModelProperties(model: ITextModel): [string, unknown][] | undefined { const uri = model.uri.toString(); const watcher = this._transientWatchers.get(uri); @@ -297,7 +297,7 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC export class ModelTransientSettingWatcher extends Disposable { public readonly uri: string; - private readonly _values: { [key: string]: any }; + private readonly _values: { [key: string]: unknown }; constructor(uri: string, model: ITextModel, owner: AbstractCodeEditorService) { super(); @@ -307,11 +307,11 @@ export class ModelTransientSettingWatcher extends Disposable { this._register(model.onWillDispose(() => owner._removeWatcher(this))); } - public set(key: string, value: any): void { + public set(key: string, value: unknown): void { this._values[key] = value; } - public get(key: string): any { + public get(key: string): unknown { return this._values[key]; } @@ -826,7 +826,7 @@ class DecorationCSSRules { return cssTextArr.join(''); } - private collectBorderSettingsCSSText(opts: any, cssTextArr: string[]): boolean { + private collectBorderSettingsCSSText(opts: unknown, cssTextArr: string[]): boolean { if (this.collectCSSText(opts, ['border', 'borderColor', 'borderRadius', 'borderSpacing', 'borderStyle', 'borderWidth'], cssTextArr)) { cssTextArr.push(strings.format('box-sizing: border-box;')); return true; @@ -834,10 +834,10 @@ class DecorationCSSRules { return false; } - private collectCSSText(opts: any, properties: string[], cssTextArr: string[]): boolean { + private collectCSSText(opts: unknown, properties: string[], cssTextArr: string[]): boolean { const lenBefore = cssTextArr.length; for (const property of properties) { - const value = this.resolveValue(opts[property]); + const value = this.resolveValue((opts as Record)[property] as string | ThemeColor); if (typeof value === 'string') { cssTextArr.push(strings.format(_CSS_MAP[property], value)); } diff --git a/code/src/vs/editor/browser/services/bulkEditService.ts b/code/src/vs/editor/browser/services/bulkEditService.ts index bb0be7cd5d5..282760016b2 100644 --- a/code/src/vs/editor/browser/services/bulkEditService.ts +++ b/code/src/vs/editor/browser/services/bulkEditService.ts @@ -37,7 +37,7 @@ export class ResourceEdit { export class ResourceTextEdit extends ResourceEdit implements IWorkspaceTextEdit { - static is(candidate: any): candidate is IWorkspaceTextEdit { + static is(candidate: unknown): candidate is IWorkspaceTextEdit { if (candidate instanceof ResourceTextEdit) { return true; } @@ -66,7 +66,7 @@ export class ResourceTextEdit extends ResourceEdit implements IWorkspaceTextEdit export class ResourceFileEdit extends ResourceEdit implements IWorkspaceFileEdit { - static is(candidate: any): candidate is IWorkspaceFileEdit { + static is(candidate: unknown): candidate is IWorkspaceFileEdit { if (candidate instanceof ResourceFileEdit) { return true; } else { diff --git a/code/src/vs/editor/browser/services/codeEditorService.ts b/code/src/vs/editor/browser/services/codeEditorService.ts index 733018faea7..570d91f6c2f 100644 --- a/code/src/vs/editor/browser/services/codeEditorService.ts +++ b/code/src/vs/editor/browser/services/codeEditorService.ts @@ -43,18 +43,18 @@ export interface ICodeEditorService { */ getFocusedCodeEditor(): ICodeEditor | null; - registerDecorationType(description: string, key: string, options: IDecorationRenderOptions, parentTypeKey?: string, editor?: ICodeEditor): void; + registerDecorationType(description: string, key: string, options: IDecorationRenderOptions, parentTypeKey?: string, editor?: ICodeEditor): IDisposable; listDecorationTypes(): string[]; removeDecorationType(key: string): void; resolveDecorationOptions(typeKey: string, writable: boolean): IModelDecorationOptions; resolveDecorationCSSRules(decorationTypeKey: string): CSSRuleList | null; - setModelProperty(resource: URI, key: string, value: any): void; - getModelProperty(resource: URI, key: string): any; + setModelProperty(resource: URI, key: string, value: unknown): void; + getModelProperty(resource: URI, key: string): unknown; - setTransientModelProperty(model: ITextModel, key: string, value: any): void; - getTransientModelProperty(model: ITextModel, key: string): any; - getTransientModelProperties(model: ITextModel): [string, any][] | undefined; + setTransientModelProperty(model: ITextModel, key: string, value: unknown): void; + getTransientModelProperty(model: ITextModel, key: string): unknown; + getTransientModelProperties(model: ITextModel): [string, unknown][] | undefined; getActiveCodeEditor(): ICodeEditor | null; openCodeEditor(input: ITextResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise; diff --git a/code/src/vs/editor/browser/services/contribution.ts b/code/src/vs/editor/browser/services/contribution.ts new file mode 100644 index 00000000000..e1a39059b0d --- /dev/null +++ b/code/src/vs/editor/browser/services/contribution.ts @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { registerSingleton, InstantiationType } from '../../../platform/instantiation/common/extensions.js'; +import { IEditorWorkerService } from '../../common/services/editorWorker.js'; +import { EditorWorkerService } from './editorWorkerService.js'; + +registerSingleton(IEditorWorkerService, EditorWorkerService, InstantiationType.Eager /* registers link detection and word based suggestions for any document */); diff --git a/code/src/vs/editor/browser/services/editorWorkerService.ts b/code/src/vs/editor/browser/services/editorWorkerService.ts index 3475a737bec..e60fcf56735 100644 --- a/code/src/vs/editor/browser/services/editorWorkerService.ts +++ b/code/src/vs/editor/browser/services/editorWorkerService.ts @@ -7,7 +7,8 @@ import { timeout } from '../../../base/common/async.js'; import { Disposable, IDisposable } from '../../../base/common/lifecycle.js'; import { URI } from '../../../base/common/uri.js'; import { logOnceWebWorkerWarning, IWebWorkerClient, Proxied } from '../../../base/common/worker/webWorker.js'; -import { createWebWorker, IWebWorkerDescriptor } from '../../../base/browser/webWorkerFactory.js'; +import { WebWorkerDescriptor } from '../../../platform/webWorker/browser/webWorkerDescriptor.js'; +import { IWebWorkerService } from '../../../platform/webWorker/browser/webWorkerService.js'; import { Position } from '../../common/core/position.js'; import { IRange, Range } from '../../common/core/range.js'; import { ITextModel } from '../../common/model.js'; @@ -35,6 +36,7 @@ import { WorkerTextModelSyncClient } from '../../common/services/textModelSync/t import { EditorWorkerHost } from '../../common/services/editorWorkerHost.js'; import { StringEdit } from '../../common/core/edits/stringEdit.js'; import { OffsetRange } from '../../common/core/ranges/offsetRange.js'; +import { FileAccess } from '../../../base/common/network.js'; /** * Stop the worker if it was not needed for 5 min. @@ -52,7 +54,7 @@ function canSyncModel(modelService: IModelService, resource: URI): boolean { return true; } -export abstract class EditorWorkerService extends Disposable implements IEditorWorkerService { +export class EditorWorkerService extends Disposable implements IEditorWorkerService { declare readonly _serviceBrand: undefined; @@ -61,16 +63,23 @@ export abstract class EditorWorkerService extends Disposable implements IEditorW private readonly _logService: ILogService; constructor( - workerDescriptor: IWebWorkerDescriptor, @IModelService modelService: IModelService, @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, @ILogService logService: ILogService, @ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService, @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, + @IWebWorkerService private readonly _webWorkerService: IWebWorkerService, ) { super(); this._modelService = modelService; - this._workerManager = this._register(new WorkerManager(workerDescriptor, this._modelService)); + + const workerDescriptor = new WebWorkerDescriptor({ + esmModuleLocation: () => FileAccess.asBrowserUri('vs/editor/common/services/editorWebWorkerMain.js'), + esmModuleLocationBundler: () => new URL('../../common/services/editorWebWorkerMain.ts?workerModule', import.meta.url), + label: 'editorWorkerService' + }); + + this._workerManager = this._register(new WorkerManager(workerDescriptor, this._modelService, this._webWorkerService)); this._logService = logService; // register default link-provider and default completions-provider @@ -84,7 +93,7 @@ export abstract class EditorWorkerService extends Disposable implements IEditorW return links && { links }; } })); - this._register(languageFeaturesService.completionProvider.register('*', new WordBasedCompletionItemProvider(this._workerManager, configurationService, this._modelService, this._languageConfigurationService, this._logService))); + this._register(languageFeaturesService.completionProvider.register('*', new WordBasedCompletionItemProvider(this._workerManager, configurationService, this._modelService, this._languageConfigurationService, this._logService, languageFeaturesService))); } public override dispose(): void { @@ -254,7 +263,8 @@ class WordBasedCompletionItemProvider implements languages.CompletionItemProvide configurationService: ITextResourceConfigurationService, modelService: IModelService, private readonly languageConfigurationService: ILanguageConfigurationService, - private readonly logService: ILogService + private readonly logService: ILogService, + private readonly languageFeaturesService: ILanguageFeaturesService, ) { this._workerManager = workerManager; this._configurationService = configurationService; @@ -263,13 +273,17 @@ class WordBasedCompletionItemProvider implements languages.CompletionItemProvide async provideCompletionItems(model: ITextModel, position: Position): Promise { type WordBasedSuggestionsConfig = { - wordBasedSuggestions?: 'off' | 'currentDocument' | 'matchingDocuments' | 'allDocuments'; + wordBasedSuggestions?: 'off' | 'currentDocument' | 'matchingDocuments' | 'allDocuments' | 'offWithInlineSuggestions'; }; const config = this._configurationService.getValue(model.uri, position, 'editor'); if (config.wordBasedSuggestions === 'off') { return undefined; } + if (config.wordBasedSuggestions === 'offWithInlineSuggestions' && this.languageFeaturesService.inlineCompletionsProvider.has(model)) { + return undefined; + } + const models: URI[] = []; if (config.wordBasedSuggestions === 'currentDocument') { // only current file and only if not too large @@ -326,15 +340,18 @@ class WordBasedCompletionItemProvider implements languages.CompletionItemProvide class WorkerManager extends Disposable { private readonly _modelService: IModelService; + private readonly _webWorkerService: IWebWorkerService; private _editorWorkerClient: EditorWorkerClient | null; private _lastWorkerUsedTime: number; constructor( - private readonly _workerDescriptor: IWebWorkerDescriptor, - @IModelService modelService: IModelService + private readonly _workerDescriptor: WebWorkerDescriptor, + @IModelService modelService: IModelService, + @IWebWorkerService webWorkerService: IWebWorkerService ) { super(); this._modelService = modelService; + this._webWorkerService = webWorkerService; this._editorWorkerClient = null; this._lastWorkerUsedTime = (new Date()).getTime(); @@ -386,7 +403,7 @@ class WorkerManager extends Disposable { public withWorker(): Promise { this._lastWorkerUsedTime = (new Date()).getTime(); if (!this._editorWorkerClient) { - this._editorWorkerClient = new EditorWorkerClient(this._workerDescriptor, false, this._modelService); + this._editorWorkerClient = new EditorWorkerClient(this._workerDescriptor, false, this._modelService, this._webWorkerService); } return Promise.resolve(this._editorWorkerClient); } @@ -415,38 +432,41 @@ class SynchronousWorkerClient implements IWebWorkerClient } export interface IEditorWorkerClient { - fhr(method: string, args: any[]): Promise; + fhr(method: string, args: unknown[]): Promise; } export class EditorWorkerClient extends Disposable implements IEditorWorkerClient { private readonly _modelService: IModelService; + private readonly _webWorkerService: IWebWorkerService; private readonly _keepIdleModels: boolean; private _worker: IWebWorkerClient | null; private _modelManager: WorkerTextModelSyncClient | null; private _disposed = false; constructor( - private readonly _workerDescriptorOrWorker: IWebWorkerDescriptor | Worker | Promise, + private readonly _workerDescriptorOrWorker: WebWorkerDescriptor | Worker | Promise, keepIdleModels: boolean, @IModelService modelService: IModelService, + @IWebWorkerService webWorkerService: IWebWorkerService ) { super(); this._modelService = modelService; + this._webWorkerService = webWorkerService; this._keepIdleModels = keepIdleModels; this._worker = null; this._modelManager = null; } // foreign host request - public fhr(method: string, args: any[]): Promise { + public fhr(method: string, args: unknown[]): Promise { throw new Error(`Not implemented!`); } private _getOrCreateWorker(): IWebWorkerClient { if (!this._worker) { try { - this._worker = this._register(createWebWorker(this._workerDescriptorOrWorker)); + this._worker = this._register(this._webWorkerService.createWorkerClient(this._workerDescriptorOrWorker)); EditorWorkerHost.setChannel(this._worker, this._createEditorWorkerHost()); } catch (err) { logOnceWebWorkerWarning(err); diff --git a/code/src/vs/editor/browser/services/hoverService/hover.css b/code/src/vs/editor/browser/services/hoverService/hover.css deleted file mode 100644 index 96333ca1a39..00000000000 --- a/code/src/vs/editor/browser/services/hoverService/hover.css +++ /dev/null @@ -1,141 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-workbench .workbench-hover { - position: relative; - font-size: 13px; - line-height: 19px; - /* Must be higher than sash's z-index and terminal canvases */ - z-index: 40; - overflow: hidden; - max-width: 700px; - background: var(--vscode-editorHoverWidget-background); - border: 1px solid var(--vscode-editorHoverWidget-border); - border-radius: 3px; - color: var(--vscode-editorHoverWidget-foreground); - box-shadow: 0 2px 8px var(--vscode-widget-shadow); -} - -.monaco-workbench .workbench-hover .monaco-action-bar .action-item .codicon { - /* Given our font-size, adjust action icons accordingly */ - width: 13px; - height: 13px; -} - -.monaco-workbench .workbench-hover hr { - border-bottom: none; -} - -.monaco-workbench .workbench-hover.compact { - font-size: 12px; -} - -.monaco-workbench .workbench-hover.compact .monaco-action-bar .action-item .codicon { - /* Given our font-size, adjust action icons accordingly */ - width: 12px; - height: 12px; -} - -.monaco-workbench .workbench-hover.compact .hover-contents { - padding: 2px 8px; -} - -.monaco-workbench .workbench-hover-container.locked .workbench-hover { - outline: 1px solid var(--vscode-editorHoverWidget-border); -} -.monaco-workbench .workbench-hover-container:focus-within.locked .workbench-hover { - outline-color: var(--vscode-focusBorder); -} - -.monaco-workbench .workbench-hover-pointer { - position: absolute; - /* Must be higher than workbench hover z-index */ - z-index: 41; - pointer-events: none; -} - -.monaco-workbench .workbench-hover-pointer:after { - content: ''; - position: absolute; - width: 5px; - height: 5px; - background-color: var(--vscode-editorHoverWidget-background); - border-right: 1px solid var(--vscode-editorHoverWidget-border); - border-bottom: 1px solid var(--vscode-editorHoverWidget-border); -} -.monaco-workbench .workbench-hover-container:not(:focus-within).locked .workbench-hover-pointer:after { - width: 4px; - height: 4px; - border-right-width: 2px; - border-bottom-width: 2px; -} -.monaco-workbench .workbench-hover-container:focus-within .workbench-hover-pointer:after { - border-right: 1px solid var(--vscode-focusBorder); - border-bottom: 1px solid var(--vscode-focusBorder); -} - -.monaco-workbench .workbench-hover-pointer.left { left: -3px; } -.monaco-workbench .workbench-hover-pointer.right { right: 3px; } -.monaco-workbench .workbench-hover-pointer.top { top: -3px; } -.monaco-workbench .workbench-hover-pointer.bottom { bottom: 3px; } - -.monaco-workbench .workbench-hover-pointer.left:after { - transform: rotate(135deg); -} - -.monaco-workbench .workbench-hover-pointer.right:after { - transform: rotate(315deg); -} - -.monaco-workbench .workbench-hover-pointer.top:after { - transform: rotate(225deg); -} - -.monaco-workbench .workbench-hover-pointer.bottom:after { - transform: rotate(45deg); -} - -.monaco-workbench .workbench-hover a { - color: var(--vscode-textLink-foreground); -} - -.monaco-workbench .workbench-hover a:focus { - outline: 1px solid; - outline-offset: -1px; - text-decoration: underline; - outline-color: var(--vscode-focusBorder); -} - -.monaco-workbench .workbench-hover a.codicon:focus, -.monaco-workbench .workbench-hover a.monaco-button:focus { - text-decoration: none; -} - -.monaco-workbench .workbench-hover a:hover, -.monaco-workbench .workbench-hover a:active { - color: var(--vscode-textLink-activeForeground); -} - -.monaco-workbench .workbench-hover code { - background: var(--vscode-textCodeBlock-background); -} - -.monaco-workbench .workbench-hover .hover-row .actions { - background: var(--vscode-editorHoverWidget-statusBarBackground); -} - -.monaco-workbench .workbench-hover.right-aligned { - /* The context view service wraps strangely when it's right up against the edge without this */ - left: 1px; -} - -.monaco-workbench .workbench-hover.right-aligned .hover-row.status-bar .actions { - flex-direction: row-reverse; -} - -.monaco-workbench .workbench-hover.right-aligned .hover-row.status-bar .actions .action-container { - margin-right: 0; - margin-left: 16px; -} diff --git a/code/src/vs/editor/browser/services/inlineCompletionsService.ts b/code/src/vs/editor/browser/services/inlineCompletionsService.ts index a7416fcb73b..0f8fcc0cfdb 100644 --- a/code/src/vs/editor/browser/services/inlineCompletionsService.ts +++ b/code/src/vs/editor/browser/services/inlineCompletionsService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { WindowIntervalTimer } from '../../../base/browser/dom.js'; +import { TimeoutTimer } from '../../../base/common/async.js'; import { BugIndicatingError } from '../../../base/common/errors.js'; import { Emitter, Event } from '../../../base/common/event.js'; import { Disposable } from '../../../base/common/lifecycle.js'; @@ -21,7 +21,7 @@ export const IInlineCompletionsService = createDecorator; + readonly onDidChangeIsSnoozing: Event; /** * Get the remaining time (in ms) for which inline completions should be snoozed, @@ -73,7 +73,7 @@ export class InlineCompletionsService extends Disposable implements IInlineCompl return Math.max(0, this._snoozeTimeEnd - Date.now()); } - private _timer: WindowIntervalTimer; + private _timer: TimeoutTimer; constructor( @IContextKeyService private _contextKeyService: IContextKeyService, @@ -81,7 +81,7 @@ export class InlineCompletionsService extends Disposable implements IInlineCompl ) { super(); - this._timer = this._register(new WindowIntervalTimer()); + this._timer = this._register(new TimeoutTimer()); const inlineCompletionsSnoozing = InlineCompletionsSnoozing.bindTo(this._contextKeyService); this._register(this.onDidChangeIsSnoozing(() => inlineCompletionsSnoozing.set(this.isSnoozing()))); @@ -195,17 +195,17 @@ export class SnoozeInlineCompletion extends Action2 { const inlineCompletionsService = accessor.get(IInlineCompletionsService); const storageService = accessor.get(IStorageService); - let durationMinutes: number | undefined; + let durationMs: number | undefined; if (args.length > 0 && typeof args[0] === 'number') { - durationMinutes = args[0]; + durationMs = args[0] * 60_000; } - if (!durationMinutes) { - durationMinutes = await this.getDurationFromUser(quickInputService, storageService); + if (!durationMs) { + durationMs = await this.getDurationFromUser(quickInputService, storageService); } - if (durationMinutes) { - inlineCompletionsService.setSnoozeDuration(durationMinutes); + if (durationMs) { + inlineCompletionsService.setSnoozeDuration(durationMs); } } @@ -222,7 +222,7 @@ export class SnoozeInlineCompletion extends Action2 { ]; const picked = await quickInputService.pick(items, { - placeHolder: localize('snooze.placeholder', "Select snooze duration for Code completions and NES"), + placeHolder: localize('snooze.placeholder', "Select snooze duration for Inline Suggestions"), activeItem: items.find(item => item.value === lastSelectedDuration), }); diff --git a/code/src/vs/editor/browser/services/openerService.ts b/code/src/vs/editor/browser/services/openerService.ts index 64765857710..1453d666dfa 100644 --- a/code/src/vs/editor/browser/services/openerService.ts +++ b/code/src/vs/editor/browser/services/openerService.ts @@ -46,7 +46,7 @@ class CommandOpener implements IOpener { } // execute as command - let args: any = []; + let args: unknown[] = []; try { args = parse(decodeURIComponent(target.query)); } catch { @@ -170,10 +170,15 @@ export class OpenerService implements IOpenerService { } async open(target: URI | string, options?: OpenOptions): Promise { + const targetURI = typeof target === 'string' ? URI.parse(target) : target; + + // Internal schemes are not openable and must instead be handled in event listeners + if (targetURI.scheme === Schemas.internal) { + return false; + } // check with contributed validators if (!options?.skipValidation) { - const targetURI = typeof target === 'string' ? URI.parse(target) : target; const validationTarget = this._resolvedUriTargets.get(targetURI) ?? target; // validate against the original URI that this URI resolves to, if one exists for (const validator of this._validators) { if (!(await validator.shouldOpen(validationTarget, options))) { diff --git a/code/src/vs/editor/browser/triggerInlineEditCommandsRegistry.ts b/code/src/vs/editor/browser/triggerInlineEditCommandsRegistry.ts new file mode 100644 index 00000000000..21cd1c5fe1d --- /dev/null +++ b/code/src/vs/editor/browser/triggerInlineEditCommandsRegistry.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. + *--------------------------------------------------------------------------------------------*/ + +/** + * Registry for commands that can trigger Inline Edits (NES) when invoked. + */ +export abstract class TriggerInlineEditCommandsRegistry { + + private static REGISTERED_COMMANDS = new Set(); + + public static getRegisteredCommands(): readonly string[] { + return [...TriggerInlineEditCommandsRegistry.REGISTERED_COMMANDS]; + } + + public static registerCommand(commandId: string): void { + TriggerInlineEditCommandsRegistry.REGISTERED_COMMANDS.add(commandId); + } +} diff --git a/code/src/vs/editor/browser/view.ts b/code/src/vs/editor/browser/view.ts index ef187061795..913c7c970e2 100644 --- a/code/src/vs/editor/browser/view.ts +++ b/code/src/vs/editor/browser/view.ts @@ -293,7 +293,7 @@ export class View extends ViewEventHandler { if (usingExperimentalEditContext) { return this._instantiationService.createInstance(NativeEditContext, this._ownerID, this._context, this._overflowGuardContainer, this._viewController, this._createTextAreaHandlerHelper()); } else { - return this._instantiationService.createInstance(TextAreaEditContext, this._context, this._overflowGuardContainer, this._viewController, this._createTextAreaHandlerHelper()); + return this._instantiationService.createInstance(TextAreaEditContext, this._ownerID, this._context, this._overflowGuardContainer, this._viewController, this._createTextAreaHandlerHelper()); } } @@ -652,6 +652,15 @@ export class View extends ViewEventHandler { return visibleRange.left; } + public getLineWidth(modelLineNumber: number): number { + const model = this._context.viewModel.model; + const viewLine = this._context.viewModel.coordinatesConverter.convertModelPositionToViewPosition(new Position(modelLineNumber, model.getLineMaxColumn(modelLineNumber))).lineNumber; + this._flushAccumulatedAndRenderNow(); + const width = this._viewLines.getLineWidth(viewLine); + + return width; + } + public getTargetAtClientPoint(clientX: number, clientY: number): IMouseTarget | null { const mouseTarget = this._pointerHandler.getTargetAtClientPoint(clientX, clientY); if (!mouseTarget) { @@ -664,7 +673,7 @@ export class View extends ViewEventHandler { return new OverviewRuler(this._context, cssClassName); } - public change(callback: (changeAccessor: IViewZoneChangeAccessor) => any): void { + public change(callback: (changeAccessor: IViewZoneChangeAccessor) => unknown): void { this._viewZones.changeViewZones(callback); this._scheduleRender(); } @@ -723,7 +732,9 @@ export class View extends ViewEventHandler { widgetData.position?.preference ?? null, widgetData.position?.positionAffinity ?? null ); - this._scheduleRender(); + if (this._contentWidgets.shouldRender()) { + this._scheduleRender(); + } } public removeContentWidget(widgetData: IContentWidgetData): void { diff --git a/code/src/vs/editor/browser/view/domLineBreaksComputer.ts b/code/src/vs/editor/browser/view/domLineBreaksComputer.ts index 6eed0a076be..881275f34af 100644 --- a/code/src/vs/editor/browser/view/domLineBreaksComputer.ts +++ b/code/src/vs/editor/browser/view/domLineBreaksComputer.ts @@ -130,7 +130,7 @@ function createLineBreaks(targetWindow: Window, requests: string[], fontInfo: Fo containerDomNode.innerHTML = trustedhtml as string; containerDomNode.style.position = 'absolute'; - containerDomNode.style.top = '10000'; + containerDomNode.style.top = '10000px'; if (wordBreak === 'keepAll') { // word-break: keep-all; overflow-wrap: anywhere containerDomNode.style.wordBreak = 'keep-all'; diff --git a/code/src/vs/editor/browser/view/renderingContext.ts b/code/src/vs/editor/browser/view/renderingContext.ts index fdb24034701..1ed624ecfe4 100644 --- a/code/src/vs/editor/browser/view/renderingContext.ts +++ b/code/src/vs/editor/browser/view/renderingContext.ts @@ -87,7 +87,7 @@ export class RenderingContext extends RestrictedRenderingContext { public linesVisibleRangesForRange(range: Range, includeNewLines: boolean): LineVisibleRanges[] | null { const domRanges = this._viewLines.linesVisibleRangesForRange(range, includeNewLines); if (!this._viewLinesGpu) { - return domRanges ?? null; + return domRanges; } const gpuRanges = this._viewLinesGpu.linesVisibleRangesForRange(range, includeNewLines); if (!domRanges) { diff --git a/code/src/vs/editor/browser/view/viewLayer.ts b/code/src/vs/editor/browser/view/viewLayer.ts index 3f1b0905954..1a20b814445 100644 --- a/code/src/vs/editor/browser/view/viewLayer.ts +++ b/code/src/vs/editor/browser/view/viewLayer.ts @@ -546,7 +546,7 @@ class ViewLayerRenderer { if (wasInvalid[i]) { const source = hugeDomNode.firstChild; const lineDomNode = line.getDomNode()!; - lineDomNode.parentNode!.replaceChild(source, lineDomNode); + lineDomNode.replaceWith(source); line.setDomNode(source); } } diff --git a/code/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts b/code/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts index f6bd3004c86..6653405c375 100644 --- a/code/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts +++ b/code/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts @@ -116,7 +116,9 @@ export class ViewContentWidgets extends ViewPart { const myWidget = this._widgets[widget.getId()]; myWidget.setPosition(primaryAnchor, secondaryAnchor, preference, affinity); - this.setShouldRender(); + if (!myWidget.useDisplayNone) { + this.setShouldRender(); + } } public removeWidget(widget: IContentWidget): void { @@ -209,6 +211,7 @@ class Widget { private _isVisible: boolean; private _renderData: IRenderData | null; + public readonly useDisplayNone: boolean; constructor(context: ViewContext, viewDomNode: FastDomNode, actual: IContentWidget) { this._context = context; @@ -223,6 +226,7 @@ class Widget { this.id = this._actual.getId(); this.allowEditorOverflow = (this._actual.allowEditorOverflow || false) && allowOverflow; this.suppressMouseDown = this._actual.suppressMouseDown || false; + this.useDisplayNone = this._actual.useDisplayNone || false; this._fixedOverflowWidgets = options.get(EditorOption.fixedOverflowWidgets); this._contentWidth = layoutInfo.contentWidth; @@ -289,7 +293,7 @@ class Widget { public setPosition(primaryAnchor: IPosition | null, secondaryAnchor: IPosition | null, preference: ContentWidgetPositionPreference[] | null, affinity: PositionAffinity | null): void { this._setPosition(affinity, primaryAnchor, secondaryAnchor); this._preference = preference; - if (this._primaryAnchor.viewPosition && this._preference && this._preference.length > 0) { + if (!this.useDisplayNone && this._primaryAnchor.viewPosition && this._preference && this._preference.length > 0) { // this content widget would like to be visible if possible // we change it from `display:none` to `display:block` even if it // might be outside the viewport such that we can measure its size @@ -618,6 +622,7 @@ class AnchorCoordinate { ) { } } +// eslint-disable-next-line @typescript-eslint/no-explicit-any function safeInvoke any>(fn: T, thisArg: ThisParameterType, ...args: Parameters): ReturnType | null { try { return fn.call(thisArg, ...args); diff --git a/code/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts b/code/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts index 8d627025769..56143c6a32c 100644 --- a/code/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts +++ b/code/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts @@ -5,7 +5,7 @@ import './currentLineHighlight.css'; import { DynamicViewOverlay } from '../../view/dynamicViewOverlay.js'; -import { editorLineHighlight, editorLineHighlightBorder } from '../../../common/core/editorColorRegistry.js'; +import { editorLineHighlight, editorInactiveLineHighlight, editorLineHighlightBorder } from '../../../common/core/editorColorRegistry.js'; import { RenderingContext } from '../../view/renderingContext.js'; import { ViewContext } from '../../../common/viewModel/viewContext.js'; import * as viewEvents from '../../../common/viewEvents.js'; @@ -236,10 +236,20 @@ export class CurrentLineMarginHighlightOverlay extends AbstractLineHighlightOver registerThemingParticipant((theme, collector) => { const lineHighlight = theme.getColor(editorLineHighlight); + const inactiveLineHighlight = theme.getColor(editorInactiveLineHighlight); + + // Apply active line highlight when editor is focused if (lineHighlight) { - collector.addRule(`.monaco-editor .view-overlays .current-line { background-color: ${lineHighlight}; }`); - collector.addRule(`.monaco-editor .margin-view-overlays .current-line-margin { background-color: ${lineHighlight}; border: none; }`); + collector.addRule(`.monaco-editor.focused .view-overlays .current-line { background-color: ${lineHighlight}; }`); + collector.addRule(`.monaco-editor.focused .margin-view-overlays .current-line-margin { background-color: ${lineHighlight}; border: none; }`); + } + + // Apply inactive line highlight when editor is not focused + if (inactiveLineHighlight) { + collector.addRule(`.monaco-editor .view-overlays .current-line { background-color: ${inactiveLineHighlight}; }`); + collector.addRule(`.monaco-editor .margin-view-overlays .current-line-margin { background-color: ${inactiveLineHighlight}; border: none; }`); } + if (!lineHighlight || lineHighlight.isTransparent() || theme.defines(editorLineHighlightBorder)) { const lineHighlightBorder = theme.getColor(editorLineHighlightBorder); if (lineHighlightBorder) { diff --git a/code/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts b/code/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts index dd565eac9e4..875311054f8 100644 --- a/code/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts +++ b/code/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts @@ -94,8 +94,7 @@ export abstract class DedupOverlay extends DynamicViewOverlay { let prevClassName: string | null = null; let prevEndLineIndex = 0; - for (let i = 0, len = decorations.length; i < len; i++) { - const d = decorations[i]; + for (const d of decorations) { const className = d.className; const zIndex = d.zIndex; let startLineIndex = Math.max(d.startLineNumber, visibleStartLineNumber) - visibleStartLineNumber; @@ -110,8 +109,8 @@ export abstract class DedupOverlay extends DynamicViewOverlay { prevEndLineIndex = endLineIndex; } - for (let i = startLineIndex; i <= prevEndLineIndex; i++) { - output[i].add(new LineDecorationToRender(className, zIndex, d.tooltip)); + for (let lineIndex = startLineIndex; lineIndex <= prevEndLineIndex; lineIndex++) { + output[lineIndex].add(new LineDecorationToRender(className, zIndex, d.tooltip)); } } diff --git a/code/src/vs/editor/browser/viewParts/minimap/minimap.ts b/code/src/vs/editor/browser/viewParts/minimap/minimap.ts index 6fb1f36868b..ad53e1e16c1 100644 --- a/code/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/code/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -1663,7 +1663,7 @@ class InnerMinimap extends Disposable { continue; } highlightedLines.set(line, true); - const y = layout.getYForLineNumber(startLineNumber, minimapLineHeight); + const y = layout.getYForLineNumber(line, minimapLineHeight); canvasContext.fillRect(MINIMAP_GUTTER_WIDTH, y, canvasContext.canvas.width, minimapLineHeight); } } diff --git a/code/src/vs/editor/browser/viewParts/overlayWidgets/overlayWidgets.ts b/code/src/vs/editor/browser/viewParts/overlayWidgets/overlayWidgets.ts index a84da6b2b1d..d286e3b2074 100644 --- a/code/src/vs/editor/browser/viewParts/overlayWidgets/overlayWidgets.ts +++ b/code/src/vs/editor/browser/viewParts/overlayWidgets/overlayWidgets.ts @@ -124,7 +124,7 @@ export class ViewOverlayWidgets extends ViewPart { public setWidgetPosition(widget: IOverlayWidget, position: IOverlayWidgetPosition | null): boolean { const widgetData = this._widgets[widget.getId()]; const preference = position ? position.preference : null; - const stack = position?.stackOridinal; + const stack = position?.stackOrdinal; if (widgetData.preference === preference && widgetData.stack === stack) { this._updateMaxMinWidth(); return false; diff --git a/code/src/vs/editor/browser/viewParts/overviewRuler/overviewRuler.ts b/code/src/vs/editor/browser/viewParts/overviewRuler/overviewRuler.ts index 11292eb56a1..2c9deddd77c 100644 --- a/code/src/vs/editor/browser/viewParts/overviewRuler/overviewRuler.ts +++ b/code/src/vs/editor/browser/viewParts/overviewRuler/overviewRuler.ts @@ -136,7 +136,7 @@ export class OverviewRuler extends ViewEventHandler implements IOverviewRuler { private _renderOneLane(ctx: CanvasRenderingContext2D, colorZones: ColorZone[], id2Color: string[], width: number): void { - let currentColorId = 0; + let currentColorId = 0; // will never match a real color id which is > 0 let currentFrom = 0; let currentTo = 0; @@ -147,7 +147,9 @@ export class OverviewRuler extends ViewEventHandler implements IOverviewRuler { const zoneTo = zone.to; if (zoneColorId !== currentColorId) { - ctx.fillRect(0, currentFrom, width, currentTo - currentFrom); + if (currentColorId !== 0) { + ctx.fillRect(0, currentFrom, width, currentTo - currentFrom); + } currentColorId = zoneColorId; ctx.fillStyle = id2Color[currentColorId]; diff --git a/code/src/vs/editor/browser/viewParts/rulers/rulers.ts b/code/src/vs/editor/browser/viewParts/rulers/rulers.ts index c0a46927d17..f34f20f43a9 100644 --- a/code/src/vs/editor/browser/viewParts/rulers/rulers.ts +++ b/code/src/vs/editor/browser/viewParts/rulers/rulers.ts @@ -66,13 +66,11 @@ export class Rulers extends ViewPart { } if (currentCount < desiredCount) { - const { tabSize } = this._context.viewModel.model.getOptions(); - const rulerWidth = tabSize; let addCount = desiredCount - currentCount; while (addCount > 0) { const node = createFastDomNode(document.createElement('div')); node.setClassName('view-ruler'); - node.setWidth(rulerWidth); + node.setWidth('1px'); this.domNode.appendChild(node); this._renderedRulers.push(node); addCount--; diff --git a/code/src/vs/editor/browser/viewParts/scrollDecoration/scrollDecoration.ts b/code/src/vs/editor/browser/viewParts/scrollDecoration/scrollDecoration.ts index 71a9a7605c7..dc5dc300709 100644 --- a/code/src/vs/editor/browser/viewParts/scrollDecoration/scrollDecoration.ts +++ b/code/src/vs/editor/browser/viewParts/scrollDecoration/scrollDecoration.ts @@ -9,7 +9,7 @@ import { ViewPart } from '../../view/viewPart.js'; import { RenderingContext, RestrictedRenderingContext } from '../../view/renderingContext.js'; import { ViewContext } from '../../../common/viewModel/viewContext.js'; import * as viewEvents from '../../../common/viewEvents.js'; -import { EditorOption } from '../../../common/config/editorOptions.js'; +import { EditorOption, RenderMinimap } from '../../../common/config/editorOptions.js'; export class ScrollDecorationViewPart extends ViewPart { @@ -56,7 +56,7 @@ export class ScrollDecorationViewPart extends ViewPart { const options = this._context.configuration.options; const layoutInfo = options.get(EditorOption.layoutInfo); - if (layoutInfo.minimap.renderMinimap === 0 || (layoutInfo.minimap.minimapWidth > 0 && layoutInfo.minimap.minimapLeft === 0)) { + if (layoutInfo.minimap.renderMinimap === RenderMinimap.None || (layoutInfo.minimap.minimapWidth > 0 && layoutInfo.minimap.minimapLeft === 0)) { this._width = layoutInfo.width; } else { this._width = layoutInfo.width - layoutInfo.verticalScrollbarWidth; diff --git a/code/src/vs/editor/browser/viewParts/viewLines/domReadingContext.ts b/code/src/vs/editor/browser/viewParts/viewLines/domReadingContext.ts index 1a11700242e..c336dacbcf7 100644 --- a/code/src/vs/editor/browser/viewParts/viewLines/domReadingContext.ts +++ b/code/src/vs/editor/browser/viewParts/viewLines/domReadingContext.ts @@ -20,7 +20,8 @@ export class DomReadingContext { const rect = this._domNode.getBoundingClientRect(); this.markDidDomLayout(); this._clientRectDeltaLeft = rect.left; - this._clientRectScale = rect.width / this._domNode.offsetWidth; + const offsetWidth = this._domNode.offsetWidth; + this._clientRectScale = offsetWidth > 0 ? rect.width / offsetWidth : 1; } } diff --git a/code/src/vs/editor/browser/viewParts/viewLines/viewLine.ts b/code/src/vs/editor/browser/viewParts/viewLines/viewLine.ts index 927a9a39cb0..a3763b98430 100644 --- a/code/src/vs/editor/browser/viewParts/viewLines/viewLine.ts +++ b/code/src/vs/editor/browser/viewParts/viewLines/viewLine.ts @@ -704,7 +704,7 @@ function createNormalRenderedLine(domNode: FastDomNode | null, rend } export function getColumnOfNodeOffset(characterMapping: CharacterMapping, spanNode: HTMLElement, offset: number): number { - const spanNodeTextContentLength = spanNode.textContent!.length; + const spanNodeTextContentLength = spanNode.textContent.length; let spanIndex = -1; while (spanNode) { diff --git a/code/src/vs/editor/browser/viewParts/viewLines/viewLines.ts b/code/src/vs/editor/browser/viewParts/viewLines/viewLines.ts index ccf5bc01ef1..f7086529469 100644 --- a/code/src/vs/editor/browser/viewParts/viewLines/viewLines.ts +++ b/code/src/vs/editor/browser/viewParts/viewLines/viewLines.ts @@ -245,12 +245,10 @@ export class ViewLines extends ViewPart implements IViewLines { return r; } public override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { - if (true/*e.inlineDecorationsChanged*/) { - const rendStartLineNumber = this._visibleLines.getStartLineNumber(); - const rendEndLineNumber = this._visibleLines.getEndLineNumber(); - for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) { - this._visibleLines.getVisibleLine(lineNumber).onDecorationsChanged(); - } + const rendStartLineNumber = this._visibleLines.getStartLineNumber(); + const rendEndLineNumber = this._visibleLines.getEndLineNumber(); + for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) { + this._visibleLines.getVisibleLine(lineNumber).onDecorationsChanged(); } return true; } @@ -318,7 +316,7 @@ export class ViewLines extends ViewPart implements IViewLines { } } this.domNode.setWidth(e.scrollWidth); - return this._visibleLines.onScrollChanged(e) || true; + return this._visibleLines.onScrollChanged(e) || e.scrollTopChanged || e.scrollLeftChanged; } public override onTokensChanged(e: viewEvents.ViewTokensChangedEvent): boolean { @@ -415,12 +413,6 @@ export class ViewLines extends ViewPart implements IViewLines { } public linesVisibleRangesForRange(_range: Range, includeNewLines: boolean): LineVisibleRanges[] | null { - if (this.shouldRender()) { - // Cannot read from the DOM because it is dirty - // i.e. the model & the dom are out of sync, so I'd be reading something stale - return null; - } - const originalEndLineNumber = _range.endLineNumber; const range = Range.intersectRanges(_range, this._lastRenderedData.getCurrentVisibleRange()); if (!range) { @@ -480,12 +472,6 @@ export class ViewLines extends ViewPart implements IViewLines { } private _visibleRangesForLineRange(lineNumber: number, startColumn: number, endColumn: number): VisibleRanges | null { - if (this.shouldRender()) { - // Cannot read from the DOM because it is dirty - // i.e. the model & the dom are out of sync, so I'd be reading something stale - return null; - } - if (lineNumber < this._visibleLines.getStartLineNumber() || lineNumber > this._visibleLines.getEndLineNumber()) { return null; } @@ -541,7 +527,7 @@ export class ViewLines extends ViewPart implements IViewLines { // only proceed if we just did a layout return; } - if (this._asyncUpdateLineWidths.isScheduled()) { + if (!this._asyncUpdateLineWidths.isScheduled()) { // reading widths is not scheduled => widths are up-to-date return; } diff --git a/code/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts b/code/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts index dca6206ec1f..55ddb01f83d 100644 --- a/code/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts +++ b/code/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts @@ -240,7 +240,7 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { const module = this._device.createShaderModule({ label: 'Monaco shader module', - code: this._renderStrategy.value!.wgsl, + code: this._renderStrategy.value.wgsl, }); // #endregion Shader module diff --git a/code/src/vs/editor/browser/viewParts/viewZones/viewZones.ts b/code/src/vs/editor/browser/viewParts/viewZones/viewZones.ts index 4b94e5660d3..d26309116ce 100644 --- a/code/src/vs/editor/browser/viewParts/viewZones/viewZones.ts +++ b/code/src/vs/editor/browser/viewParts/viewZones/viewZones.ts @@ -196,7 +196,7 @@ export class ViewZones extends ViewPart { }; } - public changeViewZones(callback: (changeAccessor: IViewZoneChangeAccessor) => any): boolean { + public changeViewZones(callback: (changeAccessor: IViewZoneChangeAccessor) => void): boolean { let zonesHaveChanged = false; this._context.viewModel.changeWhitespace((whitespaceAccessor: IWhitespaceChangeAccessor) => { @@ -413,10 +413,11 @@ export class ViewZones extends ViewPart { } } -function safeInvoke1Arg(func: Function, arg1: any): any { +function safeInvoke1Arg(func: Function, arg1: unknown): unknown { try { return func(arg1); } catch (e) { onUnexpectedError(e); + return undefined; } } diff --git a/code/src/vs/editor/browser/viewParts/whitespace/whitespace.ts b/code/src/vs/editor/browser/viewParts/whitespace/whitespace.ts index 5e4aaddb3da..546d268130c 100644 --- a/code/src/vs/editor/browser/viewParts/whitespace/whitespace.ts +++ b/code/src/vs/editor/browser/viewParts/whitespace/whitespace.ts @@ -90,14 +90,6 @@ export class WhitespaceOverlay extends DynamicViewOverlay { return; } - const startLineNumber = ctx.visibleRange.startLineNumber; - const endLineNumber = ctx.visibleRange.endLineNumber; - const lineCount = endLineNumber - startLineNumber + 1; - const needed = new Array(lineCount); - for (let i = 0; i < lineCount; i++) { - needed[i] = true; - } - this._renderResult = []; for (let lineNumber = ctx.viewportData.startLineNumber; lineNumber <= ctx.viewportData.endLineNumber; lineNumber++) { const lineIndex = lineNumber - ctx.viewportData.startLineNumber; diff --git a/code/src/vs/editor/browser/widget/codeEditor/codeEditorContributions.ts b/code/src/vs/editor/browser/widget/codeEditor/codeEditorContributions.ts index 107fa40928c..93d8ed040f8 100644 --- a/code/src/vs/editor/browser/widget/codeEditor/codeEditorContributions.ts +++ b/code/src/vs/editor/browser/widget/codeEditor/codeEditorContributions.ts @@ -76,8 +76,8 @@ export class CodeEditorContributions extends Disposable { }, 5000)); } - public saveViewState(): { [key: string]: any } { - const contributionsState: { [key: string]: any } = {}; + public saveViewState(): { [key: string]: unknown } { + const contributionsState: { [key: string]: unknown } = {}; for (const [id, contribution] of this._instances) { if (typeof contribution.saveViewState === 'function') { contributionsState[id] = contribution.saveViewState(); @@ -86,7 +86,7 @@ export class CodeEditorContributions extends Disposable { return contributionsState; } - public restoreViewState(contributionsState: { [key: string]: any }): void { + public restoreViewState(contributionsState: { [key: string]: unknown }): void { for (const [id, contribution] of this._instances) { if (typeof contribution.restoreViewState === 'function') { contribution.restoreViewState(contributionsState[id]); diff --git a/code/src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts b/code/src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts index a770f47d708..6687e50593d 100644 --- a/code/src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts +++ b/code/src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts @@ -62,6 +62,7 @@ import { IThemeService, registerThemingParticipant } from '../../../../platform/ import { MenuId } from '../../../../platform/actions/common/actions.js'; import { TextModelEditSource, EditSources } from '../../../common/textModelEditSource.js'; import { TextEdit } from '../../../common/core/edits/textEdit.js'; +import { isObject } from '../../../../base/common/types.js'; export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeEditor { @@ -598,8 +599,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE if (!this._modelData) { return -1; } - const maxCol = this._modelData.model.getLineMaxColumn(lineNumber); - return CodeEditorWidget._getVerticalOffsetAfterPosition(this._modelData, lineNumber, maxCol, includeViewZones); + return CodeEditorWidget._getVerticalOffsetAfterPosition(this._modelData, lineNumber, Number.MAX_SAFE_INTEGER, includeViewZones); } public getLineHeightForPosition(position: IPosition): number { @@ -677,6 +677,13 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._modelData.viewModel.revealRange('api', revealHorizontal, viewRange, verticalType, scrollType); } + public revealAllCursors(revealHorizontal: boolean, minimalReveal?: boolean): void { + if (!this._modelData) { + return; + } + this._modelData.viewModel.revealAllCursors('api', revealHorizontal, minimalReveal); + } + public revealLine(lineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { this._revealLine(lineNumber, VerticalRevealType.Simple, scrollType); } @@ -773,7 +780,8 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE public setSelection(editorRange: Range, source?: string): void; public setSelection(selection: ISelection, source?: string): void; public setSelection(editorSelection: Selection, source?: string): void; - public setSelection(something: any, source: string = 'api'): void { + public setSelection(something: unknown, source?: string): void; + public setSelection(something: unknown, source: string = 'api'): void { const isSelection = Selection.isISelection(something); const isRange = Range.isIRange(something); @@ -782,7 +790,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } if (isSelection) { - this._setSelectionImpl(something, source); + this._setSelectionImpl(something, source); } else if (isRange) { // act as if it was an IRange const selection: ISelection = { @@ -1029,7 +1037,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } const codeEditorState = s as editorCommon.ICodeEditorViewState | null; if (codeEditorState && codeEditorState.cursorState && codeEditorState.viewState) { - const cursorState = codeEditorState.cursorState; + const cursorState = codeEditorState.cursorState; if (Array.isArray(cursorState)) { if (cursorState.length > 0) { this._modelData.viewModel.restoreCursorState(cursorState); @@ -1077,7 +1085,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return this._actions.get(id) || null; } - public trigger(source: string | null | undefined, handlerId: string, payload: any): void { + public trigger(source: string | null | undefined, handlerId: string, payload: unknown): void { payload = payload || {}; try { @@ -1136,7 +1144,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } } - protected _triggerCommand(handlerId: string, payload: any): void { + protected _triggerCommand(handlerId: string, payload: unknown): void { this._commandService.executeCommand(handlerId, payload); } @@ -1202,11 +1210,13 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._modelData.viewModel.cut(source); } - private _triggerEditorCommand(source: string | null | undefined, handlerId: string, payload: any): boolean { + private _triggerEditorCommand(source: string | null | undefined, handlerId: string, payload: unknown): boolean { const command = EditorExtensionsRegistry.getEditorCommand(handlerId); if (command) { payload = payload || {}; - payload.source = source; + if (isObject(payload)) { + (payload as { source: string | null | undefined }).source = source; + } this._instantiationService.invokeFunction((accessor) => { Promise.resolve(command.runEditorCommand(accessor, this, payload)).then(undefined, onUnexpectedError); }); @@ -1303,7 +1313,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return new EditorDecorationsCollection(this, decorations); } - public changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => any): any { + public changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): T | null { if (!this._modelData) { // callback will not be called return null; @@ -1432,7 +1442,12 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE delete this._decorationTypeKeysToIds[decorationTypeKey]; } if (this._decorationTypeSubtypes.hasOwnProperty(decorationTypeKey)) { + const items = this._decorationTypeSubtypes[decorationTypeKey]; + for (const subType of Object.keys(items)) { + this._removeDecorationType(decorationTypeKey + '-' + subType); + } delete this._decorationTypeSubtypes[decorationTypeKey]; + } } @@ -1658,6 +1673,13 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return this._modelData.view.getOffsetForColumn(lineNumber, column); } + public getWidthOfLine(lineNumber: number): number { + if (!this._modelData || !this._modelData.hasRealView) { + return -1; + } + return this._modelData.view.getLineWidth(lineNumber); + } + public render(forceRedraw: boolean = false): void { if (!this._modelData || !this._modelData.hasRealView) { return; @@ -1981,7 +2003,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return this._codeEditorService.resolveDecorationOptions(typeKey, writable); } - public getTelemetryData(): { [key: string]: any } | undefined { + public getTelemetryData(): object | undefined { return this._telemetryData; } @@ -2381,7 +2403,7 @@ class EditorDecorationsCollection implements editorCommon.IEditorDecorationsColl } } - public onDidChange(listener: (e: IModelDecorationsChangedEvent) => any, thisArgs?: any, disposables?: IDisposable[] | DisposableStore): IDisposable { + public onDidChange(listener: (e: IModelDecorationsChangedEvent) => unknown, thisArgs?: unknown, disposables?: IDisposable[] | DisposableStore): IDisposable { return this._editor.onDidChangeModelDecorations((e) => { if (this._isChangingDecorations) { return; diff --git a/code/src/vs/editor/browser/widget/diffEditor/commands.ts b/code/src/vs/editor/browser/widget/diffEditor/commands.ts index 0d0676fea62..425266acea8 100644 --- a/code/src/vs/editor/browser/widget/diffEditor/commands.ts +++ b/code/src/vs/editor/browser/widget/diffEditor/commands.ts @@ -173,12 +173,25 @@ export class RevertHunkOrSelection extends Action2 { super({ id: 'diffEditor.revert', title: localize2('revert', 'Revert'), - f1: false, + f1: true, category: diffEditorCategory, + precondition: ContextKeyExpr.has('isInDiffEditor'), }); } - run(accessor: ServicesAccessor, arg: DiffEditorSelectionHunkToolbarContext): unknown { + run(accessor: ServicesAccessor, arg?: DiffEditorSelectionHunkToolbarContext): unknown { + return arg ? this.runViaToolbarContext(accessor, arg) : this.runViaCursorOrSelection(accessor); + } + + runViaCursorOrSelection(accessor: ServicesAccessor): unknown { + const diffEditor = findFocusedDiffEditor(accessor); + if (diffEditor instanceof DiffEditorWidget) { + diffEditor.revertFocusedRangeMappings(); + } + return undefined; + } + + runViaToolbarContext(accessor: ServicesAccessor, arg: DiffEditorSelectionHunkToolbarContext): unknown { const diffEditor = findDiffEditor(accessor, arg.originalUri, arg.modifiedUri); if (diffEditor instanceof DiffEditorWidget) { diffEditor.revertRangeMappings(arg.mapping.innerChanges ?? []); diff --git a/code/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.ts b/code/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.ts index c5dac3172be..9660b641fdd 100644 --- a/code/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.ts +++ b/code/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.ts @@ -617,14 +617,14 @@ class View extends Disposable { if (item.modifiedLineNumber !== undefined) { let html: string | TrustedHTML = this._getLineHtml(modifiedModel, modifiedOptions, modifiedModelOpts.tabSize, item.modifiedLineNumber, this._languageService.languageIdCodec); if (AccessibleDiffViewer._ttPolicy) { - html = AccessibleDiffViewer._ttPolicy.createHTML(html as string); + html = AccessibleDiffViewer._ttPolicy.createHTML(html); } cell.insertAdjacentHTML('beforeend', html as string); lineContent = modifiedModel.getLineContent(item.modifiedLineNumber); } else { let html: string | TrustedHTML = this._getLineHtml(originalModel, originalOptions, originalModelOpts.tabSize, item.originalLineNumber, this._languageService.languageIdCodec); if (AccessibleDiffViewer._ttPolicy) { - html = AccessibleDiffViewer._ttPolicy.createHTML(html as string); + html = AccessibleDiffViewer._ttPolicy.createHTML(html); } cell.insertAdjacentHTML('beforeend', html as string); lineContent = originalModel.getLineContent(item.originalLineNumber); diff --git a/code/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts b/code/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts index 351715f2d1b..f0790e419d8 100644 --- a/code/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts +++ b/code/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts @@ -72,6 +72,7 @@ export class DiffEditorEditors extends Disposable { this.isModifiedFocused = observableCodeEditor(this.modified).isFocused; this.isFocused = derived(this, reader => this.isOriginalFocused.read(reader) || this.isModifiedFocused.read(reader)); + // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any this._argCodeEditorWidgetOptions = null as any; this._register(autorunHandleChanges({ diff --git a/code/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/copySelection.ts b/code/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/copySelection.ts new file mode 100644 index 00000000000..f138cbfc9de --- /dev/null +++ b/code/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/copySelection.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 { addDisposableListener } from '../../../../../../base/browser/dom.js'; +import { DisposableStore } from '../../../../../../base/common/lifecycle.js'; +import { Range } from '../../../../../common/core/range.js'; +import { DetailedLineRangeMapping } from '../../../../../common/diff/rangeMapping.js'; +import { ITextModel } from '../../../../../common/model.js'; +import { IClipboardService } from '../../../../../../platform/clipboard/common/clipboardService.js'; +import { RenderLinesResult } from './renderLines.js'; + +export interface IEnableViewZoneCopySelectionOptions { + /** The view zone HTML element that contains the deleted codes. */ + domNode: HTMLElement; + + /** The diff entry for the current view zone. */ + diffEntry: DetailedLineRangeMapping; + + /** The original text model, to get the original text based on selection. */ + originalModel: ITextModel; + + /** The render lines result that can translate DOM positions to model positions. */ + renderLinesResult: RenderLinesResult; + + /** The clipboard service to write the selected text to. */ + clipboardService: IClipboardService; +} + +export function enableCopySelection(options: IEnableViewZoneCopySelectionOptions): DisposableStore { + const { domNode, renderLinesResult, diffEntry, originalModel, clipboardService } = options; + const viewZoneDisposable = new DisposableStore(); + + viewZoneDisposable.add(addDisposableListener(domNode, 'copy', (e) => { + e.preventDefault(); + const selection = domNode.ownerDocument.getSelection(); + if (!selection || selection.rangeCount === 0) { + return; + } + + const domRange = selection.getRangeAt(0); + if (!domRange || domRange.collapsed) { + return; + } + + const startElement = domRange.startContainer.nodeType === Node.TEXT_NODE + ? domRange.startContainer.parentElement + : domRange.startContainer as HTMLElement; + const endElement = domRange.endContainer.nodeType === Node.TEXT_NODE + ? domRange.endContainer.parentElement + : domRange.endContainer as HTMLElement; + + if (!startElement || !endElement) { + return; + } + + const startPosition = renderLinesResult.getModelPositionAt(startElement, domRange.startOffset); + const endPosition = renderLinesResult.getModelPositionAt(endElement, domRange.endOffset); + + if (!startPosition || !endPosition) { + return; + } + + const adjustedStart = startPosition.delta(diffEntry.original.startLineNumber - 1); + const adjustedEnd = endPosition.delta(diffEntry.original.startLineNumber - 1); + + const range = adjustedEnd.isBefore(adjustedStart) ? + Range.fromPositions(adjustedEnd, adjustedStart) : + Range.fromPositions(adjustedStart, adjustedEnd); + + const selectedText = originalModel.getValueInRange(range); + clipboardService.writeText(selectedText); + })); + + return viewZoneDisposable; +} diff --git a/code/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts b/code/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts index cc9a23d23b3..62bf7ece01a 100644 --- a/code/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts +++ b/code/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts @@ -199,7 +199,7 @@ export class DiffEditorViewZones extends Disposable { originalModelTokenizationCompleted.read(reader); // Update view-zones once tokenization completes const deletedCodeDomNode = document.createElement('div'); - deletedCodeDomNode.classList.add('view-lines', 'line-delete', 'monaco-mouse-cursor-text'); + deletedCodeDomNode.classList.add('view-lines', 'line-delete', 'line-delete-selectable', 'monaco-mouse-cursor-text'); const originalModel = this._editors.original.getModel()!; // `a.originalRange` can be out of bound when the diff has not been updated yet. // In this case, we do an early return. @@ -241,10 +241,11 @@ export class DiffEditorViewZones extends Disposable { new InlineDiffDeletedCodeMargin( () => assertReturnsDefined(zoneId), marginDomNode, + deletedCodeDomNode, this._editors.modified, a.diff, this._diffEditorWidget, - result.viewLineCounts, + result, this._editors.original.getModel()!, this._contextMenuService, this._clipboardService, @@ -273,7 +274,7 @@ export class DiffEditorViewZones extends Disposable { marginDomNode, setZoneId(id) { zoneId = id; }, showInHiddenAreas: true, - suppressMouseDown: true, + suppressMouseDown: false, }); } @@ -395,8 +396,8 @@ export class DiffEditorViewZones extends Disposable { this._register(autorun(reader => { /** @description update scroll modified */ const newScrollTopModified = this._originalScrollTop.read(reader) - - (this._originalScrollOffsetAnimated.get() - this._modifiedScrollOffsetAnimated.read(reader)) - - (this._originalTopPadding.get() - this._modifiedTopPadding.read(reader)); + - (this._originalScrollOffsetAnimated.read(undefined) - this._modifiedScrollOffsetAnimated.read(reader)) + - (this._originalTopPadding.read(undefined) - this._modifiedTopPadding.read(reader)); if (newScrollTopModified !== this._editors.modified.getScrollTop()) { this._editors.modified.setScrollTop(newScrollTopModified, ScrollType.Immediate); } @@ -405,8 +406,8 @@ export class DiffEditorViewZones extends Disposable { this._register(autorun(reader => { /** @description update scroll original */ const newScrollTopOriginal = this._modifiedScrollTop.read(reader) - - (this._modifiedScrollOffsetAnimated.get() - this._originalScrollOffsetAnimated.read(reader)) - - (this._modifiedTopPadding.get() - this._originalTopPadding.read(reader)); + - (this._modifiedScrollOffsetAnimated.read(undefined) - this._originalScrollOffsetAnimated.read(reader)) + - (this._modifiedTopPadding.read(undefined) - this._originalTopPadding.read(reader)); if (newScrollTopOriginal !== this._editors.original.getScrollTop()) { this._editors.original.setScrollTop(newScrollTopOriginal, ScrollType.Immediate); } @@ -419,8 +420,8 @@ export class DiffEditorViewZones extends Disposable { let deltaOrigToMod = 0; if (m) { - const trueTopOriginal = this._editors.original.getTopForLineNumber(m.lineRangeMapping.original.startLineNumber, true) - this._originalTopPadding.get(); - const trueTopModified = this._editors.modified.getTopForLineNumber(m.lineRangeMapping.modified.startLineNumber, true) - this._modifiedTopPadding.get(); + const trueTopOriginal = this._editors.original.getTopForLineNumber(m.lineRangeMapping.original.startLineNumber, true) - this._originalTopPadding.read(undefined); + const trueTopModified = this._editors.modified.getTopForLineNumber(m.lineRangeMapping.modified.startLineNumber, true) - this._modifiedTopPadding.read(undefined); deltaOrigToMod = trueTopModified - trueTopOriginal; } @@ -438,9 +439,9 @@ export class DiffEditorViewZones extends Disposable { } if (this._editors.modified.hasTextFocus()) { - this._originalScrollOffset.set(this._modifiedScrollOffset.get() - deltaOrigToMod, undefined, true); + this._originalScrollOffset.set(this._modifiedScrollOffset.read(undefined) - deltaOrigToMod, undefined, true); } else { - this._modifiedScrollOffset.set(this._originalScrollOffset.get() + deltaOrigToMod, undefined, true); + this._modifiedScrollOffset.set(this._originalScrollOffset.read(undefined) + deltaOrigToMod, undefined, true); } })); } diff --git a/code/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/inlineDiffDeletedCodeMargin.ts b/code/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/inlineDiffDeletedCodeMargin.ts index fa63055ff08..22541178471 100644 --- a/code/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/inlineDiffDeletedCodeMargin.ts +++ b/code/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/inlineDiffDeletedCodeMargin.ts @@ -18,6 +18,8 @@ import { EndOfLineSequence, ITextModel } from '../../../../../common/model.js'; import { localize } from '../../../../../../nls.js'; import { IClipboardService } from '../../../../../../platform/clipboard/common/clipboardService.js'; import { IContextMenuService } from '../../../../../../platform/contextview/browser/contextView.js'; +import { enableCopySelection } from './copySelection.js'; +import { RenderLinesResult } from './renderLines.js'; export class InlineDiffDeletedCodeMargin extends Disposable { private readonly _diffActions: HTMLElement; @@ -38,10 +40,11 @@ export class InlineDiffDeletedCodeMargin extends Disposable { constructor( private readonly _getViewZoneId: () => string, private readonly _marginDomNode: HTMLElement, + private readonly _deletedCodeDomNode: HTMLElement, private readonly _modifiedEditor: CodeEditorWidget, private readonly _diff: DetailedLineRangeMapping, private readonly _editor: DiffEditorWidget, - private readonly _viewLineCounts: number[], + private readonly _renderLinesResult: RenderLinesResult, private readonly _originalTextModel: ITextModel, private readonly _contextMenuService: IContextMenuService, private readonly _clipboardService: IClipboardService, @@ -64,12 +67,13 @@ export class InlineDiffDeletedCodeMargin extends Disposable { let currentLineNumberOffset = 0; const useShadowDOM = _modifiedEditor.getOption(EditorOption.useShadowDOM) && !isIOS; // Do not use shadow dom on IOS #122035 - const showContextMenu = (x: number, y: number) => { + const showContextMenu = (anchor: { x: number; y: number }, baseActions?: Action[], onHide?: () => void) => { this._contextMenuService.showContextMenu({ domForShadowRoot: useShadowDOM ? _modifiedEditor.getDomNode() ?? undefined : undefined, - getAnchor: () => ({ x, y }), + getAnchor: () => anchor, + onHide, getActions: () => { - const actions: Action[] = []; + const actions: Action[] = baseActions ?? []; const isDeletion = _diff.modified.isEmpty; // default action @@ -135,7 +139,7 @@ export class InlineDiffDeletedCodeMargin extends Disposable { const { top, height } = getDomNodePagePosition(this._diffActions); const pad = Math.floor(lineHeight / 3); e.preventDefault(); - showContextMenu(e.posx, top + height + pad); + showContextMenu({ x: e.posx, y: top + height + pad }); })); this._register(_modifiedEditor.onMouseMove((e: IEditorMouseEvent) => { @@ -147,18 +151,12 @@ export class InlineDiffDeletedCodeMargin extends Disposable { } })); - this._register(_modifiedEditor.onMouseDown((e: IEditorMouseEvent) => { - if (!e.event.leftButton) { return; } - - if (e.target.type === MouseTargetType.CONTENT_VIEW_ZONE || e.target.type === MouseTargetType.GUTTER_VIEW_ZONE) { - const viewZoneId = e.target.detail.viewZoneId; - - if (viewZoneId === this._getViewZoneId()) { - e.event.preventDefault(); - currentLineNumberOffset = this._updateLightBulbPosition(this._marginDomNode, e.event.browserEvent.y, lineHeight); - showContextMenu(e.event.posx, e.event.posy + lineHeight); - } - } + this._register(enableCopySelection({ + domNode: this._deletedCodeDomNode, + diffEntry: _diff, + originalModel: this._originalTextModel, + renderLinesResult: this._renderLinesResult, + clipboardService: _clipboardService, })); } @@ -168,10 +166,10 @@ export class InlineDiffDeletedCodeMargin extends Disposable { const lineNumberOffset = Math.floor(offset / lineHeight); const newTop = lineNumberOffset * lineHeight; this._diffActions.style.top = `${newTop}px`; - if (this._viewLineCounts) { + if (this._renderLinesResult.viewLineCounts) { let acc = 0; - for (let i = 0; i < this._viewLineCounts.length; i++) { - acc += this._viewLineCounts[i]; + for (let i = 0; i < this._renderLinesResult.viewLineCounts.length; i++) { + acc += this._renderLinesResult.viewLineCounts[i]; if (lineNumberOffset < acc) { return i; } diff --git a/code/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/renderLines.ts b/code/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/renderLines.ts index 6d54628cc0d..fc04faf7104 100644 --- a/code/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/renderLines.ts +++ b/code/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/renderLines.ts @@ -8,13 +8,15 @@ import { applyFontInfo } from '../../../../config/domFontInfo.js'; import { ICodeEditor } from '../../../../editorBrowser.js'; import { EditorFontLigatures, EditorOption, FindComputedEditorOptionValueById } from '../../../../../common/config/editorOptions.js'; import { FontInfo } from '../../../../../common/config/fontInfo.js'; +import { Position } from '../../../../../common/core/position.js'; import { StringBuilder } from '../../../../../common/core/stringBuilder.js'; import { ModelLineProjectionData } from '../../../../../common/modelLineProjectionData.js'; import { IViewLineTokens, LineTokens } from '../../../../../common/tokens/lineTokens.js'; import { LineDecoration } from '../../../../../common/viewLayout/lineDecorations.js'; -import { RenderLineInput, renderViewLine } from '../../../../../common/viewLayout/viewLineRenderer.js'; +import { CharacterMapping, ForeignElementType, RenderLineInput, RenderLineOutput, renderViewLine } from '../../../../../common/viewLayout/viewLineRenderer.js'; import { ViewLineRenderingData } from '../../../../../common/viewModel.js'; import { InlineDecoration } from '../../../../../common/viewModel/inlineDecorations.js'; +import { getColumnOfNodeOffset } from '../../../../viewParts/viewLines/viewLine.js'; const ttPolicy = createTrustedTypesPolicy('diffEditorWidget', { createHTML: value => value }); @@ -27,6 +29,7 @@ export function renderLines(source: LineSource, options: RenderOptions, decorati let maxCharsPerLine = 0; let renderedLineCount = 0; const viewLineCounts: number[] = []; + const renderOutputs: RenderLineOutputWithOffset[] = []; for (let lineIndex = 0; lineIndex < source.lineTokens.length; lineIndex++) { const lineNumber = lineIndex + 1; const lineTokens = source.lineTokens[lineIndex]; @@ -37,7 +40,7 @@ export function renderLines(source: LineSource, options: RenderOptions, decorati let lastBreakOffset = 0; for (const breakOffset of lineBreakData.breakOffsets) { const viewLineTokens = lineTokens.sliceAndInflate(lastBreakOffset, breakOffset, 0); - maxCharsPerLine = Math.max(maxCharsPerLine, renderOriginalLine( + const result = renderOriginalLine( renderedLineCount, viewLineTokens, LineDecoration.extractWrapped(actualDecorations, lastBreakOffset, breakOffset), @@ -47,14 +50,16 @@ export function renderLines(source: LineSource, options: RenderOptions, decorati options, sb, noExtra, - )); + ); + maxCharsPerLine = Math.max(maxCharsPerLine, result.maxCharWidth); + renderOutputs.push(new RenderLineOutputWithOffset(result.output.characterMapping, result.output.containsForeignElements, lastBreakOffset)); renderedLineCount++; lastBreakOffset = breakOffset; } viewLineCounts.push(lineBreakData.breakOffsets.length); } else { viewLineCounts.push(1); - maxCharsPerLine = Math.max(maxCharsPerLine, renderOriginalLine( + const result = renderOriginalLine( renderedLineCount, lineTokens, actualDecorations, @@ -64,7 +69,9 @@ export function renderLines(source: LineSource, options: RenderOptions, decorati options, sb, noExtra, - )); + ); + maxCharsPerLine = Math.max(maxCharsPerLine, result.maxCharWidth); + renderOutputs.push(new RenderLineOutputWithOffset(result.output.characterMapping, result.output.containsForeignElements, 0)); renderedLineCount++; } } @@ -75,14 +82,15 @@ export function renderLines(source: LineSource, options: RenderOptions, decorati domNode.innerHTML = trustedhtml as string; const minWidthInPx = (maxCharsPerLine * options.typicalHalfwidthCharacterWidth); - return { - heightInLines: renderedLineCount, + return new RenderLinesResult( + renderedLineCount, minWidthInPx, viewLineCounts, - }; + renderOutputs, + source, + ); } - export class LineSource { constructor( public readonly lineTokens: LineTokens[], @@ -170,10 +178,83 @@ export class RenderOptions { } } -export interface RenderLinesResult { - minWidthInPx: number; - heightInLines: number; - viewLineCounts: number[]; +export class RenderLinesResult { + constructor( + public readonly heightInLines: number, + public readonly minWidthInPx: number, + public readonly viewLineCounts: number[], + private readonly _renderOutputs: RenderLineOutputWithOffset[], + private readonly _source: LineSource, + ) { } + + /** + * Returns the model position for a given DOM node and offset within that node. + * @param domNode The span node within a view-line where the offset is located + * @param offset The offset within the span node + * @returns The Position in the model, or undefined if the position cannot be determined + */ + public getModelPositionAt(domNode: HTMLElement, offset: number): Position | undefined { + // Find the view-line element that contains this span + let viewLineElement: HTMLElement | null = domNode; + while (viewLineElement && !viewLineElement.classList.contains('view-line')) { + viewLineElement = viewLineElement.parentElement; + } + + if (!viewLineElement) { + return undefined; + } + + // Find the container that has all view lines + const container = viewLineElement.parentElement; + if (!container) { + return undefined; + } + + // Find the view line index based on the element + // eslint-disable-next-line no-restricted-syntax + const viewLines = container.querySelectorAll('.view-line'); + let viewLineIndex = -1; + for (let i = 0; i < viewLines.length; i++) { + if (viewLines[i] === viewLineElement) { + viewLineIndex = i; + break; + } + } + + if (viewLineIndex === -1 || viewLineIndex >= this._renderOutputs.length) { + return undefined; + } + + // Map view line index back to model line + let modelLineNumber = 1; + let remainingViewLines = viewLineIndex; + for (let i = 0; i < this.viewLineCounts.length; i++) { + if (remainingViewLines < this.viewLineCounts[i]) { + modelLineNumber = i + 1; + break; + } + remainingViewLines -= this.viewLineCounts[i]; + } + + if (modelLineNumber > this._source.lineTokens.length) { + return undefined; + } + + const renderOutput = this._renderOutputs[viewLineIndex]; + if (!renderOutput) { + return undefined; + } + + const column = getColumnOfNodeOffset(renderOutput.characterMapping, domNode, offset) + renderOutput.offset; + + return new Position(modelLineNumber, column); + } +} + +class RenderLineOutputWithOffset extends RenderLineOutput { + constructor(characterMapping: CharacterMapping, containsForeignElements: ForeignElementType, public readonly offset: number) { + super(characterMapping, containsForeignElements); + } } function renderOriginalLine( @@ -186,7 +267,7 @@ function renderOriginalLine( options: RenderOptions, sb: StringBuilder, noExtra: boolean, -): number { +): { output: RenderLineOutput; maxCharWidth: number } { sb.appendString('