Use Npm scopes in Azure Artifacts

Azure DevOps Services | Azure DevOps Server 2022 - Azure DevOps Server 2019

Npm scopes serve as a means to categorize related packages into groups. These scopes enable you to create packages with identical names to those created by different users without encountering conflicts. By using scopes, you have the ability to segregate public and private packages by adding the scope prefix @scopeName and configuring the .npmrc file to exclusively use a feed with that particular scope.

Azure Artifacts provides the capability to publish and download both scoped and nonscoped packages from feeds or public registries. Npm scopes are particularly valuable when working with self-hosted on-premises servers lacking internet access, as configuring upstream sources in such scenarios isn't feasible. In summary, when using scopes:

  • We don't have to worry about name collisions.
  • No need to change the npm registry in order to install or publish our packages.
  • Each npm organization/user has their own scope, and only the owner or the scope members can publish packages to their scope.

Project setup

  1. Sign in to your Azure DevOps organization, and then navigate to your project.

  2. Select Artifacts, and then select Connect to feed.

    A screenshot showing how to connect to a feed.

  3. Select npm, and then select Other.

  4. Add a .npmrc file in the same directory as your package.json, and paste the following snippet into your file.

    • Organization-scoped feed:

      registry=https://pkgs.dev.azure.com/<ORGANIZATION_NAME/_packaging/<FEED_NAME>/npm/registry/
      
      always-auth=true
      
    • Project-scoped feed:

      registry=https://pkgs.dev.azure.com/<ORGANIZATION_NAME>/<PROJECT_NAME>/_packaging/<FEED_NAME>/npm/registry/
      
      always-auth=true
      

Set up credentials

  1. Copy the following snippet into your user-level .npmrc file, (Example: C:\Users\FabrikamUser.npmrc). Make sure you don't paste it into the .npmrc file within your source repository.

    • Organization-scoped feed:

      ; begin auth token
      //pkgs.dev.azure.com/<ORGANIZATION_NAME>/_packaging/<FEED_NAME>/npm/registry/:username=[ENTER_ANY_VALUE_BUT_NOT_AN_EMPTY_STRING]
      //pkgs.dev.azure.com/<ORGANIZATION_NAME>/_packaging/<FEED_NAME>/npm/registry/:_password=[BASE64_ENCODED_PERSONAL_ACCESS_TOKEN]
      //pkgs.dev.azure.com/<ORGANIZATION_NAME>/_packaging/<FEED_NAME>/npm/registry/:email=npm requires email to be set but doesn't use the value
      //pkgs.dev.azure.com/<ORGANIZATION_NAME>/_packaging/<FEED_NAME>/npm/:username=[ANY_VALUE_BUT_NOT_AN_EMPTY_STRING]
      //pkgs.dev.azure.com/<ORGANIZATION_NAME>/_packaging/<FEED_NAME>/npm/:_password=[BASE64_ENCODED_PERSONAL_ACCESS_TOKEN]
      //pkgs.dev.azure.com/<ORGANIZATION_NAME>/_packaging/<FEED_NAME>/npm/:email=npm requires email to be set but doesn't use the value
      ; end auth token
      
    • Project-scoped feed:

      ; begin auth token
      //pkgs.dev.azure.com/<ORGANIZATION_NAME>/<PROJECT_NAME>/_packaging/<FEED_NAME>/npm/registry/:username=[ENTER_ANY_VALUE_BUT_NOT_AN_EMPTY_STRING]
      //pkgs.dev.azure.com/<ORGANIZATION_NAME>/<PROJECT_NAME>/_packaging/<FEED_NAME>/npm/registry/:_password=[BASE64_ENCODED_PERSONAL_ACCESS_TOKEN]
      //pkgs.dev.azure.com/<ORGANIZATION_NAME>/<PROJECT_NAME>/_packaging/<FEED_NAME>/npm/registry/:email=npm requires email to be set but doesn't use the value
      //pkgs.dev.azure.com/<ORGANIZATION_NAME>/<PROJECT_NAME>/_packaging/<FEED_NAME>/npm/:username=[ENTER_ANY_VALUE_BUT_NOT_AN_EMPTY_STRING]
      //pkgs.dev.azure.com/<ORGANIZATION_NAME>/<PROJECT_NAME>/_packaging/<FEED_NAME>/npm/:_password=[BASE64_ENCODED_PERSONAL_ACCESS_TOKEN]
      //pkgs.dev.azure.com/<ORGANIZATION_NAME>/<PROJECT_NAME>/_packaging/<FEED_NAME>/npm/:email=npm requires email to be set but doesn't use the value
      ; end auth token
      
  2. Generate a personal access token with Packaging > Read & write scopes.

  3. Run the following command to encode your newly generated personal access token. When prompted, paste your personal access token and then copy the resulting Base64 encoded value.

    node -e "require('readline') .createInterface({input:process.stdin,output:process.stdout,historySize:0}) .question('PAT> ',p => { b64=Buffer.from(p.trim()).toString('base64');console.log(b64);process.exit(); })"
    
  4. Open your .npmrc file and replace the placeholder [BASE64_ENCODED_PERSONAL_ACCESS_TOKEN] with your encoded personal access token you just created.

Scope setup

In your .npmrc file, replace registry=<YOUR_SOURCE_URL> with @ScopeName:registry=<YOUR_SOURCE_URL>.

Make sure to include both the scope and package names in your package.json file as follows: { "name": "@ScopeName/PackageName" }. See the examples below:

  • Organization-scoped feed:

    @ScopeName:registry=https://pkgs.dev.azure.com/<ORGANIZATION_NAME>/_packaging/<FEED_NAME>/npm/registry/
    
    always-auth=true
    
    {
    "name": "@ScopeName/PackageName" 
    }
    
  • Project-scoped feed:

    @ScopeName:registry=https://pkgs.dev.azure.com/<ORGANIZATION_NAME>/<PROJECT_NAME>/_packaging/<FEED_NAME>/npm/registry/
    
    always-auth=true
    
    {
    "name": "@ScopeName/PackageName" 
    }
    
  • Example:

    @local:registry=https://pkgs.dev.azure.com/FabrikamOrg/NpmDemo/_packaging/FabrikamFeed/npm/registry/
    
    always-auth=true
    
    {
      "name": "@demo/js-e2e-express-server",
      "version": "2.0.0",
      "description": "JavaScript server written with Express.js",
      "main": "index.js",
      "directories": {
        "doc": "docs",
        "test": "test"
      }
    

Publish scoped packages

Open a command prompt window, navigate to your project directory, and run the following command to publish your scoped package. In our example, our package is listed under the @local view.

npm publish

A screenshot showing a scoped package in an Azure Artifacts feed.

Upstream sources vs scopes

Upstream sources give you the most flexibility to use a combination of scoped and nonscoped packages in your feed, as well as both scoped and nonscoped packages from public registries like npmjs.com.

Scopes, however, impose a naming restriction on your packages: each package name must start with @<scope>. If you want to publish your private packages to public registries, you must do so with the scopes intact. If you remove package scopes when deploying your packages, you'll need to update all the references in your package.json file. With that in mind, scopes can serve as a viable alternative to upstream sources.