Skip to content

Commit b6f4f4c

Browse files
committed
The refactor to externalize the registry setup into scripts and call them from setup.yml:22-67 (plus the new documented scripts under scripts).
1 parent 13e6236 commit b6f4f4c

File tree

6 files changed

+252
-88
lines changed

6 files changed

+252
-88
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
[CmdletBinding()]
2+
<#
3+
.SYNOPSIS
4+
Creates a temporary npm user config (.npmrc) file for Azure Pipelines.
5+
6+
.DESCRIPTION
7+
Ensures the path exists and points to a file (not a directory), then sets pipeline
8+
variables so subsequent steps can use a job-scoped npm config instead of relying
9+
on a checked-in repository .npmrc.
10+
11+
Variables set:
12+
- NPM_CONFIG_USERCONFIG: points npm/npx to the temp .npmrc
13+
- NPM_CONFIG_REGISTRY: (optional) registry URL to use for installs
14+
15+
.PARAMETER Path
16+
Full path to the .npmrc file to create/use (e.g. $(Agent.TempDirectory)/.npmrc).
17+
18+
.PARAMETER Registry
19+
Optional custom npm registry URL. If provided, sets NPM_CONFIG_REGISTRY.
20+
21+
.EXAMPLE
22+
./ensure-npm-userconfig.ps1 -Path "$(Agent.TempDirectory)/.npmrc" -Registry "$(AZURE_ARTIFACTS_FEED)"
23+
#>
24+
param(
25+
[Parameter(Mandatory = $true)]
26+
[string]$Path,
27+
28+
[Parameter(Mandatory = $false)]
29+
[string]$Registry = ''
30+
)
31+
32+
if (Test-Path -LiteralPath $Path -PathType Container) {
33+
throw "npmrcPath points to a directory (expected a file): $Path"
34+
}
35+
36+
$parent = Split-Path -Parent $Path
37+
if ($parent -and -not (Test-Path -LiteralPath $parent)) {
38+
New-Item -ItemType Directory -Path $parent -Force | Out-Null
39+
}
40+
41+
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
42+
New-Item -ItemType File -Path $Path -Force | Out-Null
43+
}
44+
45+
Write-Host "##vso[task.setvariable variable=NPM_CONFIG_USERCONFIG]$Path"
46+
47+
if (-not [string]::IsNullOrWhiteSpace($Registry)) {
48+
Write-Host "##vso[task.setvariable variable=NPM_CONFIG_REGISTRY]$Registry"
49+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
[CmdletBinding()]
2+
<#
3+
.SYNOPSIS
4+
Ensures the npm user config contains "always-auth=true".
5+
6+
.DESCRIPTION
7+
npmAuthenticate@0 may overwrite the working .npmrc. This script is intended to run
8+
after npmAuthenticate@0 to append "always-auth=true" if it is not already present.
9+
10+
.PARAMETER Path
11+
Path to the npm user config file to update.
12+
13+
.EXAMPLE
14+
./finalize-npm-config.ps1 -Path "$(Agent.TempDirectory)/.npmrc"
15+
#>
16+
param(
17+
[Parameter(Mandatory = $true)]
18+
[string]$Path
19+
)
20+
21+
$existing = if (Test-Path -LiteralPath $Path) {
22+
Get-Content -LiteralPath $Path -ErrorAction Stop
23+
} else {
24+
@()
25+
}
26+
27+
if ($existing -notcontains 'always-auth=true') {
28+
'always-auth=true' | Out-File -FilePath $Path -Append -Encoding utf8
29+
Write-Host "Appended always-auth=true -> $Path"
30+
} else {
31+
Write-Host "always-auth=true already present in $Path"
32+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
[CmdletBinding()]
2+
<#
3+
.SYNOPSIS
4+
Configures npm (and yarn, if present) to use a custom registry for the current job.
5+
6+
.DESCRIPTION
7+
Intended for Azure Pipelines jobs that authenticate using npmAuthenticate@0 against a
8+
temp user config (.npmrc). This script sets per-process environment variables so npm
9+
reads from the provided user config and targets the provided registry.
10+
11+
Notes:
12+
- Normalizes the registry to ensure it ends with '/'.
13+
- Writes npm's registry setting into the user config file via `npm config set`.
14+
- If yarn is installed on the agent, updates yarn's registry as well.
15+
16+
.PARAMETER NpmrcPath
17+
Path to the npm user config file (the file used by npmAuthenticate@0).
18+
19+
.PARAMETER Registry
20+
Custom registry URL.
21+
22+
.EXAMPLE
23+
./setup-npm-and-yarn.ps1 -NpmrcPath "$(Agent.TempDirectory)/.npmrc" -Registry "$(AZURE_ARTIFACTS_FEED)"
24+
#>
25+
param(
26+
[Parameter(Mandatory = $true)]
27+
[string]$NpmrcPath,
28+
29+
[Parameter(Mandatory = $true)]
30+
[string]$Registry
31+
)
32+
33+
$Registry = $Registry.Trim()
34+
if (-not $Registry.EndsWith('/')) {
35+
$Registry = "$Registry/"
36+
}
37+
38+
$env:NPM_CONFIG_USERCONFIG = $NpmrcPath
39+
$env:NPM_CONFIG_REGISTRY = $Registry
40+
41+
# Configure npm to use the custom registry (writes to the user config file).
42+
npm config set registry "$Registry"
43+
44+
# Configure yarn if available.
45+
$yarn = Get-Command yarn -ErrorAction SilentlyContinue
46+
if ($null -ne $yarn) {
47+
yarn config set registry "$Registry"
48+
} else {
49+
Write-Host "yarn not found; skipping yarn registry configuration"
50+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* Rewrites lockfiles to use a custom npm registry.
3+
*
4+
* Purpose
5+
* - Some lockfiles contain hardcoded references to public registries.
6+
* - In Azure Pipelines, we want installs and npx to consistently resolve from a
7+
* configured private/custom registry feed.
8+
*
9+
* Inputs
10+
* - Environment variable: NPM_CONFIG_REGISTRY (required)
11+
*
12+
* Behavior
13+
* - Recursively scans the repo (excluding node_modules and .git) for:
14+
* - package-lock.json
15+
* - yarn.lock
16+
* - Replaces URLs matching: https://registry.<something>.(com|org)/
17+
* with the provided registry URL.
18+
*/
19+
const fs = require('fs').promises;
20+
const path = require('path');
21+
22+
async function* getLockFiles(dir) {
23+
const files = await fs.readdir(dir);
24+
25+
for (const file of files) {
26+
const fullPath = path.join(dir, file);
27+
const stat = await fs.stat(fullPath);
28+
29+
if (stat.isDirectory()) {
30+
if (file === 'node_modules' || file === '.git') {
31+
continue;
32+
}
33+
yield* getLockFiles(fullPath);
34+
continue;
35+
}
36+
37+
if (file === 'yarn.lock' || file === 'package-lock.json') {
38+
yield fullPath;
39+
}
40+
}
41+
}
42+
43+
async function rewrite(file, registry) {
44+
let contents = await fs.readFile(file, 'utf8');
45+
const re = /https:\/\/registry\.[^.]+\.(com|org)\//g;
46+
contents = contents.replace(re, registry);
47+
await fs.writeFile(file, contents);
48+
}
49+
50+
async function main() {
51+
let registry = process.env.NPM_CONFIG_REGISTRY;
52+
if (!registry) {
53+
throw new Error('NPM_CONFIG_REGISTRY is not set');
54+
}
55+
56+
if (!registry.endsWith('/')) {
57+
registry += '/';
58+
}
59+
60+
const root = process.cwd();
61+
for await (const file of getLockFiles(root)) {
62+
await rewrite(file, registry);
63+
console.log('Updated node registry:', file);
64+
}
65+
}
66+
67+
main().catch((err) => {
68+
console.error(err);
69+
process.exit(1);
70+
});
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
[CmdletBinding()]
2+
<#
3+
.SYNOPSIS
4+
Rewrites lockfiles to use a custom npm registry.
5+
6+
.DESCRIPTION
7+
Some lockfiles can contain hardcoded references to public npm registries.
8+
This wrapper sets NPM_CONFIG_REGISTRY and runs the Node helper script
9+
(setup-npm-registry.js) that performs in-repo lockfile rewrites.
10+
11+
.PARAMETER Registry
12+
Custom registry URL.
13+
14+
.EXAMPLE
15+
./setup-npm-registry.ps1 -Registry "$(AZURE_ARTIFACTS_FEED)"
16+
#>
17+
param(
18+
[Parameter(Mandatory = $true)]
19+
[string]$Registry
20+
)
21+
22+
$Registry = $Registry.Trim()
23+
if (-not $Registry.EndsWith('/')) {
24+
$Registry = "$Registry/"
25+
}
26+
27+
$env:NPM_CONFIG_REGISTRY = $Registry
28+
29+
$scriptPath = Join-Path $PSScriptRoot 'setup-npm-registry.js'
30+
if (-not (Test-Path -LiteralPath $scriptPath -PathType Leaf)) {
31+
throw "Expected JS helper script at: $scriptPath"
32+
}
33+
34+
node $scriptPath

build/templates/setup.yml

Lines changed: 17 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -30,47 +30,17 @@ steps:
3030
- ${{ if ne(parameters.customNPMRegistry, '') }}:
3131
# When using a private/custom registry, configure npm to read auth/config from a temp user config
3232
# instead of relying on a checked-in project .npmrc.
33-
- pwsh: |
34-
$path = "${{ parameters.npmrcPath }}"
35-
36-
if (Test-Path -LiteralPath $path -PathType Container) {
37-
throw "npmrcPath points to a directory (expected a file): $path"
38-
}
39-
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-
}
44-
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 }}"
33+
- pwsh: >
34+
$(Build.SourcesDirectory)/build/scripts/ensure-npm-userconfig.ps1
35+
-Path "${{ parameters.npmrcPath }}"
36+
-Registry "${{ parameters.customNPMRegistry }}"
5137
displayName: 📦 Setup NPM User Config
5238
5339
# Configure npm/yarn to use the custom registry and ensure auth headers are sent.
54-
- pwsh: |
55-
$path = "${{ parameters.npmrcPath }}"
56-
$registry = "${{ parameters.customNPMRegistry }}"
57-
58-
$env:NPM_CONFIG_USERCONFIG = $path
59-
$env:NPM_CONFIG_REGISTRY = $registry
60-
61-
npm config set registry "$registry"
62-
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
67-
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-
}
40+
- pwsh: >
41+
$(Build.SourcesDirectory)/build/scripts/setup-npm-and-yarn.ps1
42+
-NpmrcPath "${{ parameters.npmrcPath }}"
43+
-Registry "${{ parameters.customNPMRegistry }}"
7444
displayName: 📦 Setup NPM & Yarn
7545
7646
# Populate the temp .npmrc with auth for the configured registry.
@@ -79,58 +49,17 @@ steps:
7949
workingFile: ${{ parameters.npmrcPath }}
8050
displayName: 📦 Setup NPM Authentication
8151

52+
# Ensure the registry always sends auth headers (npmAuthenticate may overwrite the file).
53+
- pwsh: >
54+
$(Build.SourcesDirectory)/build/scripts/finalize-npm-config.ps1
55+
-Path "${{ parameters.npmrcPath }}"
56+
displayName: 📦 Finalize NPM config
57+
8258
# Some lockfiles contain hardcoded references to public registries. Rewrite them so installs
8359
# and `npx` resolve from the custom registry consistently.
84-
- pwsh: |
85-
$registry = "${{ parameters.customNPMRegistry }}"
86-
$env:NPM_CONFIG_REGISTRY = $registry
87-
$scriptPath = Join-Path "$(Agent.TempDirectory)" 'setup-npm-registry.js'
88-
89-
$lines = @(
90-
"const fs = require('fs').promises;",
91-
"const path = require('path');",
92-
"",
93-
"async function* getLockFiles(dir) {",
94-
" const files = await fs.readdir(dir);",
95-
"",
96-
" for (const file of files) {",
97-
" const fullPath = path.join(dir, file);",
98-
" const stat = await fs.stat(fullPath);",
99-
"",
100-
" if (stat.isDirectory()) {",
101-
" if (file === 'node_modules' || file === '.git') {",
102-
" continue;",
103-
" }",
104-
" yield* getLockFiles(fullPath);",
105-
" } else if (file === 'yarn.lock' || file === 'package-lock.json') {",
106-
" yield fullPath;",
107-
" }",
108-
" }",
109-
"}",
110-
"",
111-
"async function rewrite(file, registry) {",
112-
" let contents = await fs.readFile(file, 'utf8');",
113-
" const re = new RegExp('https://registry\\\\.[^.]+\\\\.(com|org)/', 'g');",
114-
" contents = contents.replace(re, registry);",
115-
" await fs.writeFile(file, contents);",
116-
"}",
117-
"",
118-
"async function main() {",
119-
" const root = process.cwd();",
120-
" const registry = process.env.NPM_CONFIG_REGISTRY;",
121-
" if (!registry) { throw new Error('NPM_CONFIG_REGISTRY is not set'); }",
122-
"",
123-
" for await (const file of getLockFiles(root)) {",
124-
" await rewrite(file, registry);",
125-
" console.log('Updated node registry:', file);",
126-
" }",
127-
"}",
128-
"",
129-
"main();"
130-
)
131-
132-
Set-Content -LiteralPath $scriptPath -Value ($lines -join "`n") -Encoding utf8
133-
node $scriptPath
60+
- pwsh: >
61+
$(Build.SourcesDirectory)/build/scripts/setup-npm-registry.ps1
62+
-Registry "${{ parameters.customNPMRegistry }}"
13463
displayName: 📦 Setup NPM Registry
13564
13665
- script: npm config get registry

0 commit comments

Comments
 (0)