From 1929227a5667c0f95c7c22670a3a218713eb8eb6 Mon Sep 17 00:00:00 2001 From: Martin Hill Date: Sat, 2 May 2026 19:36:28 +0100 Subject: [PATCH 01/13] Add Prebid.js build and deploy pipeline --- .github/workflows/deploy-prebid.yml | 57 +++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 .github/workflows/deploy-prebid.yml diff --git a/.github/workflows/deploy-prebid.yml b/.github/workflows/deploy-prebid.yml new file mode 100644 index 0000000000..46d5a84213 --- /dev/null +++ b/.github/workflows/deploy-prebid.yml @@ -0,0 +1,57 @@ +name: Build & Deploy Prebid.js + +on: + push: + branches: + - main + workflow_dispatch: + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build Prebid.js + run: | + npx gulp build --modules=consentManagementGpp,consentManagementTcf,consentManagementUsp,storageControl,tcfControl,gppControl_usnat,gppControl_usstates,allowActivities,bidResponseFilter,bidViewability,currency,priceFloors,debugging,geolocationRtdProvider,multibid,nativeRendering,prebidServerBidAdapter,previousAuctionInfo,s2sTesting,schain,rules,timeoutRtdProvider,videoModule,validationFpdModule,adfBidAdapter,tpcBidAdapter,magniteBidAdapter,pubmaticBidAdapter,ixBidAdapter + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Upload to S3 + run: | + # Upload the latest build (short-lived cache) + aws s3 cp build/prebid.js \ + s3://${{ secrets.S3_BUCKET }}/prebid/prebid.js \ + --cache-control "public, max-age=3600, s-maxage=86400" \ + --content-type "application/javascript" + + # Upload a versioned copy (long-lived cache) + VERSION=$(node -p "require('./package.json').version") + aws s3 cp build/prebid.js \ + s3://${{ secrets.S3_BUCKET }}/prebid/prebid-${VERSION}.js \ + --cache-control "public, max-age=31536000, immutable" \ + --content-type "application/javascript" + + - name: Invalidate CloudFront cache + run: | + aws cloudfront create-invalidation \ + --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \ + --paths "/prebid/prebid.js" + From e2ccd10bb83d7aa65b86050886dadb8b0d3f7118 Mon Sep 17 00:00:00 2001 From: Martin Hill Date: Sat, 2 May 2026 20:00:41 +0100 Subject: [PATCH 02/13] Fix deploy path from prebid to prod --- .github/workflows/deploy-prebid.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy-prebid.yml b/.github/workflows/deploy-prebid.yml index 46d5a84213..f2327899b8 100644 --- a/.github/workflows/deploy-prebid.yml +++ b/.github/workflows/deploy-prebid.yml @@ -38,14 +38,14 @@ jobs: run: | # Upload the latest build (short-lived cache) aws s3 cp build/prebid.js \ - s3://${{ secrets.S3_BUCKET }}/prebid/prebid.js \ + s3://${{ secrets.S3_BUCKET }}/prod/prebid.js \ --cache-control "public, max-age=3600, s-maxage=86400" \ --content-type "application/javascript" # Upload a versioned copy (long-lived cache) VERSION=$(node -p "require('./package.json').version") aws s3 cp build/prebid.js \ - s3://${{ secrets.S3_BUCKET }}/prebid/prebid-${VERSION}.js \ + s3://${{ secrets.S3_BUCKET }}/prod/prebid-${VERSION}.js \ --cache-control "public, max-age=31536000, immutable" \ --content-type "application/javascript" @@ -53,5 +53,5 @@ jobs: run: | aws cloudfront create-invalidation \ --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \ - --paths "/prebid/prebid.js" + --paths "/prod/prebid.js" From 476e692d9ca2173f4927701510a435f4b673ffbc Mon Sep 17 00:00:00 2001 From: Martin Hill Date: Sat, 2 May 2026 20:03:38 +0100 Subject: [PATCH 03/13] Fix branch name from main to master --- .github/workflows/deploy-prebid.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-prebid.yml b/.github/workflows/deploy-prebid.yml index f2327899b8..a43513fb27 100644 --- a/.github/workflows/deploy-prebid.yml +++ b/.github/workflows/deploy-prebid.yml @@ -3,7 +3,7 @@ name: Build & Deploy Prebid.js on: push: branches: - - main + - master workflow_dispatch: jobs: From f7085b575ec6528d249e22b38b403f7724a90ade Mon Sep 17 00:00:00 2001 From: Martin Hill Date: Sat, 2 May 2026 20:09:36 +0100 Subject: [PATCH 04/13] git workflow fix 1 --- .github/workflows/deploy-prebid.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/deploy-prebid.yml b/.github/workflows/deploy-prebid.yml index a43513fb27..b316e7cf8d 100644 --- a/.github/workflows/deploy-prebid.yml +++ b/.github/workflows/deploy-prebid.yml @@ -6,6 +6,9 @@ on: - master workflow_dispatch: +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + jobs: build-and-deploy: runs-on: ubuntu-latest @@ -27,6 +30,9 @@ jobs: run: | npx gulp build --modules=consentManagementGpp,consentManagementTcf,consentManagementUsp,storageControl,tcfControl,gppControl_usnat,gppControl_usstates,allowActivities,bidResponseFilter,bidViewability,currency,priceFloors,debugging,geolocationRtdProvider,multibid,nativeRendering,prebidServerBidAdapter,previousAuctionInfo,s2sTesting,schain,rules,timeoutRtdProvider,videoModule,validationFpdModule,adfBidAdapter,tpcBidAdapter,magniteBidAdapter,pubmaticBidAdapter,ixBidAdapter + - name: List build output + run: find build/ -name "prebid.js" | head -20 + - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: From 34c8a9ec3c544531a865f711db714a69d44c0edb Mon Sep 17 00:00:00 2001 From: Martin Hill Date: Sat, 2 May 2026 20:12:27 +0100 Subject: [PATCH 05/13] adding tpcBidAdapter files --- modules/tpcBidAdapter.js | 148 +++++++++++++ modules/tpcBidAdapter.md | 157 +++++++++++++ test/spec/modules/tpcBidAdapter_spec.js | 280 ++++++++++++++++++++++++ 3 files changed, 585 insertions(+) create mode 100644 modules/tpcBidAdapter.js create mode 100644 modules/tpcBidAdapter.md create mode 100644 test/spec/modules/tpcBidAdapter_spec.js diff --git a/modules/tpcBidAdapter.js b/modules/tpcBidAdapter.js new file mode 100644 index 0000000000..502f254b34 --- /dev/null +++ b/modules/tpcBidAdapter.js @@ -0,0 +1,148 @@ +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { pbsExtensions } from '../libraries/pbsExtensions/pbsExtensions.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; +import { deepAccess, deepSetValue, deepClone, logWarn, logError, triggerPixel, isEmpty, shuffle } from '../src/utils.js'; + +const BIDDER_CODE = 'tpc'; +const PBS_ENDPOINT = 'https://pbs.tpcsrv.com/openrtb2/auction'; +const USER_SYNC_ENDPOINT = 'https://pbs.tpcsrv.com/cookie_sync'; +const MAX_SYNC_COUNT = 10; + +const converter = ortbConverter({ + processors: pbsExtensions, + context: { + netRevenue: true, + ttl: 300, + }, + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + const { placementId, bidder } = bidRequest.params; + if (placementId) { + deepSetValue(imp, 'ext.prebid.bidder.tpc.placementId', placementId); + } + if (bidder) { + deepSetValue(imp, 'ext.prebid.bidder.tpc.bidder', bidder); + } + return imp; + }, + overrides: { + bidResponse: { + bidderCode(orig, bidResponse, bid, { bidRequest }) { + const useSourceBidderCode = deepAccess(bidRequest, 'params.useSourceBidderCode', false); + if (useSourceBidderCode) { + orig.apply(this, [...arguments].slice(1)); + } + }, + }, + }, +}); + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + aliases: [], + + isBidRequestValid(bid) { + if (!deepAccess(bid, 'params.accountId')) { + logWarn(`${BIDDER_CODE}: bid missing required params.accountId`, bid); + return false; + } + return true; + }, + + buildRequests(validBidRequests, bidderRequest) { + const { bidder } = validBidRequests[0]; + const data = converter.toORTB({ bidRequests: validBidRequests, bidderRequest }); + const accountId = deepAccess(validBidRequests[0], 'params.accountId'); + if (accountId) { + deepSetValue(data, 'site.publisher.id', accountId); + } + data.ext.prebid.passthrough = { + ...data.ext.prebid.passthrough, + tpc: { bidder }, + }; + data.tmax = (bidderRequest.timeout || 1500) - 100; + return { + method: 'POST', + url: PBS_ENDPOINT, + data, + }; + }, + + interpretResponse(serverResponse, request) { + if (!serverResponse?.body) return []; + const resp = deepClone(serverResponse.body); + const { bidder } = request.data.ext.prebid.passthrough.tpc; + const modifiers = { + responsetimemillis: (values) => Math.max(...values), + errors: (values) => [].concat(...values), + }; + Object.entries(modifiers).forEach(([field, combineFn]) => { + const obj = resp.ext?.[field]; + if (!isEmpty(obj)) { + resp.ext[field] = { [bidder]: combineFn(Object.values(obj)) }; + } + }); + return converter.fromORTB({ response: resp, request: request.data }).bids; + }, + + getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) { + if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) return []; + + let bidders = []; + serverResponses.forEach(({ body }) => { + Object.keys(body?.ext?.responsetimemillis || {}).forEach(b => { + if (!bidders.includes(b)) bidders.push(b); + }); + }); + + if (!bidders.length) return []; + + bidders = shuffle(bidders).slice(0, MAX_SYNC_COUNT); + + const params = new URLSearchParams(); + params.set('bidders', bidders.join(',')); + params.set('max_sync_count', MAX_SYNC_COUNT); + + if (gdprConsent) { + params.set('gdpr', gdprConsent.gdprApplies ? '1' : '0'); + if (gdprConsent.consentString) { + params.set('gdpr_consent', gdprConsent.consentString); + } + } + if (uspConsent) { + params.set('us_privacy', uspConsent); + } + if (gppConsent?.gppString) { + params.set('gpp', gppConsent.gppString); + } + if (Array.isArray(gppConsent?.applicableSections)) { + params.set('gpp_sid', gppConsent.applicableSections.join(',')); + } + + const syncUrl = `${USER_SYNC_ENDPOINT}?${params.toString()}`; + if (syncOptions.iframeEnabled) { + return [{ type: 'iframe', url: syncUrl }]; + } + return [{ type: 'image', url: syncUrl }]; + }, + + onBidWon(bid) { + if (bid.pbsWurl) triggerPixel(bid.pbsWurl); + if (bid.burl) triggerPixel(bid.burl); + }, + + onBidderError({ error }) { + if (error.status === 400 && error.responseText) { + const match = error.responseText.match(/found for id: (.*)/); + if (match?.[1]) { + logError(`${BIDDER_CODE}: account '${match[1]}' not found. Please verify your accountId.`, error); + return; + } + } + logError(`${BIDDER_CODE} bidder error`, error); + }, +}; + +registerBidder(spec); diff --git a/modules/tpcBidAdapter.md b/modules/tpcBidAdapter.md new file mode 100644 index 0000000000..3f81ea4e88 --- /dev/null +++ b/modules/tpcBidAdapter.md @@ -0,0 +1,157 @@ +# Overview + +``` +Module Name: TPC Bid Adapter +Module Type: Bidder Adapter +Maintainer: your-team@tpcsrv.com +``` + +Connects to the TPC Prebid Server at `pbs.tpcsrv.com` for header bidding. +Supports **banner**, **video** (instream & outstream), and **native** ad formats. + +--- + +## Consent & Privacy Support + +| Signal | Support | +|--------|---------| +| TCF / GDPR | ✅ Passed in `regs.ext.gdpr` + `user.ext.consent` | +| US Privacy (legacy) | ✅ Passed in `regs.ext.us_privacy` | +| GPP (Global Privacy Platform) | ✅ Passed in `regs.gpp` + `regs.gpp_sid` | +| COPPA | ✅ Reads `pbjs.setConfig({ coppa: true })` | + +--- + +## User ID & Identity + +Any EIDs collected by the Prebid.js **User ID module** are forwarded to Prebid Server in `user.eids`. + +--- + +## User Syncing + +After each auction the adapter triggers a single **iframe** (preferred) or **pixel** sync with the +PBS `/cookie_sync` endpoint. Bidder codes are derived automatically from the auction response, so +PBS syncs only the seats that actually responded. + +--- + +## Bid Params + +| Name | Scope | Type | Description | +|------|-------|------|-------------| +| `accountId` | **Required** | string | Publisher account ID on pbs.tpcsrv.com | +| `placementId` | Optional | string | Placement / tag identifier | +| `bidder` | Optional | string | Downstream PBS bidder code for single-bidder passthrough | + +--- + +## Ad Unit Examples + +### Banner + +```js +var adUnits = [{ + code: 'banner-div', + mediaTypes: { + banner: { sizes: [[300, 250], [728, 90]] }, + }, + bids: [{ + bidder: 'tpc', + params: { + accountId: 'pub-1234', + placementId: 'homepage-leaderboard', + }, + }], +}]; +``` + +### Video (instream) + +```js +var adUnits = [{ + code: 'video-div', + mediaTypes: { + video: { + playerSize: [[640, 480]], + context: 'instream', + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6], + playbackmethod: [2], + skip: 1, + }, + }, + bids: [{ + bidder: 'tpc', + params: { + accountId: 'pub-1234', + placementId: 'pre-roll-640', + }, + }], +}]; +``` + +### Native + +```js +var adUnits = [{ + code: 'native-div', + mediaTypes: { + native: { + ortb: { + assets: [ + { id: 1, required: 1, title: { len: 80 } }, + { id: 2, required: 1, img: { type: 3, w: 300, h: 250 } }, + { id: 3, required: 0, data: { type: 2 } }, // description + ], + }, + }, + }, + bids: [{ + bidder: 'tpc', + params: { + accountId: 'pub-1234', + }, + }], +}]; +``` + +### Multi-format + +```js +var adUnits = [{ + code: 'multi-div', + mediaTypes: { + banner: { sizes: [[300, 250]] }, + video: { + playerSize: [[300, 250]], + context: 'outstream', + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4], + }, + }, + bids: [{ + bidder: 'tpc', + params: { + accountId: 'pub-1234', + placementId: 'sidebar-multi', + }, + }], +}]; +``` + +--- + +## Full Example Setup + +```js +pbjs.que.push(function () { + pbjs.addAdUnits(adUnits); + + pbjs.requestBids({ + bidsBackHandler: function (bids) { + // ... send targeting to ad server + }, + }); +}); +``` diff --git a/test/spec/modules/tpcBidAdapter_spec.js b/test/spec/modules/tpcBidAdapter_spec.js new file mode 100644 index 0000000000..69371b2999 --- /dev/null +++ b/test/spec/modules/tpcBidAdapter_spec.js @@ -0,0 +1,280 @@ +import { spec } from 'modules/tpcBidAdapter.js'; +import { parseUrl } from 'src/utils.js'; + +const expect = require('chai').expect; + +const PBS_HOST = 'pbs.tpcsrv.com'; +const ACCOUNT_ID = 'pub-1234'; +const PLACEMENT_ID = 'homepage-leaderboard'; +const TEST_DOMAIN = 'example.com'; +const TEST_PAGE = `https://${TEST_DOMAIN}/page.html`; +const ADUNIT_CODE = '/1234/header-bid-tag-0'; + +const BID_PARAMS = { + params: { + accountId: ACCOUNT_ID, + placementId: PLACEMENT_ID, + } +}; + +const BID_REQUEST = { + bidder: 'tpc', + ...BID_PARAMS, + ortb2Imp: { + ext: { + tid: 'e13391ea-00f3-495d-99a6-d937990d73a9' + } + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + adUnitCode: ADUNIT_CODE, + transactionId: 'e13391ea-00f3-495d-99a6-d937990d73a9', + sizes: [[300, 250]], + bidId: '123456789', + bidderRequestId: '1decd098c76ed2', + auctionId: '251a6a36-a5c5-4b82-b2b3-538c148a29dd', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + ortb2: { + site: { + page: TEST_PAGE, + domain: TEST_DOMAIN, + publisher: { + domain: TEST_DOMAIN + } + }, + device: { + w: 1848, + h: 1007, + dnt: 0, + ua: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36', + language: 'en', + } + } +}; + +const BIDDER_REQUEST = { + bidderCode: BID_REQUEST.bidder, + auctionId: BID_REQUEST.auctionId, + bidderRequestId: BID_REQUEST.bidderRequestId, + bids: [BID_REQUEST], + ortb2: BID_REQUEST.ortb2, + auctionStart: 1681224591370, + timeout: 1000, + refererInfo: { + reachedTop: true, + isAmp: false, + numIframes: 0, + stack: [TEST_PAGE], + topmostLocation: TEST_PAGE, + location: TEST_PAGE, + canonicalUrl: null, + page: TEST_PAGE, + domain: TEST_DOMAIN, + ref: null, + }, + start: 1681224591375 +}; + +const BID_RESPONSE = { + seatbid: [ + { + bid: [ + { + id: '123456789', + impid: BID_REQUEST.bidId, + price: 1.5, + adm: '', + adomain: ['example.com'], + crid: 'creative-abc-123', + w: 300, + h: 250, + exp: 300, + mtype: 1, + ext: { + prebid: { + type: 'banner', + targeting: { + hb_size: '300x250', + hb_bidder: 'tpc', + hb_pb: '1.50' + }, + meta: { + advertiserDomains: ['example.com'] + } + }, + origbidcpm: 1.5 + } + } + ], + seat: 'appnexus', + group: 0 + } + ], + cur: 'USD', + ext: { + responsetimemillis: { + appnexus: 50 + }, + tmaxrequest: 900, + prebid: { + auctiontimestamp: 1678646619765, + passthrough: { + tpc: { + bidder: spec.code + } + } + } + } +}; + +const S2S_RESPONSE_BIDDER = BID_RESPONSE.seatbid[0].seat; + +const buildRequest = (params) => { + const bidRequest = { + ...BID_REQUEST, + params: { + ...BID_REQUEST.params, + ...params, + }, + }; + return spec.buildRequests([bidRequest], BIDDER_REQUEST); +}; + +describe('TPC Bid Adapter', function () { + describe('isBidRequestValid', () => { + it('should return true for a valid bid with accountId', () => { + expect(spec.isBidRequestValid(BID_REQUEST)).to.be.true; + }); + it('should return false when accountId is missing', () => { + const bid = { ...BID_REQUEST, params: {} }; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', () => { + const { data, url } = buildRequest(); + it('should POST to the correct PBS endpoint', () => { + expect(url).equal(`https://${PBS_HOST}/openrtb2/auction`); + }); + it('should set site.publisher.id to the accountId', () => { + expect(data.site.publisher.id).equal(ACCOUNT_ID); + }); + it('should include bidder code in the passthrough object', () => { + expect(data.ext.prebid.passthrough.tpc.bidder).equal(spec.code); + }); + it('should set tmax below the bidder timeout', () => { + expect(data.tmax).be.greaterThan(0); + expect(data.tmax).be.lessThan(BIDDER_REQUEST.timeout); + }); + it('should set placementId in imp.ext.prebid.bidder.tpc', () => { + expect(data.imp[0].ext.prebid.bidder.tpc.placementId).equal(PLACEMENT_ID); + }); + }); + + describe('buildRequests without placementId', () => { + const { data } = buildRequest({ placementId: undefined }); + it('should not set placementId when not provided', () => { + expect(data.imp[0].ext?.prebid?.bidder?.tpc?.placementId).to.be.undefined; + }); + }); + + describe('interpretResponse', () => { + const request = buildRequest(); + const [bid] = spec.interpretResponse({ body: BID_RESPONSE }, request); + it('should return the correct bid values', () => { + const respBid = BID_RESPONSE.seatbid[0].bid[0]; + expect(bid.cpm).equal(respBid.price); + expect(bid.ad).equal(respBid.adm); + expect(bid.width).equal(respBid.w); + expect(bid.height).equal(respBid.h); + }); + it('should not expose the S2S seat as bidderCode by default', () => { + expect(bid.bidderCode).not.equal(S2S_RESPONSE_BIDDER); + }); + it('should return an empty array when there is no body', () => { + expect(spec.interpretResponse({}, request)).to.deep.equal([]); + }); + }); + + describe('interpretResponse with useSourceBidderCode', () => { + const request = buildRequest({ useSourceBidderCode: true }); + const [bid] = spec.interpretResponse({ body: BID_RESPONSE }, request); + it('should expose the S2S seat as bidderCode', () => { + expect(bid.bidderCode).equal(S2S_RESPONSE_BIDDER); + }); + }); + + describe('getUserSyncs with iframeEnabled', () => { + const allSyncs = spec.getUserSyncs({ iframeEnabled: true }, [{ body: BID_RESPONSE }], null, null, null); + const [{ url, type }] = allSyncs; + const parsed = parseUrl(url); + it('should return a single sync object', () => { + expect(allSyncs.length).equal(1); + }); + it('should use iframe sync type', () => { + expect(type).equal('iframe'); + }); + it('should sync to the cookie_sync endpoint', () => { + expect(parsed.hostname).equal(PBS_HOST); + expect(parsed.pathname).equal('/cookie_sync'); + }); + it('should include at least one bidder', () => { + expect(parsed.search.bidders.split(',').length).be.greaterThan(0); + }); + }); + + describe('getUserSyncs with pixelEnabled only', () => { + const allSyncs = spec.getUserSyncs({ iframeEnabled: false, pixelEnabled: true }, [{ body: BID_RESPONSE }], null, null, null); + it('should return a pixel sync when iframe is not enabled', () => { + expect(allSyncs.length).equal(1); + expect(allSyncs[0].type).equal('image'); + }); + }); + + describe('getUserSyncs with no sync options enabled', () => { + const allSyncs = spec.getUserSyncs({ iframeEnabled: false, pixelEnabled: false }, [{ body: BID_RESPONSE }], null, null, null); + it('should return an empty array', () => { + expect(allSyncs).to.deep.equal([]); + }); + }); + + describe('getUserSyncs with no bidders in response', () => { + const allSyncs = spec.getUserSyncs({ iframeEnabled: true }, [{ body: {} }], null, null, null); + it('should return an empty array when no bidders responded', () => { + expect(allSyncs).to.deep.equal([]); + }); + }); + + describe('getUserSyncs with consent signals', () => { + const gdprConsent = { gdprApplies: true, consentString: 'abc123' }; + const uspConsent = '1YNN'; + const gppConsent = { gppString: 'gpp_str', applicableSections: [7, 8] }; + const [{ url }] = spec.getUserSyncs( + { iframeEnabled: true }, + [{ body: BID_RESPONSE }], + gdprConsent, + uspConsent, + gppConsent + ); + const { search } = parseUrl(url); + it('should include GDPR params', () => { + expect(search.gdpr).equal('1'); + expect(search.gdpr_consent).equal('abc123'); + }); + it('should include US Privacy param', () => { + expect(search.us_privacy).equal('1YNN'); + }); + it('should include GPP params', () => { + expect(search.gpp).equal('gpp_str'); + expect(search.gpp_sid).equal('7,8'); + }); + }); +}); From f60ad86e6f2f96e6d2c2f965b18141485eaa9695 Mon Sep 17 00:00:00 2001 From: Martin Hill Date: Sat, 2 May 2026 20:17:03 +0100 Subject: [PATCH 06/13] workflow fix dist path --- .github/workflows/deploy-prebid.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-prebid.yml b/.github/workflows/deploy-prebid.yml index b316e7cf8d..a8bb7d9d63 100644 --- a/.github/workflows/deploy-prebid.yml +++ b/.github/workflows/deploy-prebid.yml @@ -43,14 +43,14 @@ jobs: - name: Upload to S3 run: | # Upload the latest build (short-lived cache) - aws s3 cp build/prebid.js \ + aws s3 cp build/dist/prebid.js \ s3://${{ secrets.S3_BUCKET }}/prod/prebid.js \ --cache-control "public, max-age=3600, s-maxage=86400" \ --content-type "application/javascript" # Upload a versioned copy (long-lived cache) VERSION=$(node -p "require('./package.json').version") - aws s3 cp build/prebid.js \ + aws s3 cp build/dist/prebid.js \ s3://${{ secrets.S3_BUCKET }}/prod/prebid-${VERSION}.js \ --cache-control "public, max-age=31536000, immutable" \ --content-type "application/javascript" From 0709ba216ed961227b703058498e859b560b022b Mon Sep 17 00:00:00 2001 From: Martin Hill Date: Sun, 3 May 2026 14:06:53 +0100 Subject: [PATCH 07/13] add user id modules to build --- .github/workflows/deploy-prebid.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-prebid.yml b/.github/workflows/deploy-prebid.yml index a8bb7d9d63..207ecf82f6 100644 --- a/.github/workflows/deploy-prebid.yml +++ b/.github/workflows/deploy-prebid.yml @@ -28,7 +28,7 @@ jobs: - name: Build Prebid.js run: | - npx gulp build --modules=consentManagementGpp,consentManagementTcf,consentManagementUsp,storageControl,tcfControl,gppControl_usnat,gppControl_usstates,allowActivities,bidResponseFilter,bidViewability,currency,priceFloors,debugging,geolocationRtdProvider,multibid,nativeRendering,prebidServerBidAdapter,previousAuctionInfo,s2sTesting,schain,rules,timeoutRtdProvider,videoModule,validationFpdModule,adfBidAdapter,tpcBidAdapter,magniteBidAdapter,pubmaticBidAdapter,ixBidAdapter + npx gulp build --modules=consentManagementGpp,consentManagementTcf,consentManagementUsp,storageControl,tcfControl,gppControl_usnat,gppControl_usstates,allowActivities,bidResponseFilter,bidViewability,currency,priceFloors,debugging,geolocationRtdProvider,multibid,nativeRendering,prebidServerBidAdapter,previousAuctionInfo,s2sTesting,schain,rules,timeoutRtdProvider,videoModule,validationFpdModule,userId,sharedIdSystem,id5IdSystem,amxIdSystem,adfBidAdapter,tpcBidAdapter,magniteBidAdapter,pubmaticBidAdapter,ixBidAdapter - name: List build output run: find build/ -name "prebid.js" | head -20 From 1f9322e0a6815e93d12d020c6c867c2db6893993 Mon Sep 17 00:00:00 2001 From: Martin Hill Date: Sun, 3 May 2026 14:23:27 +0100 Subject: [PATCH 08/13] + uid,criteo user id modules to build process --- .github/workflows/deploy-prebid.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-prebid.yml b/.github/workflows/deploy-prebid.yml index 207ecf82f6..1a9e35538c 100644 --- a/.github/workflows/deploy-prebid.yml +++ b/.github/workflows/deploy-prebid.yml @@ -28,7 +28,7 @@ jobs: - name: Build Prebid.js run: | - npx gulp build --modules=consentManagementGpp,consentManagementTcf,consentManagementUsp,storageControl,tcfControl,gppControl_usnat,gppControl_usstates,allowActivities,bidResponseFilter,bidViewability,currency,priceFloors,debugging,geolocationRtdProvider,multibid,nativeRendering,prebidServerBidAdapter,previousAuctionInfo,s2sTesting,schain,rules,timeoutRtdProvider,videoModule,validationFpdModule,userId,sharedIdSystem,id5IdSystem,amxIdSystem,adfBidAdapter,tpcBidAdapter,magniteBidAdapter,pubmaticBidAdapter,ixBidAdapter + npx gulp build --modules=consentManagementGpp,consentManagementTcf,consentManagementUsp,storageControl,tcfControl,gppControl_usnat,gppControl_usstates,allowActivities,bidResponseFilter,bidViewability,currency,priceFloors,debugging,geolocationRtdProvider,multibid,nativeRendering,prebidServerBidAdapter,previousAuctionInfo,s2sTesting,schain,rules,timeoutRtdProvider,videoModule,validationFpdModule,userId,sharedIdSystem,id5IdSystem,amxIdSystem,unifiedId,criteo,adfBidAdapter,tpcBidAdapter,magniteBidAdapter,pubmaticBidAdapter,ixBidAdapter - name: List build output run: find build/ -name "prebid.js" | head -20 From b95c62fb6134f7edd1346658e401807c86979548 Mon Sep 17 00:00:00 2001 From: Martin Hill Date: Sun, 3 May 2026 14:27:01 +0100 Subject: [PATCH 09/13] add criteo id fix to build process --- .github/workflows/deploy-prebid.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-prebid.yml b/.github/workflows/deploy-prebid.yml index 1a9e35538c..94786a6da1 100644 --- a/.github/workflows/deploy-prebid.yml +++ b/.github/workflows/deploy-prebid.yml @@ -28,7 +28,7 @@ jobs: - name: Build Prebid.js run: | - npx gulp build --modules=consentManagementGpp,consentManagementTcf,consentManagementUsp,storageControl,tcfControl,gppControl_usnat,gppControl_usstates,allowActivities,bidResponseFilter,bidViewability,currency,priceFloors,debugging,geolocationRtdProvider,multibid,nativeRendering,prebidServerBidAdapter,previousAuctionInfo,s2sTesting,schain,rules,timeoutRtdProvider,videoModule,validationFpdModule,userId,sharedIdSystem,id5IdSystem,amxIdSystem,unifiedId,criteo,adfBidAdapter,tpcBidAdapter,magniteBidAdapter,pubmaticBidAdapter,ixBidAdapter + npx gulp build --modules=consentManagementGpp,consentManagementTcf,consentManagementUsp,storageControl,tcfControl,gppControl_usnat,gppControl_usstates,allowActivities,bidResponseFilter,bidViewability,currency,priceFloors,debugging,geolocationRtdProvider,multibid,nativeRendering,prebidServerBidAdapter,previousAuctionInfo,s2sTesting,schain,rules,timeoutRtdProvider,videoModule,validationFpdModule,userId,sharedIdSystem,id5IdSystem,amxIdSystem,unifiedId,criteoIdSystem,adfBidAdapter,tpcBidAdapter,magniteBidAdapter,pubmaticBidAdapter,ixBidAdapter - name: List build output run: find build/ -name "prebid.js" | head -20 From 1d3240f5f502b6056dc9a83cc3dadf2ae5d828a5 Mon Sep 17 00:00:00 2001 From: Martin Hill Date: Sun, 3 May 2026 14:29:43 +0100 Subject: [PATCH 10/13] add uid fix to build process --- .github/workflows/deploy-prebid.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-prebid.yml b/.github/workflows/deploy-prebid.yml index 94786a6da1..ead81a2c28 100644 --- a/.github/workflows/deploy-prebid.yml +++ b/.github/workflows/deploy-prebid.yml @@ -28,7 +28,7 @@ jobs: - name: Build Prebid.js run: | - npx gulp build --modules=consentManagementGpp,consentManagementTcf,consentManagementUsp,storageControl,tcfControl,gppControl_usnat,gppControl_usstates,allowActivities,bidResponseFilter,bidViewability,currency,priceFloors,debugging,geolocationRtdProvider,multibid,nativeRendering,prebidServerBidAdapter,previousAuctionInfo,s2sTesting,schain,rules,timeoutRtdProvider,videoModule,validationFpdModule,userId,sharedIdSystem,id5IdSystem,amxIdSystem,unifiedId,criteoIdSystem,adfBidAdapter,tpcBidAdapter,magniteBidAdapter,pubmaticBidAdapter,ixBidAdapter + npx gulp build --modules=consentManagementGpp,consentManagementTcf,consentManagementUsp,storageControl,tcfControl,gppControl_usnat,gppControl_usstates,allowActivities,bidResponseFilter,bidViewability,currency,priceFloors,debugging,geolocationRtdProvider,multibid,nativeRendering,prebidServerBidAdapter,previousAuctionInfo,s2sTesting,schain,rules,timeoutRtdProvider,videoModule,validationFpdModule,userId,sharedIdSystem,id5IdSystem,amxIdSystem,unifiedIdSystem,criteoIdSystem,adfBidAdapter,tpcBidAdapter,magniteBidAdapter,pubmaticBidAdapter,ixBidAdapter - name: List build output run: find build/ -name "prebid.js" | head -20 From 657a6f01bd8a71aa2466e6d3bc6f451be5c38cef Mon Sep 17 00:00:00 2001 From: Martin Hill Date: Thu, 7 May 2026 19:54:34 +0100 Subject: [PATCH 11/13] add architecture overview and command reference to CLAUDE.md Co-Authored-By: Claude Sonnet 4.6 --- AGENTS.md | 43 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 43b12bccf9..13c6bcceb3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,7 +1,26 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + # Repo guidelines for Codex This file contains instructions for the Codex agent and its friends when working on tasks in this repository. +## Architecture Overview + +Prebid.js is a header bidding library that coordinates real-time auctions between publishers and demand partners (bidders). The codebase is organized into three layers: + +- **`src/`** — Core auction engine. Migrating from JS to TypeScript. Key files: `prebid.ts` (public API / `pbjs` global), `auction.ts` (auction lifecycle), `adapterManager.ts` (bidder orchestration), `config.ts` (global config), `hook.ts` (extensibility system), `storageManager.ts` (all storage access), `userSync.ts` (pixel/iframe syncs), `fpd/` (First Party Data enrichment), `activities/` (privacy/consent permission framework). +- **`modules/`** — ~1400 opt-in modules. Naming conventions: `*BidAdapter.js` (demand partners), `*IdSystem.js` (user ID modules), `*RtdProvider.js` (real-time data), `*AnalyticsAdapter.js` (event tracking). Each module self-registers on import; unused modules are tree-shaken from builds. +- **`libraries/`** — Shared utility code imported by multiple adapters (e.g. `appnexusUtils/`, `chunk/`, `cmp/`). Prefer importing from here over duplicating logic in adapter files. + +### Key patterns + +- **Hook system** (`src/hook.ts`): modules extend core behavior by hooking into named functions rather than patching them directly. Use `module.exports.hook` or `wrapHook`. +- **Activities framework** (`src/activities/`): privacy-aware permission checks. Call `isActivityAllowed()` before transmitting user data; never bypass this. +- **StorageManager** (`src/storageManager.ts`): the only permitted way to access cookies and localStorage. Modules must not call `document.cookie` or `localStorage` directly. +- **ORTB2 / FPD**: global first-party data lives on `bidderRequest.ortb2`; impression-level FPD on `bidRequest.ortb2imp`. Adapters should read from these rather than requiring publishers to duplicate data in bidder params. + ## Programmatic checks - if you don't have an eslint cache, establish one early with `npx eslint --cache --cache-strategy content`. eslint can easily take two minutes to run. - Before committing code changes, run lint and run tests on the files you have changed. Successful linting has no output. @@ -10,6 +29,22 @@ This file contains instructions for the Codex agent and its friends when working - If additional tests are added, ensure they pass in the environment. - `gulp review-start` can be used for manual testing; it opens coverage reports and integration examples such as `integrationExamples/gpt/hello_world.html`. +## Commands + +| Task | Command | +|------|---------| +| Dev server + watch + test | `gulp serve-and-test --file ` | +| Build (production) | `gulp build` | +| Build (dev, no precompile) | `gulp build-bundle-dev-no-precomp` | +| Lint all | `gulp lint` | +| Lint changed files only | `npx eslint '[files]' --cache --cache-strategy content` | +| Test single spec | `gulp test --file test/spec/modules/myAdapter_spec.js` | +| Test (no re-lint) | `gulp test --nolint --file ` | +| Manual review hub | `gulp review-start` | +| Build with specific modules | `gulp build --modules=openxBidAdapter,rubiconBidAdapter` | + +Tests live in `test/spec/` (core) and `test/spec/modules/` (adapters). The test framework is Mocha + Chai + Sinon, run via Karma. Use the global XHR stub at `test/mocks/xhr` for Ajax tests; do not create your own `sinon.useFakeXMLHttpRequest()`. + ## PR message guidelines - Summaries should describe the changes concisely and reference file lines using the citation format. Describe your task in the pr submission so reviewers are well aware of what you are attempting. - Document the results of `gulp lint` and `gulp test` in the PR description if the commands are successful. @@ -17,7 +52,7 @@ This file contains instructions for the Codex agent and its friends when working - Keep PRs scoped to a single change type. Add release labels (`feature`, `maintenance`, `fix`, `bug`) and a SemVer label (`major`, `minor`, `patch`). ## Issue template -- Fill out every section of `.github/ISSUE_TEMPLATE.md` when filing issues, including steps to reproduce and platform details. If there isn't an associated issue, include this template into any PR. +- Fill out every section of `.github/ISSUE_TEMPLATE.md` when filing issues, including steps to reproduce and platform details. If there isn't an associated issue, include this template into any PR. ## General guidance - Node.js `>=20` is required; dependencies are managed with `npm`. @@ -52,9 +87,9 @@ This file contains instructions for the Codex agent and its friends when working ## Common adapter types - When bid adapter changes need shared type references, look in the core source modules first: -- `src/adapters/bidderFactory.js` for bidder registration/build and bidder-spec wiring concepts. -- `src/userSync.js` for user sync interfaces, sync option handling, and sync registration behavior. -- `src/adapterManager.js` for adapter manager orchestration and type usage patterns around bidder lifecycle. +- `src/adapters/bidderFactory.ts` for bidder registration/build and bidder-spec wiring concepts. +- `src/userSync.ts` for user sync interfaces, sync option handling, and sync registration behavior. +- `src/adapterManager.ts` for adapter manager orchestration and type usage patterns around bidder lifecycle. - Prefer importing or mirroring conventions from these modules instead of redefining local ad-hoc shapes. - Use imported types for id, analytics, and rtd modules as well whenever possible. - Always define types for public interface to an adapter, eg each bidder parameter. From 687b104ee55b72d7e1f080685b4c2e779910cb4e Mon Sep 17 00:00:00 2001 From: Martin Hill Date: Sun, 10 May 2026 15:54:51 +0100 Subject: [PATCH 12/13] AGENTS.md: revert to upstream parity --- AGENTS.md | 43 ++++--------------------------------------- 1 file changed, 4 insertions(+), 39 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 13c6bcceb3..43b12bccf9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,26 +1,7 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - # Repo guidelines for Codex This file contains instructions for the Codex agent and its friends when working on tasks in this repository. -## Architecture Overview - -Prebid.js is a header bidding library that coordinates real-time auctions between publishers and demand partners (bidders). The codebase is organized into three layers: - -- **`src/`** — Core auction engine. Migrating from JS to TypeScript. Key files: `prebid.ts` (public API / `pbjs` global), `auction.ts` (auction lifecycle), `adapterManager.ts` (bidder orchestration), `config.ts` (global config), `hook.ts` (extensibility system), `storageManager.ts` (all storage access), `userSync.ts` (pixel/iframe syncs), `fpd/` (First Party Data enrichment), `activities/` (privacy/consent permission framework). -- **`modules/`** — ~1400 opt-in modules. Naming conventions: `*BidAdapter.js` (demand partners), `*IdSystem.js` (user ID modules), `*RtdProvider.js` (real-time data), `*AnalyticsAdapter.js` (event tracking). Each module self-registers on import; unused modules are tree-shaken from builds. -- **`libraries/`** — Shared utility code imported by multiple adapters (e.g. `appnexusUtils/`, `chunk/`, `cmp/`). Prefer importing from here over duplicating logic in adapter files. - -### Key patterns - -- **Hook system** (`src/hook.ts`): modules extend core behavior by hooking into named functions rather than patching them directly. Use `module.exports.hook` or `wrapHook`. -- **Activities framework** (`src/activities/`): privacy-aware permission checks. Call `isActivityAllowed()` before transmitting user data; never bypass this. -- **StorageManager** (`src/storageManager.ts`): the only permitted way to access cookies and localStorage. Modules must not call `document.cookie` or `localStorage` directly. -- **ORTB2 / FPD**: global first-party data lives on `bidderRequest.ortb2`; impression-level FPD on `bidRequest.ortb2imp`. Adapters should read from these rather than requiring publishers to duplicate data in bidder params. - ## Programmatic checks - if you don't have an eslint cache, establish one early with `npx eslint --cache --cache-strategy content`. eslint can easily take two minutes to run. - Before committing code changes, run lint and run tests on the files you have changed. Successful linting has no output. @@ -29,22 +10,6 @@ Prebid.js is a header bidding library that coordinates real-time auctions betwee - If additional tests are added, ensure they pass in the environment. - `gulp review-start` can be used for manual testing; it opens coverage reports and integration examples such as `integrationExamples/gpt/hello_world.html`. -## Commands - -| Task | Command | -|------|---------| -| Dev server + watch + test | `gulp serve-and-test --file ` | -| Build (production) | `gulp build` | -| Build (dev, no precompile) | `gulp build-bundle-dev-no-precomp` | -| Lint all | `gulp lint` | -| Lint changed files only | `npx eslint '[files]' --cache --cache-strategy content` | -| Test single spec | `gulp test --file test/spec/modules/myAdapter_spec.js` | -| Test (no re-lint) | `gulp test --nolint --file ` | -| Manual review hub | `gulp review-start` | -| Build with specific modules | `gulp build --modules=openxBidAdapter,rubiconBidAdapter` | - -Tests live in `test/spec/` (core) and `test/spec/modules/` (adapters). The test framework is Mocha + Chai + Sinon, run via Karma. Use the global XHR stub at `test/mocks/xhr` for Ajax tests; do not create your own `sinon.useFakeXMLHttpRequest()`. - ## PR message guidelines - Summaries should describe the changes concisely and reference file lines using the citation format. Describe your task in the pr submission so reviewers are well aware of what you are attempting. - Document the results of `gulp lint` and `gulp test` in the PR description if the commands are successful. @@ -52,7 +17,7 @@ Tests live in `test/spec/` (core) and `test/spec/modules/` (adapters). The test - Keep PRs scoped to a single change type. Add release labels (`feature`, `maintenance`, `fix`, `bug`) and a SemVer label (`major`, `minor`, `patch`). ## Issue template -- Fill out every section of `.github/ISSUE_TEMPLATE.md` when filing issues, including steps to reproduce and platform details. If there isn't an associated issue, include this template into any PR. +- Fill out every section of `.github/ISSUE_TEMPLATE.md` when filing issues, including steps to reproduce and platform details. If there isn't an associated issue, include this template into any PR. ## General guidance - Node.js `>=20` is required; dependencies are managed with `npm`. @@ -87,9 +52,9 @@ Tests live in `test/spec/` (core) and `test/spec/modules/` (adapters). The test ## Common adapter types - When bid adapter changes need shared type references, look in the core source modules first: -- `src/adapters/bidderFactory.ts` for bidder registration/build and bidder-spec wiring concepts. -- `src/userSync.ts` for user sync interfaces, sync option handling, and sync registration behavior. -- `src/adapterManager.ts` for adapter manager orchestration and type usage patterns around bidder lifecycle. +- `src/adapters/bidderFactory.js` for bidder registration/build and bidder-spec wiring concepts. +- `src/userSync.js` for user sync interfaces, sync option handling, and sync registration behavior. +- `src/adapterManager.js` for adapter manager orchestration and type usage patterns around bidder lifecycle. - Prefer importing or mirroring conventions from these modules instead of redefining local ad-hoc shapes. - Use imported types for id, analytics, and rtd modules as well whenever possible. - Always define types for public interface to an adapter, eg each bidder parameter. From 544770142ad57328e8c691926edb91211ff26800 Mon Sep 17 00:00:00 2001 From: Martin Hill Date: Sun, 10 May 2026 15:55:49 +0100 Subject: [PATCH 13/13] ci: remove deploy workflow (moved to private deployments repo) --- .github/workflows/deploy-prebid.yml | 63 ----------------------------- 1 file changed, 63 deletions(-) delete mode 100644 .github/workflows/deploy-prebid.yml diff --git a/.github/workflows/deploy-prebid.yml b/.github/workflows/deploy-prebid.yml deleted file mode 100644 index ead81a2c28..0000000000 --- a/.github/workflows/deploy-prebid.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: Build & Deploy Prebid.js - -on: - push: - branches: - - master - workflow_dispatch: - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -jobs: - build-and-deploy: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'npm' - - - name: Install dependencies - run: npm ci - - - name: Build Prebid.js - run: | - npx gulp build --modules=consentManagementGpp,consentManagementTcf,consentManagementUsp,storageControl,tcfControl,gppControl_usnat,gppControl_usstates,allowActivities,bidResponseFilter,bidViewability,currency,priceFloors,debugging,geolocationRtdProvider,multibid,nativeRendering,prebidServerBidAdapter,previousAuctionInfo,s2sTesting,schain,rules,timeoutRtdProvider,videoModule,validationFpdModule,userId,sharedIdSystem,id5IdSystem,amxIdSystem,unifiedIdSystem,criteoIdSystem,adfBidAdapter,tpcBidAdapter,magniteBidAdapter,pubmaticBidAdapter,ixBidAdapter - - - name: List build output - run: find build/ -name "prebid.js" | head -20 - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v4 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: ${{ secrets.AWS_REGION }} - - - name: Upload to S3 - run: | - # Upload the latest build (short-lived cache) - aws s3 cp build/dist/prebid.js \ - s3://${{ secrets.S3_BUCKET }}/prod/prebid.js \ - --cache-control "public, max-age=3600, s-maxage=86400" \ - --content-type "application/javascript" - - # Upload a versioned copy (long-lived cache) - VERSION=$(node -p "require('./package.json').version") - aws s3 cp build/dist/prebid.js \ - s3://${{ secrets.S3_BUCKET }}/prod/prebid-${VERSION}.js \ - --cache-control "public, max-age=31536000, immutable" \ - --content-type "application/javascript" - - - name: Invalidate CloudFront cache - run: | - aws cloudfront create-invalidation \ - --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \ - --paths "/prod/prebid.js" -