OverviewFilesCommitsIssuesDocsReleasesPeers

nvidia-rtx-godot / .github / actions / fetch-gh-release-asset / index.ts

167 lines · typescript ref:
/* eslint-disable no-void */
import { dirname } from 'path';
import { mkdir, writeFile } from 'fs/promises';
import * as core from '@actions/core';
import * as github from '@actions/github';
import retry from 'async-retry';
import type { Context } from '@actions/github/lib/context';
import type { HeadersInit } from 'node-fetch';
import fetch from 'node-fetch';

interface GetRepoResult {
  readonly owner: string;
  readonly repo: string;
}

const getRepo = (inputRepoString: string, context: Context): GetRepoResult => {
  if (inputRepoString === '') {
    return { owner: context.repo.owner, repo: context.repo.repo };
  } else {
    const [owner, repo] = inputRepoString.split('/');
    if (typeof owner === 'undefined' || typeof repo === 'undefined')
      throw new Error('Malformed repo');
    return { owner, repo };
  }
};

interface GetReleaseOptions {
  readonly owner: string;
  readonly repo: string;
  readonly version: string;
}

const getRelease = (
  octokit: ReturnType<typeof github.getOctokit>,
  { owner, repo, version }: GetReleaseOptions
) => {
  const tagsMatch = version.match(/^tags\/(.*)$/);
  if (version === 'latest') {
    return octokit.rest.repos.getLatestRelease({ owner, repo });
  } else if (tagsMatch !== null && tagsMatch[1]) {
    return octokit.rest.repos.getReleaseByTag({
      owner,
      repo,
      tag: tagsMatch[1],
    });
  } else {
    return octokit.rest.repos.getRelease({
      owner,
      repo,
      release_id: Math.trunc(Number(version)),
    });
  }
};

type GetReleaseResult = ReturnType<typeof getRelease> extends Promise<infer T>
  ? T
  : never;

type Asset = GetReleaseResult['data']['assets'][0];

interface FetchAssetFileOptions {
  readonly id: number;
  readonly outputPath: string;
  readonly owner: string;
  readonly repo: string;
  readonly token: string;
}

const baseFetchAssetFile = async (
  octokit: ReturnType<typeof github.getOctokit>,
  { id, outputPath, owner, repo, token }: FetchAssetFileOptions
) => {
  const {
    body,
    headers: { accept, 'user-agent': userAgent },
    method,
    url,
  } = octokit.request.endpoint(
    'GET /repos/:owner/:repo/releases/assets/:asset_id',
    {
      asset_id: id,
      headers: {
        accept: 'application/octet-stream',
      },
      owner,
      repo,
    }
  );
  let headers: HeadersInit = {
    accept,
  };
  if (token !== '')
    headers = { ...headers, authorization: `token ${token}` };

  if (typeof userAgent !== 'undefined')
    headers = { ...headers, 'user-agent': userAgent };

  const response = await fetch(url, { body, headers, method });
  if (!response.ok) {
    const text = await response.text();
    core.warning(text);
    throw new Error('Invalid response');
  }
  const blob = await response.blob();
  const arrayBuffer = await blob.arrayBuffer();
  await mkdir(dirname(outputPath), { recursive: true });
  void (await writeFile(outputPath, new Uint8Array(arrayBuffer)));
};

const fetchAssetFile = (
  octokit: ReturnType<typeof github.getOctokit>,
  options: FetchAssetFileOptions
) =>
  retry(() => baseFetchAssetFile(octokit, options), {
    retries: 5,
    minTimeout: 1000,
  });

const printOutput = (release: GetReleaseResult): void => {
  core.setOutput('version', release.data.tag_name);
  core.setOutput('name', release.data.name);
  core.setOutput('body', release.data.body);
};

const filterByFileName = (file: string) => (asset: Asset) =>
  file === asset.name;

const filterByRegex = (file: string) => (asset: Asset) =>
  new RegExp(file).test(asset.name);

const main = async (): Promise<void> => {
  const { owner, repo } = getRepo(
    core.getInput('repo', { required: false }),
    github.context
  );
  const token = core.getInput('token', { required: false });
  const version = core.getInput('version', { required: false }) || 'latest';
  const inputTarget = core.getInput('target', { required: false });
  const file = core.getInput('file', { required: true });
  const usesRegex = core.getBooleanInput('regex', { required: false });
  const target = inputTarget === '' ? file : inputTarget;
  const baseUrl =
    core.getInput('octokitBaseUrl', { required: false }) || undefined;

  const octokit = github.getOctokit(token, { baseUrl });
  const release = await getRelease(octokit, { owner, repo, version });

  const assetFilterFn = usesRegex
    ? filterByRegex(file)
    : filterByFileName(file);

  const assets = release.data.assets.filter(assetFilterFn);
  if (assets.length === 0) throw new Error('Could not find asset id');
  for (const asset of assets) {
    await fetchAssetFile(octokit, {
      id: asset.id,
      outputPath: usesRegex ? `${target}${asset.name}` : target,
      owner,
      repo,
      token,
    });
  }
  printOutput(release);
};

void main();