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); +});