getUpToDateRefsFromGitHub.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. import fetch from 'node-fetch'
  2. import { spawnSync } from 'node:child_process'
  3. import prompts from 'prompts'
  4. import { TARGET_BRANCH, REPO_NAME, REPO_OWNER, STABLE_BRANCH } from './config.js'
  5. async function apiCall (endpoint, errorMessage) {
  6. const response = await fetch(
  7. `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}${endpoint}`,
  8. )
  9. if (response.ok) {
  10. return response.json()
  11. }
  12. console.warn(response)
  13. throw new Error(errorMessage)
  14. }
  15. export async function getRemoteHEAD () {
  16. return (
  17. await apiCall(
  18. `/git/ref/heads/${TARGET_BRANCH}`,
  19. 'Cannot get remote HEAD, check your internet connection.',
  20. )
  21. ).object.sha
  22. }
  23. async function getLatestReleaseSHA () {
  24. const response = await fetch(`https://raw.githubusercontent.com/${REPO_OWNER}/${REPO_NAME}/${TARGET_BRANCH}/packages/uppy/package.json`)
  25. if (!response.ok) throw new Error(`Network call failed: ${response.status} ${response.statusText}`)
  26. const { version } = await response.json()
  27. const tag_name = `uppy@${version}`
  28. console.log(`Last release was ${tag_name}.`)
  29. return (
  30. await apiCall(
  31. `/git/ref/tags/${encodeURIComponent(tag_name)}`,
  32. `Failed to fetch information for release ${JSON.stringify(tag_name)}`,
  33. )
  34. ).object.sha
  35. }
  36. function getStableBranchMergeBase (REMOTE_HEAD) {
  37. spawnSync('git', ['fetch', `https://github.com/${REPO_OWNER}/${REPO_NAME}.git`, STABLE_BRANCH])
  38. const STABLE_HEAD = spawnSync('git', ['rev-parse', 'FETCH_HEAD']).stdout.toString().trim()
  39. return [[
  40. spawnSync('git', ['merge-base', REMOTE_HEAD, 'FETCH_HEAD']).stdout.toString().trim(),
  41. STABLE_HEAD,
  42. ].join('..'), STABLE_HEAD]
  43. }
  44. async function getLocalHEAD () {
  45. return spawnSync('git', ['rev-parse', 'HEAD']).stdout.toString().trim()
  46. }
  47. export function rewindGitHistory (spawnOptions, sha) {
  48. return spawnSync('git', ['reset', sha, '--hard'], spawnOptions).status === 0
  49. }
  50. export async function validateGitStatus (spawnOptions) {
  51. const latestRelease = getLatestReleaseSHA() // run in parallel to speed things up
  52. const [REMOTE_HEAD, LOCAL_HEAD] = await Promise.all([getRemoteHEAD(), getLocalHEAD()])
  53. const { status, stderr } = spawnSync(
  54. 'git',
  55. ['diff', '--exit-code', '--quiet', REMOTE_HEAD, '--', '.'],
  56. spawnOptions,
  57. )
  58. if (status !== 0) {
  59. console.error(stderr.toString())
  60. console.log(
  61. `git repository is not clean and/or not in sync with ${REPO_OWNER}/${REPO_NAME}`,
  62. )
  63. if (spawnSync(
  64. 'git',
  65. ['diff', '--exit-code', '--quiet', LOCAL_HEAD, '--', '.'],
  66. spawnOptions,
  67. ).status !== 0) {
  68. const { value } = await prompts({
  69. type: 'confirm',
  70. name: 'value',
  71. message:
  72. 'Do you want to hard reset your local repository (all uncommitted changes will be lost)?',
  73. })
  74. if (!value) {
  75. throw new Error(
  76. 'Please ensure manually that your local repository is clean and up to date.',
  77. )
  78. }
  79. }
  80. if (stderr.indexOf('bad object') !== -1) {
  81. // eslint-disable-next-line no-shadow
  82. const { status, stdout, stderr } = spawnSync(
  83. 'git',
  84. [
  85. 'fetch',
  86. `https://github.com/${REPO_OWNER}/${REPO_NAME}.git`,
  87. TARGET_BRANCH,
  88. ],
  89. spawnOptions,
  90. )
  91. if (status) {
  92. console.log(stdout.toString())
  93. console.error(stderr.toString())
  94. throw new Error('Failed to fetch, please ensure manually that your local repository is up to date')
  95. }
  96. }
  97. if (!rewindGitHistory(spawnOptions, REMOTE_HEAD)) {
  98. throw new Error(
  99. 'Failed to reset, please ensure manually that your local repository is clean and up to date.',
  100. )
  101. }
  102. }
  103. return [await latestRelease, LOCAL_HEAD, ...getStableBranchMergeBase(REMOTE_HEAD)]
  104. }