microsoft/vscode
13 workflows · maturity 50% · 9 patterns · GitHub ↗
Practices
○ Matrix✓ Permissions○ Security scan○ AI review✓ Cache✓ Concurrency✓ Reusable workflows
Detected patterns
Security dimensions
Workflows (13)
api-proposal-version-check perms .github/workflows/api-proposal-version-check.yml
View raw YAML
name: API Proposal Version Check
on:
pull_request:
branches:
- main
- 'release/*'
paths:
- 'src/vscode-dts/vscode.proposed.*.d.ts'
issue_comment:
types: [created]
permissions:
contents: read
pull-requests: write
actions: write
concurrency:
group: api-proposal-${{ github.event.pull_request.number || github.event.issue.number }}
cancel-in-progress: true
jobs:
check-version-changes:
name: Check API Proposal Version Changes
# Run on PR events, or on issue_comment if it's on a PR and contains the override command
if: |
github.event_name == 'pull_request' ||
(github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
contains(github.event.comment.body, '/api-proposal-change-required') &&
(github.event.comment.author_association == 'OWNER' ||
github.event.comment.author_association == 'MEMBER' ||
github.event.comment.author_association == 'COLLABORATOR'))
runs-on: ubuntu-latest
steps:
- name: Get PR info
id: pr_info
uses: actions/github-script@v8
with:
script: |
let prNumber, headSha, baseSha;
if (context.eventName === 'pull_request') {
prNumber = context.payload.pull_request.number;
headSha = context.payload.pull_request.head.sha;
baseSha = context.payload.pull_request.base.sha;
} else {
// issue_comment event - need to fetch PR details
prNumber = context.payload.issue.number;
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});
headSha = pr.head.sha;
baseSha = pr.base.sha;
}
core.setOutput('number', prNumber);
core.setOutput('head_sha', headSha);
core.setOutput('base_sha', baseSha);
- name: Check for override comment
id: check_override
uses: actions/github-script@v8
with:
script: |
const prNumber = ${{ steps.pr_info.outputs.number }};
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber
});
// Only accept overrides from trusted users (repo members/collaborators)
const trustedAssociations = ['OWNER', 'MEMBER', 'COLLABORATOR'];
let overrideComment = null;
const untrustedOverrides = [];
comments.forEach((comment, index) => {
const hasOverrideText = comment.body.includes('/api-proposal-change-required');
const isTrusted = trustedAssociations.includes(comment.author_association);
console.log(`Comment ${index + 1}:`);
console.log(` Author: ${comment.user.login}`);
console.log(` Author association: ${comment.author_association}`);
console.log(` Created at: ${comment.created_at}`);
console.log(` Contains override command: ${hasOverrideText}`);
console.log(` Author is trusted: ${isTrusted}`);
console.log(` Would be valid override: ${hasOverrideText && isTrusted}`);
if (hasOverrideText) {
if (isTrusted && !overrideComment) {
overrideComment = comment;
} else if (!isTrusted) {
untrustedOverrides.push(comment);
}
}
});
if (overrideComment) {
console.log(`✅ Override comment FOUND`);
console.log(` Comment ID: ${overrideComment.id}`);
console.log(` Author: ${overrideComment.user.login}`);
console.log(` Association: ${overrideComment.author_association}`);
console.log(` Created at: ${overrideComment.created_at}`);
core.setOutput('override_found', 'true');
core.setOutput('override_user', overrideComment.user.login);
} else {
if (untrustedOverrides.length > 0) {
console.log(`⚠️ Found ${untrustedOverrides.length} override comment(s) from UNTRUSTED user(s):`);
untrustedOverrides.forEach((comment, index) => {
console.log(` Untrusted override ${index + 1}:`);
console.log(` Author: ${comment.user.login}`);
console.log(` Association: ${comment.author_association}`);
console.log(` Created at: ${comment.created_at}`);
console.log(` Comment ID: ${comment.id}`);
});
console.log(` Trusted associations are: ${trustedAssociations.join(', ')}`);
}
console.log('❌ No valid override comment found');
core.setOutput('override_found', 'false');
}
# If triggered by the override comment, re-run the failed workflow to update its status
# Only allow trusted users to trigger re-runs to prevent spam
- name: Re-run failed workflow on override
if: |
steps.check_override.outputs.override_found == 'true' &&
github.event_name == 'issue_comment' &&
(github.event.comment.author_association == 'OWNER' ||
github.event.comment.author_association == 'MEMBER' ||
github.event.comment.author_association == 'COLLABORATOR')
uses: actions/github-script@v8
with:
script: |
const headSha = '${{ steps.pr_info.outputs.head_sha }}';
console.log(`Override comment found by ${{ steps.check_override.outputs.override_user }}`);
console.log('API proposal version change has been acknowledged.');
// Find the failed workflow run for this PR's head SHA
const { data: runs } = await github.rest.actions.listWorkflowRuns({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'api-proposal-version-check.yml',
head_sha: headSha,
status: 'completed',
per_page: 10
});
// Find the most recent failed run
const failedRun = runs.workflow_runs.find(run =>
run.conclusion === 'failure' && run.event === 'pull_request'
);
if (failedRun) {
console.log(`Re-running failed workflow run ${failedRun.id}`);
await github.rest.actions.reRunWorkflow({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: failedRun.id
});
console.log('Workflow re-run triggered successfully');
} else {
console.log('No failed pull_request workflow run found to re-run');
// The check will pass on this run since override exists
}
- name: Pass on override comment
if: steps.check_override.outputs.override_found == 'true'
run: |
echo "Override comment found by ${{ steps.check_override.outputs.override_user }}"
echo "API proposal version change has been acknowledged."
# Only continue checking if no override found
- name: Checkout repository
if: steps.check_override.outputs.override_found != 'true'
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Check for version changes
if: steps.check_override.outputs.override_found != 'true'
id: version_check
env:
HEAD_SHA: ${{ steps.pr_info.outputs.head_sha }}
BASE_SHA: ${{ steps.pr_info.outputs.base_sha }}
run: |
set -e
# Use merge-base to get accurate diff of what the PR actually changes
MERGE_BASE=$(git merge-base "$BASE_SHA" "$HEAD_SHA")
echo "Merge base: $MERGE_BASE"
# Get the list of changed proposed API files (diff against merge-base)
CHANGED_FILES=$(git diff --name-only "$MERGE_BASE" "$HEAD_SHA" -- 'src/vscode-dts/vscode.proposed.*.d.ts' || true)
if [ -z "$CHANGED_FILES" ]; then
echo "No proposed API files changed"
echo "version_changed=false" >> $GITHUB_OUTPUT
exit 0
fi
echo "Changed proposed API files:"
echo "$CHANGED_FILES"
VERSION_CHANGED="false"
CHANGED_LIST=""
for FILE in $CHANGED_FILES; do
# Check if file exists in head
if ! git cat-file -e "$HEAD_SHA:$FILE" 2>/dev/null; then
echo "File $FILE was deleted, skipping version check"
continue
fi
# Get version from head (current PR)
HEAD_VERSION=$(git show "$HEAD_SHA:$FILE" | grep -E '^// version: [0-9]+' | sed 's/.*version: //' || echo "")
# Get version from merge-base (what the PR is based on)
BASE_VERSION=$(git show "$MERGE_BASE:$FILE" 2>/dev/null | grep -E '^// version: [0-9]+' | sed 's/.*version: //' || echo "")
echo "File: $FILE"
echo " Base version: ${BASE_VERSION:-'(none)'}"
echo " Head version: ${HEAD_VERSION:-'(none)'}"
# Check if version was added or changed
if [ -n "$HEAD_VERSION" ] && [ "$HEAD_VERSION" != "$BASE_VERSION" ]; then
echo " -> Version changed!"
VERSION_CHANGED="true"
FILENAME=$(basename "$FILE")
if [ -n "$CHANGED_LIST" ]; then
CHANGED_LIST="$CHANGED_LIST, $FILENAME"
else
CHANGED_LIST="$FILENAME"
fi
fi
done
echo "version_changed=$VERSION_CHANGED" >> $GITHUB_OUTPUT
echo "changed_files=$CHANGED_LIST" >> $GITHUB_OUTPUT
- name: Post warning comment
if: steps.check_override.outputs.override_found != 'true' && steps.version_check.outputs.version_changed == 'true'
uses: actions/github-script@v8
with:
script: |
const prNumber = ${{ steps.pr_info.outputs.number }};
const changedFiles = '${{ steps.version_check.outputs.changed_files }}';
// Check if we already posted a warning comment
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber
});
const marker = '<!-- api-proposal-version-warning -->';
const existingComment = comments.find(comment =>
comment.body.includes(marker)
);
const body = `${marker}
## ⚠️ API Proposal Version Change Detected
The following proposed API files have version changes: **${changedFiles}**
API proposal version changes should only be used when maintaining compatibility is not possible. Consider keeping the version as is and maintaining backward compatibility.
**Any version changes must be adopted by the consuming extensions before the next insiders for the extension to work.**
---
If the version change is required, comment \`/api-proposal-change-required\` to unblock this check and acknowledge that you will update any critical consuming extensions (Copilot Chat).`;
if (existingComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existingComment.id,
body: body
});
console.log('Updated existing warning comment');
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: body
});
console.log('Posted new warning comment');
}
- name: Fail if version changed without override
if: steps.check_override.outputs.override_found != 'true' && steps.version_check.outputs.version_changed == 'true'
run: |
echo "::error::API proposal version changed in: ${{ steps.version_check.outputs.changed_files }}"
echo "To unblock, comment '/api-proposal-change-required' on the PR."
exit 1
copilot-setup-steps .github/workflows/copilot-setup-steps.yml
View raw YAML
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()
monaco-editor perms .github/workflows/monaco-editor.yml
View raw YAML
name: Monaco Editor checks
on:
push:
branches:
- main
- release/*
pull_request:
branches:
- main
- release/*
permissions: {}
jobs:
main:
name: Monaco Editor checks
runs-on: ubuntu-latest
timeout-minutes: 40
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- 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.ts)" >> $GITHUB_OUTPUT
- name: Cache node modules
id: cacheNodeModules
uses: actions/cache@v5
with:
path: "**/node_modules"
key: ${{ runner.os }}-cacheNodeModules20-${{ steps.nodeModulesCacheKey.outputs.value }}
restore-keys: ${{ runner.os }}-cacheNodeModules20-
- name: Get npm cache directory path
id: npmCacheDirPath
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
- name: Cache npm directory
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
uses: actions/cache@v5
with:
path: ${{ steps.npmCacheDirPath.outputs.dir }}
key: ${{ runner.os }}-npmCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }}
restore-keys: ${{ runner.os }}-npmCacheDir-
- name: Install system dependencies
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
run: |
sudo apt update
sudo apt install -y libxkbfile-dev pkg-config libkrb5-dev libxss1
- name: Execute npm
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
env:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
run: |
npm ci
- name: Download Playwright
run: npm run playwright-install
- name: Run Monaco Editor Checks
run: npm run monaco-compile-check
- name: Editor Distro & ESM
run: npm run gulp editor-distro
- name: Editor ESM sources check
working-directory: ./test/monaco
run: npm run esm-check
- name: Typings validation prep
run: |
mkdir typings-test
- name: Typings validation
working-directory: ./typings-test
run: |
npm init -yp
../node_modules/.bin/tsc --init
echo "import '../out-monaco-editor-core';" > a.ts
../node_modules/.bin/tsc --noEmit
- name: Package Editor with Webpack
working-directory: ./test/monaco
run: npm run bundle-webpack
- name: Compile Editor Tests
working-directory: ./test/monaco
run: npm run compile
- name: Run Editor Tests
timeout-minutes: 5
working-directory: ./test/monaco
run: npm run test
no-engineering-system-changes perms .github/workflows/no-engineering-system-changes.yml
View raw YAML
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: Allow automated distro updates
id: distro_exception
if: ${{ steps.engineering_systems_check.outputs.engineering_systems_modified == 'true' && github.event.pull_request.user.login == 'vs-code-engineering[bot]' }}
run: |
# Allow the vs-code-engineering bot ONLY when package.json is the
# sole changed file and the diff exclusively touches the "distro" field.
ONLY_PKG=$(jq -e '. == ["package.json"]' "$HOME/files.json" > /dev/null 2>&1 && echo true || echo false)
if [[ "$ONLY_PKG" != "true" ]]; then
echo "Bot modified files beyond package.json — not allowed"
echo "allowed=false" >> $GITHUB_OUTPUT
exit 0
fi
DIFF=$(gh pr diff ${{ github.event.pull_request.number }} --repo ${{ github.repository }}) || {
echo "Failed to fetch PR diff — not allowed"
echo "allowed=false" >> $GITHUB_OUTPUT
exit 0
}
CHANGED_LINES=$(echo "$DIFF" | grep -E '^[+-]' | grep -vE '^(\+\+\+|---)' | wc -l)
DISTRO_LINES=$(echo "$DIFF" | grep -cE '^[+-][[:space:]]*"distro"[[:space:]]*:' || true)
if [[ "$CHANGED_LINES" -eq 2 && "$DISTRO_LINES" -eq 2 ]]; then
echo "Distro-only update by bot — allowing"
echo "allowed=true" >> $GITHUB_OUTPUT
else
echo "Bot changed more than the distro field — not allowed"
echo "allowed=false" >> $GITHUB_OUTPUT
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Prevent Copilot from modifying engineering systems
if: ${{ steps.engineering_systems_check.outputs.engineering_systems_modified == 'true' && steps.distro_exception.outputs.allowed != '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@b91aabaa861c777dcdb14e2387e30eddf04619ae # v3.0.0
id: get_permissions
if: ${{ steps.engineering_systems_check.outputs.engineering_systems_modified == 'true' && steps.distro_exception.outputs.allowed != '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' && steps.distro_exception.outputs.allowed != '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.distro_exception.outputs.allowed != '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
pr perms .github/workflows/pr.yml
View raw YAML
name: Code OSS
on:
pull_request:
branches:
- main
- 'release/*'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
env:
VSCODE_QUALITY: 'oss'
jobs:
compile:
name: Compile & Hygiene
runs-on: ubuntu-22.04
steps:
- name: Checkout microsoft/vscode
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
- name: Prepare node_modules cache key
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@v5
with:
path: .build/node_modules_cache
key: "node_modules-compile-${{ 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 tools
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: sudo apt update -y && sudo apt install -y build-essential pkg-config libx11-dev libx11-xcb-dev libxkbfile-dev libnotify-bin libkrb5-dev
- name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
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:
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: Type check /build/ scripts
run: npm run typecheck
working-directory: build
- name: Compile & Hygiene
run: npm exec -- npm-run-all2 -lp core-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 }}
- name: Check cyclic dependencies
run: node build/lib/checkCyclicDependencies.ts out-build
linux-cli-tests:
name: Linux
uses: ./.github/workflows/pr-linux-cli-test.yml
with:
job_name: CLI
rustup_toolchain: 1.88
linux-electron-tests:
name: Linux
uses: ./.github/workflows/pr-linux-test.yml
with:
job_name: Electron
electron_tests: true
linux-browser-tests:
name: Linux
uses: ./.github/workflows/pr-linux-test.yml
with:
job_name: Browser
browser_tests: true
linux-remote-tests:
name: Linux
uses: ./.github/workflows/pr-linux-test.yml
with:
job_name: Remote
remote_tests: true
macos-electron-tests:
name: macOS
uses: ./.github/workflows/pr-darwin-test.yml
with:
job_name: Electron
electron_tests: true
macos-browser-tests:
name: macOS
uses: ./.github/workflows/pr-darwin-test.yml
with:
job_name: Browser
browser_tests: true
macos-remote-tests:
name: macOS
uses: ./.github/workflows/pr-darwin-test.yml
with:
job_name: Remote
remote_tests: true
windows-electron-tests:
name: Windows
uses: ./.github/workflows/pr-win32-test.yml
with:
job_name: Electron
electron_tests: true
windows-browser-tests:
name: Windows
uses: ./.github/workflows/pr-win32-test.yml
with:
job_name: Browser
browser_tests: true
windows-remote-tests:
name: Windows
uses: ./.github/workflows/pr-win32-test.yml
with:
job_name: Remote
remote_tests: true
pr-darwin-test .github/workflows/pr-darwin-test.yml
View raw YAML
on:
workflow_call:
inputs:
job_name:
type: string
required: true
electron_tests:
type: boolean
default: false
browser_tests:
type: boolean
default: false
remote_tests:
type: boolean
default: false
jobs:
macOS-test:
name: ${{ inputs.job_name }}
runs-on: macos-14-xlarge
env:
ARTIFACT_NAME: ${{ (inputs.electron_tests && 'electron') || (inputs.browser_tests && 'browser') || (inputs.remote_tests && 'remote') || 'unknown' }}
NPM_ARCH: arm64
VSCODE_ARCH: arm64
steps:
- name: Checkout microsoft/vscode
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
- name: Prepare node_modules cache key
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@v5
with:
path: .build/node_modules_cache
key: "node_modules-macos-${{ 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 dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: |
set -e
c++ --version
xcode-select -print-path
python3 -m pip install --break-system-packages 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: ${{ env.NPM_ARCH }}
VSCODE_ARCH: ${{ env.VSCODE_ARCH }}
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# 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"
- 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 ${{ env.VSCODE_ARCH }}" "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"
- name: 🧪 Run unit tests (node.js)
if: ${{ inputs.electron_tests }}
timeout-minutes: 15
run: npm run test-node
- name: 🧪 Run unit tests (Browser, Webkit)
if: ${{ inputs.browser_tests }}
timeout-minutes: 30
run: npm run test-browser-no-install -- --browser webkit --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"
- name: 🧪 Run integration tests (Browser, Webkit)
if: ${{ inputs.browser_tests }}
timeout-minutes: 20
run: ./scripts/test-web-integration.sh --browser webkit
- name: 🧪 Run integration tests (Remote)
if: ${{ inputs.remote_tests }}
timeout-minutes: 20
run: ./scripts/test-remote-integration.sh
- 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
run: ps -ef
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
- 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
- name: Diagnostics after smoke test run
run: ps -ef
continue-on-error: true
if: always()
- name: Publish Crash Reports
uses: actions/upload-artifact@v7
if: failure()
continue-on-error: true
with:
name: ${{ format('crash-dump-macos-{0}-{1}-{2}', env.VSCODE_ARCH, env.ARTIFACT_NAME, github.run_attempt) }}
path: .build/crashes
if-no-files-found: ignore
# 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@v7
if: failure()
continue-on-error: true
with:
name: ${{ format('node-modules-macos-{0}-{1}-{2}', env.VSCODE_ARCH, env.ARTIFACT_NAME, github.run_attempt) }}
path: node_modules
if-no-files-found: ignore
- name: Publish Log Files
uses: actions/upload-artifact@v7
if: always()
continue-on-error: true
with:
name: ${{ format('logs-macos-{0}-{1}-{2}', env.VSCODE_ARCH, env.ARTIFACT_NAME, github.run_attempt) }}
path: .build/logs
if-no-files-found: ignore
pr-linux-cli-test .github/workflows/pr-linux-cli-test.yml
View raw YAML
on:
workflow_call:
inputs:
job_name:
type: string
required: true
rustup_toolchain:
type: string
required: true
jobs:
linux-cli-test:
name: ${{ inputs.job_name }}
runs-on: ubuntu-22.04
env:
RUSTUP_TOOLCHAIN: ${{ inputs.rustup_toolchain }}
steps:
- name: Checkout microsoft/vscode
uses: actions/checkout@v6
- name: Install Rust
run: |
set -e
curl https://sh.rustup.rs -sSf | sh -s -- -y --profile minimal --default-toolchain $RUSTUP_TOOLCHAIN
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Set Rust version
run: |
set -e
rustup default $RUSTUP_TOOLCHAIN
rustup update $RUSTUP_TOOLCHAIN
rustup component add clippy
- name: Check Rust versions
run: |
set -e
rustc --version
cargo --version
- name: Clippy lint
run: cargo clippy -- -D warnings
working-directory: cli
- name: 🧪 Run unit tests
run: cargo test
working-directory: cli
pr-linux-test .github/workflows/pr-linux-test.yml
View raw YAML
on:
workflow_call:
inputs:
job_name:
type: string
required: true
electron_tests:
type: boolean
default: false
browser_tests:
type: boolean
default: false
remote_tests:
type: boolean
default: false
jobs:
linux-test:
name: ${{ inputs.job_name }}
runs-on: ubuntu-22.04
env:
ARTIFACT_NAME: ${{ (inputs.electron_tests && 'electron') || (inputs.browser_tests && 'browser') || (inputs.remote_tests && 'remote') || 'unknown' }}
NPM_ARCH: x64
VSCODE_ARCH: x64
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 \
bubblewrap \
socat
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 $VSCODE_ARCH $(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: ${{ env.NPM_ARCH }}
VSCODE_ARCH: ${{ env.VSCODE_ARCH }}
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 ${{ env.VSCODE_ARCH }}" "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()
- name: Publish Crash Reports
uses: actions/upload-artifact@v7
if: failure()
continue-on-error: true
with:
name: ${{ format('crash-dump-linux-{0}-{1}-{2}', env.VSCODE_ARCH, env.ARTIFACT_NAME, github.run_attempt) }}
path: .build/crashes
if-no-files-found: ignore
# 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@v7
if: failure()
continue-on-error: true
with:
name: ${{ format('node-modules-linux-{0}-{1}-{2}', env.VSCODE_ARCH, env.ARTIFACT_NAME, github.run_attempt) }}
path: node_modules
if-no-files-found: ignore
- name: Publish Log Files
uses: actions/upload-artifact@v7
if: always()
continue-on-error: true
with:
name: ${{ format('logs-linux-{0}-{1}-{2}', env.VSCODE_ARCH, env.ARTIFACT_NAME, github.run_attempt) }}
path: .build/logs
if-no-files-found: ignore
pr-node-modules perms .github/workflows/pr-node-modules.yml
View raw YAML
name: Code OSS (node_modules)
on:
push:
branches:
- main
permissions: {}
jobs:
compile:
name: Compile
runs-on: ubuntu-22.04
steps:
- name: Checkout microsoft/vscode
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
- name: Prepare node_modules cache key
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@v5
with:
path: .build/node_modules_cache
key: "node_modules-compile-${{ 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 tools
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: sudo apt update -y && sudo apt install -y build-essential pkg-config libx11-dev libx11-xcb-dev libxkbfile-dev libnotify-bin libkrb5-dev
- name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
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:
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
GITHUB_TOKEN: ${{ secrets.VSCODE_OSS }}
- 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: Prepare built-in extensions cache key
run: |
set -e
mkdir -p .build
node build/azure-pipelines/common/computeBuiltInDepsCacheKey.ts > .build/builtindepshash
- name: Restore built-in extensions cache
id: cache-builtin-extensions
uses: actions/cache@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.VSCODE_OSS }}
linux:
name: Linux
runs-on: ubuntu-22.04
env:
NPM_ARCH: x64
VSCODE_ARCH: x64
steps:
- name: Checkout microsoft/vscode
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
- name: Prepare node_modules cache key
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@v5
with:
path: .build/node_modules_cache
key: "node_modules-linux-${{ hashFiles('.build/packagelockhash') }}"
- 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.VSCODE_OSS }}
- 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: ${{ env.NPM_ARCH }}
VSCODE_ARCH: ${{ env.VSCODE_ARCH }}
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
GITHUB_TOKEN: ${{ secrets.VSCODE_OSS }}
- 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
macOS:
name: macOS
runs-on: macos-14-xlarge
env:
NPM_ARCH: arm64
VSCODE_ARCH: arm64
steps:
- name: Checkout microsoft/vscode
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
- name: Prepare node_modules cache key
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@v5
with:
path: .build/node_modules_cache
key: "node_modules-macos-${{ hashFiles('.build/packagelockhash') }}"
- name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: |
set -e
c++ --version
xcode-select -print-path
python3 -m pip install --break-system-packages 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: ${{ env.NPM_ARCH }}
VSCODE_ARCH: ${{ env.VSCODE_ARCH }}
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
GITHUB_TOKEN: ${{ secrets.VSCODE_OSS }}
# 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"
- 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
windows:
name: Windows
runs-on: [ self-hosted, 1ES.Pool=1es-vscode-oss-windows-2022-x64 ]
env:
NPM_ARCH: x64
VSCODE_ARCH: x64
steps:
- name: Checkout microsoft/vscode
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
- name: Prepare node_modules cache key
shell: pwsh
run: |
mkdir .build -ea 0
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@v5
id: node-modules-cache
with:
path: .build/node_modules_cache
key: "node_modules-windows-${{ hashFiles('.build/packagelockhash') }}"
- name: Install dependencies
if: steps.node-modules-cache.outputs.cache-hit != 'true'
shell: pwsh
run: |
. build/azure-pipelines/win32/exec.ps1
$ErrorActionPreference = "Stop"
for ($i = 1; $i -le 5; $i++) {
try {
exec { npm ci }
break
}
catch {
if ($i -eq 5) {
Write-Error "npm ci failed after 5 attempts"
throw
}
Write-Host "npm ci failed attempt $i, retrying..."
Start-Sleep -Seconds 2
}
}
env:
npm_config_arch: ${{ env.NPM_ARCH }}
npm_config_foreground_scripts: "true"
VSCODE_ARCH: ${{ env.VSCODE_ARCH }}
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
GITHUB_TOKEN: ${{ secrets.VSCODE_OSS }}
- name: Create node_modules archive
if: steps.node-modules-cache.outputs.cache-hit != 'true'
shell: pwsh
run: |
. 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 }
pr-win32-test .github/workflows/pr-win32-test.yml
View raw YAML
on:
workflow_call:
inputs:
job_name:
type: string
required: true
electron_tests:
type: boolean
default: false
browser_tests:
type: boolean
default: false
remote_tests:
type: boolean
default: false
jobs:
windows-test:
name: ${{ inputs.job_name }}
runs-on: windows-2022
env:
ARTIFACT_NAME: ${{ (inputs.electron_tests && 'electron') || (inputs.browser_tests && 'browser') || (inputs.remote_tests && 'remote') || 'unknown' }}
NPM_ARCH: x64
VSCODE_ARCH: x64
steps:
- name: Checkout microsoft/vscode
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
- name: Prepare node_modules cache key
shell: pwsh
run: |
mkdir .build -ea 0
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@v5
id: node-modules-cache
with:
path: .build/node_modules_cache
key: "node_modules-windows-${{ hashFiles('.build/packagelockhash') }}"
- name: Extract node_modules cache
if: steps.node-modules-cache.outputs.cache-hit == 'true'
shell: pwsh
run: 7z.exe x .build/node_modules_cache/cache.7z -aoa
- name: Install dependencies
if: steps.node-modules-cache.outputs.cache-hit != 'true'
shell: pwsh
run: |
. build/azure-pipelines/win32/exec.ps1
$ErrorActionPreference = "Stop"
for ($i = 1; $i -le 5; $i++) {
try {
exec { npm ci }
break
}
catch {
if ($i -eq 5) {
Write-Error "npm ci failed after 5 attempts"
throw
}
Write-Host "npm ci failed attempt $i, retrying..."
Start-Sleep -Seconds 2
}
}
env:
npm_config_arch: ${{ env.NPM_ARCH }}
npm_config_foreground_scripts: "true"
VSCODE_ARCH: ${{ env.VSCODE_ARCH }}
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create node_modules archive
if: steps.node-modules-cache.outputs.cache-hit != 'true'
shell: pwsh
run: |
. 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 }
- name: Create .build folder
shell: pwsh
run: mkdir .build -ea 0
- name: Prepare built-in extensions cache key
shell: pwsh
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
shell: pwsh
run: npm run gulp "transpile-client-esbuild" "transpile-extensions"
- name: Download Electron and Playwright
shell: pwsh
run: |
for ($i = 1; $i -le 3; $i++) {
try {
npm exec -- npm-run-all2 -lp "electron ${{ env.VSCODE_ARCH }}" "playwright-install"
break
}
catch {
if ($i -eq 3) {
Write-Error "Download failed after 3 attempts"
throw
}
Write-Host "Download failed attempt $i, retrying..."
Start-Sleep -Seconds 2
}
}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: 🧪 Run unit tests (Electron)
if: ${{ inputs.electron_tests }}
shell: pwsh
run: .\scripts\test.bat --tfs "Unit Tests"
timeout-minutes: 15
- name: 🧪 Run unit tests (node.js)
if: ${{ inputs.electron_tests }}
shell: pwsh
run: npm run test-node
timeout-minutes: 15
- name: 🧪 Run unit tests (Browser, Chromium)
if: ${{ inputs.browser_tests }}
shell: pwsh
run: node test/unit/browser/index.js --browser chromium --tfs "Browser Unit Tests"
env:
DEBUG: "*browser*"
timeout-minutes: 20
- name: Build integration tests
shell: pwsh
run: |
. build/azure-pipelines/win32/exec.ps1
$ErrorActionPreference = "Stop"
exec { npm run gulp `
compile-extension:configuration-editing `
compile-extension:css-language-features-server `
compile-extension:emmet `
compile-extension:git `
compile-extension:github-authentication `
compile-extension:html-language-features-server `
compile-extension:ipynb `
compile-extension:notebook-renderers `
compile-extension:json-language-features-server `
compile-extension:markdown-language-features `
compile-extension-media `
compile-extension:microsoft-authentication `
compile-extension:typescript-language-features `
compile-extension:vscode-api-tests `
compile-extension:vscode-colorize-tests `
compile-extension:vscode-colorize-perf-tests `
compile-extension:vscode-test-resolver `
}
- name: Diagnostics before integration test runs
shell: pwsh
run: .\build\azure-pipelines\win32\listprocesses.bat
continue-on-error: true
if: always()
- name: 🧪 Run integration tests (Electron)
if: ${{ inputs.electron_tests }}
shell: pwsh
run: .\scripts\test-integration.bat --tfs "Integration Tests"
timeout-minutes: 20
- name: 🧪 Run integration tests (Browser, Chromium)
if: ${{ inputs.browser_tests }}
shell: pwsh
run: .\scripts\test-web-integration.bat --browser chromium
timeout-minutes: 20
- name: 🧪 Run integration tests (Remote)
if: ${{ inputs.remote_tests }}
shell: pwsh
run: .\scripts\test-remote-integration.bat
timeout-minutes: 20
- name: Diagnostics after integration test runs
shell: pwsh
run: .\build\azure-pipelines\win32\listprocesses.bat
continue-on-error: true
if: always()
- name: Diagnostics before smoke test run
shell: pwsh
run: .\build\azure-pipelines\win32\listprocesses.bat
continue-on-error: true
if: always()
- name: Compile smoke tests
working-directory: test/smoke
shell: pwsh
run: npm run compile
- name: Compile extensions for smoke tests
shell: pwsh
run: npm run gulp compile-extension-media
- name: 🧪 Run smoke tests (Electron)
if: ${{ inputs.electron_tests }}
timeout-minutes: 20
shell: pwsh
run: npm run smoketest-no-compile -- --tracing
- name: 🧪 Run smoke tests (Browser, Chromium)
if: ${{ inputs.browser_tests }}
timeout-minutes: 20
shell: pwsh
run: npm run smoketest-no-compile -- --web --tracing --headless
- name: 🧪 Run smoke tests (Remote)
if: ${{ inputs.remote_tests }}
timeout-minutes: 20
shell: pwsh
run: npm run smoketest-no-compile -- --remote --tracing
- name: Diagnostics after smoke test run
shell: pwsh
run: .\build\azure-pipelines\win32\listprocesses.bat
continue-on-error: true
if: always()
- name: Publish Crash Reports
uses: actions/upload-artifact@v7
if: failure()
continue-on-error: true
with:
name: ${{ format('crash-dump-windows-{0}-{1}-{2}', env.VSCODE_ARCH, env.ARTIFACT_NAME, github.run_attempt) }}
path: .build/crashes
if-no-files-found: ignore
# 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@v7
if: failure()
continue-on-error: true
with:
name: ${{ format('node-modules-windows-{0}-{1}-{2}', env.VSCODE_ARCH, env.ARTIFACT_NAME, github.run_attempt) }}
path: node_modules
if-no-files-found: ignore
- name: Publish Log Files
uses: actions/upload-artifact@v7
if: always()
continue-on-error: true
with:
name: ${{ format('logs-windows-{0}-{1}-{2}', env.VSCODE_ARCH, env.ARTIFACT_NAME, github.run_attempt) }}
path: .build/logs
if-no-files-found: ignore
screenshot-test perms .github/workflows/screenshot-test.yml
View raw YAML
name: Checking Component Screenshots
on:
push:
branches: [main]
pull_request:
branches:
- main
- 'release/*'
permissions:
contents: read
statuses: write
concurrency:
group: screenshots-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
jobs:
screenshots:
name: Checking Component Screenshots
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
lfs: true
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
- name: Install dependencies
run: npm ci --ignore-scripts
env:
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install build dependencies
run: npm ci
working-directory: build
- name: Install build/vite dependencies
run: rm -f package-lock.json && npm install
working-directory: build/vite
- name: Build vite
run: npm run build
working-directory: build/vite
- name: Install Playwright Chromium
run: npx playwright install chromium
- name: Capture screenshots
run: ./node_modules/.bin/component-explorer screenshot --project ./test/componentFixtures/component-explorer.json
- name: Compare screenshots
id: compare
run: |
./node_modules/.bin/component-explorer screenshot:compare \
--project ./test/componentFixtures \
--report ./test/componentFixtures/.screenshots/report
continue-on-error: true
- name: Prepare explorer artifact
run: |
mkdir -p /tmp/explorer-artifact/screenshot-report
cp -r build/vite/dist/* /tmp/explorer-artifact/
if [ -d test/componentFixtures/.screenshots/report ]; then
cp -r test/componentFixtures/.screenshots/report/* /tmp/explorer-artifact/screenshot-report/
fi
- name: Upload explorer artifact
uses: actions/upload-artifact@v7
with:
name: component-explorer
path: /tmp/explorer-artifact/
- name: Upload screenshot report
if: steps.compare.outcome == 'failure'
uses: actions/upload-artifact@v7
with:
name: screenshot-diff
path: |
test/componentFixtures/.screenshots/current/
test/componentFixtures/.screenshots/report/
- name: Set check title
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
REPORT="test/componentFixtures/.screenshots/report/report.json"
STATE="success"
if [ -f "$REPORT" ]; then
CHANGED=$(node -e "const r = require('./$REPORT'); console.log(r.summary.added + r.summary.removed + r.summary.changed)")
TITLE="⚠ ${CHANGED} screenshots changed"
BLOCKS_CI=$(node -e "
const r = require('./$REPORT');
const blocking = Object.entries(r.fixtures).filter(([, f]) =>
f.status !== 'unchanged' && (f.labels || []).includes('blocks-ci')
);
if (blocking.length > 0) {
console.log(blocking.map(([name]) => name).join(', '));
}
")
if [ -n "$BLOCKS_CI" ]; then
STATE="failure"
TITLE="❌ ${CHANGED} screenshots changed (blocks CI: ${BLOCKS_CI})"
fi
else
TITLE="✅ Screenshots match"
fi
SHA="${{ github.event.pull_request.head.sha || github.sha }}"
DETAILS_URL="https://hediet-ghartifactpreview.azurewebsites.net/${{ github.repository }}/run/${{ github.run_id }}/component-explorer/___explorer.html?report=./screenshot-report/report.json&search=changed"
gh api "repos/${{ github.repository }}/statuses/$SHA" \
--input - <<EOF || echo "::warning::Could not create commit status (expected for fork PRs)"
{"state":"$STATE","target_url":"$DETAILS_URL","description":"$TITLE","context":"Component Screenshots"}
EOF
# - name: Post PR comment
# if: github.event_name == 'pull_request'
# env:
# GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# run: |
# COMMENT_MARKER="<!-- screenshot-report -->"
# BODY="$COMMENT_MARKER"$'\n'
#
# if [ -f test/componentFixtures/.screenshots/report.md ]; then
# BODY+=$(cat test/componentFixtures/.screenshots/report.md)
# BODY+=$'\n\n'
# BODY+="📦 [Download the \`screenshot-diff\` artifact](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) to review images."
# else
# BODY+="## Screenshots ✅"$'\n\n'
# BODY+="No visual changes detected."
# fi
#
# # Find existing comment
# EXISTING=$(gh api "repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" \
# --paginate --jq ".[] | select(.body | startswith(\"$COMMENT_MARKER\")) | .id" | head -1)
#
# if [ -n "$EXISTING" ]; then
# gh api "repos/${{ github.repository }}/issues/comments/$EXISTING" -X PATCH -f body="$BODY"
# else
# gh pr comment "${{ github.event.pull_request.number }}" --body "$BODY"
# fi
sessions-e2e perms .github/workflows/sessions-e2e.yml
View raw YAML
name: Sessions E2E Tests
# on:
# pull_request:
# branches:
# - main
# - 'release/*'
# paths:
# - 'src/vs/sessions/**'
# - 'scripts/code-sessions-web.*'
permissions:
contents: read
concurrency:
group: sessions-e2e-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
jobs:
sessions-e2e:
name: Sessions E2E Tests
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
- name: Install build tools
run: sudo apt update -y && sudo apt install -y build-essential pkg-config libx11-dev libx11-xcb-dev libxkbfile-dev libnotify-bin libkrb5-dev xvfb
- name: Install dependencies
run: npm ci
env:
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install build dependencies
run: npm ci
working-directory: build
- name: Transpile sources
run: npm run transpile-client
- name: Install E2E test dependencies
run: npm ci
working-directory: src/vs/sessions/test/e2e
- name: Install Playwright browsers
run: npx playwright install chromium
- name: Run Sessions E2E tests
run: xvfb-run npm test
working-directory: src/vs/sessions/test/e2e
- name: Upload failure screenshots
if: failure()
uses: actions/upload-artifact@v7
with:
name: sessions-e2e-failures
path: src/vs/sessions/test/e2e/out/failure-*.png
retention-days: 7
telemetry perms .github/workflows/telemetry.yml
View raw YAML
name: 'Telemetry'
on: pull_request
permissions: {}
jobs:
check-metadata:
name: 'Check metadata'
runs-on: 'ubuntu-latest'
steps:
- uses: 'actions/checkout@v6'
with:
persist-credentials: false
- uses: 'actions/setup-node@v6'
with:
node-version: 'lts/*'
- name: 'Run vscode-telemetry-extractor'
run: 'npx --package=@vscode/telemetry-extractor@1.14.0 --yes vscode-telemetry-extractor -s .'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}