2026-09-15 15:15:46 +08:00
#!/usr/bin/env node
import { existsSync , readdirSync , readFileSync } from 'node:fs'
import { mkdir , writeFile } from 'node:fs/promises'
import { dirname , join , resolve } from 'node:path'
import { spawnSync } from 'node:child_process'
const domestic = process . env . ADMIN_TEST_GATEWAY || 'http://127.0.0.1:9000'
2026-10-08 15:55:47 +08:00
// Unified gateway is the only public entry point after the architecture cutover.
// Keep an explicit override for older local runs, but never default acceptance
// to the retired standalone overseas gateway.
const overseas = process . env . ADMIN_OVERSEAS_GATEWAY || domestic
2026-09-15 15:15:46 +08:00
const store = process . env . CODEX_BROWSER_LOCAL_STORAGE || join ( process . env . APPDATA || '' , 'Codex' , 'web' , 'Codex' , 'Default' , 'Partitions' , 'codex-browser-app' , 'Local Storage' , 'leveldb' )
const outputDir = resolve ( process . env . OVERSEAS_CURRENT_SESSION_OUTPUT_DIR || 'reports/current-session-overseas-relabel' )
const adminLoginId = process . env . ADMIN_TEST_USERNAME || 'platform_admin'
const adminPassword = process . env . ADMIN_TEST_PASSWORD || 'ChangeMe123!'
const marker = `CURRENT- ${ new Date (). toISOString (). replace ( /\D/g , '' ). slice ( 0 , 14 ) } - ${ process . pid } `
const summary = { marker , startedAt : new Date (). toISOString (), scope : 'CURRENT_CODEX_BROWSER_SESSION' , passed : false }
function assert ( value , message ) { if ( ! value ) throw new Error ( message ) }
function id ( value ) { return value == null ? '' : String ( value ) }
function jwtId ( token , ... claimNames ) {
try {
const payload = Buffer . from ( token . split ( '.' )[ 1 ], 'base64url' ). toString ( 'utf8' )
for ( const claimName of claimNames ) {
const match = payload . match ( new RegExp ( `" ${ claimName } "\\s*:\\s*(?:"(\\d+)"|(\\d+))` ))
if ( match ) return match [ 1 ] || match [ 2 ]
}
} catch {}
return ''
}
async function api ( base , token , method , path , body ) {
const response = await fetch ( ` ${ base }${ path } ` , { method ,
headers : { ...( token ? { Authorization : `Bearer ${ token } ` } : {}), ...( body === undefined ? {} : { 'Content-Type' : 'application/json' }) },
body : body === undefined ? undefined : JSON . stringify ( body ) })
const text = await response . text (); let value
try { value = text ? JSON . parse ( text ) : null } catch { value = { raw : text } }
if ( ! response . ok || String ( value ? . code ) !== '200' ) throw new Error ( ` ${ method } ${ path } : HTTP= ${ response . status } , code= ${ value ? . code } , message= ${ value ? . message } ` )
return value . data
}
async function rawApi ( base , token , method , path , body ) {
const response = await fetch ( ` ${ base }${ path } ` , { method ,
headers : { ...( token ? { Authorization : `Bearer ${ token } ` } : {}), ...( body === undefined ? {} : { 'Content-Type' : 'application/json' }) },
body : body === undefined ? undefined : JSON . stringify ( body ) })
const text = await response . text (); let value
try { value = text ? JSON . parse ( text ) : null } catch { value = null }
return { http : response . status , code : String ( value ? . code || '' ), data : value ? . data }
}
function storedValues ( key ) {
assert ( existsSync ( store ), `Codex browser local storage not found: ${ store } ` )
const values = []
const files = readdirSync ( store ). filter (( name ) => / \ .( ?: log | ldb ) $ / . test ( name ))
. map (( name ) => ({ name , path : join ( store , name ) }))
. sort (( left , right ) => readFileSync ( right . path ). length - readFileSync ( left . path ). length )
for ( const file of files ) {
const source = readFileSync ( file . path ). toString ( 'latin1' )
let offset = 0
while (( offset = source . indexOf ( key , offset )) >= 0 ) {
const match = source . slice ( offset + key . length , offset + key . length + 256 ). match ( /[A-Za-z0-9_-]{48,160}/ )
if ( match ) values . push ( match [ 0 ])
offset += key . length
}
}
return [... new Set ( values )]. reverse ()
}
function storedJwt ( key ) {
for ( const file of readdirSync ( store ). filter (( name ) => / \ .( ?: log | ldb ) $ / . test ( name )). reverse ()) {
const source = readFileSync ( join ( store , file )). toString ( 'latin1' )
let offset = source . lastIndexOf ( key )
while ( offset >= 0 ) {
const match = source . slice ( offset + key . length , offset + key . length + 2000 )
. match ( /eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/ )
if ( match ) return match [ 0 ]
offset = source . lastIndexOf ( key , offset - 1 )
}
}
return ''
}
async function refreshStoredSession ( key , label ) {
for ( const refreshToken of storedValues ( key )) {
try { return await api ( domestic , '' , 'POST' , '/api/auth/refresh' , { refreshToken }) } catch {}
}
throw new Error ( `No valid ${ label } refresh token was found in the current Codex browser session` )
}
async function disableStalePdas ( adminToken ) {
const users = await api ( domestic , adminToken , 'GET' , '/api/platform/v1/iam/users?page=1&size=100&keyword=pda_current_' )
for ( const user of users . records || []) {
if ( String ( user . username || '' ). startsWith ( 'pda_current_' ) && user . status === 'ACTIVE' ) {
await api ( domestic , adminToken , 'PUT' , `/api/platform/v1/iam/users/ ${ id ( user . membershipId || user . id ) } /status` , { status : 'DISABLED' })
}
}
}
async function createPda ( adminToken , warehouseId , onCreated ) {
const roles = await api ( domestic , adminToken , 'GET' , '/api/platform/v1/iam/roles' )
const role = roles . find (( item ) => item . roleCode === 'OVERSEAS_WAREHOUSE_PDA_OPERATOR' && item . status === 'ACTIVE' )
assert ( role , 'Active OVERSEAS_WAREHOUSE_PDA_OPERATOR role not found' )
const suffix = ` ${ Date . now () }${ process . pid } ` . slice ( - 12 )
const username = `pda_current_ ${ suffix } `
const password = `PdaCurrent ${ suffix } A9`
const created = await api ( domestic , adminToken , 'POST' , '/api/platform/v1/iam/users' , {
username , displayName : `当前会话海外PDA验收- ${ suffix } ` , phone : `19 ${ suffix . slice ( - 9 ) } ` ,
email : ` ${ username } @example.com` , tenantId : '0' , roleIds : [ id ( role . id )],
overseasWarehouseId : warehouseId , password , status : 'ACTIVE' ,
})
onCreated ({ membershipId : id ( created . membershipId ), token : null })
const login = await api ( domestic , '' , 'POST' , '/api/auth/login' , { loginId : username , password })
assert ( login . overseasAccessToken , 'Temporary PDA login did not return an overseas token' )
onCreated ({ membershipId : id ( created . membershipId ), token : login . overseasAccessToken })
for ( let attempt = 0 ; attempt < 60 ; attempt += 1 ) {
const response = await rawApi ( overseas , login . overseasAccessToken , 'GET' , '/api/pda/v1/last-leg/receiving-containers/current' )
if ( response . http === 200 && response . code !== '403' && response . code !== 'A0403' ) {
return { membershipId : id ( created . membershipId ), token : login . overseasAccessToken }
}
await new Promise (( resolve ) => setTimeout ( resolve , 500 ))
}
throw new Error ( 'Temporary PDA security projection did not converge' )
}
2026-09-17 17:37:29 +08:00
async function merchantSessionForTenant ( adminToken , tenantId ) {
2026-10-08 15:55:47 +08:00
// Tenant 0 is the platform scope, not a merchant tenant and cannot be impersonated.
assert ( /^\d+$/ . test ( id ( tenantId )) && BigInt ( tenantId ) > 0 n ,
`Invalid merchant tenant for impersonation: ${ id ( tenantId ) || 'missing' } ` )
2026-09-17 17:37:29 +08:00
const ticket = await api ( domestic , adminToken , 'POST' ,
`/api/platform/v1/iam/tenants/ ${ tenantId } /impersonation` , { reason : ` ${ marker } 海外换标真实验收` })
return api ( domestic , '' , 'POST' , '/api/auth/impersonations/exchange' , { code : ticket . code })
}
async function findRelabelScope ( adminToken , preferredMerchant = null ) {
const sessions = []
if ( preferredMerchant ? . accessToken && preferredMerchant ? . overseasAccessToken ) sessions . push ( preferredMerchant )
if ( ! sessions . length ) {
const tenants = await api ( domestic , adminToken , 'GET' , '/api/platform/v1/iam/tenants?page=1&size=200&status=ACTIVE' )
for ( const tenant of tenants . records || []) {
try { sessions . push ( await merchantSessionForTenant ( adminToken , id ( tenant . id ))) } catch {}
}
}
for ( const session of sessions ) {
const customerId = jwtId ( session . overseasAccessToken , 'tenant_id' , 'tenantId' )
if ( ! customerId ) continue
let warehouses
try { warehouses = await api ( domestic , session . accessToken , 'GET' , '/api/merchant/v1/warehouses?warehouseType=OVERSEAS&page=1&size=200' ) }
catch { continue }
for ( const warehouse of warehouses . records || []) {
if ( warehouse . status !== 'ACTIVE' ) continue
let candidates
try { candidates = await api ( overseas , session . overseasAccessToken , 'GET' , `/api/merchant/v1/last-leg/relabel-applications/inventory-candidates?operationWarehouseId= ${ encodeURIComponent ( id ( warehouse . id )) } ` ) }
catch { continue }
if ( candidates . some (( item ) => String ( item . inboundOrderNo || '' ). startsWith ( 'DS-' )
&& Number ( item . availablePackages ) >= 5 && item . destinationFbaWarehouseCode
&& item . positions ? . some (( position ) => position . palletNo && position . palletNo !== 'UNBOUND'
&& Number ( position . availablePackages ) >= 5 ))) {
return { merchant : session , customerId , warehouse }
}
}
}
throw new Error ( 'No active customer has a destination warehouse and five cartons on a physical source pallet' )
}
2026-09-15 15:15:46 +08:00
await mkdir ( outputDir , { recursive : true })
let admin
let pda
try {
try {
admin = await refreshStoredSession ( 'cc-admin-refresh-token' , 'platform' )
} catch {
admin = await api ( domestic , '' , 'POST' , '/api/auth/login' , { loginId : adminLoginId , password : adminPassword })
}
const storedMerchantToken = storedJwt ( 'cc-client-overseas-access-token' )
const storedCustomerId = jwtId ( storedMerchantToken , 'tenant_id' , 'tenantId' )
2026-09-17 17:37:29 +08:00
let preferredMerchant = null
2026-09-15 15:15:46 +08:00
try {
2026-09-17 17:37:29 +08:00
preferredMerchant = await refreshStoredSession ( 'cc-client-refresh-token' , 'merchant' )
2026-09-15 15:15:46 +08:00
} catch {
2026-10-08 15:55:47 +08:00
if ( storedCustomerId && BigInt ( storedCustomerId ) > 0 n ) {
try { preferredMerchant = await merchantSessionForTenant ( admin . accessToken , storedCustomerId ) } catch {}
}
2026-09-15 15:15:46 +08:00
}
2026-10-08 15:55:47 +08:00
// Domestic gateway architecture issues one platform access token; an overseas
// token is only required for the PDA session created below.
assert ( admin . accessToken , 'Platform login/refresh response did not contain an access token' )
2026-09-17 17:37:29 +08:00
const resume = process . env . OVERSEAS_RESUME_FULFILLMENT === 'true'
if ( resume && ( ! process . env . OVERSEAS_TEST_CUSTOMER_ID || ! process . env . OVERSEAS_TEST_WAREHOUSE_ID )) throw new Error ( 'Resume requires explicit customer and warehouse from the saved run' )
const scope = resume ? {
merchant : await merchantSessionForTenant ( admin . accessToken , process . env . OVERSEAS_TEST_CUSTOMER_ID ),
customerId : process . env . OVERSEAS_TEST_CUSTOMER_ID ,
warehouse : { id : process . env . OVERSEAS_TEST_WAREHOUSE_ID , warehouseCode : process . env . OVERSEAS_TEST_WAREHOUSE_CODE || null },
} : await findRelabelScope ( admin . accessToken , preferredMerchant )
const { merchant , customerId , warehouse : selected } = scope
// Do not disable accounts from another acceptance run; cleanup owns only this run's account.
2026-09-15 15:15:46 +08:00
pda = await createPda ( admin . accessToken , id ( selected . id ), ( created ) => { pda = created })
const relabelReport = join ( outputDir , 'relabel.json' )
const fulfillmentReport = join ( outputDir , 'fulfillment.json' )
const childEnv = { ... process . env , ADMIN_TEST_GATEWAY : domestic , ADMIN_OVERSEAS_GATEWAY : overseas ,
ADMIN_OVERSEAS_ACCESS_TOKEN : admin . overseasAccessToken , MERCHANT_OVERSEAS_ACCESS_TOKEN : merchant . overseasAccessToken ,
PDA_OVERSEAS_ACCESS_TOKEN : pda . token , OVERSEAS_TEST_CUSTOMER_ID : customerId ,
OVERSEAS_TEST_WAREHOUSE_ID : id ( selected . id ), OVERSEAS_ACCEPTANCE_OUTPUT : relabelReport ,
OVERSEAS_ACCEPTANCE_SOURCE : relabelReport , OVERSEAS_FULFILLMENT_OUTPUT : fulfillmentReport }
2026-09-17 17:37:29 +08:00
const scripts = process . env . OVERSEAS_CLOSURE_EXTRA === 'true' ? [ 'relabel-closure-local-acceptance.mjs' ] : process . env . OVERSEAS_RESUME_FULFILLMENT === 'true'
? [ 'overseas-relabel-fulfillment-acceptance.mjs' ]
: [ 'overseas-relabel-real-acceptance.mjs' , 'overseas-relabel-fulfillment-acceptance.mjs' ]
for ( const script of scripts ) {
2026-09-15 15:15:46 +08:00
const result = spawnSync ( process . execPath , [ resolve ( 'scripts' , script )], { cwd : resolve ( '.' ), env : childEnv , stdio : 'inherit' })
if ( result . error ) throw result . error
if ( result . status !== 0 ) throw new Error ( ` ${ script } failed with exit code ${ result . status } ` )
}
Object . assign ( summary , { customerId , warehouseId : id ( selected . id ), warehouseCode : selected . warehouseCode ,
relabelReport , fulfillmentReport , passed : true , finishedAt : new Date (). toISOString () })
} finally {
if ( admin ? . accessToken && pda ? . membershipId ) {
try { await api ( domestic , admin . accessToken , 'PUT' , `/api/platform/v1/iam/users/ ${ pda . membershipId } /status` , { status : 'DISABLED' }); summary . pdaCleanup = 'DISABLED' }
catch ( error ) { summary . pdaCleanup = `FAILED: ${ error . message } ` }
}
await writeFile ( join ( outputDir , 'summary.json' ), JSON . stringify ( summary , null , 2 ), 'utf8' )
}
console . log ( JSON . stringify ( summary , null , 2 ))