diff --git a/generator/konfig-dash/.changeset/selfish-ads-rest.md b/generator/konfig-dash/.changeset/selfish-ads-rest.md new file mode 100644 index 000000000..ca11acda4 --- /dev/null +++ b/generator/konfig-dash/.changeset/selfish-ads-rest.md @@ -0,0 +1,7 @@ +--- +'konfig-openapi-spec': minor +'konfig-cli': minor +'konfig-lib': minor +--- + +add readmeHeaderSnippet configuration diff --git a/generator/konfig-dash/api/src/functions/formatPython/formatPython.ts b/generator/konfig-dash/api/src/functions/formatPython/formatPython.ts index 9b3299a6b..de3d25035 100644 --- a/generator/konfig-dash/api/src/functions/formatPython/formatPython.ts +++ b/generator/konfig-dash/api/src/functions/formatPython/formatPython.ts @@ -1,6 +1,6 @@ import type { APIGatewayEvent, Context } from 'aws-lambda' import { urlForBlackdApi } from 'src/lib/urlForBlackdApi' -import axios from 'axios' +import axios, { AxiosError } from 'axios' import { CORS_HEADERS_METHOD_HEADERS, CORS_HEADERS_ORIGIN, @@ -30,22 +30,35 @@ export const handler = async (event: APIGatewayEvent, context: Context) => { } } if (event.body === null) throw Error('Missing Request body') - const { data: formattedSource } = await axios.post( - urlForBlackdApi(), - event.body - ) + try { + const { data: formattedSource } = await axios.post( + urlForBlackdApi(), + event.body + ) - return { - statusCode: 200, - headers: { - ...CORS_HEADERS_ORIGIN, - 'Content-Type': 'text/plain', - }, - // For some reason blackd returns an empty string if the - // code snippet is already formatted so we have to handle - // that edge case with an empty string check - // From: https://black.readthedocs.io/en/stable/usage_and_configuration/black_as_a_server.html - // "HTTP 204: If the input is already well-formatted. The response body is empty." - body: formattedSource == '' ? event.body : formattedSource, + return { + statusCode: 200, + headers: { + ...CORS_HEADERS_ORIGIN, + 'Content-Type': 'text/plain', + }, + // For some reason blackd returns an empty string if the + // code snippet is already formatted so we have to handle + // that edge case with an empty string check + // From: https://black.readthedocs.io/en/stable/usage_and_configuration/black_as_a_server.html + // "HTTP 204: If the input is already well-formatted. The response body is empty." + body: formattedSource == '' ? event.body : formattedSource, + } + } catch (e) { + if (e instanceof AxiosError) { + return { + statusCode: 500, + headers: { + ...CORS_HEADERS_ORIGIN, + 'Content-Type': 'text/plain', + }, + body: event.body, + } + } } } diff --git a/generator/konfig-dash/api/src/lib/prepare-java-request-properties.ts b/generator/konfig-dash/api/src/lib/prepare-java-request-properties.ts index 54b2957b8..7c1eaf005 100644 --- a/generator/konfig-dash/api/src/lib/prepare-java-request-properties.ts +++ b/generator/konfig-dash/api/src/lib/prepare-java-request-properties.ts @@ -14,9 +14,16 @@ import { * Now all thats necessary to give the Java Generator more context is: * * 1. Modify the AdditionalProperties schema in the Java API's OpenAPI specification at api.yaml - * 2. Run generate-models.sh - * 3. Make updates to KonfigYaml.ts / KonfigYamlCommon.ts - * 4. Extract data from body and return as a key-value pair in the properties object + * (file located at: [KONFIG REPO]/misc/openapi-generator-configs/openapi-generator-api/api.yaml) + * 2. Update JavaGenerateApiRequestBody.ts file with same changes as api.yaml + * 3. Run generate-models.sh + * (file located at: [KONFIG REPO]/misc/openapi-generator-configs/openapi-generator-api/generate-models.sh) + * 4. Make updates to KonfigYaml.ts / KonfigYamlCommon.ts + * 5. Extract data from body and return as a key-value pair in the properties object (in this function implementation) + * + * Note: If you are adding a configuration that points to a file like "readmeHeaderSnippet", you need to add code to + * "/generator/konfig-dash/packages/konfig-cli/src/commands/generate.ts" to read the file contents and send the contents + * to the generator api instead. */ export function prepareJavaRequestProperties({ body, @@ -42,6 +49,10 @@ export function prepareJavaRequestProperties({ properties['gitRepoName'] = git.repoName } + if ('readmeHeaderSnippet' in generatorConfig) { + properties['readmeHeaderSnippet'] = generatorConfig.readmeHeaderSnippet + } + if ('outputDirectory' in generatorConfig) { properties['outputDirectory'] = generatorConfig.outputDirectory } diff --git a/generator/konfig-dash/packages/konfig-cli/src/commands/generate.ts b/generator/konfig-dash/packages/konfig-cli/src/commands/generate.ts index 4210a6588..1d267c1e1 100644 --- a/generator/konfig-dash/packages/konfig-cli/src/commands/generate.ts +++ b/generator/konfig-dash/packages/konfig-cli/src/commands/generate.ts @@ -20,7 +20,7 @@ import { GeneratorGitConfig, } from 'konfig-lib' import globby from 'globby' -import { Konfig } from 'konfig-typescript-sdk' +import { Konfig, KonfigError } from 'konfig-typescript-sdk' import * as fs from 'fs-extra' import axios, { AxiosError } from 'axios' import * as os from 'os' @@ -52,6 +52,7 @@ import { isSubmodule } from '../util/is-submodule' import { getHostForGenerateApi } from '../util/get-host-for-generate-api' import { getSdkDefaultBranch } from '../util/get-sdk-default-branch' import { insertTableOfContents } from '../util/insert-table-of-contents' +import boxen from 'boxen' function getOutputDir( outputFlag: string | undefined, @@ -1166,22 +1167,7 @@ export default class Deploy extends Command { ]) for (const markdownPath of markdownFiles) { const markdown = fs.readFileSync(markdownPath, 'utf-8') - const pythonSnippetRegex = - // rewrite the following regex to not include "```" in the match - /\`\`\`python\r?\n([\s\S]*?)\r?\n\`\`\`/g - - // find all code snippets in the markdown string that matches typescriptSnippetRegex - // and format them and replace the code snippets with the formatted code snippets - const formattedMarkdown = await replaceAsync( - markdown, - pythonSnippetRegex, - async (_, codeSnippet) => { - const { data: formattedCodeSnippet } = - await konfig.sdk.formatPython(codeSnippet) - return '```python\n' + formattedCodeSnippet + '```' - } - ) - fs.writeFileSync(markdownPath, formattedMarkdown) + await formatPythonSnippet({ markdown, markdownPath, konfig }) } CliUx.ux.action.stop() @@ -1415,11 +1401,17 @@ function handleReadmeSnippet< C extends object & { readmeSnippet?: string asyncReadmeSnippet?: string + readmeHeaderSnippet?: string readmeDescriptionSnippet?: string } >({ config }: { config: C }): C { if (config.readmeSnippet !== undefined) config.readmeSnippet = fs.readFileSync(config.readmeSnippet, 'utf-8') + if (config.readmeHeaderSnippet !== undefined) + config.readmeHeaderSnippet = fs.readFileSync( + config.readmeHeaderSnippet, + 'utf-8' + ) if (config.asyncReadmeSnippet !== undefined) config.asyncReadmeSnippet = fs.readFileSync( config.asyncReadmeSnippet, @@ -1564,6 +1556,60 @@ function constructGoGenerationRequest({ return requestGo } +async function formatPythonSnippet({ + markdown, + markdownPath, + konfig, +}: { + konfig: Konfig + markdownPath: string + markdown: string +}) { + const pythonSnippetRegex = /\`\`\`python\r?\n([\s\S]*?)\r?\n\`\`\`/g + + // find all code snippets in the markdown string that matches typescriptSnippetRegex + // and format them and replace the code snippets with the formatted code snippets + try { + const formattedMarkdown = await replaceAsync( + markdown, + pythonSnippetRegex, + async (match, codeSnippet, offset) => { + // Check if the block is preceded by a line ending with '>' + const blockStartIndex = offset - 1 + const startOfLineIndex = + markdown.lastIndexOf('\n', blockStartIndex - 1) + 1 + const lineBeforeBlock = markdown.substring( + startOfLineIndex, + blockStartIndex + ) + + if (lineBeforeBlock.endsWith('>')) { + // If it is, we leave the match unaltered + return match + } else { + // If it's not, proceed with formatting + const { data: formattedCodeSnippet } = await konfig.sdk.formatPython( + codeSnippet + ) + return '```python\n' + formattedCodeSnippet + '```' + } + } + ) + fs.writeFileSync(markdownPath, formattedMarkdown) + } catch (e) { + if (e instanceof KonfigError) + if (typeof e.responseBody === 'string') { + console.log( + boxen(e.responseBody, { + title: "Warning: Couldn't format Python code snippet", + titleAlignment: 'center', + borderColor: 'yellow', + }) + ) + } + } +} + function constructPhpGenerationRequest({ configDir, phpGeneratorConfig, diff --git a/generator/konfig-dash/packages/konfig-lib/src/JavaGenerateApiRequestBody.ts b/generator/konfig-dash/packages/konfig-lib/src/JavaGenerateApiRequestBody.ts index 3d86a3774..4707a25a4 100644 --- a/generator/konfig-dash/packages/konfig-lib/src/JavaGenerateApiRequestBody.ts +++ b/generator/konfig-dash/packages/konfig-lib/src/JavaGenerateApiRequestBody.ts @@ -3,6 +3,7 @@ import { z } from './zod' import { TemplateFiles } from './TemplateFiles' import { clientStateWithExamples, + readmeHeaderSnippet, topLevelOperationsOrderedSchema, } from './KonfigYaml' import { tagPrioritySchema } from './KonfigYamlCommon' @@ -11,6 +12,7 @@ const additionalProperties = z .object({ useDescriptionInOperationTableDocumentation: z.boolean().optional(), apiPackage: z.string().optional(), + readmeHeaderSnippet: readmeHeaderSnippet, artifactId: z.string().optional(), artifactUrl: z.string().optional(), authorEmail: z.string().describe('engineering@acme.com').optional(), diff --git a/generator/konfig-dash/packages/konfig-lib/src/KonfigYaml.ts b/generator/konfig-dash/packages/konfig-lib/src/KonfigYaml.ts index 040499a7d..ec2689729 100644 --- a/generator/konfig-dash/packages/konfig-lib/src/KonfigYaml.ts +++ b/generator/konfig-dash/packages/konfig-lib/src/KonfigYaml.ts @@ -198,8 +198,16 @@ export const pythonResponseTypeVersion = z "Choose which version of Konfig's implementation of responses for the Python SDK to use." ) +export const readmeHeaderSnippet = z + .string() + .optional() + .describe( + 'A snippet of markdown that will be inserted at the top of the README.md file. This is useful for adding a custom header to the README.md file that is not generated by Konfig.' + ) + export const pythonConfig = z.object({ useDescriptionInOperationTableDocumentation, + readmeHeaderSnippet, language: z.literal('python').default('python'), packageName: z.string().describe('acme_client'), projectName: z.string().describe('acme-python-sdk'), diff --git a/generator/konfig-dash/packages/konfig-openapi-spec/openapi.yaml b/generator/konfig-dash/packages/konfig-openapi-spec/openapi.yaml index db34b4e46..b0e62b933 100644 --- a/generator/konfig-dash/packages/konfig-openapi-spec/openapi.yaml +++ b/generator/konfig-dash/packages/konfig-openapi-spec/openapi.yaml @@ -539,6 +539,11 @@ components: type: boolean description: Whether or not to use the operation's description in the operation table documentation. By default the summary is used. + readmeHeaderSnippet: + type: string + description: A snippet of markdown that will be inserted at the top of the + README.md file. This is useful for adding a custom header to + the README.md file that is not generated by Konfig. language: type: string enum: @@ -2215,6 +2220,11 @@ components: type: boolean description: Whether or not to use the operation's description in the operation table documentation. By default the summary is used. + readmeHeaderSnippet: + type: string + description: A snippet of markdown that will be inserted at the top of the + README.md file. This is useful for adding a custom header + to the README.md file that is not generated by Konfig. language: type: string enum: diff --git a/generator/konfig-generator-api/.idea/runConfigurations/OpenApiGeneratorApplication.xml b/generator/konfig-generator-api/.idea/runConfigurations/OpenApiGeneratorApplication.xml index b00c35912..67b35f2a9 100644 --- a/generator/konfig-generator-api/.idea/runConfigurations/OpenApiGeneratorApplication.xml +++ b/generator/konfig-generator-api/.idea/runConfigurations/OpenApiGeneratorApplication.xml @@ -3,7 +3,7 @@