Skip to content

Commit 86a5d33

Browse files
authored
Fix publish pipeline (#933)
* pass .nmprc to publish * just always copy * temp disable store publish * try uploading npmrc * check .npmrc is a file * 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. * Explicitly setting $env:NPM_CONFIG_REGISTRY in the “📦 Setup NPM Registry” step. Making the generated JS do const registry = process.env.NPM_CONFIG_REGISTRY; (and error if it’s missing), instead of trying to inline/concatenate a JSON-escaped registry string. * fix regex * uncomment publish * The refactor to externalize the registry setup into scripts and call them from setup.yml:22-67 (plus the new documented scripts under scripts). * prettier
1 parent f0c478d commit 86a5d33

12 files changed

+337
-34
lines changed

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)
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/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: 19 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.

build/templates/publish.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,12 +102,12 @@ steps:
102102
103103
if ('${{ parameters.preRelease }}' -eq 'True') {
104104
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
105+
Write-Host "Executing: npx @vscode/vsce@latest publish --pat *** --packagePath $vsixPath --manifestPath $manifestPath --signaturePath $signaturePath --pre-release"
106+
npx @vscode/vsce@latest publish --pat $aadToken --packagePath $vsixPath --manifestPath $manifestPath --signaturePath $signaturePath --pre-release
107107
} else {
108108
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
109+
Write-Host "Executing: npx @vscode/vsce@latest publish --pat *** --packagePath $vsixPath --manifestPath $manifestPath --signaturePath $signaturePath"
110+
npx @vscode/vsce@latest publish --pat $aadToken --packagePath $vsixPath --manifestPath $manifestPath --signaturePath $signaturePath
111111
}
112112
113113
if ($LASTEXITCODE -ne 0) {

0 commit comments

Comments
 (0)