diff --git a/src/Plugins/InputPathToUrl.js b/src/Plugins/InputPathToUrl.js index da033ff0b..f2683c9fa 100644 --- a/src/Plugins/InputPathToUrl.js +++ b/src/Plugins/InputPathToUrl.js @@ -1,27 +1,47 @@ +import path from "node:path"; import { TemplatePath } from "@11ty/eleventy-utils"; import isValidUrl from "../Util/ValidUrl.js"; -import memoize from "../Util/MemoizeFunction.js"; -function normalizeInputPath(inputPath, inputDir, contentMap) { +function getValidPath(contentMap, testPath) { + let normalized = TemplatePath.addLeadingDotSlash(testPath); + + // it must exist in the content map to be valid + if (contentMap[normalized]) { + return normalized; + } +} + +function normalizeInputPath(targetInputPath, inputDir, sourceInputPath, contentMap) { // inputDir is optional at the beginning of the developer supplied-path - let normalized; // Input directory already on the input path - if (TemplatePath.join(inputPath).startsWith(TemplatePath.join(inputDir))) { - normalized = inputPath; - } else { - normalized = TemplatePath.join(inputDir, inputPath); + if (TemplatePath.join(targetInputPath).startsWith(TemplatePath.join(inputDir))) { + let absolutePath = getValidPath(contentMap, targetInputPath); + if (absolutePath) { + return absolutePath; + } } - normalized = TemplatePath.addLeadingDotSlash(normalized); + // Relative to project input directory + let relativeToInputDir = getValidPath(contentMap, TemplatePath.join(inputDir, targetInputPath)); + if (relativeToInputDir) { + return relativeToInputDir; + } - // it must exist in the content map to be valid - if (contentMap[normalized]) { - return normalized; + if (targetInputPath && !path.isAbsolute(targetInputPath)) { + // Relative to source file’s input path + let sourceInputDir = TemplatePath.getDirFromFilePath(sourceInputPath); + let relativeToSourceFile = getValidPath( + contentMap, + TemplatePath.join(sourceInputDir, targetInputPath), + ); + if (relativeToSourceFile) { + return relativeToSourceFile; + } } // the transform may have sent in a URL so we just return it as-is - return inputPath; + return targetInputPath; } function parseFilePath(filepath) { @@ -63,30 +83,30 @@ function FilterPlugin(eleventyConfig) { contentMap = inputPathToUrl; }); - eleventyConfig.addFilter( - "inputPathToUrl", - memoize(function (filepath) { - if (!contentMap) { - throw new Error("Internal error: contentMap not available for `inputPathToUrl` filter."); - } - - if (isValidUrl(filepath)) { - return filepath; - } - - let inputDir = eleventyConfig.directories.input; - let suffix = ""; - [suffix, filepath] = parseFilePath(filepath); - filepath = normalizeInputPath(filepath, inputDir, contentMap); - - let urls = contentMap[filepath]; - if (!urls || urls.length === 0) { - throw new Error("`inputPathToUrl` filter could not find a matching target for " + filepath); - } - - return `${urls[0]}${suffix}`; - }), - ); + eleventyConfig.addFilter("inputPathToUrl", function (targetFilePath) { + if (!contentMap) { + throw new Error("Internal error: contentMap not available for `inputPathToUrl` filter."); + } + + if (isValidUrl(targetFilePath)) { + return targetFilePath; + } + + let inputDir = eleventyConfig.directories.input; + let suffix = ""; + [suffix, targetFilePath] = parseFilePath(targetFilePath); + // @ts-ignore + targetFilePath = normalizeInputPath(targetFilePath, inputDir, this.page.inputPath, contentMap); + + let urls = contentMap[targetFilePath]; + if (!urls || urls.length === 0) { + throw new Error( + "`inputPathToUrl` filter could not find a matching target for " + targetFilePath, + ); + } + + return `${urls[0]}${suffix}`; + }); } function TransformPlugin(eleventyConfig, defaultOptions = {}) { @@ -102,24 +122,30 @@ function TransformPlugin(eleventyConfig, defaultOptions = {}) { contentMap = inputPathToUrl; }); - eleventyConfig.htmlTransformer.addUrlTransform(opts.extensions, function (filepathOrUrl) { + eleventyConfig.htmlTransformer.addUrlTransform(opts.extensions, function (targetFilepathOrUrl) { if (!contentMap) { throw new Error("Internal error: contentMap not available for the `pathToUrl` Transform."); } - if (isValidUrl(filepathOrUrl)) { - return filepathOrUrl; + if (isValidUrl(targetFilepathOrUrl)) { + return targetFilepathOrUrl; } let inputDir = eleventyConfig.directories.input; let suffix = ""; - [suffix, filepathOrUrl] = parseFilePath(filepathOrUrl); - filepathOrUrl = normalizeInputPath(filepathOrUrl, inputDir, contentMap); - - let urls = contentMap[filepathOrUrl]; - if (!filepathOrUrl || !urls || urls.length === 0) { + [suffix, targetFilepathOrUrl] = parseFilePath(targetFilepathOrUrl); + // @ts-ignore + targetFilepathOrUrl = normalizeInputPath( + targetFilepathOrUrl, + inputDir, + this.page.inputPath, + contentMap, + ); + + let urls = contentMap[targetFilepathOrUrl]; + if (!targetFilepathOrUrl || !urls || urls.length === 0) { // fallback, transforms don’t error on missing paths (though the pathToUrl filter does) - return `${filepathOrUrl}${suffix}`; + return `${targetFilepathOrUrl}${suffix}`; } return `${urls[0]}${suffix}`; diff --git a/test/InputPathToUrlPluginTest.js b/test/InputPathToUrlPluginTest.js index b1058d40c..0dc665142 100644 --- a/test/InputPathToUrlPluginTest.js +++ b/test/InputPathToUrlPluginTest.js @@ -61,9 +61,6 @@ test("Using the transform (and the filter too)", async (t) => { }, }); - elev.setIsVerbose(false); - elev.disableLogger(); - let results = await elev.toJSON(); // filter is already available in the default config. t.is( @@ -82,9 +79,6 @@ test("Using the filter", async (t) => { configPath: false, }); - elev.setIsVerbose(false); - elev.disableLogger(); - let results = await elev.toJSON(); t.is( getContentFor(results, "/filter/index.html"), @@ -106,9 +100,6 @@ test("Using the transform and the base plugin", async (t) => { }, }); - elev.setIsVerbose(false); - elev.disableLogger(); - let results = await elev.toJSON(); t.is( getContentFor(results, "/filter/index.html"), @@ -130,9 +121,6 @@ test("Using the transform and the base plugin, reverse order", async (t) => { }, }); - elev.setIsVerbose(false); - elev.disableLogger(); - let results = await elev.toJSON(); t.is( getContentFor(results, "/filter/index.html"), @@ -144,3 +132,43 @@ test("Using the transform and the base plugin, reverse order", async (t) => { ); }); + +test("Issue #3417 Using the transform with relative path (dot slash)", async (t) => { + let elev = new Eleventy("./test/stubs-virtual/", "./test/stubs-virtual/_site", { + configPath: false, + config: function (eleventyConfig) { + // FilterPlugin is available in the default config. + eleventyConfig.addPlugin(TransformPlugin); + + eleventyConfig.addTemplate("source/test.njk", `Target`) + eleventyConfig.addTemplate("source/target.njk", "lol") + }, + }); + + let results = await elev.toJSON(); + // filter is already available in the default config. + t.is( + getContentFor(results, "/source/test/index.html"), + `Target` + ); +}); + +test("Issue #3417 Using the transform with relative path (no dot slash)", async (t) => { + let elev = new Eleventy("./test/stubs-virtual/", "./test/stubs-virtual/_site", { + configPath: false, + config: function (eleventyConfig) { + // FilterPlugin is available in the default config. + eleventyConfig.addPlugin(TransformPlugin); + + eleventyConfig.addTemplate("source/test.njk", `Target`) + eleventyConfig.addTemplate("source/target.njk", "lol") + }, + }); + + let results = await elev.toJSON(); + // filter is already available in the default config. + t.is( + getContentFor(results, "/source/test/index.html"), + `Target` + ); +});