From 0b9394785349bd01f5807a8a8af18dd973a644c2 Mon Sep 17 00:00:00 2001 From: julioest Date: Wed, 24 Jun 2026 16:23:01 -0400 Subject: [PATCH 01/19] feat: add browser-sync dev server for 2.0 preview Preview boostlook-v3.css against real Boost docs of every type with CSS hot-reload. Serves the already-built website-v2-docs site and intercepts /_/css/boostlook.css (at any depth) via middleware to inject the working bundle from memory; build/ is never mutated. Watches src/css/** and rebuilds (debounced, non-overlapping) on save. Renders the charconv specimen.adoc standalone for AsciiDoctor coverage. Adds a landing page (full-width grouped sample grid, git-sourced changelog dropdown, light/dark toggle) and docs/dev-server.md. --- .gitignore | 2 + dev-server.js | 512 +++++++++++++ docs/dev-server.md | 153 ++++ package-lock.json | 1788 ++++++++++++++++++++++++++++++++++++++++++++ package.json | 13 + src/css/README.md | 31 + 6 files changed, 2499 insertions(+) create mode 100644 dev-server.js create mode 100644 docs/dev-server.md create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.gitignore b/.gitignore index 84a7f19b..62ffbc75 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ doc/html .DS_Store +node_modules +.dev diff --git a/dev-server.js b/dev-server.js new file mode 100644 index 00000000..5afda65e --- /dev/null +++ b/dev-server.js @@ -0,0 +1,512 @@ +/* + * Local dev server for boostlook-v3.css. + * + * Serves the already-built website-v2-docs multi-doc site (Antora site guides + + * library docs) plus a standalone AsciiDoctor specimen (charconv), all styled + * with your *working* boostlook-v3.css. Edits to src/css/** rebuild the bundle + * and hot-inject the CSS into every open doc tab without a full page reload. + * + * The built site links a single stylesheet at /_/css/boostlook.css. Instead of + * overwriting that file inside build/, we intercept that route with middleware + * and serve our freshly-built boostlook-v3.css. build/ is never mutated. + * + * Usage: npm run dev + * Env: BOOSTLOOK_DOCS_BUILD override path to website-v2-docs/build + * PORT override port (default 3000) + */ + +const fs = require('fs') +const path = require('path') +const { execFileSync } = require('child_process') +const browserSync = require('browser-sync') + +const ROOT = __dirname +const V3_CSS = path.join(ROOT, 'boostlook-v3.css') +const SRC_GLOB = path.join(ROOT, 'src/css/**/*.css') +const DEV_DIR = path.join(ROOT, '.dev') // generated, gitignored +const SPECIMEN_OUT = path.join(DEV_DIR, 'specimen', 'index.html') +const LANDING_OUT = path.join(DEV_DIR, 'preview.html') + +const BUILD = process.env.BOOSTLOOK_DOCS_BUILD || + path.resolve(ROOT, '../website-v2-docs/build') +const PORT = parseInt(process.env.PORT || '3000', 10) + +function fail (msg) { + console.error(`\n[boostlook-dev] ${msg}\n`) + process.exit(1) +} + +// --- prerequisites --------------------------------------------------------- +if (!fs.existsSync(BUILD)) { + fail( + `Built docs not found at:\n ${BUILD}\n\n` + + `Build them once in the website-v2-docs repo (e.g. \`./dev.sh\`), or point\n` + + `BOOSTLOOK_DOCS_BUILD at an existing build/ directory.` + ) +} + +// --- css build ------------------------------------------------------------- +// The middleware serves from this buffer, refreshed only after a *complete* +// build, so a request can never observe a half-written bundle. +let cssBuffer = Buffer.alloc(0) + +function buildCss () { + execFileSync('sh', ['build-css.sh'], { cwd: ROOT, stdio: 'inherit' }) + cssBuffer = fs.readFileSync(V3_CSS) +} + +// --- standalone AsciiDoctor specimen (charconv) ---------------------------- +// Rendered with boostlook.rb (wraps in .boostlook) and linked to our +// CSS route. Skipped gracefully if the asciidoctor CLI is unavailable. +function renderSpecimen () { + fs.mkdirSync(path.dirname(SPECIMEN_OUT), { recursive: true }) + try { + execFileSync('asciidoctor', [ + '-r', path.join(ROOT, 'boostlook.rb'), + '-a', 'linkcss', + '-a', 'copycss!', // we serve the CSS via middleware; don't copy it out + '-a', 'stylesdir=/_/css', + '-a', 'stylesheet=boostlook.css', + '-a', 'source-highlighter=highlightjs', // client-side; no rouge gem needed + '-a', 'docinfodir=' + path.join(ROOT, 'doc'), + path.join(ROOT, 'doc/specimen.adoc'), + '-o', SPECIMEN_OUT, + ], { cwd: ROOT, stdio: 'inherit' }) + return true + } catch (e) { + console.warn('[boostlook-dev] asciidoctor not available — skipping specimen sample.') + return false + } +} + +// --- helpers --------------------------------------------------------------- +function esc (s) { + return String(s).replace(/[&<>"]/g, (c) => + ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c])) +} + +// Recent commits to boostlook, shown as a live "what changed" panel — always +// current, unlike a hand-maintained changelog. Empty array if git is absent. +function getChangelog () { + try { + const raw = execFileSync('git', + ['log', '-14', '--no-merges', '--pretty=format:%h\x1f%s\x1f%cr'], + { cwd: ROOT }).toString() + return raw.split('\n').filter(Boolean).map((line) => { + const [hash, subject, when] = line.split('\x1f') + const m = subject.match(/^([a-z]+)(?:\([^)]*\))?!?:\s*(.*)$/i) + return { + hash, + type: m ? m[1].toLowerCase() : 'misc', + text: m ? m[2] : subject, + when, + } + }) + } catch (e) { + return [] + } +} + +// --- landing page ---------------------------------------------------------- +function writeLanding (hasSpecimen) { + const has = (route) => + fs.existsSync(path.join(BUILD, route.replace(/^\/|\/$/g, ''), 'index.html')) + + const groups = [ + { + label: 'Site Documentation', + hint: 'Versioned guides for the Boost website', + items: [ + { name: 'User Guide', route: '/user-guide/', engine: 'Antora' }, + { name: 'Contributor Guide', route: '/contributor-guide/', engine: 'Antora' }, + { name: 'Formal Reviews', route: '/formal-reviews/', engine: 'Antora' }, + ], + }, + { + label: 'Library Documentation', + hint: 'Per-library reference — one of each rendering engine', + items: [ + { name: 'Boost.Capy', route: '/lib/doc/capy/', engine: 'Antora' }, + { name: 'Boost.MSM', route: '/lib/doc/msm/', engine: 'Antora' }, + { name: 'Boost.URL', route: '/lib/doc/url/', engine: 'Antora' }, + { name: 'Boost.CharConv', route: '/specimen/', engine: 'AsciiDoctor', note: 'specimen.adoc' }, + ], + }, + ] + + for (const g of groups) { + g.items = g.items.filter((it) => + it.route === '/specimen/' ? hasSpecimen : has(it.route)) + } + const visible = groups.filter((g) => g.items.length) + const total = visible.reduce((n, g) => n + g.items.length, 0) + const engines = new Set() + visible.forEach((g) => g.items.forEach((it) => engines.add(it.engine))) + + const arrow = + '' + + '' + + const sections = visible.map((g) => { + const cards = g.items.map((it) => { + const cls = it.engine.toLowerCase() + const note = it.note ? `${esc(it.note)}` : '' + return ` + + ${esc(it.name)} + ${esc(it.engine)} + + + ${esc(it.route)}${note} + + ${arrow} + ` + }).join('\n') + return `
+
+

${esc(g.label)}

+

${esc(g.hint)}

+
+
+${cards} +
+
` + }).join('\n') + + const log = getChangelog() + const chev = + '' + + '' + const changelogMenu = log.length ? `
+ + +
` : '' + + const html = ` + + + + +Boostlook 2.0 — Local Preview + + + + +
+
+
+ +
+

Boostlook

+

Local preview · ${total} samples · ${[...engines].join(' + ')}

+
+
+
+ ${changelogMenu} + +
+
+ +

Preview Boostlook 2.0 across every doc type

+

One sample of each Boost documentation engine, styled with your working boostlook-v3.css. Edit any src/css/*.css module — the styles hot-reload in place, no refresh.

+

Served from ${esc(BUILD)} · CSS injected at /_/css/boostlook.css

+ +
+${sections} +
+ + +
+ + + + +` + fs.mkdirSync(DEV_DIR, { recursive: true }) + fs.writeFileSync(LANDING_OUT, html) +} + +// --- boot ------------------------------------------------------------------ +console.log('[boostlook-dev] building CSS…') +buildCss() +const hasSpecimen = renderSpecimen() +writeLanding(hasSpecimen) + +const bs = browserSync.create() + +bs.init({ + port: PORT, + ui: false, + notify: false, + open: false, + startPath: '/preview.html', + server: { + // .dev first so /preview.html and /specimen/ resolve to our generated files; + // everything else (/_/, /user-guide/, /lib/, …) falls through to the build. + baseDir: [DEV_DIR, BUILD], + }, + // Intercept the stylesheet every page links and serve our working copy. + // The site guides link /_/css/boostlook.css, but the Antora library build + // emits its own UI assets under /lib/doc/_/, so library pages link + // /lib/doc/_/css/boostlook.css. Match the file at *any* depth. + middleware: [ + (req, res, next) => { + const p = (req.url || '').split('?')[0] + if (p.endsWith('/_/css/boostlook.css')) { + res.setHeader('Content-Type', 'text/css; charset=utf-8') + res.setHeader('Cache-Control', 'no-store') + return res.end(cssBuffer) + } + next() + }, + ], +}, (err, inst) => { + if (err) return fail('browser-sync failed to start: ' + err) + const url = inst.options.getIn(['urls', 'local']) + console.log(`\n[boostlook-dev] preview ready → ${url}\n`) +}) + +// Watch the modular CSS source. On change: rebuild the bundle, then inject. +// Debounced + guarded so rapid saves coalesce and builds never overlap (which +// would race on boostlook-v3.css). +let building = false +let pending = false +let debounce = null + +function rebuild () { + if (building) { pending = true; return } + building = true + try { + buildCss() + bs.reload('boostlook.css') // CSS-only injection, no full reload + } catch (e) { + console.error('[boostlook-dev] build-css.sh failed:', e.message) + } finally { + building = false + if (pending) { pending = false; rebuild() } + } +} + +bs.watch(SRC_GLOB).on('change', (file) => { + console.log(`[boostlook-dev] changed: ${path.relative(ROOT, file)} → rebuilding`) + clearTimeout(debounce) + debounce = setTimeout(rebuild, 120) +}) diff --git a/docs/dev-server.md b/docs/dev-server.md new file mode 100644 index 00000000..d422fbd6 --- /dev/null +++ b/docs/dev-server.md @@ -0,0 +1,153 @@ +# Boostlook v3 — Local Dev Workflow + +How to develop `boostlook-v3.css` and preview it live against **real Boost docs of +every type** — Antora site guides, Antora library docs, and standalone AsciiDoctor — +with CSS hot-reload. + +--- + +## TL;DR + +```sh +# from the boostlook repo root +npm install # once — installs browser-sync +npm run dev # → http://localhost:3000/preview.html +``` + +Then edit any `src/css/*.css` module. The bundle rebuilds and the new styles +**hot-inject** into every open doc tab — no page refresh. + +--- + +## The CSS you edit + +`boostlook-v3.css` is **generated** — never edit it directly. The source of truth is +the modular files in `src/css/`, concatenated in numeric order by `build-css.sh`. + +| File | Concern | +|------|---------| +| `00-header.css` | License, overview, conventions | +| `01-variables.css` | Root custom properties (spacing, type, icons) | +| `02-themes.css` | Light/dark theme variable mappings | +| `03-fonts.css` | `@font-face` declarations | +| `04-reset.css` | CSS reset | +| `05-global-typography.css` | Base container, headings, anchors | +| `06-global-links.css` | Paragraphs, links, footnotes | +| `07-global-code.css` | Code blocks, inline code, syntax highlighting | +| `08-global-components.css` | Quotes, pagination, admonitions, lists | +| `09-global-tables-images.css` | Tables, images | +| `10-scrollbars.css` | Scrollbars (Firefox + WebKit) | +| `11-template-layout.css` | Template scrolling, iframe, TOC common | +| `12-asciidoctor.css` | AsciiDoctor-specific styles | +| `13-antora.css` | Antora nav, toolbar, breadcrumbs, tabs, search | +| `14-quickbook.css` | Quickbook legacy wrapper | +| `15-readme.css` | Library README styles | +| `16-responsive-toc.css` | AsciiDoctor responsive TOC | + +The edit loop: + +1. Edit the right module in `src/css/`. +2. `sh build-css.sh` regenerates `boostlook-v3.css` (the dev server does this for you on save). +3. Commit **both** the `src/css/` change and the regenerated `boostlook-v3.css`. + +> Note: `boostlook-v3.css` (v3) and `boostlook.css` (v1) are separate frameworks. +> v3 work happens entirely in `src/css/`. + +--- + +## The dev server + +```sh +npm run dev +``` + +Serves a landing page at **http://localhost:3000/preview.html** linking one sample of +each documentation type, all styled with your working `boostlook-v3.css`: + +| Sample | Engine | Route | +|--------|--------|-------| +| User Guide | Antora (site) | `/user-guide/` | +| Contributor Guide | Antora (site) | `/contributor-guide/` | +| Formal Reviews | Antora (site) | `/formal-reviews/` | +| Capy | Antora (library) | `/lib/doc/capy/` | +| MSM | Antora (library) | `/lib/doc/msm/` | +| URL | Antora (library) | `/lib/doc/url/` | +| CharConv (`specimen.adoc`) | AsciiDoctor | `/specimen/` | + +Edit any `src/css/*.css` → save → the styles update live across all open tabs. + +### How it works + +See `dev-server.js`. In short: + +- **Serves pre-built docs.** It serves the already-rendered Antora output from + `../website-v2-docs/build/` rather than rebuilding — fast startup, no Antora or `b2` + toolchain required at dev time. +- **Injects your CSS via middleware.** Every built page links a single stylesheet, + `/_/css/boostlook.css`. A middleware route intercepts that path and serves your + working `boostlook-v3.css` **from an in-memory buffer**. Consequences: + - `build/` is never modified. + - A request can never observe a half-written bundle (the buffer only updates after a + *complete* build). +- **Renders the AsciiDoctor specimen on boot** via `boostlook.rb` (which wraps output in + `.boostlook`, the scope v3 selectors live under). Code is highlighted client-side with + highlight.js, so the `rouge` gem is not required. Skipped gracefully if the + `asciidoctor` CLI is missing. +- **Watches `src/css/**`.** On save it runs `build-css.sh` (debounced, non-overlapping so + concurrent builds can't race the output file) and triggers a CSS-only injection. + +### Why both this and Netlify? + +- **Local dev server** = the *inner loop*: instant CSS feedback while you work. +- **[boostlook-v3.netlify.app](https://boostlook-v3.netlify.app/)** = the *outer loop*: + a shareable, reviewable surface for stakeholders. Slower (push → build → deploy), so + not for tight iteration. + +They're complementary. + +--- + +## Configuration + +| Env var | Default | Purpose | +|---------|---------|---------| +| `BOOSTLOOK_DOCS_BUILD` | `../website-v2-docs/build` | Path to the built Antora docs to serve | +| `PORT` | `3000` | Dev server port | + +--- + +## Prerequisites + +- **Node + npm** — for the dev server (browser-sync). +- **`asciidoctor` CLI** — for the standalone specimen only; optional. +- **A built `website-v2-docs/build/` directory** — the rendered Antora docs to serve. + +### Refreshing doc content + +The served docs are a static snapshot — fine for CSS work, but **content** can be stale. +To regenerate with current content, build in the `website-v2-docs` repo: + +```sh +cd ../website-v2-docs +./dev.sh # builds antora-ui + site guides + library docs into ./build +``` + +> macOS: the build scripts expect GNU `findutils`. +> `brew install findutils` and add it to your `PATH`. + +To preview v3 *during* that full build (instead of v1), the antora-ui pipeline downloads +boostlook CSS from GitHub; pass `--skip-boostlook` and place your working CSS at +`website-v2-docs/antora-ui/src/css/boostlook.css` to use it locally. For day-to-day CSS +work this isn't needed — the dev server's middleware already swaps in your live copy. + +--- + +## Troubleshooting + +| Symptom | Fix | +|---------|-----| +| `Built docs not found at …` on startup | Build `website-v2-docs/build/` (see above) or set `BOOSTLOOK_DOCS_BUILD`. | +| Specimen page missing / `asciidoctor not available` | Install the `asciidoctor` CLI (`gem install asciidoctor`). | +| Styles don't update on save | Confirm you edited a file under `src/css/` (not `boostlook-v3.css` directly); check the terminal for a `build-css.sh failed` message. | +| Doc content looks outdated | Expected — refresh content via `./dev.sh` in `website-v2-docs`. | +| Code blocks unstyled in the specimen | highlight.js loads from CDN; needs network access. | diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..7d927dd8 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1788 @@ +{ + "name": "boostlook-dev", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "boostlook-dev", + "version": "0.0.0", + "devDependencies": { + "browser-sync": "^3.0.3" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.0.tgz", + "integrity": "sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~8.3.0" + } + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/async-each-series": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/async-each-series/-/async-each-series-0.1.1.tgz", + "integrity": "sha512-p4jj6Fws4Iy2m0iCmI2am2ZNZCgbdgE+P8F/8csmn2vx7ixXrO2zGcuNsD46X5uZSVecmkEy/M06X2vG8KD6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-sync": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/browser-sync/-/browser-sync-3.0.4.tgz", + "integrity": "sha512-mcYOIy4BW6sWSEnTSBjQwWsnbx2btZX78ajTTjdNfyC/EqQVcIe0nQR6894RNAMtvlfAnLaH9L2ka97zpvgenA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "browser-sync-client": "^3.0.4", + "browser-sync-ui": "^3.0.4", + "bs-recipes": "1.3.4", + "chalk": "4.1.2", + "chokidar": "^3.5.1", + "connect": "3.6.6", + "connect-history-api-fallback": "^1", + "dev-ip": "^1.0.1", + "easy-extender": "^2.3.4", + "eazy-logger": "^4.1.0", + "etag": "^1.8.1", + "fresh": "^0.5.2", + "fs-extra": "3.0.1", + "http-proxy": "^1.18.1", + "immutable": "^3", + "micromatch": "^4.0.8", + "opn": "5.3.0", + "portscanner": "2.2.0", + "raw-body": "^2.3.2", + "resp-modifier": "6.0.2", + "rx": "4.1.0", + "send": "^0.19.0", + "serve-index": "^1.9.1", + "serve-static": "^1.16.2", + "server-destroy": "1.0.1", + "socket.io": "^4.4.1", + "ua-parser-js": "^1.0.33", + "yargs": "^17.3.1" + }, + "bin": { + "browser-sync": "dist/bin.js" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/browser-sync-client": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/browser-sync-client/-/browser-sync-client-3.0.4.tgz", + "integrity": "sha512-+ew5ubXzGRKVjquBL3u6najS40TG7GxCdyBll0qSRc/n+JRV9gb/yDdRL1IAgRHqjnJTdqeBKKIQabjvjRSYRQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "etag": "1.8.1", + "fresh": "0.5.2", + "mitt": "^1.1.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/browser-sync-ui": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/browser-sync-ui/-/browser-sync-ui-3.0.4.tgz", + "integrity": "sha512-5Po3YARCZ/8yQHFzvrSjn8+hBUF7ZWac39SHsy8Tls+7tE62iq6pYWxpVU6aOOMAGD21RwFQhQeqmJPf70kHEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async-each-series": "0.1.1", + "chalk": "4.1.2", + "connect-history-api-fallback": "^1", + "immutable": "^3", + "server-destroy": "1.0.1", + "socket.io-client": "^4.4.1", + "stream-throttle": "^0.1.3" + } + }, + "node_modules/bs-recipes": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/bs-recipes/-/bs-recipes-1.3.4.tgz", + "integrity": "sha512-BXvDkqhDNxXEjeGM8LFkSbR+jzmP/CYpCiVKYn+soB1dDldeU15EBNDkwVXndKuX35wnNUaPd0qSoQEAkmQtMw==", + "dev": true, + "license": "ISC" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "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/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/connect": { + "version": "3.6.6", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.6.6.tgz", + "integrity": "sha512-OO7axMmPpu/2XuX1+2Yrg0ddju31B6xLZMWkJ5rYBu4YRmRVlOjvlY6kw2FJKiAzyxGwnrDUAG4s1Pf0sbBMCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.0", + "parseurl": "~1.3.2", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect-history-api-fallback": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", + "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dev-ip": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dev-ip/-/dev-ip-1.0.1.tgz", + "integrity": "sha512-LmVkry/oDShEgSZPNgqCIp2/TlqtExeGmymru3uCELnfyjY11IzpAproLYs+1X88fXO6DBoYP3ul2Xo2yz2j6A==", + "dev": true, + "bin": { + "dev-ip": "lib/dev-ip.js" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/easy-extender": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/easy-extender/-/easy-extender-2.3.4.tgz", + "integrity": "sha512-8cAwm6md1YTiPpOvDULYJL4ZS6WfM5/cTeVVh4JsvyYZAoqlRVUpHL9Gr5Fy7HA6xcSZicUia3DeAgO3Us8E+Q==", + "dev": true, + "dependencies": { + "lodash": "^4.17.10" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/eazy-logger": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eazy-logger/-/eazy-logger-4.1.0.tgz", + "integrity": "sha512-+mn7lRm+Zf1UT/YaH8WXtpU6PIV2iOjzP6jgKoiaq/VNrjYKp+OHZGe2znaLgDeFkw8cL9ffuaUm+nNnzcYyGw==", + "dev": true, + "dependencies": { + "chalk": "4.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/engine.io": { + "version": "6.6.9", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.9.tgz", + "integrity": "sha512-clKkw4C7nJ22mGgoVcCg6V/W/TxdNyIOTr89k2ONZu81qqkddPFDF0LXcbAwhzPD8DjkiRCjzuiO6Y+fkpD4vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "@types/ws": "^8.5.12", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.4.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.21.0" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-client": { + "version": "6.6.6", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.6.tgz", + "integrity": "sha512-iY6QdftLQ9pyiPoX082bpf/u1UewnOaJrtJIF9T0++QB34lZrj0uP+Q/bj8AlUsAxqhnkTV2BS8SBZSxOmoV5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.21.0", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-client/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", + "integrity": "sha512-ejnvM9ZXYzp6PUPUyQBMBf0Co5VX2gr5H2VQe2Ui2jWXNlxv+PYZo8wpAymJNJdLsG1R4p+M4aynF8KuoUEwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.3.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz", + "integrity": "sha512-V3Z3WZWVUYd8hoCL5xfXJCaHWYzmtwW5XWYSlLgERi8PWd8bx1kUHUk8L1BT57e49oKnDDD180mjfrHc1yA9rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^3.0.0", + "universalify": "^0.1.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "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==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/immutable": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.3.tgz", + "integrity": "sha512-AUY/VyX0E5XlibOmWt10uabJzam1zlYjwiEgQSDc5+UIkFNaF9WM0JxXKaNMGf+F/ffUF+7kRKXM9A7C0xXqMg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-like": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/is-number-like/-/is-number-like-1.0.8.tgz", + "integrity": "sha512-6rZi3ezCyFcn5L71ywzz2bS5b2Igl1En3eTlZlvKjpz1n3IZLAYMbKYAIQgFmEu0GENg92ziU/faEOA/aixjbA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lodash.isfinite": "^3.3.2" + } + }, + "node_modules/is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/jsonfile": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", + "integrity": "sha512-oBko6ZHlubVB5mRFkur5vgYR1UyqX+S6Y/oCfLhqNdcc2fYFlDpIoNc7AfKS1KOGcnNAkvsr0grLck9ANM815w==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==", + "dev": true + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isfinite": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz", + "integrity": "sha512-7FGG40uhC8Mm633uKW1r58aElFlBlxCrg9JfSi3P6aYiWmfiWF0PgMd86ZUsxE5GwWPdHoS2+48bwTh2VPkIQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mitt": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-1.2.0.tgz", + "integrity": "sha512-r6lj77KlwqLhIUku9UWYes7KJtsczvolZkzp8hbaDPPaE24OmWl5s539Mytlj22siEQKosZ26qCBgda2PKwoJw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/opn": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz", + "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/portscanner": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.2.0.tgz", + "integrity": "sha512-IFroCz/59Lqa2uBvzK3bKDbDDIEaAY8XJ1jFxcLWTqosrsc32//P4VuSB2vZXoHiHqOmx8B5L5hnKOxL/7FlPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "^2.6.0", + "is-number-like": "^1.0.3" + }, + "engines": { + "node": ">=0.4", + "npm": ">=1.0.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/resp-modifier": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/resp-modifier/-/resp-modifier-6.0.2.tgz", + "integrity": "sha512-U1+0kWC/+4ncRFYqQWTx/3qkfE6a4B/h3XXgmXypfa0SPZ3t7cbbaFk297PjQS/yov24R18h6OZe6iZwj3NSLw==", + "dev": true, + "dependencies": { + "debug": "^2.2.0", + "minimatch": "^3.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/rx": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", + "integrity": "sha512-CiaiuN6gapkdl+cZUr67W6I8jquN4lkak3vtIsIWCl4XIPP8ffsoyN6/+PuGXnQy8Cu8W2y9Xxh31Rq4M6wUug==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/send/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serve-index": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.2.tgz", + "integrity": "sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.8.0", + "mime-types": "~2.1.35", + "parseurl": "~1.3.3" + }, + "engines": { + "node": ">= 0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-static/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/server-destroy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz", + "integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/socket.io": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.3.tgz", + "integrity": "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.4.1", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.8", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.8.tgz", + "integrity": "sha512-6Oy52pbg+kvdCVvjcN+FnY7BvxZ7cIHNScbvztT/It5d0vbwoJoVZmF2gjJmnV0/4WlXRfG15zc45ySk9Ah8bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "~4.4.1", + "ws": "~8.21.0" + } + }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-adapter/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/socket.io-client": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz", + "integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-client/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/socket.io-parser": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz", + "integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha512-wuTCPGlJONk/a1kqZ4fQM2+908lC7fa7nPYpTC1EhnvqLX/IICbeP1OZGDtA374trpSq68YubKUMo8oRhN46yg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stream-throttle": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/stream-throttle/-/stream-throttle-0.1.3.tgz", + "integrity": "sha512-889+B9vN9dq7/vLbGyuHeZ6/ctf5sNuGWsDy89uNxkFTAgzy0eK7+w5fL3KLNRTkLle7EgZGvHUphZW0Q26MnQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "commander": "^2.2.0", + "limiter": "^1.0.5" + }, + "bin": { + "throttleproxy": "bin/throttleproxy.js" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/ua-parser-js": { + "version": "1.0.41", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.41.tgz", + "integrity": "sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "license": "MIT", + "bin": { + "ua-parser-js": "script/cli.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/undici-types": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "dev": true, + "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 + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..bd8783cc --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "boostlook-dev", + "version": "0.0.0", + "private": true, + "description": "Local dev server for previewing boostlook-v3.css across real Boost doc types with CSS hot-reload.", + "scripts": { + "build": "sh build-css.sh", + "dev": "node dev-server.js" + }, + "devDependencies": { + "browser-sync": "^3.0.3" + } +} diff --git a/src/css/README.md b/src/css/README.md index c67354b7..f0c1cb4d 100644 --- a/src/css/README.md +++ b/src/css/README.md @@ -38,6 +38,37 @@ sh build-css.sh This concatenates all modules (in numeric order) into `boostlook-v3.css` at the repo root. +## Local Preview (Dev Server) + +A browser-sync dev server previews `boostlook-v3.css` against **real Boost docs of +every type** — Antora site guides (User/Contributor/Formal Reviews), Antora library +docs (Capy, MSM, URL), and a standalone AsciiDoctor doc (the CharConv `specimen.adoc`). +Edit any `src/css/*.css` module and the styles hot-reload in every open tab without a +full page refresh. + +```sh +npm install # once — installs browser-sync +npm run dev # serves http://localhost:3000/preview.html +``` + +How it works (see `dev-server.js`): + +- It serves the **already-built** docs from `../website-v2-docs/build/` (rendered Antora + output) rather than rebuilding them — fast startup, no Antora/`b2` toolchain needed. +- Every built page links one stylesheet, `/_/css/boostlook.css`. A middleware route + intercepts that path and serves your **working** `boostlook-v3.css` from memory, so + `build/` is never modified and requests never see a half-written bundle. +- The AsciiDoctor specimen is rendered on boot via `boostlook.rb` (which wraps output in + `.boostlook`); requires the `asciidoctor` CLI. Skipped gracefully if absent. +- Saving a `src/css/**` file runs `build-css.sh` (debounced, non-overlapping) and injects + the new CSS. + +**Prerequisites:** Node + npm, the `asciidoctor` CLI (for the specimen only), and a built +`website-v2-docs/build/` directory. To regenerate that built site with fresh doc content, +run `./dev.sh` in the `website-v2-docs` repo (note: its build scripts expect GNU +`findutils` on macOS — `brew install findutils`). Override the build location with the +`BOOSTLOOK_DOCS_BUILD` env var, or the port with `PORT`. + ## CSS Tooling Roadmap > **Status:** Decision deferred — documenting options for future reference. From 2767045d09315adae67e5c2f25981b57c6798b26 Mon Sep 17 00:00:00 2001 From: julioest Date: Wed, 24 Jun 2026 21:13:08 -0400 Subject: [PATCH 02/19] feat: collapsible AsciiDoctor TOC nav-tree Make the standalone Asciidoctor TOC behave like the Antora nav-tree: caret toggles, collapsed by default, current path expanded, open sections persisted, and scroll-spy active-link highlighting. - 12-asciidoctor.css: caret/collapse styling reusing Antora's design (right-aligned dotted chevron that flips on expand); pointer cursor on collapsible rows only, the link unaffected. - boostlook-v3.rb: new postprocessor that wraps output in .boostlook and injects the behavior, so docs need no per-document docinfo footer. - dev-server.js: render the specimen via boostlook-v3.rb. - boostlook-v3.css: rebuilt bundle. --- boostlook-v3.css | 57 ++++++++++++ boostlook-v3.rb | 176 +++++++++++++++++++++++++++++++++++++ dev-server.js | 2 +- src/css/12-asciidoctor.css | 57 ++++++++++++ 4 files changed, 291 insertions(+), 1 deletion(-) create mode 100644 boostlook-v3.rb diff --git a/boostlook-v3.css b/boostlook-v3.css index 00479a85..e5c01841 100644 --- a/boostlook-v3.css +++ b/boostlook-v3.css @@ -4192,6 +4192,63 @@ html.dark .boostlook #toctitle .theme-toggle:hover { html.dark .boostlook #toctitle .theme-toggle .theme-icon-light { display: inline; } html.dark .boostlook #toctitle .theme-toggle .theme-icon-dark { display: none; } +/* Collapsible TOC tree — matches the Antora nav-tree caret (see 13-antora.css): + parent items become flex rows with a right-aligned dotted chevron that flips + from down to up on expand; branches collapse by default (.is-active expands + them). A consumer script adds .nav-item + .nav-item-toggle, toggles .is-active. */ +.boostlook #toc ul[class^="sectlevel"] { + list-style: none; +} +.boostlook #toc .nav-item { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + cursor: pointer; /* the row collapses on click */ +} +/* the link keeps normal link behavior; clicking it navigates, not collapses */ +.boostlook #toc .nav-item > a { + order: 0; + cursor: pointer; +} +/* nested list wraps to full width below the row; reset cursor so leaf/child + rows aren't falsely marked as collapsible (nested .nav-item rows re-apply it) */ +.boostlook #toc .nav-item > ul { + flex-basis: 100%; + order: 2; + cursor: default; +} +.boostlook #toc .nav-item-toggle { + order: 1; + background: none; + border: none; + outline: none; + width: 1.25rem; + height: 1.25rem; + padding: 0; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; +} +.boostlook #toc .nav-item-toggle::after { + content: ""; + display: block; + width: 1rem; + height: 1rem; + background-color: var(--text-main-text-body-secondary, #494d50); + -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M13 16h-2v-2h2v2Zm-2-2H9v-2h2v2Zm4 0h-2v-2h2v2Zm-6-2H7v-2h2v2Zm8 0h-2v-2h2v2ZM7 10H5V8h2v2Zm12 0h-2V8h2v2Z'/%3E%3C/svg%3E") no-repeat center / contain; + mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M13 16h-2v-2h2v2Zm-2-2H9v-2h2v2Zm4 0h-2v-2h2v2Zm-6-2H7v-2h2v2Zm8 0h-2v-2h2v2ZM7 10H5V8h2v2Zm12 0h-2V8h2v2Z'/%3E%3C/svg%3E") no-repeat center / contain; +} +.boostlook #toc .nav-item.is-active > .nav-item-toggle::after { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M13 10h-2V8h2v2ZM11 12H9v-2h2v2Zm4 0h-2v-2h2v2ZM9 14H7v-2h2v2Zm8 0h-2v-2h2v2ZM7 16H5v-2h2v2Zm12 0h-2v-2h2v2Z'/%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M13 10h-2V8h2v2ZM11 12H9v-2h2v2Zm4 0h-2v-2h2v2ZM9 14H7v-2h2v2Zm8 0h-2v-2h2v2ZM7 16H5v-2h2v2Zm12 0h-2v-2h2v2Z'/%3E%3C/svg%3E"); +} +/* collapsed by default; expanded when active (Antora parity) */ +.boostlook #toc .nav-item:not(.is-active) > ul { + display: none; +} + /*----------------- Styles specific to AsciiDoctor content end -----------------*/ /*----------------- Styles specific to Antora Templates start -----------------*/ diff --git a/boostlook-v3.rb b/boostlook-v3.rb new file mode 100644 index 00000000..01218bf7 --- /dev/null +++ b/boostlook-v3.rb @@ -0,0 +1,176 @@ +# boostlook-v3.rb — Asciidoctor postprocessor for Boostlook 2.0 (boostlook-v3.css) +# +# Wraps rendered output in
— the scope every v3 selector +# lives under — and injects the client-side behavior the CSS expects, so docs get +# it automatically without a per-document docinfo footer: +# * keeps the left TOC visible/pinned +# * turns the Asciidoctor :toc: into a collapsible nav-tree matching the Antora +# nav: caret toggles, collapsed by default, current path expanded, +# open sections persisted, and scroll-spy active-link highlighting. +# +# Caret/collapse styling lives in boostlook-v3.css (src/css/12-asciidoctor.css). +# +# Usage: asciidoctor -r ./boostlook-v3.rb ... doc.adoc + +Asciidoctor::Extensions.register do + postprocessor do + process do |doc, output| + # Wrap the body content in the .boostlook scope (footer kept outside). + output = output.sub(/(]*>)/, '\1
') + output = output.sub('', '
') + output = output.sub(/(]*id="footer"[^>]*>)/m, '
\1') + + scripts = <<~'HTML' + + + HTML + + output.sub('', "#{scripts}") + end + end +end diff --git a/dev-server.js b/dev-server.js index 5afda65e..ef537762 100644 --- a/dev-server.js +++ b/dev-server.js @@ -62,7 +62,7 @@ function renderSpecimen () { fs.mkdirSync(path.dirname(SPECIMEN_OUT), { recursive: true }) try { execFileSync('asciidoctor', [ - '-r', path.join(ROOT, 'boostlook.rb'), + '-r', path.join(ROOT, 'boostlook-v3.rb'), '-a', 'linkcss', '-a', 'copycss!', // we serve the CSS via middleware; don't copy it out '-a', 'stylesdir=/_/css', diff --git a/src/css/12-asciidoctor.css b/src/css/12-asciidoctor.css index 091e28e5..f66b28ee 100644 --- a/src/css/12-asciidoctor.css +++ b/src/css/12-asciidoctor.css @@ -85,4 +85,61 @@ html.dark .boostlook #toctitle .theme-toggle:hover { html.dark .boostlook #toctitle .theme-toggle .theme-icon-light { display: inline; } html.dark .boostlook #toctitle .theme-toggle .theme-icon-dark { display: none; } +/* Collapsible TOC tree — matches the Antora nav-tree caret (see 13-antora.css): + parent items become flex rows with a right-aligned dotted chevron that flips + from down to up on expand; branches collapse by default (.is-active expands + them). A consumer script adds .nav-item + .nav-item-toggle, toggles .is-active. */ +.boostlook #toc ul[class^="sectlevel"] { + list-style: none; +} +.boostlook #toc .nav-item { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + cursor: pointer; /* the row collapses on click */ +} +/* the link keeps normal link behavior; clicking it navigates, not collapses */ +.boostlook #toc .nav-item > a { + order: 0; + cursor: pointer; +} +/* nested list wraps to full width below the row; reset cursor so leaf/child + rows aren't falsely marked as collapsible (nested .nav-item rows re-apply it) */ +.boostlook #toc .nav-item > ul { + flex-basis: 100%; + order: 2; + cursor: default; +} +.boostlook #toc .nav-item-toggle { + order: 1; + background: none; + border: none; + outline: none; + width: 1.25rem; + height: 1.25rem; + padding: 0; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; +} +.boostlook #toc .nav-item-toggle::after { + content: ""; + display: block; + width: 1rem; + height: 1rem; + background-color: var(--text-main-text-body-secondary, #494d50); + -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M13 16h-2v-2h2v2Zm-2-2H9v-2h2v2Zm4 0h-2v-2h2v2Zm-6-2H7v-2h2v2Zm8 0h-2v-2h2v2ZM7 10H5V8h2v2Zm12 0h-2V8h2v2Z'/%3E%3C/svg%3E") no-repeat center / contain; + mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M13 16h-2v-2h2v2Zm-2-2H9v-2h2v2Zm4 0h-2v-2h2v2Zm-6-2H7v-2h2v2Zm8 0h-2v-2h2v2ZM7 10H5V8h2v2Zm12 0h-2V8h2v2Z'/%3E%3C/svg%3E") no-repeat center / contain; +} +.boostlook #toc .nav-item.is-active > .nav-item-toggle::after { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M13 10h-2V8h2v2ZM11 12H9v-2h2v2Zm4 0h-2v-2h2v2ZM9 14H7v-2h2v2Zm8 0h-2v-2h2v2ZM7 16H5v-2h2v2Zm12 0h-2v-2h2v2Z'/%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M13 10h-2V8h2v2ZM11 12H9v-2h2v2Zm4 0h-2v-2h2v2ZM9 14H7v-2h2v2Zm8 0h-2v-2h2v2ZM7 16H5v-2h2v2Zm12 0h-2v-2h2v2Z'/%3E%3C/svg%3E"); +} +/* collapsed by default; expanded when active (Antora parity) */ +.boostlook #toc .nav-item:not(.is-active) > ul { + display: none; +} + /*----------------- Styles specific to AsciiDoctor content end -----------------*/ From e56eecc667ec8609a227b3842be7830abe452697 Mon Sep 17 00:00:00 2001 From: julioest Date: Wed, 24 Jun 2026 21:13:08 -0400 Subject: [PATCH 03/19] feat: add Netlify preview build script build-preview.sh assembles the multi-doc preview folder and optionally deploys it: build CSS -> inject into antora-ui + rebuild UI bundle -> rebuild site guides -> bake 2.0 CSS into reused library docs -> render the charconv specimen -> generate the landing. --draft / --prod deploy to the boostlook-v3 Netlify site via netlify-cli. scripts/gen-landing.js generates the static preview index.html. --- build-preview.sh | 107 +++++++++++++++++++++ scripts/gen-landing.js | 209 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 316 insertions(+) create mode 100755 build-preview.sh create mode 100644 scripts/gen-landing.js diff --git a/build-preview.sh b/build-preview.sh new file mode 100755 index 00000000..20f07426 --- /dev/null +++ b/build-preview.sh @@ -0,0 +1,107 @@ +#!/bin/sh +# +# Build the Boostlook 2.0 multi-doc preview and (optionally) deploy to Netlify. +# +# Pipeline: +# 1. Build boostlook-v3.css from the src/css modules +# 2. Inject it into website-v2-docs/antora-ui and rebuild the UI bundle +# 3. Rebuild the site guides (local content) with the 2.0 UI bundle +# 4. Bake 2.0 CSS into the reused (prebuilt) library docs +# 5. Render the charconv AsciiDoctor specimen +# 6. Generate the preview landing (index.html) +# 7. Optionally deploy: --draft (preview URL) or --prod (publish) +# +# Usage: +# ./build-preview.sh # build only -> /build ready to upload +# ./build-preview.sh --draft # build + draft deploy (preview URL) +# ./build-preview.sh --prod # build + production deploy +# +# Env overrides: +# BOOSTLOOK_DOCS path to website-v2-docs (default: ../website-v2-docs) +# NETLIFY_SITE_ID Netlify site id (default: the boostlook-v3 site) +# +set -eu + +DEPLOY="" +case "${1:-}" in + --draft) DEPLOY=draft ;; + --prod) DEPLOY=prod ;; + "") ;; + *) echo "unknown argument: $1 (use --draft or --prod)"; exit 2 ;; +esac + +ROOT="$(cd "$(dirname "$0")" && pwd)" +DOCS_RAW="${BOOSTLOOK_DOCS:-$ROOT/../website-v2-docs}" +[ -d "$DOCS_RAW" ] || { echo "ERROR: website-v2-docs not found at $DOCS_RAW (set BOOSTLOOK_DOCS)"; exit 1; } +DOCS="$(cd "$DOCS_RAW" && pwd)" +[ -f "$DOCS/site.playbook.yml" ] || { echo "ERROR: $DOCS does not look like website-v2-docs (no site.playbook.yml)"; exit 1; } +BUILD="$DOCS/build" +SITE_ID="${NETLIFY_SITE_ID:-0874ef8e-22a3-4a78-ad4b-b11a9c056a12}" + +echo "boostlook : $ROOT" +echo "docs : $DOCS" +echo "build out : $BUILD" +echo + +# 1. Build the CSS bundle from src/css modules +echo "==> [1/6] build boostlook-v3.css" +sh "$ROOT/build-css.sh" + +# 2. Inject our working CSS into antora-ui and rebuild the UI bundle. +# --skip-boostlook makes gulp use the injected file instead of downloading. +echo "==> [2/6] inject 2.0 CSS + rebuild antora-ui bundle" +cp "$ROOT/boostlook-v3.css" "$DOCS/antora-ui/src/css/boostlook.css" +( cd "$DOCS/antora-ui" && npx gulp build --skip-boostlook && npx gulp bundle:pack ) + +# 3. Rebuild the site guides from local content with the fresh UI bundle. +echo "==> [3/6] rebuild site guides (antora)" +CID="$(cd "$DOCS" && git rev-parse --short HEAD 2>/dev/null || echo 0000000)" +( cd "$DOCS" && npx antora --fetch \ + --attribute page-boost-branch=develop \ + --attribute page-commit-id="$CID" \ + --stacktrace site.playbook.yml ) + +# 4. Bake the same 2.0 bundle into the reused (prebuilt) library docs, whose +# own UI assets live under lib/doc/_/ and aren't rebuilt here. +echo "==> [4/6] bake 2.0 CSS into library docs" +if [ -d "$BUILD/lib/doc/_/css" ]; then + cp "$BUILD/_/css/boostlook.css" "$BUILD/lib/doc/_/css/boostlook.css" + [ -f "$BUILD/lib/doc/_/css/boostlook-v3.css" ] && \ + cp "$BUILD/_/css/boostlook.css" "$BUILD/lib/doc/_/css/boostlook-v3.css" || true + echo " baked into lib/doc/_/css/boostlook.css" +else + echo " WARN: $BUILD/lib/doc not found — library samples will be omitted." + echo " Run 'cd $DOCS && ./dev.sh lib' once to generate them." +fi + +# 5. Render the charconv AsciiDoctor specimen (boostlook.rb adds the .boostlook +# wrapper; highlight.js avoids needing the rouge gem). +echo "==> [5/6] render charconv specimen" +if command -v asciidoctor >/dev/null 2>&1; then + mkdir -p "$BUILD/specimen" + asciidoctor -r "$ROOT/boostlook-v3.rb" \ + -a linkcss -a copycss! -a stylesdir=/_/css -a stylesheet=boostlook.css \ + -a source-highlighter=highlightjs -a docinfodir="$ROOT/doc" \ + "$ROOT/doc/specimen.adoc" -o "$BUILD/specimen/index.html" + echo " wrote specimen/index.html" +else + echo " WARN: asciidoctor not found — specimen omitted (gem install asciidoctor)." +fi + +# 6. Generate the preview landing page. +echo "==> [6/6] generate preview landing (index.html)" +node "$ROOT/scripts/gen-landing.js" "$BUILD" "$ROOT" + +echo +echo "Preview folder ready: $BUILD" + +# 7. Optional deploy via netlify-cli (requires `netlify login` once). +if [ "$DEPLOY" = draft ]; then + echo "==> deploying DRAFT to Netlify (site $SITE_ID)" + npx netlify-cli deploy --site="$SITE_ID" --dir="$BUILD" --no-build +elif [ "$DEPLOY" = prod ]; then + echo "==> deploying PRODUCTION to Netlify (site $SITE_ID)" + npx netlify-cli deploy --site="$SITE_ID" --dir="$BUILD" --no-build --prod +else + echo "Next: re-run with --draft (preview URL) or --prod (publish), or drag-drop the folder into Netlify." +fi diff --git a/scripts/gen-landing.js b/scripts/gen-landing.js new file mode 100644 index 00000000..91b17f3d --- /dev/null +++ b/scripts/gen-landing.js @@ -0,0 +1,209 @@ +/* + * Generate the Netlify preview landing page at /index.html. + * + * Usage: node scripts/gen-landing.js [BOOSTLOOK_DIR] + * BUILD_DIR the assembled deploy folder (website-v2-docs/build) + * BOOSTLOOK_DIR boostlook repo root, for the git-sourced changelog + * (defaults to the parent of this script's dir) + */ +const fs = require('fs') +const path = require('path') +const { execFileSync } = require('child_process') + +const BUILD = process.argv[2] +const BOOSTLOOK = process.argv[3] || path.resolve(__dirname, '..') +if (!BUILD || !fs.existsSync(BUILD)) { + console.error('gen-landing: BUILD dir missing or not provided: ' + BUILD) + process.exit(1) +} + +const esc = (s) => String(s).replace(/[&<>"]/g, (c) => + ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c])) + +function changelog () { + try { + const raw = execFileSync('git', + ['log', '-14', '--no-merges', '--pretty=format:%h\x1f%s\x1f%cr'], + { cwd: BOOSTLOOK }).toString() + return raw.split('\n').filter(Boolean).map((line) => { + const [hash, subject, when] = line.split('\x1f') + const m = subject.match(/^([a-z]+)(?:\([^)]*\))?!?:\s*(.*)$/i) + return { hash, type: m ? m[1].toLowerCase() : 'misc', text: m ? m[2] : subject, when } + }) + } catch (e) { return [] } +} + +const has = (route) => + fs.existsSync(path.join(BUILD, route.replace(/^\/|\/$/g, ''), 'index.html')) + +const groups = [ + { + label: 'Site Documentation', + hint: 'Versioned guides for the Boost website', + items: [ + { name: 'User Guide', route: '/user-guide/', engine: 'Antora' }, + { name: 'Contributor Guide', route: '/contributor-guide/', engine: 'Antora' }, + { name: 'Formal Reviews', route: '/formal-reviews/', engine: 'Antora' }, + ], + }, + { + label: 'Library Documentation', + hint: 'Per-library reference — one of each rendering engine', + items: [ + { name: 'Boost.Capy', route: '/lib/doc/capy/', engine: 'Antora' }, + { name: 'Boost.MSM', route: '/lib/doc/msm/', engine: 'Antora' }, + { name: 'Boost.URL', route: '/lib/doc/url/', engine: 'Antora' }, + { name: 'Boost.CharConv', route: '/specimen/', engine: 'AsciiDoctor', note: 'specimen.adoc' }, + ], + }, +] +for (const g of groups) g.items = g.items.filter((it) => has(it.route)) +const visible = groups.filter((g) => g.items.length) +const total = visible.reduce((n, g) => n + g.items.length, 0) +const engines = new Set() +visible.forEach((g) => g.items.forEach((it) => engines.add(it.engine))) + +const arrow = + '' + + '' +const chev = + '' + + '' + +const sections = visible.map((g) => { + const cards = g.items.map((it) => { + const cls = it.engine.toLowerCase() + const note = it.note ? `${esc(it.note)}` : '' + return ` + ${esc(it.name)}${esc(it.engine)} + ${esc(it.route)}${note} + ${arrow} + ` + }).join('\n') + return `
+

${esc(g.label)}

${esc(g.hint)}

+
+${cards} +
+
` +}).join('\n') + +const log = changelog() +const menu = log.length ? `
+ + +
` : '' + +const html = ` + + + + +Boostlook 2.0 — Preview + + + + +
+
+
+ +

Boostlook

Preview · ${total} samples · ${[...engines].join(' + ')}

+
+
+ ${menu} + +
+
+

Boostlook 2.0 across every Boost doc type

+

One sample of each Boost documentation engine, rendered with boostlook-v3.css. A preview of the new design, in collaboration with MetaLab.

+
+${sections} +
+ +
+ + + +` +fs.writeFileSync(path.join(BUILD, 'index.html'), html) +console.log(`gen-landing: wrote ${path.join(BUILD, 'index.html')} (${total} samples, ${log.length} changelog entries)`) From cd21674963675c164350992a9b4e9542eaa83aa5 Mon Sep 17 00:00:00 2001 From: julioest Date: Fri, 26 Jun 2026 08:45:51 -0400 Subject: [PATCH 04/19] feat: mobile changelog icon + commit timestamps - Collapse the "Recent changes" header button to an icon (clock) at <=600px so it no longer collides with the brand; hide the brand subtitle on mobile to free up room. - Changelog entries show the absolute commit date/time (YYYY-MM-DD HH:MM) instead of a relative "N ago". Applied to both the dev server landing (dev-server.js) and the deployed landing (scripts/gen-landing.js) so they stay consistent. --- dev-server.js | 17 ++++++++++++++--- scripts/gen-landing.js | 10 ++++++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/dev-server.js b/dev-server.js index ef537762..2d8fca04 100644 --- a/dev-server.js +++ b/dev-server.js @@ -90,7 +90,7 @@ function esc (s) { function getChangelog () { try { const raw = execFileSync('git', - ['log', '-14', '--no-merges', '--pretty=format:%h\x1f%s\x1f%cr'], + ['log', '-14', '--no-merges', '--date=format:%Y-%m-%d %H:%M', '--pretty=format:%h\x1f%s\x1f%cd'], { cwd: ROOT }).toString() return raw.split('\n').filter(Boolean).map((line) => { const [hash, subject, when] = line.split('\x1f') @@ -179,9 +179,13 @@ ${cards} '' + '' + const clock = + '' + + '' const changelogMenu = log.length ? `
-