2023-01-05 13:16:21 +01:00
import * as core from '@actions/core' ;
import * as tc from '@actions/tool-cache' ;
import path from 'path' ;
import BaseDistribution from '../base-distribution' ;
import { NodeInputs , INodeVersion , INodeVersionInfo } from '../base-models' ;
interface INodeRelease extends tc . IToolRelease {
lts? : string ;
}
export default class OfficialBuilds extends BaseDistribution {
constructor ( nodeInfo : NodeInputs ) {
super ( nodeInfo ) ;
}
public async setupNodeJs() {
2025-02-19 19:10:42 +05:30
if ( this . nodeInfo . mirrorURL ) {
if ( this . nodeInfo . mirrorURL === '' ) {
throw new Error (
'Mirror URL is empty. Please provide a valid mirror URL.'
) ;
}
let downloadPath = '' ;
let toolPath = '' ;
try {
core . info ( ` Attempting to download using mirror URL... ` ) ;
downloadPath = await this . downloadFromMirrorURL ( ) ; // Attempt to download from the mirror
core . info ( 'downloadPath from downloadFromMirrorURL() ' + downloadPath ) ;
if ( downloadPath ) {
toolPath = downloadPath ;
}
} catch ( err ) {
core . info ( ( err as Error ) . message ) ;
core . debug ( ( err as Error ) . stack ? ? 'empty stack' ) ;
}
} else {
let manifest : tc.IToolRelease [ ] | undefined ;
let nodeJsVersions : INodeVersion [ ] | undefined ;
const osArch = this . translateArchToDistUrl ( this . nodeInfo . arch ) ;
2023-10-19 13:43:56 +02:00
2025-02-19 19:10:42 +05:30
if ( this . isLtsAlias ( this . nodeInfo . versionSpec ) ) {
core . info ( 'Attempt to resolve LTS alias from manifest...' ) ;
2023-01-05 13:16:21 +01:00
2025-02-19 19:10:42 +05:30
// No try-catch since it's not possible to resolve LTS alias without manifest
manifest = await this . getManifest ( ) ;
2023-01-05 13:16:21 +01:00
2025-02-19 19:10:42 +05:30
this . nodeInfo . versionSpec = this . resolveLtsAliasFromManifest (
this . nodeInfo . versionSpec ,
this . nodeInfo . stable ,
manifest
) ;
}
2023-01-05 13:16:21 +01:00
2025-02-19 19:10:42 +05:30
if ( this . isLatestSyntax ( this . nodeInfo . versionSpec ) ) {
nodeJsVersions = await this . getNodeJsVersions ( ) ;
const versions = this . filterVersions ( nodeJsVersions ) ;
this . nodeInfo . versionSpec = this . evaluateVersions ( versions ) ;
2023-01-05 13:16:21 +01:00
2025-02-19 19:10:42 +05:30
core . info ( 'getting latest node version...' ) ;
}
2023-01-05 13:16:21 +01:00
2025-02-19 19:10:42 +05:30
if ( this . nodeInfo . checkLatest ) {
core . info ( 'Attempt to resolve the latest version from manifest...' ) ;
const resolvedVersion = await this . resolveVersionFromManifest (
this . nodeInfo . versionSpec ,
this . nodeInfo . stable ,
osArch ,
manifest
2023-01-05 13:16:21 +01:00
) ;
2025-02-19 19:10:42 +05:30
if ( resolvedVersion ) {
this . nodeInfo . versionSpec = resolvedVersion ;
core . info ( ` Resolved as ' ${ resolvedVersion } ' ` ) ;
} else {
core . info (
` Failed to resolve version ${ this . nodeInfo . versionSpec } from manifest `
) ;
}
2023-01-05 13:16:21 +01:00
}
2025-02-19 19:10:42 +05:30
let toolPath = this . findVersionInHostedToolCacheDirectory ( ) ;
2023-01-05 13:16:21 +01:00
2025-02-19 19:10:42 +05:30
if ( toolPath ) {
core . info ( ` Found in cache @ ${ toolPath } ` ) ;
this . addToolPath ( toolPath ) ;
return ;
}
2023-10-19 13:43:56 +02:00
2025-02-19 19:10:42 +05:30
let downloadPath = '' ;
try {
core . info ( ` Attempting to download ${ this . nodeInfo . versionSpec } ... ` ) ;
2023-10-19 13:43:56 +02:00
2025-02-19 19:10:42 +05:30
const versionInfo = await this . getInfoFromManifest (
this . nodeInfo . versionSpec ,
this . nodeInfo . stable ,
osArch ,
manifest
2023-01-05 13:16:21 +01:00
) ;
2025-02-19 19:10:42 +05:30
if ( versionInfo ) {
core . info (
` Acquiring ${ versionInfo . resolvedVersion } - ${ versionInfo . arch } from ${ versionInfo . downloadUrl } `
) ;
downloadPath = await tc . downloadTool (
versionInfo . downloadUrl ,
undefined ,
this . nodeInfo . auth
) ;
if ( downloadPath ) {
toolPath = await this . extractArchive (
downloadPath ,
versionInfo ,
false
) ;
}
} else {
core . info (
'Not found in manifest. Falling back to download directly from Node'
2024-09-07 01:00:34 +05:30
) ;
2023-01-05 13:16:21 +01:00
}
2025-02-19 19:10:42 +05:30
} catch ( err ) {
// Rate limit?
if (
err instanceof tc . HTTPError &&
( err . httpStatusCode === 403 || err . httpStatusCode === 429 )
) {
core . info (
` Received HTTP status code ${ err . httpStatusCode } . This usually indicates the rate limit has been exceeded `
) ;
} else {
core . info ( ( err as Error ) . message ) ;
}
core . debug ( ( err as Error ) . stack ? ? 'empty stack' ) ;
core . info ( 'Falling back to download directly from Node' ) ;
2023-10-19 13:43:56 +02:00
}
2025-02-19 19:10:42 +05:30
if ( ! toolPath ) {
toolPath = await this . downloadDirectlyFromNode ( ) ;
2023-01-05 13:16:21 +01:00
}
2023-10-19 13:43:56 +02:00
2025-02-19 19:10:42 +05:30
if ( this . osPlat != 'win32' ) {
toolPath = path . join ( toolPath , 'bin' ) ;
}
2023-01-05 13:16:21 +01:00
2025-02-19 19:10:42 +05:30
core . addPath ( toolPath ) ;
2023-01-05 13:16:21 +01:00
}
}
2023-10-19 13:43:56 +02:00
protected addToolPath ( toolPath : string ) {
if ( this . osPlat != 'win32' ) {
toolPath = path . join ( toolPath , 'bin' ) ;
}
core . addPath ( toolPath ) ;
}
protected async downloadDirectlyFromNode() {
const nodeJsVersions = await this . getNodeJsVersions ( ) ;
const versions = this . filterVersions ( nodeJsVersions ) ;
const evaluatedVersion = this . evaluateVersions ( versions ) ;
if ( ! evaluatedVersion ) {
throw new Error (
` Unable to find Node version ' ${ this . nodeInfo . versionSpec } ' for platform ${ this . osPlat } and architecture ${ this . nodeInfo . arch } . `
) ;
}
const toolName = this . getNodejsDistInfo ( evaluatedVersion ) ;
try {
const toolPath = await this . downloadNodejs ( toolName ) ;
return toolPath ;
} catch ( error ) {
if ( error instanceof tc . HTTPError && error . httpStatusCode === 404 ) {
2023-10-19 16:59:10 +02:00
core . warning (
2023-10-19 13:43:56 +02:00
` Node version ${ this . nodeInfo . versionSpec } for platform ${ this . osPlat } and architecture ${ this . nodeInfo . arch } was found but failed to download. ` +
'This usually happens when downloadable binaries are not fully updated at https://nodejs.org/. ' +
'To resolve this issue you may either fall back to the older version or try again later.'
) ;
}
throw error ;
}
}
2023-01-05 13:16:21 +01:00
protected evaluateVersions ( versions : string [ ] ) : string {
let version = '' ;
if ( this . isLatestSyntax ( this . nodeInfo . versionSpec ) ) {
core . info ( ` getting latest node version... ` ) ;
return versions [ 0 ] ;
}
version = super . evaluateVersions ( versions ) ;
return version ;
}
protected getDistributionUrl ( ) : string {
2025-02-19 19:10:42 +05:30
if ( this . nodeInfo . mirrorURL ) {
return this . nodeInfo . mirrorURL ;
}
2023-01-05 13:16:21 +01:00
return ` https://nodejs.org/dist ` ;
}
private getManifest ( ) : Promise < tc.IToolRelease [ ] > {
core . debug ( 'Getting manifest from actions/node-versions@main' ) ;
return tc . getManifestFromRepo (
'actions' ,
'node-versions' ,
this . nodeInfo . auth ,
'main'
) ;
}
private resolveLtsAliasFromManifest (
versionSpec : string ,
stable : boolean ,
manifest : INodeRelease [ ]
) : string {
const alias = versionSpec . split ( 'lts/' ) [ 1 ] ? . toLowerCase ( ) ;
if ( ! alias ) {
throw new Error (
` Unable to parse LTS alias for Node version ' ${ versionSpec } ' `
) ;
}
core . debug ( ` LTS alias ' ${ alias } ' for Node version ' ${ versionSpec } ' ` ) ;
// Supported formats are `lts/<alias>`, `lts/*`, and `lts/-n`. Where asterisk means highest possible LTS and -n means the nth-highest.
const n = Number ( alias ) ;
const aliases = Object . fromEntries (
manifest
. filter ( x = > x . lts && x . stable === stable )
. map ( x = > [ x . lts ! . toLowerCase ( ) , x ] )
. reverse ( )
) ;
const numbered = Object . values ( aliases ) ;
const release =
alias === '*'
? numbered [ numbered . length - 1 ]
: n < 0
? numbered [ numbered . length - 1 + n ]
: aliases [ alias ] ;
if ( ! release ) {
throw new Error (
` Unable to find LTS release ' ${ alias } ' for Node version ' ${ versionSpec } '. `
) ;
}
core . debug (
` Found LTS release ' ${ release . version } ' for Node version ' ${ versionSpec } ' `
) ;
return release . version . split ( '.' ) [ 0 ] ;
}
private async resolveVersionFromManifest (
versionSpec : string ,
stable : boolean ,
osArch : string ,
manifest : tc.IToolRelease [ ] | undefined
) : Promise < string | undefined > {
try {
const info = await this . getInfoFromManifest (
versionSpec ,
stable ,
osArch ,
manifest
) ;
return info ? . resolvedVersion ;
} catch ( err ) {
core . info ( 'Unable to resolve version from manifest...' ) ;
2023-10-19 10:40:59 +02:00
core . debug ( ( err as Error ) . message ) ;
2023-01-05 13:16:21 +01:00
}
}
private async getInfoFromManifest (
versionSpec : string ,
stable : boolean ,
osArch : string ,
manifest : tc.IToolRelease [ ] | undefined
) : Promise < INodeVersionInfo | null > {
let info : INodeVersionInfo | null = null ;
if ( ! manifest ) {
core . debug ( 'No manifest cached' ) ;
manifest = await this . getManifest ( ) ;
}
const rel = await tc . findFromManifest (
versionSpec ,
stable ,
manifest ,
osArch
) ;
if ( rel && rel . files . length > 0 ) {
info = < INodeVersionInfo > { } ;
info . resolvedVersion = rel . version ;
info . arch = rel . files [ 0 ] . arch ;
info . downloadUrl = rel . files [ 0 ] . download_url ;
info . fileName = rel . files [ 0 ] . filename ;
}
return info ;
}
private isLtsAlias ( versionSpec : string ) : boolean {
return versionSpec . startsWith ( 'lts/' ) ;
}
private isLatestSyntax ( versionSpec ) : boolean {
return [ 'current' , 'latest' , 'node' ] . includes ( versionSpec ) ;
}
2025-02-19 19:10:42 +05:30
protected async downloadFromMirrorURL() {
const nodeJsVersions = await this . getMirrorUrlVersions ( ) ;
const versions = this . filterVersions ( nodeJsVersions ) ;
const evaluatedVersion = this . evaluateVersions ( versions ) ;
if ( ! evaluatedVersion ) {
throw new Error (
` Unable to find Node version ' ${ this . nodeInfo . versionSpec } ' for platform ${ this . osPlat } and architecture ${ this . nodeInfo . arch } from the provided mirror-url ${ this . nodeInfo . mirrorURL } . Please check the mirror-url `
) ;
}
const toolName = this . getNodejsMirrorURLInfo ( evaluatedVersion ) ;
try {
const toolPath = await this . downloadNodejs ( toolName ) ;
return toolPath ;
} catch ( error ) {
if ( error instanceof tc . HTTPError && error . httpStatusCode === 404 ) {
core . error (
` Node version ${ this . nodeInfo . versionSpec } for platform ${ this . osPlat } and architecture ${ this . nodeInfo . arch } was found but failed to download. ` +
'This usually happens when downloadable binaries are not fully updated at https://nodejs.org/. ' +
'To resolve this issue you may either fall back to the older version or try again later.'
) ;
} else {
// For any other error type, you can log the error message.
core . error ( ` An unexpected error occurred like url might not correct ` ) ;
}
throw error ;
}
}
2023-01-05 13:16:21 +01:00
}