Files
react-native/docgen/server/build-docs.js
T
2017-10-31 14:09:41 -07:00

524 lines
14 KiB
JavaScript

/**
* Copyright (c) 2017-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
const fetch = require('node-fetch');
const filepath = require('filepath');
const fm = require('front-matter');
const fs = require('fs-extra');
const glob = require('glob-promise');
const jsdom = require('jsdom');
const mkdirp = require('mkdirp');
const Promise = require('bluebird');
const shell = require('shelljs');
const convert = require('./convert.js');
const slugify = require('../core/slugify');
const CWD = process.cwd();
const AUTODOCS_PREFIX = 'autogen_';
const MARKDOWN_EXTENSION = 'md';
const GIT_USER = 'hramos'; //process.env.GIT_USER;
const GITHUB_USERNAME = 'facebook'; // process.env.GITHUB_USERNAME;
const GITHUB_REPONAME = 'react-native'; // process.env.GITHUB_REPONAME;
const remoteBranch = `https://${GIT_USER}@github.com/${GITHUB_USERNAME}/${GITHUB_REPONAME}.git`;
const CHECKOUT_DIR = `${GITHUB_REPONAME}-docs`;
const BUILD_DIR = 'build';
const DOCS_DIR = 'versioned_docs';
const SIDEBAR_DIR = 'versioned_sidebars';
const { JSDOM } = jsdom;
// Start up a server. Don't forget to close the connection when done.
const server = require('./server.js');
server.noconvert = true;
const argv = require('minimist')(process.argv.slice(2), {
alias: {
'c': 'clean'
},
default: {
'autodocs': true
}
});
function runChecks() {
if (!shell.which('git')) {
shell.echo('Sorry, this script requires git');
shell.exit(1);
}
if (!GIT_USER) {
shell.echo('GIT_USER undefined.');
shell.exit(1);
}
if (!GITHUB_USERNAME) {
shell.echo('GITHUB_USERNAME undefined.');
shell.exit(1);
}
if (!GITHUB_REPONAME) {
shell.echo('GITHUB_REPONAME undefined.');
shell.exit(1);
}
}
function cleanFiles() {
return fs.remove(BUILD_DIR);
}
/**
* Generates documentation for a given file.
*
* @param {*} file
* @param {*} options
*/
function generateAutodocForFile(file, options) {
if (file.match(/src\/react-native\/js/)) {
// Ensure we're only processing extracted docs
console.log(`Skipping ${file}`);
return;
}
const pathToOutputDir = filepath.create(BUILD_DIR, DOCS_DIR, options.version);
console.log(`Processing ${file}`);
const url = 'http://localhost:8079/' + file
.replace(/^src/, '')
.replace(/\.js$/, '.html');
return fetch(url)
.then(response => {
if(response.ok) {
return response.text();
}
throw new Error('Network response was not ok.');
})
.then(body => {
const dom = new JSDOM(body);
const markdown = generateMarkdownFromDOM(dom);
const frontmatter = fm(markdown);
const pathToOutputFile = pathToOutputDir.append(`${AUTODOCS_PREFIX}${frontmatter.attributes.original_id}.${MARKDOWN_EXTENSION}`);
return fs.outputFile(pathToOutputFile.toString(), markdown);
})
.catch(error => {
console.log(error);
reject(error);
});
}
/**
* Generates Markdown documentation.
* Uses the convert script to extract docs from source files.
*/
function generateAutodocs(options = { }) {
console.log(`Generating Markdown files from JavaScript sources.`);
const metadata = convert({extractDocs: true});
return Promise.resolve()
.then(function() {
return glob('src/**/*.js');
})
.then(function(files) {
let queue = Promise.resolve();
files.forEach(function(file) {
queue = queue.then(function() {
return generateAutodocForFile(file, options);
});
});
return queue;
})
.then(function() {
console.log(`Generated Markdown files from JavaScript sources.`);
return metadata;
})
.catch(function(e) {
console.error(e);
process.exit(1);
});
}
// Check out gh-pages branch
function checkOutDocs() {
const pathToGitCheckout = filepath.create(BUILD_DIR, CHECKOUT_DIR);
let sidebarMetadata = {};
const p = Promise.resolve();
return p
.then(() => {
return fs.ensureDir(pathToGitCheckout.toString());
})
.then(() => {
shell.cd(CWD);
shell.cd(BUILD_DIR);
shell.cd(CHECKOUT_DIR);
return fs.exists(pathToGitCheckout.append(`.git`).toString())
}).then(gitCheckoutExists => {
if (!gitCheckoutExists) {
shell.exec(`git init`).code !== 0;
if (shell.exec(`git remote add origin ${remoteBranch}`).code !== 0) {
throw new Error('Error: git remote failed');
}
}
return;
})
.then(() => {
if (shell.exec(`git fetch`).code !== 0) {
throw new Error('Error: git fetch failed');
}
if (shell.exec(`git checkout gh-pages`).code !== 0) {
throw new Error('Error: git checkout failed');
}
return glob('releases/**/*.html');
}).then(files => {
let seq = Promise.resolve();
files.forEach(function(file) {
seq = seq.then(() => {
return extractMarkdownFromHTMLDocs(file);
}).then((res) => {
// console.log(res);
if (res.markdown === undefined) {
return;
}
const { frontmatter, markdown } = res;
const version = extractDocVersionFromFilename(file);
if (!version) {
return;
}
const pathToOutputFile = filepath.create(CWD, '..', 'website', DOCS_DIR, version, `${frontmatter.attributes.original_id}.${MARKDOWN_EXTENSION}`);
if (sidebarMetadata[version] === undefined) {
sidebarMetadata[version] = [];
}
if (frontmatter.attributes.original_id !== "404"
&& frontmatter.attributes.original_id !== "index"
&& frontmatter.attributes.original_id !== "help"
&& frontmatter.attributes.original_id !== "users"
&& frontmatter.attributes.original_id !== "showcase"
&& frontmatter.attributes.original_id !== "support"
&& frontmatter.attributes.original_id !== "versions") {
sidebarMetadata[version].push(frontmatter.attributes.id);
return fs.outputFile(pathToOutputFile.toString(), markdown);
}
return;
});
});
return seq;
}).then(() => {
console.log(sidebarMetadata)
const pathToSidebarMetadataFile = filepath.create(CWD, BUILD_DIR, SIDEBAR_DIR, `sidebars-metadata.json`);
return fs.outputFile(pathToSidebarMetadataFile.toString(), JSON.stringify(sidebarMetadata));
}).then(() => {
filepath.create(CWD, '..', 'website', SIDEBAR_DIR);
for (const version in sidebarMetadata) {
if (sidebarMetadata.hasOwnProperty(version)) {
const documents = sidebarMetadata[version];
let sidebar = {};
sidebar[`version-${version}-docs`] = { "APIs": documents };
const pathToSidebarFile = filepath.create(CWD, '..', 'website', SIDEBAR_DIR, `version-${version}-sidebars.json`);
console.log(`Writing ${pathToSidebarFile}: ${sidebar}`);
fs.outputFileSync(pathToSidebarFile.toString(), JSON.stringify(sidebar));
}
}
return;
}).then(() => {
filepath.create(CWD, BUILD_DIR, SIDEBAR_DIR);
const versions = Object.keys(sidebarMetadata);
const pathToVersionsFile = filepath.create(CWD, BUILD_DIR, `versions.json`);
return fs.outputFile(pathToVersionsFile.toString(), JSON.stringify(versions.reverse()));
}).then(() => {
shell.cd(CWD);
shell.cd(BUILD_DIR);
shell.cd(CHECKOUT_DIR);
shell.exec(`git config core.sparsecheckout true`).code !== 0;
shell.exec(`echo "docs/*" >> .git/info/sparse-checkout`).code !== 0;
shell.exec(`echo "Libraries/*" >> .git/info/sparse-checkout`).code !== 0;
if (shell.exec(`git checkout master`).code !== 0) {
throw new Error('Error: git checkout failed');
}
// maybe should clear out before?
shell.cp('-r', 'docs/*.md', '../../docs/.');
return glob('docs/**/*.md');
}).then(() => {
// now we handle sidebarring
// and make versioned sidebar?
return; //copy
}).then(() => {
// then finally we run docgen over Libraries/...
});
}
function checkoutMasterDocs() {
const pathToGitCheckout = filepath.create(BUILD_DIR, CHECKOUT_DIR);
let sidebarMetadata = {};
const p = Promise.resolve();
return p
.then(() => {
return fs.ensureDir(pathToGitCheckout.toString());
})
.then(() => {
shell.cd(CWD);
shell.cd(BUILD_DIR);
shell.cd(CHECKOUT_DIR);
return fs.exists(pathToGitCheckout.append(`.git`).toString())
})
.then(gitCheckoutExists => {
if (!gitCheckoutExists) {
shell.exec(`git init`).code !== 0;
if (shell.exec(`git remote add origin ${remoteBranch}`).code !== 0) {
throw new Error('Error: git remote failed');
}
}
return;
})
.then(() => {
if (shell.exec(`git fetch`).code !== 0) {
throw new Error('Error: git fetch failed');
}
shell.exec(`git config core.sparsecheckout true`).code !== 0;
shell.exec(`echo "docs/*" >> .git/info/sparse-checkout`).code !== 0;
shell.exec(`echo "Libraries/*" >> .git/info/sparse-checkout`).code !== 0;
if (shell.exec(`git checkout master`).code !== 0) {
throw new Error('Error: git checkout failed');
}
return glob('docs/**/*.md');
})
.then((files) => {
let seq = Promise.resolve();
files.forEach((file) => {
seq = seq.then(() => {
return fs.readFile(file, 'utf8');
}).then((originalMarkdown) => {
const frontmatter = fm(originalMarkdown);
const category = frontmatter.attributes.category;
const transformedMarkdown = [
'---',
'id: ' + frontmatter.attributes.id,
'title: ' + frontmatter.attributes.title,
'---',
frontmatter.body
].filter(function(line) {
return line;
}).join('\n');
if (!sidebarMetadata.hasOwnProperty(category)) {
sidebarMetadata[category] = [];
}
sidebarMetadata[category].push(frontmatter.attributes.id);
const pathToOutputFile = filepath.create(CWD, '..', 'docs', `${frontmatter.attributes.id}.${MARKDOWN_EXTENSION}`);
return fs.outputFile(pathToOutputFile.toString(), transformedMarkdown);
});
});
// now we convert
// now we handle sidebarring
// and make versioned sidebar?
return seq; //copy
})
.then(() => {
console.log(`My sidebar: ${JSON.stringify(sidebarMetadata)}`);
// then finally we run docgen over Libraries/...
console.log(`should codegen`)
const pathToOutputFile = filepath.create(CWD, '..', 'website', `gen-sidebars.json`);
return fs.outputFile(pathToOutputFile.toString(), { "docs": sidebarMetadata });
});
}
function extractDocVersionFromFilename(file) {
const re = new RegExp('\/([0-9]*[.]?[0-9]*)');
return file.match(re)[1];
}
function extractComponentNameFromFilename(file) {
const re = new RegExp('([A-Za-z-]*).html');
return file.match(re)[1];
}
function extractMarkdownFromHTMLDocs(file) {
if (file.indexOf("404") !== -1) {
return { };
}
// console.log(`Processing ${file}`);
return JSDOM.fromFile(filepath.create(file).toString())
.then((dom) => {
const body = bodyContentFromDOM(dom);
if (!body) {
return {};
}
const componentName = extractComponentNameFromFilename(file);
const version = extractDocVersionFromFilename(file);
const markdown = generateMarkdown(componentName, body, version);
const frontmatter = fm(markdown);
return { frontmatter, markdown };
})
}
// DOM FORMATTING FUNCS
function bodyContentFromDOM(dom) {
const el = dom.window.document.querySelector('.inner-content');
if (el) {
return el.innerHTML;
} else {
return null;
}
}
function componentNameFromDOM(dom) {
const el = dom.window.document.querySelector('h1');
if (el) {
let componentName = el.innerHTML;
const re = new RegExp('docs/([A-Za-z]*)(.html)');
const parsedTitle = el.innerHTML.match(re);
if (parsedTitle) {
componentName = parsedTitle[1];
}
return componentName;
} else {
return 'Component';
}
}
function componentCategoryFromDOM(dom) {
const el = dom.window.document.querySelector('meta[property="rn:category"]');
if (el) {
return el.content;
} else {
return 'Components';
}
}
/**
* Generates a markdown formatted file, including frontmatter.
*
* @param {*} dom
*/
function generateMarkdownFromDOM(dom) {
const body = bodyContentFromDOM(dom);
if (!body) {
return;
}
const componentName = componentNameFromDOM(dom);
const slug = slugify(componentName);
const markdown = [
'---',
'id: ' + slug,
'title: ' + componentName,
'---',
body
]
.filter(function(line) {
return line;
})
.join('\n');
return markdown;
}
function generateMarkdown(componentName, body, version) {
const slug = slugify(componentName);
let markdown = [
'---',
'id: ' + slug,
'title: ' + componentName,
'---',
body
];
if (version) {
markdown = [
'---',
'id: version-' + version + '-' + slug,
'title: ' + componentName,
'original_id: ' + slug,
'---',
body
];
}
return markdown.filter(function(line) {
return line;
}).join('\n');
}
function generateMetatadaFile(categories) {
const categoriesMetadataFile = `${BUILD_DIR}/sidebar-metadata.json`;
return fs.outputFile(categoriesMetadataFile, JSON.stringify(categories));
}
// END DOM
function checkoutAutodocs() {
return Promise.resolve()
.then(() => {
return generateAutodocs();
})
.finally(function() {
server.close();
}).catch(function(e) {
console.error(e);
process.exit(1);
});
}
if (argv.clean) {
cleanFiles();
}
// if (argv.autodocs) {
runChecks();
// checkOutDocs();
checkoutMasterDocs()
.then(() => {
checkoutAutodocs();
})
// }
/**
* Check out gh-pages branch, cd releases/
* For each version,
* For each HTML,
* Parse out jsdom
* Get body contents
* Generate frontmatter
* Save into versioned_docs
* Then finally generate sidebar files for each version
*
*/
module.exports = generateAutodocs;