change-version.mjs 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. #!/usr/bin/env node
  2. /*!
  3. * Script to update version number references in the project.
  4. * Copyright 2017-2025 The Bootstrap Authors
  5. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
  6. */
  7. import { execFile } from 'node:child_process'
  8. import fs from 'node:fs/promises'
  9. import process from 'node:process'
  10. const VERBOSE = process.argv.includes('--verbose')
  11. const DRY_RUN = process.argv.includes('--dry') || process.argv.includes('--dry-run')
  12. // These are the files we only care about replacing the version
  13. const FILES = [
  14. 'README.md',
  15. 'config.yml',
  16. 'js/src/base-component.js',
  17. 'package.js',
  18. 'scss/mixins/_banner.scss',
  19. 'site/data/docs-versions.yml'
  20. ]
  21. // Blame TC39... https://github.com/benjamingr/RegExp.escape/issues/37
  22. function regExpQuote(string) {
  23. return string.replace(/[$()*+-.?[\\\]^{|}]/g, '\\$&')
  24. }
  25. function regExpQuoteReplacement(string) {
  26. return string.replace(/\$/g, '$$')
  27. }
  28. async function replaceRecursively(file, oldVersion, newVersion) {
  29. const originalString = await fs.readFile(file, 'utf8')
  30. const newString = originalString
  31. .replace(
  32. new RegExp(regExpQuote(oldVersion), 'g'),
  33. regExpQuoteReplacement(newVersion)
  34. )
  35. // Also replace the version used by the rubygem,
  36. // which is using periods (`.`) instead of hyphens (`-`)
  37. .replace(
  38. new RegExp(regExpQuote(oldVersion.replace(/-/g, '.')), 'g'),
  39. regExpQuoteReplacement(newVersion.replace(/-/g, '.'))
  40. )
  41. // No need to move any further if the strings are identical
  42. if (originalString === newString) {
  43. return
  44. }
  45. if (VERBOSE) {
  46. console.log(`Found ${oldVersion} in ${file}`)
  47. }
  48. if (DRY_RUN) {
  49. return
  50. }
  51. await fs.writeFile(file, newString, 'utf8')
  52. }
  53. function bumpNpmVersion(newVersion) {
  54. if (DRY_RUN) {
  55. return
  56. }
  57. execFile('npm', ['version', newVersion, '--no-git-tag'], { shell: true }, error => {
  58. if (error) {
  59. console.error(error)
  60. process.exit(1)
  61. }
  62. })
  63. }
  64. function showUsage(args) {
  65. console.error('USAGE: change-version old_version new_version [--verbose] [--dry[-run]]')
  66. console.error('Got arguments:', args)
  67. process.exit(1)
  68. }
  69. async function main(args) {
  70. let [oldVersion, newVersion] = args
  71. if (!oldVersion || !newVersion) {
  72. showUsage(args)
  73. }
  74. // Strip any leading `v` from arguments because
  75. // otherwise we will end up with duplicate `v`s
  76. [oldVersion, newVersion] = [oldVersion, newVersion].map(arg => {
  77. return arg.startsWith('v') ? arg.slice(1) : arg
  78. })
  79. if (oldVersion === newVersion) {
  80. showUsage(args)
  81. }
  82. bumpNpmVersion(newVersion)
  83. try {
  84. await Promise.all(
  85. FILES.map(file => replaceRecursively(file, oldVersion, newVersion))
  86. )
  87. } catch (error) {
  88. console.error(error)
  89. process.exit(1)
  90. }
  91. }
  92. main(process.argv.slice(2))