Skip to content

Commit

Permalink
docs: automatically build and publish versioned docs
Browse files Browse the repository at this point in the history
  • Loading branch information
Dan-Shields authored and Alex Van Camp committed Aug 30, 2019
1 parent 484d839 commit bdb60cb
Show file tree
Hide file tree
Showing 8 changed files with 5,789 additions and 10,608 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ npm-debug.log
/bundles_off
/_workingTest

# Compiled docs
# Docs
checkout
docs
.grunt

Expand Down
3 changes: 1 addition & 2 deletions .jsdoc.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@
"private": true,
"recurse": true,
"template": "./node_modules/minami",
"tutorials": "./tutorials",
"destination": "./docs/"
"tutorials": "./tutorials"
},
"markdown": {
"idInHeadings": true
Expand Down
7 changes: 7 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ jobs:
skip_cleanup: true
on:
tags: true
- stage: "Build Docs"
if: type = push AND branch = master
script: skip
deploy:
skip_cleanup: true
provider: script
script: npm run docs:publish
notifications:
webhooks:
urls:
Expand Down
16,076 changes: 5,477 additions & 10,599 deletions package-lock.json

Large diffs are not rendered by default.

15 changes: 9 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,15 @@
"test": "npm run typetest && nyc --reporter=none ava --config ava.config.js && nyc report --reporter=html --reporter=text",
"report-coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov",
"static": "eslint index.js lib src test",
"build": "npm run static && npm run docs:build && gulp",
"build": "npm run static && gulp",
"instrument": "nyc instrument ./src ./instrumented",
"postinstrument": "node test/helpers/retarget-browser-coverage.js",
"docs": "npm run docs:build && npm run docs:publish",
"docs:build": "rimraf docs && jsdoc -c .jsdoc.json && node scripts/readme-toc.js",
"docs:publish": "npm run docs:build && echo nodecg.com >> docs/CNAME && gh-pages -d docs -s \"**\"",
"docs:build": "node scripts/docs-build-all.js",
"docs:publish": "node scripts/docs-build-all.js publish",
"docs:serve": "superstatic",
"prerelease": "npm t",
"release": "standard-version",
"postrelease": "npm publish && git push --follow-tags && npm run docs:publish",
"postrelease": "npm publish && git push --follow-tags",
"typetest": "cd typetest/fake-bundle && npm i && npm run build"
},
"nyc": {
Expand Down Expand Up @@ -169,6 +169,8 @@
"babelify": "^10.0.0",
"brfs": "^2.0.2",
"browserify": "^16.2.3",
"child-process-promise": "^2.2.1",
"cli-spinner": "^0.2.10",
"del": "^4.1.0",
"eslint": "^6.1.0",
"eslint-config-xo": "^0.26.0",
Expand All @@ -180,7 +182,7 @@
"gulp-util": "^3.0.8",
"is-windows": "^1.0.2",
"jsdoc": "^3.6.2",
"minami": "github:nodecg/minami#fbe470b796dd26f2b3d04584e5fc90451bea3ab8",
"minami": "github:nodecg/minami#75b4bbfc7e9d834f840be2358f4ddedf738cbfa8",
"nyc": "^14.1.1",
"p-retry": "^4.1.0",
"puppeteer": "^1.19.0",
Expand All @@ -192,6 +194,7 @@
"sinon": "^7.3.1",
"socket.io-client": "^2.2.0",
"standard-version": "^6.0.1",
"superstatic": "^6.0.4",
"taffydb": "^2.7.3",
"temp": "^0.9.0",
"vinyl-buffer": "^1.0.1",
Expand Down
281 changes: 281 additions & 0 deletions scripts/docs-build-all.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
const git = require('simple-git/promise')();
const semver = require('semver');
const exec = require('child-process-promise').exec;
const spin = require('cli-spinner').Spinner;
const readline = require('readline');
const ghpages = require('gh-pages');
const fs = require('fs');
const rimraf = require('rimraf');

const isCI = process.env.CONTINUOUS_INTEGRATION || false;
const docsSite = process.env.DOCS_SITE || 'nodecg.com';

// These can't really be changed without a fair few changes to the code
const checkoutDir = './checkout';
const buildDir = './docs';

const shouldPush = process.argv.slice(2)[0] === 'publish';

let remote;
let gitUser;

if (isCI) {
const ghToken = process.env.GITHUB_TOKEN;
const gitSlug = process.env.TRAVIS_REPO_SLUG;

remote = 'https://' + ghToken + '@github.com/' + gitSlug;

gitUser = {
name: 'NodeCG Docs Bot',
email: '[email protected]'
};
}

let spinner;

function startSpinner(text) {
spinner = spin(text + ' %s');
spinner.start();
}

function stopSpinner(text = null, fail = false) {
// Red text if this is a failure message, green if not
const color = fail ? '\x1b[31m' : '\x1b[32m';

spinner.stop();
process.stdout.clearLine();
readline.cursorTo(process.stdout, 0);

if (text) {
process.stdout.write(color + text + '\x1b[0m');
}

process.stdout.write('\n');
}

// Build and publish docs for the current commit
function buildLatest() {
return exec('node ./scripts/readme-toc.js')
.then(() => {
return exec('jsdoc -c .jsdoc.json -d ' + buildDir + '/master');
});
}

// Search git tags and find versions that should be built
function fetchVersions() {
return git.tags()
.then(tags => {
const desiredVersions = [];

// Get all version tags. Only go as far back as 0.7
const allVersions = tags.all.filter(tag => semver.valid(tag) && semver.gte(tag, '0.7.0'));

// Find the latest patch for each major.minor version and add it to desiredVersions
allVersions.forEach(version => {
if (semver.maxSatisfying(allVersions, semver.major(version) + '.' + semver.minor(version) + '.x') === version) {
desiredVersions.push({tag: version, versionName: 'v' + semver.major(version) + '.' + semver.minor(version)});
}
});

return desiredVersions;
});
}

function delBuildFolder() {
return new Promise(resolve => {
rimraf(buildDir, resolve);
});
}

function delCheckoutDirectory() {
return new Promise(resolve => {
rimraf(checkoutDir, resolve);
});
}

// Build docs for a specific version
function buildVersion(version) {
// Delete and recreate checkout directory
return delCheckoutDirectory()
.then(() => {
fs.mkdirSync(checkoutDir, {recursive: true});
})

.then(() => {
return git
.raw(['--work-tree=' + checkoutDir, 'checkout', version.tag, '--', './tutorials'])
.then(() => git.raw(['--work-tree=' + checkoutDir, 'checkout', version.tag, '--', './lib/api.js']))
.then(() => git.raw(['--work-tree=' + checkoutDir, 'checkout', version.tag, '--', './README.md']))
.then(() => git.raw(['--work-tree=' + checkoutDir, 'checkout', version.tag, '--', './scripts/readme-toc.js']));
})

.then(() => {
return exec('node ./scripts/readme-toc.js', {cwd: checkoutDir});
})
.catch(() => {
// Some versions have no reademe-toc script, so continue if it errors
})
.then(() => {
// Run the docs build command and output to version sub-directory
return exec('jsdoc -c ../.jsdoc.json -d ../' + buildDir + '/' + version.versionName, {cwd: checkoutDir});
});
}

function createIndexFile(redirectVersion) {
const indexFile = buildDir + '/index.html';

// Copy index template into docs directory
return new Promise((resolve, reject) => {
fs.copyFile('./scripts/docs_index.html', indexFile, err => {
if (err) {
reject(err);
} else {
resolve();
}
});
})
.then(() => {
// Read the copied template
return new Promise((resolve, reject) => {
fs.readFile(indexFile, 'utf8', (err, data) => {
if (err) {
console.log(err);
reject(err);
} else {
resolve(data);
}
});
});
})
.then(data => {
const result = data.replace(/%VERSION%/g, redirectVersion);

// Rewrite the file with the applied version
return new Promise((resolve, reject) => {
fs.writeFile(indexFile, result, 'utf8', err => {
if (err) {
console.log(err);
reject(err);
} else {
resolve();
}
});
});
});
}

function createVersionsFile(versions) {
const versionsFile = buildDir + '/versions.json';

// Sort versions in descending order
versions.sort((a, b) => semver.rcompare(a.tag, b.tag));

// Add master at the start
versions.unshift({versionName: 'master', tag: 'master'});

const fileContents = JSON.stringify(versions);

// Write JSON array of versions to file
return new Promise((resolve, reject) => {
fs.writeFile(versionsFile, fileContents, 'utf8', err => {
if (err) {
console.log(err);
reject(err);
} else {
resolve();
}
});
});
}

function publish() {
return exec('echo ' + docsSite + ' >> ' + buildDir + '/CNAME')
.then(() => {
return new Promise((resolve, reject) => {
ghpages.publish(buildDir, {
message: 'Update docs',
src: '**',
repo: remote,
user: gitUser,
silent: true
}, err => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
});
}

async function run() {
startSpinner('Fetching versions...');

// These tasks can run simultaneously
const setupResults = await Promise.all([fetchVersions(), delBuildFolder()]);
const versionsToBuild = setupResults[0];

stopSpinner('Fetched Versions');

// Build latest from current commit
startSpinner('Building latest...');
await buildLatest();
stopSpinner('Built latest');

let redirectVersion = 'master';

if (versionsToBuild.length > 0) {
// Build all found versions
for (let i = 0; i < versionsToBuild.length; i++) {
startSpinner('Building ' + versionsToBuild[i].versionName + '...');

try {
// eslint-disable-next-line no-await-in-loop
await buildVersion(versionsToBuild[i]);

stopSpinner('Built ' + versionsToBuild[i].versionName);
} catch (err) {
stopSpinner('Failed to build ' + versionsToBuild[i].versionName, true);
process.stdout.write(err.stderr || err);
process.stdout.write('\n');
}
}

await delCheckoutDirectory();

startSpinner('Creating versions file...');
await createVersionsFile(versionsToBuild);
stopSpinner('Created Versions File');

redirectVersion = versionsToBuild[versionsToBuild.length - 1].versionName;
} else {
console.log('No tags found to build, only master built');
}

startSpinner('Creating index file...');
await createIndexFile(redirectVersion);
stopSpinner('Created Index File');

await git.raw(['reset']);

if (shouldPush) {
startSpinner('Publishing to gh-pages...');

try {
await publish();

stopSpinner('Successfully Published');
} catch (err) {
stopSpinner('FAILED TO PUBLISH', true);
process.exit(1);
}
} else {
console.log('\x1b[33m%s\x1b[0m', 'Skipping Publish');
}

console.log();
console.log('\x1b[42mDocs Build complete\x1b[0m');
}

run();
8 changes: 8 additions & 0 deletions scripts/docs_index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!DOCTYPE html>
<html lang="en">
<head>
<script>
window.location = "%VERSION%";
</script>
</head>
</html>
4 changes: 4 additions & 0 deletions superstatic.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"public": "docs",
"trailingSlash": true
}

0 comments on commit bdb60cb

Please sign in to comment.