1313 * names to include in the bundle (e.g. "rubicon,appnexus,openx").
1414 * Each name must have a corresponding {name}BidAdapter.js module in
1515 * the prebid.js package. Default: "rubicon".
16+ *
17+ * TSJS_PREBID_USER_ID_MODULES — Ignored for production builds. User ID
18+ * modules are selected from src/integrations/prebid/user_id_modules.json
19+ * so attested bundles are deterministic. For local experiments only, use
20+ * TSJS_PREBID_USER_ID_MODULES_DEV_OVERRIDE.
1621 */
1722
23+ import crypto from 'node:crypto' ;
1824import fs from 'node:fs' ;
25+ import { createRequire } from 'node:module' ;
1926import path from 'node:path' ;
2027import { fileURLToPath } from 'node:url' ;
2128import { build } from 'vite' ;
2229
2330const __dirname = path . dirname ( fileURLToPath ( import . meta. url ) ) ;
31+ const require = createRequire ( import . meta. url ) ;
2432const srcDir = path . resolve ( __dirname , 'src' ) ;
2533const distDir = path . resolve ( __dirname , '..' , 'dist' ) ;
2634const integrationsDir = path . join ( srcDir , 'integrations' ) ;
@@ -30,10 +38,27 @@ const integrationsDir = path.join(srcDir, 'integrations');
3038// ---------------------------------------------------------------------------
3139
3240const DEFAULT_PREBID_ADAPTERS = 'rubicon' ;
33- const ADAPTERS_FILE = path . join (
41+ const ADAPTERS_FILE = path . join ( integrationsDir , 'prebid' , '_adapters.generated.ts' ) ;
42+ const USER_IDS_FILE = path . join ( integrationsDir , 'prebid' , '_user_ids.generated.ts' ) ;
43+
44+ const USER_ID_REGISTRY_FILE = path . join ( integrationsDir , 'prebid' , 'user_id_modules.json' ) ;
45+ const USER_IDS_MANIFEST_FILE = path . join ( distDir , 'prebid-user-id-modules.json' ) ;
46+ const LIVE_INTENT_SHIM_ALIAS = 'prebid.js/modules/liveIntentIdSystem.js' ;
47+ const PREBID_PACKAGE_DIR = path . join ( __dirname , 'node_modules' , 'prebid.js' ) ;
48+ const PREBID_LIVE_INTENT_STANDARD = path . join (
49+ PREBID_PACKAGE_DIR ,
50+ 'dist' ,
51+ 'src' ,
52+ 'libraries' ,
53+ 'liveIntentId' ,
54+ 'idSystem.js'
55+ ) ;
56+ const PREBID_GLOBAL_MODULE = path . join ( PREBID_PACKAGE_DIR , 'dist' , 'src' , 'src' , 'prebidGlobal.js' ) ;
57+ const LIVE_INTENT_SHIM = path . join (
3458 integrationsDir ,
3559 'prebid' ,
36- '_adapters.generated.ts' ,
60+ 'prebid_modules' ,
61+ 'liveIntentIdSystem.ts'
3762) ;
3863
3964/**
@@ -53,17 +78,12 @@ function generatePrebidAdapters() {
5378 if ( names . length === 0 ) {
5479 console . warn (
5580 '[build-all] TSJS_PREBID_ADAPTERS is empty, falling back to default:' ,
56- DEFAULT_PREBID_ADAPTERS ,
81+ DEFAULT_PREBID_ADAPTERS
5782 ) ;
5883 names . push ( DEFAULT_PREBID_ADAPTERS ) ;
5984 }
6085
61- const modulesDir = path . join (
62- __dirname ,
63- 'node_modules' ,
64- 'prebid.js' ,
65- 'modules' ,
66- ) ;
86+ const modulesDir = path . join ( __dirname , 'node_modules' , 'prebid.js' , 'modules' ) ;
6787
6888 // Validate each adapter and build import lines
6989 const imports = [ ] ;
@@ -72,7 +92,7 @@ function generatePrebidAdapters() {
7292 const modulePath = path . join ( modulesDir , moduleFile ) ;
7393 if ( ! fs . existsSync ( modulePath ) ) {
7494 console . error (
75- `[build-all] WARNING: Prebid adapter "${ name } " not found (expected ${ moduleFile } ), skipping` ,
95+ `[build-all] WARNING: Prebid adapter "${ name } " not found (expected ${ moduleFile } ), skipping`
7696 ) ;
7797 continue ;
7898 }
@@ -81,7 +101,7 @@ function generatePrebidAdapters() {
81101
82102 if ( imports . length === 0 ) {
83103 console . error (
84- '[build-all] WARNING: No valid Prebid adapters found, bundle will have no client-side adapters' ,
104+ '[build-all] WARNING: No valid Prebid adapters found, bundle will have no client-side adapters'
85105 ) ;
86106 }
87107
@@ -100,18 +120,133 @@ function generatePrebidAdapters() {
100120 fs . writeFileSync ( ADAPTERS_FILE , content ) ;
101121
102122 const adapterNames = names . filter ( ( name ) =>
103- fs . existsSync ( path . join ( modulesDir , `${ name } BidAdapter.js` ) ) ,
123+ fs . existsSync ( path . join ( modulesDir , `${ name } BidAdapter.js` ) )
104124 ) ;
105125 console . log ( '[build-all] Prebid adapters:' , adapterNames ) ;
106126}
107127
128+ function readUserIdRegistry ( ) {
129+ return JSON . parse ( fs . readFileSync ( USER_ID_REGISTRY_FILE , 'utf8' ) ) ;
130+ }
131+
132+ function requireExistingFile ( filePath , description ) {
133+ if ( ! fs . existsSync ( filePath ) ) {
134+ throw new Error ( `[build-all] Missing ${ description } : ${ filePath } ` ) ;
135+ }
136+ }
137+
138+ function prebidPackageVersion ( ) {
139+ const packageJsonPath = path . join ( PREBID_PACKAGE_DIR , 'package.json' ) ;
140+ const packageJson = JSON . parse ( fs . readFileSync ( packageJsonPath , 'utf8' ) ) ;
141+ return packageJson . version ;
142+ }
143+
144+ function sourceToModuleMap ( entries ) {
145+ const map = { } ;
146+ for ( const entry of entries ) {
147+ for ( const source of entry . eidSources ?? [ ] ) {
148+ map [ source ] = entry . moduleName ;
149+ }
150+ }
151+ return map ;
152+ }
153+
154+ function validateUserIdImport ( entry ) {
155+ requireExistingFile ( LIVE_INTENT_SHIM , 'LiveIntent ESM shim' ) ;
156+ requireExistingFile ( PREBID_LIVE_INTENT_STANDARD , 'Prebid LiveIntent standard ESM module' ) ;
157+ requireExistingFile ( PREBID_GLOBAL_MODULE , 'Prebid global module' ) ;
158+
159+ if ( entry . moduleName === 'liveIntentIdSystem' ) {
160+ return ;
161+ }
162+
163+ try {
164+ require . resolve ( entry . importPath , { paths : [ __dirname ] } ) ;
165+ } catch ( error ) {
166+ throw new Error (
167+ `[build-all] Required Prebid user ID module "${ entry . moduleName } " could not be resolved from ${ entry . importPath } : ${ error . message } `
168+ ) ;
169+ }
170+ }
171+
172+ /**
173+ * Generate `_user_ids.generated.ts` with deterministic User ID imports.
174+ *
175+ * Production builds intentionally ignore TSJS_PREBID_USER_ID_MODULES so the
176+ * attested JS artifact does not vary per publisher. A dev-only override exists
177+ * for local experiments and should not be used for trusted deployments.
178+ */
179+ function generatePrebidUserIdModules ( ) {
180+ const registry = readUserIdRegistry ( ) ;
181+ const entriesByModule = new Map ( registry . modules . map ( ( entry ) => [ entry . moduleName , entry ] ) ) ;
182+ const override = process . env . TSJS_PREBID_USER_ID_MODULES_DEV_OVERRIDE ;
183+ const moduleNames = override
184+ ? override
185+ . split ( ',' )
186+ . map ( ( s ) => s . trim ( ) )
187+ . filter ( Boolean )
188+ : registry . defaultPreset ;
189+
190+ if ( process . env . TSJS_PREBID_USER_ID_MODULES && ! override ) {
191+ console . warn (
192+ '[build-all] TSJS_PREBID_USER_ID_MODULES is ignored for deterministic attested builds. ' +
193+ 'Use TSJS_PREBID_USER_ID_MODULES_DEV_OVERRIDE only for local experiments.'
194+ ) ;
195+ }
196+
197+ if ( override ) {
198+ console . warn (
199+ '[build-all] WARNING: using TSJS_PREBID_USER_ID_MODULES_DEV_OVERRIDE. ' +
200+ 'This changes the Prebid bundle and breaks production attestation assumptions.'
201+ ) ;
202+ }
203+
204+ const selectedEntries = moduleNames . map ( ( moduleName ) => {
205+ const entry = entriesByModule . get ( moduleName ) ;
206+ if ( ! entry ) {
207+ throw new Error ( `[build-all] Unknown Prebid user ID module in preset: ${ moduleName } ` ) ;
208+ }
209+ validateUserIdImport ( entry ) ;
210+ return entry ;
211+ } ) ;
212+
213+ const imports = selectedEntries . map ( ( entry ) => `import '${ entry . importPath } ';` ) ;
214+
215+ const content = [
216+ '// Auto-generated by build-all.mjs — manual edits will be overwritten at build time.' ,
217+ '//' ,
218+ '// Deterministic Prebid.js user ID module preset for attested builds.' ,
219+ '// TSJS_PREBID_USER_ID_MODULES is intentionally ignored in production builds.' ,
220+ '// Use TSJS_PREBID_USER_ID_MODULES_DEV_OVERRIDE only for local experiments.' ,
221+ `// Modules: ${ moduleNames . join ( ', ' ) } ` ,
222+ '' ,
223+ ...imports ,
224+ '' ,
225+ ] . join ( '\n' ) ;
226+
227+ fs . writeFileSync ( USER_IDS_FILE , content ) ;
228+
229+ const manifest = {
230+ prebidVersion : prebidPackageVersion ( ) ,
231+ deterministic : ! override ,
232+ modules : moduleNames ,
233+ sourceToModule : sourceToModuleMap ( registry . modules ) ,
234+ generatedFileHash : crypto . createHash ( 'sha256' ) . update ( content ) . digest ( 'hex' ) ,
235+ } ;
236+
237+ console . log ( '[build-all] Prebid user ID modules:' , moduleNames ) ;
238+ return manifest ;
239+ }
240+
108241generatePrebidAdapters ( ) ;
242+ const prebidUserIdManifest = generatePrebidUserIdModules ( ) ;
109243
110244// ---------------------------------------------------------------------------
111245
112246// Clean dist directory
113247fs . rmSync ( distDir , { recursive : true , force : true } ) ;
114248fs . mkdirSync ( distDir , { recursive : true } ) ;
249+ fs . writeFileSync ( USER_IDS_MANIFEST_FILE , `${ JSON . stringify ( prebidUserIdManifest , null , 2 ) } \n` ) ;
115250
116251// Discover integration modules: directories in src/integrations/ with index.ts
117252const integrationModules = fs . existsSync ( integrationsDir )
@@ -120,8 +255,7 @@ const integrationModules = fs.existsSync(integrationsDir)
120255 . filter ( ( name ) => {
121256 const fullPath = path . join ( integrationsDir , name ) ;
122257 return (
123- fs . statSync ( fullPath ) . isDirectory ( ) &&
124- fs . existsSync ( path . join ( fullPath , 'index.ts' ) )
258+ fs . statSync ( fullPath ) . isDirectory ( ) && fs . existsSync ( path . join ( fullPath , 'index.ts' ) )
125259 ) ;
126260 } )
127261 . sort ( )
@@ -139,11 +273,15 @@ async function buildModule(name, entryPath) {
139273 root : __dirname ,
140274 resolve : {
141275 alias : {
276+ [ LIVE_INTENT_SHIM_ALIAS ] : LIVE_INTENT_SHIM ,
277+ 'prebid.js/modules/liveIntentIdSystem' : LIVE_INTENT_SHIM ,
278+ 'tsjs-prebid/liveIntentIdSystemStandard' : PREBID_LIVE_INTENT_STANDARD ,
279+ 'tsjs-prebid/prebidGlobal' : PREBID_GLOBAL_MODULE ,
142280 // prebid.js doesn't expose src/adapterManager.js via its package
143281 // "exports" map, but we need it for client-side bidder validation.
144282 'prebid.js/src/adapterManager.js' : path . resolve (
145283 __dirname ,
146- 'node_modules/prebid.js/dist/src/src/adapterManager.js' ,
284+ 'node_modules/prebid.js/dist/src/src/adapterManager.js'
147285 ) ,
148286 } ,
149287 } ,
@@ -176,9 +314,7 @@ async function buildModule(name, entryPath) {
176314await buildModule ( 'core' , path . join ( srcDir , 'core' , 'index.ts' ) ) ;
177315
178316await Promise . all (
179- integrationModules . map ( ( name ) =>
180- buildModule ( name , path . join ( integrationsDir , name , 'index.ts' ) ) ,
181- ) ,
317+ integrationModules . map ( ( name ) => buildModule ( name , path . join ( integrationsDir , name , 'index.ts' ) ) )
182318) ;
183319
184320// List all built files
0 commit comments