#!/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' // 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 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') } async function merchantSessionForTenant(adminToken, tenantId) { // Tenant 0 is the platform scope, not a merchant tenant and cannot be impersonated. assert(/^\d+$/.test(id(tenantId)) && BigInt(tenantId) > 0n, `Invalid merchant tenant for impersonation: ${id(tenantId) || 'missing'}`) 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') } 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') let preferredMerchant = null try { preferredMerchant = await refreshStoredSession('cc-client-refresh-token', 'merchant') } catch { if (storedCustomerId && BigInt(storedCustomerId) > 0n) { try { preferredMerchant = await merchantSessionForTenant(admin.accessToken, storedCustomerId) } catch {} } } // 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') 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. 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 } 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) { 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))