Skip to content

Commit 25685c3

Browse files
authored
Merge branch 'main' into copilot/fix-env-variable-multi-line
2 parents e4a6a92 + 96a1935 commit 25685c3

19 files changed

+459
-247
lines changed

.github/copilot-instructions.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Copilot Instructions for vscode-python-debugger
2+
3+
## Learnings
4+
5+
- Always use `run.executable` (the actual Python binary path) instead of `activatedRun.executable` for interpreter identification in `getInterpreterDetails`, `getSettingsPythonPath`, and `getExecutableCommand`. `activatedRun.executable` may be a wrapper command (e.g. `pixi run python`) set by environment managers like pixi or conda, which breaks the debugger if used as a replacement for the binary. (1)

.vscode/settings.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,6 @@
2222
},
2323
"git.branchProtection": ["main"],
2424
"git.branchRandomName.enable": true,
25-
}
25+
"python-envs.defaultEnvManager": "ms-python.python:venv",
26+
"python-envs.pythonProjects": []
27+
}

build/azure-devdiv-pipeline.pre-release.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ extends:
108108
buildSteps: ${{ parameters.buildSteps }}
109109
isPreRelease: true
110110
standardizedVersioning: true
111+
customNPMRegistry: $(AZURE_ARTIFACTS_FEED)
111112

112113
- stage: Publish
113114
displayName: Publish Extension
@@ -122,3 +123,4 @@ extends:
122123
ghCreateTag: true
123124
ghCreateRelease: true
124125
ghReleaseAddChangeLog: true
126+
customNPMRegistry: $(AZURE_ARTIFACTS_FEED)

build/azure-devdiv-pipeline.stable.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ extends:
105105
buildPlatforms: ${{ parameters.buildPlatforms }}
106106
buildSteps: ${{ parameters.buildSteps }}
107107
isPreRelease: false
108+
customNPMRegistry: $(AZURE_ARTIFACTS_FEED)
108109

109110
- stage: Publish
110111
displayName: Publish Extension
@@ -116,3 +117,4 @@ extends:
116117
publishExtension: ${{ parameters.publishExtension }}
117118
preRelease: false
118119
teamName: $(TeamName)
120+
customNPMRegistry: $(AZURE_ARTIFACTS_FEED)

build/templates/package.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@ parameters:
1010
type: object
1111
displayName: 'List of platforms to build'
1212

13+
- name: customNPMRegistry
14+
type: string
15+
default: ''
16+
displayName: 'Custom NPM registry (optional)'
17+
18+
- name: nodeVersion
19+
type: string
20+
default: '22.17.0'
21+
displayName: 'Node version to install'
22+
1323
- name: buildSteps
1424
type: stepList
1525
default: []
@@ -73,6 +83,9 @@ jobs:
7383

7484
steps:
7585
- template: setup.yml@self
86+
parameters:
87+
customNPMRegistry: ${{ parameters.customNPMRegistry }}
88+
nodeVersion: ${{ parameters.nodeVersion }}
7689

7790
- ${{ if and(eq(parameters.isPreRelease, true), eq(parameters.standardizedVersioning, true)) }}:
7891
- template: modify-extension-version.yml@self

build/templates/publish-extension.yml

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@ parameters:
2626
type: object
2727
displayName: 'List of platforms to sign and publish'
2828

29+
- name: customNPMRegistry
30+
type: string
31+
default: ''
32+
displayName: 'Custom NPM registry (optional)'
33+
34+
- name: nodeVersion
35+
type: string
36+
default: '22.17.0'
37+
displayName: 'Node version to install'
38+
2939
# Signing parameters
3040
- name: signType
3141
type: string
@@ -151,6 +161,8 @@ jobs:
151161
signType: ${{ parameters.signType }}
152162
verifySignature: ${{ parameters.verifySignature }}
153163
teamName: ${{ parameters.teamName }}
164+
customNPMRegistry: ${{ parameters.customNPMRegistry }}
165+
nodeVersion: ${{ parameters.nodeVersion }}
154166

155167
# Job 2: Publish to marketplace
156168
- ${{ if eq(parameters.publishExtension, true) }}:
@@ -165,17 +177,19 @@ jobs:
165177
type: releaseJob # This makes a job a release job
166178
isProduction: true # Indicates a production release
167179
steps:
168-
- template: setup.yml
169-
parameters:
170-
installNode: true
171-
installPython: false
172-
173180
- task: 1ES.DownloadPipelineArtifact@1
174181
inputs:
175182
artifactName: extension
176183
targetPath: $(Build.ArtifactStagingDirectory)/${{ parameters.publishFolder }}
177184
displayName: 🚛 Download signed extension
178185

186+
- template: setup.yml
187+
parameters:
188+
installNode: true
189+
installPython: false
190+
customNPMRegistry: ${{ parameters.customNPMRegistry }}
191+
nodeVersion: ${{ parameters.nodeVersion }}
192+
179193
# Extract VSIX to read publisher/version for GitHub release tagging.
180194
# Use Agent.TempDirectory to avoid reusing Build.ArtifactStagingDirectory which
181195
# is reserved for final artifact staging.
@@ -199,6 +213,13 @@ jobs:
199213
}
200214
201215
$package = Get-Content $packageJsonPath -Raw | ConvertFrom-Json
216+
$enabledApiProposals = @()
217+
if ($null -ne $package.enabledApiProposals) {
218+
$enabledApiProposals = @($package.enabledApiProposals)
219+
}
220+
$enabledApiProposalsJson = ($enabledApiProposals | ConvertTo-Json -Compress)
221+
Write-Host "##vso[task.setvariable variable=EnabledApiProposalsJson]$enabledApiProposalsJson"
222+
Write-Host ("enabledApiProposals: " + ($(if ($enabledApiProposals.Count -gt 0) { $enabledApiProposals -join ', ' } else { '<none>' })))
202223
Write-Host "##vso[task.setvariable variable=publisher;isOutput=true]$($package.publisher)"
203224
Write-Host "##vso[task.setvariable variable=version;isOutput=true]$($package.version)"
204225
name: SetPublisherAndVersion
@@ -218,6 +239,7 @@ jobs:
218239
azureSubscription: ${{ parameters.azureSubscription }}
219240
buildPlatforms: ${{ parameters.buildPlatforms }}
220241
manifestName: ${{ parameters.manifestName }}
242+
enabledApiProposalsJson: $(EnabledApiProposalsJson)
221243
signatureName: ${{ parameters.signatureName }}
222244
publishFolder: ${{ parameters.publishFolder }}
223245
preRelease: ${{ parameters.preRelease }}

build/templates/publish.yml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ parameters:
4242
- name: preRelease
4343
type: boolean
4444
default: false
45+
- name: enabledApiProposalsJson
46+
type: string
47+
default: ''
4548

4649
steps:
4750
# Node & vsce expected to be prepared by parent pipeline; omit local installation.
@@ -102,12 +105,14 @@ steps:
102105
103106
if ('${{ parameters.preRelease }}' -eq 'True') {
104107
Write-Host 'Publishing as pre-release'
105-
Write-Host "Executing: npx vsce publish --pat *** --packagePath $vsixPath --manifestPath $manifestPath --signaturePath $signaturePath --pre-release"
106-
npx vsce publish --pat $aadToken --packagePath $vsixPath --manifestPath $manifestPath --signaturePath $signaturePath --pre-release
108+
$displayCmd = "Executing: npx @vscode/vsce@latest publish --pat *** --packagePath $vsixPath --manifestPath $manifestPath --signaturePath $signaturePath --no-verify --pre-release"
109+
Write-Host $displayCmd
110+
npx @vscode/vsce@latest publish --pat $aadToken --packagePath $vsixPath --manifestPath $manifestPath --signaturePath $signaturePath --no-verify --pre-release
107111
} else {
108112
Write-Host 'Publishing as stable release'
109-
Write-Host "Executing: npx vsce publish --pat *** --packagePath $vsixPath --manifestPath $manifestPath --signaturePath $signaturePath"
110-
npx vsce publish --pat $aadToken --packagePath $vsixPath --manifestPath $manifestPath --signaturePath $signaturePath
113+
$displayCmd = "Executing: npx @vscode/vsce@latest publish --pat *** --packagePath $vsixPath --manifestPath $manifestPath --signaturePath $signaturePath --no-verify"
114+
Write-Host $displayCmd
115+
npx @vscode/vsce@latest publish --pat $aadToken --packagePath $vsixPath --manifestPath $manifestPath --signaturePath $signaturePath --no-verify
111116
}
112117
113118
if ($LASTEXITCODE -ne 0) {

build/templates/setup.yml

Lines changed: 128 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,145 @@
11
# DevDiv pipeline setup steps
2+
#
3+
# Notes on custom registry support:
4+
# - Custom npm registry setup is implemented inline below (no external scripts).
5+
# - The template creates a temp .npmrc, sets NPM_CONFIG_USERCONFIG/REGISTRY, and
6+
# rewrites lockfiles via a transient JS helper emitted into Agent.TempDirectory.
27
parameters:
38
- name: installNode
49
type: boolean
510
default: true
611
- name: installPython
712
type: boolean
813
default: true
14+
- name: customNPMRegistry
15+
type: string
16+
default: ''
17+
- name: nodeVersion
18+
type: string
19+
default: '22.17.0'
20+
- name: pythonVersion
21+
type: string
22+
default: '3.9'
23+
- name: npmrcPath
24+
type: string
25+
default: '$(Agent.TempDirectory)/.npmrc'
926

1027
steps:
1128
- ${{ if eq(parameters.installNode, true) }}:
12-
- pwsh: |
13-
if (-not (Test-Path '.npmrc')) {
14-
Write-Host 'No .npmrc found, creating one with public npm registry'
15-
@"
16-
# Force public npm registry to avoid CI auth (E401) when no token is provided
17-
registry=https://registry.npmjs.org/
18-
# Do not require auth for public installs
19-
always-auth=false
20-
"@ | Out-File -FilePath '.npmrc' -Encoding utf8
21-
} else {
22-
Write-Host '.npmrc already exists'
23-
}
24-
displayName: Ensure .npmrc exists
25-
26-
- task: npmAuthenticate@0
29+
- task: NodeTool@0
2730
inputs:
28-
workingFile: .npmrc
31+
versionSpec: ${{ parameters.nodeVersion }}
32+
checkLatest: true
33+
displayName: 🛠 Install Node ${{ parameters.nodeVersion }}
34+
35+
- ${{ if ne(parameters.customNPMRegistry, '') }}:
36+
# When using a private/custom registry, configure npm to read auth/config from a temp user config
37+
# instead of relying on a checked-in project .npmrc.
38+
- pwsh: |
39+
$path = "${{ parameters.npmrcPath }}"
40+
41+
if (Test-Path -LiteralPath $path -PathType Container) {
42+
throw "npmrcPath points to a directory (expected a file): $path"
43+
}
44+
45+
$parent = Split-Path -Parent $path
46+
if ($parent -and -not (Test-Path -LiteralPath $parent)) {
47+
New-Item -ItemType Directory -Path $parent -Force | Out-Null
48+
}
49+
50+
if (-not (Test-Path -LiteralPath $path -PathType Leaf)) {
51+
New-Item -ItemType File -Path $path -Force | Out-Null
52+
}
53+
54+
Write-Host "##vso[task.setvariable variable=NPM_CONFIG_USERCONFIG]$path"
55+
Write-Host "##vso[task.setvariable variable=NPM_CONFIG_REGISTRY]${{ parameters.customNPMRegistry }}"
56+
displayName: 📦 Setup NPM User Config
57+
58+
# Configure npm/yarn to use the custom registry and ensure auth headers are sent.
59+
- pwsh: |
60+
$path = "${{ parameters.npmrcPath }}"
61+
$registry = "${{ parameters.customNPMRegistry }}"
62+
63+
$env:NPM_CONFIG_USERCONFIG = $path
64+
$env:NPM_CONFIG_REGISTRY = $registry
65+
66+
npm config set registry "$registry"
67+
68+
# npm >v7 deprecated the `always-auth` config option, refs npm/cli@72a7eeb
69+
# following is a workaround for yarn-like clients to send authorization header for GET
70+
# requests to the registry.
71+
"always-auth=true" | Out-File -FilePath $path -Append -Encoding utf8
72+
73+
$yarn = Get-Command yarn -ErrorAction SilentlyContinue
74+
if ($null -ne $yarn) {
75+
yarn config set registry "$registry"
76+
} else {
77+
Write-Host "yarn not found; skipping yarn registry configuration"
78+
}
79+
displayName: 📦 Setup NPM & Yarn
80+
81+
# Populate the temp .npmrc with auth for the configured registry.
82+
- task: npmAuthenticate@0
83+
inputs:
84+
workingFile: ${{ parameters.npmrcPath }}
85+
displayName: 📦 Setup NPM Authentication
86+
87+
# Some lockfiles contain hardcoded references to public registries. Rewrite them so installs
88+
# and `npx` resolve from the custom registry consistently.
89+
- pwsh: |
90+
$registry = "${{ parameters.customNPMRegistry }}"
91+
$env:NPM_CONFIG_REGISTRY = $registry
92+
$scriptPath = Join-Path "$(Agent.TempDirectory)" 'setup-npm-registry.js'
93+
$lines = @(
94+
"const fs = require('fs').promises;",
95+
"const path = require('path');",
96+
"",
97+
"async function* getLockFiles(dir) {",
98+
" const files = await fs.readdir(dir);",
99+
"",
100+
" for (const file of files) {",
101+
" const fullPath = path.join(dir, file);",
102+
" const stat = await fs.stat(fullPath);",
103+
"",
104+
" if (stat.isDirectory()) {",
105+
" if (file === 'node_modules' || file === '.git') {",
106+
" continue;",
107+
" }",
108+
" yield* getLockFiles(fullPath);",
109+
" } else if (file === 'yarn.lock' || file === 'package-lock.json') {",
110+
" yield fullPath;",
111+
" }",
112+
" }",
113+
"}",
114+
"",
115+
"async function rewrite(file, registry) {",
116+
" let contents = await fs.readFile(file, 'utf8');",
117+
" const re = new RegExp('https://registry\\.[^.]+\\.(com|org)/', 'g');",
118+
" contents = contents.replace(re, registry);",
119+
" await fs.writeFile(file, contents);",
120+
"}",
121+
"",
122+
"async function main() {",
123+
" const root = process.cwd();",
124+
" const registry = process.env.NPM_CONFIG_REGISTRY;",
125+
" if (!registry) { throw new Error('NPM_CONFIG_REGISTRY is not set'); }",
126+
"",
127+
" for await (const file of getLockFiles(root)) {",
128+
" await rewrite(file, registry);",
129+
" console.log('Updated node registry:', file);",
130+
" }",
131+
"}",
132+
"",
133+
"main();"
134+
)
135+
136+
Set-Content -LiteralPath $scriptPath -Value ($lines -join "`n") -Encoding utf8
137+
node $scriptPath
138+
displayName: 📦 Setup NPM Registry
29139
30140
- script: npm config get registry
31141
displayName: Verify NPM Registry
32142

33-
- task: NodeTool@0
34-
inputs:
35-
versionSpec: '22.17.0'
36-
checkLatest: true
37-
displayName: Select Node 22 LTS
38-
39143
- ${{ if eq(parameters.installPython, true) }}:
40144
- task: PipAuthenticate@1
41145
displayName: 'Pip Authenticate'
@@ -44,7 +148,7 @@ steps:
44148

45149
- task: UsePythonVersion@0
46150
inputs:
47-
versionSpec: '3.9' # note Install Python dependencies step below relies on Python 3.9
151+
versionSpec: ${{ parameters.pythonVersion }}
48152
addToPath: true
49153
architecture: 'x64'
50-
displayName: Select Python version
154+
displayName: Select Python ${{ parameters.pythonVersion }}

build/templates/sign.yml

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,15 @@ parameters:
1313
- name: buildPlatforms
1414
type: object
1515
displayName: 'List of platforms to sign'
16+
- name: customNPMRegistry
17+
type: string
18+
default: ''
1619
- name: workingDirectory
1720
type: string
1821
default: '$(Build.StagingDirectory)'
22+
- name: nodeVersion
23+
type: string
24+
default: '22.17.0'
1925
- name: signType
2026
type: string
2127
default: real
@@ -73,15 +79,30 @@ steps:
7379
restoreDirectory: '$(Build.SourcesDirectory)/packages'
7480
nugetConfigPath: '$(Build.SourcesDirectory)/build/NuGet.config'
7581

76-
# Setup Node.js and npm authentication
7782
- template: setup.yml@self
83+
parameters:
84+
customNPMRegistry: ${{ parameters.customNPMRegistry }}
85+
nodeVersion: ${{ parameters.nodeVersion }}
7886

7987
- task: Npm@1
8088
displayName: 'npm ci (install vsce)'
8189
inputs:
8290
command: ci
8391
workingDir: '$(Build.SourcesDirectory)'
8492

93+
# Workaround for intermittent MicroBuild plugin install collisions
94+
# (e.g. "An item with the specified name ...\\MicroBuild\\Plugins\\Az.Accounts already exists")
95+
- powershell: |
96+
$pluginsRoot = "$(Agent.TempDirectory)/MicroBuild/Plugins"
97+
$azAccountsDir = Join-Path $pluginsRoot "Az.Accounts"
98+
99+
Write-Host "MicroBuild plugins root: $pluginsRoot"
100+
if (Test-Path $azAccountsDir) {
101+
Write-Host "Removing existing MicroBuild plugin directory: $azAccountsDir"
102+
Remove-Item -LiteralPath $azAccountsDir -Recurse -Force -ErrorAction SilentlyContinue
103+
}
104+
displayName: '🧹 Clean MicroBuild Az.Accounts plugin cache'
105+
85106
# ✅ Enable MicroBuildSigningPlugin for PME enforcement (once for all platforms)
86107
- task: MicroBuildSigningPlugin@4
87108
displayName: Enable MicroBuild Signing

0 commit comments

Comments
 (0)