diff --git a/dist/index.js b/dist/index.js index c001c1b..ee73265 100644 --- a/dist/index.js +++ b/dist/index.js @@ -10000,6 +10000,12 @@ var ALL_TARGET_TRIPLES = [ "x86_64-apple-darwin", "x86_64-unknown-linux-musl" ]; +var TARGET_DUPLES = [ + "linux-amd64", + "linux-arm64", + "darwin-amd64", + "darwin-arm64" +]; function architectureLabel(arch2) { switch (arch2) { case "arm64": @@ -10035,15 +10041,37 @@ function getTargetTriple(arch2, platform2) { const { vendor, operatingSystem } = platformLabel(platform2); return `${architecture}-${vendor}-${operatingSystem}`; } +function getTargetDuple(arch2, platform2) { + switch (platform2) { + case "darwin": + return arch2 === "arm64" ? "darwin-arm64" : "darwin-amd64"; + case "linux": + return arch2 === "arm64" ? "linux-arm64" : "linux-amd64"; + default: + throw new Error( + `Unsupported platform ${platform2} for target duple conversion` + ); + } +} function stripTargetTriple(value) { if (ALL_TARGET_TRIPLES.find((targetTriple) => targetTriple === value)) { return none(); } - const stripped = ALL_TARGET_TRIPLES.reduce( + const strippedTriple = ALL_TARGET_TRIPLES.reduce( (value2, targetTriple) => value2.replace(new RegExp(`-${targetTriple}$`), ""), value ); - return some(stripped); + if (strippedTriple !== value) { + return some(strippedTriple); + } + const strippedDuple = TARGET_DUPLES.reduce( + (value2, duple) => value2.replace(new RegExp(`${duple}$`), ""), + value + ); + if (strippedDuple !== value) { + return some(strippedDuple); + } + return some(value); } // src/types.ts @@ -10111,20 +10139,31 @@ async function findExactSemanticVersionTag(octokit, slug, target) { `Expected to find an exact semantic version tag matching ${target} for ${slug.owner}/${slug.repository}` ); } -async function fetchReleaseAssetMetadataFromTag(octokit, slug, binaryName, tag, targetTriple) { +async function fetchReleaseAssetMetadataFromTag(octokit, slug, binaryName, tag, targetTriple, targetDuple) { const releaseMetadata = await octokit.rest.repos.getReleaseByTag({ owner: slug.owner, repo: slug.repository, tag }); if (isSome(binaryName)) { - const targetLabel = `${binaryName.value}-${targetTriple}`; - const asset2 = releaseMetadata.data.assets.find( - (asset3) => asset3.label === targetLabel - ); + const targetLabelTraditional = `${binaryName.value}-${targetTriple}`; + const targetLabelDuple = `${binaryName.value}-${targetDuple}`; + const asset2 = releaseMetadata.data.assets.find((asset3) => { + if (typeof asset3.label === "string") { + if (asset3.label === targetLabelTraditional || asset3.label === targetLabelDuple) { + return true; + } + } + if (typeof asset3.name === "string") { + if (asset3.name === targetLabelTraditional || asset3.name === targetLabelDuple) { + return true; + } + } + return false; + }); if (asset2 === void 0) { throw new Error( - `Expected to find asset in release ${slug.owner}/${slug.repository}@${tag} with label ${targetLabel}` + `Expected to find asset in release ${slug.owner}/${slug.repository}@${tag} with label or name ${targetLabelTraditional} or ${targetLabelDuple}` ); } return { @@ -10132,23 +10171,39 @@ async function fetchReleaseAssetMetadataFromTag(octokit, slug, binaryName, tag, url: asset2.url }; } - const matchingTargetTriples = releaseMetadata.data.assets.filter( - (asset2) => typeof asset2.label === "string" && asset2.label.endsWith(targetTriple) - ); - if (matchingTargetTriples.length === 0) { + const matchingAssets = releaseMetadata.data.assets.filter((asset2) => { + if (typeof asset2.label === "string") { + if (asset2.label.endsWith(targetTriple) || asset2.label.endsWith(targetDuple)) { + return true; + } + } + if (typeof asset2.name === "string") { + if (asset2.name.endsWith(targetTriple) || asset2.name.endsWith(targetDuple)) { + return true; + } + } + return false; + }); + if (matchingAssets.length === 0) { throw new Error( - `Expected to find asset in release ${slug.owner}/${slug.repository}@${tag} with label ending in ${targetTriple}` + `Expected to find asset in release ${slug.owner}/${slug.repository}@${tag} with label or name ending in ${targetTriple} or ${targetDuple}` ); } - if (matchingTargetTriples.length > 1) { + if (matchingAssets.length > 1) { throw new Error( - `Ambiguous targets: expected to find a single asset in release ${slug.owner}/${slug.repository}@${tag} matching target triple ${targetTriple}, but found ${matchingTargetTriples.length}. + `Ambiguous targets: expected to find a single asset in release ${slug.owner}/${slug.repository}@${tag} matching target triple ${targetTriple} or target duple ${targetDuple}, but found ${matchingAssets.length}. To resolve, specify the desired binary with the target format ${slug.owner}/${slug.repository}/@${tag}` ); } - const asset = matchingTargetTriples.shift(); - const targetName = stripTargetTriple(asset.label); + const asset = matchingAssets.shift(); + let matchField; + if (typeof asset.label === "string" && (asset.label.endsWith(targetTriple) || asset.label.endsWith(targetDuple))) { + matchField = asset.label; + } else { + matchField = asset.name; + } + const targetName = stripTargetTriple(matchField); return { binaryName: targetName, url: asset.url @@ -10166,7 +10221,10 @@ function getDestinationDirectory(storageDirectory, slug, tag, platform2, archite ); } async function installGitHubReleaseBinary(octokit, targetRelease, storageDirectory, token, ignoreExisting) { - const targetTriple = getTargetTriple((0, import_node_os.arch)(), (0, import_node_os.platform)()); + const currentArch = (0, import_node_os.arch)(); + const currentPlatform = (0, import_node_os.platform)(); + const targetTriple = getTargetTriple(currentArch, currentPlatform); + const targetDuple = getTargetDuple(currentArch, currentPlatform); const releaseTag = await findExactSemanticVersionTag( octokit, targetRelease.slug, @@ -10176,15 +10234,16 @@ async function installGitHubReleaseBinary(octokit, targetRelease, storageDirecto storageDirectory, targetRelease.slug, releaseTag, - (0, import_node_os.platform)(), - (0, import_node_os.arch)() + currentPlatform, + currentArch ); const releaseAsset = await fetchReleaseAssetMetadataFromTag( octokit, targetRelease.slug, targetRelease.binaryName, releaseTag, - targetTriple + targetTriple, + targetDuple ); const destinationBasename = unwrapOrDefault( releaseAsset.binaryName, diff --git a/src/fetch.ts b/src/fetch.ts index 7c6ef12..e0c3db0 100644 --- a/src/fetch.ts +++ b/src/fetch.ts @@ -8,6 +8,7 @@ import { RepositorySlug, SemanticVersion, Sha1Hash, + TargetDuple, TargetTriple, BinaryName, } from "./types"; @@ -133,6 +134,7 @@ export async function fetchReleaseAssetMetadataFromTag( binaryName: Option, tag: ExactSemanticVersion, targetTriple: TargetTriple, + targetDuple: TargetDuple, ): Promise { // Maintainer's note: this impure function call makes this function difficult to test. const releaseMetadata = await octokit.rest.repos.getReleaseByTag({ @@ -141,46 +143,88 @@ export async function fetchReleaseAssetMetadataFromTag( tag, }); - // When the binary name is provided, look for matching binary and target triple. + // When the binary name is provided, look for matching binary with target triple or target duple if (isSome(binaryName)) { - const targetLabel = `${binaryName.value}-${targetTriple}`; - const asset = releaseMetadata.data.assets.find( - (asset) => asset.label === targetLabel, - ); + const targetLabelTraditional = `${binaryName.value}-${targetTriple}`; + const targetLabelDuple = `${binaryName.value}-${targetDuple}`; + + const asset = releaseMetadata.data.assets.find((asset) => { + // Check for label match + if (typeof asset.label === "string") { + if (asset.label === targetLabelTraditional || asset.label === targetLabelDuple) { + return true; + } + } + + // Check for name match + if (typeof asset.name === "string") { + if (asset.name === targetLabelTraditional || asset.name === targetLabelDuple) { + return true; + } + } + + return false; + }); + if (asset === undefined) { throw new Error( - `Expected to find asset in release ${slug.owner}/${slug.repository}@${tag} with label ${targetLabel}`, + `Expected to find asset in release ${slug.owner}/${slug.repository}@${tag} with label or name ${targetLabelTraditional} or ${targetLabelDuple}`, ); } + return { binaryName: binaryName, url: asset.url, }; } - // When the binary name is not provided, support two use cases: + // When the binary name is not provided, support these use cases: // 1. There is only one binary uploaded to this release, a named binary. - // 2. There is an asset label matching the target triple (with no binary name). + // 2. There is an asset label matching the target triple or target duple. // In both cases, we assume that's the binary the user meant. // If there is ambiguity, exit with an error. - const matchingTargetTriples = releaseMetadata.data.assets.filter( - (asset) => - typeof asset.label === "string" && asset.label.endsWith(targetTriple), - ); - if (matchingTargetTriples.length === 0) { + const matchingAssets = releaseMetadata.data.assets.filter((asset) => { + // Check label match + if (typeof asset.label === "string") { + if (asset.label.endsWith(targetTriple) || asset.label.endsWith(targetDuple)) { + return true; + } + } + + // Check name match + if (typeof asset.name === "string") { + if (asset.name.endsWith(targetTriple) || asset.name.endsWith(targetDuple)) { + return true; + } + } + + return false; + }); + + if (matchingAssets.length === 0) { throw new Error( - `Expected to find asset in release ${slug.owner}/${slug.repository}@${tag} with label ending in ${targetTriple}`, + `Expected to find asset in release ${slug.owner}/${slug.repository}@${tag} with label or name ending in ${targetTriple} or ${targetDuple}`, ); } - if (matchingTargetTriples.length > 1) { + if (matchingAssets.length > 1) { throw new Error( - `Ambiguous targets: expected to find a single asset in release ${slug.owner}/${slug.repository}@${tag} matching target triple ${targetTriple}, but found ${matchingTargetTriples.length}. + `Ambiguous targets: expected to find a single asset in release ${slug.owner}/${slug.repository}@${tag} matching target triple ${targetTriple} or target duple ${targetDuple}, but found ${matchingAssets.length}. To resolve, specify the desired binary with the target format ${slug.owner}/${slug.repository}/@${tag}`, ); } - const asset = matchingTargetTriples.shift()!; - const targetName = stripTargetTriple(asset.label!); + const asset = matchingAssets.shift()!; + + // Determine which field matched to use for stripping the target triple + let matchField: string; + if (typeof asset.label === "string" && + (asset.label.endsWith(targetTriple) || asset.label.endsWith(targetDuple))) { + matchField = asset.label; + } else { + matchField = asset.name!; + } + + const targetName = stripTargetTriple(matchField); return { binaryName: targetName, url: asset.url, diff --git a/src/index.ts b/src/index.ts index 1068c9c..c04f10e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,7 +13,7 @@ import { parseTargetReleases, parseToken, } from "./parse"; -import { getTargetTriple } from "./platform"; +import { getTargetTriple, getTargetDuple } from "./platform"; import { fetchReleaseAssetMetadataFromTag, findExactSemanticVersionTag, @@ -48,7 +48,10 @@ async function installGitHubReleaseBinary( token: string, ignoreExisting: boolean, ): Promise { - const targetTriple = getTargetTriple(arch(), platform()); + const currentArch = arch(); + const currentPlatform = platform(); + const targetTriple = getTargetTriple(currentArch, currentPlatform); + const targetDuple = getTargetDuple(currentArch, currentPlatform); const releaseTag = await findExactSemanticVersionTag( octokit, @@ -60,8 +63,8 @@ async function installGitHubReleaseBinary( storageDirectory, targetRelease.slug, releaseTag, - platform(), - arch(), + currentPlatform, + currentArch, ); const releaseAsset = await fetchReleaseAssetMetadataFromTag( @@ -70,6 +73,7 @@ async function installGitHubReleaseBinary( targetRelease.binaryName, releaseTag, targetTriple, + targetDuple, ); const destinationBasename = unwrapOrDefault( diff --git a/src/platform.ts b/src/platform.ts index 0e88b5f..d103347 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -1,4 +1,4 @@ -import type { TargetTriple } from "./types"; +import type { TargetTriple, TargetDuple } from "./types"; import { none, some, type Option } from "./option"; const ALL_TARGET_TRIPLES: readonly TargetTriple[] = [ @@ -8,6 +8,14 @@ const ALL_TARGET_TRIPLES: readonly TargetTriple[] = [ "x86_64-unknown-linux-musl", ] as unknown as readonly TargetTriple[]; +// Target duples (Go format OS-architecture combinations) +export const TARGET_DUPLES: readonly TargetDuple[] = [ + "linux-amd64", + "linux-arm64", + "darwin-amd64", + "darwin-arm64", +] as unknown as readonly TargetDuple[]; + function architectureLabel(arch: string): string { switch (arch) { case "arm64": @@ -54,16 +62,55 @@ export function getTargetTriple( } /** - * String the string of its target triple suffix + * Get the target duple (e.g. "linux-amd64") for the given architecture and platform + */ +export function getTargetDuple( + arch: string, + platform: NodeJS.Platform, +): TargetDuple { + switch (platform) { + case "darwin": + return arch === "arm64" ? "darwin-arm64" as TargetDuple : "darwin-amd64" as TargetDuple; + case "linux": + return arch === "arm64" ? "linux-arm64" as TargetDuple : "linux-amd64" as TargetDuple; + default: + throw new Error( + `Unsupported platform ${platform} for target duple conversion`, + ); + } +} + +/** + * Strip the target triple or target duple suffix from a string */ export function stripTargetTriple(value: string): Option { // Can't strip away the target tuple if nothing else remains if (ALL_TARGET_TRIPLES.find((targetTriple) => targetTriple === value)) { return none(); } - const stripped = ALL_TARGET_TRIPLES.reduce( + + // Try to strip traditional target triple format first + const strippedTriple = ALL_TARGET_TRIPLES.reduce( (value, targetTriple) => value.replace(new RegExp(`-${targetTriple}$`), ""), value, ); - return some(stripped); + + // If the value changed, a target triple was found and stripped + if (strippedTriple !== value) { + return some(strippedTriple); + } + + // Try to strip target duple suffix + const strippedDuple = TARGET_DUPLES.reduce( + (value, duple) => value.replace(new RegExp(`${duple}$`), ""), + value, + ); + + // If the value changed, a target duple was found and stripped + if (strippedDuple !== value) { + return some(strippedDuple); + } + + // Nothing was stripped, return the original value + return some(value); } diff --git a/src/types.ts b/src/types.ts index e5bf5ae..3b2bef2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -32,6 +32,7 @@ export type RepositorySlug = { }; export type TargetTriple = string & { readonly __tag: unique symbol }; +export type TargetDuple = string & { readonly __tag: unique symbol }; export type BinaryName = string & { readonly __tag: unique symbol }; export type TargetRelease = {