UNPKG

@process-engine/ci_tools

Version:
268 lines (215 loc) 7.21 kB
import * as glob from 'glob'; import * as mime from 'mime-types'; import { Octokit } from '@octokit/rest'; import * as Path from 'path'; import * as yargsParser from 'yargs-parser'; import { readFileSync, statSync } from 'fs'; import { getCurrentRepoNameWithOwner, getFullCommitMessageFromRef, isExistingTag } from '../git/git'; import { getPackageVersionTag } from '../versions/package_version'; import { setupGit } from './internal/setup-git-and-npm-connections'; type GitTag = string; type GitHubRepo = { owner: string; name: string; }; const COMMAND_NAME = 'update-github-release'; const BADGE = `[${COMMAND_NAME}]\t`; const DEFAULT_MODE = 'node'; const SKIP_CI_MESSAGE = '[skip ci]'; const DOC = ` Updates or creates a GitHub release using the current version (or given \`--version-tag\`). Uploads all given \`--assets\`, resolving globs and updating existing assets on GitHub. OPTIONS --mode sets the package mode [dotnet, node, python] (default: node) `; // DOC: see above export async function run(...args): Promise<boolean> { const argv = yargsParser(args, { default: { mode: DEFAULT_MODE } }); const isDryRun = argv.dry; const mode = argv.mode; let versionTag = argv.versionTag; let title = argv.title; let text = argv.text; setupGit(); if (versionTag == null) { versionTag = await getPackageVersionTag(mode); console.log(`${BADGE}No --version-tag given, versionTag set to:`, versionTag); } if (argv.useTitleAndTextFromGitTag) { if (!isExistingTag(versionTag)) { console.error(`${BADGE}Tag does not exists: ${versionTag}`); console.error(`${BADGE}Aborting.`); return false; } const { subject, body } = getFullCommitMessageFromRef(versionTag); title = subject; text = body.replace(SKIP_CI_MESSAGE, '').trim(); console.log(`${BADGE}`); console.log(`${BADGE}Option --use-title-and-text-from-git-tag was given.`); console.log(`${BADGE}title set to:`, title); console.log(`${BADGE}text set to:`, text); } let assets = []; if (argv.assets) { const list = Array.isArray(argv.assets) ? argv.assets : [argv.assets]; list.forEach((pattern: string): void => { const files = glob.sync(pattern); assets = assets.concat(files); }); console.log(`${BADGE}`); console.log(`${BADGE}Option --assets was given:`, assets); } const success = await updateGitHubRelease(versionTag, title, text, assets, isDryRun); if (success) { console.log(`${BADGE}Success.`); } return success; } export function getShortDoc(): string { return DOC.trim().split('\n')[0]; } export function printHelp(): void { console.log( `Usage: ci_tools ${COMMAND_NAME} [--use-title-and-text-from-git-tag | --title [--text]] [--assets <asset-name-or-glob> ...] [--version-tag] [--dry] [--mode <MODE>]` ); console.log(''); console.log(DOC.trim()); } /** * Updates the corresponding GitHub release for `versionTag` using the given `title` and `text`. */ export async function updateGitHubRelease( versionTag: GitTag, title: string, text: string, assets: string[], dryRun: boolean = false ): Promise<boolean> { const repo = getCurrentRepoNameWithOwnerAsObject(getCurrentRepoNameWithOwner()); const octokit = createOctokit(process.env.GH_TOKEN); const releaseId = await getExistingReleaseId(octokit, repo, versionTag); const releaseExists = releaseId != null; if (releaseExists) { if (dryRun) { console.log(`${BADGE}Would now update existing release. Skipping since this is a dry run!`); return true; } console.log(`${BADGE}Updating existing release for ${versionTag} ...`); return updateExistingRelease(octokit, repo, releaseId, title, text, assets); } if (dryRun) { console.log(`${BADGE}Would now create a new release. Skipping since this is a dry run!`); return true; } console.log(`${BADGE}Creating new release for ${versionTag} ...`); return createNewRelease(octokit, repo, versionTag, title, text, assets); } async function updateExistingRelease( octokit: Octokit, repo: GitHubRepo, releaseId: number, title: string, text: string, assets: string[] ): Promise<boolean> { const response = await octokit.repos.updateRelease({ owner: repo.owner, repo: repo.name, release_id: releaseId, name: title, body: text, }); let success = response.status === 200; if (success) { const uploadUrl = response.data.upload_url; for (const filename of assets) { console.log(`${BADGE}- Uploading '${filename}' ...`); try { const uploadSuccess = await uploadAsset(octokit, uploadUrl, filename); success = success && uploadSuccess; } catch (error) { const data = tryToParseJson(error.message); const errorIsFromOctokitAndAssetAlreadyExists = data?.errors?.length === 1 && data?.errors[0]?.code === 'already_exists'; if (errorIsFromOctokitAndAssetAlreadyExists) { console.log(`${BADGE} INFO: Asset '${filename}' already exists.`); } else { throw error; } } } } return success; } async function uploadAsset(octokit: Octokit, uploadUrl: string, filename: string): Promise<boolean> { const buffer: Buffer = readFileSync(filename); const name: string = Path.basename(filename).replace(' ', '_'); const contentLength: number = statSync(filename).size; const contentType: string = mime.lookup(filename) || 'text/plain'; const options: any = { url: uploadUrl, file: buffer, contentType: contentType, contentLength: contentLength, name: name, }; const uploadResponse = await octokit.repos.uploadReleaseAsset(options); return uploadResponse.status === 201; } async function createNewRelease( octokit: Octokit, repo: GitHubRepo, versionTag: GitTag, title: string, text: string, assets: string[] ): Promise<boolean> { const isPrerelease = versionTag.match(/-/) != null; const response = await octokit.repos.createRelease({ owner: repo.owner, repo: repo.name, tag_name: versionTag, name: title, body: text, prerelease: isPrerelease, }); const success = response.status === 201; if (success) { const releaseId = response.data.id; return updateExistingRelease(octokit, repo, releaseId, title, text, assets); } return success; } function createOctokit(githubAuthToken: string): Octokit { const octokit = new Octokit({ auth: githubAuthToken, }); return octokit; } function getCurrentRepoNameWithOwnerAsObject(nameWithOwner: string): GitHubRepo { const parts = nameWithOwner.split('/'); return { name: parts[1], owner: parts[0], }; } async function getExistingReleaseId(octokit: Octokit, repo: GitHubRepo, versionTag: GitTag): Promise<number | null> { try { const response = await octokit.repos.getReleaseByTag({ owner: repo.owner, repo: repo.name, tag: versionTag, }); return response.data.id; } catch (error) { return null; } } function tryToParseJson(text: string): any | null { try { return JSON.parse(text); } catch (e) { return null; } }