codetour-builder
v0.4.1
Published
Build CodeTour .tour files from a JS manifest — resolves tree-sitter anchors, fills step numbers, auto-generates appendix back-links, validates, and emits .tour JSON.
Maintainers
Readme
codetour-builder
Build CodeTour .tour files from a JS manifest.
You write a manifest (markdown + tree-sitter queries); the builder:
- resolves each step's CSS selector (tree-sitter based) to a line / selection
- assigns step numbers (1-based, manifest order)
- fills
[@id]step refs and[@tourId]tour refs (hub/spoke) - auto-discovers ↩ back-links — any step referenced via
[@stepId]from another step's desc (same tour) gets a "Back to" link, deduped + ordered by step number - validates (ids unique, refs resolve, files exist, selectors resolve)
JSON.stringifys everything — no manual escaping
CSS/query steps are emitted with both a top-level line and a selection: line drives CodeTour's gutter markers + reveal target, selection drives the precise text highlight. Pattern/line steps emit line only.
Supports JS / TS / JSX / TSX for queries.
Install
npm install -g codetour-builder # global command
# or
npm install codetour-builder # local devDep, use npxUsage
codetour-builder <manifest.mjs> # build + write .tour files into cwd
codetour-builder <manifest.mjs> --dry # validate only, write nothing
codetour-builder <manifest.mjs> --out .tours/<topic>/ # write .tour files into a folder
codetour-test-query <file> ".css.selector[text=...]" # debug a CSS selector (default)Run from the project root. Step file: paths resolve against the current working directory and pass through into the .tour files verbatim — CodeTour resolves them against the workspace root, so both the build and playback need the same base. Use workspace-root-relative paths like src/index.ts. Emitted .tour files land in --out <dir> (default: current directory), and --out directories are created if missing. Exit 0 = success; non-zero prints an ERRORS: list.
Testing queries before writing the manifest
Iterate on a query against a real file before committing it to a manifest step:
codetour-test-query src/index.ts ".method_definition .identifier[text=x]"It prints every match with capture name, node type, 1-based line range, and a code snippet — so you can see exactly where the pattern hits.
Try the package in 30 seconds
cd examples
codetour-builder example-manifest.mjs # writes example-quick.tour
codetour-builder example-manifest.mjs --dry
codetour-test-query sample-source.js ".method_definition"The manifest
ES module, default export = array of tours (one manifest can emit many tours — e.g. hub + quick + detailed).
export default [{
id: "hub", // tour id (slug), unique
title: "Example: Pipeline",
isPrimary: true, // optional — present on first open
filename: "hub", // optional — output name, default slug(title)
steps: [
{ id: "choose", title: "Choose a tour",
desc: `Pick: [quick][@quick] or [detailed][@detailed]` }, // content-only step
],
}, {
id: "quick",
title: "Pipeline quick",
steps: [
{ id: "overview", title: "1 · Overview",
file: "sample.js", // relative to project root (cwd)
selector: { kind: "css", selector: ".function_declaration" },
desc: `**\`buildPipeline\`** is the entry point.` },
],
}, {
id: "detailed",
title: "Pipeline detailed",
steps: [
{ id: "root", title: "0 · Start here",
file: "sample.js",
selector: { kind: "css", selector: ".function_declaration" },
desc: `The [appendix][@compute-total] holds each file.` },
{ id: "compute-total", title: "A1 · computeTotal",
file: "sample.js",
selector: { kind: "css", selector: ".function_declaration" },
desc: `Sums items. Refers back to [the root][@root].` },
],
}]compute-total is referenced by root ([appendix][@compute-total]) and references root in its own desc — so the builder auto-appends ↩ back-links on both steps, deduped and ordered by step number.
Tour fields
| field | required | meaning |
|---|---|---|
| id | yes | slug, unique across the manifest |
| title | yes | display name; also the CodeTour tour-ref target |
| isPrimary | no | present this tour on first open |
| filename | no | .tour output name (default: slug of title) |
| steps | yes | ordered step objects |
Step fields
| field | required | meaning |
|---|---|---|
| id | yes | unique within the tour; used in [@id] refs |
| title | yes | display title |
| desc | yes | markdown body — template literal |
| file | with selector | path relative to the project root (cwd the builder runs from) |
| selector | no | locate the position (below) |
Steps with no file/selector = content-only (intro, choice page, hub splash).
Auto ↩ back-links
Back-links are not declared — the builder discovers them. For each step, it
reverse-indexes same-tour [@stepId] refs written in other steps' desc and
appends a ↩️ [Back to <step>][#N] link to every referenced step, deduped and
ordered by the referencing step's number. Cross-tour refs ([@tourId]) never
produce back-links.
So to give an appendix step a back-link, just reference it from the main flow
(via [@stepId]), and optionally reference the main flow from the appendix.
Selectors
Exactly one of three kinds per step:
selector: { kind: "css", selector: ".method_definition .identifier[text=x]" } // preferred
selector: { kind: "pattern", pattern: "^export function\*" } // regex on a line
selector: { kind: "line", line: 169 } // explicit 1-based linecss— CSS-style selector over the tree-sitter parse tree. Descendant combinator (A B) skips intermediate nodes; attribute selectors ([text=x],[text*=y]) filter by content. Seereferences/selectors.mdin the skill for the full reference. (Legacykind: "query"S-expressions still work but are not recommended — no level-skipping, no text filtering.)pattern— JS regex matched per line; first match → singleline.line— explicit 1-based line. Last resort.
Selectors work on .js .jsx .mjs .cjs (tree-sitter-javascript) and .ts .tsx .mts .cts (tree-sitter-typescript). Other extensions fail css — use pattern/line.
Refs between steps and tours
Write these in markdown; the builder replaces them:
[label][@stepId]— same-tour step ref →[label][#N][label][@tourId]— whole-tour ref →[label][Tour Title](hub/spoke)
The builder matches only the [@id] marker (not the whole [label][@id]), so the label keeps its authored markdown — labels containing inline code or emphasis work fine: [`hello`][@stepId] → [`hello`][#N]. Every authored [@id] must resolve to a step or tour id; an unknown ref is a build error (never silently left in the output).
Same-tour [@stepId] refs also drive the auto ↩ back-links (see above). Never write [#N] by hand — numbers shift when steps reorder.
Hub / quick / detailed
One manifest, array of 3 tours:
- hub —
isPrimary, content-only "choose a mode" step with[@quick]+[@detailed] - quick — 5-7 skim steps
- detailed — numbered main flow + appendix (appendix steps get ↩ back-links automatically when the main flow references them via
[@stepId])
Builder emits one .tour per tour into the --out folder (default: current directory).
Markdown rules
descis a template literal.- Code blocks: use HTML
<pre><code>...</code></pre>, not triple-backtick fences (fences trigger CodeTour's "Insert Code" button + need backtick escaping). Escape</>as</>. - Inline code
`x`in a template literal — escape:\`x\`. - Placeholders replaced only in plain text (remark AST) — never inside code blocks/inline code.
Publishing
Update the changelog
Add the changes for the new version to CHANGELOG.md.
Verify the release
node bin/build-tour.mjs --helpPublish the release
npm version minor -m "chore: release v%s"
git push origin main --follow-tags
VERSION="$(node -p "require('./package.json').version")"
gh release create "v$VERSION" --generate-notes
npm publishUse npm version patch or npm version major when appropriate.
License
MIT
