Azure DevOps 专用代理 - 如何使用 nvm 安装节点

Azure DevOps private agent - how to install node using nvm

我正尝试在 Ubuntu 上为 Azure DevOps 设置私有构建代理。我需要使用 npm 任务进行构建。

我尝试使用nvm安装最新的node,安装成功:

curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.1/install.sh | bash
nvm install 11.10.1

我可以检查 node -vnpm -v。但是当从管道执行 npm 任务时 - 它失败并显示

Unable to locate executable file: 'npm'. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also check the file mode to verify the file is executable.

在我的 PATH 中有 /usr/local/nvm/versions/node/v11.10.1/binls -l 显示:

lrwxrwxrwx 1 500 500 38 Feb 28 06:00 /usr/local/nvm/versions/node/v11.10.1/bin/npm -> ../lib/node_modules/npm/bin/npm-cli.js

我还为 npm-cli.js 添加了 777(只是为了尝试!),但仍然没有成功。

我也发现了这个类似的问题 - https://github.com/Microsoft/azure-pipelines-agent/issues/1862

如何在 Ubuntu Azure DevOps 代理上使用 nvm 正确安装节点和 npm?

作为临时解决方案,我安装了节点

curl -sL https://deb.nodesource.com/setup_11.x | sudo -E bash -
sudo apt-get install nodejs

而不是 nvm,它工作正常。

为了让它工作,我最终创建了一个代理到 nvm 的 bash 脚本,然后告诉代理将 PATH 变量更新为当前 NVM。

/usr/local/bin/nvm 创建一个包含以下内容的文件:

#!/usr/bin/env bash
# Store the current path so we can check to see if it changes
OLD_PATH=$PATH

# Normal NVM stuff
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm

# Execute NVM and pass all the arguments
nvm $@

# Check to see if the path has changed, and if an azure agent is running the
# command by checking the existence of the $AGENT_VERSION variable.
if [[ "$OLD_PATH" != "$PATH" && ! -z ${AGENT_VERSION+x} ]]; then
    # Grab the current node executable Something like:
    #  /Users/digitaldev/.nvm/versions/node/v10.16.3/bin
    CURRENT_NODE=$(nvm which current)
    # Resolve to directory of the node executable Something like
    # /Users/digitaldev/.nvm/versions/node/v10.16.3
    BIN_DIR=$(dirname "$CURRENT_NODE")
    # Tell Azure Pipeline to update the PATH [1]
    echo "##vso[task.prependpath]$BIN_DIR"
fi

使文件可执行:

chmod +x /usr/local/bin/nvm

现在,当 Azure Pipeline 运行时 nvm 它将更新路径以查找节点的 nvm 版本。

[1] Logging Commands PrependPath

只有 nvm

我根据 nvm.sh 的说明创建了一个这样的模板,并相应地设置了 NVM_DIR 和路径。

steps:
  - bash: |
      curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.36.0/install.sh | bash
      export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
      [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
      nvm install
      nvm use
      echo "##vso[task.setvariable variable=NVM_DIR;]$NVM_DIR"
      echo "##vso[task.setvariable variable=PATH;]$PATH"
    displayName: "Install Node.js"

我还添加了 .nvmrc,如 nvm.sh 中所述,以便指定要使用的节点版本。

然后我在我的管道中使用它如下

steps:
  - template: templates/install-node-js.yml
  - bash: |
      node --version

具有缓存和节点依赖性

这是我安装 node 的模板,也对 package-lock.json 中的包进行缓存。

steps:
  - task: Cache@2
    inputs:
      key: 'nvm | "$(Agent.OS)" | .nvmrc'
      path: $(Pipeline.Workspace)/.nvm
    displayName: "Cache NVM"
  - task: Cache@2
    inputs:
      key: 'npm | "$(Agent.OS)" | package-lock.json'
      path: $(Build.SourcesDirectory)/node_modules
    displayName: "Cache node dependencies"
  - bash: |
      set -e
      if [ $(System.Debug) ]
      then
        set -x
      fi
      if [ ! -s $NVM_DIR/nvm.sh ]
      then
        mkdir -p $NVM_DIR
        curl -so- https://raw.githubusercontent.com/nvm-sh/nvm/v0.36.0/install.sh | bash
      fi
      . $NVM_DIR/nvm.sh || true
      nvm install
      nvm use
      npm ci
      echo "##vso[task.setvariable variable=PATH;]$PATH"
    env:
      NVM_DIR: $(Pipeline.Workspace)/.nvm
    displayName: Install Node and dependencies

Azure DevOps 已经在他们的 Linux 和 macOS 构建代理中包含 nvm 一段时间了。

如果您想利用他们所做的工作来创建您自己的代理,此存储库包含他们使用的所有代码。

https://github.com/actions/virtual-environments/

Ubuntu Linux 代理正在使用 Packer (https://packer.io) 来配置 VM。

https://github.com/actions/virtual-environments/tree/main/images/linux

我们使用 Azure 托管代理和类似的东西来设置 Node 项目(或 React Native/Cordova/Ionic 移动构建),最重要的部分是任何时候你想使用 nvm 或它安装的包(项目或全局范围)你需要 . ${NVM_DIR}/nvm.sh.

    steps:
     - script: |
        # Setup node using nvm
        # NODE_VERSION=12         # or whatever your preferred version is
        # We are using .nvmrc instead of specific version
        npm config delete prefix  # avoid a warning
        . ${NVM_DIR}/nvm.sh       # This is the most important bit to utilize nvm
        nvm install # uses .nvmrc implicitly
        nvm use
        nvm alias default $(cat .nvmrc) 
        # nvm alias can understand eg. lts/erbium better than 
        # nvm_version_path which can't resolve the lts/* codenames
        VERSION_PATH="$(nvm_version_path $(cat $(nvm_alias_path)/$(cat $(nvm_alias_path)/default)))"
        which nvm
        # This will inject the node version we told nvm was default to the $PATH
        echo "##vso[task.prependPath]$VERSION_PATH"
        # Optionally upgrade npm here or using `nvm install-latest-npm`
        #npm install -g npm # upgrades to latest that supports lockfileVersion@2
        npm install