version-packages.mjs 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. import { exec, spawnSync } from 'node:child_process'
  2. import { randomUUID } from 'node:crypto'
  3. import fs from 'node:fs/promises'
  4. import { tmpdir } from 'node:os'
  5. import path from 'node:path'
  6. import url from 'node:url'
  7. import prettier from 'prettier'
  8. const __dirname = path.dirname(url.fileURLToPath(import.meta.url))
  9. const root = path.resolve(__dirname, '..')
  10. // The known workspace is: @tailwindcss/oxide
  11. // All the workspaces in `crates/node/npm/*` should always be in sync with
  12. // `@tailwindcss/oxide`. You can think of them as one big package, but they are
  13. // split into multiple packages because they are OS specific.
  14. const syncedWorkspaces = new Map([
  15. [
  16. '@tailwindcss/oxide',
  17. [
  18. 'crates/node/npm/android-arm-eabi',
  19. 'crates/node/npm/android-arm64',
  20. 'crates/node/npm/darwin-arm64',
  21. 'crates/node/npm/darwin-x64',
  22. 'crates/node/npm/freebsd-x64',
  23. 'crates/node/npm/linux-arm-gnueabihf',
  24. 'crates/node/npm/linux-arm64-gnu',
  25. 'crates/node/npm/linux-arm64-musl',
  26. 'crates/node/npm/linux-x64-gnu',
  27. 'crates/node/npm/linux-x64-musl',
  28. 'crates/node/npm/win32-x64-msvc',
  29. ],
  30. ],
  31. ['@tailwindcss/cli', ['packages/@tailwindcss-standalone']],
  32. ])
  33. const inverseSyncedWorkspaces = new Map()
  34. for (let [name, paths] of syncedWorkspaces) {
  35. for (let [idx, filePath] of paths.entries()) {
  36. // Make sure all the paths are absolute paths
  37. paths[idx] = path.resolve(root, filePath, 'package.json')
  38. // Make sure inverse lookup table exists
  39. inverseSyncedWorkspaces.set(paths[idx], name)
  40. }
  41. }
  42. exec('pnpm --silent --filter=!./playgrounds/* -r exec pwd', async (err, stdout) => {
  43. if (err) {
  44. console.error(err)
  45. process.exit(1)
  46. }
  47. let paths = stdout
  48. .trim()
  49. .split('\n')
  50. .map((x) => path.resolve(x, 'package.json'))
  51. // Workspaces that are in sync with another workspace should not be updated
  52. // manually, they should be updated by updating the main workspace.
  53. .filter((x) => !inverseSyncedWorkspaces.has(x))
  54. let workspaces = new Map()
  55. // Track all the workspaces
  56. for (let path of paths) {
  57. let pkg = await fs.readFile(path, 'utf8').then(JSON.parse)
  58. if (pkg.private) continue
  59. workspaces.set(pkg.name, { version: pkg.version ?? '', path })
  60. }
  61. // Build the editable output
  62. let lines = ['# Update the versions of the packages you want to change', '']
  63. for (let [name, info] of workspaces) {
  64. lines.push(`${name}: ${info.version}`)
  65. }
  66. let output = lines.join('\n')
  67. // Edit the file
  68. {
  69. // Figure out which editor to use.
  70. //
  71. // In this case we still split on whitespace, because it can happen that the
  72. // EDITOR env variable is configured as `code --wait`. This means that we
  73. // want `code` as the editor, but `--wait` is one of the arguments.
  74. let args = process.env.EDITOR.split(' ')
  75. let editor = args.shift()
  76. // Create a temporary file which will be edited
  77. let filepath = path.resolve(tmpdir(), `version-${randomUUID()}.txt`)
  78. await fs.writeFile(filepath, output)
  79. // Edit the file, once the editor is closed, the file will be saved and we
  80. // can read the changes
  81. spawnSync(editor, [...args, filepath], {
  82. stdio: 'inherit',
  83. })
  84. let newOutput = await fs.readFile(filepath, 'utf8').then((x) => x.trim().split('\n'))
  85. // Cleanup temporary file
  86. await fs.unlink(filepath)
  87. // Update the package.json files
  88. for (let line of newOutput) {
  89. if (line[0] === '#') continue // Skip comment lines
  90. if (line.trim() === '') continue // Skip empty lines
  91. let [name, version = ''] = line.split(':').map((x) => x.trim())
  92. // Figure out all the paths to the package.json files that need to be
  93. // updated with the new version
  94. let paths = [
  95. // The package.json file of the main workspace
  96. workspaces.get(name).path,
  97. // The package.json files of the workspaces that are in sync with the
  98. // main workspace
  99. ...(syncedWorkspaces.get(name) ?? []),
  100. ]
  101. for (let pkgPath of paths) {
  102. let pkg = await fs.readFile(pkgPath, 'utf8').then(JSON.parse)
  103. let name = pkg.name
  104. if (version !== '') {
  105. // Ensure the version is set after the name and before everything else
  106. delete pkg.name
  107. delete pkg.version
  108. // This allows us to keep the order of the keys in the package.json
  109. pkg = { name, version, ...pkg }
  110. }
  111. await fs.writeFile(
  112. pkgPath,
  113. await prettier
  114. .format(JSON.stringify(pkg, null, 2), { filepath: pkgPath })
  115. .then((x) => `${x.trim()}\n`),
  116. )
  117. }
  118. }
  119. }
  120. console.log('Done.')
  121. })