feat: sync tests; migrate tests to shared lib

This commit is contained in:
TheGiddyLimit
2026-02-22 23:24:45 +00:00
parent 4f07500ae6
commit 003cc9ef1d
10 changed files with 9 additions and 800 deletions
-3
View File
@@ -1,3 +0,0 @@
import {BrewCleanerHtml} from "./html-cleaner.js";
await BrewCleanerHtml.pRun();
-47
View File
@@ -1,47 +0,0 @@
import {isMainThread, parentPort} from "worker_threads";
import {BrewCleanerHtml} from "./html-cleaner.js";
if (isMainThread) throw new Error(`Worker must not be started in main thread!`);
let isCancelled = false;
parentPort
.on("message", async msg => {
switch (msg.type) {
case "init": {
parentPort.postMessage({
type: "ready",
payload: {},
});
break;
}
case "cancel": {
isCancelled = true;
break;
}
case "work": {
if (isCancelled) {
parentPort.postMessage({
type: "done",
payload: {},
});
return;
}
const {messages = []} = BrewCleanerHtml.getFileMessages({file: msg.payload.file});
parentPort.postMessage({
type: "done",
payload: {
isError: !!messages.length,
messages,
},
});
break;
}
}
});
-177
View File
@@ -1,177 +0,0 @@
import {getCleanJson, ObjectWalker, Uf, Um} from "5etools-utils";
import he from "he";
import sanitizeHtml from "sanitize-html";
import fs from "fs";
import os from "os";
import path from "path";
import url from "url";
import {Worker} from "worker_threads";
import {Deferred, WorkerList} from "5etools-utils/lib/WorkerList.js";
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
export class BrewCleanerHtml {
static _LOG_TAG = `HTML`;
static _OPTS_SANITIZE = {
allowedTags: [
// region Custom things which look like tags
"<$name$>",
// endregion
],
allowedAttributes: {},
};
static _getCleanFileMeta ({file}) {
const fileData = Uf.readJsonSync(file);
const messages = [];
const {_meta, _test} = fileData;
delete fileData._meta;
delete fileData._test;
const keyStack = [];
const objectStack = [];
const isInFoundryDescriptionEffect = () => {
if (objectStack.at(-1)?.key !== "system.description.value") return false;
return keyStack.at(-1) === "changes" && ["effects", "foundryEffects"].includes(keyStack.at(-2));
};
const fileOut = ObjectWalker.walk({
obj: fileData,
filePath: file,
primitiveHandlers: {
string: (str, {filePath, lastKey}) => {
if (lastKey === "value" && isInFoundryDescriptionEffect()) return str;
const clean = he.unescape(
sanitizeHtml(
str,
this._OPTS_SANITIZE,
),
);
if (clean !== str) {
const msg = `Sanitized ${keyStack.map(k => `"${k}"`).join(" -> ")}:\n${str}\n${clean}`;
messages.push(msg);
Um.info(this._LOG_TAG, msg);
}
return clean;
},
preObject: (obj) => objectStack.push(obj),
postObject: () => objectStack.pop(),
preArray: (_, {lastKey}) => keyStack.push(lastKey),
postArray: () => keyStack.pop(),
},
isModify: true,
});
const out = {$schema: fileOut.$schema, _meta, _test};
Object.assign(out, fileOut);
return {
messages,
out,
};
}
static async _pUpdateDir (dir) {
Uf.listJsonFiles(dir)
.forEach(file => {
const {messages, out} = this._getCleanFileMeta({file})
if (!messages?.length) return;
fs.writeFileSync(file, getCleanJson(out));
});
}
static async pRun () {
await Uf.pRunOnDirs(
async (dir) => {
Um.info(this._LOG_TAG, `Sanitizing HTML in dir "${dir}"...`);
await this._pUpdateDir(dir);
},
{
isSerial: true,
},
);
Um.info(this._LOG_TAG, "Done!");
}
static getFileMessages ({file}) {
return this._getCleanFileMeta({file});
}
static async pGetErrorsOnDirsWorkers ({isFailFast = false} = {}) {
Um.info(this._LOG_TAG, `Testing for HTML...`);
const cntWorkers = Math.max(1, os.cpus().length - 1);
const messages = [];
const fileQueue = [];
Uf.runOnDirs((dir) => fileQueue.push(...Uf.listJsonFiles(dir)));
const workerList = new WorkerList();
let cntFailures = 0;
const workers = [...new Array(cntWorkers)]
.map(() => {
// Relative `Worker` paths do not function in packages, so give an exact path
const worker = new Worker(path.join(__dirname, "html-cleaner-test-worker"));
worker.on("message", (msg) => {
switch (msg.type) {
case "ready":
case "done": {
if (msg.payload.isError) {
messages.push(...msg.payload.messages);
if (isFailFast) workers.forEach(worker => worker.postMessage({type: "cancel"}));
}
if (worker.dIsActive) worker.dIsActive.resolve();
workerList.add(worker);
break;
}
}
});
worker.on("error", e => {
console.error(e);
cntFailures++;
});
worker.postMessage({
type: "init",
payload: {},
});
return worker;
});
while (fileQueue.length) {
if (isFailFast && messages.length) break;
const file = fileQueue.shift();
const worker = await workerList.get();
worker.dIsActive = new Deferred();
worker.postMessage({
type: "work",
payload: {
file,
},
});
}
await Promise.all(workers.map(it => it.dIsActive?.promise));
await Promise.all(workers.map(it => it.terminate()));
return {messages, isUnknownError: !!cntFailures};
}
}
-41
View File
@@ -1,41 +0,0 @@
import fs from "fs";
import {DataTester, BraceCheck, EscapeCharacterCheck} from "5etools-utils";
import * as Uf from "5etools-utils/lib/UtilFs.js";
import {ImageUrlCheck} from "./test-file-contents/ImageUrlCheck.js";
import {CopySourceCheck} from "./test-file-contents/CopySourceCheck.js";
const TIME_TAG = "\tRun duration";
console.time(TIME_TAG);
async function main () {
const dataTesters = [
new BraceCheck(),
new EscapeCharacterCheck(),
new ImageUrlCheck(),
new CopySourceCheck(),
];
DataTester.register({dataTesters});
await Uf.pRunOnDirs(
async (dir) => {
console.log(`Running on directory "${dir}"...`);
await DataTester.pRun(
dir,
dataTesters,
);
},
{
isSerial: true,
},
);
const outMessage = DataTester.getLogReport(dataTesters);
if (outMessage) fs.writeFileSync("./_test/test-data.error.log", outMessage, "utf-8");
console.timeEnd(TIME_TAG);
if (outMessage) process.exit(1);
}
export default main();
-103
View File
@@ -1,103 +0,0 @@
import {DataTesterBase} from "5etools-utils/lib/TestData.js";
import {ObjectWalker} from "5etools-utils/lib/ObjectWalker.js";
import {UtilSource} from "5etools-utils/lib/UtilSource.js";
export class CopySourceCheck extends DataTesterBase {
static _FileState = class {
sources;
dependencies;
internalCopies;
constructor (
{
contents,
}
) {
this.sources = new Set(
(contents._meta?.sources?.map(src => src?.json) || [])
.filter(Boolean),
);
this.dependencies = Object.fromEntries(
Object.entries(contents._meta?.dependencies || {})
.map(([prop, arr]) => [prop, new Set(arr)]),
);
this.internalCopies = new Set(contents._meta?.internalCopies || []);
}
}
registerParsedFileCheckers (parsedJsonChecker) {
parsedJsonChecker.registerFileHandler(this);
}
handleFile (file, contents) {
if (!file.includes("Kobold Press; Scarlet Citadel.json")) return;
const fileState = new this.constructor._FileState({contents});
Object.entries(contents)
.filter(([prop, arr]) => {
if (prop.startsWith("_")) return;
if (!(arr instanceof Array)) return;
arr
.forEach(ent => {
const propStack = [prop];
const inlineDependencies = new Set();
ObjectWalker.walk({
obj: ent,
filePath: file,
primitiveHandlers: {
preObject: this._onPreObject.bind(this, {fileState, propStack, inlineDependencies}),
object: this._checkObject.bind(this, {fileState, propStack, inlineDependencies}),
postObject: this._onPostObject.bind(this, {fileState, propStack, inlineDependencies}),
},
});
});
});
}
_onPreObject ({fileState, propStack, inlineDependencies}, obj) {
if (obj.type !== "statblockInline") return;
propStack.push(obj.dataType);
(obj.dependencies || []).forEach(dep => inlineDependencies.add(dep));
}
_onPostObject ({fileState, propStack, inlineDependencies}, obj) {
if (obj.type !== "statblockInline") return;
propStack.pop();
inlineDependencies.clear();
}
_checkObject ({fileState, propStack, inlineDependencies}, obj, {filePath}) {
if (!obj._copy?.source) return;
const prop = propStack.at(-1);
const sourceCopy = obj._copy.source;
// Classes/subclasses have an alternate structure
if (["class", "subclass"].includes(prop) && UtilSource.isSiteSource(sourceCopy)) {
const classNameLower = obj._copy.className?.toLowerCase();
if (
fileState.dependencies[prop]?.has(classNameLower)
|| inlineDependencies.has(classNameLower)
) return;
}
// If a "root" entity, i.e. not in a `statblockInline`, allow internal copies
if (
propStack.length === 1
&& fileState.internalCopies.has(prop)
&& fileState.sources.has(sourceCopy)
) return;
if (
fileState.dependencies[prop]?.has(sourceCopy)
|| inlineDependencies.has(sourceCopy)
) return;
this._addMessage(`Entity "${propStack.join(" -> ")}" "${obj.name}" "_copy" source "${sourceCopy}" did not match sources found in dependencies in file "${filePath}"\n`);
}
}
-61
View File
@@ -1,61 +0,0 @@
import {DataTesterBase} from "5etools-utils/lib/TestData.js";
import {ObjectWalker} from "5etools-utils/lib/ObjectWalker.js";
export class ImageUrlCheck extends DataTesterBase {
static _URL_PREFIX_HOMEBREW_IMG = `https://raw.githubusercontent.com/TheGiddyLimit/homebrew-img/main/`;
static _RE_HOMEBREW_IMG_PATH = /^(?<type>img|pdf)\/(?<source>[^/]+)\//;
static _FileState = class {
sources;
constructor (
{
contents,
}
) {
this.sources = new Set(
[
...(contents._meta?.sources?.map(src => src?.json) || [])
.filter(Boolean)
.map(srcJson => srcJson.replace(/:/g, "")),
...(contents._test?.additionalImageSources || [])
.map(srcJson => srcJson.replace(/:/g, "")),
],
);
}
}
registerParsedFileCheckers (parsedJsonChecker) {
parsedJsonChecker.registerFileHandler(this);
}
handleFile (file, contents) {
const fileState = new this.constructor._FileState({contents});
ObjectWalker.walk({
obj: contents,
filePath: file,
primitiveHandlers: {
object: this._checkObject.bind(this, {fileState}),
},
});
}
_checkObject ({fileState}, obj, {filePath}) {
if (obj.type !== "image" || obj.href?.type !== "external" || !obj.href?.url) return;
const {url} = obj.href;
if (!url.toLowerCase().startsWith(this.constructor._URL_PREFIX_HOMEBREW_IMG.toLowerCase())) return;
const mPath = this.constructor._RE_HOMEBREW_IMG_PATH.exec(url.slice(this.constructor._URL_PREFIX_HOMEBREW_IMG.length));
if (!mPath) {
this._addMessage(`Unknown "homebrew-img" URL pattern in file "${filePath}": "${url}"\n`);
return;
}
const {source, type} = mPath.groups;
if (fileState.sources.has(source)) return;
this._addMessage(`Image source part "${source}" in "homebrew-img" ${type} URL did not match sources found in file "_meta" or "_test" in file "${filePath}": "${url}"\n`);
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
import {BrewCleanerHtml} from "../_node/html-cleaner.js";
import {BrewCleanerHtml} from "5etools-utils";
import {Um} from "5etools-utils";
const {messages, isUnknownError = false} = await BrewCleanerHtml.pGetErrorsOnDirsWorkers();
+4 -24
View File
@@ -1,6 +1,5 @@
import fs from "fs";
import {BrewTester} from "5etools-utils";
const _TIME_TAG = "\tRun duration";
const _ALLOWLIST_DIRS = new Set([
"Adventurers League",
"AL",
@@ -141,25 +140,6 @@ const _ALLOWLIST_DIRS = new Set([
"YARV",
]);
async function main () {
console.time(_TIME_TAG);
if (!fs.existsSync("_img")) {
console.timeEnd(_TIME_TAG);
return;
}
const extraDirs = fs.readdirSync("_img")
.filter(dir => !_ALLOWLIST_DIRS.has(dir));
if (!extraDirs.length) {
console.timeEnd(_TIME_TAG);
return;
}
console.error(`Extra directories found in "_img":\n${extraDirs.map(d => `\t${d}`).join("\n")}`);
console.timeEnd(_TIME_TAG);
process.exit(1);
}
export default main();
await BrewTester.pTestImgDirectories({
dirAllowlist: _ALLOWLIST_DIRS,
});
+1 -338
View File
@@ -10,9 +10,7 @@
"license": "MIT",
"devDependencies": {
"5etools-utils": "^0.15.3",
"commander": "^14.0.3",
"he": "^1.2.0",
"sanitize-html": "^2.17.1"
"commander": "^14.0.3"
}
},
"node_modules/5etools-utils": {
@@ -83,94 +81,6 @@
"node": ">=20"
}
},
"node_modules/deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
"dev": true,
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.2",
"entities": "^4.2.0"
},
"funding": {
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
}
},
"node_modules/domelementtype": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
]
},
"node_modules/domhandler": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
"dev": true,
"dependencies": {
"domelementtype": "^2.3.0"
},
"engines": {
"node": ">= 4"
},
"funding": {
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
"node_modules/domutils": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
"integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
"dev": true,
"dependencies": {
"dom-serializer": "^2.0.0",
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3"
},
"funding": {
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"dev": true,
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"dev": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -212,43 +122,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"dev": true,
"bin": {
"he": "bin/he"
}
},
"node_modules/htmlparser2": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
"dev": true,
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1",
"entities": "^4.4.0"
}
},
"node_modules/is-plain-object": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-stream": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz",
@@ -275,70 +148,12 @@
"integrity": "sha512-1QoztHPsMQqhDq0hlXY5ZqcEdUzxQEIxgFkKl4WUp2pgShObl+9ovi4kRh2TfvAfxAoHOJ9vIMEqk3k4iex7tg==",
"dev": true
},
"node_modules/nanoid": {
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/number-precision": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/number-precision/-/number-precision-1.6.0.tgz",
"integrity": "sha512-05OLPgbgmnixJw+VvEh18yNPUo3iyp4BEWJcrLu4X9W05KmMifN7Mu5exYvQXqxxeNWhvIF+j3Rij+HmddM/hQ==",
"dev": true
},
"node_modules/parse-srcset": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz",
"integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==",
"dev": true
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true
},
"node_modules/postcss": {
"version": "8.4.47",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
"integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.1.0",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
@@ -349,30 +164,6 @@
"node": ">=0.10.0"
}
},
"node_modules/sanitize-html": {
"version": "2.17.1",
"resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.17.1.tgz",
"integrity": "sha512-ehFCW+q1a4CSOWRAdX97BX/6/PDEkCqw7/0JXZAGQV57FQB3YOkTa/rrzHPeJ+Aghy4vZAFfWMYyfxIiB7F/gw==",
"dev": true,
"license": "MIT",
"dependencies": {
"deepmerge": "^4.2.2",
"escape-string-regexp": "^4.0.0",
"htmlparser2": "^8.0.0",
"is-plain-object": "^5.0.0",
"parse-srcset": "^1.0.2",
"postcss": "^8.3.11"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/type-fest": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
@@ -429,61 +220,6 @@
"integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==",
"dev": true
},
"deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
"dev": true
},
"dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
"dev": true,
"requires": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.2",
"entities": "^4.2.0"
}
},
"domelementtype": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
"dev": true
},
"domhandler": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
"dev": true,
"requires": {
"domelementtype": "^2.3.0"
}
},
"domutils": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
"integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
"dev": true,
"requires": {
"dom-serializer": "^2.0.0",
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3"
}
},
"entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"dev": true
},
"escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"dev": true
},
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -506,30 +242,6 @@
"type-fest": "^4.41.0"
}
},
"he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"dev": true
},
"htmlparser2": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
"dev": true,
"requires": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1",
"entities": "^4.4.0"
}
},
"is-plain-object": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
"dev": true
},
"is-stream": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz",
@@ -548,67 +260,18 @@
"integrity": "sha512-1QoztHPsMQqhDq0hlXY5ZqcEdUzxQEIxgFkKl4WUp2pgShObl+9ovi4kRh2TfvAfxAoHOJ9vIMEqk3k4iex7tg==",
"dev": true
},
"nanoid": {
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
"dev": true
},
"number-precision": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/number-precision/-/number-precision-1.6.0.tgz",
"integrity": "sha512-05OLPgbgmnixJw+VvEh18yNPUo3iyp4BEWJcrLu4X9W05KmMifN7Mu5exYvQXqxxeNWhvIF+j3Rij+HmddM/hQ==",
"dev": true
},
"parse-srcset": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz",
"integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==",
"dev": true
},
"picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true
},
"postcss": {
"version": "8.4.47",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
"integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
"dev": true,
"requires": {
"nanoid": "^3.3.7",
"picocolors": "^1.1.0",
"source-map-js": "^1.2.1"
}
},
"require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"dev": true
},
"sanitize-html": {
"version": "2.17.1",
"resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.17.1.tgz",
"integrity": "sha512-ehFCW+q1a4CSOWRAdX97BX/6/PDEkCqw7/0JXZAGQV57FQB3YOkTa/rrzHPeJ+Aghy4vZAFfWMYyfxIiB7F/gw==",
"dev": true,
"requires": {
"deepmerge": "^4.2.2",
"escape-string-regexp": "^4.0.0",
"htmlparser2": "^8.0.0",
"is-plain-object": "^5.0.0",
"parse-srcset": "^1.0.2",
"postcss": "^8.3.11"
}
},
"source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true
},
"type-fest": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
+3 -5
View File
@@ -9,10 +9,10 @@
"build:index": "node _node/generate-index.js",
"build:clean": "npm run build:clean:base",
"build:clean:base": "node _node/clean-and-test.js",
"build:clean:html": "node _node/clean-html.js",
"build:clean:html": "clean-html",
"test:json": "test-json-brew",
"test:clean": "npm run build:clean:base -- --dry",
"test:file-contents": "node _test/test-file-contents.js",
"test:file-contents": "test-file-contents --img-repo-name homebrew-img --url-prefix-expected https://raw.githubusercontent.com/TheGiddyLimit/homebrew-img/main/",
"test:file-locations": "test-file-locations",
"test:file-names": "test-file-names",
"test:file-props": "test-file-props",
@@ -33,8 +33,6 @@
"homepage": "https://github.com/TheGiddyLimit/homebrew#readme",
"devDependencies": {
"5etools-utils": "^0.15.3",
"commander": "^14.0.3",
"he": "^1.2.0",
"sanitize-html": "^2.17.1"
"commander": "^14.0.3"
}
}