import { orgContextConfig } from '../src/org-context.js'; import { deploymentOrgSettings } from './org-context-settings.mjs'; export const productionTarget = Object.freeze({ worker: 'baron-qa-gateway-prod', config: 'wrangler.production.toml', bucket: 'baron-qa-write', origin: 'https://qa.baroncs.co.kr', apiOrigin: 'https://qa.brsw.kr', projectId: '01a1c543-ab4f-744f-8eff-ab85f2644271', channelId: '01a1c544-35ac-7674-9bf0-2469bfbe3e52', authIssuer: 'https://app.brsw.kr/oidc', orgContextApiUrl: 'https://app.brsw.kr/api/v1/integrations/org-context' }); function required(env, name) { const value = String(env[name] || '').trim(); if (!value) throw new Error(name + ' is required for production'); return value; } function httpsBase(value, name) { let url; try { url = new URL(value); } catch { throw new Error(name + ' must be an HTTPS origin'); } if (url.protocol !== 'https:' || url.username || url.password || url.search || url.hash || url.pathname !== '/') { throw new Error(name + ' must be an HTTPS origin'); } return url.origin; } function httpsIssuer(value) { let url; try { url = new URL(value); } catch { throw new Error('AUTH_ISSUER must be an HTTPS issuer URL'); } if (url.protocol !== 'https:' || url.username || url.password || url.search || url.hash) { throw new Error('AUTH_ISSUER must be an HTTPS issuer URL'); } return url.origin + url.pathname.replace(/\/+$/, ''); } export function productionSettings(env) { // These names are mapped exclusively from PROD_* settings in the production workflow. required(env, 'CLOUDFLARE_API_TOKEN'); const secrets = { SESSION_SECRET: required(env, 'SESSION_SECRET'), ABC_API_KEY: required(env, 'ABC_API_KEY'), ORG_CONTEXT_KEY_ID: required(env, 'ORG_CONTEXT_KEY_ID'), ORG_CONTEXT_KEY_SECRET: required(env, 'ORG_CONTEXT_KEY_SECRET') }; if (secrets.SESSION_SECRET.length < 32) throw new Error('SESSION_SECRET must contain at least 32 characters'); if (required(env, 'ORG_CONTEXT_ENABLED') !== 'true') throw new Error('ORG_CONTEXT_ENABLED must be true for production'); const organization = deploymentOrgSettings({ ...env, ORG_CONTEXT_API_URL: env.ORG_CONTEXT_API_URL || productionTarget.orgContextApiUrl }); orgContextConfig({ ...organization.vars, ...secrets }); const issuer = httpsIssuer(env.AUTH_ISSUER || productionTarget.authIssuer); const vars = { ...organization.vars, AUTH_CLIENT_ID: required(env, 'AUTH_CLIENT_ID'), AUTH_AUTHORIZE_URL: issuer + '/oauth2/auth', AUTH_TOKEN_URL: issuer + '/oauth2/token', AUTH_USERINFO_URL: issuer + '/userinfo', ABC_API_BASE_URL: httpsBase(env.ABC_API_BASE_URL || productionTarget.apiOrigin, 'ABC_API_BASE_URL'), ABC_PROJECT_ID: String(env.ABC_PROJECT_ID || productionTarget.projectId).trim(), ABC_CHANNEL_ID: String(env.ABC_CHANNEL_ID || productionTarget.channelId).trim() }; const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; for (const name of ['ABC_PROJECT_ID', 'ABC_CHANNEL_ID']) { if (!uuid.test(vars[name])) throw new Error(name + ' must be a UUID'); } // Fail before uploads if a PROD_* variable points at staging or another channel. for (const [name, expected] of Object.entries({ ABC_API_BASE_URL: productionTarget.apiOrigin, ABC_PROJECT_ID: productionTarget.projectId, ABC_CHANNEL_ID: productionTarget.channelId })) { if (vars[name] !== expected) throw new Error(name + ' must match the production EGBIM Q&A target: ' + expected); } const revision = required(env, 'DEPLOY_COMMIT_SHA'); if (!/^[0-9a-f]{40}$/.test(revision)) throw new Error('DEPLOY_COMMIT_SHA must be a full lowercase Git commit SHA'); vars.QA_ASSET_PREFIX = 'releases/' + revision; return { ...productionTarget, vars, secrets }; }