import * as core from '@actions/core'; import * as exec from '@actions/exec'; import fs from 'fs'; import path from 'path'; export function getNodeVersionFromFile(versionFilePath: string): string | null { if (!fs.existsSync(versionFilePath)) { throw new Error( `The specified node version file at: ${versionFilePath} does not exist` ); } const contents = fs.readFileSync(versionFilePath, 'utf8'); // Try parsing the file as an NPM `package.json` file. try { const manifest = JSON.parse(contents); // Presume package.json file. if (typeof manifest === 'object' && !!manifest) { // Support Volta. // See https://docs.volta.sh/guide/understanding#managing-your-project if (manifest.volta?.node) { return manifest.volta.node; } if (manifest.engines?.node) { return manifest.engines.node; } // Support Volta workspaces. // See https://docs.volta.sh/advanced/workspaces if (manifest.volta?.extends) { const extendedFilePath = path.resolve( path.dirname(versionFilePath), manifest.volta.extends ); core.info('Resolving node version from ' + extendedFilePath); return getNodeVersionFromFile(extendedFilePath); } // If contents are an object, we parsed JSON // this can happen if node-version-file is a package.json // yet contains no volta.node or engines.node // // If node-version file is _not_ JSON, control flow // will not have reached these lines. // // And because we've reached here, we know the contents // *are* JSON, so no further string parsing makes sense. return null; } } catch { core.info('Node version file is not JSON file'); } const found = contents.match(/^(?:node(js)?\s+)?v?(?[^\s]+)$/m); return found?.groups?.version ?? contents.trim(); } export async function printEnvDetailsAndSetOutput() { core.startGroup('Environment details'); const promises = ['node', 'npm', 'yarn'].map(async tool => { const output = await getToolVersion(tool, ['--version']); return {tool, output}; }); const tools = await Promise.all(promises); tools.forEach(({tool, output}) => { if (tool === 'node') { core.setOutput(`${tool}-version`, output); } core.info(`${tool}: ${output}`); }); core.endGroup(); } async function getToolVersion(tool: string, options: string[]) { try { const {stdout, stderr, exitCode} = await exec.getExecOutput(tool, options, { ignoreReturnCode: true, silent: true }); if (exitCode > 0) { core.info(`[warning]${stderr}`); return ''; } return stdout.trim(); } catch (err) { return ''; } } export const unique = () => { const encountered = new Set(); return (value: unknown): boolean => { if (encountered.has(value)) return false; encountered.add(value); return true; }; };