From de0a19367e95c3c172b18a687ff7574b96cea912 Mon Sep 17 00:00:00 2001 From: FND Date: Mon, 25 Apr 2022 09:54:35 +0200 Subject: [PATCH 1/3] switched to named instead of default exports see https://humanwhocodes.com/blog/2019/01/stop-using-default-exports-javascript-module/ for details note that this constitutes a breaking change for developers --- bin/faucet | 6 +++--- lib/cli.js | 4 ++-- lib/config.js | 2 +- lib/index.js | 8 ++++---- lib/manager.js | 8 ++++---- lib/manifest.js | 6 +++--- lib/util/files/finder.js | 2 +- lib/util/files/index.js | 2 +- lib/util/resolve.js | 3 ++- lib/util/runner.js | 2 +- test/test_manager.js | 2 +- test/test_manifest.js | 2 +- test/test_runner.js | 2 +- test/test_util.js | 2 +- 14 files changed, 26 insertions(+), 25 deletions(-) diff --git a/bin/faucet b/bin/faucet index 6eb573c..08231b2 100755 --- a/bin/faucet +++ b/bin/faucet @@ -1,8 +1,8 @@ #!/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); +faucetDispatch(referenceDir, config, options); diff --git a/lib/cli.js b/lib/cli.js index 8a0156d..6c84fe7 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -1,6 +1,6 @@ "use strict"; -let readConfig = require("./config"); +let { readConfig } = require("./config"); let { abort, repr } = require("./util"); let parseArgs = require("minimist"); @@ -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 = function parseCLI(argv = process.argv.slice(2), help = HELP) { argv = parseArgs(argv, { boolean: ["watch", "fingerprint", "sourcemaps", "compact"], alias: { diff --git a/lib/config.js b/lib/config.js index f585bc2..9bc7c3a 100644 --- a/lib/config.js +++ b/lib/config.js @@ -2,7 +2,7 @@ let path = require("path"); -module.exports = function readConfig(rootDir, filepath = "faucet.config.js") { +exports.readConfig = function readConfig(rootDir, filepath = "faucet.config.js") { let configPath = path.resolve(rootDir, filepath); return { referenceDir: path.dirname(configPath), diff --git a/lib/index.js b/lib/index.js index 16d19e3..b6bcca5 100644 --- a/lib/index.js +++ b/lib/index.js @@ -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; diff --git a/lib/manager.js b/lib/manager.js index 3630419..c75f97a 100644 --- a/lib/manager.js +++ b/lib/manager.js @@ -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; diff --git a/lib/manifest.js b/lib/manifest.js index b7985a9..51db3ff 100644 --- a/lib/manifest.js +++ b/lib/manifest.js @@ -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`"); diff --git a/lib/util/files/finder.js b/lib/util/files/finder.js index 5764be7..2cf86d9 100644 --- a/lib/util/files/finder.js +++ b/lib/util/files/finder.js @@ -1,7 +1,7 @@ let { readdir, stat } = require("fs/promises"); let path = require("path"); -module.exports = class FileFinder { +exports.FileFinder = class FileFinder { constructor(directory, { skipDotfiles, filter = () => true } = {}) { this.directory = directory; this.filter = filename => { diff --git a/lib/util/files/index.js b/lib/util/files/index.js index d067696..f2d8ef0 100644 --- a/lib/util/files/index.js +++ b/lib/util/files/index.js @@ -8,7 +8,7 @@ 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) { +exports.createFile = function createFile(filepath, contents) { let lock = LOCKS.get(filepath); if(lock) { // defer return lock.then(() => createFile(filepath, contents)); diff --git a/lib/util/resolve.js b/lib/util/resolve.js index 410e185..afb8124 100644 --- a/lib/util/resolve.js +++ b/lib/util/resolve.js @@ -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) { diff --git a/lib/util/runner.js b/lib/util/runner.js index d2ce80f..90ec0af 100644 --- a/lib/util/runner.js +++ b/lib/util/runner.js @@ -1,6 +1,6 @@ "use strict"; -module.exports = class SerializedRunner { +exports.SerializedRunner = class SerializedRunner { constructor(asyncOp) { this.asyncOp = asyncOp; } diff --git a/test/test_manager.js b/test/test_manager.js index 6375827..9966e48 100644 --- a/test/test_manager.js +++ b/test/test_manager.js @@ -1,7 +1,7 @@ /* global describe, before, after, it */ "use strict"; -let AssetManager = require("../lib/manager"); +let { AssetManager } = require("../lib/manager"); let path = require("path"); let assert = require("assert"); diff --git a/test/test_manifest.js b/test/test_manifest.js index 041cc36..e175209 100644 --- a/test/test_manifest.js +++ b/test/test_manifest.js @@ -1,7 +1,7 @@ /* global describe, before, after, it */ "use strict"; -let Manifest = require("../lib/manifest"); +let { Manifest } = require("../lib/manifest"); let path = require("path"); let assert = require("assert"); diff --git a/test/test_runner.js b/test/test_runner.js index 8297f70..bf5a621 100644 --- a/test/test_runner.js +++ b/test/test_runner.js @@ -1,7 +1,7 @@ /* global describe, it */ "use strict"; -let SerializedRunner = require("../lib/util/runner"); +let { SerializedRunner } = require("../lib/util/runner"); let { strictEqual: assertSame, deepStrictEqual: assertDeep } = require("assert"); describe("watch mode", () => { diff --git a/test/test_util.js b/test/test_util.js index ba36a99..06f06e0 100644 --- a/test/test_util.js +++ b/test/test_util.js @@ -2,7 +2,7 @@ "use strict"; let { generateFingerprint } = require("../lib/util"); -let FileFinder = require("../lib/util/files/finder"); +let { FileFinder } = require("../lib/util/files/finder"); let path = require("path"); let assert = require("assert"); From ae3f548eddf1e6156fb396a0fe5c36cc118f181b Mon Sep 17 00:00:00 2001 From: FND Date: Mon, 25 Apr 2022 10:33:57 +0200 Subject: [PATCH 2/3] ensured dynamic imports are asynchronous this is required in preparation of switching to ESM, where synchronous `require` will have to be replaced with dynamic `import` note that this constitutes a breaking change for developers --- bin/faucet | 6 +++-- lib/cli.js | 4 +-- lib/config.js | 4 +-- lib/index.js | 12 +++++---- lib/plugins.js | 43 ++++++++++++++++-------------- lib/server.js | 14 +++++----- lib/util/index.js | 8 +++--- test/test_plugins.js | 62 ++++++++++++++++++++++---------------------- 8 files changed, 79 insertions(+), 74 deletions(-) diff --git a/bin/faucet b/bin/faucet index 08231b2..79d8496 100755 --- a/bin/faucet +++ b/bin/faucet @@ -4,5 +4,7 @@ let { faucetDispatch } = require("../lib"); let { parseCLI } = require("../lib/cli"); -let { referenceDir, config, options } = parseCLI(); -faucetDispatch(referenceDir, config, options); +parseCLI(). + then(({ referenceDir, config, options }) => { + faucetDispatch(referenceDir, config, options); + }); diff --git a/lib/cli.js b/lib/cli.js index 6c84fe7..01dba5b 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -27,7 +27,7 @@ Options: serve generated files via HTTP with live reloading `.trim(); -exports.parseCLI = 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: { @@ -56,6 +56,6 @@ exports.parseCLI = 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 }; }; diff --git a/lib/config.js b/lib/config.js index 9bc7c3a..b87fa5c 100644 --- a/lib/config.js +++ b/lib/config.js @@ -2,10 +2,10 @@ let path = require("path"); -exports.readConfig = 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) }; }; diff --git a/lib/index.js b/lib/index.js index b6bcca5..6606d1f 100644 --- a/lib/index.js +++ b/lib/index.js @@ -22,7 +22,7 @@ exports.faucetDispatch = 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 }) => { @@ -40,8 +40,10 @@ exports.faucetDispatch = 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); + }); }); } @@ -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, diff --git a/lib/plugins.js b/lib/plugins.js index 80805e8..b940706 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -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)}`); @@ -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; @@ -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 }; } diff --git a/lib/server.js b/lib/server.js index 7d3a961..9ecb696 100644 --- a/lib/server.js +++ b/lib/server.js @@ -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 }); diff --git a/lib/util/index.js b/lib/util/index.js index cf619be..0698bc2 100644 --- a/lib/util/index.js +++ b/lib/util/index.js @@ -13,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; diff --git a/test/test_plugins.js b/test/test_plugins.js index 0d730a7..104631d 100644 --- a/test/test_plugins.js +++ b/test/test_plugins.js @@ -39,8 +39,8 @@ describe("plugin registration", () => { updateNodePath(NODE_PATH); }); - it("only loads default plugins referenced within configuration", () => { - let res = pluginsByBucket({ + it("only loads default plugins referenced within configuration", async () => { + let res = await pluginsByBucket({ js: [{ foo: "lorem" }] }); assertDeep(normalizeAll(res), { @@ -53,7 +53,7 @@ describe("plugin registration", () => { markup: [] }); - res = pluginsByBucket({ + res = await pluginsByBucket({ sass: [{ bar: "ipsum" }] }); assertDeep(normalizeAll(res), { @@ -66,7 +66,7 @@ describe("plugin registration", () => { markup: [] }); - res = pluginsByBucket({ + res = await pluginsByBucket({ js: [{ foo: "lorem" }], sass: [{ bar: "ipsum" }] }); @@ -84,8 +84,8 @@ describe("plugin registration", () => { }); }); - it("allows overriding default plugins", () => { - let res = pluginsByBucket({ + it("allows overriding default plugins", async () => { + let res = await pluginsByBucket({ js: [{ foo: "bar" }], plugins: [{ key: "js", @@ -120,12 +120,12 @@ describe("plugin resolution", () => { updateNodePath(NODE_PATH); }); - it("provides a default set of plugins", () => { - let res = _determinePlugins(); + it("provides a default set of plugins", async () => { + let res = await _determinePlugins(); assertDeep(normalizePlugins(res), DEFAULTS); }); - it("supports custom plugins", () => { + it("supports custom plugins", async () => { // plugin function within configuration let anon = () => {}; let config = [{ @@ -133,7 +133,7 @@ describe("plugin resolution", () => { bucket: "static", plugin: anon }]; - let res = _determinePlugins(config); + let res = await _determinePlugins(config); assertDeep(normalizePlugins(res), Object.assign({}, DEFAULTS, { dummy: { bucket: "static", @@ -144,7 +144,7 @@ describe("plugin resolution", () => { // nested package identifier let pkg = "faucet-pipeline-dummy"; config[0].plugin = pkg; - res = _determinePlugins(config); + res = await _determinePlugins(config); assertDeep(normalizePlugins(res), Object.assign({}, DEFAULTS, { dummy: { bucket: "static", @@ -154,7 +154,7 @@ describe("plugin resolution", () => { })); // simple package identifier - res = _determinePlugins([pkg]); + res = await _determinePlugins([pkg]); assertDeep(normalizePlugins(res), Object.assign({}, DEFAULTS, { dummy: { bucket: "static", @@ -163,12 +163,12 @@ describe("plugin resolution", () => { })); }); - it("allows overriding plugins' default configuration", () => { + it("allows overriding plugins' default configuration", async () => { let config = [{ key: "yummy", plugin: "faucet-pipeline-dummy" }]; - let res = _determinePlugins(config); + let res = await _determinePlugins(config); assertDeep(normalizePlugins(res), Object.assign({}, DEFAULTS, { yummy: { bucket: "static", @@ -177,7 +177,7 @@ describe("plugin resolution", () => { })); config[0].bucket = "styles"; - res = _determinePlugins(config); + res = await _determinePlugins(config); assertDeep(normalizePlugins(res), Object.assign({}, DEFAULTS, { yummy: { bucket: "styles", @@ -188,12 +188,12 @@ describe("plugin resolution", () => { }); it("balks at invalid package identifiers", () => { - assert.throws(() => { - _determinePlugins(["faucet-pipeline-yummy"]); + assert.rejects(async () => { + return _determinePlugins(["faucet-pipeline-yummy"]); }, /exit 1/); - assert.throws(() => { - _determinePlugins([{ + assert.rejects(() => { + return _determinePlugins([{ // NB: local configuration must not be comprehensive to ensure // plugin is loaded key: "yummy", @@ -203,8 +203,8 @@ describe("plugin resolution", () => { }); it("balks at duplicate configuration keys", () => { - assert.throws(() => { - _determinePlugins([{ + assert.rejects(() => { + return _determinePlugins([{ key: "dummy", bucket: "static", plugin: () => {} @@ -217,16 +217,16 @@ describe("plugin resolution", () => { }); it("balks at invalid plugins", () => { - assert.throws(() => { - _determinePlugins(["faucet-pipeline-invalid-a"]); + assert.rejects(() => { + return _determinePlugins(["faucet-pipeline-invalid-a"]); }, /exit 1/); - assert.throws(() => { - _determinePlugins(["faucet-pipeline-invalid-b"]); + assert.rejects(() => { + return _determinePlugins(["faucet-pipeline-invalid-b"]); }, /exit 1/); - assert.throws(() => { - _determinePlugins(["faucet-pipeline-invalid-c"]); + assert.rejects(() => { + return _determinePlugins(["faucet-pipeline-invalid-c"]); }, /exit 1/); }); @@ -237,14 +237,14 @@ describe("plugin resolution", () => { }; ["static", "scripts", "styles", "markup"].forEach(bucket => { plugin.bucket = bucket; - assert.doesNotThrow(() => { - _determinePlugins([plugin]); + assert.doesNotReject(() => { + return _determinePlugins([plugin]); }, /exit 1/); }); plugin.bucket = "dummy"; - assert.throws(() => { - _determinePlugins([plugin]); + assert.rejects(() => { + return _determinePlugins([plugin]); }, /exit 1/); }); }); From f2b89b610849ac51fe38eeac4a7472fa9a9f7e57 Mon Sep 17 00:00:00 2001 From: FND Date: Mon, 25 Apr 2022 11:15:00 +0200 Subject: [PATCH 3/3] switched to ESM, abandoning CommonJS --- bin/faucet | 6 ++-- lib/cli.js | 12 ++++---- lib/config.js | 10 +++---- lib/index.js | 26 ++++++++--------- lib/manager.js | 16 +++++----- lib/manifest.js | 14 ++++----- lib/plugins.js | 15 +++------- lib/server.js | 26 +++++++---------- lib/util/files/finder.js | 8 ++--- lib/util/files/index.js | 12 ++++---- lib/util/index.js | 29 ++++++++----------- lib/util/resolve.js | 15 ++++------ lib/util/runner.js | 6 ++-- package.json | 1 + test/bin/extract-sourcemap | 4 +-- .../faucet-pipeline-dummy/index.js | 8 ++--- .../faucet-pipeline-invalid-a/index.js | 6 ++-- .../faucet-pipeline-invalid-b/index.js | 6 ++-- .../faucet-pipeline-invalid-c/index.js | 6 ++-- .../node_modules/faucet-pipeline-js/index.js | 8 ++--- .../faucet-pipeline-sass/index.js | 8 ++--- .../faucet-pipeline-static/index.js | 8 ++--- test/test_manager.js | 13 ++++----- test/test_manifest.js | 13 ++++----- test/test_plugins.js | 29 +++++++++---------- test/test_runner.js | 6 ++-- test/test_server.js | 8 ++--- test/test_util.js | 19 +++++------- 28 files changed, 138 insertions(+), 200 deletions(-) diff --git a/bin/faucet b/bin/faucet index 79d8496..7d1f495 100755 --- a/bin/faucet +++ b/bin/faucet @@ -1,8 +1,6 @@ #!/usr/bin/env node -"use strict"; - -let { faucetDispatch } = require("../lib"); -let { parseCLI } = require("../lib/cli"); +import { faucetDispatch } from "../lib/index.js"; +import { parseCLI } from "../lib/cli.js"; parseCLI(). then(({ referenceDir, config, options }) => { diff --git a/lib/cli.js b/lib/cli.js index 01dba5b..00945ff 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -1,8 +1,6 @@ -"use strict"; - -let { readConfig } = require("./config"); -let { abort, repr } = require("./util"); -let parseArgs = require("minimist"); +import { readConfig } from "./config.js"; +import { abort, repr } from "./util/index.js"; +import parseArgs from "minimist"; let HELP = ` Usage: @@ -27,7 +25,7 @@ Options: serve generated files via HTTP with live reloading `.trim(); -exports.parseCLI = async function parseCLI(argv = process.argv.slice(2), help = HELP) { +export async function parseCLI(argv = process.argv.slice(2), help = HELP) { argv = parseArgs(argv, { boolean: ["watch", "fingerprint", "sourcemaps", "compact"], alias: { @@ -58,4 +56,4 @@ exports.parseCLI = async function parseCLI(argv = process.argv.slice(2), help = let rootDir = process.cwd(); let { referenceDir, config } = await readConfig(rootDir, argv.config); return { referenceDir, config, options }; -}; +} diff --git a/lib/config.js b/lib/config.js index b87fa5c..984f3b2 100644 --- a/lib/config.js +++ b/lib/config.js @@ -1,11 +1,9 @@ -"use strict"; +import path from "path"; -let path = require("path"); - -exports.readConfig = async function readConfig(rootDir, filepath = "faucet.config.js") { +export async function readConfig(rootDir, filepath = "faucet.config.js") { let configPath = path.resolve(rootDir, filepath); return { referenceDir: path.dirname(configPath), - config: await require(configPath) + config: await import(configPath) }; -}; +} diff --git a/lib/index.js b/lib/index.js index 6606d1f..4a83f0e 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,14 +1,12 @@ -"use strict"; +import { liveServer, staticServer } from "./server.js"; +import { pluginsByBucket } from "./plugins.js"; +import { AssetManager } from "./manager.js"; +import { resolvePath } from "./util/resolve.js"; +import { abort, repr } from "./util/index.js"; +import { SerializedRunner } from "./util/runner.js"; +import browserslist from "browserslist"; -let server = require("./server"); -let { pluginsByBucket } = require("./plugins"); -let { AssetManager } = require("./manager"); -let { resolvePath } = require("./util/resolve"); -let { abort, repr } = require("./util"); -let { SerializedRunner } = require("./util/runner"); -let browserslist = require("browserslist"); - -exports.faucetDispatch = async function faucetDispatch(referenceDir, config, +export async function faucetDispatch(referenceDir, config, { watch, fingerprint, sourcemaps, compact, serve, liveserve }) { config = await config; @@ -51,11 +49,11 @@ exports.faucetDispatch = async function faucetDispatch(referenceDir, config, abort("ERROR: serve and liveserve must not be used together"); } if(serve) { - server.static(serve, assetManager.manifest.webRoot); + staticServer(serve, assetManager.manifest.webRoot); } else if(liveserve) { - server.live(liveserve, assetManager.manifest.webRoot); + liveServer(liveserve, assetManager.manifest.webRoot); } -}; +} function buildStep(plugins) { return files => Promise.all(plugins.map(plugin => plugin(files))). @@ -63,7 +61,7 @@ function buildStep(plugins) { } async function makeWatcher(watchDirs, referenceDir) { - let niteOwl = await require("nite-owl"); + let niteOwl = await import("nite-owl"); if(watchDirs) { watchDirs = watchDirs.map(dir => resolvePath(dir, referenceDir, diff --git a/lib/manager.js b/lib/manager.js index c75f97a..f4bbaa7 100644 --- a/lib/manager.js +++ b/lib/manager.js @@ -1,12 +1,10 @@ -"use strict"; +import { Manifest } from "./manifest.js"; +import { createFile } from "./util/files/index.js"; +import { resolvePath } from "./util/resolve.js"; +import { reportFileStatus, abort, generateFingerprint } from "./util/index.js"; +import path from "path"; -let { Manifest } = require("./manifest"); -let { createFile } = require("./util/files"); -let { resolvePath } = require("./util/resolve"); -let { reportFileStatus, abort, generateFingerprint } = require("./util"); -let path = require("path"); - -exports.AssetManager = class AssetManager { +export class AssetManager { constructor(referenceDir, { manifestConfig, fingerprint, exitOnError } = {}) { this.referenceDir = referenceDir; this.fingerprint = fingerprint; @@ -57,4 +55,4 @@ exports.AssetManager = class AssetManager { actualPath = path.relative(referenceDir, actualPath); return this.manifest.set(originalPath, actualPath, targetDir); } -}; +} diff --git a/lib/manifest.js b/lib/manifest.js index 51db3ff..188db0f 100644 --- a/lib/manifest.js +++ b/lib/manifest.js @@ -1,11 +1,9 @@ -"use strict"; +import { createFile } from "./util/files/index.js"; +import { resolvePath } from "./util/resolve.js"; +import { abort } from "./util/index.js"; +import path from "path"; -let { createFile } = require("./util/files"); -let { resolvePath } = require("./util/resolve"); -let { abort } = require("./util"); -let path = require("path"); - -exports.Manifest = class Manifest { +export 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`"); @@ -52,4 +50,4 @@ exports.Manifest = class Manifest { return memo; }, {}); } -}; +} diff --git a/lib/plugins.js b/lib/plugins.js index b940706..49be14a 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -1,6 +1,4 @@ -"use strict"; - -let { loadExtension, abort, repr } = require("./util"); +import { loadExtension, abort, repr } from "./util/index.js"; // common plugins included for convenience let DEFAULTS = [{ @@ -26,15 +24,10 @@ let DEFAULT_KEYS = DEFAULTS.reduce((memo, plugin) => { }, new Set()); let BUCKETS = new Set(["static", "scripts", "styles", "markup"]); -module.exports = { - pluginsByBucket, - _determinePlugins: determinePlugins -}; - // returns plugin functions grouped by bucket and filtered by relevance (based // on configuration) -async function pluginsByBucket(config, defaults) { - let plugins = await determinePlugins(config.plugins, defaults); +export async function pluginsByBucket(config, defaults) { + let plugins = await _determinePlugins(config.plugins, defaults); let buckets = [...BUCKETS].reduce((memo, bucket) => { memo[bucket] = []; return memo; @@ -60,7 +53,7 @@ async function pluginsByBucket(config, defaults) { // `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 -async function determinePlugins(plugins = [], defaults = DEFAULTS) { +export 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 diff --git a/lib/server.js b/lib/server.js index 9ecb696..7535523 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1,28 +1,24 @@ -let { loadExtension, abort, repr } = require("./util"); +import { loadExtension, abort, repr } from "./util/index.js"; let DEFAULTS = { host: "localhost", port: 3000 }; -exports.static = async (config, webroot) => { - let donny = await loadExtension("donny", "failed to activate server"); - let [host, port] = parseHost(config); +export async function liveServer(config, root) { + let server = await loadExtension("live-server", "failed to activate live-server"); + let [host, port] = _parseHost(config); + server.start({ port, host, root, open: false }); +} +export async function staticServer(config, webroot) { + let donny = await loadExtension("donny", "failed to activate server"); + let [host, port] = _parseHost(config); await donny({ port, bind: host, webroot }); console.error(`serving ${repr(webroot)} at http://${host}:${port}`); -}; - -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 }); -}; - -exports._parseHost = parseHost; +} -function parseHost(config) { +export function _parseHost(config) { let { host, port } = DEFAULTS; if(config === true) { return [host, port]; diff --git a/lib/util/files/finder.js b/lib/util/files/finder.js index 2cf86d9..88f85a8 100644 --- a/lib/util/files/finder.js +++ b/lib/util/files/finder.js @@ -1,7 +1,7 @@ -let { readdir, stat } = require("fs/promises"); -let path = require("path"); +import { readdir, stat } from "fs/promises"; +import path from "path"; -exports.FileFinder = class FileFinder { +export class FileFinder { constructor(directory, { skipDotfiles, filter = () => true } = {}) { this.directory = directory; this.filter = filename => { @@ -23,7 +23,7 @@ exports.FileFinder = class FileFinder { return filesWithinDirectory(this.directory, filepaths). then(filepaths => filepaths.filter(this.filter)); } -}; +} function tree(filepath, referenceDir = filepath) { return stat(filepath). diff --git a/lib/util/files/index.js b/lib/util/files/index.js index f2d8ef0..46f03e4 100644 --- a/lib/util/files/index.js +++ b/lib/util/files/index.js @@ -1,14 +1,12 @@ -"use strict"; - -let { writeFile } = require("fs/promises"); -let { mkdirSync } = require("fs"); -let path = require("path"); +import { writeFile } from "fs/promises"; +import { mkdirSync } from "fs"; +import path from "path"; let KNOWN = new Set(); // avoids redundant `mkdir` invocations let LOCKS = new Map(); // avoids concurrent write operations and creates target directory if necessary -exports.createFile = function createFile(filepath, contents) { +export function createFile(filepath, contents) { let lock = LOCKS.get(filepath); if(lock) { // defer return lock.then(() => createFile(filepath, contents)); @@ -26,4 +24,4 @@ exports.createFile = function createFile(filepath, contents) { return prom.then(() => { LOCKS.delete(filepath); }); -}; +} diff --git a/lib/util/index.js b/lib/util/index.js index 0698bc2..f8841a3 100644 --- a/lib/util/index.js +++ b/lib/util/index.js @@ -1,45 +1,40 @@ -"use strict"; - -let path = require("path"); -let crypto = require("crypto"); - -exports.abort = abort; -exports.repr = repr; +import path from "path"; +import crypto from "crypto"; // reports success or failure for a given file path (typically regarding // compilation or write operations) -exports.reportFileStatus = (filepath, referenceDir, error) => { +export function reportFileStatus(filepath, referenceDir, error) { let ref = path.relative(referenceDir, filepath); console.error(error ? `✗ ${ref}: ${error.message || error}` : `✓ ${ref}`); -}; +} // attempts to load a module, prompting the user to install the corresponding // package if it is unavailable -exports.loadExtension = async (pkg, errorMessage, supplier = pkg) => { +export async function loadExtension(pkg, errorMessage, supplier = pkg) { try { - return await require(pkg); + return await import(pkg); } catch(err) { - if(err.code !== "MODULE_NOT_FOUND") { + if(err.code !== "ERR_MODULE_NOT_FOUND") { throw err; } abort(`${errorMessage} - please install ${repr(supplier)}`); } -}; +} -exports.generateFingerprint = (filepath, data) => { +export function generateFingerprint(filepath, data) { let filename = path.basename(filepath); let ext = filename.indexOf(".") === -1 ? "" : "." + filename.split(".").pop(); let name = ext.length === 0 ? filename : path.basename(filepath, ext); let hash = generateHash(data); return path.join(path.dirname(filepath), `${name}-${hash}${ext}`); -}; +} -function abort(msg, code = 1) { +export function abort(msg, code = 1) { console.error(msg); process.exit(code); } -function repr(value, jsonify = true) { +export function repr(value, jsonify = true) { if(jsonify) { value = JSON.stringify(value); } diff --git a/lib/util/resolve.js b/lib/util/resolve.js index afb8124..82dbc96 100644 --- a/lib/util/resolve.js +++ b/lib/util/resolve.js @@ -1,11 +1,8 @@ -"use strict"; +import { abort, repr } from "./index.js"; +import { statSync } from "fs"; +import path from "path"; -let { abort, repr } = require("./"); -let fs = require("fs"); -let path = require("path"); - -exports.resolvePath = function resolvePath(filepath, referenceDir, - { enforceRelative } = {}) { +export function resolvePath(filepath, referenceDir, { enforceRelative } = {}) { if(/^\.?\.\//.test(filepath)) { // starts with `./` or `../` return path.resolve(referenceDir, filepath); } else if(enforceRelative) { @@ -19,11 +16,11 @@ exports.resolvePath = function resolvePath(filepath, referenceDir, // resolution algorithm) let resolved = path.resolve(referenceDir, "node_modules", filepath); try { - fs.statSync(resolved); // ensures file/directory exists + statSync(resolved); // ensures file/directory exists } catch(_err) { abort(`ERROR: could not resolve ${repr(filepath)}`); } return resolved; } } -}; +} diff --git a/lib/util/runner.js b/lib/util/runner.js index 90ec0af..bc90d52 100644 --- a/lib/util/runner.js +++ b/lib/util/runner.js @@ -1,6 +1,4 @@ -"use strict"; - -exports.SerializedRunner = class SerializedRunner { +export class SerializedRunner { constructor(asyncOp) { this.asyncOp = asyncOp; } @@ -32,7 +30,7 @@ exports.SerializedRunner = class SerializedRunner { } return res; } -}; +} function augment(promise) { promise.finally = always; diff --git a/package.json b/package.json index 5bf5e23..637ce96 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "bugs": { "url": "https://github.com/faucet-pipeline/faucet-pipeline-core/issues" }, + "type": "module", "main": "lib/index.js", "bin": { "faucet": "bin/faucet" diff --git a/test/bin/extract-sourcemap b/test/bin/extract-sourcemap index 5b9594f..f2d8d4f 100755 --- a/test/bin/extract-sourcemap +++ b/test/bin/extract-sourcemap @@ -1,7 +1,5 @@ #!/usr/bin/env node -"use strict"; - -let { readFileSync } = require("fs"); +import { readFileSync } from "fs"; let URL_PATTERN = /sourceMappingURL=data:(\S+)/; let MAP_PATTERN = /;base64,(\S+)$/; diff --git a/test/fixtures/node_modules/faucet-pipeline-dummy/index.js b/test/fixtures/node_modules/faucet-pipeline-dummy/index.js index c1366dd..97cd286 100644 --- a/test/fixtures/node_modules/faucet-pipeline-dummy/index.js +++ b/test/fixtures/node_modules/faucet-pipeline-dummy/index.js @@ -1,5 +1,3 @@ -"use strict"; - -exports.key = "dummy"; -exports.bucket = "static"; -exports.plugin = function faucetDummy() {}; +export let key = "dummy"; +export let bucket = "static"; +export let plugin = function faucetDummy() {}; diff --git a/test/fixtures/node_modules/faucet-pipeline-invalid-a/index.js b/test/fixtures/node_modules/faucet-pipeline-invalid-a/index.js index a983f31..fb7de87 100644 --- a/test/fixtures/node_modules/faucet-pipeline-invalid-a/index.js +++ b/test/fixtures/node_modules/faucet-pipeline-invalid-a/index.js @@ -1,4 +1,2 @@ -"use strict"; - -exports.key = "dummy"; -exports.plugin = function faucetInvalidA() {}; +export let key = "dummy"; +export let plugin = function faucetInvalidA() {}; diff --git a/test/fixtures/node_modules/faucet-pipeline-invalid-b/index.js b/test/fixtures/node_modules/faucet-pipeline-invalid-b/index.js index 1a0cc91..5571aec 100644 --- a/test/fixtures/node_modules/faucet-pipeline-invalid-b/index.js +++ b/test/fixtures/node_modules/faucet-pipeline-invalid-b/index.js @@ -1,4 +1,2 @@ -"use strict"; - -exports.bucket = "static"; -exports.plugin = function faucetInvalidB() {}; +export let bucket = "static"; +export let plugin = function faucetInvalidB() {}; diff --git a/test/fixtures/node_modules/faucet-pipeline-invalid-c/index.js b/test/fixtures/node_modules/faucet-pipeline-invalid-c/index.js index 6cc9a7e..f971793 100644 --- a/test/fixtures/node_modules/faucet-pipeline-invalid-c/index.js +++ b/test/fixtures/node_modules/faucet-pipeline-invalid-c/index.js @@ -1,4 +1,2 @@ -"use strict"; - -exports.key = "dummy"; -exports.bucket = "static"; +export let key = "dummy"; +export let bucket = "static"; diff --git a/test/fixtures/node_modules/faucet-pipeline-js/index.js b/test/fixtures/node_modules/faucet-pipeline-js/index.js index 37642c6..7260c95 100644 --- a/test/fixtures/node_modules/faucet-pipeline-js/index.js +++ b/test/fixtures/node_modules/faucet-pipeline-js/index.js @@ -1,5 +1,3 @@ -"use strict"; - -exports.key = "js"; -exports.bucket = "scripts"; -exports.plugin = function faucetJS() {}; +export let key = "js"; +export let bucket = "scripts"; +export let plugin = function faucetJS() {}; diff --git a/test/fixtures/node_modules/faucet-pipeline-sass/index.js b/test/fixtures/node_modules/faucet-pipeline-sass/index.js index af03a1c..10feb0a 100644 --- a/test/fixtures/node_modules/faucet-pipeline-sass/index.js +++ b/test/fixtures/node_modules/faucet-pipeline-sass/index.js @@ -1,5 +1,3 @@ -"use strict"; - -exports.key = "sass"; -exports.bucket = "scripts"; -exports.plugin = function faucetSass() {}; +export let key = "sass"; +export let bucket = "scripts"; +export let plugin = function faucetSass() {}; diff --git a/test/fixtures/node_modules/faucet-pipeline-static/index.js b/test/fixtures/node_modules/faucet-pipeline-static/index.js index dcf60f1..b566f48 100644 --- a/test/fixtures/node_modules/faucet-pipeline-static/index.js +++ b/test/fixtures/node_modules/faucet-pipeline-static/index.js @@ -1,5 +1,3 @@ -"use strict"; - -exports.key = "static"; -exports.bucket = "scripts"; -exports.plugin = function faucetStatic() {}; +export let key = "static"; +export let bucket = "scripts"; +export let plugin = function faucetStatic() {}; diff --git a/test/test_manager.js b/test/test_manager.js index 9966e48..81c5f4a 100644 --- a/test/test_manager.js +++ b/test/test_manager.js @@ -1,14 +1,13 @@ /* global describe, before, after, it */ -"use strict"; +import { AssetManager } from "../lib/manager.js"; +import { fileURLToPath } from "url"; +import path from "path"; +import assert, { strictEqual as assertSame } from "assert"; -let { AssetManager } = require("../lib/manager"); -let path = require("path"); -let assert = require("assert"); - -let assertSame = assert.strictEqual; +let ROOT = path.dirname(fileURLToPath(import.meta.url)); describe("asset manager", () => { - let root = path.resolve(__dirname, "fixtures"); + let root = path.resolve(ROOT, "fixtures"); let cwd; let { exit } = process; diff --git a/test/test_manifest.js b/test/test_manifest.js index e175209..3f5eebb 100644 --- a/test/test_manifest.js +++ b/test/test_manifest.js @@ -1,14 +1,13 @@ /* global describe, before, after, it */ -"use strict"; +import { Manifest } from "../lib/manifest.js"; +import { fileURLToPath } from "url"; +import path from "path"; +import { strictEqual as assertSame } from "assert"; -let { Manifest } = require("../lib/manifest"); -let path = require("path"); -let assert = require("assert"); - -let assertSame = assert.strictEqual; +let ROOT = path.dirname(fileURLToPath(import.meta.url)); describe("manifest", () => { - let root = path.resolve(__dirname, "fixtures"); + let root = path.resolve(ROOT, "fixtures"); let cwd; before(() => { diff --git a/test/test_plugins.js b/test/test_plugins.js index 104631d..a5ef5cb 100644 --- a/test/test_plugins.js +++ b/test/test_plugins.js @@ -1,13 +1,11 @@ /* global describe, before, after, it */ -"use strict"; +import { pluginsByBucket, _determinePlugins } from "../lib/plugins.js"; +import { fileURLToPath } from "url"; +import path from "path"; +import assert, { deepStrictEqual as assertDeep } from "assert"; -let { pluginsByBucket, _determinePlugins } = require("../lib/plugins"); -let path = require("path"); -let assert = require("assert"); - -let { deepStrictEqual: assertDeep } = assert; - -let ROOT = path.resolve(__dirname, "fixtures"); +let ROOT = path.dirname(fileURLToPath(import.meta.url)); +let FIXTURES_PATH = path.resolve(ROOT, "fixtures"); let DEFAULTS = { js: { bucket: "scripts", @@ -28,15 +26,15 @@ let DEFAULTS = { }; let { NODE_PATH } = process.env; -let CUSTOM_NODE_PATH = path.resolve(ROOT, "node_modules"); +let CUSTOM_NODE_PATH = path.resolve(FIXTURES_PATH, "node_modules"); describe("plugin registration", () => { before(() => { - updateNodePath(NODE_PATH, CUSTOM_NODE_PATH); + return updateNodePath(NODE_PATH, CUSTOM_NODE_PATH); }); after(() => { - updateNodePath(NODE_PATH); + return updateNodePath(NODE_PATH); }); it("only loads default plugins referenced within configuration", async () => { @@ -112,12 +110,12 @@ describe("plugin resolution", () => { process.exit = code => { throw new Error(`exit ${code}`); }; - updateNodePath(NODE_PATH, CUSTOM_NODE_PATH); + return updateNodePath(NODE_PATH, CUSTOM_NODE_PATH); }); after(() => { process.exit = exit; - updateNodePath(NODE_PATH); + return updateNodePath(NODE_PATH); }); it("provides a default set of plugins", async () => { @@ -269,7 +267,8 @@ function normalizePlugins(obj) { return obj; } -function updateNodePath(...paths) { +async function updateNodePath(...paths) { process.env.NODE_PATH = paths.join(":"); - require("module").Module._initPaths(); + let { Module } = await import("module"); + Module._initPaths(); } diff --git a/test/test_runner.js b/test/test_runner.js index bf5a621..7eaed91 100644 --- a/test/test_runner.js +++ b/test/test_runner.js @@ -1,8 +1,6 @@ /* global describe, it */ -"use strict"; - -let { SerializedRunner } = require("../lib/util/runner"); -let { strictEqual: assertSame, deepStrictEqual: assertDeep } = require("assert"); +import { SerializedRunner } from "../lib/util/runner.js"; +import { strictEqual as assertSame, deepStrictEqual as assertDeep } from "assert"; describe("watch mode", () => { it("avoids concurrent compilation, queueing recompilation", () => { diff --git a/test/test_server.js b/test/test_server.js index e7f77a4..85512fe 100644 --- a/test/test_server.js +++ b/test/test_server.js @@ -1,10 +1,6 @@ /* global describe, before, after, it */ -"use strict"; - -let { _parseHost } = require("../lib/server"); -let assert = require("assert"); - -let assertSame = assert.strictEqual; +import { _parseHost } from "../lib/server.js"; +import assert, { strictEqual as assertSame } from "assert"; describe("server host parsing", () => { let { exit } = process; diff --git a/test/test_util.js b/test/test_util.js index 06f06e0..cc37717 100644 --- a/test/test_util.js +++ b/test/test_util.js @@ -1,15 +1,12 @@ /* global describe, it */ -"use strict"; - -let { generateFingerprint } = require("../lib/util"); -let { FileFinder } = require("../lib/util/files/finder"); -let path = require("path"); -let assert = require("assert"); - -let assertSame = assert.strictEqual; -let assertDeep = assert.deepStrictEqual; - -let FIXTURES_PATH = path.resolve(__dirname, "fixtures"); +import { generateFingerprint } from "../lib/util/index.js"; +import { FileFinder } from "../lib/util/files/finder.js"; +import { fileURLToPath } from "url"; +import path from "path"; +import { strictEqual as assertSame, deepStrictEqual as assertDeep } from "assert"; + +let ROOT = path.dirname(fileURLToPath(import.meta.url)); +let FIXTURES_PATH = path.resolve(ROOT, "fixtures"); describe("fingerprinting", () => { it("generates a content-dependent hash", () => {