Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions bin/faucet
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#!/usr/bin/env node
"use strict";

let faucet = require("../lib");
let parseCLI = require("../lib/cli");
let { faucetDispatch } = require("../lib");
let { parseCLI } = require("../lib/cli");

let { referenceDir, config, options } = parseCLI();
faucet(referenceDir, config, options);
parseCLI().
then(({ referenceDir, config, options }) => {
faucetDispatch(referenceDir, config, options);
});
6 changes: 3 additions & 3 deletions lib/cli.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use strict";

let readConfig = require("./config");
let { readConfig } = require("./config");
let { abort, repr } = require("./util");
let parseArgs = require("minimist");

Expand All @@ -27,7 +27,7 @@ Options:
serve generated files via HTTP with live reloading
`.trim();

module.exports = function parseCLI(argv = process.argv.slice(2), help = HELP) {
exports.parseCLI = async function parseCLI(argv = process.argv.slice(2), help = HELP) {
argv = parseArgs(argv, {
boolean: ["watch", "fingerprint", "sourcemaps", "compact"],
alias: {
Expand Down Expand Up @@ -56,6 +56,6 @@ module.exports = function parseCLI(argv = process.argv.slice(2), help = HELP) {
}

let rootDir = process.cwd();
let { referenceDir, config } = readConfig(rootDir, argv.config);
let { referenceDir, config } = await readConfig(rootDir, argv.config);
return { referenceDir, config, options };
};
4 changes: 2 additions & 2 deletions lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

let path = require("path");

module.exports = function readConfig(rootDir, filepath = "faucet.config.js") {
exports.readConfig = async function readConfig(rootDir, filepath = "faucet.config.js") {
let configPath = path.resolve(rootDir, filepath);
return {
referenceDir: path.dirname(configPath),
config: require(configPath)
config: await require(configPath)
};
};
20 changes: 11 additions & 9 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

let server = require("./server");
let { pluginsByBucket } = require("./plugins");
let AssetManager = require("./manager");
let resolvePath = require("./util/resolve");
let { AssetManager } = require("./manager");
let { resolvePath } = require("./util/resolve");
let { abort, repr } = require("./util");
let SerializedRunner = require("./util/runner");
let { SerializedRunner } = require("./util/runner");
let browserslist = require("browserslist");

module.exports = async function faucetDispatch(referenceDir, config,
exports.faucetDispatch = async function faucetDispatch(referenceDir, config,
{ watch, fingerprint, sourcemaps, compact, serve, liveserve }) {
config = await config;

Expand All @@ -22,7 +22,7 @@ module.exports = async function faucetDispatch(referenceDir, config,
browsers = [browsers];
}

let plugins = pluginsByBucket(config);
let plugins = await pluginsByBucket(config);
// initialize plugins with corresponding configuration
let buckets = Object.keys(plugins).reduce((memo, bucket) => {
memo[bucket] = plugins[bucket].map(({ plugin, config }) => {
Expand All @@ -40,8 +40,10 @@ module.exports = async function faucetDispatch(referenceDir, config,

if(watch) {
makeWatcher(config.watchDirs, referenceDir).
on("edit", filepaths => {
runner.rerun(filepaths);
then(watcher => {
watcher.on("edit", filepaths => {
runner.rerun(filepaths);
});
});
}

Expand All @@ -60,8 +62,8 @@ function buildStep(plugins) {
then(() => files);
}

function makeWatcher(watchDirs, referenceDir) {
let niteOwl = require("nite-owl");
async function makeWatcher(watchDirs, referenceDir) {
let niteOwl = await require("nite-owl");

if(watchDirs) {
watchDirs = watchDirs.map(dir => resolvePath(dir, referenceDir,
Expand Down
12 changes: 6 additions & 6 deletions lib/manager.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"use strict";

let Manifest = require("./manifest");
let createFile = require("./util/files");
let resolvePath = require("./util/resolve");
let { Manifest } = require("./manifest");
let { createFile } = require("./util/files");
let { resolvePath } = require("./util/resolve");
let { reportFileStatus, abort, generateFingerprint } = require("./util");
let path = require("path");

module.exports = class AssetManager {
exports.AssetManager = class AssetManager {
constructor(referenceDir, { manifestConfig, fingerprint, exitOnError } = {}) {
this.referenceDir = referenceDir;
this.fingerprint = fingerprint;
Expand All @@ -30,9 +30,9 @@ module.exports = class AssetManager {
}

return createFile(filepath, data).
then(_ => this.manifest &&
then(() => this.manifest &&
this._updateManifest(originalPath, filepath, targetDir)).
then(_ => {
then(() => {
reportFileStatus(originalPath, this.referenceDir, error);
if(error && this.exitOnError) {
throw error;
Expand Down
6 changes: 3 additions & 3 deletions lib/manifest.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"use strict";

let createFile = require("./util/files");
let resolvePath = require("./util/resolve");
let { createFile } = require("./util/files");
let { resolvePath } = require("./util/resolve");
let { abort } = require("./util");
let path = require("path");

module.exports = class Manifest {
exports.Manifest = class Manifest {
constructor(referenceDir, { target, key, value, baseURI, webRoot } = {}) {
if(value && (baseURI || webRoot)) {
abort("ERROR: `value` must not be used with `baseURI` and/or `webRoot`");
Expand Down
43 changes: 23 additions & 20 deletions lib/plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,47 +33,50 @@ module.exports = {

// returns plugin functions grouped by bucket and filtered by relevance (based
// on configuration)
function pluginsByBucket(config, defaults) {
let plugins = determinePlugins(config.plugins, defaults);
async function pluginsByBucket(config, defaults) {
let plugins = await determinePlugins(config.plugins, defaults);
let buckets = [...BUCKETS].reduce((memo, bucket) => {
memo[bucket] = [];
return memo;
}, {});
Object.entries(plugins).forEach(([key, _plugin]) => {
for(let [key, _plugin] of Object.entries(plugins)) {
let pluginConfig = config[key];
if(!pluginConfig) {
return;
continue;
}

let { bucket, plugin } = _plugin;
if(!plugin.call) {
({ plugin } = await loadPlugin(plugin));
}
buckets[bucket].push({
plugin: plugin.call ? plugin : loadPlugin(plugin).plugin,
plugin,
config: pluginConfig
});
});
}
return buckets;
}

// `plugins` is an array of plugins, each either a package identifier or a
// `{ key, bucket, plugin }` object`, with `key` being the configuration key and
// `plugin` being either a function or a package identifier
function determinePlugins(plugins = [], defaults = DEFAULTS) {
async function determinePlugins(plugins = [], defaults = DEFAULTS) {
let registry = {};
// NB: default plugins are resolved lazily because eager loading would
// result in them becoming a hard dependency rather than a convenience
// preset - however, that requires us to duplicate the respective
// configuration keys and buckets here
defaults.forEach(plugin => {
registerPlugin(registry, plugin, false);
});
plugins.forEach(plugin => {
registerPlugin(registry, plugin, true);
});
for(let plugin of defaults) {
await registerPlugin(registry, plugin, false);
}
for(let plugin of plugins) {
await registerPlugin(registry, plugin, true);
}
return registry;
}

function registerPlugin(registry, _plugin, eager) {
let { key, bucket, plugin } = resolvePlugin(_plugin, eager);
async function registerPlugin(registry, _plugin, eager) {
let { key, bucket, plugin } = await resolvePlugin(_plugin, eager);
// NB: default plugins may be overridden
if(registry[key] && !DEFAULT_KEYS.has(key)) {
abort(`ERROR: duplicate plugin key ${repr(key, false)}`);
Expand All @@ -84,14 +87,14 @@ function registerPlugin(registry, _plugin, eager) {
}
}

function resolvePlugin(_plugin, eager) {
async function resolvePlugin(_plugin, eager) {
if(_plugin.substr) { // package identifier
_plugin = loadPlugin(_plugin);
_plugin = await loadPlugin(_plugin);
}

let { key, bucket, plugin } = _plugin;
if(eager && plugin.substr && (!key || !bucket)) { // auto-configuration
let _plugin = loadPlugin(plugin);
let _plugin = await loadPlugin(plugin);
plugin = _plugin.plugin;
// local configuration takes precedence
key = key || _plugin.key;
Expand All @@ -104,13 +107,13 @@ function resolvePlugin(_plugin, eager) {
return { key, bucket, plugin };
}

function loadPlugin(pkg) {
async function loadPlugin(pkg) {
let fail = prop => abort(`ERROR: invalid plugin ${repr(pkg)}; ` +
`missing ${repr(prop, false)}`);
let {
key = fail("key"),
bucket = fail("bucket"),
plugin = fail("plugin")
} = loadExtension(pkg, "ERROR: missing plugin");
} = await loadExtension(pkg, "ERROR: missing plugin");
return { key, bucket, plugin };
}
14 changes: 6 additions & 8 deletions lib/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,16 @@ let DEFAULTS = {
port: 3000
};

exports.static = (config, webroot) => {
let donny = loadExtension("donny", "failed to activate server");
exports.static = async (config, webroot) => {
let donny = await loadExtension("donny", "failed to activate server");
let [host, port] = parseHost(config);

donny({ port, bind: host, webroot }).
then(() => {
console.error(`serving ${repr(webroot)} at http://${host}:${port}`);
});
await donny({ port, bind: host, webroot });
console.error(`serving ${repr(webroot)} at http://${host}:${port}`);
};

exports.live = (config, root) => {
let liveServer = loadExtension("live-server", "failed to activate live-server");
exports.live = async (config, root) => {
let liveServer = await loadExtension("live-server", "failed to activate live-server");
let [host, port] = parseHost(config);

liveServer.start({ port, host, root, open: false });
Expand Down
10 changes: 3 additions & 7 deletions lib/util/files/finder.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
let fs = require("fs");
let { readdir, stat } = require("fs/promises");
let path = require("path");
let { promisify } = require("util");

let stat = promisify(fs.stat);
let readDir = promisify(fs.readdir);

module.exports = class FileFinder {
exports.FileFinder = class FileFinder {
constructor(directory, { skipDotfiles, filter = () => true } = {}) {
this.directory = directory;
this.filter = filename => {
Expand Down Expand Up @@ -36,7 +32,7 @@ function tree(filepath, referenceDir = filepath) {
return [path.relative(referenceDir, filepath)];
}

return readDir(filepath).
return readdir(filepath).
then(entries => {
let res = Promise.all(entries.map(entry => {
return tree(path.join(filepath, entry), referenceDir);
Expand Down
39 changes: 13 additions & 26 deletions lib/util/files/index.js
Original file line number Diff line number Diff line change
@@ -1,42 +1,29 @@
"use strict";

let { abort, repr } = require("../");
let fs = require("fs");
let { writeFile } = require("fs/promises");
let { mkdirSync } = require("fs");
let path = require("path");
let { promisify } = require("util");

let KNOWN = {}; // avoids redundant `mkdirp` invocations
let LOCKS = {};

let writeFile = promisify(fs.writeFile);
let KNOWN = new Set(); // avoids redundant `mkdir` invocations
let LOCKS = new Map();

// avoids concurrent write operations and creates target directory if necessary
module.exports = function createFile(filepath, contents) {
let lock = LOCKS[filepath];
exports.createFile = function createFile(filepath, contents) {
let lock = LOCKS.get(filepath);
if(lock) { // defer
return lock.then(_ => createFile(filepath, contents));
return lock.then(() => createFile(filepath, contents));
}

// create directory if necessary
if(!KNOWN[filepath]) {
KNOWN[filepath] = true;
if(!KNOWN.has(filepath)) {
KNOWN.add(filepath);
// NB: `sync` avoids race condition for subsequent operations
mkdirpSync(path.dirname(filepath));
mkdirSync(path.dirname(filepath), { recursive: true });
}

let prom = writeFile(filepath, contents);
LOCKS[filepath] = prom;
return prom.then(_ => {
delete LOCKS[filepath];
LOCKS.set(filepath, prom);
return prom.then(() => {
LOCKS.delete(filepath);
});
};

function mkdirpSync(directory) {
try {
// NB: `recursive` option was introduced in Node v10.12.0
fs.mkdirSync(directory, { recursive: true });
} catch(err) {
abort(`ERROR: auto-creating ${repr(directory)} requires ` +
"Node v10.12.0 or above");
}
}
10 changes: 4 additions & 6 deletions lib/util/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@

let path = require("path");
let crypto = require("crypto");
let { promisify } = require("util");

exports.abort = abort;
exports.promisify = promisify; // deprecated
exports.repr = repr;

// reports success or failure for a given file path (typically regarding
Expand All @@ -15,11 +13,11 @@ exports.reportFileStatus = (filepath, referenceDir, error) => {
console.error(error ? `✗ ${ref}: ${error.message || error}` : `✓ ${ref}`);
};

// attempts to `require` a module, prompting the user to install the
// corresponding package if it is unavailable
exports.loadExtension = (pkg, errorMessage, supplier = pkg) => {
// attempts to load a module, prompting the user to install the corresponding
// package if it is unavailable
exports.loadExtension = async (pkg, errorMessage, supplier = pkg) => {
try {
return require(pkg);
return await require(pkg);
} catch(err) {
if(err.code !== "MODULE_NOT_FOUND") {
throw err;
Expand Down
3 changes: 2 additions & 1 deletion lib/util/resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ let { abort, repr } = require("./");
let fs = require("fs");
let path = require("path");

module.exports = function resolvePath(filepath, referenceDir, { enforceRelative } = {}) {
exports.resolvePath = function resolvePath(filepath, referenceDir,
{ enforceRelative } = {}) {
if(/^\.?\.\//.test(filepath)) { // starts with `./` or `../`
return path.resolve(referenceDir, filepath);
} else if(enforceRelative) {
Expand Down
Loading