diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 2f0f9e2cbcc..d520857dd40 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -277,7 +277,7 @@ jobs: - name: Generate code snippets run: yarn cli snippets javascript ${{ fromJSON(needs.setup.outputs.JAVASCRIPT_DATA).toRun }} - + - name: Build the snippets to check validity run: yarn cli build snippets javascript ${{ fromJSON(needs.setup.outputs.JAVASCRIPT_DATA).toRun }} @@ -689,12 +689,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.ALGOLIA_BOT_TOKEN }} - - name: Push specs and snippets to algolia/doc - run: yarn workspace scripts pushToAlgoliaDoc - env: - GITHUB_TOKEN: ${{ secrets.ALGOLIA_BOT_TOKEN }} - - - name: Push guides to algolia/AlgoliaWeb - run: yarn workspace scripts pushToAlgoliaWeb + - name: Push generated files to repositories + run: yarn workspace scripts pushToRepository env: GITHUB_TOKEN: ${{ secrets.ALGOLIA_BOT_TOKEN }} diff --git a/.github/workflows/push-to-algolia-doc.yml b/.github/workflows/push-to-algolia-doc.yml deleted file mode 100644 index a80b74152f0..00000000000 --- a/.github/workflows/push-to-algolia-doc.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Push specs and snippets to Algolia doc - -on: workflow_dispatch - -jobs: - release: - name: Scheduled Release - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: main - - - name: Setup - id: setup - uses: ./.github/actions/setup - with: - type: minimal - - - run: yarn workspace scripts pushToAlgoliaDoc - env: - GITHUB_TOKEN: ${{ secrets.ALGOLIA_BOT_TOKEN }} - FORCE: true diff --git a/.github/workflows/push-to-algolia-web.yml b/.github/workflows/push-to-algolia-web.yml deleted file mode 100644 index 60d9f4eda84..00000000000 --- a/.github/workflows/push-to-algolia-web.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Push snippets to AlgoliaWeb - -on: workflow_dispatch - -jobs: - push: - name: Manual trigger push for onboarding guides - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: main - - - name: Setup - id: setup - uses: ./.github/actions/setup - with: - type: minimal - - - run: yarn workspace scripts pushToAlgoliaWeb - env: - GITHUB_TOKEN: ${{ secrets.ALGOLIA_BOT_TOKEN }} - FORCE: true diff --git a/.github/workflows/push-to-repository.yml b/.github/workflows/push-to-repository.yml new file mode 100644 index 00000000000..c8cbffeed6f --- /dev/null +++ b/.github/workflows/push-to-repository.yml @@ -0,0 +1,33 @@ +name: Push generated files to the given repository + +on: + workflow_dispatch: + inputs: + name: + description: 'the name of the repository to push files to' + required: true + type: choice + options: + - AlgoliaWeb + - doc + +jobs: + push: + name: Push generated files to the given repository + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: main + + - name: Setup + id: setup + uses: ./.github/actions/setup + with: + type: minimal + + - run: yarn workspace scripts pushToRepository ${{ inputs.name }} + env: + GITHUB_TOKEN: ${{ secrets.ALGOLIA_BOT_TOKEN }} + FORCE: true diff --git a/scripts/ci/codegen/pushToAlgoliaDoc.ts b/scripts/ci/codegen/pushToAlgoliaDoc.ts deleted file mode 100644 index d19d5a5efad..00000000000 --- a/scripts/ci/codegen/pushToAlgoliaDoc.ts +++ /dev/null @@ -1,95 +0,0 @@ -import fsp from 'fs/promises'; -import { resolve } from 'path'; - -import { - configureGitHubAuthor, - ensureGitHubToken, - getOctokit, - gitBranchExists, - gitCommit, - OWNER, - run, - setVerbose, - toAbsolutePath, -} from '../../common.js'; -import { getNbGitDiff } from '../utils.js'; - -import { commitStartRelease } from './text.js'; - -async function pushToAlgoliaDoc(): Promise { - const githubToken = ensureGitHubToken(); - - const repository = 'doc'; - const lastCommitMessage = await run('git log -1 --format="%s"'); - const author = (await run('git log -1 --format="Co-authored-by: %an <%ae>"')).trim(); - const coAuthors = (await run('git log -1 --format="%(trailers:key=Co-authored-by)"')) - .split('\n') - .map((coAuthor) => coAuthor.trim()) - .filter(Boolean); - - if (!process.env.FORCE && !lastCommitMessage.startsWith(commitStartRelease)) { - return; - } - - console.log(`Pushing to ${OWNER}/${repository}`); - - const targetBranch = 'feat/automated-update-from-api-clients-automation-repository'; - const githubURL = `https://${githubToken}:${githubToken}@github.com/${OWNER}/${repository}`; - const tempGitDir = resolve(process.env.RUNNER_TEMP! || toAbsolutePath('foo/local/test'), repository); - await fsp.rm(tempGitDir, { force: true, recursive: true }); - await run(`git clone --depth 1 ${githubURL} ${tempGitDir}`); - if (await gitBranchExists(targetBranch, tempGitDir)) { - await run(`git fetch origin ${targetBranch}`, { cwd: tempGitDir }); - await run(`git push -d origin ${targetBranch}`, { cwd: tempGitDir }); - } - await run(`git checkout -B ${targetBranch}`, { cwd: tempGitDir }); - - const pathToSpecs = toAbsolutePath(`${tempGitDir}/app_data/api/specs`); - await run(`rm -rf ${pathToSpecs}/* || true`); - await run(`cp ${toAbsolutePath('specs/bundled/README.md')} ${pathToSpecs}`); - await run(`cp ${toAbsolutePath('specs/major-breaking-changes-rename.json')} ${pathToSpecs}`); - await run(`cp ${toAbsolutePath('specs/bundled/*.doc.yml')} ${pathToSpecs}`); - await run(`cp ${toAbsolutePath('config/clients.config.json')} ${pathToSpecs}`); - await run(`cp ${toAbsolutePath('docs/bundled/*.json')} ${pathToSpecs}`); - // add block extension ban words like `analytics` so we use a different file name just so the doc dans render it - await run(`mv ${pathToSpecs}/analytics.doc.yml ${pathToSpecs}/searchstats.doc.yml`); - - if ((await getNbGitDiff({ head: null, cwd: tempGitDir })) === 0) { - console.log('❎ Skipping push docs because there is no change.'); - - return; - } - - await configureGitHubAuthor(tempGitDir); - - const message = 'feat: update specs and supported versions'; - await run('git add .', { cwd: tempGitDir }); - await gitCommit({ - message, - coAuthors: [author, ...coAuthors], - cwd: tempGitDir, - }); - await run(`git push -f -u origin ${targetBranch}`, { cwd: tempGitDir }); - - console.log(`Creating pull request on ${OWNER}/${repository}...`); - const octokit = getOctokit(); - const { data } = await octokit.pulls.create({ - owner: OWNER, - repo: repository, - title: message, - body: [ - 'This PR is automatically created by https://github.com/algolia/api-clients-automation', - 'It contains the latest released OpenAPI specs, the release version support dates, and the generated code snippets.', - ].join('\n\n'), - base: 'master', - head: targetBranch, - }); - - console.log(`Pull request created on ${OWNER}/${repository}`); - console.log(` > ${data.url}`); -} - -if (import.meta.url.endsWith(process.argv[1])) { - setVerbose(false); - pushToAlgoliaDoc(); -} diff --git a/scripts/ci/codegen/pushToAlgoliaWeb.ts b/scripts/ci/codegen/pushToAlgoliaWeb.ts deleted file mode 100644 index ae7e6097ed4..00000000000 --- a/scripts/ci/codegen/pushToAlgoliaWeb.ts +++ /dev/null @@ -1,126 +0,0 @@ -import fsp from 'fs/promises'; -import { resolve } from 'path'; - -import { - configureGitHubAuthor, - ensureGitHubToken, - exists, - getOctokit, - gitBranchExists, - gitCommit, - LANGUAGES, - OWNER, - run, - setVerbose, - toAbsolutePath, -} from '../../common.js'; -import { getNbGitDiff } from '../utils.js'; - -import { getClientsConfigField } from '../../config.js'; -import { commitStartRelease } from './text.js'; - -async function generateJSON(outputFile: string): Promise { - const guides = {}; - for (const language of LANGUAGES) { - if (!(await exists(toAbsolutePath(`docs/guides/${language}`)))) { - continue; - } - - const pathToGuides = toAbsolutePath( - `docs/guides/${language}/${getClientsConfigField(language, ['snippets', 'outputFolder'])}`, - ); - const files = await fsp.readdir(pathToGuides); - for (const file of files) { - const extension = getClientsConfigField(language, ['snippets', 'extension']); - if (!file.endsWith(extension)) { - continue; - } - - const guideName = file.replaceAll(extension, ''); - if (!guides[guideName]) { - guides[guideName] = {}; - } - - guides[guideName][language] = (await fsp.readFile(`${pathToGuides}/${file}`, 'utf-8')) - .replace('ALGOLIA_APPLICATION_ID', 'YourApplicationID') - .replace('ALGOLIA_API_KEY', 'YourWriteAPIKey') - .replace('', 'movies_index'); - } - } - - await fsp.writeFile(outputFile, JSON.stringify(guides, null, 2)); -} - -async function pushToAlgoliaWeb(): Promise { - const githubToken = ensureGitHubToken(); - - const repository = 'AlgoliaWeb'; - const lastCommitMessage = await run('git log -1 --format="%s"'); - const author = (await run('git log -1 --format="Co-authored-by: %an <%ae>"')).trim(); - const coAuthors = (await run('git log -1 --format="%(trailers:key=Co-authored-by)"')) - .split('\n') - .map((coAuthor) => coAuthor.trim()) - .filter(Boolean); - - if (!process.env.FORCE && !lastCommitMessage.startsWith(commitStartRelease)) { - return; - } - - const targetBranch = 'feat/automated-update-from-api-clients-automation-repository'; - const githubURL = `https://${githubToken}:${githubToken}@github.com/${OWNER}/${repository}`; - const tempGitDir = resolve(process.env.RUNNER_TEMP! || toAbsolutePath('foo/local/test'), repository); - await fsp.rm(tempGitDir, { force: true, recursive: true }); - await run(`git clone --depth 1 ${githubURL} ${tempGitDir}`); - - const outputFile = toAbsolutePath(`${tempGitDir}/_client/src/routes/launchpad/onboarding-snippets.json`); - - console.log(`Generating JSON output file from guides at path ${outputFile}`); - - await generateJSON(outputFile); - - console.log(`Pushing to ${OWNER}/${repository}`); - if (await gitBranchExists(targetBranch, tempGitDir)) { - await run(`git fetch origin ${targetBranch}`, { cwd: tempGitDir }); - await run(`git push -d origin ${targetBranch}`, { cwd: tempGitDir }); - } - await run(`git checkout -B ${targetBranch}`, { cwd: tempGitDir }); - - if ((await getNbGitDiff({ head: null, cwd: tempGitDir })) === 0) { - console.log('❎ Skipping push to AlgoliaWeb because there is no change.'); - - return; - } - - await configureGitHubAuthor(tempGitDir); - - const message = 'feat: update generated guides'; - await run('git add .', { cwd: tempGitDir }); - await gitCommit({ - message, - coAuthors: [author, ...coAuthors], - cwd: tempGitDir, - }); - await run(`git push -f -u origin ${targetBranch}`, { cwd: tempGitDir }); - - console.log(`Creating pull request on ${OWNER}/${repository}...`); - const octokit = getOctokit(); - const { data } = await octokit.pulls.create({ - owner: OWNER, - repo: repository, - title: message, - body: [ - 'This PR is automatically created by https://github.com/algolia/api-clients-automation', - 'It contains the latest generated guides.', - ].join('\n\n'), - base: 'develop', - head: targetBranch, - }); - - console.log(`Pull request created on ${OWNER}/${repository}`); - console.log(` > ${data.url}`); -} - -if (import.meta.url.endsWith(process.argv[1])) { - setVerbose(false); - pushToAlgoliaWeb(); -} diff --git a/scripts/ci/codegen/pushToRepository.ts b/scripts/ci/codegen/pushToRepository.ts new file mode 100644 index 00000000000..71adc585f84 --- /dev/null +++ b/scripts/ci/codegen/pushToRepository.ts @@ -0,0 +1,171 @@ +import fsp from 'fs/promises'; +import path, { resolve } from 'path'; + +import { + configureGitHubAuthor, + ensureGitHubToken, + exists, + getOctokit, + gitBranchExists, + gitCommit, + LANGUAGES, + OWNER, + run, + setVerbose, + toAbsolutePath, +} from '../../common.js'; +import { getNbGitDiff } from '../utils.js'; +import { GuidesToPush, pushToRepositoryConfiguration, RepositoryConfiguration, SpecsToPush } from './types.js'; + +import { getClientsConfigField } from '../../config.js'; +import { commitStartRelease } from './text.js'; + +async function handleSpecFiles(spec: SpecsToPush, tempGitDir: string): Promise { + const pathToSpecs = toAbsolutePath(`${tempGitDir}/${spec.output}`); + + await run(`rm -rf ${pathToSpecs}/* || true`); + await run(`cp ${toAbsolutePath('specs/bundled/README.md')} ${pathToSpecs}`); + await run(`cp ${toAbsolutePath('specs/major-breaking-changes-rename.json')} ${pathToSpecs}`); + await run(`cp ${toAbsolutePath('specs/bundled/*.doc.yml')} ${pathToSpecs}`); + await run(`cp ${toAbsolutePath('config/clients.config.json')} ${pathToSpecs}`); + await run(`cp ${toAbsolutePath('docs/bundled/*.json')} ${pathToSpecs}`); + // adblock extensions ban words like `analytics` so we use a different file name just so the doc dans render it + await run(`mv ${pathToSpecs}/analytics.doc.yml ${pathToSpecs}/searchstats.doc.yml`); +} + +async function handleGuideFiles(guide: GuidesToPush, tempGitDir: string): Promise { + const guides = {}; + for (const language of LANGUAGES) { + if (!(await exists(toAbsolutePath(`docs/guides/${language}`)))) { + continue; + } + + const extension = getClientsConfigField(language, ['snippets', 'extension']); + const pathToGuides = toAbsolutePath( + `docs/guides/${language}/${getClientsConfigField(language, ['snippets', 'outputFolder'])}`, + ); + + const files = await fsp.readdir(pathToGuides); + for (const file of files.filter((file) => guide.names.some((guideName) => guideName === file.split('.')[0]))) { + if (!file.endsWith(extension)) { + continue; + } + + const guideName = file.replaceAll(extension, ''); + if (!guides[guideName]) { + guides[guideName] = {}; + } + + guides[guideName][language] = await fsp.readFile(`${pathToGuides}/${file}`, 'utf-8'); + + if (guide.placeholderVariables) { + for (const [k, v] of Object.entries(guide.placeholderVariables)) { + guides[guideName][language] = guides[guideName][language].replace(k, v); + } + } + } + } + + const outputPath = toAbsolutePath(`${tempGitDir}/${guide.output}`); + + await fsp.mkdir(path.dirname(outputPath), { recursive: true }); + + await fsp.writeFile(outputPath, JSON.stringify(guides, null, 2)); +} + +async function pushToRepository(repository: string, config: RepositoryConfiguration): Promise { + const githubToken = ensureGitHubToken(); + + const lastCommitMessage = await run('git log -1 --format="%s"'); + const author = (await run('git log -1 --format="Co-authored-by: %an <%ae>"')).trim(); + const coAuthors = (await run('git log -1 --format="%(trailers:key=Co-authored-by)"')) + .split('\n') + .map((coAuthor) => coAuthor.trim()) + .filter(Boolean); + + if (!process.env.FORCE && !lastCommitMessage.startsWith(commitStartRelease)) { + return; + } + + console.log(`Preparing push to ${OWNER}/${repository}`); + + const tempGitDir = resolve(process.env.RUNNER_TEMP! || toAbsolutePath('foo/local/test'), repository); + await fsp.rm(tempGitDir, { force: true, recursive: true }); + + const githubURL = `https://${githubToken}:${githubToken}@github.com/${OWNER}/${repository}`; + await run(`git clone --depth 1 ${githubURL} ${tempGitDir}`); + + for (const task of config.tasks) { + await run(`git checkout ${config.baseBranch}`, { cwd: tempGitDir }); + await run(`git pull origin ${config.baseBranch}`, { cwd: tempGitDir }); + + if (await gitBranchExists(task.prBranch, tempGitDir)) { + await run(`git fetch origin ${task.prBranch}`, { cwd: tempGitDir }); + await run(`git push -d origin ${task.prBranch}`, { cwd: tempGitDir }); + } + await run(`git checkout -B ${task.prBranch}`, { cwd: tempGitDir }); + + console.log(`Handling '${task.files.type}' file(s)`); + + if (task.files.type === 'specs') { + await handleSpecFiles(task.files, tempGitDir); + } else { + await handleGuideFiles(task.files, tempGitDir); + } + + if ((await getNbGitDiff({ head: null, cwd: tempGitDir })) === 0) { + console.log(`❎ Skipping push to ${OWNER}/${repository} because there is no change.`); + + continue; + } + + console.log(`Pushing to '${task.prBranch}`); + + await configureGitHubAuthor(tempGitDir); + + await run('git add .', { cwd: tempGitDir }); + await gitCommit({ + message: task.commitMessage, + coAuthors: [author, ...coAuthors], + cwd: tempGitDir, + }); + + if (process.env.DRYRUN) { + console.log(`asked for a dry run, stopping before push and PR for '${repository}' on task '${task.prBranch}'`); + + continue; + } + + await run(`git push -f -u origin ${task.prBranch}`, { cwd: tempGitDir }); + + console.log(`Creating pull request on ${OWNER}/${repository}...`); + const octokit = getOctokit(); + const { data } = await octokit.pulls.create({ + owner: OWNER, + repo: repository, + title: task.commitMessage, + body: [ + 'This PR is automatically created by https://github.com/algolia/api-clients-automation', + 'It contains the latest generated guides.', + ].join('\n\n'), + base: config.baseBranch, + head: task.prBranch, + }); + + console.log(`Pull request created on ${OWNER}/${repository}`); + console.log(` > ${data.url}`); + } +} + +if (import.meta.url.endsWith(process.argv[1])) { + setVerbose(false); + const repositories = process.argv.slice(2) as Array; + + await Promise.allSettled( + Object.entries(pushToRepositoryConfiguration).map(([name, config]) => { + if (repositories.length === 0 || repositories.includes(name)) { + return pushToRepository(name, config); + } + }), + ); +} diff --git a/scripts/ci/codegen/types.ts b/scripts/ci/codegen/types.ts new file mode 100644 index 00000000000..c3333fdbcfd --- /dev/null +++ b/scripts/ci/codegen/types.ts @@ -0,0 +1,80 @@ +export type GuidesToPush = { + // the type of changes to push to the repository (post-processing of files will be performed) + type: 'guides'; + + // the name of the guides to push in this pull request + names: string[]; + + // the name of the JSON file output that will contain every guides in `names` + output: string; + + // a key-value of the variable in guides to replace, with the name you'd like to use instead + placeholderVariables?: Record; +}; + +export type SpecsToPush = { + type: 'specs'; + output: string; +}; + +type RepositoryTask = { + // the name of the pull request branch + prBranch: string; + + // the commit message of the pull request (will also be used as the title) + commitMessage: string; + + files: GuidesToPush | SpecsToPush; +}; + +export type RepositoryConfiguration = { + // the name of the branch to base the pull request on + baseBranch: string; + + // the pull requests tasks to create for this repository + tasks: Array; +}; + +export const pushToRepositoryConfiguration: { [k in 'AlgoliaWeb' | 'doc']: RepositoryConfiguration } = { + AlgoliaWeb: { + baseBranch: 'develop', + tasks: [ + { + prBranch: 'feat/update-generated-onboarding-guides', + commitMessage: 'feat: update generated onboarding guides', + files: { + type: 'guides', + names: ['saveObjectsMovies'], + output: '_client/src/routes/launchpad/onboarding-snippets.json', + placeholderVariables: { + ALGOLIA_APPLICATION_ID: 'YourApplicationID', + ALGOLIA_API_KEY: 'YourWriteAPIKey', + '': 'movies_index', + }, + }, + }, + { + prBranch: 'feat/update-generated-ingestion-guides', + commitMessage: 'feat: update generated ingestion guides', + files: { + type: 'guides', + names: ['pushSetup'], + output: '_client/src/routes/connectors/generated/ingestion-snippets.json', + }, + }, + ], + }, + doc: { + baseBranch: 'master', + tasks: [ + { + prBranch: 'feat/automated-update-from-api-clients-automation-repository', + commitMessage: 'feat: update specs and supported versions', + files: { + type: 'specs', + output: 'app_data/api/specs', + }, + }, + ], + }, +}; diff --git a/scripts/ci/utils.ts b/scripts/ci/utils.ts index 88dbc6d2e88..49f4599904c 100644 --- a/scripts/ci/utils.ts +++ b/scripts/ci/utils.ts @@ -29,13 +29,12 @@ export async function getNbGitDiff({ const checkHead = head === null ? '' : `...${head}`; const changes = parseInt( - ( - await run(`git add -N . && git diff --shortstat ${branch}${checkHead} -- ${path} | wc -l`, { - cwd, - }) - ).trim(), + await run(`git add -N . && git diff --shortstat ${branch}${checkHead} -- ${path} | wc -l | tr -d ' '`, { + cwd, + }), 10, ); + if (isNaN(changes)) { return 0; } diff --git a/scripts/package.json b/scripts/package.json index f23fe394269..0473e4ea5a9 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -12,8 +12,7 @@ "lint:deadcode": "knip", "pre-commit": "node ./ci/husky/pre-commit.mjs", "pushGeneratedCode": "yarn runScript dist/ci/codegen/pushGeneratedCode.js", - "pushToAlgoliaDoc": "yarn runScript dist/ci/codegen/pushToAlgoliaDoc.js", - "pushToAlgoliaWeb": "yarn runScript dist/ci/codegen/pushToAlgoliaWeb.js", + "pushToRepository": "yarn runScript dist/ci/codegen/pushToRepository.js $*", "runScript": "NODE_NO_WARNINGS=1 node --enable-source-maps", "setRunVariables": "yarn runScript dist/ci/githubActions/setRunVariables.js", "spreadGeneration": "yarn runScript dist/ci/codegen/spreadGeneration.js", diff --git a/website/docs/generating-guides.md b/website/docs/generating-guides.md new file mode 100644 index 00000000000..27192eee519 --- /dev/null +++ b/website/docs/generating-guides.md @@ -0,0 +1,58 @@ +--- +title: Generating Guides +--- + +# Generating Guides + +You can create guides which allow you to write arbitrary code in any language and let the CI test the code and push it to your repo automatically. + +## Create guide templates + +Go to `templates` and you will find folders for each language. Inside, you can add a `.mustache` file to each language's `/guides` folder. This `.mustache` file serves as the template for your guide, and allows you to compose guides that inject client code programmatically. + +Whenever API client methods are updated, a PR will be automatically generated based on a `pushConfig` which describes the expected set of guides and destination repo details. + +## Create a push config + +See the [`pushToRepositoryConfiguration`](https://github.com/algolia/api-clients-automation/blob/main/scripts/ci/codegen/types.ts#L38) and add your repository to the map, or a `task` if it's already listed. + +### Push Config Properties + +> [See the definition of `RepositoryConfiguration`](https://github.com/algolia/api-clients-automation/blob/main/scripts/ci/codegen/types.ts#L30) + +### Example config + +> A `task` describes a pull request action for the files selected + +```js +{ + AlgoliaWeb: { + baseBranch: 'develop', + tasks: [ + { + prBranch: 'feat/update-generated-onboarding-guides', + commitMessage: 'feat: update generated onboarding guides', + files: { + type: 'guides', + names: ['saveObjectsMovies'], + output: '_client/src/routes/launchpad/onboarding-snippets.json', + placeholderVariables: { + ALGOLIA_APPLICATION_ID: 'YourApplicationID', + ALGOLIA_API_KEY: 'YourWriteAPIKey', + '': 'movies_index', + }, + }, + }, + { + prBranch: 'feat/update-generated-ingestion-guides', + commitMessage: 'feat: update generated ingestion guides', + files: { + type: 'guides', + names: ['pushSetup'], + output: '_client/src/routes/connectors/generated/ingestion-snippets.json', + }, + }, + ], + }, +} +```