diff --git a/.azure-pipelines/npm-cfs-variables.yml b/.azure-pipelines/npm-cfs-variables.yml new file mode 100644 index 00000000..261e5d08 --- /dev/null +++ b/.azure-pipelines/npm-cfs-variables.yml @@ -0,0 +1,28 @@ +# Variables required to route npm package restore through the Central Feed Service +# (CFS). Consumed by every pipeline in this directory alongside the npm-cfs.yml steps +# template, which is where these values are actually applied. +# +# Both are declared here rather than in each pipeline so the feed URL exists in +# exactly one place. +# +# npm_config_registry is not redundant with the registry written into the generated +# .npmrc. npm resolves configuration in the order cli > environment > project .npmrc +# > user .npmrc, so a registry supplied only through the user config is outranked by +# anything the agent image already configures -- Microsoft hosted images ship a user +# level .npmrc pointing at an internal proxy, and a pool that exports +# npm_config_registry would win outright. Restore would then quietly resolve from +# somewhere other than CFS while the build still reported success. Declaring the +# variable here puts the redirect at environment precedence, where only an explicit +# command line flag can override it. +# +# npm matches npm_config_* environment variables case insensitively, so the +# uppercased form that Azure Pipelines exports applies to every step on every OS. +# That matters because package restore here is not driven by a single task: the Npm +# tasks, `npx json`, `npx @vscode/vsce` and the vsce invocation inside AzureCLI@2 +# all inherit the agent environment rather than reading a task input. + +variables: + - name: npm_config_registry + value: https://pkgs.dev.azure.com/mseng/VSJava/_packaging/vscjava/npm/registry/ + - name: npm_config_userconfig + value: $(Agent.TempDirectory)/.npmrc diff --git a/.azure-pipelines/npm-cfs.yml b/.azure-pipelines/npm-cfs.yml new file mode 100644 index 00000000..c7445e78 --- /dev/null +++ b/.azure-pipelines/npm-cfs.yml @@ -0,0 +1,58 @@ +# Routes npm package restore through the Central Feed Service (CFS), as required by +# SFI Network Isolation. Consumed by every build pipeline in this directory. +# +# Pipelines must also include the companion variables template: +# variables: +# - template: /.azure-pipelines/npm-cfs-variables.yml@self +# which declares the feed URL and the generated .npmrc path. The redirect itself is +# carried by the npm_config_registry environment variable that template exports; see +# its header for why the generated .npmrc alone is not enough. +# +# The .npmrc is generated at build time into the agent temp directory rather than +# being committed to the repository, so that: +# * open source contributors and the GitHub Actions workflows keep restoring from +# the public npm registry -- npm rewrites the host of every `resolved` URL in +# package-lock.json to the configured registry, so a single lockfile serves both; +# * the credential that NpmAuthenticate injects never lands inside the workspace; +# * the configuration does not depend on the repository being checked out, so +# release jobs consuming a prebuilt artifact work the same way as build jobs. +# +# The registry is still written into that file because NpmAuthenticate discovers the +# registries to authenticate by reading it. npm then takes the URL from the +# environment and the matching credential from this file. +# +# The file is written with `npm config set` rather than a shell redirect because +# these pipelines span both Linux and Windows pools. `script:` maps to CmdLine@2, +# which runs on both, and the npm invocation itself is shell agnostic. PowerShell@2 +# is avoided because it resolves `pwsh` before `powershell` and hard fails when +# neither is on PATH, which is not guaranteed on a custom Linux image. +# +# This template must run after the Node install task, and before any step that +# restores packages -- including `npx`, which resolves downloads through the +# configured registry. +# +# Consumers must reference this file as `/.azure-pipelines/npm-cfs.yml@self`. A +# relative path is resolved against the file doing the including, which for these +# pipelines is the 1ES extends template in another repository, so the unqualified +# form is looked up in 1ESPipelineTemplates and fails YAML compilation. + +steps: + - script: npm config set registry $(npm_config_registry) --location=user --userconfig="$(npm_config_userconfig)" + displayName: Configure CFS npm registry + + # Appends `//pkgs.dev.azure.com/.../registry/:_authToken=` for every + # registry it finds in the file above. `always-auth` is deliberately not written: + # it is not read by this task and is rejected outright by the npm 10 shipped with + # Node 20. + - task: NpmAuthenticate@0 + displayName: Authenticate to CFS feed + inputs: + workingFile: $(npm_config_userconfig) + + # Restore silently falling back to the public registry is the failure mode this + # whole template exists to prevent, and it leaves no trace in the build log, so it + # is asserted rather than assumed. Written in node, which the agent already + # provides, to avoid shell differences between the Linux and Windows pools. + - script: >- + node -e "const cp=require('child_process');const r=cp.execSync('npm config get registry').toString().trim();console.log('npm registry -> '+r);if(!r.startsWith('https://pkgs.dev.azure.com/')){console.error('##vso[task.logissue type=error]npm is not configured against the CFS feed');process.exit(1);}" + displayName: Verify CFS npm registry diff --git a/.azure-pipelines/release-nightly.yml b/.azure-pipelines/release-nightly.yml index 0532a0a0..5ad63838 100644 --- a/.azure-pipelines/release-nightly.yml +++ b/.azure-pipelines/release-nightly.yml @@ -8,6 +8,7 @@ name: $(Date:yyyyMMdd).$(Rev:r) # Use the current date and a revision number for variables: - name: Codeql.Enabled value: true + - template: /.azure-pipelines/npm-cfs-variables.yml@self resources: repositories: - repository: self @@ -46,6 +47,7 @@ extends: displayName: 'Use Node.js 20.x' inputs: version: '20.x' + - template: /.azure-pipelines/npm-cfs.yml@self - task: AzureCLI@2 displayName: 'Publish Extension' inputs: diff --git a/.azure-pipelines/release.yml b/.azure-pipelines/release.yml index 0532a0a0..5ad63838 100644 --- a/.azure-pipelines/release.yml +++ b/.azure-pipelines/release.yml @@ -8,6 +8,7 @@ name: $(Date:yyyyMMdd).$(Rev:r) # Use the current date and a revision number for variables: - name: Codeql.Enabled value: true + - template: /.azure-pipelines/npm-cfs-variables.yml@self resources: repositories: - repository: self @@ -46,6 +47,7 @@ extends: displayName: 'Use Node.js 20.x' inputs: version: '20.x' + - template: /.azure-pipelines/npm-cfs.yml@self - task: AzureCLI@2 displayName: 'Publish Extension' inputs: diff --git a/.azure-pipelines/vscode-java-test-ci.yml b/.azure-pipelines/vscode-java-test-ci.yml index 2dfe81ab..828ad376 100644 --- a/.azure-pipelines/vscode-java-test-ci.yml +++ b/.azure-pipelines/vscode-java-test-ci.yml @@ -2,6 +2,7 @@ name: $(Date:yyyyMMdd).$(Rev:r) variables: - name: Codeql.Enabled value: true + - template: /.azure-pipelines/npm-cfs-variables.yml@self resources: repositories: - repository: self @@ -49,6 +50,7 @@ extends: versionSpec: "21" jdkArchitectureOption: x64 jdkSourceOption: PreInstalled + - template: /.azure-pipelines/npm-cfs.yml@self - task: Npm@1 displayName: npm install inputs: diff --git a/.azure-pipelines/vscode-java-test-nightly.yml b/.azure-pipelines/vscode-java-test-nightly.yml index ad784fd4..886a7302 100644 --- a/.azure-pipelines/vscode-java-test-nightly.yml +++ b/.azure-pipelines/vscode-java-test-nightly.yml @@ -2,6 +2,7 @@ name: $(Date:yyyyMMdd).$(Rev:r) variables: - name: Codeql.Enabled value: true + - template: /.azure-pipelines/npm-cfs-variables.yml@self schedules: - cron: 0 2 * * * branches: @@ -65,6 +66,7 @@ extends: jdkDestinationDirectory: $(Agent.ToolsDirectory)/ms-jdk21 - script: java --version displayName: 'Check Java installation' + - template: /.azure-pipelines/npm-cfs.yml@self - task: Npm@1 displayName: npm install inputs: diff --git a/.azure-pipelines/vscode-java-test-rc.yml b/.azure-pipelines/vscode-java-test-rc.yml index d7771f25..7f1a00da 100644 --- a/.azure-pipelines/vscode-java-test-rc.yml +++ b/.azure-pipelines/vscode-java-test-rc.yml @@ -2,6 +2,7 @@ name: $(Date:yyyyMMdd).$(Rev:r) variables: - name: Codeql.Enabled value: true + - template: /.azure-pipelines/npm-cfs-variables.yml@self resources: repositories: - repository: self @@ -60,6 +61,7 @@ extends: jdkDestinationDirectory: $(Agent.ToolsDirectory)/ms-jdk21 - script: java --version displayName: 'Check Java installation' + - template: /.azure-pipelines/npm-cfs.yml@self - task: Npm@1 displayName: npm install inputs: