Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
bf63569
Pin Corepack explicitly for the VS Code extension build
adamint May 28, 2026
aae3dd5
Address code review for Corepack pin
adamint May 28, 2026
bfe70a5
Drop unnecessary --registry override on corepack install
adamint May 28, 2026
7b49877
Fix Corepack registry handling
adamint May 28, 2026
ff7454b
Avoid Corepack registry override for Azure Artifacts
adamint May 28, 2026
f544e0b
Force Corepack shim install in CI
adamint May 28, 2026
0959adb
Prepend npm Corepack shim path in CI
adamint May 28, 2026
f8ea0a5
Seed Corepack Yarn cache via npm pack
adamint May 29, 2026
98078ee
Invoke npm CLI directly for Corepack Yarn seed
adamint May 29, 2026
1a26f40
Handle duplicate equivalent ATS capabilities
adamint May 29, 2026
d1c3252
Merge remote-tracking branch 'microsoft/main' into dev/adamint/extens…
adamint May 29, 2026
328aa22
Align duplicate ATS compatibility fix
adamint May 29, 2026
bc819bc
Remove duplicate ATS compatibility fix
adamint May 29, 2026
21d7304
Merge remote-tracking branch 'microsoft/main' into dev/adamint/extens…
adamint May 29, 2026
c15ab5a
Export NPM_REGISTRY in build.sh and document Corepack cache-path coup…
adamint May 29, 2026
994f7b2
Address PR review feedback for Corepack bootstrap
adamint May 29, 2026
fdcb5f5
Set COREPACK_HOME via $GITHUB_ENV instead of job env
adamint May 29, 2026
1c9eeee
Merge remote-tracking branch 'microsoft/main' into dev/adamint/extens…
adamint May 30, 2026
93ac6f2
Address PR #17630 round-3 review feedback
adamint May 30, 2026
0e2136e
Address PR #17630 Mitch review feedback
adamint May 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 112 additions & 5 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,9 @@ jobs:
extension_tests_win:
name: Run VS Code extension tests (Windows)
runs-on: windows-latest
env:
NPM_REGISTRY: https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/
COREPACK_ENABLE_DOWNLOAD_PROMPT: 0
defaults:
run:
working-directory: ./extension
Expand All @@ -322,23 +325,126 @@ jobs:
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: '20.x'
- name: Install Corepack
run: |
# Scope Corepack's cache to this job so prepareCorepackYarn.mjs cannot
# collide with any other build sharing the runner's user profile.
# Cannot be set at job-level env: the `runner` context is unavailable
# in job env evaluation, so we forward it through $GITHUB_ENV instead.
$CorepackHome = Join-Path $env:RUNNER_TEMP 'corepack'
$env:COREPACK_HOME = $CorepackHome
"COREPACK_HOME=$CorepackHome" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append

$CorepackVersion = (Get-Content -Raw -Path 'scripts/corepack-version.txt').Trim()
# The hosted Windows image already has a yarn shim in npm's global
# prefix. The npm Corepack package owns that shim too, so force only
# in CI where the tool install is isolated to this ephemeral job.
npm install --global --force --registry "$env:NPM_REGISTRY" "corepack@$CorepackVersion"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

$npmGlobalBin = (npm prefix --global).Trim()
$env:PATH = "$npmGlobalBin$([IO.Path]::PathSeparator)$env:PATH"
$npmGlobalBin | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append

$installed = (corepack --version).Trim()
if ($installed -ne $CorepackVersion) {
Write-Error "corepack version mismatch: expected $CorepackVersion, got '$installed'. The bundled Corepack on PATH may be taking precedence over the npm-global install."
exit 1
}

corepack enable
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

# Seed Corepack's cache from the internal dnceng npm feed. Corepack's
# built-in `corepack prepare --activate` would download Yarn 1.x from
# registry.yarnpkg.com (hardcoded in Corepack 0.34's config.json and
# not redirectable via COREPACK_NPM_REGISTRY), bypassing the dnceng
# mirror this workflow is supposed to validate. The shared
# prepareCorepackYarn.mjs script does the equivalent via `npm pack`
# against $NPM_REGISTRY, then drops the same on-disk layout Corepack
# would have written.
node ./scripts/prepareCorepackYarn.mjs
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

corepack yarn --version
- name: Validate lockfile registries
run: node -e "const fs = require('fs'); const lock = fs.readFileSync('yarn.lock', 'utf8'); if (/registry\\.(?:npmjs\\.org|yarnpkg\\.com)/.test(lock)) { throw new Error('extension/yarn.lock contains public npm registry URLs. Regenerate it using the internal dotnet-public-npm feed before restoring.'); }"
# Allowlist scoped to lines starting with "resolved" (the only lines in
# yarn.lock that carry a tarball URL — `npm:` aliases don't).
# CONTRIBUTING.MD asks contributors to regenerate yarn.lock through the
# internal dotnet-public-npm feed; an allowlist catches drift to any
# other public mirror (npmmirror.com, jsr.io, github.com tarballs, ...)
# rather than only npmjs.org and yarnpkg.com.
run: |
node -e "const fs = require('fs'); const allow = 'pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm'; const bad = fs.readFileSync('yarn.lock', 'utf8').split(/\r?\n/).filter(l => /^\s*resolved\s+\x22/.test(l)).filter(l => !l.includes(allow)); if (bad.length) { throw new Error('extension/yarn.lock contains resolved entries outside the internal dotnet-public-npm feed. Regenerate it through the internal feed before restoring. First offender -> ' + bad[0]); }"
- name: Install dependencies
run: yarn install --frozen-lockfile --non-interactive
run: corepack yarn install --frozen-lockfile --non-interactive
- name: Run tests
run: yarn test
run: corepack yarn test
- name: Override extension version for PR builds
if: ${{ inputs.extensionVersionOverride != '' }}
run: yarn version --new-version "${{ inputs.extensionVersionOverride }}" --no-git-tag-version
run: corepack yarn version --new-version "${{ inputs.extensionVersionOverride }}" --no-git-tag-version
- name: Package VSIX
run: yarn run vsce package --pre-release -o out/aspire-extension.vsix
run: corepack yarn run vsce package --pre-release -o out/aspire-extension.vsix
- name: Upload VSIX
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: aspire-extension
path: extension/out/aspire-extension.vsix

extension_bootstrap_linux:
name: Validate VS Code extension bootstrap (Linux)
# Without this, the non-Windows code paths in
# extension/scripts/prepareCorepackYarn.mjs (npm invocation that does not go
# through node.exe, POSIX tar) are only exercised on contributor machines
# and never on a fresh CI image. extension_tests_win covers the Windows
# paths; this job covers Linux/macOS. macOS is omitted because the only
# platform-specific branch beyond Linux is the cache path, which is
# explicitly overridden via COREPACK_HOME in the build entrypoints.
runs-on: ubuntu-latest
env:
NPM_REGISTRY: https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/
COREPACK_ENABLE_DOWNLOAD_PROMPT: 0
defaults:
run:
working-directory: ./extension
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node.js environment
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: '20.x'
- name: Install Corepack
run: |
set -euo pipefail
# Scope Corepack's cache to this job. Set via $GITHUB_ENV because the
# `runner` context is not available in job-level env evaluation.
export COREPACK_HOME="$RUNNER_TEMP/corepack"
echo "COREPACK_HOME=$COREPACK_HOME" >> "$GITHUB_ENV"

CorepackVersion="$(tr -d '[:space:]' < scripts/corepack-version.txt)"
npm install --global --force --registry "$NPM_REGISTRY" "corepack@${CorepackVersion}"

installed="$(corepack --version 2>/dev/null || true)"
if [ "$installed" != "$CorepackVersion" ]; then
echo "corepack version mismatch: expected $CorepackVersion, got '$installed'. The bundled Corepack on PATH may be taking precedence over the npm-global install."
exit 1
fi

corepack enable
Comment thread
adamint marked this conversation as resolved.
- name: Seed Corepack Yarn cache via prepareCorepackYarn.mjs
# Exercises the script's non-Windows branches against a clean cache
# directory. Failing here means a contributor on Linux/macOS following
# CONTRIBUTING.MD would also be broken.
run: node ./scripts/prepareCorepackYarn.mjs
- name: Validate lockfile registries
# Mirror of the same allowlist guard in extension_tests_win so this
# cross-platform bootstrap also catches drift to a public-registry URL.
run: |
node -e "const fs = require('fs'); const allow = 'pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm'; const bad = fs.readFileSync('yarn.lock', 'utf8').split(/\r?\n/).filter(l => /^\s*resolved\s+\x22/.test(l)).filter(l => !l.includes(allow)); if (bad.length) { throw new Error('extension/yarn.lock contains resolved entries outside the internal dotnet-public-npm feed. Regenerate it through the internal feed before restoring. First offender -> ' + bad[0]); }"
- name: Validate seeded cache via yarn install
run: corepack yarn install --frozen-lockfile --non-interactive
Comment thread
adamint marked this conversation as resolved.

typescript_sdk_tests:
name: TypeScript SDK Unit Tests
uses: ./.github/workflows/typescript-sdk-tests.yml
Expand All @@ -364,6 +470,7 @@ jobs:
prepare_homebrew_installer_artifacts,
build_cli_e2e_image,
extension_tests_win,
extension_bootstrap_linux,
cli_starter_validation_windows,
typescript_sdk_tests,
typescript_api_compat,
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ extension/dist/
extension/.localization/
extension/out/
extension/node_modules/
extension/.corepack-cache/
**/.vscode-test/
extension/.version

Expand Down
14 changes: 9 additions & 5 deletions eng/pipelines/azure-pipelines-codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,18 @@ jobs:
inputs:
version: '20.x'

- task: npmAuthenticate@0
displayName: NPM authenticate
inputs:
workingFile: $(Build.SourcesDirectory)\.npmrc

- task: PowerShell@2
displayName: Install yarn
displayName: Set .npmrc environment
inputs:
targetType: 'inline'
script: |
npm install -g yarn@1.22.22
yarn --version
workingDirectory: '$(Build.SourcesDirectory)'
script: Write-Host "##vso[task.setvariable variable=NPM_CONFIG_USERCONFIG]$(Build.SourcesDirectory)\.npmrc"

- template: /eng/pipelines/templates/install-corepack.yml

- task: PowerShell@2
displayName: Install vsce
Expand Down
11 changes: 3 additions & 8 deletions eng/pipelines/azure-pipelines-unofficial.yml
Original file line number Diff line number Diff line change
Expand Up @@ -209,14 +209,9 @@ extends:
targetType: 'inline'
script: Write-Host "##vso[task.setvariable variable=NPM_CONFIG_USERCONFIG]$(Build.SourcesDirectory)\.npmrc"

- task: PowerShell@2
displayName: 🟣Install yarn
inputs:
targetType: 'inline'
script: |
npm install -g yarn@1.22.22
yarn --version
workingDirectory: '$(Build.SourcesDirectory)'
- template: /eng/pipelines/templates/install-corepack.yml
parameters:
displayPrefix: '🟣'

- task: PowerShell@2
displayName: 🟣Install vsce
Expand Down
11 changes: 3 additions & 8 deletions eng/pipelines/azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -298,14 +298,9 @@ extends:
targetType: 'inline'
script: Write-Host "##vso[task.setvariable variable=NPM_CONFIG_USERCONFIG]$(Build.SourcesDirectory)\.npmrc"

- task: PowerShell@2
displayName: 🟣Install yarn
inputs:
targetType: 'inline'
script: |
npm install -g yarn@1.22.22
yarn --version
workingDirectory: '$(Build.SourcesDirectory)'
- template: /eng/pipelines/templates/install-corepack.yml
parameters:
displayPrefix: '🟣'

- task: PowerShell@2
displayName: 🟣Install vsce
Expand Down
9 changes: 9 additions & 0 deletions eng/pipelines/common-variables.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ variables:
- name: _InternalBuildArgs
value: ''

# npm global installs and the Corepack Yarn cache seeder don't use the repo
# .npmrc, so pass this registry explicitly.
- name: NPM_REGISTRY
value: https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/
# Disable the interactive "Do you want to download yarn@x.y.z?" prompt so
# pipeline steps don't hang waiting for stdin.
- name: COREPACK_ENABLE_DOWNLOAD_PROMPT
value: '0'

- ${{ if notin(variables['Build.Reason'], 'PullRequest') }}:
- name: _RunAsPublic
value: False
Expand Down
55 changes: 55 additions & 0 deletions eng/pipelines/templates/install-corepack.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
parameters:
# Prefix prepended to step displayNames (e.g. the 🟣 emoji used by the
# internal-1ES jobs in azure-pipelines.yml / azure-pipelines-unofficial.yml).
# Public/CodeQL pipelines omit it. Kept as a parameter so the displayName
# convention of each calling pipeline is preserved.
- name: displayPrefix
type: string
default: ''

steps:
- task: PowerShell@2
displayName: ${{ parameters.displayPrefix }}Install Corepack
inputs:
targetType: 'inline'
script: |
# Pinned Corepack shim version, sourced from
# extension/scripts/corepack-version.txt so this script,
# extension/build.sh, extension/build.ps1, the GitHub Actions
# workflow, and every AzDO pipeline that references this template
# stay in sync. The Yarn release itself is pinned in
# extension/package.json via the `packageManager` field.
$CorepackVersion = (Get-Content -Raw -Path '$(Build.SourcesDirectory)/extension/scripts/corepack-version.txt').Trim()
$corepackHome = Join-Path '$(Agent.TempDirectory)' 'corepack'
$env:COREPACK_HOME = $corepackHome
Write-Host "##vso[task.setvariable variable=COREPACK_HOME]$corepackHome"

# Hosted Windows images can already have yarn in npm's global prefix.
# The npm Corepack package owns that shim too, so force only in CI
# where the tool install is job-scoped.
npm install -g --force --registry "$env:NPM_REGISTRY" "corepack@$CorepackVersion"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

$npmGlobalBin = (npm prefix --global).Trim()
$env:PATH = "$npmGlobalBin$([IO.Path]::PathSeparator)$env:PATH"
Write-Host "##vso[task.prependpath]$npmGlobalBin"

# Verify the version actually on PATH matches our pin. On Windows the
# Node.js installer registers a `corepack.cmd` under
# %ProgramFiles%\nodejs which may shadow the npm-global shim under
# %APPDATA%\npm, so a successful `npm install -g corepack@<version>`
# does NOT guarantee that subsequent `corepack` calls resolve to it.
$installed = (corepack --version).Trim()
if ($installed -ne $CorepackVersion) {
Write-Error "corepack version mismatch: expected $CorepackVersion, got '$installed'. The bundled Corepack on PATH may be taking precedence over the npm-global install."
exit 1
}

corepack enable
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

node ./scripts/prepareCorepackYarn.mjs
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

corepack yarn --version
workingDirectory: '$(Build.SourcesDirectory)\extension'
9 changes: 9 additions & 0 deletions eng/pipelines/templates/public-pipeline-template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ variables:
- name: _InternalBuildArgs
value: ''

# npm global installs and the Corepack Yarn cache seeder don't use the repo
# .npmrc, so pass this registry explicitly.
- name: NPM_REGISTRY
value: https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/
# Disable the interactive "Do you want to download yarn@x.y.z?" prompt so
# pipeline steps don't hang waiting for stdin.
- name: COREPACK_ENABLE_DOWNLOAD_PROMPT
value: '0'

# Set test variants based on parameter or build reason
- ${{ if ne(parameters.testVariants, '') }}:
- name: testVariants
Expand Down
40 changes: 35 additions & 5 deletions extension/CONTRIBUTING.MD
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

## Install Prerequisites

- Node.js (LTS version)
- Yarn accessible in the PATH
- Node.js (LTS version) — `npm` must be on the PATH (it ships with Node.js). The
build scripts (`build.sh` / `build.ps1`) install a pinned [Corepack](https://github.com/nodejs/corepack)
via `npm install -g corepack@<version>` from the configured registry and seed
Corepack's cache with the Yarn release pinned by the `packageManager` field in
`extension/package.json`. You do **not** need to install Yarn yourself.
- Visual Studio Code (latest) or Visual Studio Code Insiders
- [Aspire CLI](https://aspire.dev/get-started/install-cli/) must be installed and available in the PATH

Expand All @@ -26,12 +29,39 @@ You can use the `Aspire: Extension settings` command to open VS Code settings di

## Updating dependency overrides

The extension is built with **yarn**, so `package.json` uses `resolutions` for transitive dependency pins and `yarn.lock` is the authoritative lockfile.
The extension is built with **yarn**, pinned to the version recorded in `packageManager` of `package.json`. `package.json` uses `resolutions` for transitive dependency pins and `yarn.lock` is the authoritative lockfile.

When pinning a transitive dependency (e.g. to address a security advisory), add the pin to `resolutions` and regenerate `yarn.lock` in the same change:

```bash
yarn install
corepack yarn install
```

The build rejects public npm registry URLs in `yarn.lock`; ensure regenerated entries resolve through the internal `dotnet-public-npm` feed.
The build rejects public npm registry URLs in `yarn.lock`; ensure regenerated entries resolve through the internal `dotnet-public-npm` feed.

## Updating the Yarn version

Edit the `"packageManager": "yarn@x.y.z"` field in `extension/package.json`. The next `build.sh` / `build.ps1` run seeds Corepack's cache with that version before calling `corepack yarn …`. No further changes are required in `build.sh`, `build.ps1`, or `extension/Extension.proj`.

> **Heads up about the internal npm mirror.** The repo's `.npmrc` and the build scripts' `NPM_REGISTRY` default route npm package downloads through the dnceng `dotnet-public-npm` Azure Artifacts feed, which is a pull-through cache of npmjs.org. The first time anyone asks the feed for a version that has never been requested, the feed has to fetch it from npmjs.org — and anonymous pull-through fetches fail with HTTP 401. Subsequent anonymous reads work fine. If you bump the pinned Corepack or Yarn version, pre-seed the feed with credentials using `npm install --global --registry https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/ corepack@<version>` or `npm pack --registry https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/ yarn@<version>`. Do not point `COREPACK_NPM_REGISTRY` at this Azure Artifacts feed for Yarn: Corepack requests the `/<package>/<version>` npm metadata route, and Azure Artifacts returns 404 for that route even when the package and tarball exist.

## Troubleshooting

### `EACCES` from `npm install --global` on Linux/macOS

The build scripts run `npm install --global corepack@<version>` to pin the Corepack version. On systems where Node.js is installed from a package manager (apt, yum, the official `.pkg`), the npm global prefix is typically `/usr/lib/node_modules` or `/usr/local/lib/node_modules`, which is root-owned. The install will fail with `EACCES`. The cleanest fix is to use a Node version manager that puts the npm prefix in your home directory:

- [`nvm`](https://github.com/nvm-sh/nvm) — installs Node and configures the npm prefix automatically.
- [`fnm`](https://github.com/Schniz/fnm), [`asdf`](https://asdf-vm.com/), [`volta`](https://volta.sh/) — same idea, different tradeoffs.

Alternatively, point npm at a user-writable prefix without changing your Node install:

```bash
mkdir -p ~/.npm-global
npm config set prefix ~/.npm-global
export PATH="$HOME/.npm-global/bin:$PATH" # add to ~/.bashrc or ~/.zshrc
```

### `corepack version mismatch` from `build.sh` / `build.ps1`

This means the `corepack` resolved from `PATH` is not the one we just installed via `npm install -g`. Most often the system Node install (`/usr/bin/corepack`, `%ProgramFiles%\nodejs\corepack.cmd`) is sitting in front of the npm global bin directory. On Windows, ensure `%APPDATA%\npm` comes before `%ProgramFiles%\nodejs` on `PATH`. On Linux/macOS, follow the `EACCES` remediation above and the npm prefix will be on `PATH` ahead of the system Node directory.
Loading
Loading