diff --git a/.github/CODENOTIFY b/.github/CODENOTIFY index 7aba51a470b27..eaf90f0dd1afd 100644 --- a/.github/CODENOTIFY +++ b/.github/CODENOTIFY @@ -26,6 +26,7 @@ src/vs/base/browser/ui/tree/** @joaomoreno @benibenj # Platform src/vs/platform/auxiliaryWindow/** @bpasero src/vs/platform/backup/** @bpasero +src/vs/platform/browserView/** @kycutler @jruales src/vs/platform/dialogs/** @bpasero src/vs/platform/editor/** @bpasero src/vs/platform/environment/** @bpasero @@ -65,6 +66,7 @@ src/vs/code/** @bpasero @deepak1556 src/vs/workbench/services/activity/** @bpasero src/vs/workbench/services/authentication/** @TylerLeonhardt src/vs/workbench/services/auxiliaryWindow/** @bpasero +src/vs/workbench/services/browserView/** @kycutler @jruales src/vs/workbench/services/contextmenu/** @bpasero src/vs/workbench/services/dialogs/** @alexr00 @bpasero src/vs/workbench/services/editor/** @bpasero @@ -97,6 +99,7 @@ src/vs/workbench/electron-browser/** @bpasero # Workbench Contributions src/vs/workbench/contrib/authentication/** @TylerLeonhardt +src/vs/workbench/contrib/browserView/** @kycutler @jruales src/vs/workbench/contrib/files/** @bpasero src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @roblourens src/vs/workbench/contrib/localization/** @TylerLeonhardt diff --git a/.github/agents/data.md b/.github/agents/data.md index 605bd276ef9a3..37f83c638cb79 100644 --- a/.github/agents/data.md +++ b/.github/agents/data.md @@ -42,3 +42,7 @@ Your response should include: - Interpretation and analysis of the results - References to specific documentation files when applicable - Additional context or insights from the telemetry data + +# Troubleshooting + +If the connection to the Kusto cluster is timing out consistently, stop and ask the user to check whether they are connected to Azure VPN. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 62d002fc4564b..06005d9424ba3 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -58,6 +58,7 @@ MANDATORY: Always check the `VS Code - Build` watch task output via #runTasks/ge - Monitor the `VS Code - Build` task outputs for real-time compilation errors as you make changes - This task runs `Core - Build` and `Ext - Build` to incrementally compile VS Code TypeScript sources and built-in extensions - Start the task if it's not already running in the background +- For TypeScript changes in the `build` folder, you can simply run `npm run typecheck` in the `build` folder. ### TypeScript validation steps - Use the run test tool if you need to run tests. If that tool is not available, then you can use `scripts/test.sh` (or `scripts\test.bat` on Windows) for unit tests (add `--grep ` to filter tests) or `scripts/test-integration.sh` (or `scripts\test-integration.bat` on Windows) for integration tests (integration tests end with .integrationTest.ts or are in /extensions/). diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f7e3481c75b0c..fc5cda5555b2d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,3 +8,17 @@ updates: directory: "/" schedule: interval: "weekly" + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "daily" + allow: + - dependency-name: "@vscode/component-explorer" + - dependency-name: "@vscode/component-explorer-cli" + - package-ecosystem: "npm" + directory: "/build/vite" + schedule: + interval: "daily" + allow: + - dependency-name: "@vscode/component-explorer" + - dependency-name: "@vscode/component-explorer-vite-plugin" diff --git a/.github/hooks/hooks.json b/.github/hooks/hooks.json new file mode 100644 index 0000000000000..3e0f178b02329 --- /dev/null +++ b/.github/hooks/hooks.json @@ -0,0 +1,41 @@ +{ + "version": 1, + "hooks": { + "sessionStart": [ + { + "type": "command", + "bash": "if [ -f ~/.vscode-worktree-setup ]; then nohup bash -c 'npm ci && npm run compile' > /tmp/worktree-setup-$(date +%Y-%m-%d_%H-%M-%S).log 2>&1 & fi" + } + ], + "sessionEnd": [ + { + "type": "command", + "bash": "" + } + ], + "agentStop": [ + { + "type": "command", + "bash": "" + } + ], + "userPromptSubmitted": [ + { + "type": "command", + "bash": "" + } + ], + "preToolUse": [ + { + "type": "command", + "bash": "" + } + ], + "postToolUse": [ + { + "type": "command", + "bash": "" + } + ] + } +} diff --git a/.github/instructions/kusto.instructions.md b/.github/instructions/kusto.instructions.md index 2c77e92555d6c..ac247c5772415 100644 --- a/.github/instructions/kusto.instructions.md +++ b/.github/instructions/kusto.instructions.md @@ -6,7 +6,7 @@ description: Kusto exploration and telemetry analysis instructions When performing Kusto queries, telemetry analysis, or data exploration tasks for VS Code, consult the comprehensive Kusto instructions located at: -**[kusto-vscode-instructions.md](../../../vscode-internalbacklog/instructions/kusto/kusto-vscode-instructions.md)** +**[kusto-vscode-instructions.md](../../../vscode-tools/.github/skills/kusto-telemetry/kusto-vscode.instructions.md)** These instructions contain valuable information about: - Available Kusto clusters and databases for VS Code telemetry @@ -16,4 +16,4 @@ These instructions contain valuable information about: Reading these instructions before writing Kusto queries will help you write more accurate and efficient queries, avoid common pitfalls, and leverage existing knowledge about VS Code's telemetry infrastructure. -(Make sure to have the main branch of vscode-internalbacklog up to date in case there are problems). +(Make sure to have the main branch of vscode-tools up to date in case there are problems and the repository cloned from https://github.com/microsoft/vscode-tools). diff --git a/.github/skills/azure-pipelines/SKILL.md b/.github/skills/azure-pipelines/SKILL.md index b7b2e164e038d..9790401995258 100644 --- a/.github/skills/azure-pipelines/SKILL.md +++ b/.github/skills/azure-pipelines/SKILL.md @@ -66,21 +66,24 @@ Use the [queue command](./azure-pipeline.ts) to queue a validation build: ```bash # Queue a build on the current branch -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts queue +node .github/skills/azure-pipelines/azure-pipeline.ts queue # Queue with a specific source branch -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts queue --branch my-feature-branch +node .github/skills/azure-pipelines/azure-pipeline.ts queue --branch my-feature-branch -# Queue with custom variables (e.g., to skip certain stages) -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts queue --variables "SKIP_TESTS=true" +# Queue with custom parameters +node .github/skills/azure-pipelines/azure-pipeline.ts queue --parameter "VSCODE_BUILD_WEB=false" --parameter "VSCODE_PUBLISH=false" + +# Parameter value with spaces +node .github/skills/azure-pipelines/azure-pipeline.ts queue --parameter "VSCODE_BUILD_TYPE=Product Build" ``` > **Important**: Before queueing a new build, cancel any previous builds on the same branch that you no longer need. This frees up build agents and reduces resource waste: > ```bash > # Find the build ID from status, then cancel it -> node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status -> node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts cancel --build-id -> node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts queue +> node .github/skills/azure-pipelines/azure-pipeline.ts status +> node .github/skills/azure-pipelines/azure-pipeline.ts cancel --build-id +> node .github/skills/azure-pipelines/azure-pipeline.ts queue > ``` ### Script Options @@ -89,9 +92,43 @@ node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts |--------|-------------| | `--branch ` | Source branch to build (default: current git branch) | | `--definition ` | Pipeline definition ID (default: 111) | -| `--variables ` | Pipeline variables in `KEY=value` format, space-separated | +| `--parameter ` | Pipeline parameter in `KEY=value` format (repeatable) | +| `--parameters ` | Space-separated parameters in `KEY=value KEY2=value2` format | | `--dry-run` | Print the command without executing | +### Product Build Queue Parameters (`build/azure-pipelines/product-build.yml`) + +| Name | Type | Default | Allowed Values | Description | +|------|------|---------|----------------|-------------| +| `VSCODE_QUALITY` | string | `insider` | `exploration`, `insider`, `stable` | Build quality channel | +| `VSCODE_BUILD_TYPE` | string | `Product Build` | `Product`, `CI` | Build mode for Product vs CI | +| `NPM_REGISTRY` | string | `https://pkgs.dev.azure.com/monacotools/Monaco/_packaging/vscode/npm/registry/` | any URL | Custom npm registry | +| `CARGO_REGISTRY` | string | `sparse+https://pkgs.dev.azure.com/monacotools/Monaco/_packaging/vscode/Cargo/index/` | any URL | Custom Cargo registry | +| `VSCODE_BUILD_WIN32` | boolean | `true` | `true`, `false` | Build Windows x64 | +| `VSCODE_BUILD_WIN32_ARM64` | boolean | `true` | `true`, `false` | Build Windows arm64 | +| `VSCODE_BUILD_LINUX` | boolean | `true` | `true`, `false` | Build Linux x64 | +| `VSCODE_BUILD_LINUX_SNAP` | boolean | `true` | `true`, `false` | Build Linux x64 Snap | +| `VSCODE_BUILD_LINUX_ARM64` | boolean | `true` | `true`, `false` | Build Linux arm64 | +| `VSCODE_BUILD_LINUX_ARMHF` | boolean | `true` | `true`, `false` | Build Linux armhf | +| `VSCODE_BUILD_ALPINE` | boolean | `true` | `true`, `false` | Build Alpine x64 | +| `VSCODE_BUILD_ALPINE_ARM64` | boolean | `true` | `true`, `false` | Build Alpine arm64 | +| `VSCODE_BUILD_MACOS` | boolean | `true` | `true`, `false` | Build macOS x64 | +| `VSCODE_BUILD_MACOS_ARM64` | boolean | `true` | `true`, `false` | Build macOS arm64 | +| `VSCODE_BUILD_MACOS_UNIVERSAL` | boolean | `true` | `true`, `false` | Build macOS universal (requires both macOS arches) | +| `VSCODE_BUILD_WEB` | boolean | `true` | `true`, `false` | Build Web artifacts | +| `VSCODE_PUBLISH` | boolean | `true` | `true`, `false` | Publish to builds.code.visualstudio.com | +| `VSCODE_RELEASE` | boolean | `false` | `true`, `false` | Trigger release flow if successful | +| `VSCODE_STEP_ON_IT` | boolean | `false` | `true`, `false` | Skip tests | + +Example: run a quick CI-oriented validation with minimal publish/release side effects: + +```bash +node .github/skills/azure-pipelines/azure-pipeline.ts queue \ + --parameter "VSCODE_BUILD_TYPE=CI Build" \ + --parameter "VSCODE_PUBLISH=false" \ + --parameter "VSCODE_RELEASE=false" +``` + --- ## Checking Build Status @@ -99,17 +136,17 @@ node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts Use the [status command](./azure-pipeline.ts) to monitor a running build: ```bash -# Get status of the most recent build on your branch -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status +# Get status of the most recent builds +node .github/skills/azure-pipelines/azure-pipeline.ts status # Get overview of a specific build by ID -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status --build-id 123456 +node .github/skills/azure-pipelines/azure-pipeline.ts status --build-id 123456 # Watch build status (refreshes every 30 seconds) -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status --watch +node .github/skills/azure-pipelines/azure-pipeline.ts status --watch # Watch with custom interval (60 seconds) -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status --watch 60 +node .github/skills/azure-pipelines/azure-pipeline.ts status --watch 60 ``` ### Script Options @@ -133,10 +170,10 @@ Use the [cancel command](./azure-pipeline.ts) to stop a running build: ```bash # Cancel a build by ID (use status command to find IDs) -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts cancel --build-id 123456 +node .github/skills/azure-pipelines/azure-pipeline.ts cancel --build-id 123456 # Dry run (show what would be cancelled) -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts cancel --build-id 123456 --dry-run +node .github/skills/azure-pipelines/azure-pipeline.ts cancel --build-id 123456 --dry-run ``` ### Script Options @@ -149,6 +186,44 @@ node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts --- +## Testing Pipeline Changes + +When the user asks to **test changes in an Azure Pipelines build**, follow this workflow: + +1. **Queue a new build** on the current branch +2. **Poll for completion** by periodically checking the build status until it finishes + +### Polling for Build Completion + +Use a shell loop with `sleep` to poll the build status. The `sleep` command works on all major operating systems: + +```bash +# Queue the build and note the build ID from output (e.g., 123456) +node .github/skills/azure-pipelines/azure-pipeline.ts queue + +# Poll every 60 seconds until complete (works on macOS, Linux, and Windows with Git Bash/WSL) +# Replace with the actual build ID from the queue command +while true; do + node .github/skills/azure-pipelines/azure-pipeline.ts status --build-id --json 2>/dev/null | grep -q '"status": "completed"' && break + sleep 60 +done + +# Check final result +node .github/skills/azure-pipelines/azure-pipeline.ts status --build-id +``` + +Alternatively, use the built-in `--watch` flag which handles polling automatically: + +```bash +node .github/skills/azure-pipelines/azure-pipeline.ts queue +# Use the build ID returned by the queue command +node .github/skills/azure-pipelines/azure-pipeline.ts status --build-id --watch +``` + +> **Note**: The `--watch` flag polls every 30 seconds by default. Use `--watch 60` for a 60-second interval to reduce API calls. + +--- + ## Common Workflows ### 1. Quick Pipeline Validation @@ -159,45 +234,50 @@ git add -A && git commit -m "test: pipeline changes" git push origin HEAD # Check for any previous builds on this branch and cancel if needed -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts cancel --build-id # if there's an active build +node .github/skills/azure-pipelines/azure-pipeline.ts status +node .github/skills/azure-pipelines/azure-pipeline.ts cancel --build-id # if there's an active build # Queue and watch the new build -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts queue -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status --watch +node .github/skills/azure-pipelines/azure-pipeline.ts queue +node .github/skills/azure-pipelines/azure-pipeline.ts status --watch ``` ### 2. Investigate a Build ```bash # Get overview of a build (shows stages, artifacts, and log IDs) -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status --build-id 123456 +node .github/skills/azure-pipelines/azure-pipeline.ts status --build-id 123456 # Download a specific log for deeper inspection -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status --build-id 123456 --download-log 5 +node .github/skills/azure-pipelines/azure-pipeline.ts status --build-id 123456 --download-log 5 # Download an artifact -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status --build-id 123456 --download-artifact unsigned_vscode_cli_win32_x64_cli +node .github/skills/azure-pipelines/azure-pipeline.ts status --build-id 123456 --download-artifact unsigned_vscode_cli_win32_x64_cli ``` -### 3. Test with Modified Variables +### 3. Test with Modified Parameters ```bash -# Skip expensive stages during validation -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts queue --variables "VSCODE_BUILD_SKIP_INTEGRATION_TESTS=true" +# Customize build matrix for quicker validation +node .github/skills/azure-pipelines/azure-pipeline.ts queue \ + --parameter "VSCODE_BUILD_TYPE=CI Build" \ + --parameter "VSCODE_BUILD_WEB=false" \ + --parameter "VSCODE_BUILD_ALPINE=false" \ + --parameter "VSCODE_BUILD_ALPINE_ARM64=false" \ + --parameter "VSCODE_PUBLISH=false" ``` ### 4. Cancel a Running Build ```bash # First, find the build ID -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status +node .github/skills/azure-pipelines/azure-pipeline.ts status # Cancel a specific build by ID -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts cancel --build-id 123456 +node .github/skills/azure-pipelines/azure-pipeline.ts cancel --build-id 123456 # Dry run to see what would be cancelled -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts cancel --build-id 123456 --dry-run +node .github/skills/azure-pipelines/azure-pipeline.ts cancel --build-id 123456 --dry-run ``` ### 5. Iterate on Pipeline Changes @@ -210,12 +290,12 @@ git add -A && git commit --amend --no-edit git push --force-with-lease origin HEAD # Find the outdated build ID and cancel it -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts cancel --build-id +node .github/skills/azure-pipelines/azure-pipeline.ts status +node .github/skills/azure-pipelines/azure-pipeline.ts cancel --build-id # Queue a fresh build and monitor -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts queue -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status --watch +node .github/skills/azure-pipelines/azure-pipeline.ts queue +node .github/skills/azure-pipelines/azure-pipeline.ts status --watch ``` --- diff --git a/.github/skills/azure-pipelines/azure-pipeline.ts b/.github/skills/azure-pipelines/azure-pipeline.ts index 7fad554050bb3..fbb74b5dd4aa0 100644 --- a/.github/skills/azure-pipelines/azure-pipeline.ts +++ b/.github/skills/azure-pipelines/azure-pipeline.ts @@ -9,7 +9,7 @@ * A unified command-line tool for managing Azure Pipeline builds. * * Usage: - * node --experimental-strip-types azure-pipeline.ts [options] + * node azure-pipeline.ts [options] * * Commands: * queue - Queue a new pipeline build @@ -38,8 +38,8 @@ const NUMERIC_ID_PATTERN = /^\d+$/; const MAX_ID_LENGTH = 15; const BRANCH_PATTERN = /^[a-zA-Z0-9_\-./]+$/; const MAX_BRANCH_LENGTH = 256; -const VARIABLE_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*=[a-zA-Z0-9_\-./: ]*$/; -const MAX_VARIABLE_LENGTH = 256; +const PARAMETER_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*=[a-zA-Z0-9_\-./: +]*$/; +const MAX_PARAMETER_LENGTH = 256; const ARTIFACT_NAME_PATTERN = /^[a-zA-Z0-9_\-.]+$/; const MAX_ARTIFACT_NAME_LENGTH = 256; const MIN_WATCH_INTERVAL = 5; @@ -88,7 +88,7 @@ interface Artifact { interface QueueArgs { branch: string; definitionId: string; - variables: string; + parameters: string[]; dryRun: boolean; help: boolean; } @@ -159,19 +159,18 @@ function validateBranch(value: string): void { } } -function validateVariables(value: string): void { - if (!value) { +function validateParameters(values: string[]): void { + if (!values.length) { return; } - const vars = value.split(' ').filter(v => v.length > 0); - for (const v of vars) { - if (v.length > MAX_VARIABLE_LENGTH) { - console.error(colors.red(`Error: Variable '${v.substring(0, 20)}...' is too long (max ${MAX_VARIABLE_LENGTH} characters)`)); + for (const parameter of values) { + if (parameter.length > MAX_PARAMETER_LENGTH) { + console.error(colors.red(`Error: Parameter '${parameter.substring(0, 20)}...' is too long (max ${MAX_PARAMETER_LENGTH} characters)`)); process.exit(1); } - if (!VARIABLE_PATTERN.test(v)) { - console.error(colors.red(`Error: Invalid variable format '${v}'`)); - console.log('Expected format: KEY=value (alphanumeric, underscores, hyphens, dots, slashes, colons, spaces in value)'); + if (!PARAMETER_PATTERN.test(parameter)) { + console.error(colors.red(`Error: Invalid parameter format '${parameter}'`)); + console.log('Expected format: KEY=value (alphanumeric, underscores, hyphens, dots, slashes, colons, plus signs, spaces in value)'); process.exit(1); } } @@ -612,7 +611,7 @@ class AzureDevOpsClient { return JSON.parse(result); } - async queueBuild(definitionId: string, branch: string, variables?: string): Promise { + async queueBuild(definitionId: string, branch: string, parameters: string[] = []): Promise { const args = [ 'pipelines', 'run', '--organization', this.organization, @@ -621,8 +620,8 @@ class AzureDevOpsClient { '--branch', branch, ]; - if (variables) { - args.push('--variables', ...variables.split(' ')); + if (parameters.length > 0) { + args.push('--parameters', ...parameters); } args.push('--output', 'json'); @@ -771,7 +770,7 @@ class AzureDevOpsClient { // ============================================================================ function printQueueUsage(): void { - const scriptName = 'node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts queue'; + const scriptName = 'node .github/skills/azure-pipelines/azure-pipeline.ts queue'; console.log(`Usage: ${scriptName} [options]`); console.log(''); console.log('Queue an Azure DevOps pipeline build for VS Code.'); @@ -779,21 +778,23 @@ function printQueueUsage(): void { console.log('Options:'); console.log(' --branch Source branch to build (default: current git branch)'); console.log(' --definition Pipeline definition ID (default: 111)'); - console.log(' --variables Pipeline variables in "KEY=value KEY2=value2" format'); + console.log(' --parameter Pipeline parameter in "KEY=value" format (repeatable)'); + console.log(' --parameters Space-separated parameter list in "KEY=value KEY2=value2" format'); console.log(' --dry-run Print the command without executing'); console.log(' --help Show this help message'); console.log(''); console.log('Examples:'); console.log(` ${scriptName} # Queue build on current branch`); console.log(` ${scriptName} --branch my-feature # Queue build on specific branch`); - console.log(` ${scriptName} --variables "SKIP_TESTS=true" # Queue with custom variables`); + console.log(` ${scriptName} --parameter "VSCODE_BUILD_WEB=false" --parameter "VSCODE_PUBLISH=false"`); + console.log(` ${scriptName} --parameter "VSCODE_BUILD_TYPE=Product Build" # Parameter values with spaces`); } function parseQueueArgs(args: string[]): QueueArgs { const result: QueueArgs = { branch: '', definitionId: DEFAULT_DEFINITION_ID, - variables: '', + parameters: [], dryRun: false, help: false, }; @@ -807,8 +808,15 @@ function parseQueueArgs(args: string[]): QueueArgs { case '--definition': result.definitionId = args[++i] || DEFAULT_DEFINITION_ID; break; - case '--variables': - result.variables = args[++i] || ''; + case '--parameter': { + const parameter = args[++i] || ''; + if (parameter) { + result.parameters.push(parameter); + } + break; + } + case '--parameters': + result.parameters.push(...(args[++i] || '').split(' ').filter(v => v.length > 0)); break; case '--dry-run': result.dryRun = true; @@ -829,7 +837,7 @@ function parseQueueArgs(args: string[]): QueueArgs { function validateQueueArgs(args: QueueArgs): void { validateNumericId(args.definitionId, '--definition'); validateBranch(args.branch); - validateVariables(args.variables); + validateParameters(args.parameters); } async function runQueueCommand(args: string[]): Promise { @@ -860,8 +868,8 @@ async function runQueueCommand(args: string[]): Promise { console.log(`Project: ${colors.green(PROJECT)}`); console.log(`Definition: ${colors.green(parsedArgs.definitionId)}`); console.log(`Branch: ${colors.green(branch)}`); - if (parsedArgs.variables) { - console.log(`Variables: ${colors.green(parsedArgs.variables)}`); + if (parsedArgs.parameters.length > 0) { + console.log(`Parameters: ${colors.green(parsedArgs.parameters.join(' '))}`); } console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); console.log(''); @@ -875,8 +883,8 @@ async function runQueueCommand(args: string[]): Promise { '--id', parsedArgs.definitionId, '--branch', branch, ]; - if (parsedArgs.variables) { - cmdArgs.push('--variables', ...parsedArgs.variables.split(' ')); + if (parsedArgs.parameters.length > 0) { + cmdArgs.push('--parameters', ...parsedArgs.parameters); } cmdArgs.push('--output', 'json'); console.log(`az ${cmdArgs.join(' ')}`); @@ -887,7 +895,7 @@ async function runQueueCommand(args: string[]): Promise { try { const client = new AzureDevOpsClient(ORGANIZATION, PROJECT); - const data = await client.queueBuild(parsedArgs.definitionId, branch, parsedArgs.variables); + const data = await client.queueBuild(parsedArgs.definitionId, branch, parsedArgs.parameters); const buildId = data.id; const buildNumber = data.buildNumber; @@ -904,10 +912,10 @@ async function runQueueCommand(args: string[]): Promise { console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); console.log(''); console.log('To check status, run:'); - console.log(` node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status --build-id ${buildId}`); + console.log(` node .github/skills/azure-pipelines/azure-pipeline.ts status --build-id ${buildId}`); console.log(''); console.log('To watch progress:'); - console.log(` node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status --build-id ${buildId} --watch`); + console.log(` node .github/skills/azure-pipelines/azure-pipeline.ts status --build-id ${buildId} --watch`); } catch (e) { const error = e instanceof Error ? e : new Error(String(e)); console.error(colors.red('Error queuing build:')); @@ -921,7 +929,7 @@ async function runQueueCommand(args: string[]): Promise { // ============================================================================ function printStatusUsage(): void { - const scriptName = 'node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status'; + const scriptName = 'node .github/skills/azure-pipelines/azure-pipeline.ts status'; console.log(`Usage: ${scriptName} [options]`); console.log(''); console.log('Get status and logs of an Azure DevOps pipeline build.'); @@ -1068,7 +1076,7 @@ async function runStatusCommand(args: string[]): Promise { if (!buildId) { console.error(colors.red(`Error: No builds found for branch '${branch}'.`)); - console.log('You can queue a new build with: node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts queue'); + console.log('You can queue a new build with: node .github/skills/azure-pipelines/azure-pipeline.ts queue'); process.exit(1); } } @@ -1162,7 +1170,7 @@ async function runStatusCommand(args: string[]): Promise { // ============================================================================ function printCancelUsage(): void { - const scriptName = 'node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts cancel'; + const scriptName = 'node .github/skills/azure-pipelines/azure-pipeline.ts cancel'; console.log(`Usage: ${scriptName} --build-id [options]`); console.log(''); console.log('Cancel a running Azure DevOps pipeline build.'); @@ -1233,7 +1241,7 @@ async function runCancelCommand(args: string[]): Promise { console.error(colors.red('Error: --build-id is required.')); console.log(''); console.log('To find build IDs, run:'); - console.log(' node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status'); + console.log(' node .github/skills/azure-pipelines/azure-pipeline.ts status'); process.exit(1); } @@ -1287,7 +1295,7 @@ async function runCancelCommand(args: string[]): Promise { console.log(''); console.log('The build will transition to "cancelling" state and then "canceled".'); console.log('Check status with:'); - console.log(` node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status --build-id ${buildId}`); + console.log(` node .github/skills/azure-pipelines/azure-pipeline.ts status --build-id ${buildId}`); } catch (e) { const error = e instanceof Error ? e : new Error(String(e)); console.error(''); @@ -1390,15 +1398,15 @@ async function runAllTests(): Promise { validateBranch(''); }); - it('validateVariables accepts valid variable formats', () => { - validateVariables('KEY=value'); - validateVariables('MY_VAR=some-value'); - validateVariables('A=1 B=2 C=3'); - validateVariables('PATH=/usr/bin:path'); + it('validateParameters accepts valid parameter formats', () => { + validateParameters(['KEY=value']); + validateParameters(['MY_VAR=some-value']); + validateParameters(['A=1', 'B=2', 'C=3']); + validateParameters(['PATH=/usr/bin:path']); }); - it('validateVariables accepts empty string', () => { - validateVariables(''); + it('validateParameters accepts empty list', () => { + validateParameters([]); }); it('validateArtifactName accepts valid artifact names', () => { @@ -1429,9 +1437,14 @@ async function runAllTests(): Promise { assert.strictEqual(args.definitionId, '222'); }); - it('parseQueueArgs parses --variables correctly', () => { - const args = parseQueueArgs(['--variables', 'KEY=value']); - assert.strictEqual(args.variables, 'KEY=value'); + it('parseQueueArgs parses --parameters correctly', () => { + const args = parseQueueArgs(['--parameters', 'KEY=value']); + assert.deepStrictEqual(args.parameters, ['KEY=value']); + }); + + it('parseQueueArgs parses repeated --parameter correctly', () => { + const args = parseQueueArgs(['--parameter', 'A=1', '--parameter', 'B=two words']); + assert.deepStrictEqual(args.parameters, ['A=1', 'B=two words']); }); it('parseQueueArgs parses --dry-run correctly', () => { @@ -1440,10 +1453,10 @@ async function runAllTests(): Promise { }); it('parseQueueArgs parses combined arguments', () => { - const args = parseQueueArgs(['--branch', 'main', '--definition', '333', '--variables', 'A=1 B=2', '--dry-run']); + const args = parseQueueArgs(['--branch', 'main', '--definition', '333', '--parameters', 'A=1 B=2', '--dry-run']); assert.strictEqual(args.branch, 'main'); assert.strictEqual(args.definitionId, '333'); - assert.strictEqual(args.variables, 'A=1 B=2'); + assert.deepStrictEqual(args.parameters, ['A=1', 'B=2']); assert.strictEqual(args.dryRun, true); }); @@ -1516,12 +1529,12 @@ async function runAllTests(): Promise { assert.ok(cmd.includes('json')); }); - it('queueBuild includes variables when provided', async () => { + it('queueBuild includes parameters when provided', async () => { const client = new TestableAzureDevOpsClient(ORGANIZATION, PROJECT); - await client.queueBuild('111', 'main', 'KEY=value OTHER=test'); + await client.queueBuild('111', 'main', ['KEY=value', 'OTHER=test']); const cmd = client.capturedCommands[0]; - assert.ok(cmd.includes('--variables')); + assert.ok(cmd.includes('--parameters')); assert.ok(cmd.includes('KEY=value')); assert.ok(cmd.includes('OTHER=test')); }); @@ -1718,7 +1731,7 @@ async function runAllTests(): Promise { describe('Integration Tests', () => { it('full queue command flow constructs correct az commands', async () => { const client = new TestableAzureDevOpsClient(ORGANIZATION, PROJECT); - await client.queueBuild('111', 'feature/test', 'DEBUG=true'); + await client.queueBuild('111', 'feature/test', ['DEBUG=true']); assert.strictEqual(client.capturedCommands.length, 1); const cmd = client.capturedCommands[0]; @@ -1733,7 +1746,7 @@ async function runAllTests(): Promise { assert.ok(cmd.includes('111')); assert.ok(cmd.includes('--branch')); assert.ok(cmd.includes('feature/test')); - assert.ok(cmd.includes('--variables')); + assert.ok(cmd.includes('--parameters')); assert.ok(cmd.includes('DEBUG=true')); }); @@ -1797,7 +1810,7 @@ async function runAllTests(): Promise { // ============================================================================ function printMainUsage(): void { - const scriptName = 'node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts'; + const scriptName = 'node .github/skills/azure-pipelines/azure-pipeline.ts'; console.log(`Usage: ${scriptName} [options]`); console.log(''); console.log('Azure DevOps Pipeline CLI for VS Code builds.'); diff --git a/.github/skills/component-fixtures/SKILL.md b/.github/skills/component-fixtures/SKILL.md new file mode 100644 index 0000000000000..6c7eb5a6059dc --- /dev/null +++ b/.github/skills/component-fixtures/SKILL.md @@ -0,0 +1,343 @@ +--- +name: component-fixtures +description: Use when creating or updating component fixtures for screenshot testing, or when designing UI components to be fixture-friendly. Covers fixture file structure, theming, service setup, CSS scoping, async rendering, and common pitfalls. +--- + +# Component Fixtures + +Component fixtures render isolated UI components for visual screenshot testing via the component explorer. Fixtures live in `src/vs/workbench/test/browser/componentFixtures/` and are auto-discovered by the Vite dev server using the glob `src/**/*.fixture.ts`. + +Use tools `mcp_component-exp_`* to list and screenshot fixtures. If you cannot see these tools, inform the user to them on. + +## Running Fixtures Locally + +1. Start the component explorer daemon: run the **Launch Component Explorer** task +2. Use the `mcp_component-exp_list_fixtures` tool to see all available fixtures and their URLs +3. Use the `mcp_component-exp_screenshot` tool to capture screenshots programmatically + +## File Structure + +Each fixture file exports a default `defineThemedFixtureGroup(...)`. The file must end with `.fixture.ts`. + +``` +src/vs/workbench/test/browser/componentFixtures/ + fixtureUtils.ts # Shared helpers (DO NOT import @vscode/component-explorer elsewhere) + myComponent.fixture.ts # Your fixture file +``` + +## Basic Pattern + +```typescript +import { ComponentFixtureContext, createEditorServices, defineComponentFixture, defineThemedFixtureGroup } from './fixtureUtils.js'; + +export default defineThemedFixtureGroup({ path: 'myFeature/' }, { + Default: defineComponentFixture({ render: renderMyComponent }), + AnotherVariant: defineComponentFixture({ render: renderMyComponent }), +}); + +function renderMyComponent({ container, disposableStore, theme }: ComponentFixtureContext): void { + container.style.width = '400px'; + + const instantiationService = createEditorServices(disposableStore, { + colorTheme: theme, + additionalServices: (reg) => { + // Register additional services the component needs + reg.define(IMyService, MyServiceImpl); + reg.defineInstance(IMockService, mockInstance); + }, + }); + + const widget = disposableStore.add( + instantiationService.createInstance(MyWidget, /* constructor args */) + ); + container.appendChild(widget.domNode); +} +``` + +Key points: +- **`defineThemedFixtureGroup`** automatically creates Dark and Light variants for each fixture +- **`defineComponentFixture`** wraps your render function with theme setup and shadow DOM isolation +- **`createEditorServices`** provides a `TestInstantiationService` with base editor services pre-registered +- Always register created widgets with `disposableStore.add(...)` to prevent leaks +- Pass `colorTheme: theme` to `createEditorServices` so theme colors render correctly + +## Utilities from fixtureUtils.ts + +| Export | Purpose | +|---|---| +| `defineComponentFixture` | Creates Dark/Light themed fixture variants from a render function | +| `defineThemedFixtureGroup` | Groups multiple themed fixtures into a named fixture group | +| `createEditorServices` | Creates `TestInstantiationService` with all base editor services | +| `registerWorkbenchServices` | Registers additional workbench services (context menu, label, etc.) | +| `createTextModel` | Creates a text model via `ModelService` for editor fixtures | +| `setupTheme` | Applies theme CSS to a container (called automatically by `defineComponentFixture`) | +| `darkTheme` / `lightTheme` | Pre-loaded `ColorThemeData` instances | + +**Important:** Only `fixtureUtils.ts` may import from `@vscode/component-explorer`. All fixture files must go through the helpers in `fixtureUtils.ts`. + +## CSS Scoping + +Fixtures render inside shadow DOM. The component-explorer automatically adopts the global VS Code stylesheets and theme CSS. + +### Matching production CSS selectors + +Many VS Code components have CSS rules scoped to deep ancestor selectors (e.g., `.interactive-session .interactive-input-part > .widget-container .my-element`). In fixtures, you must recreate the required ancestor DOM structure for these selectors to match: + +```typescript +function render({ container }: ComponentFixtureContext): void { + container.classList.add('interactive-session'); + + // Recreate ancestor structure that CSS selectors expect + const inputPart = dom.$('.interactive-input-part'); + const widgetContainer = dom.$('.widget-container'); + inputPart.appendChild(widgetContainer); + container.appendChild(inputPart); + + widgetContainer.appendChild(myWidget.domNode); +} +``` + +**Design recommendation for new components:** Avoid deeply nested CSS selectors that require specific ancestor elements. Use self-contained class names (e.g., `.my-widget .my-element` rather than `.parent-view .parent-part > .wrapper .my-element`). This makes components easier to fixture and reuse. + +## Services + +### Using createEditorServices + +`createEditorServices` pre-registers these services: `IAccessibilityService`, `IKeybindingService`, `IClipboardService`, `IOpenerService`, `INotificationService`, `IDialogService`, `IUndoRedoService`, `ILanguageService`, `IConfigurationService`, `IStorageService`, `IThemeService`, `IModelService`, `ICodeEditorService`, `IContextKeyService`, `ICommandService`, `ITelemetryService`, `IHoverService`, `IUserInteractionService`, and more. + +### Additional services + +Register extra services via `additionalServices`: + +```typescript +createEditorServices(disposableStore, { + additionalServices: (reg) => { + // Class-based (instantiated by DI): + reg.define(IMyService, MyServiceImpl); + // Instance-based (pre-constructed): + reg.defineInstance(IMyService, myMockInstance); + }, +}); +``` + +### Mocking services + +Use the `mock()` helper from `base/test/common/mock.js` to create mock service instances: + +```typescript +import { mock } from '../../../../base/test/common/mock.js'; + +const myService = new class extends mock() { + override someMethod(): string { return 'test'; } + override onSomeEvent = Event.None; +}; +reg.defineInstance(IMyService, myService); +``` + +For mock view models or data objects: +```typescript +const element = new class extends mock() { }(); +``` + +## Async Rendering + +The component explorer waits **2 animation frames** after the synchronous render function returns. For most components, this is sufficient. + +If your render function returns a `Promise`, the component explorer waits for the promise to resolve. + +### Pitfall: DOM reparenting causes flickering + +Avoid moving rendered widgets between DOM parents after initial render. This causes: +- Layout recalculation (the widget jumps as `position: absolute` coordinates become invalid) +- Focus loss (blur events can trigger hide logic in widgets like QuickInput) +- Screenshot instability (the component explorer may capture an intermediate layout state) + +**Bad pattern — reparenting a widget after async wait:** +```typescript +async function render({ container }: ComponentFixtureContext): Promise { + const host = document.createElement('div'); + container.appendChild(host); + // ... create widget inside host ... + await waitForWidget(); + container.appendChild(widget); // BAD: reparenting causes flicker + host.remove(); +} +``` + +**Better pattern — render in-place with the correct DOM structure from the start:** +```typescript +function render({ container }: ComponentFixtureContext): void { + // Set up the correct DOM structure first, then create the widget inside it + const widget = createWidget(container); + container.appendChild(widget.domNode); +} +``` + +If the component absolutely requires async setup (e.g., QuickInput which renders internally), minimize DOM manipulation after the widget appears by structuring the host container to match the final layout from the beginning. + +## Adapting Existing Components for Fixtures + +Existing components often need small changes to become fixturable. When writing a fixture reveals friction, fix the component — don't work around it in the fixture. Common adaptations: + +### Decouple CSS from ancestor context + +If a component's CSS only works inside a deeply nested selector like `.workbench .sidebar .my-view .my-widget`, refactor the CSS to be self-contained. Move the styles so they're scoped to the component's own root class: + +```css +/* Before: requires specific ancestors */ +.workbench .sidebar .my-view .my-widget .header { font-weight: bold; } + +/* After: self-contained */ +.my-widget .header { font-weight: bold; } +``` + +If the component shares styles with its parent (e.g., inheriting background color), use CSS custom properties rather than relying on ancestor selectors. + +### Extract hard-coded service dependencies + +If a component reaches into singletons or global state instead of using DI, refactor it to accept services through the constructor: + +```typescript +// Before: hard to mock in fixtures +class MyWidget { + private readonly config = getSomeGlobalConfig(); +} + +// After: injectable and testable +class MyWidget { + constructor(@IConfigurationService private readonly configService: IConfigurationService) { } +} +``` + +### Add options to control auto-focus and animation + +Components that auto-focus on creation or run animations cause flaky screenshots. Add an options parameter: + +```typescript +interface IMyWidgetOptions { + shouldAutoFocus?: boolean; +} +``` + +The fixture passes `shouldAutoFocus: false`. The production call site keeps the default behavior. + +### Expose internal state for "already completed" rendering + +Many components have lifecycle states (loading → active → completed). If the component can only reach the "completed" state through user interaction, add support for initializing directly into that state via constructor data: + +```typescript +// The fixture can pass pre-filled data to render the summary/completed state +// without simulating the full user interaction flow. +const carousel: IChatQuestionCarousel = { + questions, + allowSkip: true, + kind: 'questionCarousel', + isUsed: true, // Already completed + data: { 'q1': 'answer' }, // Pre-filled answers +}; +``` + +### Make DOM node accessible + +If a component builds its DOM internally and doesn't expose the root element, add a public `readonly domNode: HTMLElement` property so fixtures can append it to the container. + +## Writing Fixture-Friendly Components + +When designing new UI components, follow these practices to make them easy to fixture: + +### 1. Accept a container element in the constructor + +```typescript +// Good: container is passed in +class MyWidget { + constructor(container: HTMLElement, @IFoo foo: IFoo) { + this.domNode = dom.append(container, dom.$('.my-widget')); + } +} + +// Also good: widget creates its own domNode for the caller to place +class MyWidget { + readonly domNode: HTMLElement; + constructor(@IFoo foo: IFoo) { + this.domNode = dom.$('.my-widget'); + } +} +``` + +### 2. Use dependency injection for all services + +All external dependencies should come through DI so fixtures can provide test implementations: + +```typescript +// Good: services injected +constructor(@IThemeService private readonly themeService: IThemeService) { } + +// Bad: reaching into globals +constructor() { this.theme = getGlobalTheme(); } +``` + +### 3. Keep CSS selectors shallow + +```css +/* Good: self-contained, easy to fixture */ +.my-widget .my-header { ... } +.my-widget .my-list-item { ... } + +/* Bad: requires deep ancestor chain */ +.workbench .sidebar .my-view .my-widget .my-header { ... } +``` + +### 4. Avoid reading from layout/window services during construction + +Components that measure the window or read layout dimensions during construction are hard to fixture because the shadow DOM container has different dimensions than the workbench: + +```typescript +// Prefer: use CSS for sizing, or accept dimensions as parameters +container.style.width = '400px'; +container.style.height = '300px'; + +// Avoid: reading from layoutService during construction +const width = this.layoutService.mainContainerDimension.width; +``` + +### 5. Support disabling auto-focus in fixtures + +Auto-focus can interfere with screenshot stability. Provide options to disable it: + +```typescript +interface IMyWidgetOptions { + shouldAutoFocus?: boolean; // Fixtures pass false +} +``` + +### 6. Expose the DOM node + +The fixture needs to append the widget's DOM to the container. Expose it as a public `readonly domNode: HTMLElement`. + +## Multiple Fixture Variants + +Create variants to show different states of the same component: + +```typescript +export default defineThemedFixtureGroup({ + // Different data states + Empty: defineComponentFixture({ render: (ctx) => renderWidget(ctx, { items: [] }) }), + WithItems: defineComponentFixture({ render: (ctx) => renderWidget(ctx, { items: sampleItems }) }), + + // Different configurations + ReadOnly: defineComponentFixture({ render: (ctx) => renderWidget(ctx, { readonly: true }) }), + Editable: defineComponentFixture({ render: (ctx) => renderWidget(ctx, { readonly: false }) }), + + // Lifecycle states + Loading: defineComponentFixture({ render: (ctx) => renderWidget(ctx, { state: 'loading' }) }), + Completed: defineComponentFixture({ render: (ctx) => renderWidget(ctx, { state: 'done' }) }), +}); +``` + +## Learnings + +Update this section with insights from your fixture development experience! + +* Do not copy the component to the fixture and modify it there. Always adapt the original component to be fixture-friendly, then render it in the fixture. This ensures the fixture tests the real component code and lifecycle, rather than a modified version that may hide bugs. + +* **Don't recompose child widgets in fixtures.** Never manually instantiate and add a sub-widget (e.g., a toolbar content widget) that the parent component is supposed to create. Instead, configure the parent correctly (e.g., set the right editor option, register the right provider) so the child appears through the normal code path. Manually recomposing hides integration bugs and doesn't test the real widget lifecycle. diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index b988d19f49ea6..bb87ac077bce2 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -77,7 +77,7 @@ jobs: working-directory: build - name: Compile & Hygiene - 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 + 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 }} diff --git a/.github/workflows/screenshot-test.yml b/.github/workflows/screenshot-test.yml index a45f8d38133bb..decfcf2a6f84d 100644 --- a/.github/workflows/screenshot-test.yml +++ b/.github/workflows/screenshot-test.yml @@ -1,4 +1,4 @@ -name: Screenshot Tests +name: Checking Component Screenshots on: push: @@ -10,8 +10,6 @@ on: permissions: contents: read - pull-requests: write - checks: write statuses: write concurrency: @@ -20,15 +18,16 @@ concurrency: jobs: screenshots: + name: Checking Component Screenshots runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: lfs: true - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version-file: .nvmrc @@ -95,39 +94,19 @@ jobs: REPORT="test/componentFixtures/.screenshots/report/report.json" 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" + TITLE="⚠ ${CHANGED} screenshots changed" else - TITLE="Screenshots match" + TITLE="✅ Screenshots match" fi SHA="${{ github.event.pull_request.head.sha || github.sha }}" - CHECK_RUN_ID=$(gh api "repos/${{ github.repository }}/commits/$SHA/check-runs" \ - --jq '.check_runs[] | select(.name == "screenshots") | .id') - DETAILS_URL="https://hediet-ghartifactpreview.azurewebsites.net/${{ github.repository }}/run/${{ github.run_id }}/component-explorer/___explorer.html?report=./screenshot-report/report.json" - if [ -n "$CHECK_RUN_ID" ]; then - gh api "repos/${{ github.repository }}/check-runs/$CHECK_RUN_ID" \ - -X PATCH --input - <> $GITHUB_STEP_SUMMARY - else - echo "## Screenshots ✅" >> $GITHUB_STEP_SUMMARY - echo "No visual changes detected." >> $GITHUB_STEP_SUMMARY - fi - # - name: Post PR comment # if: github.event_name == 'pull_request' # env: diff --git a/.vscode/extensions/vscode-extras/package-lock.json b/.vscode/extensions/vscode-extras/package-lock.json new file mode 100644 index 0000000000000..3268c74682804 --- /dev/null +++ b/.vscode/extensions/vscode-extras/package-lock.json @@ -0,0 +1,16 @@ +{ + "name": "vscode-extras", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vscode-extras", + "version": "0.0.1", + "license": "MIT", + "engines": { + "vscode": "^1.88.0" + } + } + } +} diff --git a/.vscode/extensions/vscode-extras/package.json b/.vscode/extensions/vscode-extras/package.json new file mode 100644 index 0000000000000..c773d5923c322 --- /dev/null +++ b/.vscode/extensions/vscode-extras/package.json @@ -0,0 +1,38 @@ +{ + "name": "vscode-extras", + "displayName": "VS Code Extras", + "description": "Extra utility features for the VS Code selfhost workspace", + "engines": { + "vscode": "^1.88.0" + }, + "version": "0.0.1", + "publisher": "ms-vscode", + "categories": [ + "Other" + ], + "activationEvents": [ + "workspaceContains:src/vscode-dts/vscode.d.ts" + ], + "main": "./out/extension.js", + "repository": { + "type": "git", + "url": "https://github.com/microsoft/vscode.git" + }, + "license": "MIT", + "scripts": { + "compile": "gulp compile-extension:vscode-extras", + "watch": "gulp watch-extension:vscode-extras" + }, + "contributes": { + "configuration": { + "title": "VS Code Extras", + "properties": { + "vscode-extras.npmUpToDateFeature.enabled": { + "type": "boolean", + "default": true, + "description": "Show a status bar warning when npm dependencies are out of date." + } + } + } + } +} diff --git a/.vscode/extensions/vscode-extras/src/extension.ts b/.vscode/extensions/vscode-extras/src/extension.ts new file mode 100644 index 0000000000000..675bfe9177549 --- /dev/null +++ b/.vscode/extensions/vscode-extras/src/extension.ts @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { NpmUpToDateFeature } from './npmUpToDateFeature'; + +export class Extension extends vscode.Disposable { + private readonly _output: vscode.LogOutputChannel; + private _npmFeature: NpmUpToDateFeature | undefined; + + constructor(_context: vscode.ExtensionContext) { + const disposables: vscode.Disposable[] = []; + super(() => disposables.forEach(d => d.dispose())); + + this._output = vscode.window.createOutputChannel('VS Code Extras', { log: true }); + disposables.push(this._output); + + this._updateNpmFeature(); + + disposables.push( + vscode.workspace.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('vscode-extras.npmUpToDateFeature.enabled')) { + this._updateNpmFeature(); + } + }) + ); + } + + private _updateNpmFeature(): void { + const enabled = vscode.workspace.getConfiguration('vscode-extras').get('npmUpToDateFeature.enabled', true); + if (enabled && !this._npmFeature) { + this._npmFeature = new NpmUpToDateFeature(this._output); + } else if (!enabled && this._npmFeature) { + this._npmFeature.dispose(); + this._npmFeature = undefined; + } + } +} + +let extension: Extension | undefined; + +export function activate(context: vscode.ExtensionContext) { + extension = new Extension(context); + context.subscriptions.push(extension); +} + +export function deactivate() { + extension = undefined; +} diff --git a/.vscode/extensions/vscode-extras/src/npmUpToDateFeature.ts b/.vscode/extensions/vscode-extras/src/npmUpToDateFeature.ts new file mode 100644 index 0000000000000..df9abf863ae52 --- /dev/null +++ b/.vscode/extensions/vscode-extras/src/npmUpToDateFeature.ts @@ -0,0 +1,260 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as cp from 'child_process'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as vscode from 'vscode'; + +interface FileHashes { + readonly [relativePath: string]: string; +} + +interface PostinstallState { + readonly nodeVersion: string; + readonly fileHashes: FileHashes; +} + +interface InstallState { + readonly root: string; + readonly stateContentsFile: string; + readonly current: PostinstallState; + readonly saved: PostinstallState | undefined; + readonly files: readonly string[]; +} + +export class NpmUpToDateFeature extends vscode.Disposable { + private readonly _statusBarItem: vscode.StatusBarItem; + private readonly _disposables: vscode.Disposable[] = []; + private _watchers: fs.FSWatcher[] = []; + private _terminal: vscode.Terminal | undefined; + private _stateContentsFile: string | undefined; + private _root: string | undefined; + + private static readonly _scheme = 'npm-dep-state'; + + constructor(private readonly _output: vscode.LogOutputChannel) { + const disposables: vscode.Disposable[] = []; + super(() => { + disposables.forEach(d => d.dispose()); + for (const w of this._watchers) { + w.close(); + } + }); + this._disposables = disposables; + + this._statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 10000); + this._statusBarItem.name = 'npm Install State'; + this._statusBarItem.text = '$(warning) node_modules is stale - run npm i'; + this._statusBarItem.tooltip = 'Dependencies are out of date. Click to run npm install.'; + this._statusBarItem.command = 'vscode-extras.runNpmInstall'; + this._statusBarItem.backgroundColor = new vscode.ThemeColor('statusBarItem.warningBackground'); + this._disposables.push(this._statusBarItem); + + this._disposables.push( + vscode.workspace.registerTextDocumentContentProvider(NpmUpToDateFeature._scheme, { + provideTextDocumentContent: (uri) => { + const params = new URLSearchParams(uri.query); + const source = params.get('source'); + const file = uri.path.slice(1); // strip leading / + if (source === 'saved') { + return this._readSavedContent(file); + } + return this._readCurrentContent(file); + } + }) + ); + + this._disposables.push( + vscode.commands.registerCommand('vscode-extras.runNpmInstall', () => this._runNpmInstall()) + ); + + this._disposables.push( + vscode.commands.registerCommand('vscode-extras.showDependencyDiff', (file: string) => this._showDiff(file)) + ); + + this._disposables.push( + vscode.window.onDidCloseTerminal(t => { + if (t === this._terminal) { + this._terminal = undefined; + this._check(); + } + }) + ); + + this._check(); + } + + private _runNpmInstall(): void { + if (this._terminal) { + this._terminal.dispose(); + } + const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri; + if (!workspaceRoot) { + return; + } + this._terminal = vscode.window.createTerminal({ name: 'npm install', cwd: workspaceRoot }); + this._terminal.sendText('node build/npm/fast-install.ts --force'); + this._terminal.show(); + + this._statusBarItem.text = '$(loading~spin) npm i'; + this._statusBarItem.tooltip = 'npm install is running...'; + this._statusBarItem.backgroundColor = undefined; + this._statusBarItem.command = 'vscode-extras.runNpmInstall'; + } + + private _queryState(): InstallState | undefined { + const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath; + if (!workspaceRoot) { + return undefined; + } + try { + const script = path.join(workspaceRoot, 'build', 'npm', 'installStateHash.ts'); + const output = cp.execFileSync(process.execPath, [script], { + cwd: workspaceRoot, + timeout: 10_000, + encoding: 'utf8', + }); + const parsed = JSON.parse(output.trim()); + this._output.trace('raw output:', output.trim()); + return parsed; + } catch (e) { + this._output.error('_queryState error:', e as any); + return undefined; + } + } + + private _check(): void { + const state = this._queryState(); + this._output.trace('state:', JSON.stringify(state, null, 2)); + if (!state) { + this._output.trace('no state, hiding'); + this._statusBarItem.hide(); + return; + } + + this._stateContentsFile = state.stateContentsFile; + this._root = state.root; + this._setupWatcher(state); + + const changedFiles = this._getChangedFiles(state); + this._output.trace('changedFiles:', JSON.stringify(changedFiles)); + + if (changedFiles.length === 0) { + this._statusBarItem.hide(); + } else { + this._statusBarItem.text = '$(warning) node_modules is stale - run npm i'; + const tooltip = new vscode.MarkdownString(); + tooltip.isTrusted = true; + tooltip.supportHtml = true; + tooltip.appendMarkdown('**Dependencies are out of date.** Click to run npm install.\n\nChanged files:\n\n'); + for (const entry of changedFiles) { + if (entry.isFile) { + const args = encodeURIComponent(JSON.stringify(entry.label)); + tooltip.appendMarkdown(`- [${entry.label}](command:vscode-extras.showDependencyDiff?${args})\n`); + } else { + tooltip.appendMarkdown(`- ${entry.label}\n`); + } + } + this._statusBarItem.tooltip = tooltip; + this._statusBarItem.backgroundColor = new vscode.ThemeColor('statusBarItem.warningBackground'); + this._statusBarItem.show(); + } + } + + private _showDiff(file: string): void { + const cacheBuster = Date.now().toString(); + const savedUri = vscode.Uri.from({ + scheme: NpmUpToDateFeature._scheme, + path: `/${file}`, + query: new URLSearchParams({ source: 'saved', t: cacheBuster }).toString(), + }); + const currentUri = vscode.Uri.from({ + scheme: NpmUpToDateFeature._scheme, + path: `/${file}`, + query: new URLSearchParams({ source: 'current', t: cacheBuster }).toString(), + }); + + vscode.commands.executeCommand('vscode.diff', savedUri, currentUri, `${file} (last install ↔ current)`); + } + + private _readSavedContent(file: string): string { + if (!this._stateContentsFile) { + return ''; + } + try { + const contents: Record = JSON.parse(fs.readFileSync(this._stateContentsFile, 'utf8')); + return contents[file] ?? ''; + } catch { + return ''; + } + } + + private _readCurrentContent(file: string): string { + if (!this._root) { + return ''; + } + try { + return this._normalizeFileContent(path.join(this._root, file)); + } catch { + return ''; + } + } + + private _normalizeFileContent(filePath: string): string { + const raw = fs.readFileSync(filePath, 'utf8'); + if (path.basename(filePath) === 'package.json') { + const json = JSON.parse(raw); + for (const key of NpmUpToDateFeature._packageJsonIgnoredKeys) { + delete json[key]; + } + return JSON.stringify(json, null, '\t') + '\n'; + } + return raw; + } + + private static readonly _packageJsonIgnoredKeys = ['distro']; + + private _getChangedFiles(state: InstallState): { readonly label: string; readonly isFile: boolean }[] { + if (!state.saved) { + return [{ label: '(no postinstall state found)', isFile: false }]; + } + const changed: { readonly label: string; readonly isFile: boolean }[] = []; + if (state.saved.nodeVersion !== state.current.nodeVersion) { + changed.push({ label: `Node.js version (${state.saved.nodeVersion} → ${state.current.nodeVersion})`, isFile: false }); + } + const allKeys = new Set([...Object.keys(state.current.fileHashes), ...Object.keys(state.saved.fileHashes)]); + for (const key of allKeys) { + if (state.current.fileHashes[key] !== state.saved.fileHashes[key]) { + changed.push({ label: key, isFile: true }); + } + } + return changed; + } + + private _setupWatcher(state: InstallState): void { + for (const w of this._watchers) { + w.close(); + } + this._watchers = []; + + let debounceTimer: ReturnType | undefined; + const scheduleCheck = () => { + if (debounceTimer) { + clearTimeout(debounceTimer); + } + debounceTimer = setTimeout(() => this._check(), 500); + }; + + for (const file of state.files) { + try { + const watcher = fs.watch(file, scheduleCheck); + this._watchers.push(watcher); + } catch { + // file may not exist yet + } + } + } +} diff --git a/.vscode/extensions/vscode-extras/tsconfig.json b/.vscode/extensions/vscode-extras/tsconfig.json new file mode 100644 index 0000000000000..9133c3bbf4b87 --- /dev/null +++ b/.vscode/extensions/vscode-extras/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../../extensions/tsconfig.base.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./out", + "types": [ + "node" + ] + }, + "include": [ + "src/**/*", + "../../../src/vscode-dts/vscode.d.ts" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json index d116d2c003389..47d901042e3a8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -278,6 +278,51 @@ "hidden": true, }, }, + { + "type": "chrome", + "request": "launch", + "name": "Launch VS Sessions Internal", + "windows": { + "runtimeExecutable": "${workspaceFolder}/scripts/code.bat" + }, + "osx": { + "runtimeExecutable": "${workspaceFolder}/scripts/code.sh" + }, + "linux": { + "runtimeExecutable": "${workspaceFolder}/scripts/code.sh" + }, + "port": 9222, + "timeout": 0, + "env": { + "VSCODE_EXTHOST_WILL_SEND_SOCKET": null, + "VSCODE_SKIP_PRELAUNCH": "1", + "VSCODE_DEV_DEBUG_OBSERVABLES": "1", + }, + "cleanUp": "wholeBrowser", + "killBehavior": "polite", + "runtimeArgs": [ + "--inspect-brk=5875", + "--no-cached-data", + "--crash-reporter-directory=${workspaceFolder}/.profile-oss/crashes", + // for general runtime freezes: https://github.com/microsoft/vscode/issues/127861#issuecomment-904144910 + "--disable-features=CalculateNativeWinOcclusion", + "--disable-extension=vscode.vscode-api-tests", + "--sessions" + ], + "userDataDir": "${userHome}/.vscode-oss-sessions-dev", + "webRoot": "${workspaceFolder}", + "cascadeTerminateToConfigurations": [ + "Attach to Extension Host" + ], + "pauseForSourceMap": false, + "outFiles": [ + "${workspaceFolder}/out/**/*.js" + ], + "browserLaunchLocation": "workspace", + "presentation": { + "hidden": true, + }, + }, { // To debug observables you also need the extension "ms-vscode.debug-value-editor" "type": "chrome", @@ -603,9 +648,19 @@ } }, { - "name": "Component Explorer", + "name": "Component Explorer (Edge)", "type": "msedge", - "port": 9230, + "request": "launch", + "url": "http://localhost:5337/___explorer", + "preLaunchTask": "Launch Component Explorer", + "presentation": { + "group": "1_component_explorer", + "order": 4 + } + }, + { + "name": "Component Explorer (Chrome)", + "type": "chrome", "request": "launch", "url": "http://localhost:5337/___explorer", "preLaunchTask": "Launch Component Explorer", @@ -653,6 +708,21 @@ "order": 1 } }, + { + "name": "VS Sessions", + "stopAll": true, + "configurations": [ + "Launch VS Sessions Internal", + "Attach to Main Process", + "Attach to Extension Host", + "Attach to Shared Process", + ], + "preLaunchTask": "Ensure Prelaunch Dependencies", + "presentation": { + "group": "0_vscode", + "order": 1 + } + }, { "name": "VS Code (Hot Reload)", "stopAll": true, diff --git a/.vscode/notebooks/my-work.github-issues b/.vscode/notebooks/my-work.github-issues index b6c82fff3590b..c4bc569e9da31 100644 --- a/.vscode/notebooks/my-work.github-issues +++ b/.vscode/notebooks/my-work.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "// list of repos we work in\n$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce repo:microsoft/vscode-copilot-issues repo:microsoft/vscode-extension-samples\n\n// current milestone name\n$MILESTONE=milestone:\"February 2026\"\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:\"March 2026\"\n" }, { "kind": 1, diff --git a/.vscode/tasks.json b/.vscode/tasks.json index c330df2edecc9..9e9cc12ca99ca 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -241,6 +241,19 @@ "inSessions": true, "problemMatcher": [] }, + { + "label": "Run and Compile Dev Sessions", + "type": "shell", + "command": "npm run transpile-client && ./scripts/code.sh", + "windows": { + "command": "npm run transpile-client && .\\scripts\\code.bat" + }, + "args": [ + "--sessions" + ], + "inSessions": true, + "problemMatcher": [] + }, { "type": "npm", "script": "electron", @@ -375,9 +388,23 @@ { "label": "Launch Component Explorer", "type": "shell", - "command": "npx component-explorer serve -c ./test/componentFixtures/component-explorer.json", + "command": "npx component-explorer serve -c ./test/componentFixtures/component-explorer.json -vv", "isBackground": true, - "problemMatcher": [] + "problemMatcher": { + "owner": "component-explorer", + "fileLocation": "absolute", + "pattern": { + "regexp": "^\\s*at\\s+(.+?):(\\d+):(\\d+)\\s*$", + "file": 1, + "line": 2, + "column": 3 + }, + "background": { + "activeOnStart": true, + "beginsPattern": ".*Setting up sessions.*", + "endsPattern": "Redirection server listening on.*" + } + } } ] } diff --git a/build/azure-pipelines/alpine/product-build-alpine.yml b/build/azure-pipelines/alpine/product-build-alpine.yml index 5c5714e9d5b12..a9a1b0d1292ba 100644 --- a/build/azure-pipelines/alpine/product-build-alpine.yml +++ b/build/azure-pipelines/alpine/product-build-alpine.yml @@ -64,15 +64,6 @@ jobs: 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 @@ -173,6 +164,11 @@ jobs: - template: ../common/install-builtin-extensions.yml@self + - script: npm run gulp core-ci + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Compile + - script: | set -e TARGET=$([ "$VSCODE_ARCH" == "x64" ] && echo "linux-alpine" || echo "alpine-arm64") # TODO@joaomoreno diff --git a/build/azure-pipelines/common/extract-telemetry.sh b/build/azure-pipelines/common/extract-telemetry.sh deleted file mode 100755 index 9cebe22bfd189..0000000000000 --- a/build/azure-pipelines/common/extract-telemetry.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash -set -e - -cd $BUILD_STAGINGDIRECTORY -mkdir extraction -cd extraction -git clone --depth 1 https://github.com/microsoft/vscode-extension-telemetry.git -git clone --depth 1 https://github.com/microsoft/vscode-chrome-debug-core.git -git clone --depth 1 https://github.com/microsoft/vscode-node-debug2.git -git clone --depth 1 https://github.com/microsoft/vscode-node-debug.git -git clone --depth 1 https://github.com/microsoft/vscode-html-languageservice.git -git clone --depth 1 https://github.com/microsoft/vscode-json-languageservice.git -node $BUILD_SOURCESDIRECTORY/node_modules/.bin/vscode-telemetry-extractor --sourceDir $BUILD_SOURCESDIRECTORY --excludedDir $BUILD_SOURCESDIRECTORY/extensions --outputDir . --applyEndpoints -node $BUILD_SOURCESDIRECTORY/node_modules/.bin/vscode-telemetry-extractor --config $BUILD_SOURCESDIRECTORY/build/azure-pipelines/common/telemetry-config.json -o . -mkdir -p $BUILD_SOURCESDIRECTORY/.build/telemetry -mv declarations-resolved.json $BUILD_SOURCESDIRECTORY/.build/telemetry/telemetry-core.json -mv config-resolved.json $BUILD_SOURCESDIRECTORY/.build/telemetry/telemetry-extensions.json -cd .. -rm -rf extraction diff --git a/build/azure-pipelines/common/extract-telemetry.ts b/build/azure-pipelines/common/extract-telemetry.ts new file mode 100644 index 0000000000000..a5fafac71d5f8 --- /dev/null +++ b/build/azure-pipelines/common/extract-telemetry.ts @@ -0,0 +1,95 @@ +/*--------------------------------------------------------------------------------------------- + * 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 fs from 'fs'; +import os from 'os'; +import path from 'path'; + +const BUILD_STAGINGDIRECTORY = process.env.BUILD_STAGINGDIRECTORY ?? fs.mkdtempSync(path.join(os.tmpdir(), 'vscode-telemetry-')); +const BUILD_SOURCESDIRECTORY = process.env.BUILD_SOURCESDIRECTORY ?? path.resolve(import.meta.dirname, '..', '..', '..'); + +const extractionDir = path.join(BUILD_STAGINGDIRECTORY, 'extraction'); +fs.mkdirSync(extractionDir, { recursive: true }); + +const repos = [ + 'https://github.com/microsoft/vscode-extension-telemetry.git', + 'https://github.com/microsoft/vscode-chrome-debug-core.git', + 'https://github.com/microsoft/vscode-node-debug2.git', + 'https://github.com/microsoft/vscode-node-debug.git', + 'https://github.com/microsoft/vscode-html-languageservice.git', + 'https://github.com/microsoft/vscode-json-languageservice.git', +]; + +for (const repo of repos) { + cp.execSync(`git clone --depth 1 ${repo}`, { cwd: extractionDir, stdio: 'inherit' }); +} + +const extractor = path.join(BUILD_SOURCESDIRECTORY, 'node_modules', '@vscode', 'telemetry-extractor', 'out', 'extractor.js'); +const telemetryConfig = path.join(BUILD_SOURCESDIRECTORY, 'build', 'azure-pipelines', 'common', 'telemetry-config.json'); + +interface ITelemetryConfigEntry { + eventPrefix: string; + sourceDirs: string[]; + excludedDirs: string[]; + applyEndpoints: boolean; + patchDebugEvents?: boolean; +} + +const pipelineExtensionsPathPrefix = '../../s/extensions/'; + +const telemetryConfigEntries = JSON.parse(fs.readFileSync(telemetryConfig, 'utf8')) as ITelemetryConfigEntry[]; +let hasLocalConfigOverrides = false; + +const resolvedTelemetryConfigEntries = telemetryConfigEntries.map(entry => { + const sourceDirs = entry.sourceDirs.map(sourceDir => { + if (!sourceDir.startsWith(pipelineExtensionsPathPrefix)) { + return sourceDir; + } + + const sourceDirInExtractionDir = path.resolve(extractionDir, sourceDir); + if (fs.existsSync(sourceDirInExtractionDir)) { + return sourceDir; + } + + const extensionRelativePath = sourceDir.slice(pipelineExtensionsPathPrefix.length); + const sourceDirInWorkspace = path.join(BUILD_SOURCESDIRECTORY, 'extensions', extensionRelativePath); + if (fs.existsSync(sourceDirInWorkspace)) { + hasLocalConfigOverrides = true; + return sourceDirInWorkspace; + } + + return sourceDir; + }); + + return { + ...entry, + sourceDirs, + }; +}); + +const telemetryConfigForExtraction = hasLocalConfigOverrides + ? path.join(extractionDir, 'telemetry-config.local.json') + : telemetryConfig; + +if (hasLocalConfigOverrides) { + fs.writeFileSync(telemetryConfigForExtraction, JSON.stringify(resolvedTelemetryConfigEntries, null, '\t')); +} + +try { + cp.execSync(`node "${extractor}" --sourceDir "${BUILD_SOURCESDIRECTORY}" --excludedDir "${path.join(BUILD_SOURCESDIRECTORY, 'extensions')}" --outputDir . --applyEndpoints`, { cwd: extractionDir, stdio: 'inherit' }); + cp.execSync(`node "${extractor}" --config "${telemetryConfigForExtraction}" -o .`, { cwd: extractionDir, stdio: 'inherit' }); +} catch (error) { + const message = error instanceof Error ? error.message : String(error); + console.error(`Telemetry extraction failed: ${message}`); + process.exit(1); +} + +const telemetryDir = path.join(BUILD_SOURCESDIRECTORY, '.build', 'telemetry'); +fs.mkdirSync(telemetryDir, { recursive: true }); +fs.renameSync(path.join(extractionDir, 'declarations-resolved.json'), path.join(telemetryDir, 'telemetry-core.json')); +fs.renameSync(path.join(extractionDir, 'config-resolved.json'), path.join(telemetryDir, 'telemetry-extensions.json')); + +fs.rmSync(extractionDir, { recursive: true, force: true }); diff --git a/build/azure-pipelines/common/publish.ts b/build/azure-pipelines/common/publish.ts index 572efa57bf998..fd621e4224021 100644 --- a/build/azure-pipelines/common/publish.ts +++ b/build/azure-pipelines/common/publish.ts @@ -970,15 +970,7 @@ async function main() { console.log(`\u2705 ${name}`); } - const stages = new Set(['Compile']); - - if ( - e('VSCODE_BUILD_STAGE_LINUX') === 'True' || - e('VSCODE_BUILD_STAGE_MACOS') === 'True' || - e('VSCODE_BUILD_STAGE_WINDOWS') === 'True' - ) { - stages.add('CompileCLI'); - } + const stages = new Set(['Quality']); if (e('VSCODE_BUILD_STAGE_WINDOWS') === 'True') { stages.add('Windows'); } if (e('VSCODE_BUILD_STAGE_LINUX') === 'True') { stages.add('Linux'); } diff --git a/build/azure-pipelines/common/sanity-tests.yml b/build/azure-pipelines/common/sanity-tests.yml index 3606777f9a375..ce6d95dd7e59d 100644 --- a/build/azure-pipelines/common/sanity-tests.yml +++ b/build/azure-pipelines/common/sanity-tests.yml @@ -29,18 +29,36 @@ jobs: name: ${{ parameters.poolName }} os: ${{ parameters.os }} timeoutInMinutes: 30 + templateContext: + outputs: + - output: pipelineArtifact + targetPath: $(SCREENSHOTS_DIR) + artifactName: screenshots-${{ parameters.name }} + displayName: Publish Screenshots + condition: succeededOrFailed() + continueOnError: true + sbomEnabled: false variables: TEST_DIR: $(Build.SourcesDirectory)/test/sanity LOG_FILE: $(TEST_DIR)/results.xml + SCREENSHOTS_DIR: $(TEST_DIR)/screenshots DOCKER_CACHE_DIR: $(Pipeline.Workspace)/docker-cache DOCKER_CACHE_FILE: $(DOCKER_CACHE_DIR)/${{ parameters.container }}.tar steps: - checkout: self fetchDepth: 1 fetchTags: false - sparseCheckoutDirectories: test/sanity .nvmrc + sparseCheckoutDirectories: build/azure-pipelines/config test/sanity .nvmrc displayName: Checkout test/sanity + - ${{ if eq(parameters.os, 'windows') }}: + - script: mkdir "$(SCREENSHOTS_DIR)" + displayName: Create Screenshots Directory + + - ${{ else }}: + - bash: mkdir -p "$(SCREENSHOTS_DIR)" + displayName: Create Screenshots Directory + - ${{ if and(eq(parameters.os, 'windows'), eq(parameters.arch, 'arm64')) }}: - script: | @echo off @@ -101,19 +119,19 @@ jobs: # Windows - ${{ if eq(parameters.os, 'windows') }}: - - script: $(TEST_DIR)/scripts/run-win32.cmd -c $(BUILD_COMMIT) -q $(BUILD_QUALITY) -t $(LOG_FILE) -v ${{ parameters.args }} + - script: $(TEST_DIR)/scripts/run-win32.cmd -c $(BUILD_COMMIT) -q $(BUILD_QUALITY) -t $(LOG_FILE) -s $(SCREENSHOTS_DIR) -v ${{ parameters.args }} workingDirectory: $(TEST_DIR) displayName: Run Sanity Tests # macOS - ${{ if eq(parameters.os, 'macOS') }}: - - bash: $(TEST_DIR)/scripts/run-macOS.sh -c $(BUILD_COMMIT) -q $(BUILD_QUALITY) -t $(LOG_FILE) -v ${{ parameters.args }} + - bash: $(TEST_DIR)/scripts/run-macOS.sh -c $(BUILD_COMMIT) -q $(BUILD_QUALITY) -t $(LOG_FILE) -s $(SCREENSHOTS_DIR) -v ${{ parameters.args }} workingDirectory: $(TEST_DIR) displayName: Run Sanity Tests # Native Linux host - ${{ if and(eq(parameters.container, ''), eq(parameters.os, 'linux')) }}: - - bash: $(TEST_DIR)/scripts/run-ubuntu.sh -c $(BUILD_COMMIT) -q $(BUILD_QUALITY) -t $(LOG_FILE) -v ${{ parameters.args }} + - bash: $(TEST_DIR)/scripts/run-ubuntu.sh -c $(BUILD_COMMIT) -q $(BUILD_QUALITY) -t $(LOG_FILE) -s $(SCREENSHOTS_DIR) -v ${{ parameters.args }} workingDirectory: $(TEST_DIR) displayName: Run Sanity Tests @@ -141,6 +159,7 @@ jobs: --quality "$(BUILD_QUALITY)" \ --commit "$(BUILD_COMMIT)" \ --test-results "/root/results.xml" \ + --screenshots-dir "/root/screenshots" \ --verbose \ ${{ parameters.args }} workingDirectory: $(TEST_DIR) diff --git a/build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml b/build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml deleted file mode 100644 index 94eee5e476c2a..0000000000000 --- a/build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml +++ /dev/null @@ -1,86 +0,0 @@ -parameters: - - name: VSCODE_BUILD_MACOS - type: boolean - - name: VSCODE_BUILD_MACOS_ARM64 - type: boolean - -jobs: - - job: macOSCLISign - timeoutInMinutes: 90 - templateContext: - outputParentDirectory: $(Build.ArtifactStagingDirectory)/out - outputs: - - ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}: - - output: pipelineArtifact - targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_cli_darwin_x64_cli/vscode_cli_darwin_x64_cli.zip - artifactName: vscode_cli_darwin_x64_cli - displayName: Publish signed artifact with ID vscode_cli_darwin_x64_cli - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/sign/unsigned_vscode_cli_darwin_x64_cli - sbomPackageName: "VS Code macOS x64 CLI" - sbomPackageVersion: $(Build.SourceVersion) - - ${{ if eq(parameters.VSCODE_BUILD_MACOS_ARM64, true) }}: - - output: pipelineArtifact - targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_cli_darwin_arm64_cli/vscode_cli_darwin_arm64_cli.zip - artifactName: vscode_cli_darwin_arm64_cli - displayName: Publish signed artifact with ID vscode_cli_darwin_arm64_cli - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/sign/unsigned_vscode_cli_darwin_arm64_cli - sbomPackageName: "VS Code macOS arm64 CLI" - sbomPackageVersion: $(Build.SourceVersion) - steps: - - template: ../common/checkout.yml@self - - - 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/build/azure-pipelines/darwin/product-build-darwin-cli.yml b/build/azure-pipelines/darwin/product-build-darwin-cli.yml index dc5a5d79c1457..1b6ea51bd146f 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-cli.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-cli.yml @@ -9,8 +9,8 @@ parameters: jobs: - job: macOSCLI_${{ parameters.VSCODE_ARCH }} - displayName: macOS (${{ upper(parameters.VSCODE_ARCH) }}) - timeoutInMinutes: 60 + displayName: macOS CLI (${{ upper(parameters.VSCODE_ARCH) }}) + timeoutInMinutes: 90 pool: name: AcesShared os: macOS @@ -24,11 +24,12 @@ jobs: 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 + targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_cli_darwin_$(VSCODE_ARCH)_cli/vscode_cli_darwin_$(VSCODE_ARCH)_cli.zip + artifactName: vscode_cli_darwin_$(VSCODE_ARCH)_cli + displayName: Publish vscode_cli_darwin_$(VSCODE_ARCH)_cli artifact + sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/sign/unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli + sbomPackageName: "VS Code macOS $(VSCODE_ARCH) CLI" + sbomPackageVersion: $(Build.SourceVersion) steps: - template: ../common/checkout.yml@self @@ -83,3 +84,55 @@ jobs: 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) }}: + - template: ../common/publish-artifact.yml@self + parameters: + targetPath: $(Build.ArtifactStagingDirectory)/unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli.zip + artifactName: unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli + displayName: Publish unsigned CLI + sbomEnabled: false + + - script: | + set -e + mkdir -p $(Build.ArtifactStagingDirectory)/pkg/unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli + cp $(Build.ArtifactStagingDirectory)/unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli.zip $(Build.ArtifactStagingDirectory)/pkg/unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli/unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli.zip + displayName: Prepare CLI for signing + + - task: ExtractFiles@1 + displayName: Extract unsigned CLI (for SBOM) + inputs: + archiveFilePatterns: $(Build.ArtifactStagingDirectory)/unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli.zip + destinationFolder: $(Build.ArtifactStagingDirectory)/sign/unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_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' + + - script: node build/azure-pipelines/common/sign.ts $(Agent.RootDirectory)/_tasks/EsrpCodeSigning_*/*/net6.0/esrpcli.dll sign-darwin $(Build.ArtifactStagingDirectory)/pkg/unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli "*.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/unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli "*.zip" + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: ✍️ Notarize + + - script: | + set -e + mkdir -p $(Build.ArtifactStagingDirectory)/out/vscode_cli_darwin_$(VSCODE_ARCH)_cli + mv $(Build.ArtifactStagingDirectory)/pkg/unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli/unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli.zip $(Build.ArtifactStagingDirectory)/out/vscode_cli_darwin_$(VSCODE_ARCH)_cli/vscode_cli_darwin_$(VSCODE_ARCH)_cli.zip + displayName: Rename signed artifact diff --git a/build/azure-pipelines/darwin/steps/product-build-darwin-cli-sign.yml b/build/azure-pipelines/darwin/steps/product-build-darwin-cli-sign.yml deleted file mode 100644 index 1cd0fe2a8245f..0000000000000 --- a/build/azure-pipelines/darwin/steps/product-build-darwin-cli-sign.yml +++ /dev/null @@ -1,53 +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.ts $(Agent.RootDirectory)/_tasks/EsrpCodeSigning_*/*/net6.0/esrpcli.dll sign-darwin $(Build.ArtifactStagingDirectory)/pkg "*.zip" - env: - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - displayName: ✍️ Codesign - - - script: node build/azure-pipelines/common/sign.ts $(Agent.RootDirectory)/_tasks/EsrpCodeSigning_*/*/net6.0/esrpcli.dll notarize-darwin $(Build.ArtifactStagingDirectory)/pkg "*.zip" - env: - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - displayName: ✍️ Notarize - - - ${{ each target in parameters.VSCODE_CLI_ARTIFACTS }}: - - script: | - set -e - ASSET_ID=$(echo "${{ target }}" | sed "s/unsigned_//") - mkdir -p $(Build.ArtifactStagingDirectory)/out/$ASSET_ID - mv $(Build.ArtifactStagingDirectory)/pkg/${{ target }}/${{ target }}.zip $(Build.ArtifactStagingDirectory)/out/$ASSET_ID/$ASSET_ID.zip - echo "##vso[task.setvariable variable=ASSET_ID]$ASSET_ID" - displayName: Set asset id variable diff --git a/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml b/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml index 64b91f714016f..cd5f6c287c01c 100644 --- a/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml +++ b/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml @@ -30,15 +30,6 @@ steps: 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 @@ -112,11 +103,33 @@ steps: condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) displayName: Create node_modules archive + - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: + - script: npx deemon --detach --wait -- node build/azure-pipelines/common/waitForArtifacts.ts unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: Wait for CLI artifact (background) + - script: node build/azure-pipelines/distro/mixin-quality.ts displayName: Mixin distro quality - template: ../../common/install-builtin-extensions.yml@self + - script: npm run gulp core-ci + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Compile + + - script: node build/azure-pipelines/common/extract-telemetry.ts + displayName: Generate lists of telemetry events + + - ${{ if or(eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true), eq(parameters.VSCODE_RUN_BROWSER_TESTS, true), eq(parameters.VSCODE_RUN_REMOTE_TESTS, true)) }}: + - script: | + set -e + npm run compile --prefix test/smoke + npm run compile --prefix test/integration/browser + displayName: Compile test suites (non-OSS) + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - ${{ 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 @@ -147,6 +160,11 @@ steps: displayName: Build server (web) - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: + - script: npx deemon --attach -- node build/azure-pipelines/common/waitForArtifacts.ts unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: Wait for CLI artifact + - task: DownloadPipelineArtifact@2 inputs: artifact: unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli diff --git a/build/azure-pipelines/linux/product-build-linux-ci.yml b/build/azure-pipelines/linux/product-build-linux-ci.yml index 6c6b102891a7e..619aff676407e 100644 --- a/build/azure-pipelines/linux/product-build-linux-ci.yml +++ b/build/azure-pipelines/linux/product-build-linux-ci.yml @@ -5,6 +5,9 @@ parameters: type: string - name: VSCODE_TEST_SUITE type: string + - name: VSCODE_RUN_CHECKS + type: boolean + default: false jobs: - job: Linux${{ parameters.VSCODE_TEST_SUITE }} @@ -43,6 +46,7 @@ jobs: VSCODE_ARCH: x64 VSCODE_CIBUILD: ${{ parameters.VSCODE_CIBUILD }} VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} + VSCODE_RUN_CHECKS: ${{ parameters.VSCODE_RUN_CHECKS }} ${{ if eq(parameters.VSCODE_TEST_SUITE, 'Electron') }}: VSCODE_RUN_ELECTRON_TESTS: true ${{ if eq(parameters.VSCODE_TEST_SUITE, 'Browser') }}: diff --git a/build/azure-pipelines/linux/product-build-linux-cli.yml b/build/azure-pipelines/linux/product-build-linux-cli.yml index ef160c2cc3849..a9107129b73b5 100644 --- a/build/azure-pipelines/linux/product-build-linux-cli.yml +++ b/build/azure-pipelines/linux/product-build-linux-cli.yml @@ -9,7 +9,7 @@ parameters: jobs: - job: LinuxCLI_${{ parameters.VSCODE_ARCH }} - displayName: Linux (${{ upper(parameters.VSCODE_ARCH) }}) + displayName: Linux CLI (${{ upper(parameters.VSCODE_ARCH) }}) timeoutInMinutes: 60 pool: name: 1es-ubuntu-22.04-x64 diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 31eb7c3d46668..00ffd0aaab07e 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -19,6 +19,9 @@ parameters: - name: VSCODE_RUN_REMOTE_TESTS type: boolean default: false + - name: VSCODE_RUN_CHECKS + type: boolean + default: false jobs: - job: Linux_${{ parameters.VSCODE_ARCH }} @@ -26,6 +29,7 @@ jobs: timeoutInMinutes: 90 variables: DISPLAY: ":10" + BUILDS_API_URL: $(System.CollectionUri)$(System.TeamProject)/_apis/build/builds/$(Build.BuildId)/ NPM_ARCH: ${{ parameters.NPM_ARCH }} VSCODE_ARCH: ${{ parameters.VSCODE_ARCH }} templateContext: @@ -110,3 +114,4 @@ jobs: 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_RUN_CHECKS: ${{ parameters.VSCODE_RUN_CHECKS }} diff --git a/build/azure-pipelines/linux/steps/product-build-linux-compile.yml b/build/azure-pipelines/linux/steps/product-build-linux-compile.yml index 89199ebbbb14c..82e1a19107d20 100644 --- a/build/azure-pipelines/linux/steps/product-build-linux-compile.yml +++ b/build/azure-pipelines/linux/steps/product-build-linux-compile.yml @@ -17,6 +17,9 @@ parameters: - name: VSCODE_RUN_REMOTE_TESTS type: boolean default: false + - name: VSCODE_RUN_CHECKS + type: boolean + default: false steps: - template: ../../common/checkout.yml@self @@ -35,15 +38,6 @@ steps: 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 @@ -165,11 +159,34 @@ steps: condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) displayName: Create node_modules archive + - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: + - script: npx deemon --detach --wait -- node build/azure-pipelines/common/waitForArtifacts.ts $(ARTIFACT_PREFIX)vscode_cli_linux_$(VSCODE_ARCH)_cli + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: Wait for CLI artifact (background) + - script: node build/azure-pipelines/distro/mixin-quality.ts displayName: Mixin distro quality - template: ../../common/install-builtin-extensions.yml@self + - script: npm run gulp core-ci + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Compile + + - ${{ if eq(parameters.VSCODE_ARCH, 'x64') }}: + - script: node build/azure-pipelines/common/extract-telemetry.ts + displayName: Generate lists of telemetry events + + - ${{ if or(eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true), eq(parameters.VSCODE_RUN_BROWSER_TESTS, true), eq(parameters.VSCODE_RUN_REMOTE_TESTS, true)) }}: + - script: | + set -e + npm run compile --prefix test/smoke + npm run compile --prefix test/integration/browser + displayName: Compile test suites (non-OSS) + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - ${{ 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 @@ -187,6 +204,11 @@ steps: displayName: Build client - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: + - script: npx deemon --attach -- node build/azure-pipelines/common/waitForArtifacts.ts $(ARTIFACT_PREFIX)vscode_cli_linux_$(VSCODE_ARCH)_cli + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: Wait for CLI artifact + - task: DownloadPipelineArtifact@2 inputs: artifact: $(ARTIFACT_PREFIX)vscode_cli_linux_$(VSCODE_ARCH)_cli diff --git a/build/azure-pipelines/product-build-macos.yml b/build/azure-pipelines/product-build-macos.yml deleted file mode 100644 index cc563953b0071..0000000000000 --- a/build/azure-pipelines/product-build-macos.yml +++ /dev/null @@ -1,106 +0,0 @@ -pr: none - -trigger: none - -parameters: - - name: VSCODE_QUALITY - displayName: Quality - type: string - default: insider - - name: NPM_REGISTRY - displayName: "Custom NPM Registry" - type: string - default: 'https://pkgs.dev.azure.com/monacotools/Monaco/_packaging/vscode/npm/registry/' - - name: CARGO_REGISTRY - displayName: "Custom Cargo Registry" - type: string - default: 'sparse+https://pkgs.dev.azure.com/monacotools/Monaco/_packaging/vscode/Cargo/index/' - -variables: - - name: NPM_REGISTRY - ${{ if in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI') }}: # disable terrapin when in VSCODE_CIBUILD - value: none - ${{ else }}: - value: ${{ parameters.NPM_REGISTRY }} - - name: CARGO_REGISTRY - value: ${{ parameters.CARGO_REGISTRY }} - - name: VSCODE_QUALITY - 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 - value: 600 - - name: Codeql.SkipTaskAutoInjection - value: true - - name: ARTIFACT_PREFIX - value: '' - -name: "$(Date:yyyyMMdd).$(Rev:r) (${{ parameters.VSCODE_QUALITY }})" - -resources: - repositories: - - repository: 1esPipelines - type: git - name: 1ESPipelineTemplates/1ESPipelineTemplates - ref: refs/tags/release - -extends: - template: v1/1ES.Official.PipelineTemplate.yml@1esPipelines - parameters: - sdl: - tsa: - enabled: true - configFile: $(Build.SourcesDirectory)/build/azure-pipelines/config/tsaoptions.json - codeql: - runSourceLanguagesInSourceAnalysis: true - compiled: - enabled: false - justificationForDisabling: "CodeQL breaks ESRP CodeSign on macOS (ICM #520035761, githubcustomers/microsoft-codeql-support#198)" - credscan: - suppressionsFile: $(Build.SourcesDirectory)/build/azure-pipelines/config/CredScanSuppressions.json - eslint: - enabled: true - enableExclusions: true - exclusionsFilePath: $(Build.SourcesDirectory)/.eslint-ignore - sourceAnalysisPool: 1es-windows-2022-x64 - createAdoIssuesForJustificationsForDisablement: false - containers: - ubuntu-2004-arm64: - 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: - - template: build/azure-pipelines/product-compile.yml@self - - - stage: macOS - dependsOn: - - Compile - pool: - name: AcesShared - os: macOS - demands: - - ImageOverride -equals ACES_VM_SharedPool_Sequoia - variables: - BUILDSECMON_OPT_IN: true - jobs: - - template: build/azure-pipelines/darwin/product-build-darwin-ci.yml@self - parameters: - VSCODE_CIBUILD: true - VSCODE_TEST_SUITE: Electron - - template: build/azure-pipelines/darwin/product-build-darwin-ci.yml@self - parameters: - VSCODE_CIBUILD: true - VSCODE_TEST_SUITE: Browser - - template: build/azure-pipelines/darwin/product-build-darwin-ci.yml@self - parameters: - VSCODE_CIBUILD: true - VSCODE_TEST_SUITE: Remote diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index 77c3dd0665f9e..e016db506862f 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -26,6 +26,13 @@ parameters: - exploration - insider - stable + - name: VSCODE_BUILD_TYPE + displayName: Build Type + type: string + default: Product + values: + - Product + - CI - name: NPM_REGISTRY displayName: "Custom NPM Registry" type: string @@ -90,10 +97,6 @@ parameters: displayName: "Release build if successful" type: boolean default: false - - name: VSCODE_COMPILE_ONLY - displayName: "Run Compile stage exclusively" - type: boolean - default: false - name: VSCODE_STEP_ON_IT displayName: "Skip tests" type: boolean @@ -119,9 +122,9 @@ variables: - name: VSCODE_BUILD_STAGE_WEB value: ${{ eq(parameters.VSCODE_BUILD_WEB, true) }} - name: VSCODE_CIBUILD - value: ${{ in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI') }} + value: ${{ or(in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI'), eq(parameters.VSCODE_BUILD_TYPE, 'CI')) }} - name: VSCODE_PUBLISH - value: ${{ and(eq(parameters.VSCODE_PUBLISH, true), eq(variables.VSCODE_CIBUILD, false), eq(parameters.VSCODE_COMPILE_ONLY, false)) }} + value: ${{ and(eq(parameters.VSCODE_PUBLISH, true), eq(variables.VSCODE_CIBUILD, false)) }} - name: VSCODE_SCHEDULEDBUILD value: ${{ eq(variables['Build.Reason'], 'Schedule') }} - name: VSCODE_STEP_ON_IT @@ -190,27 +193,21 @@ extends: ubuntu-2004-arm64: image: onebranch.azurecr.io/linux/ubuntu-2004-arm64:latest stages: - - stage: Compile + + - stage: Quality + dependsOn: [] pool: - name: AcesShared - os: macOS - demands: - - ImageOverride -equals ACES_VM_SharedPool_Sequoia + name: 1es-ubuntu-22.04-x64 + os: linux jobs: - - template: build/azure-pipelines/product-compile.yml@self + - template: build/azure-pipelines/product-quality-checks.yml@self - - ${{ if eq(variables['VSCODE_PUBLISH'], 'true') }}: - - stage: ValidationChecks + - ${{ if eq(variables['VSCODE_BUILD_STAGE_WINDOWS'], true) }}: + - stage: Windows dependsOn: [] pool: - name: 1es-ubuntu-22.04-x64 - os: linux - jobs: - - template: build/azure-pipelines/product-validation-checks.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_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: [] + name: 1es-windows-2022-x64 + os: windows jobs: - ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}: - template: build/azure-pipelines/win32/product-build-win32-cli.yml@self @@ -225,88 +222,6 @@ extends: 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)) }}: - - 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 eq(parameters.VSCODE_BUILD_MACOS, true) }}: - - 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)) }}: - - 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'], 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 - dependsOn: [] - pool: - name: 1es-windows-2022-x64 - os: windows - jobs: - - job: WindowsAPIScan - steps: - - template: build/azure-pipelines/win32/sdl-scan-win32.yml@self - parameters: - VSCODE_ARCH: x64 - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - - - ${{ if and(eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_WINDOWS'], true)) }}: - - stage: Windows - dependsOn: - - 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_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-windows-2022-x64 - os: windows - jobs: - ${{ if eq(variables['VSCODE_CIBUILD'], true) }}: - template: build/azure-pipelines/win32/product-build-win32-ci.yml@self parameters: @@ -341,22 +256,32 @@ extends: 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)) }}: + - ${{ if eq(variables['VSCODE_BUILD_STAGE_LINUX'], true) }}: - stage: Linux - dependsOn: - - 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_MACOS, true),eq(parameters.VSCODE_BUILD_MACOS_ARM64, true),eq(parameters.VSCODE_BUILD_WIN32, true),eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}: - - CompileCLI + dependsOn: [] pool: name: 1es-ubuntu-22.04-x64 os: linux jobs: + - ${{ 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)) }}: + - 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 eq(variables['VSCODE_CIBUILD'], true) }}: - template: build/azure-pipelines/linux/product-build-linux-ci.yml@self parameters: @@ -402,10 +327,9 @@ extends: 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)) }}: + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(variables['VSCODE_BUILD_STAGE_ALPINE'], true)) }}: - stage: Alpine - dependsOn: - - Compile + dependsOn: [] jobs: - ${{ if eq(parameters.VSCODE_BUILD_ALPINE, true) }}: - template: build/azure-pipelines/alpine/product-build-alpine.yml@self @@ -424,12 +348,9 @@ extends: VSCODE_ARCH: arm64 VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - - ${{ if and(eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_MACOS'], true)) }}: + - ${{ if eq(variables['VSCODE_BUILD_STAGE_MACOS'], true) }}: - stage: macOS - dependsOn: - - 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_MACOS, true),eq(parameters.VSCODE_BUILD_MACOS_ARM64, true),eq(parameters.VSCODE_BUILD_WIN32, true),eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}: - - CompileCLI + dependsOn: [] pool: name: AcesShared os: macOS @@ -438,6 +359,19 @@ extends: variables: BUILDSECMON_OPT_IN: true jobs: + - ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}: + - 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)) }}: + - template: build/azure-pipelines/darwin/product-build-darwin-cli.yml@self + parameters: + VSCODE_ARCH: arm64 + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + - ${{ if eq(variables['VSCODE_CIBUILD'], true) }}: - template: build/azure-pipelines/darwin/product-build-darwin-ci.yml@self parameters: @@ -470,20 +404,13 @@ extends: - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(variables['VSCODE_BUILD_MACOS_UNIVERSAL'], true)) }}: - 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)) }}: + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(variables['VSCODE_BUILD_STAGE_WEB'], true)) }}: - stage: Web - dependsOn: - - Compile + dependsOn: [] jobs: - template: build/azure-pipelines/web/product-build-web.yml@self - - ${{ if eq(variables['VSCODE_PUBLISH'], 'true') }}: + - ${{ if eq(variables['VSCODE_PUBLISH'], true) }}: - stage: Publish dependsOn: [] jobs: @@ -811,3 +738,43 @@ extends: - template: build/azure-pipelines/product-release.yml@self parameters: VSCODE_RELEASE: ${{ parameters.VSCODE_RELEASE }} + + - ${{ if eq(variables['VSCODE_CIBUILD'], true) }}: + - 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 eq(variables['VSCODE_CIBUILD'], false) }}: + - stage: APIScan + dependsOn: [] + pool: + name: 1es-windows-2022-x64 + os: windows + jobs: + - job: WindowsAPIScan + steps: + - template: build/azure-pipelines/win32/sdl-scan-win32.yml@self + parameters: + VSCODE_ARCH: x64 + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} diff --git a/build/azure-pipelines/product-compile.yml b/build/azure-pipelines/product-quality-checks.yml similarity index 64% rename from build/azure-pipelines/product-compile.yml rename to build/azure-pipelines/product-quality-checks.yml index bc13d980df2dd..983a0a4b25aea 100644 --- a/build/azure-pipelines/product-compile.yml +++ b/build/azure-pipelines/product-quality-checks.yml @@ -1,14 +1,12 @@ 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 + - job: Quality + displayName: Quality Checks + timeoutInMinutes: 20 + variables: + - name: skipComponentGovernanceDetection + value: true + - name: Codeql.SkipTaskAutoInjection + value: true steps: - template: ./common/checkout.yml@self @@ -30,7 +28,7 @@ jobs: 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 + - script: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.ts quality $(node -p process.arch) > .build/packagelockhash displayName: Prepare node_modules cache key - task: Cache@2 @@ -46,9 +44,6 @@ jobs: - 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')) @@ -71,7 +66,38 @@ jobs: 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')) + + - script: | + set -e + export VSCODE_SYSROOT_DIR=$(Build.SourcesDirectory)/.build/sysroots/glibc-2.28-gcc-8.5.0 + SYSROOT_ARCH="amd64" 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: x64 + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Download vscode sysroots + + - script: | + set -e + + source ./build/azure-pipelines/linux/setup-env.sh + 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: x64 + VSCODE_ARCH: x64 ELECTRON_SKIP_BINARY_DOWNLOAD: 1 PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 GITHUB_TOKEN: "$(github-distro-mixin-password)" @@ -93,43 +119,37 @@ jobs: - 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 + - script: node build/azure-pipelines/common/checkDistroCommit.ts + displayName: Check distro commit env: GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Compile & Hygiene - - - script: | - set -e - - [ -d "out-build" ] || { echo "ERROR: out-build folder is missing" >&2; exit 1; } - [ -n "$(find out-build -mindepth 1 2>/dev/null | head -1)" ] || { echo "ERROR: out-build folder is empty" >&2; exit 1; } - echo "out-build exists and is not empty" + BUILD_SOURCEBRANCH: "$(Build.SourceBranch)" + continueOnError: true + condition: and(succeeded(), eq(lower(variables['VSCODE_PUBLISH']), 'true')) - ls -d out-vscode-* >/dev/null 2>&1 || { echo "ERROR: No out-vscode-* folders found" >&2; exit 1; } - for folder in out-vscode-*; do - [ -d "$folder" ] || { echo "ERROR: $folder is missing" >&2; exit 1; } - [ -n "$(find "$folder" -mindepth 1 2>/dev/null | head -1)" ] || { echo "ERROR: $folder is empty" >&2; exit 1; } - echo "$folder exists and is not empty" - done + - script: node build/azure-pipelines/common/checkCopilotChatCompatibility.ts --warn-only + displayName: Check Copilot Chat compatibility + continueOnError: true + condition: and(succeeded(), eq(lower(variables['VSCODE_PUBLISH']), 'true')) - echo "All required compilation folders checked." - displayName: Validate compilation folders + - script: 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: "$(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: npm run download-builtin-extensions-cg + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Download component details of built-in extensions + condition: and(succeeded(), eq(lower(variables['VSCODE_PUBLISH']), 'true')) - - 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: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 + displayName: "Component Detection" + inputs: + sourceScanPath: $(Build.SourcesDirectory) + alertWarningLevel: Medium + continueOnError: true + condition: and(succeeded(), eq(lower(variables['VSCODE_PUBLISH']), 'true')) - task: AzureCLI@2 displayName: Fetch secrets @@ -142,6 +162,7 @@ jobs: 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" + condition: and(succeeded(), eq(lower(variables['VSCODE_PUBLISH']), 'true')) - script: | set -e @@ -151,21 +172,4 @@ jobs: 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 + condition: and(succeeded(), eq(lower(variables['VSCODE_PUBLISH']), 'true')) diff --git a/build/azure-pipelines/product-validation-checks.yml b/build/azure-pipelines/product-validation-checks.yml deleted file mode 100644 index adf61f33c428c..0000000000000 --- a/build/azure-pipelines/product-validation-checks.yml +++ /dev/null @@ -1,40 +0,0 @@ -jobs: - - job: ValidationChecks - displayName: Distro and Extension Validation - timeoutInMinutes: 15 - steps: - - template: ./common/checkout.yml@self - - - task: NodeTool@0 - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - - - template: ./distro/download-distro.yml@self - - - script: node build/azure-pipelines/distro/mixin-quality.ts - displayName: Mixin distro quality - - - task: AzureKeyVault@2 - displayName: "Azure Key Vault: Get Secrets" - inputs: - azureSubscription: vscode - KeyVaultName: vscode-build-secrets - SecretsFilter: "github-distro-mixin-password" - - - script: npm ci - workingDirectory: build - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Install build dependencies - - - script: node build/azure-pipelines/common/checkDistroCommit.ts - displayName: Check distro commit - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - BUILD_SOURCEBRANCH: "$(Build.SourceBranch)" - continueOnError: true - - - script: node build/azure-pipelines/common/checkCopilotChatCompatibility.ts --warn-only - displayName: Check Copilot Chat compatibility - continueOnError: true diff --git a/build/azure-pipelines/web/product-build-web.yml b/build/azure-pipelines/web/product-build-web.yml index 71932745be7fb..c9916acded34d 100644 --- a/build/azure-pipelines/web/product-build-web.yml +++ b/build/azure-pipelines/web/product-build-web.yml @@ -33,15 +33,6 @@ jobs: 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 @@ -118,6 +109,11 @@ jobs: - template: ../common/install-builtin-extensions.yml@self + - script: npm run gulp core-ci + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Compile + - script: | set -e npm run gulp vscode-web-min-ci diff --git a/build/azure-pipelines/win32/product-build-win32-cli-sign.yml b/build/azure-pipelines/win32/product-build-win32-cli-sign.yml deleted file mode 100644 index fa1328d99e27f..0000000000000 --- a/build/azure-pipelines/win32/product-build-win32-cli-sign.yml +++ /dev/null @@ -1,83 +0,0 @@ -parameters: - - name: VSCODE_BUILD_WIN32 - type: boolean - - name: VSCODE_BUILD_WIN32_ARM64 - type: boolean - -jobs: - - job: WindowsCLISign - timeoutInMinutes: 90 - templateContext: - outputParentDirectory: $(Build.ArtifactStagingDirectory)/out - outputs: - - ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}: - - 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) }}: - - output: pipelineArtifact - targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_cli_win32_arm64_cli.zip - artifactName: vscode_cli_win32_arm64_cli - displayName: Publish signed artifact with ID vscode_cli_win32_arm64_cli - sbomBuildDropPath: $(Build.BinariesDirectory)/sign/unsigned_vscode_cli_win32_arm64_cli - sbomPackageName: "VS Code Windows arm64 CLI" - sbomPackageVersion: $(Build.SourceVersion) - steps: - - template: ../common/checkout.yml@self - - - task: NodeTool@0 - displayName: "Use Node.js" - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - - - task: AzureKeyVault@2 - displayName: "Azure Key Vault: Get Secrets" - inputs: - azureSubscription: vscode - KeyVaultName: vscode-build-secrets - SecretsFilter: "github-distro-mixin-password" - - - powershell: node build/setup-npm-registry.ts $env:NPM_REGISTRY build - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Registry - - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - # Set the private NPM registry to the global npmrc file - # so that authentication works for subfolders like build/, remote/, extensions/ etc - # which does not have their own .npmrc file - exec { npm config set registry "$env:NPM_REGISTRY" } - $NpmrcPath = (npm config get userconfig) - echo "##vso[task.setvariable variable=NPMRC_PATH]$NpmrcPath" - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM - - - task: npmAuthenticate@0 - inputs: - workingFile: $(NPMRC_PATH) - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Authentication - - - powershell: | - . azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { npm ci } - workingDirectory: build - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - retryCountOnTaskFailure: 5 - displayName: Install build dependencies - - - template: ./steps/product-build-win32-cli-sign.yml@self - parameters: - VSCODE_CLI_ARTIFACTS: - - ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}: - - unsigned_vscode_cli_win32_x64_cli - - ${{ if eq(parameters.VSCODE_BUILD_WIN32_ARM64, true) }}: - - unsigned_vscode_cli_win32_arm64_cli diff --git a/build/azure-pipelines/win32/product-build-win32-cli.yml b/build/azure-pipelines/win32/product-build-win32-cli.yml index 5dd69c3b50de3..78461a959eda3 100644 --- a/build/azure-pipelines/win32/product-build-win32-cli.yml +++ b/build/azure-pipelines/win32/product-build-win32-cli.yml @@ -9,22 +9,23 @@ parameters: jobs: - job: WindowsCLI_${{ upper(parameters.VSCODE_ARCH) }} - displayName: Windows (${{ upper(parameters.VSCODE_ARCH) }}) + displayName: Windows CLI (${{ upper(parameters.VSCODE_ARCH) }}) pool: name: 1es-windows-2022-x64 os: windows - timeoutInMinutes: 30 + timeoutInMinutes: 90 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 + targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_cli_win32_$(VSCODE_ARCH)_cli.zip + artifactName: vscode_cli_win32_$(VSCODE_ARCH)_cli + displayName: Publish vscode_cli_win32_$(VSCODE_ARCH)_cli artifact + sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/sign + sbomPackageName: "VS Code Windows $(VSCODE_ARCH) CLI" + sbomPackageVersion: $(Build.SourceVersion) steps: - template: ../common/checkout.yml@self @@ -75,3 +76,54 @@ jobs: ${{ if eq(parameters.VSCODE_ARCH, 'arm64') }}: RUSTFLAGS: "-Ctarget-feature=+crt-static -Clink-args=/guard:cf -Clink-args=/CETCOMPAT:NO" CFLAGS: "/guard:cf /Qspectre" + + - ${{ if not(parameters.VSCODE_CHECK_ONLY) }}: + - template: ../common/publish-artifact.yml@self + parameters: + targetPath: $(Build.ArtifactStagingDirectory)/unsigned_vscode_cli_win32_$(VSCODE_ARCH)_cli.zip + artifactName: unsigned_vscode_cli_win32_$(VSCODE_ARCH)_cli + displayName: Publish unsigned CLI + sbomEnabled: false + + - task: ExtractFiles@1 + displayName: Extract unsigned CLI + inputs: + archiveFilePatterns: $(Build.ArtifactStagingDirectory)/unsigned_vscode_cli_win32_$(VSCODE_ARCH)_cli.zip + destinationFolder: $(Build.ArtifactStagingDirectory)/sign + + - 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: node build\azure-pipelines\common\sign.ts $env:EsrpCliDllPath sign-windows $(Build.ArtifactStagingDirectory)/sign "*.exe" + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: ✍️ Codesign + + - task: ArchiveFiles@2 + displayName: Archive signed CLI + inputs: + rootFolderOrFile: $(Build.ArtifactStagingDirectory)/sign + includeRootFolder: false + archiveType: zip + archiveFile: $(Build.ArtifactStagingDirectory)/out/vscode_cli_win32_$(VSCODE_ARCH)_cli.zip diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index 3a91d3cdd97db..9b4c4e27070ab 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -21,6 +21,7 @@ jobs: 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: diff --git a/build/azure-pipelines/win32/steps/product-build-win32-cli-sign.yml b/build/azure-pipelines/win32/steps/product-build-win32-cli-sign.yml deleted file mode 100644 index 0caba3d1a2b88..0000000000000 --- a/build/azure-pipelines/win32/steps/product-build-win32-cli-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' - - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $EsrpCodeSigningTool = (gci -directory -filter EsrpCodeSigning_* $(Agent.RootDirectory)\_tasks | Select-Object -last 1).FullName - $Version = (gci -directory $EsrpCodeSigningTool | Select-Object -last 1).FullName - echo "##vso[task.setvariable variable=EsrpCliDllPath]$Version\net6.0\esrpcli.dll" - displayName: Find ESRP CLI - - - ${{ each target in parameters.VSCODE_CLI_ARTIFACTS }}: - - task: DownloadPipelineArtifact@2 - displayName: Download artifact - inputs: - artifact: ${{ target }} - path: $(Build.BinariesDirectory)/pkg/${{ target }} - - - task: ExtractFiles@1 - displayName: Extract artifact - inputs: - archiveFilePatterns: $(Build.BinariesDirectory)/pkg/${{ target }}/*.zip - destinationFolder: $(Build.BinariesDirectory)/sign/${{ target }} - - - powershell: node build\azure-pipelines\common\sign.ts $env:EsrpCliDllPath sign-windows $(Build.BinariesDirectory)/sign "*.exe" - env: - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - displayName: ✍️ Codesign - - - ${{ each target in parameters.VSCODE_CLI_ARTIFACTS }}: - - powershell: | - $ASSET_ID = "${{ target }}".replace("unsigned_", ""); - echo "##vso[task.setvariable variable=ASSET_ID]$ASSET_ID" - displayName: Set asset id variable - - - task: ArchiveFiles@2 - displayName: Archive signed files - inputs: - rootFolderOrFile: $(Build.BinariesDirectory)/sign/${{ target }} - includeRootFolder: false - archiveType: zip - archiveFile: $(Build.ArtifactStagingDirectory)/out/$(ASSET_ID).zip diff --git a/build/azure-pipelines/win32/steps/product-build-win32-compile.yml b/build/azure-pipelines/win32/steps/product-build-win32-compile.yml index d6412c2342090..3cb6413480af8 100644 --- a/build/azure-pipelines/win32/steps/product-build-win32-compile.yml +++ b/build/azure-pipelines/win32/steps/product-build-win32-compile.yml @@ -37,18 +37,6 @@ steps: 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 @@ -114,11 +102,34 @@ steps: condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) displayName: Create node_modules archive + - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: + - pwsh: npx deemon --detach --wait -- node build/azure-pipelines/common/waitForArtifacts.ts unsigned_vscode_cli_win32_$(VSCODE_ARCH)_cli + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: Wait for CLI artifact (background) + - powershell: node build/azure-pipelines/distro/mixin-quality.ts displayName: Mixin distro quality - template: ../../common/install-builtin-extensions.yml@self + - powershell: npm run gulp core-ci + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Compile + + - script: node build/azure-pipelines/common/extract-telemetry.ts + displayName: Generate lists of telemetry events + + - ${{ if or(eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true), eq(parameters.VSCODE_RUN_BROWSER_TESTS, true), eq(parameters.VSCODE_RUN_REMOTE_TESTS, true)) }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { npm run compile --prefix test/smoke } + exec { npm run compile --prefix test/integration/browser } + displayName: Compile test suites (non-OSS) + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: - powershell: | npm run copy-policy-dto --prefix build @@ -181,6 +192,11 @@ steps: displayName: Build server (web) - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: + - pwsh: npx deemon --attach -- node build/azure-pipelines/common/waitForArtifacts.ts unsigned_vscode_cli_win32_$(VSCODE_ARCH)_cli + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: Wait for CLI artifact + - task: DownloadPipelineArtifact@2 inputs: artifact: unsigned_vscode_cli_win32_$(VSCODE_ARCH)_cli diff --git a/build/darwin/create-universal-app.ts b/build/darwin/create-universal-app.ts index 26aead0ca19dd..9e90e31491f58 100644 --- a/build/darwin/create-universal-app.ts +++ b/build/darwin/create-universal-app.ts @@ -28,7 +28,7 @@ async function main(buildDir?: string) { const filesToSkip = [ '**/CodeResources', '**/Credits.rtf', - '**/policies/{*.mobileconfig,**/*.plist}', + '**/policies/{*.mobileconfig,**/*.plist}' ]; await makeUniversalApp({ diff --git a/build/darwin/dmg-settings.py.template b/build/darwin/dmg-settings.py.template index 4a54a69ab0264..f471029f32a2a 100644 --- a/build/darwin/dmg-settings.py.template +++ b/build/darwin/dmg-settings.py.template @@ -6,8 +6,9 @@ format = 'ULMO' badge_icon = {{BADGE_ICON}} background = {{BACKGROUND}} -# Volume size (None = auto-calculate) -size = None +# Volume size +size = '1g' +shrink = False # Files and symlinks files = [{{APP_PATH}}] diff --git a/build/gulpfile.extensions.ts b/build/gulpfile.extensions.ts index a2eb47535f4dd..8f9ac9b2b210b 100644 --- a/build/gulpfile.extensions.ts +++ b/build/gulpfile.extensions.ts @@ -95,6 +95,7 @@ const compilations = [ '.vscode/extensions/vscode-selfhost-test-provider/tsconfig.json', '.vscode/extensions/vscode-selfhost-import-aid/tsconfig.json', + '.vscode/extensions/vscode-extras/tsconfig.json', ]; const getBaseUrl = (out: string) => `https://main.vscode-cdn.net/sourcemaps/${commit}/${out}`; @@ -289,19 +290,7 @@ export const compileAllExtensionsBuildTask = task.define('compile-extensions-bui )); 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 diff --git a/build/gulpfile.vscode.ts b/build/gulpfile.vscode.ts index c50bdfcda3f7c..c22758027d155 100644 --- a/build/gulpfile.vscode.ts +++ b/build/gulpfile.vscode.ts @@ -31,6 +31,7 @@ import minimist from 'minimist'; import { compileBuildWithoutManglingTask, compileBuildWithManglingTask } from './gulpfile.compile.ts'; import { compileNonNativeExtensionsBuildTask, compileNativeExtensionsBuildTask, compileAllExtensionsBuildTask, compileExtensionMediaBuildTask, cleanExtensionsBuildTask } from './gulpfile.extensions.ts'; import { copyCodiconsTask } from './lib/compilation.ts'; +import type { EmbeddedProductInfo } from './lib/embeddedType.ts'; import { useEsbuildTranspile } from './buildConfig.ts'; import { promisify } from 'util'; import globCallback from 'glob'; @@ -243,19 +244,17 @@ const minifyVSCodeTask = task.define('minify-vscode', task.series( )); gulp.task(minifyVSCodeTask); -const coreCIOld = task.define('core-ci-old', task.series( +gulp.task(task.define('core-ci-old', 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(coreCIOld); +))); -const coreCIEsbuild = task.define('core-ci-esbuild', task.series( +gulp.task(task.define('core-ci', task.series( copyCodiconsTask, - cleanExtensionsBuildTask, compileNonNativeExtensionsBuildTask, compileExtensionMediaBuildTask, writeISODate('out-build'), @@ -269,10 +268,7 @@ const coreCIEsbuild = task.define('core-ci-esbuild', task.series( task.define('esbuild-vscode-reh-min', () => runEsbuildBundle('out-vscode-reh-min', true, true, 'server', `${sourceMappingURLBase}/core`)), task.define('esbuild-vscode-reh-web-min', () => runEsbuildBundle('out-vscode-reh-web-min', true, true, 'server-web', `${sourceMappingURLBase}/core`)), ) -)); -gulp.task(coreCIEsbuild); - -gulp.task(task.define('core-ci', useEsbuildTranspile ? coreCIEsbuild : coreCIOld)); +))); const coreCIPR = task.define('core-ci-pr', task.series( gulp.task('compile-build-without-mangling') as task.Task, @@ -389,7 +385,7 @@ function packageTask(platform: string, arch: string, sourceFolderName: string, d const isInsiderOrExploration = quality === 'insider' || quality === 'exploration'; const embedded = isInsiderOrExploration - ? (product as typeof product & { embedded?: { nameShort: string; nameLong: string; applicationName: string; dataFolderName: string; darwinBundleIdentifier: string; urlProtocol: string } }).embedded + ? (product as typeof product & { embedded?: EmbeddedProductInfo }).embedded : undefined; const packageSubJsonStream = isInsiderOrExploration @@ -404,12 +400,9 @@ function packageTask(platform: string, arch: string, sourceFolderName: string, d const productSubJsonStream = embedded ? gulp.src(['product.json'], { base: '.' }) .pipe(jsonEditor((json: Record) => { - json.nameShort = embedded.nameShort; - json.nameLong = embedded.nameLong; - json.applicationName = embedded.applicationName; - json.dataFolderName = embedded.dataFolderName; - json.darwinBundleIdentifier = embedded.darwinBundleIdentifier; - json.urlProtocol = embedded.urlProtocol; + Object.keys(embedded).forEach(key => { + json[key] = embedded[key as keyof EmbeddedProductInfo]; + }); return json; })) .pipe(rename('product.sub.json')) @@ -500,6 +493,9 @@ function packageTask(platform: string, arch: string, sourceFolderName: string, d 'resources/win32/code_70x70.png', 'resources/win32/code_150x150.png' ], { base: '.' })); + if (embedded) { + all = es.merge(all, gulp.src('resources/win32/sessions.ico', { base: '.' })); + } } else if (platform === 'linux') { const policyDest = gulp.src('.build/policies/linux/**', { base: '.build/policies/linux' }) .pipe(rename(f => f.dirname = `policies/${f.dirname}`)); @@ -523,6 +519,13 @@ function packageTask(platform: string, arch: string, sourceFolderName: string, d darwinMiniAppName: embedded.nameShort, darwinMiniAppBundleIdentifier: embedded.darwinBundleIdentifier, darwinMiniAppIcon: 'resources/darwin/sessions.icns', + darwinMiniAppBundleURLTypes: [{ + role: 'Viewer', + name: embedded.nameLong, + urlSchemes: [embedded.urlProtocol] + }], + win32ProxyAppName: embedded.nameShort, + win32ProxyIcon: 'resources/win32/sessions.ico', } : {}) }; @@ -531,7 +534,13 @@ function packageTask(platform: string, arch: string, sourceFolderName: string, d .pipe(util.fixWin32DirectoryPermissions()) .pipe(filter(['**', '!**/.github/**'], { dot: true })) // https://github.com/microsoft/vscode/issues/116523 .pipe(electron(electronConfig)) - .pipe(filter(['**', '!LICENSE', '!version'], { dot: true })); + .pipe(filter([ + '**', + '!LICENSE', + '!version', + ...(platform === 'darwin' && !isInsiderOrExploration ? ['!**/Contents/Applications'] : []), + ...(platform === 'win32' && !isInsiderOrExploration ? ['!**/electron_proxy.exe'] : []), + ], { dot: true })); if (platform === 'linux') { result = es.merge(result, gulp.src('resources/completions/bash/code', { base: '.' }) diff --git a/build/gulpfile.vscode.win32.ts b/build/gulpfile.vscode.win32.ts index d04e7f1f0e7d3..1f525cff35a90 100644 --- a/build/gulpfile.vscode.win32.ts +++ b/build/gulpfile.vscode.win32.ts @@ -14,6 +14,7 @@ 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 type { EmbeddedProductInfo } from './lib/embeddedType.ts'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); @@ -112,6 +113,17 @@ function buildWin32Setup(arch: string, target: string): task.CallbackTask { Quality: quality }; + const isInsiderOrExploration = false; + const embedded = isInsiderOrExploration + ? (product as typeof product & { embedded?: EmbeddedProductInfo }).embedded + : undefined; + + if (embedded) { + definitions['ProxyExeBasename'] = embedded.nameShort; + definitions['ProxyAppUserId'] = embedded.win32AppUserModelId; + definitions['ProxyNameLong'] = embedded.nameLong; + } + 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`; diff --git a/build/lib/date.ts b/build/lib/date.ts index 68d52521349ed..99ba91a5282df 100644 --- a/build/lib/date.ts +++ b/build/lib/date.ts @@ -5,9 +5,23 @@ import path from 'path'; import fs from 'fs'; +import { execSync } from 'child_process'; const root = path.join(import.meta.dirname, '..', '..'); +/** + * Get the ISO date for the build. Uses the git commit date of HEAD + * so that independent builds on different machines produce the same + * timestamp (required for deterministic builds, e.g. macOS Universal). + */ +export function getGitCommitDate(): string { + try { + return execSync('git log -1 --format=%cI HEAD', { cwd: root, encoding: 'utf8' }).trim(); + } catch { + return new Date().toISOString(); + } +} + /** * Writes a `outDir/date` file with the contents of the build * so that other tasks during the build process can use it and @@ -18,7 +32,7 @@ export function writeISODate(outDir: string) { const outDirectory = path.join(root, outDir); fs.mkdirSync(outDirectory, { recursive: true }); - const date = new Date().toISOString(); + const date = getGitCommitDate(); fs.writeFileSync(path.join(outDirectory, 'date'), date, 'utf8'); resolve(); diff --git a/extensions/github-authentication/extension.webpack.config.js b/build/lib/embeddedType.ts similarity index 51% rename from extensions/github-authentication/extension.webpack.config.js rename to build/lib/embeddedType.ts index 166c1d8b1e340..4b3075f4a7165 100644 --- a/extensions/github-authentication/extension.webpack.config.js +++ b/build/lib/embeddedType.ts @@ -2,12 +2,17 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// @ts-check -import withDefaults from '../shared.webpack.config.mjs'; -export default withDefaults({ - context: import.meta.dirname, - entry: { - extension: './src/extension.ts', - }, -}); +export type EmbeddedProductInfo = { + nameShort: string; + nameLong: string; + applicationName: string; + dataFolderName: string; + darwinBundleIdentifier: string; + urlProtocol: string; + win32AppUserModelId: string; + win32MutexName: string; + win32RegValueName: string; + win32NameVersion: string; + win32VersionedUpdate: boolean; +}; diff --git a/build/lib/policies/policyData.jsonc b/build/lib/policies/policyData.jsonc index 9c1e1e0e87a8f..2ea27460de982 100644 --- a/build/lib/policies/policyData.jsonc +++ b/build/lib/policies/policyData.jsonc @@ -39,28 +39,28 @@ ], "policies": [ { - "key": "chat.mcp.gallery.serviceUrl", - "name": "McpGalleryServiceUrl", - "category": "InteractiveSession", - "minimumVersion": "1.101", + "key": "extensions.gallery.serviceUrl", + "name": "ExtensionGalleryServiceUrl", + "category": "Extensions", + "minimumVersion": "1.99", "localization": { "description": { - "key": "mcp.gallery.serviceUrl", - "value": "Configure the MCP Gallery service URL to connect to" + "key": "extensions.gallery.serviceUrl", + "value": "Configure the Marketplace service URL to connect to" } }, "type": "string", "default": "" }, { - "key": "extensions.gallery.serviceUrl", - "name": "ExtensionGalleryServiceUrl", - "category": "Extensions", - "minimumVersion": "1.99", + "key": "chat.mcp.gallery.serviceUrl", + "name": "McpGalleryServiceUrl", + "category": "InteractiveSession", + "minimumVersion": "1.101", "localization": { "description": { - "key": "extensions.gallery.serviceUrl", - "value": "Configure the Marketplace service URL to connect to" + "key": "mcp.gallery.serviceUrl", + "value": "Configure the MCP Gallery service URL to connect to" } }, "type": "string", @@ -286,6 +286,20 @@ }, "type": "boolean", "default": true + }, + { + "key": "workbench.browser.enableChatTools", + "name": "BrowserChatTools", + "category": "InteractiveSession", + "minimumVersion": "1.110", + "localization": { + "description": { + "key": "browser.enableChatTools", + "value": "When enabled, chat agents can use browser tools to open and interact with pages in the Integrated Browser." + } + }, + "type": "boolean", + "default": false } ] } diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index a913a9534fcfc..1ea3723af79fa 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -21,6 +21,7 @@ "--vscode-activityErrorBadge-foreground", "--vscode-activityWarningBadge-background", "--vscode-activityWarningBadge-foreground", + "--vscode-agentFeedbackInputWidget-border", "--vscode-agentSessionReadIndicator-foreground", "--vscode-agentSessionSelectedBadge-border", "--vscode-agentSessionSelectedUnfocusedBadge-border", @@ -644,6 +645,8 @@ "--vscode-searchEditor-findMatchBorder", "--vscode-searchEditor-textInputBorder", "--vscode-selection-background", + "--vscode-sessionsUpdateButton-downloadedBackground", + "--vscode-sessionsUpdateButton-downloadingBackground", "--vscode-settings-checkboxBackground", "--vscode-settings-checkboxBorder", "--vscode-settings-checkboxForeground", @@ -968,6 +971,14 @@ "--vscode-repl-line-height", "--vscode-sash-hover-size", "--vscode-sash-size", + "--vscode-shadow-active-tab", + "--vscode-shadow-depth-x", + "--vscode-shadow-depth-y", + "--vscode-shadow-hover", + "--vscode-shadow-lg", + "--vscode-shadow-md", + "--vscode-shadow-sm", + "--vscode-shadow-xl", "--vscode-testing-coverage-lineHeight", "--vscode-editorStickyScroll-scrollableWidth", "--vscode-editorStickyScroll-foldingOpacityTransition", diff --git a/build/next/index.ts b/build/next/index.ts index b0120837efa26..77886ad43a989 100644 --- a/build/next/index.ts +++ b/build/next/index.ts @@ -7,11 +7,13 @@ import * as esbuild from 'esbuild'; import * as fs from 'fs'; import * as path from 'path'; import { promisify } from 'util'; + import glob from 'glob'; import gulpWatch from '../lib/watch/index.ts'; import { nlsPlugin, createNLSCollector, finalizeNLS, postProcessNLS } from './nls-plugin.ts'; import { convertPrivateFields, adjustSourceMap, type ConvertPrivateFieldsResult } from './private-to-property.ts'; import { getVersion } from '../lib/getVersion.ts'; +import { getGitCommitDate } from '../lib/date.ts'; import product from '../../product.json' with { type: 'json' }; import packageJson from '../../package.json' with { type: 'json' }; import { useEsbuildTranspile } from '../buildConfig.ts'; @@ -72,7 +74,8 @@ const extensionHostEntryPoints = [ ]; function isExtensionHostBundle(filePath: string): boolean { - return extensionHostEntryPoints.some(ep => filePath.endsWith(`${ep}.js`)); + const normalized = filePath.replaceAll('\\', '/'); + return extensionHostEntryPoints.some(ep => normalized.endsWith(`${ep}.js`)); } // Workers - shared between targets @@ -419,13 +422,13 @@ function scanBuiltinExtensions(extensionsRoot: string): Array { async function bundle(outDir: string, doMinify: boolean, doNls: boolean, doManglePrivates: boolean, target: BuildTarget, sourceMapBaseUrl?: string): Promise { await cleanDir(outDir); - // Write build date file (used by packaging to embed in product.json) + // Write build date file (used by packaging to embed in product.json). + // Reuse the date from out-build/date if it exists (written by the gulp + // writeISODate task) so that all parallel bundle outputs share the same + // timestamp - this is required for deterministic builds (e.g. macOS Universal). const outDirPath = path.join(REPO_ROOT, outDir); await fs.promises.mkdir(outDirPath, { recursive: true }); - await fs.promises.writeFile(path.join(outDirPath, 'date'), new Date().toISOString(), 'utf8'); + let buildDate: string; + try { + buildDate = await fs.promises.readFile(path.join(REPO_ROOT, 'out-build', 'date'), 'utf8'); + } catch { + buildDate = getGitCommitDate(); + } + await fs.promises.writeFile(path.join(outDirPath, 'date'), buildDate, 'utf8'); console.log(`[bundle] ${SRC_DIR} → ${outDir} (target: ${target})${doMinify ? ' (minify)' : ''}${doNls ? ' (nls)' : ''}${doManglePrivates ? ' (mangle-privates)' : ''}`); const t1 = Date.now(); @@ -1128,7 +1140,7 @@ async function main(): Promise { // Write build date file (used by packaging to embed in product.json) const outDirPath = path.join(REPO_ROOT, outDir); await fs.promises.mkdir(outDirPath, { recursive: true }); - await fs.promises.writeFile(path.join(outDirPath, 'date'), new Date().toISOString(), 'utf8'); + await fs.promises.writeFile(path.join(outDirPath, 'date'), getGitCommitDate(), 'utf8'); console.log(`[transpile] ${SRC_DIR} → ${outDir}${options.excludeTests ? ' (excluding tests)' : ''}`); const t1 = Date.now(); diff --git a/build/next/working.md b/build/next/working.md index b59b347611dbd..298d1fb8cbd48 100644 --- a/build/next/working.md +++ b/build/next/working.md @@ -37,7 +37,7 @@ In [gulpfile.vscode.ts](../gulpfile.vscode.ts#L228-L242), the `core-ci` task use - `runEsbuildTranspile()` → transpile command - `runEsbuildBundle()` → bundle command -Old gulp-based bundling renamed to `core-ci-OLD`. +Old gulp-based bundling renamed to `core-ci-old`. --- @@ -134,7 +134,7 @@ npm run gulp vscode-reh-web-darwin-arm64-min 1. **`BUILD_INSERT_PACKAGE_CONFIGURATION`** - Server bootstrap files ([bootstrap-meta.ts](../../src/bootstrap-meta.ts)) have this marker for package.json injection. Currently handled by [inlineMeta.ts](../lib/inlineMeta.ts) in the old build's packaging step. -2. **Mangling** - The new build doesn't do TypeScript-based mangling yet. Old `core-ci` with mangling is now `core-ci-OLD`. +2. **Mangling** - The new build doesn't do TypeScript-based mangling yet. Old `core-ci` with mangling is now `core-ci-old`. 3. **Entry point duplication** - Entry points are duplicated between [buildfile.ts](../buildfile.ts) and [index.ts](index.ts). Consider consolidating. diff --git a/build/npm/dirs.ts b/build/npm/dirs.ts index 48d76e2731a6e..b56884af25c52 100644 --- a/build/npm/dirs.ts +++ b/build/npm/dirs.ts @@ -60,6 +60,7 @@ export const dirs = [ 'test/mcp', '.vscode/extensions/vscode-selfhost-import-aid', '.vscode/extensions/vscode-selfhost-test-provider', + '.vscode/extensions/vscode-extras', ]; if (existsSync(`${import.meta.dirname}/../../.build/distro/npm`)) { diff --git a/build/npm/fast-install.ts b/build/npm/fast-install.ts new file mode 100644 index 0000000000000..ff9a7d2097cf2 --- /dev/null +++ b/build/npm/fast-install.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. + *--------------------------------------------------------------------------------------------*/ + +import * as child_process from 'child_process'; +import { root, isUpToDate, forceInstallMessage } from './installStateHash.ts'; + +if (!process.argv.includes('--force') && isUpToDate()) { + console.log(`\x1b[32mAll dependencies up to date.\x1b[0m ${forceInstallMessage}`); + process.exit(0); +} + +const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'; +const result = child_process.spawnSync(npm, ['install'], { + cwd: root, + stdio: 'inherit', + shell: true, + env: { ...process.env, VSCODE_FORCE_INSTALL: '1' }, +}); + +process.exit(result.status ?? 1); diff --git a/build/npm/gyp/package-lock.json b/build/npm/gyp/package-lock.json index 6e28e550f4699..e2785131796d7 100644 --- a/build/npm/gyp/package-lock.json +++ b/build/npm/gyp/package-lock.json @@ -526,13 +526,13 @@ } }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" diff --git a/build/npm/installStateHash.ts b/build/npm/installStateHash.ts new file mode 100644 index 0000000000000..5674a1eaee377 --- /dev/null +++ b/build/npm/installStateHash.ts @@ -0,0 +1,120 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as crypto from 'crypto'; +import * as fs from 'fs'; +import path from 'path'; +import { dirs } from './dirs.ts'; + +export const root = fs.realpathSync.native(path.dirname(path.dirname(import.meta.dirname))); +export const stateFile = path.join(root, 'node_modules', '.postinstall-state'); +export const stateContentsFile = path.join(root, 'node_modules', '.postinstall-state-contents'); +export const forceInstallMessage = 'Run \x1b[36mnode build/npm/fast-install.ts --force\x1b[0m to force a full install.'; + +export function collectInputFiles(): string[] { + const files: string[] = []; + + for (const dir of dirs) { + const base = dir === '' ? root : path.join(root, dir); + for (const file of ['package.json', 'package-lock.json', '.npmrc']) { + const filePath = path.join(base, file); + if (fs.existsSync(filePath)) { + files.push(filePath); + } + } + } + + files.push(path.join(root, '.nvmrc')); + + return files; +} + +export interface PostinstallState { + readonly nodeVersion: string; + readonly fileHashes: Record; +} + +const packageJsonIgnoredKeys = new Set(['distro']); + +function normalizeFileContent(filePath: string): string { + const raw = fs.readFileSync(filePath, 'utf8'); + if (path.basename(filePath) === 'package.json') { + const json = JSON.parse(raw); + for (const key of packageJsonIgnoredKeys) { + delete json[key]; + } + return JSON.stringify(json, null, '\t') + '\n'; + } + return raw; +} + +function hashContent(content: string): string { + const hash = crypto.createHash('sha256'); + hash.update(content); + return hash.digest('hex'); +} + +export function computeState(): PostinstallState { + const fileHashes: Record = {}; + for (const filePath of collectInputFiles()) { + const key = path.relative(root, filePath); + try { + fileHashes[key] = hashContent(normalizeFileContent(filePath)); + } catch { + // file may not be readable + } + } + return { nodeVersion: process.versions.node, fileHashes }; +} + +export function computeContents(): Record { + const fileContents: Record = {}; + for (const filePath of collectInputFiles()) { + try { + fileContents[path.relative(root, filePath)] = normalizeFileContent(filePath); + } catch { + // file may not be readable + } + } + return fileContents; +} + +export function readSavedState(): PostinstallState | undefined { + try { + const { nodeVersion, fileHashes } = JSON.parse(fs.readFileSync(stateFile, 'utf8')); + return { nodeVersion, fileHashes }; + } catch { + return undefined; + } +} + +export function isUpToDate(): boolean { + const saved = readSavedState(); + if (!saved) { + return false; + } + const current = computeState(); + return saved.nodeVersion === current.nodeVersion + && JSON.stringify(saved.fileHashes) === JSON.stringify(current.fileHashes); +} + +export function readSavedContents(): Record | undefined { + try { + return JSON.parse(fs.readFileSync(stateContentsFile, 'utf8')); + } catch { + return undefined; + } +} + +// When run directly, output state as JSON for tooling (e.g. the vscode-extras extension). +if (import.meta.filename === process.argv[1]) { + console.log(JSON.stringify({ + root, + stateContentsFile, + current: computeState(), + saved: readSavedState(), + files: [...collectInputFiles(), stateFile], + })); +} diff --git a/build/npm/postinstall.ts b/build/npm/postinstall.ts index b6a934f74b3eb..ae2651cd188a1 100644 --- a/build/npm/postinstall.ts +++ b/build/npm/postinstall.ts @@ -8,9 +8,9 @@ import path from 'path'; import * as os from 'os'; import * as child_process from 'child_process'; import { dirs } from './dirs.ts'; +import { root, stateFile, stateContentsFile, computeState, computeContents, isUpToDate } from './installStateHash.ts'; const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'; -const root = path.dirname(path.dirname(import.meta.dirname)); const rootNpmrcConfigKeys = getNpmrcConfigKeys(path.join(root, '.npmrc')); function log(dir: string, message: string) { @@ -35,24 +35,45 @@ function run(command: string, args: string[], opts: child_process.SpawnSyncOptio } } -function npmInstall(dir: string, opts?: child_process.SpawnSyncOptions) { - opts = { +function spawnAsync(command: string, args: string[], opts: child_process.SpawnOptions): Promise { + return new Promise((resolve, reject) => { + const child = child_process.spawn(command, args, { ...opts, stdio: ['ignore', 'pipe', 'pipe'] }); + let output = ''; + child.stdout?.on('data', (data: Buffer) => { output += data.toString(); }); + child.stderr?.on('data', (data: Buffer) => { output += data.toString(); }); + child.on('error', reject); + child.on('close', (code) => { + if (code !== 0) { + reject(new Error(`Process exited with code: ${code}\n${output}`)); + } else { + resolve(output); + } + }); + }); +} + +async function npmInstallAsync(dir: string, opts?: child_process.SpawnOptions): Promise { + const finalOpts: child_process.SpawnOptions = { env: { ...process.env }, ...(opts ?? {}), - cwd: dir, - stdio: 'inherit', - shell: true + cwd: path.join(root, dir), + shell: true, }; const command = process.env['npm_command'] || 'install'; if (process.env['VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME'] && /^(.build\/distro\/npm\/)?remote$/.test(dir)) { + const syncOpts: child_process.SpawnSyncOptions = { + env: finalOpts.env, + cwd: root, + stdio: 'inherit', + shell: true, + }; 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', '--rm', '--privileged', 'multiarch/qemu-user-static', '--reset', '-p', 'yes'], syncOpts); } run('sudo', [ 'docker', 'run', @@ -63,11 +84,16 @@ function npmInstall(dir: string, opts?: child_process.SpawnSyncOptions) { '-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); + ], syncOpts); + run('sudo', ['chown', '-R', `${userinfo.uid}:${userinfo.gid}`, `${path.resolve(root, dir)}`], syncOpts); } else { log(dir, 'Installing dependencies...'); - run(npm, command.split(' '), opts); + const output = await spawnAsync(npm, command.split(' '), finalOpts); + if (output.trim()) { + for (const line of output.trim().split('\n')) { + log(dir, line); + } + } } removeParcelWatcherPrebuild(dir); } @@ -156,65 +182,115 @@ function clearInheritedNpmrcConfig(dir: string, env: NodeJS.ProcessEnv): void { } } -for (const dir of dirs) { +async function runWithConcurrency(tasks: (() => Promise)[], concurrency: number): Promise { + const errors: Error[] = []; + let index = 0; - if (dir === '') { - removeParcelWatcherPrebuild(dir); - continue; // already executed in root + async function worker() { + while (index < tasks.length) { + const i = index++; + try { + await tasks[i](); + } catch (err) { + errors.push(err as Error); + } + } } - let opts: child_process.SpawnSyncOptions | undefined; + await Promise.all(Array.from({ length: Math.min(concurrency, tasks.length) }, () => worker())); - 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 (errors.length > 0) { + for (const err of errors) { + console.error(err.message); } - if (process.env['VSCODE_REMOTE_CXX']) { - opts.env!['CXX'] = process.env['VSCODE_REMOTE_CXX']; - } else { - delete opts.env!['CXX']; + process.exit(1); + } +} + +async function main() { + if (!process.env['VSCODE_FORCE_INSTALL'] && isUpToDate()) { + log('.', 'All dependencies up to date, skipping postinstall.'); + child_process.execSync('git config pull.rebase merges'); + child_process.execSync('git config blame.ignoreRevsFile .git-blame-ignore-revs'); + return; + } + + const _state = computeState(); + + const nativeTasks: (() => Promise)[] = []; + const parallelTasks: (() => Promise)[] = []; + + for (const dir of dirs) { + if (dir === '') { + removeParcelWatcherPrebuild(dir); + continue; // already executed in root + } + + if (dir === 'build') { + nativeTasks.push(() => { + const env: NodeJS.ProcessEnv = { ...process.env }; + if (process.env['CC']) { env['CC'] = 'gcc'; } + if (process.env['CXX']) { env['CXX'] = 'g++'; } + if (process.env['CXXFLAGS']) { env['CXXFLAGS'] = ''; } + if (process.env['LDFLAGS']) { env['LDFLAGS'] = ''; } + setNpmrcConfig('build', env); + return npmInstallAsync('build', { env }); + }); + continue; + } + + if (/^(.build\/distro\/npm\/)?remote$/.test(dir)) { + const remoteDir = dir; + nativeTasks.push(() => { + const env: NodeJS.ProcessEnv = { ...process.env }; + if (process.env['VSCODE_REMOTE_CC']) { + env['CC'] = process.env['VSCODE_REMOTE_CC']; + } else { + delete env['CC']; + } + if (process.env['VSCODE_REMOTE_CXX']) { + env['CXX'] = process.env['VSCODE_REMOTE_CXX']; + } else { + delete env['CXX']; + } + if (process.env['CXXFLAGS']) { delete env['CXXFLAGS']; } + if (process.env['CFLAGS']) { delete env['CFLAGS']; } + if (process.env['LDFLAGS']) { delete env['LDFLAGS']; } + if (process.env['VSCODE_REMOTE_CXXFLAGS']) { env['CXXFLAGS'] = process.env['VSCODE_REMOTE_CXXFLAGS']; } + if (process.env['VSCODE_REMOTE_LDFLAGS']) { env['LDFLAGS'] = process.env['VSCODE_REMOTE_LDFLAGS']; } + if (process.env['VSCODE_REMOTE_NODE_GYP']) { env['npm_config_node_gyp'] = process.env['VSCODE_REMOTE_NODE_GYP']; } + setNpmrcConfig('remote', env); + return npmInstallAsync(remoteDir, { env }); + }); + continue; } - 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; - } - - // For directories that don't define their own .npmrc, clear inherited config - const env = { ...process.env }; - clearInheritedNpmrcConfig(dir, env); - npmInstall(dir, { env }); + + const taskDir = dir; + parallelTasks.push(() => { + const env = { ...process.env }; + clearInheritedNpmrcConfig(taskDir, env); + return npmInstallAsync(taskDir, { env }); + }); + } + + // Native dirs (build, remote) run sequentially to avoid node-gyp conflicts + for (const task of nativeTasks) { + await task(); + } + + // JS-only dirs run in parallel + const concurrency = Math.min(os.cpus().length, 8); + log('.', `Running ${parallelTasks.length} npm installs with concurrency ${concurrency}...`); + await runWithConcurrency(parallelTasks, concurrency); + + child_process.execSync('git config pull.rebase merges'); + child_process.execSync('git config blame.ignoreRevsFile .git-blame-ignore-revs'); + + fs.writeFileSync(stateFile, JSON.stringify(_state)); + fs.writeFileSync(stateContentsFile, JSON.stringify(computeContents())); } -child_process.execSync('git config pull.rebase merges'); -child_process.execSync('git config blame.ignoreRevsFile .git-blame-ignore-revs'); +main().catch(err => { + console.error(err); + process.exit(1); +}); diff --git a/build/npm/preinstall.ts b/build/npm/preinstall.ts index 3476fcabb5009..dd53ff4467123 100644 --- a/build/npm/preinstall.ts +++ b/build/npm/preinstall.ts @@ -6,6 +6,7 @@ import path from 'path'; import * as fs from 'fs'; import * as child_process from 'child_process'; import * as os from 'os'; +import { isUpToDate, forceInstallMessage } from './installStateHash.ts'; if (!process.env['VSCODE_SKIP_NODE_VERSION_CHECK']) { // Get the running Node.js version @@ -41,6 +42,13 @@ if (process.env.npm_execpath?.includes('yarn')) { throw new Error(); } +// Fast path: if nothing changed since last successful install, skip everything. +// This makes `npm i` near-instant when dependencies haven't changed. +if (!process.env['VSCODE_FORCE_INSTALL'] && isUpToDate()) { + console.log(`\x1b[32mAll dependencies up to date.\x1b[0m ${forceInstallMessage}`); + process.exit(0); +} + 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'); diff --git a/build/package-lock.json b/build/package-lock.json index b78c4c8389ac5..ec46db00b08cd 100644 --- a/build/package-lock.json +++ b/build/package-lock.json @@ -1027,29 +1027,6 @@ "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", @@ -2170,6 +2147,29 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/@vscode/vsce/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@vscode/vsce/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/@vscode/vsce/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2232,16 +2232,16 @@ } }, "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==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" + "brace-expansion": "^5.0.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -3493,10 +3493,23 @@ ], "license": "BSD-3-Clause" }, + "node_modules/fast-xml-builder": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.0.0.tgz", + "integrity": "sha512-fpZuDogrAgnyt9oDDz+5DBz0zgPdPZz6D4IR7iESxRXElrlGTRkHJ9eEt+SACRJwT0FNFrt71DFQIUFBJfX/uQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/fast-xml-parser": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.6.tgz", - "integrity": "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.4.1.tgz", + "integrity": "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A==", "dev": true, "funding": [ { @@ -3506,6 +3519,7 @@ ], "license": "MIT", "dependencies": { + "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { @@ -4654,10 +4668,11 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -6743,12 +6758,13 @@ } }, "node_modules/vscode-universal-bundler/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, + "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" diff --git a/build/vite/package-lock.json b/build/vite/package-lock.json index 4179138e714c7..b7e27044aefbd 100644 --- a/build/vite/package-lock.json +++ b/build/vite/package-lock.json @@ -8,8 +8,8 @@ "name": "@vscode/sample-source", "version": "0.0.0", "devDependencies": { - "@vscode/component-explorer": "^0.1.1-12", - "@vscode/component-explorer-vite-plugin": "^0.1.1-12", + "@vscode/component-explorer": "^0.1.1-19", + "@vscode/component-explorer-vite-plugin": "^0.1.1-19", "@vscode/rollup-plugin-esm-url": "^1.0.1-1", "rollup": "*", "vite": "npm:rolldown-vite@latest" @@ -315,9 +315,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", - "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", "cpu": [ "arm" ], @@ -329,9 +329,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", - "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", "cpu": [ "arm64" ], @@ -343,9 +343,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", - "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", "cpu": [ "arm64" ], @@ -357,9 +357,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", - "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", "cpu": [ "x64" ], @@ -371,9 +371,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", - "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", "cpu": [ "arm64" ], @@ -385,9 +385,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", - "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", "cpu": [ "x64" ], @@ -399,9 +399,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", - "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", "cpu": [ "arm" ], @@ -413,9 +413,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", - "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", "cpu": [ "arm" ], @@ -427,9 +427,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", - "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", "cpu": [ "arm64" ], @@ -441,9 +441,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", - "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", "cpu": [ "arm64" ], @@ -455,9 +455,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", - "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", "cpu": [ "loong64" ], @@ -469,9 +469,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", - "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", "cpu": [ "loong64" ], @@ -483,9 +483,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", - "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", "cpu": [ "ppc64" ], @@ -497,9 +497,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", - "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", "cpu": [ "ppc64" ], @@ -511,9 +511,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", - "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", "cpu": [ "riscv64" ], @@ -525,9 +525,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", - "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", "cpu": [ "riscv64" ], @@ -539,9 +539,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", - "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", "cpu": [ "s390x" ], @@ -553,9 +553,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", - "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", "cpu": [ "x64" ], @@ -567,9 +567,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", - "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", "cpu": [ "x64" ], @@ -581,9 +581,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", - "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", "cpu": [ "x64" ], @@ -595,9 +595,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", - "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", "cpu": [ "arm64" ], @@ -609,9 +609,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", - "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", "cpu": [ "arm64" ], @@ -623,9 +623,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", - "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", "cpu": [ "ia32" ], @@ -637,9 +637,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", - "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", "cpu": [ "x64" ], @@ -651,9 +651,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", - "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", "cpu": [ "x64" ], @@ -683,20 +683,22 @@ "license": "MIT" }, "node_modules/@vscode/component-explorer": { - "version": "0.1.1-12", - "resolved": "https://registry.npmjs.org/@vscode/component-explorer/-/component-explorer-0.1.1-12.tgz", - "integrity": "sha512-qqbxbu3BvqWtwFdVsROLUSd1BiScCiUPP5n0sk0yV1WDATlAl6wQMX1QlmsZy3hag8iP/MXUEj5tSBjA1T7tFw==", + "version": "0.1.1-19", + "resolved": "https://registry.npmjs.org/@vscode/component-explorer/-/component-explorer-0.1.1-19.tgz", + "integrity": "sha512-wvcjw1A8wSH/oR5q+lZrBSyOQZfvXtLPYkQJBj11FBKu35iHko0FTIPMG25Ee+TpT2/BWLd29dWwiJODDQbC8w==", "dev": true, + "license": "MIT", "dependencies": { "react": "^18.2.0", "react-dom": "^18.2.0" } }, "node_modules/@vscode/component-explorer-vite-plugin": { - "version": "0.1.1-12", - "resolved": "https://registry.npmjs.org/@vscode/component-explorer-vite-plugin/-/component-explorer-vite-plugin-0.1.1-12.tgz", - "integrity": "sha512-MG5ndoooX2X9PYto1WkNSwWKKmR5OJx3cBnUf7JHm8ERw+8RsZbLe+WS+hVOqnCVPxHy7t+0IYRFl7IC5cuwOQ==", + "version": "0.1.1-19", + "resolved": "https://registry.npmjs.org/@vscode/component-explorer-vite-plugin/-/component-explorer-vite-plugin-0.1.1-19.tgz", + "integrity": "sha512-V0wMhLvHMbeUHOzwGrBPMwwvcbGhXXaQTCGc9hNfF4fjUutOtQFu5o+9XKDG1hIcKgk5qyvcRoXjVazBcg19lA==", "dev": true, + "license": "MIT", "dependencies": { "tinyglobby": "^0.2.0" }, @@ -1167,9 +1169,9 @@ } }, "node_modules/rollup": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", - "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", "dev": true, "license": "MIT", "dependencies": { @@ -1183,31 +1185,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.57.1", - "@rollup/rollup-android-arm64": "4.57.1", - "@rollup/rollup-darwin-arm64": "4.57.1", - "@rollup/rollup-darwin-x64": "4.57.1", - "@rollup/rollup-freebsd-arm64": "4.57.1", - "@rollup/rollup-freebsd-x64": "4.57.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", - "@rollup/rollup-linux-arm-musleabihf": "4.57.1", - "@rollup/rollup-linux-arm64-gnu": "4.57.1", - "@rollup/rollup-linux-arm64-musl": "4.57.1", - "@rollup/rollup-linux-loong64-gnu": "4.57.1", - "@rollup/rollup-linux-loong64-musl": "4.57.1", - "@rollup/rollup-linux-ppc64-gnu": "4.57.1", - "@rollup/rollup-linux-ppc64-musl": "4.57.1", - "@rollup/rollup-linux-riscv64-gnu": "4.57.1", - "@rollup/rollup-linux-riscv64-musl": "4.57.1", - "@rollup/rollup-linux-s390x-gnu": "4.57.1", - "@rollup/rollup-linux-x64-gnu": "4.57.1", - "@rollup/rollup-linux-x64-musl": "4.57.1", - "@rollup/rollup-openbsd-x64": "4.57.1", - "@rollup/rollup-openharmony-arm64": "4.57.1", - "@rollup/rollup-win32-arm64-msvc": "4.57.1", - "@rollup/rollup-win32-ia32-msvc": "4.57.1", - "@rollup/rollup-win32-x64-gnu": "4.57.1", - "@rollup/rollup-win32-x64-msvc": "4.57.1", + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" } }, diff --git a/build/vite/package.json b/build/vite/package.json index 245bf4fc8001a..14f6ad51c578e 100644 --- a/build/vite/package.json +++ b/build/vite/package.json @@ -9,8 +9,8 @@ "preview": "vite preview" }, "devDependencies": { - "@vscode/component-explorer": "^0.1.1-12", - "@vscode/component-explorer-vite-plugin": "^0.1.1-12", + "@vscode/component-explorer": "^0.1.1-19", + "@vscode/component-explorer-vite-plugin": "^0.1.1-19", "@vscode/rollup-plugin-esm-url": "^1.0.1-1", "rollup": "*", "vite": "npm:rolldown-vite@latest" diff --git a/build/vite/vite.config.ts b/build/vite/vite.config.ts index cdae205f030df..24aaa12c02655 100644 --- a/build/vite/vite.config.ts +++ b/build/vite/vite.config.ts @@ -143,11 +143,8 @@ 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; - } + // the baseUrl code cannot be analyzed by vite. + // However, it is not needed, so it is okay to silence the warning. if (msg.indexOf('await import(new URL(`vs/workbench/workbench.desktop.main.js`, baseUrl).href)') !== -1) { return; } diff --git a/build/win32/code.iss b/build/win32/code.iss index f7091b28e5597..935f17dbe4150 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -95,9 +95,12 @@ Name: "runcode"; Description: "{cm:RunAfter,{#NameShort}}"; GroupDescription: "{ 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: "*"; Excludes: "\CodeSignSummary*.md,\tools,\tools\*,\policies,\policies\*,\appx,\appx\*,\resources\app\product.json,\{#ExeBasename}.exe,{#ifdef ProxyExeBasename}\{#ProxyExeBasename}.exe,{#endif}\{#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 +#ifdef ProxyExeBasename +Source: "{#ProxyExeBasename}.exe"; DestDir: "{code:GetDestDir}"; DestName: "{code:GetProxyExeBasename}"; Flags: ignoreversion +#endif Source: "tools\*"; DestDir: "{app}\{#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 @@ -113,6 +116,11 @@ Source: "appx\{#AppxPackageDll}"; DestDir: "{code:GetDestDir}\{#VersionedResourc Name: "{group}\{#NameLong}"; Filename: "{app}\{#ExeBasename}.exe"; AppUserModelID: "{#AppUserId}"; Check: ShouldUpdateShortcut(ExpandConstant('{group}\{#NameLong}.lnk')) Name: "{autodesktop}\{#NameLong}"; Filename: "{app}\{#ExeBasename}.exe"; Tasks: desktopicon; AppUserModelID: "{#AppUserId}"; Check: ShouldUpdateShortcut(ExpandConstant('{autodesktop}\{#NameLong}.lnk')) Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#NameLong}"; Filename: "{app}\{#ExeBasename}.exe"; Tasks: quicklaunchicon; AppUserModelID: "{#AppUserId}"; Check: ShouldUpdateShortcut(ExpandConstant('{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#NameLong}.lnk')) +#ifdef ProxyExeBasename +Name: "{group}\{#ProxyExeBasename}"; Filename: "{app}\{#ProxyExeBasename}.exe"; AppUserModelID: "{#ProxyAppUserId}"; Check: ShouldUpdateShortcut(ExpandConstant('{group}\{#ProxyExeBasename}.lnk')) +Name: "{autodesktop}\{#ProxyNameLong}"; Filename: "{app}\{#ProxyExeBasename}.exe"; Tasks: desktopicon; AppUserModelID: "{#ProxyAppUserId}"; Check: ShouldUpdateShortcut(ExpandConstant('{autodesktop}\{#ProxyNameLong}.lnk')) +Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#ProxyNameLong}"; Filename: "{app}\{#ProxyExeBasename}.exe"; Tasks: quicklaunchicon; AppUserModelID: "{#ProxyAppUserId}"; Check: ShouldUpdateShortcut(ExpandConstant('{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#ProxyNameLong}.lnk')) +#endif [Run] Filename: "{app}\{#ExeBasename}.exe"; Description: "{cm:LaunchProgram,{#NameLong}}"; Tasks: runcode; Flags: nowait postinstall; Check: ShouldRunAfterUpdate @@ -1562,6 +1570,16 @@ begin Result := ExpandConstant('{#ExeBasename}.exe'); end; +#ifdef ProxyExeBasename +function GetProxyExeBasename(Value: string): string; +begin + if IsBackgroundUpdate() and IsVersionedUpdate() then + Result := ExpandConstant('new_{#ProxyExeBasename}.exe') + else + Result := ExpandConstant('{#ProxyExeBasename}.exe'); +end; +#endif + function GetBinDirTunnelApplicationFilename(Value: string): string; begin if IsBackgroundUpdate() and IsVersionedUpdate() then diff --git a/build/win32/inno_updater.exe b/build/win32/inno_updater.exe index c3c4a0cd2bcb8..424e997bde5b9 100644 Binary files a/build/win32/inno_updater.exe and b/build/win32/inno_updater.exe differ diff --git a/cglicenses.json b/cglicenses.json index 2b1bc6fece5b6..37bba3145ba11 100644 --- a/cglicenses.json +++ b/cglicenses.json @@ -707,64 +707,6 @@ "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: License file starts with (MIT) before the copyright, tool can't parse it "name": "balanced-match", diff --git a/cli/Cargo.lock b/cli/Cargo.lock index cd9b8de6afba6..afe353213b1b5 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -447,6 +447,7 @@ dependencies = [ "uuid", "winapi", "winreg 0.50.0", + "winresource", "zbus", "zip", ] @@ -2645,6 +2646,15 @@ dependencies = [ "syn 2.0.115", ] +[[package]] +name = "serde_spanned" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +dependencies = [ + "serde_core", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -3004,12 +3014,36 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap 2.13.0", + "serde_core", + "serde_spanned", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.14", +] + [[package]] name = "toml_datetime" version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_edit" version = "0.19.15" @@ -3017,10 +3051,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap 2.13.0", - "toml_datetime", - "winnow", + "toml_datetime 0.6.11", + "winnow 0.5.40", +] + +[[package]] +name = "toml_parser" +version = "1.0.9+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" +dependencies = [ + "winnow 0.7.14", ] +[[package]] +name = "toml_writer" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" + [[package]] name = "tower-service" version = "0.3.3" @@ -3699,6 +3748,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" + [[package]] name = "winreg" version = "0.8.0" @@ -3718,6 +3773,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "winresource" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e287ced0f21cd11f4035fe946fd3af145f068d1acb708afd248100f89ec7432d" +dependencies = [ + "toml", + "version_check", +] + [[package]] name = "wit-bindgen" version = "0.51.0" diff --git a/cli/ThirdPartyNotices.txt b/cli/ThirdPartyNotices.txt index 61cacaea79978..9edb0ae9d2330 100644 --- a/cli/ThirdPartyNotices.txt +++ b/cli/ThirdPartyNotices.txt @@ -9189,6 +9189,32 @@ DEALINGS IN THE SOFTWARE. --------------------------------------------------------- +serde_spanned 1.0.4 - MIT OR Apache-2.0 +https://github.com/toml-rs/toml + +Copyright (c) Individual 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. +--------------------------------------------------------- + +--------------------------------------------------------- + serde_urlencoded 0.7.1 - MIT/Apache-2.0 https://github.com/nox/serde_urlencoded @@ -10517,7 +10543,34 @@ SOFTWARE. --------------------------------------------------------- +toml 0.9.12+spec-1.1.0 - MIT OR Apache-2.0 +https://github.com/toml-rs/toml + +Copyright (c) Individual 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. +--------------------------------------------------------- + +--------------------------------------------------------- + toml_datetime 0.6.11 - MIT OR Apache-2.0 +toml_datetime 0.7.5+spec-1.1.0 - MIT OR Apache-2.0 https://github.com/toml-rs/toml ../../LICENSE-MIT @@ -10533,6 +10586,58 @@ https://github.com/toml-rs/toml --------------------------------------------------------- +toml_parser 1.0.9+spec-1.1.0 - MIT OR Apache-2.0 +https://github.com/toml-rs/toml + +Copyright (c) Individual 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. +--------------------------------------------------------- + +--------------------------------------------------------- + +toml_writer 1.0.6+spec-1.1.0 - MIT OR Apache-2.0 +https://github.com/toml-rs/toml + +Copyright (c) Individual 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. +--------------------------------------------------------- + +--------------------------------------------------------- + tower-service 0.3.3 - MIT https://github.com/tower-rs/tower @@ -12700,6 +12805,7 @@ MIT License --------------------------------------------------------- winnow 0.5.40 - MIT +winnow 0.7.14 - MIT https://github.com/winnow-rs/winnow The MIT License (MIT) @@ -12755,6 +12861,40 @@ THE SOFTWARE. --------------------------------------------------------- +winresource 0.1.30 - MIT +https://github.com/BenjaminRi/winresource + +The MIT License (MIT) + +Copyright 2016 Max Resch + +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. +--------------------------------------------------------- + +--------------------------------------------------------- + wit-bindgen 0.51.0 - Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT https://github.com/bytecodealliance/wit-bindgen diff --git a/eslint.config.js b/eslint.config.js index 93a8a1b7b4396..ec5efb7c5fc94 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -828,6 +828,36 @@ export default tseslint.config( ] } }, + // git extension - ban non-type imports from git.d.ts (use git.constants for runtime values) + { + files: [ + 'extensions/git/src/**/*.ts', + ], + ignores: [ + 'extensions/git/src/api/git.constants.ts', + ], + languageOptions: { + parser: tseslint.parser, + }, + plugins: { + '@typescript-eslint': tseslint.plugin, + }, + rules: { + 'no-restricted-imports': 'off', + '@typescript-eslint/no-restricted-imports': [ + 'warn', + { + 'patterns': [ + { + 'group': ['*/api/git'], + 'allowTypeImports': true, + 'message': 'Use \'import type\' for types from git.d.ts and import runtime const enum values from git.constants instead' + }, + ] + } + ] + } + }, // vscode API { files: [ @@ -1955,7 +1985,8 @@ export default tseslint.config( 'vs/workbench/browser/**', 'vs/workbench/contrib/**', 'vs/workbench/services/*/~', - 'vs/sessions/~' + 'vs/sessions/~', + 'vs/sessions/services/*/~' ] }, { @@ -1974,6 +2005,30 @@ export default tseslint.config( 'vs/sessions/contrib/*/~' ] }, + { + 'target': 'src/vs/sessions/services/*/~', + 'restrictions': [ + 'vs/base/~', + 'vs/base/parts/*/~', + 'vs/platform/*/~', + 'vs/editor/~', + 'vs/editor/contrib/*/~', + 'vs/workbench/~', + 'vs/workbench/services/*/~', + { + 'when': 'test', + 'pattern': 'vs/workbench/contrib/*/~' + }, // TODO@layers + '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 + { + 'when': 'hasBrowser', + 'pattern': '@xterm/xterm' + } // node module allowed even in /browser/ + ] + }, ] } }, diff --git a/extensions/css-language-features/package-lock.json b/extensions/css-language-features/package-lock.json index 231eda54dba77..e5cf7c26d199e 100644 --- a/extensions/css-language-features/package-lock.json +++ b/extensions/css-language-features/package-lock.json @@ -51,9 +51,9 @@ } }, "node_modules/minimatch": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", - "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "license": "BlueOak-1.0.0", "dependencies": { "brace-expansion": "^5.0.2" diff --git a/extensions/extension-editing/src/extensionEditingBrowserMain.ts b/extensions/extension-editing/src/extensionEditingBrowserMain.ts index f9d6885c6223c..57c969d017020 100644 --- a/extensions/extension-editing/src/extensionEditingBrowserMain.ts +++ b/extensions/extension-editing/src/extensionEditingBrowserMain.ts @@ -5,11 +5,14 @@ import * as vscode from 'vscode'; import { PackageDocument } from './packageDocumentHelper'; +import { PackageDocumentL10nSupport } from './packageDocumentL10nSupport'; export function activate(context: vscode.ExtensionContext) { //package.json suggestions context.subscriptions.push(registerPackageDocumentCompletions()); + //package.json go to definition for NLS strings + context.subscriptions.push(new PackageDocumentL10nSupport()); } function registerPackageDocumentCompletions(): vscode.Disposable { @@ -18,5 +21,4 @@ function registerPackageDocumentCompletions(): vscode.Disposable { return new PackageDocument(document).provideCompletionItems(position, token); } }); - } diff --git a/extensions/extension-editing/src/extensionEditingMain.ts b/extensions/extension-editing/src/extensionEditingMain.ts index c056fbfa975ae..c620b3039541f 100644 --- a/extensions/extension-editing/src/extensionEditingMain.ts +++ b/extensions/extension-editing/src/extensionEditingMain.ts @@ -5,6 +5,7 @@ import * as vscode from 'vscode'; import { PackageDocument } from './packageDocumentHelper'; +import { PackageDocumentL10nSupport } from './packageDocumentL10nSupport'; import { ExtensionLinter } from './extensionLinter'; export function activate(context: vscode.ExtensionContext) { @@ -15,6 +16,9 @@ export function activate(context: vscode.ExtensionContext) { //package.json code actions for lint warnings context.subscriptions.push(registerCodeActionsProvider()); + // package.json l10n support + context.subscriptions.push(new PackageDocumentL10nSupport()); + context.subscriptions.push(new ExtensionLinter()); } diff --git a/extensions/extension-editing/src/packageDocumentL10nSupport.ts b/extensions/extension-editing/src/packageDocumentL10nSupport.ts new file mode 100644 index 0000000000000..4d844e98d5f71 --- /dev/null +++ b/extensions/extension-editing/src/packageDocumentL10nSupport.ts @@ -0,0 +1,204 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { getLocation, getNodeValue, parseTree, findNodeAtLocation, visit } from 'jsonc-parser'; + + +const packageJsonSelector: vscode.DocumentSelector = { language: 'json', pattern: '**/package.json' }; +const packageNlsJsonSelector: vscode.DocumentSelector = { language: 'json', pattern: '**/package.nls.json' }; + +export class PackageDocumentL10nSupport implements vscode.DefinitionProvider, vscode.ReferenceProvider, vscode.Disposable { + + private readonly _disposables: vscode.Disposable[] = []; + + constructor() { + this._disposables.push(vscode.languages.registerDefinitionProvider(packageJsonSelector, this)); + this._disposables.push(vscode.languages.registerDefinitionProvider(packageNlsJsonSelector, this)); + + this._disposables.push(vscode.languages.registerReferenceProvider(packageNlsJsonSelector, this)); + this._disposables.push(vscode.languages.registerReferenceProvider(packageJsonSelector, this)); + } + + dispose(): void { + for (const d of this._disposables) { + d.dispose(); + } + } + + public async provideDefinition(document: vscode.TextDocument, position: vscode.Position, _token: vscode.CancellationToken): Promise { + const basename = document.uri.path.split('/').pop()?.toLowerCase(); + if (basename === 'package.json') { + return this.provideNlsValueDefinition(document, position); + } + + if (basename === 'package.nls.json') { + return this.provideNlsKeyDefinition(document, position); + } + + return undefined; + } + + private async provideNlsValueDefinition(packageJsonDoc: vscode.TextDocument, position: vscode.Position): Promise { + const nlsRef = this.getNlsReferenceAtPosition(packageJsonDoc, position); + if (!nlsRef) { + return undefined; + } + + const nlsUri = vscode.Uri.joinPath(packageJsonDoc.uri, '..', 'package.nls.json'); + return this.resolveNlsDefinition(nlsRef, nlsUri); + } + + private async provideNlsKeyDefinition(nlsDoc: vscode.TextDocument, position: vscode.Position): Promise { + const nlsKey = this.getNlsKeyDefinitionAtPosition(nlsDoc, position); + if (!nlsKey) { + return undefined; + } + return this.resolveNlsDefinition(nlsKey, nlsDoc.uri); + } + + private async resolveNlsDefinition(origin: { key: string; range: vscode.Range }, nlsUri: vscode.Uri): Promise { + const target = await this.findNlsKeyDeclaration(origin.key, nlsUri); + if (!target) { + return undefined; + } + + return [{ + originSelectionRange: origin.range, + targetUri: target.uri, + targetRange: target.range, + }]; + } + + private getNlsReferenceAtPosition(packageJsonDoc: vscode.TextDocument, position: vscode.Position): { key: string; range: vscode.Range } | undefined { + const location = getLocation(packageJsonDoc.getText(), packageJsonDoc.offsetAt(position)); + if (!location.previousNode || location.previousNode.type !== 'string') { + return undefined; + } + + const value = getNodeValue(location.previousNode); + if (typeof value !== 'string') { + return undefined; + } + + const match = value.match(/^%(.+)%$/); + if (!match) { + return undefined; + } + + const nodeStart = packageJsonDoc.positionAt(location.previousNode.offset); + const nodeEnd = packageJsonDoc.positionAt(location.previousNode.offset + location.previousNode.length); + return { key: match[1], range: new vscode.Range(nodeStart, nodeEnd) }; + } + + public async provideReferences(document: vscode.TextDocument, position: vscode.Position, context: vscode.ReferenceContext, _token: vscode.CancellationToken): Promise { + const basename = document.uri.path.split('/').pop()?.toLowerCase(); + if (basename === 'package.nls.json') { + return this.provideNlsKeyReferences(document, position, context); + } + if (basename === 'package.json') { + return this.provideNlsValueReferences(document, position, context); + } + return undefined; + } + + private async provideNlsKeyReferences(nlsDoc: vscode.TextDocument, position: vscode.Position, context: vscode.ReferenceContext): Promise { + const nlsKey = this.getNlsKeyDefinitionAtPosition(nlsDoc, position); + if (!nlsKey) { + return undefined; + } + + const packageJsonUri = vscode.Uri.joinPath(nlsDoc.uri, '..', 'package.json'); + return this.findAllNlsReferences(nlsKey.key, packageJsonUri, nlsDoc.uri, context); + } + + private async provideNlsValueReferences(packageJsonDoc: vscode.TextDocument, position: vscode.Position, context: vscode.ReferenceContext): Promise { + const nlsRef = this.getNlsReferenceAtPosition(packageJsonDoc, position); + if (!nlsRef) { + return undefined; + } + + const nlsUri = vscode.Uri.joinPath(packageJsonDoc.uri, '..', 'package.nls.json'); + return this.findAllNlsReferences(nlsRef.key, packageJsonDoc.uri, nlsUri, context); + } + + private async findAllNlsReferences(nlsKey: string, packageJsonUri: vscode.Uri, nlsUri: vscode.Uri, context: vscode.ReferenceContext): Promise { + const locations = await this.findNlsReferencesInPackageJson(nlsKey, packageJsonUri); + + if (context.includeDeclaration) { + const decl = await this.findNlsKeyDeclaration(nlsKey, nlsUri); + if (decl) { + locations.push(decl); + } + } + + return locations; + } + + private async findNlsKeyDeclaration(nlsKey: string, nlsUri: vscode.Uri): Promise { + try { + const nlsDoc = await vscode.workspace.openTextDocument(nlsUri); + const nlsTree = parseTree(nlsDoc.getText()); + if (!nlsTree) { + return undefined; + } + + const node = findNodeAtLocation(nlsTree, [nlsKey]); + if (!node?.parent) { + return undefined; + } + + const keyNode = node.parent.children?.[0]; + if (!keyNode) { + return undefined; + } + + const start = nlsDoc.positionAt(keyNode.offset); + const end = nlsDoc.positionAt(keyNode.offset + keyNode.length); + return new vscode.Location(nlsUri, new vscode.Range(start, end)); + } catch { + return undefined; + } + } + + private async findNlsReferencesInPackageJson(nlsKey: string, packageJsonUri: vscode.Uri): Promise { + let packageJsonDoc: vscode.TextDocument; + try { + packageJsonDoc = await vscode.workspace.openTextDocument(packageJsonUri); + } catch { + return []; + } + + const text = packageJsonDoc.getText(); + const needle = `%${nlsKey}%`; + const locations: vscode.Location[] = []; + + visit(text, { + onLiteralValue(value, offset, length) { + if (value === needle) { + const start = packageJsonDoc.positionAt(offset); + const end = packageJsonDoc.positionAt(offset + length); + locations.push(new vscode.Location(packageJsonUri, new vscode.Range(start, end))); + } + } + }); + + return locations; + } + + private getNlsKeyDefinitionAtPosition(nlsDoc: vscode.TextDocument, position: vscode.Position): { key: string; range: vscode.Range } | undefined { + const location = getLocation(nlsDoc.getText(), nlsDoc.offsetAt(position)); + + // Must be on a top-level property key + if (location.path.length !== 1 || !location.isAtPropertyKey || !location.previousNode) { + return undefined; + } + + const key = location.path[0] as string; + const start = nlsDoc.positionAt(location.previousNode.offset); + const end = nlsDoc.positionAt(location.previousNode.offset + location.previousNode.length); + return { key, range: new vscode.Range(start, end) }; + } +} diff --git a/extensions/git/.vscodeignore b/extensions/git/.vscodeignore index a1fc5df7d26b8..9de840770944a 100644 --- a/extensions/git/.vscodeignore +++ b/extensions/git/.vscodeignore @@ -3,5 +3,5 @@ test/** out/** tsconfig*.json build/** -extension.webpack.config.js +esbuild*.mts package-lock.json diff --git a/extensions/git/esbuild.mts b/extensions/git/esbuild.mts new file mode 100644 index 0000000000000..35c8f6c63f0da --- /dev/null +++ b/extensions/git/esbuild.mts @@ -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 * as path from 'node:path'; +import { run } from '../esbuild-extension-common.mts'; + +const srcDir = path.join(import.meta.dirname, 'src'); +const outDir = path.join(import.meta.dirname, 'dist'); + +run({ + platform: 'node', + entryPoints: { + 'main': path.join(srcDir, 'main.ts'), + 'askpass-main': path.join(srcDir, 'askpass-main.ts'), + 'git-editor-main': path.join(srcDir, 'git-editor-main.ts'), + }, + srcDir, + outdir: outDir, +}, process.argv); diff --git a/extensions/git/package.json b/extensions/git/package.json index 39017ca4e1eed..1fbac49569f9f 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1147,6 +1147,46 @@ "title": "%command.deleteRef%", "category": "Git", "enablement": "!operationInProgress" + }, + { + "command": "git.repositories.worktreeCopyBranchName", + "title": "%command.artifactCopyBranchName%", + "category": "Git" + }, + { + "command": "git.repositories.worktreeCopyCommitHash", + "title": "%command.artifactCopyCommitHash%", + "category": "Git" + }, + { + "command": "git.repositories.worktreeCopyPath", + "title": "%command.artifactCopyWorktreePath%", + "category": "Git" + }, + { + "command": "git.repositories.copyCommitHash", + "title": "%command.artifactCopyCommitHash%", + "category": "Git" + }, + { + "command": "git.repositories.copyBranchName", + "title": "%command.artifactCopyBranchName%", + "category": "Git" + }, + { + "command": "git.repositories.copyTagName", + "title": "%command.artifactCopyTagName%", + "category": "Git" + }, + { + "command": "git.repositories.copyStashName", + "title": "%command.artifactCopyStashName%", + "category": "Git" + }, + { + "command": "git.repositories.stashCopyBranchName", + "title": "%command.artifactCopyBranchName%", + "category": "Git" } ], "continueEditSession": [ @@ -1846,6 +1886,38 @@ { "command": "git.repositories.deleteWorktree", "when": "false" + }, + { + "command": "git.repositories.worktreeCopyBranchName", + "when": "false" + }, + { + "command": "git.repositories.worktreeCopyCommitHash", + "when": "false" + }, + { + "command": "git.repositories.worktreeCopyPath", + "when": "false" + }, + { + "command": "git.repositories.copyCommitHash", + "when": "false" + }, + { + "command": "git.repositories.copyBranchName", + "when": "false" + }, + { + "command": "git.repositories.copyTagName", + "when": "false" + }, + { + "command": "git.repositories.copyStashName", + "when": "false" + }, + { + "command": "git.repositories.stashCopyBranchName", + "when": "false" } ], "scm/title": [ @@ -2090,6 +2162,16 @@ "group": "3_drop@3", "when": "scmProvider == git && scmArtifactGroupId == stashes" }, + { + "command": "git.repositories.stashCopyBranchName", + "group": "4_copy@1", + "when": "scmProvider == git && scmArtifactGroupId == stashes" + }, + { + "command": "git.repositories.copyStashName", + "group": "4_copy@2", + "when": "scmProvider == git && scmArtifactGroupId == stashes" + }, { "command": "git.repositories.checkout", "group": "1_checkout@1", @@ -2130,6 +2212,21 @@ "group": "4_compare@1", "when": "scmProvider == git && (scmArtifactGroupId == branches || scmArtifactGroupId == tags)" }, + { + "command": "git.repositories.copyCommitHash", + "group": "5_copy@2", + "when": "scmProvider == git && (scmArtifactGroupId == branches || scmArtifactGroupId == tags)" + }, + { + "command": "git.repositories.copyBranchName", + "group": "5_copy@1", + "when": "scmProvider == git && scmArtifactGroupId == branches" + }, + { + "command": "git.repositories.copyTagName", + "group": "5_copy@2", + "when": "scmProvider == git && scmArtifactGroupId == tags" + }, { "command": "git.repositories.openWorktreeInNewWindow", "group": "inline@1", @@ -2149,6 +2246,21 @@ "command": "git.repositories.deleteWorktree", "group": "2_modify@1", "when": "scmProvider == git && scmArtifactGroupId == worktrees" + }, + { + "command": "git.repositories.worktreeCopyCommitHash", + "group": "3_copy@2", + "when": "scmProvider == git && scmArtifactGroupId == worktrees" + }, + { + "command": "git.repositories.worktreeCopyBranchName", + "group": "3_copy@1", + "when": "scmProvider == git && scmArtifactGroupId == worktrees" + }, + { + "command": "git.repositories.worktreeCopyPath", + "group": "3_copy@3", + "when": "scmProvider == git && scmArtifactGroupId == worktrees" } ], "scm/resourceGroup/context": [ diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 9d469e33c84e9..147a75f9b7024 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -129,7 +129,7 @@ "command.stashView": "View Stash...", "command.stashView2": "View Stash", "command.timelineOpenDiff": "Open Changes", - "command.timelineCopyCommitId": "Copy Commit ID", + "command.timelineCopyCommitId": "Copy Commit Hash", "command.timelineCopyCommitMessage": "Copy Commit Message", "command.timelineSelectForCompare": "Select for Compare", "command.timelineCompareWithSelected": "Compare with Selected", @@ -148,6 +148,11 @@ "command.graphCompareWithMergeBase": "Compare with Merge Base", "command.graphCompareWithRemote": "Compare with Remote", "command.deleteRef": "Delete", + "command.artifactCopyCommitHash": "Copy Commit Hash", + "command.artifactCopyBranchName": "Copy Branch Name", + "command.artifactCopyTagName": "Copy Tag Name", + "command.artifactCopyStashName": "Copy Stash Name", + "command.artifactCopyWorktreePath": "Copy Worktree Path", "command.blameToggleEditorDecoration": "Toggle Git Blame Editor Decoration", "command.blameToggleStatusBarItem": "Toggle Git Blame Status Bar Item", "command.api.getRepositories": "Get Repositories", diff --git a/extensions/git/src/actionButton.ts b/extensions/git/src/actionButton.ts index 63eefb1de028a..5804c23f69755 100644 --- a/extensions/git/src/actionButton.ts +++ b/extensions/git/src/actionButton.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Command, Disposable, Event, EventEmitter, SourceControlActionButton, Uri, workspace, l10n, LogOutputChannel } from 'vscode'; -import { Branch, RefType, Status } from './api/git'; +import type { Branch } from './api/git'; +import { RefType, Status } from './api/git.constants'; import { OperationKind } from './operation'; import { CommitCommandsCenter } from './postCommitCommands'; import { Repository } from './repository'; diff --git a/extensions/git/src/api/api1.ts b/extensions/git/src/api/api1.ts index 0791401665ec0..e5820c0ded74a 100644 --- a/extensions/git/src/api/api1.ts +++ b/extensions/git/src/api/api1.ts @@ -5,7 +5,8 @@ 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, CloneOptions, CommitShortStat, DiffChange, Worktree, RepositoryKind, RepositoryAccessDetails } from './git'; +import type { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, Ref, Submodule, Commit, Change, RepositoryUIState, LogOptions, APIState, CommitOptions, CredentialsProvider, BranchQuery, PushErrorHandler, PublishEvent, FetchOptions, RemoteSourceProvider, RemoteSourcePublisher, PostCommitCommandsProvider, RefQuery, BranchProtectionProvider, InitOptions, SourceControlHistoryItemDetailsProvider, CloneOptions, CommitShortStat, DiffChange, Worktree, RepositoryKind, RepositoryAccessDetails } from './git'; +import { ForcePushMode, GitErrorCodes, RefType, Status } from './git.constants'; import { Event, SourceControlInputBox, Uri, SourceControl, Disposable, commands, CancellationToken } from 'vscode'; import { combinedDisposable, filterEvent, mapEvent } from '../util'; import { toGitUri } from '../uri'; @@ -346,6 +347,10 @@ export class ApiRepository implements Repository { migrateChanges(sourceRepositoryPath: string, options?: { confirmation?: boolean; deleteFromSource?: boolean; untracked?: boolean }): Promise { return this.#repository.migrateChanges(sourceRepositoryPath, options); } + + generateRandomBranchName(): Promise { + return this.#repository.generateRandomBranchName(); + } } export class ApiGit implements Git { diff --git a/extensions/git/src/api/extension.ts b/extensions/git/src/api/extension.ts index 7b0313b6c26e6..a4c6af087ce1b 100644 --- a/extensions/git/src/api/extension.ts +++ b/extensions/git/src/api/extension.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Model } from '../model'; -import { GitExtension, Repository, API } from './git'; +import type { GitExtension, Repository, API } from './git'; import { ApiRepository, ApiImpl } from './api1'; import { Event, EventEmitter } from 'vscode'; import { CloneManager } from '../cloneManager'; diff --git a/extensions/git/src/api/git.constants.ts b/extensions/git/src/api/git.constants.ts new file mode 100644 index 0000000000000..5847e21d5d0da --- /dev/null +++ b/extensions/git/src/api/git.constants.ts @@ -0,0 +1,98 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import type * as git from './git'; + +export type ForcePushMode = git.ForcePushMode; +export type RefType = git.RefType; +export type Status = git.Status; +export type GitErrorCodes = git.GitErrorCodes; + +export const ForcePushMode = Object.freeze({ + Force: 0, + ForceWithLease: 1, + ForceWithLeaseIfIncludes: 2, +}) satisfies typeof git.ForcePushMode; + +export const RefType = Object.freeze({ + Head: 0, + RemoteHead: 1, + Tag: 2, +}) satisfies typeof git.RefType; + +export const Status = Object.freeze({ + INDEX_MODIFIED: 0, + INDEX_ADDED: 1, + INDEX_DELETED: 2, + INDEX_RENAMED: 3, + INDEX_COPIED: 4, + + MODIFIED: 5, + DELETED: 6, + UNTRACKED: 7, + IGNORED: 8, + INTENT_TO_ADD: 9, + INTENT_TO_RENAME: 10, + TYPE_CHANGED: 11, + + ADDED_BY_US: 12, + ADDED_BY_THEM: 13, + DELETED_BY_US: 14, + DELETED_BY_THEM: 15, + BOTH_ADDED: 16, + BOTH_DELETED: 17, + BOTH_MODIFIED: 18, +}) satisfies typeof git.Status; + +export const GitErrorCodes = Object.freeze({ + BadConfigFile: 'BadConfigFile', + BadRevision: 'BadRevision', + AuthenticationFailed: 'AuthenticationFailed', + NoUserNameConfigured: 'NoUserNameConfigured', + NoUserEmailConfigured: 'NoUserEmailConfigured', + NoRemoteRepositorySpecified: 'NoRemoteRepositorySpecified', + NotAGitRepository: 'NotAGitRepository', + NotASafeGitRepository: 'NotASafeGitRepository', + NotAtRepositoryRoot: 'NotAtRepositoryRoot', + Conflict: 'Conflict', + StashConflict: 'StashConflict', + UnmergedChanges: 'UnmergedChanges', + PushRejected: 'PushRejected', + ForcePushWithLeaseRejected: 'ForcePushWithLeaseRejected', + ForcePushWithLeaseIfIncludesRejected: 'ForcePushWithLeaseIfIncludesRejected', + RemoteConnectionError: 'RemoteConnectionError', + DirtyWorkTree: 'DirtyWorkTree', + CantOpenResource: 'CantOpenResource', + GitNotFound: 'GitNotFound', + CantCreatePipe: 'CantCreatePipe', + PermissionDenied: 'PermissionDenied', + CantAccessRemote: 'CantAccessRemote', + RepositoryNotFound: 'RepositoryNotFound', + RepositoryIsLocked: 'RepositoryIsLocked', + BranchNotFullyMerged: 'BranchNotFullyMerged', + NoRemoteReference: 'NoRemoteReference', + InvalidBranchName: 'InvalidBranchName', + BranchAlreadyExists: 'BranchAlreadyExists', + NoLocalChanges: 'NoLocalChanges', + NoStashFound: 'NoStashFound', + LocalChangesOverwritten: 'LocalChangesOverwritten', + NoUpstreamBranch: 'NoUpstreamBranch', + IsInSubmodule: 'IsInSubmodule', + WrongCase: 'WrongCase', + CantLockRef: 'CantLockRef', + CantRebaseMultipleBranches: 'CantRebaseMultipleBranches', + PatchDoesNotApply: 'PatchDoesNotApply', + NoPathFound: 'NoPathFound', + UnknownPath: 'UnknownPath', + EmptyCommitMessage: 'EmptyCommitMessage', + BranchFastForwardRejected: 'BranchFastForwardRejected', + BranchNotYetBorn: 'BranchNotYetBorn', + TagConflict: 'TagConflict', + CherryPickEmpty: 'CherryPickEmpty', + CherryPickConflict: 'CherryPickConflict', + WorktreeContainsChanges: 'WorktreeContainsChanges', + WorktreeAlreadyExists: 'WorktreeAlreadyExists', + WorktreeBranchAlreadyUsed: 'WorktreeBranchAlreadyUsed', +}) satisfies Record; diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index 287dd4399bf2c..122134c2c8b57 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -325,6 +325,8 @@ export interface Repository { deleteWorktree(path: string, options?: { force?: boolean }): Promise; migrateChanges(sourceRepositoryPath: string, options?: { confirmation?: boolean; deleteFromSource?: boolean; untracked?: boolean }): Promise; + + generateRandomBranchName(): Promise; } export interface RemoteSource { diff --git a/extensions/git/src/artifactProvider.ts b/extensions/git/src/artifactProvider.ts index f63899efa3edb..832b5626ae0a8 100644 --- a/extensions/git/src/artifactProvider.ts +++ b/extensions/git/src/artifactProvider.ts @@ -6,7 +6,8 @@ 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 { Ref, RefType, Worktree } from './api/git'; +import type { Ref, Worktree } from './api/git'; +import { RefType } from './api/git.constants'; import { OperationKind } from './operation'; /** diff --git a/extensions/git/src/askpass.ts b/extensions/git/src/askpass.ts index 1cb1890e24245..cc9e607f08f48 100644 --- a/extensions/git/src/askpass.ts +++ b/extensions/git/src/askpass.ts @@ -6,7 +6,7 @@ import { window, InputBoxOptions, Uri, Disposable, workspace, QuickPickOptions, l10n, LogOutputChannel } from 'vscode'; import { IDisposable, EmptyDisposable, toDisposable, extractFilePathFromArgs } from './util'; import { IIPCHandler, IIPCServer } from './ipc/ipcServer'; -import { CredentialsProvider, Credentials } from './api/git'; +import type { CredentialsProvider, Credentials } from './api/git'; import { ITerminalEnvironmentProvider } from './terminal'; import { AskpassPaths } from './askpassManager'; diff --git a/extensions/git/src/autofetch.ts b/extensions/git/src/autofetch.ts index 00d6450b3baf8..201bf647f1a11 100644 --- a/extensions/git/src/autofetch.ts +++ b/extensions/git/src/autofetch.ts @@ -6,7 +6,7 @@ import { workspace, Disposable, EventEmitter, Memento, window, MessageItem, ConfigurationTarget, Uri, ConfigurationChangeEvent, l10n, env } from 'vscode'; import { Repository } from './repository'; import { eventToPromise, filterEvent, onceEvent } from './util'; -import { GitErrorCodes } from './api/git'; +import { GitErrorCodes } from './api/git.constants'; export class AutoFetcher { diff --git a/extensions/git/src/blame.ts b/extensions/git/src/blame.ts index 8773264eb70f2..83a60ec9e1879 100644 --- a/extensions/git/src/blame.ts +++ b/extensions/git/src/blame.ts @@ -13,7 +13,7 @@ import { fromGitUri, isGitUri, toGitUri } from './uri'; import { emojify, ensureEmojis } from './emoji'; import { getWorkingTreeAndIndexDiffInformation, getWorkingTreeDiffInformation } from './staging'; import { provideSourceControlHistoryItemAvatar, provideSourceControlHistoryItemHoverCommands, provideSourceControlHistoryItemMessageLinks } from './historyItemDetailsProvider'; -import { AvatarQuery, AvatarQueryCommit } from './api/git'; +import type { AvatarQuery, AvatarQueryCommit } from './api/git'; import { LRUCache } from './cache'; import { AVATAR_SIZE, getCommitHover, getHoverCommitHashCommands, processHoverRemoteCommands } from './hover'; diff --git a/extensions/git/src/branchProtection.ts b/extensions/git/src/branchProtection.ts index 0fbb3b7d4b166..b142a333b24a1 100644 --- a/extensions/git/src/branchProtection.ts +++ b/extensions/git/src/branchProtection.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, Event, EventEmitter, Uri, workspace } from 'vscode'; -import { BranchProtection, BranchProtectionProvider } from './api/git'; +import type { BranchProtection, BranchProtectionProvider } from './api/git'; import { dispose, filterEvent } from './util'; export interface IBranchProtectionProviderRegistry { diff --git a/extensions/git/src/cloneManager.ts b/extensions/git/src/cloneManager.ts index 49d57d8763c63..cee231dda779c 100644 --- a/extensions/git/src/cloneManager.ts +++ b/extensions/git/src/cloneManager.ts @@ -39,7 +39,8 @@ export class CloneManager { /* __GDPR__ "clone" : { "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } + "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: 'no_URL' }); @@ -74,7 +75,8 @@ export class CloneManager { /* __GDPR__ "clone" : { "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } + "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: 'no_directory' }); @@ -105,7 +107,8 @@ export class CloneManager { /* __GDPR__ "clone" : { "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } + "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: 'directory_not_empty' }); @@ -115,7 +118,8 @@ export class CloneManager { /* __GDPR__ "clone" : { "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } + "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: 'error' }); diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index f1456675f2e61..15f962b430703 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -7,8 +7,8 @@ import * as os from 'os'; import * as path from 'path'; 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, ProgressLocation } 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 type { CommitOptions, RemoteSourcePublisher, Remote, Branch, Ref } from './api/git'; +import { ForcePushMode, GitErrorCodes, RefType, Status } from './api/git.constants'; import { Git, GitError, Repository as GitRepository, Stash, Worktree } from './git'; import { Model } from './model'; import { GitResourceGroup, Repository, Resource, ResourceGroupType } from './repository'; @@ -106,6 +106,8 @@ class RefItem implements QuickPickItem { return `refs/remotes/${this.ref.name}`; case RefType.Tag: return `refs/tags/${this.ref.name}`; + default: + throw new Error('Unknown ref type'); } } get refName(): string | undefined { return this.ref.name; } @@ -1028,8 +1030,8 @@ export class CommandCenter { } @command('git.clone') - async clone(url?: string, parentPath?: string, options?: { ref?: string }): Promise { - await this.cloneManager.clone(url, { parentPath, ...options }); + async clone(url?: string, parentPath?: string, options?: { ref?: string; postCloneAction?: 'none' }): Promise { + return this.cloneManager.clone(url, { parentPath, ...options }); } @command('git.cloneRecursive') @@ -2940,48 +2942,6 @@ export class CommandCenter { await this._branch(repository, undefined, true); } - private async generateRandomBranchName(repository: Repository, separator: string): Promise { - const config = workspace.getConfiguration('git'); - const branchRandomNameDictionary = config.get('branchRandomName.dictionary')!; - - const dictionaries: string[][] = []; - for (const dictionary of branchRandomNameDictionary) { - if (dictionary.toLowerCase() === 'adjectives') { - dictionaries.push(adjectives); - } - if (dictionary.toLowerCase() === 'animals') { - dictionaries.push(animals); - } - if (dictionary.toLowerCase() === 'colors') { - dictionaries.push(colors); - } - if (dictionary.toLowerCase() === 'numbers') { - dictionaries.push(NumberDictionary.generate({ length: 3 })); - } - } - - if (dictionaries.length === 0) { - return ''; - } - - // 5 attempts to generate a random branch name - for (let index = 0; index < 5; index++) { - const randomName = uniqueNamesGenerator({ - dictionaries, - length: dictionaries.length, - separator - }); - - // Check for local ref conflict - const refs = await repository.getRefs({ pattern: `refs/heads/${randomName}` }); - if (refs.length === 0) { - return randomName; - } - } - - return ''; - } - private async promptForBranchName(repository: Repository, defaultName?: string, initialValue?: string): Promise { const config = workspace.getConfiguration('git'); const branchPrefix = config.get('branchPrefix')!; @@ -2995,8 +2955,7 @@ export class CommandCenter { } const getBranchName = async (): Promise => { - const branchName = branchRandomNameEnabled ? await this.generateRandomBranchName(repository, branchWhitespaceChar) : ''; - return `${branchPrefix}${branchName}`; + return await repository.generateRandomBranchName() ?? branchPrefix; }; const getValueSelection = (value: string): [number, number] | undefined => { @@ -5415,6 +5374,97 @@ export class CommandCenter { await repository.deleteWorktree(artifact.id); } + @command('git.repositories.worktreeCopyBranchName', { repository: true }) + async artifactWorktreeCopyBranchName(repository: Repository, artifact: SourceControlArtifact): Promise { + if (!repository || !artifact) { + return; + } + + const worktrees = await repository.getWorktreeDetails(); + const worktree = worktrees.find(w => w.path === artifact.id); + if (!worktree || worktree.detached) { + return; + } + + env.clipboard.writeText(worktree.ref.substring(11)); + } + + @command('git.repositories.worktreeCopyCommitHash', { repository: true }) + async artifactWorktreeCopyCommitHash(repository: Repository, artifact: SourceControlArtifact): Promise { + if (!repository || !artifact) { + return; + } + + const worktrees = await repository.getWorktreeDetails(); + const worktree = worktrees.find(w => w.path === artifact.id); + if (!worktree?.commitDetails) { + return; + } + + env.clipboard.writeText(worktree.commitDetails.hash); + } + + @command('git.repositories.worktreeCopyPath', { repository: true }) + async artifactWorktreeCopyPath(repository: Repository, artifact: SourceControlArtifact): Promise { + if (!repository || !artifact) { + return; + } + + env.clipboard.writeText(artifact.id); + } + + @command('git.repositories.copyCommitHash', { repository: true }) + async artifactCopyCommitHash(repository: Repository, artifact: SourceControlArtifact): Promise { + if (!repository || !artifact) { + return; + } + + const commit = await repository.getCommit(artifact.id); + env.clipboard.writeText(commit.hash); + } + + @command('git.repositories.copyBranchName', { repository: true }) + async artifactCopyBranchName(repository: Repository, artifact: SourceControlArtifact): Promise { + if (!repository || !artifact) { + return; + } + + env.clipboard.writeText(artifact.name); + } + + @command('git.repositories.copyTagName', { repository: true }) + async artifactCopyTagName(repository: Repository, artifact: SourceControlArtifact): Promise { + if (!repository || !artifact) { + return; + } + + env.clipboard.writeText(artifact.name); + } + + @command('git.repositories.copyStashName', { repository: true }) + async artifactCopyStashName(repository: Repository, artifact: SourceControlArtifact): Promise { + if (!repository || !artifact) { + return; + } + + env.clipboard.writeText(artifact.name); + } + + @command('git.repositories.stashCopyBranchName', { repository: true }) + async artifactStashCopyBranchName(repository: Repository, artifact: SourceControlArtifact): Promise { + if (!repository || !artifact?.description) { + return; + } + + const stashes = await repository.getStashes(); + const stash = stashes.find(s => artifact.id === `stash@{${s.index}}`); + if (!stash?.branchName) { + return; + } + + env.clipboard.writeText(stash.branchName); + } + private createCommand(id: string, key: string, method: Function, options: ScmCommandOptions): (...args: any[]) => any { const result = (...args: any[]) => { let result: Promise; diff --git a/extensions/git/src/decorationProvider.ts b/extensions/git/src/decorationProvider.ts index fb895d5aff2b3..11778f7f8f582 100644 --- a/extensions/git/src/decorationProvider.ts +++ b/extensions/git/src/decorationProvider.ts @@ -9,7 +9,8 @@ import { Repository, GitResourceGroup } from './repository'; import { Model } from './model'; import { debounce } from './decorators'; import { filterEvent, dispose, anyEvent, PromiseSource, combinedDisposable, runAndSubscribeEvent } from './util'; -import { Change, GitErrorCodes, Status } from './api/git'; +import type { Change } from './api/git'; +import { GitErrorCodes, Status } from './api/git.constants'; function equalSourceControlHistoryItemRefs(ref1?: SourceControlHistoryItemRef, ref2?: SourceControlHistoryItemRef): boolean { if (ref1 === ref2) { diff --git a/extensions/git/src/editSessionIdentityProvider.ts b/extensions/git/src/editSessionIdentityProvider.ts index 8380f03ecfd94..a3336d441743d 100644 --- a/extensions/git/src/editSessionIdentityProvider.ts +++ b/extensions/git/src/editSessionIdentityProvider.ts @@ -5,7 +5,7 @@ import * as path from 'path'; import * as vscode from 'vscode'; -import { RefType } from './api/git'; +import { RefType } from './api/git.constants'; import { Model } from './model'; export class GitEditSessionIdentityProvider implements vscode.EditSessionIdentityProvider, vscode.Disposable { diff --git a/extensions/git/src/fileSystemProvider.ts b/extensions/git/src/fileSystemProvider.ts index 19928863832a5..a09d00bfc2268 100644 --- a/extensions/git/src/fileSystemProvider.ts +++ b/extensions/git/src/fileSystemProvider.ts @@ -131,6 +131,26 @@ export class GitFileSystemProvider implements FileSystemProvider { this.cache = cache; } + private async getOrOpenRepository(uri: string | Uri): Promise { + let repository = this.model.getRepository(uri); + if (repository) { + return repository; + } + + // In case of the empty window, or the agent sessions window, no repositories are open + // so we need to explicitly open a repository before we can serve git content for the + // given git resource. + if (workspace.workspaceFolders === undefined || workspace.isAgentSessionsWorkspace) { + const fsPath = typeof uri === 'string' ? uri : fromGitUri(uri).path; + this.logger.info(`[GitFileSystemProvider][getOrOpenRepository] Opening repository for ${fsPath}`); + + await this.model.openRepository(fsPath, true, true); + repository = this.model.getRepository(uri); + } + + return repository; + } + watch(): Disposable { return EmptyDisposable; } @@ -139,7 +159,11 @@ export class GitFileSystemProvider implements FileSystemProvider { await this.model.isInitialized; const { submoduleOf, path, ref } = fromGitUri(uri); - const repository = submoduleOf ? this.model.getRepository(submoduleOf) : this.model.getRepository(uri); + + const repository = submoduleOf + ? await this.getOrOpenRepository(submoduleOf) + : await this.getOrOpenRepository(uri); + if (!repository) { this.logger.warn(`[GitFileSystemProvider][stat] Repository not found - ${uri.toString()}`); throw FileSystemError.FileNotFound(); @@ -175,7 +199,7 @@ export class GitFileSystemProvider implements FileSystemProvider { const { path, ref, submoduleOf } = fromGitUri(uri); if (submoduleOf) { - const repository = this.model.getRepository(submoduleOf); + const repository = await this.getOrOpenRepository(submoduleOf); if (!repository) { throw FileSystemError.FileNotFound(); @@ -190,7 +214,7 @@ export class GitFileSystemProvider implements FileSystemProvider { } } - const repository = this.model.getRepository(uri); + const repository = await this.getOrOpenRepository(uri); if (!repository) { this.logger.warn(`[GitFileSystemProvider][readFile] Repository not found - ${uri.toString()}`); diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 5127ae0fbb95f..5f7d1100f709c 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -13,7 +13,8 @@ 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, 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, DiffChange, Worktree as ApiWorktree } from './api/git'; +import type { Commit as ApiCommit, Ref, Branch, Remote, LogOptions, Change, CommitOptions, RefQuery as ApiRefQuery, InitOptions, DiffChange, Worktree as ApiWorktree } from './api/git'; +import { RefType, ForcePushMode, GitErrorCodes, Status } from './api/git.constants'; import * as byline from 'byline'; import { StringDecoder } from 'string_decoder'; @@ -1073,7 +1074,7 @@ function parseGitChanges(repositoryRoot: string, raw: string): Change[] { let uri = originalUri; let renameUri = originalUri; - let status = Status.UNTRACKED; + let status: Status = Status.UNTRACKED; // Copy or Rename status comes with a number (ex: 'R100'). // We don't need the number, we use only first character of the status. @@ -1138,7 +1139,7 @@ function parseGitChangesRaw(repositoryRoot: string, raw: string): DiffChange[] { let uri = originalUri; let renameUri = originalUri; - let status = Status.UNTRACKED; + let status: Status = Status.UNTRACKED; switch (change[0]) { case 'A': diff --git a/extensions/git/src/historyItemDetailsProvider.ts b/extensions/git/src/historyItemDetailsProvider.ts index be0e2b337f8f6..cccdf508fe3a8 100644 --- a/extensions/git/src/historyItemDetailsProvider.ts +++ b/extensions/git/src/historyItemDetailsProvider.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Command, Disposable } from 'vscode'; -import { AvatarQuery, SourceControlHistoryItemDetailsProvider } from './api/git'; +import type { AvatarQuery, SourceControlHistoryItemDetailsProvider } from './api/git'; import { Repository } from './repository'; import { ApiRepository } from './api/api1'; diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index 29e8705e04b35..c658b4c005eec 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -8,7 +8,8 @@ import { CancellationToken, Disposable, Event, EventEmitter, FileDecoration, Fil import { Repository, Resource } from './repository'; import { IDisposable, deltaHistoryItemRefs, dispose, filterEvent, subject, truncate } from './util'; import { toMultiFileDiffEditorUris } from './uri'; -import { AvatarQuery, AvatarQueryCommit, Branch, LogOptions, Ref, RefType } from './api/git'; +import type { AvatarQuery, AvatarQueryCommit, Branch, LogOptions, Ref } from './api/git'; +import { RefType } from './api/git.constants'; import { emojify, ensureEmojis } from './emoji'; import { Commit } from './git'; import { OperationKind, OperationResult } from './operation'; diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index b37ae9c79c5b7..b2690b24a7cdd 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -12,7 +12,7 @@ import { GitDecorations } from './decorationProvider'; import { Askpass } from './askpass'; import { toDisposable, filterEvent, eventToPromise } from './util'; import TelemetryReporter from '@vscode/extension-telemetry'; -import { GitExtension } from './api/git'; +import type { GitExtension } from './api/git'; import { GitProtocolHandler } from './protocolHandler'; import { GitExtensionImpl } from './api/extension'; import * as path from 'path'; diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 1d65d3dc2d755..deecc7c28629a 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -12,7 +12,7 @@ import { Git } from './git'; import * as path from 'path'; import * as fs from 'fs'; import { fromGitUri } from './uri'; -import { APIState as State, CredentialsProvider, PushErrorHandler, PublishEvent, RemoteSourcePublisher, PostCommitCommandsProvider, BranchProtectionProvider, SourceControlHistoryItemDetailsProvider } from './api/git'; +import type { APIState as State, CredentialsProvider, PushErrorHandler, PublishEvent, RemoteSourcePublisher, PostCommitCommandsProvider, BranchProtectionProvider, SourceControlHistoryItemDetailsProvider } from './api/git'; import { Askpass } from './askpass'; import { IPushErrorHandlerRegistry } from './pushError'; import { ApiRepository } from './api/api1'; diff --git a/extensions/git/src/postCommitCommands.ts b/extensions/git/src/postCommitCommands.ts index 69a18114a41e2..50658d14202ba 100644 --- a/extensions/git/src/postCommitCommands.ts +++ b/extensions/git/src/postCommitCommands.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Command, commands, Disposable, Event, EventEmitter, Memento, Uri, workspace, l10n } from 'vscode'; -import { PostCommitCommandsProvider } from './api/git'; +import type { PostCommitCommandsProvider } from './api/git'; import { IRepositoryResolver, Repository } from './repository'; import { ApiRepository } from './api/api1'; import { dispose } from './util'; diff --git a/extensions/git/src/pushError.ts b/extensions/git/src/pushError.ts index 6222923ff6864..71f564e8fa255 100644 --- a/extensions/git/src/pushError.ts +++ b/extensions/git/src/pushError.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from 'vscode'; -import { PushErrorHandler } from './api/git'; +import type { PushErrorHandler } from './api/git'; export interface IPushErrorHandlerRegistry { registerPushErrorHandler(provider: PushErrorHandler): Disposable; diff --git a/extensions/git/src/quickDiffProvider.ts b/extensions/git/src/quickDiffProvider.ts index 3b1aa64c8faae..961f5387555fd 100644 --- a/extensions/git/src/quickDiffProvider.ts +++ b/extensions/git/src/quickDiffProvider.ts @@ -7,7 +7,7 @@ import { FileType, l10n, LogOutputChannel, QuickDiffProvider, Uri, workspace } f import { IRepositoryResolver, Repository } from './repository'; import { isDescendant, pathEquals } from './util'; import { toGitUri } from './uri'; -import { Status } from './api/git'; +import { Status } from './api/git.constants'; export class GitQuickDiffProvider implements QuickDiffProvider { readonly label = l10n.t('Git Local Changes (Working Tree)'); diff --git a/extensions/git/src/remotePublisher.ts b/extensions/git/src/remotePublisher.ts index 1326776cde4a0..eb8ec7b8e19bb 100644 --- a/extensions/git/src/remotePublisher.ts +++ b/extensions/git/src/remotePublisher.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, Event } from 'vscode'; -import { RemoteSourcePublisher } from './api/git'; +import type { RemoteSourcePublisher } from './api/git'; export interface IRemoteSourcePublisherRegistry { readonly onDidAddRemoteSourcePublisher: Event; diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index f3b1afb4689e0..b79bb3bc4aabf 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import TelemetryReporter from '@vscode/extension-telemetry'; +import { uniqueNamesGenerator, adjectives, animals, colors, NumberDictionary } from '@joaomoreno/unique-names-generator'; import * as fs from 'fs'; import * as fsPromises from 'fs/promises'; import * as path from 'path'; @@ -11,7 +12,8 @@ import picomatch from 'picomatch'; import { CancellationError, CancellationToken, CancellationTokenSource, Command, commands, Disposable, Event, EventEmitter, ExcludeSettingOptions, FileDecoration, l10n, LogLevel, LogOutputChannel, Memento, ProgressLocation, ProgressOptions, 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, DiffChange, FetchOptions, ForcePushMode, GitErrorCodes, LogOptions, Ref, RefType, Remote, RepositoryKind, Status } from './api/git'; +import type { Branch, BranchQuery, Change, CommitOptions, DiffChange, FetchOptions, LogOptions, Ref, Remote, RepositoryKind } from './api/git'; +import { ForcePushMode, GitErrorCodes, RefType, Status } from './api/git.constants'; import { AutoFetcher } from './autofetch'; import { GitBranchProtectionProvider, IBranchProtectionProviderRegistry } from './branchProtection'; import { debounce, memoize, sequentialize, throttle } from './decorators'; @@ -3293,6 +3295,56 @@ export class Repository implements Disposable { return this.unpublishedCommits; } + async generateRandomBranchName(): Promise { + const config = workspace.getConfiguration('git', Uri.file(this.root)); + const branchRandomNameEnabled = config.get('branchRandomName.enable', false); + + if (!branchRandomNameEnabled) { + return undefined; + } + + const branchPrefix = config.get('branchPrefix', ''); + const branchWhitespaceChar = config.get('branchWhitespaceChar', '-'); + const branchRandomNameDictionary = config.get('branchRandomName.dictionary', ['adjectives', 'animals']); + + const dictionaries: string[][] = []; + for (const dictionary of branchRandomNameDictionary) { + if (dictionary.toLowerCase() === 'adjectives') { + dictionaries.push(adjectives); + } + if (dictionary.toLowerCase() === 'animals') { + dictionaries.push(animals); + } + if (dictionary.toLowerCase() === 'colors') { + dictionaries.push(colors); + } + if (dictionary.toLowerCase() === 'numbers') { + dictionaries.push(NumberDictionary.generate({ length: 3 })); + } + } + + if (dictionaries.length === 0) { + return undefined; + } + + // 5 attempts to generate a random branch name + for (let index = 0; index < 5; index++) { + const randomName = uniqueNamesGenerator({ + dictionaries, + length: dictionaries.length, + separator: branchWhitespaceChar + }); + + // Check for local ref conflict + const refs = await this.getRefs({ pattern: `refs/heads/${branchPrefix}${randomName}` }); + if (refs.length === 0) { + return `${branchPrefix}${randomName}`; + } + } + + return undefined; + } + dispose(): void { this.disposables = dispose(this.disposables); } diff --git a/extensions/git/src/repositoryCache.ts b/extensions/git/src/repositoryCache.ts index 6aa998b7679bb..8f03d8998c771 100644 --- a/extensions/git/src/repositoryCache.ts +++ b/extensions/git/src/repositoryCache.ts @@ -5,7 +5,7 @@ import { LogOutputChannel, Memento, Uri, workspace } from 'vscode'; import { LRUCache } from './cache'; -import { Remote, RepositoryAccessDetails } from './api/git'; +import type { Remote, RepositoryAccessDetails } from './api/git'; import { isDescendant } from './util'; export interface RepositoryCacheInfo { diff --git a/extensions/git/src/statusbar.ts b/extensions/git/src/statusbar.ts index d5cbe86ee7c88..32fb1f588642b 100644 --- a/extensions/git/src/statusbar.ts +++ b/extensions/git/src/statusbar.ts @@ -6,7 +6,8 @@ import { Disposable, Command, EventEmitter, Event, workspace, Uri, l10n } from 'vscode'; import { Repository } from './repository'; import { anyEvent, dispose, filterEvent } from './util'; -import { Branch, RefType, RemoteSourcePublisher } from './api/git'; +import type { Branch, RemoteSourcePublisher } from './api/git'; +import { RefType } from './api/git.constants'; import { IRemoteSourcePublisherRegistry } from './remotePublisher'; import { CheckoutOperation, CheckoutTrackingOperation, OperationKind } from './operation'; diff --git a/extensions/git/src/test/smoke.test.ts b/extensions/git/src/test/smoke.test.ts index d9a5776824b2e..c2870a2631ee3 100644 --- a/extensions/git/src/test/smoke.test.ts +++ b/extensions/git/src/test/smoke.test.ts @@ -9,7 +9,8 @@ import { workspace, commands, window, Uri, WorkspaceEdit, Range, TextDocument, e import * as cp from 'child_process'; import * as fs from 'fs'; import * as path from 'path'; -import { GitExtension, API, Repository, Status } from '../api/git'; +import type { GitExtension, API, Repository } from '../api/git'; +import { Status } from '../api/git.constants'; import { eventToPromise } from '../util'; suite('git smoke test', function () { diff --git a/extensions/git/src/timelineProvider.ts b/extensions/git/src/timelineProvider.ts index 1ccf04a423d8d..a07eb4bfba78e 100644 --- a/extensions/git/src/timelineProvider.ts +++ b/extensions/git/src/timelineProvider.ts @@ -12,7 +12,7 @@ import { CommandCenter } from './commands'; import { OperationKind, OperationResult } from './operation'; import { truncate } from './util'; import { provideSourceControlHistoryItemAvatar, provideSourceControlHistoryItemHoverCommands, provideSourceControlHistoryItemMessageLinks } from './historyItemDetailsProvider'; -import { AvatarQuery, AvatarQueryCommit } from './api/git'; +import type { AvatarQuery, AvatarQueryCommit } from './api/git'; import { getCommitHover, getHoverCommitHashCommands, processHoverRemoteCommands } from './hover'; export class GitTimelineItem extends TimelineItem { diff --git a/extensions/git/src/uri.ts b/extensions/git/src/uri.ts index 8b04fabe583eb..1d79e67e8e67b 100644 --- a/extensions/git/src/uri.ts +++ b/extensions/git/src/uri.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Uri } from 'vscode'; -import { Change, Status } from './api/git'; +import type { Change } from './api/git'; +import { Status } from './api/git.constants'; export interface GitUriParams { path: string; diff --git a/extensions/git/src/util.ts b/extensions/git/src/util.ts index c6ec6ece45c69..cbf1b56e34e51 100644 --- a/extensions/git/src/util.ts +++ b/extensions/git/src/util.ts @@ -867,10 +867,12 @@ export function getStashDescription(stash: Stash): string | undefined { return descriptionSegments.join(' \u2022 '); } +export const CopilotWorktreeBranchPrefix = 'copilot-worktree-'; + 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-'); + ? path.substring(lastSepIndex + 1).startsWith(CopilotWorktreeBranchPrefix) + : path.startsWith(CopilotWorktreeBranchPrefix); } diff --git a/extensions/github-authentication/.vscodeignore b/extensions/github-authentication/.vscodeignore index 0f1797efe9561..fd8583ab8d125 100644 --- a/extensions/github-authentication/.vscodeignore +++ b/extensions/github-authentication/.vscodeignore @@ -3,7 +3,7 @@ src/** !src/common/config.json out/** build/** -extension.webpack.config.js -extension-browser.webpack.config.js +esbuild.mts +esbuild.browser.mts tsconfig*.json package-lock.json diff --git a/extensions/github-authentication/esbuild.browser.mts b/extensions/github-authentication/esbuild.browser.mts new file mode 100644 index 0000000000000..20745e1d0870e --- /dev/null +++ b/extensions/github-authentication/esbuild.browser.mts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * 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 'node:path'; +import type { Plugin } from 'esbuild'; +import { run } from '../esbuild-extension-common.mts'; + +const srcDir = path.join(import.meta.dirname, 'src'); +const outDir = path.join(import.meta.dirname, 'dist', 'browser'); + +/** + * Plugin that rewrites `./node/*` imports to `./browser/*` for the web build, + * replacing the platform-specific implementations with their browser equivalents. + */ +const platformModulesPlugin: Plugin = { + name: 'platform-modules', + setup(build) { + build.onResolve({ filter: /\/node\// }, args => { + if (args.kind !== 'import-statement' || !args.resolveDir) { + return; + } + const remapped = args.path.replace('/node/', '/browser/'); + return build.resolve(remapped, { resolveDir: args.resolveDir, kind: args.kind }); + }); + }, +}; + +run({ + platform: 'browser', + entryPoints: { + 'extension': path.join(srcDir, 'extension.ts'), + }, + srcDir, + outdir: outDir, + additionalOptions: { + plugins: [platformModulesPlugin], + tsconfig: path.join(import.meta.dirname, 'tsconfig.browser.json'), + }, +}, process.argv); diff --git a/extensions/github-authentication/esbuild.mts b/extensions/github-authentication/esbuild.mts new file mode 100644 index 0000000000000..2b75ca703da06 --- /dev/null +++ b/extensions/github-authentication/esbuild.mts @@ -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 * as path from 'node:path'; +import { run } from '../esbuild-extension-common.mts'; + +const srcDir = path.join(import.meta.dirname, 'src'); +const outDir = path.join(import.meta.dirname, 'dist'); + +run({ + platform: 'node', + entryPoints: { + 'extension': path.join(srcDir, 'extension.ts'), + }, + srcDir, + outdir: outDir, +}, process.argv); diff --git a/extensions/github-authentication/extension-browser.webpack.config.js b/extensions/github-authentication/extension-browser.webpack.config.js deleted file mode 100644 index 70a7fd87cf4a3..0000000000000 --- a/extensions/github-authentication/extension-browser.webpack.config.js +++ /dev/null @@ -1,24 +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: false, - entry: { - extension: './src/extension.ts', - }, - resolve: { - alias: { - 'uuid': path.resolve(import.meta.dirname, 'node_modules/uuid/dist/esm-browser/index.js'), - './node/authServer': path.resolve(import.meta.dirname, 'src/browser/authServer'), - './node/crypto': path.resolve(import.meta.dirname, 'src/browser/crypto'), - './node/fetch': path.resolve(import.meta.dirname, 'src/browser/fetch'), - './node/buffer': path.resolve(import.meta.dirname, 'src/browser/buffer'), - } - } -}); diff --git a/extensions/github-authentication/media/index.html b/extensions/github-authentication/media/index.html index 3292e2a08fc9f..2df45293528fa 100644 --- a/extensions/github-authentication/media/index.html +++ b/extensions/github-authentication/media/index.html @@ -30,9 +30,17 @@

Launching