diff --git a/.github/workflows/publish-action.yml b/.github/workflows/publish-action.yml index 8501ffd..c5cda7b 100644 --- a/.github/workflows/publish-action.yml +++ b/.github/workflows/publish-action.yml @@ -12,7 +12,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: "20" + node-version: "22.x" cache: npm - run: npm ci - run: npm run build diff --git a/.github/workflows/test-action.yml b/.github/workflows/test-action.yml index 02b5e84..29ce7ed 100644 --- a/.github/workflows/test-action.yml +++ b/.github/workflows/test-action.yml @@ -25,7 +25,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: "20" + node-version: "22.x" cache: npm - run: npm ci - run: npm run build diff --git a/package-lock.json b/package-lock.json index f05b727..2ca61c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@actions/tool-cache": "^2.0.1", "@octokit/auth-unauthenticated": "^5.0.1", "@types/node": "^20.9.0", + "@types/semver": "^7.7.1", "execa": "^8.0.1", "semver": "^7.5.4", "typescript": "^5.0.0", @@ -635,6 +636,13 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", + "dev": true, + "license": "MIT" + }, "node_modules/before-after-hook": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", diff --git a/package.json b/package.json index 5eee1b3..63c3e7d 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "@actions/tool-cache": "^2.0.1", "@octokit/auth-unauthenticated": "^5.0.1", "@types/node": "^20.9.0", + "@types/semver": "^7.7.1", "execa": "^8.0.1", "semver": "^7.5.4", "typescript": "^5.0.0", diff --git a/src/main.ts b/src/main.ts index 4cdee3d..b28013a 100755 --- a/src/main.ts +++ b/src/main.ts @@ -1,11 +1,10 @@ import * as core from "@actions/core"; import * as tc from "@actions/tool-cache"; import * as github from "@actions/github"; -import { join, basename } from "node:path"; +import { join } from "node:path"; +import { existsSync } from "node:fs"; import semver from "semver"; import process from "node:process"; -import { pipeline } from "node:stream/promises"; -import { createWriteStream } from "node:fs"; import { $ } from "execa"; import { createUnauthenticatedAuth } from "@octokit/auth-unauthenticated"; @@ -28,40 +27,78 @@ if (version === "latest") { repo: "cli", }); const versions = releases.map((release) => release.tag_name.slice(1)); - version = semver.maxSatisfying(versions, version); + version = semver.maxSatisfying(versions, version) ?? "2.28.0"; } core.debug(`Resolved version: ${version}`); +const platformTypes: Partial> = { + linux: "linux", + darwin: "macOS", + win32: "windows", +}; + +const archTypes: Partial> = { + x64: "amd64", + arm: "arm", + arm64: "arm64", +}; + +const extByPlatform: Partial> = { + linux: "tar.gz", + darwin: semver.lt(version, "2.28.0") ? "tar.gz" : "zip", + win32: "zip", +}; + +const platform = platformTypes[process.platform] ?? "linux"; +const arch = archTypes[process.arch] ?? "amd64"; +const ext = extByPlatform[process.platform] ?? "tar.gz"; +const folderName = `gh_${version}_${platform}_${arch}`; +const zipFileName = `${folderName}.${ext}`; +const downloadUrl = `https://github.com/cli/cli/releases/download/v${version}/${zipFileName}`; +const binFileName = platform === 'windows' ? "gh.exe" : "gh"; + let found = tc.find("gh", version); core.setOutput("cache-hit", !!found); if (!found) { - const platform = { - linux: "linux", - darwin: "macOS", - win32: "windows", - }[process.platform]; - const arch = { - x64: "amd64", - arm: "arm", - arm64: "arm64", - }[process.arch]; - const ext = { - linux: "tar.gz", - darwin: semver.lt(version, "2.28.0") ? "tar.gz" : "zip", - win32: "zip", - }[process.platform]; - const file = `gh_${version}_${platform}_${arch}.${ext}`; - found = await tc.downloadTool( - `https://github.com/cli/cli/releases/download/v${version}/${file}` - ); - if (file.endsWith(".zip")) { - found = await tc.extractZip(found); + core.debug(`Downloading GH CLI ${version} from ${downloadUrl} ...`); + const downloadedFile = await tc.downloadTool(downloadUrl); + core.debug(`Downloaded GH CLI ${version} to ${downloadedFile}`); + if (ext === "zip") { + found = await tc.extractZip(downloadedFile); } else { - found = await tc.extractTar(found); + found = await tc.extractTar(downloadedFile); } found = await tc.cacheDir(found, "gh", version); + core.debug(`Cached GH CLI ${version} to ${found}`); +} else { + core.debug(`Using cached GH CLI ${version} from ${found}`); } -core.addPath(found); +const bin0Dir = found; +const bin1Dir = join(found, "bin"); +const bin2Dir = join(found, folderName, "bin"); + +const bin0Path = join(bin0Dir, binFileName); +const bin1Path = join(bin1Dir, binFileName); +const bin2Path = join(bin2Dir, binFileName); + +let binDir: string; +if (existsSync(bin0Path)) { + core.debug(`Found GH CLI binary in ${bin0Dir}`); + binDir = bin0Dir; +} else if (existsSync(bin1Path)) { + core.debug(`Found GH CLI binary in ${bin1Dir}`); + binDir = bin1Dir; +} else if (existsSync(bin2Path)) { + core.debug(`Found GH CLI binary in ${bin2Dir}`); + binDir = bin2Dir; +} else { + core.error(`Could not find GH CLI binary in ${found}`); + core.setFailed(`Could not find GH CLI binary in ${found}`); + process.exit(1); +} + +core.addPath(binDir); +core.debug(`Added ${binDir} to PATH`); core.setOutput("gh-version", version); const token = core.getInput("token");