diff --git a/packages/cli/src/lifecycles/graph.js b/packages/cli/src/lifecycles/graph.js index 1187c53f5..fc757278f 100644 --- a/packages/cli/src/lifecycles/graph.js +++ b/packages/cli/src/lifecycles/graph.js @@ -26,6 +26,18 @@ function getLabelFromRoute(_route) { .join(" "); } +// decodeURIComponent is only meant to normalize percent-encoded filenames; raw +// frontmatter values (e.g. "100% Complete") can contain a bare "%" that throws +// URIError and aborts the whole build, so fall back to the original string +// https://github.com/ProjectEvergreen/greenwood/issues/1709 +function safeDecodeURIComponent(value) { + try { + return decodeURIComponent(value); + } catch { + return value; + } +} + function getIdFromRelativePathPath(relativePathPath, extension) { return relativePathPath .replace(`.${extension}`, "") @@ -284,9 +296,9 @@ const generateGraph = async (compilation) => { }); const page = { - id: decodeURIComponent(getIdFromRelativePathPath(relativePagePath, extension)), - label: decodeURIComponent(label), - title: title ? decodeURIComponent(title) : title, + id: safeDecodeURIComponent(getIdFromRelativePathPath(relativePagePath, extension)), + label: safeDecodeURIComponent(label), + title: title ? safeDecodeURIComponent(title) : title, route: `${basePath}${route}`, layout, data: customData || {}, diff --git a/packages/cli/test/cases/build.default.frontmatter-percent/build.default.frontmatter-percent.spec.js b/packages/cli/test/cases/build.default.frontmatter-percent/build.default.frontmatter-percent.spec.js new file mode 100644 index 000000000..31b58862f --- /dev/null +++ b/packages/cli/test/cases/build.default.frontmatter-percent/build.default.frontmatter-percent.spec.js @@ -0,0 +1,77 @@ +/* + * Use Case + * Run Greenwood build with a page whose frontmatter title/label contain a literal + * "%" that is not part of a valid percent-encoding sequence (e.g. "100% Complete"). + * + * User Result + * Should generate a bare bones Greenwood build without crashing, with the "%" + * containing title reaching the output verbatim. + * + * User Command + * greenwood build + * + * User Config + * None + * + * User Workspace + * Greenwood default + * src/ + * layouts/ + * page.html + * pages/ + * index.html (frontmatter: title "100% Complete", label "Save 20%") + */ +// https://github.com/ProjectEvergreen/greenwood/issues/1709 +import fs from "node:fs"; +import { JSDOM } from "jsdom"; +import path from "node:path"; +import { expect } from "chai"; +import { runSmokeTest } from "../../../../../test/smoke-test.js"; +import { getOutputTeardownFiles } from "../../../../../test/utils.js"; +import { Runner } from "gallinago"; +import { fileURLToPath } from "node:url"; + +describe("Build Greenwood With: ", function () { + const LABEL = "Frontmatter title and label containing a literal percent sign"; + const cliPath = path.join(process.cwd(), "packages/cli/src/bin.js"); + const outputPath = fileURLToPath(new URL(".", import.meta.url)); + let runner; + + before(function () { + this.context = { + publicDir: path.join(outputPath, "public"), + }; + runner = new Runner(); + }); + + describe(LABEL, function () { + before(async function () { + await runner.setup(outputPath); + await runner.runCommand(cliPath, "build"); + }); + + runSmokeTest(["public", "index"], LABEL); + + describe("Percent-containing Frontmatter Title", function () { + let dom; + + before(async function () { + dom = await JSDOM.fromFile(path.resolve(this.context.publicDir, "./index.html")); + }); + + it("should output an index.html file", function () { + expect(fs.existsSync(path.join(this.context.publicDir, "./index.html"))).to.be.true; + }); + + it("should keep the literal '%' in the frontmatter verbatim", function () { + const title = dom.window.document.querySelector("head title").textContent; + + expect(title).to.be.equal("100% Complete"); + }); + }); + }); + + after(async function () { + await runner.teardown(getOutputTeardownFiles(outputPath)); + }); +}); diff --git a/packages/cli/test/cases/build.default.frontmatter-percent/src/layouts/page.html b/packages/cli/test/cases/build.default.frontmatter-percent/src/layouts/page.html new file mode 100644 index 000000000..0a13021f1 --- /dev/null +++ b/packages/cli/test/cases/build.default.frontmatter-percent/src/layouts/page.html @@ -0,0 +1,8 @@ +<html> + <head> + <title>Placeholder Title + + + + + diff --git a/packages/cli/test/cases/build.default.frontmatter-percent/src/pages/index.html b/packages/cli/test/cases/build.default.frontmatter-percent/src/pages/index.html new file mode 100644 index 000000000..a38b33553 --- /dev/null +++ b/packages/cli/test/cases/build.default.frontmatter-percent/src/pages/index.html @@ -0,0 +1,8 @@ +--- +title: 100% Complete +label: Save 20% +--- + + +

Percent

+