Skip to content

Latest commit

 

History

History
158 lines (125 loc) · 10.7 KB

File metadata and controls

158 lines (125 loc) · 10.7 KB

AGENTS.md

Guidance for AI coding agents (and human contributors) working in this repository.

What this is

Community-built documentation site for MyNode (a Bitcoin/Lightning node platform). Content is Markdown, compiled into a static site with VuePress 1. Live at https://docs.mynodebtc.com/.

Commands

yarn install       # install deps
yarn docs:dev      # serve locally with hot reload (port 8080, or next available)
yarn docs:build    # build static site to docs/.vuepress/dist
yarn docs:lint     # reject Markdown that VuePress would compile into executable code
yarn docs:check    # render every page and check what Vue will compile against an allowlist
yarn deps:refresh  # update every locked dependency within its range, then build

docs:dev and docs:build run through node --openssl-legacy-provider because the bundled VuePress 1 uses webpack 4, which is incompatible with OpenSSL 3 in newer Node releases. Don't remove that flag.

There are two automated checks. yarn docs:lint (scripts/check-markdown.js) reads the raw Markdown and needs no dependencies. yarn docs:check (scripts/check-templates.js) renders every page with the site's own markdown-it setup and parses the result with Vue's template compiler, then rejects any tag, attribute or URL scheme not on its allowlist. docs:build runs docs:check first. Otherwise verification is building the site and checking pages render.

Markdown is not inert

VuePress compiles each .md file into a Vue single-file component, so Markdown content is executable. A <script> block in Markdown becomes the page's SFC script and runs both in visitors' browsers and on the build machine during SSR; {{ }} is evaluated as a Vue expression (including inside inline backticks — fenced code blocks are the only inert form); onerror= handlers and javascript: hrefs pass through verbatim.

This matters because docs.mynodebtc.com is same-site with www.mynodebtc.com, so the main site's SameSite=Lax session cookie is sent on requests originating from the docs origin. scripts/check-markdown.js rejects these patterns and runs in CI on pull requests and before every deploy. It also rejects:

  • Frontmatter with a language tag (---js, ---toml, ...). Use a plain --- YAML fence. The frontmatter parser runs ---js blocks as JavaScript on the build machine; config.js also disables that engine so the build fails if the check is bypassed. Don't remove that override.

  • Any attribute whose name starts with v-, :, @ or #, including forms like v-on:click and @click.prevent, because Vue compiles them into live bindings.

  • <component> and any is= attribute, which make Vue render a different element by name.

  • Disguised javascript: URLs, including ones hidden with character codes, tabs, newlines or control characters.

  • Anything after an opening code fence except a language name (and an optional {1,3} line range). VuePress pastes that text into an HTML attribute unescaped.

  • <<< snippet imports, which embed a file from the build machine into the page.

  • Files in .vuepress/public/ that aren't on the extension allowlist (.html, .php, .htaccess, ...), and SVGs anywhere under docs/ that contain script, event handlers, <foreignObject> or links other than #id. An SVG opened directly runs as a page on the docs origin, and GitHub shows it in a diff as a picture.

Only the inside of a fenced code block counts as inert. HTML comments and inline code are checked like everything else, because a <!-- or a backtick inside an attribute value used to hide the rest of the tag from the check. If the check can't be sure how markdown-it reads a fence (unclosed, inside an HTML block, a ::: line inside, ...), it stops treating later fences in that file as inert.

docs:check sees what Vue will actually compile, so it catches what the Markdown linter has to guess at. If a page legitimately needs a new tag or attribute, add it to the allowlist in scripts/check-templates.js in the same PR. In pull requests it runs with the base branch's .vuepress/ and dependencies, with only the PR's pages swapped in.

To show any of these as an example, put it in a fenced code block. The linter is a denylist, so review of Markdown PRs is still the main control. Treat any change to scripts/check-markdown.js or .github/workflows/ as security-relevant; the reasons behind each rule are in the comments in check-markdown.js.

Deployment

Pushing to master triggers .github/workflows/deploy.yml, which builds the site and rsyncs docs/.vuepress/dist/ (with --delete) over SSH to the production server. Watch the Actions tab after merging — a failed build or rsync means the live site stops updating until it's fixed. Because of --delete, anything present on the server but missing from dist/ gets removed on the next deploy, so any server-side file the site depends on (e.g. custom error pages) needs to be produced by the build itself, not added by hand.

CI is pinned for reproducibility: Node 22 via actions/setup-node, and every action pinned to a commit SHA with a version comment. Keep new actions pinned the same way. Install is yarn install --frozen-lockfile --ignore-scripts, in CI and in deploy.sh.

Dependencies

  • Use yarn only. Don't run npm install; it creates a package-lock.json that competes with yarn.lock. Commit yarn.lock together with any package.json change, or CI fails at install.
  • Dependabot opens weekly grouped PRs for actions and npm (.github/dependabot.yml). Those PRs only get the Markdown check, not a build, and merging deploys. Before merging an npm update, build the branch, compare the pages' <meta> tags with the current build, and look at a page in a browser.
  • Dependabot's version updates only raise the packages listed in package.json. Yarn keeps every other yarn.lock entry as long as it still satisfies its range, so the ~1,200 transitive dependencies don't move on their own and pile up security alerts. Every few months, and whenever the alert list grows, run yarn deps:refresh. It runs yarn upgrade (every locked version moves to the newest its range allows; package.json is not touched) and then builds. Before committing the new yarn.lock, compare docs/.vuepress/dist with a build from the old lockfile: the same files, the same <meta> tags, and in the HTML only expected changes such as code highlighting. Merge it on its own PR, since merging deploys.
  • Alerts that remain after a refresh are capped by VuePress 1's own dependency ranges (VuePress 1 is end-of-life). resolutions in package.json forces patched versions past those ranges where the newer version keeps the API its caller uses: toml, loader-utils (under vuepress-html-webpack-plugin, which also drops json5 0.5), serialize-javascript, linkify-it, node-forge, esbuild (declared by @vuepress/core but never loaded), highlight.js (types only), and form-data, tough-cookie and qs under request (Algolia search client, unused here). A path such as **/request/qs needs the **/ prefix or yarn ignores it. Before adding one, check the new version still loads with require() (several are now ES-module-only, e.g. decode-uri-component 0.5) and exports the same shape (nth-check 2 doesn't), then build and compare. serialize-javascript 7 needs Node 20+, so deploy.sh does too.
  • What is still flagged after that has no fix, or only one in a major version that VuePress 1 can't use: the dev server (webpack-dev-server, webpack-dev-middleware, http-proxy-middleware, ip, uuid), the CSS pipeline (postcss 7, svgo 1, nth-check), markdown-it 8, globbing (braces, micromatch), Vue 2 itself, html-minifier, request, got, elliptic and decode-uri-component. Dismiss those in the Dependabot UI rather than forcing them. Removing them needs a move off VuePress 1.
  • vuepress-plugin-seo is held below 0.2.0. 0.2.0 targets VuePress 2 and on this site silently drops all Open Graph, Twitter and verification tags while the build still passes.

Security reports

Don't open public issues for security problems. See .github/SECURITY.md.

URL forms

Both of these resolve to the same page:

https://docs.mynodebtc.com/intro/getting-started.html
https://docs.mynodebtc.com/intro/getting-started

This repo's existing internal links use both forms inconsistently — that's fine, don't normalize them. The compatibility is handled by server-side configuration outside this repo, not by VuePress or anything here, so changes in this codebase won't affect it either way.

docs:build also copies the generated 404.html to error/404.html in the build output specifically so the server's custom-404 config (which expects a file at that path) keeps working across deploys.

Content structure

  • All pages live under docs/, one subdirectory per app or topic (e.g. bitcoin/, lightning/, tor/, troubleshooting/).
  • Images go in docs/.vuepress/public/images/<SUBDIRECTORY>/, referenced from Markdown as /images/<SUBDIRECTORY>/<FILENAME> (via <img> tag or Markdown image syntax).
  • Every page that should be reachable from the nav/sidebar must be added to docs/.vuepress/config.js (themeConfig.sidebar), not just placed in docs/. The sidebar array is a hand-maintained tree of sections and pages — adding a Markdown file alone does not expose it.
  • Commented-out entries in config.js's sidebar (e.g. the "Setup Base Images" block) are intentionally disabled pages, not dead code to delete.

Theming

  • docs/.vuepress/styles/palette.styl sets Stylus variables consumed by the VuePress default theme at build time (colors, widths, navbar height).
  • docs/.vuepress/styles/index.styl is injected after the default theme CSS and defines the actual design tokens as CSS custom properties on :root, with an html[data-theme="light"] override block for light mode. Dark is the default; almost all colors should be added/edited as --mn-* custom properties here rather than hardcoded, so both themes stay in sync.
  • docs/.vuepress/enhanceApp.js injects the light/dark toggle button into the navbar client-side (VuePress 1's theme has no built-in toggle) and persists the choice in localStorage under mn-theme. The inline script in config.js's head array applies the saved theme before first paint to avoid a flash of the wrong theme.

SEO plugin

config.js configures vuepress-plugin-seo with a fairly involved customMeta callback (Twitter card tags, Google site verification). Page-level SEO fields (title, description, image, tags) are driven by each Markdown file's frontmatter — check a page's frontmatter before assuming a metadata field needs to be added in config.js itself.