ci: Carry the CFS npm redirect in the environment - #1713
Merged
Conversation
Routing package restore through CFS by writing the registry into a generated user level .npmrc leaves the redirect at user config precedence, which npm resolves as cli > environment > project .npmrc > user .npmrc. Anything the agent image already configures therefore wins. Microsoft hosted images do ship a user level .npmrc pointing at an internal proxy, and a pool exporting npm_config_registry would override us outright. Restore would then resolve from somewhere other than CFS while the build still reported success, because npm does not log the registry it used and the package counts look identical either way. This was observed in vscode-gradle, where a build restored 718 packages against a CFS feed that cached nothing at all. Declare npm_config_registry as a pipeline variable so the redirect sits at environment precedence instead, where only an explicit command line flag can displace it. npm matches npm_config_* case insensitively, so the uppercased form Azure Pipelines exports applies on both the Windows and Linux pools, and to every restore path here: npm install, npx json, npx @vscode/vsce, and the vsce invocation inside AzureCLI@2. The generated .npmrc is still written, because NpmAuthenticate discovers the registries to authenticate by reading it. npm now takes the URL from the environment and the matching credential from that file. Move both variables into npm-cfs-variables.yml so the feed URL keeps living in exactly one place now that two variables describe it. Finally, assert the result rather than assuming it. A silent fallback to the public registry is the one failure this template exists to prevent and it leaves no trace in the build log, so the pipelines now fail if npm is not pointed at the CFS feed by the time restore begins.
wenytang-ms
requested review from
chagong,
jdneo and
testforstephen
as code owners
July 28, 2026 05:31
chagong
approved these changes
Jul 28, 2026
chagong
approved these changes
Jul 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow up to #1708 and #1711, applying what the equivalent change in microsoft/vscode-gradle#1901 turned up once it actually ran.
Problem
#1708 routed npm through CFS by generating a user level
.npmrcin the agent temp directory. npm resolves configuration in the orderso a registry supplied only through the user config sits at the second lowest precedence and is displaced by anything the agent image already configures. Microsoft hosted images do ship a user level
.npmrcpointing at an internal proxy, and a pool exportingnpm_config_registrywould override us outright.The consequence is not a build failure. Restore resolves from somewhere other than CFS, the packages install, and the build goes green — npm never logs which registry it used, and the package counts are identical either way.
This is not hypothetical. In vscode-gradle the same configuration produced a build that restored 718 packages against a CFS feed that had cached nothing. The only way to notice was to inspect the feed afterwards.
Change
Move the redirect to environment precedence.
npm_config_registryis declared as a pipeline variable, so only an explicit command line flag can displace it. npm matchesnpm_config_*case insensitively, so the uppercased form Azure Pipelines exports applies on both the Windows (VSEng-MicroBuildVSStable) and Linux (1ES_JavaTooling_Ubuntu-2004) pools, and to every restore path in these pipelines —npm install,npx json@9.0.6,npx @vscode/vsce, and thevsce publishinsideAzureCLI@2. None of those read a task input, so a variable is the only lever that reaches all of them.Keep generating the
.npmrc.NpmAuthenticate@0discovers the registries to authenticate by reading that file, so it still needs the registry written into it. npm now takes the URL from the environment and the matching credential from the file. Still nothing is committed, so outside contributors and.github/workflows/ci.ymlare unaffected and the token never lands in the workspace.Assert the outcome. A new
Verify CFS npm registrystep fails the build if npm is not pointed at the feed by the time restore begins. Silent fallback is the one failure this template exists to prevent and it leaves no trace in the log, so it is checked rather than assumed. Written in node — guaranteed by the precedingNodeTool@0— to avoid shell differences between the two pools.Consolidate the variables into
npm-cfs-variables.ymlso the feed URL keeps living in exactly one place now that two variables describe it.Validation
Template expansion, via the pipelines preview API on this branch. No builds queued.
1ES-CI (16481) is currently disabled and cannot be previewed. It carries the same two template references as the others.
Expanded step order on
1ES-Nightly:and the expanded variable block:
The verify script was run under
cmd.exeagainst both outcomes. With the machine's own user.npmrcin effect it reportshttps://packagefeedproxy.microsoft.io/npm/and exits 1; withnpm_config_registryset it reports the CFS feed and exits 0 — which also demonstrates the environment variable overriding a preconfigured user.npmrc, the exact case this change exists for.Runtime evidence comes from vscode-gradle, which carries the identical templates: after this change its build cached 525 of 525 npm packages into the
vscjavafeed within the build window, against a feed that held zero beforehand.package.jsonandpackage-lock.jsonare untouched, and no npm or npx command line changes.Not covered
Project Collection Build Service (mseng), which holds contributor onvscjava— confirmed, since all five definitions run withjobAuthScope=projectCollection.NodeTool@0/UseNode@1,APIScan@2withtoolVersion: Latest, and the MicroBuildMicroBuildToolsetNuGet feed are separate outbound fetches and are not addressed here.rc,releaseandrelease-nightlyaretrigger: none, and 1ES-CI is disabled, so onlynightlyruns on its own.