From 2a772f8856ec0bb88d9e7e7951a18c15c8c57df0 Mon Sep 17 00:00:00 2001 From: Caleb Ukle Date: Mon, 6 May 2024 08:41:47 -0500 Subject: [PATCH 1/4] feat(steps): add install-node step This step allows installing a defined version of node via nvm. This version can be passed to agents via the `NX_CLOUD_NODE_VERSION` environment variable or via populating a `.nvmrc` file in the workspace. ```yaml - name: Install Node v18 uses: 'nrwl/nx-cloud-workflows//workflow-steps/install-nodes/main.yaml' env: NX_CLOUD_NODE_VERSION: 18 ``` if a `.nvmrc` file exists in the workspace (via a checkout step), then the `NX_CLOUD_NODE_VERSION` can be omitted. ```yaml - name: Install Node v18 uses: 'nrwl/nx-cloud-workflows//workflow-steps/install-nodes/main.yaml' ``` --- workflow-steps/install-node/main.js | 36 +++++++++++++++++++++++++++ workflow-steps/install-node/main.yaml | 6 +++++ 2 files changed, 42 insertions(+) create mode 100644 workflow-steps/install-node/main.js create mode 100644 workflow-steps/install-node/main.yaml diff --git a/workflow-steps/install-node/main.js b/workflow-steps/install-node/main.js new file mode 100644 index 0000000..0a49929 --- /dev/null +++ b/workflow-steps/install-node/main.js @@ -0,0 +1,36 @@ +const { execSync } = require('child_process'); +const { existsSync } = require('fs'); + +// TODO: switch step to remove nvm instaall when nvm is in the base image +if (process.env.NX_CLOUD_NODE_VERSION || existsSync('.nvmrc')) { + try { + // have to run as single command to keep the nvm env vars in the same shell + + // nvm does not work with npm_config_prefix set, so unset it + const nvmSetupCommand = `unset NPM_CONFIG_PREFIX`; + const nvmInstallCommand = `curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash && source ~/.profile`; + // if NX_CLOUD_NODE_VERSION is set, use it, otherwise assume a .nvmrc file exists + const installNodeCommand = `nvm install ${ + process.env.NX_CLOUD_NODE_VERSION || '' + } --default`; + // nvm sets up the node path in the active shell + // we need to append it to the nx cloud env, so next steps will get the correct node version + const appendPathCommand = `echo "PATH=$PATH" >> ${process.env.NX_CLOUD_ENV}`; + + execSync( + `${nvmSetupCommand} && ${nvmInstallCommand} && ${installNodeCommand} && ${appendPathCommand}`, + { + stdio: 'inherit', + // use bash, since we need to call `source` to load nvm + shell: '/bin/bash', + }, + ); + } catch (e) { + console.error(e); + throw new Error( + `Failed to install node version using nvm ${ + process.env.NX_CLOUD_NODE_VERSION || '' + }`, + ); + } +} diff --git a/workflow-steps/install-node/main.yaml b/workflow-steps/install-node/main.yaml new file mode 100644 index 0000000..0640bc3 --- /dev/null +++ b/workflow-steps/install-node/main.yaml @@ -0,0 +1,6 @@ +name: Install Node +description: Install a specific version of Node.js via nvm + +definition: + using: 'node' + main: workflow-steps/install-node/main.js From c5e955c09900fd593e6d0887e8a4057f2c4aaf71 Mon Sep 17 00:00:00 2001 From: Caleb Ukle Date: Tue, 14 May 2024 12:41:17 -0500 Subject: [PATCH 2/4] use nvm from base image --- workflow-steps/install-node/main.js | 93 +++++++++++++++++++++------ workflow-steps/install-node/main.yaml | 3 + 2 files changed, 76 insertions(+), 20 deletions(-) diff --git a/workflow-steps/install-node/main.js b/workflow-steps/install-node/main.js index 0a49929..0134b72 100644 --- a/workflow-steps/install-node/main.js +++ b/workflow-steps/install-node/main.js @@ -1,36 +1,89 @@ const { execSync } = require('child_process'); -const { existsSync } = require('fs'); +const { existsSync, readFileSync } = require('fs'); -// TODO: switch step to remove nvm instaall when nvm is in the base image -if (process.env.NX_CLOUD_NODE_VERSION || existsSync('.nvmrc')) { +// Allow using inputs or env until we fully switch to inputs +const nodeVersionInput = + process.env.NX_CLOUD_INPUT_node_version || process.env.NODE_VERSION; + +// set defaults incase they are not set yet +process.env.NVM_DIR ??= '/home/workflows/.nvm'; +process.env.COREPACK_ENABLE_AUTO_PIN ??= 0; + +const maybeVoltaNodeVersion = getVoltaNodeVersion(); + +if (nodeVersionInput) { + runNvmInstall(nodeVersionInput); +} else if (isUsingNvm()) { + // nvm will auto detect version in .nvmrc, no need to pass version + runNvmInstall(null); +} else if (maybeVoltaNodeVersion) { + runNvmInstall(maybeVoltaNodeVersion); +} else { + console.warn( + `No node version specified. You can use the step inputs to define a node version.`, + ); + console.log( + `Falling back to the default node version in the base image. ${execSync( + 'node -v', + )}`, + ); +} + +function getVoltaNodeVersion() { try { - // have to run as single command to keep the nvm env vars in the same shell - - // nvm does not work with npm_config_prefix set, so unset it - const nvmSetupCommand = `unset NPM_CONFIG_PREFIX`; - const nvmInstallCommand = `curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash && source ~/.profile`; - // if NX_CLOUD_NODE_VERSION is set, use it, otherwise assume a .nvmrc file exists - const installNodeCommand = `nvm install ${ - process.env.NX_CLOUD_NODE_VERSION || '' + if (existsSync('package.json')) { + const packageJsonContents = + JSON.parse(readFileSync('package.json')) ?? {}; + + return packageJsonContents['volta']?.['node']; + } + } catch (e) { + return null; + } +} + +function isUsingNvm() { + try { + return existsSync('.nvmrc'); + } catch (e) { + return false; + } +} + +function runNvmInstall(version) { + try { + // enable nvm and then run the install command + // nvm command isn't available since nx agents don't run the bash profile + const installNodeWithNvm = `. $NVM_DIR/nvm.sh && nvm install ${ + version || '' } --default`; - // nvm sets up the node path in the active shell - // we need to append it to the nx cloud env, so next steps will get the correct node version - const appendPathCommand = `echo "PATH=$PATH" >> ${process.env.NX_CLOUD_ENV}`; + const reenableCorePack = `corepack enable`; + // install outside of the current directory, + // otherwise corepack errors if a different package mangager is used than is defined in the workspace + const reinstallPackageManagers = `cd .. && corepack prepare yarn@1 && corepack prepare pnpm@8`; + const printVersions = ['node', 'npm', 'yarn', 'pnpm'] + .map((cmd) => `echo "${cmd}: $(${cmd} -v)"`) + .join(' && '); + + // path will be updated via nvm to include the new node versions, + const saveEnvVars = `echo "PATH=$PATH\nNVM_DIR=${process.env.NVM_DIR}\nCOREPACK_ENABLE_AUTO_PIN=0" >> $NX_CLOUD_ENV`; execSync( - `${nvmSetupCommand} && ${nvmInstallCommand} && ${installNodeCommand} && ${appendPathCommand}`, + [ + installNodeWithNvm, + reenableCorePack, + reinstallPackageManagers, + printVersions, + saveEnvVars, + ].join(' && '), { stdio: 'inherit', - // use bash, since we need to call `source` to load nvm - shell: '/bin/bash', }, ); } catch (e) { console.error(e); throw new Error( - `Failed to install node version using nvm ${ - process.env.NX_CLOUD_NODE_VERSION || '' - }`, + `Failed to install node version using nvm ${version || ''}`, ); } } diff --git a/workflow-steps/install-node/main.yaml b/workflow-steps/install-node/main.yaml index 0640bc3..02110f1 100644 --- a/workflow-steps/install-node/main.yaml +++ b/workflow-steps/install-node/main.yaml @@ -1,5 +1,8 @@ name: Install Node description: Install a specific version of Node.js via nvm +inputs: + - name: node_version + description: 'The node version to be installed' definition: using: 'node' From 07735a20958fad0b3e6903cbd36fdd3da52c9e36 Mon Sep 17 00:00:00 2001 From: Caleb Ukle Date: Fri, 24 May 2024 09:59:47 -0500 Subject: [PATCH 3/4] add readme --- workflow-steps/install-node/README.md | 30 +++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 workflow-steps/install-node/README.md diff --git a/workflow-steps/install-node/README.md b/workflow-steps/install-node/README.md new file mode 100644 index 0000000..5c21e04 --- /dev/null +++ b/workflow-steps/install-node/README.md @@ -0,0 +1,30 @@ +## Usage + +```yaml +- name: Install Node + uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/install-node/main.yaml' + inputs: + node_version: '20' +``` + +### Options + +#### node_version + +The node version to be installed. Any valid [nvm](https://github.com/nvm-sh/nvm/blob/master/README.md#usage) version is accepted. +This input is optional, as the step will also check for a `.nvmrc` file in the root of the repository. +If the file is present, the step will install the version specified in the file. +If the file is not present, the step will not install any node version and leave the default installed node version on the image for subsequent steps + +For those using `volta`, the `volta.node` field in the `package.json` will also be picked up and used if present. + +The current order of precedence is: + +1. `node_version` input +1. `.nvmrc` file +1. `volta.node` field in `package.json` + +For example: + +- Install a specific node version: `node_version: '20.11.1'` +- Install latest of major node version: `node_version: '20'` From 1be76d3283b6db5d00e5f5fb22decdb2c92fbc7d Mon Sep 17 00:00:00 2001 From: Caleb Ukle Date: Fri, 24 May 2024 15:07:50 -0500 Subject: [PATCH 4/4] put OS check to prevent windows people from getting cryptic errors we'll follow up with support windows when we figure out what toolchain makes the most sense to use for custom node versions in windows --- workflow-steps/install-node/README.md | 3 + workflow-steps/install-node/main.js | 149 +++++++++++++------------- 2 files changed, 80 insertions(+), 72 deletions(-) diff --git a/workflow-steps/install-node/README.md b/workflow-steps/install-node/README.md index 5c21e04..fd78f85 100644 --- a/workflow-steps/install-node/README.md +++ b/workflow-steps/install-node/README.md @@ -1,5 +1,8 @@ ## Usage +> [!WARNING] +> This step current does not support Windows yet. + ```yaml - name: Install Node uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/install-node/main.yaml' diff --git a/workflow-steps/install-node/main.js b/workflow-steps/install-node/main.js index 0134b72..a299a5b 100644 --- a/workflow-steps/install-node/main.js +++ b/workflow-steps/install-node/main.js @@ -1,89 +1,94 @@ +const { platform } = require('os'); const { execSync } = require('child_process'); const { existsSync, readFileSync } = require('fs'); -// Allow using inputs or env until we fully switch to inputs -const nodeVersionInput = - process.env.NX_CLOUD_INPUT_node_version || process.env.NODE_VERSION; +if (platform === 'win32') { + throw new Error('Windows is not supported with this reuseable step yet.'); +} else { + // Allow using inputs or env until we fully switch to inputs + const nodeVersionInput = + process.env.NX_CLOUD_INPUT_node_version || process.env.NODE_VERSION; -// set defaults incase they are not set yet -process.env.NVM_DIR ??= '/home/workflows/.nvm'; -process.env.COREPACK_ENABLE_AUTO_PIN ??= 0; + // set defaults incase they are not set yet + process.env.NVM_DIR ??= '/home/workflows/.nvm'; + process.env.COREPACK_ENABLE_AUTO_PIN ??= 0; -const maybeVoltaNodeVersion = getVoltaNodeVersion(); + const maybeVoltaNodeVersion = getVoltaNodeVersion(); -if (nodeVersionInput) { - runNvmInstall(nodeVersionInput); -} else if (isUsingNvm()) { - // nvm will auto detect version in .nvmrc, no need to pass version - runNvmInstall(null); -} else if (maybeVoltaNodeVersion) { - runNvmInstall(maybeVoltaNodeVersion); -} else { - console.warn( - `No node version specified. You can use the step inputs to define a node version.`, - ); - console.log( - `Falling back to the default node version in the base image. ${execSync( - 'node -v', - )}`, - ); -} + if (nodeVersionInput) { + runNvmInstall(nodeVersionInput); + } else if (isUsingNvm()) { + // nvm will auto detect version in .nvmrc, no need to pass version + runNvmInstall(null); + } else if (maybeVoltaNodeVersion) { + runNvmInstall(maybeVoltaNodeVersion); + } else { + console.warn( + `No node version specified. You can use the step inputs to define a node version.`, + ); + console.log( + `Falling back to the default node version in the base image. ${execSync( + 'node -v', + )}`, + ); + } -function getVoltaNodeVersion() { - try { - if (existsSync('package.json')) { - const packageJsonContents = - JSON.parse(readFileSync('package.json')) ?? {}; + function getVoltaNodeVersion() { + try { + if (existsSync('package.json')) { + const packageJsonContents = + JSON.parse(readFileSync('package.json')) ?? {}; - return packageJsonContents['volta']?.['node']; + return packageJsonContents['volta']?.['node']; + } + } catch (e) { + return null; } - } catch (e) { - return null; } -} -function isUsingNvm() { - try { - return existsSync('.nvmrc'); - } catch (e) { - return false; + function isUsingNvm() { + try { + return existsSync('.nvmrc'); + } catch (e) { + return false; + } } -} -function runNvmInstall(version) { - try { - // enable nvm and then run the install command - // nvm command isn't available since nx agents don't run the bash profile - const installNodeWithNvm = `. $NVM_DIR/nvm.sh && nvm install ${ - version || '' - } --default`; - const reenableCorePack = `corepack enable`; - // install outside of the current directory, - // otherwise corepack errors if a different package mangager is used than is defined in the workspace - const reinstallPackageManagers = `cd .. && corepack prepare yarn@1 && corepack prepare pnpm@8`; - const printVersions = ['node', 'npm', 'yarn', 'pnpm'] - .map((cmd) => `echo "${cmd}: $(${cmd} -v)"`) - .join(' && '); + function runNvmInstall(version) { + try { + // enable nvm and then run the install command + // nvm command isn't available since nx agents don't run the bash profile + const installNodeWithNvm = `. $NVM_DIR/nvm.sh && nvm install ${ + version || '' + } --default`; + const reenableCorePack = `corepack enable`; + // install outside of the current directory, + // otherwise corepack errors if a different package mangager is used than is defined in the workspace + const reinstallPackageManagers = `cd .. && corepack prepare yarn@1 && corepack prepare pnpm@8`; + const printVersions = ['node', 'npm', 'yarn', 'pnpm'] + .map((cmd) => `echo "${cmd}: $(${cmd} -v)"`) + .join(' && '); - // path will be updated via nvm to include the new node versions, - const saveEnvVars = `echo "PATH=$PATH\nNVM_DIR=${process.env.NVM_DIR}\nCOREPACK_ENABLE_AUTO_PIN=0" >> $NX_CLOUD_ENV`; + // path will be updated via nvm to include the new node versions, + const saveEnvVars = `echo "PATH=$PATH\nNVM_DIR=${process.env.NVM_DIR}\nCOREPACK_ENABLE_AUTO_PIN=0" >> $NX_CLOUD_ENV`; - execSync( - [ - installNodeWithNvm, - reenableCorePack, - reinstallPackageManagers, - printVersions, - saveEnvVars, - ].join(' && '), - { - stdio: 'inherit', - }, - ); - } catch (e) { - console.error(e); - throw new Error( - `Failed to install node version using nvm ${version || ''}`, - ); + execSync( + [ + installNodeWithNvm, + reenableCorePack, + reinstallPackageManagers, + printVersions, + saveEnvVars, + ].join(' && '), + { + stdio: 'inherit', + }, + ); + } catch (e) { + console.error(e); + throw new Error( + `Failed to install node version using nvm ${version || ''}`, + ); + } } }