blob: 6bad010614dfb32e73bdc5dfeaf3819098e75207 [file] [log] [blame] [edit]
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
/**
* @fileoverview Module that will generate the API documentation for the
* `selenium-webdriver` npm package.
*/
'use strict';
const child_process = require('child_process'),
fs = require('fs'),
path = require('path');
const PROJECT_ROOT = path.join(__dirname, '../..');
/**
* @param {string} command the command to run.
* @param {!Array<string>} args command arguments.
* @param {!Object} opts command options.
* @return {!Promise<void>} a promise that will resolve when the command
* completes.
*/
function exec(command, args, opts) {
console.log(`${command} ${args.join(' ')}`);
return new Promise(function(fulfill, reject) {
child_process.spawn(command, args, opts)
.on('error', reject)
.on('exit', function(code, signal) {
if (code) {
reject(Error(`command terminated with status=${code}`));
} else if (signal) {
reject(Error(`command killed with signal=${signal}`));
} else {
fulfill();
}
});
});
}
/**
* @param {string} aPath path to the directory to create.
* @return {!Promise<string>} promise that will resolve with the path to the
* created directory.
*/
function mkdirp(aPath) {
return new Promise(function(fulfill, reject) {
console.log('...creating %s', aPath);
fs.mkdir(aPath, function(err) {
if (!err) return fulfill(aPath);
switch (err.code) {
case 'EEXIST':
fulfill(aPath);
break;
case 'ENOENT':
mkdirp(path.dirname(aPath))
.then(() => mkdirp(aPath))
.then(fulfill);
break;
default:
reject(err);
break;
}
});
});
}
/**
* @return {!Promise<string>} a promise that will resolve with the path to the
* dossier jar.
*/
function installDossier() {
return new Promise(function(fulfill, reject) {
let buildNodeDir = path.join(PROJECT_ROOT);
let jar = path.join(buildNodeDir, 'node_modules/js-dossier/dossier.jar');
fs.stat(jar, function(err) {
if (!err) return fulfill(jar);
console.log('Installing dossier...');
const args = ['install', 'js-dossier'];
const opts = {cwd: buildNodeDir, stdio: 'inherit'};
exec('npm', args, opts).then(() => fulfill(jar), reject);
});
});
}
/**
* @return {!Promise<!Array<path>>} a promise for the list of modules to
* generate docs for.
*/
function getModules() {
console.log('Scanning sources...');
const excludeDirs = [
path.join(__dirname, 'selenium-webdriver/example'),
path.join(__dirname, 'selenium-webdriver/lib/atoms'),
path.join(__dirname, 'selenium-webdriver/lib/firefox'),
path.join(__dirname, 'selenium-webdriver/lib/safari'),
path.join(__dirname, 'selenium-webdriver/lib/test'),
path.join(__dirname, 'selenium-webdriver/lib/tools'),
path.join(__dirname, 'selenium-webdriver/devtools/generator'),
path.join(__dirname, 'selenium-webdriver/node_modules'),
path.join(__dirname, 'selenium-webdriver/test')
];
function scan(dir) {
return listFiles(dir).then(function(files) {
return files.filter(f => excludeDirs.indexOf(f) === -1);
}).then(function(files) {
return Promise.all(files.map(isDir))
.then(function(isDir) {
let jsFiles = files.filter(
(file, index) => !isDir[index] && file.endsWith('.js'));
return Promise
.all(files.filter((f, i) => isDir[i]).map(scan))
.then(files => jsFiles.concat.apply(jsFiles, files));
});
});
}
return scan(path.join(__dirname, 'selenium-webdriver'));
}
/**
* @param {string} path the path to check.
* @return {!Promise<boolean>} a promise that will resolve with whether the
* given path is a directory.
*/
function isDir(path) {
return new Promise(function(fulfill, reject) {
fs.stat(path, function(err, stats) {
if (err) return reject(err);
fulfill(stats.isDirectory());
});
});
}
/**
* @param {string} dir path to the directory to list.
* @return {!Promise<!Array<string>>} a promise that will resolve with the list
* of files in the directory.
*/
function listFiles(dir) {
return new Promise(function(fulfill, reject) {
fs.readdir(dir, function(err, files) {
if (err) return reject(err);
files = (files || []).map(f => path.join(dir, f));
fulfill(files);
});
});
}
/**
* @param {!Array<string>} modules List of files to generate docs for.
* @return {!Object} the JSON config.
*/
function buildConfig(modules) {
console.log('Generating dossier config...');
let webdriver = path.join(__dirname, 'selenium-webdriver');
let externs = path.join(__dirname, 'externs');
return {
output: path.join(
PROJECT_ROOT, 'build/javascript/node/selenium-webdriver-docs'),
customPages: [
{
name: 'Changes',
path: path.join(webdriver, 'CHANGES.md')
}
],
readme: path.join(webdriver, 'README.md'),
language: 'ES6_STRICT',
moduleNamingConvention: 'NODE',
modules: modules,
// Exclude modules that are considered purely implementation details.
moduleFilters: [
path.join(webdriver, 'lib/devmode.js'),
path.join(webdriver, 'lib/symbols.js')
],
externs: [path.join(externs, 'global.js')],
externModules: [
path.join(externs, 'jszip.js'),
path.join(externs, 'mocha.js'),
path.join(externs, 'rimraf.js'),
path.join(externs, 'tmp.js'),
path.join(externs, 'ws.js'),
path.join(externs, 'xml2js.js')
],
sourceUrlTemplate:
'https://github.com/SeleniumHQ/selenium/tree/trunk/'
+ 'javascript/node/selenium-webdriver/%path%#L%line%',
strict: false
}
}
/**
* @param {!Object} config the JSON config to write
* @return {!Promise<!Object>} a promise that will resolve with the parsed
* JSON config.
*/
function writeConfig(config) {
console.log('Creating output root...');
return mkdirp(config.output).then(function() {
let configFile = config.output + '.json';
console.log('Writing config...');
return new Promise(function(fulfill, reject) {
fs.writeFile(configFile, JSON.stringify(config), 'utf8', function(err) {
if (err) {
reject(Error(`failed to write config file: ${err}`));
return;
}
fulfill(config);
});
});
});
}
/**
* @param {!Object} config The json config to use.
* @return {!Promise<void>} a promise that will resolve when the task is
* complete.
*/
function generateDocs(config) {
return installDossier().then(function(jar) {
let args = ['-jar', jar, '-c', config.output + '.json'];
console.log(`Generating ${config.output}...`);
return exec('java', args, {stdio: 'inherit'});
});
}
/**
* Prints the given error and exits the program.
* @param {!Error} e the error to print.
*/
function die(e) {
console.error(e.stack);
process.exit(1);
}
function main() {
return getModules()
.then(buildConfig)
.then(writeConfig)
.then(generateDocs);
}
module.exports = main;
if (module === require.main) {
return main().then(() => console.log('DONE!'), die);
}