bmad-method 6.0.5-next.8 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/package.json +1 -1
  2. package/src/bmm/agents/analyst.agent.yaml +1 -1
  3. package/src/bmm/module-help.csv +1 -1
  4. package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-02-vision.md +1 -1
  5. package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-03-users.md +1 -1
  6. package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-04-metrics.md +1 -1
  7. package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-05-scope.md +1 -1
  8. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02-discovery.md +1 -1
  9. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02b-vision.md +1 -1
  10. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02c-executive-summary.md +1 -1
  11. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-03-success.md +1 -1
  12. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-04-journeys.md +1 -1
  13. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-05-domain.md +1 -1
  14. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-06-innovation.md +1 -1
  15. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-07-project-type.md +1 -1
  16. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-08-scoping.md +1 -1
  17. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-09-functional.md +1 -1
  18. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-10-nonfunctional.md +1 -1
  19. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-11-polish.md +1 -1
  20. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-01-discovery.md +1 -1
  21. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-01-discovery.md +1 -1
  22. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-02-discovery.md +1 -1
  23. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-03-core-experience.md +2 -2
  24. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-04-emotional-response.md +2 -2
  25. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-05-inspiration.md +2 -2
  26. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-06-design-system.md +2 -2
  27. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-07-defining-experience.md +2 -2
  28. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-08-visual-foundation.md +2 -2
  29. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-09-design-directions.md +2 -2
  30. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-10-user-journeys.md +2 -2
  31. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-11-component-strategy.md +2 -2
  32. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-12-ux-patterns.md +2 -2
  33. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-13-responsive-accessibility.md +2 -2
  34. package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-02-context.md +2 -2
  35. package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-03-starter.md +2 -2
  36. package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-04-decisions.md +2 -2
  37. package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-05-patterns.md +2 -2
  38. package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-06-structure.md +2 -2
  39. package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-07-validation.md +2 -2
  40. package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-01-validate-prerequisites.md +31 -13
  41. package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-02-design-epics.md +1 -1
  42. package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-03-create-stories.md +6 -2
  43. package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-04-final-validation.md +1 -1
  44. package/src/bmm/workflows/3-solutioning/create-epics-and-stories/templates/epics-template.md +4 -0
  45. package/src/bmm/workflows/4-implementation/code-review/workflow.md +4 -7
  46. package/src/bmm/workflows/bmad-quick-flow/bmad-quick-dev-new-preview/workflow.md +0 -4
  47. package/src/bmm/workflows/bmad-quick-flow/quick-dev/workflow.md +1 -1
  48. package/src/bmm/workflows/bmad-quick-flow/quick-spec/workflow.md +1 -1
  49. package/src/bmm/workflows/generate-project-context/steps/step-02-generate.md +1 -1
  50. package/src/core/module-help.csv +2 -2
  51. package/src/core/workflows/bmad-brainstorming/SKILL.md +6 -0
  52. package/src/core/workflows/bmad-brainstorming/bmad-skill-manifest.yaml +1 -0
  53. package/src/core/workflows/{brainstorming → bmad-brainstorming}/workflow.md +2 -5
  54. package/src/core/workflows/bmad-party-mode/SKILL.md +6 -0
  55. package/src/core/workflows/bmad-party-mode/bmad-skill-manifest.yaml +1 -0
  56. package/src/core/workflows/{party-mode → bmad-party-mode}/steps/step-03-graceful-exit.md +0 -1
  57. package/src/core/workflows/{party-mode → bmad-party-mode}/workflow.md +0 -4
  58. package/tools/cli/external-official-modules.yaml +18 -18
  59. package/tools/cli/installers/lib/core/installer.js +25 -8
  60. package/tools/cli/installers/lib/ide/_base-ide.js +0 -1
  61. package/tools/cli/installers/lib/ide/_config-driven.js +9 -4
  62. package/tools/cli/installers/lib/ide/manager.js +3 -3
  63. package/tools/cli/lib/agent/compiler.js +1 -1
  64. package/.augment/code_review_guidelines.yaml +0 -231
  65. package/.coderabbit.yaml +0 -85
  66. package/.github/CODE_OF_CONDUCT.md +0 -128
  67. package/.github/FUNDING.yaml +0 -15
  68. package/.github/ISSUE_TEMPLATE/bug-report.yaml +0 -124
  69. package/.github/ISSUE_TEMPLATE/config.yaml +0 -8
  70. package/.github/ISSUE_TEMPLATE/documentation.yaml +0 -55
  71. package/.github/ISSUE_TEMPLATE/feature-request.md +0 -22
  72. package/.github/ISSUE_TEMPLATE/issue.md +0 -32
  73. package/.github/PULL_REQUEST_TEMPLATE.md +0 -13
  74. package/.github/scripts/discord-helpers.sh +0 -34
  75. package/.github/workflows/coderabbit-review.yaml +0 -22
  76. package/.github/workflows/discord.yaml +0 -90
  77. package/.github/workflows/docs.yaml +0 -64
  78. package/.github/workflows/publish.yaml +0 -133
  79. package/.github/workflows/quality.yaml +0 -116
  80. package/.husky/pre-commit +0 -20
  81. package/.markdownlint-cli2.yaml +0 -41
  82. package/.nvmrc +0 -1
  83. package/.prettierignore +0 -12
  84. package/.vscode/settings.json +0 -96
  85. package/CHANGELOG.md +0 -1785
  86. package/CNAME +0 -1
  87. package/CONTRIBUTING.md +0 -176
  88. package/CONTRIBUTORS.md +0 -32
  89. package/SECURITY.md +0 -85
  90. package/TRADEMARK.md +0 -55
  91. package/Wordmark.png +0 -0
  92. package/banner-bmad-method.png +0 -0
  93. package/docs/404.md +0 -9
  94. package/docs/_STYLE_GUIDE.md +0 -370
  95. package/docs/explanation/advanced-elicitation.md +0 -49
  96. package/docs/explanation/adversarial-review.md +0 -59
  97. package/docs/explanation/brainstorming.md +0 -33
  98. package/docs/explanation/established-projects-faq.md +0 -50
  99. package/docs/explanation/party-mode.md +0 -59
  100. package/docs/explanation/preventing-agent-conflicts.md +0 -112
  101. package/docs/explanation/project-context.md +0 -157
  102. package/docs/explanation/quick-dev-new-preview.md +0 -73
  103. package/docs/explanation/quick-flow.md +0 -77
  104. package/docs/explanation/why-solutioning-matters.md +0 -77
  105. package/docs/how-to/customize-bmad.md +0 -172
  106. package/docs/how-to/established-projects.md +0 -117
  107. package/docs/how-to/get-answers-about-bmad.md +0 -138
  108. package/docs/how-to/install-bmad.md +0 -116
  109. package/docs/how-to/non-interactive-installation.md +0 -171
  110. package/docs/how-to/project-context.md +0 -136
  111. package/docs/how-to/quick-fixes.md +0 -123
  112. package/docs/how-to/shard-large-documents.md +0 -78
  113. package/docs/how-to/upgrade-to-v6.md +0 -100
  114. package/docs/index.md +0 -60
  115. package/docs/reference/agents.md +0 -28
  116. package/docs/reference/commands.md +0 -145
  117. package/docs/reference/modules.md +0 -76
  118. package/docs/reference/testing.md +0 -106
  119. package/docs/reference/workflow-map.md +0 -89
  120. package/docs/roadmap.mdx +0 -136
  121. package/docs/tutorials/getting-started.md +0 -275
  122. package/docs/zh-cn/404.md +0 -9
  123. package/docs/zh-cn/_STYLE_GUIDE.md +0 -370
  124. package/docs/zh-cn/explanation/advanced-elicitation.md +0 -62
  125. package/docs/zh-cn/explanation/adversarial-review.md +0 -71
  126. package/docs/zh-cn/explanation/brainstorming.md +0 -43
  127. package/docs/zh-cn/explanation/established-projects-faq.md +0 -60
  128. package/docs/zh-cn/explanation/party-mode.md +0 -79
  129. package/docs/zh-cn/explanation/preventing-agent-conflicts.md +0 -137
  130. package/docs/zh-cn/explanation/project-context.md +0 -176
  131. package/docs/zh-cn/explanation/quick-flow.md +0 -93
  132. package/docs/zh-cn/explanation/why-solutioning-matters.md +0 -90
  133. package/docs/zh-cn/how-to/customize-bmad.md +0 -182
  134. package/docs/zh-cn/how-to/established-projects.md +0 -134
  135. package/docs/zh-cn/how-to/get-answers-about-bmad.md +0 -144
  136. package/docs/zh-cn/how-to/install-bmad.md +0 -105
  137. package/docs/zh-cn/how-to/non-interactive-installation.md +0 -181
  138. package/docs/zh-cn/how-to/project-context.md +0 -152
  139. package/docs/zh-cn/how-to/quick-fixes.md +0 -140
  140. package/docs/zh-cn/how-to/shard-large-documents.md +0 -86
  141. package/docs/zh-cn/how-to/upgrade-to-v6.md +0 -120
  142. package/docs/zh-cn/index.md +0 -69
  143. package/docs/zh-cn/reference/agents.md +0 -41
  144. package/docs/zh-cn/reference/commands.md +0 -166
  145. package/docs/zh-cn/reference/modules.md +0 -94
  146. package/docs/zh-cn/reference/testing.md +0 -122
  147. package/docs/zh-cn/reference/workflow-map.md +0 -104
  148. package/docs/zh-cn/roadmap.mdx +0 -152
  149. package/docs/zh-cn/tutorials/getting-started.md +0 -300
  150. package/eslint.config.mjs +0 -141
  151. package/prettier.config.mjs +0 -32
  152. package/src/core/workflows/brainstorming/bmad-skill-manifest.yaml +0 -3
  153. package/src/core/workflows/party-mode/bmad-skill-manifest.yaml +0 -3
  154. package/test/README.md +0 -295
  155. package/test/adversarial-review-tests/README.md +0 -56
  156. package/test/adversarial-review-tests/sample-content.md +0 -46
  157. package/test/adversarial-review-tests/test-cases.yaml +0 -103
  158. package/test/fixtures/agent-schema/invalid/critical-actions/actions-as-string.agent.yaml +0 -27
  159. package/test/fixtures/agent-schema/invalid/critical-actions/empty-string-in-actions.agent.yaml +0 -30
  160. package/test/fixtures/agent-schema/invalid/menu/empty-menu.agent.yaml +0 -22
  161. package/test/fixtures/agent-schema/invalid/menu/missing-menu.agent.yaml +0 -20
  162. package/test/fixtures/agent-schema/invalid/menu-commands/empty-command-target.agent.yaml +0 -25
  163. package/test/fixtures/agent-schema/invalid/menu-commands/no-command-target.agent.yaml +0 -24
  164. package/test/fixtures/agent-schema/invalid/menu-triggers/camel-case.agent.yaml +0 -25
  165. package/test/fixtures/agent-schema/invalid/menu-triggers/compound-invalid-format.agent.yaml +0 -25
  166. package/test/fixtures/agent-schema/invalid/menu-triggers/compound-mismatched-kebab.agent.yaml +0 -25
  167. package/test/fixtures/agent-schema/invalid/menu-triggers/duplicate-triggers.agent.yaml +0 -31
  168. package/test/fixtures/agent-schema/invalid/menu-triggers/empty-trigger.agent.yaml +0 -25
  169. package/test/fixtures/agent-schema/invalid/menu-triggers/leading-asterisk.agent.yaml +0 -25
  170. package/test/fixtures/agent-schema/invalid/menu-triggers/snake-case.agent.yaml +0 -25
  171. package/test/fixtures/agent-schema/invalid/menu-triggers/trigger-with-spaces.agent.yaml +0 -25
  172. package/test/fixtures/agent-schema/invalid/metadata/empty-module-string.agent.yaml +0 -26
  173. package/test/fixtures/agent-schema/invalid/metadata/empty-name.agent.yaml +0 -24
  174. package/test/fixtures/agent-schema/invalid/metadata/extra-metadata-fields.agent.yaml +0 -27
  175. package/test/fixtures/agent-schema/invalid/metadata/missing-id.agent.yaml +0 -23
  176. package/test/fixtures/agent-schema/invalid/persona/empty-principles-array.agent.yaml +0 -24
  177. package/test/fixtures/agent-schema/invalid/persona/empty-string-in-principles.agent.yaml +0 -27
  178. package/test/fixtures/agent-schema/invalid/persona/extra-persona-fields.agent.yaml +0 -27
  179. package/test/fixtures/agent-schema/invalid/persona/missing-role.agent.yaml +0 -24
  180. package/test/fixtures/agent-schema/invalid/prompts/empty-content.agent.yaml +0 -29
  181. package/test/fixtures/agent-schema/invalid/prompts/extra-prompt-fields.agent.yaml +0 -31
  182. package/test/fixtures/agent-schema/invalid/prompts/missing-content.agent.yaml +0 -28
  183. package/test/fixtures/agent-schema/invalid/prompts/missing-id.agent.yaml +0 -28
  184. package/test/fixtures/agent-schema/invalid/top-level/empty-file.agent.yaml +0 -5
  185. package/test/fixtures/agent-schema/invalid/top-level/extra-top-level-keys.agent.yaml +0 -28
  186. package/test/fixtures/agent-schema/invalid/top-level/missing-agent-key.agent.yaml +0 -11
  187. package/test/fixtures/agent-schema/invalid/yaml-errors/invalid-indentation.agent.yaml +0 -19
  188. package/test/fixtures/agent-schema/invalid/yaml-errors/malformed-yaml.agent.yaml +0 -18
  189. package/test/fixtures/agent-schema/valid/critical-actions/empty-critical-actions.agent.yaml +0 -24
  190. package/test/fixtures/agent-schema/valid/critical-actions/no-critical-actions.agent.yaml +0 -22
  191. package/test/fixtures/agent-schema/valid/critical-actions/valid-critical-actions.agent.yaml +0 -27
  192. package/test/fixtures/agent-schema/valid/menu/multiple-menu-items.agent.yaml +0 -31
  193. package/test/fixtures/agent-schema/valid/menu/single-menu-item.agent.yaml +0 -22
  194. package/test/fixtures/agent-schema/valid/menu-commands/all-command-types.agent.yaml +0 -38
  195. package/test/fixtures/agent-schema/valid/menu-commands/multiple-commands.agent.yaml +0 -23
  196. package/test/fixtures/agent-schema/valid/menu-triggers/compound-triggers.agent.yaml +0 -31
  197. package/test/fixtures/agent-schema/valid/menu-triggers/kebab-case-triggers.agent.yaml +0 -34
  198. package/test/fixtures/agent-schema/valid/metadata/core-agent-with-module.agent.yaml +0 -24
  199. package/test/fixtures/agent-schema/valid/metadata/empty-module-name-in-path.agent.yaml +0 -24
  200. package/test/fixtures/agent-schema/valid/metadata/malformed-path-treated-as-core.agent.yaml +0 -24
  201. package/test/fixtures/agent-schema/valid/metadata/module-agent-correct.agent.yaml +0 -24
  202. package/test/fixtures/agent-schema/valid/metadata/module-agent-missing-module.agent.yaml +0 -23
  203. package/test/fixtures/agent-schema/valid/metadata/wrong-module-value.agent.yaml +0 -24
  204. package/test/fixtures/agent-schema/valid/persona/complete-persona.agent.yaml +0 -24
  205. package/test/fixtures/agent-schema/valid/prompts/empty-prompts.agent.yaml +0 -24
  206. package/test/fixtures/agent-schema/valid/prompts/no-prompts.agent.yaml +0 -22
  207. package/test/fixtures/agent-schema/valid/prompts/valid-prompts-minimal.agent.yaml +0 -28
  208. package/test/fixtures/agent-schema/valid/prompts/valid-prompts-with-description.agent.yaml +0 -30
  209. package/test/fixtures/agent-schema/valid/top-level/minimal-core-agent.agent.yaml +0 -24
  210. package/test/fixtures/file-refs-csv/invalid/all-empty-workflow.csv +0 -3
  211. package/test/fixtures/file-refs-csv/invalid/empty-data.csv +0 -1
  212. package/test/fixtures/file-refs-csv/invalid/no-workflow-column.csv +0 -3
  213. package/test/fixtures/file-refs-csv/invalid/unresolvable-vars.csv +0 -3
  214. package/test/fixtures/file-refs-csv/valid/bmm-style.csv +0 -3
  215. package/test/fixtures/file-refs-csv/valid/core-style.csv +0 -3
  216. package/test/fixtures/file-refs-csv/valid/minimal.csv +0 -2
  217. package/test/test-agent-schema.js +0 -387
  218. package/test/test-cli-integration.sh +0 -159
  219. package/test/test-file-refs-csv.js +0 -133
  220. package/test/test-install-to-bmad.js +0 -154
  221. package/test/test-installation-components.js +0 -1796
  222. package/test/test-rehype-plugins.mjs +0 -1050
  223. package/test/test-workflow-path-regex.js +0 -88
  224. package/test/unit-test-schema.js +0 -133
  225. package/tools/build-docs.mjs +0 -464
  226. package/tools/docs/_prompt-external-modules-page.md +0 -59
  227. package/tools/docs/fix-refs.md +0 -91
  228. package/tools/docs/native-skills-migration-checklist.md +0 -281
  229. package/tools/fix-doc-links.js +0 -285
  230. package/tools/validate-agent-schema.js +0 -110
  231. package/tools/validate-doc-links.js +0 -407
  232. package/tools/validate-file-refs.js +0 -556
  233. package/website/README.md +0 -75
  234. package/website/astro.config.mjs +0 -157
  235. package/website/public/diagrams/quick-dev-diagram.png +0 -0
  236. package/website/public/favicon.ico +0 -0
  237. package/website/public/img/bmad-dark.png +0 -0
  238. package/website/public/img/bmad-light.png +0 -0
  239. package/website/public/workflow-map-diagram.html +0 -361
  240. package/website/src/components/Banner.astro +0 -62
  241. package/website/src/components/Header.astro +0 -96
  242. package/website/src/components/MobileMenuFooter.astro +0 -33
  243. package/website/src/content/config.ts +0 -7
  244. package/website/src/content/i18n/zh-CN.json +0 -28
  245. package/website/src/lib/site-url.mjs +0 -25
  246. package/website/src/pages/404.astro +0 -11
  247. package/website/src/pages/robots.txt.ts +0 -48
  248. package/website/src/rehype-base-paths.js +0 -112
  249. package/website/src/rehype-markdown-links.js +0 -119
  250. package/website/src/styles/custom.css +0 -805
  251. /package/src/core/workflows/{brainstorming → bmad-brainstorming}/brain-methods.csv +0 -0
  252. /package/src/core/workflows/{brainstorming → bmad-brainstorming}/steps/step-01-session-setup.md +0 -0
  253. /package/src/core/workflows/{brainstorming → bmad-brainstorming}/steps/step-01b-continue.md +0 -0
  254. /package/src/core/workflows/{brainstorming → bmad-brainstorming}/steps/step-02a-user-selected.md +0 -0
  255. /package/src/core/workflows/{brainstorming → bmad-brainstorming}/steps/step-02b-ai-recommended.md +0 -0
  256. /package/src/core/workflows/{brainstorming → bmad-brainstorming}/steps/step-02c-random-selection.md +0 -0
  257. /package/src/core/workflows/{brainstorming → bmad-brainstorming}/steps/step-02d-progressive-flow.md +0 -0
  258. /package/src/core/workflows/{brainstorming → bmad-brainstorming}/steps/step-03-technique-execution.md +0 -0
  259. /package/src/core/workflows/{brainstorming → bmad-brainstorming}/steps/step-04-idea-organization.md +0 -0
  260. /package/src/core/workflows/{brainstorming → bmad-brainstorming}/template.md +0 -0
  261. /package/src/core/workflows/{party-mode → bmad-party-mode}/steps/step-01-agent-loading.md +0 -0
  262. /package/src/core/workflows/{party-mode → bmad-party-mode}/steps/step-02-discussion-orchestration.md +0 -0
@@ -1,1050 +0,0 @@
1
- /**
2
- * Rehype Plugin Tests
3
- *
4
- * Tests for rehype-markdown-links and rehype-base-paths plugins:
5
- * - findFirstDelimiter helper
6
- * - detectContentDir helper
7
- * - Transformer skip conditions
8
- * - Path resolution
9
- * - Index handling
10
- * - Query/hash preservation
11
- * - Base path prefixing
12
- * - Element rewriting
13
- * - Raw HTML rewriting
14
- * - Integration (both plugins together)
15
- *
16
- * Usage: node test/test-rehype-plugins.mjs
17
- */
18
-
19
- import rehypeMarkdownLinks, { findFirstDelimiter, detectContentDir } from '../website/src/rehype-markdown-links.js';
20
- import rehypeBasePaths from '../website/src/rehype-base-paths.js';
21
-
22
- // ANSI colors
23
- const colors = {
24
- reset: '\u001B[0m',
25
- green: '\u001B[32m',
26
- red: '\u001B[31m',
27
- yellow: '\u001B[33m',
28
- cyan: '\u001B[36m',
29
- dim: '\u001B[2m',
30
- };
31
-
32
- let passed = 0;
33
- let failed = 0;
34
-
35
- /**
36
- * Test helper: Assert condition
37
- */
38
- function assert(condition, testName, errorMessage = '') {
39
- if (condition) {
40
- console.log(`${colors.green}\u2713${colors.reset} ${testName}`);
41
- passed++;
42
- } else {
43
- console.log(`${colors.red}\u2717${colors.reset} ${testName}`);
44
- if (errorMessage) {
45
- console.log(` ${colors.dim}${errorMessage}${colors.reset}`);
46
- }
47
- failed++;
48
- }
49
- }
50
-
51
- // ---------------------------------------------------------------------------
52
- // Helpers
53
- // ---------------------------------------------------------------------------
54
-
55
- const CONTENT_DIR = '/project/src/content/docs';
56
- const STD_FILE = { path: '/project/src/content/docs/guide/intro.md' };
57
- const STD_OPTS = { contentDir: CONTENT_DIR };
58
- const BASE = '/BMAD-METHOD/';
59
-
60
- function transform(tree, file, options = {}) {
61
- const plugin = rehypeMarkdownLinks(options);
62
- plugin(tree, file);
63
- return tree;
64
- }
65
-
66
- function transformBase(tree, options = {}) {
67
- const plugin = rehypeBasePaths(options);
68
- plugin(tree);
69
- return tree;
70
- }
71
-
72
- function makeAnchorTree(href) {
73
- return {
74
- type: 'root',
75
- children: [
76
- {
77
- type: 'element',
78
- tagName: 'a',
79
- properties: { href },
80
- children: [{ type: 'text', value: 'link' }],
81
- },
82
- ],
83
- };
84
- }
85
-
86
- function makeElementTree(tagName, properties) {
87
- return {
88
- type: 'root',
89
- children: [
90
- {
91
- type: 'element',
92
- tagName,
93
- properties: { ...properties },
94
- children: [],
95
- },
96
- ],
97
- };
98
- }
99
-
100
- function getHref(tree) {
101
- return tree.children[0].properties.href;
102
- }
103
-
104
- function getSrc(tree) {
105
- return tree.children[0].properties.src;
106
- }
107
-
108
- function getRawValue(tree) {
109
- return tree.children[0].value;
110
- }
111
-
112
- // ---------------------------------------------------------------------------
113
- // Test Suite
114
- // ---------------------------------------------------------------------------
115
-
116
- function runTests() {
117
- console.log(`${colors.cyan}========================================`);
118
- console.log('Rehype Plugin Tests');
119
- console.log(`========================================${colors.reset}\n`);
120
-
121
- // ============================================================
122
- // findFirstDelimiter helper
123
- // ============================================================
124
- console.log(`${colors.yellow}findFirstDelimiter helper (8 tests)${colors.reset}\n`);
125
-
126
- assert(findFirstDelimiter('page') === -1, 'No delimiters returns -1', `Expected -1, got ${findFirstDelimiter('page')}`);
127
-
128
- assert(findFirstDelimiter('page.md?v=1') === 7, 'Only ? returns its index (7)', `Expected 7, got ${findFirstDelimiter('page.md?v=1')}`);
129
-
130
- assert(findFirstDelimiter('page.md#sec') === 7, 'Only # returns its index (7)', `Expected 7, got ${findFirstDelimiter('page.md#sec')}`);
131
-
132
- assert(
133
- findFirstDelimiter('page.md?v=1#sec') === 7,
134
- '? before # returns index of ?',
135
- `Expected 7, got ${findFirstDelimiter('page.md?v=1#sec')}`,
136
- );
137
-
138
- assert(
139
- findFirstDelimiter('page.md#sec?v=1') === 7,
140
- '# before ? returns index of #',
141
- `Expected 7, got ${findFirstDelimiter('page.md#sec?v=1')}`,
142
- );
143
-
144
- assert(findFirstDelimiter('') === -1, 'Empty string returns -1', `Expected -1, got ${findFirstDelimiter('')}`);
145
-
146
- assert(findFirstDelimiter('#top') === 0, '# at position 0 returns 0', `Expected 0, got ${findFirstDelimiter('#top')}`);
147
-
148
- assert(findFirstDelimiter('?q=1') === 0, '? at position 0 returns 0', `Expected 0, got ${findFirstDelimiter('?q=1')}`);
149
-
150
- console.log('');
151
-
152
- // ============================================================
153
- // detectContentDir helper
154
- // ============================================================
155
- console.log(`${colors.yellow}detectContentDir helper (6 tests)${colors.reset}\n`);
156
-
157
- assert(
158
- detectContentDir('/project/src/content/docs/guide/intro.md') === '/project/src/content/docs',
159
- 'Standard path finds content dir',
160
- `Got ${detectContentDir('/project/src/content/docs/guide/intro.md')}`,
161
- );
162
-
163
- assert(
164
- detectContentDir('/some/random/path/file.md') === null,
165
- 'No match returns null',
166
- `Got ${detectContentDir('/some/random/path/file.md')}`,
167
- );
168
-
169
- assert(detectContentDir('/src/content') === null, 'Too few segments returns null', `Got ${detectContentDir('/src/content')}`);
170
-
171
- assert(
172
- detectContentDir('/src/content/docs') === '/src/content/docs',
173
- 'Exactly 3 matching segments returns match',
174
- `Got ${detectContentDir('/src/content/docs')}`,
175
- );
176
-
177
- assert(
178
- detectContentDir('/a/src/content/docs/nested/src/content/docs/deep/file.md') === '/a/src/content/docs/nested/src/content/docs',
179
- 'Nested double match finds innermost',
180
- `Got ${detectContentDir('/a/src/content/docs/nested/src/content/docs/deep/file.md')}`,
181
- );
182
-
183
- assert(detectContentDir('') === null, 'Empty string returns null', `Got ${detectContentDir('')}`);
184
-
185
- console.log('');
186
-
187
- // ============================================================
188
- // Transformer skip conditions
189
- // ============================================================
190
- console.log(`${colors.yellow}Transformer skip conditions (21 tests)${colors.reset}\n`);
191
-
192
- {
193
- const tree = makeAnchorTree('https://example.com');
194
- transform(tree, STD_FILE, STD_OPTS);
195
- assert(getHref(tree) === 'https://example.com', 'External https URL unchanged', `Got ${getHref(tree)}`);
196
- }
197
-
198
- {
199
- const tree = makeAnchorTree('http://example.com');
200
- transform(tree, STD_FILE, STD_OPTS);
201
- assert(getHref(tree) === 'http://example.com', 'External http URL unchanged', `Got ${getHref(tree)}`);
202
- }
203
-
204
- {
205
- const tree = makeAnchorTree('//cdn.example.com/path');
206
- transform(tree, STD_FILE, STD_OPTS);
207
- assert(getHref(tree) === '//cdn.example.com/path', 'Protocol-relative // unchanged', `Got ${getHref(tree)}`);
208
- }
209
-
210
- {
211
- const tree = makeAnchorTree('mailto:user@example.com');
212
- transform(tree, STD_FILE, STD_OPTS);
213
- assert(getHref(tree) === 'mailto:user@example.com', 'mailto: unchanged', `Got ${getHref(tree)}`);
214
- }
215
-
216
- {
217
- const tree = makeAnchorTree('tel:+15551234567');
218
- transform(tree, STD_FILE, STD_OPTS);
219
- assert(getHref(tree) === 'tel:+15551234567', 'tel: unchanged', `Got ${getHref(tree)}`);
220
- }
221
-
222
- {
223
- const tree = makeAnchorTree('./page.html');
224
- transform(tree, STD_FILE, STD_OPTS);
225
- assert(getHref(tree) === './page.html', '.html unchanged', `Got ${getHref(tree)}`);
226
- }
227
-
228
- {
229
- const tree = makeAnchorTree('./doc.pdf');
230
- transform(tree, STD_FILE, STD_OPTS);
231
- assert(getHref(tree) === './doc.pdf', '.pdf unchanged', `Got ${getHref(tree)}`);
232
- }
233
-
234
- {
235
- const tree = makeAnchorTree('./page.mdx');
236
- transform(tree, STD_FILE, STD_OPTS);
237
- assert(getHref(tree) === './page.mdx', '.mdx unchanged', `Got ${getHref(tree)}`);
238
- }
239
-
240
- {
241
- const tree = makeAnchorTree('#section');
242
- transform(tree, STD_FILE, STD_OPTS);
243
- assert(getHref(tree) === '#section', '#section unchanged', `Got ${getHref(tree)}`);
244
- }
245
-
246
- {
247
- const tree = makeAnchorTree('?page=2');
248
- transform(tree, STD_FILE, STD_OPTS);
249
- assert(getHref(tree) === '?page=2', '?page=2 unchanged', `Got ${getHref(tree)}`);
250
- }
251
-
252
- {
253
- const tree = makeAnchorTree('');
254
- transform(tree, STD_FILE, STD_OPTS);
255
- assert(getHref(tree) === '', 'Empty href unchanged', `Got ${getHref(tree)}`);
256
- }
257
-
258
- {
259
- // Non-anchor element (div) unchanged
260
- const tree = {
261
- type: 'root',
262
- children: [
263
- {
264
- type: 'element',
265
- tagName: 'div',
266
- properties: { href: 'page.md' },
267
- children: [],
268
- },
269
- ],
270
- };
271
- transform(tree, STD_FILE, STD_OPTS);
272
- assert(tree.children[0].properties.href === 'page.md', 'Non-anchor element (div) unchanged', `Got ${tree.children[0].properties.href}`);
273
- }
274
-
275
- {
276
- // Anchor without properties (no crash)
277
- const tree = {
278
- type: 'root',
279
- children: [{ type: 'element', tagName: 'a', children: [] }],
280
- };
281
- let threw = false;
282
- try {
283
- transform(tree, STD_FILE, STD_OPTS);
284
- } catch {
285
- threw = true;
286
- }
287
- assert(!threw, 'Anchor without properties unchanged (no crash)');
288
- }
289
-
290
- {
291
- // Anchor with numeric href
292
- const tree = {
293
- type: 'root',
294
- children: [
295
- {
296
- type: 'element',
297
- tagName: 'a',
298
- properties: { href: 42 },
299
- children: [],
300
- },
301
- ],
302
- };
303
- transform(tree, STD_FILE, STD_OPTS);
304
- assert(tree.children[0].properties.href === 42, 'Anchor with numeric href unchanged', `Got ${tree.children[0].properties.href}`);
305
- }
306
-
307
- {
308
- // Anchor with null href
309
- const tree = {
310
- type: 'root',
311
- children: [
312
- {
313
- type: 'element',
314
- tagName: 'a',
315
- properties: { href: null },
316
- children: [],
317
- },
318
- ],
319
- };
320
- transform(tree, STD_FILE, STD_OPTS);
321
- assert(tree.children[0].properties.href === null, 'Anchor with null href unchanged', `Got ${tree.children[0].properties.href}`);
322
- }
323
-
324
- {
325
- // Anchor with undefined href
326
- const tree = {
327
- type: 'root',
328
- children: [
329
- {
330
- type: 'element',
331
- tagName: 'a',
332
- properties: { href: undefined },
333
- children: [],
334
- },
335
- ],
336
- };
337
- transform(tree, STD_FILE, STD_OPTS);
338
- assert(
339
- tree.children[0].properties.href === undefined,
340
- 'Anchor with undefined href unchanged',
341
- `Got ${tree.children[0].properties.href}`,
342
- );
343
- }
344
-
345
- {
346
- // Target outside content root unchanged
347
- const tree = makeAnchorTree('../../../../../../outside.md');
348
- transform(tree, STD_FILE, STD_OPTS);
349
- assert(getHref(tree) === '../../../../../../outside.md', 'Target outside content root unchanged', `Got ${getHref(tree)}`);
350
- }
351
-
352
- {
353
- // No file path -> no processing
354
- const tree = makeAnchorTree('sibling.md');
355
- transform(tree, { path: undefined }, STD_OPTS);
356
- assert(getHref(tree) === 'sibling.md', 'No file path -> no processing', `Got ${getHref(tree)}`);
357
- }
358
-
359
- {
360
- // Empty string path -> no processing
361
- const tree = makeAnchorTree('sibling.md');
362
- transform(tree, { path: '' }, STD_OPTS);
363
- assert(getHref(tree) === 'sibling.md', 'Empty string path -> no processing', `Got ${getHref(tree)}`);
364
- }
365
-
366
- {
367
- // page.MD (uppercase) unchanged
368
- const tree = makeAnchorTree('page.MD');
369
- transform(tree, STD_FILE, STD_OPTS);
370
- assert(getHref(tree) === 'page.MD', 'page.MD (uppercase) unchanged', `Got ${getHref(tree)}`);
371
- }
372
-
373
- {
374
- // page.Md (mixed case) unchanged
375
- const tree = makeAnchorTree('page.Md');
376
- transform(tree, STD_FILE, STD_OPTS);
377
- assert(getHref(tree) === 'page.Md', 'page.Md (mixed case) unchanged', `Got ${getHref(tree)}`);
378
- }
379
-
380
- console.log('');
381
-
382
- // ============================================================
383
- // Error conditions
384
- // ============================================================
385
- console.log(`${colors.yellow}Error conditions (1 test)${colors.reset}\n`);
386
-
387
- {
388
- // No content dir + no contentDir option -> throws
389
- const tree = makeAnchorTree('sibling.md');
390
- const file = { path: '/some/random/path/file.md' };
391
- let threw = false;
392
- let errorMsg = '';
393
- try {
394
- transform(tree, file, {});
395
- } catch (error) {
396
- threw = true;
397
- errorMsg = error.message;
398
- }
399
- assert(
400
- threw && errorMsg.includes('Could not detect content directory'),
401
- 'No content dir + no contentDir option throws',
402
- `threw=${threw}, msg=${errorMsg}`,
403
- );
404
- }
405
-
406
- console.log('');
407
-
408
- // ============================================================
409
- // Path resolution
410
- // ============================================================
411
- console.log(`${colors.yellow}Path resolution (7 tests)${colors.reset}\n`);
412
-
413
- {
414
- const tree = makeAnchorTree('sibling.md');
415
- transform(tree, STD_FILE, STD_OPTS);
416
- assert(getHref(tree) === '/guide/sibling/', 'Bare relative sibling.md -> /guide/sibling/', `Got ${getHref(tree)}`);
417
- }
418
-
419
- {
420
- const tree = makeAnchorTree('./sibling.md');
421
- transform(tree, STD_FILE, STD_OPTS);
422
- assert(getHref(tree) === '/guide/sibling/', 'Dot-slash ./sibling.md -> /guide/sibling/', `Got ${getHref(tree)}`);
423
- }
424
-
425
- {
426
- const tree = makeAnchorTree('../other/page.md');
427
- transform(tree, STD_FILE, STD_OPTS);
428
- assert(getHref(tree) === '/other/page/', 'Parent ../other/page.md -> /other/page/', `Got ${getHref(tree)}`);
429
- }
430
-
431
- {
432
- // Use a file two levels deep so ../../ still stays inside content root
433
- const deepFile = {
434
- path: '/project/src/content/docs/guide/sub/intro.md',
435
- };
436
- const tree = makeAnchorTree('../../root-level.md');
437
- transform(tree, deepFile, STD_OPTS);
438
- assert(getHref(tree) === '/root-level/', 'Deep parent ../../root-level.md -> /root-level/', `Got ${getHref(tree)}`);
439
- }
440
-
441
- {
442
- const tree = makeAnchorTree('./sub/deep/page.md');
443
- transform(tree, STD_FILE, STD_OPTS);
444
- assert(getHref(tree) === '/guide/sub/deep/page/', 'Into subdir ./sub/deep/page.md -> /guide/sub/deep/page/', `Got ${getHref(tree)}`);
445
- }
446
-
447
- {
448
- const tree = makeAnchorTree('/docs/guide/page.md');
449
- transform(tree, STD_FILE, STD_OPTS);
450
- assert(getHref(tree) === '/guide/page/', 'Absolute /docs/guide/page.md -> /guide/page/', `Got ${getHref(tree)}`);
451
- }
452
-
453
- {
454
- const tree = makeAnchorTree('/guide/page.md');
455
- transform(tree, STD_FILE, STD_OPTS);
456
- assert(getHref(tree) === '/guide/page/', 'Absolute /guide/page.md -> /guide/page/', `Got ${getHref(tree)}`);
457
- }
458
-
459
- console.log('');
460
-
461
- // ============================================================
462
- // Index handling
463
- // ============================================================
464
- console.log(`${colors.yellow}Index handling (5 tests)${colors.reset}\n`);
465
-
466
- {
467
- const tree = makeAnchorTree('index.md');
468
- transform(tree, STD_FILE, STD_OPTS);
469
- assert(getHref(tree) === '/guide/', 'index.md -> /guide/', `Got ${getHref(tree)}`);
470
- }
471
-
472
- {
473
- const tree = makeAnchorTree('./sub/index.md');
474
- transform(tree, STD_FILE, STD_OPTS);
475
- assert(getHref(tree) === '/guide/sub/', './sub/index.md -> /guide/sub/', `Got ${getHref(tree)}`);
476
- }
477
-
478
- {
479
- const tree = makeAnchorTree('../index.md');
480
- transform(tree, STD_FILE, STD_OPTS);
481
- assert(getHref(tree) === '/', '../index.md -> /', `Got ${getHref(tree)}`);
482
- }
483
-
484
- {
485
- // Root index.md: file at content root
486
- const rootFile = {
487
- path: '/project/src/content/docs/intro.md',
488
- };
489
- const tree = makeAnchorTree('index.md');
490
- transform(tree, rootFile, STD_OPTS);
491
- assert(getHref(tree) === '/', 'Root index.md -> /', `Got ${getHref(tree)}`);
492
- }
493
-
494
- {
495
- const tree = makeAnchorTree('/docs/index.md');
496
- transform(tree, STD_FILE, STD_OPTS);
497
- assert(getHref(tree) === '/', '/docs/index.md -> /', `Got ${getHref(tree)}`);
498
- }
499
-
500
- console.log('');
501
-
502
- // ============================================================
503
- // Query/hash preservation
504
- // ============================================================
505
- console.log(`${colors.yellow}Query/hash preservation (5 tests)${colors.reset}\n`);
506
-
507
- {
508
- const tree = makeAnchorTree('page.md#section');
509
- transform(tree, STD_FILE, STD_OPTS);
510
- assert(getHref(tree) === '/guide/page/#section', 'page.md#section -> /guide/page/#section', `Got ${getHref(tree)}`);
511
- }
512
-
513
- {
514
- const tree = makeAnchorTree('page.md?foo=bar');
515
- transform(tree, STD_FILE, STD_OPTS);
516
- assert(getHref(tree) === '/guide/page/?foo=bar', 'page.md?foo=bar -> /guide/page/?foo=bar', `Got ${getHref(tree)}`);
517
- }
518
-
519
- {
520
- const tree = makeAnchorTree('page.md?foo=bar#section');
521
- transform(tree, STD_FILE, STD_OPTS);
522
- assert(
523
- getHref(tree) === '/guide/page/?foo=bar#section',
524
- 'page.md?foo=bar#section -> /guide/page/?foo=bar#section',
525
- `Got ${getHref(tree)}`,
526
- );
527
- }
528
-
529
- {
530
- const tree = makeAnchorTree('page.md#section?foo=bar');
531
- transform(tree, STD_FILE, STD_OPTS);
532
- assert(
533
- getHref(tree) === '/guide/page/#section?foo=bar',
534
- 'page.md#section?foo=bar -> /guide/page/#section?foo=bar',
535
- `Got ${getHref(tree)}`,
536
- );
537
- }
538
-
539
- {
540
- const tree = makeAnchorTree('index.md#top');
541
- transform(tree, STD_FILE, STD_OPTS);
542
- assert(getHref(tree) === '/guide/#top', 'index.md#top -> /guide/#top', `Got ${getHref(tree)}`);
543
- }
544
-
545
- console.log('');
546
-
547
- // ============================================================
548
- // Base path
549
- // ============================================================
550
- console.log(`${colors.yellow}Base path (4 tests)${colors.reset}\n`);
551
-
552
- {
553
- const tree = makeAnchorTree('page.md');
554
- transform(tree, STD_FILE, { ...STD_OPTS, base: '/' });
555
- assert(getHref(tree) === '/guide/page/', 'Base / -> /guide/page/', `Got ${getHref(tree)}`);
556
- }
557
-
558
- {
559
- const tree = makeAnchorTree('page.md');
560
- transform(tree, STD_FILE, { ...STD_OPTS, base: '/BMAD-METHOD/' });
561
- assert(getHref(tree) === '/BMAD-METHOD/guide/page/', 'Base /BMAD-METHOD/ -> /BMAD-METHOD/guide/page/', `Got ${getHref(tree)}`);
562
- }
563
-
564
- {
565
- const tree = makeAnchorTree('page.md');
566
- transform(tree, STD_FILE, { ...STD_OPTS, base: '/BMAD-METHOD' });
567
- assert(getHref(tree) === '/BMAD-METHOD/guide/page/', 'Base /BMAD-METHOD (no trailing slash) -> same result', `Got ${getHref(tree)}`);
568
- }
569
-
570
- {
571
- const tree = makeAnchorTree('page.md');
572
- transform(tree, STD_FILE, { ...STD_OPTS, base: '/org/repo/docs/' });
573
- assert(getHref(tree) === '/org/repo/docs/guide/page/', 'Base /org/repo/docs/ -> /org/repo/docs/guide/page/', `Got ${getHref(tree)}`);
574
- }
575
-
576
- console.log('');
577
-
578
- // ============================================================
579
- // Normalization
580
- // ============================================================
581
- console.log(`${colors.yellow}Normalization (3 tests)${colors.reset}\n`);
582
-
583
- {
584
- const tree = makeAnchorTree('page.md');
585
- transform(tree, STD_FILE, { ...STD_OPTS, base: '/' });
586
- assert(!getHref(tree).includes('//'), 'No // in output for root base', `Got ${getHref(tree)}`);
587
- }
588
-
589
- {
590
- const tree = makeAnchorTree('page.md');
591
- transform(tree, STD_FILE, { ...STD_OPTS, base: '/BMAD-METHOD/' });
592
- assert(!getHref(tree).includes('//'), 'No // in output for subpath base', `Got ${getHref(tree)}`);
593
- }
594
-
595
- {
596
- const tree = makeAnchorTree('page.md#section');
597
- transform(tree, STD_FILE, STD_OPTS);
598
- const href = getHref(tree);
599
- const hashIndex = href.indexOf('#');
600
- assert(href[hashIndex - 1] === '/', 'Trailing slash before suffix', `Got ${href}`);
601
- }
602
-
603
- console.log('');
604
-
605
- // ============================================================
606
- // Edge cases
607
- // ============================================================
608
- console.log(`${colors.yellow}Edge cases (5 tests)${colors.reset}\n`);
609
-
610
- {
611
- const tree = makeAnchorTree('v2.0.md');
612
- transform(tree, STD_FILE, STD_OPTS);
613
- assert(getHref(tree) === '/guide/v2.0/', 'v2.0.md -> /guide/v2.0/', `Got ${getHref(tree)}`);
614
- }
615
-
616
- {
617
- const tree = makeAnchorTree('file.test.md');
618
- transform(tree, STD_FILE, STD_OPTS);
619
- assert(getHref(tree) === '/guide/file.test/', 'file.test.md -> /guide/file.test/', `Got ${getHref(tree)}`);
620
- }
621
-
622
- {
623
- const tree = makeAnchorTree('markdown-guide/foo.md');
624
- transform(tree, STD_FILE, STD_OPTS);
625
- assert(getHref(tree) === '/guide/markdown-guide/foo/', 'markdown-guide/foo.md -> /guide/markdown-guide/foo/', `Got ${getHref(tree)}`);
626
- }
627
-
628
- {
629
- // .md bare -> processes (not left as ".md")
630
- const tree = makeAnchorTree('.md');
631
- transform(tree, STD_FILE, STD_OPTS);
632
- assert(getHref(tree) !== '.md', '.md bare -> processes (not left as ".md")', `Got ${getHref(tree)}`);
633
- }
634
-
635
- {
636
- const tree = makeAnchorTree('\u00FCber-guide.md');
637
- transform(tree, STD_FILE, STD_OPTS);
638
- assert(getHref(tree) === '/guide/\u00FCber-guide/', '\u00FCber-guide.md -> /guide/\u00FCber-guide/', `Got ${getHref(tree)}`);
639
- }
640
-
641
- console.log('');
642
-
643
- // ============================================================
644
- // rehype-base-paths: Option handling
645
- // ============================================================
646
- console.log(`${colors.yellow}rehype-base-paths: Option handling (5 tests)${colors.reset}\n`);
647
-
648
- {
649
- const tree = makeAnchorTree('/page/');
650
- transformBase(tree, {});
651
- assert(getHref(tree) === '/page/', 'Default no-op for absolute href', `Got ${getHref(tree)}`);
652
- }
653
-
654
- {
655
- const tree = makeAnchorTree('/page/');
656
- transformBase(tree, { base: '/BMAD-METHOD/' });
657
- assert(getHref(tree) === '/BMAD-METHOD/page/', 'Base /BMAD-METHOD/ prefixes', `Got ${getHref(tree)}`);
658
- }
659
-
660
- {
661
- const tree = makeAnchorTree('/page/');
662
- transformBase(tree, { base: '/BMAD-METHOD' });
663
- assert(getHref(tree) === '/BMAD-METHOD/page/', 'Base /BMAD-METHOD normalizes (adds trailing slash)', `Got ${getHref(tree)}`);
664
- }
665
-
666
- {
667
- const tree = makeAnchorTree('/page/');
668
- transformBase(tree, { base: '' });
669
- assert(getHref(tree) === '/page/', 'Empty string falls back to / (no-op)', `Got ${getHref(tree)}`);
670
- }
671
-
672
- {
673
- const tree = makeAnchorTree('/page/');
674
- transformBase(tree, { base: '/' });
675
- assert(getHref(tree) === '/page/', 'Root / is no-op', `Got ${getHref(tree)}`);
676
- }
677
-
678
- console.log('');
679
-
680
- // ============================================================
681
- // rehype-base-paths: Element rewriting
682
- // ============================================================
683
- console.log(`${colors.yellow}rehype-base-paths: Element rewriting (9 tests)${colors.reset}\n`);
684
-
685
- {
686
- const tree = makeAnchorTree('/page/');
687
- transformBase(tree, { base: BASE });
688
- assert(getHref(tree) === '/BMAD-METHOD/page/', 'a[href] prefixed', `Got ${getHref(tree)}`);
689
- }
690
-
691
- {
692
- const tree = makeElementTree('img', { src: '/img/logo.png' });
693
- transformBase(tree, { base: BASE });
694
- assert(getSrc(tree) === '/BMAD-METHOD/img/logo.png', 'img[src] prefixed', `Got ${getSrc(tree)}`);
695
- }
696
-
697
- {
698
- const tree = makeElementTree('link', { href: '/styles/main.css' });
699
- transformBase(tree, { base: BASE });
700
- assert(
701
- tree.children[0].properties.href === '/BMAD-METHOD/styles/main.css',
702
- 'link[href] prefixed',
703
- `Got ${tree.children[0].properties.href}`,
704
- );
705
- }
706
-
707
- {
708
- const tree = makeElementTree('script', { src: '/js/app.js' });
709
- transformBase(tree, { base: BASE });
710
- assert(getSrc(tree) === '/js/app.js', 'script[src] NOT prefixed (not in tag list)', `Got ${getSrc(tree)}`);
711
- }
712
-
713
- {
714
- const tree = makeElementTree('video', { src: '/media/intro.mp4' });
715
- transformBase(tree, { base: BASE });
716
- assert(getSrc(tree) === '/BMAD-METHOD/media/intro.mp4', 'video[src] prefixed', `Got ${getSrc(tree)}`);
717
- }
718
-
719
- {
720
- const tree = makeElementTree('audio', { src: '/media/clip.mp3' });
721
- transformBase(tree, { base: BASE });
722
- assert(getSrc(tree) === '/BMAD-METHOD/media/clip.mp3', 'audio[src] prefixed', `Got ${getSrc(tree)}`);
723
- }
724
-
725
- {
726
- const tree = makeElementTree('iframe', { src: '/embed/widget' });
727
- transformBase(tree, { base: BASE });
728
- assert(getSrc(tree) === '/BMAD-METHOD/embed/widget', 'iframe[src] prefixed', `Got ${getSrc(tree)}`);
729
- }
730
-
731
- {
732
- const tree = makeElementTree('area', { href: '/map/region' });
733
- transformBase(tree, { base: BASE });
734
- assert(
735
- tree.children[0].properties.href === '/map/region',
736
- 'area[href] NOT prefixed (not in tag list)',
737
- `Got ${tree.children[0].properties.href}`,
738
- );
739
- }
740
-
741
- {
742
- const tree = makeElementTree('source', { src: '/media/alt.mp4' });
743
- transformBase(tree, { base: BASE });
744
- assert(getSrc(tree) === '/BMAD-METHOD/media/alt.mp4', 'source[src] prefixed', `Got ${getSrc(tree)}`);
745
- }
746
-
747
- console.log('');
748
-
749
- // ============================================================
750
- // rehype-base-paths: No-op base /
751
- // ============================================================
752
- console.log(`${colors.yellow}rehype-base-paths: No-op base / (2 tests)${colors.reset}\n`);
753
-
754
- {
755
- const tree = makeAnchorTree('/page/');
756
- transformBase(tree, { base: '/' });
757
- assert(getHref(tree) === '/page/', 'a[href] unchanged with base /', `Got ${getHref(tree)}`);
758
- }
759
-
760
- {
761
- const tree = makeElementTree('img', { src: '/img/logo.png' });
762
- transformBase(tree, { base: '/' });
763
- assert(getSrc(tree) === '/img/logo.png', 'img[src] unchanged with base /', `Got ${getSrc(tree)}`);
764
- }
765
-
766
- console.log('');
767
-
768
- // ============================================================
769
- // rehype-base-paths: Skip conditions
770
- // ============================================================
771
- console.log(`${colors.yellow}rehype-base-paths: Skip conditions (10 tests)${colors.reset}\n`);
772
-
773
- {
774
- const tree = makeAnchorTree('//cdn.example.com/path');
775
- transformBase(tree, { base: BASE });
776
- assert(getHref(tree) === '//cdn.example.com/path', 'Protocol-relative skipped', `Got ${getHref(tree)}`);
777
- }
778
-
779
- {
780
- const tree = makeAnchorTree('https://example.com');
781
- transformBase(tree, { base: BASE });
782
- assert(getHref(tree) === 'https://example.com', 'External https skipped', `Got ${getHref(tree)}`);
783
- }
784
-
785
- {
786
- const tree = makeAnchorTree('http://example.com');
787
- transformBase(tree, { base: BASE });
788
- assert(getHref(tree) === 'http://example.com', 'External http skipped', `Got ${getHref(tree)}`);
789
- }
790
-
791
- {
792
- const tree = makeAnchorTree('data:text/html,hello');
793
- transformBase(tree, { base: BASE });
794
- assert(getHref(tree) === 'data:text/html,hello', 'data: URI skipped', `Got ${getHref(tree)}`);
795
- }
796
-
797
- {
798
- const tree = makeAnchorTree('#section');
799
- transformBase(tree, { base: BASE });
800
- assert(getHref(tree) === '#section', '#section skipped', `Got ${getHref(tree)}`);
801
- }
802
-
803
- {
804
- const tree = makeAnchorTree('');
805
- transformBase(tree, { base: BASE });
806
- assert(getHref(tree) === '', 'Empty href skipped', `Got ${getHref(tree)}`);
807
- }
808
-
809
- {
810
- const tree = makeAnchorTree('/BMAD-METHOD/page/');
811
- transformBase(tree, { base: BASE });
812
- assert(getHref(tree) === '/BMAD-METHOD/page/', 'Already prefixed skipped', `Got ${getHref(tree)}`);
813
- }
814
-
815
- {
816
- const tree = makeAnchorTree('relative/path');
817
- transformBase(tree, { base: BASE });
818
- assert(getHref(tree) === 'relative/path', 'Relative path skipped', `Got ${getHref(tree)}`);
819
- }
820
-
821
- {
822
- // Non-target element (button with href-like attribute via properties)
823
- const tree = makeElementTree('button', { href: '/page/' });
824
- transformBase(tree, { base: BASE });
825
- assert(tree.children[0].properties.href === '/page/', 'Non-target element skipped', `Got ${tree.children[0].properties.href}`);
826
- }
827
-
828
- {
829
- // Non-target attribute (data-url on an img)
830
- const tree = makeElementTree('img', {
831
- src: '/img/logo.png',
832
- 'data-url': '/some/path',
833
- });
834
- transformBase(tree, { base: BASE });
835
- assert(
836
- tree.children[0].properties['data-url'] === '/some/path',
837
- 'Non-target attribute (data-url) skipped',
838
- `Got ${tree.children[0].properties['data-url']}`,
839
- );
840
- }
841
-
842
- console.log('');
843
-
844
- // ============================================================
845
- // rehype-base-paths: Anchor .md handling
846
- // ============================================================
847
- console.log(`${colors.yellow}rehype-base-paths: Anchor .md handling (4 tests)${colors.reset}\n`);
848
-
849
- {
850
- const tree = makeAnchorTree('/docs/guide/page.md');
851
- transformBase(tree, { base: BASE });
852
- assert(getHref(tree) === '/docs/guide/page.md', '.md href skipped', `Got ${getHref(tree)}`);
853
- }
854
-
855
- {
856
- const tree = makeAnchorTree('/docs/guide/page.md#section');
857
- transformBase(tree, { base: BASE });
858
- assert(getHref(tree) === '/docs/guide/page.md#section', '.md#section skipped', `Got ${getHref(tree)}`);
859
- }
860
-
861
- {
862
- const tree = makeAnchorTree('/docs/guide/page.md?v=1');
863
- transformBase(tree, { base: BASE });
864
- assert(getHref(tree) === '/docs/guide/page.md?v=1', '.md?v=1 skipped', `Got ${getHref(tree)}`);
865
- }
866
-
867
- {
868
- const tree = makeAnchorTree('/docs/index.md');
869
- transformBase(tree, { base: BASE });
870
- assert(getHref(tree) === '/docs/index.md', 'index.md skipped', `Got ${getHref(tree)}`);
871
- }
872
-
873
- console.log('');
874
-
875
- // ============================================================
876
- // rehype-base-paths: srcset
877
- // ============================================================
878
- console.log(`${colors.yellow}rehype-base-paths: srcset (1 test)${colors.reset}\n`);
879
-
880
- {
881
- const tree = makeElementTree('img', {
882
- src: '/img/logo.png',
883
- srcset: '/img/logo-2x.png 2x',
884
- });
885
- transformBase(tree, { base: BASE });
886
- assert(
887
- tree.children[0].properties.srcset === '/img/logo-2x.png 2x',
888
- 'srcset not handled by plugin',
889
- `Got ${tree.children[0].properties.srcset}`,
890
- );
891
- }
892
-
893
- console.log('');
894
-
895
- // ============================================================
896
- // rehype-base-paths: Raw HTML
897
- // ============================================================
898
- console.log(`${colors.yellow}rehype-base-paths: Raw HTML (7 tests)${colors.reset}\n`);
899
-
900
- {
901
- const tree = {
902
- type: 'root',
903
- children: [{ type: 'raw', value: '<img src="/img/logo.png">' }],
904
- };
905
- transformBase(tree, { base: BASE });
906
- assert(getRawValue(tree) === '<img src="/BMAD-METHOD/img/logo.png">', 'Raw img src rewritten', `Got ${getRawValue(tree)}`);
907
- }
908
-
909
- {
910
- const tree = {
911
- type: 'root',
912
- children: [{ type: 'raw', value: '<a href="/page/">link</a>' }],
913
- };
914
- transformBase(tree, { base: BASE });
915
- assert(getRawValue(tree) === '<a href="/BMAD-METHOD/page/">link</a>', 'Raw a href rewritten', `Got ${getRawValue(tree)}`);
916
- }
917
-
918
- {
919
- const tree = {
920
- type: 'root',
921
- children: [{ type: 'raw', value: '<img src="//cdn.example.com/img.png">' }],
922
- };
923
- transformBase(tree, { base: BASE });
924
- assert(getRawValue(tree) === '<img src="//cdn.example.com/img.png">', 'Raw protocol-relative unchanged', `Got ${getRawValue(tree)}`);
925
- }
926
-
927
- {
928
- const tree = {
929
- type: 'root',
930
- children: [
931
- {
932
- type: 'raw',
933
- value: '<img src="/BMAD-METHOD/img/logo.png">',
934
- },
935
- ],
936
- };
937
- transformBase(tree, { base: BASE });
938
- assert(getRawValue(tree) === '<img src="/BMAD-METHOD/img/logo.png">', 'Raw already prefixed unchanged', `Got ${getRawValue(tree)}`);
939
- }
940
-
941
- {
942
- const tree = {
943
- type: 'root',
944
- children: [
945
- {
946
- type: 'raw',
947
- value: '<a href="/page/"><img src="/img/logo.png"></a>',
948
- },
949
- ],
950
- };
951
- transformBase(tree, { base: BASE });
952
- assert(
953
- getRawValue(tree) === '<a href="/BMAD-METHOD/page/"><img src="/BMAD-METHOD/img/logo.png"></a>',
954
- 'Raw multiple attributes rewritten',
955
- `Got ${getRawValue(tree)}`,
956
- );
957
- }
958
-
959
- {
960
- const tree = {
961
- type: 'root',
962
- children: [
963
- {
964
- type: 'raw',
965
- value: '<a href="https://example.com">external</a>',
966
- },
967
- ],
968
- };
969
- transformBase(tree, { base: BASE });
970
- assert(getRawValue(tree) === '<a href="https://example.com">external</a>', 'Raw external URL unchanged', `Got ${getRawValue(tree)}`);
971
- }
972
-
973
- {
974
- // Base / skips raw visit entirely
975
- const tree = {
976
- type: 'root',
977
- children: [{ type: 'raw', value: '<img src="/img/logo.png">' }],
978
- };
979
- transformBase(tree, { base: '/' });
980
- assert(getRawValue(tree) === '<img src="/img/logo.png">', 'Base / skips raw visit', `Got ${getRawValue(tree)}`);
981
- }
982
-
983
- console.log('');
984
-
985
- // ============================================================
986
- // Integration: both plugins together
987
- // ============================================================
988
- console.log(`${colors.yellow}Integration: both plugins together (4 tests)${colors.reset}\n`);
989
-
990
- {
991
- // ./sibling.md through both -> no double prefix
992
- const tree = makeAnchorTree('./sibling.md');
993
- transform(tree, STD_FILE, { ...STD_OPTS, base: BASE });
994
- transformBase(tree, { base: BASE });
995
- const href = getHref(tree);
996
- assert(href === '/BMAD-METHOD/guide/sibling/', './sibling.md through both -> no double prefix', `Got ${href}`);
997
- }
998
-
999
- {
1000
- // img /img/logo.png -> only base-paths prefixes
1001
- const tree = makeElementTree('img', { src: '/img/logo.png' });
1002
- // markdown-links doesn't touch img elements, so just run base-paths
1003
- transformBase(tree, { base: BASE });
1004
- assert(getSrc(tree) === '/BMAD-METHOD/img/logo.png', 'img /img/logo.png -> only base-paths prefixes', `Got ${getSrc(tree)}`);
1005
- }
1006
-
1007
- {
1008
- // External -> both skip
1009
- const tree = makeAnchorTree('https://example.com');
1010
- transform(tree, STD_FILE, { ...STD_OPTS, base: BASE });
1011
- transformBase(tree, { base: BASE });
1012
- assert(getHref(tree) === 'https://example.com', 'External -> both skip', `Got ${getHref(tree)}`);
1013
- }
1014
-
1015
- {
1016
- // /page/ (non-.md) -> only base-paths prefixes
1017
- const tree = makeAnchorTree('/page/');
1018
- transform(tree, STD_FILE, { ...STD_OPTS, base: BASE });
1019
- transformBase(tree, { base: BASE });
1020
- assert(getHref(tree) === '/BMAD-METHOD/page/', '/page/ (non-.md) -> only base-paths prefixes', `Got ${getHref(tree)}`);
1021
- }
1022
-
1023
- console.log('');
1024
-
1025
- // ============================================================
1026
- // Summary
1027
- // ============================================================
1028
- console.log(`${colors.cyan}========================================`);
1029
- console.log('Test Results:');
1030
- console.log(` Passed: ${colors.green}${passed}${colors.reset}`);
1031
- console.log(` Failed: ${colors.red}${failed}${colors.reset}`);
1032
- console.log(`========================================${colors.reset}\n`);
1033
-
1034
- if (failed === 0) {
1035
- console.log(`${colors.green}All rehype plugin tests passed!${colors.reset}\n`);
1036
- process.exit(0);
1037
- } else {
1038
- console.log(`${colors.red}Some rehype plugin tests failed${colors.reset}\n`);
1039
- process.exit(1);
1040
- }
1041
- }
1042
-
1043
- // Run tests
1044
- try {
1045
- runTests();
1046
- } catch (error) {
1047
- console.error(`${colors.red}Test runner failed:${colors.reset}`, error.message);
1048
- console.error(error.stack);
1049
- process.exit(1);
1050
- }