daemon-sec-lotl

DÆMONBins: GTFOBins × LOLBAS × WADComs × LOOBins in one filterable catalog
git clone https://git.daemon-sec.xyz/daemon-sec-lotl.git
Log | Files | Refs | Submodules | README | LICENSE

astro.config.mjs (3085B)


      1 // @ts-check
      2 import { defineConfig } from 'astro/config';
      3 import mdx from '@astrojs/mdx';
      4 import sitemap from '@astrojs/sitemap';
      5 
      6 /**
      7  * DÆMONBins deploys to Vercel at the domain root — no base path — so every
      8  * internal link resolves from `/`. `src/lib/url.ts`'s `url()` helper stays the
      9  * single chokepoint for internal links (with an empty base it is a passthrough
     10  * that just guarantees a leading slash), which keeps the components portable if
     11  * the site is ever moved back under a sub-path.
     12  *
     13  * `site` is used for the sitemap, canonical URLs and Open Graph tags. Set it to
     14  * the site's real production origin — the Vercel custom domain (or the default
     15  * `*.vercel.app` URL) once known.
     16  */
     17 const SITE = 'https://lotl.daemon-sec.xyz';
     18 
     19 /**
     20  * The drop cap, matching the cheatsheet/main site: the opening letter of a
     21  * prose block floated three lines deep in EB Garamond, split into a real
     22  * `<span class="cap">` so it stays selectable and searchable.
     23  */
     24 const CAP_MIN_CHARS = 80;
     25 function rehypeDropCap() {
     26   /** @param {any} node hast node */
     27   function textLength(node) {
     28     if (node.type === 'text') return node.value.trim().length;
     29     if (!node.children) return 0;
     30     return node.children.reduce((/** @type {number} */ n, /** @type {any} */ c) => n + textLength(c), 0);
     31   }
     32   /**
     33    * @param {any} node hast node
     34    * @returns {any} the first non-blank hast text node, or null
     35    */
     36   function firstText(node) {
     37     if (!node.children) return null;
     38     for (const child of node.children) {
     39       if (child.type === 'text' && child.value.trim()) return child;
     40       if (child.type === 'element') {
     41         /** @type {any} */
     42         const found = firstText(child);
     43         if (found) return found;
     44       }
     45     }
     46     return null;
     47   }
     48   return (/** @type {any} */ tree) => {
     49     const paragraph = tree.children.find(
     50       (/** @type {any} */ n) => n.type === 'element' && n.tagName === 'p' && textLength(n) >= CAP_MIN_CHARS,
     51     );
     52     if (!paragraph) return;
     53     const lead = firstText(paragraph);
     54     if (!lead) return;
     55     const trimmed = lead.value.trimStart();
     56     const letter = trimmed[0];
     57     if (!letter || !/[A-Za-z0-9]/.test(letter)) return;
     58     lead.value = trimmed.slice(1);
     59     paragraph.children.unshift({
     60       type: 'element',
     61       tagName: 'span',
     62       properties: { className: ['cap'] },
     63       children: [{ type: 'text', value: letter }],
     64     });
     65   };
     66 }
     67 
     68 export default defineConfig({
     69   site: SITE,
     70   // 'never' + the default directory output: URLs (sitemap, Astro.url, canonical)
     71   // carry no trailing slash — matching vercel.json's `trailingSlash: false` so
     72   // no canonical ever points at a 308 — while files still land as
     73   // …/index.html, which keeps Pagefind's result hrefs clean.
     74   trailingSlash: 'never',
     75   integrations: [mdx(), sitemap()],
     76   markdown: {
     77     rehypePlugins: [rehypeDropCap],
     78     shikiConfig: {
     79       // One theme in both modes — code blocks are dark plates everywhere, so
     80       // there is no light listing to flip to (see prose.css).
     81       theme: 'rose-pine-moon',
     82       wrap: false,
     83     },
     84   },
     85 });