text: /**
   * Copyright (c) 2020 Google Inc
   *
   * Permission is hereby granted, free of charge, to any person obtaining a copy of
   * this software and associated documentation files (the "Software"), to deal in
   * the Software without restriction, including without limitation the rights to
   * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
   * the Software, and to permit persons to whom the Software is furnished to do so,
   * subject to the following conditions:
   *
   * The above copyright notice and this permission notice shall be included in all
   * copies or substantial portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
   * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
   * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
   * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
   */
  
  /**
   * Copyright (c) 2018 Zach Leatherman
   *
   * Permission is hereby granted, free of charge, to any person obtaining a copy of
   * this software and associated documentation files (the "Software"), to deal in
   * the Software without restriction, including without limitation the rights to
   * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
   * the Software, and to permit persons to whom the Software is furnished to do so,
   * subject to the following conditions:
   *
   * The above copyright notice and this permission notice shall be included in all
   * copies or substantial portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
   * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
   * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
   * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
   */
  
  const { DateTime } = require("luxon");
  const { promisify } = require("util");
  const fs = require("fs");
  const hasha = require("hasha");
  const readFile = promisify(fs.readFile);
  const stat = promisify(fs.stat);
  const execFile = promisify(require("child_process").execFile);
  const pluginRss = require("@11ty/eleventy-plugin-rss");
  const pluginSyntaxHighlight = require("@11ty/eleventy-plugin-syntaxhighlight");
  const pluginNavigation = require("@11ty/eleventy-navigation");
  const markdownIt = require("markdown-it");
  const markdownItAnchor = require("markdown-it-anchor");
  const localImages = require("./third_party/eleventy-plugin-local-images/.eleventy.js");
  const CleanCSS = require("clean-css");
  const GA_ID = require("./_data/metadata.json").googleAnalyticsId;
  
  module.exports = function (eleventyConfig) {
      const pluginEmbedTweet = require("eleventy-plugin-embed-tweet")
      eleventyConfig.addPlugin(pluginEmbedTweet);
      eleventyConfig.addPlugin(pluginRss);
      eleventyConfig.addPlugin(pluginSyntaxHighlight);
      eleventyConfig.addPlugin(pluginNavigation);
  
      eleventyConfig.addPlugin(localImages, {
          distPath: "_site",
          assetPath: "/img/remote",
          selector:
              "img,amp-img,amp-video,meta[property='og:image'],meta[name='twitter:image'],amp-story",
          verbose: false,
      });
  
      eleventyConfig.addPlugin(require("./_11ty/img-dim.js"));
      eleventyConfig.addPlugin(require("./_11ty/json-ld.js"));
      eleventyConfig.addPlugin(require("./_11ty/optimize-html.js"));
      eleventyConfig.addPlugin(require("./_11ty/apply-csp.js"));
      eleventyConfig.setDataDeepMerge(true);
      eleventyConfig.addLayoutAlias("post", "layouts/post.njk");
      eleventyConfig.addNunjucksAsyncFilter("addHash", function (
          absolutePath,
          callback
      ) {
          readFile(`_site${absolutePath}`, {
              encoding: "utf-8",
          })
              .then((content) => {
                  return hasha.async(content);
              })
              .then((hash) => {
                  callback(null, `${absolutePath}?hash=${hash.substr(0, 10)}`);
              })
              .catch((error) => callback(error));
      });
  
      async function lastModifiedDate(filename) {
          try {
              const { stdout } = await execFile("git", [
                  "log",
                  "-1",
                  "--format=%cd",
                  filename,
              ]);
              return new Date(stdout);
          } catch (e) {
              console.error(e.message);
              // Fallback to stat if git isn't working.
              const stats = await stat(filename);
              return stats.mtime; // Date
          }
      }
      // Cache the lastModifiedDate call because shelling out to git is expensive.
      // This means the lastModifiedDate will never change per single eleventy invocation.
      const lastModifiedDateCache = new Map();
      eleventyConfig.addNunjucksAsyncFilter("lastModifiedDate", function (
          filename,
          callback
      ) {
          const call = (result) => {
              result.then((date) => callback(null, date));
              result.catch((error) => callback(error));
          };
          const cached = lastModifiedDateCache.get(filename);
          if (cached) {
              return call(cached);
          }
          const promise = lastModifiedDate(filename);
          lastModifiedDateCache.set(filename, promise);
          call(promise);
      });
  
      eleventyConfig.addFilter("encodeURIComponent", function (str) {
          return encodeURIComponent(str);
      });
  
      eleventyConfig.addFilter("cssmin", function (code) {
          return new CleanCSS({}).minify(code).styles;
      });
  
      eleventyConfig.addFilter("readableDate", (dateObj) => {
          return DateTime.fromJSDate(dateObj, { zone: "utc" }).toFormat(
              "dd LLL yyyy"
          );
      });
  
      // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-date-string
      eleventyConfig.addFilter("htmlDateString", (dateObj) => {
          return DateTime.fromJSDate(dateObj, { zone: "utc" }).toFormat("yyyy-LL-dd");
      });
  
      eleventyConfig.addFilter("sitemapDateTimeString", (dateObj) => {
          const dt = DateTime.fromJSDate(dateObj, { zone: "utc" });
          if (!dt.isValid) {
              return "";
          }
          return dt.toISO();
      });
  
      // Get the first `n` elements of a collection.
      eleventyConfig.addFilter("head", (array, n) => {
          if (n < 0) {
              return array.slice(n);
          }
  
          return array.slice(0, n);
      });
  
      eleventyConfig.addCollection("tagList", require("./_11ty/getTagList"));
  
      eleventyConfig.addPassthroughCopy("img");
      eleventyConfig.addPassthroughCopy("css");
      // We need to copy cached.js only if GA is used
      eleventyConfig.addPassthroughCopy(GA_ID ? "js" : "js/*[!cached].*");
      eleventyConfig.addPassthroughCopy("fonts");
      eleventyConfig.addPassthroughCopy("_headers");
  
      // We need to rebuild upon JS change to update the CSP.
      eleventyConfig.addWatchTarget("./js/");
      // We need to rebuild on CSS change to inline it.
      eleventyConfig.addWatchTarget("./css/");
      // Unfortunately this means .eleventyignore needs to be maintained redundantly.
      // But without this the JS build artefacts doesn't trigger a build.
      eleventyConfig.setUseGitIgnore(false);
  
      /* Markdown Overrides */
      let markdownLibrary = markdownIt({
          html: true,
          breaks: true,
          linkify: true,
      }).use(markdownItAnchor, {
          permalink: true,
          permalinkClass: "direct-link",
          permalinkSymbol: "#",
      });
      eleventyConfig.setLibrary("md", markdownLibrary);
  
      // Browsersync Overrides
      eleventyConfig.setBrowserSyncConfig({
          callbacks: {
              ready: function (err, browserSync) {
                  const content_404 = fs.readFileSync("_site/404.html");
  
                  browserSync.addMiddleware("*", (req, res) => {
                      // Provides the 404 content without redirect.
                      res.write(content_404);
                      res.end();
                  });
              },
          },
          ui: false,
          ghostMode: false,
      });
  
      return {
          templateFormats: ["md", "njk", "html", "liquid"],
  
          // If your site lives in a different subdirectory, change this.
          // Leading or trailing slashes are all normalized away, so don’t worry about those.
  
          // If you don’t have a subdirectory, use "" or "/" (they do the same thing)
          // This is only used for link URLs (it does not affect your file structure)
          // Best paired with the `url` filter: https://www.11ty.io/docs/filters/url/
  
          // You can also pass this in on the command line using `--pathprefix`
          // pathPrefix: "/",
  
          markdownTemplateEngine: "liquid",
          htmlTemplateEngine: "njk",
          dataTemplateEngine: "njk",
  
          // These are all optional, defaults are shown:
          dir: {
              input: ".",
              includes: "_includes",
              data: "_data",
              // Warning hardcoded throughout repo. Find and replace is your friend :)
              output: "_site",
          },
      };
  };
  