import 'colors'; import axios from 'axios'; import * as Diff from 'diff'; import semver from 'semver'; import crypto from 'crypto'; import fs from 'fs/promises'; import BeautyHtml from 'beauty-html'; import { DOMParser } from '@xmldom/xmldom'; import core from '@actions/core'; import { Octokit } from '@octokit/rest'; const D360_TOKEN = core.getInput('D360_TOKEN'); const D360_ARTICLE_ID = core.getInput('D360_ARTICLE_ID'); const D360_USER_ID = core.getInput('D360_USER_ID'); const PUBLISH = core.getInput('PUBLISH') === 'true'; const LTS_VERSIONS = (core.getInput('LTS_VERSIONS') || '7.10').split(',').map((v) => v.trim()); const octokit = new Octokit({ auth: core.getInput('GH_TOKEN'), }); async function requestDocument360(method = 'get', api, data = {}) { return axios.request({ method, maxBodyLength: Infinity, url: `https://apihub.us.document360.io/v1/${api}`, headers: { 'accept': 'application/json', 'api_token': D360_TOKEN, }, data, }); } function md5(text) { return crypto.createHash('md5').update(text).digest("hex"); } async function generateTable({ owner, repo } = {}) { const response = await requestDocument360('get', `Articles/${D360_ARTICLE_ID}/en`); // console.log(response.data.data); // const releasesResult = JSON.parse(await fs.readFile('/tmp/releasesResult')); const releasesResult = await octokit.paginate(octokit.repos.listReleases.endpoint.merge({ owner, repo, per_page: 100 })); // await fs.writeFile('/tmp/releasesResult', JSON.stringify(releasesResult)); const releases = releasesResult .filter((release) => !release.tag_name.includes('-rc') && semver.gte(release.tag_name, '1.0.0')) .sort((a, b) => semver.compare(b.tag_name, a.tag_name)); const releasesMap = {}; for (const release of releases) { release.releaseDate = new Date(release.published_at); releasesMap[release.tag_name] = release; } let index = 0; // eslint-disable-next-line no-constant-condition while (true) { const release = releases[index]; release.minor_tag = release.tag_name.replace(/\.\d+$/, ''); release.minorRelease = releasesMap[`${release.minor_tag}.0`]; if (!releases[index + 1]) { break; } const currentVersion = semver.parse(release.tag_name); const previousVersion = semver.parse(releases[index + 1].tag_name); releases[index + 1].nextRelease = release; // Remove duplicated due to patches if (currentVersion.major === previousVersion.major && currentVersion.minor === previousVersion.minor) { releases.splice(index + 1, 1); continue; } // Mark LTS if (LTS_VERSIONS.includes(`${currentVersion.major}.${currentVersion.minor}`)) { release.lts = true; } index++; } releases[0].last = true; const releaseData = []; for (const { tag_name, html_url, lts, last, nextRelease, minorRelease, minor_tag} of releases) { let supportDate; let supportDateStart; let releasedAt = new Date(minorRelease.releaseDate); releasedAt.setDate(1); let minorDate = new Date(minorRelease.releaseDate); minorDate.setDate(1); supportDateStart = minorDate; supportDate = new Date(minorDate); supportDate.setMonth(supportDate.getMonth() + (lts ? 12 : 6)); releaseData.push({ release: { version: minor_tag, releasedAt, extendedSupport: { start: supportDateStart, end: supportDate, }, lts: lts === true, }, latestPatch: { version: tag_name, url: html_url, } }) } function header({data, salt = ''}) { return [ '', `

${data}

`, '', ].join(''); } function line({data, salt = ''}) { return [ '', `

${data}

`, '', ].join(''); } const text = [ '', header({data: 'Rocket.Chat Release'}), header({data: 'Released At'}), header({data: 'End of Life'}), '', ]; releaseData.forEach(({release, latestPatch}) => { const releasedAt = release.releasedAt.toLocaleString('en', { month: 'short', year: "numeric" }); const endOfLife = !release.extendedSupport ? 'TBD' : release.extendedSupport.end.toLocaleString('en', { month: 'short', year: "numeric" }); const link = `${release.version}${release.lts ? " LTS" : ""} (${latestPatch.version})`; text.push( '', line({data: link}), line({data: releasedAt, salt: release.version}), line({data: endOfLife, salt: release.version}), '', ); }); const content = response.data.data.html_content.replace(/.+(\n.+)*<\/tbody>/m, `${text.join('').replace(/\t|\n/g, '')}`) // console.log(content); const parser = new BeautyHtml({ parser: DOMParser }); const diff = Diff.diffLines(parser.beautify(response.data.data.html_content), parser.beautify(content), { ignoreWhitespace: true, newlineIsToken: false }); diff.forEach((item) => { let color = 'green'; if (item.removed) { color = 'red'; } if (item.removed || item.added) { item.value.split('\n').forEach((line) => { if (line === '') { return }; console.log(`${item.removed ? '-' : '+'} ${line}`[color]); }) } }); if (diff.length === 1) { console.log('No changes found'); return; } if (response.data.data.status === 3) { console.log('forking article', response.data.data.version_number); const forkResponse = await requestDocument360('put', `Articles/${D360_ARTICLE_ID}/fork`, { lang_code: "en", user_id: D360_USER_ID, version_number: response.data.data.version_number, }); console.log(forkResponse.data); if (!forkResponse.data.success) { throw new Error(`Failed to fork article: ${JSON.stringify(forkResponse.data.errors)}`); } } console.log('Updating article'); const updateResponse = await requestDocument360('put', `Articles/${D360_ARTICLE_ID}/en`, { content, }); console.log(updateResponse.data); if (PUBLISH) { console.log('publishing article', updateResponse.data.data.version_number); const forkResponse = await requestDocument360('post', `Articles/${D360_ARTICLE_ID}/en/publish`, { user_id: D360_USER_ID, version_number: updateResponse.data.data.version_number, publish_message: 'Update support versions table via GitHub Action', }); console.log(forkResponse.data); } } generateTable({ owner: 'RocketChat', repo: 'Rocket.Chat' });