wip: moving from separate site repo to plugin

b8a3bbdc1113ccb3120a8e1b01dd674de289fe7d

Tucker McKnight | Sun Aug 24 2025

wip: moving from separate site repo to plugin
.gitignore:0
Before
0
After
0
dist
node_modules
main.ts:0
Before
0
After
0
import fsImport from 'fs'
import util from 'util'
import childProcess from 'child_process'
import repos from './src/repos.ts'
import branches from './src/branches.ts'
import flatFiles from './src/flatFiles.ts'
import {getLocation} from './src/helpers.ts'
import repoOperations from './src/vcses/operations.ts'

const exec = util.promisify(childProcess.exec)

export default async (eleventyConfig, reposConfiguration) => {
  // TODO: check if the render plugin is available and throw an error otherwise
  // TODO: check if the highlight function is available
  // TODO: throw an error if reposConfiguration is undefined

  const reposData = await repos(reposConfiguration)

  eleventyConfig.addFilter("getFileName", (filePath) => {
    const pathParts = filePath.split("/")
    return pathParts[pathParts.length - 1]
  })

  eleventyConfig.addFilter("getDirectoryContents", (repo, branch, dirPath) => {
    return reposData[repo].branches[branch].files.filter(file => file.startsWith(dirPath) && file !== dirPath)
  })

  eleventyConfig.addFilter("getRelativePath", (currentDir, fullFilePath) => {
    return fullFilePath.replace(`${currentDir}/`, "")
  })

  eleventyConfig.addFilter("lineNumbers", (code) => {
    const numLines = code.split('\n').length
    const lineNumbers = []
    for (let i = 1; i <= numLines; i++) {
      lineNumbers.push(i)
    }

    return lineNumbers
  })

  eleventyConfig.addFilter("highlightCode", (code, language) => {
    return eleventyConfig.javascript.functions.highlight(language, code)
  })

  eleventyConfig.addFilter("languageExtension", (filename, repoName) => {
    let extension = filename.split(".")
    extension = extension[extension.length - 1]
    const extensionsConfig = reposConfiguration.repos[repoName].languageExtensions
    return extensionsConfig && extensionsConfig[extension] ? extensionsConfig[extension] : extension
  })

  eleventyConfig.addFilter("topLevelFilesOnly", (files, currentLevel) => {
    const onlyUnique = (value, index, array) => {
      return array.findIndex(test => test.name === value.name) === index;
    }

    const currentLevelDirLength = currentLevel.split('/').length

    const topLevels: Array<string> = []
    files.forEach((file) => {
      if (file.startsWith(currentLevel)) {
        const parts = file.split("/").filter(part => part !== ".")
        topLevels.push(parts.slice(0, currentLevelDirLength).join('/'))
      }
    })

    const withNameAndDirAttrs = topLevels.map((file) => {
      // is a directory if the entire filename, plus a slash, is contained inside of any
      // other file
      const isDirectory: boolean = files.some((testFile) => {
        return testFile.startsWith(file + '/') && (testFile !== file)
      })

      return {name: file.replace(currentLevel, ''), fullPath: file, isDirectory}
    })

    const sortedByDirectory = withNameAndDirAttrs.filter(onlyUnique).toSorted((a, b) => {
      if (a.isDirectory && b.isDirectory) {
        return 0
      }
      if (a.isDirectory && !b.isDirectory) {
        return -1
      }
      return 1
    })

    return sortedByDirectory
  })

  eleventyConfig.addAsyncFilter("getFileLastTouchInfo", async (repo, branch, filename) => {
    const ignoreExtensions = ['.png', '.jpg']
    if (ignoreExtensions.some(extension => filename.endsWith(extension))) {
      return ""
    }

    const config = reposConfiguration.repos[repo]
    const location = getLocation(reposConfiguration, branch, repo)
    return repoOperations[config._type].getFileLastTouchInfo(repo, branch, filename, location)
  })

  eleventyConfig.addAsyncFilter("isDirectory", async(filename, repoName, branchName) => {
    const files = reposData[repoName].branches[branchName].files
    const isDirectory = files.some((testFile) => {
        return testFile.startsWith(filename + '/') && (testFile !== filename)
      })
    return isDirectory
  })

  eleventyConfig.addAsyncFilter("getFileContents", async (repo, branch, filename) => {
    const location = getLocation(reposConfiguration, branch, repo)
    let command = ''
    const config = reposConfiguration.repos[repo]
    if (config._type === "git") {
      command = `git show ${branch}:${filename}`
    }
    else if (config._type === "darcs") {
      command = `darcs show contents ${filename}`
    }
    const res = await exec(`(cd ${location} && ${command})`)
    return res.stdout
  })

  eleventyConfig.addAsyncFilter("getReadMe", async (repoName, branchName) => {
    const location = getLocation(reposConfiguration, branchName, repoName)
    const config = reposConfiguration.repos[repoName]
    let command = ''
    if (config._type === "git") {
      command = `git show ${branchName}:README.md`
    }
    else if (config._type === "darcs") {
      command = `darcs show contents README.md`
    }
    try {
      const res = await exec(`(cd ${location} && ${command})`)
      return res.stdout
    } catch {
      return ""
    }
  })

  eleventyConfig.addFilter("jsonStringify", data => JSON.stringify(data))

  const topLayoutPartial = fsImport.readFileSync(`${__dirname}/partial_templates/main_top.njk`).toString()
  const bottomLayoutPartial = fsImport.readFileSync(`${__dirname}/partial_templates/main_bottom.njk`).toString()

  // INDEX.NJK
  const indexTemplate = fsImport.readFileSync(`${__dirname}/templates/index.njk`).toString()
  eleventyConfig.addTemplate(
    'repos/index.njk',
    topLayoutPartial + indexTemplate + bottomLayoutPartial,
    {
      permalink: "repos/index.html",
    }
  )

  // BRANCHES.NJK
  const branchesTemplate = fsImport.readFileSync(`${__dirname}/templates/branches.njk`).toString()
  const branchesData = branches(reposData)
  eleventyConfig.addTemplate(
    'repos/branches.njk',
    topLayoutPartial + branchesTemplate + bottomLayoutPartial,
    {
      pagination: {
        data: "branches",
        size: 1,
        alias: "branchInfo",
      },
      branches: branchesData,
      permalink: (data) => {
        const repoName = data.branchInfo.repoName
        const branchName = data.branchInfo.branchName
        return `repos/${eleventyConfig.getFilter("slugify")(repoName)}/branches/${eleventyConfig.getFilter("slugify")(branchName)}/list/`
      },
      eleventyComputed: {
        nav: {
          repoName: (data) => data.branchInfo.repoName,
          branchName: (data) => data.branchInfo.branchName,
        }
      },
      navTab: "branches",
    }
  )

  // FILE.NJK
  const fileTemplate = fsImport.readFileSync(`${__dirname}/templates/file.njk`).toString()
  const flatFilesData = flatFiles(reposData)
  eleventyConfig.addTemplate(
    'repos/file.njk',
    topLayoutPartial + fileTemplate + bottomLayoutPartial,
    {
      pagination: {
        data: "flatFiles",
        size: 1,
        alias: "fileInfo",
      },
      flatFiles: flatFilesData,
      permalink: (data) => {
        const repoName = data.fileInfo.repoName
        const branchName = data.fileInfo.branchName
        return `repos/${eleventyConfig.getFilter("slugify")(repoName)}/branches/${eleventyConfig.getFilter("slugify")(branchName)}/files/${eleventyConfig.getFilter("slugify")(data.fileInfo.file)}.html`
      },
      eleventyComputed: {
        nav: {
          repoName: (data) => data.fileInfo.repoName,
          branchName: (data) => data.fileInfo.branchName,
        }
      },
      navTab: "files",
    }
  )

  // FILES.NJK
  const filesTemplate = fsImport.readFileSync(`${__dirname}/templates/files.njk`).toString()
  eleventyConfig.addTemplate(
    'repos/files.njk',
    topLayoutPartial + filesTemplate + bottomLayoutPartial,
    {
      pagination: {
        data: "branches",
        size: 1,
        alias: "branchInfo",
      },
      branches: branchesData,
      permalink: (data) => {
        const repoName = data.branchInfo.repoName
        const branchName = data.branchInfo.branchName
        return `repos/${eleventyConfig.getFilter("slugify")(repoName)}/branches/${eleventyConfig.getFilter("slugify")(branchName)}/files/`
      },
      eleventyComputed: {
        nav: {
          repoName: (data) => data.branchInfo.repoName,
          branchName: (data) => data.branchInfo.branchName,
        }
      },
      navTab: "files",
    }
  )

  // REPO.NJK
  const repoTemplate = fsImport.readFileSync(`${__dirname}/templates/repo.njk`).toString()
  eleventyConfig.addTemplate(
    'repos/repo.njk',
    topLayoutPartial + repoTemplate + bottomLayoutPartial,
    {
      pagination: {
        data: "branches",
        size: 1,
        alias: "branch",
      },
      branches: branchesData,
      permalink: (data) => {
        const repoName = data.branch.repoName
        const branchName = data.branch.branchName
        return `repos/${eleventyConfig.getFilter("slugify")(repoName)}/branches/${eleventyConfig.getFilter("slugify")(branchName)}/`
      },
      eleventyComputed: {
        nav: {
          repoName: (data) => data.branch.repoName,
          branchName: (data) => data.branch.branchName,
        }
      },
      navTab: "landing",
    }
  )
  eleventyConfig.addGlobalData("repos", reposData)
  eleventyConfig.addGlobalData("reposConfig", reposConfiguration)

}
make.sh:0
Before
0
After
0
#!/bin/bash
rm -R dist
mkdir dist
mkdir dist/templates
mkdir dist/partial_templates
cp templates/*.njk dist/templates
cp partial_templates/*.njk dist/partial_templates
package-lock.json:0
Before
0
After
0
{
  "name": "eleventy-plugin",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "eleventy-plugin",
      "version": "1.0.0",
      "license": "ISC",
      "dependencies": {
        "@11ty/eleventy": "^3.1.2",
        "diff": "^8.0.2",
        "lodash": "^4.17.21"
      },
      "devDependencies": {
        "@types/node": "^24.0.7",
        "typescript": "^5.8.3"
      }
    },
    "node_modules/@11ty/dependency-tree": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/@11ty/dependency-tree/-/dependency-tree-4.0.0.tgz",
      "integrity": "sha512-PTOnwM8Xt+GdJmwRKg4pZ8EKAgGoK7pedZBfNSOChXu8MYk2FdEsxdJYecX4t62owpGw3xK60q9TQv/5JI59jw==",
      "license": "MIT",
      "dependencies": {
        "@11ty/eleventy-utils": "^2.0.1"
      }
    },
    "node_modules/@11ty/dependency-tree-esm": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/@11ty/dependency-tree-esm/-/dependency-tree-esm-2.0.0.tgz",
      "integrity": "sha512-+4ySOON4aEAiyAGuH6XQJtxpGSpo6nibfG01krgix00sqjhman2+UaDUopq6Ksv8/jBB3hqkhsHe3fDE4z8rbA==",
      "license": "MIT",
      "dependencies": {
        "@11ty/eleventy-utils": "^2.0.1",
        "acorn": "^8.14.0",
        "dependency-graph": "^1.0.0",
        "normalize-path": "^3.0.0"
      }
    },
    "node_modules/@11ty/eleventy": {
      "version": "3.1.2",
      "resolved": "https://registry.npmjs.org/@11ty/eleventy/-/eleventy-3.1.2.tgz",
      "integrity": "sha512-IcsDlbXnBf8cHzbM1YBv3JcTyLB35EK88QexmVyFdVJVgUU6bh9g687rpxryJirHzo06PuwnYaEEdVZQfIgRGg==",
      "license": "MIT",
      "dependencies": {
        "@11ty/dependency-tree": "^4.0.0",
        "@11ty/dependency-tree-esm": "^2.0.0",
        "@11ty/eleventy-dev-server": "^2.0.8",
        "@11ty/eleventy-plugin-bundle": "^3.0.6",
        "@11ty/eleventy-utils": "^2.0.7",
        "@11ty/lodash-custom": "^4.17.21",
        "@11ty/posthtml-urls": "^1.0.1",
        "@11ty/recursive-copy": "^4.0.2",
        "@sindresorhus/slugify": "^2.2.1",
        "bcp-47-normalize": "^2.3.0",
        "chokidar": "^3.6.0",
        "debug": "^4.4.1",
        "dependency-graph": "^1.0.0",
        "entities": "^6.0.1",
        "filesize": "^10.1.6",
        "gray-matter": "^4.0.3",
        "iso-639-1": "^3.1.5",
        "js-yaml": "^4.1.0",
        "kleur": "^4.1.5",
        "liquidjs": "^10.21.1",
        "luxon": "^3.6.1",
        "markdown-it": "^14.1.0",
        "minimist": "^1.2.8",
        "moo": "^0.5.2",
        "node-retrieve-globals": "^6.0.1",
        "nunjucks": "^3.2.4",
        "picomatch": "^4.0.2",
        "please-upgrade-node": "^3.2.0",
        "posthtml": "^0.16.6",
        "posthtml-match-helper": "^2.0.3",
        "semver": "^7.7.2",
        "slugify": "^1.6.6",
        "tinyglobby": "^0.2.14"
      },
      "bin": {
        "eleventy": "cmd.cjs"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/11ty"
      }
    },
    "node_modules/@11ty/eleventy-dev-server": {
      "version": "2.0.8",
      "resolved": "https://registry.npmjs.org/@11ty/eleventy-dev-server/-/eleventy-dev-server-2.0.8.tgz",
      "integrity": "sha512-15oC5M1DQlCaOMUq4limKRYmWiGecDaGwryr7fTE/oM9Ix8siqMvWi+I8VjsfrGr+iViDvWcH/TVI6D12d93mA==",
      "license": "MIT",
      "dependencies": {
        "@11ty/eleventy-utils": "^2.0.1",
        "chokidar": "^3.6.0",
        "debug": "^4.4.0",
        "finalhandler": "^1.3.1",
        "mime": "^3.0.0",
        "minimist": "^1.2.8",
        "morphdom": "^2.7.4",
        "please-upgrade-node": "^3.2.0",
        "send": "^1.1.0",
        "ssri": "^11.0.0",
        "urlpattern-polyfill": "^10.0.0",
        "ws": "^8.18.1"
      },
      "bin": {
        "eleventy-dev-server": "cmd.js"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/11ty"
      }
    },
    "node_modules/@11ty/eleventy-plugin-bundle": {
      "version": "3.0.6",
      "resolved": "https://registry.npmjs.org/@11ty/eleventy-plugin-bundle/-/eleventy-plugin-bundle-3.0.6.tgz",
      "integrity": "sha512-wlEIMa1SEe6HE6ZyREEnPQiTw72337a2MPkyn0D1IzrqHrKU9euB17mv27LnnnyKvMJamCCqtU0985F5yyDL8g==",
      "license": "MIT",
      "dependencies": {
        "@11ty/eleventy-utils": "^2.0.2",
        "debug": "^4.4.0",
        "posthtml-match-helper": "^2.0.3"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/11ty"
      }
    },
    "node_modules/@11ty/eleventy-utils": {
      "version": "2.0.7",
      "resolved": "https://registry.npmjs.org/@11ty/eleventy-utils/-/eleventy-utils-2.0.7.tgz",
      "integrity": "sha512-6QE+duqSQ0GY9rENXYb4iPR4AYGdrFpqnmi59tFp9VrleOl0QSh8VlBr2yd6dlhkdtj7904poZW5PvGr9cMiJQ==",
      "license": "MIT",
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/11ty"
      }
    },
    "node_modules/@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==",
      "license": "MIT",
      "engines": {
        "node": ">=14"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/11ty"
      }
    },
    "node_modules/@11ty/posthtml-urls": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/@11ty/posthtml-urls/-/posthtml-urls-1.0.1.tgz",
      "integrity": "sha512-6EFN/yYSxC/OzYXpq4gXDyDMlX/W+2MgCvvoxf11X1z76bqkqFJ8eep5RiBWfGT5j0323a1pwpelcJJdR46MCw==",
      "license": "MIT",
      "dependencies": {
        "evaluate-value": "^2.0.0",
        "http-equiv-refresh": "^2.0.1",
        "list-to-array": "^1.1.0",
        "parse-srcset": "^1.0.2"
      },
      "engines": {
        "node": ">= 6"
      }
    },
    "node_modules/@11ty/recursive-copy": {
      "version": "4.0.2",
      "resolved": "https://registry.npmjs.org/@11ty/recursive-copy/-/recursive-copy-4.0.2.tgz",
      "integrity": "sha512-174nFXxL/6KcYbLYpra+q3nDbfKxLxRTNVY1atq2M1pYYiPfHse++3IFNl8mjPFsd7y2qQjxLORzIjHMjL3NDQ==",
      "license": "ISC",
      "dependencies": {
        "errno": "^1.0.0",
        "junk": "^3.1.0",
        "maximatch": "^0.1.0",
        "slash": "^3.0.0"
      },
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@sindresorhus/slugify": {
      "version": "2.2.1",
      "resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-2.2.1.tgz",
      "integrity": "sha512-MkngSCRZ8JdSOCHRaYd+D01XhvU3Hjy6MGl06zhOk614hp9EOAp5gIkBeQg7wtmxpitU6eAL4kdiRMcJa2dlrw==",
      "license": "MIT",
      "dependencies": {
        "@sindresorhus/transliterate": "^1.0.0",
        "escape-string-regexp": "^5.0.0"
      },
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/@sindresorhus/transliterate": {
      "version": "1.6.0",
      "resolved": "https://registry.npmjs.org/@sindresorhus/transliterate/-/transliterate-1.6.0.tgz",
      "integrity": "sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ==",
      "license": "MIT",
      "dependencies": {
        "escape-string-regexp": "^5.0.0"
      },
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/@types/node": {
      "version": "24.0.7",
      "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.7.tgz",
      "integrity": "sha512-YIEUUr4yf8q8oQoXPpSlnvKNVKDQlPMWrmOcgzoduo7kvA2UF0/BwJ/eMKFTiTtkNL17I0M6Xe2tvwFU7be6iw==",
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "undici-types": "~7.8.0"
      }
    },
    "node_modules/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==",
      "license": "MIT"
    },
    "node_modules/acorn": {
      "version": "8.15.0",
      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
      "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
      "license": "MIT",
      "bin": {
        "acorn": "bin/acorn"
      },
      "engines": {
        "node": ">=0.4.0"
      }
    },
    "node_modules/acorn-walk": {
      "version": "8.3.4",
      "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
      "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
      "license": "MIT",
      "dependencies": {
        "acorn": "^8.11.0"
      },
      "engines": {
        "node": ">=0.4.0"
      }
    },
    "node_modules/anymatch": {
      "version": "3.1.3",
      "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
      "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
      "license": "ISC",
      "dependencies": {
        "normalize-path": "^3.0.0",
        "picomatch": "^2.0.4"
      },
      "engines": {
        "node": ">= 8"
      }
    },
    "node_modules/anymatch/node_modules/picomatch": {
      "version": "2.3.1",
      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
      "license": "MIT",
      "engines": {
        "node": ">=8.6"
      },
      "funding": {
        "url": "https://github.com/sponsors/jonschlinkert"
      }
    },
    "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==",
      "license": "Python-2.0"
    },
    "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==",
      "license": "MIT",
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "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==",
      "license": "MIT",
      "dependencies": {
        "array-uniq": "^1.0.1"
      },
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/array-uniq": {
      "version": "1.0.3",
      "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
      "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==",
      "license": "MIT",
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/arrify": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
      "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==",
      "license": "MIT",
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/asap": {
      "version": "2.0.6",
      "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
      "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
      "license": "MIT"
    },
    "node_modules/balanced-match": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
      "license": "MIT"
    },
    "node_modules/bcp-47": {
      "version": "2.1.0",
      "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-2.1.0.tgz",
      "integrity": "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==",
      "license": "MIT",
      "dependencies": {
        "is-alphabetical": "^2.0.0",
        "is-alphanumerical": "^2.0.0",
        "is-decimal": "^2.0.0"
      },
      "funding": {
        "type": "github",
        "url": "https://github.com/sponsors/wooorm"
      }
    },
    "node_modules/bcp-47-match": {
      "version": "2.0.3",
      "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz",
      "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==",
      "license": "MIT",
      "funding": {
        "type": "github",
        "url": "https://github.com/sponsors/wooorm"
      }
    },
    "node_modules/bcp-47-normalize": {
      "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==",
      "license": "MIT",
      "dependencies": {
        "bcp-47": "^2.0.0",
        "bcp-47-match": "^2.0.0"
      },
      "funding": {
        "type": "github",
        "url": "https://github.com/sponsors/wooorm"
      }
    },
    "node_modules/binary-extensions": {
      "version": "2.3.0",
      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
      "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
      "license": "MIT",
      "engines": {
        "node": ">=8"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/brace-expansion": {
      "version": "1.1.12",
      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
      "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
      "license": "MIT",
      "dependencies": {
        "balanced-match": "^1.0.0",
        "concat-map": "0.0.1"
      }
    },
    "node_modules/braces": {
      "version": "3.0.3",
      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
      "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
      "license": "MIT",
      "dependencies": {
        "fill-range": "^7.1.1"
      },
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/chokidar": {
      "version": "3.6.0",
      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
      "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
      "license": "MIT",
      "dependencies": {
        "anymatch": "~3.1.2",
        "braces": "~3.0.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"
      },
      "engines": {
        "node": ">= 8.10.0"
      },
      "funding": {
        "url": "https://paulmillr.com/funding/"
      },
      "optionalDependencies": {
        "fsevents": "~2.3.2"
      }
    },
    "node_modules/commander": {
      "version": "10.0.1",
      "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
      "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
      "license": "MIT",
      "engines": {
        "node": ">=14"
      }
    },
    "node_modules/concat-map": {
      "version": "0.0.1",
      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
      "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
      "license": "MIT"
    },
    "node_modules/debug": {
      "version": "4.4.1",
      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
      "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
      "license": "MIT",
      "dependencies": {
        "ms": "^2.1.3"
      },
      "engines": {
        "node": ">=6.0"
      },
      "peerDependenciesMeta": {
        "supports-color": {
          "optional": true
        }
      }
    },
    "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==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/dependency-graph": {
      "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==",
      "license": "MIT",
      "engines": {
        "node": ">=4"
      }
    },
    "node_modules/diff": {
      "version": "8.0.2",
      "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz",
      "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==",
      "license": "BSD-3-Clause",
      "engines": {
        "node": ">=0.3.1"
      }
    },
    "node_modules/dom-serializer": {
      "version": "1.4.1",
      "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz",
      "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==",
      "license": "MIT",
      "dependencies": {
        "domelementtype": "^2.0.1",
        "domhandler": "^4.2.0",
        "entities": "^2.0.0"
      },
      "funding": {
        "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
      }
    },
    "node_modules/dom-serializer/node_modules/entities": {
      "version": "2.2.0",
      "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
      "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
      "license": "BSD-2-Clause",
      "funding": {
        "url": "https://github.com/fb55/entities?sponsor=1"
      }
    },
    "node_modules/domelementtype": {
      "version": "2.3.0",
      "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
      "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/fb55"
        }
      ],
      "license": "BSD-2-Clause"
    },
    "node_modules/domhandler": {
      "version": "4.3.1",
      "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz",
      "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==",
      "license": "BSD-2-Clause",
      "dependencies": {
        "domelementtype": "^2.2.0"
      },
      "engines": {
        "node": ">= 4"
      },
      "funding": {
        "url": "https://github.com/fb55/domhandler?sponsor=1"
      }
    },
    "node_modules/domutils": {
      "version": "2.8.0",
      "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
      "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==",
      "license": "BSD-2-Clause",
      "dependencies": {
        "dom-serializer": "^1.0.1",
        "domelementtype": "^2.2.0",
        "domhandler": "^4.2.0"
      },
      "funding": {
        "url": "https://github.com/fb55/domutils?sponsor=1"
      }
    },
    "node_modules/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==",
      "license": "MIT"
    },
    "node_modules/encodeurl": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
      "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/entities": {
      "version": "6.0.1",
      "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
      "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
      "license": "BSD-2-Clause",
      "engines": {
        "node": ">=0.12"
      },
      "funding": {
        "url": "https://github.com/fb55/entities?sponsor=1"
      }
    },
    "node_modules/errno": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/errno/-/errno-1.0.0.tgz",
      "integrity": "sha512-3zV5mFS1E8/1bPxt/B0xxzI1snsg3uSCIh6Zo1qKg6iMw93hzPANk9oBFzSFBFrwuVoQuE3rLoouAUfwOAj1wQ==",
      "license": "MIT",
      "dependencies": {
        "prr": "~1.0.1"
      },
      "bin": {
        "errno": "cli.js"
      }
    },
    "node_modules/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==",
      "license": "MIT"
    },
    "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==",
      "license": "MIT",
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/esm-import-transformer": {
      "version": "3.0.5",
      "resolved": "https://registry.npmjs.org/esm-import-transformer/-/esm-import-transformer-3.0.5.tgz",
      "integrity": "sha512-1GKLvfuMnnpI75l8c6sHoz0L3Z872xL5akGuBudgqTDPv4Vy6f2Ec7jEMKTxlqWl/3kSvNbHELeimJtnqgYniw==",
      "license": "MIT",
      "dependencies": {
        "acorn": "^8.15.0"
      }
    },
    "node_modules/esprima": {
      "version": "4.0.1",
      "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
      "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
      "license": "BSD-2-Clause",
      "bin": {
        "esparse": "bin/esparse.js",
        "esvalidate": "bin/esvalidate.js"
      },
      "engines": {
        "node": ">=4"
      }
    },
    "node_modules/etag": {
      "version": "1.8.1",
      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
      "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
      "license": "MIT",
      "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==",
      "license": "MIT",
      "engines": {
        "node": ">= 8"
      }
    },
    "node_modules/extend-shallow": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
      "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
      "license": "MIT",
      "dependencies": {
        "is-extendable": "^0.1.0"
      },
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/fdir": {
      "version": "6.5.0",
      "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
      "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
      "license": "MIT",
      "engines": {
        "node": ">=12.0.0"
      },
      "peerDependencies": {
        "picomatch": "^3 || ^4"
      },
      "peerDependenciesMeta": {
        "picomatch": {
          "optional": true
        }
      }
    },
    "node_modules/filesize": {
      "version": "10.1.6",
      "resolved": "https://registry.npmjs.org/filesize/-/filesize-10.1.6.tgz",
      "integrity": "sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w==",
      "license": "BSD-3-Clause",
      "engines": {
        "node": ">= 10.4.0"
      }
    },
    "node_modules/fill-range": {
      "version": "7.1.1",
      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
      "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
      "license": "MIT",
      "dependencies": {
        "to-regex-range": "^5.0.1"
      },
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/finalhandler": {
      "version": "1.3.1",
      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
      "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
      "license": "MIT",
      "dependencies": {
        "debug": "2.6.9",
        "encodeurl": "~2.0.0",
        "escape-html": "~1.0.3",
        "on-finished": "2.4.1",
        "parseurl": "~1.3.3",
        "statuses": "2.0.1",
        "unpipe": "~1.0.0"
      },
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/finalhandler/node_modules/debug": {
      "version": "2.6.9",
      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
      "license": "MIT",
      "dependencies": {
        "ms": "2.0.0"
      }
    },
    "node_modules/finalhandler/node_modules/ms": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
      "license": "MIT"
    },
    "node_modules/fresh": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
      "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/fsevents": {
      "version": "2.3.3",
      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
      "hasInstallScript": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "darwin"
      ],
      "engines": {
        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
      }
    },
    "node_modules/glob-parent": {
      "version": "5.1.2",
      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
      "license": "ISC",
      "dependencies": {
        "is-glob": "^4.0.1"
      },
      "engines": {
        "node": ">= 6"
      }
    },
    "node_modules/gray-matter": {
      "version": "4.0.3",
      "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
      "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
      "license": "MIT",
      "dependencies": {
        "js-yaml": "^3.13.1",
        "kind-of": "^6.0.2",
        "section-matter": "^1.0.0",
        "strip-bom-string": "^1.0.0"
      },
      "engines": {
        "node": ">=6.0"
      }
    },
    "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==",
      "license": "MIT",
      "dependencies": {
        "sprintf-js": "~1.0.2"
      }
    },
    "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==",
      "license": "MIT",
      "dependencies": {
        "argparse": "^1.0.7",
        "esprima": "^4.0.0"
      },
      "bin": {
        "js-yaml": "bin/js-yaml.js"
      }
    },
    "node_modules/htmlparser2": {
      "version": "7.2.0",
      "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz",
      "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==",
      "funding": [
        "https://github.com/fb55/htmlparser2?sponsor=1",
        {
          "type": "github",
          "url": "https://github.com/sponsors/fb55"
        }
      ],
      "license": "MIT",
      "dependencies": {
        "domelementtype": "^2.0.1",
        "domhandler": "^4.2.2",
        "domutils": "^2.8.0",
        "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==",
      "license": "BSD-2-Clause",
      "engines": {
        "node": ">=0.12"
      },
      "funding": {
        "url": "https://github.com/fb55/entities?sponsor=1"
      }
    },
    "node_modules/http-equiv-refresh": {
      "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==",
      "license": "MIT",
      "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==",
      "license": "MIT",
      "dependencies": {
        "depd": "2.0.0",
        "inherits": "2.0.4",
        "setprototypeof": "1.2.0",
        "statuses": "2.0.1",
        "toidentifier": "1.0.1"
      },
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/inherits": {
      "version": "2.0.4",
      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
      "license": "ISC"
    },
    "node_modules/is-alphabetical": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
      "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==",
      "license": "MIT",
      "funding": {
        "type": "github",
        "url": "https://github.com/sponsors/wooorm"
      }
    },
    "node_modules/is-alphanumerical": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz",
      "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==",
      "license": "MIT",
      "dependencies": {
        "is-alphabetical": "^2.0.0",
        "is-decimal": "^2.0.0"
      },
      "funding": {
        "type": "github",
        "url": "https://github.com/sponsors/wooorm"
      }
    },
    "node_modules/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==",
      "license": "MIT",
      "dependencies": {
        "binary-extensions": "^2.0.0"
      },
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/is-decimal": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz",
      "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==",
      "license": "MIT",
      "funding": {
        "type": "github",
        "url": "https://github.com/sponsors/wooorm"
      }
    },
    "node_modules/is-extendable": {
      "version": "0.1.1",
      "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
      "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
      "license": "MIT",
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/is-extglob": {
      "version": "2.1.1",
      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
      "license": "MIT",
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/is-glob": {
      "version": "4.0.3",
      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
      "license": "MIT",
      "dependencies": {
        "is-extglob": "^2.1.1"
      },
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/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==",
      "license": "ISC"
    },
    "node_modules/is-number": {
      "version": "7.0.0",
      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
      "license": "MIT",
      "engines": {
        "node": ">=0.12.0"
      }
    },
    "node_modules/iso-639-1": {
      "version": "3.1.5",
      "resolved": "https://registry.npmjs.org/iso-639-1/-/iso-639-1-3.1.5.tgz",
      "integrity": "sha512-gXkz5+KN7HrG0Q5UGqSMO2qB9AsbEeyLP54kF1YrMsIxmu+g4BdB7rflReZTSTZGpfj8wywu6pfPBCylPIzGQA==",
      "license": "MIT",
      "engines": {
        "node": ">=6.0"
      }
    },
    "node_modules/js-yaml": {
      "version": "4.1.0",
      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
      "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
      "license": "MIT",
      "dependencies": {
        "argparse": "^2.0.1"
      },
      "bin": {
        "js-yaml": "bin/js-yaml.js"
      }
    },
    "node_modules/junk": {
      "version": "3.1.0",
      "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz",
      "integrity": "sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==",
      "license": "MIT",
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/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==",
      "license": "MIT",
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/kleur": {
      "version": "4.1.5",
      "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
      "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
      "license": "MIT",
      "engines": {
        "node": ">=6"
      }
    },
    "node_modules/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==",
      "license": "MIT",
      "dependencies": {
        "uc.micro": "^2.0.0"
      }
    },
    "node_modules/liquidjs": {
      "version": "10.21.1",
      "resolved": "https://registry.npmjs.org/liquidjs/-/liquidjs-10.21.1.tgz",
      "integrity": "sha512-NZXmCwv3RG5nire3fmIn9HsOyJX3vo+ptp0yaXUHAMzSNBhx74Hm+dAGJvscUA6lNqbLuYfXgNavRQ9UbUJhQQ==",
      "license": "MIT",
      "dependencies": {
        "commander": "^10.0.0"
      },
      "bin": {
        "liquid": "bin/liquid.js",
        "liquidjs": "bin/liquid.js"
      },
      "engines": {
        "node": ">=14"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/liquidjs"
      }
    },
    "node_modules/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==",
      "license": "MIT"
    },
    "node_modules/lodash": {
      "version": "4.17.21",
      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
      "license": "MIT"
    },
    "node_modules/luxon": {
      "version": "3.7.1",
      "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.1.tgz",
      "integrity": "sha512-RkRWjA926cTvz5rAb1BqyWkKbbjzCGchDUIKMCUvNi17j6f6j8uHGDV82Aqcqtzd+icoYpELmG3ksgGiFNNcNg==",
      "license": "MIT",
      "engines": {
        "node": ">=12"
      }
    },
    "node_modules/markdown-it": {
      "version": "14.1.0",
      "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
      "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
      "license": "MIT",
      "dependencies": {
        "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"
      },
      "bin": {
        "markdown-it": "bin/markdown-it.mjs"
      }
    },
    "node_modules/markdown-it/node_modules/entities": {
      "version": "4.5.0",
      "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
      "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
      "license": "BSD-2-Clause",
      "engines": {
        "node": ">=0.12"
      },
      "funding": {
        "url": "https://github.com/fb55/entities?sponsor=1"
      }
    },
    "node_modules/maximatch": {
      "version": "0.1.0",
      "resolved": "https://registry.npmjs.org/maximatch/-/maximatch-0.1.0.tgz",
      "integrity": "sha512-9ORVtDUFk4u/NFfo0vG/ND/z7UQCVZBL539YW0+U1I7H1BkZwizcPx5foFv7LCPcBnm2U6RjFnQOsIvN4/Vm2A==",
      "license": "MIT",
      "dependencies": {
        "array-differ": "^1.0.0",
        "array-union": "^1.0.1",
        "arrify": "^1.0.0",
        "minimatch": "^3.0.0"
      },
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/mdurl": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
      "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
      "license": "MIT"
    },
    "node_modules/mime": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
      "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
      "license": "MIT",
      "bin": {
        "mime": "cli.js"
      },
      "engines": {
        "node": ">=10.0.0"
      }
    },
    "node_modules/mime-db": {
      "version": "1.54.0",
      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
      "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/mime-types": {
      "version": "3.0.1",
      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
      "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
      "license": "MIT",
      "dependencies": {
        "mime-db": "^1.54.0"
      },
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/minimatch": {
      "version": "3.1.2",
      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
      "license": "ISC",
      "dependencies": {
        "brace-expansion": "^1.1.7"
      },
      "engines": {
        "node": "*"
      }
    },
    "node_modules/minimist": {
      "version": "1.2.8",
      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
      "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
      "license": "MIT",
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/minipass": {
      "version": "7.1.2",
      "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
      "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
      "license": "ISC",
      "engines": {
        "node": ">=16 || 14 >=14.17"
      }
    },
    "node_modules/moo": {
      "version": "0.5.2",
      "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz",
      "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==",
      "license": "BSD-3-Clause"
    },
    "node_modules/morphdom": {
      "version": "2.7.7",
      "resolved": "https://registry.npmjs.org/morphdom/-/morphdom-2.7.7.tgz",
      "integrity": "sha512-04GmsiBcalrSCNmzfo+UjU8tt3PhZJKzcOy+r1FlGA7/zri8wre3I1WkYN9PT3sIeIKfW9bpyElA+VzOg2E24g==",
      "license": "MIT"
    },
    "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==",
      "license": "MIT"
    },
    "node_modules/node-retrieve-globals": {
      "version": "6.0.1",
      "resolved": "https://registry.npmjs.org/node-retrieve-globals/-/node-retrieve-globals-6.0.1.tgz",
      "integrity": "sha512-j0DeFuZ/Wg3VlklfbxUgZF/mdHMTEiEipBb3q0SpMMbHaV3AVfoUQF8UGxh1s/yjqO0TgRZd4Pi/x2yRqoQ4Eg==",
      "license": "MIT",
      "dependencies": {
        "acorn": "^8.14.1",
        "acorn-walk": "^8.3.4",
        "esm-import-transformer": "^3.0.3"
      }
    },
    "node_modules/normalize-path": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
      "license": "MIT",
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/nunjucks": {
      "version": "3.2.4",
      "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.4.tgz",
      "integrity": "sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==",
      "license": "BSD-2-Clause",
      "dependencies": {
        "a-sync-waterfall": "^1.0.0",
        "asap": "^2.0.3",
        "commander": "^5.1.0"
      },
      "bin": {
        "nunjucks-precompile": "bin/precompile"
      },
      "engines": {
        "node": ">= 6.9.0"
      },
      "peerDependencies": {
        "chokidar": "^3.3.0"
      },
      "peerDependenciesMeta": {
        "chokidar": {
          "optional": true
        }
      }
    },
    "node_modules/nunjucks/node_modules/commander": {
      "version": "5.1.0",
      "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz",
      "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==",
      "license": "MIT",
      "engines": {
        "node": ">= 6"
      }
    },
    "node_modules/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==",
      "license": "MIT",
      "dependencies": {
        "ee-first": "1.1.1"
      },
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/parse-srcset": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz",
      "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==",
      "license": "MIT"
    },
    "node_modules/parseurl": {
      "version": "1.3.3",
      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/picomatch": {
      "version": "4.0.3",
      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
      "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
      "license": "MIT",
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/jonschlinkert"
      }
    },
    "node_modules/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==",
      "license": "MIT",
      "dependencies": {
        "semver-compare": "^1.0.0"
      }
    },
    "node_modules/posthtml": {
      "version": "0.16.6",
      "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.16.6.tgz",
      "integrity": "sha512-JcEmHlyLK/o0uGAlj65vgg+7LIms0xKXe60lcDOTU7oVX/3LuEuLwrQpW3VJ7de5TaFKiW4kWkaIpJL42FEgxQ==",
      "license": "MIT",
      "dependencies": {
        "posthtml-parser": "^0.11.0",
        "posthtml-render": "^3.0.0"
      },
      "engines": {
        "node": ">=12.0.0"
      }
    },
    "node_modules/posthtml-match-helper": {
      "version": "2.0.3",
      "resolved": "https://registry.npmjs.org/posthtml-match-helper/-/posthtml-match-helper-2.0.3.tgz",
      "integrity": "sha512-p9oJgTdMF2dyd7WE54QI1LvpBIkNkbSiiECKezNnDVYhGhD1AaOnAkw0Uh0y5TW+OHO8iBdSqnd8Wkpb6iUqmw==",
      "license": "MIT",
      "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",
      "integrity": "sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw==",
      "license": "MIT",
      "dependencies": {
        "htmlparser2": "^7.1.1"
      },
      "engines": {
        "node": ">=12"
      }
    },
    "node_modules/posthtml-render": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/posthtml-render/-/posthtml-render-3.0.0.tgz",
      "integrity": "sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA==",
      "license": "MIT",
      "dependencies": {
        "is-json": "^2.0.1"
      },
      "engines": {
        "node": ">=12"
      }
    },
    "node_modules/prr": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
      "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==",
      "license": "MIT"
    },
    "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==",
      "license": "MIT",
      "engines": {
        "node": ">=6"
      }
    },
    "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==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/readdirp": {
      "version": "3.6.0",
      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
      "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
      "license": "MIT",
      "dependencies": {
        "picomatch": "^2.2.1"
      },
      "engines": {
        "node": ">=8.10.0"
      }
    },
    "node_modules/readdirp/node_modules/picomatch": {
      "version": "2.3.1",
      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
      "license": "MIT",
      "engines": {
        "node": ">=8.6"
      },
      "funding": {
        "url": "https://github.com/sponsors/jonschlinkert"
      }
    },
    "node_modules/section-matter": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
      "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
      "license": "MIT",
      "dependencies": {
        "extend-shallow": "^2.0.1",
        "kind-of": "^6.0.0"
      },
      "engines": {
        "node": ">=4"
      }
    },
    "node_modules/semver": {
      "version": "7.7.2",
      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
      "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
      "license": "ISC",
      "bin": {
        "semver": "bin/semver.js"
      },
      "engines": {
        "node": ">=10"
      }
    },
    "node_modules/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==",
      "license": "MIT"
    },
    "node_modules/send": {
      "version": "1.2.0",
      "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
      "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
      "license": "MIT",
      "dependencies": {
        "debug": "^4.3.5",
        "encodeurl": "^2.0.0",
        "escape-html": "^1.0.3",
        "etag": "^1.8.1",
        "fresh": "^2.0.0",
        "http-errors": "^2.0.0",
        "mime-types": "^3.0.1",
        "ms": "^2.1.3",
        "on-finished": "^2.4.1",
        "range-parser": "^1.2.1",
        "statuses": "^2.0.1"
      },
      "engines": {
        "node": ">= 18"
      }
    },
    "node_modules/setprototypeof": {
      "version": "1.2.0",
      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
      "license": "ISC"
    },
    "node_modules/slash": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
      "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
      "license": "MIT",
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/slugify": {
      "version": "1.6.6",
      "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz",
      "integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==",
      "license": "MIT",
      "engines": {
        "node": ">=8.0.0"
      }
    },
    "node_modules/sprintf-js": {
      "version": "1.0.3",
      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
      "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
      "license": "BSD-3-Clause"
    },
    "node_modules/ssri": {
      "version": "11.0.0",
      "resolved": "https://registry.npmjs.org/ssri/-/ssri-11.0.0.tgz",
      "integrity": "sha512-aZpUoMN/Jj2MqA4vMCeiKGnc/8SuSyHbGSBdgFbZxP8OJGF/lFkIuElzPxsN0q8TQQ+prw3P4EDfB3TBHHgfXw==",
      "license": "ISC",
      "dependencies": {
        "minipass": "^7.0.3"
      },
      "engines": {
        "node": "^16.14.0 || >=18.0.0"
      }
    },
    "node_modules/statuses": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
      "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.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",
      "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==",
      "license": "MIT",
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/tinyglobby": {
      "version": "0.2.14",
      "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
      "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
      "license": "MIT",
      "dependencies": {
        "fdir": "^6.4.4",
        "picomatch": "^4.0.2"
      },
      "engines": {
        "node": ">=12.0.0"
      },
      "funding": {
        "url": "https://github.com/sponsors/SuperchupuDev"
      }
    },
    "node_modules/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==",
      "license": "MIT",
      "dependencies": {
        "is-number": "^7.0.0"
      },
      "engines": {
        "node": ">=8.0"
      }
    },
    "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==",
      "license": "MIT",
      "engines": {
        "node": ">=0.6"
      }
    },
    "node_modules/typescript": {
      "version": "5.8.3",
      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
      "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
      "dev": true,
      "license": "Apache-2.0",
      "bin": {
        "tsc": "bin/tsc",
        "tsserver": "bin/tsserver"
      },
      "engines": {
        "node": ">=14.17"
      }
    },
    "node_modules/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==",
      "license": "MIT"
    },
    "node_modules/undici-types": {
      "version": "7.8.0",
      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
      "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
      "dev": true,
      "license": "MIT"
    },
    "node_modules/unpipe": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
      "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/urlpattern-polyfill": {
      "version": "10.1.0",
      "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.1.0.tgz",
      "integrity": "sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==",
      "license": "MIT"
    },
    "node_modules/ws": {
      "version": "8.18.3",
      "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
      "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
      "license": "MIT",
      "engines": {
        "node": ">=10.0.0"
      },
      "peerDependencies": {
        "bufferutil": "^4.0.1",
        "utf-8-validate": ">=5.0.2"
      },
      "peerDependenciesMeta": {
        "bufferutil": {
          "optional": true
        },
        "utf-8-validate": {
          "optional": true
        }
      }
    }
  }
}
package.json:0
Before
0
After
0
{
  "name": "eleventy-plugin",
  "version": "1.0.0",
  "main": "dist/main.js",
  "scripts": {
    "build": "./make.sh && npx tsc"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "devDependencies": {
    "@types/node": "^24.0.7",
    "typescript": "^5.8.3"
  },
  "dependencies": {
    "@11ty/eleventy": "^3.1.2",
    "diff": "^8.0.2",
    "lodash": "^4.17.21"
  }
}
partial_templates/main_bottom.njk:0
Before
0
After
0
        </div>
      </div>
    </div>
    <script>
      const toggleUnifiedMode = (e) => {
        const diffs = document.getElementById('diffs')
        const afterDiffs = document.querySelectorAll('.diff-right')
        if (e.checked) {
          diffs.classList.add("unified")
          afterDiffs.forEach((elem) => {
            elem.classList.remove('border-start', 'ps-2')
          })
        }
        else {
          diffs.classList.remove("unified")
          afterDiffs.forEach((elem) => {
            elem.classList.add('border-start', 'ps-2')
          })
        }
      }

      const selectBranch = (e) => {
        const values = e.value.split(",")
        window.location = `/repos/${values[0]}/branches/${values[1]}/${values[2]}`
      }
    </script>
    <script src="/static/main.js"></script>
  </body>
</html>
partial_templates/main_top.njk:0
Before
0
After
0
<!DOCTYPE html>
<html>
  <head>
    <title>{% if nav.title %}{{nav.title}}{% else %}Repositories{% endif %}</title>
    <script>
      window.jsVars = {};
    
      {% if nav %}
        window.jsVars['baseUrl'] = `{{ repoConfig.repos[nav.repoName].baseUrl | jsonStringify | safe }}`;
    
        window.jsVars['nav'] = {{nav | jsonStringify | safe}};
    
        window.jsVars['cloneDiv'] = `{%- if reposConfig.repos[nav.repoName]._type == "darcs" -%}{% set url = repos[nav.repoName].cloneUrl + (nav.branchName | slugify) %}
<label class="form-label">HTTPS URL</label>
<div class="input-group d-flex flex-nowrap">
  <span class="clone overflow-hidden input-group-text">
    {{ url }}
  </span>
  <button data-clone-url="{{url}}" class="btn btn-primary" id="clone-button">Copy</button>
</div>{%- elif reposConfig.repos[nav.repoName]._type == "git" -%}<label class="form-label">HTTPS URL</label>
<div class="input-group d-flex flex-nowrap">
  <span class="clone overflow-hidden input-group-text">
    {% set url = repos[nav.repoName].cloneUrl %}
    {{ url }}
  </span>
  <button data-clone-url="{{url}}" class="btn btn-primary" id="clone-button">Copy</button>
</div>
{%- endif -%}`;
    
      {% endif %}
    </script>
    <script src="/static/top.js"></script>
    <link rel="stylesheet" id="prism-theme" type="text/css" href="/prism.css" />
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-SgOJa3DmI69IUzQ2PVdRZhwQ+dy64/BUtbMJw1MZ8t5HZApcHrRKUc4W0kG879m7" crossorigin="anonymous">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.6/dist/js/bootstrap.bundle.min.js" integrity="sha384-j1CDi7MgGQ12Z7Qab0qlWQ/Qqz24Gc6BM0thvEMVjHnfYGF0rmFCozFSxQBxwHKO" crossorigin="anonymous"></script>
    <link rel="stylesheet" type="text/css" href="/static/main.css" />
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>

  <body>
    <div class="container-lg">
      <div class="row pt-5">
        <div class="col">
          <header>
            <div class="row d-flex justify-content-between pb-3">
              <div class="col-auto">
                <nav class="fs-4">
                  <a href="/">Repositories</a>{% if nav.repoName %}<span class="text-secondary mx-2">&gt;</span><a href="/repos/{{nav.repoName | slugify}}/branches/{{reposConfig.repos[nav.repoName].defaultBranch | slugify}}">{{nav.repoName}}</a>{% endif %}
                </nav>
              </div>
              <div class="col-auto d-flex align-items-center">
                <div class="dropdown">
                  <button class="dropdown-toggle btn" id="dark-mode-switch" type="button" data-bs-toggle="dropdown" aria-expanded="false">
                    <i class="bi bi-brightness-high"></i>
                  </button>
                  <ul class="dropdown-menu">
                    <li><button class="btn" data-theme-pref="light" onclick="toggleDarkMode(this)"><i class="bi bi-brightness-high mx-1"></i>Light</button></li>
                    <li><button class="btn" data-theme-pref="dark" onclick="toggleDarkMode(this)"><i class="bi bi-moon mx-1"></i>Dark</button></li>
                    <li><button class="btn" data-theme-pref="auto" onclick="toggleDarkMode(this)"><i class="bi bi-yin-yang mx-1"></i>Auto</button></li>
                  </ul>
                </div>
              </div>
            </div>
            {% if nav.repoName %}
            <div class="row mb-4">
              <div class="col-12 col-md order-2 order-md-1 pe-0">
                <nav class="nav-tabs">
                  <ul class="nav">
                    <li class="nav-item">
                      <a class="nav-link {% if navTab == "landing" %}active{% endif %}" href="/repos/{{nav.repoName | slugify}}/branches/{{nav.branchName}}">Landing Page</a>
                    </li>
                    <li class="nav-item">
                      <a class="nav-link {% if navTab == "files" %}active{% endif %}" href="/repos/{{nav.repoName | slugify}}/branches/{{nav.branchName}}/files">Files</a>
                    </li>
                    <li class="nav-item">
                      <a class="nav-link {% if navTab == "patches" %}active{% endif %}" href="/repos/{{nav.repoName | slugify}}/branches/{{nav.branchName}}/patches/page1">Changes</a>
                    </li>
                    <li class="nav-item">
                      <a class="nav-link {% if navTab == "branches" %}active{% endif %}" href="/repos/{{nav.repoName | slugify}}/branches/{{nav.branchName | slugify}}/list">Branches</a>
                    </li>
                  </ul>
                </nav>
              </div>
              <div class="col-12 col-md-auto order-1 order-md-2 pb-2 pb-md-0 mb-2 mb-md-0 border-bottom">
                <div class="row">
                  <div class="col-auto px-2">
                    <button type="button" id="clone-popover-btn" class="btn btn-sm btn-primary" data-bs-toggle="popover" data-bs-placement="bottom">Clone <i class="bi bi-caret-down-fill"></i></button>
                  </div>
                  <div class="col-auto px-2">
                    <div class="input-group input-group-sm">
                      <span class="input-group-text">Branch</span>
                      <select class="form-select" onchange="selectBranch(this)" aria-label="Repository branch selector">
                        {% for branch in branches %}
                        {% if branch.repoName == nav.repoName %}
                        <option value="{{branch.repoName | slugify}},{{branch.branchName | slugify}},{{nav.path}}" {% if branch.branchName == nav.branchName %}selected{% endif %}>{{branch.branchName}}</option>
                        {% endif %}
                        {% endfor %}
                      </select>
                    </div>
                  </div>
                </div>
              </div>
            </div>
            {% endif %}
          </header>
        </div>
      </div>
    </div>
    <div class="container-{% if width == "full"%}fluid{% else %}lg{% endif %}">
      <div class="row">
        <div class="col">
src/branches.ts:0
Before
0
After
0
let cachedBranches = null

export default (repos) => {
  if (cachedBranches !== null) { return cachedBranches }

  cachedBranches = Object.keys(repos).flatMap((repoName) => {
    return Object.keys(repos[repoName].branches).map((branchName) => {
      return {
        branchName,
        repoName,
      }
    })
  })

  return cachedBranches
}
src/configTypes.ts:0
Before
0
After
0
/**
 * The ReposConfiguration object contains information about your local repositories,
 * like their name and location on your local filesystem. Add repositories to this
 * configuration object to make a static site for them.
 * 
 * This static site generator works with both git and darcs repositories. Because of the
 * differences between these two version control systems, the configuration for them
 * looks a little different. Both types of configuration objects can be nested underneath
 * the {@link ReposConfiguration.repos} key.
 * 
 * You will also need to set the {@link ReposConfiguration.baseUrl} to the URL of your
 * live website.
 * @example
 * const config: ReposConfiguration = {
 *   repos: {
 *     "My Darcs Project": {
 *       _type: "darcs",
 *       baseUrl: "https://repos.tuckerm.us",
 *       defaultBranch: 'main',
 *       branches: {
 *         'main': {
 *           location: "/home/alice/projects/my_darcs_project",
 *           description: "Main branch of this project."
 *         },
 *         'drafts': {
 *           location: "/home/alice/projects/my_darcs_project_drafts/",
 *           description: "Some things that are a work in progress",
 *         },
 *       },
 *       languageExtensions: {
 *         "njk": "html",
 *       },
 *     },
 *   },
 * }
 */
type ReposConfiguration = {
  repos: {
    /** An object containing the configuration for your repositories.
    * Each key in this object is a repository name, and the value has several
    * config options for that repository. The required config options describe
    * the path to the repository and which branches should be pulled. See the specific
    * definitions of {@link GitConfig} and {@link DarcsConfig} for more details
    * about what goes in these configuration objects.
    */
    [repoName: string]: GitConfig | DarcsConfig
  },
  /**
  * The root URL where this website will be. E.g.: https://blog.example.com/repos.
  * This URL will be used when a clone or pull command is being shown on your site.
  * @example baseUrl: "https://repos.tuckerm.us"
  */
  baseUrl: string,
}
type GitConfig = {
  _type: "git",
  /* The absolute path to the repository.
  * @example location: "/home/alice/projects/git_repo"
  */
  location: string,
  description?: string,
  defaultBranch: string,
  branchesToPull: Array<string>,
  languageExtensions?: {
    [fileExtension: string]: string
  }
}

/**
* A configuration object for a darcs repository.
*
* @example
* "My Darcs Project": {
*   _type: "darcs",
*   baseUrl: "https://repos.tuckerm.us",
*   defaultBranch: 'main',
*   branches: {
*     'main': {
*       location: "/home/alice/projects/my_darcs_project",
*       description: "Main branch of this project."
*     },
*     'drafts': {
*       location: "/home/alice/projects/my_darcs_project_drafts/",
*       description: "Some things that are a work in progress",
*     },
*   },
*   languageExtensions: {
*     "njk": "html",
*   },
* }
*/
type DarcsConfig = {
  /** Must be set to `"darcs"`. */
  _type: "darcs",
  /**
  * The name of the default branch. Should match one of the keys in {@link DarcsConfig.branches}.
  * @example defaultBranch: "main"
  * */
  defaultBranch: string,
  /** The branches of this repository to generate pages for. Since darcs doesn't have
  * branches in the same way that other VCSs do, a "branch" in this case is an entirely
  * separate repository. So, these "branches" are more like repos that are grouped under
  * the same project name. That is why you need to specify a separate location for each
  * branch.
  * @example
  * branches: {
  *   main: {
  *     location: "/home/alice/projects/my-project/main",
  *     description: "The main release branch of this project."
  *   },
  *   drafts: {
  *     location: "/home/alice/projects/my-project/drafts",
  *     description: "Undeployed works-in-progress for this project."
  *   }
  * }
  */
  branches: {
    /**
    * Each key in this object will be the name that is used for that branch.
    */
    [branchName: string]: {
      /**
      * The absolute path to the repository for this branch.
      * @example location: "/home/alice/projects/darcs_repo_main"
      */
      location: string,
      /**
      * A description of this branch. You may want to clarify what this branch is used for here.
      */
      description?: string,
    }
  },
  /**
  * If your repository has any uncommon file extensions that should be treated like a different
  * type of file, list them here. If you include `{njk: "html"}` here, that will tell the
  * syntax highlighter to highlight an `njk` file like an `html` file. The key is the file
  * extension in your code, and the value is the file extension that the syntax highlighter
  * will know about.
  * @example
  * languageExtensions: {
  *   njk: "html",
  *   jss: "js",
  *   madeupformat: "txt"
  * }
  */
  languageExtensions?: {
    [fileExtension: string]: string
  }
}

export {
  ReposConfiguration,
  GitConfig,
  DarcsConfig,
}
src/dataTypes.ts:0
Before
0
After
0
export type BranchInfo = {
  files: Array<string>,
  patches: Array<any>
}
src/flatFiles.ts:0
Before
0
After
0
import repos from './repos.ts'

let cachedFlatFiles = null

export default (repos) => {
  if (cachedFlatFiles !== null) { return cachedFlatFiles }

  cachedFlatFiles = Object.keys(repos).flatMap((repoName) => {
    return Object.keys(repos[repoName].branches).flatMap((branchName) => {
      return repos[repoName].branches[branchName].files.map((file) => {
        return {
          file,
          branchName,
          repoName,
        }
      })
    })
  })

  return cachedFlatFiles
}
src/helpers.ts:0
Before
0
After
0
import _ from 'lodash'
import * as Diff from 'diff'

type NavValues = {
  repoName: string | ((data: any) => string),
  branchName: string | ((data: any) => string),
  path: string | ((data: any) => string),
  title: string | ((data: any) => string),
}
type Hunk = {
  file: string,
  lineNumber: number,
  previousText: string,
  afterText: string,
}

type DiffInfo = {
  file: string,
  lineNumber: number,
  previousText: string,
  afterText: string,
}

const getGitDiffsFromPatchText = (patchText: string): Array<DiffInfo> => {
  const lines = patchText.split("\n")
  const hunks: Array<Hunk> = []
  let previousHunk = -1
  const filenameRegex = RegExp(/diff --git a\/(.*?) b\/(.*?)/)
  const lineNumberRegex = RegExp(/@@ -(.*?)[,| ].*/)
  let previousFilename = ''
  let currentFilename = ''
  let skipFourStartingAt = -1
  lines.forEach((line, index) => {
    if (line.startsWith("diff")) {
      previousFilename = currentFilename
      currentFilename = line.match(filenameRegex)[1]
      if (previousHunk !== -1) {
        skipFourStartingAt = index
      }
    }
    if (line.startsWith("@@") || index == lines.length - 1) {
      if (previousHunk === -1) {
        previousHunk = index
        return
      }

      let hunkEndIndex = index + 1
      if (skipFourStartingAt !== -1 && skipFourStartingAt < hunkEndIndex) {
        hunkEndIndex = skipFourStartingAt
        skipFourStartingAt = -1
      }
      const lastHunk = lines.slice(previousHunk, hunkEndIndex)
      let lastHunkBefore = lastHunk.filter(line => line.startsWith("-")).map(str => str.replace("-", "")).join("\n")
      let lastHunkAfter = lastHunk.filter(line => line.startsWith("+")).map(str => str.replace("+", "")).join("\n")
      lastHunkBefore = _.escape(lastHunkBefore)
      lastHunkAfter = _.escape(lastHunkAfter)
      const changeObject = Diff.diffWordsWithSpace(lastHunkBefore, lastHunkAfter)
      let previousText = ""
      let afterText = ""

      changeObject.forEach((obj) => {
        if (!obj.added && !obj.removed) {
          previousText = previousText + obj.value
          afterText = afterText + obj.value
        }
        if (obj.added) {
          afterText = afterText + "<mark>" + obj.value + "</mark>"
        }
        if (obj.removed) {
          previousText = previousText + "<mark>" + obj.value + "</mark>"
        }
      })

      hunks.push({
        file: previousFilename !== '' ? previousFilename : currentFilename,
        lineNumber: parseInt(lines[previousHunk].match(lineNumberRegex)[1]),
        previousText,
        afterText,
      })
      previousFilename = ''
      previousHunk = index
    }
  })

  return hunks
}

/** @hidden */
const getDarcsDiffsFromPatchText = (patchText: string): Array<DiffInfo> => {
  const lines = patchText.split("\n")
  const hunks: Array<Hunk> = []
  let previousHunk = -1
  lines.forEach((line, index) => {
    if (line.startsWith("hunk") || index === lines.length - 1) {
      if (previousHunk === -1) {
        previousHunk = index
        return
      }
      // get diff from previous hunk to this next one
      const lastHunk = lines.slice(previousHunk, index + 1) // slice is non-inclusive for the end argument
      let lastHunkBefore = lastHunk.filter(line => line.startsWith("-")).map(str => str.replace("-", "")).join("\n")
      let lastHunkAfter = lastHunk.filter(line => line.startsWith("+")).map(str => str.replace("+", "")).join("\n")
      lastHunkBefore = _.escape(lastHunkBefore)
      lastHunkAfter = _.escape(lastHunkAfter)
      let filename = lines[previousHunk].replace("hunk ./", "")
      const changeObject = Diff.diffWords(lastHunkBefore, lastHunkAfter)
      let previousText = ""
      let afterText = ""

      changeObject.forEach((obj) => {
        if (!obj.added && !obj.removed) {
          previousText = previousText + obj.value
          afterText = afterText + obj.value
        }
        if (obj.added) {
          afterText = afterText + "<mark>" + obj.value + "</mark>"
        }
        if (obj.removed) {
          previousText = previousText + "<mark>" + obj.value + "</mark>"
        }
      })
      const regex = RegExp(/(.*) ([0-9]+)$/)
      const matches = filename.match(regex)
      const file = matches[1]
      const lineNumber: number = parseInt(matches[2])

      hunks.push({
        file,
        lineNumber,
        previousText,
        afterText,
      })
      previousHunk = index
    }
  })
  return hunks
}

const getLocation = (reposConfig: any, branchName: string, repoName: string): string => {
  const config = reposConfig.repos[repoName]
  if (config._type === "darcs") {
    return config.branches[branchName].location
  }
  else if (config._type === "git") {
    return config.location
  }
}

export {
  NavValues,
  getDarcsDiffsFromPatchText,
  getGitDiffsFromPatchText,
  getLocation,
}
src/repos.ts:0
Before
0
After
0
import {
  type DarcsConfig,
  type GitConfig,
} from './configTypes.ts'
import {type BranchInfo} from './dataTypes.ts'

import repoOperations from './vcses/operations.ts'
import { getLocation} from './helpers.ts'
import repoHelpers from './vcses/helpers.ts'

type BranchInfoTuple = [string, BranchInfo]
type BranchObject = {
  [key: string]: BranchInfo
}
type RepoObjectTuple = [string, BranchObject]
type RepoObject = {
  [key: string]: {
    branches: BranchObject,
    cloneUrl: string,
  }
}

const getBranchNames = (repoConfig: DarcsConfig | GitConfig): Array<string> => {
  if (repoConfig._type === 'darcs') {
    return Object.keys(repoConfig.branches)
  }
  else if (repoConfig._type === 'git') {
    return repoConfig.branchesToPull
  }
}

let cachedRepos = null

const repos: (reposConfig: any) => Promise<RepoObject> = async (reposConfig) => {
  if (cachedRepos !== null) { return cachedRepos }

  const repoNames = Object.keys(reposConfig.repos)
  const reposTuples: RepoObjectTuple[] = await Promise.all(repoNames.map(async (repoName): Promise<RepoObjectTuple> => {
    const vcs = reposConfig.repos[repoName]._type
    const branchNames = getBranchNames(reposConfig.repos[repoName])
    const branchTuples: BranchInfoTuple[] = await Promise.all(branchNames.map(async (branchName): Promise<BranchInfoTuple> => {
      const repoLocation = getLocation(reposConfig, branchName, repoName)
      const files = await repoOperations[vcs].getFileList(repoName, branchName, repoLocation)
      const patches = await repoOperations[vcs].getBranchInfo(repoName, branchName, repoLocation)
      return [branchName, {
        files,
        patches,
      }]
    }))

    const branchesObject: BranchObject = {}
    for (let branchTuple of branchTuples) {
      branchesObject[branchTuple[0]] = branchTuple[1]
    }
    return [repoName, branchesObject]
  }))

  const reposObject: RepoObject = {}
  for (let repoTuple of reposTuples) {
    const repoName = repoTuple[0]
    const repoType = reposConfig.repos[repoName]._type
    reposObject[repoName] = {
      branches: repoTuple[1],
      cloneUrl: repoHelpers[repoType].cloneUrl(reposConfig.baseUrl, repoName)
    }
  }

  cachedRepos = reposObject
  return reposObject
}

export default repos
src/vcses/darcs/helpers.ts:0
Before
0
After
0
export default {
    cloneUrl: (baseUrl: string, repoName: string) => {
        return `${baseUrl}/repos/${repoName.toLowerCase().replaceAll(" ", "-")}/branches/`
    }
}
src/vcses/darcs/operations.ts:0
Before
0
After
0
import util from 'util'
import childProcess from 'child_process'
const exec = util.promisify(childProcess.exec)
import {getDarcsDiffsFromPatchText} from '../../helpers.ts'

export const getFileList = async(repoName: string, branchName: string, repoLocation: string) => {
  const command = "darcs show files"

  const result = await exec(`(cd ${repoLocation} && ${command})`)
  const files = result.stdout.split("\n").filter(item => item.length > 0 && item != ".")
  return files
}

export const getBranchInfo = async (repoName: string, branchName: string, repoLocation: string) => {
  const patches = new Map()

  const totalPatchesCountRes = await exec(`(cd ${repoLocation} && darcs log --count)`)
  const totalPatchesCount = parseInt(totalPatchesCountRes.stdout)
  let hunkRegex = RegExp(/^ *hunk /)
  // Get 100 patches at a time and parse those
  for (let i = 1; i <= totalPatchesCount; i = i + 100) {
    let patchesSubsetRes = await exec(`(cd ${repoLocation} && darcs log --index=${i}-${i+100} -v)`)
    let patchesSubset = patchesSubsetRes.stdout.split("\n")
    do {
      const nextPatchStart = patchesSubset.findIndex((line, index) => {
        return (index > 0 && line.startsWith("patch ")) || index === patchesSubset.length - 1
      })
      const isEndOfFile = nextPatchStart === patchesSubset.length - 1
      const currentPatch = patchesSubset.slice(0, isEndOfFile ? nextPatchStart : nextPatchStart - 1)
      patchesSubset = patchesSubset.slice(nextPatchStart)
      const hash = currentPatch[0].replace("patch ", "").trim()
      const author = currentPatch[1].replace("Author: ", "").trim()
      const date = currentPatch[2].replace("Date: ", "").trim()
      const name = currentPatch[3].replace("  * ", "").trim()
      const diffStart = currentPatch.findIndex((line) => {
        return line.match(hunkRegex)
      })
      const description = currentPatch.slice(5, diffStart).map(str => str.replace("  ", "")).join("\n").trim()
      const diffs = getDarcsDiffsFromPatchText(currentPatch.slice(diffStart).map(str => str.trimStart()).join("\n"))
      patches.set(hash, {
        name,
        description,
        author,
        date,
        hash,
        diffs,
      })
    } while (patchesSubset.length > 1)
  }

  return Array.from(patches.values())
}

export const getFileLastTouchInfo = async (repoName: string, branchName: string, filename: string, repoLocation: string) => {
  const command = `darcs annotate --machine-readable ${filename}`
  const res = await exec(`(cd ${repoLocation} && ${command})`)
  const output = res.stdout
  const outputLines = output.split("\n").map((line) => {
    return line.split(' ')[0]
  })

  return outputLines.map((line) => {
    return {sha: line, author: ''}
  })
}
src/vcses/git/helpers.ts:0
Before
0
After
0
export default {
    cloneUrl: (baseUrl: string, repoName: string) => {
        return `${baseUrl}/repos/${repoName.toLowerCase().replaceAll(" ", "-")}.git`
    }
}
src/vcses/git/operations.ts:0
Before
0
After
0
import util from 'util'
import childProcess from 'child_process'
const exec = util.promisify(childProcess.exec)
import { getGitDiffsFromPatchText} from '../../helpers.ts'

export const getFileList = async (repoName: string, branchName: string, repoLocation: string) => {
  const command = `git ls-tree -r --name-only ${branchName}`

  const result = await exec(`(cd ${repoLocation} && ${command})`)
  let files = result.stdout.split("\n").filter(item => item.length > 0 && item != ".")
  // TODO: this could be better. This is adding each sub-path of a file to a set, so that
  // we don't wind up with repeats, and then converting that back into an array. E.g. if
  // we have two files:
  // - posts/blog/one.md
  // - posts/blog/two.md
  // this will add the following to the set:
  // posts, posts/blog, posts/blog/one.md, posts, posts/blog, posts/blog/two.md
  // The repeats will be omitted because it's a Set, and the resulting array will
  // be [posts, posts/blog, posts/blog/one.md, posts/blog/two.md].
  // This is because it's convenient to have the directories show up as their own "file"
  // in the file list, even though git doesn't treat them that way.
  const fileSet: Set<string> = new Set()
  files.forEach((file) => {
    const fileParts = file.split("/")
    const allPathsInFile = fileParts.reduce((accumulator, currentValue, index) => {
      // Skip the first iteration, we have already added it as the initialValue
      if (index === 0) { return accumulator }

      accumulator.push(accumulator[accumulator.length - 1] + '/' + currentValue)
      return accumulator
    }, [fileParts[0]])

    allPathsInFile.forEach((path) => {
      fileSet.add(path)
    })
  })
  return Array.from(fileSet)
}

export const getBranchInfo = async (repoName: string, branchName: string, repoLocation: string) => {
  const patches = new Map()

  const totalPatchesCountRes = await exec(`(cd ${repoLocation} && git rev-list --count ${branchName})`)
  const totalPatchesCount = parseInt(totalPatchesCountRes.stdout)
  for (let i = 0; i < totalPatchesCount; i = i + 10) {
    const gitLogSubsetRes = await exec(`(cd ${repoLocation} && git log ${branchName} -p -n 10 --skip ${i})`)
    let gitLogSubset = gitLogSubsetRes.stdout.split("\n")
    do {
      const nextPatchStart = gitLogSubset.findIndex((line, index) => {
        return (index > 0 && line.startsWith("commit ")) || index === gitLogSubset.length - 1
      })
      const isEndOfFile = nextPatchStart === gitLogSubset.length - 1
      const currentPatch = gitLogSubset.slice(0, isEndOfFile ? nextPatchStart : nextPatchStart - 1)
      gitLogSubset = gitLogSubset.slice(nextPatchStart)

      const hash = currentPatch[0].replace("commit ", "").trim()
      const author = currentPatch[1].replace("Author: ", "").trim()
      const date = currentPatch[2].replace("Date: ", "").trim()
      const diffStart = currentPatch.findIndex((line) => {
        return line.startsWith("diff ")
      })
      // Git log is indent four spaces by default -- remove those.
      const commitMessage = currentPatch.slice(4, diffStart).map(str => str.replace("    ", ""))
      const name = commitMessage[0].trim()
      const description = commitMessage.slice(1, commitMessage.length - 1).filter((line) => {
        // git log --porcelain output adds these "Ignore-this:" lines and I'm not sure what they are
        return !line.startsWith('Ignore-this: ')
      }).join("\n").trim()

      const diffs = getGitDiffsFromPatchText(currentPatch.slice(diffStart).join("\n"))
      patches.set(hash, {
        name,
        description,
        author,
        date,
        hash,
        diffs,
      })
    } while (gitLogSubset.length > 1)
  }

  return Array.from(patches.values())
}

export const getFileLastTouchInfo = async (repo, branch, filename, repoLocation) => {
  const regex = RegExp(".* [0-9]+ [0-9]+")
  const command = `git blame --porcelain ${branch} ${filename}`
  const res = await exec(`(cd ${repoLocation} && ${command})`)
  const output = res.stdout
  const outputLines = output.split("\n")
  const initialValue: Array<Array<string>> = [[outputLines[0]]]
  const chunked = outputLines.reduce((accumulator, currentLine, currentIndex) => {
    // skip the first iteration since we already gave it initialValue
    if (currentIndex == 0) { return accumulator }

    if (currentLine.match(regex)) {
      accumulator.push([currentLine])
      return accumulator
    }
    else {
      accumulator[accumulator.length - 1].push(currentLine)
      return accumulator
    }
  }, initialValue)

  let currentAuthor = ''
  let authorsAndShasByLine = []
  chunked.forEach((chunk) => {
    let shaAndLineNumberParts = chunk[0].split(' ')
    let line = parseInt(shaAndLineNumberParts[2])
    let sha = shaAndLineNumberParts[0]
    if (chunk[1] && chunk[1].startsWith('author ')) {
      currentAuthor = chunk[1].replace('author ', '')
    }
    authorsAndShasByLine[line - 1] = {sha, author: currentAuthor}
  })
  return authorsAndShasByLine
}
src/vcses/helpers.ts:0
Before
0
After
0
import gitHelpers from './git/helpers.ts'
import darcsHelpers from './darcs/helpers.ts'

export default {
    git: gitHelpers,
    darcs: darcsHelpers
}
src/vcses/operations.ts:0
Before
0
After
0
import {
  getBranchInfo as getGitBranchInfo,
  getFileList as getGitFileList,
  getFileLastTouchInfo as getGitFileLastTouchInfo,
} from './git/operations.ts'
import {
  getBranchInfo as getDarcsBranchInfo,
  getFileList as getDarcsFileList,
  getFileLastTouchInfo as getDarcsFileLastTouchInfo,
} from './darcs/operations.ts'
import {type BranchInfo} from '../dataTypes.ts'

type RepoOperationsType = {
  [vcs: string]: {
    getBranchInfo: (repoName: string, branchName: string, repoLocation: string) => Promise<BranchInfo['patches']>,
    getFileList: (repoName: string, branchName: string, repoLocation: string) => Promise<BranchInfo['files']>,
    getFileLastTouchInfo: (repoName: string, branchName: string, filename: string, repoLocation: string) => Promise<Array<{sha: string, author: string}>>,
  }
}

const repoOperations: RepoOperationsType = {
  git: {
    getBranchInfo: getGitBranchInfo,
    getFileList: getGitFileList,
    getFileLastTouchInfo: getGitFileLastTouchInfo,
  },
  darcs: {
    getBranchInfo: getDarcsBranchInfo,
    getFileList: getDarcsFileList,
    getFileLastTouchInfo: getDarcsFileLastTouchInfo,
  }
}

export default repoOperations
templates/branches.njk:0
Before
0
After
0
<ul>
{% for branch in branches %}
  {% set description = reposConfig.repos[branch.repoName].branches[branch.branchName].description %}
  {% if branch.repoName == branchInfo.repoName %}
  <li><a href="/repos/{{branch.repoName | slugify}}/branches/{{branch.branchName | slugify}}">{{branch.branchName}}</a>{% if branch.branchName == branchInfo.branchName %} (current){% endif %}{% if description %} - {{ description }}{% endif %}</li>
  {% endif %}
{% endfor %}
</ul>
templates/file.njk:0
Before
0
After
0
<h3>{{fileInfo.file | getFileName}}</h3>
{% if fileInfo.file | isDirectory(fileInfo.repoName, fileInfo.branchName) %}
<ul class="list-group">
{% set dirs = fileInfo.repoName | getDirectoryContents(fileInfo.branchName, fileInfo.file) | topLevelFilesOnly(fileInfo.file + '/') %}
{% for dir in dirs %}
  <li class="list-group-item">
    {% if dir.isDirectory %}
    <i class="bi bi-folder-fill"></i>
    {% else %}
    <i class="bi bi-file-earmark"></i>
    {% endif %}
    <a href="/repos/{{fileInfo.repoName | slugify}}/branches/{{fileInfo.branchName | slugify}}/files/{{dir.fullPath | slugify}}.html">{{fileInfo.file | getRelativePath(dir.name)}}</a>
  </li>
{% endfor %}
</ul>
{% else %}
<div class="row py-2">
  <div class="col">
    <div class="form-check form-switch">
      <input class="form-check-input" type="checkbox" role="switch" id="showLastTouch">
      <label class="form-check-label" for="showLastTouch">Show last line change</label>
    </div>
  </div>
</div>
<div class="row">
  <div class="col-auto border-end">
    {% set fileContents = fileInfo.repoName | getFileContents(fileInfo.branchName, fileInfo.file) %}
    <code style="white-space: pre;"><pre class="language-text">
{%- for lineNumber in fileContents | lineNumbers -%}
{{ lineNumber }}
{% endfor -%}</pre></code>
  </div>
  <div id="annotations" class="col-auto d-none">
    {% set annotations = fileInfo.repoName | getFileLastTouchInfo(fileInfo.branchName, fileInfo.file) %}
    <code style="white-space: pre;"><pre class="language-text">
{%- for annotation in annotations -%}
<a href="/repos/{{fileInfo.repoName | slugify}}/branches/{{fileInfo.branchName | slugify}}/patches/{{annotation.sha}}">{{ (annotation.sha | truncate(6, true, '')) }}</a> {{ annotation.author }}
{% endfor -%}
    </pre></code>
  </div>
  <div class="col overflow-scroll">
    <code>
{{- fileContents | highlightCode((fileInfo.file | languageExtension(fileInfo.repoName))) | safe }}
    </code>
  </div>
</div>
{% endif %}

<script type="text/javascript">
  const toggleLastTouch = (event) => {
    const isOn = event.target.checked
    const annotations = document.getElementById("annotations")
    if (isOn) {
      annotations.classList.remove("d-none")
    } else {
      annotations.classList.add("d-none")
    }
  }

  document.getElementById("showLastTouch")?.addEventListener('click', toggleLastTouch)
</script>
templates/files.njk:0
Before
0
After
0
<div class="row">
  <div class="col">
    <ul class="list-group">
    {% set files = repos[branchInfo.repoName].branches[branchInfo.branchName].files | topLevelFilesOnly('') %}
    {% for file in files %}
      <li class="list-group-item">
        {% if file.isDirectory %}
        <i class="bi bi-folder-fill"></i>
        {% else %}
        <i class="bi bi-file-earmark"></i>
        {% endif %}
        <a href="/repos/{{branchInfo.repoName | slugify}}/branches/{{branchInfo.branchName | slugify}}/files/{{file.fullPath | slugify}}.html">{{file.name}}</a>
      </li>
    {% endfor %}
    </ul>
  </div>
</div>
templates/index.njk:0
Before
0
After
0
<ul>
{% for repoName, options in repos %}
  <li><a href="/repos/{{repoName | slugify}}/branches/{{reposConfig.repos[repoName].defaultBranch}}">{{repoName}}</a></li>
{% endfor %}
</ul>
templates/repo.njk:0
Before
0
After
0
<div class="row">
  <div class="col-md-8 col-sm-12 order-md-1 order-sm-2">
    {{ branch.repoName | getReadMe(branch.branchName) | renderContent("md") | safe }}
  </div>
  <div class="col-md-4 col-sm-12 order-md-2 order-sm-1">
    <div class="row">
      <div class="col">
        <div class="row align-items-center">
          <div class="col-auto">
            <h2 class="fs-6 my-0">Recent patches in {{branch.branchName}}</h2>
          </div>
          <div class="col-auto">
            <a href="/repos/{{ branch.repoName | slugify }}/branches/{{ branch.branchName | slugify }}/patches.xml" class="initialism">RSS<i class="bi bi-rss-fill ms-2" style="color: orange;"></i></a>
          </div>
        </div>
        {% for patch in repos[branch.repoName].branches[branch.branchName].patches | batch(3) | first %}
        <div class="card mt-2 mb-4">
          <div class="card-body">
            <a href="/repos/{{branch.repoName | slugify}}/branches/{{branch.branchName | slugify}}/patches/{{patch.hash}}" class="text-primary d-inline-block card-title fs-5">{{patch.name}}</a>
            <p class="card-subtitle fs-6 mb-2 text-body-secondary">{{patch.date}}</p>
            <p class="card-subtitle fs-6 mb-2 text-body-secondary">{{patch.author}}</p>
            <p class="card-text">{{patch.description | truncate(150)}}</p>
          </div>
          <div class="card-footer">
            {% if reposConfig.repos[branch.repoName]._type == "darcs" %}
            <button data-hash="{{patch.hash}}" data-vcs="darcs" class="copy-btn btn btn-sm btn-outline-primary ms-2">
              <i class="bi-copy bi me-1"></i>darcs pull {{patch.hash | truncate(6, true, "")}}
            </button>
            {% elif reposConfig.repos[branch.repoName]._type == "git" %}
            <button data-hash="{{patch.hash}}" data-vcs="git" class="copy-btn btn btn-sm btn-outline-primary">
              {{patch.hash | truncate(8, true, "")}} <i class="bi-copy bi me-1"></i>
            </button>
            {% endif %}
          </div>
        </div>
        {% endfor %}
      </div>
    </div>
  </div>
</div>
tsconfig.json:0
Before
0
After
0
{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "rewriteRelativeImportExtensions": true,
    "noImplicitAny": false,
    "module": "nodenext",
    "target": "es2017",
    "lib": ["es2023", "dom"],
    "allowJs": true,
    "outDir": "dist"
  },
  "include": ["**/*.ts"],
  "exclude": [
    "dist/**/*",
    "ts/frontend/**/*",
  ],
}