Created
March 10, 2022 22:47
-
-
Save bdombro/c032cb979ed7369f9ded7085f85ba422 to your computer and use it in GitHub Desktop.
A shared lib for zx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env zx | |
/** | |
* Util/Lib functions for zx scripts | |
*/ | |
/* eslint-disable i18next/no-literal-string */ | |
/* eslint-disable no-console */ | |
export class HandledError extends Error {} | |
export function throwHandledError(message) { | |
throw new HandledError(message); | |
} | |
export function throwError(message) { | |
throw new Error(message); | |
} | |
if (!globalThis.$) { | |
throwError('This script must be ran using zx (https://github.com/google/zx)'); | |
} | |
export async function assertGit() { | |
$`which git`.catch(_ => | |
throwError('This script requires git to be installed'), | |
); | |
} | |
export async function assertGithubCli() { | |
await $`which gh`.catch(_ => | |
throwError( | |
'This script requires github CLI (gh) to be installed and configured', | |
), | |
); | |
await $`gh auth status -h ghe.spotify.net`.catch(_ => | |
throwError( | |
'This script requires github CLI (gh) to authenticated with ghe.spotify.net', | |
), | |
); | |
} | |
export async function silenced(fn) { | |
return async () => { | |
const verboseBefore = $.verbose; | |
$.verbose = false; | |
await promisified(fn)(); | |
$.verbose = verboseBefore; | |
console.log('hello2'); | |
}; | |
} | |
export function log(msg, shouldReplaceLast) { | |
return shouldReplaceLast | |
? process.stdout.write(`${msg}\r`) | |
: console.log(msg); | |
} | |
log.info = async (msg, shouldReplaceLast) => { | |
const msgArray = Array.isArray(msg) ? msg : [msg]; | |
for (const m of msgArray) { | |
await log(chalk.gray(' - ' + m), shouldReplaceLast); | |
} | |
}; | |
log.announce = (msg, shouldReplaceLast) => | |
log(chalk.black.bgGreen(msg), shouldReplaceLast); | |
log.warn = (msg, shouldReplaceLast) => | |
log(chalk.white.bgOrange(msg), shouldReplaceLast); | |
log.error = (msg, shouldReplaceLast) => | |
log(chalk.white.bgRed(msg), shouldReplaceLast); | |
log.newLine = () => log(''); | |
log.say = async msg => !$.silent && $`say ${msg}`; | |
export async function waitWithTimer( | |
cmdPromise, | |
prefix = '', | |
logger = log.info, | |
) { | |
let running = true; | |
let seconds = 0; | |
const resPromise = cmdPromise.finally(() => (running = false)); | |
while (running) { | |
await logger(`${prefix}${seconds++}s`, seconds); | |
await sleep(1000); | |
} | |
log.newLine(); | |
return await resPromise; | |
} | |
export const getGitCtxT = throttle(getGitCtx, 10000); | |
export async function getGitCtx(includePrCtx) { | |
const verboseBefore = $.verbose; | |
$.verbose = false; | |
await $`git fetch`; | |
const branch = (await $`git branch --show-current`).stdout.replace('\n', ''); | |
const isLocalOnly = await $`git fetch origin ${branch}`.then(() => false).catch(() => true); | |
const isBehind = Number( | |
await $`git rev-list --left-only --count origin/master...${branch}`, | |
); | |
const localStatus = (await $`git status --porcelain`).stdout | |
.split('\n') | |
.map(line => line.trim()) | |
.filter(Boolean) | |
.reduce((acc, line) => { | |
acc.isDirty = true; | |
acc.isClean = false; | |
const [type, file] = line.split(' '); | |
const typeMap = { M: 'modified', A: 'added', D: 'deleted' }; | |
acc[typeMap[type]].push(file); | |
return acc; | |
}, { modified: [], added: [], deleted: [], isDirty: false, isClean: true }); | |
; | |
const ctx = { | |
branch, | |
isBehind, | |
isLocalOnly, | |
...localStatus, | |
}; | |
if (includePrCtx) { | |
const prViewJson = JSON.parse( | |
await $`gh pr view --json number,state,mergeable,reviewDecision,statusCheckRollup,closed,mergedAt`, | |
); | |
// Sometimes status checks are not ready yet, so we should assume pending | |
if (!prViewJson.statusCheckRollup?.length) { | |
prViewJson.statusCheckRollup = [{ state: 'PENDING' }]; | |
} | |
const prCtx = { | |
...prViewJson, | |
checks: { | |
failure: prViewJson.statusCheckRollup.filter(s => s.state === 'FAILURE') | |
.length, | |
successful: prViewJson.statusCheckRollup.filter(s => s.state === 'SUCCESS') | |
.length, | |
pending: prViewJson.statusCheckRollup.filter(s => s.state === 'PENDING') | |
.length, | |
}, | |
}; | |
Object.assign(ctx, prCtx); | |
} | |
$.verbose = verboseBefore; | |
if ($.verbose) console.log(ctx); | |
return ctx; | |
} | |
export function promisify(fn) { | |
return async () => fn(); | |
} | |
/** | |
* Returns the last response from a function if called again before wait interval | |
*/ | |
export function throttle(fn, wait = 1e3) { | |
let last = 0; | |
let lastRes = null; | |
return function throttled() { | |
const now = Date.now(); | |
if (now - last < wait) return lastRes; | |
last = now; | |
lastRes = fn.apply(this, arguments); | |
return lastRes; | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment