generate-docs.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. import * as fs from 'fs'
  2. import * as os from 'os'
  3. import * as path from 'path'
  4. import * as yaml from 'js-yaml'
  5. //
  6. // SUMMARY
  7. //
  8. // This script rebuilds the usage section in the README.md to be consistent with the action.yml
  9. function updateUsage(
  10. actionReference: string,
  11. actionYamlPath = 'action.yml',
  12. readmePath = 'README.md',
  13. startToken = '<!-- start usage -->',
  14. endToken = '<!-- end usage -->'
  15. ): void {
  16. if (!actionReference) {
  17. throw new Error('Parameter actionReference must not be empty')
  18. }
  19. // Load the action.yml
  20. const actionYaml = yaml.safeLoad(fs.readFileSync(actionYamlPath).toString())
  21. // Load the README
  22. const originalReadme = fs.readFileSync(readmePath).toString()
  23. // Find the start token
  24. const startTokenIndex = originalReadme.indexOf(startToken)
  25. if (startTokenIndex < 0) {
  26. throw new Error(`Start token '${startToken}' not found`)
  27. }
  28. // Find the end token
  29. const endTokenIndex = originalReadme.indexOf(endToken)
  30. if (endTokenIndex < 0) {
  31. throw new Error(`End token '${endToken}' not found`)
  32. } else if (endTokenIndex < startTokenIndex) {
  33. throw new Error('Start token must appear before end token')
  34. }
  35. // Build the new README
  36. const newReadme: string[] = []
  37. // Append the beginning
  38. newReadme.push(originalReadme.substr(0, startTokenIndex + startToken.length))
  39. // Build the new usage section
  40. newReadme.push('```yaml', `- uses: ${actionReference}`, ' with:')
  41. const inputs = actionYaml.inputs
  42. let firstInput = true
  43. for (const key of Object.keys(inputs)) {
  44. const input = inputs[key]
  45. // Line break between inputs
  46. if (!firstInput) {
  47. newReadme.push('')
  48. }
  49. // Constrain the width of the description
  50. const width = 80
  51. let description = (input.description as string)
  52. .trimRight()
  53. .replace(/\r\n/g, '\n') // Convert CR to LF
  54. .replace(/ +/g, ' ') // Squash consecutive spaces
  55. .replace(/ \n/g, '\n') // Squash space followed by newline
  56. while (description) {
  57. // Longer than width? Find a space to break apart
  58. let segment: string = description
  59. if (description.length > width) {
  60. segment = description.substr(0, width + 1)
  61. while (!segment.endsWith(' ') && !segment.endsWith('\n') && segment) {
  62. segment = segment.substr(0, segment.length - 1)
  63. }
  64. // Trimmed too much?
  65. if (segment.length < width * 0.67) {
  66. segment = description
  67. }
  68. } else {
  69. segment = description
  70. }
  71. // Check for newline
  72. const newlineIndex = segment.indexOf('\n')
  73. if (newlineIndex >= 0) {
  74. segment = segment.substr(0, newlineIndex + 1)
  75. }
  76. // Append segment
  77. newReadme.push(` # ${segment}`.trimRight())
  78. // Remaining
  79. description = description.substr(segment.length)
  80. }
  81. if (input.default !== undefined) {
  82. // Append blank line if description had paragraphs
  83. if ((input.description as string).trimRight().match(/\n[ ]*\r?\n/)) {
  84. newReadme.push(` #`)
  85. }
  86. // Default
  87. newReadme.push(` # Default: ${input.default}`)
  88. }
  89. // Input name
  90. newReadme.push(` ${key}: ''`)
  91. firstInput = false
  92. }
  93. newReadme.push('```')
  94. // Append the end
  95. newReadme.push(originalReadme.substr(endTokenIndex))
  96. // Write the new README
  97. fs.writeFileSync(readmePath, newReadme.join(os.EOL))
  98. }
  99. updateUsage(
  100. 'actions/checkout@v4',
  101. path.join(__dirname, '..', '..', 'action.yml'),
  102. path.join(__dirname, '..', '..', 'README.md')
  103. )