From 07b2683f280f17d0bf70830c4abed5f5c3640864 Mon Sep 17 00:00:00 2001 From: Kevin Van Lierde Date: Wed, 14 Feb 2024 19:41:52 +0100 Subject: [PATCH] BREAKING: solves #179, loosen dependency on jstransformers Adds required transform option, as aligned with metalsmith/in-place --- package-lock.json | 30 ---- package.json | 1 - src/get-transformer.js | 20 --- src/index.js | 145 ++++++++++++----- src/utils.js | 36 +++++ test/index.js | 358 ++++++++++++++++++----------------------- 6 files changed, 302 insertions(+), 288 deletions(-) delete mode 100644 src/get-transformer.js create mode 100644 src/utils.js diff --git a/package-lock.json b/package-lock.json index 4b00c2e..d1c8757 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "2.7.0", "license": "MIT", "dependencies": { - "inputformat-to-jstransformer": "^1.4.0", "is-utf8": "^0.2.1", "jstransformer": "^1.0.0" }, @@ -6160,17 +6159,6 @@ "node": ">=10" } }, - "node_modules/inputformat-to-jstransformer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/inputformat-to-jstransformer/-/inputformat-to-jstransformer-1.4.0.tgz", - "integrity": "sha512-Ub+Wjb0mjaND4IS/GDvQ+TEyd1i9U4OdrF58mBY7QTYu8CK5K34DPV7mrvo/WQBJLj7UJWQc7QAmFb7CbQ5lLw==", - "dependencies": { - "require-one": "^1.0.3" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/inquirer": { "version": "9.2.12", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.12.tgz", @@ -9832,11 +9820,6 @@ "node": ">=0.10.0" } }, - "node_modules/require-one": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/require-one/-/require-one-1.0.3.tgz", - "integrity": "sha1-Dv68zpgP78PfhM4A8mnhnItvSZA=" - }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -16150,14 +16133,6 @@ "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", "dev": true }, - "inputformat-to-jstransformer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/inputformat-to-jstransformer/-/inputformat-to-jstransformer-1.4.0.tgz", - "integrity": "sha512-Ub+Wjb0mjaND4IS/GDvQ+TEyd1i9U4OdrF58mBY7QTYu8CK5K34DPV7mrvo/WQBJLj7UJWQc7QAmFb7CbQ5lLw==", - "requires": { - "require-one": "^1.0.3" - } - }, "inquirer": { "version": "9.2.12", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.12.tgz", @@ -18709,11 +18684,6 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, - "require-one": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/require-one/-/require-one-1.0.3.tgz", - "integrity": "sha1-Dv68zpgP78PfhM4A8mnhnItvSZA=" - }, "resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", diff --git a/package.json b/package.json index 71fc086..05d34cf 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,6 @@ "test": "c8 mocha" }, "dependencies": { - "inputformat-to-jstransformer": "^1.4.0", "is-utf8": "^0.2.1", "jstransformer": "^1.0.0" }, diff --git a/src/get-transformer.js b/src/get-transformer.js deleted file mode 100644 index 670ce59..0000000 --- a/src/get-transformer.js +++ /dev/null @@ -1,20 +0,0 @@ -import jstransformer from 'jstransformer' -import toTransformer from 'inputformat-to-jstransformer' - -const cache = {} - -/** - * Gets jstransformer for an extension, and caches them - */ -function getTransformer(ext) { - if (ext in cache) { - return cache[ext] - } - - const transformer = toTransformer(ext) - cache[ext] = transformer ? jstransformer(transformer) : false - - return cache[ext] -} - -export default getTransformer diff --git a/src/index.js b/src/index.js index fae1878..91f6c79 100644 --- a/src/index.js +++ b/src/index.js @@ -1,12 +1,55 @@ import path from 'path' import isUtf8 from 'is-utf8' -import getTransformer from './get-transformer.js' +import { getTransformer } from './utils.js' /* c8 ignore next 3 */ let debug = () => { throw new Error('uninstantiated debug') } +/** + * @callback Render + * @param {string} source + * @param {Object} options + * @param {Object} locals + * @returns {string} + */ + +/** + * @callback RenderAsync + * @param {string} source + * @param {Object} options + * @param {Object} locals + * @param {Function} callback + * @returns {Promise} + */ + +/** + * @callback Compile + * @param {string} source + * @param {Object} options + * @returns {string} + */ + +/** + * @callback CompileAsync + * @param {string} source + * @param {Object} options + * @param {Function} callback + * @returns {Promise} + */ + +/** + * @typedef {Object} JsTransformer + * @property {string} name + * @property {string[]} inputFormats + * @property {string} outputFormat + * @property {Render} [render] + * @property {RenderAsync} [renderAsync] + * @property {Compile} [compile] + * @property {CompileAsync} [compileAsync] + */ + /** * @typedef {Object} Options `@metalsmith/layouts` options * @property {string} [default] A default layout to apply to files, eg `default.njk`. @@ -20,38 +63,54 @@ let debug = () => { * Resolves layouts, in the following order: * 1. Layouts in the frontmatter * 2. Skips file if layout: false in frontmatter - * 3. Default layout in the settings + * 3. Default layout in the options */ -function getLayout({ file, settings }) { +function getLayout({ file, options }) { if (file.layout || file.layout === false) { return file.layout } - return settings.default + return options.default +} + +/** + * Set default options based on jstransformer `transform` + * @param {JsTransformer} transform + * @returns {Options} + */ +function normalizeOptions(options, transform) { + return { + default: null, + pattern: '**', + directory: 'layouts', + suppressNoFilesError: false, + engineOptions: {}, + ...options, + transform + } } /** * Engine, renders file with the appropriate layout */ -function render({ filename, files, metadata, settings, metalsmith }) { +function render({ filename, files, metalsmith, options, transform }) { const file = files[filename] - const layout = getLayout({ file, settings }) - const extension = layout.split('.').pop() + const layout = getLayout({ file, options }) debug.info('Rendering "%s" with layout "%s"', filename, layout) + const metadata = metalsmith.metadata() // Stringify file contents const contents = file.contents.toString() - const transform = getTransformer(extension) const locals = { ...metadata, ...file, contents } - const layoutPath = path.join(metalsmith.path(settings.directory), layout) + const layoutPath = path.join(metalsmith.path(options.directory), layout) // Transform the contents return transform - .renderFileAsync(layoutPath, settings.engineOptions, locals) + .renderFileAsync(layoutPath, options.engineOptions, locals) .then((rendered) => { // Update file with results file.contents = Buffer.from(rendered.body) @@ -68,9 +127,9 @@ function render({ filename, files, metadata, settings, metalsmith }) { * Validate, checks whether a file should be processed */ -function validate({ filename, files, settings }) { +function validate({ filename, files, options }) { const file = files[filename] - const layout = getLayout({ file, settings }) + const layout = getLayout({ file, options }) debug.info(`Validating ${filename}`) @@ -92,15 +151,17 @@ function validate({ filename, files, settings }) { return false } - // Files without an applicable jstransformer are ignored + // Layouts with an extension mismatch are ignored const extension = layout.split('.').pop() - const transformer = getTransformer(extension) + let inputFormats = options.transform.inputFormats + if (!Array.isArray(inputFormats)) inputFormats = [options.transform.inputFormats] - if (!transformer) { - debug.warn('Validation failed, no jstransformer found for layout for "%s"', filename) + if (!inputFormats.includes(extension)) { + debug.warn('Validation failed, layout for "%s" does not have an extension', filename) + return false } - return transformer + return true } /** @@ -109,41 +170,51 @@ function validate({ filename, files, settings }) { * @returns {import('metalsmith').Plugin} */ function layouts(options) { - return function layouts(files, metalsmith, done) { + let transform + + return async function layouts(files, metalsmith, done) { debug = metalsmith.debug('@metalsmith/layouts') - const metadata = metalsmith.metadata() - const defaults = { - pattern: '**', - directory: 'layouts', - engineOptions: {}, - suppressNoFilesError: false + if (!options.transform) { + done(new Error('"transform" option is required')) + return } - const settings = { ...defaults, ...options } - - debug('Running with options: %o', settings) // Check whether the pattern option is valid - if (!(typeof settings.pattern === 'string' || Array.isArray(settings.pattern))) { + if ( + options.pattern && + !(typeof options.pattern === 'string' || Array.isArray(options.pattern)) + ) { return done( - new Error( - 'invalid pattern, the pattern option should be a string or array of strings. See https://www.npmjs.com/package/@metalsmith/layouts#pattern' - ) + new Error('invalid pattern, the pattern option should be a string or array of strings.') ) } - // Filter files by the pattern - const matchedFiles = metalsmith.match(settings.pattern, Object.keys(files)) + // skip resolving the transform option on repeat runs + if (!transform) { + try { + transform = await getTransformer(options.transform, debug) + } catch (err) { + // pass through jstransformer & Node import resolution errors + return done(err) + } + } + + options = normalizeOptions(options, transform) + + debug('Running with options %O', options) + + const matchedFiles = metalsmith.match(options.pattern, Object.keys(files)) - // Filter files by validity - const validFiles = matchedFiles.filter((filename) => validate({ filename, files, settings })) + // Filter files by validity, pass basename to avoid dots in folder path + const validFiles = matchedFiles.filter((filename) => validate({ filename, files, options })) // Let the user know when there are no files to process, unless the check is suppressed if (validFiles.length === 0) { const message = 'no files to process. See https://www.npmjs.com/package/@metalsmith/layouts#suppressnofileserror' - if (settings.suppressNoFilesError) { + if (options.suppressNoFilesError) { debug.error(message) return done() } @@ -153,7 +224,7 @@ function layouts(options) { // Map all files that should be processed to an array of promises and call done when finished return Promise.all( - validFiles.map((filename) => render({ filename, files, metadata, settings, metalsmith })) + validFiles.map((filename) => render({ filename, files, metalsmith, options, transform })) ) .then(() => { debug('Finished rendering %s file%s', validFiles.length, validFiles.length > 1 ? 's' : '') diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..dc4e826 --- /dev/null +++ b/src/utils.js @@ -0,0 +1,36 @@ +import { isAbsolute } from 'path' +import jstransformer from 'jstransformer' + +/** + * Get a transformer by name ("jstransformer-ejs"), shortened name ("ejs") or filesystem path + * @param {string|import('./index').JsTransformer} namePathOrTransformer + * @param {import('metalsmith').Debugger} debug + * @returns {Promise} + */ +export function getTransformer(namePathOrTransformer, debug) { + let transform = null + const t = namePathOrTransformer + const tName = t + const tPath = t + + // let the jstransformer constructor throw errors + if (typeof t !== 'string') { + transform = Promise.resolve(t) + } else { + if (isAbsolute(tPath) || tPath.startsWith('.') || tName.startsWith('jstransformer-')) { + debug('Importing transformer: %s', tPath) + transform = import(tPath).then((t) => t.default) + } else { + debug('Importing transformer: jstransformer-%s', tName) + // suppose a shorthand where the jstransformer- prefix is omitted, more likely + transform = import(`jstransformer-${tName}`) + .then((t) => t.default) + .catch(() => { + // else fall back to trying to import the name + debug.warn('"jstransformer-%s" not found, trying "%s" instead', tName, tName) + return import(tName).then((t) => t.default) + }) + } + } + return transform.then(jstransformer) +} diff --git a/test/index.js b/test/index.js index e4779c6..430f71f 100644 --- a/test/index.js +++ b/test/index.js @@ -3,265 +3,223 @@ import Metalsmith from 'metalsmith' import equal from 'assert-dir-equal' import path from 'path' -import { doesNotThrow, strictEqual } from 'assert' +import { rejects, doesNotReject, strictEqual } from 'assert' import plugin from '../src/index.js' -const fixture = path.resolve.bind(process.cwd(), 'test/fixtures') +function fixture(dir) { + dir = path.resolve('./test/fixtures', dir) + return { + dir, + expected: path.resolve(dir, 'expected'), + actual: path.resolve(dir, 'build') + } +} describe('@metalsmith/layouts', () => { - it('should apply a single layout to a single file', (done) => { - Metalsmith(fixture('single-file')) + it('should apply a single layout to a single file', async () => { + const { dir, actual, expected } = fixture('single-file') + await Metalsmith(dir) .env('DEBUG', process.env.DEBUG) - .use(plugin()) - .build((err) => { - if (err) done(err) - doesNotThrow(() => equal(fixture('single-file/build'), fixture('single-file/expected'))) - done() - }) + .use(plugin({ transform: 'handlebars' })) + .build() + + equal(actual, expected) }) - it('should apply a single layout to a single file with an async jstransformer', (done) => { - Metalsmith(fixture('single-file-async')) + it('should apply a single layout to a single file with an async jstransformer', async () => { + const { dir, actual, expected } = fixture('single-file-async') + await Metalsmith(dir) .env('DEBUG', process.env.DEBUG) - .use(plugin()) - .build((err) => { - if (err) done(err) - doesNotThrow(() => - equal(fixture('single-file-async/build'), fixture('single-file-async/expected')) - ) - done() - }) + .use(plugin({ transform: 'qejs' })) + .build() + + equal(actual, expected) }) - it('should apply a single layout to multiple files', (done) => { - Metalsmith(fixture('multiple-files')) + it('should apply a single layout to multiple files', async () => { + const { dir, actual, expected } = fixture('multiple-files') + await Metalsmith(dir) .env('DEBUG', process.env.DEBUG) - .use(plugin()) - .build((err) => { - if (err) done(err) - doesNotThrow(() => - equal(fixture('multiple-files/build'), fixture('multiple-files/expected')) - ) - done() - }) + .use(plugin({ transform: 'handlebars' })) + .build() + + equal(actual, expected) }) - it('should apply multiple layouts to multiple files', (done) => { - Metalsmith(fixture('multiple-files-and-layouts')) + it('should apply multiple layouts to multiple files', async () => { + const { dir, actual, expected } = fixture('multiple-files-and-layouts') + await Metalsmith(dir) .env('DEBUG', process.env.DEBUG) - .use(plugin()) - .build((err) => { - if (err) done(err) - doesNotThrow(() => - equal( - fixture('multiple-files-and-layouts/build'), - fixture('multiple-files-and-layouts/expected') - ) - ) - done() - }) + .use(plugin({ transform: 'handlebars' })) + .build() + + equal(actual, expected) }) - it('should apply a default layout', (done) => { - Metalsmith(fixture('default-layout')) + it('should apply a default layout', async () => { + const { dir, actual, expected } = fixture('default-layout') + await Metalsmith(dir) .env('DEBUG', process.env.DEBUG) - .use(plugin({ default: 'standard.hbs' })) - .build((err) => { - if (err) done(err) - doesNotThrow(() => - equal(fixture('default-layout/build'), fixture('default-layout/expected')) - ) - done() - }) + .use(plugin({ transform: 'handlebars', default: 'standard.hbs' })) + .build() + + equal(actual, expected) }) - it('should override a default layout', (done) => { - Metalsmith(fixture('override-default-layout')) + it('should override a default layout', async () => { + const { dir, actual, expected } = fixture('override-default-layout') + await Metalsmith(dir) .env('DEBUG', process.env.DEBUG) - .use(plugin({ default: 'standard.hbs' })) - .build((err) => { - if (err) done(err) - doesNotThrow(() => - equal( - fixture('override-default-layout/build'), - fixture('override-default-layout/expected') - ) - ) - done() - }) + .use(plugin({ transform: 'handlebars', default: 'standard.hbs' })) + .build() + + equal(actual, expected) }) - it('should apply a string pattern', (done) => { - Metalsmith(fixture('string-pattern')) + it('should apply a string pattern', async () => { + const { dir, actual, expected } = fixture('string-pattern') + await Metalsmith(dir) .env('DEBUG', process.env.DEBUG) - .use(plugin({ pattern: 'match.html' })) - .build((err) => { - if (err) done(err) - doesNotThrow(() => - equal(fixture('string-pattern/build'), fixture('string-pattern/expected')) - ) - done() - }) + .use(plugin({ transform: 'handlebars', pattern: 'match.html' })) + .build() + + equal(actual, expected) }) - it('should apply an array of string patterns', (done) => { - Metalsmith(fixture('array-pattern')) + it('should apply an array of string patterns', async () => { + const { dir, actual, expected } = fixture('array-pattern') + await Metalsmith(dir) .env('DEBUG', process.env.DEBUG) - .use(plugin({ pattern: ['match.html', 'also.html'] })) - .build((err) => { - if (err) done(err) - doesNotThrow(() => equal(fixture('array-pattern/build'), fixture('array-pattern/expected'))) - done() - }) + .use(plugin({ transform: 'handlebars', pattern: ['match.html', 'also.html'] })) + .build() + + equal(actual, expected) }) - it('should ignore binary files', (done) => { - Metalsmith(fixture('ignore-binary')) + it('should ignore binary files', async () => { + const { dir, actual, expected } = fixture('ignore-binary') + await Metalsmith(dir) .env('DEBUG', process.env.DEBUG) - .use(plugin({ default: 'standard.hbs' })) - .build((err) => { - if (err) done(err) - doesNotThrow(() => equal(fixture('ignore-binary/build'), fixture('ignore-binary/expected'))) - done() - }) + .use(plugin({ transform: 'handlebars', default: 'standard.hbs' })) + .build() + + equal(actual, expected) }) - it('should accept an alternate directory for layouts', (done) => { - Metalsmith(fixture('directory')) + it('should accept an alternate directory for layouts', async () => { + const { dir, actual, expected } = fixture('directory') + await Metalsmith(dir) .env('DEBUG', process.env.DEBUG) - .use(plugin({ directory: 'templates' })) - .build((err) => { - if (err) done(err) - doesNotThrow(() => equal(fixture('directory/build'), fixture('directory/expected'))) - done() - }) + .use(plugin({ transform: 'handlebars', directory: 'templates' })) + .build() + + equal(actual, expected) }) - it('should process variables from the frontmatter', (done) => { - Metalsmith(fixture('variables')) + it('should process variables from the frontmatter', async () => { + const { dir, actual, expected } = fixture('variables') + await Metalsmith(dir) .env('DEBUG', process.env.DEBUG) - .use(plugin()) - .build((err) => { - if (err) done(err) - doesNotThrow(() => equal(fixture('variables/build'), fixture('variables/expected'))) - done() - }) + .use(plugin({ transform: 'handlebars' })) + .build() + + equal(actual, expected) }) - it('should process variables from the metadata', (done) => { - Metalsmith(fixture('metadata')) + it('should process variables from the metadata', async () => { + const { dir, actual, expected } = fixture('metadata') + await Metalsmith(dir) .env('DEBUG', process.env.DEBUG) .metadata({ text: 'Some text' }) - .use(plugin()) - .build((err) => { - if (err) done(err) - doesNotThrow(() => equal(fixture('metadata/build'), fixture('metadata/expected'))) - done() - }) + .use(plugin({ transform: 'handlebars' })) + .build() + + equal(actual, expected) }) - it('should override variables from the metadata with local ones', (done) => { - Metalsmith(fixture('override-metadata')) + it('should override variables from the metadata with local ones', async () => { + const { dir, actual, expected } = fixture('override-metadata') + await Metalsmith(dir) .env('DEBUG', process.env.DEBUG) .metadata({ text: 'Some text' }) - .use(plugin()) - .build((err) => { - if (err) done(err) - doesNotThrow(() => - equal(fixture('override-metadata/build'), fixture('override-metadata/expected')) - ) - done() - }) + .use(plugin({ transform: 'handlebars' })) + .build() + + equal(actual, expected) }) - it('should return an error when there are no valid files to process', (done) => { - Metalsmith(fixture('no-files')) - .env('DEBUG', process.env.DEBUG) - .use(plugin()) - .build((err) => { - strictEqual(err instanceof Error, true) - strictEqual( - err.message, - 'no files to process. See https://www.npmjs.com/package/@metalsmith/layouts#suppressnofileserror' - ) - done() - }) + it('should return an error when there are no valid files to process', async () => { + const { dir } = fixture('no-files') + rejects( + async () => { + await Metalsmith(dir).env('DEBUG', process.env.DEBUG).use(plugin()).build() + }, + { message: 'no files to process.' } + ) }) - it('should not return an error when there are no valid files to process and suppressNoFilesError is true', (done) => { - Metalsmith(fixture('no-files')) - .env('DEBUG', process.env.DEBUG) - .use(plugin({ suppressNoFilesError: true })) - .build((err) => { - strictEqual(err, null) - done() - }) + it('should not return an error when there are no valid files to process and suppressNoFilesError is true', async () => { + const { dir } = fixture('no-files') + doesNotReject(async () => { + await Metalsmith(dir) + .env('DEBUG', process.env.DEBUG) + .use(plugin({ suppressNoFilesError: true })) + .build() + }) }) - it('should return an error for an invalid pattern', (done) => { - Metalsmith(fixture('invalid-pattern')) - .env('DEBUG', process.env.DEBUG) - .use(plugin({ pattern: () => {} })) - .build((err) => { - strictEqual(err instanceof Error, true) - strictEqual( - err.message, - 'invalid pattern, the pattern option should be a string or array of strings. See https://www.npmjs.com/package/@metalsmith/layouts#pattern' - ) - done() - }) + it('should return an error for an invalid pattern', async () => { + const { dir } = fixture('invalid-pattern') + rejects( + async () => { + await Metalsmith(dir) + .env('DEBUG', process.env.DEBUG) + .use(plugin({ pattern: () => {} })) + .build() + }, + { message: 'invalid pattern, the pattern option should be a string or array of strings.' } + ) }) - it('should ignore layouts without an extension', (done) => { - Metalsmith(fixture('ignore-invalid-layout')) + it('should ignore layouts without an extension', async () => { + const { dir, actual, expected } = fixture('ignore-invalid-layout') + await Metalsmith(dir) .env('DEBUG', process.env.DEBUG) - .use(plugin()) - .build((err) => { - if (err) done(err) - doesNotThrow(() => - equal(fixture('ignore-invalid-layout/build'), fixture('ignore-invalid-layout/expected')) - ) - done() - }) + .use(plugin({ transform: 'handlebars' })) + .build() + + equal(actual, expected) }) - it('should ignore layouts without a jstransformer', (done) => { - Metalsmith(fixture('ignore-layout-without-jstransformer')) + it('should ignore layouts without a jstransformer', async () => { + const { dir, actual, expected } = fixture('ignore-layout-without-jstransformer') + await Metalsmith(dir) .env('DEBUG', process.env.DEBUG) - .use(plugin()) - .build((err) => { - if (err) done(err) - doesNotThrow(() => - equal( - fixture('ignore-layout-without-jstransformer/build'), - fixture('ignore-layout-without-jstransformer/expected') - ) - ) - done() - }) + .use(plugin({ transform: 'handlebars' })) + .build() + + equal(actual, expected) }) - it('should allow default layouts to be disabled from the frontmatter', (done) => { - Metalsmith(fixture('override-default')) + it('should allow default layouts to be disabled from the frontmatter', async () => { + const { dir, actual, expected } = fixture('override-default') + await Metalsmith(dir) .env('DEBUG', process.env.DEBUG) - .use(plugin({ default: 'standard.hbs' })) - .build((err) => { - if (err) done(err) - doesNotThrow(() => - equal(fixture('override-default/build'), fixture('override-default/expected')) - ) - done() - }) + .use(plugin({ transform: 'handlebars', default: 'standard.hbs' })) + .build() + + equal(actual, expected) }) - it('should prefix rendering errors with the filename', (done) => { - Metalsmith(fixture('rendering-error')) - .env('DEBUG', process.env.DEBUG) - .use(plugin()) - .build((err) => { - strictEqual(err instanceof Error, true) - strictEqual(err.message.slice(0, 'index.html'.length + 1), 'index.html:') - done() - }) + it('should prefix rendering errors with the filename', async () => { + const { dir } = fixture('rendering-error') + try { + await Metalsmith(dir) + .env('DEBUG', process.env.DEBUG) + .use(plugin({ transform: 'handlebars' })) + .build() + } catch (err) { + strictEqual(err.message.slice(0, 'index.html'.length + 1), 'index.html:') + } }) })