@apimatic/cli 1.4.0 → 2.0.0-beta.1
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/LICENSE +21 -0
- package/README.md +128 -212
- package/apimatic.schema.json +550 -0
- package/lib/actions/action-result.js +3 -0
- package/lib/actions/action-result.js.map +1 -1
- package/lib/actions/api/transform.js +36 -33
- package/lib/actions/api/transform.js.map +1 -1
- package/lib/actions/api/validate.d.ts +9 -6
- package/lib/actions/api/validate.js +44 -38
- package/lib/actions/api/validate.js.map +1 -1
- package/lib/actions/auth/login.js +7 -4
- package/lib/actions/auth/login.js.map +1 -1
- package/lib/actions/auth/logout.js +2 -1
- package/lib/actions/auth/logout.js.map +1 -1
- package/lib/actions/auth/status.js +5 -3
- package/lib/actions/auth/status.js.map +1 -1
- package/lib/actions/plugin/generate.d.ts +5 -1
- package/lib/actions/plugin/generate.js +83 -56
- package/lib/actions/plugin/generate.js.map +1 -1
- package/lib/actions/plugin/publish.d.ts +2 -1
- package/lib/actions/plugin/publish.js +34 -37
- package/lib/actions/plugin/publish.js.map +1 -1
- package/lib/actions/plugin/record-metadata.d.ts +3 -2
- package/lib/actions/plugin/record-metadata.js +33 -30
- package/lib/actions/plugin/record-metadata.js.map +1 -1
- package/lib/actions/portal/generate.d.ts +8 -6
- package/lib/actions/portal/generate.js +50 -61
- package/lib/actions/portal/generate.js.map +1 -1
- package/lib/actions/portal/prepare-project.d.ts +25 -0
- package/lib/actions/portal/prepare-project.js +68 -0
- package/lib/actions/portal/prepare-project.js.map +1 -0
- package/lib/actions/portal/serve.d.ts +21 -6
- package/lib/actions/portal/serve.js +170 -153
- package/lib/actions/portal/serve.js.map +1 -1
- package/lib/actions/publishing/profile/list.js +4 -2
- package/lib/actions/publishing/profile/list.js.map +1 -1
- package/lib/actions/quickstart.d.ts +23 -5
- package/lib/actions/quickstart.js +222 -21
- package/lib/actions/quickstart.js.map +1 -1
- package/lib/actions/sdk/generate.d.ts +4 -7
- package/lib/actions/sdk/generate.js +53 -93
- package/lib/actions/sdk/generate.js.map +1 -1
- package/lib/actions/sdk/publish/interactive.d.ts +4 -3
- package/lib/actions/sdk/publish/interactive.js +96 -97
- package/lib/actions/sdk/publish/interactive.js.map +1 -1
- package/lib/actions/sdk/publish/non-interactive.d.ts +4 -2
- package/lib/actions/sdk/publish/non-interactive.js +91 -90
- package/lib/actions/sdk/publish/non-interactive.js.map +1 -1
- package/lib/actions/sdk/publish.d.ts +3 -2
- package/lib/actions/sdk/publish.js +55 -53
- package/lib/actions/sdk/publish.js.map +1 -1
- package/lib/actions/sdk/record-published-sdk.d.ts +14 -0
- package/lib/actions/sdk/record-published-sdk.js +57 -0
- package/lib/actions/sdk/record-published-sdk.js.map +1 -0
- package/lib/client-utils/auth-manager.js +1 -1
- package/lib/client-utils/auth-manager.js.map +1 -1
- package/lib/commands/api/transform.d.ts +1 -1
- package/lib/commands/api/transform.js +44 -40
- package/lib/commands/api/transform.js.map +1 -1
- package/lib/commands/api/validate.js +16 -16
- package/lib/commands/api/validate.js.map +1 -1
- package/lib/commands/auth/login.js +14 -15
- package/lib/commands/auth/login.js.map +1 -1
- package/lib/commands/auth/logout.js +5 -6
- package/lib/commands/auth/logout.js.map +1 -1
- package/lib/commands/auth/status.js +4 -5
- package/lib/commands/auth/status.js.map +1 -1
- package/lib/commands/plugin/generate.d.ts +3 -3
- package/lib/commands/plugin/generate.js +20 -20
- package/lib/commands/plugin/generate.js.map +1 -1
- package/lib/commands/plugin/publish.js +18 -18
- package/lib/commands/plugin/publish.js.map +1 -1
- package/lib/commands/portal/generate.d.ts +9 -8
- package/lib/commands/portal/generate.js +36 -27
- package/lib/commands/portal/generate.js.map +1 -1
- package/lib/commands/portal/serve.d.ts +7 -9
- package/lib/commands/portal/serve.js +41 -44
- package/lib/commands/portal/serve.js.map +1 -1
- package/lib/commands/publishing/profile/list.js +8 -12
- package/lib/commands/publishing/profile/list.js.map +1 -1
- package/lib/commands/quickstart.d.ts +5 -5
- package/lib/commands/quickstart.js +18 -24
- package/lib/commands/quickstart.js.map +1 -1
- package/lib/commands/sdk/generate.d.ts +5 -7
- package/lib/commands/sdk/generate.js +58 -70
- package/lib/commands/sdk/generate.js.map +1 -1
- package/lib/commands/sdk/publish.d.ts +6 -7
- package/lib/commands/sdk/publish.js +66 -60
- package/lib/commands/sdk/publish.js.map +1 -1
- package/lib/hooks/node-version.d.ts +3 -0
- package/lib/hooks/node-version.js +12 -0
- package/lib/hooks/node-version.js.map +1 -0
- package/lib/hooks/not-found.js +2 -3
- package/lib/hooks/not-found.js.map +1 -1
- package/lib/hooks/utils.d.ts +4 -2
- package/lib/hooks/utils.js +2 -2
- package/lib/hooks/utils.js.map +1 -1
- package/lib/infrastructure/env-info.d.ts +3 -0
- package/lib/infrastructure/env-info.js +13 -6
- package/lib/infrastructure/env-info.js.map +1 -1
- package/lib/infrastructure/file-service.d.ts +35 -8
- package/lib/infrastructure/file-service.js +149 -53
- package/lib/infrastructure/file-service.js.map +1 -1
- package/lib/infrastructure/file-watch-service.d.ts +40 -0
- package/lib/infrastructure/file-watch-service.js +167 -0
- package/lib/infrastructure/file-watch-service.js.map +1 -0
- package/lib/infrastructure/generation-status-poller.d.ts +8 -7
- package/lib/infrastructure/generation-status-poller.js +20 -12
- package/lib/infrastructure/generation-status-poller.js.map +1 -1
- package/lib/infrastructure/launcher-service.js +6 -6
- package/lib/infrastructure/launcher-service.js.map +1 -1
- package/lib/infrastructure/network-service.d.ts +3 -0
- package/lib/infrastructure/network-service.js +15 -1
- package/lib/infrastructure/network-service.js.map +1 -1
- package/lib/infrastructure/portal-build-service.d.ts +19 -0
- package/lib/infrastructure/portal-build-service.js +58 -0
- package/lib/infrastructure/portal-build-service.js.map +1 -0
- package/lib/infrastructure/portal-dev-server-service.d.ts +29 -0
- package/lib/infrastructure/portal-dev-server-service.js +135 -0
- package/lib/infrastructure/portal-dev-server-service.js.map +1 -0
- package/lib/infrastructure/portal-pages-service.d.ts +17 -0
- package/lib/infrastructure/portal-pages-service.js +103 -0
- package/lib/infrastructure/portal-pages-service.js.map +1 -0
- package/lib/infrastructure/portal-project-service.d.ts +77 -0
- package/lib/infrastructure/portal-project-service.js +309 -0
- package/lib/infrastructure/portal-project-service.js.map +1 -0
- package/lib/infrastructure/service-error.d.ts +1 -0
- package/lib/infrastructure/service-error.js +39 -41
- package/lib/infrastructure/service-error.js.map +1 -1
- package/lib/infrastructure/services/api-client-factory.js +15 -13
- package/lib/infrastructure/services/api-client-factory.js.map +1 -1
- package/lib/infrastructure/services/api-service.d.ts +7 -5
- package/lib/infrastructure/services/api-service.js +28 -20
- package/lib/infrastructure/services/api-service.js.map +1 -1
- package/lib/infrastructure/services/auth-service.js +4 -9
- package/lib/infrastructure/services/auth-service.js.map +1 -1
- package/lib/infrastructure/services/file-download-service.js +3 -3
- package/lib/infrastructure/services/file-download-service.js.map +1 -1
- package/lib/infrastructure/services/plugin-service.d.ts +1 -8
- package/lib/infrastructure/services/plugin-service.js +11 -22
- package/lib/infrastructure/services/plugin-service.js.map +1 -1
- package/lib/infrastructure/services/portal-artifacts-service.d.ts +44 -0
- package/lib/infrastructure/services/portal-artifacts-service.js +268 -0
- package/lib/infrastructure/services/portal-artifacts-service.js.map +1 -0
- package/lib/infrastructure/services/portal-authorization-service.d.ts +22 -0
- package/lib/infrastructure/services/portal-authorization-service.js +23 -0
- package/lib/infrastructure/services/portal-authorization-service.js.map +1 -0
- package/lib/infrastructure/services/publishing-api-service.d.ts +1 -1
- package/lib/infrastructure/services/publishing-api-service.js +11 -15
- package/lib/infrastructure/services/publishing-api-service.js.map +1 -1
- package/lib/infrastructure/services/sdk-generation-service.d.ts +21 -0
- package/lib/infrastructure/services/sdk-generation-service.js +118 -0
- package/lib/infrastructure/services/sdk-generation-service.js.map +1 -0
- package/lib/infrastructure/services/telemetry-service.js +5 -4
- package/lib/infrastructure/services/telemetry-service.js.map +1 -1
- package/lib/infrastructure/services/transformation-service.js +29 -32
- package/lib/infrastructure/services/transformation-service.js.map +1 -1
- package/lib/infrastructure/services/validation-service.d.ts +12 -53
- package/lib/infrastructure/services/validation-service.js +24 -199
- package/lib/infrastructure/services/validation-service.js.map +1 -1
- package/lib/infrastructure/timer-extensions.d.ts +2 -0
- package/lib/infrastructure/timer-extensions.js +10 -0
- package/lib/infrastructure/timer-extensions.js.map +1 -0
- package/lib/infrastructure/tmp-extensions.d.ts +31 -1
- package/lib/infrastructure/tmp-extensions.js +86 -3
- package/lib/infrastructure/tmp-extensions.js.map +1 -1
- package/lib/infrastructure/zip-service.js +2 -2
- package/lib/infrastructure/zip-service.js.map +1 -1
- package/lib/prompts/api/validate.d.ts +6 -7
- package/lib/prompts/api/validate.js +12 -12
- package/lib/prompts/api/validate.js.map +1 -1
- package/lib/prompts/format.d.ts +4 -0
- package/lib/prompts/format.js +5 -0
- package/lib/prompts/format.js.map +1 -1
- package/lib/prompts/plugin/generate.d.ts +17 -12
- package/lib/prompts/plugin/generate.js +94 -44
- package/lib/prompts/plugin/generate.js.map +1 -1
- package/lib/prompts/plugin/publish.d.ts +2 -1
- package/lib/prompts/plugin/publish.js +8 -7
- package/lib/prompts/plugin/publish.js.map +1 -1
- package/lib/prompts/plugin/record-metadata.d.ts +1 -0
- package/lib/prompts/plugin/record-metadata.js +10 -7
- package/lib/prompts/plugin/record-metadata.js.map +1 -1
- package/lib/prompts/portal/artifacts.d.ts +8 -0
- package/lib/prompts/portal/artifacts.js +26 -0
- package/lib/prompts/portal/artifacts.js.map +1 -0
- package/lib/prompts/portal/authorization.d.ts +6 -0
- package/lib/prompts/portal/authorization.js +29 -0
- package/lib/prompts/portal/authorization.js.map +1 -0
- package/lib/prompts/portal/code-samples.d.ts +1 -0
- package/lib/prompts/portal/code-samples.js +9 -0
- package/lib/prompts/portal/code-samples.js.map +1 -0
- package/lib/prompts/portal/generate.d.ts +14 -9
- package/lib/prompts/portal/generate.js +48 -26
- package/lib/prompts/portal/generate.js.map +1 -1
- package/lib/prompts/portal/prepare-project.d.ts +20 -0
- package/lib/prompts/portal/prepare-project.js +36 -0
- package/lib/prompts/portal/prepare-project.js.map +1 -0
- package/lib/prompts/portal/serve.d.ts +32 -12
- package/lib/prompts/portal/serve.js +102 -39
- package/lib/prompts/portal/serve.js.map +1 -1
- package/lib/prompts/portal/source.d.ts +26 -0
- package/lib/prompts/portal/source.js +253 -0
- package/lib/prompts/portal/source.js.map +1 -0
- package/lib/prompts/prompt.d.ts +11 -3
- package/lib/prompts/prompt.js +39 -15
- package/lib/prompts/prompt.js.map +1 -1
- package/lib/prompts/quickstart.d.ts +46 -3
- package/lib/prompts/quickstart.js +199 -12
- package/lib/prompts/quickstart.js.map +1 -1
- package/lib/prompts/sdk/generate.d.ts +15 -13
- package/lib/prompts/sdk/generate.js +29 -32
- package/lib/prompts/sdk/generate.js.map +1 -1
- package/lib/prompts/sdk/publish/interactive.d.ts +10 -6
- package/lib/prompts/sdk/publish/interactive.js +26 -24
- package/lib/prompts/sdk/publish/interactive.js.map +1 -1
- package/lib/prompts/sdk/publish/non-interactive.d.ts +2 -1
- package/lib/prompts/sdk/publish/non-interactive.js +5 -4
- package/lib/prompts/sdk/publish/non-interactive.js.map +1 -1
- package/lib/prompts/sdk/publish.d.ts +3 -3
- package/lib/prompts/sdk/publish.js +13 -17
- package/lib/prompts/sdk/publish.js.map +1 -1
- package/lib/prompts/sdk/record-published-sdk.d.ts +7 -0
- package/lib/prompts/sdk/record-published-sdk.js +26 -0
- package/lib/prompts/sdk/record-published-sdk.js.map +1 -0
- package/lib/types/apimatic-config/document.d.ts +70 -0
- package/lib/types/apimatic-config/document.js +167 -0
- package/lib/types/apimatic-config/document.js.map +1 -0
- package/lib/types/apimatic-config/languages-block.d.ts +39 -0
- package/lib/types/apimatic-config/languages-block.js +58 -0
- package/lib/types/apimatic-config/languages-block.js.map +1 -0
- package/lib/types/apimatic-config-context.d.ts +48 -0
- package/lib/types/apimatic-config-context.js +121 -0
- package/lib/types/apimatic-config-context.js.map +1 -0
- package/lib/types/build/build.d.ts +1 -89
- package/lib/types/build/build.js +6 -161
- package/lib/types/build/build.js.map +1 -1
- package/lib/types/events/domain-event.js +3 -0
- package/lib/types/events/domain-event.js.map +1 -1
- package/lib/types/events/quickstart-completed.js +3 -3
- package/lib/types/events/quickstart-completed.js.map +1 -1
- package/lib/types/events/quickstart-initiated.js +3 -3
- package/lib/types/events/quickstart-initiated.js.map +1 -1
- package/lib/types/events/sdk-publish-validation-failed.js +1 -1
- package/lib/types/events/sdk-publish-validation-failed.js.map +1 -1
- package/lib/types/file/directory.d.ts +4 -9
- package/lib/types/file/directory.js +17 -65
- package/lib/types/file/directory.js.map +1 -1
- package/lib/types/file/directoryPath.d.ts +17 -0
- package/lib/types/file/directoryPath.js +47 -2
- package/lib/types/file/directoryPath.js.map +1 -1
- package/lib/types/file/fileName.d.ts +8 -0
- package/lib/types/file/fileName.js +28 -6
- package/lib/types/file/fileName.js.map +1 -1
- package/lib/types/file/filePath.d.ts +18 -2
- package/lib/types/file/filePath.js +39 -4
- package/lib/types/file/filePath.js.map +1 -1
- package/lib/types/file/resource-input.js +1 -2
- package/lib/types/file/resource-input.js.map +1 -1
- package/lib/types/file/urlPath.js +2 -1
- package/lib/types/file/urlPath.js.map +1 -1
- package/lib/types/flags-provider.js +22 -22
- package/lib/types/flags-provider.js.map +1 -1
- package/lib/types/package-settings-context.js +2 -1
- package/lib/types/package-settings-context.js.map +1 -1
- package/lib/types/plugin/plugin-config.d.ts +10 -50
- package/lib/types/plugin/plugin-config.js +7 -1
- package/lib/types/plugin/plugin-config.js.map +1 -1
- package/lib/types/plugin-config-context.d.ts +30 -37
- package/lib/types/plugin-config-context.js +108 -139
- package/lib/types/plugin-config-context.js.map +1 -1
- package/lib/types/plugin-context.js +3 -2
- package/lib/types/plugin-context.js.map +1 -1
- package/lib/types/portal/code-samples.d.ts +27 -0
- package/lib/types/portal/code-samples.js +67 -0
- package/lib/types/portal/code-samples.js.map +1 -0
- package/lib/types/portal/config/ai-config.d.ts +15 -0
- package/lib/types/portal/config/ai-config.js +26 -0
- package/lib/types/portal/config/ai-config.js.map +1 -0
- package/lib/types/portal/config/brand-config.d.ts +55 -0
- package/lib/types/portal/config/brand-config.js +105 -0
- package/lib/types/portal/config/brand-config.js.map +1 -0
- package/lib/types/portal/config/color.d.ts +19 -0
- package/lib/types/portal/config/color.js +43 -0
- package/lib/types/portal/config/color.js.map +1 -0
- package/lib/types/portal/config/fields.d.ts +23 -0
- package/lib/types/portal/config/fields.js +67 -0
- package/lib/types/portal/config/fields.js.map +1 -0
- package/lib/types/portal/config/link.d.ts +18 -0
- package/lib/types/portal/config/link.js +72 -0
- package/lib/types/portal/config/link.js.map +1 -0
- package/lib/types/portal/config/navigation-config.d.ts +13 -0
- package/lib/types/portal/config/navigation-config.js +30 -0
- package/lib/types/portal/config/navigation-config.js.map +1 -0
- package/lib/types/portal/config/site-config.d.ts +32 -0
- package/lib/types/portal/config/site-config.js +88 -0
- package/lib/types/portal/config/site-config.js.map +1 -0
- package/lib/types/portal/config/static-asset.d.ts +25 -0
- package/lib/types/portal/config/static-asset.js +77 -0
- package/lib/types/portal/config/static-asset.js.map +1 -0
- package/lib/types/portal/content-notices.d.ts +20 -0
- package/lib/types/portal/content-notices.js +2 -0
- package/lib/types/portal/content-notices.js.map +1 -0
- package/lib/types/portal/content-tree.d.ts +103 -0
- package/lib/types/portal/content-tree.js +344 -0
- package/lib/types/portal/content-tree.js.map +1 -0
- package/lib/types/portal/endpoint.d.ts +6 -0
- package/lib/types/portal/endpoint.js +12 -0
- package/lib/types/portal/endpoint.js.map +1 -0
- package/lib/types/portal/generated-pages.d.ts +68 -0
- package/lib/types/portal/generated-pages.js +111 -0
- package/lib/types/portal/generated-pages.js.map +1 -0
- package/lib/types/portal/generation-status.d.ts +31 -0
- package/lib/types/portal/generation-status.js +26 -0
- package/lib/types/portal/generation-status.js.map +1 -0
- package/lib/types/portal/openapi-document.d.ts +48 -0
- package/lib/types/portal/openapi-document.js +138 -0
- package/lib/types/portal/openapi-document.js.map +1 -0
- package/lib/types/portal/page-fragments.d.ts +13 -0
- package/lib/types/portal/page-fragments.js +24 -0
- package/lib/types/portal/page-fragments.js.map +1 -0
- package/lib/types/portal/page-template.d.ts +15 -0
- package/lib/types/portal/page-template.js +64 -0
- package/lib/types/portal/page-template.js.map +1 -0
- package/lib/types/portal/page.d.ts +24 -0
- package/lib/types/portal/page.js +100 -0
- package/lib/types/portal/page.js.map +1 -0
- package/lib/types/portal/portal-artifacts.d.ts +25 -0
- package/lib/types/portal/portal-artifacts.js +30 -0
- package/lib/types/portal/portal-artifacts.js.map +1 -0
- package/lib/types/portal/portal-config.d.ts +69 -0
- package/lib/types/portal/portal-config.js +105 -0
- package/lib/types/portal/portal-config.js.map +1 -0
- package/lib/types/portal/portal-downloads.d.ts +8 -0
- package/lib/types/portal/portal-downloads.js +14 -0
- package/lib/types/portal/portal-downloads.js.map +1 -0
- package/lib/types/portal/portal-languages.d.ts +15 -0
- package/lib/types/portal/portal-languages.js +45 -0
- package/lib/types/portal/portal-languages.js.map +1 -0
- package/lib/types/portal/portal-navigation.d.ts +65 -0
- package/lib/types/portal/portal-navigation.js +217 -0
- package/lib/types/portal/portal-navigation.js.map +1 -0
- package/lib/types/portal/portal-sdk.d.ts +15 -0
- package/lib/types/portal/portal-sdk.js +28 -0
- package/lib/types/portal/portal-sdk.js.map +1 -0
- package/lib/types/portal/portal-source.d.ts +129 -0
- package/lib/types/portal/portal-source.js +2 -0
- package/lib/types/portal/portal-source.js.map +1 -0
- package/lib/types/portal/portal-stylesheet.d.ts +19 -0
- package/lib/types/portal/portal-stylesheet.js +40 -0
- package/lib/types/portal/portal-stylesheet.js.map +1 -0
- package/lib/types/portal/portal-tabs.d.ts +33 -0
- package/lib/types/portal/portal-tabs.js +38 -0
- package/lib/types/portal/portal-tabs.js.map +1 -0
- package/lib/types/portal/preview-config.d.ts +20 -0
- package/lib/types/portal/preview-config.js +32 -0
- package/lib/types/portal/preview-config.js.map +1 -0
- package/lib/types/portal/preview-content.d.ts +18 -0
- package/lib/types/portal/preview-content.js +50 -0
- package/lib/types/portal/preview-content.js.map +1 -0
- package/lib/types/portal/published-package.d.ts +12 -0
- package/lib/types/portal/published-package.js +35 -0
- package/lib/types/portal/published-package.js.map +1 -0
- package/lib/types/portal/unknown-fields.d.ts +5 -0
- package/lib/types/portal/unknown-fields.js +10 -0
- package/lib/types/portal/unknown-fields.js.map +1 -0
- package/lib/types/portal-context.d.ts +38 -6
- package/lib/types/portal-context.js +71 -24
- package/lib/types/portal-context.js.map +1 -1
- package/lib/types/portal-source-context.d.ts +77 -0
- package/lib/types/portal-source-context.js +408 -0
- package/lib/types/portal-source-context.js.map +1 -0
- package/lib/types/project-context.d.ts +49 -0
- package/lib/types/project-context.js +151 -0
- package/lib/types/project-context.js.map +1 -0
- package/lib/types/project-layout.d.ts +9 -0
- package/lib/types/project-layout.js +10 -0
- package/lib/types/project-layout.js.map +1 -0
- package/lib/types/publish/package-settings-configuration.d.ts +2 -0
- package/lib/types/publish/profile-id.js +1 -0
- package/lib/types/publish/profile-id.js.map +1 -1
- package/lib/types/publish/publishing-profile.d.ts +3 -1
- package/lib/types/publish/publishing-profile.js +33 -15
- package/lib/types/publish/publishing-profile.js.map +1 -1
- package/lib/types/publish/publishing-profiles.js +2 -1
- package/lib/types/publish/publishing-profiles.js.map +1 -1
- package/lib/types/publish/version.js +1 -0
- package/lib/types/publish/version.js.map +1 -1
- package/lib/types/resource-context.js +3 -2
- package/lib/types/resource-context.js.map +1 -1
- package/lib/types/sdk/generate.d.ts +22 -16
- package/lib/types/sdk/generate.js +64 -41
- package/lib/types/sdk/generate.js.map +1 -1
- package/lib/types/sdk/stability-choice.d.ts +12 -0
- package/lib/types/sdk/stability-choice.js +24 -0
- package/lib/types/sdk/stability-choice.js.map +1 -0
- package/lib/types/sdk-context.d.ts +4 -5
- package/lib/types/sdk-context.js +10 -18
- package/lib/types/sdk-context.js.map +1 -1
- package/lib/types/spec-context.d.ts +5 -4
- package/lib/types/spec-context.js +9 -8
- package/lib/types/spec-context.js.map +1 -1
- package/lib/types/temp-context.d.ts +1 -1
- package/lib/types/temp-context.js +5 -4
- package/lib/types/temp-context.js.map +1 -1
- package/lib/types/transform-context.d.ts +9 -5
- package/lib/types/transform-context.js +21 -18
- package/lib/types/transform-context.js.map +1 -1
- package/lib/utils/error-utils.d.ts +2 -0
- package/lib/utils/error-utils.js +5 -0
- package/lib/utils/error-utils.js.map +1 -0
- package/lib/utils/json-utils.d.ts +2 -0
- package/lib/utils/json-utils.js +4 -0
- package/lib/utils/json-utils.js.map +1 -0
- package/lib/utils/string-utils.d.ts +8 -1
- package/lib/utils/string-utils.js +13 -26
- package/lib/utils/string-utils.js.map +1 -1
- package/lib/utils/utils.d.ts +1 -3
- package/lib/utils/utils.js +12 -40
- package/lib/utils/utils.js.map +1 -1
- package/oclif.manifest.json +69 -357
- package/package.json +47 -25
- package/portal-pages/context-plugin.mdx +26 -0
- package/portal-pages/sdk.mdx +8 -0
- package/portal-pages/sdks.mdx +11 -0
- package/portal-template/NOTICE +37 -0
- package/portal-template/downloads.ts +41 -0
- package/portal-template/generated-pages-reload.ts +51 -0
- package/portal-template/package.json +11 -0
- package/portal-template/portal-config.ts +32 -0
- package/portal-template/prerender-pages.ts +81 -0
- package/portal-template/spec-reload.ts +26 -0
- package/portal-template/src/components/api-page.tsx +15 -0
- package/portal-template/src/components/code-block.tsx +9 -0
- package/portal-template/src/components/command-block.tsx +50 -0
- package/portal-template/src/components/example-layout.tsx +99 -0
- package/portal-template/src/components/logos.tsx +127 -0
- package/portal-template/src/components/mdx.tsx +28 -0
- package/portal-template/src/components/not-found.tsx +11 -0
- package/portal-template/src/components/plugin-install.tsx +22 -0
- package/portal-template/src/components/plugin-support.tsx +53 -0
- package/portal-template/src/components/sdk-actions.tsx +79 -0
- package/portal-template/src/components/sdk-cards.tsx +100 -0
- package/portal-template/src/components/search.tsx +41 -0
- package/portal-template/src/components/usage-tabs.tsx +92 -0
- package/portal-template/src/lib/code-samples.server.ts +40 -0
- package/portal-template/src/lib/code-samples.ts +43 -0
- package/portal-template/src/lib/code-titles.ts +29 -0
- package/portal-template/src/lib/install-command.ts +13 -0
- package/portal-template/src/lib/json.ts +5 -0
- package/portal-template/src/lib/layout.shared.tsx +41 -0
- package/portal-template/src/lib/layout.tsx +16 -0
- package/portal-template/src/lib/llms.server.ts +54 -0
- package/portal-template/src/lib/media-adapters.ts +34 -0
- package/portal-template/src/lib/navigation.ts +406 -0
- package/portal-template/src/lib/openapi-bundle.server.ts +343 -0
- package/portal-template/src/lib/openapi-filter.ts +191 -0
- package/portal-template/src/lib/openapi-labels.ts +15 -0
- package/portal-template/src/lib/openapi-methods.ts +12 -0
- package/portal-template/src/lib/openapi-section.server.ts +280 -0
- package/portal-template/src/lib/openapi-slim.ts +202 -0
- package/portal-template/src/lib/openapi.server.ts +16 -0
- package/portal-template/src/lib/portal-types.ts +36 -0
- package/portal-template/src/lib/portal.server.ts +13 -0
- package/portal-template/src/lib/portal.ts +11 -0
- package/portal-template/src/lib/rehype-code-titles.ts +53 -0
- package/portal-template/src/lib/request-examples.ts +72 -0
- package/portal-template/src/lib/seo.ts +21 -0
- package/portal-template/src/lib/shared.ts +51 -0
- package/portal-template/src/lib/shiki-bundle.ts +104 -0
- package/portal-template/src/lib/sitemap.server.ts +20 -0
- package/portal-template/src/lib/source.server.ts +27 -0
- package/portal-template/src/lib/source.ts +49 -0
- package/portal-template/src/lib/tabs.ts +37 -0
- package/portal-template/src/router.tsx +12 -0
- package/portal-template/src/routes/$.tsx +194 -0
- package/portal-template/src/routes/__root.tsx +56 -0
- package/portal-template/src/routes/api/search[.]json.ts +9 -0
- package/portal-template/src/routes/llms-full[.]txt.ts +10 -0
- package/portal-template/src/routes/llms[.]txt.ts +10 -0
- package/portal-template/src/routes/robots[.]txt.ts +10 -0
- package/portal-template/src/routes/sitemap[.]xml.ts +10 -0
- package/portal-template/src/routes/spa-shell.tsx +7 -0
- package/portal-template/src/routes/{$}[.]md.ts +21 -0
- package/portal-template/src/styles/app.css +33 -0
- package/portal-template/tsconfig.json +26 -0
- package/portal-template/vite.config.ts +68 -0
- package/lib/actions/plugin/record-sdk.d.ts +0 -14
- package/lib/actions/plugin/record-sdk.js +0 -60
- package/lib/actions/plugin/record-sdk.js.map +0 -1
- package/lib/actions/portal/copilot.d.ts +0 -17
- package/lib/actions/portal/copilot.js +0 -89
- package/lib/actions/portal/copilot.js.map +0 -1
- package/lib/actions/portal/quickstart.d.ts +0 -18
- package/lib/actions/portal/quickstart.js +0 -238
- package/lib/actions/portal/quickstart.js.map +0 -1
- package/lib/actions/portal/recipe/new-recipe.d.ts +0 -15
- package/lib/actions/portal/recipe/new-recipe.js +0 -177
- package/lib/actions/portal/recipe/new-recipe.js.map +0 -1
- package/lib/actions/portal/toc/new-toc.d.ts +0 -21
- package/lib/actions/portal/toc/new-toc.js +0 -95
- package/lib/actions/portal/toc/new-toc.js.map +0 -1
- package/lib/actions/sdk/merge-source-tree.d.ts +0 -12
- package/lib/actions/sdk/merge-source-tree.js +0 -78
- package/lib/actions/sdk/merge-source-tree.js.map +0 -1
- package/lib/actions/sdk/quickstart.d.ts +0 -18
- package/lib/actions/sdk/quickstart.js +0 -188
- package/lib/actions/sdk/quickstart.js.map +0 -1
- package/lib/actions/sdk/save-changes.d.ts +0 -8
- package/lib/actions/sdk/save-changes.js +0 -93
- package/lib/actions/sdk/save-changes.js.map +0 -1
- package/lib/application/portal/recipe/portal-recipe.d.ts +0 -8
- package/lib/application/portal/recipe/portal-recipe.js +0 -33
- package/lib/application/portal/recipe/portal-recipe.js.map +0 -1
- package/lib/application/portal/recipe/recipe-generator.d.ts +0 -21
- package/lib/application/portal/recipe/recipe-generator.js +0 -130
- package/lib/application/portal/recipe/recipe-generator.js.map +0 -1
- package/lib/application/portal/toc/toc-content-parser.d.ts +0 -5
- package/lib/application/portal/toc/toc-content-parser.js +0 -44
- package/lib/application/portal/toc/toc-content-parser.js.map +0 -1
- package/lib/application/portal/toc/toc-structure-generator.d.ts +0 -11
- package/lib/application/portal/toc/toc-structure-generator.js +0 -165
- package/lib/application/portal/toc/toc-structure-generator.js.map +0 -1
- package/lib/commands/portal/copilot.d.ts +0 -13
- package/lib/commands/portal/copilot.js +0 -34
- package/lib/commands/portal/copilot.js.map +0 -1
- package/lib/commands/portal/recipe/new.d.ts +0 -12
- package/lib/commands/portal/recipe/new.js +0 -45
- package/lib/commands/portal/recipe/new.js.map +0 -1
- package/lib/commands/portal/toc/new.d.ts +0 -16
- package/lib/commands/portal/toc/new.js +0 -68
- package/lib/commands/portal/toc/new.js.map +0 -1
- package/lib/commands/sdk/save-changes.d.ts +0 -14
- package/lib/commands/sdk/save-changes.js +0 -51
- package/lib/commands/sdk/save-changes.js.map +0 -1
- package/lib/infrastructure/debounce-service.d.ts +0 -10
- package/lib/infrastructure/debounce-service.js +0 -55
- package/lib/infrastructure/debounce-service.js.map +0 -1
- package/lib/infrastructure/services/portal-service.d.ts +0 -43
- package/lib/infrastructure/services/portal-service.js +0 -337
- package/lib/infrastructure/services/portal-service.js.map +0 -1
- package/lib/prompts/plugin/record-sdk.d.ts +0 -9
- package/lib/prompts/plugin/record-sdk.js +0 -40
- package/lib/prompts/plugin/record-sdk.js.map +0 -1
- package/lib/prompts/portal/copilot.d.ts +0 -18
- package/lib/prompts/portal/copilot.js +0 -82
- package/lib/prompts/portal/copilot.js.map +0 -1
- package/lib/prompts/portal/quickstart.d.ts +0 -42
- package/lib/prompts/portal/quickstart.js +0 -243
- package/lib/prompts/portal/quickstart.js.map +0 -1
- package/lib/prompts/portal/recipe/new-recipe.d.ts +0 -29
- package/lib/prompts/portal/recipe/new-recipe.js +0 -165
- package/lib/prompts/portal/recipe/new-recipe.js.map +0 -1
- package/lib/prompts/portal/toc/new-toc.d.ts +0 -16
- package/lib/prompts/portal/toc/new-toc.js +0 -50
- package/lib/prompts/portal/toc/new-toc.js.map +0 -1
- package/lib/prompts/sdk/merge-source-tree.d.ts +0 -22
- package/lib/prompts/sdk/merge-source-tree.js +0 -90
- package/lib/prompts/sdk/merge-source-tree.js.map +0 -1
- package/lib/prompts/sdk/quickstart.d.ts +0 -38
- package/lib/prompts/sdk/quickstart.js +0 -196
- package/lib/prompts/sdk/quickstart.js.map +0 -1
- package/lib/prompts/sdk/save-changes.d.ts +0 -21
- package/lib/prompts/sdk/save-changes.js +0 -84
- package/lib/prompts/sdk/save-changes.js.map +0 -1
- package/lib/types/build-context.d.ts +0 -26
- package/lib/types/build-context.js +0 -111
- package/lib/types/build-context.js.map +0 -1
- package/lib/types/events/recipe-creation-failed.d.ts +0 -5
- package/lib/types/events/recipe-creation-failed.js +0 -8
- package/lib/types/events/recipe-creation-failed.js.map +0 -1
- package/lib/types/events/sdk-changes-saved.d.ts +0 -8
- package/lib/types/events/sdk-changes-saved.js +0 -11
- package/lib/types/events/sdk-changes-saved.js.map +0 -1
- package/lib/types/events/sdk-changes-tracked.d.ts +0 -8
- package/lib/types/events/sdk-changes-tracked.js +0 -11
- package/lib/types/events/sdk-changes-tracked.js.map +0 -1
- package/lib/types/events/sdk-conflicts-resolved.d.ts +0 -8
- package/lib/types/events/sdk-conflicts-resolved.js +0 -11
- package/lib/types/events/sdk-conflicts-resolved.js.map +0 -1
- package/lib/types/events/toc-creation-failed.d.ts +0 -5
- package/lib/types/events/toc-creation-failed.js +0 -8
- package/lib/types/events/toc-creation-failed.js.map +0 -1
- package/lib/types/merge-source-tree-context.d.ts +0 -28
- package/lib/types/merge-source-tree-context.js +0 -63
- package/lib/types/merge-source-tree-context.js.map +0 -1
- package/lib/types/plugin/language-entry.d.ts +0 -9
- package/lib/types/plugin/language-entry.js +0 -64
- package/lib/types/plugin/language-entry.js.map +0 -1
- package/lib/types/recipe/recipe.d.ts +0 -22
- package/lib/types/recipe/recipe.js +0 -6
- package/lib/types/recipe/recipe.js.map +0 -1
- package/lib/types/recipe-context.d.ts +0 -9
- package/lib/types/recipe-context.js +0 -26
- package/lib/types/recipe-context.js.map +0 -1
- package/lib/types/save-changes-context.d.ts +0 -17
- package/lib/types/save-changes-context.js +0 -42
- package/lib/types/save-changes-context.js.map +0 -1
- package/lib/types/sdl/sdl.d.ts +0 -10
- package/lib/types/sdl/sdl.js +0 -18
- package/lib/types/sdl/sdl.js.map +0 -1
- package/lib/types/toc/toc-components.d.ts +0 -36
- package/lib/types/toc/toc-components.js +0 -94
- package/lib/types/toc/toc-components.js.map +0 -1
- package/lib/types/toc/toc.d.ts +0 -67
- package/lib/types/toc/toc.js +0 -2
- package/lib/types/toc/toc.js.map +0 -1
- package/lib/types/toc-context.d.ts +0 -12
- package/lib/types/toc-context.js +0 -26
- package/lib/types/toc-context.js.map +0 -1
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import type { DistributiveOmit, OperationOutput, PagesBuilder, WebhookOutput } from 'fumadocs-openapi';
|
|
3
|
+
import { createOpenAPI, type OpenAPIPageData } from 'fumadocs-openapi/server';
|
|
4
|
+
import { bundleSpec } from './openapi-bundle.server';
|
|
5
|
+
import { placeCodeSamples, readCodeSamples } from './code-samples.server';
|
|
6
|
+
import { isJsonObject, type JsonObject } from './json';
|
|
7
|
+
import { operationLabels } from './openapi-labels';
|
|
8
|
+
import { withoutInternalOperations } from './openapi-filter';
|
|
9
|
+
import { apiBaseDir } from './shared';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The reference pages of one OpenAPI document, mounted under `api/<slug>`. Shared by the
|
|
13
|
+
* site and the prerender list so the URLs emitted at build time are the routes that exist.
|
|
14
|
+
* One server per document, because `staticSource()` emits pages for every schema its server
|
|
15
|
+
* knows about, so sharing a server across sections duplicates pages.
|
|
16
|
+
*/
|
|
17
|
+
export async function openApiSection(slug: string, file: string, codeSamplesFile: string | null) {
|
|
18
|
+
const load = async () =>
|
|
19
|
+
placeCodeSamples(withoutInternalOperations(await bundleSpec(file)), await readCodeSamples(codeSamplesFile));
|
|
20
|
+
const specName = path.basename(file);
|
|
21
|
+
const baseDir = `${apiBaseDir}/${slug}`;
|
|
22
|
+
const unsafe = new Set<string>();
|
|
23
|
+
const section = await createOpenAPI({ input: { [slug]: load } }).staticSource({
|
|
24
|
+
baseDir,
|
|
25
|
+
groupBy: 'tag',
|
|
26
|
+
meta: true,
|
|
27
|
+
slugify(tag: string) {
|
|
28
|
+
const held = unsafeIn(tag);
|
|
29
|
+
if (held !== undefined) {
|
|
30
|
+
unsafe.add(`the tag '${tag}' holds ${held}`);
|
|
31
|
+
}
|
|
32
|
+
return slugify(tag);
|
|
33
|
+
},
|
|
34
|
+
name(this: PagesBuilder, output: PageOutput) {
|
|
35
|
+
return pageName.call(this, output, unsafe);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
if (unsafe.size > 0) {
|
|
39
|
+
throw new Error(unsafeNamesMessage([...unsafe], specName));
|
|
40
|
+
}
|
|
41
|
+
refuseSharedPages(section.files, specName);
|
|
42
|
+
refuseSharedFolders(section.files, baseDir, specName);
|
|
43
|
+
return { files: section.files };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** What a Windows folder name cannot hold, and what a URL path segment cannot hold unescaped. */
|
|
47
|
+
const UNSAFE_IN_PATH = /[<>:"/\\|?*#%{}[\]^`\p{Cc}]/gu;
|
|
48
|
+
|
|
49
|
+
/** What in a name the portal cannot put in a folder or a URL, or undefined when there is none. */
|
|
50
|
+
function unsafeIn(name: string): string | undefined {
|
|
51
|
+
const characters = [...new Set(name.match(UNSAFE_IN_PATH) ?? [])].map((character) =>
|
|
52
|
+
/\p{Cc}/u.test(character) ? 'a control character' : `'${character}'`
|
|
53
|
+
);
|
|
54
|
+
// Windows drops a trailing dot, and '..' climbs out of the folder.
|
|
55
|
+
const dots = [...(name.startsWith('.') ? ['a leading dot'] : []), ...(name.endsWith('.') ? ['a trailing dot'] : [])];
|
|
56
|
+
const held = [...characters, ...dots];
|
|
57
|
+
return held.length > 0 ? held.join(', ') : undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function unsafeNamesMessage(unsafe: string[], specName: string): string {
|
|
61
|
+
return [
|
|
62
|
+
`Some names in '${specName}' hold what the portal cannot put in a URL or a folder name:`,
|
|
63
|
+
...unsafe.map((line) => ` ${line}`),
|
|
64
|
+
'Remove it from each name. An operation or webhook named after its path or name needs an operationId instead.'
|
|
65
|
+
].join('\n');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** A tag's folder, and the page of a webhook without an operationId. */
|
|
69
|
+
function slugify(name: string): string {
|
|
70
|
+
return name.replace(/\s+/g, '-').toLowerCase();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
type PageOutput = DistributiveOmit<OperationOutput | WebhookOutput, 'path'>;
|
|
74
|
+
|
|
75
|
+
/** Fumadocs' own page name with its spaces made hyphens, noting in `unsafe` what else it cannot hold. */
|
|
76
|
+
function pageName(this: PagesBuilder, output: PageOutput, unsafe: Set<string>): string {
|
|
77
|
+
const found =
|
|
78
|
+
output.type === 'operation' ? this.fromExtractedOperation(output.item) : this.fromExtractedWebhook(output.item);
|
|
79
|
+
const operationId = found?.operation.operationId;
|
|
80
|
+
const method = output.item.method.toUpperCase();
|
|
81
|
+
if (operationId) {
|
|
82
|
+
const held = unsafeIn(operationId);
|
|
83
|
+
const label = output.type === 'operation' ? `${method} ${output.item.path}` : `${method} ${output.item.name} (webhook)`;
|
|
84
|
+
if (held !== undefined) {
|
|
85
|
+
unsafe.add(`the operationId '${operationId}' of ${label} holds ${held}`);
|
|
86
|
+
}
|
|
87
|
+
return operationId.replace(/\s+/g, '-');
|
|
88
|
+
}
|
|
89
|
+
if (output.type === 'webhook') {
|
|
90
|
+
const held = unsafeIn(output.item.name);
|
|
91
|
+
if (held !== undefined) {
|
|
92
|
+
unsafe.add(`the webhook '${output.item.name}' has no operationId, and its name holds ${held}`);
|
|
93
|
+
}
|
|
94
|
+
return slugify(output.item.name);
|
|
95
|
+
}
|
|
96
|
+
const route = this.routePathToFilePath(output.item.path);
|
|
97
|
+
const held = route.split('/').flatMap((segment) => unsafeIn(segment) ?? []);
|
|
98
|
+
if (held.length > 0) {
|
|
99
|
+
unsafe.add(`${method} ${output.item.path} has no operationId, and its path holds ${held.join(', ')}`);
|
|
100
|
+
}
|
|
101
|
+
return path.join(route, output.item.method.toLowerCase());
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
type SectionFile = Awaited<ReturnType<ReturnType<typeof createOpenAPI>['staticSource']>>['files'][number];
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Fumadocs writes a page per tag an operation lists, in the tag's folder and named after the
|
|
108
|
+
* operationId, so two pages land at one path, and one is left out without a word, when two
|
|
109
|
+
* operations share an operationId (or have none, and paths that read alike), or when one
|
|
110
|
+
* operation lists a tag twice, or two tags that differ only in case or spacing.
|
|
111
|
+
*/
|
|
112
|
+
function refuseSharedPages(files: SectionFile[], specName: string): void {
|
|
113
|
+
const pages = new Map<string, OpenAPIPageData>();
|
|
114
|
+
for (const file of files) {
|
|
115
|
+
if (file.type === 'meta') {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const earlier = pages.get(file.path);
|
|
119
|
+
if (earlier) {
|
|
120
|
+
const folder = path.basename(path.dirname(file.path));
|
|
121
|
+
throw new Error(sharedPageMessage(folder, documented(earlier), documented(file.data), specName));
|
|
122
|
+
}
|
|
123
|
+
pages.set(file.path, file.data);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** The operation or webhook a page documents, and what its page is named after. */
|
|
128
|
+
interface DocumentedOperation {
|
|
129
|
+
label: string;
|
|
130
|
+
/** Where it sits in the document: under `paths` or `webhooks`, at a path or name, under a method. */
|
|
131
|
+
location: { in: 'paths' | 'webhooks'; key: string | undefined; method: string | undefined };
|
|
132
|
+
operationId: string | undefined;
|
|
133
|
+
tags: string[];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function documented(page: OpenAPIPageData): DocumentedOperation {
|
|
137
|
+
const props = page.getOpenAPIPageProps();
|
|
138
|
+
const document = props.payload.bundled as unknown as JsonObject;
|
|
139
|
+
const [operation] = props.operations ?? [];
|
|
140
|
+
const [webhook] = props.webhooks ?? [];
|
|
141
|
+
const location = operation
|
|
142
|
+
? { in: 'paths' as const, key: operation.path, method: operation.method }
|
|
143
|
+
: { in: 'webhooks' as const, key: webhook?.name, method: webhook?.method };
|
|
144
|
+
const { operationId, tags } = operationObject(document[location.in], location.key, location.method);
|
|
145
|
+
return {
|
|
146
|
+
label: operationLabels(props).join(', '),
|
|
147
|
+
location,
|
|
148
|
+
operationId: typeof operationId === 'string' ? operationId : undefined,
|
|
149
|
+
tags: Array.isArray(tags) ? tags.filter((tag): tag is string => typeof tag === 'string') : []
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function operationObject(items: unknown, key: string | undefined, method: string | undefined): JsonObject {
|
|
154
|
+
const item = isJsonObject(items) && key !== undefined ? items[key] : undefined;
|
|
155
|
+
const operation = isJsonObject(item) && method !== undefined ? item[method] : undefined;
|
|
156
|
+
return isJsonObject(operation) ? operation : {};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function sharedPageMessage(
|
|
160
|
+
folder: string,
|
|
161
|
+
earlier: DocumentedOperation,
|
|
162
|
+
later: DocumentedOperation,
|
|
163
|
+
specName: string
|
|
164
|
+
): string {
|
|
165
|
+
if (sameLocation(earlier.location, later.location)) {
|
|
166
|
+
return sharedTagFolderMessage(folder, earlier, specName);
|
|
167
|
+
}
|
|
168
|
+
const labels = [` ${earlier.label}`, ` ${later.label}`];
|
|
169
|
+
if (earlier.operationId !== undefined && earlier.operationId === later.operationId) {
|
|
170
|
+
return [
|
|
171
|
+
`Two operations in '${specName}' have the same operationId '${earlier.operationId}':`,
|
|
172
|
+
...labels,
|
|
173
|
+
'Give each operation a unique operationId.'
|
|
174
|
+
].join('\n');
|
|
175
|
+
}
|
|
176
|
+
if (earlier.operationId === undefined && later.operationId === undefined) {
|
|
177
|
+
return [
|
|
178
|
+
`Two operations in '${specName}' have no operationId, so they would be documented on the same page:`,
|
|
179
|
+
...labels,
|
|
180
|
+
'Give each operation an operationId.'
|
|
181
|
+
].join('\n');
|
|
182
|
+
}
|
|
183
|
+
return [
|
|
184
|
+
`Two operations in '${specName}' would be documented on the same page:`,
|
|
185
|
+
...labels,
|
|
186
|
+
'Give each operation a unique operationId.'
|
|
187
|
+
].join('\n');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function sameLocation(a: DocumentedOperation['location'], b: DocumentedOperation['location']): boolean {
|
|
191
|
+
return a.in === b.in && a.key === b.key && a.method === b.method;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** One operation lands twice on a page when two of its tags name the page's folder. */
|
|
195
|
+
function sharedTagFolderMessage(folder: string, operation: DocumentedOperation, specName: string): string {
|
|
196
|
+
const tags = operation.tags.filter((tag) => slugify(tag) === folder);
|
|
197
|
+
const repeated = tags.find((tag, index) => tags.indexOf(tag) !== index);
|
|
198
|
+
if (repeated !== undefined) {
|
|
199
|
+
return [
|
|
200
|
+
`The operation ${operation.label} in '${specName}' lists the tag '${repeated}' more than once.`,
|
|
201
|
+
'Remove the repeated tag.'
|
|
202
|
+
].join('\n');
|
|
203
|
+
}
|
|
204
|
+
if (tags.length >= 2) {
|
|
205
|
+
return [
|
|
206
|
+
`The operation ${operation.label} in '${specName}' lists the tags '${tags[0]}' and '${tags[1]}', which the portal shows as one section.`,
|
|
207
|
+
'Rename one of them so they differ in more than case or spacing.'
|
|
208
|
+
].join('\n');
|
|
209
|
+
}
|
|
210
|
+
return [
|
|
211
|
+
`The operation ${operation.label} in '${specName}' would be documented twice on the same page.`,
|
|
212
|
+
'Give each of its tags a distinct name.'
|
|
213
|
+
].join('\n');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Fumadocs groups by a tag's name but names the folder after its slug, so two tags that slugify
|
|
218
|
+
* alike, on different operations, write two metas into one folder. One replaces the other, and
|
|
219
|
+
* the operations it listed drop out of the sidebar without a word.
|
|
220
|
+
*/
|
|
221
|
+
function refuseSharedFolders(files: SectionFile[], baseDir: string, specName: string): void {
|
|
222
|
+
const metas = new Set<string>();
|
|
223
|
+
for (const file of files) {
|
|
224
|
+
if (file.type !== 'meta') {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (metas.has(file.path)) {
|
|
228
|
+
const folder = path.join(path.relative(baseDir, path.dirname(file.path)));
|
|
229
|
+
const parents = tagParents(files);
|
|
230
|
+
const tags = [...parents.keys()].filter((tag) => folderOf(tag, parents) === folder);
|
|
231
|
+
throw new Error(sharedFolderMessage(folder.split(path.sep).join('/'), tags, specName));
|
|
232
|
+
}
|
|
233
|
+
metas.add(file.path);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** The tags the document declares, then those its operations list without declaring them, each with its parent. */
|
|
238
|
+
function tagParents(files: SectionFile[]): Map<string, string | undefined> {
|
|
239
|
+
const pages = files.flatMap((file) => (file.type === 'meta' ? [] : [file.data]));
|
|
240
|
+
const parents = new Map(pages.flatMap(declaredTags));
|
|
241
|
+
for (const tag of pages.flatMap((page) => documented(page).tags)) {
|
|
242
|
+
if (!parents.has(tag)) {
|
|
243
|
+
parents.set(tag, undefined);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return parents;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** The tags a page's document declares, each with its parent. */
|
|
250
|
+
function declaredTags(page: OpenAPIPageData): [string, string | undefined][] {
|
|
251
|
+
const { tags } = page.getOpenAPIPageProps().payload.bundled as unknown as JsonObject;
|
|
252
|
+
return (Array.isArray(tags) ? tags : []).flatMap((tag): [string, string | undefined][] =>
|
|
253
|
+
isJsonObject(tag) && typeof tag.name === 'string'
|
|
254
|
+
? [[tag.name, typeof tag.parent === 'string' ? tag.parent : undefined]]
|
|
255
|
+
: []
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** Where Fumadocs files a tag: its slug, inside its parent's folder. */
|
|
260
|
+
function folderOf(tag: string, parents: Map<string, string | undefined>, seen = new Set<string>()): string {
|
|
261
|
+
seen.add(tag);
|
|
262
|
+
const parent = parents.get(tag);
|
|
263
|
+
const above = parent !== undefined && !seen.has(parent) ? folderOf(parent, parents, seen) : '';
|
|
264
|
+
return path.join(above, slugify(tag));
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function sharedFolderMessage(folder: string, tags: string[], specName: string): string {
|
|
268
|
+
const [first, second] = tags;
|
|
269
|
+
if (second !== undefined) {
|
|
270
|
+
return [
|
|
271
|
+
`The tags '${first}' and '${second}' in '${specName}' would share the folder '${folder}', so the portal would show only one of them.`,
|
|
272
|
+
'Rename one of them so they differ in more than case or spacing.'
|
|
273
|
+
].join('\n');
|
|
274
|
+
}
|
|
275
|
+
// Fumadocs files the operations that list no tag under 'unknown'.
|
|
276
|
+
return [
|
|
277
|
+
`The tag '${first ?? folder}' in '${specName}' would share the folder '${folder}' with the operations that have no tag.`,
|
|
278
|
+
'Rename the tag, or tag those operations.'
|
|
279
|
+
].join('\n');
|
|
280
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import type { OpenAPIPageProps_Spec } from 'fumadocs-openapi/ui';
|
|
2
|
+
import { isJsonObject } from './json';
|
|
3
|
+
import { OPERATION_METHODS } from './openapi-methods';
|
|
4
|
+
|
|
5
|
+
type Document = OpenAPIPageProps_Spec['payload']['bundled'];
|
|
6
|
+
type Components = Record<string, Record<string, unknown>>;
|
|
7
|
+
type Wanted = { key: string; method?: string };
|
|
8
|
+
|
|
9
|
+
// A reference names a component by kind and name; anything after that addresses a node
|
|
10
|
+
// inside it, which is kept along with the whole component rather than on its own.
|
|
11
|
+
const COMPONENT_REF = /^#\/components\/([^/]+)\/([^/]+)/;
|
|
12
|
+
|
|
13
|
+
// The bundler embeds every external document under `x-ext` and rewrites file and URL
|
|
14
|
+
// references to point inside it. These never take the `#/components/` shape above.
|
|
15
|
+
const EXTERNAL_REF = /^#\/x-ext\/(.+)$/;
|
|
16
|
+
|
|
17
|
+
const METHODS = new Set(OPERATION_METHODS);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Cuts the bundled document down to what this page can reach: the operations it renders, the
|
|
21
|
+
* components and embedded external documents they reference (transitively), and the
|
|
22
|
+
* document-level fields the client reads. Without this every page carries the whole
|
|
23
|
+
* specification twice -- inlined in its HTML and again in the server-function cache -- so the
|
|
24
|
+
* output grows with pages × document size.
|
|
25
|
+
*/
|
|
26
|
+
export function slimOpenAPIPageProps(props: OpenAPIPageProps_Spec): OpenAPIPageProps_Spec {
|
|
27
|
+
const bundled = props.payload.bundled as Document & {
|
|
28
|
+
paths?: Record<string, unknown>;
|
|
29
|
+
webhooks?: Record<string, unknown>;
|
|
30
|
+
components?: Components;
|
|
31
|
+
'x-ext'?: Record<string, unknown>;
|
|
32
|
+
security?: unknown;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const { paths, webhooks, components, 'x-ext': external, ...rest } = bundled;
|
|
36
|
+
const operations: Wanted[] = (props.operations ?? []).map((operation) => ({
|
|
37
|
+
key: operation.path,
|
|
38
|
+
method: operation.method
|
|
39
|
+
}));
|
|
40
|
+
const hooks: Wanted[] = (props.webhooks ?? []).map((webhook) => ({ key: webhook.name, method: webhook.method }));
|
|
41
|
+
|
|
42
|
+
const keptPaths = pick(paths, operations);
|
|
43
|
+
const keptWebhooks = pick(webhooks, hooks);
|
|
44
|
+
// `rest` is a root too: it survives into the output whole, so a reference inside it has to
|
|
45
|
+
// keep resolving. Visiting only collects; nothing is retained for having been looked at.
|
|
46
|
+
const reached = reachable({ components, external }, [rest, keptPaths, keptWebhooks]);
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
...props,
|
|
50
|
+
payload: {
|
|
51
|
+
...props.payload,
|
|
52
|
+
bundled: {
|
|
53
|
+
...rest,
|
|
54
|
+
...(keptPaths && { paths: keptPaths }),
|
|
55
|
+
...(keptWebhooks && { webhooks: keptWebhooks }),
|
|
56
|
+
...(reached.components && { components: reached.components }),
|
|
57
|
+
...(reached.external && { 'x-ext': reached.external })
|
|
58
|
+
} as Document
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Keeps the named path items or webhooks, each cut down to the methods the page renders. */
|
|
64
|
+
function pick(
|
|
65
|
+
record: Record<string, unknown> | undefined,
|
|
66
|
+
wanted: Wanted[]
|
|
67
|
+
): Record<string, unknown> | undefined {
|
|
68
|
+
if (!record) return undefined;
|
|
69
|
+
|
|
70
|
+
const methodsByKey = new Map<string, Set<string>>();
|
|
71
|
+
for (const { key, method } of wanted) {
|
|
72
|
+
if (!(key in record)) continue;
|
|
73
|
+
const methods = methodsByKey.get(key) ?? new Set<string>();
|
|
74
|
+
if (typeof method === 'string') methods.add(method.toLowerCase());
|
|
75
|
+
methodsByKey.set(key, methods);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const out: Record<string, unknown> = {};
|
|
79
|
+
for (const [key, methods] of methodsByKey) out[key] = narrowToMethods(record[key], methods);
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Drops the operations the page does not render. Fumadocs reads only `pathItem[method]`, so
|
|
85
|
+
* without this a page carries every sibling method and the schema closure each one reaches.
|
|
86
|
+
* Non-method fields stay: the renderer reads path-level `parameters` and `servers` here too.
|
|
87
|
+
*/
|
|
88
|
+
function narrowToMethods(item: unknown, methods: Set<string>): unknown {
|
|
89
|
+
if (methods.size === 0 || !isJsonObject(item)) return item;
|
|
90
|
+
|
|
91
|
+
const entries = Object.entries(item);
|
|
92
|
+
// An item that is itself a reference carries no operations to narrow.
|
|
93
|
+
if (entries.some(([key]) => key === '$ref')) return item;
|
|
94
|
+
|
|
95
|
+
const out: Record<string, unknown> = {};
|
|
96
|
+
for (const [key, value] of entries) {
|
|
97
|
+
// Fumadocs builds no page for an operation under `additionalOperations`, so a page never renders one.
|
|
98
|
+
const isOperation = METHODS.has(key.toLowerCase()) || key === 'additionalOperations';
|
|
99
|
+
if (!isOperation || methods.has(key.toLowerCase())) out[key] = value;
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
interface Sections {
|
|
105
|
+
components?: Components;
|
|
106
|
+
external?: Record<string, unknown>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Walks from the roots and keeps everything the page's own items can reach: the named
|
|
111
|
+
* component behind any `#/components/...` string, and the exact node a `#/x-ext/...` string
|
|
112
|
+
* addresses, rebuilt at the same path so the reference still resolves. Matching any string,
|
|
113
|
+
* not only `$ref`, also covers `discriminator.mapping` and vendor extensions.
|
|
114
|
+
*/
|
|
115
|
+
function reachable(sections: Sections, roots: unknown[]): Sections {
|
|
116
|
+
const { components, external } = sections;
|
|
117
|
+
const keptComponents: Components = {};
|
|
118
|
+
const keptExternal: Record<string, unknown> = {};
|
|
119
|
+
|
|
120
|
+
const seenExternal = new Set<string>();
|
|
121
|
+
const queue: unknown[] = [...roots];
|
|
122
|
+
|
|
123
|
+
// Security schemes are looked up by name from `security`, never through a reference, so
|
|
124
|
+
// they are kept whole rather than reached. Still walked, because a scheme in a document
|
|
125
|
+
// bundled from several files carries `#/x-ext/...` references of its own.
|
|
126
|
+
if (components?.securitySchemes) {
|
|
127
|
+
keptComponents.securitySchemes = components.securitySchemes;
|
|
128
|
+
queue.push(components.securitySchemes);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const visitComponent = (kind: string, name: string): void => {
|
|
132
|
+
const target = components?.[kind]?.[name];
|
|
133
|
+
if (target === undefined || keptComponents[kind]?.[name] !== undefined) return;
|
|
134
|
+
keptComponents[kind] ??= {};
|
|
135
|
+
keptComponents[kind][name] = target;
|
|
136
|
+
queue.push(target);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const visitExternal = (pointer: string): void => {
|
|
140
|
+
if (seenExternal.has(pointer)) return;
|
|
141
|
+
seenExternal.add(pointer);
|
|
142
|
+
const segments = pointer.split('/').map(unescapePointer);
|
|
143
|
+
const target = resolvePointer(external, segments);
|
|
144
|
+
if (target === undefined) return;
|
|
145
|
+
writePointer(keptExternal, segments, target);
|
|
146
|
+
queue.push(target);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const visit = (node: unknown): void => {
|
|
150
|
+
if (typeof node === 'string') {
|
|
151
|
+
const component = COMPONENT_REF.exec(node);
|
|
152
|
+
if (component) {
|
|
153
|
+
visitComponent(component[1], unescapePointer(component[2]));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const embedded = EXTERNAL_REF.exec(node);
|
|
157
|
+
if (embedded) visitExternal(embedded[1]);
|
|
158
|
+
} else if (Array.isArray(node)) {
|
|
159
|
+
for (const item of node) visit(item);
|
|
160
|
+
} else if (node !== null && typeof node === 'object') {
|
|
161
|
+
for (const value of Object.values(node)) visit(value);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
while (queue.length > 0) visit(queue.pop());
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
components: components && Object.keys(keptComponents).length > 0 ? keptComponents : undefined,
|
|
169
|
+
external: external && Object.keys(keptExternal).length > 0 ? keptExternal : undefined
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function resolvePointer(root: unknown, segments: string[]): unknown {
|
|
174
|
+
let node: unknown = root;
|
|
175
|
+
for (const segment of segments) {
|
|
176
|
+
if (node === null || typeof node !== 'object') return undefined;
|
|
177
|
+
node = (node as Record<string, unknown>)[segment];
|
|
178
|
+
}
|
|
179
|
+
return node;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Rebuilds just enough of the path for the reference to resolve against what is kept.
|
|
183
|
+
function writePointer(root: Record<string, unknown>, segments: string[], value: unknown): void {
|
|
184
|
+
let node = root;
|
|
185
|
+
for (const segment of segments.slice(0, -1)) {
|
|
186
|
+
const next = node[segment];
|
|
187
|
+
if (next === null || typeof next !== 'object') node[segment] = {};
|
|
188
|
+
node = node[segment] as Record<string, unknown>;
|
|
189
|
+
}
|
|
190
|
+
node[segments.at(-1) as string] = value;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** A component named `a/b` is referenced as `a~1b`, and may also be percent-encoded. */
|
|
194
|
+
function unescapePointer(segment: string): string {
|
|
195
|
+
let decoded = segment;
|
|
196
|
+
try {
|
|
197
|
+
decoded = decodeURIComponent(segment);
|
|
198
|
+
} catch {
|
|
199
|
+
// Not percent-encoded; use it as written.
|
|
200
|
+
}
|
|
201
|
+
return decoded.replaceAll('~1', '/').replaceAll('~0', '~');
|
|
202
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { MetaData, StaticSource } from 'fumadocs-core/source';
|
|
2
|
+
import type { OpenAPIPageData } from 'fumadocs-openapi/server';
|
|
3
|
+
import { openApiSection } from './openapi-section.server';
|
|
4
|
+
import { codeSamplesFile, specs } from './portal.server';
|
|
5
|
+
|
|
6
|
+
export type OpenApiSource = StaticSource<{ pageData: OpenAPIPageData; metaData: MetaData }>;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Every document's reference pages as one source. Merged so a reference page has the same
|
|
10
|
+
* type whichever document it came from, which is what lets the routes tell it apart from a
|
|
11
|
+
* Markdown page.
|
|
12
|
+
*/
|
|
13
|
+
export async function openApiSource(): Promise<OpenApiSource> {
|
|
14
|
+
const sections = await Promise.all(Object.entries(specs).map(([slug, file]) => openApiSection(slug, file, codeSamplesFile)));
|
|
15
|
+
return { files: sections.flatMap((section) => section.files) };
|
|
16
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Kept out of `portal.ts`, which imports the generated `portal.identity.json`, so the CLI's tests
|
|
2
|
+
// can compare these with the type the CLI writes that file from, without a prepared project.
|
|
3
|
+
|
|
4
|
+
export type PortalColorMode = 'light' | 'dark' | 'both';
|
|
5
|
+
|
|
6
|
+
export interface PortalLink {
|
|
7
|
+
label: string;
|
|
8
|
+
url: string;
|
|
9
|
+
/** Whether it leaves the portal, which opens it in a new tab. */
|
|
10
|
+
external: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Site-relative, one per colour mode; the same URL twice when one image serves both. */
|
|
14
|
+
export interface PortalLogo {
|
|
15
|
+
light: string;
|
|
16
|
+
dark: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface PortalFavicon {
|
|
20
|
+
/** Site-relative, and inside the static directory. */
|
|
21
|
+
url: string;
|
|
22
|
+
type: string | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface Portal {
|
|
26
|
+
name: string;
|
|
27
|
+
description: string | null;
|
|
28
|
+
/** Origin only, with no trailing slash. */
|
|
29
|
+
siteUrl: string | null;
|
|
30
|
+
logo: PortalLogo | null;
|
|
31
|
+
favicon: PortalFavicon | null;
|
|
32
|
+
colorMode: PortalColorMode;
|
|
33
|
+
links: PortalLink[];
|
|
34
|
+
/** Whether each page offers to open itself in an external AI assistant. */
|
|
35
|
+
pageActions: boolean;
|
|
36
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import portalConfig from '../../portal.config.json';
|
|
2
|
+
import type { BuildPaths } from '../../portal-config';
|
|
3
|
+
|
|
4
|
+
const paths = portalConfig as BuildPaths;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Section slug to the absolute path of its OpenAPI document on the machine running the build.
|
|
8
|
+
* Behind `.server.` so these paths are never published to a visitor.
|
|
9
|
+
*/
|
|
10
|
+
export const specs = paths.specs;
|
|
11
|
+
|
|
12
|
+
/** The code samples the CLI placed beside `portal.config.json`, or null for a build without any. */
|
|
13
|
+
export const codeSamplesFile = paths.codeSamples;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import identity from '../../portal.identity.json';
|
|
2
|
+
import type { Portal } from './portal-types';
|
|
3
|
+
|
|
4
|
+
// The CLI writes `portal.identity.json` when it prepares the build, and rewrites it when the
|
|
5
|
+
// `portal` block changes under `portal serve`.
|
|
6
|
+
//
|
|
7
|
+
// This module is imported by the browser bundle, and a retained JSON module is not tree-shaken
|
|
8
|
+
// per property, so the file holds these fields and nothing else: importing `portal.config.json`
|
|
9
|
+
// here would publish the build machine's absolute paths to every visitor. Those live in
|
|
10
|
+
// `portal.server.ts`.
|
|
11
|
+
export const portal = identity as Portal;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { parseCodeBlockAttributes } from 'fumadocs-core/mdx-plugins/codeblock-utils';
|
|
2
|
+
import { CODE_BLOCK_TITLES } from './code-titles';
|
|
3
|
+
import { bundledLanguage } from './shiki-bundle';
|
|
4
|
+
|
|
5
|
+
interface HastNode {
|
|
6
|
+
type: string;
|
|
7
|
+
tagName?: string;
|
|
8
|
+
name?: string;
|
|
9
|
+
properties?: { className?: unknown; metastring?: unknown };
|
|
10
|
+
data?: { meta?: string | null };
|
|
11
|
+
children?: HastNode[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const LANGUAGE_CLASS = 'language-';
|
|
15
|
+
|
|
16
|
+
/** Where `remarkCodeTab` puts a tabbed block, by its own tabs or an MDX `<Tabs>`: the tab already names it. */
|
|
17
|
+
const CODE_TABS = new Set(['CodeBlockTab', 'Tab', 'TabsContent']);
|
|
18
|
+
|
|
19
|
+
/** Keeps each fence's meta through `rehype-raw`, which drops `data.meta` and keeps the property `rehypeCode` falls back to. */
|
|
20
|
+
export function rehypeKeepCodeMeta() {
|
|
21
|
+
const visit = (node: HastNode): void => {
|
|
22
|
+
const meta = node.tagName === 'code' ? node.data?.meta : null;
|
|
23
|
+
if (meta) {
|
|
24
|
+
node.properties = { ...node.properties, metastring: meta };
|
|
25
|
+
}
|
|
26
|
+
node.children?.forEach(visit);
|
|
27
|
+
};
|
|
28
|
+
return visit;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Titles each fence its author left untitled after its language, once `rehype-raw` has made HTML ones elements too. */
|
|
32
|
+
export function rehypeCodeTitles() {
|
|
33
|
+
const visit = (node: HastNode): void => {
|
|
34
|
+
if (node.name !== undefined && CODE_TABS.has(node.name)) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const code = node.tagName === 'pre' ? node.children?.[0] : undefined;
|
|
38
|
+
if (code?.tagName !== 'code') {
|
|
39
|
+
node.children?.forEach(visit);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const classes = Array.isArray(code.properties?.className) ? code.properties.className : [];
|
|
43
|
+
const languageClass = classes.find((name) => typeof name === 'string' && name.startsWith(LANGUAGE_CLASS));
|
|
44
|
+
const language = typeof languageClass === 'string' ? bundledLanguage(languageClass.slice(LANGUAGE_CLASS.length)) : null;
|
|
45
|
+
const meta = typeof code.properties?.metastring === 'string' ? code.properties.metastring : '';
|
|
46
|
+
// Read as `rehypeCode` reads it, which shows no title for a bare or unquoted one.
|
|
47
|
+
const { title } = parseCodeBlockAttributes(meta, ['title']).attributes;
|
|
48
|
+
if (language !== null && (title === undefined || title === null)) {
|
|
49
|
+
code.properties = { ...code.properties, metastring: `${meta} title="${CODE_BLOCK_TITLES[language]}"`.trim() };
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
return visit;
|
|
53
|
+
}
|