Guidance for AI coding agents (and human contributors) working in this repository.
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/.
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 builddocs: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.
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---jsblocks as JavaScript on the build machine;config.jsalso 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 likev-on:clickand@click.prevent, because Vue compiles them into live bindings. -
<component>and anyis=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 underdocs/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.
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.
- Use yarn only. Don't run
npm install; it creates apackage-lock.jsonthat competes withyarn.lock. Commityarn.locktogether with anypackage.jsonchange, 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 otheryarn.lockentry 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, runyarn deps:refresh. It runsyarn upgrade(every locked version moves to the newest its range allows;package.jsonis not touched) and then builds. Before committing the newyarn.lock, comparedocs/.vuepress/distwith 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).
resolutionsinpackage.jsonforces patched versions past those ranges where the newer version keeps the API its caller uses:toml,loader-utils(undervuepress-html-webpack-plugin, which also dropsjson50.5),serialize-javascript,linkify-it,node-forge,esbuild(declared by@vuepress/corebut never loaded),highlight.js(types only), andform-data,tough-cookieandqsunderrequest(Algolia search client, unused here). A path such as**/request/qsneeds the**/prefix or yarn ignores it. Before adding one, check the new version still loads withrequire()(several are now ES-module-only, e.g.decode-uri-component0.5) and exports the same shape (nth-check2 doesn't), then build and compare.serialize-javascript7 needs Node 20+, sodeploy.shdoes 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 (postcss7,svgo1,nth-check),markdown-it8, globbing (braces,micromatch), Vue 2 itself,html-minifier,request,got,ellipticanddecode-uri-component. Dismiss those in the Dependabot UI rather than forcing them. Removing them needs a move off VuePress 1. vuepress-plugin-seois 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.
Don't open public issues for security problems. See .github/SECURITY.md.
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.
- 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 indocs/. 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.
docs/.vuepress/styles/palette.stylsets Stylus variables consumed by the VuePress default theme at build time (colors, widths, navbar height).docs/.vuepress/styles/index.stylis injected after the default theme CSS and defines the actual design tokens as CSS custom properties on:root, with anhtml[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.jsinjects the light/dark toggle button into the navbar client-side (VuePress 1's theme has no built-in toggle) and persists the choice inlocalStorageundermn-theme. The inline script inconfig.js'sheadarray applies the saved theme before first paint to avoid a flash of the wrong theme.
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.