From a70cd458c21e1d5d3400911213897a279ccf8bd6 Mon Sep 17 00:00:00 2001 From: Glavatskikh Denis Date: Tue, 2 Jun 2026 12:11:32 +0200 Subject: [PATCH] feat: auto-generate README dependency lists from Wizard API Add scripts/update-readme-deps.mjs which fetches the recommended Appodeal dependency blocks from the Dependencies Wizard API (mediations -> sdks -> dependencies) for android and ios, reading the SDK version from package.json. The API output is used verbatim, except the iOS Podfile target gets the React Native autolinking lines injected (the API renders a plain native target). Results are written between invisible HTML-comment markers () in README.md; missing markers are a hard error so the file is never partially rewritten. The API base URL comes from the APPODEAL_API_URL env/repository variable (no hardcoded fallback). A GitHub Action regenerates the lists daily, on relevant pushes, and on manual dispatch, committing any diff. Run locally via `yarn update-readme-deps`. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/update-readme-deps.yml | 54 +++++++++ README.md | 38 +++--- package.json | 1 + scripts/update-readme-deps.mjs | 145 +++++++++++++++++++++++ 4 files changed, 219 insertions(+), 19 deletions(-) create mode 100644 .github/workflows/update-readme-deps.yml create mode 100644 scripts/update-readme-deps.mjs diff --git a/.github/workflows/update-readme-deps.yml b/.github/workflows/update-readme-deps.yml new file mode 100644 index 0000000..8d82f4c --- /dev/null +++ b/.github/workflows/update-readme-deps.yml @@ -0,0 +1,54 @@ +name: Update README dependencies + +# Keeps the Appodeal dependency lists in README.md in sync with the live +# Dependencies Wizard API (URL from the APPODEAL_API_URL repository variable). +# The SDK version comes from package.json, so a version bump (push) refreshes the +# lists; the daily schedule catches adapter-version changes published between releases. + +on: + workflow_dispatch: {} + push: + branches: [master] + paths: + - 'package.json' + - 'scripts/update-readme-deps.mjs' + schedule: + # Daily at 06:00 UTC. + - cron: '0 6 * * *' + +permissions: + contents: write + +concurrency: + group: update-readme-deps + cancel-in-progress: true + +jobs: + update: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Regenerate README dependency lists + # URL comes only from the APPODEAL_API_URL repository variable + # (Settings → Secrets and variables → Actions → Variables). No hardcoded fallback — + # if the variable is unset the script fails loudly. + env: + APPODEAL_API_URL: ${{ vars.APPODEAL_API_URL }} + run: node scripts/update-readme-deps.mjs + + - name: Commit changes if any + run: | + if git diff --quiet -- README.md; then + echo "README dependencies already up to date." + exit 0 + fi + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add README.md + git commit -m "chore: sync README dependency lists from Wizard API" + git push diff --git a/README.md b/README.md index eb83252..fdc67de 100644 --- a/README.md +++ b/README.md @@ -90,31 +90,30 @@ yarn add react-native-appodeal 1. Go to `ios/` folder and open *Podfile* 2. Add Appodeal adapters. Add pods into `./ios/Podfile`: + ```ruby -source 'https://cdn.cocoapods.org' +platform :ios, '15.0' + source 'https://github.com/appodeal/CocoaPods.git' source 'https://github.com/bidon-io/CocoaPods-Specs.git' - -platform :ios, '13.0' +source 'https://cdn.cocoapods.org' use_frameworks! def appodeal - pod 'Appodeal', '4.1.0' - # Add adapter pods here. - # Use the Appodeal Dependencies Wizard to generate an up-to-date list: - # https://docs.appodeal.com/ios/advanced/configure-mediated-networks + pod 'Appodeal', '4.1.0' end -target 'YourAppName' do - use_frameworks! - use_modular_headers! - appodeal +target 'Sample' do + project 'Sample/Sample.xcodeproj' + appodeal + use_modular_headers! - config = use_native_modules! - use_react_native!(:path => config[:reactNativePath]) + config = use_native_modules! + use_react_native!(:path => config[:reactNativePath]) end ``` + > [!TIP] > Use the [Appodeal Dependencies Wizard](https://docs.appodeal.com/ios/advanced/configure-mediated-networks) to configure mediated networks and generate an always up-to-date dependency list for your Podfile. @@ -195,16 +194,17 @@ To improve ad performance the following entries should be added: Add dependencies into `android/app/build.gradle` -``` groovy + +``` kotlin +repositories { + maven { url = uri("https://artifactory.appodeal.com/appodeal") } +} +/* build.gradle.kts */ dependencies { - // ... other project dependencies - // Appodeal SDK 4.1.0 implementation("com.appodeal.ads.sdk:core:4.1.0") - // Add adapter dependencies here. - // Use the Appodeal Dependencies Wizard to generate an up-to-date list: - // https://docs.appodeal.com/android/advanced/configure-mediated-networks } ``` + Add repository into `android/build.gradle` diff --git a/package.json b/package.json index d12be00..dcc421e 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "typecheck": "tsc", "lint": "eslint \"**/*.{js,ts,tsx}\"", "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib", + "update-readme-deps": "node scripts/update-readme-deps.mjs", "prepare": "bob build", "release": "release-it --only-version" }, diff --git a/scripts/update-readme-deps.mjs b/scripts/update-readme-deps.mjs new file mode 100644 index 0000000..501559f --- /dev/null +++ b/scripts/update-readme-deps.mjs @@ -0,0 +1,145 @@ +#!/usr/bin/env node +// Updates the README.md dependency blocks from the Appodeal Wizard API. +// API output is used verbatim; only the iOS Podfile target gets RN linking injected. + +import { readFile, writeFile } from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; + +// API base URL — required, supplied via the APPODEAL_API_URL env var (set by the +// GitHub Action / your shell). No fallback: fail loudly rather than hit a guessed host. +const API = (() => { + const raw = process.env.APPODEAL_API_URL; + if (!raw) { + throw new Error('APPODEAL_API_URL env var is required'); + } + return raw.replace(/\/+$/, ''); +})(); +const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..'); +const README = join(ROOT, 'README.md'); + +// Category codes returned by the /sdks endpoint. +const CATEGORY_NETWORK = 2; +const CATEGORY_SERVICE = 3; + +/** Read the single source-of-truth version from package.json. */ +async function getVersion() { + const pkg = JSON.parse(await readFile(join(ROOT, 'package.json'), 'utf8')); + if (!pkg.version) throw new Error('package.json has no "version" field'); + return pkg.version; +} + +async function apiFetch(path, options = {}) { + const res = await fetch(`${API}${path}`, { + headers: { 'content-type': 'application/json', accept: '*/*' }, + signal: AbortSignal.timeout(30_000), + ...options, + }); + if (!res.ok) { + throw new Error(`API ${path} -> HTTP ${res.status} ${res.statusText}`); + } + return res; +} + +/** Collect every version id from a list of {versions:[{id}]} entries. */ +function collectVersionIds(entries) { + return (entries ?? []).flatMap((e) => (e.versions ?? []).map((v) => v.id)); +} + +/** Run the 3-step recommended pipeline and return the rendered dependency text. */ +async function fetchDependencyBlock(platform, version, lang) { + // Step 1 — recommended mediations. + const mediationsRes = await apiFetch( + `/v4/${platform}/${version}/mediations?recommended=true` + ); + const mediations = collectVersionIds((await mediationsRes.json()).mediations); + + // Step 2 — recommended sdks for those mediations, split into networks / services. + const sdksRes = await apiFetch(`/v4/${platform}/${version}/sdks?recommended=true`, { + method: 'POST', + body: JSON.stringify({ mediations, networks: [], services: [] }), + }); + const sdks = (await sdksRes.json()).sdks ?? []; + const networks = collectVersionIds(sdks.filter((s) => s.category === CATEGORY_NETWORK)); + const services = collectVersionIds(sdks.filter((s) => s.category === CATEGORY_SERVICE)); + + // Step 3 — render. iOS has no language suffix (always Ruby). + const path = + platform === 'ios' + ? `/v4/${platform}/${version}/dependencies` + : `/v4/${platform}/${version}/dependencies/${lang}`; + const depsRes = await apiFetch(path, { + method: 'POST', + body: JSON.stringify({ mediations, networks, services }), + }); + return (await depsRes.text()).replace(/\t/g, ' '); +} + +/** Wrap rendered code in a fenced Markdown block. */ +function fenced(lang, body) { + return `${lang}\n${body.trimEnd()}\n\`\`\``; +} + +/** + * Inject the React Native linking lines into the iOS Podfile target. The Wizard renders + * a plain native target (`target 'Sample' do ... end`); RN apps additionally need the + * autolinking calls, otherwise the copied Podfile won't build. Everything else from the + * API response is left untouched. + */ +function addReactNativeLinking(podfile) { + const linking = [ + '', + ' use_modular_headers!', + '', + ' config = use_native_modules!', + ' use_react_native!(:path => config[:reactNativePath])', + ].join('\n'); + // The target block is the last one in the response; insert before its closing `end`. + const trimmed = podfile.trimEnd(); + const patched = trimmed.replace(/\n[ \t]*end$/, `${linking}\nend`); + if (patched === trimmed) { + throw new Error('Could not locate the iOS Podfile target `end` to inject RN linking'); + } + return patched; +} + +/** + * Replace everything between the HTML-comment markers for `name` with `block`. + * Markers live OUTSIDE the fenced code block so they stay invisible in rendered Markdown: + * ...block... + */ +function replaceBetweenMarkers(readme, name, block) { + const startRe = new RegExp(``); + const endRe = new RegExp(``); + const startMatch = readme.match(startRe); + if (!startMatch) throw new Error(`Marker appodeal-deps:${name}:start not found in README.md`); + const startIdx = startMatch.index + startMatch[0].length; + + const endMatch = readme.slice(startIdx).match(endRe); + if (!endMatch) throw new Error(`Marker appodeal-deps:${name}:end not found after start in README.md`); + const endIdx = startIdx + endMatch.index; + + return `${readme.slice(0, startIdx)}\n${block}\n${readme.slice(endIdx)}`; +} + +async function main() { + const version = await getVersion(); + console.log(`Updating README dependency lists for Appodeal SDK ${version}`); + + // android uses kts (`kt`) verbatim; ios ignores the language, returns Ruby, and gets + // the React Native linking injected into its target. + const android = await fetchDependencyBlock('android', version, 'kt'); + const ios = addReactNativeLinking(await fetchDependencyBlock('ios', version)); + + let readme = await readFile(README, 'utf8'); + readme = replaceBetweenMarkers(readme, 'android', fenced('``` kotlin', android)); + readme = replaceBetweenMarkers(readme, 'ios', fenced('```ruby', ios)); + + await writeFile(README, readme, 'utf8'); + console.log('Done: README dependency blocks updated.'); +} + +main().catch((err) => { + console.error(`\n✖ ${err.message}`); + process.exit(1); +});