Skip to content

feat(content-reuse): wire ::include snippet pre-fetch + dependency manifest (content-reuse Task 6/8) - #3035

Merged
jung-thomas merged 8 commits into
DEVfrom
worktree-include-prefetch
Nov 6, 2026
Merged

jung-thomas merged 8 commits into
DEVfrom
worktree-include-prefetch

Conversation

@jung-thomas

Copy link
Copy Markdown
Contributor

What / Why

Finishes the `::include[snippet]` pre-fetch pipeline that was merged as a stub in the content-reuse feature branch. The directive grammar, expand hook, and directive context were already shipped; this PR wires the actual GitHub fetch, disk cache, and dep-manifest steps.

Before this PR: `snippetMap: new Map()` was always passed to `composeTutorial`, so `::include` directives in tutorial source always threw `directive/include-not-found`. The snippet-deps manifest (`GET /content/manifest/snippet-deps`) always returned `{}`.

After this PR:

  • `fetchSnippets()` lists and downloads `_snippets/*.md` from repos opted-in via `SNIPPETS_ENABLED_REPOS` env var (comma-separated bare repo names) plus the shared `tutorial-snippets` repo. Content is disk-cached at `.tutorial-cache/snippets//` using the GitHub file SHA for cache validation.
  • `buildSnippetDeps()` scans each tutorial's raw markdown for `::include[]` labels and produces the real snippet-key → dependent-slug map, which `publish-content.ts` uploads to HANA as `_manifest_snippet-deps`.
  • Image URLs inside snippet content are rewritten to the snippet's own repo/branch base URL (not the including tutorial's) so images render correctly regardless of which tutorial includes the snippet.
  • Errors thrown inside nested snippet expansion include the snippet key and tutorial slug in the message for author-facing diagnostics.
  • Gate: `SNIPPETS_ENABLED_REPOS` defaults to empty — all existing tutorials fetch and compose byte-identically to before. Nothing changes for repos not in the allowlist.

5-Part Status

Part Description Status
1 fetchSnippets() + real snippetMap wired to composeTutorial Done (prior 2 commits + Task 4 commit)
2 buildSnippetDeps() real impl (snippetKey→[slugs] map) Done (prior commit + Task 4 commit)
3 Origin-aware image/attachment resolution for included content Done (Task 3 commit)
4 Error line mapping: snippet:line (included from tutorial:line) Done (Task 5 commit)
5 Per-repo allow flag (SNIPPETS_ENABLED_REPOS), default empty Done (in fetchSnippets Task 1 commit; gate regression tests in Task 5 commit)

How Tested

  • 8 test files, 57 tests total, all passing:
    • 31 existing include tests (unchanged, no regression)
    • 5 existing corpus regression tests (unchanged)
    • 4 new github-snippets tests (fetchSnippets gate, per-repo filtering, shared/ prefix)
    • 6 new github-snippet-deps tests (buildSnippetDeps resolution, dedup, unknown keys)
    • 3 new include-images tests (origin-aware URL rewriting)
    • 4 new snippet-gate-regression tests (empty map → not-found, no-include unaffected, error augmentation, no double-wrap)
    • 2 new snippet-corpus-regression tests (BUNDLED_FIXTURE byte-identical with empty snippetMap)
    • 2 new fetch-snippets-integration tests (backward-compat byte-identical, snippetMap inlining)
  • 17/17 static guards pass
  • All new/modified files use LF line endings (verified with `file`)

Deploy Scope

Backend only (fetch pipeline / build scripts). No CAP service changes, no HANA schema changes, no MTA changes, no approuter changes.

Activates previously-stubbed `::include` and unblocks the `meta-tutorials` directive-content-reuse sample tutorial.

To activate snippet fetching in a build: set `SNIPPETS_ENABLED_REPOS=` in the build environment (CI or local `.env`). Default is empty (off). The shared `tutorial-snippets` repo is always included when any repos are opted in.

…sk 3)

Adds rewriteSnippetImages() to include.ts which rewrites relative image
paths inside snippet markdown to absolute GitHub raw URLs pointing to the
snippet's own repo/branch, not the including tutorial's.

- shared/<name> snippets → tutorial-snippets/main
- <repo>/_snippets/<name> snippets → <repo>/main
- Absolute URLs and parent-relative (../) paths are left untouched
- snippetRepoMap in ExpandDirectivesContext allows explicit override
…pipeline

- Imports fetchSnippets from github.ts in fetch-tutorials.ts
- Adds snippet pre-fetch block after help-titles (gated by SNIPPETS_ENABLED_REPOS)
- Passes real snippetMap to every composeTutorial() call (replaces new Map() stub)
- buildSnippetDeps now receives real snippetMap + disk-cached rawMd per slug
- Removes TODO comments for Task 6/8; all fetch-tutorials.ts stubs are now wired
- Adds backward-compat integration test (empty snippetMap = byte-identical runs)
…th include source context

When expandDirectives() throws inside a snippet's content, the error
message is now augmented with:
  "<original message> (included from <tutorialSlug>:<line> via <snippetKey>)"

Avoids double-wrapping when the message already contains "included from".

Also adds gate regression tests confirming:
- empty snippetMap → directive/include-not-found (behavior unchanged for non-opted-in repos)
- tutorials with no ::include are unaffected (no error, identical output)
… undefined local var

The token variable in main() scope was undefined at runtime (it's only defined
inside fetchMarkdown()). fetchSnippets() uses the token for GitHub auth headers,
so passing undefined would skip auth and fail on private repos' _snippets/ dirs.

Use process.env.GITHUB_TOKEN ?? process.env.TUTORIALS_GITHUB_TOKEN directly at
the call site (stored as snippetToken) to avoid the scope issue.
…ude regex

Three bugs found in code review:

1. CRITICAL: fetchSnippets() stored shared snippets as 'shared/cf-cli.md' but
   resolveSnippetKey() normalizes ::include[shared:cf-cli] to 'shared/cf-cli'
   (no extension). Every shared snippet include threw directive/include-not-found.
   Fix: strip .md from the key when building shared/ keys. Per-repo keys keep .md
   since ::include[step.md] labels include the extension and suffix-match works.

2. MEDIUM: Same mismatch caused buildSnippetDeps to emit 'shared/cf-cli' keys
   while the rebuild workflow would compare against 'tutorial-snippets/...cf-cli.md'
   paths — shared-snippet changes never triggered targeted rebuilds.
   Fix: same as above (key consistency).

3. LOW: INCLUDE_RE in buildSnippetDeps used /:?:include/ matching one-colon
   :include[x] (not valid directive syntax) and producing false-positive dep entries.
   Fix: /::include/ (require two colons, matching actual leaf directive syntax).

Also updates tests:
- github-snippets.test.ts: assert 'shared/cf-cli' (not 'shared/cf-cli.md')
- github-snippet-deps.test.ts: add comment clarifying key convention; add test
  confirming :include (single colon) is not matched
- include-images.test.ts: shared snippet test uses 'shared/setup' key + label
  (no .md extension), matching the actual fetchSnippets output convention
@jung-thomas
jung-thomas merged commit d6d38cd into DEV Nov 6, 2026
3 of 4 checks passed
@jung-thomas
jung-thomas deleted the worktree-include-prefetch branch November 6, 2026 16:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant