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.
27parameters :
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
1027steps :
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 }}
0 commit comments