announce.mjs 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. import { globby as glob } from 'globby';
  2. import { fileURLToPath } from 'node:url';
  3. import { readFile } from 'node:fs/promises';
  4. import { setOutput } from './utils.mjs';
  5. const { GITHUB_REF = 'main' } = process.env;
  6. const baseUrl = new URL(`https://github.com/withastro/astro/blob/${GITHUB_REF}/`);
  7. const emojis = ['🎉', '🥳', '🚀', '🧑‍🚀', '🎊', '🏆', '✅', '🤩', '🤖', '🙌'];
  8. const descriptors = [
  9. 'new releases',
  10. 'hot and fresh updates',
  11. 'shiny updates',
  12. 'exciting changes',
  13. 'package updates',
  14. 'awesome updates',
  15. 'bug fixes and features',
  16. 'updates',
  17. ];
  18. const verbs = [
  19. "just went out!",
  20. "just launched!",
  21. "now available!",
  22. "in the wild!",
  23. "now live!",
  24. "hit the registry!",
  25. "to share!",
  26. "for you!",
  27. "for y’all! 🤠",
  28. "comin’ your way!",
  29. "comin’ atcha!",
  30. "comin’ in hot!",
  31. "freshly minted on the blockchain! (jk)",
  32. "[is] out (now with 100% more reticulated splines!)",
  33. "(as seen on TV!)",
  34. "just dropped!",
  35. "– artisanally hand-crafted just for you.",
  36. "– oh happy day!",
  37. "– enjoy!",
  38. "now out. Be the first on your block to download!",
  39. "made with love 💕",
  40. "[is] out! Our best [version] yet!",
  41. "[is] here. DOWNLOAD! DOWNLOAD! DOWNLOAD!",
  42. "... HUZZAH!",
  43. "[has] landed!",
  44. "landed! The internet just got a little more fun.",
  45. "– from our family to yours.",
  46. "– go forth and build!"
  47. ];
  48. const extraVerbs = [
  49. 'new',
  50. 'here',
  51. 'released',
  52. 'freshly made',
  53. 'going out',
  54. 'hitting the registry',
  55. 'available',
  56. 'live now',
  57. 'hot and fresh',
  58. 'for you',
  59. "comin' atcha",
  60. ];
  61. function item(items) {
  62. return items[Math.floor(Math.random() * items.length)];
  63. }
  64. const plurals = new Map([
  65. ['is', 'are'],
  66. ['has', 'have'],
  67. ]);
  68. function pluralize(text) {
  69. return text.replace(/(\[([^\]]+)\])/gm, (_, _full, match) =>
  70. plurals.has(match) ? plurals.get(match) : `${match}s`
  71. );
  72. }
  73. function singularlize(text) {
  74. return text.replace(/(\[([^\]]+)\])/gm, (_, _full, match) => `${match}`);
  75. }
  76. const packageMap = new Map();
  77. async function generatePackageMap() {
  78. const packageRoot = new URL('../../packages/', import.meta.url);
  79. const packages = await glob(['*/package.json', '*/*/package.json'], {
  80. cwd: fileURLToPath(packageRoot),
  81. });
  82. await Promise.all(
  83. packages.map(async (pkg) => {
  84. const pkgFile = fileURLToPath(new URL(pkg, packageRoot));
  85. const content = await readFile(pkgFile).then((res) => JSON.parse(res.toString()));
  86. packageMap.set(content.name, `./packages/${pkg.replace('/package.json', '')}`);
  87. })
  88. );
  89. }
  90. async function generateMessage() {
  91. await generatePackageMap();
  92. const releases = process.argv.slice(2)[0];
  93. const data = JSON.parse(releases);
  94. const packages = await Promise.all(
  95. data.map(({ name, version }) => {
  96. const p = packageMap.get(name);
  97. if (!p) {
  98. throw new Error(`Unable to find entrypoint for "${name}"!`);
  99. }
  100. return {
  101. name,
  102. version,
  103. url: new URL(`${p}/CHANGELOG.md#${version.replace(/\./g, '')}`, baseUrl).toString(),
  104. };
  105. })
  106. );
  107. const emoji = item(emojis);
  108. const descriptor = item(descriptors);
  109. const verb = item(verbs);
  110. let message = '';
  111. if (packages.length === 1) {
  112. const { name, version, url } = packages[0];
  113. message += `${emoji} \`${name}@${version}\` ${singularlize(
  114. verb
  115. )}\nRead the [release notes →](<${url}>)\n`;
  116. } else {
  117. message += `${emoji} Some ${descriptor} ${pluralize(verb)}\n\n`;
  118. for (const { name, version, url } of packages) {
  119. message += `• \`${name}@${version}\` Read the [release notes →](<${url}>)\n`;
  120. }
  121. }
  122. if (message.length < 2000) {
  123. return message;
  124. } else {
  125. const { name, version, url } = packages.find((pkg) => pkg.name === 'astro') ?? packages[0];
  126. message = `${emoji} Some ${descriptor} ${pluralize(verb)}\n\n`;
  127. message += `• \`${name}@${version}\` Read the [release notes →](<${url}>)\n`;
  128. message += `\nAlso ${item(extraVerbs)}:`;
  129. const remainingPackages = packages.filter((p) => p.name !== name);
  130. for (const { name, version, url } of remainingPackages) {
  131. message += `\n• \`${name}@${version}\``;
  132. }
  133. if (message.length < 2000) {
  134. return message;
  135. } else {
  136. message = `${emoji} Some ${descriptor} ${pluralize(verb)}\n\n`;
  137. message += `• \`${name}@${version}\` Read the [release notes →](<${url}>)\n`;
  138. message += `\n\nAlso ${item(extraVerbs)}: ${remainingPackages.length} other packages!`;
  139. return message;
  140. }
  141. }
  142. }
  143. async function run() {
  144. const content = await generateMessage();
  145. console.log(content);
  146. setOutput('DISCORD_MESSAGE', content);
  147. }
  148. run();