diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 537a8df4..c6e2bfe2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,6 +18,7 @@ To build this project locally, you will need to have [Node.js](https://nodejs.or You can use the following commands to build this project locally: - `npm install` to install dependencies. +- `npm run generate` to build the OpenAPI reference. - `npm start` to start a development server. - `npm run build` to build a production release (this may be necessary to work with multi-lingual content). - `npm run serve` to serve a production release locally. @@ -33,6 +34,18 @@ $ npm run docusaurus write-translations $ npm run docusaurus write-translations -- -l de ``` +### Provide a custom description for an OpenAPI operation + +By default, the OpenAPI description pages use the OpenAPI `.description` field of the respective resource. You can override the description with a custom markdown document by placing it in `generator/overlays///description.md`. Please note that you will need to run `npm run generate` for changes to take effect. + +### Override an OpenAPI tag index page. + +By default, the tag index pages will be generated from the tag description and an auto-generated list of all operations. You can override this index page by providing a custom markdown document at `generator/overlays//.mdx`. Please note that you will need to run `npm run generate` for changes to take effect. + +### Provide custom usage examples for OpenAPI operations + +The usage examples for OpenAPI operations are generated from the OpenAPI specification. You can override the usage examples by providing a custom markdown document at `generator/overlays///example-.md`. Please note that you will need to run `npm run generate` for changes to take effect. Supported languages are `curl`, `javascript`, `php` and `cli`. + [md]: https://www.markdownguide.org [mdx]: https://mdxjs.com [docu-md]: https://docusaurus.io/docs/markdown-features diff --git a/generator/generate-ref.ts b/generator/generate-ref.ts index 0ecbc71f..f43d582c 100644 --- a/generator/generate-ref.ts +++ b/generator/generate-ref.ts @@ -32,22 +32,67 @@ function determineServerURLAndBasePath(apiVersion: APIVersion, spec: OpenAPIV3.D return [serverURL, basePath]; } -function renderAPISpecToFile(operationFile: string, frontMatterYAML: string, urlPathWithBase: string, method: string, serializedSpec: string, serverURL: string, apiVersion: APIVersion) { +function loadDescriptionOverride(apiVersion: APIVersion, operationId: string): string | undefined { + const descriptionOverridePath = path.join("generator", "overlays", apiVersion, operationId, "description.md"); + if (fs.existsSync(descriptionOverridePath)) { + return fs.readFileSync(descriptionOverridePath, { encoding: "utf-8" }); + } + return undefined; +} + +function loadCodeExample(apiVersion: APIVersion, operationId: string, language: string): string | undefined { + const codeExamplePath = path.join("generator", "overlays", apiVersion, operationId, `example-${language}.md`); + if (fs.existsSync(codeExamplePath)) { + return fs.readFileSync(codeExamplePath, { encoding: "utf-8" }); + } + return undefined; +} + +function renderAPISpecToFile( + operationFile: string, + urlPathWithBase: string, + method: string, + spec: OpenAPIV3.OperationObject, + serverURL: string, + apiVersion: APIVersion, +) { const withSDKExamples = apiVersion !== "v1"; + const serializedSpec = JSON.stringify(spec); + const summary: string = stripTrailingDot(spec.summary); + + const frontMatter = yaml.stringify({ + title: summary ?? spec.operationId, + description: spec.description ?? "", + openapi: { + method + } + }); + + const descriptionOverride = loadDescriptionOverride(apiVersion, spec.operationId); + const exampleOverrides = [ + ["curl", "cURL", loadCodeExample(apiVersion, spec.operationId, "curl")], + ["javascript", "JavaScript SDK", loadCodeExample(apiVersion, spec.operationId, "javascript")], + ["php", "PHP SDK", loadCodeExample(apiVersion, spec.operationId, "php")], + ["cli", "mw CLI", loadCodeExample(apiVersion, spec.operationId, "cli")], + ].filter(([,, i]) => i !== undefined).map(([key, label, content]) => `\n\n${content}\n\n`); // Yes, this is JavaScript that renders more JavaScript (or mdx, to be precise). // Yes, this is a bit weird and opens up a whole can of worms. Oh, well. // language=text fs.writeFileSync(operationFile, `--- -${frontMatterYAML} +${frontMatter} --- import {OperationRequest, OperationResponses} from "@site/src/components/openapi/OperationReference"; import {OperationMetadata} from "@site/src/components/openapi/OperationMetadata"; import {OperationUsage} from "@site/src/components/openapi/OperationUsage"; +import OperationLink from "@site/src/components/OperationLink"; +import TabItem from "@theme/TabItem"; - + + +${descriptionOverride ?? ""} ## Request @@ -59,7 +104,9 @@ import {OperationUsage} from "@site/src/components/openapi/OperationUsage"; ## Usage examples - + +${exampleOverrides.join("\n\n")} + `); } @@ -79,6 +126,12 @@ function stripTrailingDot(str: string | undefined): string | undefined { async function renderTagIndexPage(apiVersion: APIVersion, name: string, description: string, outputPath: string, sidebarItems: any[]): Promise { const indexFile = path.join(outputPath, "index.mdx"); + const overrideFile = path.join("generator", "overlays", apiVersion, slugFromTagName(name) + ".mdx"); + if (fs.existsSync(overrideFile)) { + fs.copyFileSync(overrideFile, indexFile); + return; + } + const frontMatter = yaml.stringify({ title: name, description, @@ -145,17 +198,7 @@ async function renderAPIDocs(apiVersion: APIVersion, outputPath: string) { } }); - const frontMatter = { - title: summary ?? operation.operationId, - description: operation.description ?? "", - openapi: { - method - } - }; - - const frontMatterYAML = yaml.stringify(frontMatter); - - renderAPISpecToFile(operationFile, frontMatterYAML, urlPathWithBase, method, serializedSpec, serverURL, apiVersion); + renderAPISpecToFile(operationFile, urlPathWithBase, method, operation, serverURL, apiVersion); } } } diff --git a/generator/overlays/v2/app-request-appinstallation/description.md b/generator/overlays/v2/app-request-appinstallation/description.md new file mode 100644 index 00000000..f4117396 --- /dev/null +++ b/generator/overlays/v2/app-request-appinstallation/description.md @@ -0,0 +1,19 @@ +This API operation requests a new app installation. + +## Usage notes + +### Determining the `appVersionId` + +Both the app that should be installed and the specific version are identified by the `appVersionId` body parameter. + +To determine available apps, use the API operation. To determine available versions for a given app, use the app ID (as returned by the `app-list-apps` operation) as input parameter for the operation. + +### On user inputs + +Pay attention to the `userInputs` parameter in the request body. This parameter is a list of objects, each with a `name` and `value` property. + +The allowed values for `name` are dependent on the app version being installed. To determine the required inputs for a given app version, inspect the `userInputs` property of the app version object returned by the operation. + +### Determining when the installation is complete + +Note that this operation does not block until the installation is complete. To determine the status of the installation, use the operation. When the operation is complete, the `.appVersion.current` property will be equal to `.appVersion.desired`. \ No newline at end of file diff --git a/generator/overlays/v2/app-request-appinstallation/example-cli.md b/generator/overlays/v2/app-request-appinstallation/example-cli.md new file mode 100644 index 00000000..9565c3ca --- /dev/null +++ b/generator/overlays/v2/app-request-appinstallation/example-cli.md @@ -0,0 +1,14 @@ +```shellsession +$ mw app create php \ + --project-id $PROJECT_ID \ + --site-title "My TYPO3 site" + +$ mw app install typo3 \ + --version 12.4.16 \ + --install-mode composer \ + --project-id $PROJECT_ID \ + --admin-email admin@typo3.example \ + --admin-pass securepassword \ + --admin-user admin \ + --site-title "My TYPO3 site" +``` \ No newline at end of file diff --git a/generator/overlays/v2/customer.mdx b/generator/overlays/v2/customer.mdx new file mode 100644 index 00000000..e6bf7780 --- /dev/null +++ b/generator/overlays/v2/customer.mdx @@ -0,0 +1,20 @@ +--- +title: Organizations/Customers +description: The customer API allows you to manage your own organizations and users. +displayed_sidebar: apiSidebar + +--- + +import OperationDocCardList from "@site/src/components/openapi/OperationDocCardList"; + +# Organizations/Customers + +The customer API allows you to manage your own organizations and users. + +:::important Important to know: "customers" vs. "organizations" + +Please note the naming discrepancy between the API and the UI; the UI refers to **"organizations"**, which are referred to as **"customers"** in the API. These are **not** two different kinds of resources; they are the same thing, just differently named. + +::: + + diff --git a/i18n/en/docusaurus-plugin-content-docs/current.json b/i18n/en/docusaurus-plugin-content-docs/current.json index 8b54bb65..fbe43659 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current.json +++ b/i18n/en/docusaurus-plugin-content-docs/current.json @@ -168,11 +168,11 @@ "description": "The generated-index page description for category Conversation in sidebar apiSidebar" }, "sidebar.apiSidebar.category.Customer": { - "message": "Customer", + "message": "Organization/Customer", "description": "The label for category Customer in sidebar apiSidebar" }, "sidebar.apiSidebar.category.Customer.link.generated-index.title": { - "message": "Customer", + "message": "Organization/Customer", "description": "The generated-index page title for category Customer in sidebar apiSidebar" }, "sidebar.apiSidebar.category.Customer.link.generated-index.description": { diff --git a/src/components/OperationLink/index.tsx b/src/components/OperationLink/index.tsx index f075f76d..bb8c0e1d 100644 --- a/src/components/OperationLink/index.tsx +++ b/src/components/OperationLink/index.tsx @@ -11,6 +11,8 @@ export default function OperationLink({ operation, children, }: PropsWithChildren) { + children = children || {operation}; + const url = `/docs/v2/reference/${tag.toLowerCase()}/${operation}`; return {children}; } diff --git a/src/components/openapi/OperationMetadata.tsx b/src/components/openapi/OperationMetadata.tsx index e1d63200..0890654a 100644 --- a/src/components/openapi/OperationMetadata.tsx +++ b/src/components/openapi/OperationMetadata.tsx @@ -57,10 +57,12 @@ export function OperationMetadata({ method, path, spec, + withDescription = true, }: { path: string; method: string; spec: OpenAPIV3.OperationObject; + withDescription?: boolean; }) { return ( <> @@ -92,7 +94,7 @@ export function OperationMetadata({
- {spec.description ? {spec.description} : null} + {spec.description && withDescription ? {spec.description} : null} ); } diff --git a/src/components/openapi/OperationUsage.tsx b/src/components/openapi/OperationUsage.tsx index c6399af7..09d2d723 100644 --- a/src/components/openapi/OperationUsage.tsx +++ b/src/components/openapi/OperationUsage.tsx @@ -14,6 +14,7 @@ interface OperationUsageProps { baseURL: string; withJavascript?: boolean; withPHP?: boolean; + children?: ReactElement[] | ReactElement; } export function OperationUsage(props: OperationUsageProps) { @@ -25,38 +26,45 @@ export function OperationUsage(props: OperationUsageProps) { withJavascript = true, withPHP = true, } = props; + let { children = [] } = props; - const items: ReactElement[] = [ - - - {generateCurlCodeExample(method, url, spec, baseURL)} - - , - ]; + if (!Array.isArray(children)) { + children = [children]; + } - if (withJavascript) { - items.push( + if (withPHP && !children.some(i => i.key === "php")) { + children.unshift( + + + {generatePHPCodeExample(method, url, spec, baseURL)} + + + ); + } + + if (withJavascript && !children.some(i => i.key === "javascript")) { + children.unshift( {generateJavascriptCodeExample(method, url, spec, baseURL)} - , + ); } - if (withPHP) { - items.push( - - - {generatePHPCodeExample(method, url, spec, baseURL)} + if (!children.some(i => i.key === "curl")) { + children.unshift( + + + {generateCurlCodeExample(method, url, spec, baseURL)} - , + ); } return ( - {items} + {children} ); }