inject.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. const fs = require('fs')
  2. const path = require('path')
  3. const chalk = require('chalk')
  4. const { spawn } = require('child_process')
  5. const readline = require('readline')
  6. const YAML = require('js-yaml')
  7. const gzipSize = require('gzip-size')
  8. const prettierBytes = require('@transloadit/prettier-bytes')
  9. const esbuild = require('esbuild')
  10. const touch = require('touch')
  11. const glob = require('glob')
  12. const webRoot = __dirname
  13. const uppyRoot = path.join(__dirname, '../packages/uppy')
  14. const robodogRoot = path.join(__dirname, '../packages/@uppy/robodog')
  15. const localesRoot = path.join(__dirname, '../packages/@uppy/locales')
  16. const configPath = path.join(webRoot, '/themes/uppy/_config.yml')
  17. // eslint-disable-next-line import/no-dynamic-require
  18. const { version } = require(path.join(uppyRoot, '/package.json'))
  19. const regionalDisplayNames = new Intl.DisplayNames('en-US', { type: 'region' })
  20. const languageDisplayNames = new Intl.DisplayNames('en-US', { type: 'language' })
  21. const defaultConfig = {
  22. comment: 'Auto updated by inject.js',
  23. uppy_version_anchor: '001',
  24. uppy_version: '0.0.1',
  25. uppy_bundle_kb_sizes: {},
  26. config: {},
  27. }
  28. // Keeping a whitelist so utils etc are excluded
  29. // It may be easier to maintain a blacklist instead
  30. const packages = [
  31. // Bundles
  32. 'uppy',
  33. '@uppy/robodog',
  34. // Integrations
  35. '@uppy/react',
  36. // Core
  37. '@uppy/core',
  38. // Plugins -- please keep these sorted alphabetically
  39. '@uppy/aws-s3',
  40. '@uppy/aws-s3-multipart',
  41. '@uppy/dashboard',
  42. '@uppy/drag-drop',
  43. '@uppy/dropbox',
  44. '@uppy/file-input',
  45. '@uppy/form',
  46. '@uppy/golden-retriever',
  47. '@uppy/google-drive',
  48. '@uppy/informer',
  49. '@uppy/instagram',
  50. '@uppy/image-editor',
  51. '@uppy/progress-bar',
  52. '@uppy/screen-capture',
  53. '@uppy/status-bar',
  54. '@uppy/thumbnail-generator',
  55. '@uppy/transloadit',
  56. '@uppy/tus',
  57. '@uppy/url',
  58. '@uppy/webcam',
  59. '@uppy/xhr-upload',
  60. '@uppy/drop-target',
  61. // Stores
  62. '@uppy/store-default',
  63. '@uppy/store-redux',
  64. ]
  65. const excludes = {
  66. '@uppy/react': ['react'],
  67. }
  68. // eslint-disable-next-line no-use-before-define
  69. inject().catch((err) => {
  70. console.error(err)
  71. process.exit(1)
  72. })
  73. async function getMinifiedSize (pkg, name) {
  74. const packageJSON = fs.readFileSync(path.join(pkg, 'package.json'))
  75. // eslint-disable-next-line no-shadow
  76. const { main, version } = JSON.parse(packageJSON)
  77. const external = excludes[name] ?? []
  78. if (name !== '@uppy/core' && name !== 'uppy') {
  79. // Preact is already unconditionally included through @uppy/core
  80. external.unshift('@uppy/core', 'preact')
  81. }
  82. const { outputFiles:[{ contents: bundle }] } = await esbuild.build({
  83. write: false,
  84. bundle: true,
  85. entryPoints: [path.resolve(pkg, main)],
  86. minify: true,
  87. external,
  88. })
  89. const gzipped = await gzipSize(bundle)
  90. return {
  91. minified: bundle.length,
  92. gzipped,
  93. version,
  94. }
  95. }
  96. async function injectSizes (config) {
  97. console.info(chalk.grey('Generating bundle sizes…'))
  98. const padTarget = Math.max(...packages.map((cur) => cur.length)) + 2
  99. const sizesPromise = Promise.all(
  100. packages.map(async (pkg) => {
  101. const result = await getMinifiedSize(path.join(__dirname, '../packages', pkg), pkg)
  102. console.info(chalk.green(
  103. // ✓ @uppy/pkgname: 10.0 kB min / 2.0 kB gz
  104. ` ✓ ${pkg}: ${' '.repeat(padTarget - pkg.length)}${
  105. `${prettierBytes(result.minified)} min`.padEnd(10)
  106. } / ${prettierBytes(result.gzipped)} gz`,
  107. ))
  108. return [pkg, {
  109. ...result,
  110. prettyMinified: prettierBytes(result.minified),
  111. prettyGzipped: prettierBytes(result.gzipped),
  112. }]
  113. }),
  114. ).then(Object.fromEntries)
  115. // eslint-disable-next-line no-param-reassign
  116. config.uppy_bundle_kb_sizes = await sizesPromise
  117. }
  118. const sourceUppy = path.join(webRoot, '/themes/uppy/source/uppy/')
  119. const sourceUppyLocales = path.join(sourceUppy, 'locales')
  120. async function injectBundles () {
  121. await Promise.all([
  122. fs.promises.mkdir(sourceUppy, { recursive:true }),
  123. fs.promises.mkdir(sourceUppyLocales, { recursive:true }),
  124. ])
  125. const cmds = [
  126. `cp -vfR ${path.join(uppyRoot, '/dist/*')} ${sourceUppy}`,
  127. `cp -vfR ${path.join(robodogRoot, '/dist/*')} ${sourceUppy}`,
  128. `cp -vfR ${path.join(localesRoot, '/dist/*')} ${sourceUppyLocales}`,
  129. ].join(' && ')
  130. const cp = spawn(cmds, { stdio:['ignore', 'pipe', 'inherit'], shell: true })
  131. await Promise.race([
  132. new Promise((resolve, reject) => cp.on('error', reject)),
  133. (async () => {
  134. const stdout = readline.createInterface({
  135. input: cp.stdout,
  136. })
  137. for await (const line of stdout) {
  138. console.info(chalk.green('✓ injected: '), chalk.grey(line))
  139. }
  140. })(),
  141. ])
  142. }
  143. // re-enable after rate limiter issue is fixed
  144. //
  145. async function injectGhStars () {
  146. const opts = {}
  147. if ('GITHUB_TOKEN' in process.env) {
  148. opts.auth = process.env.GITHUB_TOKEN
  149. }
  150. // eslint-disable-next-line global-require
  151. const { Octokit } = require('@octokit/rest')
  152. const octokit = new Octokit(opts)
  153. const { headers, data } = await octokit.repos.get({
  154. owner: 'transloadit',
  155. repo: 'uppy',
  156. })
  157. console.log(`${headers['x-ratelimit-remaining']} requests remaining until we hit GitHub ratelimiter`)
  158. const dstpath = path.join(webRoot, 'themes', 'uppy', 'layout', 'partials', 'generated_stargazers.ejs')
  159. fs.writeFileSync(dstpath, String(data.stargazers_count), 'utf-8')
  160. console.log(`${data.stargazers_count} stargazers written to '${dstpath}'`)
  161. }
  162. async function injectMarkdown () {
  163. const sources = {
  164. '.github/ISSUE_TEMPLATE/integration_help.md': 'src/_template/integration_help.md',
  165. '.github/CONTRIBUTING.md': 'src/_template/contributing.md',
  166. }
  167. for (const src of Object.keys(sources)) {
  168. const dst = sources[src]
  169. // strip yaml frontmatter:
  170. const srcpath = path.join(uppyRoot, `/../../${src}`)
  171. const dstpath = path.join(webRoot, dst)
  172. const parts = fs.readFileSync(srcpath, 'utf-8').split(/---\s*\n/)
  173. if (parts.length >= 3) {
  174. parts.shift()
  175. parts.shift()
  176. }
  177. let content = `<!-- WARNING! This file was injected. Please edit in "${src}" instead and run "${path.basename(__filename)}" -->\n\n`
  178. content += parts.join('---\n')
  179. fs.writeFileSync(dstpath, content, 'utf-8')
  180. console.info(chalk.green('✓ injected: '), chalk.grey(srcpath))
  181. }
  182. touch(path.join(webRoot, '/src/support.md'))
  183. }
  184. function injectLocaleList () {
  185. const mdTable = [
  186. `<!-- WARNING! This file was automatically injected. Please run "${path.basename(__filename)}" to re-generate -->\n\n`,
  187. '| %count% Locales | NPM | CDN | Source on GitHub |',
  188. '| --------------- | ------------------ | ------------------- | ---------------- |',
  189. ]
  190. const mdRows = []
  191. const localeList = {}
  192. const localePackagePath = path.join(localesRoot, 'src', '*.js')
  193. // eslint-disable-next-line import/no-dynamic-require, global-require
  194. const localePackageVersion = require(path.join(localesRoot, 'package.json')).version
  195. glob.sync(localePackagePath).forEach((localePath) => {
  196. const localeName = path.basename(localePath, '.js')
  197. const [languageCode, regionCode, variant] = localeName.split(/[-_]/)
  198. const languageName = languageDisplayNames.of(languageCode)
  199. const regionName = regionalDisplayNames.of(regionCode)
  200. const npmPath = `<code class="raw"><a href="https://www.npmjs.com/package/@uppy/locales">@uppy/locales</a>/lib/${localeName}</code>`
  201. const cdnPath = `[\`${localeName}.min.js\`](https://releases.transloadit.com/uppy/locales/v${localePackageVersion}/${localeName}.min.js)`
  202. const githubSource = `[\`${localeName}.js\`](https://github.com/transloadit/uppy/blob/main/packages/%40uppy/locales/src/${localeName}.js)`
  203. const mdTableRow = `| ${languageName}<br/> <small>${regionName}</small>${variant ? `<br /><small>(${variant})</small>` : ''} | ${npmPath} | ${cdnPath} | ✏️ ${githubSource} |`
  204. mdRows.push(mdTableRow)
  205. localeList[localeName] = `${languageName} (${regionName}${variant ? `, ${variant}` : ''})`
  206. })
  207. const resultingMdTable = mdTable.concat(mdRows.sort()).join('\n').replace('%count%', mdRows.length)
  208. const dstpath = path.join(webRoot, 'src', '_template', 'list_of_locale_packs.md')
  209. const localeListDstPath = path.join(webRoot, 'src', 'examples', 'locale_list.json')
  210. fs.writeFileSync(dstpath, `${resultingMdTable}\n`, 'utf-8')
  211. console.info(chalk.green('✓ injected: '), chalk.grey(dstpath))
  212. fs.writeFileSync(localeListDstPath, JSON.stringify(localeList), 'utf-8')
  213. console.info(chalk.green('✓ injected: '), chalk.grey(localeListDstPath))
  214. }
  215. async function readConfig () {
  216. try {
  217. const buf = await fs.promises.readFile(configPath, 'utf8')
  218. return YAML.safeLoad(buf)
  219. } catch (err) {
  220. return {}
  221. }
  222. }
  223. async function inject () {
  224. const config = await readConfig()
  225. await injectGhStars()
  226. await injectMarkdown()
  227. injectLocaleList()
  228. config.uppy_version = version
  229. config.uppy_version_anchor = version.replace(/[^\d]+/g, '')
  230. await injectSizes(config)
  231. const saveConfig = { ...defaultConfig, ...config }
  232. await fs.promises.writeFile(configPath, YAML.safeDump(saveConfig), 'utf-8')
  233. console.info(chalk.green('✓ rewritten: '), chalk.grey(configPath))
  234. try {
  235. await injectBundles()
  236. } catch (error) {
  237. console.error(
  238. chalk.red('x failed to inject: '),
  239. chalk.grey(`uppy bundle into site, because: ${error}`),
  240. )
  241. process.exit(1)
  242. }
  243. }