Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build/azure-devdiv-pipeline.pre-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ extends:
buildSteps: ${{ parameters.buildSteps }}
isPreRelease: true
standardizedVersioning: true
customNPMRegistry: $(AZURE_ARTIFACTS_FEED)

- stage: Publish
displayName: Publish Extension
Expand All @@ -122,3 +123,4 @@ extends:
ghCreateTag: true
ghCreateRelease: true
ghReleaseAddChangeLog: true
customNPMRegistry: $(AZURE_ARTIFACTS_FEED)
2 changes: 2 additions & 0 deletions build/azure-devdiv-pipeline.stable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ extends:
buildPlatforms: ${{ parameters.buildPlatforms }}
buildSteps: ${{ parameters.buildSteps }}
isPreRelease: false
customNPMRegistry: $(AZURE_ARTIFACTS_FEED)

- stage: Publish
displayName: Publish Extension
Expand All @@ -116,3 +117,4 @@ extends:
publishExtension: ${{ parameters.publishExtension }}
preRelease: false
teamName: $(TeamName)
customNPMRegistry: $(AZURE_ARTIFACTS_FEED)
49 changes: 49 additions & 0 deletions build/scripts/ensure-npm-userconfig.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
[CmdletBinding()]
<#
.SYNOPSIS
Creates a temporary npm user config (.npmrc) file for Azure Pipelines.

.DESCRIPTION
Ensures the path exists and points to a file (not a directory), then sets pipeline
variables so subsequent steps can use a job-scoped npm config instead of relying
on a checked-in repository .npmrc.

Variables set:
- NPM_CONFIG_USERCONFIG: points npm/npx to the temp .npmrc
- NPM_CONFIG_REGISTRY: (optional) registry URL to use for installs

.PARAMETER Path
Full path to the .npmrc file to create/use (e.g. $(Agent.TempDirectory)/.npmrc).

.PARAMETER Registry
Optional custom npm registry URL. If provided, sets NPM_CONFIG_REGISTRY.

.EXAMPLE
./ensure-npm-userconfig.ps1 -Path "$(Agent.TempDirectory)/.npmrc" -Registry "$(AZURE_ARTIFACTS_FEED)"
#>
param(
[Parameter(Mandatory = $true)]
[string]$Path,

[Parameter(Mandatory = $false)]
[string]$Registry = ''
)

if (Test-Path -LiteralPath $Path -PathType Container) {
throw "npmrcPath points to a directory (expected a file): $Path"
}

$parent = Split-Path -Parent $Path
if ($parent -and -not (Test-Path -LiteralPath $parent)) {
New-Item -ItemType Directory -Path $parent -Force | Out-Null
}

if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
New-Item -ItemType File -Path $Path -Force | Out-Null
}

Write-Host "##vso[task.setvariable variable=NPM_CONFIG_USERCONFIG]$Path"

if (-not [string]::IsNullOrWhiteSpace($Registry)) {
Write-Host "##vso[task.setvariable variable=NPM_CONFIG_REGISTRY]$Registry"
}
32 changes: 32 additions & 0 deletions build/scripts/finalize-npm-config.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[CmdletBinding()]
<#
.SYNOPSIS
Ensures the npm user config contains "always-auth=true".

.DESCRIPTION
npmAuthenticate@0 may overwrite the working .npmrc. This script is intended to run
after npmAuthenticate@0 to append "always-auth=true" if it is not already present.

.PARAMETER Path
Path to the npm user config file to update.

.EXAMPLE
./finalize-npm-config.ps1 -Path "$(Agent.TempDirectory)/.npmrc"
#>
param(
[Parameter(Mandatory = $true)]
[string]$Path
)

$existing = if (Test-Path -LiteralPath $Path) {
Get-Content -LiteralPath $Path -ErrorAction Stop
} else {
@()
}

if ($existing -notcontains 'always-auth=true') {
'always-auth=true' | Out-File -FilePath $Path -Append -Encoding utf8
Write-Host "Appended always-auth=true -> $Path"
} else {
Write-Host "always-auth=true already present in $Path"
}
50 changes: 50 additions & 0 deletions build/scripts/setup-npm-and-yarn.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
[CmdletBinding()]
<#
.SYNOPSIS
Configures npm (and yarn, if present) to use a custom registry for the current job.

.DESCRIPTION
Intended for Azure Pipelines jobs that authenticate using npmAuthenticate@0 against a
temp user config (.npmrc). This script sets per-process environment variables so npm
reads from the provided user config and targets the provided registry.

Notes:
- Normalizes the registry to ensure it ends with '/'.
- Writes npm's registry setting into the user config file via `npm config set`.
- If yarn is installed on the agent, updates yarn's registry as well.

.PARAMETER NpmrcPath
Path to the npm user config file (the file used by npmAuthenticate@0).

.PARAMETER Registry
Custom registry URL.

.EXAMPLE
./setup-npm-and-yarn.ps1 -NpmrcPath "$(Agent.TempDirectory)/.npmrc" -Registry "$(AZURE_ARTIFACTS_FEED)"
#>
param(
[Parameter(Mandatory = $true)]
[string]$NpmrcPath,

[Parameter(Mandatory = $true)]
[string]$Registry
)

$Registry = $Registry.Trim()
if (-not $Registry.EndsWith('/')) {
$Registry = "$Registry/"
}

$env:NPM_CONFIG_USERCONFIG = $NpmrcPath
$env:NPM_CONFIG_REGISTRY = $Registry

# Configure npm to use the custom registry (writes to the user config file).
npm config set registry "$Registry"

# Configure yarn if available.
$yarn = Get-Command yarn -ErrorAction SilentlyContinue
if ($null -ne $yarn) {
yarn config set registry "$Registry"
} else {
Write-Host "yarn not found; skipping yarn registry configuration"
}
70 changes: 70 additions & 0 deletions build/scripts/setup-npm-registry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* Rewrites lockfiles to use a custom npm registry.
*
* Purpose
* - Some lockfiles contain hardcoded references to public registries.
* - In Azure Pipelines, we want installs and npx to consistently resolve from a
* configured private/custom registry feed.
*
* Inputs
* - Environment variable: NPM_CONFIG_REGISTRY (required)
*
* Behavior
* - Recursively scans the repo (excluding node_modules and .git) for:
* - package-lock.json
* - yarn.lock
* - Replaces URLs matching: https://registry.<something>.(com|org)/
* with the provided registry URL.
*/
const fs = require('fs').promises;
const path = require('path');

async function* getLockFiles(dir) {
const files = await fs.readdir(dir);

for (const file of files) {
const fullPath = path.join(dir, file);
const stat = await fs.stat(fullPath);

if (stat.isDirectory()) {
if (file === 'node_modules' || file === '.git') {
continue;
}
yield* getLockFiles(fullPath);
continue;
}

if (file === 'yarn.lock' || file === 'package-lock.json') {
yield fullPath;
}
}
}

async function rewrite(file, registry) {
let contents = await fs.readFile(file, 'utf8');
const re = /https:\/\/registry\.[^.]+\.(com|org)\//g;
contents = contents.replace(re, registry);
await fs.writeFile(file, contents);
}

async function main() {
let registry = process.env.NPM_CONFIG_REGISTRY;
if (!registry) {
throw new Error('NPM_CONFIG_REGISTRY is not set');
}

if (!registry.endsWith('/')) {
registry += '/';
}

const root = process.cwd();
for await (const file of getLockFiles(root)) {
await rewrite(file, registry);
console.log('Updated node registry:', file);
}
}

main().catch((err) => {
console.error(err);
process.exit(1);
});
34 changes: 34 additions & 0 deletions build/scripts/setup-npm-registry.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[CmdletBinding()]
<#
.SYNOPSIS
Rewrites lockfiles to use a custom npm registry.

.DESCRIPTION
Some lockfiles can contain hardcoded references to public npm registries.
This wrapper sets NPM_CONFIG_REGISTRY and runs the Node helper script
(setup-npm-registry.js) that performs in-repo lockfile rewrites.

.PARAMETER Registry
Custom registry URL.

.EXAMPLE
./setup-npm-registry.ps1 -Registry "$(AZURE_ARTIFACTS_FEED)"
#>
param(
[Parameter(Mandatory = $true)]
[string]$Registry
)

$Registry = $Registry.Trim()
if (-not $Registry.EndsWith('/')) {
$Registry = "$Registry/"
}

$env:NPM_CONFIG_REGISTRY = $Registry

$scriptPath = Join-Path $PSScriptRoot 'setup-npm-registry.js'
if (-not (Test-Path -LiteralPath $scriptPath -PathType Leaf)) {
throw "Expected JS helper script at: $scriptPath"
}

node $scriptPath
13 changes: 13 additions & 0 deletions build/templates/package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ parameters:
type: object
displayName: 'List of platforms to build'

- name: customNPMRegistry
type: string
default: ''
displayName: 'Custom NPM registry (optional)'

- name: nodeVersion
type: string
default: '22.17.0'
displayName: 'Node version to install'

- name: buildSteps
type: stepList
default: []
Expand Down Expand Up @@ -73,6 +83,9 @@ jobs:

steps:
- template: setup.yml@self
parameters:
customNPMRegistry: ${{ parameters.customNPMRegistry }}
nodeVersion: ${{ parameters.nodeVersion }}

- ${{ if and(eq(parameters.isPreRelease, true), eq(parameters.standardizedVersioning, true)) }}:
- template: modify-extension-version.yml@self
Expand Down
24 changes: 19 additions & 5 deletions build/templates/publish-extension.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ parameters:
type: object
displayName: 'List of platforms to sign and publish'

- name: customNPMRegistry
type: string
default: ''
displayName: 'Custom NPM registry (optional)'

- name: nodeVersion
type: string
default: '22.17.0'
displayName: 'Node version to install'

# Signing parameters
- name: signType
type: string
Expand Down Expand Up @@ -151,6 +161,8 @@ jobs:
signType: ${{ parameters.signType }}
verifySignature: ${{ parameters.verifySignature }}
teamName: ${{ parameters.teamName }}
customNPMRegistry: ${{ parameters.customNPMRegistry }}
nodeVersion: ${{ parameters.nodeVersion }}

# Job 2: Publish to marketplace
- ${{ if eq(parameters.publishExtension, true) }}:
Expand All @@ -165,17 +177,19 @@ jobs:
type: releaseJob # This makes a job a release job
isProduction: true # Indicates a production release
steps:
- template: setup.yml
parameters:
installNode: true
installPython: false

- task: 1ES.DownloadPipelineArtifact@1
inputs:
artifactName: extension
targetPath: $(Build.ArtifactStagingDirectory)/${{ parameters.publishFolder }}
displayName: 🚛 Download signed extension

- template: setup.yml
parameters:
installNode: true
installPython: false
customNPMRegistry: ${{ parameters.customNPMRegistry }}
nodeVersion: ${{ parameters.nodeVersion }}

# Extract VSIX to read publisher/version for GitHub release tagging.
# Use Agent.TempDirectory to avoid reusing Build.ArtifactStagingDirectory which
# is reserved for final artifact staging.
Expand Down
8 changes: 4 additions & 4 deletions build/templates/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,12 @@ steps:

if ('${{ parameters.preRelease }}' -eq 'True') {
Write-Host 'Publishing as pre-release'
Write-Host "Executing: npx vsce publish --pat *** --packagePath $vsixPath --manifestPath $manifestPath --signaturePath $signaturePath --pre-release"
npx vsce publish --pat $aadToken --packagePath $vsixPath --manifestPath $manifestPath --signaturePath $signaturePath --pre-release
Write-Host "Executing: npx @vscode/vsce@latest publish --pat *** --packagePath $vsixPath --manifestPath $manifestPath --signaturePath $signaturePath --pre-release"
npx @vscode/vsce@latest publish --pat $aadToken --packagePath $vsixPath --manifestPath $manifestPath --signaturePath $signaturePath --pre-release
} else {
Write-Host 'Publishing as stable release'
Write-Host "Executing: npx vsce publish --pat *** --packagePath $vsixPath --manifestPath $manifestPath --signaturePath $signaturePath"
npx vsce publish --pat $aadToken --packagePath $vsixPath --manifestPath $manifestPath --signaturePath $signaturePath
Write-Host "Executing: npx @vscode/vsce@latest publish --pat *** --packagePath $vsixPath --manifestPath $manifestPath --signaturePath $signaturePath"
npx @vscode/vsce@latest publish --pat $aadToken --packagePath $vsixPath --manifestPath $manifestPath --signaturePath $signaturePath
}

if ($LASTEXITCODE -ne 0) {
Expand Down
Loading
Loading