diff --git a/.github/workflows/retag-release.yaml b/.github/workflows/retag-release.yaml new file mode 100644 index 000000000..5f4d26c69 --- /dev/null +++ b/.github/workflows/retag-release.yaml @@ -0,0 +1,20 @@ +# This script adds a "vX.Y.Z" tag to every new release. +# +# Our releases are tagged like "1.2.3" but we want people to be able to write +# GitHub Action workflows to say "uses: readmeio/readme@v1.2.3" because that's +# the usual GitHub convention. + +name: retag-release + +on: + release: + types: [created] + +jobs: + retag-release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/github-script@v6 + with: + script: require('./bin/retag-release.js')(github, context); diff --git a/bin/retag-release.js b/bin/retag-release.js new file mode 100644 index 000000000..113e6098d --- /dev/null +++ b/bin/retag-release.js @@ -0,0 +1,42 @@ +/* eslint-disable no-console */ +module.exports = async (github, context) => { + const { owner, repo } = context.repo; + const oldTag = context.payload.release.tag_name; + if (!oldTag.match(/^[0-9]+\.[0-9]+\.[0-9]+$/)) { + console.log('Not retagging this release: This script will only retag releases that use'); + console.log(`semantic versioning, like "1.2.3", but this release's tag is "${oldTag}".`); + return {}; + } + const newTag = `v${oldTag}`; + console.log(`Retagging release "${oldTag}" as "${newTag}".`); + + const oldRef = await github.rest.git.getRef({ + owner, + repo, + ref: `tags/${oldTag}`, + }); + if (oldRef.status < 200 || oldRef.status >= 400) { + console.log(oldRef); + throw new Error(`GitHub API call returned HTTP status code ${oldRef.status}`); + } + const sha = oldRef.data.object.sha; + console.log(`Found tag "${oldTag}"; commit hash is ${sha}`); + + console.log(`Creating tag "${newTag}" pointing to commit hash ${sha}...`); + const newRef = await github.rest.git.createRef({ + owner, + repo, + ref: `refs/tags/${newTag}`, + sha, + }); + if (newRef.status < 200 || newRef.status >= 400) { + console.log(newRef); + throw new Error(`GitHub API call returned HTTP status code ${newRef.status}`); + } + console.log('Successfully retagged this release.'); + return { + original_tag: oldTag, + new_tag: newTag, + sha, + }; +}; diff --git a/src/cmds/changelogs/index.js b/src/cmds/changelogs/index.js index 5912ebb55..df61fe01d 100644 --- a/src/cmds/changelogs/index.js +++ b/src/cmds/changelogs/index.js @@ -1,10 +1,9 @@ const chalk = require('chalk'); const config = require('config'); -const fs = require('fs'); -const path = require('path'); const { debug } = require('../../lib/logger'); const pushDoc = require('../../lib/pushDoc'); +const { readdirRecursive } = require('../../lib/pushDoc'); module.exports = class ChangelogsCommand { constructor() { @@ -48,19 +47,6 @@ module.exports = class ChangelogsCommand { return Promise.reject(new Error(`No folder provided. Usage \`${config.get('cli')} ${this.usage}\`.`)); } - // Find the files to sync - const readdirRecursive = folderToSearch => { - const filesInFolder = fs.readdirSync(folderToSearch, { withFileTypes: true }); - const files = filesInFolder - .filter(fileHandle => fileHandle.isFile()) - .map(fileHandle => path.join(folderToSearch, fileHandle.name)); - const folders = filesInFolder.filter(fileHandle => fileHandle.isDirectory()); - const subFiles = [].concat( - ...folders.map(fileHandle => readdirRecursive(path.join(folderToSearch, fileHandle.name))) - ); - return [...files, ...subFiles]; - }; - // Strip out non-markdown files const files = readdirRecursive(folder).filter( file => file.toLowerCase().endsWith('.md') || file.toLowerCase().endsWith('.markdown') diff --git a/src/cmds/custompages/index.js b/src/cmds/custompages/index.js index 5e9c9e802..36fe4f27f 100644 --- a/src/cmds/custompages/index.js +++ b/src/cmds/custompages/index.js @@ -1,10 +1,9 @@ const chalk = require('chalk'); const config = require('config'); -const fs = require('fs'); -const path = require('path'); const { debug } = require('../../lib/logger'); const pushDoc = require('../../lib/pushDoc'); +const { readdirRecursive } = require('../../lib/pushDoc'); module.exports = class CustomPagesCommand { constructor() { @@ -48,19 +47,6 @@ module.exports = class CustomPagesCommand { return Promise.reject(new Error(`No folder provided. Usage \`${config.get('cli')} ${this.usage}\`.`)); } - // Find the files to sync - const readdirRecursive = folderToSearch => { - const filesInFolder = fs.readdirSync(folderToSearch, { withFileTypes: true }); - const files = filesInFolder - .filter(fileHandle => fileHandle.isFile()) - .map(fileHandle => path.join(folderToSearch, fileHandle.name)); - const folders = filesInFolder.filter(fileHandle => fileHandle.isDirectory()); - const subFiles = [].concat( - ...folders.map(fileHandle => readdirRecursive(path.join(folderToSearch, fileHandle.name))) - ); - return [...files, ...subFiles]; - }; - // Strip out non-markdown files const files = readdirRecursive(folder).filter( file => diff --git a/src/cmds/docs/index.js b/src/cmds/docs/index.js index a06cc1cfe..3ddd47d56 100644 --- a/src/cmds/docs/index.js +++ b/src/cmds/docs/index.js @@ -1,11 +1,10 @@ const chalk = require('chalk'); const config = require('config'); -const fs = require('fs'); -const path = require('path'); const { getProjectVersion } = require('../../lib/versionSelect'); const { debug } = require('../../lib/logger'); const pushDoc = require('../../lib/pushDoc'); +const { readdirRecursive } = require('../../lib/pushDoc'); module.exports = class DocsCommand { constructor() { @@ -61,19 +60,6 @@ module.exports = class DocsCommand { debug(`selectedVersion: ${selectedVersion}`); - // Find the files to sync - const readdirRecursive = folderToSearch => { - const filesInFolder = fs.readdirSync(folderToSearch, { withFileTypes: true }); - const files = filesInFolder - .filter(fileHandle => fileHandle.isFile()) - .map(fileHandle => path.join(folderToSearch, fileHandle.name)); - const folders = filesInFolder.filter(fileHandle => fileHandle.isDirectory()); - const subFiles = [].concat( - ...folders.map(fileHandle => readdirRecursive(path.join(folderToSearch, fileHandle.name))) - ); - return [...files, ...subFiles]; - }; - // Strip out non-markdown files const files = readdirRecursive(folder).filter( file => file.toLowerCase().endsWith('.md') || file.toLowerCase().endsWith('.markdown') diff --git a/src/lib/pushDoc.js b/src/lib/pushDoc.js index 0d507580e..5c973a444 100644 --- a/src/lib/pushDoc.js +++ b/src/lib/pushDoc.js @@ -117,3 +117,21 @@ module.exports = async function pushDoc(key, selectedVersion, dryRun, filepath, throw err; }); }; + +/** + * Recursively grabs all files within a given directory + * (including subdirectories) + * @param {String} folderToSearch path to directory + * @returns {String[]} array of files + */ +module.exports.readdirRecursive = function readdirRecursive(folderToSearch) { + const filesInFolder = fs.readdirSync(folderToSearch, { withFileTypes: true }); + const files = filesInFolder + .filter(fileHandle => fileHandle.isFile()) + .map(fileHandle => path.join(folderToSearch, fileHandle.name)); + const folders = filesInFolder.filter(fileHandle => fileHandle.isDirectory()); + const subFiles = [].concat( + ...folders.map(fileHandle => readdirRecursive(path.join(folderToSearch, fileHandle.name))) + ); + return [...files, ...subFiles]; +};