Skip to content

Commit d4f50fe

Browse files
authored
Merge branch 'main' into genetic-kangaroo
2 parents 75bab58 + ecbcdac commit d4f50fe

File tree

9 files changed

+584
-38
lines changed

9 files changed

+584
-38
lines changed

build/azure-pipeline.npm.yml

Lines changed: 120 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@ parameters:
2020
- preview
2121

2222
- name: publishPackage
23-
displayName: 🚀 Publish to npm
23+
displayName: 🚀 Publish npm
24+
type: boolean
25+
default: false
26+
27+
- name: publishToConsumptionFeed
28+
displayName: 📡 Publish to msft_consumption feed
2429
type: boolean
2530
default: false
2631

@@ -32,7 +37,7 @@ parameters:
3237
versionSpec: '22.21.1'
3338
displayName: Select Node version
3439

35-
- script: npm ci
40+
- script: npm install
3641
workingDirectory: $(Build.SourcesDirectory)/pythonEnvironmentsApi
3742
displayName: Install package dependencies
3843

@@ -56,6 +61,18 @@ variables:
5661
value: next
5762
${{ else }}:
5863
value: latest
64+
- name: AzureArtifactsFeedUrl
65+
value: 'https://pkgs.dev.azure.com/azure-public/vside/_packaging/python-environments/npm/registry/'
66+
# Same URL without the https:// prefix (used in .npmrc auth lines)
67+
- name: AzureArtifactsFeedUrlNoProtocol
68+
value: 'pkgs.dev.azure.com/azure-public/vside/_packaging/python-environments/npm/registry/'
69+
# Managed Identity service connection for Azure Artifacts auth (shared with Pylance)
70+
- name: AzureServiceConnection
71+
value: 'PylanceSecureVsIdePublishWithManagedIdentity'
72+
- name: ConsumptionFeedUrl
73+
value: 'https://pkgs.dev.azure.com/azure-public/vside/_packaging/msft_consumption/npm/registry/'
74+
- name: ConsumptionFeedUrlNoProtocol
75+
value: 'pkgs.dev.azure.com/azure-public/vside/_packaging/msft_consumption/npm/registry/'
5976

6077
extends:
6178
template: azure-pipelines/MicroBuild.1ES.Official.yml@MicroBuildTemplate
@@ -68,7 +85,8 @@ extends:
6885
enabled: true
6986
pool:
7087
name: AzurePipelines-EO
71-
os: windows
88+
image: 1ESPT-Ubuntu22.04
89+
os: linux
7290

7391
customBuildTags:
7492
- ES365AIMigrationTooling
@@ -79,6 +97,11 @@ extends:
7997
jobs:
8098
- job: BuildPackage
8199
displayName: Build npm package
100+
templateContext:
101+
outputs:
102+
- output: pipelineArtifact
103+
targetPath: $(Build.ArtifactStagingDirectory)
104+
artifactName: npm-package
82105
steps:
83106
- ${{ each step in parameters.buildSteps }}:
84107
- ${{ step }}
@@ -90,39 +113,112 @@ extends:
90113
contents: '*.tgz'
91114
targetFolder: $(Build.ArtifactStagingDirectory)
92115

93-
- task: 1ES.PublishBuildArtifacts@1
94-
displayName: Publish build artifact
95-
inputs:
96-
pathToPublish: $(Build.ArtifactStagingDirectory)
97-
artifactName: npm-package
98-
99116
- stage: Publish
100-
displayName: Publish to npm
117+
displayName: Publish to Azure Artifacts
101118
dependsOn: Build
102119
condition: and(succeeded(), eq('${{ parameters.publishPackage }}', 'true'))
103120
jobs:
104121
- job: PublishPackage
105122
displayName: Publish $(PackageName)
106-
steps:
107-
- task: DownloadBuildArtifacts@1
108-
displayName: Download build artifact
109-
inputs:
110-
buildType: current
111-
downloadType: single
123+
templateContext:
124+
type: releaseJob
125+
isProduction: true
126+
inputs:
127+
- input: pipelineArtifact
112128
artifactName: npm-package
113-
downloadPath: $(Build.ArtifactStagingDirectory)
129+
targetPath: $(Pipeline.Workspace)/npm-package
130+
steps:
131+
- checkout: none
114132

115133
- task: NodeTool@0
116134
inputs:
117135
versionSpec: '22.21.1'
118136
displayName: Select Node version
119137

120-
- bash: echo '//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}' > .npmrc
121-
workingDirectory: $(Build.SourcesDirectory)/pythonEnvironmentsApi
122-
displayName: Configure npm auth
138+
# Acquire a short-lived AAD token via Managed Identity (no stored secrets)
139+
# SEE https://eng.ms/docs/cloud-ai-platform/devdiv/one-engineering-system-1es/1es-docs/1es-security-configuration/configuration-guides/pat-burndown-guidance
140+
- task: AzureCLI@2
141+
displayName: Acquire AAD token via Managed Identity
142+
inputs:
143+
azureSubscription: '$(AzureServiceConnection)'
144+
scriptType: 'pscore'
145+
scriptLocation: 'inlineScript'
146+
inlineScript: |
147+
$token = az account get-access-token --query accessToken --resource 499b84ac-1321-427f-aa17-267ca6975798 -o tsv
148+
Write-Host "##vso[task.setvariable variable=AzdoToken;issecret=true]$token"
149+
150+
- powershell: |
151+
@"
152+
registry=$(AzureArtifactsFeedUrl)
153+
always-auth=true
154+
"@ | Out-File -FilePath .npmrc
155+
156+
@"
157+
; begin auth token
158+
//$(AzureArtifactsFeedUrlNoProtocol):username=VssSessionToken
159+
//$(AzureArtifactsFeedUrlNoProtocol):_authToken=$env:AZDO_TOKEN
160+
//$(AzureArtifactsFeedUrlNoProtocol):email=not-used@example.com
161+
; end auth token
162+
"@ | Out-File -FilePath $HOME/.npmrc
163+
env:
164+
AZDO_TOKEN: $(AzdoToken)
165+
displayName: Create .npmrc files
166+
167+
- powershell: |
168+
$tgz = Get-ChildItem "$(Pipeline.Workspace)/npm-package/*.tgz" | Select-Object -First 1
169+
if (-not $tgz) {
170+
Write-Error "No .tgz file found in $(Pipeline.Workspace)/npm-package/"
171+
exit 1
172+
}
173+
Write-Host "Publishing: $($tgz.FullName)"
174+
if ("$(npmTag)" -eq "next") {
175+
npm publish $tgz.FullName --registry $(AzureArtifactsFeedUrl) --tag next --ignore-scripts
176+
} else {
177+
npm publish $tgz.FullName --registry $(AzureArtifactsFeedUrl) --ignore-scripts
178+
}
179+
displayName: npm publish (${{ parameters.quality }})
180+
181+
- stage: PublishConsumption
182+
displayName: Publish package to msft_consumption feed
183+
dependsOn: Publish
184+
condition: and(not(failed()), eq('${{ parameters.publishToConsumptionFeed }}', 'true'))
185+
jobs:
186+
- job: PullToConsumption
187+
displayName: Pull $(PackageName) to msft_consumption
188+
steps:
189+
- checkout: none
190+
191+
- task: NodeTool@0
192+
inputs:
193+
versionSpec: '22.21.1'
194+
displayName: Select Node version
123195

124-
- bash: npm publish $(Build.ArtifactStagingDirectory)/npm-package/*.tgz --tag $(npmTag) --access public
125-
displayName: Publish to npm (${{ parameters.quality }})
126-
workingDirectory: $(Build.SourcesDirectory)/pythonEnvironmentsApi
196+
- task: AzureCLI@2
197+
displayName: Acquire AAD token via Managed Identity
198+
inputs:
199+
azureSubscription: '$(AzureServiceConnection)'
200+
scriptType: 'pscore'
201+
scriptLocation: 'inlineScript'
202+
inlineScript: |
203+
$token = az account get-access-token --query accessToken --resource 499b84ac-1321-427f-aa17-267ca6975798 -o tsv
204+
Write-Host "##vso[task.setvariable variable=AzdoToken;issecret=true]$token"
205+
206+
- powershell: |
207+
@"
208+
registry=$(ConsumptionFeedUrl)
209+
always-auth=true
210+
"@ | Out-File -FilePath .npmrc
211+
212+
@"
213+
; begin auth token
214+
//$(ConsumptionFeedUrlNoProtocol):username=VssSessionToken
215+
//$(ConsumptionFeedUrlNoProtocol):_authToken=$env:AZDO_TOKEN
216+
//$(ConsumptionFeedUrlNoProtocol):email=not-used@example.com
217+
; end auth token
218+
"@ | Out-File -FilePath $HOME/.npmrc
127219
env:
128-
NODE_AUTH_TOKEN: $(NpmAuthToken)
220+
AZDO_TOKEN: $(AzdoToken)
221+
displayName: Create .npmrc files
222+
223+
- script: npm i -g $(PackageName)@$(npmTag) --registry $(ConsumptionFeedUrl)
224+
displayName: Pull to msft_consumption

build/azure-pipeline.stable.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ extends:
112112
project: 'Monaco'
113113
definition: 593
114114
buildVersionToDownload: 'latestFromBranch'
115-
branchName: 'refs/heads/release/2026.2'
115+
branchName: 'refs/heads/release/2026.4'
116116
targetPath: '$(Build.SourcesDirectory)/python-env-tools/bin'
117117
artifactName: 'bin-$(buildTarget)'
118118
itemPattern: |

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "vscode-python-envs",
33
"displayName": "Python Environments",
44
"description": "Provides a unified python environment experience",
5-
"version": "1.21.0",
5+
"version": "1.23.0",
66
"publisher": "ms-python",
77
"preview": true,
88
"engines": {

pythonEnvironmentsApi/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"clean": "node -e \"const fs = require('fs'); fs.rmSync('./out', { recursive: true, force: true });\""
3535
},
3636
"devDependencies": {
37+
"@types/node": "^22.0.0",
3738
"@types/vscode": "^1.99.0",
3839
"typescript": "^5.1.3"
3940
}

src/features/terminal/terminalManager.ts

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
AutoActivationType,
3434
getAutoActivationType,
3535
getEnvironmentForTerminal,
36+
shouldActivateInCurrentTerminal,
3637
waitForShellIntegration,
3738
} from './utils';
3839

@@ -405,8 +406,21 @@ export class TerminalManagerImpl implements TerminalManager {
405406

406407
public async initialize(api: PythonEnvironmentApi): Promise<void> {
407408
const actType = getAutoActivationType();
409+
410+
// When activateEnvInCurrentTerminal is explicitly false,
411+
// skip activation for ALL pre-existing terminals (terminals open before extension load).
412+
// New terminals opened after extension load are still activated via autoActivateOnTerminalOpen.
413+
const skipPreExistingTerminals = !shouldActivateInCurrentTerminal() && terminals().length > 0;
414+
if (skipPreExistingTerminals) {
415+
traceVerbose(
416+
'python.terminal.activateEnvInCurrentTerminal is explicitly disabled, skipping activation for pre-existing terminals',
417+
);
418+
}
419+
408420
if (actType === ACT_TYPE_COMMAND) {
409-
await Promise.all(terminals().map(async (t) => this.activateUsingCommand(api, t)));
421+
if (!skipPreExistingTerminals) {
422+
await Promise.all(terminals().map(async (t) => this.activateUsingCommand(api, t)));
423+
}
410424
} else if (actType === ACT_TYPE_SHELL) {
411425
const shells = new Set(
412426
terminals()
@@ -415,14 +429,16 @@ export class TerminalManagerImpl implements TerminalManager {
415429
);
416430
if (shells.size > 0) {
417431
await this.handleSetupCheck(shells);
418-
await Promise.all(
419-
terminals().map(async (t) => {
420-
// If the shell is not set up, we activate using command fallback.
421-
if (this.shellSetup.get(identifyTerminalShell(t)) === false) {
422-
await this.activateUsingCommand(api, t);
423-
}
424-
}),
425-
);
432+
if (!skipPreExistingTerminals) {
433+
await Promise.all(
434+
terminals().map(async (t) => {
435+
// If the shell is not set up, we activate using command fallback.
436+
if (this.shellSetup.get(identifyTerminalShell(t)) === false) {
437+
await this.activateUsingCommand(api, t);
438+
}
439+
}),
440+
);
441+
}
426442
}
427443
}
428444
}

src/features/terminal/utils.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,52 @@ export async function setAutoActivationType(value: AutoActivationType): Promise<
262262
return await config.update('terminal.autoActivationType', value, true);
263263
}
264264

265+
/**
266+
* Determines whether activation commands should be sent to pre-existing terminals
267+
* (terminals open before extension load).
268+
*
269+
* Checks the legacy `python.terminal.activateEnvInCurrentTerminal` setting using `inspect()`
270+
* to distinguish between the default value and an explicitly user-set value.
271+
*
272+
* Priority: workspaceFolderValue > workspaceValue > globalRemoteValue > globalLocalValue > globalValue
273+
* (matches the precedence used by getShellIntegrationEnabledCache and getAutoActivationType)
274+
*
275+
* - If the user has explicitly set the value to `false` at any scope, returns `false`.
276+
* - Otherwise (default or explicitly `true`), returns `true`.
277+
*
278+
* @returns `false` only when the user has explicitly set the setting to `false`; `true` otherwise.
279+
*/
280+
export function shouldActivateInCurrentTerminal(): boolean {
281+
const pythonConfig = getConfiguration('python');
282+
const inspected = pythonConfig.inspect<boolean>('terminal.activateEnvInCurrentTerminal');
283+
284+
if (!inspected) {
285+
return true;
286+
}
287+
288+
// Only respect `false` when the user has deliberately set it.
289+
// Priority: workspaceFolder > workspace > globalRemote > globalLocal > global
290+
const inspectValue = inspected as Record<string, unknown>;
291+
292+
if (inspected.workspaceFolderValue === false) {
293+
return false;
294+
}
295+
if (inspected.workspaceValue === false) {
296+
return false;
297+
}
298+
if ('globalRemoteValue' in inspected && inspectValue.globalRemoteValue === false) {
299+
return false;
300+
}
301+
if ('globalLocalValue' in inspected && inspectValue.globalLocalValue === false) {
302+
return false;
303+
}
304+
if (inspected.globalValue === false) {
305+
return false;
306+
}
307+
308+
return true;
309+
}
310+
265311
export async function getAllDistinctProjectEnvironments(
266312
api: PythonProjectGetterApi & PythonProjectEnvironmentApi,
267313
): Promise<PythonEnvironment[] | undefined> {

0 commit comments

Comments
 (0)