@wrikka/create-docs 0.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.
- package/README.md +137 -0
- package/bin/cli.js +408 -0
- package/dist/index.cjs +20 -0
- package/dist/index.d.cts +475 -0
- package/dist/index.d.ts +475 -0
- package/dist/index.js +20 -0
- package/docs/_dir.yml +2 -0
- package/docs/config.md +75 -0
- package/docs/data-sources.md +88 -0
- package/docs/deploy.md +38 -0
- package/docs/features.md +51 -0
- package/docs/getting-started.md +42 -0
- package/docs/index.md +34 -0
- package/docs/markdown.md +154 -0
- package/docs/theming.md +53 -0
- package/package.json +142 -0
- package/scripts/generate-docs-index.ts +111 -0
- package/scripts/github-pull.ts +124 -0
- package/src/adapters/config/index.ts +1 -0
- package/src/adapters/config/node-config-loader.ts +36 -0
- package/src/adapters/fs/fs-docs-loader.ts +31 -0
- package/src/adapters/fs/index.ts +12 -0
- package/src/adapters/fs/memory-fs.ts +60 -0
- package/src/adapters/fs/node-fs.ts +116 -0
- package/src/adapters/git/index.ts +164 -0
- package/src/adapters/github/github-pull.ts +340 -0
- package/src/adapters/index.ts +6 -0
- package/src/adapters/velite.ts +112 -0
- package/src/adapters/vite/docs-watcher.ts +36 -0
- package/src/adapters/vite/index.ts +2 -0
- package/src/adapters/vite/polling-watcher.ts +89 -0
- package/src/code-block-enhancements/index.ts +3 -0
- package/src/config/index.ts +15 -0
- package/src/content/index.ts +11 -0
- package/src/content-validation/index.ts +5 -0
- package/src/index.ts +174 -0
- package/src/live-editor/index.ts +7 -0
- package/src/modules/analytics/application/plausible-adapter.ts +103 -0
- package/src/modules/analytics/application/umami-adapter.ts +109 -0
- package/src/modules/analytics/domain/events/index.ts +37 -0
- package/src/modules/analytics/domain/models/index.ts +35 -0
- package/src/modules/analytics/domain/operations/analytics-operations.ts +67 -0
- package/src/modules/analytics/domain/validators/index.ts +50 -0
- package/src/modules/analytics/index.ts +9 -0
- package/src/modules/analytics/ports/analytics-port.ts +17 -0
- package/src/modules/analytics/types/analytics.ts +16 -0
- package/src/modules/api-docs/application/type-docs-adapter.ts +34 -0
- package/src/modules/api-docs/domain/events/index.ts +42 -0
- package/src/modules/api-docs/domain/models/index.ts +40 -0
- package/src/modules/api-docs/domain/operations/api-docs-operations.ts +59 -0
- package/src/modules/api-docs/domain/validators/index.ts +56 -0
- package/src/modules/api-docs/index.ts +8 -0
- package/src/modules/api-docs/ports/api-docs-port.ts +21 -0
- package/src/modules/api-docs/types/api-docs.ts +15 -0
- package/src/modules/api-reference/openapi.ts +166 -0
- package/src/modules/api-reference/orpc.ts +94 -0
- package/src/modules/code-block-enhancements/application/usecases/index.ts +39 -0
- package/src/modules/code-block-enhancements/domain/code-operations.ts +72 -0
- package/src/modules/code-block-enhancements/domain/events/index.ts +58 -0
- package/src/modules/code-block-enhancements/domain/index.ts +11 -0
- package/src/modules/code-block-enhancements/domain/models/index.ts +36 -0
- package/src/modules/code-block-enhancements/domain/validators/index.ts +50 -0
- package/src/modules/code-block-enhancements/index.ts +10 -0
- package/src/modules/code-block-enhancements/ports/index.ts +18 -0
- package/src/modules/code-block-enhancements/types/index.ts +37 -0
- package/src/modules/code-execution/application/sandbox-adapter.ts +44 -0
- package/src/modules/code-execution/domain/events/index.ts +58 -0
- package/src/modules/code-execution/domain/models/index.ts +33 -0
- package/src/modules/code-execution/domain/operations/execution-operations.ts +60 -0
- package/src/modules/code-execution/domain/validators/index.ts +50 -0
- package/src/modules/code-execution/index.ts +8 -0
- package/src/modules/code-execution/ports/execution-port.ts +15 -0
- package/src/modules/code-execution/types/execution.ts +15 -0
- package/src/modules/comments/application/giscus-adapter.ts +55 -0
- package/src/modules/comments/domain/events/index.ts +55 -0
- package/src/modules/comments/domain/models/index.ts +30 -0
- package/src/modules/comments/domain/operations/comment-operations.ts +46 -0
- package/src/modules/comments/domain/validators/index.ts +50 -0
- package/src/modules/comments/index.ts +8 -0
- package/src/modules/comments/ports/comment-port.ts +18 -0
- package/src/modules/comments/types/comments.ts +19 -0
- package/src/modules/config/application/usecases/index.ts +1 -0
- package/src/modules/config/application/usecases/loadConfig.ts +38 -0
- package/src/modules/config/application/workflows/index.ts +1 -0
- package/src/modules/config/application/workflows/validateConfig.ts +14 -0
- package/src/modules/config/domain/events/index.ts +50 -0
- package/src/modules/config/domain/models/index.ts +9 -0
- package/src/modules/config/domain/operations/defaults.ts +74 -0
- package/src/modules/config/domain/operations/index.ts +2 -0
- package/src/modules/config/domain/operations/resolve.ts +40 -0
- package/src/modules/config/domain/validators/index.ts +152 -0
- package/src/modules/config/index.ts +23 -0
- package/src/modules/config/ports/index.ts +15 -0
- package/src/modules/config/types/index.ts +113 -0
- package/src/modules/config/types/public.ts +19 -0
- package/src/modules/content/application/cache/index.ts +6 -0
- package/src/modules/content/application/cache/parse-cache.ts +6 -0
- package/src/modules/content/application/cache/parseCache.ts +56 -0
- package/src/modules/content/application/usecases/index.ts +2 -0
- package/src/modules/content/application/usecases/load-doc-page.ts +1 -0
- package/src/modules/content/application/usecases/loadDocPage.ts +111 -0
- package/src/modules/content/application/usecases/scanDocsDir.ts +105 -0
- package/src/modules/content/application/workflows/index.ts +1 -0
- package/src/modules/content/application/workflows/processDocs.ts +51 -0
- package/src/modules/content/domain/events/index.ts +61 -0
- package/src/modules/content/domain/models/index.ts +13 -0
- package/src/modules/content/domain/operations/build-page.ts +146 -0
- package/src/modules/content/domain/operations/buildPage.ts +93 -0
- package/src/modules/content/domain/operations/index.ts +17 -0
- package/src/modules/content/domain/operations/parse.ts +241 -0
- package/src/modules/content/domain/validators/frontmatter.ts +82 -0
- package/src/modules/content/domain/validators/index.ts +6 -0
- package/src/modules/content/index.ts +27 -0
- package/src/modules/content/ports/index.ts +45 -0
- package/src/modules/content/ports/velite.ts +134 -0
- package/src/modules/content/types/index.ts +76 -0
- package/src/modules/content/types/public.ts +1 -0
- package/src/modules/content-linting/application/builtin-linting-adapter.ts +82 -0
- package/src/modules/content-linting/domain/events/index.ts +58 -0
- package/src/modules/content-linting/domain/models/index.ts +46 -0
- package/src/modules/content-linting/domain/operations/linting-operations.ts +82 -0
- package/src/modules/content-linting/domain/validators/index.ts +50 -0
- package/src/modules/content-linting/index.ts +8 -0
- package/src/modules/content-linting/ports/linting-port.ts +21 -0
- package/src/modules/content-linting/types/linting.ts +18 -0
- package/src/modules/content-validation/application/usecases/index.ts +18 -0
- package/src/modules/content-validation/domain/events/index.ts +7 -0
- package/src/modules/content-validation/domain/index.ts +16 -0
- package/src/modules/content-validation/domain/models/index.ts +33 -0
- package/src/modules/content-validation/domain/operations/index.ts +157 -0
- package/src/modules/content-validation/domain/types/index.ts +17 -0
- package/src/modules/content-validation/domain/validators/index.ts +8 -0
- package/src/modules/content-validation/index.ts +9 -0
- package/src/modules/content-validation/ports/index.ts +15 -0
- package/src/modules/content-validation/types/index.ts +5 -0
- package/src/modules/export/application/puppeteer-export-adapter.ts +63 -0
- package/src/modules/export/domain/events/index.ts +55 -0
- package/src/modules/export/domain/models/index.ts +38 -0
- package/src/modules/export/domain/operations/export-operations.ts +56 -0
- package/src/modules/export/domain/validators/index.ts +52 -0
- package/src/modules/export/index.ts +8 -0
- package/src/modules/export/ports/export-port.ts +15 -0
- package/src/modules/export/types/export.ts +15 -0
- package/src/modules/external-search/application/algolia-adapter.ts +182 -0
- package/src/modules/external-search/application/meilisearch-adapter.ts +161 -0
- package/src/modules/external-search/domain/events/index.ts +42 -0
- package/src/modules/external-search/domain/models/index.ts +42 -0
- package/src/modules/external-search/domain/operations/search-operations.ts +53 -0
- package/src/modules/external-search/domain/validators/index.ts +55 -0
- package/src/modules/external-search/index.ts +9 -0
- package/src/modules/external-search/ports/external-search-port.ts +23 -0
- package/src/modules/external-search/types/search.ts +19 -0
- package/src/modules/git-history/application/simple-git-adapter.ts +46 -0
- package/src/modules/git-history/domain/events/index.ts +34 -0
- package/src/modules/git-history/domain/models/index.ts +25 -0
- package/src/modules/git-history/domain/operations/git-history-operations.ts +56 -0
- package/src/modules/git-history/domain/validators/index.ts +52 -0
- package/src/modules/git-history/index.ts +8 -0
- package/src/modules/git-history/ports/git-history-port.ts +21 -0
- package/src/modules/git-history/types/git-history.ts +15 -0
- package/src/modules/i18n/application/fs-locale-loader.ts +39 -0
- package/src/modules/i18n/domain/events/index.ts +34 -0
- package/src/modules/i18n/domain/models/index.ts +34 -0
- package/src/modules/i18n/domain/operations/locale-operations.ts +74 -0
- package/src/modules/i18n/domain/validators/index.ts +52 -0
- package/src/modules/i18n/index.ts +8 -0
- package/src/modules/i18n/ports/locale-loader-port.ts +18 -0
- package/src/modules/i18n/types/locale.ts +17 -0
- package/src/modules/image-optimization/application/sharp-adapter.ts +58 -0
- package/src/modules/image-optimization/domain/events/index.ts +39 -0
- package/src/modules/image-optimization/domain/models/index.ts +27 -0
- package/src/modules/image-optimization/domain/operations/image-operations.ts +65 -0
- package/src/modules/image-optimization/domain/validators/index.ts +52 -0
- package/src/modules/image-optimization/index.ts +8 -0
- package/src/modules/image-optimization/ports/image-optimizer-port.ts +15 -0
- package/src/modules/image-optimization/types/image.ts +17 -0
- package/src/modules/live-editor/application/usecases/index.ts +60 -0
- package/src/modules/live-editor/domain/events/index.ts +37 -0
- package/src/modules/live-editor/domain/gitOperations.ts +110 -0
- package/src/modules/live-editor/domain/index.ts +15 -0
- package/src/modules/live-editor/domain/models/index.ts +21 -0
- package/src/modules/live-editor/domain/validators/index.ts +55 -0
- package/src/modules/live-editor/index.ts +14 -0
- package/src/modules/live-editor/ports/index.ts +49 -0
- package/src/modules/live-editor/types/index.ts +80 -0
- package/src/modules/live-preview/application/vite-preview-adapter.ts +20 -0
- package/src/modules/live-preview/domain/operations/preview-operations.ts +56 -0
- package/src/modules/live-preview/index.ts +8 -0
- package/src/modules/live-preview/ports/preview-port.ts +20 -0
- package/src/modules/live-preview/types/preview.ts +16 -0
- package/src/modules/mdx/application/mdx-js-adapter.ts +38 -0
- package/src/modules/mdx/domain/operations/parse-mdx.ts +43 -0
- package/src/modules/mdx/index.ts +8 -0
- package/src/modules/mdx/ports/mdx-parser-port.ts +15 -0
- package/src/modules/mdx/types/mdx.ts +14 -0
- package/src/modules/navigation/application/usecases/buildNav.ts +15 -0
- package/src/modules/navigation/application/usecases/buildSidebar.ts +45 -0
- package/src/modules/navigation/application/usecases/index.ts +2 -0
- package/src/modules/navigation/domain/events/index.ts +30 -0
- package/src/modules/navigation/domain/models/index.ts +7 -0
- package/src/modules/navigation/domain/operations/group.ts +79 -0
- package/src/modules/navigation/domain/operations/index.ts +10 -0
- package/src/modules/navigation/domain/operations/sort.ts +27 -0
- package/src/modules/navigation/domain/shared/utils/array.ts +39 -0
- package/src/modules/navigation/domain/validators/index.ts +88 -0
- package/src/modules/navigation/index.ts +15 -0
- package/src/modules/navigation/ports/index.ts +9 -0
- package/src/modules/navigation/types/index.ts +38 -0
- package/src/modules/navigation/types/public.ts +9 -0
- package/src/modules/openapi/application/swagger-parser-adapter.ts +178 -0
- package/src/modules/openapi/domain/operations/parse-openapi.ts +46 -0
- package/src/modules/openapi/index.ts +8 -0
- package/src/modules/openapi/ports/openapi-parser-port.ts +15 -0
- package/src/modules/openapi/types/openapi.ts +37 -0
- package/src/modules/plugin/application/usecases/build-virtual-modules.ts +53 -0
- package/src/modules/plugin/application/usecases/index.ts +1 -0
- package/src/modules/plugin/application/workflows/build-docs.ts +79 -0
- package/src/modules/plugin/application/workflows/index.ts +1 -0
- package/src/modules/plugin/domain/events/index.ts +62 -0
- package/src/modules/plugin/domain/models/index.ts +1 -0
- package/src/modules/plugin/domain/operations/build-virtual.ts +76 -0
- package/src/modules/plugin/domain/operations/index.ts +11 -0
- package/src/modules/plugin/domain/validators/index.ts +30 -0
- package/src/modules/plugin/index.ts +14 -0
- package/src/modules/plugin/ports/index.ts +21 -0
- package/src/modules/plugin/types/index.ts +27 -0
- package/src/modules/plugin/types/public.ts +1 -0
- package/src/modules/search/application/usecases/index.ts +1 -0
- package/src/modules/search/application/usecases/indexPages.ts +15 -0
- package/src/modules/search/domain/events/index.ts +32 -0
- package/src/modules/search/domain/models/index.ts +1 -0
- package/src/modules/search/domain/operations/build-index.ts +6 -0
- package/src/modules/search/domain/operations/buildIndex.ts +70 -0
- package/src/modules/search/domain/operations/index.ts +7 -0
- package/src/modules/search/domain/operations/query.ts +186 -0
- package/src/modules/search/domain/validators/index.ts +32 -0
- package/src/modules/search/index.ts +9 -0
- package/src/modules/search/types/index.ts +22 -0
- package/src/modules/search/types/public.ts +1 -0
- package/src/modules/search-analytics/application/local-storage-adapter.ts +68 -0
- package/src/modules/search-analytics/domain/operations/search-analytics-operations.ts +74 -0
- package/src/modules/search-analytics/index.ts +8 -0
- package/src/modules/search-analytics/ports/search-analytics-port.ts +20 -0
- package/src/modules/search-analytics/types/search-analytics.ts +16 -0
- package/src/modules/search-highlighting/application/dom-highlighting-adapter.ts +36 -0
- package/src/modules/search-highlighting/domain/operations/highlighting-operations.ts +72 -0
- package/src/modules/search-highlighting/index.ts +8 -0
- package/src/modules/search-highlighting/ports/highlighting-port.ts +14 -0
- package/src/modules/search-highlighting/types/highlighting.ts +15 -0
- package/src/modules/seo-optimization/application/usecases/index.ts +18 -0
- package/src/modules/seo-optimization/domain/index.ts +12 -0
- package/src/modules/seo-optimization/domain/seoOperations.ts +92 -0
- package/src/modules/seo-optimization/index.ts +8 -0
- package/src/modules/seo-optimization/ports/index.ts +18 -0
- package/src/modules/seo-optimization/types/index.ts +41 -0
- package/src/modules/theme/application/css-theme-adapter.ts +50 -0
- package/src/modules/theme/domain/operations/theme-operations.ts +68 -0
- package/src/modules/theme/index.ts +8 -0
- package/src/modules/theme/ports/theme-port.ts +8 -0
- package/src/modules/theme/types/theme.ts +12 -0
- package/src/modules/toc/application/markdown-toc-adapter.ts +106 -0
- package/src/modules/toc/domain/operations/toc-operations.ts +92 -0
- package/src/modules/toc/index.ts +8 -0
- package/src/modules/toc/ports/toc-port.ts +21 -0
- package/src/modules/toc/types/toc.ts +17 -0
- package/src/modules/toc-generator/application/usecases/index.ts +30 -0
- package/src/modules/toc-generator/domain/index.ts +12 -0
- package/src/modules/toc-generator/domain/tocOperations.ts +104 -0
- package/src/modules/toc-generator/index.ts +9 -0
- package/src/modules/toc-generator/ports/index.ts +20 -0
- package/src/modules/toc-generator/types/index.ts +50 -0
- package/src/modules/version-control/domain/events/index.ts +39 -0
- package/src/modules/version-control/domain/models/index.ts +25 -0
- package/src/modules/version-control/domain/validators/index.ts +50 -0
- package/src/modules/versioning/application/fs-version-loader.ts +44 -0
- package/src/modules/versioning/domain/operations/versioning-operations.ts +80 -0
- package/src/modules/versioning/index.ts +8 -0
- package/src/modules/versioning/ports/version-loader-port.ts +18 -0
- package/src/modules/versioning/types/versioning.ts +17 -0
- package/src/modules/workflow/domain/events/index.ts +55 -0
- package/src/modules/workflow/domain/models/index.ts +40 -0
- package/src/modules/workflow/domain/validators/index.ts +50 -0
- package/src/modules/yaml-parser/application/js-yaml-adapter.ts +34 -0
- package/src/modules/yaml-parser/domain/operations/parse-yaml.ts +58 -0
- package/src/modules/yaml-parser/index.ts +7 -0
- package/src/modules/yaml-parser/ports/yaml-parser-port.ts +18 -0
- package/src/navigation/index.ts +9 -0
- package/src/plugin/index.ts +4 -0
- package/src/presentation/index.ts +1 -0
- package/src/presentation/live-editor/index.ts +22 -0
- package/src/presentation/live-editor/live-editor-component.ts +219 -0
- package/src/presentation/live-editor/virtual-module.ts +16 -0
- package/src/presentation/solid/CodeBlock.tsx +120 -0
- package/src/presentation/solid/Toc.tsx +58 -0
- package/src/presentation/solid/index.ts +11 -0
- package/src/presentation/vite-plugin/docs-plugin.ts +212 -0
- package/src/presentation/vite-plugin/index.ts +9 -0
- package/src/presentation/vite-plugin/transform-markdown.ts +26 -0
- package/src/runtime/analytics.ts +59 -0
- package/src/runtime/api-diff.ts +115 -0
- package/src/runtime/app.tsx +32 -0
- package/src/runtime/components/AccentPicker.tsx +114 -0
- package/src/runtime/components/ApiPlayground.tsx +332 -0
- package/src/runtime/components/AskAiDialog.tsx +127 -0
- package/src/runtime/components/BackToTop.tsx +38 -0
- package/src/runtime/components/Banner.tsx +75 -0
- package/src/runtime/components/Breadcrumbs.tsx +42 -0
- package/src/runtime/components/CollectionDropdown.tsx +85 -0
- package/src/runtime/components/CustomizeDrawer.tsx +195 -0
- package/src/runtime/components/DocMarkdown.tsx +579 -0
- package/src/runtime/components/DocPrevNext.tsx +54 -0
- package/src/runtime/components/DocToc.tsx +147 -0
- package/src/runtime/components/DocsDropdown.tsx +184 -0
- package/src/runtime/components/Footer.tsx +52 -0
- package/src/runtime/components/GitHubAuth.tsx +82 -0
- package/src/runtime/components/GitHubStats.tsx +73 -0
- package/src/runtime/components/Head.tsx +225 -0
- package/src/runtime/components/LocaleDropdown.tsx +75 -0
- package/src/runtime/components/MobileBottomNav.tsx +111 -0
- package/src/runtime/components/NotFound.tsx +139 -0
- package/src/runtime/components/PageActions.tsx +144 -0
- package/src/runtime/components/RelatedDocs.tsx +50 -0
- package/src/runtime/components/ScrollProgress.tsx +32 -0
- package/src/runtime/components/SearchPalette.tsx +333 -0
- package/src/runtime/components/ShowcaseCard.tsx +87 -0
- package/src/runtime/components/SidebarNav.tsx +237 -0
- package/src/runtime/components/Skeleton.tsx +39 -0
- package/src/runtime/components/ThemeToggle.tsx +26 -0
- package/src/runtime/components/TopNav.tsx +270 -0
- package/src/runtime/components/VersionDropdown.tsx +189 -0
- package/src/runtime/components/markdown/components.ts +280 -0
- package/src/runtime/config.ts +225 -0
- package/src/runtime/content.ts +451 -0
- package/src/runtime/context.tsx +30 -0
- package/src/runtime/data.ts +93 -0
- package/src/runtime/github.ts +226 -0
- package/src/runtime/icons.ts +32 -0
- package/src/runtime/index.ts +153 -0
- package/src/runtime/layouts/DocsLayout.tsx +144 -0
- package/src/runtime/markdown-content.css +996 -0
- package/src/runtime/pages/AbTestPage.tsx +88 -0
- package/src/runtime/pages/AnalyticsPage.tsx +90 -0
- package/src/runtime/pages/ApiDiffPage.tsx +86 -0
- package/src/runtime/pages/ApiEndpointPage.tsx +248 -0
- package/src/runtime/pages/BuilderPage.tsx +150 -0
- package/src/runtime/pages/ChangelogPage.tsx +210 -0
- package/src/runtime/pages/CollectionPage.tsx +140 -0
- package/src/runtime/pages/CommunityPage.tsx +384 -0
- package/src/runtime/pages/CreatePage.tsx +448 -0
- package/src/runtime/pages/DocPage.tsx +193 -0
- package/src/runtime/pages/EditPage.tsx +201 -0
- package/src/runtime/pages/HomePage.tsx +187 -0
- package/src/runtime/pages/IssuesPage.tsx +140 -0
- package/src/runtime/pages/OAuthCallbackPage.tsx +80 -0
- package/src/runtime/pages/PluginsPage.tsx +249 -0
- package/src/runtime/pages/ShowcasePage.tsx +202 -0
- package/src/runtime/pwa.ts +39 -0
- package/src/runtime/router.tsx +192 -0
- package/src/runtime/seo.ts +192 -0
- package/src/runtime/theme.css +175 -0
- package/src/runtime/theme.ts +224 -0
- package/src/runtime/types.ts +217 -0
- package/src/search/index.ts +6 -0
- package/src/seo-optimization/index.ts +4 -0
- package/src/shared/constants/index.ts +26 -0
- package/src/shared/errors/index.ts +195 -0
- package/src/shared/index.ts +27 -0
- package/src/shared/types/brand.ts +14 -0
- package/src/shared/types/client.d.ts +45 -0
- package/src/shared/types/either.ts +38 -0
- package/src/shared/types/index.ts +42 -0
- package/src/shared/types/option.ts +52 -0
- package/src/shared/types/result.ts +81 -0
- package/src/shared/utils/array.ts +97 -0
- package/src/shared/utils/index.ts +46 -0
- package/src/shared/utils/object.ts +47 -0
- package/src/shared/utils/string.ts +74 -0
- package/src/toc-generator/index.ts +1 -0
- package/src/types/js-yaml.d.ts +42 -0
- package/src/worker.ts +462 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain events for API docs
|
|
3
|
+
*/
|
|
4
|
+
export interface ApiEndpointAddedEvent {
|
|
5
|
+
readonly type: "API_ENDPOINT_ADDED";
|
|
6
|
+
readonly timestamp: number;
|
|
7
|
+
readonly path: string;
|
|
8
|
+
readonly method: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ApiEndpointUpdatedEvent {
|
|
12
|
+
readonly type: "API_ENDPOINT_UPDATED";
|
|
13
|
+
readonly timestamp: number;
|
|
14
|
+
readonly path: string;
|
|
15
|
+
readonly method: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type ApiDocsDomainEvent =
|
|
19
|
+
| ApiEndpointAddedEvent
|
|
20
|
+
| ApiEndpointUpdatedEvent;
|
|
21
|
+
|
|
22
|
+
export const createApiEndpointAddedEvent = (
|
|
23
|
+
path: string,
|
|
24
|
+
method: string,
|
|
25
|
+
now: number = Date.now(),
|
|
26
|
+
): ApiEndpointAddedEvent => ({
|
|
27
|
+
type: "API_ENDPOINT_ADDED",
|
|
28
|
+
timestamp: now,
|
|
29
|
+
path,
|
|
30
|
+
method,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export const createApiEndpointUpdatedEvent = (
|
|
34
|
+
path: string,
|
|
35
|
+
method: string,
|
|
36
|
+
now: number = Date.now(),
|
|
37
|
+
): ApiEndpointUpdatedEvent => ({
|
|
38
|
+
type: "API_ENDPOINT_UPDATED",
|
|
39
|
+
timestamp: now,
|
|
40
|
+
path,
|
|
41
|
+
method,
|
|
42
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain models for API docs
|
|
3
|
+
*/
|
|
4
|
+
export interface ApiEndpoint {
|
|
5
|
+
readonly path: string;
|
|
6
|
+
readonly method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
7
|
+
readonly description: string;
|
|
8
|
+
readonly parameters?: ReadonlyArray<ApiParameter>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ApiParameter {
|
|
12
|
+
readonly name: string;
|
|
13
|
+
readonly type: string;
|
|
14
|
+
readonly required: boolean;
|
|
15
|
+
readonly description: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const createApiEndpoint = (
|
|
19
|
+
path: string,
|
|
20
|
+
method: ApiEndpoint["method"],
|
|
21
|
+
description: string,
|
|
22
|
+
parameters?: ReadonlyArray<ApiParameter>,
|
|
23
|
+
): ApiEndpoint => ({
|
|
24
|
+
path,
|
|
25
|
+
method,
|
|
26
|
+
description,
|
|
27
|
+
parameters,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export const createApiParameter = (
|
|
31
|
+
name: string,
|
|
32
|
+
type: string,
|
|
33
|
+
required: boolean,
|
|
34
|
+
description: string,
|
|
35
|
+
): ApiParameter => ({
|
|
36
|
+
name,
|
|
37
|
+
type,
|
|
38
|
+
required,
|
|
39
|
+
description,
|
|
40
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure API docs operations.
|
|
3
|
+
* No I/O. No exceptions thrown. Returns Result types.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Result } from "@create-docs/shared/types";
|
|
7
|
+
import { err, ok } from "@create-docs/shared/types";
|
|
8
|
+
import type { ApiDocsConfig, ApiEndpoint } from "../../types/api-docs";
|
|
9
|
+
|
|
10
|
+
export type ApiDocsError = {
|
|
11
|
+
readonly kind: "api-docs-error";
|
|
12
|
+
readonly message: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Group endpoints by path prefix.
|
|
17
|
+
*/
|
|
18
|
+
export const groupEndpointsByPath = (
|
|
19
|
+
endpoints: readonly ApiEndpoint[],
|
|
20
|
+
): Result<Record<string, readonly ApiEndpoint[]>, ApiDocsError> => {
|
|
21
|
+
try {
|
|
22
|
+
const groups: Record<string, ApiEndpoint[]> = {};
|
|
23
|
+
|
|
24
|
+
for (const endpoint of endpoints) {
|
|
25
|
+
const prefix = endpoint.path.split("/")[1] || "root";
|
|
26
|
+
if (!groups[prefix]) {
|
|
27
|
+
groups[prefix] = [];
|
|
28
|
+
}
|
|
29
|
+
groups[prefix].push(endpoint);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return ok(groups);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
return err({
|
|
35
|
+
kind: "api-docs-error",
|
|
36
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Generate markdown from endpoint.
|
|
43
|
+
*/
|
|
44
|
+
export const generateEndpointMarkdown = (endpoint: ApiEndpoint): string => {
|
|
45
|
+
return `## ${endpoint.method.toUpperCase()} ${endpoint.path}
|
|
46
|
+
|
|
47
|
+
${endpoint.description}
|
|
48
|
+
`;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Validate API docs config.
|
|
53
|
+
*/
|
|
54
|
+
export const validateApiDocsConfig = (config: ApiDocsConfig): boolean => {
|
|
55
|
+
if (!config.source) return false;
|
|
56
|
+
if (!config.sourcePath) return false;
|
|
57
|
+
if (!config.outputPath) return false;
|
|
58
|
+
return true;
|
|
59
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// API Docs Domain Schemas - Arktype validation schemas
|
|
2
|
+
// Following /follow-arktype workflow for type-safe runtime validation
|
|
3
|
+
|
|
4
|
+
import { type Type, type } from "arktype";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* API Path Schema
|
|
8
|
+
*/
|
|
9
|
+
export const apiPathSchema: Type = type("string >= 1").narrow((path: string) =>
|
|
10
|
+
path.startsWith("/"),
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
export type ApiPath = typeof apiPathSchema.infer;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* HTTP Method Schema
|
|
17
|
+
*/
|
|
18
|
+
export const httpMethodSchema: Type = type(
|
|
19
|
+
"'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'",
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
export type HttpMethod = typeof httpMethodSchema.infer;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Validation errors
|
|
26
|
+
*/
|
|
27
|
+
export class ValidationError extends Error {
|
|
28
|
+
constructor(message: string) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.name = "ValidationError";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Validate API path using Arktype
|
|
36
|
+
*/
|
|
37
|
+
export const validateApiPath = (path: unknown): ApiPath => {
|
|
38
|
+
const result = apiPathSchema(path);
|
|
39
|
+
if (result instanceof type.errors) {
|
|
40
|
+
throw new ValidationError(
|
|
41
|
+
"API path must be a non-empty string starting with /",
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
return result as ApiPath;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Validate HTTP method using Arktype
|
|
49
|
+
*/
|
|
50
|
+
export const validateHttpMethod = (method: unknown): HttpMethod => {
|
|
51
|
+
const result = httpMethodSchema(method);
|
|
52
|
+
if (result instanceof type.errors) {
|
|
53
|
+
throw new ValidationError("Invalid HTTP method");
|
|
54
|
+
}
|
|
55
|
+
return result as HttpMethod;
|
|
56
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port for API docs operations.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type ApiDocsPort = {
|
|
6
|
+
/**
|
|
7
|
+
* Extract API endpoints from source.
|
|
8
|
+
*/
|
|
9
|
+
readonly extractEndpoints: (
|
|
10
|
+
sourcePath: string,
|
|
11
|
+
config: unknown,
|
|
12
|
+
) => Promise<readonly unknown[]>;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Generate documentation from endpoints.
|
|
16
|
+
*/
|
|
17
|
+
readonly generateDocs: (
|
|
18
|
+
endpoints: readonly unknown[],
|
|
19
|
+
outputPath: string,
|
|
20
|
+
) => Promise<void>;
|
|
21
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API docs types for auto-generating API documentation.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type ApiDocsConfig = {
|
|
6
|
+
readonly source: "openapi" | "typescript" | "graphql";
|
|
7
|
+
readonly sourcePath: string;
|
|
8
|
+
readonly outputPath: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type ApiEndpoint = {
|
|
12
|
+
readonly path: string;
|
|
13
|
+
readonly method: string;
|
|
14
|
+
readonly description: string;
|
|
15
|
+
};
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-safe OpenAPI → ApiEndpoint adapter.
|
|
3
|
+
* Accepts a spec object, JSON/YAML string, or URL.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { JSON_SCHEMA, load } from "js-yaml";
|
|
7
|
+
import type {
|
|
8
|
+
ApiEndpoint,
|
|
9
|
+
ApiParameter,
|
|
10
|
+
HttpMethod,
|
|
11
|
+
} from "../../runtime/types";
|
|
12
|
+
|
|
13
|
+
const METHODS = new Set(["get", "post", "put", "delete", "patch"]);
|
|
14
|
+
|
|
15
|
+
const slug = (method: string, path: string, opId?: string): string =>
|
|
16
|
+
opId ??
|
|
17
|
+
`${method}-${path}`
|
|
18
|
+
.toLowerCase()
|
|
19
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
20
|
+
.replace(/^-|-$/g, "");
|
|
21
|
+
|
|
22
|
+
const exampleFromSchema = (schema: unknown): unknown => {
|
|
23
|
+
if (!schema || typeof schema !== "object") return undefined;
|
|
24
|
+
const s = schema as Record<string, unknown>;
|
|
25
|
+
if (s.example !== undefined) return s.example;
|
|
26
|
+
if (s.default !== undefined) return s.default;
|
|
27
|
+
if (Array.isArray(s.enum)) return s.enum[0];
|
|
28
|
+
if (s.type === "object" && s.properties) {
|
|
29
|
+
const out: Record<string, unknown> = {};
|
|
30
|
+
for (const [k, v] of Object.entries(
|
|
31
|
+
s.properties as Record<string, unknown>,
|
|
32
|
+
)) {
|
|
33
|
+
const ex = exampleFromSchema(v);
|
|
34
|
+
if (ex !== undefined) out[k] = ex;
|
|
35
|
+
}
|
|
36
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
37
|
+
}
|
|
38
|
+
if (s.type === "array" && s.items) {
|
|
39
|
+
const ex = exampleFromSchema(s.items);
|
|
40
|
+
return ex !== undefined ? [ex] : [];
|
|
41
|
+
}
|
|
42
|
+
if (s.type === "string") return "string";
|
|
43
|
+
if (s.type === "number" || s.type === "integer") return 0;
|
|
44
|
+
if (s.type === "boolean") return true;
|
|
45
|
+
return undefined;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const jsonContent = (
|
|
49
|
+
content: unknown,
|
|
50
|
+
): { example?: unknown; contentType?: string } => {
|
|
51
|
+
if (!content || typeof content !== "object") return {};
|
|
52
|
+
const record = content as Record<string, Record<string, unknown>>;
|
|
53
|
+
const ct =
|
|
54
|
+
Object.keys(record).find((k) => k.includes("json")) ??
|
|
55
|
+
Object.keys(record)[0];
|
|
56
|
+
if (!ct) return {};
|
|
57
|
+
const media = record[ct];
|
|
58
|
+
return {
|
|
59
|
+
contentType: ct,
|
|
60
|
+
example: media?.example ?? exampleFromSchema(media?.schema),
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/** Parse a spec object into endpoints grouped-ready ApiEndpoint[]. */
|
|
65
|
+
export function endpointsFromOpenApiSpec(
|
|
66
|
+
spec: Record<string, unknown>,
|
|
67
|
+
options?: { server?: string },
|
|
68
|
+
): ApiEndpoint[] {
|
|
69
|
+
const paths = spec.paths as Record<string, unknown> | undefined;
|
|
70
|
+
if (!paths || typeof paths !== "object") return [];
|
|
71
|
+
|
|
72
|
+
const servers = spec.servers as { url?: string }[] | undefined;
|
|
73
|
+
const defaultServer = options?.server ?? servers?.[0]?.url;
|
|
74
|
+
|
|
75
|
+
const endpoints: ApiEndpoint[] = [];
|
|
76
|
+
for (const [path, item] of Object.entries(paths)) {
|
|
77
|
+
if (!item || typeof item !== "object") continue;
|
|
78
|
+
for (const [method, operation] of Object.entries(
|
|
79
|
+
item as Record<string, unknown>,
|
|
80
|
+
)) {
|
|
81
|
+
if (!METHODS.has(method) || !operation || typeof operation !== "object")
|
|
82
|
+
continue;
|
|
83
|
+
const op = operation as Record<string, unknown>;
|
|
84
|
+
const tags = Array.isArray(op.tags) ? (op.tags as string[]) : [];
|
|
85
|
+
const parameters: ApiParameter[] = Array.isArray(op.parameters)
|
|
86
|
+
? op.parameters.map((p: Record<string, unknown>) => ({
|
|
87
|
+
name: String(p.name ?? ""),
|
|
88
|
+
in: (p.in as ApiParameter["in"]) ?? "query",
|
|
89
|
+
required: Boolean(p.required),
|
|
90
|
+
description:
|
|
91
|
+
typeof p.description === "string" ? p.description : undefined,
|
|
92
|
+
schema: p.schema,
|
|
93
|
+
example: p.example ?? exampleFromSchema(p.schema),
|
|
94
|
+
}))
|
|
95
|
+
: [];
|
|
96
|
+
|
|
97
|
+
const body = op.requestBody as Record<string, unknown> | undefined;
|
|
98
|
+
const bodyContent = body ? jsonContent(body.content) : {};
|
|
99
|
+
|
|
100
|
+
const responses: ApiEndpoint["responses"] = {};
|
|
101
|
+
const resps = op.responses as Record<string, unknown> | undefined;
|
|
102
|
+
if (resps) {
|
|
103
|
+
for (const [code, r] of Object.entries(resps)) {
|
|
104
|
+
const rec = r as Record<string, unknown>;
|
|
105
|
+
responses[code] = {
|
|
106
|
+
description:
|
|
107
|
+
typeof rec.description === "string" ? rec.description : "",
|
|
108
|
+
example: jsonContent(rec.content).example,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
endpoints.push({
|
|
114
|
+
id: slug(
|
|
115
|
+
method,
|
|
116
|
+
path,
|
|
117
|
+
typeof op.operationId === "string" ? op.operationId : undefined,
|
|
118
|
+
),
|
|
119
|
+
method: method.toUpperCase() as HttpMethod,
|
|
120
|
+
path,
|
|
121
|
+
summary: typeof op.summary === "string" ? op.summary : undefined,
|
|
122
|
+
description:
|
|
123
|
+
typeof op.description === "string" ? op.description : undefined,
|
|
124
|
+
tag: tags[0],
|
|
125
|
+
parameters,
|
|
126
|
+
requestBody: body
|
|
127
|
+
? {
|
|
128
|
+
description:
|
|
129
|
+
typeof body.description === "string"
|
|
130
|
+
? body.description
|
|
131
|
+
: undefined,
|
|
132
|
+
required: Boolean(body.required),
|
|
133
|
+
example: bodyContent.example,
|
|
134
|
+
contentType: bodyContent.contentType,
|
|
135
|
+
}
|
|
136
|
+
: undefined,
|
|
137
|
+
responses,
|
|
138
|
+
server: defaultServer,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return endpoints;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Load endpoints from a spec object, raw JSON/YAML text, or URL. */
|
|
146
|
+
export async function endpointsFromOpenApi(
|
|
147
|
+
source: Record<string, unknown> | string,
|
|
148
|
+
options?: { server?: string },
|
|
149
|
+
): Promise<ApiEndpoint[]> {
|
|
150
|
+
let spec: Record<string, unknown>;
|
|
151
|
+
if (typeof source === "object" && source !== null) {
|
|
152
|
+
spec = source;
|
|
153
|
+
} else {
|
|
154
|
+
let text = source;
|
|
155
|
+
if (/^https?:\/\//i.test(text) || /^\.?\//.test(text)) {
|
|
156
|
+
const res = await fetch(text);
|
|
157
|
+
if (!res.ok) throw new Error(`Failed to fetch spec: ${res.status}`);
|
|
158
|
+
text = await res.text();
|
|
159
|
+
}
|
|
160
|
+
const trimmed = text.trim();
|
|
161
|
+
spec = trimmed.startsWith("{")
|
|
162
|
+
? JSON.parse(trimmed)
|
|
163
|
+
: (load(trimmed, { schema: JSON_SCHEMA }) as Record<string, unknown>);
|
|
164
|
+
}
|
|
165
|
+
return endpointsFromOpenApiSpec(spec, options);
|
|
166
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* oRPC-native adapter: walk a router object and turn each procedure
|
|
3
|
+
* into an ApiEndpoint for the reference + playground.
|
|
4
|
+
*
|
|
5
|
+
* oRPC HTTP default: POST /<basePath>/<dot.separated.path> with
|
|
6
|
+
* `{ "json": <input> }` bodies (RPCHandler). Route metadata is read
|
|
7
|
+
* defensively from `~orpc` so custom OpenAPIHandler routes work too.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ApiEndpoint, HttpMethod } from "../../runtime/types";
|
|
11
|
+
|
|
12
|
+
type UnknownRecord = Record<string, unknown>;
|
|
13
|
+
|
|
14
|
+
const asRecord = (v: unknown): UnknownRecord | undefined =>
|
|
15
|
+
v && typeof v === "object" ? (v as UnknownRecord) : undefined;
|
|
16
|
+
|
|
17
|
+
const readRoute = (proc: unknown): UnknownRecord | undefined => {
|
|
18
|
+
const meta = asRecord(asRecord(proc)?.["~orpc"]);
|
|
19
|
+
if (!meta) return undefined;
|
|
20
|
+
const direct = asRecord(meta.route);
|
|
21
|
+
if (direct) return direct;
|
|
22
|
+
const contract = asRecord(meta.procedureContract);
|
|
23
|
+
return asRecord(asRecord(contract?.["~orpc"])?.route);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export interface OrpcAdapterOptions {
|
|
27
|
+
/** e.g. "/rpc" — used when a procedure has no explicit route.path. */
|
|
28
|
+
basePath?: string;
|
|
29
|
+
/** Absolute server URL for the playground. Defaults to same-origin. */
|
|
30
|
+
server?: string;
|
|
31
|
+
/** Group all endpoints under this tag when no meta tags exist. */
|
|
32
|
+
tag?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function endpointsFromOrpcRouter(
|
|
36
|
+
router: unknown,
|
|
37
|
+
options: OrpcAdapterOptions = {},
|
|
38
|
+
): ApiEndpoint[] {
|
|
39
|
+
const basePath = options.basePath ?? "/rpc";
|
|
40
|
+
const endpoints: ApiEndpoint[] = [];
|
|
41
|
+
|
|
42
|
+
const walk = (node: unknown, keyPath: string[]) => {
|
|
43
|
+
const rec = asRecord(node);
|
|
44
|
+
if (!rec) return;
|
|
45
|
+
|
|
46
|
+
const route = readRoute(node);
|
|
47
|
+
if (route) {
|
|
48
|
+
const method = (
|
|
49
|
+
typeof route.method === "string" ? route.method : "POST"
|
|
50
|
+
).toUpperCase() as HttpMethod;
|
|
51
|
+
const dotPath = keyPath.join(".");
|
|
52
|
+
const path =
|
|
53
|
+
typeof route.path === "string" ? route.path : `${basePath}/${dotPath}`;
|
|
54
|
+
const summary =
|
|
55
|
+
(typeof route.summary === "string" && route.summary) || dotPath;
|
|
56
|
+
endpoints.push({
|
|
57
|
+
id: dotPath.replace(/[^a-zA-Z0-9]+/g, "-"),
|
|
58
|
+
method,
|
|
59
|
+
path,
|
|
60
|
+
summary,
|
|
61
|
+
description:
|
|
62
|
+
typeof route.description === "string"
|
|
63
|
+
? route.description
|
|
64
|
+
: `oRPC procedure \`${dotPath}\`. Send input as \`{ "json": <input> }\`.`,
|
|
65
|
+
tag: options.tag ?? keyPath[0],
|
|
66
|
+
parameters: [],
|
|
67
|
+
requestBody:
|
|
68
|
+
method === "GET"
|
|
69
|
+
? undefined
|
|
70
|
+
: {
|
|
71
|
+
required: false,
|
|
72
|
+
contentType: "application/json",
|
|
73
|
+
example: { json: {} },
|
|
74
|
+
},
|
|
75
|
+
responses: {
|
|
76
|
+
"200": {
|
|
77
|
+
description: "oRPC JSON response",
|
|
78
|
+
example: { json: {} },
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
server: options.server,
|
|
82
|
+
});
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (const [key, value] of Object.entries(rec)) {
|
|
87
|
+
if (key.startsWith("~") || key.startsWith("_")) continue;
|
|
88
|
+
walk(value, [...keyPath, key]);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
walk(router, []);
|
|
93
|
+
return endpoints;
|
|
94
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code Block Enhancements Use Cases
|
|
3
|
+
*
|
|
4
|
+
* Orchestration layer for code block enhancements
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
addLineNumbers,
|
|
9
|
+
enhanceCodeBlock,
|
|
10
|
+
parseCodeBlocks,
|
|
11
|
+
} from "../../domain";
|
|
12
|
+
import type { CodeBlockOptions } from "../../types";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Enhance all code blocks in markdown content
|
|
16
|
+
*/
|
|
17
|
+
export const enhanceCodeBlocks = (
|
|
18
|
+
content: string,
|
|
19
|
+
options: CodeBlockOptions = {},
|
|
20
|
+
): string => {
|
|
21
|
+
const codeBlocks = parseCodeBlocks(content);
|
|
22
|
+
let enhancedContent = content;
|
|
23
|
+
|
|
24
|
+
for (const block of codeBlocks) {
|
|
25
|
+
const enhanced = enhanceCodeBlock(block, options);
|
|
26
|
+
const originalCode = block.code;
|
|
27
|
+
const newCode = enhanced.lineNumbers
|
|
28
|
+
? addLineNumbers(originalCode)
|
|
29
|
+
: originalCode;
|
|
30
|
+
|
|
31
|
+
// Replace code block in content
|
|
32
|
+
enhancedContent = enhancedContent.replace(
|
|
33
|
+
`\`\`\`${block.language || ""}${block.filename ? ` ${block.filename}` : ""}\n${originalCode}\n\`\`\``,
|
|
34
|
+
`\`\`\`${enhanced.language || ""}${enhanced.filename ? ` ${enhanced.filename}` : ""}\n${newCode}\n\`\`\``,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return enhancedContent;
|
|
39
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code Block Operations
|
|
3
|
+
*
|
|
4
|
+
* Pure functions for code block enhancements
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { CodeBlock, CodeBlockOptions } from "../types";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Parse code blocks from markdown content
|
|
11
|
+
*/
|
|
12
|
+
export const parseCodeBlocks = (content: string): CodeBlock[] => {
|
|
13
|
+
const codeBlocks: CodeBlock[] = [];
|
|
14
|
+
const lines = content.split("\n");
|
|
15
|
+
let inCodeBlock = false;
|
|
16
|
+
let currentBlock: Partial<CodeBlock> = {};
|
|
17
|
+
let codeLines: string[] = [];
|
|
18
|
+
|
|
19
|
+
for (const line of lines) {
|
|
20
|
+
const codeMatch = line.match(/^```(\w+)?\s*(.*)?$/);
|
|
21
|
+
const endMatch = line.match(/^```$/);
|
|
22
|
+
|
|
23
|
+
if (codeMatch && !inCodeBlock) {
|
|
24
|
+
inCodeBlock = true;
|
|
25
|
+
currentBlock = {
|
|
26
|
+
language: codeMatch[1] || undefined,
|
|
27
|
+
filename: codeMatch[2] || undefined,
|
|
28
|
+
};
|
|
29
|
+
codeLines = [];
|
|
30
|
+
} else if (endMatch && inCodeBlock) {
|
|
31
|
+
inCodeBlock = false;
|
|
32
|
+
currentBlock.code = codeLines.join("\n");
|
|
33
|
+
codeBlocks.push(currentBlock as CodeBlock);
|
|
34
|
+
currentBlock = {};
|
|
35
|
+
codeLines = [];
|
|
36
|
+
} else if (inCodeBlock) {
|
|
37
|
+
codeLines.push(line);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return codeBlocks;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Enhance code block with options
|
|
46
|
+
*/
|
|
47
|
+
export const enhanceCodeBlock = (
|
|
48
|
+
block: CodeBlock,
|
|
49
|
+
options: CodeBlockOptions = {},
|
|
50
|
+
): CodeBlock => {
|
|
51
|
+
return {
|
|
52
|
+
...block,
|
|
53
|
+
lineNumbers: options.lineNumbers ?? true,
|
|
54
|
+
highlightLines: options.highlightLines || block.highlightLines,
|
|
55
|
+
filename: options.filename || block.filename,
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Add line numbers to code
|
|
61
|
+
*/
|
|
62
|
+
export const addLineNumbers = (code: string): string => {
|
|
63
|
+
const lines = code.split("\n");
|
|
64
|
+
const maxDigits = lines.length.toString().length;
|
|
65
|
+
|
|
66
|
+
return lines
|
|
67
|
+
.map((line, index) => {
|
|
68
|
+
const lineNumber = (index + 1).toString().padStart(maxDigits, " ");
|
|
69
|
+
return `${lineNumber} | ${line}`;
|
|
70
|
+
})
|
|
71
|
+
.join("\n");
|
|
72
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain events for code block enhancements
|
|
3
|
+
*/
|
|
4
|
+
export interface CodeBlockCopiedEvent {
|
|
5
|
+
readonly type: "CODE_BLOCK_COPIED";
|
|
6
|
+
readonly timestamp: number;
|
|
7
|
+
readonly blockId: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface CodeBlockDownloadedEvent {
|
|
11
|
+
readonly type: "CODE_BLOCK_DOWNLOADED";
|
|
12
|
+
readonly timestamp: number;
|
|
13
|
+
readonly blockId: string;
|
|
14
|
+
readonly filename: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface LineHighlightedEvent {
|
|
18
|
+
readonly type: "LINE_HIGHLIGHTED";
|
|
19
|
+
readonly timestamp: number;
|
|
20
|
+
readonly blockId: string;
|
|
21
|
+
readonly lineNumber: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type CodeBlockEnhancementsDomainEvent =
|
|
25
|
+
| CodeBlockCopiedEvent
|
|
26
|
+
| CodeBlockDownloadedEvent
|
|
27
|
+
| LineHighlightedEvent;
|
|
28
|
+
|
|
29
|
+
export const createCodeBlockCopiedEvent = (
|
|
30
|
+
blockId: string,
|
|
31
|
+
now: number = Date.now(),
|
|
32
|
+
): CodeBlockCopiedEvent => ({
|
|
33
|
+
type: "CODE_BLOCK_COPIED",
|
|
34
|
+
timestamp: now,
|
|
35
|
+
blockId,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export const createCodeBlockDownloadedEvent = (
|
|
39
|
+
blockId: string,
|
|
40
|
+
filename: string,
|
|
41
|
+
now: number = Date.now(),
|
|
42
|
+
): CodeBlockDownloadedEvent => ({
|
|
43
|
+
type: "CODE_BLOCK_DOWNLOADED",
|
|
44
|
+
timestamp: now,
|
|
45
|
+
blockId,
|
|
46
|
+
filename,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
export const createLineHighlightedEvent = (
|
|
50
|
+
blockId: string,
|
|
51
|
+
lineNumber: number,
|
|
52
|
+
now: number = Date.now(),
|
|
53
|
+
): LineHighlightedEvent => ({
|
|
54
|
+
type: "LINE_HIGHLIGHTED",
|
|
55
|
+
timestamp: now,
|
|
56
|
+
blockId,
|
|
57
|
+
lineNumber,
|
|
58
|
+
});
|