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