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
19 changes: 11 additions & 8 deletions packages/cli/src/lib/hashing-utils.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
// https://gist.github.com/hyamamoto/fd435505d29ebfa3d9716fd2be8d42f0#gistcomment-2775538
function hashString(inputString) {
let h = 0;
import { createHash } from "node:crypto";

for (let i = 0; i < inputString.length; i += 1) {
h = (Math.imul(31, h) + inputString.charCodeAt(i)) | 0;
}

return Math.abs(h).toString();
/**
* Generates a hash of the input string.
* @param {string} inputString - The string to hash.
* @param {number} [bytes=6] - Number of bytes to be generated by the hash (default: 6).
* Note: This specifies the number of bytes, not the length of the hash.
* The hash length in characters depends on the bytes and is approximately `(bytes * 8 / 6)` when encoded in base64url.
* @returns {string} A base64url-encoded hash of the input string.
*/
function hashString(inputString, bytes = 6) {
return createHash("shake256", { outputLength: bytes }).update(inputString).digest("base64url");
}

export { hashString };
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ import fs from "node:fs";
import glob from "glob-promise";
import { JSDOM } from "jsdom";
import path from "node:path";
import { getDependencyFiles, getOutputTeardownFiles } from "../../../../../test/utils.js";
import {
getDependencyFiles,
getOutputTeardownFiles,
HASH_REGEX,
} from "../../../../../test/utils.js";
import { Runner } from "gallinago";
import { fileURLToPath } from "node:url";

Expand Down Expand Up @@ -132,7 +136,19 @@ describe("Build Greenwood With: ", function () {
const customCss = await fs.promises.readFile(cssFiles[0], "utf-8");

expect(cssFiles.length).to.be.equal(1);
expect(customCss).to.be.equal(expectedCss);
const regex = HASH_REGEX.replace("{8}", "{8,10}");
const normalizeCss = (css) =>
css
.replace(
new RegExp(`webcomponents\\.${regex}\\.jpg`, "g"),
"webcomponents.[HASH].jpg",
)
.replace(new RegExp(`bar\\.${regex}\\.baz`, "g"), "bar.[HASH].baz");

const normalizedCustomCss = normalizeCss(customCss);
const normalizedExpectedCss = normalizeCss(expectedCss);

expect(normalizedCustomCss).to.be.equal(normalizedExpectedCss);
});
});

Expand Down Expand Up @@ -172,14 +188,14 @@ describe("Build Greenwood With: ", function () {
it("should have the expected @font-face file bundle path in the referenced <style> tag in index.html", async function () {
const styleTag = Array.from(dom.window.document.querySelectorAll("head style"));

expect(styleTag[0].textContent).to.contain(
`src:url('/${fontPath}/Geist-Regular.965782360.woff2')`,
expect(styleTag[0].textContent.replace(/\s+/g, "")).to.match(
new RegExp(`src:url\\('/${fontPath}/Geist-Regular\\.${HASH_REGEX}\\.woff2'\\)`),
);
});
});

describe("user workspace reference", () => {
const imagePath = "images/webcomponents.1079385342.jpg";
const imagePath = `images/webcomponents.*.jpg`;

it("should have the expected background image from the user's workspace the output directory", async function () {
expect(
Expand All @@ -193,14 +209,16 @@ describe("Build Greenwood With: ", function () {
);
const contents = await fs.promises.readFile(mainCss[0], "utf-8");

expect(contents).to.contain(
`body{background-color:green;background-image:url('/${imagePath}');}`,
expect(contents.replace(/\s+/g, "")).to.match(
new RegExp(
`body\\{background-color:green;background-image:url\\('/images/webcomponents\\.${HASH_REGEX}\\.jpg'\\);\\}`,
),
);
});
});

describe("inline scratch dir workspace reference", () => {
const imagePath = "images/link.1200825667.png";
const imagePath = `images/link.*.png`;

it("should have the expected background image from the user's workspace the output directory", async function () {
expect(
Expand All @@ -210,15 +228,16 @@ describe("Build Greenwood With: ", function () {

it("should have the expected background-image url file bundle path in the referenced <style> tag in index.html", async function () {
const styleTag = Array.from(dom.window.document.querySelectorAll("head style"));

expect(styleTag[0].textContent).to.contain(
`html{background-image:url('/${imagePath}')}`,
expect(styleTag[0].textContent).to.match(
new RegExp(
`html\\{background-image:url\\('/images/link\\.${HASH_REGEX}\\.png'\\)\\}`,
),
);
});
});

describe("absolute user workspace reference", () => {
const resourcePath = "foo/bar.642520792.baz";
const resourcePath = `foo/bar.*.baz`;

it("should have the expected resource reference from the user's workspace in the output directory", async function () {
expect(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ import fs from "node:fs";
import glob from "glob-promise";
import { JSDOM } from "jsdom";
import path from "node:path";
import { getOutputTeardownFiles, getDependencyFiles } from "../../../../../test/utils.js";
import {
getOutputTeardownFiles,
getDependencyFiles,
HASH_REGEX,
} from "../../../../../test/utils.js";
import { Runner } from "gallinago";
import { fileURLToPath } from "node:url";

Expand Down Expand Up @@ -118,8 +122,10 @@ describe("Build Greenwood With: ", function () {
dom.window.document.querySelectorAll("head > script:not([src])"),
).filter((tag) => !tag.getAttribute("data-gwd"))[0];

expect(inlineScriptTag.textContent.replace(/\n/g, "")).to.equal(
'import"/368592136.dlaVsmnb.js";import"/lit-html.CYd3Xodq.js";//# sourceMappingURL=368592136.BFJXtrkH.js.map',
expect(inlineScriptTag.textContent.replace(/\n/g, "")).to.match(
new RegExp(
`import"\\/${HASH_REGEX}\\.${HASH_REGEX}\\.js";import"\\/lit-html\\.${HASH_REGEX}\\.js";\\/\\/# sourceMappingURL=${HASH_REGEX}\\.${HASH_REGEX}\\.js\\.map`,
),
);
});
});
Expand Down Expand Up @@ -197,12 +203,10 @@ describe("Build Greenwood With: ", function () {
path.join(this.context.publicDir, "styles/theme.*.css"),
);
const contents = fs.readFileSync(themeFile[0], "utf-8");

expect(
contents.indexOf(
"@font-face {font-family:'FontAwesome';src:url('/node-modules/font-awesome/fonts/fontawesome-webfont.139345087.eot?v=4.7.0');",
) > 0,
).to.equal(true);
const contentsRegex = new RegExp(
`@font-face\\s*\\{font-family:'FontAwesome';src:url\\('/node-modules/font-awesome/fonts/fontawesome-webfont\\.${HASH_REGEX}\\.eot\\?v=4\\.7\\.0'\\);`,
);
expect(contents).to.match(contentsRegex);
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import fs from "node:fs";
import glob from "glob-promise";
import { JSDOM } from "jsdom";
import path from "node:path";
import { getOutputTeardownFiles } from "../../../../../test/utils.js";
import { getOutputTeardownFiles, HASH_REGEX } from "../../../../../test/utils.js";
import { runSmokeTest } from "../../../../../test/smoke-test.js";
import { Runner } from "gallinago";
import { fileURLToPath } from "node:url";
Expand Down Expand Up @@ -128,10 +128,9 @@ describe("Build Greenwood With: ", function () {
});

it("should have expected attributes on the `<link>` tag from frontmatter imports", async function () {
const regexExpression = new RegExp(`/components/counter/counter.${HASH_REGEX}.css`);
const link = Array.from(dom.window.document.querySelectorAll("head link")).find(
(link) =>
link.getAttribute("href") === "/components/counter/counter.1173776585.css" &&
link.rel !== "preload",
(link) => regexExpression.test(link.getAttribute("href")) && link.rel !== "preload",
);

expect(link.getAttribute("data-gwd-opt")).to.equal(null);
Expand Down Expand Up @@ -219,8 +218,9 @@ describe("Build Greenwood With: ", function () {
});

it("should have expected attributes on the `<link>` tag from frontmatter imports", async function () {
const link = Array.from(dom.window.document.querySelectorAll("head link")).find(
(link) => link.getAttribute("href") === "/styles/frontmatter-custom.1672594117.css",
const regexExpression = new RegExp(`/styles/frontmatter-custom.${HASH_REGEX}.css`);
const link = Array.from(dom.window.document.querySelectorAll("head link")).find((link) =>
regexExpression.test(link.getAttribute("href")),
);

expect(link).to.not.be.undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,13 +224,13 @@ describe("Build Greenwood With: ", function () {

it("should have the expected main.css file in the output directory", async function () {
expect(
await glob.promise(path.join(this.context.publicDir, "styles", "main.*[a-z0-9].css")),
await glob.promise(path.join(this.context.publicDir, "styles", "main.*.css")),
).to.have.lengthOf(1);
});

it("should have the expected other.css file in the output directory", async function () {
expect(
await glob.promise(path.join(this.context.publicDir, "styles", "other.*[a-z0-9].css")),
await glob.promise(path.join(this.context.publicDir, "styles", "other.*.css")),
).to.have.lengthOf(1);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import chai from "chai";
import { JSDOM } from "jsdom";
import path from "node:path";
import { runSmokeTest } from "../../../../../test/smoke-test.js";
import { getOutputTeardownFiles } from "../../../../../test/utils.js";
import { getOutputTeardownFiles, HASH_REGEX } from "../../../../../test/utils.js";
import { Runner } from "gallinago";
import { fileURLToPath } from "node:url";

Expand Down Expand Up @@ -101,7 +101,7 @@ describe("Build Greenwood With: ", function () {

it("should add one page layout <link> tag", function () {
expect(linkTags[0].rel).to.equal("stylesheet");
expect(linkTags[0].href).to.match(/styles\/theme.[a-z0-9]{10}.css/);
expect(linkTags[0].href).to.match(new RegExp(`/styles/theme.${HASH_REGEX}.css`));
});

it("should add one page layout <style> tag", function () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import path from "node:path";
import { runSmokeTest } from "../../../../../test/smoke-test.js";
import { Runner } from "gallinago";
import { fileURLToPath } from "node:url";
import { HASH_REGEX } from "../../../../../test/utils.js";

const expect = chai.expect;

Expand Down Expand Up @@ -429,7 +430,7 @@ describe("Develop Greenwood With: ", function () {
});

it("should return the correct etag header", function (done) {
expect(response.headers.get("etag")).to.equal("2130309740");
expect(response.headers.get("etag")).to.match(new RegExp(HASH_REGEX));
done();
});

Expand All @@ -445,7 +446,7 @@ describe("Develop Greenwood With: ", function () {

before(async function () {
response = await fetch(`${hostname}:${port}/assets/splash-clip.mp4`, {
headers: new Headers({ "if-none-match": "2130309740" }),
headers: new Headers({ "if-none-match": "QgHxJEgC" }),
});
body = await response.clone().text();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import fs from "node:fs";
import glob from "glob-promise";
import path from "node:path";
import { Runner } from "gallinago";
import { getOutputTeardownFiles } from "../../../../../test/utils.js";
import { getOutputTeardownFiles, HASH_REGEX } from "../../../../../test/utils.js";
import { fileURLToPath } from "node:url";

const expect = chai.expect;
Expand Down Expand Up @@ -66,7 +66,7 @@ describe("Build Greenwood With: ", function () {

expect(scripts.length).to.equal(1);
expect(scriptContents).to.match(
/import r from"\/styles\/theme\.([a-zA-Z0-9]{8}.)\.css"with{type:"css"};/,
new RegExp(`import r from"/styles/theme\\.${HASH_REGEX}\\.css"with\\{type:"css"\\};`),
);
});

Expand All @@ -75,7 +75,7 @@ describe("Build Greenwood With: ", function () {

expect(scripts.length).to.equal(1);
expect(scriptContents).to.match(
/import a from"\/card\.([a-zA-Z0-9-]{8})\.css"with{type:"css"};/,
new RegExp(`import a from"/card\\.${HASH_REGEX}\\.css"with\\{type:"css"\\};`),
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import glob from "glob-promise";
import { JSDOM } from "jsdom";
import path from "node:path";
import { runSmokeTest } from "../../../../../test/smoke-test.js";
import { getOutputTeardownFiles } from "../../../../../test/utils.js";
import { getOutputTeardownFiles, HASH_REGEX } from "../../../../../test/utils.js";
import { Runner } from "gallinago";
import { fileURLToPath } from "node:url";

Expand Down Expand Up @@ -70,8 +70,8 @@ describe("Build Greenwood With: ", function () {
const scriptContents = fs.readFileSync(scripts[0], "utf-8");

expect(scripts.length).to.equal(1);
expect(scriptContents).to.contain(
`import e from"/hero.${cssFileHash}.css"with{type:"css"}`,
Comment thread
shyok21 marked this conversation as resolved.
expect(scriptContents).to.match(
new RegExp(`import e from"/hero\\.${HASH_REGEX}\\.css"with\\{type:"css"\\};`),
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import fs from "node:fs";
import glob from "glob-promise";
import { JSDOM } from "jsdom";
import path from "node:path";
import { getOutputTeardownFiles } from "../../../../../test/utils.js";
import { getOutputTeardownFiles, HASH_REGEX } from "../../../../../test/utils.js";
import { Runner } from "gallinago";
import { fileURLToPath } from "node:url";

Expand All @@ -55,7 +55,6 @@ describe("Serve Greenwood With: ", function () {
const hostname = "http://127.0.0.1:8080";
const basePath = "/my-path";
const jsHash = "eCbOYUm_";
Comment thread
shyok21 marked this conversation as resolved.
const cssHash = "105034690";
let runner;

before(function () {
Expand Down Expand Up @@ -142,7 +141,9 @@ describe("Serve Greenwood With: ", function () {
// TODO for some reason there is an extra <link> tag in the head, should only be 1
// https://github.com/ProjectEvergreen/greenwood/issues/1051
expect(links.length).to.equal(2);
expect(links[1].getAttribute("href")).to.equal(`${basePath}/card.${jsHash}.js`);
Comment thread
shyok21 marked this conversation as resolved.
expect(links[1].getAttribute("href")).to.match(
new RegExp(`${basePath}/card.${HASH_REGEX}.js`),
);

done();
});
Expand All @@ -155,7 +156,9 @@ describe("Serve Greenwood With: ", function () {
// TODO for some reason there is an extra <script> tag in the head, should only be 1
// https://github.com/ProjectEvergreen/greenwood/issues/1051
expect(scripts.length).to.equal(2);
expect(scripts[0].getAttribute("src")).to.equal(`${basePath}/card.${jsHash}.js`);
expect(scripts[0].getAttribute("src")).to.match(
new RegExp(`${basePath}/card\\.${HASH_REGEX}\\.js`),
);

done();
});
Expand All @@ -166,7 +169,9 @@ describe("Serve Greenwood With: ", function () {
);

expect(links.length).to.equal(1);
expect(links[0].getAttribute("href")).to.equal(`${basePath}/styles/main.${cssHash}.css`);
expect(links[0].getAttribute("href")).to.match(
new RegExp(`${basePath}/styles/main\\.${HASH_REGEX}\\.css`),
);

done();
});
Expand All @@ -177,7 +182,9 @@ describe("Serve Greenwood With: ", function () {
);

expect(styles.length).to.equal(1);
expect(styles[0].getAttribute("href")).to.equal(`${basePath}/styles/main.${cssHash}.css`);
expect(styles[0].getAttribute("href")).to.match(
new RegExp(`${basePath}/styles/main\\.${HASH_REGEX}\\.css`),
);

done();
});
Expand Down Expand Up @@ -213,7 +220,7 @@ describe("Serve Greenwood With: ", function () {
let body = "";

before(async function () {
response = await fetch(`${hostname}${basePath}/styles/main.${cssHash}.css`);
response = await fetch(`${hostname}${basePath}/styles/main.Bs32TLjt.css`);
body = await response.clone().text();
});

Expand All @@ -228,8 +235,10 @@ describe("Serve Greenwood With: ", function () {
});

it("should return the correct response body", function (done) {
expect(body).to.contain(
"*{color:blue;background-image:url('/my-path/images/webcomponents.1079385342.jpg');}",
expect(body).to.match(
new RegExp(
`\\*\\{color:blue;background-image:url\\('/my-path/images/webcomponents\\.${HASH_REGEX}\\.jpg'\\);\\}`,
),
);
done();
});
Expand Down
Loading