diff --git a/build/scripts/ensure-npm-userconfig.ps1 b/build/scripts/ensure-npm-userconfig.ps1 deleted file mode 100644 index 07d04f34..00000000 --- a/build/scripts/ensure-npm-userconfig.ps1 +++ /dev/null @@ -1,49 +0,0 @@ -[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" -} diff --git a/build/scripts/finalize-npm-config.ps1 b/build/scripts/finalize-npm-config.ps1 deleted file mode 100644 index f8fba2aa..00000000 --- a/build/scripts/finalize-npm-config.ps1 +++ /dev/null @@ -1,32 +0,0 @@ -[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" -} diff --git a/build/scripts/setup-npm-and-yarn.ps1 b/build/scripts/setup-npm-and-yarn.ps1 deleted file mode 100644 index c97f66f8..00000000 --- a/build/scripts/setup-npm-and-yarn.ps1 +++ /dev/null @@ -1,50 +0,0 @@ -[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" -} diff --git a/build/scripts/setup-npm-registry.js b/build/scripts/setup-npm-registry.js deleted file mode 100644 index b7c43c5a..00000000 --- a/build/scripts/setup-npm-registry.js +++ /dev/null @@ -1,70 +0,0 @@ -/** - * 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..(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); -}); diff --git a/build/scripts/setup-npm-registry.ps1 b/build/scripts/setup-npm-registry.ps1 deleted file mode 100644 index 8d93cf60..00000000 --- a/build/scripts/setup-npm-registry.ps1 +++ /dev/null @@ -1,34 +0,0 @@ -[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 diff --git a/build/templates/setup.yml b/build/templates/setup.yml index f6b37227..d2c6a46d 100644 --- a/build/templates/setup.yml +++ b/build/templates/setup.yml @@ -1,4 +1,9 @@ # DevDiv pipeline setup steps +# +# Notes on custom registry support: +# - Custom npm registry setup is implemented inline below (no external scripts). +# - The template creates a temp .npmrc, sets NPM_CONFIG_USERCONFIG/REGISTRY, and +# rewrites lockfiles via a transient JS helper emitted into Agent.TempDirectory. parameters: - name: installNode type: boolean @@ -30,17 +35,47 @@ steps: - ${{ if ne(parameters.customNPMRegistry, '') }}: # When using a private/custom registry, configure npm to read auth/config from a temp user config # instead of relying on a checked-in project .npmrc. - - pwsh: > - $(Build.SourcesDirectory)/build/scripts/ensure-npm-userconfig.ps1 - -Path "${{ parameters.npmrcPath }}" - -Registry "${{ parameters.customNPMRegistry }}" + - pwsh: | + $path = "${{ parameters.npmrcPath }}" + + 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" + Write-Host "##vso[task.setvariable variable=NPM_CONFIG_REGISTRY]${{ parameters.customNPMRegistry }}" displayName: 📦 Setup NPM User Config # Configure npm/yarn to use the custom registry and ensure auth headers are sent. - - pwsh: > - $(Build.SourcesDirectory)/build/scripts/setup-npm-and-yarn.ps1 - -NpmrcPath "${{ parameters.npmrcPath }}" - -Registry "${{ parameters.customNPMRegistry }}" + - pwsh: | + $path = "${{ parameters.npmrcPath }}" + $registry = "${{ parameters.customNPMRegistry }}" + + $env:NPM_CONFIG_USERCONFIG = $path + $env:NPM_CONFIG_REGISTRY = $registry + + npm config set registry "$registry" + + # npm >v7 deprecated the `always-auth` config option, refs npm/cli@72a7eeb + # following is a workaround for yarn-like clients to send authorization header for GET + # requests to the registry. + "always-auth=true" | Out-File -FilePath $path -Append -Encoding utf8 + + $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" + } displayName: 📦 Setup NPM & Yarn # Populate the temp .npmrc with auth for the configured registry. @@ -49,17 +84,57 @@ steps: workingFile: ${{ parameters.npmrcPath }} displayName: 📦 Setup NPM Authentication - # Ensure the registry always sends auth headers (npmAuthenticate may overwrite the file). - - pwsh: > - $(Build.SourcesDirectory)/build/scripts/finalize-npm-config.ps1 - -Path "${{ parameters.npmrcPath }}" - displayName: 📦 Finalize NPM config - # Some lockfiles contain hardcoded references to public registries. Rewrite them so installs # and `npx` resolve from the custom registry consistently. - - pwsh: > - $(Build.SourcesDirectory)/build/scripts/setup-npm-registry.ps1 - -Registry "${{ parameters.customNPMRegistry }}" + - pwsh: | + $registry = "${{ parameters.customNPMRegistry }}" + $env:NPM_CONFIG_REGISTRY = $registry + $scriptPath = Join-Path "$(Agent.TempDirectory)" 'setup-npm-registry.js' + $lines = @( + "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);", + " } else if (file === 'yarn.lock' || file === 'package-lock.json') {", + " yield fullPath;", + " }", + " }", + "}", + "", + "async function rewrite(file, registry) {", + " let contents = await fs.readFile(file, 'utf8');", + " const re = new RegExp('https://registry\\.[^.]+\\.(com|org)/', 'g');", + " contents = contents.replace(re, registry);", + " await fs.writeFile(file, contents);", + "}", + "", + "async function main() {", + " const root = process.cwd();", + " const registry = process.env.NPM_CONFIG_REGISTRY;", + " if (!registry) { throw new Error('NPM_CONFIG_REGISTRY is not set'); }", + "", + " for await (const file of getLockFiles(root)) {", + " await rewrite(file, registry);", + " console.log('Updated node registry:', file);", + " }", + "}", + "", + "main();" + ) + + Set-Content -LiteralPath $scriptPath -Value ($lines -join "`n") -Encoding utf8 + node $scriptPath displayName: 📦 Setup NPM Registry - script: npm config get registry