formatChangeLog.js 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. import { createInterface } from 'node:readline'
  2. import { createWriteStream } from 'node:fs'
  3. import { spawn } from 'node:child_process'
  4. import prompts from 'prompts'
  5. const subsystem = /((?<=^packages\/)@uppy\/[a-z0-9-]+|(?<=^)(?:docs|e2e|examples))\//
  6. async function inferPackageForCommit (sha, spawnOptions) {
  7. const cp = spawn('git', ['--no-pager', 'log', '-1', '--name-only', sha], spawnOptions)
  8. const candidates = {}
  9. for await (const path of createInterface({ input: cp.stdout })) {
  10. const match = subsystem.exec(path)
  11. if (match != null) {
  12. candidates[match[1]] ??= 0
  13. candidates[match[1]]++
  14. }
  15. }
  16. const maxVal = Math.max(...Object.values(candidates))
  17. return {
  18. inferredPackages: Number.isFinite(maxVal)
  19. ? Object.entries(candidates).flatMap(
  20. ([pkg, nbOfFiles]) => (nbOfFiles === maxVal || nbOfFiles === maxVal - 1 ? [pkg] : []),
  21. ).join(',')
  22. : 'meta',
  23. candidates,
  24. }
  25. }
  26. export default async function formatChangeLog (
  27. spawnOptions,
  28. LAST_RELEASE_COMMIT,
  29. changeLogUrl,
  30. ) {
  31. const changeLogCommits = createWriteStream(changeLogUrl)
  32. const gitLog = spawn('git', [
  33. '--no-pager',
  34. 'log',
  35. '--format="%H::%s::%an"',
  36. `${LAST_RELEASE_COMMIT}..HEAD`,
  37. ], spawnOptions)
  38. const expectedFormat = /^"([a-f0-9]+)::(?:((?:@uppy\/[a-z0-9-]+(?:,@uppy\/[a-z0-9-]+)*)|meta|docs|e2e|examples):\s?)?(.+?)(\s\(#\d+\))?::(.+)"$/ // eslint-disable-line max-len
  39. for await (const log of createInterface({ input: gitLog.stdout })) {
  40. const [, sha, packageName, title, PR, authorName] = expectedFormat.exec(log)
  41. const formattedCommitTitle = {
  42. packageName,
  43. title,
  44. authorInfo: PR ? `${authorName} / #${PR.slice(3, -1)}` : authorName,
  45. }
  46. if (!packageName) {
  47. console.log(
  48. `No package info found in commit title: ${sha} (https://github.com/transloadit/uppy/commit/${sha})`,
  49. )
  50. console.log(log)
  51. const { inferredPackages, candidates } = await inferPackageForCommit(sha, spawnOptions)
  52. const { useInferred } = await prompts({
  53. type: 'confirm',
  54. name: 'useInferred',
  55. message: `Assign commit to ${inferredPackages} (inferred from the files it touches)?`,
  56. initial: true,
  57. })
  58. if (useInferred) {
  59. formattedCommitTitle.packageName = inferredPackages
  60. } else {
  61. const response = await prompts({
  62. type: 'autocompleteMultiselect',
  63. name: 'value',
  64. message: 'Which package(s) does this commit belong to?',
  65. min: 1,
  66. choices: [
  67. { title: 'Docs', value: 'docs' },
  68. { title: 'Meta', value: 'meta' },
  69. ...Object.entries(candidates)
  70. .sort((a, b) => a[1] > b[1])
  71. .map(([value]) => ({ title: value, value })),
  72. ],
  73. })
  74. if (!Array.isArray(response.value)) throw new Error('Aborting release')
  75. formattedCommitTitle.packageName = response.value.join(',')
  76. }
  77. }
  78. changeLogCommits.write(
  79. `- ${formattedCommitTitle.packageName}: ${formattedCommitTitle.title} (${formattedCommitTitle.authorInfo})\n`,
  80. )
  81. }
  82. changeLogCommits.close()
  83. }