Skip to content

Commit 83a3269

Browse files
committed
Remove npmrc artifact; add custom registry setup
Stop publishing/downloading .npmrc as a pipeline artifact. Instead, configure npm to use a temp user config (NPM_CONFIG_USERCONFIG=$(Agent.TempDirectory)/.npmrc) and support customNPMRegistry in the shared setup template, including auth (npmAuthenticate@0) and lockfile registry rewrites. Thread $(AZURE_ARTIFACTS_FEED) through DevDiv pipeline templates to enable the custom registry flow.
1 parent a923b6c commit 83a3269

6 files changed

Lines changed: 149 additions & 83 deletions

File tree

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 & 21 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: []
@@ -71,13 +81,11 @@ jobs:
7181
targetPath: '$(Build.ArtifactStagingDirectory)/drop'
7282
artifactName: vsix-${{ coalesce(platform.vsceTarget, 'universal') }}
7383

74-
- output: pipelineArtifact
75-
displayName: Publish .npmrc
76-
targetPath: '$(Build.SourcesDirectory)/.npmrc'
77-
artifactName: npmrc
78-
7984
steps:
8085
- template: setup.yml@self
86+
parameters:
87+
customNPMRegistry: ${{ parameters.customNPMRegistry }}
88+
nodeVersion: ${{ parameters.nodeVersion }}
8189

8290
- ${{ if and(eq(parameters.isPreRelease, true), eq(parameters.standardizedVersioning, true)) }}:
8391
- template: modify-extension-version.yml@self
@@ -136,19 +144,3 @@ jobs:
136144
contents: '*.vsix'
137145
targetFolder: $(Build.ArtifactStagingDirectory)/drop
138146
displayName: 📦 Copy VSIX to staging
139-
140-
- ${{ if eq(coalesce(platform.vsceTarget, 'universal'), 'universal') }}:
141-
- pwsh: |
142-
$source = "${{ parameters.workingDirectory }}/.npmrc"
143-
$destDir = "$(Build.ArtifactStagingDirectory)/npmrc"
144-
$dest = Join-Path $destDir '.npmrc'
145-
146-
New-Item -ItemType Directory -Path $destDir -Force | Out-Null
147-
148-
if (-not (Test-Path $source)) {
149-
throw "Expected .npmrc at: $source"
150-
}
151-
152-
Copy-Item -LiteralPath $source -Destination $dest -Force
153-
Write-Host "Copied $source -> $dest"
154-
displayName: 📦 Stage .npmrc for artifact

build/templates/publish-extension.yml

Lines changed: 14 additions & 7 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) }}:
@@ -171,17 +183,12 @@ jobs:
171183
targetPath: $(Build.ArtifactStagingDirectory)/${{ parameters.publishFolder }}
172184
displayName: 🚛 Download signed extension
173185

174-
- task: 1ES.DownloadPipelineArtifact@1
175-
inputs:
176-
artifactName: npmrc
177-
targetPath: $(Pipeline.Workspace)/npmrc
178-
displayName: 🚛 Download .npmrc artifact
179-
180186
- template: setup.yml
181187
parameters:
182188
installNode: true
183189
installPython: false
184-
npmrcPath: $(Pipeline.Workspace)/npmrc/.npmrc
190+
customNPMRegistry: ${{ parameters.customNPMRegistry }}
191+
nodeVersion: ${{ parameters.nodeVersion }}
185192

186193
# Extract VSIX to read publisher/version for GitHub release tagging.
187194
# Use Agent.TempDirectory to avoid reusing Build.ArtifactStagingDirectory which

build/templates/setup.yml

Lines changed: 110 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -6,70 +6,133 @@ parameters:
66
- name: installPython
77
type: boolean
88
default: true
9+
- name: customNPMRegistry
10+
type: string
11+
default: ''
12+
- name: nodeVersion
13+
type: string
14+
default: '22.17.0'
15+
- name: pythonVersion
16+
type: string
17+
default: '3.9'
918
- name: npmrcPath
1019
type: string
11-
default: '$(Build.SourcesDirectory)/.npmrc'
20+
default: '$(Agent.TempDirectory)/.npmrc'
1221

1322
steps:
1423
- ${{ if eq(parameters.installNode, true) }}:
15-
- pwsh: |
16-
$npmrcPath = "${{ parameters.npmrcPath }}"
24+
- task: NodeTool@0
25+
inputs:
26+
versionSpec: ${{ parameters.nodeVersion }}
27+
checkLatest: true
28+
displayName: 🛠 Install Node ${{ parameters.nodeVersion }}
1729

18-
if (Test-Path -LiteralPath $npmrcPath -PathType Container) {
19-
throw "npmrcPath points to a directory (expected a file): $npmrcPath"
20-
}
30+
- ${{ if ne(parameters.customNPMRegistry, '') }}:
31+
# When using a private/custom registry, configure npm to read auth/config from a temp user config
32+
# instead of relying on a checked-in project .npmrc.
33+
- pwsh: |
34+
$path = "${{ parameters.npmrcPath }}"
2135
22-
$parent = Split-Path -Parent $npmrcPath
23-
if ($parent -and -not (Test-Path -LiteralPath $parent)) {
24-
New-Item -ItemType Directory -Path $parent -Force | Out-Null
25-
}
36+
if (Test-Path -LiteralPath $path -PathType Container) {
37+
throw "npmrcPath points to a directory (expected a file): $path"
38+
}
2639
27-
if (-not (Test-Path -LiteralPath $npmrcPath -PathType Leaf)) {
28-
New-Item -ItemType File -Path $npmrcPath -Force | Out-Null
29-
}
30-
displayName: Ensure .npmrc is a file
40+
$parent = Split-Path -Parent $path
41+
if ($parent -and -not (Test-Path -LiteralPath $parent)) {
42+
New-Item -ItemType Directory -Path $parent -Force | Out-Null
43+
}
3144
32-
- task: npmAuthenticate@0
33-
inputs:
34-
workingFile: ${{ parameters.npmrcPath }}
45+
if (-not (Test-Path -LiteralPath $path -PathType Leaf)) {
46+
New-Item -ItemType File -Path $path -Force | Out-Null
47+
}
48+
49+
Write-Host "##vso[task.setvariable variable=NPM_CONFIG_USERCONFIG]$path"
50+
Write-Host "##vso[task.setvariable variable=NPM_CONFIG_REGISTRY]${{ parameters.customNPMRegistry }}"
51+
displayName: 📦 Setup NPM User Config
3552
36-
- pwsh: |
37-
$source = "${{ parameters.npmrcPath }}"
38-
$dest = "$(Build.SourcesDirectory)/.npmrc"
53+
# Configure npm/yarn to use the custom registry and ensure auth headers are sent.
54+
- pwsh: |
55+
$path = "${{ parameters.npmrcPath }}"
56+
$registry = "${{ parameters.customNPMRegistry }}"
3957
40-
if (-not (Test-Path -LiteralPath $source -PathType Leaf)) {
41-
throw "Expected authenticated .npmrc file at: $source"
42-
}
58+
$env:NPM_CONFIG_USERCONFIG = $path
59+
$env:NPM_CONFIG_REGISTRY = $registry
4360
44-
if (Test-Path -LiteralPath $dest -PathType Container) {
45-
throw "Expected destination .npmrc to be a file, but it is a directory: $dest"
46-
}
61+
npm config set registry "$registry"
4762
48-
$srcFull = [System.IO.Path]::GetFullPath($source)
49-
$destFull = [System.IO.Path]::GetFullPath($dest)
63+
# npm >v7 deprecated the `always-auth` config option, refs npm/cli@72a7eeb
64+
# following is a workaround for yarn-like clients to send authorization header for GET
65+
# requests to the registry.
66+
"always-auth=true" | Out-File -FilePath $path -Append -Encoding utf8
5067
51-
if ($srcFull -ieq $destFull) {
52-
Write-Host "Authenticated .npmrc already in repo root: $dest"
53-
return
54-
}
68+
$yarn = Get-Command yarn -ErrorAction SilentlyContinue
69+
if ($null -ne $yarn) {
70+
yarn config set registry "$registry"
71+
} else {
72+
Write-Host "yarn not found; skipping yarn registry configuration"
73+
}
74+
displayName: 📦 Setup NPM & Yarn
5575
56-
if (-not (Test-Path -LiteralPath $dest -PathType Leaf)) {
57-
Copy-Item -LiteralPath $source -Destination $dest -Force
58-
Write-Host "Copied authenticated .npmrc -> $dest"
59-
} else {
60-
Write-Host "Repo root .npmrc already exists; not overwriting: $dest"
61-
}
62-
displayName: Copy .npmrc to repo root (if missing)
76+
# Populate the temp .npmrc with auth for the configured registry.
77+
- task: npmAuthenticate@0
78+
inputs:
79+
workingFile: ${{ parameters.npmrcPath }}
80+
displayName: 📦 Setup NPM Authentication
81+
82+
# Some lockfiles contain hardcoded references to public registries. Rewrite them so installs
83+
# and `npx` resolve from the custom registry consistently.
84+
- pwsh: |
85+
$registry = "${{ parameters.customNPMRegistry }}"
86+
$scriptPath = Join-Path "$(Agent.TempDirectory)" 'setup-npm-registry.js'
87+
88+
$lines = @(
89+
"const fs = require('fs').promises;",
90+
"const path = require('path');",
91+
"",
92+
"async function* getLockFiles(dir) {",
93+
" const files = await fs.readdir(dir);",
94+
"",
95+
" for (const file of files) {",
96+
" const fullPath = path.join(dir, file);",
97+
" const stat = await fs.stat(fullPath);",
98+
"",
99+
" if (stat.isDirectory()) {",
100+
" if (file === 'node_modules' || file === '.git') {",
101+
" continue;",
102+
" }",
103+
" yield* getLockFiles(fullPath);",
104+
" } else if (file === 'yarn.lock' || file === 'package-lock.json') {",
105+
" yield fullPath;",
106+
" }",
107+
" }",
108+
"}",
109+
"",
110+
"async function rewrite(file, registry) {",
111+
" let contents = await fs.readFile(file, 'utf8');",
112+
" contents = contents.replace(/https:\\/\\/registry\\.[^.]+\\.(com|org)\\//g, registry);",
113+
" await fs.writeFile(file, contents);",
114+
"}",
115+
"",
116+
"async function main() {",
117+
" const root = process.cwd();",
118+
" const registry = process.env.NPM_CONFIG_REGISTRY || " + JSON.stringify($registry) + ";",
119+
"",
120+
" for await (const file of getLockFiles(root)) {",
121+
" await rewrite(file, registry);",
122+
" console.log('Updated node registry:', file);",
123+
" }",
124+
"}",
125+
"",
126+
"main();"
127+
)
128+
129+
Set-Content -LiteralPath $scriptPath -Value ($lines -join "`n") -Encoding utf8
130+
node $scriptPath
131+
displayName: 📦 Setup NPM Registry
63132
64133
- script: npm config get registry
65134
displayName: Verify NPM Registry
66135

67-
- task: NodeTool@0
68-
inputs:
69-
versionSpec: '22.17.0'
70-
checkLatest: true
71-
displayName: Select Node 22 LTS
72-
73136
- ${{ if eq(parameters.installPython, true) }}:
74137
- task: PipAuthenticate@1
75138
displayName: 'Pip Authenticate'
@@ -78,7 +141,7 @@ steps:
78141

79142
- task: UsePythonVersion@0
80143
inputs:
81-
versionSpec: '3.9' # note Install Python dependencies step below relies on Python 3.9
144+
versionSpec: ${{ parameters.pythonVersion }}
82145
addToPath: true
83146
architecture: 'x64'
84-
displayName: Select Python version
147+
displayName: Select Python ${{ parameters.pythonVersion }}

build/templates/sign.yml

Lines changed: 8 additions & 8 deletions
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,16 +79,10 @@ steps:
7379
restoreDirectory: '$(Build.SourcesDirectory)/packages'
7480
nugetConfigPath: '$(Build.SourcesDirectory)/build/NuGet.config'
7581

76-
# Setup Node.js and npm authentication
77-
- task: DownloadPipelineArtifact@2
78-
inputs:
79-
artifactName: npmrc
80-
targetPath: $(Pipeline.Workspace)/npmrc
81-
displayName: 🚛 Download .npmrc artifact
82-
8382
- template: setup.yml@self
8483
parameters:
85-
npmrcPath: $(Pipeline.Workspace)/npmrc/.npmrc
84+
customNPMRegistry: ${{ parameters.customNPMRegistry }}
85+
nodeVersion: ${{ parameters.nodeVersion }}
8686

8787
- task: Npm@1
8888
displayName: 'npm ci (install vsce)'

0 commit comments

Comments
 (0)