2022-03-02 01:55:43 +00:00
|
|
|
import {EOL} from 'os'
|
2022-03-02 01:36:04 +00:00
|
|
|
import {constants, promises} from 'fs'
|
|
|
|
const {access, appendFile, writeFile} = promises
|
2022-02-23 23:09:05 +00:00
|
|
|
|
2022-03-02 01:36:04 +00:00
|
|
|
export interface TableCell {
|
|
|
|
/**
|
|
|
|
* Cell content
|
|
|
|
*/
|
|
|
|
data: string
|
|
|
|
/**
|
|
|
|
* Render cell as header
|
|
|
|
* (optional) default: false
|
|
|
|
*/
|
|
|
|
header?: boolean
|
|
|
|
/**
|
|
|
|
* Number of columns the cell extends
|
|
|
|
* (optional) default: '1'
|
|
|
|
*/
|
|
|
|
colspan?: string
|
|
|
|
/**
|
|
|
|
* Number of rows the cell extends
|
|
|
|
* (optional) default: '1'
|
|
|
|
*/
|
|
|
|
rowspan?: string
|
|
|
|
}
|
2022-02-23 23:09:05 +00:00
|
|
|
|
|
|
|
export class MarkdownSummary {
|
|
|
|
static ENV_VAR = 'GITHUB_STEP_SUMMARY'
|
|
|
|
private buffer: string
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
this.buffer = ''
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Finds the summary file path from the environment, rejects if not found
|
|
|
|
*
|
|
|
|
* @returns step summary file path
|
|
|
|
*/
|
|
|
|
private async filePath(): Promise<string> {
|
|
|
|
const filePath = process.env[MarkdownSummary.ENV_VAR]
|
|
|
|
if (!filePath) {
|
|
|
|
throw new Error(
|
|
|
|
`Unable to find environment variable for ${MarkdownSummary.ENV_VAR}`
|
|
|
|
)
|
|
|
|
}
|
2022-03-02 01:36:04 +00:00
|
|
|
|
|
|
|
try {
|
|
|
|
await access(filePath, constants.R_OK | constants.W_OK)
|
|
|
|
} catch {
|
|
|
|
throw new Error(`Unable to access summary file: ${filePath}`)
|
2022-02-23 23:09:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return filePath
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-03-02 01:36:04 +00:00
|
|
|
* Wraps content in an html tag, adding any HTML attributes
|
|
|
|
*
|
|
|
|
* @param tag HTML tag to wrap
|
|
|
|
* @param content content within the tag
|
|
|
|
* @param attrs key value list of html attributes to add
|
|
|
|
*/
|
|
|
|
private wrap(
|
|
|
|
tag: string,
|
|
|
|
content: string,
|
|
|
|
attrs: {[key: string]: string} = {}
|
|
|
|
): string {
|
|
|
|
const htmlAttrs = Object.entries(attrs)
|
|
|
|
.map(([key, value]) => `${key}="${value}"`)
|
|
|
|
.join(' ')
|
|
|
|
|
|
|
|
return `<${tag}${htmlAttrs && htmlAttrs.padStart(1)}>${content}</${tag}>`
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Writes text in the buffer to the summary buffer file, will append by default
|
|
|
|
*
|
|
|
|
* @param {boolean} [overwrite=false] (optional) replace existing content in summary file with buffer contents, default: false
|
2022-02-23 23:09:05 +00:00
|
|
|
*
|
|
|
|
* @returns {MarkdownSummary} markdown summary instance
|
|
|
|
*/
|
2022-03-02 01:36:04 +00:00
|
|
|
async write(overwrite = false): Promise<MarkdownSummary> {
|
2022-02-23 23:09:05 +00:00
|
|
|
const filePath = await this.filePath()
|
2022-03-02 01:36:04 +00:00
|
|
|
const writeFunc = overwrite ? writeFile : appendFile
|
|
|
|
await writeFunc(filePath, this.buffer, {encoding: 'utf8'})
|
|
|
|
return this.clearBuffer()
|
|
|
|
}
|
2022-02-23 23:09:05 +00:00
|
|
|
|
2022-03-02 01:36:04 +00:00
|
|
|
/**
|
|
|
|
* If the summary buffer is empty
|
|
|
|
*
|
|
|
|
* @returns {boolen} true if the buffer is empty
|
|
|
|
*/
|
|
|
|
isEmptyBuffer(): boolean {
|
|
|
|
return this.buffer.length === 0
|
2022-02-23 23:09:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clears the summary buffer without writing to summary file
|
|
|
|
*
|
|
|
|
* @returns {MarkdownSummary} markdown summary instance
|
|
|
|
*/
|
2022-03-02 01:36:04 +00:00
|
|
|
clearBuffer(): MarkdownSummary {
|
2022-02-23 23:09:05 +00:00
|
|
|
this.buffer = ''
|
|
|
|
return this
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-03-02 01:55:43 +00:00
|
|
|
* Adds raw text to the summary buffer
|
|
|
|
*
|
|
|
|
* @param {string} text content to add
|
2022-02-23 23:09:05 +00:00
|
|
|
*
|
|
|
|
* @returns {MarkdownSummary} markdown summary instance
|
|
|
|
*/
|
2022-03-02 01:55:43 +00:00
|
|
|
add(text: string): MarkdownSummary {
|
|
|
|
this.buffer += text
|
2022-02-23 23:09:05 +00:00
|
|
|
return this
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-03-02 01:55:43 +00:00
|
|
|
* Adds the operating system-specific end-of-line marker to the buffer
|
2022-03-02 01:36:04 +00:00
|
|
|
*
|
2022-02-23 23:09:05 +00:00
|
|
|
* @returns {MarkdownSummary} markdown summary instance
|
|
|
|
*/
|
2022-03-02 01:55:43 +00:00
|
|
|
addEOL(): MarkdownSummary {
|
|
|
|
return this.add(EOL)
|
2022-02-23 23:09:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-03-02 01:36:04 +00:00
|
|
|
* Adds an HTML codeblock to the summary buffer
|
2022-02-23 23:09:05 +00:00
|
|
|
*
|
|
|
|
* @param {string} code content to render within fenced code block
|
2022-03-02 01:36:04 +00:00
|
|
|
* @param {string} lang (optional) language to syntax highlight code
|
2022-02-23 23:09:05 +00:00
|
|
|
*
|
|
|
|
* @returns {MarkdownSummary} markdown summary instance
|
|
|
|
*/
|
2022-03-02 01:36:04 +00:00
|
|
|
addCodeBlock(code: string, lang?: string): MarkdownSummary {
|
|
|
|
const attrs = {
|
|
|
|
...(lang && {lang})
|
|
|
|
}
|
|
|
|
const element = this.wrap('pre', this.wrap('code', code), attrs)
|
2022-03-02 01:55:43 +00:00
|
|
|
return this.add(element).addEOL()
|
2022-02-23 23:09:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-03-02 01:36:04 +00:00
|
|
|
* Adds an HTML list to the summary buffer
|
2022-02-23 23:09:05 +00:00
|
|
|
*
|
|
|
|
* @param {string[]} items list of items to render
|
|
|
|
* @param {boolean} [ordered=false] if the rendered list should be ordered or not (default: false)
|
|
|
|
*
|
|
|
|
* @returns {MarkdownSummary} markdown summary instance
|
|
|
|
*/
|
|
|
|
addList(items: string[], ordered = false): MarkdownSummary {
|
2022-03-02 01:36:04 +00:00
|
|
|
const tag = ordered ? 'ol' : 'ul'
|
|
|
|
const listItems = items.map(item => this.wrap('li', item)).join('')
|
|
|
|
const element = this.wrap(tag, listItems)
|
2022-03-02 01:55:43 +00:00
|
|
|
return this.add(element).addEOL()
|
2022-02-23 23:09:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-03-02 01:36:04 +00:00
|
|
|
* Adds an HTML table to the summary buffer
|
2022-02-23 23:09:05 +00:00
|
|
|
*
|
2022-03-02 01:36:04 +00:00
|
|
|
* @param {TableCell[]} rows table rows
|
2022-02-23 23:09:05 +00:00
|
|
|
*
|
|
|
|
* @returns {MarkdownSummary} markdown summary instance
|
|
|
|
*/
|
2022-03-02 01:36:04 +00:00
|
|
|
addTable(rows: TableCell[][]): MarkdownSummary {
|
|
|
|
const tableBody = rows
|
2022-02-23 23:09:05 +00:00
|
|
|
.map(row => {
|
2022-03-02 01:36:04 +00:00
|
|
|
const cells = row
|
|
|
|
.map(({header, data, colspan, rowspan}) => {
|
|
|
|
const tag = header ? 'th' : 'td'
|
|
|
|
const attrs = {
|
|
|
|
...(colspan && {colspan}),
|
|
|
|
...(rowspan && {rowspan})
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.wrap(tag, data, attrs)
|
|
|
|
})
|
|
|
|
.join('')
|
|
|
|
|
|
|
|
return this.wrap('tr', cells)
|
2022-02-23 23:09:05 +00:00
|
|
|
})
|
2022-03-02 01:36:04 +00:00
|
|
|
.join('')
|
|
|
|
|
|
|
|
const element = this.wrap('table', tableBody)
|
2022-03-02 01:55:43 +00:00
|
|
|
return this.add(element).addEOL()
|
2022-02-23 23:09:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-03-02 01:36:04 +00:00
|
|
|
* Adds a collapsable HTML details element to the summary buffer
|
2022-02-23 23:09:05 +00:00
|
|
|
*
|
|
|
|
* @param {string} label text for the closed state
|
|
|
|
* @param {string} content collapsable content
|
|
|
|
*
|
|
|
|
* @returns {MarkdownSummary} markdown summary instance
|
|
|
|
*/
|
|
|
|
addDetails(label: string, content: string): MarkdownSummary {
|
2022-03-02 01:36:04 +00:00
|
|
|
const element = this.wrap('details', this.wrap('summary', label) + content)
|
2022-03-02 01:55:43 +00:00
|
|
|
return this.add(element).addEOL()
|
2022-02-23 23:09:05 +00:00
|
|
|
}
|
|
|
|
}
|