diff --git a/launch-templates/linux.yaml b/launch-templates/linux.yaml index 381d0b8..aaf6085 100644 --- a/launch-templates/linux.yaml +++ b/launch-templates/linux.yaml @@ -40,6 +40,51 @@ common-js-init-steps: &common-js-init-steps - name: Install Browsers (if needed) uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/install-browsers/main.yaml' +common-universal-init-steps: &common-universal-init-steps + - name: Checkout + uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/checkout/main.yaml' + - name: Restore Node Modules Cache + uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/cache/main.yaml' + inputs: + key: 'package-lock.json|yarn.lock|pnpm-lock.yaml|patches/**|.yarn/patches/**' + paths: 'node_modules' + base-branch: 'main' + - name: Restore Browser Binary Cache + uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/cache/main.yaml' + inputs: + key: 'package-lock.json|yarn.lock|pnpm-lock.yaml|"browsers"' + paths: | + '../.cache/Cypress' + base-branch: 'main' + - name: Restore Rust Cache + uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/cache/main.yaml' + inputs: + key: '"2" | Cargo.lock | rust-toolchain.toml' + paths: '~/.cargo' + base-branch: 'main' + - name: Restore Gradle Dependencies Cache + uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/cache/main.yaml' + inputs: + key: '"1" | gradle/wrapper/gradle-wrapper.properties | gradle/libs.versions.toml | gradle.properties | settings.gradle.kts | build.gradle.kts' + paths: '~/.gradle/caches' + base-branch: 'main' + - name: Restore Gradle Wrapper Cache + uses: 'nrwl/nx-cloud-workflows/v5/workflow-steps/cache/main.yaml' + inputs: + key: '"1" | gradle/wrapper/gradle-wrapper.properties' + paths: '~/.gradle/wrapper' + base-branch: 'main' + - name: Install Mise (if config present) + uses: 'nrwl/nx-cloud-workflows/8997d86c066a752fc445b711e49b083c21816304/workflow-steps/install-mise-if-present/main.yaml' + - name: Install Rust (if Rust project) + uses: 'nrwl/nx-cloud-workflows/8997d86c066a752fc445b711e49b083c21816304/workflow-steps/install-rust-if-present/main.yaml' + - name: Install Node (if JS project) + uses: 'nrwl/nx-cloud-workflows/8997d86c066a752fc445b711e49b083c21816304/workflow-steps/install-node-if-present/main.yaml' + - name: Install Node Modules (if lock file) + uses: 'nrwl/nx-cloud-workflows/8997d86c066a752fc445b711e49b083c21816304/workflow-steps/install-node-modules-if-present/main.yaml' + - name: Install Java (if JVM project) + uses: 'nrwl/nx-cloud-workflows/8997d86c066a752fc445b711e49b083c21816304/workflow-steps/install-java-if-present/main.yaml' + launch-templates: linux-small-js: resource-class: 'docker_linux_amd64/small' @@ -125,3 +170,7 @@ launch-templates: resource-class: 'docker_linux_amd64/extra_large+' image: 'ubuntu22.04-node20.19-v2' init-steps: *common-dotnet-init-steps + universal-large: + resource-class: 'docker_linux_amd64/large' + image: 'ubuntu22.04-node20.19-v2' + init-steps: *common-universal-init-steps diff --git a/workflow-steps/install-java-if-present/main.js b/workflow-steps/install-java-if-present/main.js new file mode 100644 index 0000000..3272e2d --- /dev/null +++ b/workflow-steps/install-java-if-present/main.js @@ -0,0 +1,148 @@ +const { platform, arch, tmpdir } = require('os'); +const { execSync } = require('child_process'); +const { existsSync, writeFileSync } = require('fs'); + +const jvmMarkers = [ + 'build.gradle', + 'build.gradle.kts', + 'settings.gradle', + 'settings.gradle.kts', + 'pom.xml', +]; + +if (!jvmMarkers.some((f) => existsSync(f))) { + console.log( + 'No JVM project detected (no build.gradle, build.gradle.kts, settings.gradle*, or pom.xml). Skipping Java install.', + ); + return; +} + +if ( + existsSync('mise.toml') || + existsSync('.mise.toml') || + existsSync('.tool-versions') +) { + console.log( + 'Mise config detected — Java is expected to be managed via mise. Skipping auto java= install to avoid overwriting the user config.', + ); + return; +} + +const javaVersion = process.env.NX_CLOUD_INPUT_java_version || '21'; +console.log(`JVM project detected. Installing Java ${javaVersion} via mise...`); + +// Write a mise.toml so the inlined mise install picks up the tool. +writeFileSync('mise.toml', `[tools]\njava = "${javaVersion}"\n`, { + encoding: 'utf-8', +}); + +// --- inlined install-mise logic --- + +const miseGhVersion = + process.env['NX_CLOUD_INPUT_mise-version'] || 'v2025.12.2'; +const installArgs = process.env['NX_CLOUD_INPUT_install-args'] || ''; + +const MISE_INSTALL_DIR = '$HOME/.local/bin'; +const MISE_SHIM_DIR = '$HOME/.local/share/mise/shims'; +const TMP_DIR = tmpdir(); + +process.env.MISE_TRUSTED_CONFIG_PATHS = process.cwd(); +process.env.MISE_YES = '1'; + +function retryWithBackoff( + fn, + maxRetries = 3, + retryDelayMs = 1000, + fnName = 'operation', +) { + let lastError; + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + fn(); + if (attempt > 1) + console.log(`> ${fnName} succeeded on attempt ${attempt}`); + return; + } catch (error) { + lastError = error; + console.error(`> ${fnName} failed on attempt ${attempt}/${maxRetries}`); + console.error(` Error: ${error.message}`); + if (attempt < maxRetries) { + const delay = retryDelayMs * Math.pow(2, attempt - 1); + console.log(`> Retrying in ${delay}ms...`); + execSync(`sleep ${delay / 1000}`, { stdio: 'inherit' }); + } + } + } + throw lastError; +} + +if (!miseGhVersion.startsWith('v')) { + console.error(`Invalid mise_version: ${miseGhVersion}`); + process.exit(1); +} + +function getVersionUrl(_platform, _arch) { + let resolvedPlatform; + let resolvedArch; + if (_platform === 'darwin') resolvedPlatform = 'macos'; + else if (_platform === 'win32') resolvedPlatform = 'windows'; + else resolvedPlatform = 'linux'; + + if (_arch === 'x64' || _arch === 'arm64') resolvedArch = _arch; + else { + console.error('Unsupported architecture: ', _arch); + process.exit(1); + } + + const version = `${miseGhVersion}-${resolvedPlatform}-${resolvedArch}`; + const url = `https://github.com/jdx/mise/releases/download/${miseGhVersion}/mise-${version}.tar.gz`; + console.log(`> Resolved mise version to download as: ${version}`); + return url; +} + +function downloadMise(downloadUrl) { + console.log(`> Downloading mise from ${downloadUrl}`); + execSync(`mkdir -p ${MISE_INSTALL_DIR}`, { stdio: 'inherit' }); + retryWithBackoff( + () => { + execSync(`curl -fsSL ${downloadUrl} | tar -xzf - -C ${TMP_DIR}`, { + stdio: 'inherit', + }); + }, + 3, + 1000, + 'mise download', + ); + execSync(`mv ${TMP_DIR}/mise/bin/mise ${MISE_INSTALL_DIR}/mise`, { + stdio: 'inherit', + }); +} + +function setupMise(installCommandArgs) { + execSync(`chmod +x ${MISE_INSTALL_DIR}/mise`, { stdio: 'inherit' }); + execSync(`mkdir -p ${MISE_SHIM_DIR}`, { stdio: 'inherit' }); + + const newPATH = `${MISE_INSTALL_DIR}:${MISE_SHIM_DIR}:$PATH`; + const setPath = `export PATH="${newPATH}" && echo PATH="${newPATH}" >> $NX_CLOUD_ENV`; + const setMiseEnvVars = `echo "MISE_YES=1" >> $NX_CLOUD_ENV && echo "MISE_TRUSTED_CONFIG_PATHS=$HOME/workspace" >> $NX_CLOUD_ENV`; + const whichMise = `echo "mise is located at $(which mise)"`; + const miseVersion = `echo "mise version is: $(mise version)"`; + const installMiseTools = `mise install ${installCommandArgs}`; + + execSync( + [setPath, setMiseEnvVars, whichMise, miseVersion, installMiseTools].join( + ' && ', + ), + { stdio: 'inherit' }, + ); +} + +try { + const dlUrl = getVersionUrl(platform(), arch()); + downloadMise(dlUrl); + setupMise(installArgs); +} catch (error) { + console.error('> Failed to install Java via mise:'); + console.error(error); + process.exit(1); +} diff --git a/workflow-steps/install-java-if-present/main.yaml b/workflow-steps/install-java-if-present/main.yaml new file mode 100644 index 0000000..559efdb --- /dev/null +++ b/workflow-steps/install-java-if-present/main.yaml @@ -0,0 +1,10 @@ +name: Install Java (if JVM project present) +description: Installs Java via mise when a Gradle or Maven project is detected (build.gradle, build.gradle.kts, settings.gradle*, or pom.xml). Otherwise skips. +inputs: + - name: java_version + description: 'Java version to install (passed to mise as java=).' + default: '21' + +definition: + using: 'node' + main: workflow-steps/install-java-if-present/main.js diff --git a/workflow-steps/install-mise-if-present/main.js b/workflow-steps/install-mise-if-present/main.js new file mode 100644 index 0000000..0a058b7 --- /dev/null +++ b/workflow-steps/install-mise-if-present/main.js @@ -0,0 +1,133 @@ +const { platform, arch, tmpdir } = require('os'); +const { execSync } = require('child_process'); +const { existsSync } = require('fs'); + +const miseMarkers = ['mise.toml', '.mise.toml', '.tool-versions']; + +if (!miseMarkers.some((f) => existsSync(f))) { + console.log( + 'No mise config detected (no mise.toml, .mise.toml, or .tool-versions). Skipping mise install.', + ); + return; +} + +// --- inlined install-mise logic (minus setToolVersions, since we use the +// existing mise config file from the repo) --- + +const runInstall = process.env['NX_CLOUD_INPUT_auto-install'] + ? process.env['NX_CLOUD_INPUT_auto-install'] === 'true' + : true; +const miseGhVersion = + process.env['NX_CLOUD_INPUT_mise-version'] || 'v2025.12.2'; +const installArgs = process.env['NX_CLOUD_INPUT_install-args'] || ''; + +const MISE_INSTALL_DIR = '$HOME/.local/bin'; +const MISE_SHIM_DIR = '$HOME/.local/share/mise/shims'; +const TMP_DIR = tmpdir(); + +process.env.MISE_TRUSTED_CONFIG_PATHS = process.cwd(); +process.env.MISE_YES = '1'; + +function retryWithBackoff( + fn, + maxRetries = 3, + retryDelayMs = 1000, + fnName = 'operation', +) { + let lastError; + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + fn(); + if (attempt > 1) + console.log(`> ${fnName} succeeded on attempt ${attempt}`); + return; + } catch (error) { + lastError = error; + console.error(`> ${fnName} failed on attempt ${attempt}/${maxRetries}`); + console.error(` Error: ${error.message}`); + if (attempt < maxRetries) { + const delay = retryDelayMs * Math.pow(2, attempt - 1); + console.log(`> Retrying in ${delay}ms...`); + execSync(`sleep ${delay / 1000}`, { stdio: 'inherit' }); + } + } + } + console.error(`> ${fnName} failed after ${maxRetries} attempts`); + throw lastError; +} + +if (!miseGhVersion.startsWith('v')) { + console.error(`Invalid mise_version: ${miseGhVersion}`); + process.exit(1); +} + +function getVersionUrl(_platform, _arch) { + let resolvedPlatform; + let resolvedArch; + if (_platform === 'darwin') resolvedPlatform = 'macos'; + else if (_platform === 'win32') resolvedPlatform = 'windows'; + else resolvedPlatform = 'linux'; + + if (_arch === 'x64' || _arch === 'arm64') resolvedArch = _arch; + else { + console.error('Unsupported architecture: ', _arch); + process.exit(1); + } + + const version = `${miseGhVersion}-${resolvedPlatform}-${resolvedArch}`; + const url = `https://github.com/jdx/mise/releases/download/${miseGhVersion}/mise-${version}.tar.gz`; + console.log(`> Resolved mise version to download as: ${version}`); + return url; +} + +function downloadMise(downloadUrl) { + console.log(`> Downloading mise from ${downloadUrl}`); + execSync(`mkdir -p ${MISE_INSTALL_DIR}`, { stdio: 'inherit' }); + retryWithBackoff( + () => { + execSync(`curl -fsSL ${downloadUrl} | tar -xzf - -C ${TMP_DIR}`, { + stdio: 'inherit', + }); + }, + 3, + 1000, + 'mise download', + ); + console.log(`> Download successful! Moving binary to: ${MISE_INSTALL_DIR}`); + execSync(`mv ${TMP_DIR}/mise/bin/mise ${MISE_INSTALL_DIR}/mise`, { + stdio: 'inherit', + }); +} + +function setupMise(autoInstall, installCommandArgs) { + console.log('> Setting up mise...\n\n'); + execSync(`chmod +x ${MISE_INSTALL_DIR}/mise`, { stdio: 'inherit' }); + execSync(`mkdir -p ${MISE_SHIM_DIR}`, { stdio: 'inherit' }); + + const newPATH = `${MISE_INSTALL_DIR}:${MISE_SHIM_DIR}:$PATH`; + const setPath = `export PATH="${newPATH}" && echo PATH="${newPATH}" >> $NX_CLOUD_ENV`; + const setMiseEnvVars = `echo "MISE_YES=1" >> $NX_CLOUD_ENV && echo "MISE_TRUSTED_CONFIG_PATHS=$HOME/workspace" >> $NX_CLOUD_ENV`; + const whichMise = `echo "mise is located at $(which mise)"`; + const miseVersion = `echo "mise version is: $(mise version)"`; + const installMiseTools = autoInstall + ? `mise install ${installCommandArgs}` + : `echo "Skipping auto install. You will need to manually run mise install"`; + + execSync( + [setPath, setMiseEnvVars, whichMise, miseVersion, installMiseTools].join( + ' && ', + ), + { stdio: 'inherit' }, + ); + console.log('\n> Finished setting up mise!'); +} + +try { + const dlUrl = getVersionUrl(platform(), arch()); + downloadMise(dlUrl); + setupMise(runInstall, installArgs); +} catch (error) { + console.error('> Failed to install mise: '); + console.error(error); + process.exit(1); +} diff --git a/workflow-steps/install-mise-if-present/main.yaml b/workflow-steps/install-mise-if-present/main.yaml new file mode 100644 index 0000000..c9e15cf --- /dev/null +++ b/workflow-steps/install-mise-if-present/main.yaml @@ -0,0 +1,6 @@ +name: Install Mise (if mise config present) +description: Installs mise and any tools defined in mise.toml, .mise.toml, or .tool-versions. Skips if no config is detected. + +definition: + using: 'node' + main: workflow-steps/install-mise-if-present/main.js diff --git a/workflow-steps/install-node-if-present/main.js b/workflow-steps/install-node-if-present/main.js new file mode 100644 index 0000000..6a61e41 --- /dev/null +++ b/workflow-steps/install-node-if-present/main.js @@ -0,0 +1,124 @@ +const { platform } = require('os'); +const { execSync } = require('child_process'); +const { existsSync, readFileSync } = require('fs'); + +const jsMarkers = [ + 'package.json', + '.nvmrc', + 'package-lock.json', + 'yarn.lock', + 'pnpm-lock.yaml', + 'pnpm-lock.yml', +]; + +if (!jsMarkers.some((f) => existsSync(f))) { + console.log( + 'No JS project detected (no package.json, .nvmrc, or lock file). Skipping Node install.', + ); + return; +} + +async function main() { + if (platform === 'win32') { + throw new Error('Windows is not supported with this reuseable step yet.'); + } + + const nodeVersionInput = + process.env.NX_CLOUD_INPUT_node_version || process.env.NODE_VERSION; + const maxRetries = process.env.NX_CLOUD_INPUT_max_retries || 3; + + process.env.NVM_DIR ??= '/home/workflows/.nvm'; + process.env.COREPACK_ENABLE_AUTO_PIN ??= 0; + + const maybeVoltaNodeVersion = getVoltaNodeVersion(); + + if (nodeVersionInput) { + await runNvmInstall(nodeVersionInput, maxRetries); + } else if (isUsingNvm()) { + await runNvmInstall(null, maxRetries); + } else if (maybeVoltaNodeVersion) { + await runNvmInstall(maybeVoltaNodeVersion, maxRetries); + } 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')) ?? {}; + return packageJsonContents['volta']?.['node']; + } + } catch (e) { + return null; + } +} + +function isUsingNvm() { + try { + return existsSync('.nvmrc'); + } catch (e) { + return false; + } +} + +async function runNvmInstall(version, maxRetries = 3) { + const installNodeWithNvm = `. $NVM_DIR/nvm.sh && nvm install -b ${ + version || '' + } --default`; + const reenableCorePack = `npm install -g corepack@latest && corepack enable`; + const reinstallPackageManagers = `cd .. && corepack prepare yarn@1 && corepack prepare pnpm@9`; + const printVersions = ['node', 'npm', 'yarn', 'pnpm'] + .map((cmd) => `echo "${cmd}: $(${cmd} -v)"`) + .join(' && '); + const saveEnvVars = `echo "PATH=$PATH\nNVM_DIR=${process.env.NVM_DIR}\nCOREPACK_ENABLE_AUTO_PIN=0" >> $NX_CLOUD_ENV`; + const run = () => + execSync( + [ + installNodeWithNvm, + reenableCorePack, + reinstallPackageManagers, + printVersions, + saveEnvVars, + ].join(' && '), + { stdio: 'inherit' }, + ); + + let retryCount = 0; + while (retryCount < maxRetries) { + try { + run(); + break; + } catch (e) { + retryCount++; + if (retryCount >= maxRetries) { + throw new Error( + `Failed to install node version using nvm ${version || ''}`, + ); + } + const delay = Math.max( + 3_000, + Math.pow(2, retryCount) * Math.random() * 1_250, + ); + console.log( + `Installing node failed. Retrying install in ${(delay / 1000).toFixed( + 0, + )} seconds...`, + ); + if (process.env.NX_VERBOSE_LOGGING === 'true') { + console.warn(e); + } + await new Promise((resolve) => setTimeout(resolve, delay)); + } + } +} + +main(); diff --git a/workflow-steps/install-node-if-present/main.yaml b/workflow-steps/install-node-if-present/main.yaml new file mode 100644 index 0000000..506aaaa --- /dev/null +++ b/workflow-steps/install-node-if-present/main.yaml @@ -0,0 +1,9 @@ +name: Install Node (if JS project present) +description: Install Node.js only when a JS project is detected (package.json, .nvmrc, or a lock file). Otherwise skips. +inputs: + - name: node_version + description: 'The node version to be installed' + +definition: + using: 'node' + main: workflow-steps/install-node-if-present/main.js diff --git a/workflow-steps/install-node-modules-if-present/main.js b/workflow-steps/install-node-modules-if-present/main.js new file mode 100644 index 0000000..e499193 --- /dev/null +++ b/workflow-steps/install-node-modules-if-present/main.js @@ -0,0 +1,118 @@ +const { execSync } = require('child_process'); +const { existsSync, readFileSync, writeFileSync } = require('fs'); + +const lockFiles = [ + 'package-lock.json', + 'yarn.lock', + 'pnpm-lock.yaml', + 'pnpm-lock.yml', +]; + +if (!lockFiles.some((f) => existsSync(f))) { + console.log('No lock file detected. Skipping node_modules install.'); + return; +} + +async function main() { + const workingDirectory = process.env.NX_WORKING_DIRECTORY; + if (workingDirectory) { + console.log(`Changing to working directory: ${workingDirectory}`); + process.chdir(workingDirectory); + } + + const command = getInstallCommand(); + if (!command) { + console.log( + 'No lock file detected after chdir. Skipping node_modules install.', + ); + return; + } + + console.log(`Installing dependencies using ${command.split(' ')[0]}`); + console.log(` Running command: ${command}\n`); + + const maxRetries = Number(process.env.NX_CLOUD_INPUT_max_retries) || 3; + await runCommandWithRetries(command, maxRetries); +} + +async function runCommandWithRetries(command, maxRetries) { + let retryCount = 0; + while (retryCount < maxRetries) { + try { + execSync(command, { stdio: 'inherit' }); + patchJest(); + console.log('Installed dependencies successfully!'); + break; + } catch (e) { + retryCount++; + if (retryCount >= maxRetries) { + throw new Error(`Failed to install node_modules via ${command}`); + } + const delay = Math.max( + 3_000, + Math.pow(2, retryCount) * Math.random() * 1_250, + ); + console.log( + `Installing node_modules failed. Retrying install in ${( + delay / 1000 + ).toFixed(0)} seconds...`, + ); + if (process.env.NX_VERBOSE_LOGGING === 'true') { + console.warn(e); + } + await new Promise((resolve) => setTimeout(resolve, delay)); + } + } +} + +function getInstallCommand() { + if (existsSync('package-lock.json')) { + const legacyInstallInput = + process.env.NX_CLOUD_INPUT_npm_legacy_install || + process.env.NX_CLOUD_NPM_LEGACY_INSTALL; + + if ( + legacyInstallInput !== undefined && + (legacyInstallInput === 'false' || legacyInstallInput === false) + ) { + return 'npm ci'; + } else { + console.log( + "Installing with --legacy-peer-deps for compatiblity, set the npm_legacy_install step input or NX_CLOUD_NPM_LEGACY_INSTALL env var to 'false' to disable.", + ); + return 'npm ci --legacy-peer-deps'; + } + } else if (existsSync('yarn.lock')) { + const [major] = execSync(`yarn --version`, { encoding: 'utf-8' }) + .trim() + .split('.'); + const useBerry = +major >= 2; + return useBerry + ? 'yarn install --immutable' + : 'yarn install --frozen-lockfile'; + } else if (existsSync('pnpm-lock.yaml') || existsSync('pnpm-lock.yml')) { + return 'pnpm install --frozen-lockfile'; + } +} + +function patchJest() { + try { + const path = + 'node_modules/jest-config/build/readConfigFileAndSetRootDir.js'; + const contents = readFileSync(path, 'utf-8'); + writeFileSync( + path, + contents.replace( + "const tsNode = await import('ts-node');", + "require('ts-node'); const tsNode = await import('ts-node');", + ), + ); + } catch (e) { + if (process.env.NX_VERBOSE_LOGGING == 'true') { + console.log(e); + } + console.log('no need to patch jest'); + } +} + +main(); diff --git a/workflow-steps/install-node-modules-if-present/main.yaml b/workflow-steps/install-node-modules-if-present/main.yaml new file mode 100644 index 0000000..b4d7368 --- /dev/null +++ b/workflow-steps/install-node-modules-if-present/main.yaml @@ -0,0 +1,10 @@ +name: Install Node Modules (if lock file present) +description: Installs node_modules only when a lock file is detected (package-lock.json, yarn.lock, or pnpm-lock.yaml). Otherwise skips. + +inputs: + - name: npm_legacy_install + description: 'Install with legacy peer dependency resolution. This only applies to npm.' + +definition: + using: 'node' + main: workflow-steps/install-node-modules-if-present/main.js diff --git a/workflow-steps/install-rust-if-present/main.js b/workflow-steps/install-rust-if-present/main.js new file mode 100644 index 0000000..c7be392 --- /dev/null +++ b/workflow-steps/install-rust-if-present/main.js @@ -0,0 +1,36 @@ +const { existsSync, appendFileSync } = require('fs'); +const { execSync } = require('child_process'); + +const rustMarkers = ['Cargo.toml', 'Cargo.lock', 'rust-toolchain.toml']; + +if (!rustMarkers.some((f) => existsSync(f))) { + console.log( + 'No Rust project detected (no Cargo.toml, Cargo.lock, or rust-toolchain.toml). Skipping Rust install.', + ); + return; +} + +console.log('Rust project detected. Installing rustup/cargo...'); + +const steps = [ + `curl --proto '=https' --tlsv1.3 https://sh.rustup.rs -sSf | sh -s -- -y`, + `. "$HOME/.cargo/env"`, +]; + +if (existsSync('rust-toolchain.toml')) { + steps.push('rustup install'); +} + +steps.push('rustc --version'); + +execSync(steps.join(' && '), { stdio: 'inherit', shell: '/bin/bash' }); + +if (process.env.NX_CLOUD_ENV) { + const cargoBin = `${process.env.HOME}/.cargo/bin`; + appendFileSync( + process.env.NX_CLOUD_ENV, + `PATH=${cargoBin}:${process.env.PATH}\n`, + ); +} + +console.log('Rust install complete.'); diff --git a/workflow-steps/install-rust-if-present/main.yaml b/workflow-steps/install-rust-if-present/main.yaml new file mode 100644 index 0000000..d75f420 --- /dev/null +++ b/workflow-steps/install-rust-if-present/main.yaml @@ -0,0 +1,6 @@ +name: Install Rust (if Rust project present) +description: Installs rustup/cargo when a Rust project is detected (Cargo.toml, Cargo.lock, or rust-toolchain.toml). Honors rust-toolchain.toml when present. + +definition: + using: 'node' + main: workflow-steps/install-rust-if-present/main.js