From fe2b929e4555ad6ebbe7519dff75eab2175fcd62 Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 29 Apr 2024 22:58:12 +0100 Subject: [PATCH 001/156] refactor: move untested code into testable class --- index.js | 140 ++------------- src/interlinker.js | 162 ++++++++++++++++++ tests/{interlinker.test.js => plugin.test.js} | 0 3 files changed, 175 insertions(+), 127 deletions(-) create mode 100644 src/interlinker.js rename tests/{interlinker.test.js => plugin.test.js} (100%) diff --git a/index.js b/index.js index b04fa5d..984fd76 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,5 @@ const {wikilinkInlineRule, wikilinkRenderRule} = require('./src/markdown-ext'); -const { EleventyRenderPlugin } = require('@11ty/eleventy'); -const WikilinkParser = require('./src/wikilink-parser'); -const chalk = require('chalk'); +const {Interlinker} = require("./src/interlinker"); /** * Some code borrowed from: @@ -26,150 +24,38 @@ module.exports = function (eleventyConfig, options = {}) { }, }, options); - const rm = new EleventyRenderPlugin.RenderManager(); - const wikilinkParser = new WikilinkParser(opts); + const interlinker = new Interlinker(opts); - const compileTemplate = async (data) => { - if (compiledEmbeds.has(data.url)) return; - - const frontMatter = data.template.frontMatter; - - const layout = (data.data.hasOwnProperty(opts.layoutKey)) - ? data.data[opts.layoutKey] - : opts.defaultLayout; - - const language = (data.data.hasOwnProperty(opts.layoutTemplateLangKey)) - ? data.data[opts.layoutTemplateLangKey] - : opts.defaultLayoutLang === null - ? data.page.templateSyntax - : opts.defaultLayoutLang; - - const tpl = layout === null - ? frontMatter.content - : `{% layout "${layout}" %} {% block content %} ${frontMatter.content} {% endblock %}`; - - const fn = await rm.compile(tpl, language, {templateConfig, extensionMap}); - const result = await fn({content: frontMatter.content, ...data.data}); - - compiledEmbeds.set(data.url, result); - } - - let templateConfig; + // TODO: document eleventyConfig.on("eleventy.config", (cfg) => { - templateConfig = cfg; + interlinker.templateConfig = cfg; }); - let extensionMap; + // TODO: document eleventyConfig.on("eleventy.extensionmap", (map) => { - extensionMap = map; + interlinker.extensionMap = map; }); - // Set of WikiLinks pointing to non-existent pages - const deadWikiLinks = new Set(); - - // Map of what WikiLinks to what - const linkMapCache = new Map(); - - // Map of WikiLinks that have triggered an embed compile - const compiledEmbeds = new Map(); - - eleventyConfig.on('eleventy.after', () => { - deadWikiLinks.forEach( - slug => console.warn(chalk.blue('[@photogabble/wikilinks]'), chalk.yellow('WARNING'), `WikiLink found pointing to non-existent [${slug}], has been set to default stub.`) - ); - }); + eleventyConfig.on('eleventy.after', () => interlinker.deadLinksReport()); // Teach Markdown-It how to display MediaWiki Links. eleventyConfig.amendLibrary('md', (md) => { // WikiLink Embed md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( - wikilinkParser, + interlinker.wikiLinkParser, )); md.renderer.rules.inline_wikilink = wikilinkRenderRule( - wikilinkParser, - linkMapCache, - compiledEmbeds, - deadWikiLinks, + interlinker.wikiLinkParser, + interlinker.linkMapCache, + interlinker.compiledEmbeds, + interlinker.deadWikiLinks, opts ); }); // Add backlinks computed global data, this is executed before the templates are compiled and thus markdown parsed. eleventyConfig.addGlobalData('eleventyComputed', { - backlinks: async (data) => { - // @see https://www.11ty.dev/docs/data-computed/#declaring-your-dependencies - const dependencies = [data.title, data.page, data.collections.all]; - if (dependencies[0] === undefined || !dependencies[1].fileSlug || dependencies[2].length === 0) return []; - - const compilePromises = []; - const allPages = data.collections.all; - const currentSlug = opts.slugifyFn(data.title); - let backlinks = []; - let currentSlugs = new Set([currentSlug, data.page.fileSlug]); - const currentPage = allPages.find(page => page.url === data.page.url); - - // Populate our link map for use later in replacing WikiLinks with page permalinks. - // Pages can list aliases in their front matter, if those exist we should map them - // as well. - - linkMapCache.set(currentSlug, { - page: data.collections.all.find(page => page.url === data.page.url), - title: data.title - }); - - // If a page has defined aliases, then add those to the link map. These must be unique. - - if (data.aliases) { - for (const alias of data.aliases) { - const aliasSlug = opts.slugifyFn(alias); - linkMapCache.set(aliasSlug, { - page: currentPage, - title: alias - }); - currentSlugs.add(aliasSlug) - } - } - - // Loop over all pages and build their outbound links if they have not already been parsed. - allPages.forEach(page => { - if (!page.data.outboundLinks) { - const pageContent = page.template.frontMatter.content; - const outboundLinks = (pageContent.match(wikilinkParser.wikiLinkRegExp) || []); - page.data.outboundLinks = wikilinkParser.parseMultiple(outboundLinks); - - page.data.outboundLinks - .filter(link => link.isEmbed && compiledEmbeds.has(link.slug) === false) - .map(link => allPages.find(page => { - const found = (page.fileSlug === link.slug || (page.data.title && opts.slugifyFn(page.data.title) === link.slug)); - if (found) return true; - - const aliases = ((page.data.aliases && Array.isArray(page.data.aliases)) ? page.data.aliases : []).reduce(function(set, alias){ - set.add(opts.slugifyFn(alias)); - return set; - }, new Set()); - - return aliases.has(link.slug); - })) - .filter(link => (typeof link !== 'undefined')) - .forEach(link => compilePromises.push(compileTemplate(link))) - } - - // If the page links to our current page either by its title or by its aliases then - // add that page to our current page's backlinks. - if (page.data.outboundLinks.some(link => currentSlugs.has(link.slug))) { - backlinks.push({ - url: page.url, - title: page.data.title, - }) - } - }); - - // Block iteration until compilation complete. - if (compilePromises.length > 0) await Promise.all(compilePromises); - - // The backlinks for the current page. - return backlinks; - } + backlinks: interlinker.computeBacklinks }); }; diff --git a/src/interlinker.js b/src/interlinker.js new file mode 100644 index 0000000..30f7759 --- /dev/null +++ b/src/interlinker.js @@ -0,0 +1,162 @@ +const {EleventyRenderPlugin} = require("@11ty/eleventy"); +const WikilinkParser = require("./wikilink-parser"); +const chalk = require("chalk"); + +class Interlinker { + constructor(opts) { + this.opts = opts + + // Set of WikiLinks pointing to non-existent pages + this.deadWikiLinks = new Set(); + + // Map of what WikiLinks to what + this.linkMapCache = new Map(); + + // Map of WikiLinks that have triggered an embed compile + this.compiledEmbeds = new Map(); + + // TODO: document + this.templateConfig = undefined; + this.extensionMap = undefined; + + // TODO: document + this.rm = new EleventyRenderPlugin.RenderManager(); + this.wikiLinkParser = new WikilinkParser(opts); + } + + /** + * @todo document + * @param data + * @return {Promise<*>} + */ + async compileTemplate (data) { + if (this.compiledEmbeds.has(data.url)) return; + + const frontMatter = data.template.frontMatter; + + const layout = (data.data.hasOwnProperty(this.opts.layoutKey)) + ? data.data[this.opts.layoutKey] + : this.opts.defaultLayout; + + const language = (data.data.hasOwnProperty(this.opts.layoutTemplateLangKey)) + ? data.data[this.opts.layoutTemplateLangKey] + : this.opts.defaultLayoutLang === null + ? data.page.templateSyntax + : this.opts.defaultLayoutLang; + + const tpl = layout === null + ? frontMatter.content + : `{% layout "${layout}" %} {% block content %} ${frontMatter.content} {% endblock %}`; + + const fn = await this.rm.compile(tpl, language, { + templateConfig: this.templateConfig, + extensionMap: this.extensionMap + }); + + const result = await fn({content: frontMatter.content, ...data.data}); + + this.compiledEmbeds.set(data.url, result); + + return result; + } + + /** + * This is a computed function that gets added to the global data of 11ty prompting its + * invocation for every page. + * + * @param {Object} data + * @return {Promise>} + */ + async computeBacklinks (data) { + // 11ty will invoke this several times during its build cycle, accessing the values we + // need helps 11ty automatically detect data dependency and invoke the function only + // once they are met. + // @see https://www.11ty.dev/docs/data-computed/#declaring-your-dependencies + const dependencies = [data.title, data.page, data.collections.all]; + if (dependencies[0] === undefined || !dependencies[1].fileSlug || dependencies[2].length === 0) return []; + + const {slugifyFn} = this.opts; + + const compilePromises = []; + const allPages = data.collections.all; + const currentSlug = slugifyFn(data.title); + let backlinks = []; + let currentSlugs = new Set([currentSlug, data.page.fileSlug]); + const currentPage = allPages.find(page => page.url === data.page.url); + + // Populate our link map for use later in replacing WikiLinks with page permalinks. + // Pages can list aliases in their front matter, if those exist we should map them + // as well. + + // TODO: 1.1.0 key files by pathname + // TODO: 1.1.0 key files by title + this.linkMapCache.set(currentSlug, { + page: data.collections.all.find(page => page.url === data.page.url), + title: data.title + }); + + // If a page has defined aliases, then add those to the link map. These must be unique. + + if (data.aliases) { + for (const alias of data.aliases) { + const aliasSlug = slugifyFn(alias); + this.linkMapCache.set(aliasSlug, { + page: currentPage, + title: alias + }); + currentSlugs.add(aliasSlug) + } + } + + // Loop over all pages and build their outbound links if they have not already been parsed. + + allPages.forEach(page => { + if (!page.data.outboundLinks) { + const pageContent = page.template.frontMatter.content; + const outboundLinks = (pageContent.match(this.wikiLinkParser.wikiLinkRegExp) || []); + page.data.outboundLinks = this.wikiLinkParser.parseMultiple(outboundLinks); + + page.data.outboundLinks + .filter(link => link.isEmbed && this.compiledEmbeds.has(link.slug) === false) + .map(link => allPages.find(page => { + const found = (page.fileSlug === link.slug || (page.data.title && slugifyFn(page.data.title) === link.slug)); + if (found) return true; + + const aliases = ((page.data.aliases && Array.isArray(page.data.aliases)) ? page.data.aliases : []).reduce(function(set, alias){ + set.add(slugifyFn(alias)); + return set; + }, new Set()); + + return aliases.has(link.slug); + })) + .filter(link => (typeof link !== 'undefined')) + .forEach(link => compilePromises.push(this.compileTemplate(link))) + } + + // If the page links to our current page either by its title or by its aliases then + // add that page to our current page's backlinks. + if (page.data.outboundLinks.some(link => currentSlugs.has(link.slug))) { + backlinks.push({ + url: page.url, + title: page.data.title, + }) + } + }); + + // Block iteration until compilation complete. + if (compilePromises.length > 0) await Promise.all(compilePromises); + + // The backlinks for the current page. + return backlinks; + } + + deadLinksReport () { + this.deadWikiLinks.forEach( + slug => console.warn(chalk.blue('[@photogabble/wikilinks]'), chalk.yellow('WARNING'), `WikiLink found pointing to non-existent [${slug}], has been set to default stub.`) + ) + } +} + +module.exports = { + Interlinker +} diff --git a/tests/interlinker.test.js b/tests/plugin.test.js similarity index 100% rename from tests/interlinker.test.js rename to tests/plugin.test.js From d59a624105e3a368cbac766053855c61d85b21f0 Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 29 Apr 2024 23:04:32 +0100 Subject: [PATCH 002/156] chore: tidy --- index.js | 3 ++- src/interlinker.js | 14 +++++++------- src/wikilink-parser.js | 6 ++---- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/index.js b/index.js index 984fd76..00bb9dc 100644 --- a/index.js +++ b/index.js @@ -11,6 +11,7 @@ const {Interlinker} = require("./src/interlinker"); module.exports = function (eleventyConfig, options = {}) { /** @var { import('@photogabble/eleventy-plugin-interlinker').EleventyPluginInterlinkOptions } opts */ const opts = Object.assign({ + // TODO: 1.1.0 add custom resolving functions (#19) defaultLayout: null, defaultLayoutLang: null, layoutKey: 'embedLayout', @@ -18,7 +19,7 @@ module.exports = function (eleventyConfig, options = {}) { unableToLocateEmbedFn: () => '[UNABLE TO LOCATE EMBED]', slugifyFn: (input) => { const slugify = eleventyConfig.getFilter('slugify'); - if(typeof slugify !== 'function') throw new Error('Unable to load slugify filter.'); + if (typeof slugify !== 'function') throw new Error('Unable to load slugify filter.'); return slugify(input); }, diff --git a/src/interlinker.js b/src/interlinker.js index 30f7759..d922a69 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -29,12 +29,12 @@ class Interlinker { * @param data * @return {Promise<*>} */ - async compileTemplate (data) { + async compileTemplate(data) { if (this.compiledEmbeds.has(data.url)) return; const frontMatter = data.template.frontMatter; - const layout = (data.data.hasOwnProperty(this.opts.layoutKey)) + const layout = (data.data.hasOwnProperty(this.opts.layoutKey)) ? data.data[this.opts.layoutKey] : this.opts.defaultLayout; @@ -67,7 +67,7 @@ class Interlinker { * @param {Object} data * @return {Promise>} */ - async computeBacklinks (data) { + async computeBacklinks(data) { // 11ty will invoke this several times during its build cycle, accessing the values we // need helps 11ty automatically detect data dependency and invoke the function only // once they are met. @@ -88,8 +88,8 @@ class Interlinker { // Pages can list aliases in their front matter, if those exist we should map them // as well. - // TODO: 1.1.0 key files by pathname - // TODO: 1.1.0 key files by title + // TODO: 1.1.0 key files by pathname (#13) + // TODO: 1.1.0 key files by title (#5) this.linkMapCache.set(currentSlug, { page: data.collections.all.find(page => page.url === data.page.url), title: data.title @@ -122,7 +122,7 @@ class Interlinker { const found = (page.fileSlug === link.slug || (page.data.title && slugifyFn(page.data.title) === link.slug)); if (found) return true; - const aliases = ((page.data.aliases && Array.isArray(page.data.aliases)) ? page.data.aliases : []).reduce(function(set, alias){ + const aliases = ((page.data.aliases && Array.isArray(page.data.aliases)) ? page.data.aliases : []).reduce(function (set, alias) { set.add(slugifyFn(alias)); return set; }, new Set()); @@ -150,7 +150,7 @@ class Interlinker { return backlinks; } - deadLinksReport () { + deadLinksReport() { this.deadWikiLinks.forEach( slug => console.warn(chalk.blue('[@photogabble/wikilinks]'), chalk.yellow('WARNING'), `WikiLink found pointing to non-existent [${slug}], has been set to default stub.`) ) diff --git a/src/wikilink-parser.js b/src/wikilink-parser.js index 316c163..f89f9be 100644 --- a/src/wikilink-parser.js +++ b/src/wikilink-parser.js @@ -5,8 +5,6 @@ module.exports = class WikilinkParser { */ wikiLinkRegExp = /(? part.trim()); const slug = this.slugifyFn(parts[0].replace(/.(md|markdown)\s?$/i, "").trim()); @@ -37,7 +35,7 @@ module.exports = class WikilinkParser { } } - parseMultiple (links) { + parseMultiple(links) { return links.map(link => this.parseSingle(link)); } } From 2700ef3da28cd02a2834d9dcb0641ce6a96f0e1e Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 29 Apr 2024 23:07:54 +0100 Subject: [PATCH 003/156] docs: document use of eleventy.extensionmap --- index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 00bb9dc..bbbf2b4 100644 --- a/index.js +++ b/index.js @@ -32,7 +32,9 @@ module.exports = function (eleventyConfig, options = {}) { interlinker.templateConfig = cfg; }); - // TODO: document + // This triggers on an undocumented internal 11ty event that is triggered once EleventyExtensionMap + // has been loaded. This is used by the EleventyRenderPlugin which is made user of within the + // compileTemplate method of the Interlinker class. eleventyConfig.on("eleventy.extensionmap", (map) => { interlinker.extensionMap = map; }); From 876c0488988c1f140e1bb92693fad4896ebfec62 Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 29 Apr 2024 23:18:53 +0100 Subject: [PATCH 004/156] docs: document use of eleventy.after --- index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/index.js b/index.js index bbbf2b4..d80d74e 100644 --- a/index.js +++ b/index.js @@ -39,6 +39,9 @@ module.exports = function (eleventyConfig, options = {}) { interlinker.extensionMap = map; }); + // After 11ty has finished generating the site output a list of wikilinks that do not link to + // anything. + // TODO: 1.1.0 have this contain more details such as which file(s) are linking eleventyConfig.on('eleventy.after', () => interlinker.deadLinksReport()); // Teach Markdown-It how to display MediaWiki Links. From 0da22f260be93436a9b88932c110c7640307f3aa Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 29 Apr 2024 23:39:42 +0100 Subject: [PATCH 005/156] chore: update ava version --- package-lock.json | 4 ++-- package.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1a307dc..694ac81 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,9 +14,9 @@ }, "devDependencies": { "@11ty/eleventy": "^2.0.0", - "ava": "^5.1.1", + "ava": "^5.2.0", "c8": "^7.12.0", - "slugify": "^1.6.5" + "slugify": "^1.6.6" }, "engines": { "node": ">=16" diff --git a/package.json b/package.json index e117ddc..0a114a3 100644 --- a/package.json +++ b/package.json @@ -48,9 +48,9 @@ }, "devDependencies": { "@11ty/eleventy": "^2.0.0", - "ava": "^5.1.1", + "ava": "^5.2.0", "c8": "^7.12.0", - "slugify": "^1.6.5" + "slugify": "^1.6.6" }, "directories": { "test": "tests" From cbc74296fe435eb9d9e43380e0b2cce4cf80718f Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 30 Apr 2024 00:02:29 +0100 Subject: [PATCH 006/156] docs: set 11ty compatibility --- package.json | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 0a114a3..ed21c99 100644 --- a/package.json +++ b/package.json @@ -26,13 +26,8 @@ "url": "https://github.com/photogabble/eleventy-plugin-interlinker/issues" }, "homepage": "https://github.com/photogabble/eleventy-plugin-interlinker#readme", - "peerDependencies": { - "@11ty/eleventy": ">=2.0.0-beta.1" - }, - "peerDependenciesMeta": { - "@11ty/eleventy": { - "optional": true - } + "11ty": { + "compatibility": ">=2.0.0" }, "files": [ "index.js", From a0c3b7d13c39de0caf1f1ce8575b79d5b82fbdcc Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 30 Apr 2024 00:02:48 +0100 Subject: [PATCH 007/156] docs: document compileTemplate function --- src/interlinker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interlinker.js b/src/interlinker.js index d922a69..25b5085 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -25,7 +25,7 @@ class Interlinker { } /** - * @todo document + * Compiles the template associated with a WikiLink when invoked via the ![[embed syntax]] * @param data * @return {Promise<*>} */ From ecf7c9717c2b7421176108ee1c2a9a297f477261 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 30 Apr 2024 00:03:35 +0100 Subject: [PATCH 008/156] chore: wrap invocation of computeBacklinks in closure to preserve "this" context --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index d80d74e..57d1b21 100644 --- a/index.js +++ b/index.js @@ -62,6 +62,6 @@ module.exports = function (eleventyConfig, options = {}) { // Add backlinks computed global data, this is executed before the templates are compiled and thus markdown parsed. eleventyConfig.addGlobalData('eleventyComputed', { - backlinks: interlinker.computeBacklinks + backlinks: async (data) => await interlinker.computeBacklinks(data) }); }; From aac72c13867f38fef38896af19944a224bde0c09 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 30 Apr 2024 00:04:05 +0100 Subject: [PATCH 009/156] feat: improve test coverage through programmatic use of 11ty --- tests/eleventy.test.js | 25 +++++++++++++++++++ tests/fixtures/sample-small-website/about.md | 5 ++++ .../sample-small-website/eleventy.config.js | 12 +++++++++ .../sample-small-website/hello.liquid | 5 ++++ 4 files changed, 47 insertions(+) create mode 100644 tests/eleventy.test.js create mode 100644 tests/fixtures/sample-small-website/about.md create mode 100644 tests/fixtures/sample-small-website/eleventy.config.js create mode 100644 tests/fixtures/sample-small-website/hello.liquid diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js new file mode 100644 index 0000000..5279b2e --- /dev/null +++ b/tests/eleventy.test.js @@ -0,0 +1,25 @@ +const test = require("ava"); +const Eleventy = require("@11ty/eleventy"); + +function normalize(str) { + return str.trim().replace(/\r\n/g, "\n"); +} + +test("Sample page (wikilink from markdown to liquid)", async t => { + let elev = new Eleventy(`${__dirname}/fixtures/sample-small-website/`, `${__dirname}/fixtures/sample-small-website/_site`, { + configPath: `${__dirname}/fixtures/sample-small-website/eleventy.config.js` + }); + + let results = await elev.toJSON(); + + t.is(2, results.length); + + const [result] = results.filter(result => result.url === '/about/'); + + t.is( + '

This is to show that we can link between Markdown files and liquid files.

', + normalize(result.content) + ); +}); + + diff --git a/tests/fixtures/sample-small-website/about.md b/tests/fixtures/sample-small-website/about.md new file mode 100644 index 0000000..1f05f3d --- /dev/null +++ b/tests/fixtures/sample-small-website/about.md @@ -0,0 +1,5 @@ +--- +title: About +--- + +This is to show that we can link between Markdown files and [[hello|liquid files]]. diff --git a/tests/fixtures/sample-small-website/eleventy.config.js b/tests/fixtures/sample-small-website/eleventy.config.js new file mode 100644 index 0000000..9b10b5c --- /dev/null +++ b/tests/fixtures/sample-small-website/eleventy.config.js @@ -0,0 +1,12 @@ +module.exports = function (eleventyConfig) { + eleventyConfig.addPlugin( + require('../../../index.js'), + ); + + return { + dir: { + includes: "_includes", + layouts: "_layouts", + } + } +} diff --git a/tests/fixtures/sample-small-website/hello.liquid b/tests/fixtures/sample-small-website/hello.liquid new file mode 100644 index 0000000..8c1a288 --- /dev/null +++ b/tests/fixtures/sample-small-website/hello.liquid @@ -0,0 +1,5 @@ +--- +title: Hello +--- + +Hello World! From 3d124adc3f5153892d6e54452f7eb5667c0eba04 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 30 Apr 2024 00:07:34 +0100 Subject: [PATCH 010/156] chore: test on Linux, Windows and MacOS --- .github/workflows/node-test.yml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/node-test.yml b/.github/workflows/node-test.yml index 2ba81d8..8a8c32c 100644 --- a/.github/workflows/node-test.yml +++ b/.github/workflows/node-test.yml @@ -1,6 +1,3 @@ -# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions - name: Node.js CI on: @@ -11,13 +8,11 @@ on: jobs: build: - - runs-on: ubuntu-latest - + runs-on: ${{ matrix.os }} strategy: matrix: - node-version: [14.x, 16.x] - # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ + node-version: ["14", "16", "18"] + os: ["ubuntu-latest", "macos-latest", "windows-latest"] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} From 05bfab13547961a8b4eb4f2940013c59b7d8f3be Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 30 Apr 2024 00:17:47 +0100 Subject: [PATCH 011/156] chore: update checkout and setup-node action functions --- .github/workflows/node-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/node-test.yml b/.github/workflows/node-test.yml index 8a8c32c..e665161 100644 --- a/.github/workflows/node-test.yml +++ b/.github/workflows/node-test.yml @@ -14,9 +14,9 @@ jobs: node-version: ["14", "16", "18"] os: ["ubuntu-latest", "macos-latest", "windows-latest"] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} cache: 'npm' From cde1f11fc623d56c16cf85349225c1921961b303 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 30 Apr 2024 00:23:20 +0100 Subject: [PATCH 012/156] chore: update node version reference --- .github/workflows/node-test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/node-test.yml b/.github/workflows/node-test.yml index e665161..116d5b0 100644 --- a/.github/workflows/node-test.yml +++ b/.github/workflows/node-test.yml @@ -11,14 +11,14 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - node-version: ["14", "16", "18"] + node: [14.x, 16.x, 18.x] os: ["ubuntu-latest", "macos-latest", "windows-latest"] steps: - uses: actions/checkout@v3 - - name: Use Node.js ${{ matrix.node-version }} + - name: Use Node.js ${{ matrix.node }} uses: actions/setup-node@v3 with: - node-version: ${{ matrix.node-version }} + node-version: ${{ matrix.node }} cache: 'npm' - run: npm ci - run: npm test From ab82560e2032de7dcfe8db1397dee0ace0bad9c3 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 30 Apr 2024 00:24:26 +0100 Subject: [PATCH 013/156] chore: drop support for Node v14 --- .github/workflows/node-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/node-test.yml b/.github/workflows/node-test.yml index 116d5b0..4adb91a 100644 --- a/.github/workflows/node-test.yml +++ b/.github/workflows/node-test.yml @@ -11,7 +11,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - node: [14.x, 16.x, 18.x] + node: [16.x, 18.x] os: ["ubuntu-latest", "macos-latest", "windows-latest"] steps: - uses: actions/checkout@v3 From 915c668081c9d9b611ce1aeaa6b403f0ae2959ce Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 30 Apr 2024 00:27:57 +0100 Subject: [PATCH 014/156] chore: add test with node 20 --- .github/workflows/node-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/node-test.yml b/.github/workflows/node-test.yml index 4adb91a..32e6606 100644 --- a/.github/workflows/node-test.yml +++ b/.github/workflows/node-test.yml @@ -11,7 +11,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - node: [16.x, 18.x] + node: [16.x, 18.x, 20.x] os: ["ubuntu-latest", "macos-latest", "windows-latest"] steps: - uses: actions/checkout@v3 From 3fe4ba2600b699578c23f369f90f8773740a113b Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 30 Apr 2024 00:29:05 +0100 Subject: [PATCH 015/156] chore: publish using node 20 --- .github/workflows/npm-publish.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 6cdebaf..a5449b8 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 20 - run: npm ci - run: npm test @@ -25,7 +25,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 20 registry-url: https://registry.npmjs.org/ - run: npm ci - run: npm publish From 1824b9c1b37e8190c759c65b204defec9dae754e Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 30 Apr 2024 00:34:56 +0100 Subject: [PATCH 016/156] chore: set ava config --- package.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/package.json b/package.json index ed21c99..c35e834 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,16 @@ "11ty": { "compatibility": ">=2.0.0" }, + "ava": { + "failFast": true, + "files": [ + "./tests/*.test.js" + ], + "ignoredByWatcher": [ + "**/_site/**", + ".cache" + ] + }, "files": [ "index.js", "index.d.ts", From 5393b35da3948432f1ab769fd914dae299201feb Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 30 Apr 2024 00:35:22 +0100 Subject: [PATCH 017/156] chore: split normalize into helpers.js and use to pass tests on windows --- tests/eleventy.test.js | 5 +---- tests/helpers.js | 7 +++++++ tests/markdown-wikilink-renderer.test.js | 6 +++++- 3 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 tests/helpers.js diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index 5279b2e..f423a79 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -1,9 +1,6 @@ const test = require("ava"); const Eleventy = require("@11ty/eleventy"); - -function normalize(str) { - return str.trim().replace(/\r\n/g, "\n"); -} +const{normalize} = require('./helpers'); test("Sample page (wikilink from markdown to liquid)", async t => { let elev = new Eleventy(`${__dirname}/fixtures/sample-small-website/`, `${__dirname}/fixtures/sample-small-website/_site`, { diff --git a/tests/helpers.js b/tests/helpers.js new file mode 100644 index 0000000..ae697b9 --- /dev/null +++ b/tests/helpers.js @@ -0,0 +1,7 @@ +function normalize(str) { + return str.trim().replace(/\r\n/g, "\n"); +} + +module.exports = { + normalize, +} diff --git a/tests/markdown-wikilink-renderer.test.js b/tests/markdown-wikilink-renderer.test.js index 3cafe74..8e2b283 100644 --- a/tests/markdown-wikilink-renderer.test.js +++ b/tests/markdown-wikilink-renderer.test.js @@ -1,5 +1,6 @@ const {wikilinkInlineRule, wikilinkRenderRule} = require('../src/markdown-ext'); const WikilinkParser = require('../src/wikilink-parser'); +const {normalize} = require('./helpers'); const slugify = require('slugify'); const test = require('ava'); const fs = require('fs'); @@ -158,5 +159,8 @@ test('inline rule correctly parses mixed wikilink and embed in multiline input', const markdown = fs.readFileSync(__dirname + '/fixtures/multiline.md', {encoding:'utf8', flag:'r'}); const html = fs.readFileSync(__dirname + '/fixtures/multiline.html', {encoding:'utf8', flag:'r'}); - t.is(md.render(markdown), html); + t.is( + normalize(md.render(markdown)), + normalize(html) + ); }); From 3ca5083d81f73d0c59ef2e0f18e9903f3962610e Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 30 Apr 2024 09:53:46 +0100 Subject: [PATCH 018/156] chore: add isArray check --- src/interlinker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interlinker.js b/src/interlinker.js index 25b5085..2f6ffa5 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -97,7 +97,7 @@ class Interlinker { // If a page has defined aliases, then add those to the link map. These must be unique. - if (data.aliases) { + if (data.aliases && Array.isArray(data.aliases)) { for (const alias of data.aliases) { const aliasSlug = slugifyFn(alias); this.linkMapCache.set(aliasSlug, { From d623964aa3accdbd2c19cdf16934c690c83b0b87 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 30 Apr 2024 18:34:26 +0100 Subject: [PATCH 019/156] chore: normalize by stripping newlines --- tests/helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/helpers.js b/tests/helpers.js index ae697b9..ab75801 100644 --- a/tests/helpers.js +++ b/tests/helpers.js @@ -1,5 +1,5 @@ function normalize(str) { - return str.trim().replace(/\r\n/g, "\n"); + return str.trim().replace(/\r?\n|\r/g, ''); } module.exports = { From 378f06963bc787bec7f88c178f09edb922550157 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 30 Apr 2024 18:35:52 +0100 Subject: [PATCH 020/156] chore: add test fixture for programmatic 11ty --- tests/fixtures/sample-small-website/_layouts/default.liquid | 2 ++ tests/fixtures/sample-small-website/about.md | 1 + tests/fixtures/sample-small-website/hello.liquid | 3 ++- 3 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/sample-small-website/_layouts/default.liquid diff --git a/tests/fixtures/sample-small-website/_layouts/default.liquid b/tests/fixtures/sample-small-website/_layouts/default.liquid new file mode 100644 index 0000000..9065374 --- /dev/null +++ b/tests/fixtures/sample-small-website/_layouts/default.liquid @@ -0,0 +1,2 @@ +
{{ content }}
+
{%- for link in backlinks %}{{ link.title }}{%- endfor %}
diff --git a/tests/fixtures/sample-small-website/about.md b/tests/fixtures/sample-small-website/about.md index 1f05f3d..17a26a6 100644 --- a/tests/fixtures/sample-small-website/about.md +++ b/tests/fixtures/sample-small-website/about.md @@ -1,5 +1,6 @@ --- title: About +layout: default.liquid --- This is to show that we can link between Markdown files and [[hello|liquid files]]. diff --git a/tests/fixtures/sample-small-website/hello.liquid b/tests/fixtures/sample-small-website/hello.liquid index 8c1a288..4491deb 100644 --- a/tests/fixtures/sample-small-website/hello.liquid +++ b/tests/fixtures/sample-small-website/hello.liquid @@ -1,5 +1,6 @@ --- title: Hello +layout: default.liquid --- -Hello World! +This is to show that we can link back via regular internal links. From f4f5c6eb52cdf3c9f86f006d2355ac7996913412 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 30 Apr 2024 18:36:48 +0100 Subject: [PATCH 021/156] feat: add html link parser... ... this has the same interface as the wikilink parser and makes the plugin aware of internal href links. --- src/html-link-parser.js | 42 +++++++++++++++++++++++++ tests/html-internal-link-parser.test.js | 21 +++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 src/html-link-parser.js create mode 100644 tests/html-internal-link-parser.test.js diff --git a/src/html-link-parser.js b/src/html-link-parser.js new file mode 100644 index 0000000..40e20f9 --- /dev/null +++ b/src/html-link-parser.js @@ -0,0 +1,42 @@ +module.exports = class HTMLLinkParser { + + /** + * This regex finds all html tags with an href that begins with / denoting they are internal links. + * + * @type {RegExp} + */ + internalLinkRegex = /href="\/(.*?)"/g; + + /** + * Parses a single HTML link into the link object understood by the Interlinker. Unlike with Wikilinks we only + * care about the href so that we can look up the linked page record. + * + * @param {string} link + * @return {{isEmbed: boolean, href: string}} + */ + parseSingle(link) { + return { + href: link.slice(6, -1) + .replace(/.(md|markdown)\s?$/i, "") + .replace("\\", "") + .trim() + .split("#")[0], + isEmbed: false, + } + } + + parseMultiple(links) { + return links.map(link => this.parseSingle(link)); + } + + /** + * Find's all internal href links within an HTML document. + * @param {string} document + * @return {Array<{isEmbed: false, href: string}>} + */ + find(document) { + return this.parseMultiple( + (document.match(this.internalLinkRegex) || []) + ) + } +} diff --git a/tests/html-internal-link-parser.test.js b/tests/html-internal-link-parser.test.js new file mode 100644 index 0000000..3fc9d2f --- /dev/null +++ b/tests/html-internal-link-parser.test.js @@ -0,0 +1,21 @@ +const HTMLLinkParser = require('../src/html-link-parser'); +const test = require('ava'); + +test('html link parser grabs multiple href, ignoring external links', t => { + const parser = new HTMLLinkParser(); + const links = parser.find('

Hello world this is a link home and this is a link somewhere

The following link should be ignored example.com.

'); + + t.is(2, links.length); + + const expectedLinks = ['/home', '/somewhere']; + for (const link of links) { + t.is(false, link.isEmbed); // HTML embed not supported for anchor links + + const idx = expectedLinks.indexOf(link.href); + t.is(true, idx !== -1); + + expectedLinks.splice(idx, 1); + } + + t.is(0, expectedLinks.length); +}); From 3828cc96da6eb78b91d52b8cd3f1063a564ac333 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 30 Apr 2024 18:39:42 +0100 Subject: [PATCH 022/156] refactor: outboundLinks is computed instead of backlinks... ... this is because the computed value is executed for each file that 11ty parses and instead of looping over all files for _each_ file, it can populate the backlinks of files being linked to. Removal of this loop on each file should speed up the plugin when used in large projects. --- index.js | 5 ++- src/interlinker.js | 94 ++++++++++++++++++++++++------------------ src/wikilink-parser.js | 21 ++++++++++ 3 files changed, 79 insertions(+), 41 deletions(-) diff --git a/index.js b/index.js index 57d1b21..ad7ea4c 100644 --- a/index.js +++ b/index.js @@ -60,8 +60,9 @@ module.exports = function (eleventyConfig, options = {}) { ); }); - // Add backlinks computed global data, this is executed before the templates are compiled and thus markdown parsed. + // Add outboundLinks computed global data, this is executed before the templates are compiled and + // thus markdown parsed. eleventyConfig.addGlobalData('eleventyComputed', { - backlinks: async (data) => await interlinker.computeBacklinks(data) + outboundLinks: async (data) => await interlinker.compute(data) }); }; diff --git a/src/interlinker.js b/src/interlinker.js index 2f6ffa5..ebdbbb1 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -1,5 +1,6 @@ const {EleventyRenderPlugin} = require("@11ty/eleventy"); const WikilinkParser = require("./wikilink-parser"); +const HTMLLinkParser = require("./html-link-parser"); const chalk = require("chalk"); class Interlinker { @@ -21,7 +22,9 @@ class Interlinker { // TODO: document this.rm = new EleventyRenderPlugin.RenderManager(); + this.wikiLinkParser = new WikilinkParser(opts); + this.HTMLLinkParser = new HTMLLinkParser(); } /** @@ -67,7 +70,7 @@ class Interlinker { * @param {Object} data * @return {Promise>} */ - async computeBacklinks(data) { + async compute(data) { // 11ty will invoke this several times during its build cycle, accessing the values we // need helps 11ty automatically detect data dependency and invoke the function only // once they are met. @@ -80,7 +83,6 @@ class Interlinker { const compilePromises = []; const allPages = data.collections.all; const currentSlug = slugifyFn(data.title); - let backlinks = []; let currentSlugs = new Set([currentSlug, data.page.fileSlug]); const currentPage = allPages.find(page => page.url === data.page.url); @@ -108,46 +110,60 @@ class Interlinker { } } - // Loop over all pages and build their outbound links if they have not already been parsed. - - allPages.forEach(page => { - if (!page.data.outboundLinks) { - const pageContent = page.template.frontMatter.content; - const outboundLinks = (pageContent.match(this.wikiLinkParser.wikiLinkRegExp) || []); - page.data.outboundLinks = this.wikiLinkParser.parseMultiple(outboundLinks); - - page.data.outboundLinks - .filter(link => link.isEmbed && this.compiledEmbeds.has(link.slug) === false) - .map(link => allPages.find(page => { - const found = (page.fileSlug === link.slug || (page.data.title && slugifyFn(page.data.title) === link.slug)); - if (found) return true; - - const aliases = ((page.data.aliases && Array.isArray(page.data.aliases)) ? page.data.aliases : []).reduce(function (set, alias) { - set.add(slugifyFn(alias)); - return set; - }, new Set()); - - return aliases.has(link.slug); - })) - .filter(link => (typeof link !== 'undefined')) - .forEach(link => compilePromises.push(this.compileTemplate(link))) - } + // Identify this pages outbound internal links both as wikilink _and_ regular html anchor tags. For each outlink + // lookup the other page and add this to its backlinks data value. + if (currentPage.template.frontMatter?.content) { + const pageContent = currentPage.template.frontMatter.content; + const outboundLinks = [ + ...this.wikiLinkParser.find(pageContent), + ...this.HTMLLinkParser.find(pageContent), + ].map((link) => { + // Lookup the page this link, links to and add this page to its backlinks + const page = allPages.find((page) => { + if (link.href && (page.url === link.href || page.url === `${link.href}/`)) { + return true; + } + + if (page.fileSlug === link.slug || (page.data.title && slugifyFn(page.data.title) === link.slug)) { + return true; + } + + const aliases = ((page.data.aliases && Array.isArray(page.data.aliases)) ? page.data.aliases : []).reduce(function (set, alias) { + set.add(slugifyFn(alias)); + return set; + }, new Set()); + + return aliases.has(link.slug); + }); - // If the page links to our current page either by its title or by its aliases then - // add that page to our current page's backlinks. - if (page.data.outboundLinks.some(link => currentSlugs.has(link.slug))) { - backlinks.push({ - url: page.url, - title: page.data.title, - }) - } - }); + if (!page.data.backlinks) { + page.data.backlinks = []; + } - // Block iteration until compilation complete. - if (compilePromises.length > 0) await Promise.all(compilePromises); + page.data.backlinks.push({ + url: currentPage.url, + title: currentPage.data.title, + }); + + // If this is an embed and the embed template hasn't been compiled, add this to the queue + if (link.isEmbed && this.compiledEmbeds.has(link.slug) === false) { + compilePromises.push(this.compileTemplate(link)); + } + + return { + ...link, + href: page.url, + } + }); + + // Block iteration until compilation complete. + if (compilePromises.length > 0) await Promise.all(compilePromises); + + // The computed outbound links for the current page. + return outboundLinks; + } - // The backlinks for the current page. - return backlinks; + return []; } deadLinksReport() { diff --git a/src/wikilink-parser.js b/src/wikilink-parser.js index f89f9be..eec8050 100644 --- a/src/wikilink-parser.js +++ b/src/wikilink-parser.js @@ -1,6 +1,7 @@ module.exports = class WikilinkParser { /** * This regex finds all WikiLink style links: [[id|optional text]] as well as WikiLink style embeds: ![[id]] + * * @type {RegExp} */ wikiLinkRegExp = /(? part.trim()); @@ -38,4 +48,15 @@ module.exports = class WikilinkParser { parseMultiple(links) { return links.map(link => this.parseSingle(link)); } + + /** + * Finds all wikilinks within a document (HTML or otherwise). + * @param {string} document + * @return {{isEmbed: boolean, anchor: (string|null), name: string, link: string, title: (string|null), slug: string}} + */ + find(document) { + return this.parseMultiple( + (document.match(this.wikiLinkRegExp) || []) + ) + } } From 4a987cd826bb8390200fd6f6a91aae89ea8c0db1 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 30 Apr 2024 18:40:33 +0100 Subject: [PATCH 023/156] chore: update test to be aware of new fixture structure... ... this tests both the wikilink parsing and the html link parsing. --- tests/eleventy.test.js | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index f423a79..71fe55a 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -1,22 +1,28 @@ const test = require("ava"); const Eleventy = require("@11ty/eleventy"); -const{normalize} = require('./helpers'); +const {normalize} = require('./helpers'); -test("Sample page (wikilink from markdown to liquid)", async t => { +function findResultByUrl(results, url) { + const [result] = results.filter(result => result.url === url); + return result; +} + +test("Sample page (wikilinks and regular links)", async t => { let elev = new Eleventy(`${__dirname}/fixtures/sample-small-website/`, `${__dirname}/fixtures/sample-small-website/_site`, { - configPath: `${__dirname}/fixtures/sample-small-website/eleventy.config.js` + configPath: `${__dirname}/fixtures/sample-small-website/eleventy.config.js`, }); let results = await elev.toJSON(); t.is(2, results.length); - const [result] = results.filter(result => result.url === '/about/'); + t.is( + `

This is to show that we can link between Markdown files and liquid files.

`, + normalize(findResultByUrl(results, '/about/').content) + ); t.is( - '

This is to show that we can link between Markdown files and liquid files.

', - normalize(result.content) + '
This is to show that we can link back via regular internal links.
', + normalize(findResultByUrl(results, '/hello/').content) ); }); - - From bad366010a79238ccc00c6854bc29aa0670c8454 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 30 Apr 2024 21:17:06 +0100 Subject: [PATCH 024/156] chore: fix paths on windows --- tests/eleventy.test.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index 71fe55a..7c342fb 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -1,6 +1,7 @@ const test = require("ava"); const Eleventy = require("@11ty/eleventy"); const {normalize} = require('./helpers'); +const path = require("path"); function findResultByUrl(results, url) { const [result] = results.filter(result => result.url === url); @@ -8,8 +9,8 @@ function findResultByUrl(results, url) { } test("Sample page (wikilinks and regular links)", async t => { - let elev = new Eleventy(`${__dirname}/fixtures/sample-small-website/`, `${__dirname}/fixtures/sample-small-website/_site`, { - configPath: `${__dirname}/fixtures/sample-small-website/eleventy.config.js`, + let elev = new Eleventy(path.join(__dirname, '/fixtures/sample-small-website'), path.join(__dirname, '/fixtures/sample-small-website/_site'), { + configPath: path.join(__dirname, '/fixtures/sample-small-website/eleventy.config.js'), }); let results = await elev.toJSON(); From ef1eb0761de5c6241a67bdfb60efea9d2950d19f Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 1 May 2024 08:44:24 +0100 Subject: [PATCH 025/156] chore: normalise fixture paths for windows --- tests/eleventy.test.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index 7c342fb..6f60aba 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -1,16 +1,18 @@ const test = require("ava"); const Eleventy = require("@11ty/eleventy"); const {normalize} = require('./helpers'); -const path = require("path"); +const path = require('node:path'); function findResultByUrl(results, url) { const [result] = results.filter(result => result.url === url); return result; } +const fixturePath = (p) => path.normalize(path.join(__dirname, 'fixtures', p)); + test("Sample page (wikilinks and regular links)", async t => { - let elev = new Eleventy(path.join(__dirname, '/fixtures/sample-small-website'), path.join(__dirname, '/fixtures/sample-small-website/_site'), { - configPath: path.join(__dirname, '/fixtures/sample-small-website/eleventy.config.js'), + let elev = new Eleventy(fixturePath('sample-small-website'), fixturePath('sample-small-website/_site'), { + configPath: fixturePath('sample-small-website/eleventy.config.js'), }); let results = await elev.toJSON(); From 26da3f369f2c29110420d094fce5b7a8c29b6424 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 1 May 2024 08:49:50 +0100 Subject: [PATCH 026/156] chore: add debug output for Github Actions --- tests/eleventy.test.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index 6f60aba..d614195 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -11,6 +11,10 @@ function findResultByUrl(results, url) { const fixturePath = (p) => path.normalize(path.join(__dirname, 'fixtures', p)); test("Sample page (wikilinks and regular links)", async t => { + + console.log('===FIXTURE PATH', fixturePath('sample-small-website')); + console.log('===DIRNAME', __dirname); + let elev = new Eleventy(fixturePath('sample-small-website'), fixturePath('sample-small-website/_site'), { configPath: fixturePath('sample-small-website/eleventy.config.js'), }); From a70b7585b4de0c3abfc5db17a11a471b3877af00 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 1 May 2024 08:54:57 +0100 Subject: [PATCH 027/156] chore: swap arguments for ava.is. First argument is expected, second is actual. --- tests/eleventy.test.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index d614195..a569bd3 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -11,25 +11,21 @@ function findResultByUrl(results, url) { const fixturePath = (p) => path.normalize(path.join(__dirname, 'fixtures', p)); test("Sample page (wikilinks and regular links)", async t => { - - console.log('===FIXTURE PATH', fixturePath('sample-small-website')); - console.log('===DIRNAME', __dirname); - let elev = new Eleventy(fixturePath('sample-small-website'), fixturePath('sample-small-website/_site'), { configPath: fixturePath('sample-small-website/eleventy.config.js'), }); let results = await elev.toJSON(); - t.is(2, results.length); + t.is(results.length, 2); t.is( - `

This is to show that we can link between Markdown files and liquid files.

`, - normalize(findResultByUrl(results, '/about/').content) + normalize(findResultByUrl(results, '/about/').content), + `

This is to show that we can link between Markdown files and liquid files.

` ); t.is( + normalize(findResultByUrl(results, '/hello/').content), '
This is to show that we can link back via regular internal links.
', - normalize(findResultByUrl(results, '/hello/').content) ); }); From 0c2b32fca691ca43755c33f6e8fe069fad478dbe Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 1 May 2024 18:41:00 +0100 Subject: [PATCH 028/156] chore: export Interlinker class directly --- index.js | 2 +- src/interlinker.js | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index ad7ea4c..1720f91 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,5 @@ const {wikilinkInlineRule, wikilinkRenderRule} = require('./src/markdown-ext'); -const {Interlinker} = require("./src/interlinker"); +const Interlinker = require("./src/interlinker"); /** * Some code borrowed from: diff --git a/src/interlinker.js b/src/interlinker.js index ebdbbb1..48a1643 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -3,7 +3,11 @@ const WikilinkParser = require("./wikilink-parser"); const HTMLLinkParser = require("./html-link-parser"); const chalk = require("chalk"); -class Interlinker { +/** + * Interlinker: + * + */ +module.exports = class Interlinker { constructor(opts) { this.opts = opts @@ -172,7 +176,3 @@ class Interlinker { ) } } - -module.exports = { - Interlinker -} From 3860d62975586e2d7683bdd53f14fb77af15f858 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 1 May 2024 18:41:26 +0100 Subject: [PATCH 029/156] chore: update types to include LinkMeta and PageDirectoryService --- index.d.ts | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/index.d.ts b/index.d.ts index d704f8d..46281d3 100644 --- a/index.d.ts +++ b/index.d.ts @@ -30,12 +30,30 @@ interface SlugifyFn { (input: string): string; } +// Data structure for internal links identified by HTMLLinkParser. +// This is a subset of WikilinkMeta. +type LinkMeta = { + href: string + isEmbed: false +} + +// Data structure for wikilinks identified by WikiLinkParser. type WikilinkMeta = { - title: string|null, - name: string, - link: string, - slug: string, + title: string|null + name: string + anchor: string|null + link: string + slug: string isEmbed: boolean + + // href and path are loaded from the linked page + href?: string + path?: string +} + +interface PageDirectoryService { + findByLink(link : WikilinkMeta|LinkMeta): any; + findByFile(file : any): any; } -export {EleventyPluginInterlinkOptions, SlugifyFn, WikilinkMeta}; +export {EleventyPluginInterlinkOptions, SlugifyFn, WikilinkMeta, LinkMeta, PageDirectoryService}; From a0974744e29d50bdf0dd7a3ead31822982e4b153 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 1 May 2024 18:42:45 +0100 Subject: [PATCH 030/156] doc: document type usage --- src/html-link-parser.js | 10 +++++++--- src/interlinker.js | 2 +- src/wikilink-parser.js | 6 ++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/html-link-parser.js b/src/html-link-parser.js index 40e20f9..91b9a96 100644 --- a/src/html-link-parser.js +++ b/src/html-link-parser.js @@ -12,7 +12,7 @@ module.exports = class HTMLLinkParser { * care about the href so that we can look up the linked page record. * * @param {string} link - * @return {{isEmbed: boolean, href: string}} + * @return {import('@photogabble/eleventy-plugin-interlinker').LinkMeta} */ parseSingle(link) { return { @@ -25,14 +25,18 @@ module.exports = class HTMLLinkParser { } } + /** + * @param {Array} links + * @return {Array} + */ parseMultiple(links) { return links.map(link => this.parseSingle(link)); } /** - * Find's all internal href links within an HTML document. + * Find's all internal href links within an HTML document and returns the parsed result. * @param {string} document - * @return {Array<{isEmbed: false, href: string}>} + * @return {Array} */ find(document) { return this.parseMultiple( diff --git a/src/interlinker.js b/src/interlinker.js index 48a1643..1739dec 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -34,7 +34,7 @@ module.exports = class Interlinker { /** * Compiles the template associated with a WikiLink when invoked via the ![[embed syntax]] * @param data - * @return {Promise<*>} + * @return {Promise} */ async compileTemplate(data) { if (this.compiledEmbeds.has(data.url)) return; diff --git a/src/wikilink-parser.js b/src/wikilink-parser.js index eec8050..222b788 100644 --- a/src/wikilink-parser.js +++ b/src/wikilink-parser.js @@ -20,7 +20,8 @@ module.exports = class WikilinkParser { * @todo add support for referencing file by path (#13) * * @param {string} link - * @return {{isEmbed: boolean, anchor: (string|null), name: string, link: string, title: (string|null), slug: string}} + * @param {import('@photogabble/eleventy-plugin-interlinker').PageDirectoryService} pageDirectory + * @return {import('@photogabble/eleventy-plugin-interlinker').WikilinkMeta} */ parseSingle(link) { const isEmbed = link.startsWith('!'); @@ -52,7 +53,8 @@ module.exports = class WikilinkParser { /** * Finds all wikilinks within a document (HTML or otherwise). * @param {string} document - * @return {{isEmbed: boolean, anchor: (string|null), name: string, link: string, title: (string|null), slug: string}} + * @param {import('@photogabble/eleventy-plugin-interlinker').PageDirectoryService} pageDirectory + * @return {Array} */ find(document) { return this.parseMultiple( From 60f02e4c5bbd33dfd9f65201c4946b0dcb62c583 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 1 May 2024 18:44:55 +0100 Subject: [PATCH 031/156] refactor: page lookup into service --- src/find-page.js | 34 ++++++++++++++++++++++++++++++++++ src/interlinker.js | 25 +++++++------------------ 2 files changed, 41 insertions(+), 18 deletions(-) create mode 100644 src/find-page.js diff --git a/src/find-page.js b/src/find-page.js new file mode 100644 index 0000000..eb1f8bf --- /dev/null +++ b/src/find-page.js @@ -0,0 +1,34 @@ +/** + * Page Lookup Service: + * This wraps the 11ty all pages collection providing two methods for finding pages. + * + * @param {Array} allPages + * @param {import('@photogabble/eleventy-plugin-interlinker').SlugifyFn} slugifyFn + * @return {import('@photogabble/eleventy-plugin-interlinker').PageDirectoryService} + */ +const pageLookup = (allPages = [], slugifyFn) => { + return { + findByLink: (link) => allPages.find((page) => { + if (link.href && (page.url === link.href || page.url === `${link.href}/`)) { + return true; + } + + if (page.fileSlug === link.slug || (page.data.title && slugifyFn(page.data.title) === link.slug)) { + return true; + } + + const aliases = ((page.data.aliases && Array.isArray(page.data.aliases)) ? page.data.aliases : []).reduce(function (set, alias) { + set.add(slugifyFn(alias)); + return set; + }, new Set()); + + return aliases.has(link.slug); + }), + + findByFile: (file) => allPages.find((page) => page.url === file.page.url), + } +} + +module.exports = { + pageLookup +} diff --git a/src/interlinker.js b/src/interlinker.js index 1739dec..084c924 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -2,6 +2,7 @@ const {EleventyRenderPlugin} = require("@11ty/eleventy"); const WikilinkParser = require("./wikilink-parser"); const HTMLLinkParser = require("./html-link-parser"); const chalk = require("chalk"); +const {pageLookup} = require("./find-page"); /** * Interlinker: @@ -85,10 +86,13 @@ module.exports = class Interlinker { const {slugifyFn} = this.opts; const compilePromises = []; - const allPages = data.collections.all; + const pageDirectory = pageLookup( + data.collections.all, + slugifyFn + ); const currentSlug = slugifyFn(data.title); let currentSlugs = new Set([currentSlug, data.page.fileSlug]); - const currentPage = allPages.find(page => page.url === data.page.url); + const currentPage = pageDirectory.findByFile(data); // Populate our link map for use later in replacing WikiLinks with page permalinks. // Pages can list aliases in their front matter, if those exist we should map them @@ -123,22 +127,7 @@ module.exports = class Interlinker { ...this.HTMLLinkParser.find(pageContent), ].map((link) => { // Lookup the page this link, links to and add this page to its backlinks - const page = allPages.find((page) => { - if (link.href && (page.url === link.href || page.url === `${link.href}/`)) { - return true; - } - - if (page.fileSlug === link.slug || (page.data.title && slugifyFn(page.data.title) === link.slug)) { - return true; - } - - const aliases = ((page.data.aliases && Array.isArray(page.data.aliases)) ? page.data.aliases : []).reduce(function (set, alias) { - set.add(slugifyFn(alias)); - return set; - }, new Set()); - - return aliases.has(link.slug); - }); + const page = pageDirectory.findByLink(link); if (!page.data.backlinks) { page.data.backlinks = []; From 87a339d88dc0e07dfef24d80ccd067535f7b3aa2 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 1 May 2024 18:45:57 +0100 Subject: [PATCH 032/156] refactor: wikilink parser to use page lookup service for href and title --- src/interlinker.js | 4 +-- src/wikilink-parser.js | 63 +++++++++++++++++++++++++++++++++++------- 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/src/interlinker.js b/src/interlinker.js index 084c924..6d940ea 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -28,7 +28,7 @@ module.exports = class Interlinker { // TODO: document this.rm = new EleventyRenderPlugin.RenderManager(); - this.wikiLinkParser = new WikilinkParser(opts); + this.wikiLinkParser = new WikilinkParser(opts, this.deadWikiLinks); this.HTMLLinkParser = new HTMLLinkParser(); } @@ -123,7 +123,7 @@ module.exports = class Interlinker { if (currentPage.template.frontMatter?.content) { const pageContent = currentPage.template.frontMatter.content; const outboundLinks = [ - ...this.wikiLinkParser.find(pageContent), + ...this.wikiLinkParser.find(pageContent, pageDirectory), ...this.HTMLLinkParser.find(pageContent), ].map((link) => { // Lookup the page this link, links to and add this page to its backlinks diff --git a/src/wikilink-parser.js b/src/wikilink-parser.js index 222b788..96e99e9 100644 --- a/src/wikilink-parser.js +++ b/src/wikilink-parser.js @@ -7,10 +7,15 @@ module.exports = class WikilinkParser { wikiLinkRegExp = /(?} deadWikiLinks * @param { import('@photogabble/eleventy-plugin-interlinker').EleventyPluginInterlinkOptions } opts */ - constructor(opts) { + constructor(opts, deadWikiLinks) { this.slugifyFn = opts.slugifyFn; + this.deadWikiLinks = deadWikiLinks; + + // TODO: when 11ty is in serve mode, this cache should clear at the beginning of each build (add issue) + this.linkCache = new Map(); } /** @@ -23,11 +28,14 @@ module.exports = class WikilinkParser { * @param {import('@photogabble/eleventy-plugin-interlinker').PageDirectoryService} pageDirectory * @return {import('@photogabble/eleventy-plugin-interlinker').WikilinkMeta} */ - parseSingle(link) { + parseSingle(link, pageDirectory) { + if (this.linkCache.has(link)) { + return this.linkCache.get(link); + } + const isEmbed = link.startsWith('!'); const parts = link.slice((isEmbed ? 3 : 2), -2).split("|").map(part => part.trim()); - const slug = this.slugifyFn(parts[0].replace(/.(md|markdown)\s?$/i, "").trim()); - let name = parts[0]; + let name = parts[0].replace(/.(md|markdown)\s?$/i, ""); let anchor = null; if (name.includes('#')) { @@ -36,7 +44,14 @@ module.exports = class WikilinkParser { anchor = nameParts[1]; } - return { + const slug = this.slugifyFn(name); + + if (name.startsWith('/')) { + // TODO: if name begins with / then this is lookup by pathname (#13) + } + + /** @var {import('@photogabble/eleventy-plugin-interlinker').WikilinkMeta} */ + const meta = { title: parts.length === 2 ? parts[1] : null, name, anchor, @@ -44,21 +59,49 @@ module.exports = class WikilinkParser { slug, isEmbed } + + // Lookup page data from 11ty's collection to obtain url and title if currently null + const page = pageDirectory.findByLink(meta); + if (page) { + if (meta.title === null && page.data.title) meta.title = page.data.title; + meta.href = page.url; + meta.path = page.inputPath; + } else { + // If this wikilink goes to a page that doesn't exist, add to deadWikiLinks list and + // update href for stub post. + this.deadWikiLinks.add(link); + // @todo make the stub post url configurable, or even able to be disabled. (add issue) + meta.href = '/stubs'; + } + + // Cache discovered meta to link, this cache can then be used by the Markdown render rule + // to display the link. + this.linkCache.set(link, meta); + + return meta; } - parseMultiple(links) { - return links.map(link => this.parseSingle(link)); + /** + * @param {Array} links + * @param {import('@photogabble/eleventy-plugin-interlinker').PageDirectoryService} pageDirectory + * @return {Array} + */ + parseMultiple(links, pageDirectory) { + return links.map(link => this.parseSingle(link, pageDirectory)); } /** - * Finds all wikilinks within a document (HTML or otherwise). + * Finds all wikilinks within a document (HTML or otherwise) and returns their + * parsed result. + * * @param {string} document * @param {import('@photogabble/eleventy-plugin-interlinker').PageDirectoryService} pageDirectory * @return {Array} */ - find(document) { + find(document, pageDirectory) { return this.parseMultiple( - (document.match(this.wikiLinkRegExp) || []) + (document.match(this.wikiLinkRegExp) || []), + pageDirectory ) } } From d599e8e33b5e0f519692b7f8782d8667772c5108 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 1 May 2024 18:47:38 +0100 Subject: [PATCH 033/156] doc: add todo items for 1.1.0 --- index.js | 2 ++ src/interlinker.js | 1 + 2 files changed, 3 insertions(+) diff --git a/index.js b/index.js index 1720f91..6b4b3a5 100644 --- a/index.js +++ b/index.js @@ -65,4 +65,6 @@ module.exports = function (eleventyConfig, options = {}) { eleventyConfig.addGlobalData('eleventyComputed', { outboundLinks: async (data) => await interlinker.compute(data) }); + + // TODO: 1.1.0 Make Interlinker class available via global data }; diff --git a/src/interlinker.js b/src/interlinker.js index 6d940ea..a69db16 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -139,6 +139,7 @@ module.exports = class Interlinker { }); // If this is an embed and the embed template hasn't been compiled, add this to the queue + // @TODO compiledEmbeds should be keyed by the wikilink text as i'll be allowing setting embed values via namespace, or other method e.g ![[ident||template]] if (link.isEmbed && this.compiledEmbeds.has(link.slug) === false) { compilePromises.push(this.compileTemplate(link)); } From c312533d19fd693afb0382d9b4dba21d70f0da22 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 1 May 2024 18:48:53 +0100 Subject: [PATCH 034/156] refactor: update markdown parse and render to use wikilink cache for link lookup --- src/markdown-ext.js | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/markdown-ext.js b/src/markdown-ext.js index 82bda89..a72c7fa 100644 --- a/src/markdown-ext.js +++ b/src/markdown-ext.js @@ -32,9 +32,13 @@ const wikilinkInlineRule = (wikilinkParser) => (state, silent) => { if (!found) return false; - const token = state.push('inline_wikilink', '', 0); - const wikiLink = wikilinkParser.parseSingle(text); + const wikiLink = wikilinkParser.linkCache.get(text); + + // By this time in the execution cycle the wikilink parser's cache should contain all + // wikilinks. In the unlikely case that it doesn't we ignore the wikilink. + if (!wikiLink) return false; + const token = state.push('inline_wikilink', '', 0); token.content = wikiLink.slug; token.meta = wikiLink; @@ -43,7 +47,6 @@ const wikilinkInlineRule = (wikilinkParser) => (state, silent) => { }; /** - * * @param {WikilinkParser} wikilinkParser * @param { Map } linkMapCache * @param { Map } compiledEmbeds @@ -53,10 +56,12 @@ const wikilinkInlineRule = (wikilinkParser) => (state, silent) => { */ const wikilinkRenderRule = (wikilinkParser, linkMapCache, compiledEmbeds, deadWikiLinks, opts) => (tokens, idx) => { const token = tokens[idx]; - const link = linkMapCache.get(token.content); - const wikilink = token.meta; + const link = token.meta; + + // TODO: remove dependency upon linkMapCache and deadWikiLinks - if (token.meta.isEmbed) { + if (link.isEmbed) { + // TODO: move embed not found error handling to wikilinkInlineRule (refactor) if (!link) { console.error(chalk.blue('[@photogabble/wikilinks]'), chalk.red('ERROR'), `WikiLink Embed found pointing to non-existent [${token.content}], doesn't exist.`); return (typeof opts.unableToLocateEmbedFn === 'function') @@ -64,24 +69,19 @@ const wikilinkRenderRule = (wikilinkParser, linkMapCache, compiledEmbeds, deadWi : ''; } - const templateContent = compiledEmbeds.get(link.page.url); + // TODO: compiledEmbeds should be keyed by wikilink text because with namespacing (#14) and custom resolving functions (#19) we can have different rendered output based upon either + const templateContent = compiledEmbeds.get(link.href); if (!templateContent) throw new Error(`WikiLink Embed found pointing to [${token.content}], has no compiled template.`); - - return compiledEmbeds.get(link.page.url); + return templateContent; } const anchor = { - href: '#', - text: '', + href: link.href, + text: link.title ?? link.name, }; - if (!link) { - deadWikiLinks.add(token.content); - anchor.text = wikilink.title ?? wikilink.name; - anchor.href = '/stubs'; - } else { - anchor.text = wikilink.title ?? link.title; - anchor.href = link.page.url; + if (link.anchor) { + anchor.href = `${anchor.href}#${link.anchor}`; } return `${anchor.text}`; From 85c046eb752e71682477f4ae606ef0e80a342386 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 1 May 2024 18:49:07 +0100 Subject: [PATCH 035/156] refactor: update tests to be aware of changed functionality --- tests/markdown-wikilink-parser.test.js | 93 +++++++++++++++++++----- tests/markdown-wikilink-renderer.test.js | 68 ++++++++--------- tests/wikilink-parser.test.js | 54 ++++++++++---- 3 files changed, 149 insertions(+), 66 deletions(-) diff --git a/tests/markdown-wikilink-parser.test.js b/tests/markdown-wikilink-parser.test.js index 20014df..9e1833d 100644 --- a/tests/markdown-wikilink-parser.test.js +++ b/tests/markdown-wikilink-parser.test.js @@ -3,9 +3,16 @@ const WikilinkParser = require('../src/wikilink-parser'); const slugify = require('slugify'); const test = require('ava'); -const wikilinkParser = new WikilinkParser({slugifyFn: (text) => slugify(text)}); +const opts = {slugifyFn: (text) => slugify(text)}; test('inline rule correctly parses single wikilink', t => { + const wikilinkParser = new WikilinkParser(opts, new Set); + wikilinkParser.linkCache.set('[[wiki link]]', { + title: 'Wiki link', + href: '/test/', + isEmbed: false, + }); + const md = require('markdown-it')({html: true}); md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( wikilinkParser @@ -14,12 +21,26 @@ test('inline rule correctly parses single wikilink', t => { const parsed = md.parseInline('Hello world, this is some text with a [[wiki link]] inside!', {}); // Check there is only one inline_wikilink_embed token in parsed result - t.is(1, parsed.length); - t.is(3, parsed[0].children.length); - t.is(1, parsed[0].children.filter(child => child.type === 'inline_wikilink').length); + t.is(parsed.length, 1); + t.is(parsed[0].children.length, 3); + t.is(parsed[0].children.filter(child => child.type === 'inline_wikilink').length, 1); }); test('inline rule correctly parses multiple wikilink', t => { + const wikilinkParser = new WikilinkParser(opts, new Set); + wikilinkParser.linkCache.set('[[wiki links]]', { + title: 'Wiki link', + slug: 'wiki-links', + href: '/test/', + isEmbed: false, + }); + wikilinkParser.linkCache.set('[[here]]', { + title: 'here', + slug: 'here', + href: '/here/', + isEmbed: false, + }); + const md = require('markdown-it')({html: true}); md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( wikilinkParser @@ -28,14 +49,22 @@ test('inline rule correctly parses multiple wikilink', t => { const parsed = md.parseInline('Hello world, this is some text with two [[wiki links]] inside! The second one is [[here]].', {}); // Check there is only one inline_wikilink_embed token in parsed result - t.is(1, parsed.length); - t.is(5, parsed[0].children.length); - t.is(2, parsed[0].children.filter(child => child.type === 'inline_wikilink').length); - t.is('wiki-links', parsed[0].children[1].content); - t.is('here', parsed[0].children[3].content); + t.is(parsed.length, 1); + t.is(parsed[0].children.length, 5); + t.is(parsed[0].children.filter(child => child.type === 'inline_wikilink').length, 2); + t.is(parsed[0].children[1].meta.slug, 'wiki-links'); + t.is(parsed[0].children[3].meta.slug, 'here'); }); test('inline rule correctly parses single wikilink embed', t => { + const wikilinkParser = new WikilinkParser(opts, new Set); + wikilinkParser.linkCache.set('![[wiki link embed]]', { + title: 'wiki link embed', + slug: 'wiki-link-embed', + href: '/test/', + isEmbed: true, + }); + const md = require('markdown-it')({html: true}); md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( wikilinkParser @@ -44,12 +73,26 @@ test('inline rule correctly parses single wikilink embed', t => { const parsed = md.parseInline('Hello world, this is some text with a ![[wiki link embed]] inside!', {}); // Check there is only one inline_wikilink_embed token in parsed result - t.is(1, parsed.length); - t.is(3, parsed[0].children.length); - t.is(1, parsed[0].children.filter(child => child.type === 'inline_wikilink').length); + t.is(parsed.length, 1); + t.is(parsed[0].children.length, 3); + t.is(parsed[0].children.filter(child => child.type === 'inline_wikilink').length, 1); }); test('inline rule correctly parses multiple wikilink embeds', t => { + const wikilinkParser = new WikilinkParser(opts, new Set); + wikilinkParser.linkCache.set('![[wiki link embeds]]', { + title: 'wiki link embed', + slug: 'wiki-link-embed', + href: '/test/', + isEmbed: true, + }); + wikilinkParser.linkCache.set('![[here]]', { + title: 'here embed', + slug: 'here', + href: '/test/', + isEmbed: true, + }); + const md = require('markdown-it')({html: true}); md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( wikilinkParser @@ -58,12 +101,26 @@ test('inline rule correctly parses multiple wikilink embeds', t => { const parsed = md.parseInline('Hello world, this is some text with two ![[wiki link embeds]] inside! The second one is ![[here]].', {}); // Check there is only one inline_wikilink_embed token in parsed result - t.is(1, parsed.length); - t.is(5, parsed[0].children.length); - t.is(2, parsed[0].children.filter(child => child.type === 'inline_wikilink').length); + t.is(parsed.length, 1); + t.is(parsed[0].children.length, 5); + t.is(parsed[0].children.filter(child => child.type === 'inline_wikilink').length, 2); }); test('inline rule correctly parses mixed wikilink and wikilink embeds', t => { + const wikilinkParser = new WikilinkParser(opts, new Set); + wikilinkParser.linkCache.set('![[wiki link embeds]]', { + title: 'wiki link embeds', + slug: 'wiki-link-embeds', + href: '/test/', + isEmbed: true, + }); + wikilinkParser.linkCache.set('[[here]]', { + title: 'here', + slug: 'here', + href: '/test/', + isEmbed: false, + }); + const md = require('markdown-it')({html: true}); md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( wikilinkParser @@ -72,7 +129,7 @@ test('inline rule correctly parses mixed wikilink and wikilink embeds', t => { const parsed = md.parseInline('Hello world, this is some text with mixed ![[wiki link embeds]] inside! The wiki link is [[here]].', {}); // Check there is only one inline_wikilink_embed token in parsed result - t.is(1, parsed.length); - t.is(5, parsed[0].children.length); - t.is(2, parsed[0].children.filter(child => child.type === 'inline_wikilink').length); + t.is(parsed.length, 1); + t.is(parsed[0].children.length, 5); + t.is(parsed[0].children.filter(child => child.type === 'inline_wikilink').length, 2); }); diff --git a/tests/markdown-wikilink-renderer.test.js b/tests/markdown-wikilink-renderer.test.js index 8e2b283..8ebce6e 100644 --- a/tests/markdown-wikilink-renderer.test.js +++ b/tests/markdown-wikilink-renderer.test.js @@ -6,18 +6,18 @@ const test = require('ava'); const fs = require('fs'); const opts = {slugifyFn: (text) => slugify(text)}; -const wikilinkParser = new WikilinkParser(opts); test('inline rule correctly parses single wikilink', t => { + const wikilinkParser = new WikilinkParser(opts, new Set); + const linkMapCache = new Map; const compiledEmbeds = new Map; const deadWikiLinks = new Set; - linkMapCache.set('wiki-link', { + wikilinkParser.linkCache.set('[[wiki link]]', { title: 'Wiki Link', - page: { - url: '/wiki-link/' - } + href: '/wiki-link/', + isEmbed: false, }); const md = require('markdown-it')({html: true}); @@ -40,22 +40,22 @@ test('inline rule correctly parses single wikilink', t => { }); test('inline rule correctly parses multiple wikilinks', t => { + const wikilinkParser = new WikilinkParser(opts, new Set); + const linkMapCache = new Map; const compiledEmbeds = new Map; const deadWikiLinks = new Set; - linkMapCache.set('wiki-link', { + wikilinkParser.linkCache.set('[[wiki link]]', { title: 'Wiki Link', - page: { - url: '/wiki-link/' - } + href: '/wiki-link/', + isEmbed: false, }); - linkMapCache.set('another-wiki-link', { + wikilinkParser.linkCache.set('[[another wiki link]]', { title: 'Another Wiki Link', - page: { - url: '/another-wiki-link/' - } + href: '/another-wiki-link/', + isEmbed: false, }); const md = require('markdown-it')({html: true}); @@ -78,16 +78,16 @@ test('inline rule correctly parses multiple wikilinks', t => { }); test('inline rule correctly parses single embed', t => { + const wikilinkParser = new WikilinkParser(opts, new Set); + const linkMapCache = new Map; const compiledEmbeds = new Map; const deadWikiLinks = new Set; - linkMapCache.set('wiki-embed', { + wikilinkParser.linkCache.set('![[wiki-embed]]', { title: 'Wiki Embed', - page: { - inputPath: '/src/wiki-embed.md', - url: '/wiki-embed/' - } + href: '/wiki-embed/', + isEmbed: true, }); compiledEmbeds.set('/wiki-embed/', 'Wiki Embed Test'); @@ -112,32 +112,34 @@ test('inline rule correctly parses single embed', t => { }); test('inline rule correctly parses mixed wikilink and embed in multiline input', t => { + const wikilinkParser = new WikilinkParser(opts, new Set); + const linkMapCache = new Map; const compiledEmbeds = new Map; const deadWikiLinks = new Set; - linkMapCache.set('inline-embed', { + wikilinkParser.linkCache.set('![[inline embed]]', { title: 'Inline Embed', - page: { - inputPath: '/src/inline-embed.md', - url: '/inline-embed/' - } + href: '/inline-embed/', + isEmbed: true, }); - linkMapCache.set('this-is-an-embed-on-its-own', { + wikilinkParser.linkCache.set('![[this is an embed on its own]]', { title: 'This is an embed on its own', - page: { - inputPath: '/src/lonely-embed.md', - url: '/lonely-embed/' - } + href: '/lonely-embed/', + isEmbed: true, }); - linkMapCache.set('wiki-link', { + wikilinkParser.linkCache.set('[[wiki link]]', { title: 'Wiki Link', - page: { - inputPath: '/src/wiki-link.md', - url: '/wiki-link/' - } + href: '/wiki-link/', + isEmbed: false, + }); + + wikilinkParser.linkCache.set('[[wiki link|Wikilinks]]', { + title: 'Wikilinks', + href: '/wiki-link/', + isEmbed: false, }); compiledEmbeds.set('/inline-embed/', 'inline embed'); diff --git a/tests/wikilink-parser.test.js b/tests/wikilink-parser.test.js index 8b766a4..bc8491d 100644 --- a/tests/wikilink-parser.test.js +++ b/tests/wikilink-parser.test.js @@ -1,10 +1,21 @@ const WikilinkParser = require('../src/wikilink-parser'); const test = require('ava'); +const {pageLookup} = require("../src/find-page"); +const slugify = require("slugify"); + +const pageDirectory = pageLookup([ + { + fileSlug: 'hello-world', + data: { + title: 'Hello World, Title', + }, + } +], slugify); test('parses wikilink', t => { - const parser = new WikilinkParser({slugifyFn: () => '...'}); - t.like(parser.parseSingle('[[hello world]]'), { - title: null, + const parser = new WikilinkParser({slugifyFn: slugify}, new Set()); + t.like(parser.parseSingle('[[hello world]]', pageDirectory), { + title: 'Hello World, Title', anchor: null, name: 'hello world', isEmbed: false @@ -12,8 +23,8 @@ test('parses wikilink', t => { }); test('parses wikilink with title', t => { - const parser = new WikilinkParser({slugifyFn: () => '...'}); - t.like(parser.parseSingle('[[hello world|Howdy]]'), { + const parser = new WikilinkParser({slugifyFn: slugify}, new Set()); + t.like(parser.parseSingle('[[hello world|Howdy]]', pageDirectory), { title: 'Howdy', anchor: null, name: 'hello world', @@ -22,9 +33,9 @@ test('parses wikilink with title', t => { }); test('parses wikilink with anchor', t => { - const parser = new WikilinkParser({slugifyFn: () => '...'}); - t.like(parser.parseSingle('[[hello world#heading one]]'), { - title: null, + const parser = new WikilinkParser({slugifyFn: slugify}, new Set()); + t.like(parser.parseSingle('[[hello world#heading one]]', pageDirectory), { + title: 'Hello World, Title', anchor: 'heading one', name: 'hello world', isEmbed: false @@ -32,9 +43,9 @@ test('parses wikilink with anchor', t => { }); test('parses wikilink embed', t => { - const parser = new WikilinkParser({slugifyFn: () => '...'}); - t.like(parser.parseSingle('![[hello world]]'), { - title: null, + const parser = new WikilinkParser({slugifyFn: slugify}, new Set()); + t.like(parser.parseSingle('![[hello world]]', pageDirectory), { + title: 'Hello World, Title', anchor: null, name: 'hello world', isEmbed: true @@ -42,13 +53,13 @@ test('parses wikilink embed', t => { }); test('parses wikilinks with weird formatting', t => { - const parser = new WikilinkParser({slugifyFn: () => '...'}); + const parser = new WikilinkParser({slugifyFn: slugify}, new Set()); const checks = [ { str: '[[hello world]]', result: { - title: null, + title: 'Hello World, Title', name: 'hello world', isEmbed: false } @@ -80,7 +91,7 @@ test('parses wikilinks with weird formatting', t => { { str: '![[hello world]]', result: { - title: null, + title: 'Hello World, Title', name: 'hello world', isEmbed: true } @@ -88,7 +99,20 @@ test('parses wikilinks with weird formatting', t => { ]; for (const check of checks) { - const result = parser.parseSingle(check.str); + const result = parser.parseSingle(check.str, pageDirectory); t.like(result, check.result); } }); + +test('populates dead links set', t => { + const deadLinks = new Set(); + const parser = new WikilinkParser({slugifyFn: slugify}, deadLinks); + t.is(deadLinks.size, 0); + + parser.parseSingle('[[hello world]]', pageDirectory); + t.is(deadLinks.size, 0); + + const invalid = parser.parseSingle('[[invalid]]', pageDirectory); + t.is(deadLinks.size, 1); + t.is(invalid.href, '/stubs'); +}) From f8e1c7c4f2b5eb651fe3b39e8e717d01e7a0d224 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 1 May 2024 18:50:01 +0100 Subject: [PATCH 036/156] chore: npm update --- package-lock.json | 8 -------- 1 file changed, 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 694ac81..26aaa55 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,14 +20,6 @@ }, "engines": { "node": ">=16" - }, - "peerDependencies": { - "@11ty/eleventy": ">=2.0.0-beta.1" - }, - "peerDependenciesMeta": { - "@11ty/eleventy": { - "optional": true - } } }, "node_modules/@11ty/dependency-tree": { From 5df55e8569bcb512c23495c1e4f426fb5bd693f6 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 1 May 2024 18:52:57 +0100 Subject: [PATCH 037/156] refactor: remove linkMapCache and deadWikiLinks arguments from wikilinkRenderRule --- index.js | 2 -- src/markdown-ext.js | 6 +----- tests/markdown-wikilink-renderer.test.js | 20 -------------------- 3 files changed, 1 insertion(+), 27 deletions(-) diff --git a/index.js b/index.js index 6b4b3a5..ee19967 100644 --- a/index.js +++ b/index.js @@ -53,9 +53,7 @@ module.exports = function (eleventyConfig, options = {}) { md.renderer.rules.inline_wikilink = wikilinkRenderRule( interlinker.wikiLinkParser, - interlinker.linkMapCache, interlinker.compiledEmbeds, - interlinker.deadWikiLinks, opts ); }); diff --git a/src/markdown-ext.js b/src/markdown-ext.js index a72c7fa..6490280 100644 --- a/src/markdown-ext.js +++ b/src/markdown-ext.js @@ -48,18 +48,14 @@ const wikilinkInlineRule = (wikilinkParser) => (state, silent) => { /** * @param {WikilinkParser} wikilinkParser - * @param { Map } linkMapCache * @param { Map } compiledEmbeds - * @param { Set } deadWikiLinks * @param { import('@photogabble/eleventy-plugin-interlinker').EleventyPluginInterlinkOptions } opts * @returns {(function(*, *): (string))|*} */ -const wikilinkRenderRule = (wikilinkParser, linkMapCache, compiledEmbeds, deadWikiLinks, opts) => (tokens, idx) => { +const wikilinkRenderRule = (wikilinkParser, compiledEmbeds, opts) => (tokens, idx) => { const token = tokens[idx]; const link = token.meta; - // TODO: remove dependency upon linkMapCache and deadWikiLinks - if (link.isEmbed) { // TODO: move embed not found error handling to wikilinkInlineRule (refactor) if (!link) { diff --git a/tests/markdown-wikilink-renderer.test.js b/tests/markdown-wikilink-renderer.test.js index 8ebce6e..c0e08fb 100644 --- a/tests/markdown-wikilink-renderer.test.js +++ b/tests/markdown-wikilink-renderer.test.js @@ -9,10 +9,7 @@ const opts = {slugifyFn: (text) => slugify(text)}; test('inline rule correctly parses single wikilink', t => { const wikilinkParser = new WikilinkParser(opts, new Set); - - const linkMapCache = new Map; const compiledEmbeds = new Map; - const deadWikiLinks = new Set; wikilinkParser.linkCache.set('[[wiki link]]', { title: 'Wiki Link', @@ -27,9 +24,7 @@ test('inline rule correctly parses single wikilink', t => { md.renderer.rules.inline_wikilink = wikilinkRenderRule( wikilinkParser, - linkMapCache, compiledEmbeds, - deadWikiLinks, opts ); @@ -41,10 +36,7 @@ test('inline rule correctly parses single wikilink', t => { test('inline rule correctly parses multiple wikilinks', t => { const wikilinkParser = new WikilinkParser(opts, new Set); - - const linkMapCache = new Map; const compiledEmbeds = new Map; - const deadWikiLinks = new Set; wikilinkParser.linkCache.set('[[wiki link]]', { title: 'Wiki Link', @@ -65,9 +57,7 @@ test('inline rule correctly parses multiple wikilinks', t => { md.renderer.rules.inline_wikilink = wikilinkRenderRule( wikilinkParser, - linkMapCache, compiledEmbeds, - deadWikiLinks, opts ); @@ -79,10 +69,7 @@ test('inline rule correctly parses multiple wikilinks', t => { test('inline rule correctly parses single embed', t => { const wikilinkParser = new WikilinkParser(opts, new Set); - - const linkMapCache = new Map; const compiledEmbeds = new Map; - const deadWikiLinks = new Set; wikilinkParser.linkCache.set('![[wiki-embed]]', { title: 'Wiki Embed', @@ -99,9 +86,7 @@ test('inline rule correctly parses single embed', t => { md.renderer.rules.inline_wikilink = wikilinkRenderRule( wikilinkParser, - linkMapCache, compiledEmbeds, - deadWikiLinks, opts ); @@ -113,10 +98,7 @@ test('inline rule correctly parses single embed', t => { test('inline rule correctly parses mixed wikilink and embed in multiline input', t => { const wikilinkParser = new WikilinkParser(opts, new Set); - - const linkMapCache = new Map; const compiledEmbeds = new Map; - const deadWikiLinks = new Set; wikilinkParser.linkCache.set('![[inline embed]]', { title: 'Inline Embed', @@ -152,9 +134,7 @@ test('inline rule correctly parses mixed wikilink and embed in multiline input', md.renderer.rules.inline_wikilink = wikilinkRenderRule( wikilinkParser, - linkMapCache, compiledEmbeds, - deadWikiLinks, opts ); From b09e11ad3ad99c6d2bbcdc40ec39c53abeb77f48 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 1 May 2024 19:47:24 +0100 Subject: [PATCH 038/156] doc: add reference to oleeskild/digitalgarden --- index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/index.js b/index.js index ee19967..383576a 100644 --- a/index.js +++ b/index.js @@ -4,6 +4,7 @@ const Interlinker = require("./src/interlinker"); /** * Some code borrowed from: * @see https://git.sr.ht/~boehs/site/tree/master/item/html/pages/garden/garden.11tydata.js + * @see https://github.com/oleeskild/digitalgarden/blob/main/src/helpers/linkUtils.js * * @param { import('@11ty/eleventy/src/UserConfig') } eleventyConfig * @param { import('@photogabble/eleventy-plugin-interlinker').EleventyPluginInterlinkOptions } options From b762c59c8206fda7ae6c8e54e1220a51947863b8 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 1 May 2024 19:48:08 +0100 Subject: [PATCH 039/156] chore: links href is already set --- src/interlinker.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/interlinker.js b/src/interlinker.js index a69db16..7fcbfce 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -144,10 +144,7 @@ module.exports = class Interlinker { compilePromises.push(this.compileTemplate(link)); } - return { - ...link, - href: page.url, - } + return link; }); // Block iteration until compilation complete. From ac4e45ab2fc0e5d38737ec0b27243fb44f3805e0 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 1 May 2024 19:49:38 +0100 Subject: [PATCH 040/156] refactor: use unableToLocateEmbedFn when embed content not found --- src/markdown-ext.js | 9 ++---- src/wikilink-parser.js | 1 + tests/markdown-wikilink-renderer.test.js | 35 +++++++++++++++++++++++- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/markdown-ext.js b/src/markdown-ext.js index 6490280..dafe1fa 100644 --- a/src/markdown-ext.js +++ b/src/markdown-ext.js @@ -57,17 +57,14 @@ const wikilinkRenderRule = (wikilinkParser, compiledEmbeds, opts) => (tokens, id const link = token.meta; if (link.isEmbed) { - // TODO: move embed not found error handling to wikilinkInlineRule (refactor) - if (!link) { - console.error(chalk.blue('[@photogabble/wikilinks]'), chalk.red('ERROR'), `WikiLink Embed found pointing to non-existent [${token.content}], doesn't exist.`); + // TODO: compiledEmbeds should be keyed by wikilink text because with namespacing (#14) and custom resolving functions (#19) we can have different rendered output based upon either + const templateContent = compiledEmbeds.get(link.href); + if (!templateContent) { return (typeof opts.unableToLocateEmbedFn === 'function') ? opts.unableToLocateEmbedFn(token.content) : ''; } - // TODO: compiledEmbeds should be keyed by wikilink text because with namespacing (#14) and custom resolving functions (#19) we can have different rendered output based upon either - const templateContent = compiledEmbeds.get(link.href); - if (!templateContent) throw new Error(`WikiLink Embed found pointing to [${token.content}], has no compiled template.`); return templateContent; } diff --git a/src/wikilink-parser.js b/src/wikilink-parser.js index 96e99e9..1191903 100644 --- a/src/wikilink-parser.js +++ b/src/wikilink-parser.js @@ -11,6 +11,7 @@ module.exports = class WikilinkParser { * @param { import('@photogabble/eleventy-plugin-interlinker').EleventyPluginInterlinkOptions } opts */ constructor(opts, deadWikiLinks) { + this.opts = opts; this.slugifyFn = opts.slugifyFn; this.deadWikiLinks = deadWikiLinks; diff --git a/tests/markdown-wikilink-renderer.test.js b/tests/markdown-wikilink-renderer.test.js index c0e08fb..09463a9 100644 --- a/tests/markdown-wikilink-renderer.test.js +++ b/tests/markdown-wikilink-renderer.test.js @@ -5,7 +5,9 @@ const slugify = require('slugify'); const test = require('ava'); const fs = require('fs'); -const opts = {slugifyFn: (text) => slugify(text)}; +const opts = { + slugifyFn: (text) => slugify(text), +}; test('inline rule correctly parses single wikilink', t => { const wikilinkParser = new WikilinkParser(opts, new Set); @@ -146,3 +148,34 @@ test('inline rule correctly parses mixed wikilink and embed in multiline input', normalize(html) ); }); + +test('inline rule correctly displays unable to load embed content', t => { + const wikilinkParser = new WikilinkParser(opts, new Set); + const compiledEmbeds = new Map; + + wikilinkParser.linkCache.set('![[wiki-embed]]', { + title: 'Wiki Embed', + href: '/wiki-embed/', + link: '![[wiki-embed]]', + isEmbed: true, + }); + + const md = require('markdown-it')({html: true}); + md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( + wikilinkParser + )); + + md.renderer.rules.inline_wikilink = wikilinkRenderRule( + wikilinkParser, + compiledEmbeds, + { + ...opts, + unableToLocateEmbedFn: () => '[TESTING]' + } + ); + + t.is( + md.render('Hello world this is a ![[wiki-embed]]'), + "

Hello world this is a [TESTING]

\n" + ); +}) From 7d541b59f6ef11df1ba03d0b0a0013eb347ea750 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 1 May 2024 19:59:41 +0100 Subject: [PATCH 041/156] chore: get 100% test coverage on find-page.js --- tests/find-page-service.test.js | 56 +++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 tests/find-page-service.test.js diff --git a/tests/find-page-service.test.js b/tests/find-page-service.test.js new file mode 100644 index 0000000..9a8aac2 --- /dev/null +++ b/tests/find-page-service.test.js @@ -0,0 +1,56 @@ +const test = require("ava"); +const {pageLookup} = require("../src/find-page"); +const slugify = require("slugify"); + +const pageDirectory = pageLookup([ + { + fileSlug: 'hello-world', + data: { + title: 'Hello World, Title', + aliases: null + }, + url: '/hello-world/' + }, + { + fileSlug: 'hello-world-1', + data: { + title: 'Hello World, One Title', + aliases: [], + }, + url: '/hello-world/1/' + }, + { + fileSlug: 'something-else', + data: { + title: 'This is another page', + aliases: ['test-alias'] + }, + url: '/something/else/' + } +], slugify); + +test('pageLookup (find by href)', t => { + t.is(pageDirectory.findByLink({href: '/something/else', isEmbed: false}).fileSlug, 'something-else'); +}); + +test('pageLookup (find by wikilink)', t => { + t.is(pageDirectory.findByLink({ + title: 'Hello World, Title', + name: 'hello-world', + anchor: null, + link: '[[hello-world]]', + slug: 'hello-world', + isEmbed: false, + }).fileSlug, 'hello-world'); +}); + +test('pageLookup (find by alias)', t => { + t.is(pageDirectory.findByLink({ + title: 'This is another page', + name: 'test-alias', + anchor: null, + link: '[[test-alias]]', + slug: 'test-alias', + isEmbed: false, + }).fileSlug, 'something-else'); +}); From 716b5f53691eab748cd2b3cdbb4db8264c726061 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 1 May 2024 20:06:52 +0100 Subject: [PATCH 042/156] chore: add test that inline rule correctly displays anchor links --- tests/markdown-wikilink-renderer.test.js | 28 ++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/markdown-wikilink-renderer.test.js b/tests/markdown-wikilink-renderer.test.js index 09463a9..118b780 100644 --- a/tests/markdown-wikilink-renderer.test.js +++ b/tests/markdown-wikilink-renderer.test.js @@ -178,4 +178,32 @@ test('inline rule correctly displays unable to load embed content', t => { md.render('Hello world this is a ![[wiki-embed]]'), "

Hello world this is a [TESTING]

\n" ); +}); + +test('inline rule correctly displays anchor links', t => { + const wikilinkParser = new WikilinkParser(opts, new Set); + const compiledEmbeds = new Map; + + wikilinkParser.linkCache.set('[[wiki link#heading-id]]', { + title: 'Wiki Link', + href: '/wiki-link/', + anchor: 'heading-id', + isEmbed: false, + }); + + const md = require('markdown-it')({html: true}); + md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( + wikilinkParser + )); + + md.renderer.rules.inline_wikilink = wikilinkRenderRule( + wikilinkParser, + compiledEmbeds, + opts + ); + + t.is( + "

Hello world, this is some text with a Wiki Link inside!

\n", + md.render('Hello world, this is some text with a [[wiki link#heading-id]] inside!', {}) + ); }) From 7d20e2c2c54e3d615a5aa28a8b8b1995a3a32d0d Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 1 May 2024 20:20:29 +0100 Subject: [PATCH 043/156] doc: reference created issues for todo items --- index.js | 3 ++- src/wikilink-parser.js | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 383576a..7a475cc 100644 --- a/index.js +++ b/index.js @@ -42,7 +42,8 @@ module.exports = function (eleventyConfig, options = {}) { // After 11ty has finished generating the site output a list of wikilinks that do not link to // anything. - // TODO: 1.1.0 have this contain more details such as which file(s) are linking + // TODO: 1.1.0 have this contain more details such as which file(s) are linking (#23) + // TODO: 1.1.0 have this clear the interlinker cache so that next time 11ty builds its starting from fresh data! (#24) eleventyConfig.on('eleventy.after', () => interlinker.deadLinksReport()); // Teach Markdown-It how to display MediaWiki Links. diff --git a/src/wikilink-parser.js b/src/wikilink-parser.js index 1191903..e758a1a 100644 --- a/src/wikilink-parser.js +++ b/src/wikilink-parser.js @@ -15,7 +15,7 @@ module.exports = class WikilinkParser { this.slugifyFn = opts.slugifyFn; this.deadWikiLinks = deadWikiLinks; - // TODO: when 11ty is in serve mode, this cache should clear at the beginning of each build (add issue) + // TODO: when 11ty is in serve mode, this cache should clear at the beginning of each build (#24) this.linkCache = new Map(); } @@ -71,7 +71,7 @@ module.exports = class WikilinkParser { // If this wikilink goes to a page that doesn't exist, add to deadWikiLinks list and // update href for stub post. this.deadWikiLinks.add(link); - // @todo make the stub post url configurable, or even able to be disabled. (add issue) + // @todo make the stub post url configurable, or even able to be disabled. (#25) meta.href = '/stubs'; } From 7874dee996d643f59bccccacb4486059ee22cee2 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 1 May 2024 22:01:11 +0100 Subject: [PATCH 044/156] chore: add sinon dev-dependency for spying on console log --- package-lock.json | 203 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 204 insertions(+) diff --git a/package-lock.json b/package-lock.json index 26aaa55..c6ffc74 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@11ty/eleventy": "^2.0.0", "ava": "^5.2.0", "c8": "^7.12.0", + "sinon": "^17.0.1", "slugify": "^1.6.6" }, "engines": { @@ -308,6 +309,50 @@ "node": ">=8" } }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", + "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", + "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "dev": true + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -1209,6 +1254,15 @@ "node": ">= 0.8.0" } }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -2258,6 +2312,12 @@ "node": ">=0.10.0" } }, + "node_modules/just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", + "dev": true + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -2349,6 +2409,12 @@ "integrity": "sha512-m/M1U1f3ddMCs6Hq2tAsYThTBDaAKFDX3dwDo97GEYzamXi9SqUpjWi/Rrj/gf3X2n8ktwgZrlP1z6E3v/IExQ==", "dev": true }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -2674,6 +2740,19 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node_modules/nise": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", + "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" + } + }, "node_modules/nofilter": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", @@ -3528,6 +3607,24 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "node_modules/sinon": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", + "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.5", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, "node_modules/slash": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", @@ -3760,6 +3857,15 @@ "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==", "dev": true }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/type-fest": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", @@ -4325,6 +4431,52 @@ } } }, + "@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", + "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^3.0.0" + } + }, + "@sinonjs/samsam": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", + "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", + "dev": true, + "requires": { + "@sinonjs/commons": "^2.0.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + }, + "dependencies": { + "@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + } + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "dev": true + }, "@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -4995,6 +5147,12 @@ "integrity": "sha512-LmVkry/oDShEgSZPNgqCIp2/TlqtExeGmymru3uCELnfyjY11IzpAproLYs+1X88fXO6DBoYP3ul2Xo2yz2j6A==", "dev": true }, + "diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true + }, "dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -5760,6 +5918,12 @@ "integrity": "sha512-3KF80UaaSSxo8jVnRYtMKNGFOoVPBdkkVPsw+Ad0y4oxKXPduS6G6iHkrf69yJVff/VAaYXkV42rtZ7daJxU3w==", "dev": true }, + "just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", + "dev": true + }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -5822,6 +5986,12 @@ "integrity": "sha512-m/M1U1f3ddMCs6Hq2tAsYThTBDaAKFDX3dwDo97GEYzamXi9SqUpjWi/Rrj/gf3X2n8ktwgZrlP1z6E3v/IExQ==", "dev": true }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -6062,6 +6232,19 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "nise": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", + "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", + "dev": true, + "requires": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" + } + }, "nofilter": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", @@ -6681,6 +6864,20 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "sinon": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", + "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==", + "dev": true, + "requires": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.5", + "supports-color": "^7.2.0" + } + }, "slash": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", @@ -6843,6 +7040,12 @@ "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==", "dev": true }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, "type-fest": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", diff --git a/package.json b/package.json index c35e834..b3b8a13 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "@11ty/eleventy": "^2.0.0", "ava": "^5.2.0", "c8": "^7.12.0", + "sinon": "^17.0.1", "slugify": "^1.6.6" }, "directories": { From ae44b1a4121059a24dc3e6ae53906f174758a749 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 1 May 2024 23:08:09 +0100 Subject: [PATCH 045/156] feat(#23): add detailed bad link report --- index.js | 2 +- src/dead-links.js | 41 +++++++++++++++++++ src/html-link-parser.js | 29 ++++++++++--- src/interlinker.js | 28 ++++++------- src/wikilink-parser.js | 14 ++++--- tests/eleventy.test.js | 40 +++++++++++++++++- .../_layouts/default.liquid | 1 + .../website-with-broken-links/about.md | 6 +++ .../eleventy.config.js | 12 ++++++ .../website-with-broken-links/hello.liquid | 6 +++ .../website-with-broken-links/something.md | 6 +++ tests/html-internal-link-parser.test.js | 9 +++- 12 files changed, 164 insertions(+), 30 deletions(-) create mode 100644 src/dead-links.js create mode 100644 tests/fixtures/website-with-broken-links/_layouts/default.liquid create mode 100644 tests/fixtures/website-with-broken-links/about.md create mode 100644 tests/fixtures/website-with-broken-links/eleventy.config.js create mode 100644 tests/fixtures/website-with-broken-links/hello.liquid create mode 100644 tests/fixtures/website-with-broken-links/something.md diff --git a/index.js b/index.js index 7a475cc..aba39fb 100644 --- a/index.js +++ b/index.js @@ -44,7 +44,7 @@ module.exports = function (eleventyConfig, options = {}) { // anything. // TODO: 1.1.0 have this contain more details such as which file(s) are linking (#23) // TODO: 1.1.0 have this clear the interlinker cache so that next time 11ty builds its starting from fresh data! (#24) - eleventyConfig.on('eleventy.after', () => interlinker.deadLinksReport()); + eleventyConfig.on('eleventy.after', () => interlinker.deadLinks.report()); // Teach Markdown-It how to display MediaWiki Links. eleventyConfig.amendLibrary('md', (md) => { diff --git a/src/dead-links.js b/src/dead-links.js new file mode 100644 index 0000000..f332440 --- /dev/null +++ b/src/dead-links.js @@ -0,0 +1,41 @@ +const chalk = require("chalk"); +module.exports = class DeadLinks { + + constructor() { + this.gravestones = new Map; + this.fileSrc = 'unknown'; + } + + setFileSrc(fileSrc) { + this.fileSrc = fileSrc; + } + + /** + * @param {string} link + */ + add(link) { + if (!this.fileSrc) this.fileSrc = 'unknown'; + + const names = this.gravestones.has(link) + ? this.gravestones.get(this.fileSrc) + : []; + + names.push(this.fileSrc) + + this.gravestones.set(link, names); + } + + report() { + for (const [link, files] of this.gravestones.entries()) { + console.warn( + chalk.blue('[@photogabble/wikilinks]'), + chalk.yellow('WARNING'), + `${(link.includes('href') ? 'Link' : 'Wikilink')} (${link}) found pointing to to non-existent page in:` + ); + + for (const file of files) { + console.warn(`\t- ${file}`); + } + } + } +} diff --git a/src/html-link-parser.js b/src/html-link-parser.js index 91b9a96..9c992d8 100644 --- a/src/html-link-parser.js +++ b/src/html-link-parser.js @@ -7,40 +7,57 @@ module.exports = class HTMLLinkParser { */ internalLinkRegex = /href="\/(.*?)"/g; + /** + * @param { DeadLinks } deadLinks + */ + constructor(deadLinks) { + this.deadLinks = deadLinks; + } + /** * Parses a single HTML link into the link object understood by the Interlinker. Unlike with Wikilinks we only * care about the href so that we can look up the linked page record. * * @param {string} link + * @param {import('@photogabble/eleventy-plugin-interlinker').PageDirectoryService} pageDirectory * @return {import('@photogabble/eleventy-plugin-interlinker').LinkMeta} */ - parseSingle(link) { - return { + parseSingle(link, pageDirectory) { + const meta = { href: link.slice(6, -1) .replace(/.(md|markdown)\s?$/i, "") .replace("\\", "") .trim() .split("#")[0], isEmbed: false, + }; + + if (!pageDirectory.findByLink(meta)) { + this.deadLinks.add(link); } + + return meta; } /** * @param {Array} links + * @param {import('@photogabble/eleventy-plugin-interlinker').PageDirectoryService} pageDirectory * @return {Array} */ - parseMultiple(links) { - return links.map(link => this.parseSingle(link)); + parseMultiple(links, pageDirectory) { + return links.map(link => this.parseSingle(link, pageDirectory)); } /** * Find's all internal href links within an HTML document and returns the parsed result. * @param {string} document + * @param {import('@photogabble/eleventy-plugin-interlinker').PageDirectoryService} pageDirectory * @return {Array} */ - find(document) { + find(document, pageDirectory) { return this.parseMultiple( - (document.match(this.internalLinkRegex) || []) + (document.match(this.internalLinkRegex) || []), + pageDirectory ) } } diff --git a/src/interlinker.js b/src/interlinker.js index 7fcbfce..19ff4b6 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -1,8 +1,9 @@ -const {EleventyRenderPlugin} = require("@11ty/eleventy"); -const WikilinkParser = require("./wikilink-parser"); const HTMLLinkParser = require("./html-link-parser"); -const chalk = require("chalk"); +const WikilinkParser = require("./wikilink-parser"); +const {EleventyRenderPlugin} = require("@11ty/eleventy"); +const DeadLinks = require("./dead-links"); const {pageLookup} = require("./find-page"); +const chalk = require("chalk"); /** * Interlinker: @@ -13,7 +14,7 @@ module.exports = class Interlinker { this.opts = opts // Set of WikiLinks pointing to non-existent pages - this.deadWikiLinks = new Set(); + this.deadLinks = new DeadLinks(); // Map of what WikiLinks to what this.linkMapCache = new Map(); @@ -28,14 +29,16 @@ module.exports = class Interlinker { // TODO: document this.rm = new EleventyRenderPlugin.RenderManager(); - this.wikiLinkParser = new WikilinkParser(opts, this.deadWikiLinks); - this.HTMLLinkParser = new HTMLLinkParser(); + this.wikiLinkParser = new WikilinkParser(opts, this.deadLinks); + + // TODO: pass through deadWikiLinks and have HTMLLinkParser look up pages + this.HTMLLinkParser = new HTMLLinkParser(this.deadLinks); } /** * Compiles the template associated with a WikiLink when invoked via the ![[embed syntax]] * @param data - * @return {Promise} + * @return {Promise} */ async compileTemplate(data) { if (this.compiledEmbeds.has(data.url)) return; @@ -94,6 +97,8 @@ module.exports = class Interlinker { let currentSlugs = new Set([currentSlug, data.page.fileSlug]); const currentPage = pageDirectory.findByFile(data); + this.deadLinks.setFileSrc(currentPage.inputPath); + // Populate our link map for use later in replacing WikiLinks with page permalinks. // Pages can list aliases in their front matter, if those exist we should map them // as well. @@ -124,10 +129,11 @@ module.exports = class Interlinker { const pageContent = currentPage.template.frontMatter.content; const outboundLinks = [ ...this.wikiLinkParser.find(pageContent, pageDirectory), - ...this.HTMLLinkParser.find(pageContent), + ...this.HTMLLinkParser.find(pageContent, pageDirectory), ].map((link) => { // Lookup the page this link, links to and add this page to its backlinks const page = pageDirectory.findByLink(link); + if (!page) return link; if (!page.data.backlinks) { page.data.backlinks = []; @@ -156,10 +162,4 @@ module.exports = class Interlinker { return []; } - - deadLinksReport() { - this.deadWikiLinks.forEach( - slug => console.warn(chalk.blue('[@photogabble/wikilinks]'), chalk.yellow('WARNING'), `WikiLink found pointing to non-existent [${slug}], has been set to default stub.`) - ) - } } diff --git a/src/wikilink-parser.js b/src/wikilink-parser.js index e758a1a..f13cce7 100644 --- a/src/wikilink-parser.js +++ b/src/wikilink-parser.js @@ -7,18 +7,22 @@ module.exports = class WikilinkParser { wikiLinkRegExp = /(?} deadWikiLinks * @param { import('@photogabble/eleventy-plugin-interlinker').EleventyPluginInterlinkOptions } opts + * @param { DeadLinks } deadLinks */ - constructor(opts, deadWikiLinks) { + constructor(opts, deadLinks) { this.opts = opts; this.slugifyFn = opts.slugifyFn; - this.deadWikiLinks = deadWikiLinks; + this.deadLinks = deadLinks; // TODO: when 11ty is in serve mode, this cache should clear at the beginning of each build (#24) this.linkCache = new Map(); } + setPage(page) { + this.page = page; + } + /** * Parses a single WikiLink into the link object understood by the Interlinker. * @@ -68,9 +72,9 @@ module.exports = class WikilinkParser { meta.href = page.url; meta.path = page.inputPath; } else { - // If this wikilink goes to a page that doesn't exist, add to deadWikiLinks list and + // If this wikilink goes to a page that doesn't exist, add to deadLinks list and // update href for stub post. - this.deadWikiLinks.add(link); + this.deadLinks.add(link); // @todo make the stub post url configurable, or even able to be disabled. (#25) meta.href = '/stubs'; } diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index a569bd3..6da9b9a 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -1,7 +1,8 @@ -const test = require("ava"); const Eleventy = require("@11ty/eleventy"); -const {normalize} = require('./helpers'); const path = require('node:path'); +const {normalize} = require('./helpers'); +const sinon = require('sinon'); +const test = require("ava"); function findResultByUrl(results, url) { const [result] = results.filter(result => result.url === url); @@ -29,3 +30,38 @@ test("Sample page (wikilinks and regular links)", async t => { '
This is to show that we can link back via regular internal links.
', ); }); + +test("Broken page (wikilinks and regular links)", async t => { + const messages = []; + const consoleStub = sinon.stub(console, 'warn').callsFake(msg => messages.push()); + + let elev = new Eleventy(fixturePath('website-with-broken-links'), fixturePath('website-with-broken-links/_site'), { + configPath: fixturePath('website-with-broken-links/eleventy.config.js'), + }); + + let results = await elev.toJSON(); + + let logLines = []; + for (let i = 0; i < consoleStub.callCount; i++) { + const line = normalize(consoleStub.getCall(i).args.join(' ')) + // Sometimes 11ty will output benchmark info, failing the test randomly. + if (line.includes('[11ty]')) continue; + logLines.push(line); + } + + consoleStub.restore(); + + // Markdown will have the link href set to /stubs + t.is( + normalize(findResultByUrl(results, '/something/').content), + `

This page has a broken link.

` + ); + + // HTML + t.is( + normalize(findResultByUrl(results, '/hello/').content), + `
This is to show that we can identify broken internal links.
` + ); + + t.is(logLines.length, 4, 'console.warn should be called three times'); +}); diff --git a/tests/fixtures/website-with-broken-links/_layouts/default.liquid b/tests/fixtures/website-with-broken-links/_layouts/default.liquid new file mode 100644 index 0000000..c9bcd4c --- /dev/null +++ b/tests/fixtures/website-with-broken-links/_layouts/default.liquid @@ -0,0 +1 @@ +
{{ content }}
diff --git a/tests/fixtures/website-with-broken-links/about.md b/tests/fixtures/website-with-broken-links/about.md new file mode 100644 index 0000000..17a26a6 --- /dev/null +++ b/tests/fixtures/website-with-broken-links/about.md @@ -0,0 +1,6 @@ +--- +title: About +layout: default.liquid +--- + +This is to show that we can link between Markdown files and [[hello|liquid files]]. diff --git a/tests/fixtures/website-with-broken-links/eleventy.config.js b/tests/fixtures/website-with-broken-links/eleventy.config.js new file mode 100644 index 0000000..9b10b5c --- /dev/null +++ b/tests/fixtures/website-with-broken-links/eleventy.config.js @@ -0,0 +1,12 @@ +module.exports = function (eleventyConfig) { + eleventyConfig.addPlugin( + require('../../../index.js'), + ); + + return { + dir: { + includes: "_includes", + layouts: "_layouts", + } + } +} diff --git a/tests/fixtures/website-with-broken-links/hello.liquid b/tests/fixtures/website-with-broken-links/hello.liquid new file mode 100644 index 0000000..439c776 --- /dev/null +++ b/tests/fixtures/website-with-broken-links/hello.liquid @@ -0,0 +1,6 @@ +--- +title: Hello +layout: default.liquid +--- + +This is to show that we can identify broken internal links. diff --git a/tests/fixtures/website-with-broken-links/something.md b/tests/fixtures/website-with-broken-links/something.md new file mode 100644 index 0000000..e0a1dd2 --- /dev/null +++ b/tests/fixtures/website-with-broken-links/something.md @@ -0,0 +1,6 @@ +--- +title: Something +layout: default.liquid +--- + +This page has a [[broken link]]. diff --git a/tests/html-internal-link-parser.test.js b/tests/html-internal-link-parser.test.js index 3fc9d2f..d8f0901 100644 --- a/tests/html-internal-link-parser.test.js +++ b/tests/html-internal-link-parser.test.js @@ -1,9 +1,14 @@ const HTMLLinkParser = require('../src/html-link-parser'); const test = require('ava'); +const DeadLinks = require("../src/dead-links"); +const {pageLookup} = require("../src/find-page"); +const slugify = require("slugify"); + +const pageDirectory = pageLookup([], slugify); test('html link parser grabs multiple href, ignoring external links', t => { - const parser = new HTMLLinkParser(); - const links = parser.find('

Hello world this is a link home and this is a link somewhere

The following link should be ignored example.com.

'); + const parser = new HTMLLinkParser(new DeadLinks()); + const links = parser.find('

Hello world this is a link home and this is a link somewhere

The following link should be ignored example.com.

', pageDirectory); t.is(2, links.length); From aaee9fca37b3abdf6a3e48846eb00a02cc1cd871 Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 2 May 2024 08:05:32 +0100 Subject: [PATCH 046/156] feat: v1.1.0-rc1 next release --- CHANGELOG.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6714c8e..1884397 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Add inclusion of html internal links to backlink computation +- Add detailed bad link report (#26) + ## [1.0.6] - Bugfix ensuring aliases value is array when treated as one (#17) diff --git a/package.json b/package.json index b3b8a13..6e0a382 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@photogabble/eleventy-plugin-interlinker", - "version": "1.0.6", + "version": "1.1.0-rc1", "description": "Obsidian WikiLinks, BackLinks and Embed support for 11ty", "keywords": [ "11ty", From 00749b77cdaad0779287c8abdea4cfade62d76f2 Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 2 May 2024 08:41:11 +0100 Subject: [PATCH 047/156] chore: add reference to PR --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1884397..f30a9da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- Add inclusion of html internal links to backlink computation +- Add inclusion of html internal links to backlink computation (#22) - Add detailed bad link report (#26) ## [1.0.6] From 0d6a7e24f8e6ea757063b961a59ec439537bfb19 Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 2 May 2024 08:52:58 +0100 Subject: [PATCH 048/156] chore: add link to next release branch --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f30a9da..adf1653 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Bugfix pass 11ty page object to embed compiler function - Add inclusion of html internal links to backlink computation (#22) - Add detailed bad link report (#26) @@ -49,3 +50,4 @@ First release [1.0.4]: https://github.com/photogabble/eleventy-plugin-font-subsetting/releases/tag/v1.0.4 [1.0.5]: https://github.com/photogabble/eleventy-plugin-font-subsetting/releases/tag/v1.0.5 [1.0.5]: https://github.com/photogabble/eleventy-plugin-font-subsetting/releases/tag/v1.0.6 +[Unreleased]: https://github.com/photogabble/eleventy-plugin-interlinker/tree/v1.1.0 From f2eaaecaff99e5b02de0f764966ab5e2c2a36d9b Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 2 May 2024 09:00:12 +0100 Subject: [PATCH 049/156] chore: add failing test for #27 --- tests/eleventy.test.js | 20 +++++++++++++++++++ .../_layouts/default.liquid | 2 ++ .../sample-with-simple-embed/about.md | 6 ++++++ .../eleventy.config.js | 12 +++++++++++ .../sample-with-simple-embed/index.md | 6 ++++++ 5 files changed, 46 insertions(+) create mode 100644 tests/fixtures/sample-with-simple-embed/_layouts/default.liquid create mode 100644 tests/fixtures/sample-with-simple-embed/about.md create mode 100644 tests/fixtures/sample-with-simple-embed/eleventy.config.js create mode 100644 tests/fixtures/sample-with-simple-embed/index.md diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index 6da9b9a..0d39dbc 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -65,3 +65,23 @@ test("Broken page (wikilinks and regular links)", async t => { t.is(logLines.length, 4, 'console.warn should be called three times'); }); + +test("Sample page (with embed)", async t => { + let elev = new Eleventy(fixturePath('sample-with-simple-embed'), fixturePath('sample-with-simple-embed/_site'), { + configPath: fixturePath('sample-with-simple-embed/eleventy.config.js'), + }); + + let results = await elev.toJSON(); + + // Embedded page is aware of its embedding + t.is( + normalize(findResultByUrl(results, '/about/').content), + `

Hello world.

` + ); + + // Embed shows + t.is( + normalize(findResultByUrl(results, '/').content), + `

Hello world.

` + ); +}); diff --git a/tests/fixtures/sample-with-simple-embed/_layouts/default.liquid b/tests/fixtures/sample-with-simple-embed/_layouts/default.liquid new file mode 100644 index 0000000..9065374 --- /dev/null +++ b/tests/fixtures/sample-with-simple-embed/_layouts/default.liquid @@ -0,0 +1,2 @@ +
{{ content }}
+
{%- for link in backlinks %}{{ link.title }}{%- endfor %}
diff --git a/tests/fixtures/sample-with-simple-embed/about.md b/tests/fixtures/sample-with-simple-embed/about.md new file mode 100644 index 0000000..0f6b0da --- /dev/null +++ b/tests/fixtures/sample-with-simple-embed/about.md @@ -0,0 +1,6 @@ +--- +title: About +layout: default.liquid +--- + +Hello world. diff --git a/tests/fixtures/sample-with-simple-embed/eleventy.config.js b/tests/fixtures/sample-with-simple-embed/eleventy.config.js new file mode 100644 index 0000000..9b10b5c --- /dev/null +++ b/tests/fixtures/sample-with-simple-embed/eleventy.config.js @@ -0,0 +1,12 @@ +module.exports = function (eleventyConfig) { + eleventyConfig.addPlugin( + require('../../../index.js'), + ); + + return { + dir: { + includes: "_includes", + layouts: "_layouts", + } + } +} diff --git a/tests/fixtures/sample-with-simple-embed/index.md b/tests/fixtures/sample-with-simple-embed/index.md new file mode 100644 index 0000000..0abb405 --- /dev/null +++ b/tests/fixtures/sample-with-simple-embed/index.md @@ -0,0 +1,6 @@ +--- +title: Homepage +layout: default.liquid +--- + +![[about]] From ca1a630255f1d9ebe756974db59deb738fcb1537 Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 2 May 2024 19:58:45 +0100 Subject: [PATCH 050/156] refactor: change how global data is assigned --- index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index aba39fb..e22c269 100644 --- a/index.js +++ b/index.js @@ -62,8 +62,8 @@ module.exports = function (eleventyConfig, options = {}) { // Add outboundLinks computed global data, this is executed before the templates are compiled and // thus markdown parsed. - eleventyConfig.addGlobalData('eleventyComputed', { - outboundLinks: async (data) => await interlinker.compute(data) + eleventyConfig.addGlobalData('eleventyComputed.outboundLinks', () => { + return async (data) => interlinker.compute(data); }); // TODO: 1.1.0 Make Interlinker class available via global data From ec4fee2e58fb1b37f5c0135245f7b520b25f273c Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 2 May 2024 19:59:28 +0100 Subject: [PATCH 051/156] bugfix: use inputPath instead of fileSlug so we dont exclude root index --- src/interlinker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interlinker.js b/src/interlinker.js index 19ff4b6..2f9b1f9 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -84,7 +84,7 @@ module.exports = class Interlinker { // once they are met. // @see https://www.11ty.dev/docs/data-computed/#declaring-your-dependencies const dependencies = [data.title, data.page, data.collections.all]; - if (dependencies[0] === undefined || !dependencies[1].fileSlug || dependencies[2].length === 0) return []; + if (dependencies[0] === undefined || !dependencies[1].inputPath || dependencies[2].length === 0) return []; const {slugifyFn} = this.opts; From 822619cec91668622654ea384d5cc742a2bb7e26 Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 2 May 2024 19:59:45 +0100 Subject: [PATCH 052/156] chore: remove unused import --- src/interlinker.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/interlinker.js b/src/interlinker.js index 2f9b1f9..4f01b3a 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -3,7 +3,6 @@ const WikilinkParser = require("./wikilink-parser"); const {EleventyRenderPlugin} = require("@11ty/eleventy"); const DeadLinks = require("./dead-links"); const {pageLookup} = require("./find-page"); -const chalk = require("chalk"); /** * Interlinker: From 845161674b00644b5c97dbb2ff2c275976579222 Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 2 May 2024 20:00:01 +0100 Subject: [PATCH 053/156] doc: add todo for #13 --- src/interlinker.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/interlinker.js b/src/interlinker.js index 4f01b3a..fe85257 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -92,6 +92,9 @@ module.exports = class Interlinker { data.collections.all, slugifyFn ); + + // TODO: 1.1.0 remove currentSlug as part of (#13) + const currentSlug = slugifyFn(data.title); let currentSlugs = new Set([currentSlug, data.page.fileSlug]); const currentPage = pageDirectory.findByFile(data); From 7434e8af2157e905691282bff84af865b231efab Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 2 May 2024 20:00:21 +0100 Subject: [PATCH 054/156] doc: add todo for aliases --- src/find-page.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/find-page.js b/src/find-page.js index eb1f8bf..8b44548 100644 --- a/src/find-page.js +++ b/src/find-page.js @@ -17,6 +17,8 @@ const pageLookup = (allPages = [], slugifyFn) => { return true; } + // TODO: need a way to identify that wikilink is pointing to an alias, because the alias then becomes the link title + const aliases = ((page.data.aliases && Array.isArray(page.data.aliases)) ? page.data.aliases : []).reduce(function (set, alias) { set.add(slugifyFn(alias)); return set; From 87ff7f3446341c6d6ee083d2ded938c00b8e2be5 Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 2 May 2024 20:00:33 +0100 Subject: [PATCH 055/156] chore: remove unused function --- src/wikilink-parser.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/wikilink-parser.js b/src/wikilink-parser.js index f13cce7..75d857b 100644 --- a/src/wikilink-parser.js +++ b/src/wikilink-parser.js @@ -19,10 +19,6 @@ module.exports = class WikilinkParser { this.linkCache = new Map(); } - setPage(page) { - this.page = page; - } - /** * Parses a single WikiLink into the link object understood by the Interlinker. * From 43f1f67d1a026ab457fe7dbc8ecfffa883f98103 Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 2 May 2024 20:01:25 +0100 Subject: [PATCH 056/156] refactor: test to have root index to test fix in ec4fee2e --- tests/eleventy.test.js | 4 ++-- tests/fixtures/sample-with-simple-embed/index.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index 0d39dbc..80e1a55 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -66,7 +66,7 @@ test("Broken page (wikilinks and regular links)", async t => { t.is(logLines.length, 4, 'console.warn should be called three times'); }); -test("Sample page (with embed)", async t => { +test("Sample page (markdown with embed)", async t => { let elev = new Eleventy(fixturePath('sample-with-simple-embed'), fixturePath('sample-with-simple-embed/_site'), { configPath: fixturePath('sample-with-simple-embed/eleventy.config.js'), }); @@ -76,7 +76,7 @@ test("Sample page (with embed)", async t => { // Embedded page is aware of its embedding t.is( normalize(findResultByUrl(results, '/about/').content), - `

Hello world.

` + `

Hello world.

` ); // Embed shows diff --git a/tests/fixtures/sample-with-simple-embed/index.md b/tests/fixtures/sample-with-simple-embed/index.md index 0abb405..4f82d3b 100644 --- a/tests/fixtures/sample-with-simple-embed/index.md +++ b/tests/fixtures/sample-with-simple-embed/index.md @@ -1,5 +1,5 @@ --- -title: Homepage +title: Something layout: default.liquid --- From 07af0e03af1abe105b1c7862619ae1463c56c4c6 Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 2 May 2024 20:02:19 +0100 Subject: [PATCH 057/156] bugfix(#27): pass page object to compileTemplate --- src/interlinker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interlinker.js b/src/interlinker.js index fe85257..965a3c6 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -149,7 +149,7 @@ module.exports = class Interlinker { // If this is an embed and the embed template hasn't been compiled, add this to the queue // @TODO compiledEmbeds should be keyed by the wikilink text as i'll be allowing setting embed values via namespace, or other method e.g ![[ident||template]] if (link.isEmbed && this.compiledEmbeds.has(link.slug) === false) { - compilePromises.push(this.compileTemplate(link)); + compilePromises.push(this.compileTemplate(page)); } return link; From d55e8cb52ac71f591937e9b27c220be16f3f2d1f Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 2 May 2024 20:05:39 +0100 Subject: [PATCH 058/156] doc: document bugfixes --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index adf1653..d22fe9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- Bugfix pass 11ty page object to embed compiler function +- Bugfix do not exclude root index page +- Bugfix pass 11ty page object to embed compiler function (#29) - Add inclusion of html internal links to backlink computation (#22) - Add detailed bad link report (#26) From b6dd7771d95fc65bfac9234b6836377e13be42c3 Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 2 May 2024 21:53:57 +0100 Subject: [PATCH 059/156] chore: add failing test for #28 --- tests/eleventy.test.js | 57 ++++++++++++------- .../_layouts/default.liquid | 2 + .../sample-with-excluded-file/about.md | 6 ++ .../eleventy.config.js | 12 ++++ .../sample-with-excluded-file/index.md | 7 +++ tests/helpers.js | 23 ++++++++ 6 files changed, 85 insertions(+), 22 deletions(-) create mode 100644 tests/fixtures/sample-with-excluded-file/_layouts/default.liquid create mode 100644 tests/fixtures/sample-with-excluded-file/about.md create mode 100644 tests/fixtures/sample-with-excluded-file/eleventy.config.js create mode 100644 tests/fixtures/sample-with-excluded-file/index.md diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index 80e1a55..0d3bcd4 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -1,15 +1,13 @@ const Eleventy = require("@11ty/eleventy"); const path = require('node:path'); -const {normalize} = require('./helpers'); -const sinon = require('sinon'); +const {normalize, consoleMockMessages, findResultByUrl, fixturePath} = require('./helpers'); const test = require("ava"); +const sinon = require("sinon"); -function findResultByUrl(results, url) { - const [result] = results.filter(result => result.url === url); - return result; -} - -const fixturePath = (p) => path.normalize(path.join(__dirname, 'fixtures', p)); +// NOTE: Tests using sinon to mock console.warn need to be run with +// `test.serial` so that they don't run at the same time as one another to +// avoid the "Attempted to wrap warn which is already wrapped" error. +// @see https://stackoverflow.com/a/37900956/1225977 test("Sample page (wikilinks and regular links)", async t => { let elev = new Eleventy(fixturePath('sample-small-website'), fixturePath('sample-small-website/_site'), { @@ -31,25 +29,15 @@ test("Sample page (wikilinks and regular links)", async t => { ); }); -test("Broken page (wikilinks and regular links)", async t => { - const messages = []; - const consoleStub = sinon.stub(console, 'warn').callsFake(msg => messages.push()); +test.serial("Broken page (wikilinks and regular links)", async t => { + const mock = sinon.stub(console, 'warn'); let elev = new Eleventy(fixturePath('website-with-broken-links'), fixturePath('website-with-broken-links/_site'), { configPath: fixturePath('website-with-broken-links/eleventy.config.js'), }); let results = await elev.toJSON(); - - let logLines = []; - for (let i = 0; i < consoleStub.callCount; i++) { - const line = normalize(consoleStub.getCall(i).args.join(' ')) - // Sometimes 11ty will output benchmark info, failing the test randomly. - if (line.includes('[11ty]')) continue; - logLines.push(line); - } - - consoleStub.restore(); + mock.restore(); // Markdown will have the link href set to /stubs t.is( @@ -63,7 +51,7 @@ test("Broken page (wikilinks and regular links)", async t => { `
This is to show that we can identify broken internal links.
` ); - t.is(logLines.length, 4, 'console.warn should be called three times'); + t.is(consoleMockMessages(mock).length, 4, 'console.warn should be called four times'); }); test("Sample page (markdown with embed)", async t => { @@ -85,3 +73,28 @@ test("Sample page (markdown with embed)", async t => { `

Hello world.

` ); }); + +test.serial("Sample page (eleventyExcludeFromCollections set true)", async t => { + const mock = sinon.stub(console, 'warn'); + + let elev = new Eleventy(fixturePath('sample-with-excluded-file'), fixturePath('sample-with-excluded-file/_site'), { + configPath: fixturePath('sample-with-excluded-file/eleventy.config.js'), + }); + + let results = await elev.toJSON(); + + mock.restore(); + t.is(consoleMockMessages(mock).length, 2, 'console.warn should be called twice'); + + // Embedded page is aware of its embedding + t.is( + normalize(findResultByUrl(results, '/about/').content), + `

This wikilink to something will not parse because the destination has eleventyExcludeFromCollections set true.

` + ); + + // Embed shows + t.is( + normalize(findResultByUrl(results, '/').content), + `

Hello World, no links, wiki or otherwise will be parsed by the interlinker due to being excluded from collections.

` + ); +}); diff --git a/tests/fixtures/sample-with-excluded-file/_layouts/default.liquid b/tests/fixtures/sample-with-excluded-file/_layouts/default.liquid new file mode 100644 index 0000000..9065374 --- /dev/null +++ b/tests/fixtures/sample-with-excluded-file/_layouts/default.liquid @@ -0,0 +1,2 @@ +
{{ content }}
+
{%- for link in backlinks %}{{ link.title }}{%- endfor %}
diff --git a/tests/fixtures/sample-with-excluded-file/about.md b/tests/fixtures/sample-with-excluded-file/about.md new file mode 100644 index 0000000..54144ba --- /dev/null +++ b/tests/fixtures/sample-with-excluded-file/about.md @@ -0,0 +1,6 @@ +--- +title: About +layout: default.liquid +--- + +This wikilink to [[something]] will not parse because the destination has eleventyExcludeFromCollections set true. diff --git a/tests/fixtures/sample-with-excluded-file/eleventy.config.js b/tests/fixtures/sample-with-excluded-file/eleventy.config.js new file mode 100644 index 0000000..9b10b5c --- /dev/null +++ b/tests/fixtures/sample-with-excluded-file/eleventy.config.js @@ -0,0 +1,12 @@ +module.exports = function (eleventyConfig) { + eleventyConfig.addPlugin( + require('../../../index.js'), + ); + + return { + dir: { + includes: "_includes", + layouts: "_layouts", + } + } +} diff --git a/tests/fixtures/sample-with-excluded-file/index.md b/tests/fixtures/sample-with-excluded-file/index.md new file mode 100644 index 0000000..182dac6 --- /dev/null +++ b/tests/fixtures/sample-with-excluded-file/index.md @@ -0,0 +1,7 @@ +--- +title: Something +layout: default.liquid +eleventyExcludeFromCollections: true +--- + +Hello World, no links, wiki or otherwise will be parsed by the interlinker due to being excluded from collections. diff --git a/tests/helpers.js b/tests/helpers.js index ab75801..2a9f11a 100644 --- a/tests/helpers.js +++ b/tests/helpers.js @@ -1,7 +1,30 @@ +const path = require("node:path"); + function normalize(str) { return str.trim().replace(/\r?\n|\r/g, ''); } +function consoleMockMessages(mock) { + let logLines = []; + for (let i = 0; i < mock.callCount; i++) { + const line = normalize(mock.getCall(i).args.join(' ')) + // Sometimes 11ty will output benchmark info, failing the test randomly. + if (line.includes('[11ty]')) continue; + logLines.push(line); + } + return logLines; +} + +function findResultByUrl(results, url) { + const [result] = results.filter(result => result.url === url); + return result; +} + +const fixturePath = (p) => path.normalize(path.join(__dirname, 'fixtures', p)); + module.exports = { normalize, + consoleMockMessages, + findResultByUrl, + fixturePath, } From 36736b2a85f19cbe959e40154a7f8415006f594e Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 2 May 2024 21:55:18 +0100 Subject: [PATCH 060/156] bugfix(#28) dont parse page links if excluded from collections --- src/interlinker.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/interlinker.js b/src/interlinker.js index 965a3c6..fc4dc06 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -85,6 +85,8 @@ module.exports = class Interlinker { const dependencies = [data.title, data.page, data.collections.all]; if (dependencies[0] === undefined || !dependencies[1].inputPath || dependencies[2].length === 0) return []; + this.deadLinks.setFileSrc(data.page.inputPath); + const {slugifyFn} = this.opts; const compilePromises = []; @@ -98,8 +100,7 @@ module.exports = class Interlinker { const currentSlug = slugifyFn(data.title); let currentSlugs = new Set([currentSlug, data.page.fileSlug]); const currentPage = pageDirectory.findByFile(data); - - this.deadLinks.setFileSrc(currentPage.inputPath); + if (!currentPage) return []; // Populate our link map for use later in replacing WikiLinks with page permalinks. // Pages can list aliases in their front matter, if those exist we should map them From ce56d0e84069c69557577d476505ce7cf3248eca Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 2 May 2024 21:55:33 +0100 Subject: [PATCH 061/156] doc(#28) Pages Excluded from Collections --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 623d958..fff4b96 100644 --- a/README.md +++ b/README.md @@ -62,11 +62,11 @@ module.exports = (eleventyConfig) => { ### Internal Links / Wikilinks -This plugin will now parse all Wiki Links formatted for example, `[[Eleventy.js Interlink Plugin]]` appears as [Eleventy.js Interlink Plugin](https://photogabble.co.uk/projects/eleventyjs-interlink-plugin/). +This plugin will parse both Wikilinks and internal anchor links. The Wikilink format is a **page reference** wrapped in double square brackets, for example: `[[Eleventy.js Interlink Plugin]]` will appear as [Eleventy.js Interlink Plugin](https://photogabble.co.uk/projects/eleventyjs-interlink-plugin/). Using the vertical bar (`|`) you can change the text used to display a link. This can be useful when you want to work a link into a sentence without using the title of the file, for example: `[[Eleventy.js Interlink Plugin|custom display text]]` appears as [custom display text](https://www.photogabble.co.uk/projects/eleventyjs-interlink-plugin/). -> NOTE: By default this plugin will use the `title` front-matter attribute of your pages or one of the aliases (as detailed below). +> **NOTE**: By default this plugin will use the `title` front-matter attribute of your pages or one of the aliases (as detailed below) as the **page reference**. ### Aliases @@ -144,6 +144,10 @@ You can then display this information in any way you would like, I use the below {% endif %} ``` +### Pages Excluded from Collections + +Due to how this plugin obtains a pages template content, all pages with `eleventyExcludeFromCollections:true` set will **NOT** be parsed by the interlinker. + ## Known Caveats - This plugin doesn't implement all [Obsidians wikilink support](https://help.obsidian.md/Linking+notes+and+files/Internal+links) for example linking to a block in a note and linking to a heading in a note is not currently supported by this plugin From c5da43c59585d8c20c027f32b5289ec9ff646b0c Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 2 May 2024 21:59:55 +0100 Subject: [PATCH 062/156] chore: update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d22fe9d..cc6fe5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Bugfix do not parse links in pages excluded from collections (#30) - Bugfix do not exclude root index page - Bugfix pass 11ty page object to embed compiler function (#29) - Add inclusion of html internal links to backlink computation (#22) From 14af35ac78c1821e7d78a6d625e286b71f4b5778 Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 2 May 2024 22:00:38 +0100 Subject: [PATCH 063/156] feat: v1.1.0-rc2 next release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6e0a382..1b10665 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@photogabble/eleventy-plugin-interlinker", - "version": "1.1.0-rc1", + "version": "1.1.0-rc2", "description": "Obsidian WikiLinks, BackLinks and Embed support for 11ty", "keywords": [ "11ty", From de3195d389e9fca62393cbd7884111472cb53efd Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 2 May 2024 22:44:48 +0100 Subject: [PATCH 064/156] chore: added breaking test for #31 --- tests/eleventy.test.js | 23 ++++++++++++++++++- .../_layouts/default.liquid | 2 ++ ...f-contained-game-in-c-under-2-kilobytes.md | 6 +++++ .../eleventy.config.js | 12 ++++++++++ .../sample-with-hash-in-title/index.md | 6 +++++ 5 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/sample-with-hash-in-title/_layouts/default.liquid create mode 100644 tests/fixtures/sample-with-hash-in-title/building-a-self-contained-game-in-c-under-2-kilobytes.md create mode 100644 tests/fixtures/sample-with-hash-in-title/eleventy.config.js create mode 100644 tests/fixtures/sample-with-hash-in-title/index.md diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index 0d3bcd4..001b826 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -1,5 +1,4 @@ const Eleventy = require("@11ty/eleventy"); -const path = require('node:path'); const {normalize, consoleMockMessages, findResultByUrl, fixturePath} = require('./helpers'); const test = require("ava"); const sinon = require("sinon"); @@ -98,3 +97,25 @@ test.serial("Sample page (eleventyExcludeFromCollections set true)", async t => `

Hello World, no links, wiki or otherwise will be parsed by the interlinker due to being excluded from collections.

` ); }); + +test("Sample page (file with hash in title)", async t => { + + let elev = new Eleventy(fixturePath('sample-with-hash-in-title'), fixturePath('sample-with-hash-in-title/_site'), { + configPath: fixturePath('sample-with-hash-in-title/eleventy.config.js'), + }); + + let results = await elev.toJSON(); + + + // Embedded page is aware of its embedding + t.is( + normalize(findResultByUrl(results, '/building-a-self-contained-game-in-c-under-2-kilobytes/').content), + `

Hello world.

` + ); + + // Embed shows + t.is( + normalize(findResultByUrl(results, '/').content), + `

Hello world.

` + ); +}); diff --git a/tests/fixtures/sample-with-hash-in-title/_layouts/default.liquid b/tests/fixtures/sample-with-hash-in-title/_layouts/default.liquid new file mode 100644 index 0000000..9065374 --- /dev/null +++ b/tests/fixtures/sample-with-hash-in-title/_layouts/default.liquid @@ -0,0 +1,2 @@ +
{{ content }}
+
{%- for link in backlinks %}{{ link.title }}{%- endfor %}
diff --git a/tests/fixtures/sample-with-hash-in-title/building-a-self-contained-game-in-c-under-2-kilobytes.md b/tests/fixtures/sample-with-hash-in-title/building-a-self-contained-game-in-c-under-2-kilobytes.md new file mode 100644 index 0000000..f91107c --- /dev/null +++ b/tests/fixtures/sample-with-hash-in-title/building-a-self-contained-game-in-c-under-2-kilobytes.md @@ -0,0 +1,6 @@ +--- +title: Building a self-contained game in C# under 2 kilobytes +layout: default.liquid +--- + +Hello world. diff --git a/tests/fixtures/sample-with-hash-in-title/eleventy.config.js b/tests/fixtures/sample-with-hash-in-title/eleventy.config.js new file mode 100644 index 0000000..9b10b5c --- /dev/null +++ b/tests/fixtures/sample-with-hash-in-title/eleventy.config.js @@ -0,0 +1,12 @@ +module.exports = function (eleventyConfig) { + eleventyConfig.addPlugin( + require('../../../index.js'), + ); + + return { + dir: { + includes: "_includes", + layouts: "_layouts", + } + } +} diff --git a/tests/fixtures/sample-with-hash-in-title/index.md b/tests/fixtures/sample-with-hash-in-title/index.md new file mode 100644 index 0000000..841d434 --- /dev/null +++ b/tests/fixtures/sample-with-hash-in-title/index.md @@ -0,0 +1,6 @@ +--- +title: Something +layout: default.liquid +--- + +![[Building a self-contained game in C# under 2 kilobytes]] From 86e98649247d97d4b30dc5dcf4a48be7f8698bf4 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 3 May 2024 20:36:36 +0100 Subject: [PATCH 065/156] doc: document how wikilink parser works --- src/find-page.js | 2 ++ src/wikilink-parser.js | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/find-page.js b/src/find-page.js index 8b44548..516f1e0 100644 --- a/src/find-page.js +++ b/src/find-page.js @@ -13,6 +13,8 @@ const pageLookup = (allPages = [], slugifyFn) => { return true; } + // TODO: is there a need to slug the page title for comparison? We can match on link.name === page.data.title! + if (page.fileSlug === link.slug || (page.data.title && slugifyFn(page.data.title) === link.slug)) { return true; } diff --git a/src/wikilink-parser.js b/src/wikilink-parser.js index 75d857b..c4acd1b 100644 --- a/src/wikilink-parser.js +++ b/src/wikilink-parser.js @@ -34,9 +34,22 @@ module.exports = class WikilinkParser { return this.linkCache.get(link); } + // Wikilinks starting with a ! are considered Embeds e.g. `![[ ident ]]` const isEmbed = link.startsWith('!'); + + // By default, we display the linked page's title (or alias if used for lookup). This can be overloaded by + // defining the link text prefixed by a | character, e.g. `[[ ident | custom link text ]]` const parts = link.slice((isEmbed ? 3 : 2), -2).split("|").map(part => part.trim()); + + // Strip .md and .markdown extensions from the file ident. + // TODO: I am unsure if this is required might need refactoring in (#13) let name = parts[0].replace(/.(md|markdown)\s?$/i, ""); + + // Anchor link identification. This works similar to Obsidian.md except this doesn't look ahead to + // check if the referenced anchor exists. An anchor link can be referenced by a # character in the + // file ident, e.g. `[[ ident#anchor-id ]]`. + // + // This supports escaping by prefixing the # with a /, e.g `[[ Page about C/# ]]` let anchor = null; if (name.includes('#')) { From d4ef479ffdf44b28d51c6f3aa942948b73e5ae32 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 3 May 2024 20:36:52 +0100 Subject: [PATCH 066/156] doc: add todo item --- src/interlinker.js | 1 + tests/find-page-service.test.js | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/interlinker.js b/src/interlinker.js index fc4dc06..c2a44b2 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -114,6 +114,7 @@ module.exports = class Interlinker { }); // If a page has defined aliases, then add those to the link map. These must be unique. + // TODO: 1.1.0 keep track of defined aliases and throw exception if duplicates are found if (data.aliases && Array.isArray(data.aliases)) { for (const alias of data.aliases) { diff --git a/tests/find-page-service.test.js b/tests/find-page-service.test.js index 9a8aac2..54115da 100644 --- a/tests/find-page-service.test.js +++ b/tests/find-page-service.test.js @@ -36,9 +36,9 @@ test('pageLookup (find by href)', t => { test('pageLookup (find by wikilink)', t => { t.is(pageDirectory.findByLink({ title: 'Hello World, Title', - name: 'hello-world', + name: 'Hello World, Title', anchor: null, - link: '[[hello-world]]', + link: '[[Hello World, Title]]', slug: 'hello-world', isEmbed: false, }).fileSlug, 'hello-world'); @@ -54,3 +54,5 @@ test('pageLookup (find by alias)', t => { isEmbed: false, }).fileSlug, 'something-else'); }); + +// TODO: add testing when two pages share the same alias, what _should_ happen ? From ea979aaaf664ac91656a338975393ff740879865 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 3 May 2024 20:37:27 +0100 Subject: [PATCH 067/156] chore: test for anchor links --- tests/eleventy.test.js | 10 +++++++--- tests/fixtures/sample-with-hash-in-title/index.md | 4 +++- tests/fixtures/sample-with-hash-in-title/page/hello.md | 6 ++++++ 3 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 tests/fixtures/sample-with-hash-in-title/page/hello.md diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index 001b826..699c728 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -98,14 +98,18 @@ test.serial("Sample page (eleventyExcludeFromCollections set true)", async t => ); }); -test("Sample page (file with hash in title)", async t => { - +test("Sample page (files with hash in title)", async t => { let elev = new Eleventy(fixturePath('sample-with-hash-in-title'), fixturePath('sample-with-hash-in-title/_site'), { configPath: fixturePath('sample-with-hash-in-title/eleventy.config.js'), }); let results = await elev.toJSON(); + // Linked page is aware of its linking + t.is( + normalize(findResultByUrl(results, '/page/hello/').content), + `

Howdy!

` + ); // Embedded page is aware of its embedding t.is( @@ -116,6 +120,6 @@ test("Sample page (file with hash in title)", async t => { // Embed shows t.is( normalize(findResultByUrl(results, '/').content), - `

Hello world.

` + `

This link should be to a fragment identifier.

Hello world.

` ); }); diff --git a/tests/fixtures/sample-with-hash-in-title/index.md b/tests/fixtures/sample-with-hash-in-title/index.md index 841d434..610dbda 100644 --- a/tests/fixtures/sample-with-hash-in-title/index.md +++ b/tests/fixtures/sample-with-hash-in-title/index.md @@ -3,4 +3,6 @@ title: Something layout: default.liquid --- -![[Building a self-contained game in C# under 2 kilobytes]] +This link should be to [[Hello World # some-heading | a fragment identifier]]. + +![[Building a self-contained game in C/# under 2 kilobytes]] diff --git a/tests/fixtures/sample-with-hash-in-title/page/hello.md b/tests/fixtures/sample-with-hash-in-title/page/hello.md new file mode 100644 index 0000000..c4271c4 --- /dev/null +++ b/tests/fixtures/sample-with-hash-in-title/page/hello.md @@ -0,0 +1,6 @@ +--- +title: Hello World +layout: default.liquid +--- + +Howdy! From 50b0f0bc6377951d4518cfbf7a75c9fcff6c1332 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 3 May 2024 20:38:14 +0100 Subject: [PATCH 068/156] feat: allow escaping of # in page reference --- src/wikilink-parser.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/wikilink-parser.js b/src/wikilink-parser.js index c4acd1b..3ceed47 100644 --- a/src/wikilink-parser.js +++ b/src/wikilink-parser.js @@ -54,8 +54,11 @@ module.exports = class WikilinkParser { if (name.includes('#')) { const nameParts = parts[0].split('#').map(part => part.trim()); - name = nameParts[0]; - anchor = nameParts[1]; + // Allow for escaping a # when prefixed with a / + if (nameParts[0].at(-1) !== '/') { + name = nameParts[0]; + anchor = nameParts[1]; + } } const slug = this.slugifyFn(name); From 8146215b31ee768bbff70150f63f761f630e0c0b Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 3 May 2024 20:38:51 +0100 Subject: [PATCH 069/156] doc: add info on Linking to fragment identifiers --- README.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fff4b96..fcf3edb 100644 --- a/README.md +++ b/README.md @@ -62,16 +62,36 @@ module.exports = (eleventyConfig) => { ### Internal Links / Wikilinks -This plugin will parse both Wikilinks and internal anchor links. The Wikilink format is a **page reference** wrapped in double square brackets, for example: `[[Eleventy.js Interlink Plugin]]` will appear as [Eleventy.js Interlink Plugin](https://photogabble.co.uk/projects/eleventyjs-interlink-plugin/). +This plugin will parse both Wikilinks and internal anchor links to build each pages inbound and outbound internal links. -Using the vertical bar (`|`) you can change the text used to display a link. This can be useful when you want to work a link into a sentence without using the title of the file, for example: `[[Eleventy.js Interlink Plugin|custom display text]]` appears as [custom display text](https://www.photogabble.co.uk/projects/eleventyjs-interlink-plugin/). +The Wikilink format is a **page reference** wrapped in double square brackets, for example: `[[Eleventy.js Interlink Plugin]]` will appear as [Eleventy.js Interlink Plugin](https://photogabble.co.uk/projects/eleventyjs-interlink-plugin/). > **NOTE**: By default this plugin will use the `title` front-matter attribute of your pages or one of the aliases (as detailed below) as the **page reference**. +Using the vertical bar (`|`) you can change the text used to display a link. This can be useful when you want to work a link into a sentence without using the title of the file, for example: `[[Eleventy.js Interlink Plugin|custom display text]]` appears as [custom display text](https://www.photogabble.co.uk/projects/eleventyjs-interlink-plugin/). + +### Linking to fragment identifiers + +If you're using a plugin such as [markdown-it-anchor](https://www.npmjs.com/package/markdown-it-anchor) to add _anchor links_ to your headings, or have otherwise added them yourself. You can link to these in your pages by adding a `#` symbol to your page reference. + +For example, `[[Three laws of motion#Second law]]`. + +In cases where you have the `#` in the title of a page you're linking to you can escape using `/` foe example, `[[Programming in /#C, an introduction]]`. + ### Aliases Aliases provide you a way of referencing a file using different names, use the `aliases` property in your font matter to list one or more aliases that can be used to reference the file from a Wiki Link. For example, you might add _AI_ as an alias of a file titled _Artificial Intelligence_ which would then be linkable via `[[AI]]`. +```yaml +--- +title: Artificial Intelligence +aliases: + - AI +--- +``` + +Aliases should be unique identifiers, this plugin will halt the build with an error if it finds two pages sharing the same alias. + ### Linking to Pagination generated pages A common use of pagination in 11ty is [pagination of an object](https://www.11ty.dev/docs/pagination/#paging-an-object) or data file, by default these generated pages aren't included in the all pages collection and therefore are invisible to this plugin unless you set `addAllPagesToCollections: true`. From 90b6af261d44f267a439ed0d7a4c8b6219727df1 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 3 May 2024 23:17:28 +0100 Subject: [PATCH 070/156] bugfix(#31): only compile embeds if link exists --- index.d.ts | 1 + src/interlinker.js | 2 +- src/wikilink-parser.js | 4 +++- tests/eleventy.test.js | 14 ++++++++++++++ .../fixtures/sample-with-simple-embed/broken-2.md | 6 ++++++ tests/fixtures/sample-with-simple-embed/broken.md | 6 ++++++ tests/fixtures/sample-with-simple-embed/stubs.md | 6 ++++++ 7 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 tests/fixtures/sample-with-simple-embed/broken-2.md create mode 100644 tests/fixtures/sample-with-simple-embed/broken.md create mode 100644 tests/fixtures/sample-with-simple-embed/stubs.md diff --git a/index.d.ts b/index.d.ts index 46281d3..9baa6a0 100644 --- a/index.d.ts +++ b/index.d.ts @@ -45,6 +45,7 @@ type WikilinkMeta = { link: string slug: string isEmbed: boolean + exists: boolean // href and path are loaded from the linked page href?: string diff --git a/src/interlinker.js b/src/interlinker.js index c2a44b2..0111e2e 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -150,7 +150,7 @@ module.exports = class Interlinker { // If this is an embed and the embed template hasn't been compiled, add this to the queue // @TODO compiledEmbeds should be keyed by the wikilink text as i'll be allowing setting embed values via namespace, or other method e.g ![[ident||template]] - if (link.isEmbed && this.compiledEmbeds.has(link.slug) === false) { + if (link.isEmbed && link.exists && this.compiledEmbeds.has(link.slug) === false) { compilePromises.push(this.compileTemplate(page)); } diff --git a/src/wikilink-parser.js b/src/wikilink-parser.js index 3ceed47..af27b07 100644 --- a/src/wikilink-parser.js +++ b/src/wikilink-parser.js @@ -74,7 +74,8 @@ module.exports = class WikilinkParser { anchor, link, slug, - isEmbed + isEmbed, + exists: false, } // Lookup page data from 11ty's collection to obtain url and title if currently null @@ -83,6 +84,7 @@ module.exports = class WikilinkParser { if (meta.title === null && page.data.title) meta.title = page.data.title; meta.href = page.url; meta.path = page.inputPath; + meta.exists = true; } else { // If this wikilink goes to a page that doesn't exist, add to deadLinks list and // update href for stub post. diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index 699c728..7210722 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -123,3 +123,17 @@ test("Sample page (files with hash in title)", async t => { `

This link should be to a fragment identifier.

Hello world.

` ); }); + +test("Sample with simple embed (broken embed)", async t => { + let elev = new Eleventy(fixturePath('sample-with-simple-embed'), fixturePath('sample-with-simple-embed/_site'), { + configPath: fixturePath('sample-with-simple-embed/eleventy.config.js'), + }); + + let results = await elev.toJSON(); + + // Bad Wikilink Embed shows default text + t.is( + normalize(findResultByUrl(results, '/broken/').content), + `

[UNABLE TO LOCATE EMBED]

` + ); +}); diff --git a/tests/fixtures/sample-with-simple-embed/broken-2.md b/tests/fixtures/sample-with-simple-embed/broken-2.md new file mode 100644 index 0000000..945dbca --- /dev/null +++ b/tests/fixtures/sample-with-simple-embed/broken-2.md @@ -0,0 +1,6 @@ +--- +title: Broken 2 +layout: default.liquid +--- + +![[this is broken]] diff --git a/tests/fixtures/sample-with-simple-embed/broken.md b/tests/fixtures/sample-with-simple-embed/broken.md new file mode 100644 index 0000000..e9a0f22 --- /dev/null +++ b/tests/fixtures/sample-with-simple-embed/broken.md @@ -0,0 +1,6 @@ +--- +title: Broken +layout: default.liquid +--- + +![[this is broken]] diff --git a/tests/fixtures/sample-with-simple-embed/stubs.md b/tests/fixtures/sample-with-simple-embed/stubs.md new file mode 100644 index 0000000..1ff6067 --- /dev/null +++ b/tests/fixtures/sample-with-simple-embed/stubs.md @@ -0,0 +1,6 @@ +--- +title: Stubs +layout: default.liquid +--- + +Stubby Stubs. From b4ab5758467d24556669673a8a22e47e87e0b50e Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 3 May 2024 23:29:02 +0100 Subject: [PATCH 071/156] doc(#35): update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc6fe5f..842223e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Bugfix do not render embeds if the page linked doesn't exist (#35) - Bugfix do not parse links in pages excluded from collections (#30) - Bugfix do not exclude root index page - Bugfix pass 11ty page object to embed compiler function (#29) From 0c052e598c476e808101587ac2ba22ac113353c5 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 3 May 2024 23:31:12 +0100 Subject: [PATCH 072/156] bugfix(#37): broken dead-links lookup due to typo --- src/dead-links.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dead-links.js b/src/dead-links.js index f332440..34e4ebe 100644 --- a/src/dead-links.js +++ b/src/dead-links.js @@ -17,7 +17,7 @@ module.exports = class DeadLinks { if (!this.fileSrc) this.fileSrc = 'unknown'; const names = this.gravestones.has(link) - ? this.gravestones.get(this.fileSrc) + ? this.gravestones.get(link) : []; names.push(this.fileSrc) From 933b0bc369d6cfe44c1c7d60b3854db96d7f5ea0 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 3 May 2024 23:33:08 +0100 Subject: [PATCH 073/156] doc(#37): update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 842223e..cda3c42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Bugfix broken dead-links lookup due to typo (#38) - Bugfix do not render embeds if the page linked doesn't exist (#35) - Bugfix do not parse links in pages excluded from collections (#30) - Bugfix do not exclude root index page From a1676db338f582082b7df793de1a927c90b1dcec Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 3 May 2024 23:57:18 +0100 Subject: [PATCH 074/156] chore: add breaking test for #39 --- tests/eleventy.test.js | 17 +++++++++++++++-- .../linking-to-lonelyjuly.md | 6 ++++++ .../fixtures/sample-small-website/lonelyjuly.md | 7 +++++++ 3 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 tests/fixtures/sample-small-website/linking-to-lonelyjuly.md create mode 100644 tests/fixtures/sample-small-website/lonelyjuly.md diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index 7210722..a79b6d2 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -8,14 +8,14 @@ const sinon = require("sinon"); // avoid the "Attempted to wrap warn which is already wrapped" error. // @see https://stackoverflow.com/a/37900956/1225977 -test("Sample page (wikilinks and regular links)", async t => { +test("Sample small Website (wikilinks and regular links)", async t => { let elev = new Eleventy(fixturePath('sample-small-website'), fixturePath('sample-small-website/_site'), { configPath: fixturePath('sample-small-website/eleventy.config.js'), }); let results = await elev.toJSON(); - t.is(results.length, 2); + t.is(results.length, 4); t.is( normalize(findResultByUrl(results, '/about/').content), @@ -28,6 +28,19 @@ test("Sample page (wikilinks and regular links)", async t => { ); }); +test("Sample small Website (htmlenteties)", async t => { + let elev = new Eleventy(fixturePath('sample-small-website'), fixturePath('sample-small-website/_site'), { + configPath: fixturePath('sample-small-website/eleventy.config.js'), + }); + + let results = await elev.toJSON(); + + t.is( + normalize(findResultByUrl(results, '/linking-to-lonelyjuly/').content), + `
` + ); +}); + test.serial("Broken page (wikilinks and regular links)", async t => { const mock = sinon.stub(console, 'warn'); diff --git a/tests/fixtures/sample-small-website/linking-to-lonelyjuly.md b/tests/fixtures/sample-small-website/linking-to-lonelyjuly.md new file mode 100644 index 0000000..3c188a6 --- /dev/null +++ b/tests/fixtures/sample-small-website/linking-to-lonelyjuly.md @@ -0,0 +1,6 @@ +--- +title: Linking to Lonely July +layout: default.liquid +--- + +[[LonelyJuly]] diff --git a/tests/fixtures/sample-small-website/lonelyjuly.md b/tests/fixtures/sample-small-website/lonelyjuly.md new file mode 100644 index 0000000..5cb8c71 --- /dev/null +++ b/tests/fixtures/sample-small-website/lonelyjuly.md @@ -0,0 +1,7 @@ +--- +title: '>>LONELYJULY<<' +aliases: + - "LonelyJuly" +--- + +Lonely July Page From ebb8263ec22dc902dc5f9cc008de8ee7095402b7 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 4 May 2024 00:14:05 +0100 Subject: [PATCH 075/156] chore: install entities and update markdown-it --- package-lock.json | 227 +++++++++++++++++++++++++++++++++++++--------- package.json | 3 +- 2 files changed, 185 insertions(+), 45 deletions(-) diff --git a/package-lock.json b/package-lock.json index c6ffc74..e991c41 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,17 @@ { "name": "@photogabble/eleventy-plugin-interlinker", - "version": "1.0.6", + "version": "1.1.0-rc2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@photogabble/eleventy-plugin-interlinker", - "version": "1.0.6", + "version": "1.1.0-rc2", "license": "MIT", "dependencies": { "chalk": "^4.1.1", - "markdown-it": "^13.0.1" + "entities": "^4.5.0", + "markdown-it": "^14.1.0" }, "devDependencies": { "@11ty/eleventy": "^2.0.0", @@ -130,6 +131,61 @@ "url": "https://opencollective.com/11ty" } }, + "node_modules/@11ty/eleventy/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@11ty/eleventy/node_modules/entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/@11ty/eleventy/node_modules/linkify-it": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", + "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", + "dev": true, + "dependencies": { + "uc.micro": "^1.0.1" + } + }, + "node_modules/@11ty/eleventy/node_modules/markdown-it": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.2.tgz", + "integrity": "sha512-FtwnEuuK+2yVU7goGn/MJ0WBZMM9ZPgU9spqlFs7/A/pDIUNSOQZhUgOqYCficIuR2QaFnrt8LHqBWsbTAoI5w==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "~3.0.1", + "linkify-it": "^4.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/@11ty/eleventy/node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "dev": true + }, + "node_modules/@11ty/eleventy/node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true + }, "node_modules/@11ty/lodash-custom": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/@11ty/lodash-custom/-/lodash-custom-4.17.21.tgz", @@ -1400,9 +1456,9 @@ } }, "node_modules/entities": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", - "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "engines": { "node": ">=0.12" }, @@ -1931,6 +1987,18 @@ "entities": "^3.0.1" } }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/http-equiv-refresh": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/http-equiv-refresh/-/http-equiv-refresh-1.0.0.tgz", @@ -2337,11 +2405,11 @@ } }, "node_modules/linkify-it": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", - "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", "dependencies": { - "uc.micro": "^1.0.1" + "uc.micro": "^2.0.0" } }, "node_modules/liquidjs": { @@ -2464,18 +2532,19 @@ } }, "node_modules/markdown-it": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.2.tgz", - "integrity": "sha512-FtwnEuuK+2yVU7goGn/MJ0WBZMM9ZPgU9spqlFs7/A/pDIUNSOQZhUgOqYCficIuR2QaFnrt8LHqBWsbTAoI5w==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", "dependencies": { "argparse": "^2.0.1", - "entities": "~3.0.1", - "linkify-it": "^4.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" }, "bin": { - "markdown-it": "bin/markdown-it.js" + "markdown-it": "bin/markdown-it.mjs" } }, "node_modules/markdown-it/node_modules/argparse": { @@ -2568,9 +2637,9 @@ } }, "node_modules/mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==" }, "node_modules/mem": { "version": "9.0.2", @@ -3349,6 +3418,14 @@ "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==", "dev": true }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "engines": { + "node": ">=6" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -3879,9 +3956,9 @@ } }, "node_modules/uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" }, "node_modules/uglify-js": { "version": "3.17.4", @@ -4272,6 +4349,54 @@ "recursive-copy": "^2.0.14", "semver": "^7.3.8", "slugify": "^1.6.6" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "dev": true + }, + "linkify-it": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", + "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", + "dev": true, + "requires": { + "uc.micro": "^1.0.1" + } + }, + "markdown-it": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.2.tgz", + "integrity": "sha512-FtwnEuuK+2yVU7goGn/MJ0WBZMM9ZPgU9spqlFs7/A/pDIUNSOQZhUgOqYCficIuR2QaFnrt8LHqBWsbTAoI5w==", + "dev": true, + "requires": { + "argparse": "^2.0.1", + "entities": "~3.0.1", + "linkify-it": "^4.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + } + }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "dev": true + }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true + } } }, "@11ty/eleventy-dev-server": { @@ -5253,9 +5378,9 @@ "dev": true }, "entities": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", - "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==" + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" }, "errno": { "version": "0.1.8", @@ -5636,6 +5761,14 @@ "domhandler": "^4.2.2", "domutils": "^2.8.0", "entities": "^3.0.1" + }, + "dependencies": { + "entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "dev": true + } } }, "http-equiv-refresh": { @@ -5937,11 +6070,11 @@ "dev": true }, "linkify-it": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", - "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", "requires": { - "uc.micro": "^1.0.1" + "uc.micro": "^2.0.0" } }, "liquidjs": { @@ -6026,15 +6159,16 @@ } }, "markdown-it": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.2.tgz", - "integrity": "sha512-FtwnEuuK+2yVU7goGn/MJ0WBZMM9ZPgU9spqlFs7/A/pDIUNSOQZhUgOqYCficIuR2QaFnrt8LHqBWsbTAoI5w==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", "requires": { "argparse": "^2.0.1", - "entities": "~3.0.1", - "linkify-it": "^4.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" }, "dependencies": { "argparse": { @@ -6106,9 +6240,9 @@ } }, "mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==" }, "mem": { "version": "9.0.2", @@ -6690,6 +6824,11 @@ "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==", "dev": true }, + "punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==" + }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -7053,9 +7192,9 @@ "dev": true }, "uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" }, "uglify-js": { "version": "3.17.4", diff --git a/package.json b/package.json index 1b10665..91a7bcd 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,8 @@ }, "dependencies": { "chalk": "^4.1.1", - "markdown-it": "^13.0.1" + "entities": "^4.5.0", + "markdown-it": "^14.1.0" }, "devDependencies": { "@11ty/eleventy": "^2.0.0", From c8a1cac82d8a2b66a8730a5138a32ee3e326e24d Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 4 May 2024 00:15:08 +0100 Subject: [PATCH 076/156] bugfix(#39): encode html entities in link title --- src/markdown-ext.js | 4 ++-- tests/eleventy.test.js | 2 +- tests/fixtures/sample-small-website/linking-to-lonelyjuly.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/markdown-ext.js b/src/markdown-ext.js index dafe1fa..6150c49 100644 --- a/src/markdown-ext.js +++ b/src/markdown-ext.js @@ -1,4 +1,4 @@ -const chalk = require('chalk'); +const entities = require("entities"); /** * This rule will be looped through an inline token by markdown-it. @@ -70,7 +70,7 @@ const wikilinkRenderRule = (wikilinkParser, compiledEmbeds, opts) => (tokens, id const anchor = { href: link.href, - text: link.title ?? link.name, + text: entities.encodeHTML(link.title ?? link.name), }; if (link.anchor) { diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index a79b6d2..453e584 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -37,7 +37,7 @@ test("Sample small Website (htmlenteties)", async t => { t.is( normalize(findResultByUrl(results, '/linking-to-lonelyjuly/').content), - `
` + `
` ); }); diff --git a/tests/fixtures/sample-small-website/linking-to-lonelyjuly.md b/tests/fixtures/sample-small-website/linking-to-lonelyjuly.md index 3c188a6..73b54ef 100644 --- a/tests/fixtures/sample-small-website/linking-to-lonelyjuly.md +++ b/tests/fixtures/sample-small-website/linking-to-lonelyjuly.md @@ -3,4 +3,4 @@ title: Linking to Lonely July layout: default.liquid --- -[[LonelyJuly]] +[[>>LONELYJULY<<]] From 399b55f987d3ebb89130fbc306ecf4a46cde2624 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 4 May 2024 00:18:41 +0100 Subject: [PATCH 077/156] doc(#39): update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cda3c42..53ad34b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Bugfix HTML encode link titles (#40) - Bugfix broken dead-links lookup due to typo (#38) - Bugfix do not render embeds if the page linked doesn't exist (#35) - Bugfix do not parse links in pages excluded from collections (#30) From 608c0a1b29e305ca04ac113f9596ec9a1c1de53f Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 4 May 2024 00:27:23 +0100 Subject: [PATCH 078/156] feat: v1.1.0-rc3 next release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 91a7bcd..e914295 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@photogabble/eleventy-plugin-interlinker", - "version": "1.1.0-rc2", + "version": "1.1.0-rc3", "description": "Obsidian WikiLinks, BackLinks and Embed support for 11ty", "keywords": [ "11ty", From 10ea08ed3f4a3218cd1da035b8a47d6fd3148f70 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 4 May 2024 00:36:02 +0100 Subject: [PATCH 079/156] chore: add breaking test for #41 --- tests/eleventy.test.js | 15 ++++++++++++++- .../aliased-link-to-lonelyjuly.md | 6 ++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/sample-small-website/aliased-link-to-lonelyjuly.md diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index 453e584..4560f50 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -15,7 +15,7 @@ test("Sample small Website (wikilinks and regular links)", async t => { let results = await elev.toJSON(); - t.is(results.length, 4); + t.is(results.length, 5); t.is( normalize(findResultByUrl(results, '/about/').content), @@ -41,6 +41,19 @@ test("Sample small Website (htmlenteties)", async t => { ); }); +test("Sample small Website (alias text used for link)", async t => { + let elev = new Eleventy(fixturePath('sample-small-website'), fixturePath('sample-small-website/_site'), { + configPath: fixturePath('sample-small-website/eleventy.config.js'), + }); + + let results = await elev.toJSON(); + + t.is( + normalize(findResultByUrl(results, '/aliased-link-to-lonelyjuly/').content), + `

This should link with the alias as text LonelyJuly.

` + ); +}); + test.serial("Broken page (wikilinks and regular links)", async t => { const mock = sinon.stub(console, 'warn'); diff --git a/tests/fixtures/sample-small-website/aliased-link-to-lonelyjuly.md b/tests/fixtures/sample-small-website/aliased-link-to-lonelyjuly.md new file mode 100644 index 0000000..b4db5e7 --- /dev/null +++ b/tests/fixtures/sample-small-website/aliased-link-to-lonelyjuly.md @@ -0,0 +1,6 @@ +--- +title: Aliased Link to LonelyJuly +layout: default.liquid +--- + +This should link with the alias as text [[LonelyJuly]]. From 477e2120ff1c8e5eabc19fd61a1e4fecbc8e4e57 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 4 May 2024 01:07:00 +0100 Subject: [PATCH 080/156] feat: update findByLink to return lookup meta --- index.d.ts | 2 +- src/find-page.js | 40 ++++++++++++++++++++------------- tests/find-page-service.test.js | 14 +++++++----- 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/index.d.ts b/index.d.ts index 9baa6a0..385a80f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -53,7 +53,7 @@ type WikilinkMeta = { } interface PageDirectoryService { - findByLink(link : WikilinkMeta|LinkMeta): any; + findByLink(link : WikilinkMeta|LinkMeta): {page: any, found: boolean, foundByAlias: boolean}; findByFile(file : any): any; } diff --git a/src/find-page.js b/src/find-page.js index 516f1e0..60fa9bc 100644 --- a/src/find-page.js +++ b/src/find-page.js @@ -8,26 +8,36 @@ */ const pageLookup = (allPages = [], slugifyFn) => { return { - findByLink: (link) => allPages.find((page) => { - if (link.href && (page.url === link.href || page.url === `${link.href}/`)) { - return true; - } + findByLink: (link) => { + let foundByAlias = false; + const page = allPages.find((page) => { + if (link.href && (page.url === link.href || page.url === `${link.href}/`)) { + return true; + } - // TODO: is there a need to slug the page title for comparison? We can match on link.name === page.data.title! + // TODO: is there a need to slug the page title for comparison? We can match on link.name === page.data.title! - if (page.fileSlug === link.slug || (page.data.title && slugifyFn(page.data.title) === link.slug)) { - return true; - } + if (page.fileSlug === link.slug || (page.data.title && slugifyFn(page.data.title) === link.slug)) { + return true; + } - // TODO: need a way to identify that wikilink is pointing to an alias, because the alias then becomes the link title + // TODO: need a way to identify that wikilink is pointing to an alias, because the alias then becomes the link title - const aliases = ((page.data.aliases && Array.isArray(page.data.aliases)) ? page.data.aliases : []).reduce(function (set, alias) { - set.add(slugifyFn(alias)); - return set; - }, new Set()); + const aliases = ((page.data.aliases && Array.isArray(page.data.aliases)) ? page.data.aliases : []).reduce(function (set, alias) { + set.add(alias); + return set; + }, new Set()); - return aliases.has(link.slug); - }), + foundByAlias = aliases.has(link.name); + return foundByAlias; + }); + + return { + found: !!page, + page, + foundByAlias, + } + }, findByFile: (file) => allPages.find((page) => page.url === file.page.url), } diff --git a/tests/find-page-service.test.js b/tests/find-page-service.test.js index 54115da..7f2aedc 100644 --- a/tests/find-page-service.test.js +++ b/tests/find-page-service.test.js @@ -30,29 +30,33 @@ const pageDirectory = pageLookup([ ], slugify); test('pageLookup (find by href)', t => { - t.is(pageDirectory.findByLink({href: '/something/else', isEmbed: false}).fileSlug, 'something-else'); + const {page} = pageDirectory.findByLink({href: '/something/else', isEmbed: false}); + t.is(page.fileSlug, 'something-else'); }); test('pageLookup (find by wikilink)', t => { - t.is(pageDirectory.findByLink({ + const {page} = pageDirectory.findByLink({ title: 'Hello World, Title', name: 'Hello World, Title', anchor: null, link: '[[Hello World, Title]]', slug: 'hello-world', isEmbed: false, - }).fileSlug, 'hello-world'); + }); + + t.is(page.fileSlug, 'hello-world'); }); test('pageLookup (find by alias)', t => { - t.is(pageDirectory.findByLink({ + const {page} = pageDirectory.findByLink({ title: 'This is another page', name: 'test-alias', anchor: null, link: '[[test-alias]]', slug: 'test-alias', isEmbed: false, - }).fileSlug, 'something-else'); + }); + t.is(page.fileSlug, 'something-else'); }); // TODO: add testing when two pages share the same alias, what _should_ happen ? From 3d1d74e0f5be2bda21a3ab5219d6f15394401254 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 4 May 2024 01:07:22 +0100 Subject: [PATCH 081/156] chore: modify test so alias is not equal to slug --- tests/eleventy.test.js | 2 +- .../fixtures/sample-small-website/aliased-link-to-lonelyjuly.md | 2 +- tests/fixtures/sample-small-website/lonelyjuly.md | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index 4560f50..6702474 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -50,7 +50,7 @@ test("Sample small Website (alias text used for link)", async t => { t.is( normalize(findResultByUrl(results, '/aliased-link-to-lonelyjuly/').content), - `

This should link with the alias as text LonelyJuly.

` + `

This should link with the alias as text Aliased WikiLink.

` ); }); diff --git a/tests/fixtures/sample-small-website/aliased-link-to-lonelyjuly.md b/tests/fixtures/sample-small-website/aliased-link-to-lonelyjuly.md index b4db5e7..26b14fe 100644 --- a/tests/fixtures/sample-small-website/aliased-link-to-lonelyjuly.md +++ b/tests/fixtures/sample-small-website/aliased-link-to-lonelyjuly.md @@ -3,4 +3,4 @@ title: Aliased Link to LonelyJuly layout: default.liquid --- -This should link with the alias as text [[LonelyJuly]]. +This should link with the alias as text [[Aliased WikiLink]]. diff --git a/tests/fixtures/sample-small-website/lonelyjuly.md b/tests/fixtures/sample-small-website/lonelyjuly.md index 5cb8c71..ba21be5 100644 --- a/tests/fixtures/sample-small-website/lonelyjuly.md +++ b/tests/fixtures/sample-small-website/lonelyjuly.md @@ -2,6 +2,7 @@ title: '>>LONELYJULY<<' aliases: - "LonelyJuly" + - "Aliased WikiLink" --- Lonely July Page From f46e8d59ea2c8a1cf3cd5720037cf0689e1b03e4 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 4 May 2024 01:08:15 +0100 Subject: [PATCH 082/156] bugfix(#41): use alias as link text if lookup source --- src/html-link-parser.js | 2 +- src/interlinker.js | 4 ++-- src/wikilink-parser.js | 8 ++++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/html-link-parser.js b/src/html-link-parser.js index 9c992d8..b312a7f 100644 --- a/src/html-link-parser.js +++ b/src/html-link-parser.js @@ -32,7 +32,7 @@ module.exports = class HTMLLinkParser { isEmbed: false, }; - if (!pageDirectory.findByLink(meta)) { + if (!pageDirectory.findByLink(meta).found) { this.deadLinks.add(link); } diff --git a/src/interlinker.js b/src/interlinker.js index 0111e2e..83d2d2a 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -136,8 +136,8 @@ module.exports = class Interlinker { ...this.HTMLLinkParser.find(pageContent, pageDirectory), ].map((link) => { // Lookup the page this link, links to and add this page to its backlinks - const page = pageDirectory.findByLink(link); - if (!page) return link; + const {page, found} = pageDirectory.findByLink(link); + if (!found) return link; if (!page.data.backlinks) { page.data.backlinks = []; diff --git a/src/wikilink-parser.js b/src/wikilink-parser.js index af27b07..de6453d 100644 --- a/src/wikilink-parser.js +++ b/src/wikilink-parser.js @@ -79,9 +79,13 @@ module.exports = class WikilinkParser { } // Lookup page data from 11ty's collection to obtain url and title if currently null - const page = pageDirectory.findByLink(meta); + const {page, foundByAlias} = pageDirectory.findByLink(meta); if (page) { - if (meta.title === null && page.data.title) meta.title = page.data.title; + if (foundByAlias) { + meta.title = meta.name; + } else if (meta.title === null && page.data.title) { + meta.title = page.data.title; + } meta.href = page.url; meta.path = page.inputPath; meta.exists = true; From 787a37921aa91c4de2e4d01d1ccff61e8a97ba3b Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 4 May 2024 01:12:01 +0100 Subject: [PATCH 083/156] doc(#41): update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53ad34b..093504e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Bugfix use alias as link text if it's the lookup source (#42) - Bugfix HTML encode link titles (#40) - Bugfix broken dead-links lookup due to typo (#38) - Bugfix do not render embeds if the page linked doesn't exist (#35) From a8d876b17083f90d1a0de008ce6e4246cd3d53ca Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 4 May 2024 01:17:41 +0100 Subject: [PATCH 084/156] chore: remove TODO completed by #41 --- src/find-page.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/find-page.js b/src/find-page.js index 60fa9bc..cc78ab8 100644 --- a/src/find-page.js +++ b/src/find-page.js @@ -21,8 +21,6 @@ const pageLookup = (allPages = [], slugifyFn) => { return true; } - // TODO: need a way to identify that wikilink is pointing to an alias, because the alias then becomes the link title - const aliases = ((page.data.aliases && Array.isArray(page.data.aliases)) ? page.data.aliases : []).reduce(function (set, alias) { set.add(alias); return set; From 9530323f725eddf2adc471cd42e066e65d3831a7 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 4 May 2024 01:23:10 +0100 Subject: [PATCH 085/156] refactor: remove need to slugify for file matching. --- src/find-page.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/find-page.js b/src/find-page.js index cc78ab8..e13c52c 100644 --- a/src/find-page.js +++ b/src/find-page.js @@ -15,9 +15,12 @@ const pageLookup = (allPages = [], slugifyFn) => { return true; } - // TODO: is there a need to slug the page title for comparison? We can match on link.name === page.data.title! + // Order of lookup: + // 1. match file slug to link slug + // 2. match file title to link identifier (name) + // 3. match fle based upon alias - if (page.fileSlug === link.slug || (page.data.title && slugifyFn(page.data.title) === link.slug)) { + if (page.fileSlug === link.slug || (page.data.title && page.data.title === link.name)) { return true; } From 021640162d3fae08b51f5481327175a6333d4d73 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 4 May 2024 08:31:35 +0100 Subject: [PATCH 086/156] chore: added failing test for #13 --- tests/eleventy.test.js | 22 ++++++++++++++++++- .../sample-small-website/path-link-outer.md | 5 +++++ .../path-links/hello/world/page-b.md | 6 +++++ .../sample-small-website/path-links/page-a.md | 6 +++++ 4 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/sample-small-website/path-link-outer.md create mode 100644 tests/fixtures/sample-small-website/path-links/hello/world/page-b.md create mode 100644 tests/fixtures/sample-small-website/path-links/page-a.md diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index 6702474..3802766 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -15,7 +15,7 @@ test("Sample small Website (wikilinks and regular links)", async t => { let results = await elev.toJSON(); - t.is(results.length, 5); + t.is(results.length, 8); t.is( normalize(findResultByUrl(results, '/about/').content), @@ -28,6 +28,26 @@ test("Sample small Website (wikilinks and regular links)", async t => { ); }); +test("Sample small Website (path links)", async t => { + let elev = new Eleventy(fixturePath('sample-small-website'), fixturePath('sample-small-website/_site'), { + configPath: fixturePath('sample-small-website/eleventy.config.js'), + }); + + let results = await elev.toJSON(); + + const n = 1; + + // t.is( + // normalize(findResultByUrl(results, '/about/').content), + // `

This is to show that we can link between Markdown files and liquid files.

` + // ); + // + // t.is( + // normalize(findResultByUrl(results, '/hello/').content), + // '
This is to show that we can link back via regular internal links.
', + // ); +}); + test("Sample small Website (htmlenteties)", async t => { let elev = new Eleventy(fixturePath('sample-small-website'), fixturePath('sample-small-website/_site'), { configPath: fixturePath('sample-small-website/eleventy.config.js'), diff --git a/tests/fixtures/sample-small-website/path-link-outer.md b/tests/fixtures/sample-small-website/path-link-outer.md new file mode 100644 index 0000000..ed0cd60 --- /dev/null +++ b/tests/fixtures/sample-small-website/path-link-outer.md @@ -0,0 +1,5 @@ +--- +title: 'Relative page up directory' +--- + +Path Link test page diff --git a/tests/fixtures/sample-small-website/path-links/hello/world/page-b.md b/tests/fixtures/sample-small-website/path-links/hello/world/page-b.md new file mode 100644 index 0000000..0031f97 --- /dev/null +++ b/tests/fixtures/sample-small-website/path-links/hello/world/page-b.md @@ -0,0 +1,6 @@ +--- +title: Path Link Page B +layout: default.liquid +--- + +This is Page B. diff --git a/tests/fixtures/sample-small-website/path-links/page-a.md b/tests/fixtures/sample-small-website/path-links/page-a.md new file mode 100644 index 0000000..6c30dbc --- /dev/null +++ b/tests/fixtures/sample-small-website/path-links/page-a.md @@ -0,0 +1,6 @@ +--- +title: Path Link Page A +layout: default.liquid +--- + +We can Wikilink reference [[/path-links/hello/world/page-b.md|by full project path]], and [[./hello/world/page-b.md|by relative path]] from current file path [[../path-link-outer.md]]. From c173f7787a9a250911d0cdb7e8e068db6fe415ea Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 4 May 2024 21:55:25 +0100 Subject: [PATCH 087/156] bugfix: backlinks should be unique --- src/interlinker.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/interlinker.js b/src/interlinker.js index 83d2d2a..b98a49f 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -143,10 +143,12 @@ module.exports = class Interlinker { page.data.backlinks = []; } - page.data.backlinks.push({ - url: currentPage.url, - title: currentPage.data.title, - }); + if (page.data.backlinks.findIndex((backlink => backlink.url === currentPage.url)) === -1) { + page.data.backlinks.push({ + url: currentPage.url, + title: currentPage.data.title, + }); + } // If this is an embed and the embed template hasn't been compiled, add this to the queue // @TODO compiledEmbeds should be keyed by the wikilink text as i'll be allowing setting embed values via namespace, or other method e.g ![[ident||template]] From 4f52063c880ab3d7fcad166c17c093d2dc215d2e Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 4 May 2024 21:57:41 +0100 Subject: [PATCH 088/156] feat(#13) add wikilink page lookup by relative or absolute path --- index.d.ts | 1 + src/find-page.js | 17 ++++--- src/interlinker.js | 3 +- src/wikilink-parser.js | 45 ++++++++++++++----- tests/eleventy.test.js | 28 +++++++----- .../sample-small-website/path-link-outer.md | 1 + tests/wikilink-parser.test.js | 41 +++++++++++++++++ 7 files changed, 107 insertions(+), 29 deletions(-) diff --git a/index.d.ts b/index.d.ts index 385a80f..105d7e2 100644 --- a/index.d.ts +++ b/index.d.ts @@ -45,6 +45,7 @@ type WikilinkMeta = { link: string slug: string isEmbed: boolean + isPath: boolean, exists: boolean // href and path are loaded from the linked page diff --git a/src/find-page.js b/src/find-page.js index e13c52c..58a5564 100644 --- a/src/find-page.js +++ b/src/find-page.js @@ -11,15 +11,22 @@ const pageLookup = (allPages = [], slugifyFn) => { findByLink: (link) => { let foundByAlias = false; const page = allPages.find((page) => { + + // Order of lookup: + // 1. if is path link, return filePathStem match state + // 2. match file url to link href + // 3. match file slug to link slug + // 4. match file title to link identifier (name) + // 5. match fle based upon alias + + if (link.isPath) { + return page.filePathStem === link.name; + } + if (link.href && (page.url === link.href || page.url === `${link.href}/`)) { return true; } - // Order of lookup: - // 1. match file slug to link slug - // 2. match file title to link identifier (name) - // 3. match fle based upon alias - if (page.fileSlug === link.slug || (page.data.title && page.data.title === link.name)) { return true; } diff --git a/src/interlinker.js b/src/interlinker.js index b98a49f..7da4d80 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -106,7 +106,6 @@ module.exports = class Interlinker { // Pages can list aliases in their front matter, if those exist we should map them // as well. - // TODO: 1.1.0 key files by pathname (#13) // TODO: 1.1.0 key files by title (#5) this.linkMapCache.set(currentSlug, { page: data.collections.all.find(page => page.url === data.page.url), @@ -132,7 +131,7 @@ module.exports = class Interlinker { if (currentPage.template.frontMatter?.content) { const pageContent = currentPage.template.frontMatter.content; const outboundLinks = [ - ...this.wikiLinkParser.find(pageContent, pageDirectory), + ...this.wikiLinkParser.find(pageContent, pageDirectory, currentPage.filePathStem), ...this.HTMLLinkParser.find(pageContent, pageDirectory), ].map((link) => { // Lookup the page this link, links to and add this page to its backlinks diff --git a/src/wikilink-parser.js b/src/wikilink-parser.js index de6453d..81d6719 100644 --- a/src/wikilink-parser.js +++ b/src/wikilink-parser.js @@ -23,13 +23,13 @@ module.exports = class WikilinkParser { * Parses a single WikiLink into the link object understood by the Interlinker. * * @todo add parsing of namespace (#14) - * @todo add support for referencing file by path (#13) * * @param {string} link * @param {import('@photogabble/eleventy-plugin-interlinker').PageDirectoryService} pageDirectory + * @param {string|undefined} filePathStem * @return {import('@photogabble/eleventy-plugin-interlinker').WikilinkMeta} */ - parseSingle(link, pageDirectory) { + parseSingle(link, pageDirectory, filePathStem = undefined) { if (this.linkCache.has(link)) { return this.linkCache.get(link); } @@ -41,8 +41,8 @@ module.exports = class WikilinkParser { // defining the link text prefixed by a | character, e.g. `[[ ident | custom link text ]]` const parts = link.slice((isEmbed ? 3 : 2), -2).split("|").map(part => part.trim()); - // Strip .md and .markdown extensions from the file ident. - // TODO: I am unsure if this is required might need refactoring in (#13) + // Strip .md and .markdown extensions from the file ident; this is so it can be used for filePathStem match + // if path lookup. let name = parts[0].replace(/.(md|markdown)\s?$/i, ""); // Anchor link identification. This works similar to Obsidian.md except this doesn't look ahead to @@ -61,12 +61,31 @@ module.exports = class WikilinkParser { } } - const slug = this.slugifyFn(name); + // Path link identification. This supports both relative links from the linking files path and + // lookup from the project root path. + const isPath = (name.startsWith('/') || name.startsWith('../') || name.startsWith('./')); + + // This is a relative path lookup, need to mutate name so that its absolute path from project + // root so that we can match it on a pages filePathStem. + if (isPath && name.startsWith('.')) { + if (!filePathStem) throw new Error('Unable to do relative path lookup of wikilink.'); + + const cwd = filePathStem.split('/'); + const relative = name.split('/'); + const stepsBack = relative.filter(file => file === '..').length; - if (name.startsWith('/')) { - // TODO: if name begins with / then this is lookup by pathname (#13) + name = [ + ...cwd.slice(0, -(stepsBack + 1)), + ...relative.filter(file => file !== '..' && file !== '.') + ].join('/'); } + // TODO: is slugifying the name needed any more? We support wikilink ident being a page title, path or alias. + // there should be no reason why the ident can't be a slug and we can lookup based upon that as well + // without needing to slug it here... slug originally was used as a kind of id for lookup. That has + // mostly been refactored out. + const slug = this.slugifyFn(name); + /** @var {import('@photogabble/eleventy-plugin-interlinker').WikilinkMeta} */ const meta = { title: parts.length === 2 ? parts[1] : null, @@ -75,6 +94,7 @@ module.exports = class WikilinkParser { link, slug, isEmbed, + isPath, exists: false, } @@ -107,10 +127,11 @@ module.exports = class WikilinkParser { /** * @param {Array} links * @param {import('@photogabble/eleventy-plugin-interlinker').PageDirectoryService} pageDirectory + * @param {string|undefined} filePathStem * @return {Array} */ - parseMultiple(links, pageDirectory) { - return links.map(link => this.parseSingle(link, pageDirectory)); + parseMultiple(links, pageDirectory, filePathStem) { + return links.map(link => this.parseSingle(link, pageDirectory, filePathStem)); } /** @@ -119,12 +140,14 @@ module.exports = class WikilinkParser { * * @param {string} document * @param {import('@photogabble/eleventy-plugin-interlinker').PageDirectoryService} pageDirectory + * @param {string|undefined} filePathStem * @return {Array} */ - find(document, pageDirectory) { + find(document, pageDirectory, filePathStem) { return this.parseMultiple( (document.match(this.wikiLinkRegExp) || []), - pageDirectory + pageDirectory, + filePathStem ) } } diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index 3802766..30a66ce 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -35,17 +35,23 @@ test("Sample small Website (path links)", async t => { let results = await elev.toJSON(); - const n = 1; - - // t.is( - // normalize(findResultByUrl(results, '/about/').content), - // `

This is to show that we can link between Markdown files and liquid files.

` - // ); - // - // t.is( - // normalize(findResultByUrl(results, '/hello/').content), - // '
This is to show that we can link back via regular internal links.
', - // ); + // page-b is linked to from page-a + // path-link-outer is linked to from page-a + + t.is( + normalize(findResultByUrl(results, '/path-links/page-a/').content), + `

We can Wikilink reference by full project path, and by relative path from current file path Relative page up directory.

` + ); + + t.is( + normalize(findResultByUrl(results, '/path-links/hello/world/page-b/').content), + `

This is Page B.

` + ); + + t.is( + normalize(findResultByUrl(results, '/path-link-outer/').content), + `

Path Link test page

`, + ); }); test("Sample small Website (htmlenteties)", async t => { diff --git a/tests/fixtures/sample-small-website/path-link-outer.md b/tests/fixtures/sample-small-website/path-link-outer.md index ed0cd60..d1075f6 100644 --- a/tests/fixtures/sample-small-website/path-link-outer.md +++ b/tests/fixtures/sample-small-website/path-link-outer.md @@ -1,5 +1,6 @@ --- title: 'Relative page up directory' +layout: default.liquid --- Path Link test page diff --git a/tests/wikilink-parser.test.js b/tests/wikilink-parser.test.js index bc8491d..b06134b 100644 --- a/tests/wikilink-parser.test.js +++ b/tests/wikilink-parser.test.js @@ -5,10 +5,20 @@ const slugify = require("slugify"); const pageDirectory = pageLookup([ { + inputPath: '/home/user/website/hello-world.md', + filePathStem: '/hello-world', fileSlug: 'hello-world', data: { title: 'Hello World, Title', }, + }, + { + inputPath: '/home/user/website/blog/a-blog-post.md', + filePathStem: '/blog/a-blog-post', + fileSlug: 'a-blog-post', + data: { + title: 'Blog Post', + }, } ], slugify); @@ -116,3 +126,34 @@ test('populates dead links set', t => { t.is(deadLinks.size, 1); t.is(invalid.href, '/stubs'); }) + +test('parses path lookup', t => { + const deadLinks = new Set(); + const parser = new WikilinkParser({slugifyFn: slugify}, deadLinks); + + const parsed = parser.parseSingle('[[/blog/a-blog-post.md]]', pageDirectory); + t.is(parsed.isPath, true); + t.is(parsed.exists, true); + t.is(parsed.title, 'Blog Post'); +}) + +test('parses relative path lookup (single back step)', t => { + const deadLinks = new Set(); + const parser = new WikilinkParser({slugifyFn: slugify}, deadLinks); + + const parsed = parser.parseSingle('[[../a-blog-post.md]]', pageDirectory, '/blog/sub-dir/some-page'); + t.is(parsed.isPath, true); + t.is(parsed.exists, true); + t.is(parsed.title, 'Blog Post'); +}) + +test('parses relative path lookup (multiple back step)', t => { + const deadLinks = new Set(); + const parser = new WikilinkParser({slugifyFn: slugify}, deadLinks); + + const parsed = parser.parseSingle('[[../../a-blog-post.md]]', pageDirectory, '/blog/sub-dir/sub-dir/some-page'); + t.is(parsed.isPath, true); + t.is(parsed.exists, true); + t.is(parsed.title, 'Blog Post'); +}) + From c344e551d6a2272f71794a32a387564ea77bb9c9 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 4 May 2024 22:01:20 +0100 Subject: [PATCH 089/156] chore: remove dead code --- src/interlinker.js | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/src/interlinker.js b/src/interlinker.js index 7da4d80..6f2dff0 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -15,9 +15,6 @@ module.exports = class Interlinker { // Set of WikiLinks pointing to non-existent pages this.deadLinks = new DeadLinks(); - // Map of what WikiLinks to what - this.linkMapCache = new Map(); - // Map of WikiLinks that have triggered an embed compile this.compiledEmbeds = new Map(); @@ -95,37 +92,11 @@ module.exports = class Interlinker { slugifyFn ); - // TODO: 1.1.0 remove currentSlug as part of (#13) - - const currentSlug = slugifyFn(data.title); - let currentSlugs = new Set([currentSlug, data.page.fileSlug]); const currentPage = pageDirectory.findByFile(data); if (!currentPage) return []; - // Populate our link map for use later in replacing WikiLinks with page permalinks. - // Pages can list aliases in their front matter, if those exist we should map them - // as well. - - // TODO: 1.1.0 key files by title (#5) - this.linkMapCache.set(currentSlug, { - page: data.collections.all.find(page => page.url === data.page.url), - title: data.title - }); - - // If a page has defined aliases, then add those to the link map. These must be unique. // TODO: 1.1.0 keep track of defined aliases and throw exception if duplicates are found - if (data.aliases && Array.isArray(data.aliases)) { - for (const alias of data.aliases) { - const aliasSlug = slugifyFn(alias); - this.linkMapCache.set(aliasSlug, { - page: currentPage, - title: alias - }); - currentSlugs.add(aliasSlug) - } - } - // Identify this pages outbound internal links both as wikilink _and_ regular html anchor tags. For each outlink // lookup the other page and add this to its backlinks data value. if (currentPage.template.frontMatter?.content) { From 542b11eaf9a6d695b7b0774463e2fd541009df00 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 4 May 2024 22:07:38 +0100 Subject: [PATCH 090/156] doc(#13): document linking to files by path --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index fcf3edb..dc46965 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,10 @@ For example, `[[Three laws of motion#Second law]]`. In cases where you have the `#` in the title of a page you're linking to you can escape using `/` foe example, `[[Programming in /#C, an introduction]]`. +### Linking to files by path + +You can link to pages by their project path, or a path relative to the linking page, for example: `[[/blog/post-1234.md]]` would link to the page found at `/blog/post-1234` relative to the project root path, While `[[../../something.md]]` would link to a page two directories up. + ### Aliases Aliases provide you a way of referencing a file using different names, use the `aliases` property in your font matter to list one or more aliases that can be used to reference the file from a Wiki Link. For example, you might add _AI_ as an alias of a file titled _Artificial Intelligence_ which would then be linkable via `[[AI]]`. From 081a01254b115a520612ba128ad5f6886fc00ac9 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 4 May 2024 22:12:13 +0100 Subject: [PATCH 091/156] doc(#13): update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 093504e..d05ddb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Add support for referencing files by path (#44) - Bugfix use alias as link text if it's the lookup source (#42) - Bugfix HTML encode link titles (#40) - Bugfix broken dead-links lookup due to typo (#38) From b73de929bfc72b827c283f9b97b29fdddf787060 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 4 May 2024 22:25:04 +0100 Subject: [PATCH 092/156] chore: add passing test for #43 --- tests/eleventy.test.js | 23 +++++++++++++++++++ .../_layouts/default.liquid | 2 ++ .../website-with-permalink/eleventy.config.js | 12 ++++++++++ .../fixtures/website-with-permalink/index.md | 6 +++++ .../website-with-permalink/wikilink-test.md | 7 ++++++ 5 files changed, 50 insertions(+) create mode 100644 tests/fixtures/website-with-permalink/_layouts/default.liquid create mode 100644 tests/fixtures/website-with-permalink/eleventy.config.js create mode 100644 tests/fixtures/website-with-permalink/index.md create mode 100644 tests/fixtures/website-with-permalink/wikilink-test.md diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index 30a66ce..5a89ae1 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -189,3 +189,26 @@ test("Sample with simple embed (broken embed)", async t => { `

[UNABLE TO LOCATE EMBED]

` ); }); + +/** + * Test that permalink frontmatter is used for the Wikilinks href as raised in issue #43. + * @see https://github.com/photogabble/eleventy-plugin-interlinker/issues/43 + */ +test("Permalink should be used for link href", async t => { + let elev = new Eleventy(fixturePath('website-with-permalink'), fixturePath('website-with-permalink/_site'), { + configPath: fixturePath('website-with-permalink/eleventy.config.js'), + }); + + let results = await elev.toJSON(); + + t.is( + normalize(findResultByUrl(results, '/').content), + `

Link to Wikilink test should be to /wlink-tst/.

` + ); + + t.is( + normalize(findResultByUrl(results, '/wlink-tst/').content), + `

Hello World!

` + ); +}); + diff --git a/tests/fixtures/website-with-permalink/_layouts/default.liquid b/tests/fixtures/website-with-permalink/_layouts/default.liquid new file mode 100644 index 0000000..9065374 --- /dev/null +++ b/tests/fixtures/website-with-permalink/_layouts/default.liquid @@ -0,0 +1,2 @@ +
{{ content }}
+
{%- for link in backlinks %}{{ link.title }}{%- endfor %}
diff --git a/tests/fixtures/website-with-permalink/eleventy.config.js b/tests/fixtures/website-with-permalink/eleventy.config.js new file mode 100644 index 0000000..9b10b5c --- /dev/null +++ b/tests/fixtures/website-with-permalink/eleventy.config.js @@ -0,0 +1,12 @@ +module.exports = function (eleventyConfig) { + eleventyConfig.addPlugin( + require('../../../index.js'), + ); + + return { + dir: { + includes: "_includes", + layouts: "_layouts", + } + } +} diff --git a/tests/fixtures/website-with-permalink/index.md b/tests/fixtures/website-with-permalink/index.md new file mode 100644 index 0000000..5ad9cc1 --- /dev/null +++ b/tests/fixtures/website-with-permalink/index.md @@ -0,0 +1,6 @@ +--- +title: Homepage +layout: default.liquid +--- + +Link to [[Wikilink test]] should be to `/wlink-tst/`. diff --git a/tests/fixtures/website-with-permalink/wikilink-test.md b/tests/fixtures/website-with-permalink/wikilink-test.md new file mode 100644 index 0000000..fdc8ff7 --- /dev/null +++ b/tests/fixtures/website-with-permalink/wikilink-test.md @@ -0,0 +1,7 @@ +--- +title: Wikilink test +layout: default.liquid +permalink: /wlink-tst/ +--- + +Hello World! From 84802915a1cb3e90c824786595b8520f5a0db881 Mon Sep 17 00:00:00 2001 From: Simon Date: Sun, 5 May 2024 11:04:52 +0100 Subject: [PATCH 093/156] doc: add todo items --- src/find-page.js | 1 + src/interlinker.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/find-page.js b/src/find-page.js index 58a5564..dd4ce11 100644 --- a/src/find-page.js +++ b/src/find-page.js @@ -2,6 +2,7 @@ * Page Lookup Service: * This wraps the 11ty all pages collection providing two methods for finding pages. * + * @todo slugifyFn is no longer removed, remove * @param {Array} allPages * @param {import('@photogabble/eleventy-plugin-interlinker').SlugifyFn} slugifyFn * @return {import('@photogabble/eleventy-plugin-interlinker').PageDirectoryService} diff --git a/src/interlinker.js b/src/interlinker.js index 6f2dff0..d32a225 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -95,7 +95,7 @@ module.exports = class Interlinker { const currentPage = pageDirectory.findByFile(data); if (!currentPage) return []; - // TODO: 1.1.0 keep track of defined aliases and throw exception if duplicates are found + // TODO: 1.1.0 keep track of defined aliases and throw exception if duplicates are found (#46) // Identify this pages outbound internal links both as wikilink _and_ regular html anchor tags. For each outlink // lookup the other page and add this to its backlinks data value. From 52c9fce3280961b1172561106add5f118a9f8e17 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 8 May 2024 18:12:55 +0100 Subject: [PATCH 094/156] feat(#19): implement custom resolving functions --- index.d.ts | 15 +++- index.js | 14 ++- src/html-link-parser.js | 8 +- src/interlinker.js | 87 +++++++++++++------ src/markdown-ext.js | 27 +----- src/resolvers.js | 62 +++++++++++++ src/wikilink-parser.js | 38 ++++++-- tests/eleventy.test.js | 12 +++ .../_layouts/default.liquid | 2 + .../eleventy.config.js | 18 ++++ .../website-with-custom-resolving-fn/index.md | 10 +++ tests/markdown-wikilink-parser.test.js | 10 ++- tests/markdown-wikilink-renderer.test.js | 80 ++++------------- tests/wikilink-parser.test.js | 56 +++++++++--- 14 files changed, 301 insertions(+), 138 deletions(-) create mode 100644 src/resolvers.js create mode 100644 tests/fixtures/website-with-custom-resolving-fn/_layouts/default.liquid create mode 100644 tests/fixtures/website-with-custom-resolving-fn/eleventy.config.js create mode 100644 tests/fixtures/website-with-custom-resolving-fn/index.md diff --git a/index.d.ts b/index.d.ts index 105d7e2..20a68be 100644 --- a/index.d.ts +++ b/index.d.ts @@ -20,6 +20,10 @@ type EleventyPluginInterlinkOptions = { // slugifyFn is used to slugify strings. If a function isn't set then the default 11ty slugify filter is used. slugifyFn?: SlugifyFn + + // resolvingFns is a list of resolving functions. These are invoked by a wikilink containing a `:` character + // prefixed by the fn name. The page in this case is the linking page. + resolvingFns?: Map Promise>, } interface ErrorRenderFn { @@ -45,8 +49,17 @@ type WikilinkMeta = { link: string slug: string isEmbed: boolean - isPath: boolean, + isPath: boolean + + // If linked page has been found in the all collection exists will be + // true and page will be the 11ty page object. exists: boolean + page?: any + + // name of the resolving fn, if set it must exist + resolvingFnName?: string + // the resulting HTML of the resolving function + content?: string // href and path are loaded from the linked page href?: string diff --git a/index.js b/index.js index e22c269..2f43bc3 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,6 @@ const {wikilinkInlineRule, wikilinkRenderRule} = require('./src/markdown-ext'); const Interlinker = require("./src/interlinker"); +const {defaultResolvingFn, defaultEmbedFn} = require("./src/resolvers"); /** * Some code borrowed from: @@ -12,20 +13,29 @@ const Interlinker = require("./src/interlinker"); module.exports = function (eleventyConfig, options = {}) { /** @var { import('@photogabble/eleventy-plugin-interlinker').EleventyPluginInterlinkOptions } opts */ const opts = Object.assign({ - // TODO: 1.1.0 add custom resolving functions (#19) defaultLayout: null, defaultLayoutLang: null, layoutKey: 'embedLayout', layoutTemplateLangKey: 'embedLayoutLanguage', - unableToLocateEmbedFn: () => '[UNABLE TO LOCATE EMBED]', slugifyFn: (input) => { const slugify = eleventyConfig.getFilter('slugify'); if (typeof slugify !== 'function') throw new Error('Unable to load slugify filter.'); return slugify(input); }, + resolvingFns: new Map(), }, options); + // TODO: deprecate usage of unableToLocateEmbedFn in preference of using resolving fn + if (typeof opts.unableToLocateEmbedFn === 'function' && !opts.resolvingFns.has('404-embed')) { + opts.resolvingFns.set('404-embed', async (link) => opts.unableToLocateEmbedFn(link.page.fileSlug)); + } + + // Default resolving functions for converting a Wikilink into HTML. + if (!opts.resolvingFns.has('default')) opts.resolvingFns.set('default', defaultResolvingFn); + if (!opts.resolvingFns.has('default-embed')) opts.resolvingFns.set('default-embed', defaultEmbedFn); + if (!opts.resolvingFns.has('404-embed')) opts.resolvingFns.set('404-embed', async () => '[UNABLE TO LOCATE EMBED]'); + const interlinker = new Interlinker(opts); // TODO: document diff --git a/src/html-link-parser.js b/src/html-link-parser.js index b312a7f..4389831 100644 --- a/src/html-link-parser.js +++ b/src/html-link-parser.js @@ -32,10 +32,16 @@ module.exports = class HTMLLinkParser { isEmbed: false, }; - if (!pageDirectory.findByLink(meta).found) { + const {found, page} = pageDirectory.findByLink(meta); + + if (!found) { this.deadLinks.add(link); + return meta; } + meta.exists = true; + meta.page = page; + return meta; } diff --git a/src/interlinker.js b/src/interlinker.js index d32a225..5539936 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -3,12 +3,27 @@ const WikilinkParser = require("./wikilink-parser"); const {EleventyRenderPlugin} = require("@11ty/eleventy"); const DeadLinks = require("./dead-links"); const {pageLookup} = require("./find-page"); +const entities = require("entities"); + +const defaultResolvingFn = async (currentPage, link) => { + const text = entities.encodeHTML(link.title ?? link.name); + let href = link.href; + + if (link.anchor) { + href = `${href}#${link.anchor}`; + } + + return `${text}`; +} /** * Interlinker: * */ module.exports = class Interlinker { + /** + * @param {import('@photogabble/eleventy-plugin-interlinker').EleventyPluginInterlinkOptions} opts + */ constructor(opts) { this.opts = opts @@ -31,13 +46,32 @@ module.exports = class Interlinker { this.HTMLLinkParser = new HTMLLinkParser(this.deadLinks); } + /** + * Invokes the resolving function, passing it the linking page and the parsed link meta. + * @param {*} page + * @param {import('@photogabble/eleventy-plugin-interlinker').WikilinkMeta} link + * @return {Promise} + */ + async invokeResolvingFn(page, link) { + if (this.compiledEmbeds.has(link.link)) return; + if (!link.resolvingFnName) return; + + const fn = this.opts.resolvingFns.get(link.resolvingFnName); + // TODO: result can be string or object + const result = await fn(link, page); + + this.compiledEmbeds.set(link.link, result); + return result; + } + /** * Compiles the template associated with a WikiLink when invoked via the ![[embed syntax]] - * @param data + * @param {*} data + * @param {import('@photogabble/eleventy-plugin-interlinker').WikilinkMeta} link * @return {Promise} */ - async compileTemplate(data) { - if (this.compiledEmbeds.has(data.url)) return; + async compileTemplate(data, link) { + if (this.compiledEmbeds.has(link.link)) return; const frontMatter = data.template.frontMatter; @@ -61,9 +95,7 @@ module.exports = class Interlinker { }); const result = await fn({content: frontMatter.content, ...data.data}); - - this.compiledEmbeds.set(data.url, result); - + this.compiledEmbeds.set(link.link, result); return result; } @@ -101,33 +133,32 @@ module.exports = class Interlinker { // lookup the other page and add this to its backlinks data value. if (currentPage.template.frontMatter?.content) { const pageContent = currentPage.template.frontMatter.content; - const outboundLinks = [ + const outboundLinks = [ ...this.wikiLinkParser.find(pageContent, pageDirectory, currentPage.filePathStem), ...this.HTMLLinkParser.find(pageContent, pageDirectory), - ].map((link) => { - // Lookup the page this link, links to and add this page to its backlinks - const {page, found} = pageDirectory.findByLink(link); - if (!found) return link; - - if (!page.data.backlinks) { - page.data.backlinks = []; + ]; + + // Foreach link on this page, if it has its own resolving function we invoke that + // otherwise the default behaviour is to look up the page and add this page to + // its backlinks list. + + for (const link of outboundLinks) { + // If the linked page exists we can add the linking page to its backlinks array + if (link.exists) { + if (!link.page.data.backlinks) link.page.data.backlinks = []; + if (link.page.data.backlinks.findIndex((backlink => backlink.url === currentPage.url)) === -1) { + link.page.data.backlinks.push({ + url: currentPage.url, + title: currentPage.data.title, + }); + } } - if (page.data.backlinks.findIndex((backlink => backlink.url === currentPage.url)) === -1) { - page.data.backlinks.push({ - url: currentPage.url, - title: currentPage.data.title, - }); + if (link.resolvingFnName) { + const fn = this.opts.resolvingFns.get(link.resolvingFnName); + link.content = await fn(link, currentPage, this); } - - // If this is an embed and the embed template hasn't been compiled, add this to the queue - // @TODO compiledEmbeds should be keyed by the wikilink text as i'll be allowing setting embed values via namespace, or other method e.g ![[ident||template]] - if (link.isEmbed && link.exists && this.compiledEmbeds.has(link.slug) === false) { - compilePromises.push(this.compileTemplate(page)); - } - - return link; - }); + } // Block iteration until compilation complete. if (compilePromises.length > 0) await Promise.all(compilePromises); diff --git a/src/markdown-ext.js b/src/markdown-ext.js index 6150c49..11cc524 100644 --- a/src/markdown-ext.js +++ b/src/markdown-ext.js @@ -53,31 +53,8 @@ const wikilinkInlineRule = (wikilinkParser) => (state, silent) => { * @returns {(function(*, *): (string))|*} */ const wikilinkRenderRule = (wikilinkParser, compiledEmbeds, opts) => (tokens, idx) => { - const token = tokens[idx]; - const link = token.meta; - - if (link.isEmbed) { - // TODO: compiledEmbeds should be keyed by wikilink text because with namespacing (#14) and custom resolving functions (#19) we can have different rendered output based upon either - const templateContent = compiledEmbeds.get(link.href); - if (!templateContent) { - return (typeof opts.unableToLocateEmbedFn === 'function') - ? opts.unableToLocateEmbedFn(token.content) - : ''; - } - - return templateContent; - } - - const anchor = { - href: link.href, - text: entities.encodeHTML(link.title ?? link.name), - }; - - if (link.anchor) { - anchor.href = `${anchor.href}#${link.anchor}`; - } - - return `${anchor.text}`; + const {meta} = tokens[idx]; + return meta.content; }; module.exports = { diff --git a/src/resolvers.js b/src/resolvers.js new file mode 100644 index 0000000..2d7368a --- /dev/null +++ b/src/resolvers.js @@ -0,0 +1,62 @@ +const entities = require("entities"); +/** + * Default Resolving function for converting Wikilinks into html links. + * + * @param {import('@photogabble/eleventy-plugin-interlinker').WikilinkMeta} link + * @param {*} currentPage + * @param {import('./interlinker')} interlinker + * @return {Promise} + */ +const defaultResolvingFn = async (link, currentPage, interlinker) => { + const text = entities.encodeHTML(link.title ?? link.name); + let href = link.href; + + if (link.anchor) { + href = `${href}#${link.anchor}`; + } + + return `${text}`; +} + +/** + * Default Resolving function for converting Wikilinks into Embeds. + * + * @param {import('@photogabble/eleventy-plugin-interlinker').WikilinkMeta} link + * @param {*} currentPage + * @param {import('./interlinker')} interlinker + * @return {Promise} + */ +const defaultEmbedFn = async (link, currentPage, interlinker) => { + if (!link.exists || !interlinker.templateConfig || !interlinker.extensionMap) return; + + const page = link.page; + const frontMatter = page.template.frontMatter; + + const layout = (page.data.hasOwnProperty(interlinker.opts.layoutKey)) + ? page.data[interlinker.opts.layoutKey] + : interlinker.opts.defaultLayout; + + // TODO this should be based upon the layout extension + const language = (page.data.hasOwnProperty(interlinker.opts.layoutTemplateLangKey)) + ? page.data[interlinker.opts.layoutTemplateLangKey] + : interlinker.opts.defaultLayoutLang === null + ? page.page.templateSyntax + : interlinker.opts.defaultLayoutLang; + + // TODO: the layout below is liquid, will break if content contains invalid template tags such as passing njk file src + const tpl = layout === null + ? frontMatter.content + : `{% layout "${layout}" %} {% block content %} ${frontMatter.content} {% endblock %}`; + + const fn = await interlinker.rm.compile(tpl, language, { + templateConfig: interlinker.templateConfig, + extensionMap: interlinker.extensionMap + }); + + return fn({content: frontMatter.content, ...page.data}); +} + +module.exports = { + defaultEmbedFn, + defaultResolvingFn, +} diff --git a/src/wikilink-parser.js b/src/wikilink-parser.js index 81d6719..7f39ba5 100644 --- a/src/wikilink-parser.js +++ b/src/wikilink-parser.js @@ -45,9 +45,10 @@ module.exports = class WikilinkParser { // if path lookup. let name = parts[0].replace(/.(md|markdown)\s?$/i, ""); - // Anchor link identification. This works similar to Obsidian.md except this doesn't look ahead to - // check if the referenced anchor exists. An anchor link can be referenced by a # character in the - // file ident, e.g. `[[ ident#anchor-id ]]`. + //// + // Anchor link identification: + // This works similar to Obsidian.md except this doesn't look ahead to check if the referenced anchor exists. + // An anchor link can be referenced by a # character in the file ident, e.g. `[[ ident#anchor-id ]]`. // // This supports escaping by prefixing the # with a /, e.g `[[ Page about C/# ]]` let anchor = null; @@ -61,8 +62,9 @@ module.exports = class WikilinkParser { } } - // Path link identification. This supports both relative links from the linking files path and - // lookup from the project root path. + //// + // Path link identification: + // This supports both relative links from the linking files path and lookup from the project root path. const isPath = (name.startsWith('/') || name.startsWith('../') || name.startsWith('./')); // This is a relative path lookup, need to mutate name so that its absolute path from project @@ -80,6 +82,27 @@ module.exports = class WikilinkParser { ].join('/'); } + //// + // Custom Resolving Fn: + // If the author has referenced a custom resolving function via inclusion of the `:` character + // then we use that one. Otherwise, use the default resolving functions. + // As with anchor links, this supports escaping the `:` character by prefixing with `/` + let fnName = isEmbed + ? 'default-embed' + : 'default' + + if (name.includes(':')) { + const parts = name.split(':').map(part => part.trim()); + if (parts[0].at(-1) !== '/') { + fnName = parts[0]; + name = parts[1]; + } + } + + if (!this.opts.resolvingFns || this.opts.resolvingFns.has(fnName) === false) { + throw new Error(`Unable to find resolving fn [${fnName}] for wikilink ${link} on page [${filePathStem}]`); + } + // TODO: is slugifying the name needed any more? We support wikilink ident being a page title, path or alias. // there should be no reason why the ident can't be a slug and we can lookup based upon that as well // without needing to slug it here... slug originally was used as a kind of id for lookup. That has @@ -98,6 +121,8 @@ module.exports = class WikilinkParser { exists: false, } + if (fnName) meta.resolvingFnName = fnName; + // Lookup page data from 11ty's collection to obtain url and title if currently null const {page, foundByAlias} = pageDirectory.findByLink(meta); if (page) { @@ -109,12 +134,15 @@ module.exports = class WikilinkParser { meta.href = page.url; meta.path = page.inputPath; meta.exists = true; + meta.page = page; } else { // If this wikilink goes to a page that doesn't exist, add to deadLinks list and // update href for stub post. this.deadLinks.add(link); // @todo make the stub post url configurable, or even able to be disabled. (#25) meta.href = '/stubs'; + + if (isEmbed) meta.resolvingFnName = '404-embed'; } // Cache discovered meta to link, this cache can then be used by the Markdown render rule diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index 5a89ae1..89798db 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -212,3 +212,15 @@ test("Permalink should be used for link href", async t => { ); }); +test("Custom resolving functions are invoked", async t => { + let elev = new Eleventy(fixturePath('website-with-custom-resolving-fn'), fixturePath('website-with-custom-resolving-fn/_site'), { + configPath: fixturePath('website-with-custom-resolving-fn/eleventy.config.js'), + }); + + let results = await elev.toJSON(); + + t.is( + normalize(findResultByUrl(results, '/').content), + `

These wikilinks use custom resolving functions:

` + ); +}); diff --git a/tests/fixtures/website-with-custom-resolving-fn/_layouts/default.liquid b/tests/fixtures/website-with-custom-resolving-fn/_layouts/default.liquid new file mode 100644 index 0000000..9065374 --- /dev/null +++ b/tests/fixtures/website-with-custom-resolving-fn/_layouts/default.liquid @@ -0,0 +1,2 @@ +
{{ content }}
+
{%- for link in backlinks %}{{ link.title }}{%- endfor %}
diff --git a/tests/fixtures/website-with-custom-resolving-fn/eleventy.config.js b/tests/fixtures/website-with-custom-resolving-fn/eleventy.config.js new file mode 100644 index 0000000..bef6a17 --- /dev/null +++ b/tests/fixtures/website-with-custom-resolving-fn/eleventy.config.js @@ -0,0 +1,18 @@ +module.exports = function (eleventyConfig) { + eleventyConfig.addPlugin( + require('../../../index.js'), + { + resolvingFns: new Map([ + ['howdy', (link, page) => `Hello ${link.name}!`], + ['issue', (link, page) => `#${link.name}`], + ]), + } + ); + + return { + dir: { + includes: "_includes", + layouts: "_layouts", + } + } +} diff --git a/tests/fixtures/website-with-custom-resolving-fn/index.md b/tests/fixtures/website-with-custom-resolving-fn/index.md new file mode 100644 index 0000000..42ebeb8 --- /dev/null +++ b/tests/fixtures/website-with-custom-resolving-fn/index.md @@ -0,0 +1,10 @@ +--- +title: Homepage +layout: default.liquid +github: https://github.com/photogabble/eleventy-plugin-interlinker +--- + +These wikilinks use custom resolving functions: + +- [[howdy:RWC]] +- [[issue:19]] diff --git a/tests/markdown-wikilink-parser.test.js b/tests/markdown-wikilink-parser.test.js index 9e1833d..949ee69 100644 --- a/tests/markdown-wikilink-parser.test.js +++ b/tests/markdown-wikilink-parser.test.js @@ -1,9 +1,15 @@ -const {wikilinkInlineRule} = require('../src/markdown-ext'); const WikilinkParser = require('../src/wikilink-parser'); +const {wikilinkInlineRule} = require('../src/markdown-ext'); +const {defaultResolvingFn} = require("../src/resolvers"); const slugify = require('slugify'); const test = require('ava'); -const opts = {slugifyFn: (text) => slugify(text)}; +const opts = { + slugifyFn: (text) => slugify(text), + resolvingFns: new Map([ + ['default', defaultResolvingFn] + ]), +}; test('inline rule correctly parses single wikilink', t => { const wikilinkParser = new WikilinkParser(opts, new Set); diff --git a/tests/markdown-wikilink-renderer.test.js b/tests/markdown-wikilink-renderer.test.js index 118b780..2c0d315 100644 --- a/tests/markdown-wikilink-renderer.test.js +++ b/tests/markdown-wikilink-renderer.test.js @@ -15,7 +15,9 @@ test('inline rule correctly parses single wikilink', t => { wikilinkParser.linkCache.set('[[wiki link]]', { title: 'Wiki Link', + link: '[[wiki link]]', href: '/wiki-link/', + content: 'Wiki Link', isEmbed: false, }); @@ -42,13 +44,17 @@ test('inline rule correctly parses multiple wikilinks', t => { wikilinkParser.linkCache.set('[[wiki link]]', { title: 'Wiki Link', + link: '[[wiki link]]', href: '/wiki-link/', + content: 'Wiki Link', isEmbed: false, }); wikilinkParser.linkCache.set('[[another wiki link]]', { title: 'Another Wiki Link', + link: '[[another wiki link]]', href: '/another-wiki-link/', + content: 'Another Wiki Link', isEmbed: false, }); @@ -76,11 +82,11 @@ test('inline rule correctly parses single embed', t => { wikilinkParser.linkCache.set('![[wiki-embed]]', { title: 'Wiki Embed', href: '/wiki-embed/', + link: '![[wiki-embed]]', + content: 'Wiki Embed Test', isEmbed: true, }); - compiledEmbeds.set('/wiki-embed/', 'Wiki Embed Test'); - const md = require('markdown-it')({html: true}); md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( wikilinkParser @@ -104,31 +110,36 @@ test('inline rule correctly parses mixed wikilink and embed in multiline input', wikilinkParser.linkCache.set('![[inline embed]]', { title: 'Inline Embed', + link: '![[inline embed]]', href: '/inline-embed/', + content: 'inline embed', isEmbed: true, }); wikilinkParser.linkCache.set('![[this is an embed on its own]]', { title: 'This is an embed on its own', + link: '![[this is an embed on its own]]', href: '/lonely-embed/', + content: '
Embed on its own
', isEmbed: true, }); wikilinkParser.linkCache.set('[[wiki link]]', { title: 'Wiki Link', + link: '[[wiki link]]', href: '/wiki-link/', + content: 'Wiki Link', isEmbed: false, }); wikilinkParser.linkCache.set('[[wiki link|Wikilinks]]', { title: 'Wikilinks', + link: '[[wiki link|Wikilinks]]', href: '/wiki-link/', + content: 'Wikilinks', isEmbed: false, }); - compiledEmbeds.set('/inline-embed/', 'inline embed'); - compiledEmbeds.set('/lonely-embed/', '
Embed on its own
'); - const md = require('markdown-it')({html: true}); md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( wikilinkParser @@ -148,62 +159,3 @@ test('inline rule correctly parses mixed wikilink and embed in multiline input', normalize(html) ); }); - -test('inline rule correctly displays unable to load embed content', t => { - const wikilinkParser = new WikilinkParser(opts, new Set); - const compiledEmbeds = new Map; - - wikilinkParser.linkCache.set('![[wiki-embed]]', { - title: 'Wiki Embed', - href: '/wiki-embed/', - link: '![[wiki-embed]]', - isEmbed: true, - }); - - const md = require('markdown-it')({html: true}); - md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( - wikilinkParser - )); - - md.renderer.rules.inline_wikilink = wikilinkRenderRule( - wikilinkParser, - compiledEmbeds, - { - ...opts, - unableToLocateEmbedFn: () => '[TESTING]' - } - ); - - t.is( - md.render('Hello world this is a ![[wiki-embed]]'), - "

Hello world this is a [TESTING]

\n" - ); -}); - -test('inline rule correctly displays anchor links', t => { - const wikilinkParser = new WikilinkParser(opts, new Set); - const compiledEmbeds = new Map; - - wikilinkParser.linkCache.set('[[wiki link#heading-id]]', { - title: 'Wiki Link', - href: '/wiki-link/', - anchor: 'heading-id', - isEmbed: false, - }); - - const md = require('markdown-it')({html: true}); - md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( - wikilinkParser - )); - - md.renderer.rules.inline_wikilink = wikilinkRenderRule( - wikilinkParser, - compiledEmbeds, - opts - ); - - t.is( - "

Hello world, this is some text with a Wiki Link inside!

\n", - md.render('Hello world, this is some text with a [[wiki link#heading-id]] inside!', {}) - ); -}) diff --git a/tests/wikilink-parser.test.js b/tests/wikilink-parser.test.js index b06134b..4149651 100644 --- a/tests/wikilink-parser.test.js +++ b/tests/wikilink-parser.test.js @@ -1,7 +1,9 @@ const WikilinkParser = require('../src/wikilink-parser'); -const test = require('ava'); +const {defaultResolvingFn, defaultEmbedFn} = require("../src/resolvers"); const {pageLookup} = require("../src/find-page"); const slugify = require("slugify"); +const test = require('ava'); + const pageDirectory = pageLookup([ { @@ -22,8 +24,16 @@ const pageDirectory = pageLookup([ } ], slugify); +const opts = { + slugifyFn: (text) => slugify(text), + resolvingFns: new Map([ + ['default', defaultResolvingFn], + ['default-embed', defaultEmbedFn], + ]), +}; + test('parses wikilink', t => { - const parser = new WikilinkParser({slugifyFn: slugify}, new Set()); + const parser = new WikilinkParser(opts, new Set()); t.like(parser.parseSingle('[[hello world]]', pageDirectory), { title: 'Hello World, Title', anchor: null, @@ -33,7 +43,7 @@ test('parses wikilink', t => { }); test('parses wikilink with title', t => { - const parser = new WikilinkParser({slugifyFn: slugify}, new Set()); + const parser = new WikilinkParser(opts, new Set()); t.like(parser.parseSingle('[[hello world|Howdy]]', pageDirectory), { title: 'Howdy', anchor: null, @@ -43,7 +53,7 @@ test('parses wikilink with title', t => { }); test('parses wikilink with anchor', t => { - const parser = new WikilinkParser({slugifyFn: slugify}, new Set()); + const parser = new WikilinkParser(opts, new Set()); t.like(parser.parseSingle('[[hello world#heading one]]', pageDirectory), { title: 'Hello World, Title', anchor: 'heading one', @@ -53,7 +63,7 @@ test('parses wikilink with anchor', t => { }); test('parses wikilink embed', t => { - const parser = new WikilinkParser({slugifyFn: slugify}, new Set()); + const parser = new WikilinkParser(opts, new Set()); t.like(parser.parseSingle('![[hello world]]', pageDirectory), { title: 'Hello World, Title', anchor: null, @@ -63,7 +73,7 @@ test('parses wikilink embed', t => { }); test('parses wikilinks with weird formatting', t => { - const parser = new WikilinkParser({slugifyFn: slugify}, new Set()); + const parser = new WikilinkParser(opts, new Set()); const checks = [ { @@ -116,7 +126,7 @@ test('parses wikilinks with weird formatting', t => { test('populates dead links set', t => { const deadLinks = new Set(); - const parser = new WikilinkParser({slugifyFn: slugify}, deadLinks); + const parser = new WikilinkParser(opts, deadLinks); t.is(deadLinks.size, 0); parser.parseSingle('[[hello world]]', pageDirectory); @@ -129,7 +139,7 @@ test('populates dead links set', t => { test('parses path lookup', t => { const deadLinks = new Set(); - const parser = new WikilinkParser({slugifyFn: slugify}, deadLinks); + const parser = new WikilinkParser(opts, deadLinks); const parsed = parser.parseSingle('[[/blog/a-blog-post.md]]', pageDirectory); t.is(parsed.isPath, true); @@ -139,7 +149,7 @@ test('parses path lookup', t => { test('parses relative path lookup (single back step)', t => { const deadLinks = new Set(); - const parser = new WikilinkParser({slugifyFn: slugify}, deadLinks); + const parser = new WikilinkParser(opts, deadLinks); const parsed = parser.parseSingle('[[../a-blog-post.md]]', pageDirectory, '/blog/sub-dir/some-page'); t.is(parsed.isPath, true); @@ -149,7 +159,7 @@ test('parses relative path lookup (single back step)', t => { test('parses relative path lookup (multiple back step)', t => { const deadLinks = new Set(); - const parser = new WikilinkParser({slugifyFn: slugify}, deadLinks); + const parser = new WikilinkParser(opts, deadLinks); const parsed = parser.parseSingle('[[../../a-blog-post.md]]', pageDirectory, '/blog/sub-dir/sub-dir/some-page'); t.is(parsed.isPath, true); @@ -157,3 +167,29 @@ test('parses relative path lookup (multiple back step)', t => { t.is(parsed.title, 'Blog Post'); }) +test('throws error on failure to find resolvingFn', t => { + const parser = new WikilinkParser(opts, new Set()); + let errorMsg; + + try { + parser.parseSingle('[[fail:1234]]', pageDirectory, '/directory/filename'); + } catch (e) { + errorMsg = e.message; + } + + t.is(errorMsg, 'Unable to find resolving fn [fail] for wikilink [[fail:1234]] on page [/directory/filename]'); +}) + +test('sets resolvingFnName on finding resolvingFn', t => { + const parser = new WikilinkParser({ + slugifyFn: slugify, + resolvingFns: new Map([ + ['test', () => 'Hello World'] + ]), + }, new Set()); + + const link = parser.parseSingle('[[test:1234]]', pageDirectory, '/directory/filename'); + + t.is(link.resolvingFnName, 'test'); + t.is(link.name, '1234'); +}) From 02645915e857dada616f697f0ce5dbeb1f6b4015 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 8 May 2024 20:54:02 +0100 Subject: [PATCH 095/156] chore: remove dead code --- src/interlinker.js | 53 ---------------------------------------------- 1 file changed, 53 deletions(-) diff --git a/src/interlinker.js b/src/interlinker.js index 5539936..a5d6241 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -46,59 +46,6 @@ module.exports = class Interlinker { this.HTMLLinkParser = new HTMLLinkParser(this.deadLinks); } - /** - * Invokes the resolving function, passing it the linking page and the parsed link meta. - * @param {*} page - * @param {import('@photogabble/eleventy-plugin-interlinker').WikilinkMeta} link - * @return {Promise} - */ - async invokeResolvingFn(page, link) { - if (this.compiledEmbeds.has(link.link)) return; - if (!link.resolvingFnName) return; - - const fn = this.opts.resolvingFns.get(link.resolvingFnName); - // TODO: result can be string or object - const result = await fn(link, page); - - this.compiledEmbeds.set(link.link, result); - return result; - } - - /** - * Compiles the template associated with a WikiLink when invoked via the ![[embed syntax]] - * @param {*} data - * @param {import('@photogabble/eleventy-plugin-interlinker').WikilinkMeta} link - * @return {Promise} - */ - async compileTemplate(data, link) { - if (this.compiledEmbeds.has(link.link)) return; - - const frontMatter = data.template.frontMatter; - - const layout = (data.data.hasOwnProperty(this.opts.layoutKey)) - ? data.data[this.opts.layoutKey] - : this.opts.defaultLayout; - - const language = (data.data.hasOwnProperty(this.opts.layoutTemplateLangKey)) - ? data.data[this.opts.layoutTemplateLangKey] - : this.opts.defaultLayoutLang === null - ? data.page.templateSyntax - : this.opts.defaultLayoutLang; - - const tpl = layout === null - ? frontMatter.content - : `{% layout "${layout}" %} {% block content %} ${frontMatter.content} {% endblock %}`; - - const fn = await this.rm.compile(tpl, language, { - templateConfig: this.templateConfig, - extensionMap: this.extensionMap - }); - - const result = await fn({content: frontMatter.content, ...data.data}); - this.compiledEmbeds.set(link.link, result); - return result; - } - /** * This is a computed function that gets added to the global data of 11ty prompting its * invocation for every page. From 39c5d70f37284073a67b8c8ddeb767d359152dee Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 8 May 2024 20:54:14 +0100 Subject: [PATCH 096/156] doc: document types --- index.d.ts | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/index.d.ts b/index.d.ts index 20a68be..c98f4e2 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,3 +1,34 @@ +interface DeadLinks { + gravestones: Map> + fileSrc: string + + setFileSrc(fileSrc: string): void + + add(link: string): void + + report(): void +} + +interface Parser { + parseSingle(link: string, pageDirectory: PageDirectoryService, filePathStem: undefined | string): WikilinkMeta + + parseMultiple(link: string, pageDirectory: PageDirectoryService, filePathStem: undefined | string): Array + + find(document: string, pageDirectory: PageDirectoryService, filePathStem: undefined | string): Array +} + +interface Interlinker { + opts: EleventyPluginInterlinkOptions + deadLinks: DeadLinks + templateConfig: any + extensionMap: any + rm: any, + wikilinkParser: Parser & { wikiLinkRegExp: string } + HTMLLinkParser: Parser & { internalLinkRegex: string } + + compute(data: any): Promise> +} + type EleventyPluginInterlinkOptions = { // defaultLayout is the optional default layout you would like to use for wrapping your embeds. defaultLayout?: string, @@ -23,7 +54,7 @@ type EleventyPluginInterlinkOptions = { // resolvingFns is a list of resolving functions. These are invoked by a wikilink containing a `:` character // prefixed by the fn name. The page in this case is the linking page. - resolvingFns?: Map Promise>, + resolvingFns?: Map Promise>, } interface ErrorRenderFn { @@ -43,9 +74,9 @@ type LinkMeta = { // Data structure for wikilinks identified by WikiLinkParser. type WikilinkMeta = { - title: string|null + title: string | null name: string - anchor: string|null + anchor: string | null link: string slug: string isEmbed: boolean @@ -67,8 +98,9 @@ type WikilinkMeta = { } interface PageDirectoryService { - findByLink(link : WikilinkMeta|LinkMeta): {page: any, found: boolean, foundByAlias: boolean}; - findByFile(file : any): any; + findByLink(link: WikilinkMeta | LinkMeta): { page: any, found: boolean, foundByAlias: boolean }; + + findByFile(file: any): any; } export {EleventyPluginInterlinkOptions, SlugifyFn, WikilinkMeta, LinkMeta, PageDirectoryService}; From 9a4aca9109ccc881cdb2f360a8ba3b9112050507 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 8 May 2024 21:20:40 +0100 Subject: [PATCH 097/156] chore: fix typo --- src/find-page.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/find-page.js b/src/find-page.js index dd4ce11..ba7337d 100644 --- a/src/find-page.js +++ b/src/find-page.js @@ -18,7 +18,7 @@ const pageLookup = (allPages = [], slugifyFn) => { // 2. match file url to link href // 3. match file slug to link slug // 4. match file title to link identifier (name) - // 5. match fle based upon alias + // 5. match file based upon alias if (link.isPath) { return page.filePathStem === link.name; From a70a5138240bc6c1818bbb1c3e370cf332f58192 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 8 May 2024 21:21:09 +0100 Subject: [PATCH 098/156] chore: run resolving fn before mutating backlinks --- src/interlinker.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/interlinker.js b/src/interlinker.js index a5d6241..4b4f4e1 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -90,6 +90,11 @@ module.exports = class Interlinker { // its backlinks list. for (const link of outboundLinks) { + if (link.resolvingFnName) { + const fn = this.opts.resolvingFns.get(link.resolvingFnName); + link.content = await fn(link, currentPage, this); + } + // If the linked page exists we can add the linking page to its backlinks array if (link.exists) { if (!link.page.data.backlinks) link.page.data.backlinks = []; @@ -100,11 +105,6 @@ module.exports = class Interlinker { }); } } - - if (link.resolvingFnName) { - const fn = this.opts.resolvingFns.get(link.resolvingFnName); - link.content = await fn(link, currentPage, this); - } } // Block iteration until compilation complete. From 6e8ba593d2482b5e5905a153ccd9281bd4f4ab55 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 8 May 2024 21:21:50 +0100 Subject: [PATCH 099/156] doc(#19): document custom resolving functions --- README.md | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- index.d.ts | 2 +- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dc46965..c203822 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,11 @@ type EleventyPluginInterlinkOptions = { // slugifyFn is used to slugify strings. If a function // isn't set then the default 11ty slugify filter is used. - slugifyFn?: SlugifyFn + slugifyFn?: SlugifyFn, + + // resolvingFns contains functions used for resolving a wikilinks output. + // see the Custom Resolving Functions section below + resolvingFns?: Map Promise> } ``` @@ -116,6 +120,29 @@ eleventyComputed: --- ``` +### Custom Resolving Functions + +Custom resolving functions can be considered pluggable extensions to the wikilink lookup and rendering logic and can be invoked by usage of a `:` character in a wikilink prefixed by the functions name, for example: `[[issue:19]]`. + +These functions are added to the interlinker via its `resolvingFns` configuration options, for example: + +```javascript +const config = { + resolvingFns: new Map([ + ['howdy', (link, currentPage) => `Hello ${link.name}!`], + ['issue', (link, currentPage) => `#${link.name}`], + ]), +}; +``` + +When invoked the resolving function will be passed three arguments, the parsed Wikilink object (see _Wikilink Data Structure_ section below.) The linking page object from 11ty and the interlinker class instance. + +The plugin has three internal resolving functions which are defined only if not already via the plugin config: + +- `default`, this is the default resolving function and converts the Wikilink Data Structure directly into an HTML link +- `default-embed`, this is the default embed resolving function +- `404-embed`, this is invoked when the embed template is not found. This currently invokes the `unableToLocateEmbedFn` however, in a future version it will replace that config option entirely + ### Embedding Embedding files allows you to reuse content across your website while tracking what pages have used it. @@ -168,14 +195,51 @@ You can then display this information in any way you would like, I use the below {% endif %} ``` +### Page lookup logic + +This plugin will attempt to identify the page being linked using the following steps in order: + +1. if is path link, return `filePathStem` match state +2. match file url to link href +3. match file slug to link slug +4. match file title to link identifier (name) +5. match file based upon alias + ### Pages Excluded from Collections Due to how this plugin obtains a pages template content, all pages with `eleventyExcludeFromCollections:true` set will **NOT** be parsed by the interlinker. +## Wikilink Data Structure + +```typescript +type WikilinkMeta = { + title: string | null + name: string + anchor: string | null + link: string + slug: string + isEmbed: boolean + isPath: boolean + + // If linked page has been found in the all collection exists will be + // true and page will be the 11ty page object. + exists: boolean + page?: any + + // name of the resolving fn, if set it must exist + resolvingFnName?: string + // the resulting HTML of the resolving function + content?: string + + // href and path are loaded from the linked page + href?: string + path?: string +} +``` + ## Known Caveats - This plugin doesn't implement all [Obsidians wikilink support](https://help.obsidian.md/Linking+notes+and+files/Internal+links) for example linking to a block in a note and linking to a heading in a note is not currently supported by this plugin -- Doesn't identify regular internal links e.g `[Link](/some/file.md)` - Only supports embedding one note inside another, no other Obsidian file embedding functionality is currently supported by this plugin ## Roadmap diff --git a/index.d.ts b/index.d.ts index c98f4e2..42c470f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -54,7 +54,7 @@ type EleventyPluginInterlinkOptions = { // resolvingFns is a list of resolving functions. These are invoked by a wikilink containing a `:` character // prefixed by the fn name. The page in this case is the linking page. - resolvingFns?: Map Promise>, + resolvingFns?: Map Promise>, } interface ErrorRenderFn { From 459d1bd64df18f96a58a32cc6e641727464c74db Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 8 May 2024 21:26:13 +0100 Subject: [PATCH 100/156] doc(#19): update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d05ddb0..c58a2fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Add support for custom rendering functions (#47) - Add support for referencing files by path (#44) - Bugfix use alias as link text if it's the lookup source (#42) - Bugfix HTML encode link titles (#40) From 5c8f04c99128c5e233428b20e952c84af9733047 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 8 May 2024 21:38:38 +0100 Subject: [PATCH 101/156] refactor: re-order page lookup logic so title > slug --- README.md | 4 ++-- src/find-page.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c203822..d770d4c 100644 --- a/README.md +++ b/README.md @@ -201,8 +201,8 @@ This plugin will attempt to identify the page being linked using the following s 1. if is path link, return `filePathStem` match state 2. match file url to link href -3. match file slug to link slug -4. match file title to link identifier (name) +3. match file title to link identifier (name) +4. match file slug to link identifier (name) 5. match file based upon alias ### Pages Excluded from Collections diff --git a/src/find-page.js b/src/find-page.js index ba7337d..76b306d 100644 --- a/src/find-page.js +++ b/src/find-page.js @@ -28,7 +28,7 @@ const pageLookup = (allPages = [], slugifyFn) => { return true; } - if (page.fileSlug === link.slug || (page.data.title && page.data.title === link.name)) { + if ((page.data.title && page.data.title === link.name) || page.fileSlug === link.name ) { return true; } From 5000a7748333a4e1469cd96d8c1360af2dcf2890 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 8 May 2024 21:50:07 +0100 Subject: [PATCH 102/156] bugfix: when escaping # or :, make sure to remove the escape from wikilink name --- src/wikilink-parser.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/wikilink-parser.js b/src/wikilink-parser.js index 7f39ba5..1ed9a99 100644 --- a/src/wikilink-parser.js +++ b/src/wikilink-parser.js @@ -59,6 +59,8 @@ module.exports = class WikilinkParser { if (nameParts[0].at(-1) !== '/') { name = nameParts[0]; anchor = nameParts[1]; + } else { + name = name.replace('/#', '#'); } } @@ -96,6 +98,8 @@ module.exports = class WikilinkParser { if (parts[0].at(-1) !== '/') { fnName = parts[0]; name = parts[1]; + } else { + name = name.replace('/:', ':'); } } From 87aac8e2910adfad8301675026e2ae7c993ad60d Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 8 May 2024 21:56:41 +0100 Subject: [PATCH 103/156] refactor: remove internal dependency upon slugify --- index.d.ts | 10 +----- index.js | 6 ---- package-lock.json | 7 ++-- package.json | 3 +- src/find-page.js | 4 +-- src/interlinker.js | 7 +--- src/wikilink-parser.js | 8 ----- tests/markdown-wikilink-parser.test.js | 2 -- tests/markdown-wikilink-renderer.test.js | 5 +-- tests/wikilink-parser.test.js | 44 +++++++++++------------- 10 files changed, 28 insertions(+), 68 deletions(-) diff --git a/index.d.ts b/index.d.ts index 42c470f..80d428e 100644 --- a/index.d.ts +++ b/index.d.ts @@ -49,9 +49,6 @@ type EleventyPluginInterlinkOptions = { // slug that you are using. This defaults to a function that returns [UNABLE TO LOCATE EMBED]. unableToLocateEmbedFn?: ErrorRenderFn, - // slugifyFn is used to slugify strings. If a function isn't set then the default 11ty slugify filter is used. - slugifyFn?: SlugifyFn - // resolvingFns is a list of resolving functions. These are invoked by a wikilink containing a `:` character // prefixed by the fn name. The page in this case is the linking page. resolvingFns?: Map Promise>, @@ -61,10 +58,6 @@ interface ErrorRenderFn { (slug: string): string; } -interface SlugifyFn { - (input: string): string; -} - // Data structure for internal links identified by HTMLLinkParser. // This is a subset of WikilinkMeta. type LinkMeta = { @@ -78,7 +71,6 @@ type WikilinkMeta = { name: string anchor: string | null link: string - slug: string isEmbed: boolean isPath: boolean @@ -103,4 +95,4 @@ interface PageDirectoryService { findByFile(file: any): any; } -export {EleventyPluginInterlinkOptions, SlugifyFn, WikilinkMeta, LinkMeta, PageDirectoryService}; +export {EleventyPluginInterlinkOptions, WikilinkMeta, LinkMeta, PageDirectoryService}; diff --git a/index.js b/index.js index 2f43bc3..8aaf045 100644 --- a/index.js +++ b/index.js @@ -17,12 +17,6 @@ module.exports = function (eleventyConfig, options = {}) { defaultLayoutLang: null, layoutKey: 'embedLayout', layoutTemplateLangKey: 'embedLayoutLanguage', - slugifyFn: (input) => { - const slugify = eleventyConfig.getFilter('slugify'); - if (typeof slugify !== 'function') throw new Error('Unable to load slugify filter.'); - - return slugify(input); - }, resolvingFns: new Map(), }, options); diff --git a/package-lock.json b/package-lock.json index e991c41..c61b1fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@photogabble/eleventy-plugin-interlinker", - "version": "1.1.0-rc2", + "version": "1.1.0-rc3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@photogabble/eleventy-plugin-interlinker", - "version": "1.1.0-rc2", + "version": "1.1.0-rc3", "license": "MIT", "dependencies": { "chalk": "^4.1.1", @@ -17,8 +17,7 @@ "@11ty/eleventy": "^2.0.0", "ava": "^5.2.0", "c8": "^7.12.0", - "sinon": "^17.0.1", - "slugify": "^1.6.6" + "sinon": "^17.0.1" }, "engines": { "node": ">=16" diff --git a/package.json b/package.json index e914295..ccb1fdd 100644 --- a/package.json +++ b/package.json @@ -56,8 +56,7 @@ "@11ty/eleventy": "^2.0.0", "ava": "^5.2.0", "c8": "^7.12.0", - "sinon": "^17.0.1", - "slugify": "^1.6.6" + "sinon": "^17.0.1" }, "directories": { "test": "tests" diff --git a/src/find-page.js b/src/find-page.js index 76b306d..33a0d74 100644 --- a/src/find-page.js +++ b/src/find-page.js @@ -2,12 +2,10 @@ * Page Lookup Service: * This wraps the 11ty all pages collection providing two methods for finding pages. * - * @todo slugifyFn is no longer removed, remove * @param {Array} allPages - * @param {import('@photogabble/eleventy-plugin-interlinker').SlugifyFn} slugifyFn * @return {import('@photogabble/eleventy-plugin-interlinker').PageDirectoryService} */ -const pageLookup = (allPages = [], slugifyFn) => { +const pageLookup = (allPages = []) => { return { findByLink: (link) => { let foundByAlias = false; diff --git a/src/interlinker.js b/src/interlinker.js index 4b4f4e1..8074bd4 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -63,13 +63,8 @@ module.exports = class Interlinker { this.deadLinks.setFileSrc(data.page.inputPath); - const {slugifyFn} = this.opts; - const compilePromises = []; - const pageDirectory = pageLookup( - data.collections.all, - slugifyFn - ); + const pageDirectory = pageLookup(data.collections.all); const currentPage = pageDirectory.findByFile(data); if (!currentPage) return []; diff --git a/src/wikilink-parser.js b/src/wikilink-parser.js index 1ed9a99..968fa9e 100644 --- a/src/wikilink-parser.js +++ b/src/wikilink-parser.js @@ -12,7 +12,6 @@ module.exports = class WikilinkParser { */ constructor(opts, deadLinks) { this.opts = opts; - this.slugifyFn = opts.slugifyFn; this.deadLinks = deadLinks; // TODO: when 11ty is in serve mode, this cache should clear at the beginning of each build (#24) @@ -107,19 +106,12 @@ module.exports = class WikilinkParser { throw new Error(`Unable to find resolving fn [${fnName}] for wikilink ${link} on page [${filePathStem}]`); } - // TODO: is slugifying the name needed any more? We support wikilink ident being a page title, path or alias. - // there should be no reason why the ident can't be a slug and we can lookup based upon that as well - // without needing to slug it here... slug originally was used as a kind of id for lookup. That has - // mostly been refactored out. - const slug = this.slugifyFn(name); - /** @var {import('@photogabble/eleventy-plugin-interlinker').WikilinkMeta} */ const meta = { title: parts.length === 2 ? parts[1] : null, name, anchor, link, - slug, isEmbed, isPath, exists: false, diff --git a/tests/markdown-wikilink-parser.test.js b/tests/markdown-wikilink-parser.test.js index 949ee69..c6e301e 100644 --- a/tests/markdown-wikilink-parser.test.js +++ b/tests/markdown-wikilink-parser.test.js @@ -1,11 +1,9 @@ const WikilinkParser = require('../src/wikilink-parser'); const {wikilinkInlineRule} = require('../src/markdown-ext'); const {defaultResolvingFn} = require("../src/resolvers"); -const slugify = require('slugify'); const test = require('ava'); const opts = { - slugifyFn: (text) => slugify(text), resolvingFns: new Map([ ['default', defaultResolvingFn] ]), diff --git a/tests/markdown-wikilink-renderer.test.js b/tests/markdown-wikilink-renderer.test.js index 2c0d315..0283d6d 100644 --- a/tests/markdown-wikilink-renderer.test.js +++ b/tests/markdown-wikilink-renderer.test.js @@ -1,13 +1,10 @@ const {wikilinkInlineRule, wikilinkRenderRule} = require('../src/markdown-ext'); const WikilinkParser = require('../src/wikilink-parser'); const {normalize} = require('./helpers'); -const slugify = require('slugify'); const test = require('ava'); const fs = require('fs'); -const opts = { - slugifyFn: (text) => slugify(text), -}; +const opts = {}; test('inline rule correctly parses single wikilink', t => { const wikilinkParser = new WikilinkParser(opts, new Set); diff --git a/tests/wikilink-parser.test.js b/tests/wikilink-parser.test.js index 4149651..abbb11a 100644 --- a/tests/wikilink-parser.test.js +++ b/tests/wikilink-parser.test.js @@ -1,10 +1,8 @@ const WikilinkParser = require('../src/wikilink-parser'); const {defaultResolvingFn, defaultEmbedFn} = require("../src/resolvers"); const {pageLookup} = require("../src/find-page"); -const slugify = require("slugify"); const test = require('ava'); - const pageDirectory = pageLookup([ { inputPath: '/home/user/website/hello-world.md', @@ -22,10 +20,9 @@ const pageDirectory = pageLookup([ title: 'Blog Post', }, } -], slugify); +]); const opts = { - slugifyFn: (text) => slugify(text), resolvingFns: new Map([ ['default', defaultResolvingFn], ['default-embed', defaultEmbedFn], @@ -34,40 +31,40 @@ const opts = { test('parses wikilink', t => { const parser = new WikilinkParser(opts, new Set()); - t.like(parser.parseSingle('[[hello world]]', pageDirectory), { + t.like(parser.parseSingle('[[hello-world]]', pageDirectory), { title: 'Hello World, Title', anchor: null, - name: 'hello world', + name: 'hello-world', isEmbed: false }); }); test('parses wikilink with title', t => { const parser = new WikilinkParser(opts, new Set()); - t.like(parser.parseSingle('[[hello world|Howdy]]', pageDirectory), { + t.like(parser.parseSingle('[[hello-world|Howdy]]', pageDirectory), { title: 'Howdy', anchor: null, - name: 'hello world', + name: 'hello-world', isEmbed: false }); }); test('parses wikilink with anchor', t => { const parser = new WikilinkParser(opts, new Set()); - t.like(parser.parseSingle('[[hello world#heading one]]', pageDirectory), { + t.like(parser.parseSingle('[[hello-world#heading one]]', pageDirectory), { title: 'Hello World, Title', anchor: 'heading one', - name: 'hello world', + name: 'hello-world', isEmbed: false }); }); test('parses wikilink embed', t => { const parser = new WikilinkParser(opts, new Set()); - t.like(parser.parseSingle('![[hello world]]', pageDirectory), { + t.like(parser.parseSingle('![[hello-world]]', pageDirectory), { title: 'Hello World, Title', anchor: null, - name: 'hello world', + name: 'hello-world', isEmbed: true }); }); @@ -77,42 +74,42 @@ test('parses wikilinks with weird formatting', t => { const checks = [ { - str: '[[hello world]]', + str: '[[hello-world]]', result: { title: 'Hello World, Title', - name: 'hello world', + name: 'hello-world', isEmbed: false } }, { - str: '[[hello world|custom title]]', + str: '[[hello-world|custom title]]', result: { title: 'custom title', - name: 'hello world', + name: 'hello-world', isEmbed: false } }, { - str: '[[ hello world | custom title ]]', + str: '[[ hello-world | custom title ]]', result: { title: 'custom title', - name: 'hello world', + name: 'hello-world', isEmbed: false } }, { - str: '[[ hello world | custom title ]]', + str: '[[ hello-world | custom title ]]', result: { title: 'custom title', - name: 'hello world', + name: 'hello-world', isEmbed: false } }, { - str: '![[hello world]]', + str: '![[hello-world]]', result: { title: 'Hello World, Title', - name: 'hello world', + name: 'hello-world', isEmbed: true } }, @@ -129,7 +126,7 @@ test('populates dead links set', t => { const parser = new WikilinkParser(opts, deadLinks); t.is(deadLinks.size, 0); - parser.parseSingle('[[hello world]]', pageDirectory); + parser.parseSingle('[[hello-world]]', pageDirectory); t.is(deadLinks.size, 0); const invalid = parser.parseSingle('[[invalid]]', pageDirectory); @@ -182,7 +179,6 @@ test('throws error on failure to find resolvingFn', t => { test('sets resolvingFnName on finding resolvingFn', t => { const parser = new WikilinkParser({ - slugifyFn: slugify, resolvingFns: new Map([ ['test', () => 'Hello World'] ]), From 2eac79ec4f44350c7f8d7b35e30d1af0d343da06 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 8 May 2024 22:52:13 +0100 Subject: [PATCH 104/156] doc: update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c58a2fa..69c5fa1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Remove internal dependency upon slugify (#48) - Add support for custom rendering functions (#47) - Add support for referencing files by path (#44) - Bugfix use alias as link text if it's the lookup source (#42) From c0f8f7acd5aaed22cdd3be66b2e36d6e302835ba Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 8 May 2024 23:04:41 +0100 Subject: [PATCH 105/156] feat(#45): make dead link report visibility configurable --- index.d.ts | 3 ++ index.js | 5 +++- src/dead-links.js | 29 ++++++++++++------- .../eleventy.config.js | 3 ++ 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/index.d.ts b/index.d.ts index 80d428e..5456c17 100644 --- a/index.d.ts +++ b/index.d.ts @@ -49,6 +49,9 @@ type EleventyPluginInterlinkOptions = { // slug that you are using. This defaults to a function that returns [UNABLE TO LOCATE EMBED]. unableToLocateEmbedFn?: ErrorRenderFn, + // deadLinkReport is the desired output format of the dead link report, by default its set to 'console' + deadLinkReport?: 'console' | 'json' | 'none', + // resolvingFns is a list of resolving functions. These are invoked by a wikilink containing a `:` character // prefixed by the fn name. The page in this case is the linking page. resolvingFns?: Map Promise>, diff --git a/index.js b/index.js index 8aaf045..6e6bdbd 100644 --- a/index.js +++ b/index.js @@ -18,6 +18,7 @@ module.exports = function (eleventyConfig, options = {}) { layoutKey: 'embedLayout', layoutTemplateLangKey: 'embedLayoutLanguage', resolvingFns: new Map(), + deadLinkReport: 'console', }, options); // TODO: deprecate usage of unableToLocateEmbedFn in preference of using resolving fn @@ -48,7 +49,9 @@ module.exports = function (eleventyConfig, options = {}) { // anything. // TODO: 1.1.0 have this contain more details such as which file(s) are linking (#23) // TODO: 1.1.0 have this clear the interlinker cache so that next time 11ty builds its starting from fresh data! (#24) - eleventyConfig.on('eleventy.after', () => interlinker.deadLinks.report()); + eleventyConfig.on('eleventy.after', () => { + if (opts.deadLinkReport !== 'none') interlinker.deadLinks.report(opts.deadLinkReport) + }); // Teach Markdown-It how to display MediaWiki Links. eleventyConfig.amendLibrary('md', (md) => { diff --git a/src/dead-links.js b/src/dead-links.js index 34e4ebe..d3c36c5 100644 --- a/src/dead-links.js +++ b/src/dead-links.js @@ -25,17 +25,26 @@ module.exports = class DeadLinks { this.gravestones.set(link, names); } - report() { - for (const [link, files] of this.gravestones.entries()) { - console.warn( - chalk.blue('[@photogabble/wikilinks]'), - chalk.yellow('WARNING'), - `${(link.includes('href') ? 'Link' : 'Wikilink')} (${link}) found pointing to to non-existent page in:` - ); - - for (const file of files) { - console.warn(`\t- ${file}`); + /** + * @param {'console'|'json'} format + */ + report(format) { + if (format === 'console') { + for (const [link, files] of this.gravestones.entries()) { + console.warn( + chalk.blue('[@photogabble/wikilinks]'), + chalk.yellow('WARNING'), + `${(link.includes('href') ? 'Link' : 'Wikilink')} (${link}) found pointing to to non-existent page in:` + ); + + for (const file of files) { + console.warn(`\t- ${file}`); + } } + return; } + + const json = JSON.stringify(this.gravestones); + const n =1; } } diff --git a/tests/fixtures/sample-with-hash-in-title/eleventy.config.js b/tests/fixtures/sample-with-hash-in-title/eleventy.config.js index 9b10b5c..fd8d97b 100644 --- a/tests/fixtures/sample-with-hash-in-title/eleventy.config.js +++ b/tests/fixtures/sample-with-hash-in-title/eleventy.config.js @@ -1,6 +1,9 @@ module.exports = function (eleventyConfig) { eleventyConfig.addPlugin( require('../../../index.js'), + { + deadLinkReport: 'none' + } ); return { From a4c80d5737f33b276ff76ca9408e3eb42eaafb94 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 8 May 2024 23:10:20 +0100 Subject: [PATCH 106/156] bugfix: don't add link to dead links if custom resolving fn in use... ... it has access to the deadLinks class through the Interlinker class that gets passed to it and so can add the link itself if needed. --- src/wikilink-parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wikilink-parser.js b/src/wikilink-parser.js index 968fa9e..c08f1a0 100644 --- a/src/wikilink-parser.js +++ b/src/wikilink-parser.js @@ -131,7 +131,7 @@ module.exports = class WikilinkParser { meta.path = page.inputPath; meta.exists = true; meta.page = page; - } else { + } else if (['default', 'default-embed'].includes(fnName)) { // If this wikilink goes to a page that doesn't exist, add to deadLinks list and // update href for stub post. this.deadLinks.add(link); From 586a0a0e51df047f0c86e176ee73d5efb353daa8 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 8 May 2024 23:14:27 +0100 Subject: [PATCH 107/156] chore: remove now unused compiledEmbeds --- index.js | 8 +------ src/interlinker.js | 3 --- src/markdown-ext.js | 5 +---- tests/markdown-wikilink-renderer.test.js | 28 ++++-------------------- 4 files changed, 6 insertions(+), 38 deletions(-) diff --git a/index.js b/index.js index 8aaf045..263faf3 100644 --- a/index.js +++ b/index.js @@ -46,22 +46,16 @@ module.exports = function (eleventyConfig, options = {}) { // After 11ty has finished generating the site output a list of wikilinks that do not link to // anything. - // TODO: 1.1.0 have this contain more details such as which file(s) are linking (#23) // TODO: 1.1.0 have this clear the interlinker cache so that next time 11ty builds its starting from fresh data! (#24) eleventyConfig.on('eleventy.after', () => interlinker.deadLinks.report()); // Teach Markdown-It how to display MediaWiki Links. eleventyConfig.amendLibrary('md', (md) => { - // WikiLink Embed md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( interlinker.wikiLinkParser, )); - md.renderer.rules.inline_wikilink = wikilinkRenderRule( - interlinker.wikiLinkParser, - interlinker.compiledEmbeds, - opts - ); + md.renderer.rules.inline_wikilink = wikilinkRenderRule(); }); // Add outboundLinks computed global data, this is executed before the templates are compiled and diff --git a/src/interlinker.js b/src/interlinker.js index 8074bd4..e261aa5 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -30,9 +30,6 @@ module.exports = class Interlinker { // Set of WikiLinks pointing to non-existent pages this.deadLinks = new DeadLinks(); - // Map of WikiLinks that have triggered an embed compile - this.compiledEmbeds = new Map(); - // TODO: document this.templateConfig = undefined; this.extensionMap = undefined; diff --git a/src/markdown-ext.js b/src/markdown-ext.js index 11cc524..d261eef 100644 --- a/src/markdown-ext.js +++ b/src/markdown-ext.js @@ -47,12 +47,9 @@ const wikilinkInlineRule = (wikilinkParser) => (state, silent) => { }; /** - * @param {WikilinkParser} wikilinkParser - * @param { Map } compiledEmbeds - * @param { import('@photogabble/eleventy-plugin-interlinker').EleventyPluginInterlinkOptions } opts * @returns {(function(*, *): (string))|*} */ -const wikilinkRenderRule = (wikilinkParser, compiledEmbeds, opts) => (tokens, idx) => { +const wikilinkRenderRule = () => (tokens, idx) => { const {meta} = tokens[idx]; return meta.content; }; diff --git a/tests/markdown-wikilink-renderer.test.js b/tests/markdown-wikilink-renderer.test.js index 0283d6d..2fa9a30 100644 --- a/tests/markdown-wikilink-renderer.test.js +++ b/tests/markdown-wikilink-renderer.test.js @@ -8,7 +8,6 @@ const opts = {}; test('inline rule correctly parses single wikilink', t => { const wikilinkParser = new WikilinkParser(opts, new Set); - const compiledEmbeds = new Map; wikilinkParser.linkCache.set('[[wiki link]]', { title: 'Wiki Link', @@ -23,11 +22,7 @@ test('inline rule correctly parses single wikilink', t => { wikilinkParser )); - md.renderer.rules.inline_wikilink = wikilinkRenderRule( - wikilinkParser, - compiledEmbeds, - opts - ); + md.renderer.rules.inline_wikilink = wikilinkRenderRule(); t.is( "

Hello world, this is some text with a Wiki Link inside!

\n", @@ -37,7 +32,6 @@ test('inline rule correctly parses single wikilink', t => { test('inline rule correctly parses multiple wikilinks', t => { const wikilinkParser = new WikilinkParser(opts, new Set); - const compiledEmbeds = new Map; wikilinkParser.linkCache.set('[[wiki link]]', { title: 'Wiki Link', @@ -60,11 +54,7 @@ test('inline rule correctly parses multiple wikilinks', t => { wikilinkParser )); - md.renderer.rules.inline_wikilink = wikilinkRenderRule( - wikilinkParser, - compiledEmbeds, - opts - ); + md.renderer.rules.inline_wikilink = wikilinkRenderRule(); t.is( "

Hello world, this is some text with a Wiki Link inside! There is also Another Wiki Link in the same string.

\n", @@ -74,7 +64,6 @@ test('inline rule correctly parses multiple wikilinks', t => { test('inline rule correctly parses single embed', t => { const wikilinkParser = new WikilinkParser(opts, new Set); - const compiledEmbeds = new Map; wikilinkParser.linkCache.set('![[wiki-embed]]', { title: 'Wiki Embed', @@ -89,11 +78,7 @@ test('inline rule correctly parses single embed', t => { wikilinkParser )); - md.renderer.rules.inline_wikilink = wikilinkRenderRule( - wikilinkParser, - compiledEmbeds, - opts - ); + md.renderer.rules.inline_wikilink = wikilinkRenderRule(); t.is( md.render('Hello world this is a ![[wiki-embed]]'), @@ -103,7 +88,6 @@ test('inline rule correctly parses single embed', t => { test('inline rule correctly parses mixed wikilink and embed in multiline input', t => { const wikilinkParser = new WikilinkParser(opts, new Set); - const compiledEmbeds = new Map; wikilinkParser.linkCache.set('![[inline embed]]', { title: 'Inline Embed', @@ -142,11 +126,7 @@ test('inline rule correctly parses mixed wikilink and embed in multiline input', wikilinkParser )); - md.renderer.rules.inline_wikilink = wikilinkRenderRule( - wikilinkParser, - compiledEmbeds, - opts - ); + md.renderer.rules.inline_wikilink = wikilinkRenderRule(); const markdown = fs.readFileSync(__dirname + '/fixtures/multiline.md', {encoding:'utf8', flag:'r'}); const html = fs.readFileSync(__dirname + '/fixtures/multiline.html', {encoding:'utf8', flag:'r'}); From e2379926c83bc49560ccb41aea28d32a34373a5b Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 8 May 2024 23:17:59 +0100 Subject: [PATCH 108/156] chore: remove completed todo items --- src/html-link-parser.js | 2 +- src/interlinker.js | 2 -- src/wikilink-parser.js | 2 -- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/html-link-parser.js b/src/html-link-parser.js index 4389831..95044f4 100644 --- a/src/html-link-parser.js +++ b/src/html-link-parser.js @@ -1,7 +1,7 @@ module.exports = class HTMLLinkParser { /** - * This regex finds all html tags with an href that begins with / denoting they are internal links. + * This regex finds all html tags with a href that begins with / denoting they are internal links. * * @type {RegExp} */ diff --git a/src/interlinker.js b/src/interlinker.js index e261aa5..ad0ba6d 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -38,8 +38,6 @@ module.exports = class Interlinker { this.rm = new EleventyRenderPlugin.RenderManager(); this.wikiLinkParser = new WikilinkParser(opts, this.deadLinks); - - // TODO: pass through deadWikiLinks and have HTMLLinkParser look up pages this.HTMLLinkParser = new HTMLLinkParser(this.deadLinks); } diff --git a/src/wikilink-parser.js b/src/wikilink-parser.js index c08f1a0..5bdee0b 100644 --- a/src/wikilink-parser.js +++ b/src/wikilink-parser.js @@ -21,8 +21,6 @@ module.exports = class WikilinkParser { /** * Parses a single WikiLink into the link object understood by the Interlinker. * - * @todo add parsing of namespace (#14) - * * @param {string} link * @param {import('@photogabble/eleventy-plugin-interlinker').PageDirectoryService} pageDirectory * @param {string|undefined} filePathStem From 81a0439fac6c8731114c228ec7dc059c7b4ada8b Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 8 May 2024 23:22:16 +0100 Subject: [PATCH 109/156] chore: remove reference to slugifyFn --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index d770d4c..ded9572 100644 --- a/README.md +++ b/README.md @@ -40,10 +40,6 @@ type EleventyPluginInterlinkOptions = { // that returns [UNABLE TO LOCATE EMBED]. unableToLocateEmbedFn?: ErrorRenderFn, - // slugifyFn is used to slugify strings. If a function - // isn't set then the default 11ty slugify filter is used. - slugifyFn?: SlugifyFn, - // resolvingFns contains functions used for resolving a wikilinks output. // see the Custom Resolving Functions section below resolvingFns?: Map Promise> From 5cdf0deb6861da4f0bb83bc5665b751c595dc55b Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 8 May 2024 23:22:31 +0100 Subject: [PATCH 110/156] chore: tidy, remove dead code --- src/interlinker.js | 14 +------------- src/markdown-ext.js | 2 -- tests/find-page-service.test.js | 5 ++--- tests/html-internal-link-parser.test.js | 5 ++--- 4 files changed, 5 insertions(+), 21 deletions(-) diff --git a/src/interlinker.js b/src/interlinker.js index ad0ba6d..c96de1f 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -3,18 +3,6 @@ const WikilinkParser = require("./wikilink-parser"); const {EleventyRenderPlugin} = require("@11ty/eleventy"); const DeadLinks = require("./dead-links"); const {pageLookup} = require("./find-page"); -const entities = require("entities"); - -const defaultResolvingFn = async (currentPage, link) => { - const text = entities.encodeHTML(link.title ?? link.name); - let href = link.href; - - if (link.anchor) { - href = `${href}#${link.anchor}`; - } - - return `${text}`; -} /** * Interlinker: @@ -66,7 +54,7 @@ module.exports = class Interlinker { // TODO: 1.1.0 keep track of defined aliases and throw exception if duplicates are found (#46) - // Identify this pages outbound internal links both as wikilink _and_ regular html anchor tags. For each outlink + // Identify this pages outbound internal links both as wikilink _and_ regular html anchor tags. For each out-link // lookup the other page and add this to its backlinks data value. if (currentPage.template.frontMatter?.content) { const pageContent = currentPage.template.frontMatter.content; diff --git a/src/markdown-ext.js b/src/markdown-ext.js index d261eef..3801f4e 100644 --- a/src/markdown-ext.js +++ b/src/markdown-ext.js @@ -1,5 +1,3 @@ -const entities = require("entities"); - /** * This rule will be looped through an inline token by markdown-it. * diff --git a/tests/find-page-service.test.js b/tests/find-page-service.test.js index 7f2aedc..8eff7c0 100644 --- a/tests/find-page-service.test.js +++ b/tests/find-page-service.test.js @@ -1,6 +1,5 @@ -const test = require("ava"); const {pageLookup} = require("../src/find-page"); -const slugify = require("slugify"); +const test = require("ava"); const pageDirectory = pageLookup([ { @@ -27,7 +26,7 @@ const pageDirectory = pageLookup([ }, url: '/something/else/' } -], slugify); +]); test('pageLookup (find by href)', t => { const {page} = pageDirectory.findByLink({href: '/something/else', isEmbed: false}); diff --git a/tests/html-internal-link-parser.test.js b/tests/html-internal-link-parser.test.js index d8f0901..3784c6c 100644 --- a/tests/html-internal-link-parser.test.js +++ b/tests/html-internal-link-parser.test.js @@ -1,10 +1,9 @@ const HTMLLinkParser = require('../src/html-link-parser'); -const test = require('ava'); const DeadLinks = require("../src/dead-links"); const {pageLookup} = require("../src/find-page"); -const slugify = require("slugify"); +const test = require('ava'); -const pageDirectory = pageLookup([], slugify); +const pageDirectory = pageLookup([]); test('html link parser grabs multiple href, ignoring external links', t => { const parser = new HTMLLinkParser(new DeadLinks()); From 5f68271e8fcff34df3c608d653ac145c7f196f62 Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 9 May 2024 08:42:00 +0100 Subject: [PATCH 111/156] feat: write .dead-links.json if configured to do so --- src/dead-links.js | 15 ++++++++++++--- tests/eleventy.test.js | 14 +++++++++++++- .../eleventy.config.js | 1 + 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/dead-links.js b/src/dead-links.js index d3c36c5..0d099b5 100644 --- a/src/dead-links.js +++ b/src/dead-links.js @@ -1,6 +1,8 @@ +const path = require('node:path'); const chalk = require("chalk"); -module.exports = class DeadLinks { +const fs = require('node:fs'); +module.exports = class DeadLinks { constructor() { this.gravestones = new Map; this.fileSrc = 'unknown'; @@ -44,7 +46,14 @@ module.exports = class DeadLinks { return; } - const json = JSON.stringify(this.gravestones); - const n =1; + let obj = {}; + for (const [link, files] of this.gravestones.entries()) { + obj[link] = files; + } + + fs.writeFileSync( + path.join(process.env.ELEVENTY_ROOT, '.dead-links.json'), + JSON.stringify(obj) + ); } } diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index 89798db..547246f 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -1,13 +1,21 @@ const Eleventy = require("@11ty/eleventy"); const {normalize, consoleMockMessages, findResultByUrl, fixturePath} = require('./helpers'); -const test = require("ava"); +const fs = require('node:fs'); const sinon = require("sinon"); +const test = require("ava"); // NOTE: Tests using sinon to mock console.warn need to be run with // `test.serial` so that they don't run at the same time as one another to // avoid the "Attempted to wrap warn which is already wrapped" error. // @see https://stackoverflow.com/a/37900956/1225977 +test.afterEach(() => { + const deadLinksPathname = fixturePath('website-with-custom-resolving-fn/.dead-links.json'); + if (fs.existsSync(deadLinksPathname)) { + fs.rmSync(deadLinksPathname); + } +}) + test("Sample small Website (wikilinks and regular links)", async t => { let elev = new Eleventy(fixturePath('sample-small-website'), fixturePath('sample-small-website/_site'), { configPath: fixturePath('sample-small-website/eleventy.config.js'), @@ -217,10 +225,14 @@ test("Custom resolving functions are invoked", async t => { configPath: fixturePath('website-with-custom-resolving-fn/eleventy.config.js'), }); + t.false(fs.existsSync(fixturePath('website-with-custom-resolving-fn/.dead-links.json'))); + let results = await elev.toJSON(); t.is( normalize(findResultByUrl(results, '/').content), `

These wikilinks use custom resolving functions:

` ); + + t.true(fs.existsSync(fixturePath('website-with-custom-resolving-fn/.dead-links.json'))); }); diff --git a/tests/fixtures/website-with-custom-resolving-fn/eleventy.config.js b/tests/fixtures/website-with-custom-resolving-fn/eleventy.config.js index bef6a17..06f226f 100644 --- a/tests/fixtures/website-with-custom-resolving-fn/eleventy.config.js +++ b/tests/fixtures/website-with-custom-resolving-fn/eleventy.config.js @@ -6,6 +6,7 @@ module.exports = function (eleventyConfig) { ['howdy', (link, page) => `Hello ${link.name}!`], ['issue', (link, page) => `#${link.name}`], ]), + deadLinkReport: 'json', } ); From 92fcf728f6d5124f6685acdb86ce2a231d90dce5 Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 9 May 2024 08:47:33 +0100 Subject: [PATCH 112/156] doc(#45): document dead link report functionality --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index d770d4c..e037569 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,9 @@ type EleventyPluginInterlinkOptions = { // isn't set then the default 11ty slugify filter is used. slugifyFn?: SlugifyFn, + // deadLinkReport is the desired output format of the dead link report, by default its set to 'console' + deadLinkReport?: 'console' | 'json' | 'none', + // resolvingFns contains functions used for resolving a wikilinks output. // see the Custom Resolving Functions section below resolvingFns?: Map Promise> @@ -195,6 +198,12 @@ You can then display this information in any way you would like, I use the below {% endif %} ``` +### Dead link Report + +The default behaviour of this plugin is to report to the console every broken Wikilink and internal link. This behaviour is configurable via the `deadLinkReport` config option. This option accepts three values: `none`, `console` and `json` with `console` being the default. + +Setting the value to `none` will disable the dead link report while setting it to `json` will silence console output instead writing to `.dead-links.json` within the project root folder. + ### Page lookup logic This plugin will attempt to identify the page being linked using the following steps in order: From f234d335b9aad0565da6825bd4116f4650c4d4de Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 9 May 2024 08:53:04 +0100 Subject: [PATCH 113/156] doc(#45): update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69c5fa1..614a082 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Make dead link report configurable (#49) - Remove internal dependency upon slugify (#48) - Add support for custom rendering functions (#47) - Add support for referencing files by path (#44) From e6ddb866586728f8d192cc286e2a63e763fe3b50 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 10 May 2024 17:02:09 +0100 Subject: [PATCH 114/156] feat(#24): clear internal state before each 11ty build --- README.md | 4 ++-- index.js | 9 +++++++-- src/dead-links.js | 8 ++++++++ src/interlinker.js | 12 ++++++++++-- src/wikilink-parser.js | 7 +++---- tests/eleventy.test.js | 2 +- tests/html-internal-link-parser.test.js | 2 +- tests/markdown-wikilink-parser.test.js | 10 +++++----- tests/markdown-wikilink-renderer.test.js | 8 ++++---- tests/plugin.test.js | 2 +- tests/wikilink-parser.test.js | 22 +++++++++++----------- 11 files changed, 53 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 2e9f82d..58c9ac8 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ I use [Obsidian.md](https://obsidian.md/) to draft my posts before they are published on PhotoGabble. One feature of #Obsidian that I love is interlinking between notes and being able to see the connectivity graph of each note. -In January 2023 I wrote about how I [added Wiki Links support to Eleventy.js](https://www.photogabble.co.uk/noteworthy/adding-wiki-links-to-11ty/) and in doing so this plugin was borne. It has since been updated to include support for Obsidians [embedding files](https://help.obsidian.md/Linking+notes+and+files/Embedding+files). +In January 2023 I wrote about how I [added Wiki Links support to Eleventy.js](https://www.photogabble.co.uk/noteworthy/adding-wiki-links-to-11ty/) and in doing so this plugin was borne. It has since been updated to include support for Obsidian's [embedding files](https://help.obsidian.md/Linking+notes+and+files/Embedding+files). ## Install @@ -244,7 +244,7 @@ type WikilinkMeta = { ## Known Caveats -- This plugin doesn't implement all [Obsidians wikilink support](https://help.obsidian.md/Linking+notes+and+files/Internal+links) for example linking to a block in a note and linking to a heading in a note is not currently supported by this plugin +- This plugin doesn't implement all [Obsidian's wikilink support](https://help.obsidian.md/Linking+notes+and+files/Internal+links) for example linking to a block in a note and linking to a heading in a note is not currently supported by this plugin - Only supports embedding one note inside another, no other Obsidian file embedding functionality is currently supported by this plugin ## Roadmap diff --git a/index.js b/index.js index a97b47d..fa05b88 100644 --- a/index.js +++ b/index.js @@ -47,9 +47,14 @@ module.exports = function (eleventyConfig, options = {}) { // After 11ty has finished generating the site output a list of wikilinks that do not link to // anything. - // TODO: 1.1.0 have this clear the interlinker cache so that next time 11ty builds its starting from fresh data! (#24) eleventyConfig.on('eleventy.after', () => { - if (opts.deadLinkReport !== 'none') interlinker.deadLinks.report(opts.deadLinkReport) + if (opts.deadLinkReport !== 'none') interlinker.deadLinks.report(opts.deadLinkReport); + }); + + // Reset the internal state of the interlinker if running in watch mode, this stops + // the interlinker from working against out of date data. + eleventyConfig.on('eleventy.beforeWatch', () => { + interlinker.reset(); }); // Teach Markdown-It how to display MediaWiki Links. diff --git a/src/dead-links.js b/src/dead-links.js index 0d099b5..1326d21 100644 --- a/src/dead-links.js +++ b/src/dead-links.js @@ -56,4 +56,12 @@ module.exports = class DeadLinks { JSON.stringify(obj) ); } + + /** + * Reset to initial state + */ + clear() { + this.fileSrc = 'unknown'; + this.gravestones.clear(); + } } diff --git a/src/interlinker.js b/src/interlinker.js index c96de1f..72684bc 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -15,9 +15,12 @@ module.exports = class Interlinker { constructor(opts) { this.opts = opts - // Set of WikiLinks pointing to non-existent pages + // Map of WikiLinks pointing to non-existent pages this.deadLinks = new DeadLinks(); + // Map of Wikilink Meta that have been resolved by the WikilinkParser + this.linkCache = new Map(); + // TODO: document this.templateConfig = undefined; this.extensionMap = undefined; @@ -25,10 +28,15 @@ module.exports = class Interlinker { // TODO: document this.rm = new EleventyRenderPlugin.RenderManager(); - this.wikiLinkParser = new WikilinkParser(opts, this.deadLinks); + this.wikiLinkParser = new WikilinkParser(opts, this.deadLinks, this.linkCache); this.HTMLLinkParser = new HTMLLinkParser(this.deadLinks); } + reset() { + this.deadLinks.clear(); + this.linkCache.clear(); + } + /** * This is a computed function that gets added to the global data of 11ty prompting its * invocation for every page. diff --git a/src/wikilink-parser.js b/src/wikilink-parser.js index 5bdee0b..83c1fee 100644 --- a/src/wikilink-parser.js +++ b/src/wikilink-parser.js @@ -9,13 +9,12 @@ module.exports = class WikilinkParser { /** * @param { import('@photogabble/eleventy-plugin-interlinker').EleventyPluginInterlinkOptions } opts * @param { DeadLinks } deadLinks + * @param { Map } linkCache */ - constructor(opts, deadLinks) { + constructor(opts, deadLinks, linkCache) { this.opts = opts; this.deadLinks = deadLinks; - - // TODO: when 11ty is in serve mode, this cache should clear at the beginning of each build (#24) - this.linkCache = new Map(); + this.linkCache = linkCache; } /** diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index 547246f..b6764b2 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -62,7 +62,7 @@ test("Sample small Website (path links)", async t => { ); }); -test("Sample small Website (htmlenteties)", async t => { +test("Sample small Website (html entities)", async t => { let elev = new Eleventy(fixturePath('sample-small-website'), fixturePath('sample-small-website/_site'), { configPath: fixturePath('sample-small-website/eleventy.config.js'), }); diff --git a/tests/html-internal-link-parser.test.js b/tests/html-internal-link-parser.test.js index 3784c6c..ae16124 100644 --- a/tests/html-internal-link-parser.test.js +++ b/tests/html-internal-link-parser.test.js @@ -7,7 +7,7 @@ const pageDirectory = pageLookup([]); test('html link parser grabs multiple href, ignoring external links', t => { const parser = new HTMLLinkParser(new DeadLinks()); - const links = parser.find('

Hello world this is a link home and this is a link somewhere

The following link should be ignored example.com.

', pageDirectory); + const links = parser.find('

Hello world this is a link home and this is a link somewhere

The following link should be ignored example.com.

', pageDirectory); t.is(2, links.length); diff --git a/tests/markdown-wikilink-parser.test.js b/tests/markdown-wikilink-parser.test.js index c6e301e..79f9fa7 100644 --- a/tests/markdown-wikilink-parser.test.js +++ b/tests/markdown-wikilink-parser.test.js @@ -10,7 +10,7 @@ const opts = { }; test('inline rule correctly parses single wikilink', t => { - const wikilinkParser = new WikilinkParser(opts, new Set); + const wikilinkParser = new WikilinkParser(opts, new Set(), new Map()); wikilinkParser.linkCache.set('[[wiki link]]', { title: 'Wiki link', href: '/test/', @@ -31,7 +31,7 @@ test('inline rule correctly parses single wikilink', t => { }); test('inline rule correctly parses multiple wikilink', t => { - const wikilinkParser = new WikilinkParser(opts, new Set); + const wikilinkParser = new WikilinkParser(opts, new Set(), new Map()); wikilinkParser.linkCache.set('[[wiki links]]', { title: 'Wiki link', slug: 'wiki-links', @@ -61,7 +61,7 @@ test('inline rule correctly parses multiple wikilink', t => { }); test('inline rule correctly parses single wikilink embed', t => { - const wikilinkParser = new WikilinkParser(opts, new Set); + const wikilinkParser = new WikilinkParser(opts, new Set(), new Map()); wikilinkParser.linkCache.set('![[wiki link embed]]', { title: 'wiki link embed', slug: 'wiki-link-embed', @@ -83,7 +83,7 @@ test('inline rule correctly parses single wikilink embed', t => { }); test('inline rule correctly parses multiple wikilink embeds', t => { - const wikilinkParser = new WikilinkParser(opts, new Set); + const wikilinkParser = new WikilinkParser(opts, new Set(), new Map()); wikilinkParser.linkCache.set('![[wiki link embeds]]', { title: 'wiki link embed', slug: 'wiki-link-embed', @@ -111,7 +111,7 @@ test('inline rule correctly parses multiple wikilink embeds', t => { }); test('inline rule correctly parses mixed wikilink and wikilink embeds', t => { - const wikilinkParser = new WikilinkParser(opts, new Set); + const wikilinkParser = new WikilinkParser(opts, new Set(), new Map()); wikilinkParser.linkCache.set('![[wiki link embeds]]', { title: 'wiki link embeds', slug: 'wiki-link-embeds', diff --git a/tests/markdown-wikilink-renderer.test.js b/tests/markdown-wikilink-renderer.test.js index 2fa9a30..37d3b59 100644 --- a/tests/markdown-wikilink-renderer.test.js +++ b/tests/markdown-wikilink-renderer.test.js @@ -7,7 +7,7 @@ const fs = require('fs'); const opts = {}; test('inline rule correctly parses single wikilink', t => { - const wikilinkParser = new WikilinkParser(opts, new Set); + const wikilinkParser = new WikilinkParser(opts, new Set(), new Map()); wikilinkParser.linkCache.set('[[wiki link]]', { title: 'Wiki Link', @@ -31,7 +31,7 @@ test('inline rule correctly parses single wikilink', t => { }); test('inline rule correctly parses multiple wikilinks', t => { - const wikilinkParser = new WikilinkParser(opts, new Set); + const wikilinkParser = new WikilinkParser(opts, new Set(), new Map()); wikilinkParser.linkCache.set('[[wiki link]]', { title: 'Wiki Link', @@ -63,7 +63,7 @@ test('inline rule correctly parses multiple wikilinks', t => { }); test('inline rule correctly parses single embed', t => { - const wikilinkParser = new WikilinkParser(opts, new Set); + const wikilinkParser = new WikilinkParser(opts, new Set(), new Map()); wikilinkParser.linkCache.set('![[wiki-embed]]', { title: 'Wiki Embed', @@ -87,7 +87,7 @@ test('inline rule correctly parses single embed', t => { }); test('inline rule correctly parses mixed wikilink and embed in multiline input', t => { - const wikilinkParser = new WikilinkParser(opts, new Set); + const wikilinkParser = new WikilinkParser(opts, new Set(), new Map()); wikilinkParser.linkCache.set('![[inline embed]]', { title: 'Inline Embed', diff --git a/tests/plugin.test.js b/tests/plugin.test.js index ad7b018..382c7b6 100644 --- a/tests/plugin.test.js +++ b/tests/plugin.test.js @@ -40,7 +40,7 @@ test('hooks into eleventy.config', t => { plugin(eleventyMock); t.true(eleventyMock.wasCalled()); - t.is(eleventyMock.calls.get('on'), 3); // eleventy.config, eleventy.extensionmap, eleventy.after + t.is(eleventyMock.calls.get('on'), 4); // eleventy.config, eleventy.extensionmap, eleventy.after, eleventy.beforeWatch t.is(eleventyMock.calls.get('amendLibrary'), 1); // Adding Markdown-it ext t.is(eleventyMock.calls.get('addGlobalData'), 1); // Adding global eleventyComputed data }); diff --git a/tests/wikilink-parser.test.js b/tests/wikilink-parser.test.js index abbb11a..405b452 100644 --- a/tests/wikilink-parser.test.js +++ b/tests/wikilink-parser.test.js @@ -30,7 +30,7 @@ const opts = { }; test('parses wikilink', t => { - const parser = new WikilinkParser(opts, new Set()); + const parser = new WikilinkParser(opts, new Set(), new Map()); t.like(parser.parseSingle('[[hello-world]]', pageDirectory), { title: 'Hello World, Title', anchor: null, @@ -40,7 +40,7 @@ test('parses wikilink', t => { }); test('parses wikilink with title', t => { - const parser = new WikilinkParser(opts, new Set()); + const parser = new WikilinkParser(opts, new Set(), new Map()); t.like(parser.parseSingle('[[hello-world|Howdy]]', pageDirectory), { title: 'Howdy', anchor: null, @@ -50,7 +50,7 @@ test('parses wikilink with title', t => { }); test('parses wikilink with anchor', t => { - const parser = new WikilinkParser(opts, new Set()); + const parser = new WikilinkParser(opts, new Set(), new Map()); t.like(parser.parseSingle('[[hello-world#heading one]]', pageDirectory), { title: 'Hello World, Title', anchor: 'heading one', @@ -60,7 +60,7 @@ test('parses wikilink with anchor', t => { }); test('parses wikilink embed', t => { - const parser = new WikilinkParser(opts, new Set()); + const parser = new WikilinkParser(opts, new Set(), new Map()); t.like(parser.parseSingle('![[hello-world]]', pageDirectory), { title: 'Hello World, Title', anchor: null, @@ -70,7 +70,7 @@ test('parses wikilink embed', t => { }); test('parses wikilinks with weird formatting', t => { - const parser = new WikilinkParser(opts, new Set()); + const parser = new WikilinkParser(opts, new Set(), new Map()); const checks = [ { @@ -123,7 +123,7 @@ test('parses wikilinks with weird formatting', t => { test('populates dead links set', t => { const deadLinks = new Set(); - const parser = new WikilinkParser(opts, deadLinks); + const parser = new WikilinkParser(opts, deadLinks, new Map()); t.is(deadLinks.size, 0); parser.parseSingle('[[hello-world]]', pageDirectory); @@ -136,7 +136,7 @@ test('populates dead links set', t => { test('parses path lookup', t => { const deadLinks = new Set(); - const parser = new WikilinkParser(opts, deadLinks); + const parser = new WikilinkParser(opts, deadLinks, new Map()); const parsed = parser.parseSingle('[[/blog/a-blog-post.md]]', pageDirectory); t.is(parsed.isPath, true); @@ -146,7 +146,7 @@ test('parses path lookup', t => { test('parses relative path lookup (single back step)', t => { const deadLinks = new Set(); - const parser = new WikilinkParser(opts, deadLinks); + const parser = new WikilinkParser(opts, deadLinks, new Map()); const parsed = parser.parseSingle('[[../a-blog-post.md]]', pageDirectory, '/blog/sub-dir/some-page'); t.is(parsed.isPath, true); @@ -156,7 +156,7 @@ test('parses relative path lookup (single back step)', t => { test('parses relative path lookup (multiple back step)', t => { const deadLinks = new Set(); - const parser = new WikilinkParser(opts, deadLinks); + const parser = new WikilinkParser(opts, deadLinks, new Map()); const parsed = parser.parseSingle('[[../../a-blog-post.md]]', pageDirectory, '/blog/sub-dir/sub-dir/some-page'); t.is(parsed.isPath, true); @@ -165,7 +165,7 @@ test('parses relative path lookup (multiple back step)', t => { }) test('throws error on failure to find resolvingFn', t => { - const parser = new WikilinkParser(opts, new Set()); + const parser = new WikilinkParser(opts, new Set(), new Map()); let errorMsg; try { @@ -182,7 +182,7 @@ test('sets resolvingFnName on finding resolvingFn', t => { resolvingFns: new Map([ ['test', () => 'Hello World'] ]), - }, new Set()); + }, new Set(), new Map()); const link = parser.parseSingle('[[test:1234]]', pageDirectory, '/directory/filename'); From aac0cf89380f7763c2beb740aa448e05e29f0524 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 10 May 2024 17:04:21 +0100 Subject: [PATCH 115/156] doc(#24): update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 614a082..5429cfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Clear internal state before each 11ty build (#51) - Make dead link report configurable (#49) - Remove internal dependency upon slugify (#48) - Add support for custom rendering functions (#47) From eb019a3da186ea9ba2fc4444f6d116ba02f4d2be Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 10 May 2024 20:24:11 +0100 Subject: [PATCH 116/156] feat(#50): when lookup fn not found, only error if page also not found --- src/wikilink-parser.js | 70 +++++++++---------- tests/eleventy.test.js | 27 ++++++- .../_layouts/default.liquid | 2 + .../eleventy.config.js | 12 ++++ .../website-with-broken-resolving-fn/index.md | 6 ++ .../website-with-custom-resolving-fn/index.md | 1 + .../php-space-mines-introduction.md | 6 ++ 7 files changed, 84 insertions(+), 40 deletions(-) create mode 100644 tests/fixtures/website-with-broken-resolving-fn/_layouts/default.liquid create mode 100644 tests/fixtures/website-with-broken-resolving-fn/eleventy.config.js create mode 100644 tests/fixtures/website-with-broken-resolving-fn/index.md create mode 100644 tests/fixtures/website-with-custom-resolving-fn/php-space-mines-introduction.md diff --git a/src/wikilink-parser.js b/src/wikilink-parser.js index 83c1fee..e04e7ea 100644 --- a/src/wikilink-parser.js +++ b/src/wikilink-parser.js @@ -37,9 +37,19 @@ module.exports = class WikilinkParser { // defining the link text prefixed by a | character, e.g. `[[ ident | custom link text ]]` const parts = link.slice((isEmbed ? 3 : 2), -2).split("|").map(part => part.trim()); - // Strip .md and .markdown extensions from the file ident; this is so it can be used for filePathStem match - // if path lookup. - let name = parts[0].replace(/.(md|markdown)\s?$/i, ""); + /** @var {import('@photogabble/eleventy-plugin-interlinker').WikilinkMeta} */ + const meta = { + title: parts.length === 2 ? parts[1] : null, + // Strip .md and .markdown extensions from the file ident; this is so it can be used for + // filePathStem match if path lookup. + name: parts[0].replace(/.(md|markdown)\s?$/i, ""), + anchor: null, + link, + isEmbed, + isPath: false, + exists: false, + resolvingFnName: isEmbed ? 'default-embed' : 'default', + }; //// // Anchor link identification: @@ -47,34 +57,33 @@ module.exports = class WikilinkParser { // An anchor link can be referenced by a # character in the file ident, e.g. `[[ ident#anchor-id ]]`. // // This supports escaping by prefixing the # with a /, e.g `[[ Page about C/# ]]` - let anchor = null; - if (name.includes('#')) { + if (meta.name.includes('#')) { const nameParts = parts[0].split('#').map(part => part.trim()); // Allow for escaping a # when prefixed with a / if (nameParts[0].at(-1) !== '/') { - name = nameParts[0]; - anchor = nameParts[1]; + meta.name = nameParts[0]; + meta.anchor = nameParts[1]; } else { - name = name.replace('/#', '#'); + meta.name = meta.name.replace('/#', '#'); } } //// // Path link identification: // This supports both relative links from the linking files path and lookup from the project root path. - const isPath = (name.startsWith('/') || name.startsWith('../') || name.startsWith('./')); + meta.isPath = (meta.name.startsWith('/') || meta.name.startsWith('../') || meta.name.startsWith('./')); // This is a relative path lookup, need to mutate name so that its absolute path from project // root so that we can match it on a pages filePathStem. - if (isPath && name.startsWith('.')) { + if (meta.isPath && meta.name.startsWith('.')) { if (!filePathStem) throw new Error('Unable to do relative path lookup of wikilink.'); const cwd = filePathStem.split('/'); - const relative = name.split('/'); + const relative = meta.name.split('/'); const stepsBack = relative.filter(file => file === '..').length; - name = [ + meta.name = [ ...cwd.slice(0, -(stepsBack + 1)), ...relative.filter(file => file !== '..' && file !== '.') ].join('/'); @@ -85,37 +94,22 @@ module.exports = class WikilinkParser { // If the author has referenced a custom resolving function via inclusion of the `:` character // then we use that one. Otherwise, use the default resolving functions. // As with anchor links, this supports escaping the `:` character by prefixing with `/` - let fnName = isEmbed - ? 'default-embed' - : 'default' - if (name.includes(':')) { - const parts = name.split(':').map(part => part.trim()); + if (meta.name.includes(':')) { + const parts = meta.name.split(':').map(part => part.trim()); if (parts[0].at(-1) !== '/') { - fnName = parts[0]; - name = parts[1]; + if (!this.opts.resolvingFns || this.opts.resolvingFns.has(parts[0]) === false) { + const {found} = pageDirectory.findByLink(meta); + if (!found) throw new Error(`Unable to find resolving fn [${parts[0]}] for wikilink ${link} on page [${filePathStem}]`); + } else { + meta.resolvingFnName = parts[0]; + meta.name = parts[1]; + } } else { - name = name.replace('/:', ':'); + meta.name = meta.name.replace('/:', ':'); } } - if (!this.opts.resolvingFns || this.opts.resolvingFns.has(fnName) === false) { - throw new Error(`Unable to find resolving fn [${fnName}] for wikilink ${link} on page [${filePathStem}]`); - } - - /** @var {import('@photogabble/eleventy-plugin-interlinker').WikilinkMeta} */ - const meta = { - title: parts.length === 2 ? parts[1] : null, - name, - anchor, - link, - isEmbed, - isPath, - exists: false, - } - - if (fnName) meta.resolvingFnName = fnName; - // Lookup page data from 11ty's collection to obtain url and title if currently null const {page, foundByAlias} = pageDirectory.findByLink(meta); if (page) { @@ -128,7 +122,7 @@ module.exports = class WikilinkParser { meta.path = page.inputPath; meta.exists = true; meta.page = page; - } else if (['default', 'default-embed'].includes(fnName)) { + } else if (['default', 'default-embed'].includes(meta.resolvingFnName)) { // If this wikilink goes to a page that doesn't exist, add to deadLinks list and // update href for stub post. this.deadLinks.add(link); diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index b6764b2..2bb9e20 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -220,19 +220,42 @@ test("Permalink should be used for link href", async t => { ); }); -test("Custom resolving functions are invoked", async t => { +/** + * This test checks that custom resolving functions are invoked if they exist, and if not + * an exception isn't thrown if the page can be looked up (see issue #50). + */ +test("Custom resolving functions (are invoked)", async t => { let elev = new Eleventy(fixturePath('website-with-custom-resolving-fn'), fixturePath('website-with-custom-resolving-fn/_site'), { configPath: fixturePath('website-with-custom-resolving-fn/eleventy.config.js'), }); + // TODO: move this to another test t.false(fs.existsSync(fixturePath('website-with-custom-resolving-fn/.dead-links.json'))); let results = await elev.toJSON(); t.is( normalize(findResultByUrl(results, '/').content), - `

These wikilinks use custom resolving functions:

` + `

These wikilinks use custom resolving functions:

` ); t.true(fs.existsSync(fixturePath('website-with-custom-resolving-fn/.dead-links.json'))); }); + +/** + * This test must be run serially as the exception being thrown appears to interfere + * with other tests. + */ +test.serial("Custom resolving functions (throw exception on not found)", async t => { + let elev = new Eleventy(fixturePath('website-with-broken-resolving-fn'), fixturePath('website-with-broken-resolving-fn/_site'), { + configPath: fixturePath('website-with-broken-resolving-fn/eleventy.config.js'), + }); + + // Disable the console log output of 11tys error handler + const errorHandler = elev.errorHandler; + let fatalCalled = false; + errorHandler.log = () => {}; + + const error = await t.throwsAsync(elev.toJSON()); + t.is(error.message, 'Unable to find resolving fn [PHP Space Mines] for wikilink [[PHP Space Mines: Introduction|Moon Miner]] on page [/index]'); +}); diff --git a/tests/fixtures/website-with-broken-resolving-fn/_layouts/default.liquid b/tests/fixtures/website-with-broken-resolving-fn/_layouts/default.liquid new file mode 100644 index 0000000..9065374 --- /dev/null +++ b/tests/fixtures/website-with-broken-resolving-fn/_layouts/default.liquid @@ -0,0 +1,2 @@ +
{{ content }}
+
{%- for link in backlinks %}{{ link.title }}{%- endfor %}
diff --git a/tests/fixtures/website-with-broken-resolving-fn/eleventy.config.js b/tests/fixtures/website-with-broken-resolving-fn/eleventy.config.js new file mode 100644 index 0000000..37bf8a7 --- /dev/null +++ b/tests/fixtures/website-with-broken-resolving-fn/eleventy.config.js @@ -0,0 +1,12 @@ +module.exports = function (eleventyConfig) { + eleventyConfig.addPlugin( + require('../../../index.js') + ); + + return { + dir: { + includes: "_includes", + layouts: "_layouts", + } + } +} diff --git a/tests/fixtures/website-with-broken-resolving-fn/index.md b/tests/fixtures/website-with-broken-resolving-fn/index.md new file mode 100644 index 0000000..424d50f --- /dev/null +++ b/tests/fixtures/website-with-broken-resolving-fn/index.md @@ -0,0 +1,6 @@ +--- +title: Homepage +layout: default.liquid +--- + +This will cause an exception [[PHP Space Mines: Introduction|Moon Miner]] diff --git a/tests/fixtures/website-with-custom-resolving-fn/index.md b/tests/fixtures/website-with-custom-resolving-fn/index.md index 42ebeb8..c2b422b 100644 --- a/tests/fixtures/website-with-custom-resolving-fn/index.md +++ b/tests/fixtures/website-with-custom-resolving-fn/index.md @@ -8,3 +8,4 @@ These wikilinks use custom resolving functions: - [[howdy:RWC]] - [[issue:19]] +- [[PHP Space Mines: Introduction|Moon Miner]] diff --git a/tests/fixtures/website-with-custom-resolving-fn/php-space-mines-introduction.md b/tests/fixtures/website-with-custom-resolving-fn/php-space-mines-introduction.md new file mode 100644 index 0000000..60fa40e --- /dev/null +++ b/tests/fixtures/website-with-custom-resolving-fn/php-space-mines-introduction.md @@ -0,0 +1,6 @@ +--- +title: 'PHP Space Mines: Introduction' +layout: default.liquid +--- + +Hello World From a6f643b90a2679fda64522e9220c8f6f345b632f Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 10 May 2024 20:28:59 +0100 Subject: [PATCH 117/156] dov(#50): update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5429cfb..cdac6b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- On resolving fn lookup failure, only throw error if page not found (#52) - Clear internal state before each 11ty build (#51) - Make dead link report configurable (#49) - Remove internal dependency upon slugify (#48) From c3bc05904b1708de6797eb22a9f03df162244c9a Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 10 May 2024 20:38:01 +0100 Subject: [PATCH 118/156] chore: tidy --- tests/eleventy.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index 2bb9e20..186baf3 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -253,7 +253,6 @@ test.serial("Custom resolving functions (throw exception on not found)", async t // Disable the console log output of 11tys error handler const errorHandler = elev.errorHandler; - let fatalCalled = false; errorHandler.log = () => {}; const error = await t.throwsAsync(elev.toJSON()); From e8074a82d850af000bbf12cb0ada28fea81f1c1a Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 10 May 2024 20:39:16 +0100 Subject: [PATCH 119/156] feat: v1.1.0-rc4 next release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ccb1fdd..c59c7ba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@photogabble/eleventy-plugin-interlinker", - "version": "1.1.0-rc3", + "version": "1.1.0-rc4", "description": "Obsidian WikiLinks, BackLinks and Embed support for 11ty", "keywords": [ "11ty", From 3a75e6b54005102c6daab324553432a6136bdd76 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 10 May 2024 22:12:31 +0100 Subject: [PATCH 120/156] bugfix(#54): update regex to not match on new line --- src/wikilink-parser.js | 2 +- .../fixtures/website-with-permalink/hello.md | 36 +++++++++++++++++++ tests/wikilink-parser.test.js | 8 +++++ 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/website-with-permalink/hello.md diff --git a/src/wikilink-parser.js b/src/wikilink-parser.js index e04e7ea..d9779d2 100644 --- a/src/wikilink-parser.js +++ b/src/wikilink-parser.js @@ -4,7 +4,7 @@ module.exports = class WikilinkParser { * * @type {RegExp} */ - wikiLinkRegExp = /(? { + const parts = match.raw.slice(2, -2).split("|"); + const slug = slugify(parts[0].replace(/.(md|markdown)\s?$/i, "").trim()); + const found = linkMapCache.get(slug); + + if (!found) throw new Error(`Unable to find page linked by wikilink slug [${slug}]`) + + match.text = parts.length === 2 + ? parts[1] + : found.title; + + match.url = found.permalink.substring(0,1) === '/' + ? found.permalink + : `/${found.permalink}`; + } + }) +}; +``` +{% endraw %} + +[[ test ]] diff --git a/tests/wikilink-parser.test.js b/tests/wikilink-parser.test.js index 405b452..aa7c794 100644 --- a/tests/wikilink-parser.test.js +++ b/tests/wikilink-parser.test.js @@ -189,3 +189,11 @@ test('sets resolvingFnName on finding resolvingFn', t => { t.is(link.resolvingFnName, 'test'); t.is(link.name, '1234'); }) + +test('regex does not match on whitespace', t => { + const deadLinks = new Set(); + const parser = new WikilinkParser(opts, deadLinks, new Map()); + t.is(parser.find("[[ broken \nwikilink ]]", pageDirectory, '/').length, 0); + t.is(parser.find("[[ broken |wikilink \n]]", pageDirectory, '/').length, 0); + t.is(parser.find("[[\n broken wikilink]]", pageDirectory, '/').length, 0); +}) From 5a5288d29dfaf5bbd0d11fcb906b375ef58f6200 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 10 May 2024 22:18:17 +0100 Subject: [PATCH 121/156] doc(#54): update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cdac6b5..b199db5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Wikilinks should not contain new lines (#54) - On resolving fn lookup failure, only throw error if page not found (#52) - Clear internal state before each 11ty build (#51) - Make dead link report configurable (#49) From 086cf4bedc8b6ba2676251c11117b044cfd459a9 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 11 May 2024 09:40:14 +0100 Subject: [PATCH 122/156] chore: add additional wikilink regex test --- tests/wikilink-parser.test.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/wikilink-parser.test.js b/tests/wikilink-parser.test.js index aa7c794..7a6fe89 100644 --- a/tests/wikilink-parser.test.js +++ b/tests/wikilink-parser.test.js @@ -19,6 +19,14 @@ const pageDirectory = pageLookup([ data: { title: 'Blog Post', }, + }, + { + inputPath: '/home/user/website/bookmark/2024-01-04-♡-cinnis-dream-home-♡.md', + filePathStem: '/bookmark/2024-01-04-♡-cinnis-dream-home-♡', + fileSlug: 'cinnis-dream-home', + data: { + title: "♡ cinni''s dream home ♡" + } } ]); @@ -197,3 +205,11 @@ test('regex does not match on whitespace', t => { t.is(parser.find("[[ broken |wikilink \n]]", pageDirectory, '/').length, 0); t.is(parser.find("[[\n broken wikilink]]", pageDirectory, '/').length, 0); }) + +test('regex does match special ASCII', t => { + const deadLinks = new Set(); + const parser = new WikilinkParser(opts, deadLinks, new Map()); + t.is(parser.find("[[♡ cinni''s dream home ♡]]", pageDirectory, '/').length, 1); + t.is(parser.find("[[♡ cinni''s dream home ♡|Cinni]]", pageDirectory, '/').length, 1); + t.is(deadLinks.size, 0); +}) From 56cd3d76683cd58c2f6914c5c7ed38f15119a542 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 11 May 2024 10:13:23 +0100 Subject: [PATCH 123/156] bugfix(#33): stop EleventyRenderPlugin crashing in --watch mode --- index.js | 4 +++- src/interlinker.js | 8 +++----- src/resolvers.js | 5 ++++- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index fa05b88..c41c7d5 100644 --- a/index.js +++ b/index.js @@ -33,7 +33,9 @@ module.exports = function (eleventyConfig, options = {}) { const interlinker = new Interlinker(opts); - // TODO: document + // This populates templateConfig with an instance of TemplateConfig once 11ty has initiated it, it's + // used by the template compiler function that's exported by the EleventyRenderPlugin and used + // by the defaultEmbedFn resolving function for compiling embed templates. eleventyConfig.on("eleventy.config", (cfg) => { interlinker.templateConfig = cfg; }); diff --git a/src/interlinker.js b/src/interlinker.js index 72684bc..694fa3f 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -1,6 +1,5 @@ const HTMLLinkParser = require("./html-link-parser"); const WikilinkParser = require("./wikilink-parser"); -const {EleventyRenderPlugin} = require("@11ty/eleventy"); const DeadLinks = require("./dead-links"); const {pageLookup} = require("./find-page"); @@ -21,12 +20,11 @@ module.exports = class Interlinker { // Map of Wikilink Meta that have been resolved by the WikilinkParser this.linkCache = new Map(); - // TODO: document + // Instance of TemplateConfig loaded by the `eleventy.config` event this.templateConfig = undefined; - this.extensionMap = undefined; - // TODO: document - this.rm = new EleventyRenderPlugin.RenderManager(); + // Instance of EleventyExtensionMap loaded by the `eleventy.extensionmap` event + this.extensionMap = undefined; this.wikiLinkParser = new WikilinkParser(opts, this.deadLinks, this.linkCache); this.HTMLLinkParser = new HTMLLinkParser(this.deadLinks); diff --git a/src/resolvers.js b/src/resolvers.js index 2d7368a..49433e8 100644 --- a/src/resolvers.js +++ b/src/resolvers.js @@ -1,3 +1,4 @@ +const {EleventyRenderPlugin} = require("@11ty/eleventy"); const entities = require("entities"); /** * Default Resolving function for converting Wikilinks into html links. @@ -48,7 +49,9 @@ const defaultEmbedFn = async (link, currentPage, interlinker) => { ? frontMatter.content : `{% layout "${layout}" %} {% block content %} ${frontMatter.content} {% endblock %}`; - const fn = await interlinker.rm.compile(tpl, language, { + const compiler = EleventyRenderPlugin.String; + + const fn = await compiler(tpl, language, { templateConfig: interlinker.templateConfig, extensionMap: interlinker.extensionMap }); From a86b35e25be18404b5957ef2962585c7da74a718 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 11 May 2024 10:24:17 +0100 Subject: [PATCH 124/156] doc(#33): update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b199db5..f05aa14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Fix crashing bug when embedded file changed while in `--watch` mode (#56) - Wikilinks should not contain new lines (#54) - On resolving fn lookup failure, only throw error if page not found (#52) - Clear internal state before each 11ty build (#51) From 5a8e936438ad3c339561f5a9339eb492a1f877f5 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 11 May 2024 10:31:23 +0100 Subject: [PATCH 125/156] doc(#25): add stubUrl to options type --- index.d.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/index.d.ts b/index.d.ts index 5456c17..a3b8c09 100644 --- a/index.d.ts +++ b/index.d.ts @@ -41,6 +41,11 @@ type EleventyPluginInterlinkOptions = { // that embed. This will always default to `embedLayout`. layoutKey?: string, + // stubUrl is the href you want wikilinks to link to if their linking page is not found. By default this is set + // to /stubs. Passing false will disable stub url output resulting in the wikilink being displayed directly + // in the html without transformation into a html link. + stubUrl?: string|false, + // layoutTemplateLangKey informs the template renderer which engines to use for rendering an embed's layout. This // defaults to your 11ty projects default, typically: liquid,md layoutTemplateLangKey?: string, From da908fc9d1f10ff4b1ed7fb61d0722f528bde7c3 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 11 May 2024 10:44:47 +0100 Subject: [PATCH 126/156] chore(#25): add failing tests for new functionality --- tests/eleventy.test.js | 26 ++++++++++++++ .../_layouts/default.liquid | 1 + .../eleventy.config.js | 14 ++++++++ .../website-with-custom-stub-url/index.md | 6 ++++ .../_layouts/default.liquid | 1 + .../eleventy.config.js | 14 ++++++++ .../website-with-disabled-stub-url/index.md | 6 ++++ .../fixtures/website-with-permalink/hello.md | 36 ------------------- 8 files changed, 68 insertions(+), 36 deletions(-) create mode 100644 tests/fixtures/website-with-custom-stub-url/_layouts/default.liquid create mode 100644 tests/fixtures/website-with-custom-stub-url/eleventy.config.js create mode 100644 tests/fixtures/website-with-custom-stub-url/index.md create mode 100644 tests/fixtures/website-with-disabled-stub-url/_layouts/default.liquid create mode 100644 tests/fixtures/website-with-disabled-stub-url/eleventy.config.js create mode 100644 tests/fixtures/website-with-disabled-stub-url/index.md delete mode 100644 tests/fixtures/website-with-permalink/hello.md diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index 186baf3..6b192d4 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -258,3 +258,29 @@ test.serial("Custom resolving functions (throw exception on not found)", async t const error = await t.throwsAsync(elev.toJSON()); t.is(error.message, 'Unable to find resolving fn [PHP Space Mines] for wikilink [[PHP Space Mines: Introduction|Moon Miner]] on page [/index]'); }); + +test("Stub URL Config (can be customised)", async t => { + let elev = new Eleventy(fixturePath('website-with-custom-stub-url'), fixturePath('website-with-custom-stub-url/_site'), { + configPath: fixturePath('website-with-custom-stub-url/eleventy.config.js'), + }); + + const results = await elev.toJSON(); + + t.is( + normalize(findResultByUrl(results, '/').content), + `

Broken link with custom stub url broken link.

` + ); +}) + +test("Stub URL Config (can be disabled)", async t => { + let elev = new Eleventy(fixturePath('website-with-disabled-stub-url'), fixturePath('website-with-disabled-stub-url/_site'), { + configPath: fixturePath('website-with-disabled-stub-url/eleventy.config.js'), + }); + + const results = await elev.toJSON(); + + t.is( + normalize(findResultByUrl(results, '/').content), + `

Broken link with custom stub url [[ broken link ]].

` + ); +}) diff --git a/tests/fixtures/website-with-custom-stub-url/_layouts/default.liquid b/tests/fixtures/website-with-custom-stub-url/_layouts/default.liquid new file mode 100644 index 0000000..c9bcd4c --- /dev/null +++ b/tests/fixtures/website-with-custom-stub-url/_layouts/default.liquid @@ -0,0 +1 @@ +
{{ content }}
diff --git a/tests/fixtures/website-with-custom-stub-url/eleventy.config.js b/tests/fixtures/website-with-custom-stub-url/eleventy.config.js new file mode 100644 index 0000000..f57a9e4 --- /dev/null +++ b/tests/fixtures/website-with-custom-stub-url/eleventy.config.js @@ -0,0 +1,14 @@ +module.exports = function (eleventyConfig) { + eleventyConfig.addPlugin( + require('../../../index.js'), { + stubUrl: '/custom-stub-url/', + } + ); + + return { + dir: { + includes: "_includes", + layouts: "_layouts", + } + } +} diff --git a/tests/fixtures/website-with-custom-stub-url/index.md b/tests/fixtures/website-with-custom-stub-url/index.md new file mode 100644 index 0000000..b2e9855 --- /dev/null +++ b/tests/fixtures/website-with-custom-stub-url/index.md @@ -0,0 +1,6 @@ +--- +title: Homepage +layout: default.liquid +--- + +Broken link with custom stub url [[ broken link ]]. diff --git a/tests/fixtures/website-with-disabled-stub-url/_layouts/default.liquid b/tests/fixtures/website-with-disabled-stub-url/_layouts/default.liquid new file mode 100644 index 0000000..c9bcd4c --- /dev/null +++ b/tests/fixtures/website-with-disabled-stub-url/_layouts/default.liquid @@ -0,0 +1 @@ +
{{ content }}
diff --git a/tests/fixtures/website-with-disabled-stub-url/eleventy.config.js b/tests/fixtures/website-with-disabled-stub-url/eleventy.config.js new file mode 100644 index 0000000..971c4ef --- /dev/null +++ b/tests/fixtures/website-with-disabled-stub-url/eleventy.config.js @@ -0,0 +1,14 @@ +module.exports = function (eleventyConfig) { + eleventyConfig.addPlugin( + require('../../../index.js'), { + stubUrl: false, + } + ); + + return { + dir: { + includes: "_includes", + layouts: "_layouts", + } + } +} diff --git a/tests/fixtures/website-with-disabled-stub-url/index.md b/tests/fixtures/website-with-disabled-stub-url/index.md new file mode 100644 index 0000000..b2e9855 --- /dev/null +++ b/tests/fixtures/website-with-disabled-stub-url/index.md @@ -0,0 +1,6 @@ +--- +title: Homepage +layout: default.liquid +--- + +Broken link with custom stub url [[ broken link ]]. diff --git a/tests/fixtures/website-with-permalink/hello.md b/tests/fixtures/website-with-permalink/hello.md deleted file mode 100644 index e8e536f..0000000 --- a/tests/fixtures/website-with-permalink/hello.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -title: "Adding Wiki Links to 11ty" -tags: [Blogging, 11ty, "Wiki Links"] -aliases: [wiki-links] -growthStage: seedling ---- - -Hello World - -{% raw %} -```js -module.exports = function(md, linkMapCache) { - // Recognize Mediawiki links ([[text]]) - md.linkify.add("[[", { - validate: /^\s?([^\[\]\|\n\r]+)(\|[^\[\]\|\n\r]+)?\s?\]\]/, - normalize: match => { - const parts = match.raw.slice(2, -2).split("|"); - const slug = slugify(parts[0].replace(/.(md|markdown)\s?$/i, "").trim()); - const found = linkMapCache.get(slug); - - if (!found) throw new Error(`Unable to find page linked by wikilink slug [${slug}]`) - - match.text = parts.length === 2 - ? parts[1] - : found.title; - - match.url = found.permalink.substring(0,1) === '/' - ? found.permalink - : `/${found.permalink}`; - } - }) -}; -``` -{% endraw %} - -[[ test ]] From 8306e0f93f9f8ce486fac3ed707ed0c45f52b89b Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 11 May 2024 10:48:22 +0100 Subject: [PATCH 127/156] chore(#25): /stubs -> /stubs/ --- tests/eleventy.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index 6b192d4..d02d35a 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -101,7 +101,7 @@ test.serial("Broken page (wikilinks and regular links)", async t => { // Markdown will have the link href set to /stubs t.is( normalize(findResultByUrl(results, '/something/').content), - `

This page has a broken link.

` + `

This page has a broken link.

` ); // HTML @@ -148,7 +148,7 @@ test.serial("Sample page (eleventyExcludeFromCollections set true)", async t => // Embedded page is aware of its embedding t.is( normalize(findResultByUrl(results, '/about/').content), - `

This wikilink to something will not parse because the destination has eleventyExcludeFromCollections set true.

` + `

This wikilink to something will not parse because the destination has eleventyExcludeFromCollections set true.

` ); // Embed shows From 4f9b2ac842c2e01f359ff291b48b773ef76090d3 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 25 Oct 2024 21:50:09 +0100 Subject: [PATCH 128/156] feat(#25): make stub post destination configurable --- index.d.ts | 5 +++-- index.js | 3 +++ src/resolvers.js | 2 +- src/wikilink-parser.js | 3 +-- tests/eleventy.test.js | 5 ++++- tests/wikilink-parser.test.js | 3 ++- 6 files changed, 14 insertions(+), 7 deletions(-) diff --git a/index.d.ts b/index.d.ts index a3b8c09..2c3bba4 100644 --- a/index.d.ts +++ b/index.d.ts @@ -92,8 +92,9 @@ type WikilinkMeta = { // the resulting HTML of the resolving function content?: string - // href and path are loaded from the linked page - href?: string + // href and path are loaded from the linked page, if the href is + // false then it disables the transformation of wikilink into html link. + href?: string|false path?: string } diff --git a/index.js b/index.js index c41c7d5..dfbce7a 100644 --- a/index.js +++ b/index.js @@ -26,6 +26,9 @@ module.exports = function (eleventyConfig, options = {}) { opts.resolvingFns.set('404-embed', async (link) => opts.unableToLocateEmbedFn(link.page.fileSlug)); } + // Set default stub url if not set by author. + if (typeof opts.stubUrl === 'undefined') opts.stubUrl = '/stubs/'; + // Default resolving functions for converting a Wikilink into HTML. if (!opts.resolvingFns.has('default')) opts.resolvingFns.set('default', defaultResolvingFn); if (!opts.resolvingFns.has('default-embed')) opts.resolvingFns.set('default-embed', defaultEmbedFn); diff --git a/src/resolvers.js b/src/resolvers.js index 49433e8..ff860b1 100644 --- a/src/resolvers.js +++ b/src/resolvers.js @@ -16,7 +16,7 @@ const defaultResolvingFn = async (link, currentPage, interlinker) => { href = `${href}#${link.anchor}`; } - return `${text}`; + return href === false ? link.link : `${text}`; } /** diff --git a/src/wikilink-parser.js b/src/wikilink-parser.js index d9779d2..b32e41d 100644 --- a/src/wikilink-parser.js +++ b/src/wikilink-parser.js @@ -126,8 +126,7 @@ module.exports = class WikilinkParser { // If this wikilink goes to a page that doesn't exist, add to deadLinks list and // update href for stub post. this.deadLinks.add(link); - // @todo make the stub post url configurable, or even able to be disabled. (#25) - meta.href = '/stubs'; + meta.href = this.opts.stubUrl; if (isEmbed) meta.resolvingFnName = '404-embed'; } diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index d02d35a..c38b33d 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -223,8 +223,11 @@ test("Permalink should be used for link href", async t => { /** * This test checks that custom resolving functions are invoked if they exist, and if not * an exception isn't thrown if the page can be looked up (see issue #50). + * + * Must be serial test due to usage of fs.existsSync, which will pass when running as a + * single test, but fail when run as part of a parallel set. */ -test("Custom resolving functions (are invoked)", async t => { +test.serial("Custom resolving functions (are invoked)", async t => { let elev = new Eleventy(fixturePath('website-with-custom-resolving-fn'), fixturePath('website-with-custom-resolving-fn/_site'), { configPath: fixturePath('website-with-custom-resolving-fn/eleventy.config.js'), }); diff --git a/tests/wikilink-parser.test.js b/tests/wikilink-parser.test.js index 7a6fe89..011e852 100644 --- a/tests/wikilink-parser.test.js +++ b/tests/wikilink-parser.test.js @@ -35,6 +35,7 @@ const opts = { ['default', defaultResolvingFn], ['default-embed', defaultEmbedFn], ]), + stubUrl: '/stubs/', }; test('parses wikilink', t => { @@ -139,7 +140,7 @@ test('populates dead links set', t => { const invalid = parser.parseSingle('[[invalid]]', pageDirectory); t.is(deadLinks.size, 1); - t.is(invalid.href, '/stubs'); + t.is(invalid.href, '/stubs/'); }) test('parses path lookup', t => { From a116cab67e95cb36fe1b1d84a4d8c3ec2a5eb50c Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 25 Oct 2024 22:12:26 +0100 Subject: [PATCH 129/156] chore: drop support for node 16.x, add test coverage for node 21.x --- .github/workflows/node-test.yml | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/node-test.yml b/.github/workflows/node-test.yml index 32e6606..63d3d90 100644 --- a/.github/workflows/node-test.yml +++ b/.github/workflows/node-test.yml @@ -11,7 +11,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - node: [16.x, 18.x, 20.x] + node: [18.x, 20.x, 21.x] os: ["ubuntu-latest", "macos-latest", "windows-latest"] steps: - uses: actions/checkout@v3 diff --git a/package.json b/package.json index c59c7ba..570b908 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "src" ], "engines": { - "node": ">=16" + "node": ">=18" }, "dependencies": { "chalk": "^4.1.1", From 505eee558ee8318731c087573219013097ff0f32 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 25 Oct 2024 22:13:34 +0100 Subject: [PATCH 130/156] refactor(#57): CommonJS -> ESM --- index.js | 8 +- package-lock.json | 5312 ++++------------- package.json | 16 +- src/dead-links.js | 8 +- src/find-page.js | 6 +- src/html-link-parser.js | 2 +- src/interlinker.js | 10 +- src/markdown-ext.js | 9 +- src/resolvers.js | 16 +- src/wikilink-parser.js | 2 +- tests/eleventy.test.js | 10 +- tests/find-page-service.test.js | 4 +- .../sample-small-website/eleventy.config.js | 8 +- tests/helpers.js | 20 +- tests/html-internal-link-parser.test.js | 8 +- tests/markdown-wikilink-parser.test.js | 19 +- tests/markdown-wikilink-renderer.test.js | 23 +- tests/plugin.test.js | 6 +- tests/wikilink-parser.test.js | 8 +- 19 files changed, 1266 insertions(+), 4229 deletions(-) diff --git a/index.js b/index.js index dfbce7a..b68c064 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,6 @@ -const {wikilinkInlineRule, wikilinkRenderRule} = require('./src/markdown-ext'); -const Interlinker = require("./src/interlinker"); -const {defaultResolvingFn, defaultEmbedFn} = require("./src/resolvers"); +import {wikilinkInlineRule, wikilinkRenderRule} from './src/markdown-ext.js'; +import Interlinker from './src/interlinker.js'; +import {defaultResolvingFn, defaultEmbedFn} from './src/resolvers.js'; /** * Some code borrowed from: @@ -10,7 +10,7 @@ const {defaultResolvingFn, defaultEmbedFn} = require("./src/resolvers"); * @param { import('@11ty/eleventy/src/UserConfig') } eleventyConfig * @param { import('@photogabble/eleventy-plugin-interlinker').EleventyPluginInterlinkOptions } options */ -module.exports = function (eleventyConfig, options = {}) { +export default function (eleventyConfig, options = {}) { /** @var { import('@photogabble/eleventy-plugin-interlinker').EleventyPluginInterlinkOptions } opts */ const opts = Object.assign({ defaultLayout: null, diff --git a/package-lock.json b/package-lock.json index c61b1fd..259ce72 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@photogabble/eleventy-plugin-interlinker", - "version": "1.1.0-rc3", - "lockfileVersion": 2, + "version": "1.1.0-rc4", + "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@photogabble/eleventy-plugin-interlinker", - "version": "1.1.0-rc3", + "version": "1.1.0-rc4", "license": "MIT", "dependencies": { "chalk": "^4.1.1", @@ -14,71 +14,96 @@ "markdown-it": "^14.1.0" }, "devDependencies": { - "@11ty/eleventy": "^2.0.0", + "@11ty/eleventy": "^3.0.0", "ava": "^5.2.0", "c8": "^7.12.0", + "rollup": "^4.24.0", "sinon": "^17.0.1" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@11ty/dependency-tree": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@11ty/dependency-tree/-/dependency-tree-2.0.1.tgz", - "integrity": "sha512-5R+DsT9LJ9tXiSQ4y+KLFppCkQyXhzAm1AIuBWE/sbU0hSXY5pkhoqQYEcPJQFg/nglL+wD55iv2j+7O96UAvg==", - "dev": true + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@11ty/dependency-tree/-/dependency-tree-3.0.1.tgz", + "integrity": "sha512-aZizxcL4Z/clm3KPRx8i9ohW9R2gLssXfUSy7qQmQRXb4CUOyvmqk2gKeJqRmXIfMi2bB9w03SgtN5v1YwqpiA==", + "dev": true, + "dependencies": { + "@11ty/eleventy-utils": "^1.0.2" + } + }, + "node_modules/@11ty/dependency-tree-esm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@11ty/dependency-tree-esm/-/dependency-tree-esm-1.0.0.tgz", + "integrity": "sha512-Z3KN1Fkv50UM/ZzTR3VBbyOY52HnmhIVCsAV1hn2UzFsGAjyF1Cw8uohhVtheDOSuBR7ZSeo1unwkz1HxFlUtQ==", + "dev": true, + "dependencies": { + "@11ty/eleventy-utils": "^1.0.2", + "acorn": "^8.10.0", + "dependency-graph": "^0.11.0", + "normalize-path": "^3.0.0" + } + }, + "node_modules/@11ty/dependency-tree-esm/node_modules/dependency-graph": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", + "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } }, "node_modules/@11ty/eleventy": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@11ty/eleventy/-/eleventy-2.0.1.tgz", - "integrity": "sha512-t8XVUbCJByhVEa1RzO0zS2QzbL3wPY8ot1yUw9noqiSHxJWUwv6jiwm1/MZDPTYtkZH2ZHvdQIRQ5/SjG9XmLw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@11ty/eleventy/-/eleventy-3.0.0.tgz", + "integrity": "sha512-0P0ZsJXVW2QiNdhd7z+GYy6n+ivh0enx1DRdua5ta6NlzY2AhbkeWBY6U+FKA8lPS3H4+XsTpfLLfIScpPZLaQ==", "dev": true, "dependencies": { - "@11ty/dependency-tree": "^2.0.1", - "@11ty/eleventy-dev-server": "^1.0.4", - "@11ty/eleventy-utils": "^1.0.1", + "@11ty/dependency-tree": "^3.0.1", + "@11ty/dependency-tree-esm": "^1.0.0", + "@11ty/eleventy-dev-server": "^2.0.4", + "@11ty/eleventy-plugin-bundle": "^3.0.0", + "@11ty/eleventy-utils": "^1.0.3", "@11ty/lodash-custom": "^4.17.21", - "@iarna/toml": "^2.2.5", - "@sindresorhus/slugify": "^1.1.2", - "bcp-47-normalize": "^1.1.1", - "chokidar": "^3.5.3", + "@11ty/posthtml-urls": "^1.0.0", + "@11ty/recursive-copy": "^3.0.0", + "@sindresorhus/slugify": "^2.2.1", + "bcp-47-normalize": "^2.3.0", + "chardet": "^2.0.0", + "chokidar": "^3.6.0", "cross-spawn": "^7.0.3", - "debug": "^4.3.4", - "dependency-graph": "^0.11.0", - "ejs": "^3.1.9", - "fast-glob": "^3.2.12", + "debug": "^4.3.7", + "dependency-graph": "^1.0.0", + "entities": "^5.0.0", + "fast-glob": "^3.3.2", + "filesize": "^10.1.6", "graceful-fs": "^4.2.11", "gray-matter": "^4.0.3", - "hamljs": "^0.6.2", - "handlebars": "^4.7.7", "is-glob": "^4.0.3", - "iso-639-1": "^2.1.15", + "iso-639-1": "^3.1.3", + "js-yaml": "^4.1.0", "kleur": "^4.1.5", - "liquidjs": "^10.7.0", - "luxon": "^3.3.0", - "markdown-it": "^13.0.1", - "micromatch": "^4.0.5", + "liquidjs": "^10.17.0", + "luxon": "^3.5.0", + "markdown-it": "^14.1.0", + "micromatch": "^4.0.8", "minimist": "^1.2.8", "moo": "^0.5.2", - "multimatch": "^5.0.0", - "mustache": "^4.2.0", + "node-retrieve-globals": "^6.0.0", "normalize-path": "^3.0.0", - "nunjucks": "^3.2.3", - "path-to-regexp": "^6.2.1", + "nunjucks": "^3.2.4", "please-upgrade-node": "^3.2.0", "posthtml": "^0.16.6", - "posthtml-urls": "^1.0.0", - "pug": "^3.0.2", - "recursive-copy": "^2.0.14", - "semver": "^7.3.8", + "posthtml-match-helper": "^2.0.2", + "semver": "^7.6.3", "slugify": "^1.6.6" }, "bin": { - "eleventy": "cmd.js" + "eleventy": "cmd.cjs" }, "engines": { - "node": ">=14" + "node": ">=18" }, "funding": { "type": "opencollective", @@ -86,28 +111,47 @@ } }, "node_modules/@11ty/eleventy-dev-server": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@11ty/eleventy-dev-server/-/eleventy-dev-server-1.0.4.tgz", - "integrity": "sha512-qVBmV2G1KF/0o5B/3fITlrrDHy4bONUI2YuN3/WJ3BNw4NU1d/we8XhKrlgq13nNvHoBx5czYp3LZt8qRG53Fg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@11ty/eleventy-dev-server/-/eleventy-dev-server-2.0.4.tgz", + "integrity": "sha512-d0CuufX6yPtVz+RW0oJZg1pVoxo1jOrPmpXYacoiKLJm0MMC9MkPQOCXlimguHVaceHejFo5+aZB9/aGB2RR0A==", "dev": true, "dependencies": { - "@11ty/eleventy-utils": "^1.0.1", - "chokidar": "^3.5.3", - "debug": "^4.3.4", + "@11ty/eleventy-utils": "^1.0.3", + "chokidar": "^3.6.0", + "debug": "^4.3.7", "dev-ip": "^1.0.1", - "finalhandler": "^1.2.0", + "finalhandler": "^1.3.0", "mime": "^3.0.0", "minimist": "^1.2.8", - "morphdom": "^2.7.0", + "morphdom": "^2.7.4", "please-upgrade-node": "^3.2.0", - "ssri": "^8.0.1", - "ws": "^8.13.0" + "send": "^0.19.0", + "ssri": "^11.0.0", + "urlpattern-polyfill": "^10.0.0", + "ws": "^8.18.0" }, "bin": { "eleventy-dev-server": "cmd.js" }, "engines": { - "node": ">=14" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/11ty" + } + }, + "node_modules/@11ty/eleventy-plugin-bundle": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@11ty/eleventy-plugin-bundle/-/eleventy-plugin-bundle-3.0.0.tgz", + "integrity": "sha512-JSnqehT+sWSPi6e44jTXUW+KiV9284YF9fzPQvfGB4cXlk/m/SJk17CavHCleIvKXDN+jrUw9TZkwAwr85ONWQ==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "posthtml-match-helper": "^2.0.2" + }, + "engines": { + "node": ">=18" }, "funding": { "type": "opencollective", @@ -115,9 +159,9 @@ } }, "node_modules/@11ty/eleventy-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@11ty/eleventy-utils/-/eleventy-utils-1.0.2.tgz", - "integrity": "sha512-Zy2leMK1DQR6Q6ZPSagv7QpJaAz9uVbb+RmVetYFp3foMeQtOSZx7w2u5daRFmP+PeNq9vO9H4xtBToYFWZwHA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@11ty/eleventy-utils/-/eleventy-utils-1.0.3.tgz", + "integrity": "sha512-nULO91om7vQw4Y/UBjM8i7nJ1xl+/nyK4rImZ41lFxiY2d+XUz7ChAj1CDYFjrLZeu0utAYJTZ45LlcHTkUG4g==", "dev": true, "dependencies": { "normalize-path": "^3.0.0" @@ -130,16 +174,10 @@ "url": "https://opencollective.com/11ty" } }, - "node_modules/@11ty/eleventy/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, "node_modules/@11ty/eleventy/node_modules/entities": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", - "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-5.0.0.tgz", + "integrity": "sha512-BeJFvFRJddxobhvEdm5GqHzRV/X+ACeuw0/BuuxsCh1EUZcAIz8+kYmBp/LrQuloy6K1f3a0M7+IhmZ7QnkISA==", "dev": true, "engines": { "node": ">=0.12" @@ -148,43 +186,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/@11ty/eleventy/node_modules/linkify-it": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", - "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", - "dev": true, - "dependencies": { - "uc.micro": "^1.0.1" - } - }, - "node_modules/@11ty/eleventy/node_modules/markdown-it": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.2.tgz", - "integrity": "sha512-FtwnEuuK+2yVU7goGn/MJ0WBZMM9ZPgU9spqlFs7/A/pDIUNSOQZhUgOqYCficIuR2QaFnrt8LHqBWsbTAoI5w==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1", - "entities": "~3.0.1", - "linkify-it": "^4.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - }, - "bin": { - "markdown-it": "bin/markdown-it.js" - } - }, - "node_modules/@11ty/eleventy/node_modules/mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", - "dev": true - }, - "node_modules/@11ty/eleventy/node_modules/uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", - "dev": true - }, "node_modules/@11ty/lodash-custom": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/@11ty/lodash-custom/-/lodash-custom-4.17.21.tgz", @@ -198,48 +199,37 @@ "url": "https://opencollective.com/11ty" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", - "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz", - "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", - "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==", + "node_modules/@11ty/posthtml-urls": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@11ty/posthtml-urls/-/posthtml-urls-1.0.0.tgz", + "integrity": "sha512-CcsRdI933x613u7CjM+QGs7iD/m8SaDup3Apohg1+7dybigrEUHc2jGS3mcMgQKvF2+IphqmepD/FrKLlPkPEg==", "dev": true, - "bin": { - "parser": "bin/babel-parser.js" + "dependencies": { + "evaluate-value": "^2.0.0", + "http-equiv-refresh": "^2.0.1", + "list-to-array": "^1.1.0", + "object.entries": "^1.1.7", + "parse-srcset": "^1.0.2" }, "engines": { - "node": ">=6.0.0" + "node": ">= 6" } }, - "node_modules/@babel/types": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz", - "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==", + "node_modules/@11ty/recursive-copy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@11ty/recursive-copy/-/recursive-copy-3.0.0.tgz", + "integrity": "sha512-v1Mr7dWx5nk69/HRRtDHUYDV9N8+cE12IGiKSFOwML7HjOzUXwTP88e3cGuhqoVstkBil1ZEIaOB0KPP1zwqXA==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.24.1", - "@babel/helper-validator-identifier": "^7.24.5", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" + "errno": "^0.1.2", + "graceful-fs": "^4.2.11", + "junk": "^1.0.1", + "maximatch": "^0.1.0", + "mkdirp": "^3.0.1", + "pify": "^2.3.0", + "promise": "^7.0.1", + "rimraf": "^5.0.7", + "slash": "^1.0.0" } }, "node_modules/@bcoe/v8-coverage": { @@ -248,11 +238,22 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, - "node_modules/@iarna/toml": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", - "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==", - "dev": true + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", @@ -273,9 +274,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", "dev": true }, "node_modules/@jridgewell/trace-mapping": { @@ -323,47 +324,255 @@ "node": ">= 8" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", + "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz", + "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz", + "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz", + "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz", + "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz", + "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz", + "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz", + "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz", + "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz", + "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz", + "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", + "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz", + "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz", + "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz", + "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz", + "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@sindresorhus/slugify": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-1.1.2.tgz", - "integrity": "sha512-V9nR/W0Xd9TSGXpZ4iFUcFGhuOJtZX82Fzxj1YISlbSgKvIiNa7eLEZrT0vAraPOt++KHauIVNYgGRgjc13dXA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-2.2.1.tgz", + "integrity": "sha512-MkngSCRZ8JdSOCHRaYd+D01XhvU3Hjy6MGl06zhOk614hp9EOAp5gIkBeQg7wtmxpitU6eAL4kdiRMcJa2dlrw==", "dev": true, "dependencies": { - "@sindresorhus/transliterate": "^0.1.1", - "escape-string-regexp": "^4.0.0" + "@sindresorhus/transliterate": "^1.0.0", + "escape-string-regexp": "^5.0.0" }, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@sindresorhus/transliterate": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@sindresorhus/transliterate/-/transliterate-0.1.2.tgz", - "integrity": "sha512-5/kmIOY9FF32nicXH+5yLNTX4NJ4atl7jRgqAJuIn/iyDFXBktOKDxCvyGE/EzmF4ngSUvjXxQUQlQiZ5lfw+w==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/transliterate/-/transliterate-1.6.0.tgz", + "integrity": "sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ==", "dev": true, "dependencies": { - "escape-string-regexp": "^2.0.0", - "lodash.deburr": "^4.1.0" + "escape-string-regexp": "^5.0.0" }, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@sindresorhus/transliterate/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/@sinonjs/commons": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", @@ -374,38 +583,44 @@ } }, "node_modules/@sinonjs/fake-timers": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", - "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.3.1.tgz", + "integrity": "sha512-EVJO7nW5M/F5Tur0Rf2z/QoMo+1Ia963RiMtapiQrEWvY0iBUvADo8Beegwjpnle5BHkyHuoxSTW3jF43H1XRA==", "dev": true, "dependencies": { - "@sinonjs/commons": "^3.0.0" + "@sinonjs/commons": "^3.0.1" } }, "node_modules/@sinonjs/samsam": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", - "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.2.tgz", + "integrity": "sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw==", "dev": true, "dependencies": { - "@sinonjs/commons": "^2.0.0", + "@sinonjs/commons": "^3.0.1", "lodash.get": "^4.4.2", - "type-detect": "^4.0.8" + "type-detect": "^4.1.0" } }, - "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "node_modules/@sinonjs/samsam/node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", "dev": true, - "dependencies": { - "type-detect": "4.0.8" + "engines": { + "node": ">=4" } }, "node_modules/@sinonjs/text-encoding": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", - "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", + "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", + "dev": true + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "dev": true }, "node_modules/@types/istanbul-lib-coverage": { @@ -414,12 +629,6 @@ "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", "dev": true }, - "node_modules/@types/minimatch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", - "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", - "dev": true - }, "node_modules/a-sync-waterfall": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz", @@ -427,9 +636,9 @@ "dev": true }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz", + "integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -439,10 +648,13 @@ } }, "node_modules/acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", "dev": true, + "dependencies": { + "acorn": "^8.11.0" + }, "engines": { "node": ">=0.4.0" } @@ -464,9 +676,9 @@ } }, "node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, "engines": { "node": ">=12" @@ -487,12 +699,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/any-promise": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-0.1.0.tgz", - "integrity": "sha512-lqzY9o+BbeGHRCOyxQkt/Tgvz0IZhTmQiA+LxQW8wSNpcTbj8K+0cZiSEvbpNZZP9/11Gy7dnLO3GNWUXO4d1g==", - "dev": true - }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -507,21 +713,17 @@ } }, "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/array-differ": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz", - "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha512-LeZY+DZDRnvP7eMuQ6LHfCzUGxAAIViUBliK24P3hWXL6y4SortgR6Nim6xrkfSLlmH0+k+9NYNwVC2s53ZrYQ==", "dev": true, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, "node_modules/array-find-index": { @@ -534,12 +736,15 @@ } }, "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", "dev": true, + "dependencies": { + "array-uniq": "^1.0.1" + }, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, "node_modules/array-uniq": { @@ -578,18 +783,6 @@ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "dev": true }, - "node_modules/assert-never": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.2.1.tgz", - "integrity": "sha512-TaTivMB6pYI1kXwrFlEhLeGfOqoDNdTxjCdwRfFFkEA30Eu+k48W34nlok2EYWJfFFzqaEmichdNM7th6M5HNw==", - "dev": true - }, - "node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true - }, "node_modules/ava": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ava/-/ava-5.3.1.tgz", @@ -667,18 +860,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/babel-walk": { - "version": "3.0.0-canary-5", - "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz", - "integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.9.6" - }, - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -686,14 +867,14 @@ "dev": true }, "node_modules/bcp-47": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-1.0.8.tgz", - "integrity": "sha512-Y9y1QNBBtYtv7hcmoX0tR+tUNSFZGZ6OL6vKPObq8BbOhkCoyayF6ogfLTgAli/KuAEbsYHYUNq2AQuY6IuLag==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-2.1.0.tgz", + "integrity": "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==", "dev": true, "dependencies": { - "is-alphabetical": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-decimal": "^1.0.0" + "is-alphabetical": "^2.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0" }, "funding": { "type": "github", @@ -701,9 +882,9 @@ } }, "node_modules/bcp-47-match": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-1.0.3.tgz", - "integrity": "sha512-LggQ4YTdjWQSKELZF5JwchnBa1u0pIQSZf5lSdOHEdbVP55h0qICA/FUp3+W99q0xqxYa1ZQizTUH87gecII5w==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", + "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", "dev": true, "funding": { "type": "github", @@ -711,13 +892,13 @@ } }, "node_modules/bcp-47-normalize": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/bcp-47-normalize/-/bcp-47-normalize-1.1.1.tgz", - "integrity": "sha512-jWZ1Jdu3cs0EZdfCkS0UE9Gg01PtxnChjEBySeB+Zo6nkqtFfnvtoQQgP1qU1Oo4qgJgxhTI6Sf9y/pZIhPs0A==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/bcp-47-normalize/-/bcp-47-normalize-2.3.0.tgz", + "integrity": "sha512-8I/wfzqQvttUFz7HVJgIZ7+dj3vUaIyIxYXaTRP1YWoSDfzt6TUmxaKZeuXR62qBmYr+nvuWINFRl6pZ5DlN4Q==", "dev": true, "dependencies": { - "bcp-47": "^1.0.0", - "bcp-47-match": "^1.0.0" + "bcp-47": "^2.0.0", + "bcp-47-match": "^2.0.0" }, "funding": { "type": "github", @@ -753,12 +934,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -799,6 +980,21 @@ "node": ">=8" } }, + "node_modules/c8/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/c8/node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -816,6 +1012,27 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "node_modules/c8/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/c8/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -825,6 +1042,22 @@ "node": ">=8" } }, + "node_modules/c8/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/c8/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -851,6 +1084,23 @@ "node": ">=8" } }, + "node_modules/c8/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/c8/node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -889,9 +1139,9 @@ } }, "node_modules/callsites": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-4.1.0.tgz", - "integrity": "sha512-aBMbD1Xxay75ViYezwT40aQONfr+pSXTHwNKvIXhXD6+LY3F1dLIcceoC5OZKBVHbXcysz1hL9D2w0JJIMXpUw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-4.2.0.tgz", + "integrity": "sha512-kfzR4zzQtAE9PC7CzZsjl3aBNbXWuXiSeOCdLcPpBfGW8YuCqQHcRPFDbr/BPVmd3EEPVpuFzLyuT/cUhPr4OQ==", "dev": true, "engines": { "node": ">=12.20" @@ -941,14 +1191,11 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/character-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", - "integrity": "sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==", - "dev": true, - "dependencies": { - "is-regex": "^1.0.3" - } + "node_modules/chardet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.0.0.tgz", + "integrity": "sha512-xVgPpulCooDjY6zH4m9YW3jbkaBe3FKIAvF5sj5t7aBNsVl2ljIE+xwJ4iNgiDZHFQvNIpjdKdVOQvvk5ZfxbQ==", + "dev": true }, "node_modules/chokidar": { "version": "3.6.0", @@ -1016,18 +1263,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/clean-stack/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/clean-yaml-object": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/clean-yaml-object/-/clean-yaml-object-0.1.0.tgz", @@ -1076,6 +1311,21 @@ "node": ">=8" } }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/cliui/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -1117,6 +1367,23 @@ "node": ">=8" } }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/code-excerpt": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", @@ -1185,16 +1452,6 @@ "node": ">=10.18.0 <11 || >=12.14.0 <13 || >=14" } }, - "node_modules/constantinople": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", - "integrity": "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.6.0", - "@babel/types": "^7.6.1" - } - }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -1249,12 +1506,12 @@ } }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -1265,12 +1522,6 @@ } } }, - "node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -1288,13 +1539,49 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/dependency-graph": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", - "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-1.0.0.tgz", + "integrity": "sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==", "dev": true, "engines": { - "node": ">= 0.6.0" + "node": ">=4" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, "node_modules/dev-ip": { @@ -1330,12 +1617,6 @@ "node": ">=8" } }, - "node_modules/doctypes": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", - "integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==", - "dev": true - }, "node_modules/dom-serializer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", @@ -1412,21 +1693,6 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "dev": true }, - "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", - "dev": true, - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/emittery": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/emittery/-/emittery-1.0.3.tgz", @@ -1446,9 +1712,9 @@ "dev": true }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "dev": true, "engines": { "node": ">= 0.8" @@ -1498,10 +1764,22 @@ "node": ">= 0.4" } }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, "engines": { "node": ">=6" @@ -1514,17 +1792,26 @@ "dev": true }, "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==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "dev": true, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/esm-import-transformer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/esm-import-transformer/-/esm-import-transformer-3.0.2.tgz", + "integrity": "sha512-PgvO0wro44lTDM9pYeeOIfpS0lGF80jA+rjT7sBd3b07rxv1AxeNMEI5kSCqRKke2W6SPEz17W3kHOLjaiD7Cw==", + "dev": true, + "dependencies": { + "acorn": "^8.11.2" + } + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -1547,6 +1834,24 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/evaluate-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/evaluate-value/-/evaluate-value-2.0.0.tgz", + "integrity": "sha512-VonfiuDJc0z4sOO7W0Pd130VLsXN6vmBWZlrog1mCb/o7o/Nl5Lr25+Kj/nkCCAhG+zqeeGjxhkK9oHpkgTHhQ==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/extend-shallow": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", @@ -1606,52 +1911,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/figures/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dev": true, - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "node_modules/filesize": { + "version": "10.1.6", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-10.1.6.tgz", + "integrity": "sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w==", "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, "engines": { - "node": ">=10" + "node": ">= 10.4.0" } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -1661,13 +1933,13 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dev": true, "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -1722,6 +1994,15 @@ "node": ">=8.0.0" } }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -1780,20 +2061,20 @@ } }, "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": "*" + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -1811,6 +2092,58 @@ "node": ">= 6" } }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/globby": { "version": "13.2.2", "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", @@ -1830,6 +2163,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globby/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -1863,31 +2208,26 @@ "node": ">=6.0" } }, - "node_modules/hamljs": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/hamljs/-/hamljs-0.6.2.tgz", - "integrity": "sha512-/chXRp4WpL47I+HX1vCCdSbEXAljEG2FBMmgO7Am0bYsqgnEjreeWzUdX1onXqwZtcfgxbCg5WtEYYvuZ5muBg==", - "dev": true + "node_modules/gray-matter/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "node_modules/gray-matter/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" + "argparse": "^1.0.7", + "esprima": "^4.0.0" }, "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" + "js-yaml": "bin/js-yaml.js" } }, "node_modules/has-flag": { @@ -1934,21 +2274,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -1999,18 +2324,34 @@ } }, "node_modules/http-equiv-refresh": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/http-equiv-refresh/-/http-equiv-refresh-1.0.0.tgz", - "integrity": "sha512-TScO04soylRN9i/QdOdgZyhydXg9z6XdaGzEyOgDKycePeDeTT4KvigjBcI+tgfTlieLWauGORMq5F1eIDa+1w==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-equiv-refresh/-/http-equiv-refresh-2.0.1.tgz", + "integrity": "sha512-XJpDL/MLkV3dKwLzHwr2dY05dYNfBNlyPu4STQ8WvKCFdc6vC5tPXuq28of663+gHVg03C+16pHHs/+FmmDjcw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, "engines": { - "node": ">= 0.10" + "node": ">= 0.8" } }, "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "engines": { "node": ">= 4" @@ -2050,6 +2391,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, "dependencies": { "once": "^1.3.0", @@ -2072,9 +2414,9 @@ } }, "node_modules/is-alphabetical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", - "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", "dev": true, "funding": { "type": "github", @@ -2082,13 +2424,13 @@ } }, "node_modules/is-alphanumerical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", - "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", "dev": true, "dependencies": { - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0" + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" }, "funding": { "type": "github", @@ -2107,22 +2449,10 @@ "node": ">=8" } }, - "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", - "dev": true, - "dependencies": { - "hasown": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-decimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", - "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", "dev": true, "funding": { "type": "github", @@ -2135,28 +2465,6 @@ "integrity": "sha512-IOQqts/aHWbiisY5DuPJQ0gcbvaLFCa7fBa9xoLfxBZvQ+ZI/Zh9xoI7Gk+G64N0FdK4AbibytHht2tWgpJWLg==", "dev": true }, - "node_modules/is-expression": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz", - "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==", - "dev": true, - "dependencies": { - "acorn": "^7.1.1", - "object-assign": "^4.1.1" - } - }, - "node_modules/is-expression/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -2229,22 +2537,6 @@ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "dev": true }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-unicode-supported": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", @@ -2264,9 +2556,9 @@ "dev": true }, "node_modules/iso-639-1": { - "version": "2.1.15", - "resolved": "https://registry.npmjs.org/iso-639-1/-/iso-639-1-2.1.15.tgz", - "integrity": "sha512-7c7mBznZu2ktfvyT582E2msM+Udc1EjOyhVRE/0ZsjD9LBtWSm23h3PtiRh2a35XoUsTQQjJXaJzuLjXsOdFDg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/iso-639-1/-/iso-639-1-3.1.3.tgz", + "integrity": "sha512-1jz0Wh9hyLMRwqEPchb/KZCiTqfFWtc9R3nm7GHPygBAKS8wdKJ3FH4lvLsri6UtAE5Kz5SnowtXZa//6bqMyw==", "dev": true, "engines": { "node": ">=6.0" @@ -2308,22 +2600,19 @@ "node": ">=8" } }, - "node_modules/jake": { - "version": "10.8.7", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", - "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==", + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.4", - "minimatch": "^3.1.2" + "@isaacs/cliui": "^8.0.2" }, - "bin": { - "jake": "bin/cli.js" + "funding": { + "url": "https://github.com/sponsors/isaacs" }, - "engines": { - "node": ">=10" + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, "node_modules/js-string-escape": { @@ -2335,41 +2624,18 @@ "node": ">= 0.8" } }, - "node_modules/js-stringify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", - "integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==", - "dev": true - }, "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jstransformer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", - "integrity": "sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==", - "dev": true, - "dependencies": { - "is-promise": "^2.0.0", - "promise": "^7.0.1" - } - }, - "node_modules/jstransformer/node_modules/is-promise": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", - "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", - "dev": true - }, "node_modules/junk": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/junk/-/junk-1.0.3.tgz", @@ -2412,9 +2678,9 @@ } }, "node_modules/liquidjs": { - "version": "10.12.0", - "resolved": "https://registry.npmjs.org/liquidjs/-/liquidjs-10.12.0.tgz", - "integrity": "sha512-ZpT27WEqUu8IeddXoLbdeBTbRfV5r7oUKDjJMthuQKQTScgI8pbLGbSWiiAktQVpPG7mHMGsJ0JVbZYn1w9Gtg==", + "version": "10.18.0", + "resolved": "https://registry.npmjs.org/liquidjs/-/liquidjs-10.18.0.tgz", + "integrity": "sha512-gCJPmpmZ3oi2rMMHo/c+bW1LaRF+ZAKYTWQmKXPp0uK9EkWMFRmgbk3+Io4LSJGAOnpCZSgHJbNzcygx3kfAAQ==", "dev": true, "dependencies": { "commander": "^10.0.0" @@ -2470,12 +2736,6 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, - "node_modules/lodash.deburr": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/lodash.deburr/-/lodash.deburr-4.1.0.tgz", - "integrity": "sha512-m/M1U1f3ddMCs6Hq2tAsYThTBDaAKFDX3dwDo97GEYzamXi9SqUpjWi/Rrj/gf3X2n8ktwgZrlP1z6E3v/IExQ==", - "dev": true - }, "node_modules/lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", @@ -2483,21 +2743,15 @@ "dev": true }, "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true }, "node_modules/luxon": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", - "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz", + "integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==", "dev": true, "engines": { "node": ">=12" @@ -2546,11 +2800,6 @@ "markdown-it": "bin/markdown-it.mjs" } }, - "node_modules/markdown-it/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, "node_modules/matcher": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/matcher/-/matcher-5.0.0.tgz", @@ -2566,18 +2815,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/matcher/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/maximatch": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/maximatch/-/maximatch-0.1.0.tgz", @@ -2593,27 +2830,6 @@ "node": ">=0.10.0" } }, - "node_modules/maximatch/node_modules/array-differ": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", - "integrity": "sha512-LeZY+DZDRnvP7eMuQ6LHfCzUGxAAIViUBliK24P3hWXL6y4SortgR6Nim6xrkfSLlmH0+k+9NYNwVC2s53ZrYQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/maximatch/node_modules/array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", - "dev": true, - "dependencies": { - "array-uniq": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/maximatch/node_modules/arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", @@ -2666,12 +2882,12 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -2724,27 +2940,27 @@ } }, "node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", "dev": true, - "dependencies": { - "minimist": "^1.2.6" - }, "bin": { - "mkdirp": "bin/cmd.js" + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/moo": { @@ -2754,9 +2970,9 @@ "dev": true }, "node_modules/morphdom": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/morphdom/-/morphdom-2.7.2.tgz", - "integrity": "sha512-Dqb/lHFyTi7SZpY0a5R4I/0Edo+iPMbaUexsHHsLAByyixCDiLHPHyVoKVmrpL0THcT7V9Cgev9y21TQYq6wQg==", + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/morphdom/-/morphdom-2.7.4.tgz", + "integrity": "sha512-ATTbWMgGa+FaMU3FhnFYB6WgulCqwf6opOll4CBzmVDTLvPMmUPrEv8CudmLPK0MESa64+6B89fWOxP3+YIlxQ==", "dev": true }, "node_modules/ms": { @@ -2765,49 +2981,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "node_modules/multimatch": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz", - "integrity": "sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==", - "dev": true, - "dependencies": { - "@types/minimatch": "^3.0.3", - "array-differ": "^3.0.0", - "array-union": "^2.1.0", - "arrify": "^2.0.1", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/multimatch/node_modules/arrify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/mustache": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", - "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", - "dev": true, - "bin": { - "mustache": "bin/mustache" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, "node_modules/nise": { "version": "5.1.9", "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", @@ -2821,6 +2994,17 @@ "path-to-regexp": "^6.2.1" } }, + "node_modules/node-retrieve-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/node-retrieve-globals/-/node-retrieve-globals-6.0.0.tgz", + "integrity": "sha512-VoEp6WMN/JcbBrJr6LnFE11kdzpKiBKNPFrHCEK2GgFWtiYpeL85WgcZpZFFnWxAU0O65+b+ipQAy4Oxy/+Pdg==", + "dev": true, + "dependencies": { + "acorn": "^8.1.3", + "acorn-walk": "^8.3.2", + "esm-import-transformer": "^3.0.2" + } + }, "node_modules/nofilter": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", @@ -2873,13 +3057,27 @@ "node": ">= 6" } }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + } + }, + "node_modules/object.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" } }, "node_modules/on-finished": { @@ -2984,6 +3182,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true + }, "node_modules/parse-ms": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-3.0.0.tgz", @@ -3038,16 +3242,26 @@ "node": ">=8" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/path-to-regexp": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", - "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", "dev": true }, "node_modules/path-type": { @@ -3167,9 +3381,9 @@ } }, "node_modules/pkg-conf/node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", + "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", "dev": true, "engines": { "node": ">=12.20" @@ -3215,6 +3429,18 @@ "node": ">=12.0.0" } }, + "node_modules/posthtml-match-helper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/posthtml-match-helper/-/posthtml-match-helper-2.0.2.tgz", + "integrity": "sha512-ehnazjlSwcGa3P2LlFYmTmcnaembTSt9dLWIRRDVHDPidf6InWAr9leKeeLvUXgnU32g6BrFS64Je+c2Ld+l9g==", + "dev": true, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "posthtml": "^0.16.6" + } + }, "node_modules/posthtml-parser": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.11.0.tgz", @@ -3239,21 +3465,6 @@ "node": ">=12" } }, - "node_modules/posthtml-urls": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/posthtml-urls/-/posthtml-urls-1.0.0.tgz", - "integrity": "sha512-CMJ0L009sGQVUuYM/g6WJdscsq6ooAwhUuF6CDlYPMLxKp2rmCYVebEU+wZGxnQstGJhZPMvXsRhtqekILd5/w==", - "dev": true, - "dependencies": { - "http-equiv-refresh": "^1.0.0", - "list-to-array": "^1.1.0", - "parse-srcset": "^1.0.2", - "promise-each": "^2.2.0" - }, - "engines": { - "node": ">= 4" - } - }, "node_modules/pretty-ms": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-8.0.0.tgz", @@ -3278,145 +3489,12 @@ "asap": "~2.0.3" } }, - "node_modules/promise-each": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/promise-each/-/promise-each-2.2.0.tgz", - "integrity": "sha512-67roqt1k3QDA41DZ8xi0V+rF3GoaMiX7QilbXu0vXimut+9RcKBNZ/t60xCRgcsihmNUsEjh48xLfNqOrKblUg==", - "dev": true, - "dependencies": { - "any-promise": "^0.1.0" - } - }, "node_modules/prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", "dev": true }, - "node_modules/pug": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.2.tgz", - "integrity": "sha512-bp0I/hiK1D1vChHh6EfDxtndHji55XP/ZJKwsRqrz6lRia6ZC2OZbdAymlxdVFwd1L70ebrVJw4/eZ79skrIaw==", - "dev": true, - "dependencies": { - "pug-code-gen": "^3.0.2", - "pug-filters": "^4.0.0", - "pug-lexer": "^5.0.1", - "pug-linker": "^4.0.0", - "pug-load": "^3.0.0", - "pug-parser": "^6.0.0", - "pug-runtime": "^3.0.1", - "pug-strip-comments": "^2.0.0" - } - }, - "node_modules/pug-attrs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-3.0.0.tgz", - "integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==", - "dev": true, - "dependencies": { - "constantinople": "^4.0.1", - "js-stringify": "^1.0.2", - "pug-runtime": "^3.0.0" - } - }, - "node_modules/pug-code-gen": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.2.tgz", - "integrity": "sha512-nJMhW16MbiGRiyR4miDTQMRWDgKplnHyeLvioEJYbk1RsPI3FuA3saEP8uwnTb2nTJEKBU90NFVWJBk4OU5qyg==", - "dev": true, - "dependencies": { - "constantinople": "^4.0.1", - "doctypes": "^1.1.0", - "js-stringify": "^1.0.2", - "pug-attrs": "^3.0.0", - "pug-error": "^2.0.0", - "pug-runtime": "^3.0.0", - "void-elements": "^3.1.0", - "with": "^7.0.0" - } - }, - "node_modules/pug-error": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.0.0.tgz", - "integrity": "sha512-sjiUsi9M4RAGHktC1drQfCr5C5eriu24Lfbt4s+7SykztEOwVZtbFk1RRq0tzLxcMxMYTBR+zMQaG07J/btayQ==", - "dev": true - }, - "node_modules/pug-filters": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-4.0.0.tgz", - "integrity": "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==", - "dev": true, - "dependencies": { - "constantinople": "^4.0.1", - "jstransformer": "1.0.0", - "pug-error": "^2.0.0", - "pug-walk": "^2.0.0", - "resolve": "^1.15.1" - } - }, - "node_modules/pug-lexer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.1.tgz", - "integrity": "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==", - "dev": true, - "dependencies": { - "character-parser": "^2.2.0", - "is-expression": "^4.0.0", - "pug-error": "^2.0.0" - } - }, - "node_modules/pug-linker": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-4.0.0.tgz", - "integrity": "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==", - "dev": true, - "dependencies": { - "pug-error": "^2.0.0", - "pug-walk": "^2.0.0" - } - }, - "node_modules/pug-load": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-3.0.0.tgz", - "integrity": "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==", - "dev": true, - "dependencies": { - "object-assign": "^4.1.1", - "pug-walk": "^2.0.0" - } - }, - "node_modules/pug-parser": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-6.0.0.tgz", - "integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==", - "dev": true, - "dependencies": { - "pug-error": "^2.0.0", - "token-stream": "1.0.0" - } - }, - "node_modules/pug-runtime": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-3.0.1.tgz", - "integrity": "sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==", - "dev": true - }, - "node_modules/pug-strip-comments": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz", - "integrity": "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==", - "dev": true, - "dependencies": { - "pug-error": "^2.0.0" - } - }, - "node_modules/pug-walk": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz", - "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==", - "dev": true - }, "node_modules/punycode.js": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", @@ -3445,6 +3523,15 @@ } ] }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -3457,44 +3544,6 @@ "node": ">=8.10.0" } }, - "node_modules/recursive-copy": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/recursive-copy/-/recursive-copy-2.0.14.tgz", - "integrity": "sha512-K8WNY8f8naTpfbA+RaXmkaQuD1IeW9EgNEfyGxSqqTQukpVtoOKros9jUqbpEsSw59YOmpd8nCBgtqJZy5nvog==", - "dev": true, - "dependencies": { - "errno": "^0.1.2", - "graceful-fs": "^4.1.4", - "junk": "^1.0.1", - "maximatch": "^0.1.0", - "mkdirp": "^0.5.1", - "pify": "^2.3.0", - "promise": "^7.0.1", - "rimraf": "^2.7.1", - "slash": "^1.0.0" - } - }, - "node_modules/recursive-copy/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/recursive-copy/node_modules/slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -3504,23 +3553,6 @@ "node": ">=0.10.0" } }, - "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", @@ -3553,20 +3585,55 @@ } }, "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", "dev": true, "dependencies": { - "glob": "^7.1.3" + "glob": "^10.3.7" }, "bin": { - "rimraf": "bin.js" + "rimraf": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rollup": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", + "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.24.0", + "@rollup/rollup-android-arm64": "4.24.0", + "@rollup/rollup-darwin-arm64": "4.24.0", + "@rollup/rollup-darwin-x64": "4.24.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.24.0", + "@rollup/rollup-linux-arm-musleabihf": "4.24.0", + "@rollup/rollup-linux-arm64-gnu": "4.24.0", + "@rollup/rollup-linux-arm64-musl": "4.24.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0", + "@rollup/rollup-linux-riscv64-gnu": "4.24.0", + "@rollup/rollup-linux-s390x-gnu": "4.24.0", + "@rollup/rollup-linux-x64-gnu": "4.24.0", + "@rollup/rollup-linux-x64-musl": "4.24.0", + "@rollup/rollup-win32-arm64-msvc": "4.24.0", + "@rollup/rollup-win32-ia32-msvc": "4.24.0", + "@rollup/rollup-win32-x64-msvc": "4.24.0", + "fsevents": "~2.3.2" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -3604,13 +3671,10 @@ } }, "node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -3624,6 +3688,57 @@ "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", "dev": true }, + "node_modules/send": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.1.tgz", + "integrity": "sha512-p4rRk4f23ynFEfcD9LA0xRYngj+IyGiEYyqqOak8kaN0TvNmuxC2dcVeBn62GpCeR2CpWqyHCNScTP91QbAVFg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/serialize-error": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", @@ -3656,6 +3771,12 @@ "node": ">= 0.4" } }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -3702,15 +3823,12 @@ } }, "node_modules/slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==", "dev": true, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, "node_modules/slice-ansi": { @@ -3738,15 +3856,6 @@ "node": ">=8.0.0" } }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -3754,15 +3863,15 @@ "dev": true }, "node_modules/ssri": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", - "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-11.0.0.tgz", + "integrity": "sha512-aZpUoMN/Jj2MqA4vMCeiKGnc/8SuSyHbGSBdgFbZxP8OJGF/lFkIuElzPxsN0q8TQQ+prw3P4EDfB3TBHHgfXw==", "dev": true, "dependencies": { - "minipass": "^3.1.1" + "minipass": "^7.0.3" }, "engines": { - "node": ">= 8" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/stack-utils": { @@ -3812,6 +3921,57 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", @@ -3827,6 +3987,28 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", @@ -3851,6 +4033,28 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "node_modules/supertap/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/supertap/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -3862,18 +4066,6 @@ "node": ">=8" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/temp-dir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", @@ -3897,19 +4089,31 @@ "node": ">=8" } }, - "node_modules/time-zone": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/time-zone/-/time-zone-1.0.0.tgz", - "integrity": "sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA==", + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, "engines": { - "node": ">=4" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "node_modules/time-zone": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/time-zone/-/time-zone-1.0.0.tgz", + "integrity": "sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA==", "dev": true, "engines": { "node": ">=4" @@ -3927,11 +4131,14 @@ "node": ">=8.0" } }, - "node_modules/token-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", - "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==", - "dev": true + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } }, "node_modules/type-detect": { "version": "4.0.8", @@ -3959,19 +4166,6 @@ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" }, - "node_modules/uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", - "dev": true, - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -3981,10 +4175,16 @@ "node": ">= 0.8" } }, + "node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", + "dev": true + }, "node_modules/v8-to-istanbul": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", - "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", @@ -3995,15 +4195,6 @@ "node": ">=10.12.0" } }, - "node_modules/void-elements": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", - "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/well-known-symbols": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/well-known-symbols/-/well-known-symbols-2.0.0.tgz", @@ -4028,28 +4219,25 @@ "node": ">= 8" } }, - "node_modules/with": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz", - "integrity": "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==", + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "dependencies": { - "@babel/parser": "^7.9.6", - "@babel/types": "^7.9.6", - "assert-never": "^1.2.1", - "babel-walk": "3.0.0-canary-5" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">= 10.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true - }, - "node_modules/wrap-ansi": { + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", @@ -4066,7 +4254,7 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", @@ -4075,7 +4263,7 @@ "node": ">=8" } }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", @@ -4090,13 +4278,13 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/emoji-regex": { + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { + "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", @@ -4105,7 +4293,7 @@ "node": ">=8" } }, - "node_modules/wrap-ansi/node_modules/string-width": { + "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", @@ -4119,7 +4307,7 @@ "node": ">=8" } }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", @@ -4163,9 +4351,9 @@ } }, "node_modules/ws": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", - "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, "engines": { "node": ">=10.0.0" @@ -4192,12 +4380,6 @@ "node": ">=10" } }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -4296,3143 +4478,5 @@ "url": "https://github.com/sponsors/sindresorhus" } } - }, - "dependencies": { - "@11ty/dependency-tree": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@11ty/dependency-tree/-/dependency-tree-2.0.1.tgz", - "integrity": "sha512-5R+DsT9LJ9tXiSQ4y+KLFppCkQyXhzAm1AIuBWE/sbU0hSXY5pkhoqQYEcPJQFg/nglL+wD55iv2j+7O96UAvg==", - "dev": true - }, - "@11ty/eleventy": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@11ty/eleventy/-/eleventy-2.0.1.tgz", - "integrity": "sha512-t8XVUbCJByhVEa1RzO0zS2QzbL3wPY8ot1yUw9noqiSHxJWUwv6jiwm1/MZDPTYtkZH2ZHvdQIRQ5/SjG9XmLw==", - "dev": true, - "requires": { - "@11ty/dependency-tree": "^2.0.1", - "@11ty/eleventy-dev-server": "^1.0.4", - "@11ty/eleventy-utils": "^1.0.1", - "@11ty/lodash-custom": "^4.17.21", - "@iarna/toml": "^2.2.5", - "@sindresorhus/slugify": "^1.1.2", - "bcp-47-normalize": "^1.1.1", - "chokidar": "^3.5.3", - "cross-spawn": "^7.0.3", - "debug": "^4.3.4", - "dependency-graph": "^0.11.0", - "ejs": "^3.1.9", - "fast-glob": "^3.2.12", - "graceful-fs": "^4.2.11", - "gray-matter": "^4.0.3", - "hamljs": "^0.6.2", - "handlebars": "^4.7.7", - "is-glob": "^4.0.3", - "iso-639-1": "^2.1.15", - "kleur": "^4.1.5", - "liquidjs": "^10.7.0", - "luxon": "^3.3.0", - "markdown-it": "^13.0.1", - "micromatch": "^4.0.5", - "minimist": "^1.2.8", - "moo": "^0.5.2", - "multimatch": "^5.0.0", - "mustache": "^4.2.0", - "normalize-path": "^3.0.0", - "nunjucks": "^3.2.3", - "path-to-regexp": "^6.2.1", - "please-upgrade-node": "^3.2.0", - "posthtml": "^0.16.6", - "posthtml-urls": "^1.0.0", - "pug": "^3.0.2", - "recursive-copy": "^2.0.14", - "semver": "^7.3.8", - "slugify": "^1.6.6" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "entities": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", - "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", - "dev": true - }, - "linkify-it": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", - "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", - "dev": true, - "requires": { - "uc.micro": "^1.0.1" - } - }, - "markdown-it": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.2.tgz", - "integrity": "sha512-FtwnEuuK+2yVU7goGn/MJ0WBZMM9ZPgU9spqlFs7/A/pDIUNSOQZhUgOqYCficIuR2QaFnrt8LHqBWsbTAoI5w==", - "dev": true, - "requires": { - "argparse": "^2.0.1", - "entities": "~3.0.1", - "linkify-it": "^4.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - } - }, - "mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", - "dev": true - }, - "uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", - "dev": true - } - } - }, - "@11ty/eleventy-dev-server": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@11ty/eleventy-dev-server/-/eleventy-dev-server-1.0.4.tgz", - "integrity": "sha512-qVBmV2G1KF/0o5B/3fITlrrDHy4bONUI2YuN3/WJ3BNw4NU1d/we8XhKrlgq13nNvHoBx5czYp3LZt8qRG53Fg==", - "dev": true, - "requires": { - "@11ty/eleventy-utils": "^1.0.1", - "chokidar": "^3.5.3", - "debug": "^4.3.4", - "dev-ip": "^1.0.1", - "finalhandler": "^1.2.0", - "mime": "^3.0.0", - "minimist": "^1.2.8", - "morphdom": "^2.7.0", - "please-upgrade-node": "^3.2.0", - "ssri": "^8.0.1", - "ws": "^8.13.0" - } - }, - "@11ty/eleventy-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@11ty/eleventy-utils/-/eleventy-utils-1.0.2.tgz", - "integrity": "sha512-Zy2leMK1DQR6Q6ZPSagv7QpJaAz9uVbb+RmVetYFp3foMeQtOSZx7w2u5daRFmP+PeNq9vO9H4xtBToYFWZwHA==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0" - } - }, - "@11ty/lodash-custom": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@11ty/lodash-custom/-/lodash-custom-4.17.21.tgz", - "integrity": "sha512-Mqt6im1xpb1Ykn3nbcCovWXK3ggywRJa+IXIdoz4wIIK+cvozADH63lexcuPpGS/gJ6/m2JxyyXDyupkMr5DHw==", - "dev": true - }, - "@babel/helper-string-parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", - "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", - "dev": true - }, - "@babel/helper-validator-identifier": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz", - "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==", - "dev": true - }, - "@babel/parser": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", - "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==", - "dev": true - }, - "@babel/types": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz", - "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==", - "dev": true, - "requires": { - "@babel/helper-string-parser": "^7.24.1", - "@babel/helper-validator-identifier": "^7.24.5", - "to-fast-properties": "^2.0.0" - } - }, - "@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "@iarna/toml": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", - "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==", - "dev": true - }, - "@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true - }, - "@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@sindresorhus/slugify": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-1.1.2.tgz", - "integrity": "sha512-V9nR/W0Xd9TSGXpZ4iFUcFGhuOJtZX82Fzxj1YISlbSgKvIiNa7eLEZrT0vAraPOt++KHauIVNYgGRgjc13dXA==", - "dev": true, - "requires": { - "@sindresorhus/transliterate": "^0.1.1", - "escape-string-regexp": "^4.0.0" - } - }, - "@sindresorhus/transliterate": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@sindresorhus/transliterate/-/transliterate-0.1.2.tgz", - "integrity": "sha512-5/kmIOY9FF32nicXH+5yLNTX4NJ4atl7jRgqAJuIn/iyDFXBktOKDxCvyGE/EzmF4ngSUvjXxQUQlQiZ5lfw+w==", - "dev": true, - "requires": { - "escape-string-regexp": "^2.0.0", - "lodash.deburr": "^4.1.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true - } - } - }, - "@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", - "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", - "dev": true, - "requires": { - "@sinonjs/commons": "^3.0.0" - } - }, - "@sinonjs/samsam": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", - "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", - "dev": true, - "requires": { - "@sinonjs/commons": "^2.0.0", - "lodash.get": "^4.4.2", - "type-detect": "^4.0.8" - }, - "dependencies": { - "@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - } - } - }, - "@sinonjs/text-encoding": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", - "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", - "dev": true - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true - }, - "@types/minimatch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", - "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", - "dev": true - }, - "a-sync-waterfall": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz", - "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==", - "dev": true - }, - "acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "dev": true - }, - "acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", - "dev": true - }, - "aggregate-error": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-4.0.1.tgz", - "integrity": "sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==", - "dev": true, - "requires": { - "clean-stack": "^4.0.0", - "indent-string": "^5.0.0" - } - }, - "ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true - }, - "ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true - }, - "any-promise": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-0.1.0.tgz", - "integrity": "sha512-lqzY9o+BbeGHRCOyxQkt/Tgvz0IZhTmQiA+LxQW8wSNpcTbj8K+0cZiSEvbpNZZP9/11Gy7dnLO3GNWUXO4d1g==", - "dev": true - }, - "anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "array-differ": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz", - "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==", - "dev": true - }, - "array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==", - "dev": true - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", - "dev": true - }, - "arrgv": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/arrgv/-/arrgv-1.0.2.tgz", - "integrity": "sha512-a4eg4yhp7mmruZDQFqVMlxNRFGi/i1r87pt8SDHy0/I8PqSXoUTlWZRdAZo0VXgvEARcujbtTk8kiZRi1uDGRw==", - "dev": true - }, - "arrify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-3.0.0.tgz", - "integrity": "sha512-tLkvA81vQG/XqE2mjDkGQHoOINtMHtysSnemrmoGe6PydDPMRbVugqyk4A6V/WDWEfm3l+0d8anA9r8cv/5Jaw==", - "dev": true - }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true - }, - "assert-never": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.2.1.tgz", - "integrity": "sha512-TaTivMB6pYI1kXwrFlEhLeGfOqoDNdTxjCdwRfFFkEA30Eu+k48W34nlok2EYWJfFFzqaEmichdNM7th6M5HNw==", - "dev": true - }, - "async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true - }, - "ava": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ava/-/ava-5.3.1.tgz", - "integrity": "sha512-Scv9a4gMOXB6+ni4toLuhAm9KYWEjsgBglJl+kMGI5+IVDt120CCDZyB5HNU9DjmLI2t4I0GbnxGLmmRfGTJGg==", - "dev": true, - "requires": { - "acorn": "^8.8.2", - "acorn-walk": "^8.2.0", - "ansi-styles": "^6.2.1", - "arrgv": "^1.0.2", - "arrify": "^3.0.0", - "callsites": "^4.0.0", - "cbor": "^8.1.0", - "chalk": "^5.2.0", - "chokidar": "^3.5.3", - "chunkd": "^2.0.1", - "ci-info": "^3.8.0", - "ci-parallel-vars": "^1.0.1", - "clean-yaml-object": "^0.1.0", - "cli-truncate": "^3.1.0", - "code-excerpt": "^4.0.0", - "common-path-prefix": "^3.0.0", - "concordance": "^5.0.4", - "currently-unhandled": "^0.4.1", - "debug": "^4.3.4", - "emittery": "^1.0.1", - "figures": "^5.0.0", - "globby": "^13.1.4", - "ignore-by-default": "^2.1.0", - "indent-string": "^5.0.0", - "is-error": "^2.2.2", - "is-plain-object": "^5.0.0", - "is-promise": "^4.0.0", - "matcher": "^5.0.0", - "mem": "^9.0.2", - "ms": "^2.1.3", - "p-event": "^5.0.1", - "p-map": "^5.5.0", - "picomatch": "^2.3.1", - "pkg-conf": "^4.0.0", - "plur": "^5.1.0", - "pretty-ms": "^8.0.0", - "resolve-cwd": "^3.0.0", - "stack-utils": "^2.0.6", - "strip-ansi": "^7.0.1", - "supertap": "^3.0.1", - "temp-dir": "^3.0.0", - "write-file-atomic": "^5.0.1", - "yargs": "^17.7.2" - }, - "dependencies": { - "chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true - } - } - }, - "babel-walk": { - "version": "3.0.0-canary-5", - "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz", - "integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==", - "dev": true, - "requires": { - "@babel/types": "^7.9.6" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "bcp-47": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-1.0.8.tgz", - "integrity": "sha512-Y9y1QNBBtYtv7hcmoX0tR+tUNSFZGZ6OL6vKPObq8BbOhkCoyayF6ogfLTgAli/KuAEbsYHYUNq2AQuY6IuLag==", - "dev": true, - "requires": { - "is-alphabetical": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-decimal": "^1.0.0" - } - }, - "bcp-47-match": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-1.0.3.tgz", - "integrity": "sha512-LggQ4YTdjWQSKELZF5JwchnBa1u0pIQSZf5lSdOHEdbVP55h0qICA/FUp3+W99q0xqxYa1ZQizTUH87gecII5w==", - "dev": true - }, - "bcp-47-normalize": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/bcp-47-normalize/-/bcp-47-normalize-1.1.1.tgz", - "integrity": "sha512-jWZ1Jdu3cs0EZdfCkS0UE9Gg01PtxnChjEBySeB+Zo6nkqtFfnvtoQQgP1qU1Oo4qgJgxhTI6Sf9y/pZIhPs0A==", - "dev": true, - "requires": { - "bcp-47": "^1.0.0", - "bcp-47-match": "^1.0.0" - } - }, - "binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true - }, - "blueimp-md5": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz", - "integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "c8": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/c8/-/c8-7.14.0.tgz", - "integrity": "sha512-i04rtkkcNcCf7zsQcSv/T9EbUn4RXQ6mropeMcjFOsQXQ0iGLAr/xT6TImQg4+U9hmNpN9XdvPkjUL1IzbgxJw==", - "dev": true, - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@istanbuljs/schema": "^0.1.3", - "find-up": "^5.0.0", - "foreground-child": "^2.0.0", - "istanbul-lib-coverage": "^3.2.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-reports": "^3.1.4", - "rimraf": "^3.0.2", - "test-exclude": "^6.0.0", - "v8-to-istanbul": "^9.0.0", - "yargs": "^16.2.0", - "yargs-parser": "^20.2.9" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - } - } - }, - "call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dev": true, - "requires": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - } - }, - "callsites": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-4.1.0.tgz", - "integrity": "sha512-aBMbD1Xxay75ViYezwT40aQONfr+pSXTHwNKvIXhXD6+LY3F1dLIcceoC5OZKBVHbXcysz1hL9D2w0JJIMXpUw==", - "dev": true - }, - "cbor": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/cbor/-/cbor-8.1.0.tgz", - "integrity": "sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg==", - "dev": true, - "requires": { - "nofilter": "^3.1.0" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - } - } - }, - "character-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", - "integrity": "sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==", - "dev": true, - "requires": { - "is-regex": "^1.0.3" - } - }, - "chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - } - }, - "chunkd": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/chunkd/-/chunkd-2.0.1.tgz", - "integrity": "sha512-7d58XsFmOq0j6el67Ug9mHf9ELUXsQXYJBkyxhH/k+6Ke0qXRnv0kbemx+Twc6fRJ07C49lcbdgm9FL1Ei/6SQ==", - "dev": true - }, - "ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true - }, - "ci-parallel-vars": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ci-parallel-vars/-/ci-parallel-vars-1.0.1.tgz", - "integrity": "sha512-uvzpYrpmidaoxvIQHM+rKSrigjOe9feHYbw4uOI2gdfe1C3xIlxO+kVXq83WQWNniTf8bAxVpy+cQeFQsMERKg==", - "dev": true - }, - "clean-stack": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-4.2.0.tgz", - "integrity": "sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==", - "dev": true, - "requires": { - "escape-string-regexp": "5.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true - } - } - }, - "clean-yaml-object": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/clean-yaml-object/-/clean-yaml-object-0.1.0.tgz", - "integrity": "sha512-3yONmlN9CSAkzNwnRCiJQ7Q2xK5mWuEfL3PuTZcAUzhObbXsfsnMptJzXwz93nc5zn9V9TwCVMmV7w4xsm43dw==", - "dev": true - }, - "cli-truncate": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", - "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", - "dev": true, - "requires": { - "slice-ansi": "^5.0.0", - "string-width": "^5.0.0" - } - }, - "cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } - } - }, - "code-excerpt": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", - "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==", - "dev": true, - "requires": { - "convert-to-spaces": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "dev": true - }, - "common-path-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", - "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "concordance": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/concordance/-/concordance-5.0.4.tgz", - "integrity": "sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==", - "dev": true, - "requires": { - "date-time": "^3.1.0", - "esutils": "^2.0.3", - "fast-diff": "^1.2.0", - "js-string-escape": "^1.0.1", - "lodash": "^4.17.15", - "md5-hex": "^3.0.1", - "semver": "^7.3.2", - "well-known-symbols": "^2.0.0" - } - }, - "constantinople": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", - "integrity": "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==", - "dev": true, - "requires": { - "@babel/parser": "^7.6.0", - "@babel/types": "^7.6.1" - } - }, - "convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "convert-to-spaces": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", - "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==", - "dev": true - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==", - "dev": true, - "requires": { - "array-find-index": "^1.0.1" - } - }, - "date-time": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/date-time/-/date-time-3.1.0.tgz", - "integrity": "sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg==", - "dev": true, - "requires": { - "time-zone": "^1.0.0" - } - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "requires": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - } - }, - "dependency-graph": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", - "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", - "dev": true - }, - "dev-ip": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dev-ip/-/dev-ip-1.0.1.tgz", - "integrity": "sha512-LmVkry/oDShEgSZPNgqCIp2/TlqtExeGmymru3uCELnfyjY11IzpAproLYs+1X88fXO6DBoYP3ul2Xo2yz2j6A==", - "dev": true - }, - "diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", - "dev": true - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "doctypes": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", - "integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==", - "dev": true - }, - "dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "dev": true, - "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "dependencies": { - "entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true - } - } - }, - "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": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "dev": true, - "requires": { - "domelementtype": "^2.2.0" - } - }, - "domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "dev": true, - "requires": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - } - }, - "eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true - }, - "ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", - "dev": true, - "requires": { - "jake": "^10.8.5" - } - }, - "emittery": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-1.0.3.tgz", - "integrity": "sha512-tJdCJitoy2lrC2ldJcqN4vkqJ00lT+tOWNT1hBJjO/3FDMJa5TTIiYGCKGkn/WfCyOzUMObeohbVTj00fhiLiA==", - "dev": true - }, - "emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true - }, - "entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" - }, - "errno": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "dev": true, - "requires": { - "prr": "~1.0.1" - } - }, - "es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dev": true, - "requires": { - "get-intrinsic": "^1.2.4" - } - }, - "es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true - }, - "escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", - "dev": true - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "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 - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "fast-diff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true - }, - "fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - } - }, - "fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "figures": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz", - "integrity": "sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==", - "dev": true, - "requires": { - "escape-string-regexp": "^5.0.0", - "is-unicode-supported": "^1.2.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true - } - } - }, - "filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dev": true, - "requires": { - "minimatch": "^5.0.1" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "dev": true, - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - } - } - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "foreground-child": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", - "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.0", - "signal-exit": "^3.0.2" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dev": true, - "requires": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - } - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "globby": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", - "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", - "dev": true, - "requires": { - "dir-glob": "^3.0.1", - "fast-glob": "^3.3.0", - "ignore": "^5.2.4", - "merge2": "^1.4.1", - "slash": "^4.0.0" - } - }, - "gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.3" - } - }, - "graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "gray-matter": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", - "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", - "dev": true, - "requires": { - "js-yaml": "^3.13.1", - "kind-of": "^6.0.2", - "section-matter": "^1.0.0", - "strip-bom-string": "^1.0.0" - } - }, - "hamljs": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/hamljs/-/hamljs-0.6.2.tgz", - "integrity": "sha512-/chXRp4WpL47I+HX1vCCdSbEXAljEG2FBMmgO7Am0bYsqgnEjreeWzUdX1onXqwZtcfgxbCg5WtEYYvuZ5muBg==", - "dev": true - }, - "handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4", - "wordwrap": "^1.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "requires": { - "es-define-property": "^1.0.0" - } - }, - "has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "dev": true - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true - }, - "has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "requires": { - "has-symbols": "^1.0.3" - } - }, - "hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "requires": { - "function-bind": "^1.1.2" - } - }, - "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "htmlparser2": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", - "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==", - "dev": true, - "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.2", - "domutils": "^2.8.0", - "entities": "^3.0.1" - }, - "dependencies": { - "entities": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", - "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", - "dev": true - } - } - }, - "http-equiv-refresh": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/http-equiv-refresh/-/http-equiv-refresh-1.0.0.tgz", - "integrity": "sha512-TScO04soylRN9i/QdOdgZyhydXg9z6XdaGzEyOgDKycePeDeTT4KvigjBcI+tgfTlieLWauGORMq5F1eIDa+1w==", - "dev": true - }, - "ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", - "dev": true - }, - "ignore-by-default": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-2.1.0.tgz", - "integrity": "sha512-yiWd4GVmJp0Q6ghmM2B/V3oZGRmjrKLXvHR3TE1nfoXsmoggllfZUQe74EN0fJdPFZu2NIvNdrMMLm3OsV7Ohw==", - "dev": true - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true - }, - "indent-string": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", - "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "irregular-plurals": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-3.5.0.tgz", - "integrity": "sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ==", - "dev": true - }, - "is-alphabetical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", - "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", - "dev": true - }, - "is-alphanumerical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", - "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", - "dev": true, - "requires": { - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0" - } - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", - "dev": true, - "requires": { - "hasown": "^2.0.0" - } - }, - "is-decimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", - "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", - "dev": true - }, - "is-error": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-error/-/is-error-2.2.2.tgz", - "integrity": "sha512-IOQqts/aHWbiisY5DuPJQ0gcbvaLFCa7fBa9xoLfxBZvQ+ZI/Zh9xoI7Gk+G64N0FdK4AbibytHht2tWgpJWLg==", - "dev": true - }, - "is-expression": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz", - "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==", - "dev": true, - "requires": { - "acorn": "^7.1.1", - "object-assign": "^4.1.1" - }, - "dependencies": { - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - } - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-json": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-json/-/is-json-2.0.1.tgz", - "integrity": "sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "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-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "dev": true - }, - "is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "iso-639-1": { - "version": "2.1.15", - "resolved": "https://registry.npmjs.org/iso-639-1/-/iso-639-1-2.1.15.tgz", - "integrity": "sha512-7c7mBznZu2ktfvyT582E2msM+Udc1EjOyhVRE/0ZsjD9LBtWSm23h3PtiRh2a35XoUsTQQjJXaJzuLjXsOdFDg==", - "dev": true - }, - "istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true - }, - "istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - } - }, - "istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "jake": { - "version": "10.8.7", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", - "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==", - "dev": true, - "requires": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.4", - "minimatch": "^3.1.2" - } - }, - "js-string-escape": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", - "integrity": "sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==", - "dev": true - }, - "js-stringify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", - "integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==", - "dev": true - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jstransformer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", - "integrity": "sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==", - "dev": true, - "requires": { - "is-promise": "^2.0.0", - "promise": "^7.0.1" - }, - "dependencies": { - "is-promise": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", - "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", - "dev": true - } - } - }, - "junk": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/junk/-/junk-1.0.3.tgz", - "integrity": "sha512-3KF80UaaSSxo8jVnRYtMKNGFOoVPBdkkVPsw+Ad0y4oxKXPduS6G6iHkrf69yJVff/VAaYXkV42rtZ7daJxU3w==", - "dev": true - }, - "just-extend": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", - "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", - "dev": true - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "dev": true - }, - "linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", - "requires": { - "uc.micro": "^2.0.0" - } - }, - "liquidjs": { - "version": "10.12.0", - "resolved": "https://registry.npmjs.org/liquidjs/-/liquidjs-10.12.0.tgz", - "integrity": "sha512-ZpT27WEqUu8IeddXoLbdeBTbRfV5r7oUKDjJMthuQKQTScgI8pbLGbSWiiAktQVpPG7mHMGsJ0JVbZYn1w9Gtg==", - "dev": true, - "requires": { - "commander": "^10.0.0" - } - }, - "list-to-array": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/list-to-array/-/list-to-array-1.1.0.tgz", - "integrity": "sha512-+dAZZ2mM+/m+vY9ezfoueVvrgnHIGi5FvgSymbIgJOFwiznWyA59mav95L+Mc6xPtL3s9gm5eNTlNtxJLbNM1g==", - "dev": true - }, - "load-json-file": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-7.0.1.tgz", - "integrity": "sha512-Gnxj3ev3mB5TkVBGad0JM6dmLiQL+o0t23JPBZ9sd+yvSLk05mFoqKBw5N8gbbkU4TNXyqCgIrl/VM17OgUIgQ==", - "dev": true - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lodash.deburr": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/lodash.deburr/-/lodash.deburr-4.1.0.tgz", - "integrity": "sha512-m/M1U1f3ddMCs6Hq2tAsYThTBDaAKFDX3dwDo97GEYzamXi9SqUpjWi/Rrj/gf3X2n8ktwgZrlP1z6E3v/IExQ==", - "dev": true - }, - "lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", - "dev": true - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "luxon": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", - "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", - "dev": true - }, - "make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "requires": { - "semver": "^7.5.3" - } - }, - "map-age-cleaner": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", - "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", - "dev": true, - "requires": { - "p-defer": "^1.0.0" - } - }, - "markdown-it": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", - "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", - "requires": { - "argparse": "^2.0.1", - "entities": "^4.4.0", - "linkify-it": "^5.0.0", - "mdurl": "^2.0.0", - "punycode.js": "^2.3.1", - "uc.micro": "^2.1.0" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - } - } - }, - "matcher": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/matcher/-/matcher-5.0.0.tgz", - "integrity": "sha512-s2EMBOWtXFc8dgqvoAzKJXxNHibcdJMV0gwqKUaw9E2JBJuGUK7DrNKrA6g/i+v72TT16+6sVm5mS3thaMLQUw==", - "dev": true, - "requires": { - "escape-string-regexp": "^5.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true - } - } - }, - "maximatch": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/maximatch/-/maximatch-0.1.0.tgz", - "integrity": "sha512-9ORVtDUFk4u/NFfo0vG/ND/z7UQCVZBL539YW0+U1I7H1BkZwizcPx5foFv7LCPcBnm2U6RjFnQOsIvN4/Vm2A==", - "dev": true, - "requires": { - "array-differ": "^1.0.0", - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "minimatch": "^3.0.0" - }, - "dependencies": { - "array-differ": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", - "integrity": "sha512-LeZY+DZDRnvP7eMuQ6LHfCzUGxAAIViUBliK24P3hWXL6y4SortgR6Nim6xrkfSLlmH0+k+9NYNwVC2s53ZrYQ==", - "dev": true - }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", - "dev": true, - "requires": { - "array-uniq": "^1.0.1" - } - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", - "dev": true - } - } - }, - "md5-hex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-3.0.1.tgz", - "integrity": "sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==", - "dev": true, - "requires": { - "blueimp-md5": "^2.10.0" - } - }, - "mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==" - }, - "mem": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/mem/-/mem-9.0.2.tgz", - "integrity": "sha512-F2t4YIv9XQUBHt6AOJ0y7lSmP1+cY7Fm1DRh9GClTGzKST7UWLMx6ly9WZdLH/G/ppM5RL4MlQfRT71ri9t19A==", - "dev": true, - "requires": { - "map-age-cleaner": "^0.1.3", - "mimic-fn": "^4.0.0" - } - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, - "mime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", - "dev": true - }, - "mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true - }, - "minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "requires": { - "minimist": "^1.2.6" - } - }, - "moo": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", - "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==", - "dev": true - }, - "morphdom": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/morphdom/-/morphdom-2.7.2.tgz", - "integrity": "sha512-Dqb/lHFyTi7SZpY0a5R4I/0Edo+iPMbaUexsHHsLAByyixCDiLHPHyVoKVmrpL0THcT7V9Cgev9y21TQYq6wQg==", - "dev": true - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "multimatch": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz", - "integrity": "sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==", - "dev": true, - "requires": { - "@types/minimatch": "^3.0.3", - "array-differ": "^3.0.0", - "array-union": "^2.1.0", - "arrify": "^2.0.1", - "minimatch": "^3.0.4" - }, - "dependencies": { - "arrify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", - "dev": true - } - } - }, - "mustache": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", - "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", - "dev": true - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "nise": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", - "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", - "dev": true, - "requires": { - "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^11.2.2", - "@sinonjs/text-encoding": "^0.7.2", - "just-extend": "^6.2.0", - "path-to-regexp": "^6.2.1" - } - }, - "nofilter": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", - "integrity": "sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "nunjucks": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.4.tgz", - "integrity": "sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==", - "dev": true, - "requires": { - "a-sync-waterfall": "^1.0.0", - "asap": "^2.0.3", - "commander": "^5.1.0" - }, - "dependencies": { - "commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", - "dev": true - } - } - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "p-defer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==", - "dev": true - }, - "p-event": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/p-event/-/p-event-5.0.1.tgz", - "integrity": "sha512-dd589iCQ7m1L0bmC5NLlVYfy3TbBEsMUfWx9PyAgPeIcFZ/E2yaTZ4Rz4MiBmmJShviiftHVXOqfnfzJ6kyMrQ==", - "dev": true, - "requires": { - "p-timeout": "^5.0.2" - } - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "p-map": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-5.5.0.tgz", - "integrity": "sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==", - "dev": true, - "requires": { - "aggregate-error": "^4.0.0" - } - }, - "p-timeout": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-5.1.0.tgz", - "integrity": "sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==", - "dev": true - }, - "parse-ms": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-3.0.0.tgz", - "integrity": "sha512-Tpb8Z7r7XbbtBTrM9UhpkzzaMrqA2VXMT3YChzYltwV3P3pM6t8wl7TvpMnSTosz1aQAdVib7kdoys7vYOPerw==", - "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 - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "path-to-regexp": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", - "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true - }, - "pkg-conf": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-4.0.0.tgz", - "integrity": "sha512-7dmgi4UY4qk+4mj5Cd8v/GExPo0K+SlY+hulOSdfZ/T6jVH6//y7NtzZo5WrfhDBxuQ0jCa7fLZmNaNh7EWL/w==", - "dev": true, - "requires": { - "find-up": "^6.0.0", - "load-json-file": "^7.0.0" - }, - "dependencies": { - "find-up": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", - "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", - "dev": true, - "requires": { - "locate-path": "^7.1.0", - "path-exists": "^5.0.0" - } - }, - "locate-path": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", - "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", - "dev": true, - "requires": { - "p-locate": "^6.0.0" - } - }, - "p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", - "dev": true, - "requires": { - "yocto-queue": "^1.0.0" - } - }, - "p-locate": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", - "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", - "dev": true, - "requires": { - "p-limit": "^4.0.0" - } - }, - "path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", - "dev": true - }, - "yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", - "dev": true - } - } - }, - "please-upgrade-node": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", - "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", - "dev": true, - "requires": { - "semver-compare": "^1.0.0" - } - }, - "plur": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/plur/-/plur-5.1.0.tgz", - "integrity": "sha512-VP/72JeXqak2KiOzjgKtQen5y3IZHn+9GOuLDafPv0eXa47xq0At93XahYBs26MsifCQ4enGKwbjBTKgb9QJXg==", - "dev": true, - "requires": { - "irregular-plurals": "^3.3.0" - } - }, - "posthtml": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.16.6.tgz", - "integrity": "sha512-JcEmHlyLK/o0uGAlj65vgg+7LIms0xKXe60lcDOTU7oVX/3LuEuLwrQpW3VJ7de5TaFKiW4kWkaIpJL42FEgxQ==", - "dev": true, - "requires": { - "posthtml-parser": "^0.11.0", - "posthtml-render": "^3.0.0" - } - }, - "posthtml-parser": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.11.0.tgz", - "integrity": "sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw==", - "dev": true, - "requires": { - "htmlparser2": "^7.1.1" - } - }, - "posthtml-render": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/posthtml-render/-/posthtml-render-3.0.0.tgz", - "integrity": "sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA==", - "dev": true, - "requires": { - "is-json": "^2.0.1" - } - }, - "posthtml-urls": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/posthtml-urls/-/posthtml-urls-1.0.0.tgz", - "integrity": "sha512-CMJ0L009sGQVUuYM/g6WJdscsq6ooAwhUuF6CDlYPMLxKp2rmCYVebEU+wZGxnQstGJhZPMvXsRhtqekILd5/w==", - "dev": true, - "requires": { - "http-equiv-refresh": "^1.0.0", - "list-to-array": "^1.1.0", - "parse-srcset": "^1.0.2", - "promise-each": "^2.2.0" - } - }, - "pretty-ms": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-8.0.0.tgz", - "integrity": "sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q==", - "dev": true, - "requires": { - "parse-ms": "^3.0.0" - } - }, - "promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", - "dev": true, - "requires": { - "asap": "~2.0.3" - } - }, - "promise-each": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/promise-each/-/promise-each-2.2.0.tgz", - "integrity": "sha512-67roqt1k3QDA41DZ8xi0V+rF3GoaMiX7QilbXu0vXimut+9RcKBNZ/t60xCRgcsihmNUsEjh48xLfNqOrKblUg==", - "dev": true, - "requires": { - "any-promise": "^0.1.0" - } - }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", - "dev": true - }, - "pug": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.2.tgz", - "integrity": "sha512-bp0I/hiK1D1vChHh6EfDxtndHji55XP/ZJKwsRqrz6lRia6ZC2OZbdAymlxdVFwd1L70ebrVJw4/eZ79skrIaw==", - "dev": true, - "requires": { - "pug-code-gen": "^3.0.2", - "pug-filters": "^4.0.0", - "pug-lexer": "^5.0.1", - "pug-linker": "^4.0.0", - "pug-load": "^3.0.0", - "pug-parser": "^6.0.0", - "pug-runtime": "^3.0.1", - "pug-strip-comments": "^2.0.0" - } - }, - "pug-attrs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-3.0.0.tgz", - "integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==", - "dev": true, - "requires": { - "constantinople": "^4.0.1", - "js-stringify": "^1.0.2", - "pug-runtime": "^3.0.0" - } - }, - "pug-code-gen": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.2.tgz", - "integrity": "sha512-nJMhW16MbiGRiyR4miDTQMRWDgKplnHyeLvioEJYbk1RsPI3FuA3saEP8uwnTb2nTJEKBU90NFVWJBk4OU5qyg==", - "dev": true, - "requires": { - "constantinople": "^4.0.1", - "doctypes": "^1.1.0", - "js-stringify": "^1.0.2", - "pug-attrs": "^3.0.0", - "pug-error": "^2.0.0", - "pug-runtime": "^3.0.0", - "void-elements": "^3.1.0", - "with": "^7.0.0" - } - }, - "pug-error": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.0.0.tgz", - "integrity": "sha512-sjiUsi9M4RAGHktC1drQfCr5C5eriu24Lfbt4s+7SykztEOwVZtbFk1RRq0tzLxcMxMYTBR+zMQaG07J/btayQ==", - "dev": true - }, - "pug-filters": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-4.0.0.tgz", - "integrity": "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==", - "dev": true, - "requires": { - "constantinople": "^4.0.1", - "jstransformer": "1.0.0", - "pug-error": "^2.0.0", - "pug-walk": "^2.0.0", - "resolve": "^1.15.1" - } - }, - "pug-lexer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.1.tgz", - "integrity": "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==", - "dev": true, - "requires": { - "character-parser": "^2.2.0", - "is-expression": "^4.0.0", - "pug-error": "^2.0.0" - } - }, - "pug-linker": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-4.0.0.tgz", - "integrity": "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==", - "dev": true, - "requires": { - "pug-error": "^2.0.0", - "pug-walk": "^2.0.0" - } - }, - "pug-load": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-3.0.0.tgz", - "integrity": "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==", - "dev": true, - "requires": { - "object-assign": "^4.1.1", - "pug-walk": "^2.0.0" - } - }, - "pug-parser": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-6.0.0.tgz", - "integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==", - "dev": true, - "requires": { - "pug-error": "^2.0.0", - "token-stream": "1.0.0" - } - }, - "pug-runtime": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-3.0.1.tgz", - "integrity": "sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==", - "dev": true - }, - "pug-strip-comments": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz", - "integrity": "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==", - "dev": true, - "requires": { - "pug-error": "^2.0.0" - } - }, - "pug-walk": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz", - "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==", - "dev": true - }, - "punycode.js": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", - "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==" - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "recursive-copy": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/recursive-copy/-/recursive-copy-2.0.14.tgz", - "integrity": "sha512-K8WNY8f8naTpfbA+RaXmkaQuD1IeW9EgNEfyGxSqqTQukpVtoOKros9jUqbpEsSw59YOmpd8nCBgtqJZy5nvog==", - "dev": true, - "requires": { - "errno": "^0.1.2", - "graceful-fs": "^4.1.4", - "junk": "^1.0.1", - "maximatch": "^0.1.0", - "mkdirp": "^0.5.1", - "pify": "^2.3.0", - "promise": "^7.0.1", - "rimraf": "^2.7.1", - "slash": "^1.0.0" - }, - "dependencies": { - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==", - "dev": true - } - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true - }, - "resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, - "requires": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - } - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "section-matter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", - "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "kind-of": "^6.0.0" - } - }, - "semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "semver-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", - "dev": true - }, - "serialize-error": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", - "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", - "dev": true, - "requires": { - "type-fest": "^0.13.1" - } - }, - "set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "requires": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "sinon": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", - "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==", - "dev": true, - "requires": { - "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^11.2.2", - "@sinonjs/samsam": "^8.0.0", - "diff": "^5.1.0", - "nise": "^5.1.5", - "supports-color": "^7.2.0" - } - }, - "slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", - "dev": true - }, - "slice-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", - "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", - "dev": true, - "requires": { - "ansi-styles": "^6.0.0", - "is-fullwidth-code-point": "^4.0.0" - } - }, - "slugify": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz", - "integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "ssri": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", - "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "dev": true, - "requires": { - "minipass": "^3.1.1" - } - }, - "stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "requires": { - "escape-string-regexp": "^2.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true - } - } - }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true - }, - "string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "requires": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - } - }, - "strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "requires": { - "ansi-regex": "^6.0.1" - } - }, - "strip-bom-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", - "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", - "dev": true - }, - "supertap": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/supertap/-/supertap-3.0.1.tgz", - "integrity": "sha512-u1ZpIBCawJnO+0QePsEiOknOfCRq0yERxiAchT0i4li0WHNUJbf0evXXSXOcCAR4M8iMDoajXYmstm/qO81Isw==", - "dev": true, - "requires": { - "indent-string": "^5.0.0", - "js-yaml": "^3.14.1", - "serialize-error": "^7.0.1", - "strip-ansi": "^7.0.1" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true - }, - "temp-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", - "integrity": "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==", - "dev": true - }, - "test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - } - }, - "time-zone": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/time-zone/-/time-zone-1.0.0.tgz", - "integrity": "sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA==", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "token-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", - "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==", - "dev": true - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", - "dev": true - }, - "uc.micro": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", - "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" - }, - "uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", - "dev": true, - "optional": true - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true - }, - "v8-to-istanbul": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", - "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - } - }, - "void-elements": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", - "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", - "dev": true - }, - "well-known-symbols": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/well-known-symbols/-/well-known-symbols-2.0.0.tgz", - "integrity": "sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "with": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz", - "integrity": "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==", - "dev": true, - "requires": { - "@babel/parser": "^7.9.6", - "@babel/types": "^7.9.6", - "assert-never": "^1.2.1", - "babel-walk": "3.0.0-canary-5" - } - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "write-file-atomic": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", - "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" - }, - "dependencies": { - "signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true - } - } - }, - "ws": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", - "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", - "dev": true, - "requires": {} - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true - } - } - }, - "yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - } } } diff --git a/package.json b/package.json index 570b908..60d65e5 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,8 @@ ], "main": "index.js", "scripts": { + "buildcjs": "rollup index.js --file index.cjs --format cjs", + "prepublishOnly": "npm run buildcjs", "test": "ava", "test-coverage": "c8 ava" }, @@ -39,11 +41,12 @@ ".cache" ] }, - "files": [ - "index.js", - "index.d.ts", - "src" - ], + "type": "module", + "exports": { + "import": "./index.js", + "require": "./index.cjs" + }, + "types": "index.d.ts", "engines": { "node": ">=18" }, @@ -53,9 +56,10 @@ "markdown-it": "^14.1.0" }, "devDependencies": { - "@11ty/eleventy": "^2.0.0", + "@11ty/eleventy": "^3.0.0", "ava": "^5.2.0", "c8": "^7.12.0", + "rollup": "^4.24.0", "sinon": "^17.0.1" }, "directories": { diff --git a/src/dead-links.js b/src/dead-links.js index 1326d21..1eda375 100644 --- a/src/dead-links.js +++ b/src/dead-links.js @@ -1,8 +1,8 @@ -const path = require('node:path'); -const chalk = require("chalk"); -const fs = require('node:fs'); +import path from 'node:path'; +import chalk from 'chalk'; +import fs from 'node:fs'; -module.exports = class DeadLinks { +export default class DeadLinks { constructor() { this.gravestones = new Map; this.fileSrc = 'unknown'; diff --git a/src/find-page.js b/src/find-page.js index 33a0d74..b5b36e7 100644 --- a/src/find-page.js +++ b/src/find-page.js @@ -5,7 +5,7 @@ * @param {Array} allPages * @return {import('@photogabble/eleventy-plugin-interlinker').PageDirectoryService} */ -const pageLookup = (allPages = []) => { +export const pageLookup = (allPages = []) => { return { findByLink: (link) => { let foundByAlias = false; @@ -49,7 +49,3 @@ const pageLookup = (allPages = []) => { findByFile: (file) => allPages.find((page) => page.url === file.page.url), } } - -module.exports = { - pageLookup -} diff --git a/src/html-link-parser.js b/src/html-link-parser.js index 95044f4..1027a3f 100644 --- a/src/html-link-parser.js +++ b/src/html-link-parser.js @@ -1,4 +1,4 @@ -module.exports = class HTMLLinkParser { +export default class HTMLLinkParser { /** * This regex finds all html tags with a href that begins with / denoting they are internal links. diff --git a/src/interlinker.js b/src/interlinker.js index 694fa3f..14bb10d 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -1,13 +1,13 @@ -const HTMLLinkParser = require("./html-link-parser"); -const WikilinkParser = require("./wikilink-parser"); -const DeadLinks = require("./dead-links"); -const {pageLookup} = require("./find-page"); +import HTMLLinkParser from './html-link-parser.js'; +import WikilinkParser from './wikilink-parser.js'; +import DeadLinks from './dead-links.js'; +import {pageLookup} from './find-page.js'; /** * Interlinker: * */ -module.exports = class Interlinker { +export default class Interlinker { /** * @param {import('@photogabble/eleventy-plugin-interlinker').EleventyPluginInterlinkOptions} opts */ diff --git a/src/markdown-ext.js b/src/markdown-ext.js index 3801f4e..6ae3dc7 100644 --- a/src/markdown-ext.js +++ b/src/markdown-ext.js @@ -4,7 +4,7 @@ * @param {WikilinkParser} wikilinkParser * @returns {(function(*, *): (boolean|undefined))|*} */ -const wikilinkInlineRule = (wikilinkParser) => (state, silent) => { +export const wikilinkInlineRule = (wikilinkParser) => (state, silent) => { // Have we found the start of a WikiLink Embed `![[` if (['!', '['].includes(state.src.charAt(state.pos)) === false || silent) return false; @@ -47,12 +47,7 @@ const wikilinkInlineRule = (wikilinkParser) => (state, silent) => { /** * @returns {(function(*, *): (string))|*} */ -const wikilinkRenderRule = () => (tokens, idx) => { +export const wikilinkRenderRule = () => (tokens, idx) => { const {meta} = tokens[idx]; return meta.content; }; - -module.exports = { - wikilinkRenderRule, - wikilinkInlineRule -} diff --git a/src/resolvers.js b/src/resolvers.js index ff860b1..fcf31f2 100644 --- a/src/resolvers.js +++ b/src/resolvers.js @@ -1,5 +1,6 @@ -const {EleventyRenderPlugin} = require("@11ty/eleventy"); -const entities = require("entities"); +import {EleventyRenderPlugin} from "@11ty/eleventy"; +import {encodeHTML} from 'entities'; + /** * Default Resolving function for converting Wikilinks into html links. * @@ -8,8 +9,8 @@ const entities = require("entities"); * @param {import('./interlinker')} interlinker * @return {Promise} */ -const defaultResolvingFn = async (link, currentPage, interlinker) => { - const text = entities.encodeHTML(link.title ?? link.name); +export const defaultResolvingFn = async (link, currentPage, interlinker) => { + const text = encodeHTML(link.title ?? link.name); let href = link.href; if (link.anchor) { @@ -27,7 +28,7 @@ const defaultResolvingFn = async (link, currentPage, interlinker) => { * @param {import('./interlinker')} interlinker * @return {Promise} */ -const defaultEmbedFn = async (link, currentPage, interlinker) => { +export const defaultEmbedFn = async (link, currentPage, interlinker) => { if (!link.exists || !interlinker.templateConfig || !interlinker.extensionMap) return; const page = link.page; @@ -58,8 +59,3 @@ const defaultEmbedFn = async (link, currentPage, interlinker) => { return fn({content: frontMatter.content, ...page.data}); } - -module.exports = { - defaultEmbedFn, - defaultResolvingFn, -} diff --git a/src/wikilink-parser.js b/src/wikilink-parser.js index b32e41d..fbd6206 100644 --- a/src/wikilink-parser.js +++ b/src/wikilink-parser.js @@ -1,4 +1,4 @@ -module.exports = class WikilinkParser { +export default class WikilinkParser { /** * This regex finds all WikiLink style links: [[id|optional text]] as well as WikiLink style embeds: ![[id]] * diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index c38b33d..8258d87 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -1,8 +1,8 @@ -const Eleventy = require("@11ty/eleventy"); -const {normalize, consoleMockMessages, findResultByUrl, fixturePath} = require('./helpers'); -const fs = require('node:fs'); -const sinon = require("sinon"); -const test = require("ava"); +import Eleventy from '@11ty/eleventy'; +import {normalize, consoleMockMessages, findResultByUrl, fixturePath} from './helpers.js'; +import fs from 'node:fs'; +import sinon from 'sinon'; +import test from 'ava'; // NOTE: Tests using sinon to mock console.warn need to be run with // `test.serial` so that they don't run at the same time as one another to diff --git a/tests/find-page-service.test.js b/tests/find-page-service.test.js index 8eff7c0..7677cb1 100644 --- a/tests/find-page-service.test.js +++ b/tests/find-page-service.test.js @@ -1,5 +1,5 @@ -const {pageLookup} = require("../src/find-page"); -const test = require("ava"); +import {pageLookup} from '../src/find-page.js'; +import test from 'ava'; const pageDirectory = pageLookup([ { diff --git a/tests/fixtures/sample-small-website/eleventy.config.js b/tests/fixtures/sample-small-website/eleventy.config.js index 9b10b5c..17fa5da 100644 --- a/tests/fixtures/sample-small-website/eleventy.config.js +++ b/tests/fixtures/sample-small-website/eleventy.config.js @@ -1,7 +1,7 @@ -module.exports = function (eleventyConfig) { - eleventyConfig.addPlugin( - require('../../../index.js'), - ); +import WikiLinksPlugin from '../../../index.js'; + +export default function (eleventyConfig) { + eleventyConfig.addPlugin(WikiLinksPlugin); return { dir: { diff --git a/tests/helpers.js b/tests/helpers.js index 2a9f11a..00b55da 100644 --- a/tests/helpers.js +++ b/tests/helpers.js @@ -1,10 +1,13 @@ -const path = require("node:path"); +import { fileURLToPath } from 'url'; +import path from 'node:path'; -function normalize(str) { +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +export function normalize(str) { return str.trim().replace(/\r?\n|\r/g, ''); } -function consoleMockMessages(mock) { +export function consoleMockMessages(mock) { let logLines = []; for (let i = 0; i < mock.callCount; i++) { const line = normalize(mock.getCall(i).args.join(' ')) @@ -15,16 +18,9 @@ function consoleMockMessages(mock) { return logLines; } -function findResultByUrl(results, url) { +export function findResultByUrl(results, url) { const [result] = results.filter(result => result.url === url); return result; } -const fixturePath = (p) => path.normalize(path.join(__dirname, 'fixtures', p)); - -module.exports = { - normalize, - consoleMockMessages, - findResultByUrl, - fixturePath, -} +export const fixturePath = (p) => path.normalize(path.join(__dirname, 'fixtures', p)); diff --git a/tests/html-internal-link-parser.test.js b/tests/html-internal-link-parser.test.js index ae16124..9547c39 100644 --- a/tests/html-internal-link-parser.test.js +++ b/tests/html-internal-link-parser.test.js @@ -1,7 +1,7 @@ -const HTMLLinkParser = require('../src/html-link-parser'); -const DeadLinks = require("../src/dead-links"); -const {pageLookup} = require("../src/find-page"); -const test = require('ava'); +import HTMLLinkParser from '../src/html-link-parser.js' +import DeadLinks from '../src/dead-links.js'; +import {pageLookup} from '../src/find-page.js'; +import test from 'ava'; const pageDirectory = pageLookup([]); diff --git a/tests/markdown-wikilink-parser.test.js b/tests/markdown-wikilink-parser.test.js index 79f9fa7..5e0317d 100644 --- a/tests/markdown-wikilink-parser.test.js +++ b/tests/markdown-wikilink-parser.test.js @@ -1,7 +1,8 @@ -const WikilinkParser = require('../src/wikilink-parser'); -const {wikilinkInlineRule} = require('../src/markdown-ext'); -const {defaultResolvingFn} = require("../src/resolvers"); -const test = require('ava'); +import WikilinkParser from '../src/wikilink-parser.js'; +import {wikilinkInlineRule} from '../src/markdown-ext.js'; +import {defaultResolvingFn} from '../src/resolvers.js'; +import MarkdownIt from 'markdown-it'; +import test from 'ava'; const opts = { resolvingFns: new Map([ @@ -17,7 +18,7 @@ test('inline rule correctly parses single wikilink', t => { isEmbed: false, }); - const md = require('markdown-it')({html: true}); + const md = MarkdownIt({html: true}); md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( wikilinkParser )); @@ -45,7 +46,7 @@ test('inline rule correctly parses multiple wikilink', t => { isEmbed: false, }); - const md = require('markdown-it')({html: true}); + const md = MarkdownIt({html: true}); md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( wikilinkParser )); @@ -69,7 +70,7 @@ test('inline rule correctly parses single wikilink embed', t => { isEmbed: true, }); - const md = require('markdown-it')({html: true}); + const md = MarkdownIt({html: true}); md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( wikilinkParser )); @@ -97,7 +98,7 @@ test('inline rule correctly parses multiple wikilink embeds', t => { isEmbed: true, }); - const md = require('markdown-it')({html: true}); + const md = MarkdownIt({html: true}); md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( wikilinkParser )); @@ -125,7 +126,7 @@ test('inline rule correctly parses mixed wikilink and wikilink embeds', t => { isEmbed: false, }); - const md = require('markdown-it')({html: true}); + const md = MarkdownIt({html: true}); md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( wikilinkParser )); diff --git a/tests/markdown-wikilink-renderer.test.js b/tests/markdown-wikilink-renderer.test.js index 37d3b59..b514b78 100644 --- a/tests/markdown-wikilink-renderer.test.js +++ b/tests/markdown-wikilink-renderer.test.js @@ -1,11 +1,16 @@ -const {wikilinkInlineRule, wikilinkRenderRule} = require('../src/markdown-ext'); -const WikilinkParser = require('../src/wikilink-parser'); -const {normalize} = require('./helpers'); -const test = require('ava'); -const fs = require('fs'); +import {wikilinkInlineRule, wikilinkRenderRule} from '../src/markdown-ext.js'; +import WikilinkParser from '../src/wikilink-parser.js'; +import {fileURLToPath} from "node:url"; +import {normalize} from './helpers.js'; +import MarkdownIt from 'markdown-it'; +import path from "node:path"; +import fs from 'node:fs'; +import test from 'ava'; const opts = {}; +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + test('inline rule correctly parses single wikilink', t => { const wikilinkParser = new WikilinkParser(opts, new Set(), new Map()); @@ -17,7 +22,7 @@ test('inline rule correctly parses single wikilink', t => { isEmbed: false, }); - const md = require('markdown-it')({html: true}); + const md = MarkdownIt({html: true}); md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( wikilinkParser )); @@ -49,7 +54,7 @@ test('inline rule correctly parses multiple wikilinks', t => { isEmbed: false, }); - const md = require('markdown-it')({html: true}); + const md = MarkdownIt({html: true}); md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( wikilinkParser )); @@ -73,7 +78,7 @@ test('inline rule correctly parses single embed', t => { isEmbed: true, }); - const md = require('markdown-it')({html: true}); + const md = MarkdownIt({html: true}); md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( wikilinkParser )); @@ -121,7 +126,7 @@ test('inline rule correctly parses mixed wikilink and embed in multiline input', isEmbed: false, }); - const md = require('markdown-it')({html: true}); + const md = MarkdownIt({html: true}); md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( wikilinkParser )); diff --git a/tests/plugin.test.js b/tests/plugin.test.js index 382c7b6..52c5556 100644 --- a/tests/plugin.test.js +++ b/tests/plugin.test.js @@ -1,3 +1,6 @@ +import plugin from '../index.js'; +import test from 'ava'; + const mockFactory = (src = {}) => { return new Proxy({ ...src, @@ -29,9 +32,6 @@ const mockFactory = (src = {}) => { }); }; -const plugin = require('../index'); -const test = require('ava'); - test('hooks into eleventy.config', t => { const eleventyMock = mockFactory(); eleventyMock.addNullMock(['on', 'amendLibrary', 'addGlobalData']); diff --git a/tests/wikilink-parser.test.js b/tests/wikilink-parser.test.js index 011e852..ed3ea68 100644 --- a/tests/wikilink-parser.test.js +++ b/tests/wikilink-parser.test.js @@ -1,7 +1,7 @@ -const WikilinkParser = require('../src/wikilink-parser'); -const {defaultResolvingFn, defaultEmbedFn} = require("../src/resolvers"); -const {pageLookup} = require("../src/find-page"); -const test = require('ava'); +import WikilinkParser from '../src/wikilink-parser.js'; +import {defaultResolvingFn, defaultEmbedFn} from '../src/resolvers.js'; +import {pageLookup} from '../src/find-page.js'; +import test from 'ava'; const pageDirectory = pageLookup([ { From 0795363cc8aefd3dfc591c815948e0c84067f945 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 25 Oct 2024 22:17:37 +0100 Subject: [PATCH 131/156] bugfix(#57): use async template.read() to obtain page content... ... this makes the plugin work with Eleventy v3 --- src/interlinker.js | 6 ++++-- src/resolvers.js | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/interlinker.js b/src/interlinker.js index 14bb10d..c64053f 100644 --- a/src/interlinker.js +++ b/src/interlinker.js @@ -62,8 +62,10 @@ export default class Interlinker { // Identify this pages outbound internal links both as wikilink _and_ regular html anchor tags. For each out-link // lookup the other page and add this to its backlinks data value. - if (currentPage.template.frontMatter?.content) { - const pageContent = currentPage.template.frontMatter.content; + const template = await currentPage.template.read(); + + if (template?.content) { + const pageContent = template.content; const outboundLinks = [ ...this.wikiLinkParser.find(pageContent, pageDirectory, currentPage.filePathStem), ...this.HTMLLinkParser.find(pageContent, pageDirectory), diff --git a/src/resolvers.js b/src/resolvers.js index fcf31f2..75a19ec 100644 --- a/src/resolvers.js +++ b/src/resolvers.js @@ -32,7 +32,7 @@ export const defaultEmbedFn = async (link, currentPage, interlinker) => { if (!link.exists || !interlinker.templateConfig || !interlinker.extensionMap) return; const page = link.page; - const frontMatter = page.template.frontMatter; + const template = await page.template.read(); const layout = (page.data.hasOwnProperty(interlinker.opts.layoutKey)) ? page.data[interlinker.opts.layoutKey] @@ -47,8 +47,8 @@ export const defaultEmbedFn = async (link, currentPage, interlinker) => { // TODO: the layout below is liquid, will break if content contains invalid template tags such as passing njk file src const tpl = layout === null - ? frontMatter.content - : `{% layout "${layout}" %} {% block content %} ${frontMatter.content} {% endblock %}`; + ? template.content + : `{% layout "${layout}" %} {% block content %} ${template.content} {% endblock %}`; const compiler = EleventyRenderPlugin.String; @@ -57,5 +57,5 @@ export const defaultEmbedFn = async (link, currentPage, interlinker) => { extensionMap: interlinker.extensionMap }); - return fn({content: frontMatter.content, ...page.data}); + return fn({content: template.content, ...page.data}); } From 99f901d28ebb33f0244411452f44cc146a31e040 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 25 Oct 2024 22:43:37 +0100 Subject: [PATCH 132/156] doc: tag todo for #36 --- src/resolvers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resolvers.js b/src/resolvers.js index 75a19ec..df40222 100644 --- a/src/resolvers.js +++ b/src/resolvers.js @@ -45,7 +45,7 @@ export const defaultEmbedFn = async (link, currentPage, interlinker) => { ? page.page.templateSyntax : interlinker.opts.defaultLayoutLang; - // TODO: the layout below is liquid, will break if content contains invalid template tags such as passing njk file src + // TODO: (#36) the layout below is liquid, will break if content contains invalid template tags such as passing njk file src const tpl = layout === null ? template.content : `{% layout "${layout}" %} {% block content %} ${template.content} {% endblock %}`; From 5167ecd98e6a3125e75afb22e39babd189ddc60c Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 25 Oct 2024 22:44:14 +0100 Subject: [PATCH 133/156] refactor(#57): CommonJS -> ESM --- .../sample-with-excluded-file/eleventy.config.js | 8 ++++---- .../sample-with-hash-in-title/eleventy.config.js | 13 ++++++------- .../sample-with-simple-embed/eleventy.config.js | 8 ++++---- .../website-with-broken-links/eleventy.config.js | 8 ++++---- .../eleventy.config.js | 8 ++++---- .../eleventy.config.js | 7 ++++--- .../website-with-custom-stub-url/eleventy.config.js | 12 ++++++------ .../eleventy.config.js | 12 ++++++------ .../website-with-permalink/eleventy.config.js | 8 ++++---- 9 files changed, 42 insertions(+), 42 deletions(-) diff --git a/tests/fixtures/sample-with-excluded-file/eleventy.config.js b/tests/fixtures/sample-with-excluded-file/eleventy.config.js index 9b10b5c..17fa5da 100644 --- a/tests/fixtures/sample-with-excluded-file/eleventy.config.js +++ b/tests/fixtures/sample-with-excluded-file/eleventy.config.js @@ -1,7 +1,7 @@ -module.exports = function (eleventyConfig) { - eleventyConfig.addPlugin( - require('../../../index.js'), - ); +import WikiLinksPlugin from '../../../index.js'; + +export default function (eleventyConfig) { + eleventyConfig.addPlugin(WikiLinksPlugin); return { dir: { diff --git a/tests/fixtures/sample-with-hash-in-title/eleventy.config.js b/tests/fixtures/sample-with-hash-in-title/eleventy.config.js index fd8d97b..921843c 100644 --- a/tests/fixtures/sample-with-hash-in-title/eleventy.config.js +++ b/tests/fixtures/sample-with-hash-in-title/eleventy.config.js @@ -1,10 +1,9 @@ -module.exports = function (eleventyConfig) { - eleventyConfig.addPlugin( - require('../../../index.js'), - { - deadLinkReport: 'none' - } - ); +import WikiLinksPlugin from '../../../index.js'; + +export default function (eleventyConfig) { + eleventyConfig.addPlugin(WikiLinksPlugin, { + deadLinkReport: 'none' + }); return { dir: { diff --git a/tests/fixtures/sample-with-simple-embed/eleventy.config.js b/tests/fixtures/sample-with-simple-embed/eleventy.config.js index 9b10b5c..17fa5da 100644 --- a/tests/fixtures/sample-with-simple-embed/eleventy.config.js +++ b/tests/fixtures/sample-with-simple-embed/eleventy.config.js @@ -1,7 +1,7 @@ -module.exports = function (eleventyConfig) { - eleventyConfig.addPlugin( - require('../../../index.js'), - ); +import WikiLinksPlugin from '../../../index.js'; + +export default function (eleventyConfig) { + eleventyConfig.addPlugin(WikiLinksPlugin); return { dir: { diff --git a/tests/fixtures/website-with-broken-links/eleventy.config.js b/tests/fixtures/website-with-broken-links/eleventy.config.js index 9b10b5c..17fa5da 100644 --- a/tests/fixtures/website-with-broken-links/eleventy.config.js +++ b/tests/fixtures/website-with-broken-links/eleventy.config.js @@ -1,7 +1,7 @@ -module.exports = function (eleventyConfig) { - eleventyConfig.addPlugin( - require('../../../index.js'), - ); +import WikiLinksPlugin from '../../../index.js'; + +export default function (eleventyConfig) { + eleventyConfig.addPlugin(WikiLinksPlugin); return { dir: { diff --git a/tests/fixtures/website-with-broken-resolving-fn/eleventy.config.js b/tests/fixtures/website-with-broken-resolving-fn/eleventy.config.js index 37bf8a7..17fa5da 100644 --- a/tests/fixtures/website-with-broken-resolving-fn/eleventy.config.js +++ b/tests/fixtures/website-with-broken-resolving-fn/eleventy.config.js @@ -1,7 +1,7 @@ -module.exports = function (eleventyConfig) { - eleventyConfig.addPlugin( - require('../../../index.js') - ); +import WikiLinksPlugin from '../../../index.js'; + +export default function (eleventyConfig) { + eleventyConfig.addPlugin(WikiLinksPlugin); return { dir: { diff --git a/tests/fixtures/website-with-custom-resolving-fn/eleventy.config.js b/tests/fixtures/website-with-custom-resolving-fn/eleventy.config.js index 06f226f..61f5672 100644 --- a/tests/fixtures/website-with-custom-resolving-fn/eleventy.config.js +++ b/tests/fixtures/website-with-custom-resolving-fn/eleventy.config.js @@ -1,6 +1,7 @@ -module.exports = function (eleventyConfig) { - eleventyConfig.addPlugin( - require('../../../index.js'), +import WikiLinksPlugin from '../../../index.js'; + +export default function (eleventyConfig) { + eleventyConfig.addPlugin(WikiLinksPlugin, { resolvingFns: new Map([ ['howdy', (link, page) => `Hello ${link.name}!`], diff --git a/tests/fixtures/website-with-custom-stub-url/eleventy.config.js b/tests/fixtures/website-with-custom-stub-url/eleventy.config.js index f57a9e4..12cc66d 100644 --- a/tests/fixtures/website-with-custom-stub-url/eleventy.config.js +++ b/tests/fixtures/website-with-custom-stub-url/eleventy.config.js @@ -1,9 +1,9 @@ -module.exports = function (eleventyConfig) { - eleventyConfig.addPlugin( - require('../../../index.js'), { - stubUrl: '/custom-stub-url/', - } - ); +import WikiLinksPlugin from '../../../index.js'; + +export default function (eleventyConfig) { + eleventyConfig.addPlugin(WikiLinksPlugin, { + stubUrl: '/custom-stub-url/', + }); return { dir: { diff --git a/tests/fixtures/website-with-disabled-stub-url/eleventy.config.js b/tests/fixtures/website-with-disabled-stub-url/eleventy.config.js index 971c4ef..654171d 100644 --- a/tests/fixtures/website-with-disabled-stub-url/eleventy.config.js +++ b/tests/fixtures/website-with-disabled-stub-url/eleventy.config.js @@ -1,9 +1,9 @@ -module.exports = function (eleventyConfig) { - eleventyConfig.addPlugin( - require('../../../index.js'), { - stubUrl: false, - } - ); +import WikiLinksPlugin from '../../../index.js'; + +export default function (eleventyConfig) { + eleventyConfig.addPlugin(WikiLinksPlugin, { + stubUrl: false, + }); return { dir: { diff --git a/tests/fixtures/website-with-permalink/eleventy.config.js b/tests/fixtures/website-with-permalink/eleventy.config.js index 9b10b5c..17fa5da 100644 --- a/tests/fixtures/website-with-permalink/eleventy.config.js +++ b/tests/fixtures/website-with-permalink/eleventy.config.js @@ -1,7 +1,7 @@ -module.exports = function (eleventyConfig) { - eleventyConfig.addPlugin( - require('../../../index.js'), - ); +import WikiLinksPlugin from '../../../index.js'; + +export default function (eleventyConfig) { + eleventyConfig.addPlugin(WikiLinksPlugin); return { dir: { From c0ec3c48b83d1265b4da2d1e85a4d5b3e4965c91 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 25 Oct 2024 22:56:16 +0100 Subject: [PATCH 134/156] chore: update deprecated actions --- .github/workflows/node-test.yml | 4 ++-- .github/workflows/npm-publish.yml | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/node-test.yml b/.github/workflows/node-test.yml index 63d3d90..13c9e91 100644 --- a/.github/workflows/node-test.yml +++ b/.github/workflows/node-test.yml @@ -14,9 +14,9 @@ jobs: node: [18.x, 20.x, 21.x] os: ["ubuntu-latest", "macos-latest", "windows-latest"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} cache: 'npm' diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index a5449b8..2a4766d 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -11,8 +11,8 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: 20 - run: npm ci @@ -22,8 +22,8 @@ jobs: needs: build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: 20 registry-url: https://registry.npmjs.org/ From ff2bee305fe3fa465220e83202f42244b00436f2 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 25 Oct 2024 23:02:11 +0100 Subject: [PATCH 135/156] feat: v1.1.0-rc5 next release --- CHANGELOG.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f05aa14..845cbfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Make the stub post destination configurable (#59) +- Fix compatibility with Eleventy v3 (#60) +- Check that Wikilinks do not contain new lines (#55) - Fix crashing bug when embedded file changed while in `--watch` mode (#56) - Wikilinks should not contain new lines (#54) - On resolving fn lookup failure, only throw error if page not found (#52) diff --git a/package.json b/package.json index 60d65e5..6d5f619 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@photogabble/eleventy-plugin-interlinker", - "version": "1.1.0-rc4", + "version": "1.1.0-rc5", "description": "Obsidian WikiLinks, BackLinks and Embed support for 11ty", "keywords": [ "11ty", From b092297232cdefd88739c7238770cb9ab6f3e610 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 25 Oct 2024 23:19:04 +0100 Subject: [PATCH 136/156] chore: add .npmignore --- .npmignore | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .npmignore diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..b2e873a --- /dev/null +++ b/.npmignore @@ -0,0 +1,6 @@ +.idea +.gitignore +.editorconfig +.github/workflows/ +tests/ +coverage/ From 5d8e5e667a2b933de90b22a82f4629977f0f6d33 Mon Sep 17 00:00:00 2001 From: Simon Date: Sun, 27 Oct 2024 11:34:05 +0000 Subject: [PATCH 137/156] feat(#62): aliases can be either string or array --- src/find-page.js | 5 ++++- tests/find-page-service.test.js | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/find-page.js b/src/find-page.js index b5b36e7..0d296e6 100644 --- a/src/find-page.js +++ b/src/find-page.js @@ -30,7 +30,10 @@ export const pageLookup = (allPages = []) => { return true; } - const aliases = ((page.data.aliases && Array.isArray(page.data.aliases)) ? page.data.aliases : []).reduce(function (set, alias) { + const aliases = ((page.data.aliases && Array.isArray(page.data.aliases)) + ? page.data.aliases + : (typeof page.data.aliases === 'string' ? [page.data.aliases] : []) + ).reduce(function (set, alias) { set.add(alias); return set; }, new Set()); diff --git a/tests/find-page-service.test.js b/tests/find-page-service.test.js index 7677cb1..677feb3 100644 --- a/tests/find-page-service.test.js +++ b/tests/find-page-service.test.js @@ -25,6 +25,14 @@ const pageDirectory = pageLookup([ aliases: ['test-alias'] }, url: '/something/else/' + }, + { + fileSlug: 'string-lookup-test', + data: { + title: 'This is another page', + aliases: 'test-alias-string' + }, + url: '/something/else/' } ]); @@ -58,4 +66,17 @@ test('pageLookup (find by alias)', t => { t.is(page.fileSlug, 'something-else'); }); +test('pageLookup (find by alias as string)', t => { + const {page} = pageDirectory.findByLink({ + title: 'This is another page', + name: 'test-alias-string', + anchor: null, + link: '[[test-alias-string]]', + slug: 'test-alias-string', + isEmbed: false, + }); + t.is(typeof page, 'object'); + t.is(page.fileSlug, 'string-lookup-test'); +}); + // TODO: add testing when two pages share the same alias, what _should_ happen ? From e9aed095d980d86f04c9cdd2087ef11d22693a3f Mon Sep 17 00:00:00 2001 From: Simon Date: Sun, 27 Oct 2024 11:36:32 +0000 Subject: [PATCH 138/156] docs(#62): document change to aliases lookup --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 58c9ac8..94bbe46 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,8 @@ You can link to pages by their project path, or a path relative to the linking p Aliases provide you a way of referencing a file using different names, use the `aliases` property in your font matter to list one or more aliases that can be used to reference the file from a Wiki Link. For example, you might add _AI_ as an alias of a file titled _Artificial Intelligence_ which would then be linkable via `[[AI]]`. +These can be defined as either an array as shown below or a single alias via `aliaes: AI`. + ```yaml --- title: Artificial Intelligence From d7e319a06e9dc3db86ea29f82506cefcaa7b841f Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 6 Nov 2024 23:38:46 +0000 Subject: [PATCH 139/156] chore: ignore built cjs file --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2a77a84..b9a859a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules/ npm-debug.log coverage/ .idea +index.cjs From f3dc0a5e084063a3bbe8e9e844f8067eeb14f020 Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 7 Nov 2024 00:03:28 +0000 Subject: [PATCH 140/156] feat(#53): add test for markdown parser ignoring wikilink within code blocks --- tests/fixtures/within-code.html | 3 +++ tests/fixtures/within-code.md | 7 ++++++ tests/markdown-wikilink-renderer.test.js | 27 ++++++++++++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 tests/fixtures/within-code.html create mode 100644 tests/fixtures/within-code.md diff --git a/tests/fixtures/within-code.html b/tests/fixtures/within-code.html new file mode 100644 index 0000000..3c5e69b --- /dev/null +++ b/tests/fixtures/within-code.html @@ -0,0 +1,3 @@ +

Test Markdown File

+
[[wiki link]]
+

This contains a wiki link [[wiki link]] within an inline code element. This sentance does not: Wiki Link.

diff --git a/tests/fixtures/within-code.md b/tests/fixtures/within-code.md new file mode 100644 index 0000000..1511178 --- /dev/null +++ b/tests/fixtures/within-code.md @@ -0,0 +1,7 @@ +# Test Markdown File + +``` +[[wiki link]] +``` + +This contains a wiki link `[[wiki link]]` within an inline code element. This sentance does not: [[wiki link]]. diff --git a/tests/markdown-wikilink-renderer.test.js b/tests/markdown-wikilink-renderer.test.js index b514b78..0426c0a 100644 --- a/tests/markdown-wikilink-renderer.test.js +++ b/tests/markdown-wikilink-renderer.test.js @@ -91,6 +91,33 @@ test('inline rule correctly parses single embed', t => { ); }); +test('inline rule ignores wikilink within code and pre tags', t => { + const wikilinkParser = new WikilinkParser(opts, new Set(), new Map()); + + wikilinkParser.linkCache.set('[[wiki link]]', { + title: 'Wiki Link', + link: '[[wiki link]]', + href: '/wiki-link/', + content: 'Wiki Link', + isEmbed: false, + }); + + const md = MarkdownIt({html: true}); + md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( + wikilinkParser + )); + + md.renderer.rules.inline_wikilink = wikilinkRenderRule(); + + const markdown = fs.readFileSync(__dirname + '/fixtures/within-code.md', {encoding:'utf8', flag:'r'}); + const html = fs.readFileSync(__dirname + '/fixtures/within-code.html', {encoding:'utf8', flag:'r'}); + + t.is( + normalize(md.render(markdown)), + normalize(html) + ); +}); + test('inline rule correctly parses mixed wikilink and embed in multiline input', t => { const wikilinkParser = new WikilinkParser(opts, new Set(), new Map()); From f22cb1b53ee53f695eea1315f27da0f45c503480 Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 7 Nov 2024 00:06:55 +0000 Subject: [PATCH 141/156] TDD(#53): add failing test for feature --- tests/html-internal-link-parser.test.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/html-internal-link-parser.test.js b/tests/html-internal-link-parser.test.js index 9547c39..a5a8255 100644 --- a/tests/html-internal-link-parser.test.js +++ b/tests/html-internal-link-parser.test.js @@ -23,3 +23,10 @@ test('html link parser grabs multiple href, ignoring external links', t => { t.is(0, expectedLinks.length); }); + +test('html link parser ignores href within code blocks', t => { + const parser = new HTMLLinkParser(new DeadLinks()); + const links = parser.find('this is a link home', pageDirectory); + + t.is(0, links.length); +}); From 4fec5dbbe9124a724d351752f3784aafd8472101 Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 7 Nov 2024 00:26:08 +0000 Subject: [PATCH 142/156] chore(#53): require jsdom --- package-lock.json | 346 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 1 + 2 files changed, 341 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 259ce72..d730f96 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,17 @@ { "name": "@photogabble/eleventy-plugin-interlinker", - "version": "1.1.0-rc4", + "version": "1.1.0-rc5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@photogabble/eleventy-plugin-interlinker", - "version": "1.1.0-rc4", + "version": "1.1.0-rc5", "license": "MIT", "dependencies": { "chalk": "^4.1.1", "entities": "^4.5.0", + "jsdom": "^25.0.1", "markdown-it": "^14.1.0" }, "devDependencies": { @@ -659,6 +660,17 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/aggregate-error": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-4.0.1.tgz", @@ -783,6 +795,11 @@ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "dev": true }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/ava": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ava/-/ava-5.3.1.tgz", @@ -1412,6 +1429,17 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", @@ -1481,6 +1509,17 @@ "node": ">= 8" } }, + "node_modules/cssstyle": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.1.0.tgz", + "integrity": "sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA==", + "dependencies": { + "rrweb-cssom": "^0.7.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", @@ -1493,6 +1532,18 @@ "node": ">=0.10.0" } }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/date-time": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/date-time/-/date-time-3.1.0.tgz", @@ -1509,7 +1560,6 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, "dependencies": { "ms": "^2.1.3" }, @@ -1522,6 +1572,11 @@ } } }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -1556,6 +1611,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -1994,6 +2057,19 @@ "node": ">=8.0.0" } }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -2286,6 +2362,17 @@ "node": ">= 0.4" } }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -2348,6 +2435,41 @@ "node": ">= 0.8" } }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2531,6 +2653,11 @@ "node": ">=0.10.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" + }, "node_modules/is-promise": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", @@ -2636,6 +2763,45 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "25.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz", + "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", + "dependencies": { + "cssstyle": "^4.1.0", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.12", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.7.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, "node_modules/junk": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/junk/-/junk-1.0.3.tgz", @@ -2906,6 +3072,25 @@ "node": ">=10.0.0" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", @@ -2978,8 +3163,7 @@ "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/nise": { "version": "5.1.9", @@ -3057,6 +3241,11 @@ "node": ">= 6" } }, + "node_modules/nwsapi": { + "version": "2.2.13", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.13.tgz", + "integrity": "sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ==" + }, "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -3206,6 +3395,17 @@ "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==", "dev": true }, + "node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -3495,6 +3695,14 @@ "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", "dev": true }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, "node_modules/punycode.js": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", @@ -3634,6 +3842,11 @@ "fsevents": "~2.3.2" } }, + "node_modules/rrweb-cssom": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -3657,6 +3870,22 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/section-matter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", @@ -4066,6 +4295,11 @@ "node": ">=8" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, "node_modules/temp-dir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", @@ -4119,6 +4353,22 @@ "node": ">=4" } }, + "node_modules/tldts": { + "version": "6.1.58", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.58.tgz", + "integrity": "sha512-MQJrJhjHOYGYb8DobR6Y4AdDbd4TYkyQ+KBDVc5ODzs1cbrvPpfN1IemYi9jfipJ/vR1YWvrDli0hg1y19VRoA==", + "dependencies": { + "tldts-core": "^6.1.58" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.58", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.58.tgz", + "integrity": "sha512-dR936xmhBm7AeqHIhCWwK765gZ7dFyL+IqLSFAjJbFlUXGMLCb8i2PzlzaOuWBuplBTaBYseSb565nk/ZEM0Bg==" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -4140,6 +4390,28 @@ "node": ">=0.6" } }, + "node_modules/tough-cookie": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", + "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -4195,6 +4467,25 @@ "node": ">=10.12.0" } }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, "node_modules/well-known-symbols": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/well-known-symbols/-/well-known-symbols-2.0.0.tgz", @@ -4204,6 +4495,37 @@ "node": ">=6" } }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", + "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4354,7 +4676,6 @@ "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", - "dev": true, "engines": { "node": ">=10.0.0" }, @@ -4371,6 +4692,19 @@ } } }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 6d5f619..fdf5b67 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "dependencies": { "chalk": "^4.1.1", "entities": "^4.5.0", + "jsdom": "^25.0.1", "markdown-it": "^14.1.0" }, "devDependencies": { From 81589a0e137dff2bfeee0321776b5c9fa19af5ad Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 7 Nov 2024 00:37:04 +0000 Subject: [PATCH 143/156] chore(#53): fix typo --- tests/fixtures/within-code.html | 2 +- tests/fixtures/within-code.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/fixtures/within-code.html b/tests/fixtures/within-code.html index 3c5e69b..7b399e9 100644 --- a/tests/fixtures/within-code.html +++ b/tests/fixtures/within-code.html @@ -1,3 +1,3 @@

Test Markdown File

[[wiki link]]
-

This contains a wiki link [[wiki link]] within an inline code element. This sentance does not: Wiki Link.

+

This contains a wiki link [[wiki link]] within an inline code element. This sentence does not: Wiki Link.

diff --git a/tests/fixtures/within-code.md b/tests/fixtures/within-code.md index 1511178..9a74dbb 100644 --- a/tests/fixtures/within-code.md +++ b/tests/fixtures/within-code.md @@ -4,4 +4,4 @@ [[wiki link]] ``` -This contains a wiki link `[[wiki link]]` within an inline code element. This sentance does not: [[wiki link]]. +This contains a wiki link `[[wiki link]]` within an inline code element. This sentence does not: [[wiki link]]. From 4254a186557f43b752ad09329a9969b51620b05a Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 7 Nov 2024 00:49:59 +0000 Subject: [PATCH 144/156] bugfix: correct expected and actual values --- tests/html-internal-link-parser.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/html-internal-link-parser.test.js b/tests/html-internal-link-parser.test.js index a5a8255..a2abacd 100644 --- a/tests/html-internal-link-parser.test.js +++ b/tests/html-internal-link-parser.test.js @@ -9,7 +9,7 @@ test('html link parser grabs multiple href, ignoring external links', t => { const parser = new HTMLLinkParser(new DeadLinks()); const links = parser.find('

Hello world this is a link home and this is a link somewhere

The following link should be ignored example.com.

', pageDirectory); - t.is(2, links.length); + t.is(links.length, 2); const expectedLinks = ['/home', '/somewhere']; for (const link of links) { @@ -21,7 +21,7 @@ test('html link parser grabs multiple href, ignoring external links', t => { expectedLinks.splice(idx, 1); } - t.is(0, expectedLinks.length); + t.is(expectedLinks.length, 0); }); test('html link parser ignores href within code blocks', t => { From 939e15136709f79bfb4e2aaeabd3cd3bcd03819b Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 7 Nov 2024 00:51:03 +0000 Subject: [PATCH 145/156] feat(#53): use JSDOM to parse internal anchor tags --- src/html-link-parser.js | 16 ++++++++++++++-- tests/html-internal-link-parser.test.js | 10 +++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/html-link-parser.js b/src/html-link-parser.js index 1027a3f..5c98846 100644 --- a/src/html-link-parser.js +++ b/src/html-link-parser.js @@ -1,3 +1,4 @@ +import {JSDOM} from 'jsdom'; export default class HTMLLinkParser { /** @@ -24,7 +25,7 @@ export default class HTMLLinkParser { */ parseSingle(link, pageDirectory) { const meta = { - href: link.slice(6, -1) + href: link .replace(/.(md|markdown)\s?$/i, "") .replace("\\", "") .trim() @@ -61,8 +62,19 @@ export default class HTMLLinkParser { * @return {Array} */ find(document, pageDirectory) { + const dom = new JSDOM(document); + const anchors = dom.window.document.getElementsByTagName('a'); + const toParse = []; + + for (const anchor of anchors) { + // Ignore any anchor tags within either code or pre tags + if (anchor.closest('code,pre')) continue; + // Ignore any links that don't begin with / denoting internal links + if (anchor.href.startsWith('/')) toParse.push(anchor.href); + } + return this.parseMultiple( - (document.match(this.internalLinkRegex) || []), + toParse, pageDirectory ) } diff --git a/tests/html-internal-link-parser.test.js b/tests/html-internal-link-parser.test.js index a2abacd..54bde61 100644 --- a/tests/html-internal-link-parser.test.js +++ b/tests/html-internal-link-parser.test.js @@ -2,8 +2,12 @@ import HTMLLinkParser from '../src/html-link-parser.js' import DeadLinks from '../src/dead-links.js'; import {pageLookup} from '../src/find-page.js'; import test from 'ava'; +import fs from "node:fs"; +import path from "node:path"; +import {fileURLToPath} from "node:url"; const pageDirectory = pageLookup([]); +const __dirname = path.dirname(fileURLToPath(import.meta.url)); test('html link parser grabs multiple href, ignoring external links', t => { const parser = new HTMLLinkParser(new DeadLinks()); @@ -25,8 +29,8 @@ test('html link parser grabs multiple href, ignoring external links', t => { }); test('html link parser ignores href within code blocks', t => { - const parser = new HTMLLinkParser(new DeadLinks()); - const links = parser.find('this is a link home', pageDirectory); + t.is(0, ((new HTMLLinkParser(new DeadLinks())).find('this is a link home', pageDirectory)).length); - t.is(0, links.length); + const html = fs.readFileSync(__dirname + '/fixtures/within-code.html', {encoding:'utf8', flag:'r'}); + t.is(1, ((new HTMLLinkParser(new DeadLinks())).find(html, pageDirectory)).length); }); From 81b224fcfe266db7204b2da009277fb61071fe54 Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 28 Nov 2024 23:51:26 +0000 Subject: [PATCH 146/156] bugfix(#65): fix test, it now fails correctly... ... this is due to the bug wrapping things in

tags --- tests/eleventy.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index 8258d87..f9f5f92 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -129,7 +129,7 @@ test("Sample page (markdown with embed)", async t => { // Embed shows t.is( normalize(findResultByUrl(results, '/').content), - `

Hello world.

` + `

Hello world.

` ); }); From cf6ae9fc620742ac7c431a0b5cf74b2354ca341c Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 28 Nov 2024 23:51:45 +0000 Subject: [PATCH 147/156] docs: add todo for #65 --- src/markdown-ext.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/markdown-ext.js b/src/markdown-ext.js index 6ae3dc7..abb2a05 100644 --- a/src/markdown-ext.js +++ b/src/markdown-ext.js @@ -36,6 +36,9 @@ export const wikilinkInlineRule = (wikilinkParser) => (state, silent) => { // wikilinks. In the unlikely case that it doesn't we ignore the wikilink. if (!wikiLink) return false; + // TODO convert token into 'html_block' or 'html_inline' depending if its an embed or not so as to stop markdown + // wrapping in

tags. See: https://github.com/markdown-it/markdown-it/blob/master/docs/examples/renderer_rules.md + const token = state.push('inline_wikilink', '', 0); token.content = wikiLink.slug; token.meta = wikiLink; From feb6ccce882cfd555abcb4413257f7e18c734f19 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 29 Nov 2024 00:05:59 +0000 Subject: [PATCH 148/156] chore: add test for #66... ... I expected this to fail, but it seems that shortcodes get ran before the embed is embedded. The bug report can't be reproduced. --- tests/eleventy.test.js | 12 ++++++++++++ .../eleventy.config.js | 14 ++++++++++++++ .../fixtures/website-with-embed-shortcode/embed.md | 7 +++++++ .../fixtures/website-with-embed-shortcode/index.md | 7 +++++++ 4 files changed, 40 insertions(+) create mode 100644 tests/fixtures/website-with-embed-shortcode/eleventy.config.js create mode 100644 tests/fixtures/website-with-embed-shortcode/embed.md create mode 100644 tests/fixtures/website-with-embed-shortcode/index.md diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index 8258d87..0cc5f3f 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -287,3 +287,15 @@ test("Stub URL Config (can be disabled)", async t => { `

Broken link with custom stub url [[ broken link ]].

` ); }) + +test("Embedded file shortcodes get run", async t => { + let elev = new Eleventy(fixturePath('website-with-embed-shortcode'), fixturePath('website-with-embed-shortcode/_site'), { + configPath: fixturePath('website-with-embed-shortcode/eleventy.config.js'), + }); + + const results = await elev.toJSON(); + t.is( + normalize(findResultByUrl(results, '/').content), + `

Embed Below

Hello world

` // TODO: (#65) remove wrapping

from embed + ); +}); diff --git a/tests/fixtures/website-with-embed-shortcode/eleventy.config.js b/tests/fixtures/website-with-embed-shortcode/eleventy.config.js new file mode 100644 index 0000000..66c235b --- /dev/null +++ b/tests/fixtures/website-with-embed-shortcode/eleventy.config.js @@ -0,0 +1,14 @@ +import WikiLinksPlugin from '../../../index.js'; + +export default function (eleventyConfig) { + eleventyConfig.addPlugin(WikiLinksPlugin); + + eleventyConfig.addPairedShortcode('sc', (content) => content); + + return { + dir: { + includes: "_includes", + layouts: "_layouts", + } + } +} diff --git a/tests/fixtures/website-with-embed-shortcode/embed.md b/tests/fixtures/website-with-embed-shortcode/embed.md new file mode 100644 index 0000000..b844e38 --- /dev/null +++ b/tests/fixtures/website-with-embed-shortcode/embed.md @@ -0,0 +1,7 @@ +--- +title: Embedded Page +--- + +{% sc %} +Hello world +{% endsc %} diff --git a/tests/fixtures/website-with-embed-shortcode/index.md b/tests/fixtures/website-with-embed-shortcode/index.md new file mode 100644 index 0000000..ef43e48 --- /dev/null +++ b/tests/fixtures/website-with-embed-shortcode/index.md @@ -0,0 +1,7 @@ +--- +title: Embed Page +--- + +# Embed Below + +![[Embedded Page]] From 52e773fb68bb9806ff9af8ea9f1d3369e6ef0bdd Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 29 Nov 2024 00:08:14 +0000 Subject: [PATCH 149/156] chore: wrap in

just to be sure --- tests/eleventy.test.js | 2 +- tests/fixtures/website-with-embed-shortcode/eleventy.config.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index 0cc5f3f..b17a0c6 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -296,6 +296,6 @@ test("Embedded file shortcodes get run", async t => { const results = await elev.toJSON(); t.is( normalize(findResultByUrl(results, '/').content), - `

Embed Below

Hello world

` // TODO: (#65) remove wrapping

from embed + `

Embed Below

Hello world

` // TODO: (#65) remove wrapping

from embed ); }); diff --git a/tests/fixtures/website-with-embed-shortcode/eleventy.config.js b/tests/fixtures/website-with-embed-shortcode/eleventy.config.js index 66c235b..d4a65f8 100644 --- a/tests/fixtures/website-with-embed-shortcode/eleventy.config.js +++ b/tests/fixtures/website-with-embed-shortcode/eleventy.config.js @@ -3,7 +3,7 @@ import WikiLinksPlugin from '../../../index.js'; export default function (eleventyConfig) { eleventyConfig.addPlugin(WikiLinksPlugin); - eleventyConfig.addPairedShortcode('sc', (content) => content); + eleventyConfig.addPairedShortcode('sc', (content) => `

${content}
`); return { dir: { From 190ac5b8f3f7e7fd4a3ebffc51818972e2b8cfa5 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 29 Nov 2024 17:26:32 +0000 Subject: [PATCH 150/156] feat(#66): correctly compile embed templates --- src/resolvers.js | 25 ++++++++++++++----- tests/eleventy.test.js | 6 ++--- .../_layouts/test-embed.liquid | 1 + .../eleventy.config.js | 0 .../embed-with-template.md | 8 ++++++ .../embed.md | 0 .../index.md | 4 +++ 7 files changed, 35 insertions(+), 9 deletions(-) create mode 100644 tests/fixtures/website-with-liquid-embed-shortcode/_layouts/test-embed.liquid rename tests/fixtures/{website-with-embed-shortcode => website-with-liquid-embed-shortcode}/eleventy.config.js (100%) create mode 100644 tests/fixtures/website-with-liquid-embed-shortcode/embed-with-template.md rename tests/fixtures/{website-with-embed-shortcode => website-with-liquid-embed-shortcode}/embed.md (100%) rename tests/fixtures/{website-with-embed-shortcode => website-with-liquid-embed-shortcode}/index.md (54%) diff --git a/src/resolvers.js b/src/resolvers.js index df40222..e111636 100644 --- a/src/resolvers.js +++ b/src/resolvers.js @@ -38,7 +38,6 @@ export const defaultEmbedFn = async (link, currentPage, interlinker) => { ? page.data[interlinker.opts.layoutKey] : interlinker.opts.defaultLayout; - // TODO this should be based upon the layout extension const language = (page.data.hasOwnProperty(interlinker.opts.layoutTemplateLangKey)) ? page.data[interlinker.opts.layoutTemplateLangKey] : interlinker.opts.defaultLayoutLang === null @@ -46,16 +45,30 @@ export const defaultEmbedFn = async (link, currentPage, interlinker) => { : interlinker.opts.defaultLayoutLang; // TODO: (#36) the layout below is liquid, will break if content contains invalid template tags such as passing njk file src - const tpl = layout === null - ? template.content - : `{% layout "${layout}" %} {% block content %} ${template.content} {% endblock %}`; + // This is the async compile function from the RenderPlugin.js bundled with 11ty. I'm using it directly here + // to compile the embedded content. const compiler = EleventyRenderPlugin.String; - const fn = await compiler(tpl, language, { + // Compile template.content + const contentFn = await compiler(template.content, language, { templateConfig: interlinker.templateConfig, extensionMap: interlinker.extensionMap }); - return fn({content: template.content, ...page.data}); + const content = await contentFn({...page.data}); + + // If we don't have an embed layout wrapping this content, return the compiled result. + if (layout === null) return content; + + // The template string is just to invoke the embed layout, the content value is the + // compiled result of template.content. + const tpl = `{% layout "${layout}" %}` + + const tplFn = await compiler(tpl, language, { + templateConfig: interlinker.templateConfig, + extensionMap: interlinker.extensionMap + }); + + return await tplFn({content, ...page.data}); } diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index b17a0c6..035b209 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -289,13 +289,13 @@ test("Stub URL Config (can be disabled)", async t => { }) test("Embedded file shortcodes get run", async t => { - let elev = new Eleventy(fixturePath('website-with-embed-shortcode'), fixturePath('website-with-embed-shortcode/_site'), { - configPath: fixturePath('website-with-embed-shortcode/eleventy.config.js'), + let elev = new Eleventy(fixturePath('website-with-liquid-embed-shortcode'), fixturePath('website-with-liquid-embed-shortcode/_site'), { + configPath: fixturePath('website-with-liquid-embed-shortcode/eleventy.config.js'), }); const results = await elev.toJSON(); t.is( normalize(findResultByUrl(results, '/').content), - `

Embed Below

Hello world

` // TODO: (#65) remove wrapping

from embed + `

Embed Below

Hello world

Embed 2 Below

Hello world

` // TODO: (#65) remove wrapping

from embed ); }); diff --git a/tests/fixtures/website-with-liquid-embed-shortcode/_layouts/test-embed.liquid b/tests/fixtures/website-with-liquid-embed-shortcode/_layouts/test-embed.liquid new file mode 100644 index 0000000..c9bcd4c --- /dev/null +++ b/tests/fixtures/website-with-liquid-embed-shortcode/_layouts/test-embed.liquid @@ -0,0 +1 @@ +

{{ content }}
diff --git a/tests/fixtures/website-with-embed-shortcode/eleventy.config.js b/tests/fixtures/website-with-liquid-embed-shortcode/eleventy.config.js similarity index 100% rename from tests/fixtures/website-with-embed-shortcode/eleventy.config.js rename to tests/fixtures/website-with-liquid-embed-shortcode/eleventy.config.js diff --git a/tests/fixtures/website-with-liquid-embed-shortcode/embed-with-template.md b/tests/fixtures/website-with-liquid-embed-shortcode/embed-with-template.md new file mode 100644 index 0000000..29150a3 --- /dev/null +++ b/tests/fixtures/website-with-liquid-embed-shortcode/embed-with-template.md @@ -0,0 +1,8 @@ +--- +title: Embedded Page with template +embedLayout: _layouts/test-embed.liquid +--- + +{% sc %} +Hello world +{% endsc %} diff --git a/tests/fixtures/website-with-embed-shortcode/embed.md b/tests/fixtures/website-with-liquid-embed-shortcode/embed.md similarity index 100% rename from tests/fixtures/website-with-embed-shortcode/embed.md rename to tests/fixtures/website-with-liquid-embed-shortcode/embed.md diff --git a/tests/fixtures/website-with-embed-shortcode/index.md b/tests/fixtures/website-with-liquid-embed-shortcode/index.md similarity index 54% rename from tests/fixtures/website-with-embed-shortcode/index.md rename to tests/fixtures/website-with-liquid-embed-shortcode/index.md index ef43e48..79d9093 100644 --- a/tests/fixtures/website-with-embed-shortcode/index.md +++ b/tests/fixtures/website-with-liquid-embed-shortcode/index.md @@ -5,3 +5,7 @@ title: Embed Page # Embed Below ![[Embedded Page]] + +# Embed 2 Below + +![[Embedded Page with template]] From 5ab740ae315410b8469d27fb1b241614a3a02d15 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 29 Nov 2024 17:53:25 +0000 Subject: [PATCH 151/156] chore(TTD): update test to fail until bug is fixed --- tests/eleventy.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index 0fe8839..00a62c5 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -296,6 +296,6 @@ test("Embedded file shortcodes get run", async t => { const results = await elev.toJSON(); t.is( normalize(findResultByUrl(results, '/').content), - `

Embed Below

Hello world

Embed 2 Below

Hello world

` // TODO: (#65) remove wrapping

from embed + `

Embed Below

Hello world

Embed 2 Below

Hello world
` ); }); From 6f219c36c5757e50ced814ec907a09e2800c97d6 Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 2 Dec 2024 20:29:33 +0000 Subject: [PATCH 152/156] refactor(#65): add install fn for consistency --- index.js | 10 ++------ src/markdown-ext.js | 13 ++++++++++ tests/markdown-wikilink-parser.test.js | 24 ++++++------------ tests/markdown-wikilink-renderer.test.js | 32 +++++------------------- 4 files changed, 28 insertions(+), 51 deletions(-) diff --git a/index.js b/index.js index b68c064..a66c07a 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ -import {wikilinkInlineRule, wikilinkRenderRule} from './src/markdown-ext.js'; +import {install} from './src/markdown-ext.js'; import Interlinker from './src/interlinker.js'; import {defaultResolvingFn, defaultEmbedFn} from './src/resolvers.js'; @@ -63,13 +63,7 @@ export default function (eleventyConfig, options = {}) { }); // Teach Markdown-It how to display MediaWiki Links. - eleventyConfig.amendLibrary('md', (md) => { - md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( - interlinker.wikiLinkParser, - )); - - md.renderer.rules.inline_wikilink = wikilinkRenderRule(); - }); + eleventyConfig.amendLibrary('md', (md) => install(md, interlinker.wikiLinkParser)); // Add outboundLinks computed global data, this is executed before the templates are compiled and // thus markdown parsed. diff --git a/src/markdown-ext.js b/src/markdown-ext.js index abb2a05..38e182c 100644 --- a/src/markdown-ext.js +++ b/src/markdown-ext.js @@ -54,3 +54,16 @@ export const wikilinkRenderRule = () => (tokens, idx) => { const {meta} = tokens[idx]; return meta.content; }; + +export const install = (md, wikilinkParser) => { + md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( + wikilinkParser, + )); + + md.block.ruler.before('heading', 'block_wikilink', wikilinkBlockRule( + wikilinkParser, + ), { + // alt contains a list of rules which can be terminated by this one + alt: ['paragraph', 'reference', 'blockquote'] + }); +} diff --git a/tests/markdown-wikilink-parser.test.js b/tests/markdown-wikilink-parser.test.js index 5e0317d..566d201 100644 --- a/tests/markdown-wikilink-parser.test.js +++ b/tests/markdown-wikilink-parser.test.js @@ -1,5 +1,5 @@ import WikilinkParser from '../src/wikilink-parser.js'; -import {wikilinkInlineRule} from '../src/markdown-ext.js'; +import {install} from '../src/markdown-ext.js'; import {defaultResolvingFn} from '../src/resolvers.js'; import MarkdownIt from 'markdown-it'; import test from 'ava'; @@ -19,9 +19,7 @@ test('inline rule correctly parses single wikilink', t => { }); const md = MarkdownIt({html: true}); - md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( - wikilinkParser - )); + install(md, wikilinkParser); const parsed = md.parseInline('Hello world, this is some text with a [[wiki link]] inside!', {}); @@ -47,9 +45,7 @@ test('inline rule correctly parses multiple wikilink', t => { }); const md = MarkdownIt({html: true}); - md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( - wikilinkParser - )); + install(md, wikilinkParser); const parsed = md.parseInline('Hello world, this is some text with two [[wiki links]] inside! The second one is [[here]].', {}); @@ -71,9 +67,7 @@ test('inline rule correctly parses single wikilink embed', t => { }); const md = MarkdownIt({html: true}); - md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( - wikilinkParser - )); + install(md, wikilinkParser); const parsed = md.parseInline('Hello world, this is some text with a ![[wiki link embed]] inside!', {}); @@ -98,10 +92,8 @@ test('inline rule correctly parses multiple wikilink embeds', t => { isEmbed: true, }); - const md = MarkdownIt({html: true}); - md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( - wikilinkParser - )); + const md = new MarkdownIt({html: true}); + install(md, wikilinkParser); const parsed = md.parseInline('Hello world, this is some text with two ![[wiki link embeds]] inside! The second one is ![[here]].', {}); @@ -127,9 +119,7 @@ test('inline rule correctly parses mixed wikilink and wikilink embeds', t => { }); const md = MarkdownIt({html: true}); - md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( - wikilinkParser - )); + install(md, wikilinkParser); const parsed = md.parseInline('Hello world, this is some text with mixed ![[wiki link embeds]] inside! The wiki link is [[here]].', {}); diff --git a/tests/markdown-wikilink-renderer.test.js b/tests/markdown-wikilink-renderer.test.js index 0426c0a..2dc44d0 100644 --- a/tests/markdown-wikilink-renderer.test.js +++ b/tests/markdown-wikilink-renderer.test.js @@ -1,4 +1,4 @@ -import {wikilinkInlineRule, wikilinkRenderRule} from '../src/markdown-ext.js'; +import {install} from '../src/markdown-ext.js'; import WikilinkParser from '../src/wikilink-parser.js'; import {fileURLToPath} from "node:url"; import {normalize} from './helpers.js'; @@ -23,11 +23,7 @@ test('inline rule correctly parses single wikilink', t => { }); const md = MarkdownIt({html: true}); - md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( - wikilinkParser - )); - - md.renderer.rules.inline_wikilink = wikilinkRenderRule(); + install(md, wikilinkParser); t.is( "

Hello world, this is some text with a Wiki Link inside!

\n", @@ -55,11 +51,7 @@ test('inline rule correctly parses multiple wikilinks', t => { }); const md = MarkdownIt({html: true}); - md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( - wikilinkParser - )); - - md.renderer.rules.inline_wikilink = wikilinkRenderRule(); + install(md, wikilinkParser); t.is( "

Hello world, this is some text with a Wiki Link inside! There is also Another Wiki Link in the same string.

\n", @@ -79,11 +71,7 @@ test('inline rule correctly parses single embed', t => { }); const md = MarkdownIt({html: true}); - md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( - wikilinkParser - )); - - md.renderer.rules.inline_wikilink = wikilinkRenderRule(); + install(md, wikilinkParser); t.is( md.render('Hello world this is a ![[wiki-embed]]'), @@ -103,11 +91,7 @@ test('inline rule ignores wikilink within code and pre tags', t => { }); const md = MarkdownIt({html: true}); - md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( - wikilinkParser - )); - - md.renderer.rules.inline_wikilink = wikilinkRenderRule(); + install(md, wikilinkParser); const markdown = fs.readFileSync(__dirname + '/fixtures/within-code.md', {encoding:'utf8', flag:'r'}); const html = fs.readFileSync(__dirname + '/fixtures/within-code.html', {encoding:'utf8', flag:'r'}); @@ -154,11 +138,7 @@ test('inline rule correctly parses mixed wikilink and embed in multiline input', }); const md = MarkdownIt({html: true}); - md.inline.ruler.push('inline_wikilink', wikilinkInlineRule( - wikilinkParser - )); - - md.renderer.rules.inline_wikilink = wikilinkRenderRule(); + install(md, wikilinkParser); const markdown = fs.readFileSync(__dirname + '/fixtures/multiline.md', {encoding:'utf8', flag:'r'}); const html = fs.readFileSync(__dirname + '/fixtures/multiline.html', {encoding:'utf8', flag:'r'}); From 5a3d9a74faf36441bd35614d25f4cca907679f1e Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 2 Dec 2024 20:30:55 +0000 Subject: [PATCH 153/156] chore(#65): remove

wrapping embeds, tests now fail --- tests/eleventy.test.js | 6 +++--- tests/fixtures/multiline.html | 2 +- .../fixtures/sample-small-website/linking-to-lonelyjuly.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index 00a62c5..c8bd63e 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -71,7 +71,7 @@ test("Sample small Website (html entities)", async t => { t.is( normalize(findResultByUrl(results, '/linking-to-lonelyjuly/').content), - `

` + `
` ); }); @@ -180,7 +180,7 @@ test("Sample page (files with hash in title)", async t => { // Embed shows t.is( normalize(findResultByUrl(results, '/').content), - `

This link should be to a fragment identifier.

Hello world.

` + `

This link should be to a fragment identifier.

Hello world.

` ); }); @@ -194,7 +194,7 @@ test("Sample with simple embed (broken embed)", async t => { // Bad Wikilink Embed shows default text t.is( normalize(findResultByUrl(results, '/broken/').content), - `

[UNABLE TO LOCATE EMBED]

` + `
[UNABLE TO LOCATE EMBED]
` ); }); diff --git a/tests/fixtures/multiline.html b/tests/fixtures/multiline.html index 03823b0..d31ea1e 100644 --- a/tests/fixtures/multiline.html +++ b/tests/fixtures/multiline.html @@ -1,5 +1,5 @@

This is a multiline file with Wikilinks and embeds to test that the inline rule works as expected.

-

Embed on its own

+
Embed on its own

This paragraph includes an inline embed which could be used for embedding links and tracking their usage. Doing so is useful say you want to collect bookmarks and link those from multiple places and see usage.

This Wiki Link is inside a quote block.

diff --git a/tests/fixtures/sample-small-website/linking-to-lonelyjuly.md b/tests/fixtures/sample-small-website/linking-to-lonelyjuly.md index 73b54ef..77658d7 100644 --- a/tests/fixtures/sample-small-website/linking-to-lonelyjuly.md +++ b/tests/fixtures/sample-small-website/linking-to-lonelyjuly.md @@ -3,4 +3,4 @@ title: Linking to Lonely July layout: default.liquid --- -[[>>LONELYJULY<<]] +[[>>LONELYJULY<<]] website. From 204a0fdb0ac85fa9c6eb4b4c5bac5530164cff4a Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 2 Dec 2024 20:50:37 +0000 Subject: [PATCH 154/156] feat(#65): add wiki link embed block rule --- src/markdown-ext.js | 53 +++++++++++++++++++------- tests/eleventy.test.js | 2 +- tests/markdown-wikilink-parser.test.js | 22 +++++++---- tests/plugin.test.js | 11 +++++- 4 files changed, 64 insertions(+), 24 deletions(-) diff --git a/src/markdown-ext.js b/src/markdown-ext.js index 38e182c..b67c8c7 100644 --- a/src/markdown-ext.js +++ b/src/markdown-ext.js @@ -6,8 +6,6 @@ */ export const wikilinkInlineRule = (wikilinkParser) => (state, silent) => { // Have we found the start of a WikiLink Embed `![[` - if (['!', '['].includes(state.src.charAt(state.pos)) === false || silent) return false; - if (state.src.charAt(state.pos) === '[' && state.src.charAt(state.pos + 1) !== '[') return false; // Not wikilink opening if (state.src.charAt(state.pos) === '!' && state.src.substring(state.pos, state.pos + 3) !== '![[') return false; // Not embed opening @@ -15,7 +13,7 @@ export const wikilinkInlineRule = (wikilinkParser) => (state, silent) => { if (!matches) return false; // We have found the start of a WikiLink (`[[`) or Embed (`![[`) - // char at state.pos will be either [ or !, need to walk through state.src until ]] is encountered + // char at state.pos will be either `[` or `!`, need to walk through state.src until `]]` is encountered let pos = state.pos; let text = ''; let found = false; @@ -33,26 +31,53 @@ export const wikilinkInlineRule = (wikilinkParser) => (state, silent) => { const wikiLink = wikilinkParser.linkCache.get(text); // By this time in the execution cycle the wikilink parser's cache should contain all - // wikilinks. In the unlikely case that it doesn't we ignore the wikilink. + // wikilinks, including those linking to a stub. In the unlikely case that it doesn't + // we ignore the wikilink. if (!wikiLink) return false; - // TODO convert token into 'html_block' or 'html_inline' depending if its an embed or not so as to stop markdown - // wrapping in

tags. See: https://github.com/markdown-it/markdown-it/blob/master/docs/examples/renderer_rules.md - - const token = state.push('inline_wikilink', '', 0); - token.content = wikiLink.slug; - token.meta = wikiLink; + if (!silent) { + // The wikilink content is HTML generated by a "Resolving Function" lets make use of + // the builtin html_inline render rule to display it. + const token = state.push('html_inline', '', 0); + token.content = wikiLink.content.trim(); + } state.pos = state.pos + text.length; return true; }; /** - * @returns {(function(*, *): (string))|*} + * This rule is to catch Wikilink Embeds that are bookended by new lines e.g `\n![[test]]\n`, the inline rule + * would only get the content within the new lines with the newlines themselves getting tokenised as a paragraph. + * That was causing the bugs raised in issues #65 and #66. + * + * @param wikilinkParser + * @return {(function(*, *, *, *): boolean)|*} */ -export const wikilinkRenderRule = () => (tokens, idx) => { - const {meta} = tokens[idx]; - return meta.content; +export const wikilinkBlockRule = (wikilinkParser) => (state, startLine, endLine, silent) => { + let pos = state.bMarks[startLine] + state.tShift[startLine] + let max = state.eMarks[startLine] + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false } + + // This block rule only cares about Wiki Link embeds that are at a block level (on a single line bookended by `\n`) + // therefore if the line text doesn't begin with `![[` we do not continue. + let lineText = state.src.slice(pos, max); + if (lineText.substring(0, 3) !== '![[') return false; + + const wikiLink = wikilinkParser.linkCache.get(lineText); + if (!wikiLink) return false; + + if (!silent) { + const token = state.push('html_block', '', 0); + token.content = wikiLink.content.trim(); + } + + // Block level Wikilink embeds should be on a single line, increment the line counter and continue. + state.line++; + + return true; }; export const install = (md, wikilinkParser) => { diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index c8bd63e..cdabe5e 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -71,7 +71,7 @@ test("Sample small Website (html entities)", async t => { t.is( normalize(findResultByUrl(results, '/linking-to-lonelyjuly/').content), - `

` + `
` ); }); diff --git a/tests/markdown-wikilink-parser.test.js b/tests/markdown-wikilink-parser.test.js index 566d201..52a9df6 100644 --- a/tests/markdown-wikilink-parser.test.js +++ b/tests/markdown-wikilink-parser.test.js @@ -16,6 +16,7 @@ test('inline rule correctly parses single wikilink', t => { title: 'Wiki link', href: '/test/', isEmbed: false, + content: '...', }); const md = MarkdownIt({html: true}); @@ -26,7 +27,7 @@ test('inline rule correctly parses single wikilink', t => { // Check there is only one inline_wikilink_embed token in parsed result t.is(parsed.length, 1); t.is(parsed[0].children.length, 3); - t.is(parsed[0].children.filter(child => child.type === 'inline_wikilink').length, 1); + t.is(parsed[0].children.filter(child => child.type === 'html_inline').length, 1); }); test('inline rule correctly parses multiple wikilink', t => { @@ -36,12 +37,14 @@ test('inline rule correctly parses multiple wikilink', t => { slug: 'wiki-links', href: '/test/', isEmbed: false, + content: '', }); wikilinkParser.linkCache.set('[[here]]', { title: 'here', slug: 'here', href: '/here/', isEmbed: false, + content: '', }); const md = MarkdownIt({html: true}); @@ -52,9 +55,9 @@ test('inline rule correctly parses multiple wikilink', t => { // Check there is only one inline_wikilink_embed token in parsed result t.is(parsed.length, 1); t.is(parsed[0].children.length, 5); - t.is(parsed[0].children.filter(child => child.type === 'inline_wikilink').length, 2); - t.is(parsed[0].children[1].meta.slug, 'wiki-links'); - t.is(parsed[0].children[3].meta.slug, 'here'); + t.is(parsed[0].children.filter(child => child.type === 'html_inline').length, 2); + t.is(parsed[0].children[1].content, ''); + t.is(parsed[0].children[3].content, ''); }); test('inline rule correctly parses single wikilink embed', t => { @@ -64,6 +67,7 @@ test('inline rule correctly parses single wikilink embed', t => { slug: 'wiki-link-embed', href: '/test/', isEmbed: true, + content: '...', }); const md = MarkdownIt({html: true}); @@ -74,7 +78,7 @@ test('inline rule correctly parses single wikilink embed', t => { // Check there is only one inline_wikilink_embed token in parsed result t.is(parsed.length, 1); t.is(parsed[0].children.length, 3); - t.is(parsed[0].children.filter(child => child.type === 'inline_wikilink').length, 1); + t.is(parsed[0].children.filter(child => child.type === 'html_inline').length, 1); }); test('inline rule correctly parses multiple wikilink embeds', t => { @@ -83,12 +87,14 @@ test('inline rule correctly parses multiple wikilink embeds', t => { title: 'wiki link embed', slug: 'wiki-link-embed', href: '/test/', + content: 'Wiki Link Embed', isEmbed: true, }); wikilinkParser.linkCache.set('![[here]]', { title: 'here embed', slug: 'here', href: '/test/', + content: 'Here Embed', isEmbed: true, }); @@ -100,7 +106,7 @@ test('inline rule correctly parses multiple wikilink embeds', t => { // Check there is only one inline_wikilink_embed token in parsed result t.is(parsed.length, 1); t.is(parsed[0].children.length, 5); - t.is(parsed[0].children.filter(child => child.type === 'inline_wikilink').length, 2); + t.is(parsed[0].children.filter(child => child.type === 'html_inline').length, 2); }); test('inline rule correctly parses mixed wikilink and wikilink embeds', t => { @@ -110,12 +116,14 @@ test('inline rule correctly parses mixed wikilink and wikilink embeds', t => { slug: 'wiki-link-embeds', href: '/test/', isEmbed: true, + content: '...', }); wikilinkParser.linkCache.set('[[here]]', { title: 'here', slug: 'here', href: '/test/', isEmbed: false, + content: '...', }); const md = MarkdownIt({html: true}); @@ -126,5 +134,5 @@ test('inline rule correctly parses mixed wikilink and wikilink embeds', t => { // Check there is only one inline_wikilink_embed token in parsed result t.is(parsed.length, 1); t.is(parsed[0].children.length, 5); - t.is(parsed[0].children.filter(child => child.type === 'inline_wikilink').length, 2); + t.is(parsed[0].children.filter(child => child.type === 'html_inline').length, 2); }); diff --git a/tests/plugin.test.js b/tests/plugin.test.js index 52c5556..21c6994 100644 --- a/tests/plugin.test.js +++ b/tests/plugin.test.js @@ -56,6 +56,13 @@ test('registers parse and render rules with markdown-it', t => { } } }, + block: { + ruler: { + before: function (_, name, fn) { + this[name] = fn; + } + } + }, renderer: { rules: {} }, @@ -71,12 +78,12 @@ test('registers parse and render rules with markdown-it', t => { t.is(typeof fn, 'function'); t.is(typeof mdMock.inline.ruler.inline_wikilink, 'undefined'); - t.is(typeof mdMock.renderer.rules.inline_wikilink, 'undefined'); + t.is(typeof mdMock.block.ruler.block_wikilink, 'undefined'); fn(mdMock); t.is(typeof mdMock.inline.ruler.inline_wikilink, 'function'); - t.is(typeof mdMock.renderer.rules.inline_wikilink, 'function'); + t.is(typeof mdMock.block.ruler.block_wikilink, 'function'); }); plugin(eleventyMock); From 6b61faed76d925e092185a5e20d87810826b90ad Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 2 Dec 2024 20:56:07 +0000 Subject: [PATCH 155/156] chore(#53): additional test for parsing outside of code blocks --- tests/eleventy.test.js | 12 ++++++++++++ .../fixtures/website-with-embeds/eleventy.config.js | 12 ++++++++++++ tests/fixtures/website-with-embeds/wiki-link.md | 5 +++++ tests/fixtures/website-with-embeds/within-code.md | 11 +++++++++++ 4 files changed, 40 insertions(+) create mode 100644 tests/fixtures/website-with-embeds/eleventy.config.js create mode 100644 tests/fixtures/website-with-embeds/wiki-link.md create mode 100644 tests/fixtures/website-with-embeds/within-code.md diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index cdabe5e..cdca149 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -299,3 +299,15 @@ test("Embedded file shortcodes get run", async t => { `

Embed Below

Hello world

Embed 2 Below

Hello world
` ); }); + +test("Wikilinks within code blocks get ignored", async t => { + let elev = new Eleventy(fixturePath('website-with-embeds'), fixturePath('website-with-embeds/_site'), { + configPath: fixturePath('website-with-embeds/eleventy.config.js'), + }); + + const results = await elev.toJSON(); + t.is( + normalize(findResultByUrl(results, '/within-code/').content), + `

Test Markdown File

[[Wiki Link]]

This contains a wiki link [[Wiki Link]] within an inline code element. This sentence does not: Wiki Link.

` + ); +}); diff --git a/tests/fixtures/website-with-embeds/eleventy.config.js b/tests/fixtures/website-with-embeds/eleventy.config.js new file mode 100644 index 0000000..17fa5da --- /dev/null +++ b/tests/fixtures/website-with-embeds/eleventy.config.js @@ -0,0 +1,12 @@ +import WikiLinksPlugin from '../../../index.js'; + +export default function (eleventyConfig) { + eleventyConfig.addPlugin(WikiLinksPlugin); + + return { + dir: { + includes: "_includes", + layouts: "_layouts", + } + } +} diff --git a/tests/fixtures/website-with-embeds/wiki-link.md b/tests/fixtures/website-with-embeds/wiki-link.md new file mode 100644 index 0000000..85d2c96 --- /dev/null +++ b/tests/fixtures/website-with-embeds/wiki-link.md @@ -0,0 +1,5 @@ +--- +title: Wiki Link +--- + +Stub page to be linked. diff --git a/tests/fixtures/website-with-embeds/within-code.md b/tests/fixtures/website-with-embeds/within-code.md new file mode 100644 index 0000000..0d8e7fc --- /dev/null +++ b/tests/fixtures/website-with-embeds/within-code.md @@ -0,0 +1,11 @@ +--- +title: Within Code +--- + +# Test Markdown File + +``` +[[Wiki Link]] +``` + +This contains a wiki link `[[Wiki Link]]` within an inline code element. This sentence does not: [[Wiki Link]]. From adcbeb5c9cace6428f835b7e0ff2e0a3c7e1ddae Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 2 Dec 2024 21:23:01 +0000 Subject: [PATCH 156/156] chore: add test for hash in page title --- tests/eleventy.test.js | 14 +++++++++++++- .../fixtures/sample-small-website/hash-in-title.md | 5 +++++ .../linking-to-hash-in-title.md | 5 +++++ 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/sample-small-website/hash-in-title.md create mode 100644 tests/fixtures/sample-small-website/linking-to-hash-in-title.md diff --git a/tests/eleventy.test.js b/tests/eleventy.test.js index cdca149..5dea8c4 100644 --- a/tests/eleventy.test.js +++ b/tests/eleventy.test.js @@ -23,7 +23,7 @@ test("Sample small Website (wikilinks and regular links)", async t => { let results = await elev.toJSON(); - t.is(results.length, 8); + t.is(results.length, 10); t.is( normalize(findResultByUrl(results, '/about/').content), @@ -36,6 +36,18 @@ test("Sample small Website (wikilinks and regular links)", async t => { ); }); +test("Sample small Website (hash in page title)", async t => { + let elev = new Eleventy(fixturePath('sample-small-website'), fixturePath('sample-small-website/_site'), { + configPath: fixturePath('sample-small-website/eleventy.config.js'), + }); + + let results = await elev.toJSON(); + t.is( + normalize(findResultByUrl(results, '/linking-to-hash-in-title/').content), + `

This should work: Building a self-contained game in C# under 2 kilobytes.

` + ); +}); + test("Sample small Website (path links)", async t => { let elev = new Eleventy(fixturePath('sample-small-website'), fixturePath('sample-small-website/_site'), { configPath: fixturePath('sample-small-website/eleventy.config.js'), diff --git a/tests/fixtures/sample-small-website/hash-in-title.md b/tests/fixtures/sample-small-website/hash-in-title.md new file mode 100644 index 0000000..d1ea446 --- /dev/null +++ b/tests/fixtures/sample-small-website/hash-in-title.md @@ -0,0 +1,5 @@ +--- +title: Building a self-contained game in C# under 2 kilobytes +--- + +Hash In Title diff --git a/tests/fixtures/sample-small-website/linking-to-hash-in-title.md b/tests/fixtures/sample-small-website/linking-to-hash-in-title.md new file mode 100644 index 0000000..dbcbe09 --- /dev/null +++ b/tests/fixtures/sample-small-website/linking-to-hash-in-title.md @@ -0,0 +1,5 @@ +--- +title: Linking to Hash In Title +--- + +This should work: [[Building a self-contained game in C/# under 2 kilobytes]].