@gfargo/doorman 3.15.2 → 3.15.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/run.js CHANGED
@@ -21,7 +21,7 @@ ${o}`}};var B=class i extends Error{constructor(t,n,o,s=[]){super(t);this.ajvErr
21
21
  Dry run - The following IP rule would be added:`)),Ji(t),r.log(""),r.log(N.default.dim(JSON.stringify(t,null,2)));return}r.start("Loading configuration...");let o=await Z(i.config,"raw"),s={...o,ips:[...o.ips||[],t]};await Y(s,i.config),r.success(N.default.green(`\u2714 IP rule "${t.ip}" added to configuration`)),Ji(t)}else{let t=i.interactive?await tr():ir(i),n=Wt.safeParse(t);if(n.success||(r.error(N.default.red("Rule validation failed:")),n.error.errors.forEach(c=>{let d=c.path.join(".")||"rule";r.error(N.default.red(` - ${d}: ${c.message}`))}),process.exit(1)),i.dryRun){r.info(N.default.cyan(`
22
22
  Dry run - The following rule would be added:`)),Yi(t),r.log(""),r.log(N.default.dim(JSON.stringify(t,null,2)));return}r.start("Loading configuration...");let o=await Z(i.config,"raw"),s=o.rules||[],a=rr({...o,rules:s},t);if(a){if(r.warn(N.default.yellow(`\u26A0\uFE0F ${a}`)),i.ci||!process.stdin.isTTY){r.info(N.default.dim("Skipping \u2014 non-interactive and a conflicting rule already exists. Rule not added."));return}if(!await C("Proceed anyway?",{type:"confirm",initial:!1})){r.info("Cancelled.");return}}let l={...o,rules:[...s,t]};await Y(l,i.config),r.success(N.default.green(`\u2714 Rule "${t.name}" added to configuration`)),Yi(t)}r.log(""),r.log(N.default.dim(`Run ${N.default.cyan("doorman sync")} to deploy this rule.`))}catch(e){re(e,"adding rule")}};var fi={};oe(fi,{builder:()=>_s,command:()=>$s,desc:()=>Ps,handler:()=>ks});var ne=O(require("chalk")),Te=require("fs"),at=require("path"),ro=require("consola");_();$e();var ut=require("fs"),Xt=require("path");function en(i=process.cwd()){return(0,ut.existsSync)((0,Xt.join)(i,".git"))}function tn(i,e=process.cwd()){let t=(0,Xt.join)(e,".gitignore");if(!(0,ut.existsSync)(t))return!1;let n=i.replace(/^\.\//,"").replace(/^\//,"").replace(/\/$/,""),o;try{o=(0,ut.readFileSync)(t,"utf8").split(`
23
23
  `)}catch{return!1}return o.some(s=>{let a=s.trim();if(!a||a.startsWith("#"))return!1;let l=a.replace(/^\//,"").replace(/\/$/,"");return l===n||n.startsWith(`${l}/`)})}var se={type:"string",choices:H,description:"Firewall provider (auto-detected)"};var oo=require("consola");_();var io=O(require("chalk"));_();$e();var nn=require("readline");function ar(i,e){return e?.ctrl&&e.name==="c"?{type:"cancel"}:e?.name==="return"||e?.name==="enter"?{type:"submit"}:e?.name==="backspace"?{type:"backspace"}:i&&!e?.ctrl&&!e?.meta&&i.length===1&&i>=" "?{type:"append",char:i}:{type:"ignore"}}function lr(i,e){switch(e.type){case"append":return{value:i+e.char,done:!1,cancelled:!1,echo:"add"};case"backspace":return i.length===0?{value:i,done:!1,cancelled:!1,echo:"none"}:{value:i.slice(0,-1),done:!1,cancelled:!1,echo:"remove"};case"submit":return{value:i,done:!0,cancelled:!1,echo:"none"};case"cancel":return{value:i,done:!0,cancelled:!0,echo:"none"};case"ignore":return{value:i,done:!1,cancelled:!1,echo:"none"}}}async function on(i){if(process.stdout.write(i),!process.stdin.isTTY){let{createInterface:e}=await import("readline"),t=e({input:process.stdin,output:process.stdout,terminal:!1});return new Promise(n=>{t.question("",o=>{t.close(),n(o)})})}return new Promise(e=>{let t=process.stdin,n=t.isRaw;(0,nn.emitKeypressEvents)(t),t.setRawMode(!0),t.resume();let o="",s=()=>{t.removeListener("keypress",a),t.setRawMode(n??!1)},a=(l,c)=>{let d=ar(l,c),u=lr(o,d);o=u.value,u.echo==="add"?process.stdout.write("*"):u.echo==="remove"&&process.stdout.write("\b \b"),u.done&&(s(),process.stdout.write(`
24
- `),u.cancelled&&process.exit(0),e(o))};t.on("keypress",a)})}_();var rn={provider:"vercel",fields:[{key:"token",envVar:"VERCEL_TOKEN",label:"Vercel API Token",required:!0,secret:!0,promptMessage:"What is your Vercel API Auth Token? (https://vercel.com/guides/how-do-i-use-a-vercel-api-access-token#creating-an-access-token) "},{key:"projectId",envVar:"VERCEL_PROJECT_ID",label:"Vercel Project ID",required:!0,configKey:"projectId",cliAlias:"p",legacyConfigKey:"projectId",promptMessage:"What is your Vercel Project ID? (See https://vercel.com/docs/projects/project-configuration/general-settings#project-id)"},{key:"teamId",envVar:"VERCEL_TEAM_ID",label:"Vercel Team ID",required:!1,configKey:"teamId",cliAlias:"t",legacyConfigKey:"teamId",promptMessage:"What is your Vercel Team ID? Leave blank to use your Vercel default team (every account, including Hobby, is a team). Only needed if you belong to more than one team and want to target a non-default one. (See https://vercel.com/docs/accounts#find-your-team-id)"}]};var sn={provider:"cloudflare",fields:[{key:"apiToken",envVar:"CLOUDFLARE_API_TOKEN",label:"Cloudflare API Token",required:!0,secret:!0},{key:"zoneId",envVar:"CLOUDFLARE_ZONE_ID",label:"Cloudflare Zone ID",required:!0,configKey:"zoneId"},{key:"accountId",envVar:"CLOUDFLARE_ACCOUNT_ID",label:"Cloudflare Account ID",required:!1,configKey:"accountId"}]};var an={provider:"fastly",fields:[{key:"apiToken",envVar:"FASTLY_API_TOKEN",label:"Fastly API Token",required:!0,secret:!0},{key:"workspaceId",envVar:"FASTLY_WORKSPACE_ID",label:"Fastly Next-Gen WAF Workspace ID",required:!0,configKey:"workspaceId"}]};var ln={provider:"gcp",fields:[{key:"serviceAccountKeyPath",envVar:"GOOGLE_APPLICATION_CREDENTIALS",label:"GCP Service Account Key Path",required:!1,promptMessage:"Path to a GCP service account JSON key file (leave blank to use Application Default Credentials \u2014 gcloud auth application-default login, or the GCE/Cloud Run metadata server): "},{key:"projectId",envVar:"GOOGLE_CLOUD_PROJECT",label:"GCP Project ID",required:!0,configKey:"projectId"},{key:"policyName",envVar:"GCP_POLICY_NAME",label:"Cloud Armor Security Policy Name",required:!0,configKey:"policyName"}]};function cn(i,e={}){let{explicit:t={},config:n={},env:o=process.env}=e,s={};for(let a of i.fields)s[a.key]=t[a.key]||n[a.key]||o[a.envVar];return s}function dn(i,e){return i.fields.filter(t=>t.required&&!e[t.key])}var He={vercel:rn,cloudflare:sn,fastly:an,gcp:ln};var St=class{static detectionField(e){return He[e].fields.find(t=>t.required&&t.configKey)}static detect(e){let t=[];if(e&&"provider"in e){let o=e.provider;if(typeof o=="string"&&this.isValidProvider(o))return t.push(`Explicit provider specified in config: ${e.provider}`),{provider:o,confidence:"high",reasons:t};r.warn(`Invalid provider specified in config: ${String(e.provider)}`)}if(e&&"providers"in e&&typeof e.providers=="object"&&e.providers!==null){let o=e.providers;for(let s of H){let a=this.detectionField(s);if(!a?.configKey)continue;if(typeof(o[s]||{})[a.configKey]=="string")return t.push(`${a.label} found in config`),{provider:s,confidence:"high",reasons:t}}}if(e&&"projectId"in e&&typeof e.projectId=="string")return t.push("Legacy Vercel project ID found in config"),{provider:"vercel",confidence:"high",reasons:t};let n=this.detectFromEnvironment();return n.provider?n:(r.debug("Unable to auto-detect provider"),{provider:null,confidence:"low",reasons:["No provider information found in config or environment"]})}static detectFromEnvironment(){let e=[],t=process.env.DOORMAN_PROVIDER;if(t&&this.isValidProvider(t))return e.push(`Provider set via DOORMAN_PROVIDER environment variable: ${t}`),{provider:t,confidence:"high",reasons:e};for(let n of H){let o=this.detectionField(n);if(!(!o||!process.env[o.envVar]))return e.push(`${o.label} found in environment (${o.envVar})`),{provider:n,confidence:"medium",reasons:e}}return{provider:null,confidence:"low",reasons:["No provider credentials found in environment"]}}static isValidProvider(e){return H.includes(e)}static getProvider(e,t="vercel"){let n=this.detect(e);return n.provider?(r.debug(`Provider detected: ${n.provider} (${n.confidence} confidence)`),n.reasons.length>0&&r.debug(`Detection reasons: ${n.reasons.join(", ")}`),n.provider):(r.debug(`No provider detected, using fallback: ${t}`),t)}static detectAll(e){let t=new Set;if(e&&"provider"in e){let n=e.provider;typeof n=="string"&&this.isValidProvider(n)&&t.add(n)}if(e&&"providers"in e&&typeof e.providers=="object"&&e.providers!==null){let n=e.providers;for(let o of H){let s=this.detectionField(o);if(!s?.configKey)continue;typeof(n[o]||{})[s.configKey]=="string"&&t.add(o)}}e&&"projectId"in e&&typeof e.projectId=="string"&&t.add("vercel");for(let n of H){let o=this.detectionField(n);o&&process.env[o.envVar]&&t.add(n)}return Array.from(t)}};var ei=O(require("chalk"));_();$e();var Ut=i=>({$schema:Di,firewallEnabled:!0,rules:[],ips:[],...i});_();var _e=class{baseUrl;providerName;rateLimitInfo={};constructor(e,t){this.baseUrl=e,this.providerName=t}async makeRequest(e,t={}){let n=`${this.baseUrl}${e}`,{timeout:o=3e4,retries:s=3,retryDelay:a=1e3,...l}=t,c=null,d=null,u=null,p=()=>{u&&(clearTimeout(u),u=null),d&&(d.abort(),d=null)};try{for(let f=0;f<=s;f++)try{d=new AbortController,u=setTimeout(()=>{d&&d.abort()},o);let g=await fetch(n,{...l,headers:{"Content-Type":"application/json",...await this.getAuthHeaders(),...l.headers},signal:d.signal});if(u&&(clearTimeout(u),u=null),this.updateRateLimitInfo(g),g.status===429){let v=this.calculateRateLimitWait(f,g);if(f===s)throw new Error(`${this.providerName} API rate limit exceeded after ${s+1} attempt(s) (429 Too Many Requests). Retry after ~${Math.round(v/1e3)}s.`);r.warn(`Rate limit exceeded for ${this.providerName}. Waiting ${v}ms before retry (attempt ${f+1}/${s+1})...`),await this.delay(v);continue}if(!g.ok)throw await this.handleErrorResponse(g);let m;try{let v=await g.text();v.trim()?m=JSON.parse(v):m={}}catch(v){throw r.error(`Failed to parse response from ${n}:`,v),new Error(`Invalid JSON response from ${this.providerName} API: ${v instanceof Error?v.message:String(v)}`)}return this.isLargeResponse(m)&&(r.debug(`Large response detected from ${n}, validating structure...`),this.validateLargeResponse(m)),m}catch(g){if(c=g,u&&(clearTimeout(u),u=null),this.isNonRetryableError(g)||f===s)throw g;let m=a*Math.pow(2,f),v=Math.random()*.1*m,z=Math.min(m+v,3e4);r.debug(`Request failed (attempt ${f+1}/${s+1}). Retrying in ${Math.round(z)}ms...`),r.debug(`Error details: ${g instanceof Error?g.message:String(g)}`),await this.delay(z)}throw c||new Error("Request failed after all retries")}finally{p()}}async get(e,t){return this.makeRequest(e,{...t,method:"GET"})}async post(e,t,n){return this.makeRequest(e,{...n,method:"POST",body:t?JSON.stringify(t):void 0})}async put(e,t,n){return this.makeRequest(e,{...n,method:"PUT",body:t?JSON.stringify(t):void 0})}async patch(e,t,n){return this.makeRequest(e,{...n,method:"PATCH",body:t?JSON.stringify(t):void 0})}async delete(e,t){return this.makeRequest(e,{...t,method:"DELETE"})}updateRateLimitInfo(e){let t=e.headers.get("X-RateLimit-Limit"),n=e.headers.get("X-RateLimit-Remaining"),o=e.headers.get("X-RateLimit-Reset");t&&(this.rateLimitInfo.limit=parseInt(t,10)),n&&(this.rateLimitInfo.remaining=parseInt(n,10)),o&&(this.rateLimitInfo.reset=parseInt(o,10)),this.rateLimitInfo.remaining&&this.rateLimitInfo.remaining<10&&r.warn(`Approaching rate limit for ${this.providerName}: ${this.rateLimitInfo.remaining} requests remaining`)}calculateRateLimitWait(e=0,t){let o=t?this.parseRetryAfter(t.headers.get("Retry-After")):null;if(o!==null)return Math.min(Math.max(o,1e3),6e4);if(this.rateLimitInfo.reset){let l=Math.floor(Date.now()/1e3),c=(this.rateLimitInfo.reset-l)*1e3;return Math.min(Math.max(c,1e3),6e4)}let a=5e3*Math.pow(2,e);return Math.min(a,6e4)}parseRetryAfter(e){if(!e)return null;let t=Number(e);if(Number.isFinite(t))return t*1e3;let n=Date.parse(e);return Number.isNaN(n)?null:n-Date.now()}async handleErrorResponse(e){let t=`${this.providerName} API error: ${e.status} ${e.statusText}`,n=t;try{let o=await e.json();if(typeof o=="object"&&o!==null){let s=o;if(typeof s.error=="string")n=`${t} - ${s.error}`;else if(typeof s.message=="string")n=`${t} - ${s.message}`;else if(Array.isArray(s.errors)){let a=s.errors.map(l=>typeof l=="object"&&l!==null&&"message"in l?String(l.message):JSON.stringify(l)).join(", ");n=`${t} - ${a}`}}}catch{}return r.error(n),new Error(n)}isNonRetryableError(e){if(typeof e=="object"&&e!==null&&e.name==="AbortError")return!0;let t=e.message;return typeof t=="string"&&t.includes("API error: 4")?!t.includes("429"):!1}delay(e){return new Promise(t=>setTimeout(t,e))}getRateLimitInfo(){return{...this.rateLimitInfo}}isLargeResponse(e){if(!e||typeof e!="object")return!1;let t=(n,o=0)=>{if(o>10)return!1;for(let[,s]of Object.entries(n))if(Array.isArray(s)&&s.length>1e3||typeof s=="object"&&s!==null&&t(s,o+1))return!0;return!1};return t(e)}validateLargeResponse(e){if(!e||typeof e!="object")return;let t=(o,s)=>{o.length>5e3&&r.warn(`Large array detected at ${s} with ${o.length} items. This may impact performance.`);let a=Math.min(10,o.length),l=o[0];if(l&&typeof l=="object"){let c=Object.keys(l);for(let d=1;d<a;d++){let u=o[d];if(!u||typeof u!="object"){r.warn(`Inconsistent array structure at ${s}[${d}]: expected object, got ${typeof u}`);continue}let p=Object.keys(u),f=c.filter(g=>!p.includes(g));f.length>0&&r.warn(`Inconsistent array structure at ${s}[${d}]: missing keys ${f.join(", ")}`)}}},n=(o,s="root",a=0)=>{if(!(a>10))for(let[l,c]of Object.entries(o)){let d=`${s}.${l}`;Array.isArray(c)?t(c,d):typeof c=="object"&&c!==null&&n(c,d,a+1)}};n(e)}};var un=process.env.DOORMAN_VERCEL_API_BASE_URL||"https://api.vercel.com/v1/security/firewall/config",Ze=class extends _e{constructor(t,n,o){super("","vercel");this.projectId=t;this.teamId=n;this.token=o}projectId;teamId;token;async getAuthHeaders(){return{Authorization:`Bearer ${this.token}`}}getUrl(t){let n=t!==void 0?`${un}/${t}`:un;r.debug("API URL:",n);let o=this.teamId?`&teamId=${this.teamId}`:"";return`${n}?projectId=${this.projectId}${o}`}async fetchFirewallConfig(t,n){let{allowCreate:o=!0}=n??{},s=await this.get(this.getUrl(t));if(r.debug("Config Version:",t??"latest"),r.debug("Fetched Config:",t?s:s.active),t)return s;if(!s||"active"in s&&s.active===null){if(r.warn(ei.default.bold("No firewall configuration found.")),!o)return r.debug("Skipping create-configuration prompt for read-only fetch."),this.emptyConfigPlaceholder();if(await C("Would you like to create one?",{type:"confirm"})){r.debug("Creating new empty firewall configuration...");let l=await this.putEmptyConfig();return r.debug("Empty firewall configuration created successfully"),r.debug(`New configuration version: ${ei.default.yellow(l.version)}`),l}else return s.active}return s.active}emptyConfigPlaceholder(){return{version:0,id:"",firewallEnabled:!0,crs:null,rules:[],ips:[],projectKey:"",ownerId:"",updatedAt:new Date(0).toISOString()}}async putEmptyConfig(){let{$schema:t,...n}=Ut();return r.debug("Empty Config:",n),this.putConfig(n)}async putConfig(t){return(await this.put(this.getUrl(),t)).active}async fetchActiveFirewallRules(){return(await this.fetchFirewallConfig())?.rules}async updateFirewallRule(t){let n=!t.id||t.id==="-",o={action:n?"rules.insert":"rules.update",id:n?null:t.id,value:{name:t.name,description:t.description,action:t.action,conditionGroup:t.conditionGroup,active:t.active}};return this.patch(this.getUrl(),o,n?{retries:0}:void 0)}async createFirewallRule(t){return this.updateFirewallRule({...t,id:"-"})}async deleteFirewallRule(t){let n={action:"rules.remove",id:t.id,value:null};await this.patch(this.getUrl(),n)}async updateIPBlockingRule(t){let n=!t.id||t.id==="-",o={action:n?"ip.insert":"ip.update",id:n?null:t.id,value:{action:t.action,hostname:t.hostname,ip:t.ip,notes:t.notes??null}};return this.patch(this.getUrl(),o,n?{retries:0}:void 0)}async createIPBlockingRule(t){return this.updateIPBlockingRule({...t,id:"-"})}async deleteIPBlockingRule(t){let n={action:"ip.remove",id:t.id,value:null};await this.patch(this.getUrl(),n)}async verifyCredentials(){try{return await this.fetchFirewallConfig(),!0}catch(t){return r.debug("Credential verification failed:",t),!1}}};var fe=O(require("chalk"));_();_();var ke=class{validateConfig(e){let t=[],n=[];if(e||t.push({path:"root",message:"Configuration is required",code:"CONFIG_REQUIRED"}),e&&!e.rules&&t.push({path:"rules",message:"Rules array is required",code:"RULES_REQUIRED"}),e&&e.rules&&!Array.isArray(e.rules)&&t.push({path:"rules",message:"Rules must be an array",code:"RULES_INVALID_TYPE"}),e&&Array.isArray(e.rules)){let o=new Map;e.rules.forEach((s,a)=>{let l=s.id||s.name;if(!l)return;let c=o.get(l);if(c===void 0){o.set(l,a);return}let d=e.rules[c];n.push({path:`rules[${a}]`,message:s.id?`Rule "${s.name}" has the same id ("${s.id}") as rules[${c}] ("${d?.name}") \u2014 they will collide during sync diffing and one may be silently skipped.`:`Rule "${s.name}" has the same name as rules[${c}] and neither has an explicit id \u2014 they will collide during sync diffing and one may be silently skipped. Add unique ids to disambiguate.`,code:"DUPLICATE_RULE_IDENTIFIER"})})}return{valid:t.length===0,errors:t,warnings:n}}getHealthScore(e){let t=[],n=100;if(!e)return{score:0,grade:"poor",issues:[{severity:"error",category:"configuration",message:"No configuration found"}],recommendations:["Create a configuration file"]};if((!e.rules||e.rules.length===0)&&(n-=20,t.push({severity:"warning",category:"rules",message:"No rules defined",suggestion:"Add security rules to protect your application"})),e.rules&&e.rules.length>0){let a=e.rules.filter(l=>!l.description);a.length>0&&(n-=5,t.push({severity:"info",category:"maintainability",message:`${a.length} rule(s) missing descriptions`,suggestion:"Add descriptions to improve maintainability"}))}if(e.rules&&e.rules.length>0){let a=e.rules.filter(l=>l.active===!1||l.enabled===!1);a.length>0&&(n-=5,t.push({severity:"info",category:"maintenance",message:`${a.length} disabled rule(s) found`,suggestion:"Review and remove unused rules"}))}let o=this.calculateGrade(n),s=this.generateRecommendations(e,t);return{score:Math.max(0,n),grade:o,issues:t,recommendations:s}}diffItems(e,t,n,o="id"){let s=[],a=[],l=[],c=d=>d[o];for(let d of e){let u=t.find(p=>c(p)===c(d));u?n(d,u)||a.push(d):s.push(d)}for(let d of t)e.find(p=>c(p)===c(d))||l.push(d);return{toAdd:s,toUpdate:a,toDelete:l}}assertManagedRulesSupported(e){if(!(!e.managedRules||e.managedRules.length===0)&&!this.getSupportedFeatures().supportsManagedRules)throw new Error(`${this.name} does not support managed rule groups, but this config declares ${e.managedRules.length}. Remove \`managedRules\` from the config, or switch to a provider that supports them (currently Cloudflare only).`)}validateRuleCount(e,t){if(t&&e>t)throw new Error(`Rule count (${e}) exceeds provider limit (${t})`);t&&e>t*.9&&r.warn(`Approaching rule limit: ${e}/${t} rules`)}updateMetadata(e,t){return{...e,metadata:{...e.metadata,version:t,updatedAt:new Date().toISOString()}}}calculateGrade(e){return e>=80?"excellent":e>=60?"good":e>=40?"fair":"poor"}generateRecommendations(e,t){let n=[];return t.some(o=>o.category==="rules")&&n.push("Consider using predefined templates to get started"),t.some(o=>o.category==="maintainability")&&n.push("Add descriptions to all rules for better documentation"),t.some(o=>o.category==="maintenance")&&n.push("Remove or enable disabled rules to keep configuration clean"),n}logSyncStats(e){r.info(`Sync complete for ${this.name}:`),r.info(` Rules added: ${e.rulesAdded}`),r.info(` Rules updated: ${e.rulesUpdated}`),r.info(` Rules deleted: ${e.rulesDeleted}`),e.ipsAdded!==void 0&&(r.info(` IPs added: ${e.ipsAdded}`),r.info(` IPs updated: ${e.ipsUpdated}`),r.info(` IPs deleted: ${e.ipsDeleted}`)),e.managedRulesAdded!==void 0&&(r.info(` Managed rule groups added: ${e.managedRulesAdded}`),r.info(` Managed rule groups updated: ${e.managedRulesUpdated}`),r.info(` Managed rule groups deleted: ${e.managedRulesDeleted}`)),e.version&&r.info(` New version: ${e.version}`)}};pe();function pn(i){let e=[],t=[];i.conditionGroup.forEach((s,a)=>{for(let l of s.conditions){let c=dr(l.op);t.push({field:pr(l.type),operator:c,...l.value!==void 0?{value:l.value}:{},...l.neg?{negated:!0}:{},...l.key?{key:l.key}:{},group:a})}}),t.length>10&&e.push(U.createWarning("many_conditions",i.id,void 0,`Rule has ${t.length} conditions which may impact performance`,"Consider splitting complex rules into multiple simpler rules for better performance"));let n={type:i.action.mitigate.action,...i.action.mitigate.rateLimit?{rateLimit:{requests:i.action.mitigate.rateLimit.requests,window:i.action.mitigate.rateLimit.window,...i.action.mitigate.rateLimit.characteristics?{characteristics:i.action.mitigate.rateLimit.characteristics}:{},...i.action.mitigate.rateLimit.mitigationTimeout?{mitigationTimeout:i.action.mitigate.rateLimit.mitigationTimeout}:{},...i.action.mitigate.rateLimit.countingExpression?{countingExpression:i.action.mitigate.rateLimit.countingExpression}:{}}}:{},...i.action.mitigate.redirect?{redirect:{location:i.action.mitigate.redirect.location,...i.action.mitigate.redirect.permanent?{permanent:i.action.mitigate.redirect.permanent}:{}}}:{},...i.action.mitigate.actionDuration?{duration:i.action.mitigate.actionDuration}:{}};return{result:{id:i.id,name:i.name,...i.description?{description:i.description}:{},enabled:i.active,conditions:t,conditionLogic:i.conditionGroup.length>1?"OR":"AND",action:n},warnings:e}}function fn(i){let e=[],t=cr(i.conditions,i.id,e);if(i.action.response&&e.push(U.createUnsupportedFeatureWarning("custom response body","unified config","Vercel",i.id,"action.response")),t.length===0)throw new Error(`Rule "${i.name}" has no conditions Vercel can represent \u2014 every condition field is unsupported by Vercel (e.g. Cloudflare-only fields like referer/port). Cannot sync this rule to Vercel.`);return{result:{id:i.id,name:i.name,description:i.description,conditionGroup:t,action:{mitigate:{action:i.action.type,rateLimit:i.action.rateLimit?{requests:i.action.rateLimit.requests,window:i.action.rateLimit.window,characteristics:i.action.rateLimit.characteristics,mitigationTimeout:i.action.rateLimit.mitigationTimeout,countingExpression:i.action.rateLimit.countingExpression}:null,redirect:i.action.redirect?{location:i.action.redirect.location,permanent:i.action.redirect.permanent}:null,actionDuration:i.action.duration||null}},active:i.enabled},warnings:e}}function gn(i){return{id:i.id,ip:i.ip,...i.hostname?{hostname:i.hostname}:{},...i.notes?{notes:i.notes}:{},action:i.action}}function cr(i,e,t){let n=new Map;for(let s of i){let a=s.group??0,l=n.get(a);l?l.push(s):n.set(a,[s])}let o=[];for(let s of n.values()){let a=[];for(let l of s){let c=gr(l.field);if(!c){t.push(U.createUnsupportedFeatureWarning(`condition field '${l.field}'`,"unified config","Vercel",e,l.field));continue}a.push({op:ur(l.operator),neg:l.negated,type:c,key:l.key,value:l.value})}a.length>0&&o.push({conditions:a})}return o}function dr(i){return{eq:"eq",pre:"starts_with",suf:"ends_with",inc:"in",sub:"contains",re:"matches",ex:"exists",nex:"not_exists"}[i]||"eq"}function ur(i){return{eq:"eq",starts_with:"pre",ends_with:"suf",in:"inc",contains:"sub",matches:"re",exists:"ex",not_exists:"nex"}[i]||"eq"}function pr(i){return{host:"host",path:"path",method:"method",header:"header",query:"query",cookie:"cookie",ip_address:"ip",user_agent:"user_agent",geo_country:"country",geo_city:"city",geo_as_number:"asn",scheme:"scheme",region:"vercel_region",geo_country_region:"region"}[i]||i}var fr={host:"host",path:"path",method:"method",header:"header",query:"query",cookie:"cookie",ip:"ip_address",user_agent:"user_agent",country:"geo_country",city:"geo_city",asn:"geo_as_number",scheme:"scheme",target_path:"target_path",vercel_region:"region",region:"geo_country_region",protocol:"protocol",environment:"environment",geo_continent:"geo_continent",ja4_digest:"ja4_digest",ja3_digest:"ja3_digest",rate_limit_api_id:"rate_limit_api_id"};function gr(i){return fr[i]??null}pe();_();function Ye(i){return i.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}function mn(i){let e="";for(let t=0;t<i.length;t++)i[t]==="\\"&&(i[t+1]==="\\"||i[t+1]==='"')?(e+=i[t+1],t++):e+=i[t];return e}var pt=class{static vercelToCloudflare={host:"http.host",path:"http.request.uri.path",method:"http.request.method",header:"http.request.headers",query:"http.request.uri.query",cookie:"http.cookie",target_path:"http.request.uri.path",ip_address:"ip.src",region:"ip.geoip.subdivision_1",protocol:"ssl",scheme:"ssl",environment:"",user_agent:"http.user_agent",geo_continent:"ip.geoip.continent",geo_country:"ip.geoip.country",geo_country_region:"ip.geoip.subdivision_1",geo_city:"ip.geoip.city",geo_as_number:"ip.geoip.asnum",ja4_digest:"",ja3_digest:"",rate_limit_api_id:""};static cloudflareToVercel={"http.host":"host","http.request.uri.path":"path","http.request.method":"method","http.request.headers":"header","http.request.uri.query":"query","http.cookie":"cookie","ip.src":"ip_address","ip.geoip.subdivision_1":"region",ssl:"protocol","http.user_agent":"user_agent","ip.geoip.continent":"geo_continent","ip.geoip.country":"geo_country","ip.geoip.city":"geo_city","ip.geoip.asnum":"geo_as_number"};static toCloudflare(e,t){let n=this.vercelToCloudflare[e];if(!n)throw r.warn(`No Cloudflare mapping for Vercel type: ${e}`),new Error(`Unsupported Vercel rule type for Cloudflare: ${e}`);return e==="header"&&t?`${n}["${Ye(t.toLowerCase())}"]`:e==="cookie"&&t?`${n}["${Ye(t)}"]`:n}static toVercel(e){let t=e.match(/http\.request\.headers\["([^"]+)"\]/);if(t)return{type:"header",key:t[1]};let n=e.match(/http\.cookie\["([^"]+)"\]/);if(n)return{type:"cookie",key:n[1]};if(e==="http.referer")return{type:"header",key:"referer"};let o=this.cloudflareToVercel[e];if(o)return{type:o};throw r.warn(`No Vercel mapping for Cloudflare field: ${e}`),new Error(`Unsupported Cloudflare field for Vercel: ${e}`)}static isCloudflareSupported(e){let t=this.vercelToCloudflare[e];return!!t&&t!==""}static isVercelSupported(e){return!!(this.cloudflareToVercel[e]||e==="http.referer"||e.startsWith("http.request.headers[")||e.startsWith("http.cookie["))}static getSupportedCloudflareFields(){return Object.values(this.vercelToCloudflare).filter(e=>e!=="")}static getSupportedVercelTypes(){return Object.keys(this.vercelToCloudflare).filter(e=>this.isCloudflareSupported(e))}};var mr=new Set(["ip.src"]),hr="http.request.uri.args",yr="http.request.headers",Rr="http.request.cookies",Je=class{static fromVercelConditionGroups(e){if(!e||e.length===0)throw new Error("At least one condition group is required");let t=e.map(n=>{let o=n.conditions.map(s=>this.fromVercelCondition(s));if(o.length===0)throw new Error("Condition group must have at least one condition");return o.length>1?`(${o.join(" and ")})`:o[0]});return t.length>1?t.join(" or "):t[0]}static fromVercelCondition(e){let t=pt.toCloudflare(e.type,e.key),n=this.buildExistsOrComparisonExpression(t,e.op,()=>`${this.mapVercelOperator(e.op)} ${this.formatValue(t,e.value)}`);return e.neg&&(n=`not (${n})`),n}static fromUnifiedConditions(e,t="AND"){if(!e||e.length===0)throw new Error("At least one condition is required");if(!e.some(a=>a.group!==void 0)){let a=e.map(c=>this.fromUnifiedCondition(c)),l=t==="AND"?" and ":" or ";return a.length>1?`(${a.join(l)})`:a[0]}let o=new Map;for(let a of e){let l=a.group??0,c=o.get(l);c?c.push(a):o.set(l,[a])}let s=Array.from(o.values()).map(a=>this.combineWithAnd(a.map(l=>this.fromUnifiedCondition(l))));return s.length>1?this.combineWithOr(s):s[0]}static fromUnifiedCondition(e){if(e.key&&e.field==="query")return this.buildKeyedMapExpression(hr,e.key,e);if(e.field==="header"){if(!e.key)throw new Error('A "header" condition requires a key naming the header');return this.buildKeyedMapExpression(yr,e.key.toLowerCase(),e)}if(e.key&&e.field==="cookie")return this.buildKeyedMapExpression(Rr,e.key,e);let t=this.mapUnifiedFieldToCloudflare(e.field);if(t===null)throw new Error(`Unsupported condition field '${e.field}' for Cloudflare \u2014 filter it out with a warning before calling fromUnifiedCondition (see unifiedToCloudflare).`);let n=this.buildUnifiedExpression(t,e.operator,e.value);return e.negated&&(n=`not (${n})`),n}static buildKeyedMapExpression(e,t,n){let o=Ye(t),s=`${e}["${o}"]`,a;if(n.operator==="exists")a=`has_key(${e}, "${o}")`;else if(n.operator==="not_exists")a=`not (has_key(${e}, "${o}"))`;else if(n.operator==="not_contains")a=`not (any(${s}[*] contains ${this.formatValue(e,n.value)}))`;else if(n.operator==="not_in")a=`not (any(${s}[*] in ${this.formatValue(e,n.value)}))`;else{let l=this.mapUnifiedOperator(n.operator);a=`any(${s}[*] ${l} ${this.formatValue(e,n.value)})`}return n.negated?`not (${a})`:a}static buildExistsOrComparisonExpression(e,t,n){return t==="ex"||t==="exists"?`${e} exists`:t==="nex"||t==="not_exists"?`not (${e} exists)`:`${e} ${n()}`}static buildUnifiedExpression(e,t,n){return t==="not_contains"?`not (${e} contains ${this.formatValue(e,n)})`:t==="not_in"?`not (${e} in ${this.formatValue(e,n)})`:this.buildExistsOrComparisonExpression(e,t,()=>`${this.mapUnifiedOperator(t)} ${this.formatValue(e,n)}`)}static mapVercelOperator(e){return{eq:"eq",pre:"starts_with",suf:"ends_with",inc:"in",sub:"contains",re:"matches",ex:"exists"}[e]||e}static mapUnifiedOperator(e){return{eq:"eq",ne:"ne",contains:"contains",starts_with:"starts_with",ends_with:"ends_with",matches:"matches",in:"in",gt:"gt",ge:"ge",lt:"lt",le:"le",exists:"exists"}[e]||e}static mapUnifiedFieldToCloudflare(e){return{ip:"ip.src",country:"ip.geoip.country",region:"ip.geoip.subdivision_1",city:"ip.geoip.city",asn:"ip.geoip.asnum",path:"http.request.uri.path",host:"http.host",method:"http.request.method",header:"http.request.headers",query:"http.request.uri.query",cookie:"http.cookie",user_agent:"http.user_agent",referer:"http.referer",scheme:"ssl",port:"cf.edge.server_port",threat_score:"cf.threat_score"}[e]??null}static isFieldSupported(e){return this.mapUnifiedFieldToCloudflare(e)!==null}static formatValue(e,t){return Array.isArray(t)?`{${t.map(o=>this.formatSingleValue(e,o)).join(" ")}}`:this.formatSingleValue(e,t)}static formatSingleValue(e,t){if(typeof t=="string"){if(mr.has(e)){if(!Pe.safeParse(t).success)throw new Error(`Invalid IP address or CIDR range: ${t}`);return t}return`"${Ye(t)}"`}return typeof t=="number"?String(t):typeof t=="boolean"?t?"true":"false":String(t)}static validate(e){if(!e||e.trim().length===0)return!1;let t=0;for(let n of e)if(n==="("&&t++,n===")"&&t--,t<0)return!1;return t===0}static combineWithAnd(e){if(e.length===0)throw new Error("At least one expression is required");return e.length===1?e[0]:`(${e.join(" and ")})`}static combineWithOr(e){if(e.length===0)throw new Error("At least one expression is required");return e.length===1?e[0]:`(${e.join(" or ")})`}};var j=class extends Error{},Qe=class{constructor(e){this.tokens=e}tokens;pos=0;peek(){return this.tokens[this.pos]}next(){let e=this.tokens[this.pos];if(!e)throw new j("Unexpected end of expression");return this.pos++,e}expect(e){let t=this.next();if(t.type!==e)throw new j(`Expected ${e} but got '${t.value}'`);return t}is(e,t){let n=this.peek();return!!n&&n.type===e&&n.value===t}atEnd(){return this.pos>=this.tokens.length}};function Lt(i,e,t,n){let o=[];i.forEach((l,c)=>{if(e(l)){let d=n(l,c);d&&o.push(d);return}if(t(l))for(let d of l.children){if(!e(d))return;let u=n(d,c);u&&o.push(u)}});let s=i.length;return new Set(o.map(l=>l.group)).size!==s?null:{conditions:o,conditionLogic:"OR"}}var wr={"ip.src":"ip","ip.geoip.country":"country","ip.geoip.subdivision_1":"region","ip.geoip.city":"city","ip.geoip.asnum":"asn","http.request.uri.path":"path","http.host":"host","http.request.method":"method","http.request.headers":"header","http.request.uri.query":"query","http.request.uri.args":"query","http.cookie":"cookie","http.request.cookies":"cookie","http.user_agent":"user_agent","http.referer":"referer",ssl:"scheme","cf.edge.server_port":"port","cf.threat_score":"threat_score"},wn=new Set(["eq","ne","contains","starts_with","ends_with","matches","in","gt","ge","lt","le"]),Cr=new Set(["and","or","not"]);function br(i){let e=[],t=0,n=i.length;for(;t<n;){let o=i[t];if(/\s/.test(o)){t++;continue}if(o===","){t++;continue}if(o==="("){e.push({type:"LPAREN",value:o}),t++;continue}if(o===")"){e.push({type:"RPAREN",value:o}),t++;continue}if(o==="{"){e.push({type:"LBRACE",value:o}),t++;continue}if(o==="}"){e.push({type:"RBRACE",value:o}),t++;continue}if(o==="["){e.push({type:"LBRACKET",value:o}),t++;continue}if(o==="]"){e.push({type:"RBRACKET",value:o}),t++;continue}if(o==='"'){let a=t+1,l="",c=!1;for(;a<n;){let d=i[a];if(d==="\\"&&a+1<n){l+=d+i[a+1],a+=2;continue}if(i[a]==='"'){c=!0,a++;break}l+=i[a],a++}if(!c)throw new j("Unterminated string literal");e.push({type:"STRING",value:mn(l)}),t=a;continue}let s=t;for(;s<n&&!/[\s(){}[\]",]/.test(i[s]);)s++;if(s===t)throw new j(`Unexpected character '${o}' at position ${t}`);e.push({type:"WORD",value:i.slice(t,s)}),t=s}return e}function ni(i){let e=[hn(i)];for(;i.is("WORD","or");)i.next(),e.push(hn(i));return e.length===1?e[0]:{type:"or",children:e}}function hn(i){let e=[yn(i)];for(;i.is("WORD","and");)i.next(),e.push(yn(i));return e.length===1?e[0]:{type:"and",children:e}}function yn(i){if(i.is("WORD","not")){i.next(),i.expect("LPAREN");let e=ni(i);return i.expect("RPAREN"),{type:"not",child:e}}return vr(i)}function vr(i){let e=i.peek();if(e&&e.type==="LPAREN"){i.next();let t=ni(i);return i.expect("RPAREN"),t}return Ar(i)}function Ir(i){let e=i.expect("WORD");if(Cr.has(e.value))throw new j(`Expected a field reference but got keyword '${e.value}'`);if(i.peek()?.type==="LBRACKET"){i.next();let t=i.expect("STRING");return i.expect("RBRACKET"),{field:e.value,key:t.value}}return{field:e.value}}function Rn(i){let e=i.next();if(e.type==="STRING")return e.value;if(e.type==="WORD")return/^-?\d+(\.\d+)?$/.test(e.value)?Number(e.value):e.value;throw new j(`Unexpected token '${e.value}' where a value was expected`)}function Cn(i){if(i.peek()?.type==="LBRACE"){i.next();let e=[];for(;i.peek()?.type!=="RBRACE";)e.push(Rn(i));return i.expect("RBRACE"),e}return Rn(i)}function Er(i){i.next(),i.expect("LPAREN");let e=i.expect("WORD");i.expect("LBRACKET");let t=i.expect("STRING");i.expect("RBRACKET"),i.expect("LBRACKET");let n=i.expect("WORD");if(n.value!=="*")throw new j(`Expected '*' in any(...) array index but got '${n.value}'`);i.expect("RBRACKET");let o=i.next();if(o.type!=="WORD"||!wn.has(o.value))throw new j(`Unsupported or unrecognized operator '${o.value}' inside any(...)`);let s=Cn(i);return i.expect("RPAREN"),{type:"comparison",field:e.value,key:t.value,operator:o.value,value:s}}function Tr(i){i.next(),i.expect("LPAREN");let e=i.expect("WORD"),t=i.expect("STRING");return i.expect("RPAREN"),{type:"exists",field:e.value,key:t.value}}function Ar(i){if(i.is("WORD","any"))return Er(i);if(i.is("WORD","has_key"))return Tr(i);let{field:e,key:t}=Ir(i);if(i.is("WORD","exists"))return i.next(),{type:"exists",field:e,key:t};let n=i.next();if(n.type!=="WORD"||!wn.has(n.value))throw new j(`Unsupported or unrecognized operator '${n.value}'`);let o=Cn(i);return{type:"comparison",field:e,key:t,operator:n.value,value:o}}function $r(i){let e=new Qe(br(i)),t=ni(e);if(!e.atEnd())throw new j("Unexpected trailing tokens");return t}function ti(i){return i.type==="comparison"||i.type==="exists"?!0:i.type==="not"?i.child.type==="comparison"||i.child.type==="exists":!1}function ii(i,e){let t=i.type==="not",n=i.type==="not"?i.child:i;if(n.type!=="comparison"&&n.type!=="exists")return null;let o=wr[n.field];if(!o)return null;if(n.type==="exists")return{field:o,operator:t?"not_exists":"exists",value:"",key:n.key,group:e};let s=n.operator,a;return t&&(n.operator==="contains"?s="not_contains":n.operator==="in"?s="not_in":a=!0),{field:o,operator:s,value:n.value,key:n.key,negated:a,group:e}}function Pr(i){if(ti(i)){let e=ii(i,0);return e?{conditions:[e],conditionLogic:"AND"}:null}if(i.type==="and"){let e=[];for(let t of i.children){if(!ti(t))return null;let n=ii(t,0);if(!n)return null;e.push(n)}return{conditions:e,conditionLogic:"AND"}}return i.type==="or"?Lt(i.children,ti,e=>e.type==="and",ii):null}function oi(i){if(!i||!i.trim())return null;try{let e=$r(i);return Pr(e)}catch{return null}}function In(i){let e=[],t=oi(i.expression),n=[],o;t?(n=t.conditions,o=t.conditionLogic):e.push(U.createWarning("complex_expressions",i.id,"expression","Expression could not be parsed back into structured conditions \u2014 it may be hand-authored or use syntax outside what doorman itself generates.","Review the translated rule and add missing conditions manually if needed."));let s=i.action_parameters&&"response"in i.action_parameters?i.action_parameters.response:void 0,a={type:_r(i.action),rateLimit:i.ratelimit?{requests:i.ratelimit.requests_per_period,window:`${i.ratelimit.period}s`,characteristics:i.ratelimit.characteristics,mitigationTimeout:i.ratelimit.mitigation_timeout,countingExpression:i.ratelimit.counting_expression}:void 0,response:s?{statusCode:s.status_code,content:s.content,contentType:s.content_type}:void 0};return{result:{id:i.id,name:i.description||`Rule ${i.id}`,description:i.description,enabled:i.enabled??!0,conditions:n,conditionLogic:o,action:a},warnings:e}}function En(i){let e=[],t=Sr(i.conditions,i.id,e);if(t.length===0)throw new Error(`Rule "${i.name}" has no conditions Cloudflare can represent \u2014 every condition field is unsupported by Cloudflare (e.g. Vercel-only fields like environment/ja3_digest, or vercel_region). Cannot sync this rule to Cloudflare.`);let n=Je.fromUnifiedConditions(t,i.conditionLogic),o={id:i.id||crypto.randomUUID(),action:kr(i.action.type),expression:n,description:i.description||i.name,enabled:i.enabled};return i.action.rateLimit&&(o.ratelimit={characteristics:i.action.rateLimit.characteristics||["ip.src"],period:Ur(i.action.rateLimit.window),requests_per_period:i.action.rateLimit.requests},i.action.rateLimit.mitigationTimeout?o.ratelimit.mitigation_timeout=i.action.rateLimit.mitigationTimeout:(o.ratelimit.mitigation_timeout=3600,e.push(U.createWarning("rate_limiting_precision",i.id,"action.rateLimit.mitigationTimeout",`No mitigationTimeout set for rate limit rule "${i.name}" \u2014 defaulted to Cloudflare's 3600s (1 hour) block duration.`))),i.action.rateLimit.countingExpression&&(o.ratelimit.counting_expression=i.action.rateLimit.countingExpression)),i.action.type==="redirect"&&i.action.redirect&&(o.action_parameters={from_value:{status_code:i.action.redirect.statusCode??(i.action.redirect.permanent?301:302),target_url:{value:i.action.redirect.location},...i.action.redirect.preserveQueryString!==void 0?{preserve_query_string:i.action.redirect.preserveQueryString}:{}}}),i.action.response&&(o.action!=="block"?e.push(U.createUnsupportedFeatureWarning(`custom response on a '${o.action}' action`,"unified config","Cloudflare",i.id,"action.response")):i.action.response.content?o.action_parameters={response:{status_code:i.action.response.statusCode??403,content:i.action.response.content,content_type:i.action.response.contentType??"text/plain"}}:e.push(U.createWarning("lossy_conversion",i.id,"action.response","Custom response declared without `content`; Cloudflare requires a response body, so it was dropped.","Set action.response.content to the body you want returned."))),{result:o,warnings:e}}function Tn(i){if(!Pe.safeParse(i.ip).success)throw new Error(`Invalid IP address or CIDR range: ${i.ip}`);let t=i.ip.includes("/")?`ip.src in {${i.ip}}`:`ip.src eq ${i.ip}`;return{id:i.id||crypto.randomUUID(),action:i.action==="allow"?"allow":"block",expression:t,description:i.notes||`IP ${i.action}: ${i.ip}${i.hostname?` (${i.hostname})`:""}`,enabled:!0}}function An(i){let e=[],t={};if(i.action){let{action:s,warning:a}=bn(i.action,i.id,"action");a&&e.push(a),s&&(t.action=s)}i.overrides&&i.overrides.length>0&&(t.rules=i.overrides.map(s=>{let a={id:s.ruleId};if(s.enabled!==void 0&&(a.enabled=s.enabled),s.action){let{action:l,warning:c}=bn(s.action,i.id,`overrides[${s.ruleId}].action`);c&&e.push(c),l&&(a.action=l)}return a}));let n={id:i.ruleset,...Object.keys(t).length>0?{overrides:t}:{}};return{result:{id:i.id||crypto.randomUUID(),action:"execute",expression:"true",description:i.name||`Managed ruleset ${i.ruleset}`,enabled:i.enabled,action_parameters:n},warnings:e}}function $n(i){let e=[],t=i.action_parameters,n={id:i.id,ruleset:t?.id??"",enabled:i.enabled??!0,...i.description?{name:i.description}:{}};return t?.overrides?.action&&(n.action=vn(t.overrides.action)),t?.overrides?.rules&&t.overrides.rules.length>0&&(n.overrides=t.overrides.rules.map(o=>({ruleId:o.id,...o.action!==void 0?{action:vn(o.action)}:{},...o.enabled!==void 0?{enabled:o.enabled}:{}}))),{result:n,warnings:e}}function bn(i,e,t){let o={log:"log",deny:"block",block:"block",challenge:"managed_challenge",allow:"allow"}[i];return o?{action:o}:{warning:U.createUnsupportedFeatureWarning(`'${i}' as a managed-ruleset override action`,"unified config","Cloudflare",e,t)}}function vn(i){return{log:"log",block:"deny",challenge:"challenge",managed_challenge:"challenge",js_challenge:"challenge",allow:"allow"}[i]||"deny"}function _r(i){return{block:"deny",challenge:"challenge",managed_challenge:"challenge",js_challenge:"challenge",log:"log",skip:"bypass",allow:"allow",rewrite:"bypass",redirect:"redirect",execute:"log"}[i]||"deny"}function kr(i){return{log:"log",deny:"block",block:"block",challenge:"managed_challenge",bypass:"skip",rate_limit:"block",redirect:"redirect",allow:"allow"}[i]||"block"}function Sr(i,e,t){return i.filter(n=>Je.isFieldSupported(n.field)?!0:(t.push(U.createUnsupportedFeatureWarning(`condition field '${n.field}'`,"unified config","Cloudflare",e,n.field)),!1))}function Ur(i){let e=i.match(/^(\d+)([smhd])$/);if(!e||!e[1]||!e[2])throw new Error(`Invalid window format: ${i}`);let t=parseInt(e[1],10),n=e[2],s={s:1,m:60,h:3600,d:86400}[n];if(s===void 0)throw new Error(`Invalid time unit: ${n}`);return t*s}pe();function we(i,e,t){return U.createWarning("complex_expressions",i,void 0,e,t)}var Lr={ip:"ip",path:"path",domain:"host",method:"method",country:"country",scheme:"scheme",user_agent:"user_agent"},xr={ip:"ip",path:"path",host:"domain",method:"method",country:"country",scheme:"scheme",user_agent:"user_agent"},Dr={header:"request_header",query:"query_parameter",cookie:"request_cookie"},Or={request_header:"header",query_parameter:"query",request_cookie:"cookie",response_header:"response_header",post_parameter:"post_parameter",signal:"signal"};function Fr(i){return Lr[i]??i}function Nr(i){return xr[i]??null}var Vr={equals:{operator:"eq",negated:!1},does_not_equal:{operator:"eq",negated:!0},contains:{operator:"contains",negated:!1},does_not_contain:{operator:"contains",negated:!0},like:{operator:"matches",negated:!1},not_like:{operator:"matches",negated:!0},in_list:{operator:"in",negated:!1},not_in_list:{operator:"in",negated:!0},matches:{operator:"matches",negated:!1},does_not_match:{operator:"matches",negated:!0},greater_equal:{operator:"ge",negated:!1},lesser_equal:{operator:"le",negated:!1}};function Pn(i){return Vr[i]??{operator:"eq",negated:!1}}function _n(i,e,t,n){switch(i){case"eq":return e?"does_not_equal":"equals";case"ne":return"does_not_equal";case"contains":return e?"does_not_contain":"contains";case"not_contains":return"does_not_contain";case"starts_with":case"ends_with":return t.push(U.createWarning("regex_patterns",n,void 0,`Unified operator "${i}" has no direct Fastly equivalent \u2014 mapped to Fastly's wildcard "like" operator. Verify the translated value still matches as a wildcard pattern.`)),e?"not_like":"like";case"matches":return t.push(U.createWarning("regex_patterns",n,void 0,"Regular expression pattern may need adjustment for Fastly Next-Gen WAF regex syntax.")),e?"does_not_match":"matches";case"in":return e?"not_in_list":"in_list";case"not_in":return"not_in_list";case"gt":return t.push(we(n,'Unified operator "gt" (strictly greater than) has no Fastly equivalent \u2014 mapped to "greater_equal", which also matches the boundary value itself.')),"greater_equal";case"ge":return"greater_equal";case"lt":return t.push(we(n,'Unified operator "lt" (strictly less than) has no Fastly equivalent \u2014 mapped to "lesser_equal", which also matches the boundary value itself.')),"lesser_equal";case"le":return"lesser_equal";default:return"equals"}}function Br(i,e){let t=[],n=i.conditions.filter(s=>s.type==="group"),o=i.conditions.filter(s=>s.type!=="group");if(n.length>0&&o.length===0&&i.group_operator==="any")return n.forEach((s,a)=>{s.group_operator!=="all"&&e.push(we(i.id,`A condition group using "any" (OR) inside an outer "any" (OR) rule cannot be represented in doorman's flat AND-within/OR-across condition model \u2014 its members were kept in the same group (effectively AND'd) instead of OR'd. Review the translated rule against the original Fastly logic.`));for(let l of s.conditions)xt(t,l,a,e,i.id)}),{conditions:t,conditionLogic:n.length>1?"OR":"AND"};if(n.length===0)return o.forEach((s,a)=>{let l=i.group_operator==="any"?a:0;xt(t,s,l,e,i.id)}),{conditions:t,conditionLogic:i.group_operator==="any"&&o.length>1?"OR":"AND"};e.push(we(i.id,`Rule mixes top-level conditions with condition groups under group_operator "${i.group_operator}" \u2014 this doesn't fit doorman's flat AND-within/OR-across condition model. All conditions were combined with AND; review the translated rule against the original Fastly rule logic.`));for(let s of o)xt(t,s,0,e,i.id);for(let s of n)for(let a of s.conditions)xt(t,a,0,e,i.id);return{conditions:t,conditionLogic:"AND"}}function xt(i,e,t,n,o){if(e.type==="single"){let{operator:u,negated:p}=Pn(e.operator);i.push({field:Fr(e.field),operator:u,value:e.value,...p?{negated:!0}:{},group:t});return}let s=Or[e.field],a=e.conditions.find(u=>u.field==="name"),l=e.conditions.find(u=>u.field==="value"||u.field==="value_string");if(!l){if(a){i.push({field:s,operator:e.operator==="does_not_exist"?"not_exists":"exists",key:a.value,group:t});return}n.push(we(o,`Multival condition on '${e.field}' has no value or name sub-condition doorman could extract \u2014 skipped.`));return}let{operator:c,negated:d}=Pn(l.operator);i.push({field:s,operator:c,value:l.value,...d?{negated:!0}:{},...a?{key:a.value}:{},group:t})}function Mr(i,e){i.actions.length>1&&e.push(we(i.id,`Rule has ${i.actions.length} actions \u2014 doorman only models one action per rule; only the first ("${i.actions[0]?.type}") was translated.`));let t=i.actions[0];if(i.type==="rate_limit")return{type:"rate_limit",rateLimit:i.rate_limit?{requests:i.rate_limit.threshold,window:`${i.rate_limit.interval}s`,mitigationTimeout:i.rate_limit.duration}:void 0};switch(t?.type){case"block":return{type:"block"};case"allow":return{type:"allow"};case"browser_challenge":case"dynamic_challenge":case"verify_token":return{type:"challenge"};case"redirect":return{type:"redirect",redirect:{location:t.redirect_url??"",...t.response_code!==void 0?{statusCode:t.response_code}:{}}};case"add_signal":case"exclude_signal":case"deception":return e.push(U.createWarning("action_differences",i.id,"action.type",`Fastly action "${t.type}" tags or excludes a WAF signal rather than allowing/blocking the request \u2014 doorman has no matching action type. Mapped to "log" as the closest non-destructive fallback; review this rule.`)),{type:"log"};default:return e.push(we(i.id,`Unrecognised Fastly action type "${String(t?.type)}" \u2014 mapped to "log".`)),{type:"log"}}}function kn(i){let e=[],{conditions:t,conditionLogic:n}=Br(i,e),o=Mr(i,e);return t.length>10&&e.push(U.createWarning("many_conditions",i.id,void 0,`Rule has ${t.length} conditions which may impact performance`)),{result:{id:i.id,name:i.description||i.id,enabled:i.enabled,conditions:t,conditionLogic:n,action:o},warnings:e}}function qr(i,e,t){let n=new Map;for(let a of i){let l=a.group??0,c=n.get(l);c?c.push(a):n.set(l,[a])}let o=a=>{let l=Dr[a.field];if(l){if(a.key===void 0)return t.push(we(e,`Condition on '${a.field}' has no 'key' set \u2014 Fastly needs a specific ${a.field} name to match on (e.g. a header name). Skipped.`)),null;if(a.operator==="exists"||a.operator==="not_exists")return{type:"multival",field:l,operator:a.operator==="exists"?"exists":"does_not_exist",group_operator:"all",conditions:[{type:"single",field:"name",operator:"equals",value:a.key}]};let d=l==="request_header"||l==="response_header"?"value_string":"value";return{type:"multival",field:l,operator:"exists",group_operator:"all",conditions:[{type:"single",field:"name",operator:"equals",value:a.key},{type:"single",field:d,operator:_n(a.operator,a.negated,t,e),value:String(a.value)}]}}if(a.operator==="exists"||a.operator==="not_exists")return t.push(U.createUnsupportedFeatureWarning(`"${a.operator}" on field '${a.field}'`,"unified config","Fastly",e,a.field)),null;let c=Nr(a.field);return c?{type:"single",field:c,operator:_n(a.operator,a.negated,t,e),value:String(a.value)}:(t.push(U.createUnsupportedFeatureWarning(`condition field '${a.field}'`,"unified config","Fastly",e,a.field)),null)};if(n.size<=1){let a=i.map(o).filter(l=>l!==null);return{conditions:a.filter(l=>l.type==="single"),groupConditions:[],multivalConditions:a.filter(l=>l.type==="multival"),groupOperator:"all"}}let s=[];for(let a of n.values()){let l=a.map(o).filter(c=>c!==null);l.length>0&&s.push({type:"group",group_operator:"all",conditions:l})}return{conditions:[],groupConditions:s,multivalConditions:[],groupOperator:"any"}}function Wr(i,e,t){switch(i.type){case"block":case"deny":return{type:"block"};case"allow":return{type:"allow"};case"challenge":return{type:"browser_challenge"};case"bypass":return e.push(we(t,'Unified action "bypass" has no direct Fastly equivalent \u2014 mapped to "allow".')),{type:"allow"};case"log":return e.push(we(t,'Unified action "log" has no dedicated Fastly action \u2014 mapped to "allow" (request_logging is enabled at the rule level regardless).')),{type:"allow"};case"redirect":return i.redirect?{type:"redirect",redirect_url:i.redirect.location,response_code:i.redirect.statusCode??(i.redirect.permanent?301:302)}:(e.push(we(t,'Unified action "redirect" has no redirect target \u2014 mapped to "allow".')),{type:"allow"});case"rate_limit":return{type:"block_signal"};default:return e.push(U.createUnsupportedFeatureWarning(`action type '${i.type}'`,"unified config","Fastly",t,"action.type")),{type:"block"}}}function jr(i,e,t){let n=/^(\d+)\s*(s|m|h)?$/i.exec(i.trim()),o={s:1,m:60,h:3600},s=n?Number(n[1])*o[n[2]?.toLowerCase()||"s"]:NaN,a=[60,600,3600];if(!Number.isFinite(s)||s<=0)return e.push(U.createWarning("rate_limiting_precision",t,void 0,`Rate limit window "${i}" could not be parsed \u2014 defaulted to Fastly's 60-second interval.`)),60;let l=a.reduce((c,d)=>Math.abs(d-s)<Math.abs(c-s)?d:c);return l!==s&&e.push(U.createWarning("rate_limiting_precision",t,void 0,`Rate limit window "${i}" (${s}s) isn't one of Fastly's supported intervals (60s/600s/3600s) \u2014 rounded to ${l}s.`)),l}function zr(i,e,t){if(!i)throw new Error(`Rule "${e??"(unnamed)"}" has action type "rate_limit" but no rateLimit configuration \u2014 Fastly requires a threshold and interval.`);let n=jr(i.window,t,e);return t.push(U.createWarning("rate_limiting_precision",e,void 0,`Fastly rate-limit rules count requests against a named custom signal, which doorman does not create automatically \u2014 create a signal named "doorman-rate-limit-${e??"rule"}" in this workspace before syncing, or the rule will be rejected.`)),{client_identifiers:[{type:"ip"}],duration:i.mitigationTimeout??n,interval:n,signal:`doorman-rate-limit-${e??"rule"}`,threshold:i.requests}}function Sn(i){let e=[];i.description&&i.description!==i.name&&e.push(we(i.id,'Fastly rules have no separate "name" field \u2014 only "description" carries over, and it was set from this rule\'s `name`. The `description` text was dropped rather than merged in, to keep it round-trip-stable on the next sync.'));let{conditions:t,groupConditions:n,multivalConditions:o,groupOperator:s}=qr(i.conditions,i.id,e);if(t.length===0&&n.length===0&&o.length===0)throw new Error(`Rule "${i.name}" has no conditions Fastly can represent \u2014 every condition field is unsupported by Fastly. Cannot sync this rule to Fastly.`);let a=i.action.type==="rate_limit";return{result:{type:a?"rate_limit":"request",description:i.name,enabled:i.enabled,group_operator:s,request_logging:"sampled",conditions:t,group_conditions:n,multival_conditions:o,actions:[Wr(i.action,e,i.id)],rate_limit:a?zr(i.action.rateLimit,i.id,e):null},warnings:e}}function Un(i,e){return i.map(t=>({ip:t,action:e}))}function Ln(i){let e=[],t=[],n=[];for(let o of i)(o.hostname||o.notes)&&e.push(we(o.id,`IP rule "${o.ip}" sets ${o.hostname?"`hostname`":""}${o.hostname&&o.notes?" and ":""}${o.notes?"`notes`":""} \u2014 Fastly IP lists have no equivalent per-entry metadata; only the address was kept.`)),(o.action==="allow"?n:t).push(o.ip);return{result:{denyEntries:t,allowEntries:n},warnings:e}}pe();function Xe(i){return i.replace(/\\/g,"\\\\").replace(/'/g,"\\'")}function xn(i){let e="";for(let t=0;t<i.length;t++)i[t]==="\\"&&(i[t+1]==="\\"||i[t+1]==="'")?(e+=i[t+1],t++):e+=i[t];return e}var Gr=new Set(["region","city","port","scheme"]),Kr={host:"host",user_agent:"user-agent",referer:"referer"},ft=class{static fromUnifiedConditions(e,t="AND"){if(!e||e.length===0)throw new Error("At least one condition is required");if(!e.some(a=>a.group!==void 0)){let a=e.map(c=>this.fromUnifiedCondition(c)),l=t==="AND"?" && ":" || ";return a.length>1?`(${a.join(l)})`:a[0]}let o=new Map;for(let a of e){let l=a.group??0,c=o.get(l);c?c.push(a):o.set(l,[a])}let s=Array.from(o.values()).map(a=>this.combineWithAnd(a.map(l=>this.fromUnifiedCondition(l))));return s.length>1?this.combineWithOr(s):s[0]}static fromUnifiedCondition(e){if(Gr.has(e.field))throw new Error(`Cloud Armor has no CEL equivalent for the "${e.field}" condition field \u2014 this rule cannot be represented and would otherwise be silently dropped.`);if(e.field==="ip")return this.buildIpCondition(e);if(e.field==="cookie")return this.buildCookieCondition(e);let t=Kr[e.field];if(t)return this.buildHeaderCondition(t,e);if(e.field==="header"||e.field==="query"){if(e.field==="header"){if(!e.key)throw new Error('A "header" condition requires a key naming the header');return this.buildHeaderCondition(e.key,e)}return this.buildComparison("request.query",e,!1)}let n=Zr[e.field];if(n)return this.buildComparison(n.path,e,n.numeric);throw new Error(`Cloud Armor CEL builder has no mapping for condition field "${e.field}"`)}static buildIpCondition(e){let t=(Array.isArray(e.value)?e.value:[e.value]).map(String);if(t.length===0||t.some(a=>!a))throw new Error('An "ip" condition requires at least one value');let n=t.map(a=>{if(!Pe.safeParse(a).success)throw new Error(`Invalid IP address or CIDR range: ${a}`);return a.includes("/")?`inIpRange(origin.ip, '${Xe(a)}')`:`origin.ip == '${Xe(a)}'`}),o=this.isNegated(e,e.operator==="not_in"||e.operator==="ne");if(n.length>1){let a=`(${n.join(" || ")})`;return o?`!${a}`:a}let s=n[0];return o?s.startsWith("inIpRange(")?`!${s}`:`!(${s})`:s}static buildHeaderCondition(e,t){let n=`request.headers['${Xe(e)}']`,o=`has(${n})`;return t.operator==="exists"||t.operator==="not_exists"?this.isNegated(t,t.operator==="not_exists")?`!${o}`:o:`(${o} && ${this.buildComparison(n,t,!1)})`}static buildCookieCondition(e){let t="request.headers['cookie']",n=`has(${t})`;if(e.operator==="exists"||e.operator==="not_exists")return this.isNegated(e,e.operator==="not_exists")?`!${n}`:n;if(!e.key)return`(${n} && ${this.buildComparison(t,e,!1)})`;if(!["eq","ne","contains","not_contains"].includes(e.operator))throw new Error(`Cloud Armor can only check a specific cookie's value via substring search (has no parsed cookie map) \u2014 "${e.operator}" on a keyed cookie condition has no unambiguous CEL representation.`);let o=`${e.key}=${String(e.value)}`,s=`${t}.contains('${Xe(o)}')`,a=this.isNegated(e,e.operator==="ne"||e.operator==="not_contains");return`(${n} && ${a?`!${s}`:s})`}static buildComparison(e,t,n){let{value:o}=t;if(t.operator==="exists"||t.operator==="not_exists")throw new Error(`"${t.operator}" is not meaningful for Cloud Armor's "${e}" \u2014 it is always present`);let s=t.operator,a=!1;if(t.negated){let d=Hr[s];d?s=d:a=!0}if(s==="in"||s==="not_in"){let u=(Array.isArray(o)?o:[o]).map(f=>this.formatLiteral(f,n)).join(", "),p=`${e} in [${u}]`;return s==="not_in"?`!(${p})`:p}let l=this.formatLiteral(o,n),c;switch(s){case"eq":c=`${e} == ${l}`;break;case"ne":c=`${e} != ${l}`;break;case"contains":c=`${e}.contains(${l})`;break;case"not_contains":c=`!${e}.contains(${l})`;break;case"starts_with":c=`${e}.startsWith(${l})`;break;case"ends_with":c=`${e}.endsWith(${l})`;break;case"matches":c=`${e}.matches(${l})`;break;case"gt":c=`${e} > ${l}`;break;case"ge":c=`${e} >= ${l}`;break;case"lt":c=`${e} < ${l}`;break;case"le":c=`${e} <= ${l}`;break;default:throw new Error(`Cloud Armor CEL builder has no mapping for operator "${s}"`)}return a?`!(${c})`:c}static isNegated(e,t){return t!==!!e.negated}static formatLiteral(e,t){if(t){let n=Number(e);if(!Number.isFinite(n))throw new Error(`Expected a numeric value, got "${String(e)}"`);return String(n)}return`'${Xe(String(e))}'`}static combineWithAnd(e){if(e.length===0)throw new Error("At least one expression is required");return e.length===1?e[0]:`(${e.join(" && ")})`}static combineWithOr(e){if(e.length===0)throw new Error("At least one expression is required");return e.length===1?e[0]:`(${e.join(" || ")})`}},Hr={eq:"ne",ne:"eq",contains:"not_contains",not_contains:"contains",in:"not_in",not_in:"in"},Zr={country:{path:"origin.region_code",numeric:!1},asn:{path:"origin.asn",numeric:!0},path:{path:"request.path",numeric:!1},method:{path:"request.method",numeric:!1}};var Yr={"origin.region_code":"country","origin.asn":"asn","request.path":"path","request.method":"method","request.query":"query"},Jr={host:"host","user-agent":"user_agent",referer:"referer",cookie:"cookie"},Bn={contains:"contains",startsWith:"starts_with",endsWith:"ends_with",matches:"matches"};function Qr(i){let e=[],t=0,n=i.length,o=(s,a)=>i[t]===s&&i[t+1]===a;for(;t<n;){let s=i[t];if(/\s/.test(s)){t++;continue}if(s==="("){e.push({type:"LPAREN",value:s}),t++;continue}if(s===")"){e.push({type:"RPAREN",value:s}),t++;continue}if(s==="["){e.push({type:"LBRACKET",value:s}),t++;continue}if(s==="]"){e.push({type:"RBRACKET",value:s}),t++;continue}if(s===","){e.push({type:"COMMA",value:s}),t++;continue}if(o("&","&")){e.push({type:"AND",value:"&&"}),t+=2;continue}if(o("|","|")){e.push({type:"OR",value:"||"}),t+=2;continue}if(o("=","=")){e.push({type:"EQ",value:"=="}),t+=2;continue}if(o("!","=")){e.push({type:"NE",value:"!="}),t+=2;continue}if(o(">","=")){e.push({type:"GE",value:">="}),t+=2;continue}if(o("<","=")){e.push({type:"LE",value:"<="}),t+=2;continue}if(s==="!"){e.push({type:"NOT",value:"!"}),t++;continue}if(s===">"){e.push({type:"GT",value:">"}),t++;continue}if(s==="<"){e.push({type:"LT",value:"<"}),t++;continue}if(s==="."){e.push({type:"DOT",value:s}),t++;continue}if(s==="'"){let a=t+1,l="",c=!1;for(;a<n;){let d=i[a];if(d==="\\"&&a+1<n){l+=d+i[a+1],a+=2;continue}if(i[a]==="'"){c=!0,a++;break}l+=i[a],a++}if(!c)throw new j("Unterminated string literal");e.push({type:"STRING",value:xn(l)}),t=a;continue}if(/[0-9]/.test(s)||s==="-"&&/[0-9]/.test(i[t+1]??"")){let a=t+1;for(;a<n&&/[0-9.]/.test(i[a]);)a++;e.push({type:"NUMBER",value:i.slice(t,a)}),t=a;continue}if(/[A-Za-z_]/.test(s)){let a=t;for(;a<n&&/[A-Za-z0-9_]/.test(i[a]);)a++;e.push({type:"IDENT",value:i.slice(t,a)}),t=a;continue}throw new j(`Unexpected character '${s}' at position ${t}`)}return e}function Xr(i){let e=i.expect("IDENT").value;for(;i.peek()?.type==="DOT";){let t=i.pos;i.next();let n=i.expect("IDENT");if(i.peek()?.type==="LPAREN"){i.pos=t;break}e+=`.${n.value}`}return e}function ri(i){let e=Xr(i);if(i.peek()?.type==="LBRACKET"){i.next();let t=i.expect("STRING");return i.expect("RBRACKET"),{field:e,key:t.value}}return{field:e}}function Dn(i){let e=i.next();if(e.type==="STRING")return e.value;if(e.type==="NUMBER")return Number(e.value);throw new j(`Unexpected token '${e.value}' where a literal was expected`)}function es(i){if(i.is("IDENT","has")){i.next(),i.expect("LPAREN");let a=ri(i);return i.expect("RPAREN"),{type:"has",...a}}if(i.is("IDENT","inIpRange")){i.next(),i.expect("LPAREN");let a=ri(i);if(a.field!=="origin.ip")throw new j(`inIpRange must reference origin.ip, got '${a.field}'`);i.expect("COMMA");let l=i.expect("STRING");return i.expect("RPAREN"),{type:"inIpRange",value:l.value}}let e=ri(i);if(i.peek()?.type==="DOT"){i.next();let a=i.expect("IDENT").value;if(!Bn[a])throw new j(`Unrecognized method '${a}'`);i.expect("LPAREN");let l=i.expect("STRING");return i.expect("RPAREN"),{type:"methodCall",...e,method:a,value:l.value}}if(i.is("IDENT","in")){i.next(),i.expect("LBRACKET");let a=[];for(;i.peek()?.type!=="RBRACKET";)a.push(Dn(i)),i.peek()?.type==="COMMA"&&i.next();return i.expect("RBRACKET"),{type:"membership",...e,values:a}}let t=i.next(),o={EQ:"eq",NE:"ne",GT:"gt",GE:"ge",LT:"lt",LE:"le"}[t.type];if(!o)throw new j(`Unexpected token '${t.value}' after field reference`);let s=Dn(i);return{type:"comparison",...e,operator:o,value:s}}function On(i){if(i.peek()?.type==="LPAREN"){i.next();let e=Mn(i);return i.expect("RPAREN"),e}return es(i)}function Fn(i){return i.peek()?.type==="NOT"?(i.next(),{type:"not",child:On(i)}):On(i)}function Nn(i){let e=[Fn(i)];for(;i.peek()?.type==="AND";)i.next(),e.push(Fn(i));return e.length===1?e[0]:{type:"and",children:e}}function Mn(i){let e=[Nn(i)];for(;i.peek()?.type==="OR";)i.next(),e.push(Nn(i));return e.length===1?e[0]:{type:"or",children:e}}function ts(i){let e=new Qe(Qr(i)),t=Mn(e);if(!e.atEnd())throw new j("Unexpected trailing tokens");return t}function is(i){return i.type==="inIpRange"||i.type==="comparison"&&i.field==="origin.ip"&&i.operator==="eq"}function li(i){return i.type==="or"&&i.children.length>0&&i.children.every(is)}function ns(i){return i.type==="inIpRange"?i.value:String(i.value)}function qn(i){if(i.type!=="and"||i.children.length!==2)return!1;let e=i.children[0],t=i.children[1];if(e.type!=="has")return!1;let n=t.type==="not"?t.child:t;return!("field"in n)||n.field!==e.field||n.key!==e.key?!1:n.type==="comparison"||n.type==="methodCall"||n.type==="membership"}function os(i){let e=i.children[0],t=i.children[1],n=t.type==="not",o=n?t.child:t;return{guard:e,comparisonNegated:n,comparison:o}}function si(i){if(i.type==="has"||i.type==="comparison"||i.type==="methodCall"||i.type==="membership"||i.type==="inIpRange"||li(i)||qn(i))return!0;if(i.type==="not"){let e=i.child;return e.type==="has"||e.type==="comparison"||e.type==="methodCall"||e.type==="membership"||e.type==="inIpRange"||li(e)}return!1}function Wn(i,e){if(i==="origin.ip")return{unifiedField:"ip"};let t=Yr[i];if(t)return{unifiedField:t};if(i==="request.headers"&&e!==void 0){let n=Jr[e];return n?{unifiedField:n}:{unifiedField:"header",unifiedKey:e}}return null}function ai(i,e){let t=i.type==="not",n=i.type==="not"?i.child:i;if(li(n)){let o=n.children.map(s=>ns(s));return{field:"ip",operator:t?"not_in":"in",value:o,...e!==void 0?{group:e}:{}}}if(n.type==="inIpRange"||n.type==="comparison"&&n.field==="origin.ip"){let o=n.type==="inIpRange"?n.value:String(n.value);return{field:"ip",operator:t?"ne":"eq",value:o,...e!==void 0?{group:e}:{}}}if(n.type==="and"&&qn(n)){if(t)return null;let{guard:o,comparisonNegated:s,comparison:a}=os(n);return a.type!=="comparison"&&a.type!=="methodCall"&&a.type!=="membership"?null:Vn(a,s,o.field,o.key,e)}if(n.type==="comparison"||n.type==="methodCall"||n.type==="membership")return Vn(n,t,n.field,n.key,e);if(n.type==="has"){let o=Wn(n.field,n.key);return o?{field:o.unifiedField,operator:t?"not_exists":"exists",value:"",...o.unifiedKey!==void 0?{key:o.unifiedKey}:{},...e!==void 0?{group:e}:{}}:null}return null}function Vn(i,e,t,n,o){let s=Wn(t,n);if(!s)return null;let a,l,c=!1;if(i.type==="comparison")e?i.operator==="eq"?a="ne":i.operator==="ne"?a="eq":(a=i.operator,c=!0):a=i.operator,l=i.value;else if(i.type==="methodCall"){let d=Bn[i.method];if(!d)return null;e?d==="contains"?a="not_contains":(a=d,c=!0):a=d,l=i.value}else a=e?"not_in":"in",l=i.values;return{field:s.unifiedField,operator:a,value:l,...c?{negated:!0}:{},...s.unifiedKey!==void 0?{key:s.unifiedKey}:{},...o!==void 0?{group:o}:{}}}function rs(i){if(si(i)){let e=ai(i,void 0);return e?{conditions:[e],conditionLogic:"AND"}:null}if(i.type==="and"){let e=[];for(let t of i.children){if(!si(t))return null;let n=ai(t,void 0);if(!n)return null;e.push(n)}return{conditions:e,conditionLogic:"AND"}}return i.type==="or"?Lt(i.children,si,e=>e.type==="and",ai):null}function Me(i){if(!i||!i.trim())return null;try{let e=ts(i);return rs(e)}catch{return null}}function ci(i){if(!i.match.expr)throw new Error(`Cloud Armor rule at priority ${i.priority} has no CEL match expression (uses the basic versionedExpr/config shape) \u2014 caller must filter these out before translating.`);return i.match.expr.expression}function jn(i){if(i.priority===void 0)throw new Error(`Rule "${i.name}" has no priority assigned \u2014 CloudArmorFirewallService must assign one before calling unifiedToGcp`);let e=[],t=ft.fromUnifiedConditions(i.conditions,i.conditionLogic),{action:n,actionWarning:o}=as(i.action.type,i.name,i.id);o&&e.push(o);let s={priority:i.priority,description:(i.description||i.name).slice(0,64),match:{expr:{expression:t}},action:n,preview:i.action.type==="log"?!0:void 0};if((n==="throttle"||n==="rate_based_ban")&&i.action.rateLimit){let a=ls(i.action.rateLimit.window);s.rateLimitOptions={rateLimitThreshold:{count:i.action.rateLimit.requests,intervalSec:a},conformAction:"allow",exceedAction:"deny(429)",enforceOnKey:"IP"}}else n==="throttle"&&!i.action.rateLimit&&e.push(U.createWarning("lossy_conversion",i.id,"action.rateLimit",`Rule "${i.name}" maps to Cloud Armor's "throttle" action but declares no rateLimit block \u2014 Cloud Armor requires rateLimitOptions for this action; the rule will likely be rejected by the API.`));return n==="redirect"&&(i.action.redirect?s.redirectOptions={type:"EXTERNAL_302",target:i.action.redirect.location}:e.push(U.createWarning("lossy_conversion",i.id,"action.redirect",`Rule "${i.name}" maps to Cloud Armor's "redirect" action but declares no redirect target \u2014 Cloud Armor requires redirectOptions for this action; the rule will likely be rejected by the API.`))),{result:s,warnings:e}}function zn(i,e){let t=[],n=e!==void 0?e:Me(ci(i)),o=[],s;n?(o=n.conditions,s=n.conditionLogic):t.push(U.createWarning("complex_expressions",String(i.priority),"match.expr.expression","CEL expression could not be parsed back into structured conditions \u2014 it may be hand-authored or use syntax outside what doorman itself generates.","Review the translated rule and add missing conditions manually if needed."));let a={type:ss(i.action),...i.rateLimitOptions?{rateLimit:{requests:i.rateLimitOptions.rateLimitThreshold.count,window:`${i.rateLimitOptions.rateLimitThreshold.intervalSec}s`}}:{},...i.redirectOptions?{redirect:{location:i.redirectOptions.target}}:{}};return{result:{id:String(i.priority),name:i.description||`Rule ${i.priority}`,...i.description?{description:i.description}:{},enabled:!i.preview,conditions:o,...s==="OR"?{conditionLogic:s}:{},action:a,priority:i.priority},warnings:t}}function Gn(i,e){if(i.action!=="allow"&&i.action!=="deny(403)"||i.preview)return!1;let t=e!==void 0?e:Me(ci(i));if(!t||t.conditions.length!==1)return!1;let n=t.conditions[0];return n.field==="ip"&&(n.operator==="eq"||n.operator==="in")&&!n.key}function Kn(i,e){let o=(e!==void 0?e:Me(ci(i))).conditions[0].value;return{id:String(i.priority),ip:Array.isArray(o)?String(o[0]):String(o),...i.description?{notes:i.description}:{},action:i.action==="allow"?"allow":"deny"}}function Hn(i,e){if(!Pe.safeParse(i.ip).success)throw new Error(`Invalid IP address or CIDR range: ${i.ip}`);let t=ft.fromUnifiedCondition({field:"ip",operator:"eq",value:i.ip});return{priority:e,description:(i.notes||`IP ${i.action}: ${i.ip}${i.hostname?` (${i.hostname})`:""}`).slice(0,64),match:{expr:{expression:t}},action:i.action==="allow"?"allow":"deny(403)"}}function ss(i){return{allow:"allow","deny(403)":"deny","deny(404)":"deny","deny(502)":"deny",rate_based_ban:"rate_limit",throttle:"rate_limit",redirect:"redirect"}[i]||"deny"}function as(i,e,t){switch(i){case"log":return{action:"allow"};case"deny":case"block":return{action:"deny(403)"};case"challenge":return{action:"deny(403)",actionWarning:U.createUnsupportedFeatureWarning("challenge action","unified config","Cloud Armor",t,"action")};case"bypass":return{action:"allow"};case"rate_limit":return{action:"throttle"};case"redirect":return{action:"redirect"};case"allow":return{action:"allow"};default:return{action:"deny(403)",actionWarning:U.createWarning("lossy_conversion",t,"action",`Rule "${e}" has unrecognized action "${i}" \u2014 defaulted to Cloud Armor's "deny(403)".`)}}}function ls(i){let e=i.match(/^(\d+)([smhd])$/);if(!e||!e[1]||!e[2])throw new Error(`Invalid window format: ${i}`);let t=parseInt(e[1],10),o={s:1,m:60,h:3600,d:86400}[e[2]];if(o===void 0)throw new Error(`Invalid time unit: ${e[2]}`);return t*o}var T=class{static vercelToUnified=pn;static unifiedToVercel=fn;static vercelIPToUnified=gn;static cloudflareToUnified=In;static unifiedToCloudflare=En;static unifiedIPToCloudflare=Tn;static unifiedManagedRuleGroupToCloudflare=An;static cloudflareToUnifiedManagedRuleGroup=$n;static fastlyToUnified=kn;static unifiedToFastly=Sn;static fastlyListEntriesToUnified=Un;static unifiedIPsToFastlyEntries=Ln;static gcpToUnified=zn;static unifiedToGcp=jn;static gcpToUnifiedIP=Kn;static unifiedIPToGcp=Hn;static gcpLooksLikeIpRule=Gn};pe();function Oe(i,e){if(i===e)return!0;if(typeof i!="object"||i===null||typeof e!="object"||e===null||Array.isArray(i)!==Array.isArray(e))return!1;let t=Object.keys(i),n=Object.keys(e);if(t.length!==n.length)return!1;for(let o of t)if(!n.includes(o)||!Oe(i[o],e[o]))return!1;return!0}function Ce(i){let{id:e,...t}=i;return t}function gt(i){return[...i].sort((e,t)=>{let n=e.priority!==void 0,o=t.priority!==void 0;return!n&&!o?0:n?o?e.priority-t.priority:-1:1})}function Dt(i){return i.some(e=>e.priority!==void 0)}var et=class extends ke{constructor(t){super();this.client=t}client;name="vercel";async fetchConfig(t){try{r.debug("Fetching Vercel firewall configuration");let n=await this.client.fetchFirewallConfig(t),o=n.rules.map(a=>{let l=T.vercelToUnified(a);return l.warnings.length>0&&l.warnings.forEach(c=>{let{TranslationWarningSystem:d}=(pe(),Re(Se)),u=d.formatWarning(c);r.warn(`Rule ${a.name}:
24
+ `),u.cancelled&&process.exit(0),e(o))};t.on("keypress",a)})}_();var rn={provider:"vercel",fields:[{key:"token",envVar:"VERCEL_TOKEN",label:"Vercel API Token",required:!0,secret:!0,promptMessage:"What is your Vercel API Auth Token? (https://vercel.com/guides/how-do-i-use-a-vercel-api-access-token#creating-an-access-token) "},{key:"projectId",envVar:"VERCEL_PROJECT_ID",label:"Vercel Project ID",required:!0,configKey:"projectId",cliAlias:"p",legacyConfigKey:"projectId",promptMessage:"What is your Vercel Project ID? (See https://vercel.com/docs/projects/project-configuration/general-settings#project-id)"},{key:"teamId",envVar:"VERCEL_TEAM_ID",label:"Vercel Team ID",required:!1,configKey:"teamId",cliAlias:"t",legacyConfigKey:"teamId",promptMessage:"What is your Vercel Team ID? Leave blank to use your Vercel default team (every account, including Hobby, is a team). Only needed if you belong to more than one team and want to target a non-default one. (See https://vercel.com/docs/accounts#find-your-team-id)"}]};var sn={provider:"cloudflare",fields:[{key:"apiToken",envVar:"CLOUDFLARE_API_TOKEN",label:"Cloudflare API Token",required:!0,secret:!0},{key:"zoneId",envVar:"CLOUDFLARE_ZONE_ID",label:"Cloudflare Zone ID",required:!0,configKey:"zoneId"},{key:"accountId",envVar:"CLOUDFLARE_ACCOUNT_ID",label:"Cloudflare Account ID",required:!1,configKey:"accountId"}]};var an={provider:"fastly",fields:[{key:"apiToken",envVar:"FASTLY_API_TOKEN",label:"Fastly API Token",required:!0,secret:!0},{key:"workspaceId",envVar:"FASTLY_WORKSPACE_ID",label:"Fastly Next-Gen WAF Workspace ID",required:!0,configKey:"workspaceId"}]};var ln={provider:"gcp",fields:[{key:"serviceAccountKeyPath",envVar:"GOOGLE_APPLICATION_CREDENTIALS",label:"GCP Service Account Key Path",required:!1,promptMessage:"Path to a GCP service account JSON key file (leave blank to use Application Default Credentials \u2014 gcloud auth application-default login, or the GCE/Cloud Run metadata server): "},{key:"projectId",envVar:"GOOGLE_CLOUD_PROJECT",label:"GCP Project ID",required:!0,configKey:"projectId"},{key:"policyName",envVar:"GCP_POLICY_NAME",label:"Cloud Armor Security Policy Name",required:!0,configKey:"policyName"}]};function cn(i,e={}){let{explicit:t={},config:n={},env:o=process.env}=e,s={};for(let a of i.fields)s[a.key]=t[a.key]||n[a.key]||o[a.envVar];return s}function dn(i,e){return i.fields.filter(t=>t.required&&!e[t.key])}var He={vercel:rn,cloudflare:sn,fastly:an,gcp:ln};var St=class{static detectionField(e){return He[e].fields.find(t=>t.required&&t.configKey)}static detect(e){let t=[];if(e&&"provider"in e){let o=e.provider;if(typeof o=="string"&&this.isValidProvider(o))return t.push(`Explicit provider specified in config: ${e.provider}`),{provider:o,confidence:"high",reasons:t};r.warn(`Invalid provider specified in config: ${String(e.provider)}`)}if(e&&"providers"in e&&typeof e.providers=="object"&&e.providers!==null){let o=e.providers;for(let s of H){let a=this.detectionField(s);if(!a?.configKey)continue;if(typeof(o[s]||{})[a.configKey]=="string")return t.push(`${a.label} found in config`),{provider:s,confidence:"high",reasons:t}}}if(e&&"projectId"in e&&typeof e.projectId=="string")return t.push("Legacy Vercel project ID found in config"),{provider:"vercel",confidence:"high",reasons:t};let n=this.detectFromEnvironment();return n.provider?n:(r.debug("Unable to auto-detect provider"),{provider:null,confidence:"low",reasons:["No provider information found in config or environment"]})}static detectFromEnvironment(){let e=[],t=process.env.DOORMAN_PROVIDER;if(t&&this.isValidProvider(t))return e.push(`Provider set via DOORMAN_PROVIDER environment variable: ${t}`),{provider:t,confidence:"high",reasons:e};for(let n of H){let o=this.detectionField(n);if(!(!o||!process.env[o.envVar]))return e.push(`${o.label} found in environment (${o.envVar})`),{provider:n,confidence:"medium",reasons:e}}return{provider:null,confidence:"low",reasons:["No provider credentials found in environment"]}}static isValidProvider(e){return H.includes(e)}static getProvider(e,t="vercel"){let n=this.detect(e);return n.provider?(r.debug(`Provider detected: ${n.provider} (${n.confidence} confidence)`),n.reasons.length>0&&r.debug(`Detection reasons: ${n.reasons.join(", ")}`),n.provider):(r.debug(`No provider detected, using fallback: ${t}`),t)}static detectAll(e){let t=new Set;if(e&&"provider"in e){let n=e.provider;typeof n=="string"&&this.isValidProvider(n)&&t.add(n)}if(e&&"providers"in e&&typeof e.providers=="object"&&e.providers!==null){let n=e.providers;for(let o of H){let s=this.detectionField(o);if(!s?.configKey)continue;typeof(n[o]||{})[s.configKey]=="string"&&t.add(o)}}e&&"projectId"in e&&typeof e.projectId=="string"&&t.add("vercel");for(let n of H){let o=this.detectionField(n);o&&process.env[o.envVar]&&t.add(n)}return Array.from(t)}};var ei=O(require("chalk"));_();$e();var Ut=i=>({$schema:Di,firewallEnabled:!0,rules:[],ips:[],...i});_();var _e=class{baseUrl;providerName;rateLimitInfo={};constructor(e,t){this.baseUrl=e,this.providerName=t}async makeRequest(e,t={}){let n=`${this.baseUrl}${e}`,{timeout:o=3e4,retries:s=3,retryDelay:a=1e3,...l}=t,c=null,d=null,u=null,p=()=>{u&&(clearTimeout(u),u=null),d&&(d.abort(),d=null)};try{for(let f=0;f<=s;f++)try{d=new AbortController,u=setTimeout(()=>{d&&d.abort()},o);let g=await fetch(n,{...l,headers:{"Content-Type":"application/json",...await this.getAuthHeaders(),...l.headers},signal:d.signal});if(u&&(clearTimeout(u),u=null),this.updateRateLimitInfo(g),g.status===429){let v=this.calculateRateLimitWait(f,g);if(f===s)throw new Error(`${this.providerName} API rate limit exceeded after ${s+1} attempt(s) (429 Too Many Requests). Retry after ~${Math.round(v/1e3)}s.`);r.warn(`Rate limit exceeded for ${this.providerName}. Waiting ${v}ms before retry (attempt ${f+1}/${s+1})...`),await this.delay(v);continue}if(!g.ok)throw await this.handleErrorResponse(g);let m;try{let v=await g.text();v.trim()?m=JSON.parse(v):m={}}catch(v){throw r.error(`Failed to parse response from ${n}:`,v),new Error(`Invalid JSON response from ${this.providerName} API: ${v instanceof Error?v.message:String(v)}`)}return this.isLargeResponse(m)&&(r.debug(`Large response detected from ${n}, validating structure...`),this.validateLargeResponse(m)),m}catch(g){if(c=g,u&&(clearTimeout(u),u=null),this.isNonRetryableError(g)||f===s)throw g;let m=a*Math.pow(2,f),v=Math.random()*.1*m,z=Math.min(m+v,3e4);r.debug(`Request failed (attempt ${f+1}/${s+1}). Retrying in ${Math.round(z)}ms...`),r.debug(`Error details: ${g instanceof Error?g.message:String(g)}`),await this.delay(z)}throw c||new Error("Request failed after all retries")}finally{p()}}async get(e,t){return this.makeRequest(e,{...t,method:"GET"})}async post(e,t,n){return this.makeRequest(e,{...n,method:"POST",body:t?JSON.stringify(t):void 0})}async put(e,t,n){return this.makeRequest(e,{...n,method:"PUT",body:t?JSON.stringify(t):void 0})}async patch(e,t,n){return this.makeRequest(e,{...n,method:"PATCH",body:t?JSON.stringify(t):void 0})}async delete(e,t){return this.makeRequest(e,{...t,method:"DELETE"})}updateRateLimitInfo(e){let t=e.headers.get("X-RateLimit-Limit"),n=e.headers.get("X-RateLimit-Remaining"),o=e.headers.get("X-RateLimit-Reset");t&&(this.rateLimitInfo.limit=parseInt(t,10)),n&&(this.rateLimitInfo.remaining=parseInt(n,10)),o&&(this.rateLimitInfo.reset=parseInt(o,10)),this.rateLimitInfo.remaining&&this.rateLimitInfo.remaining<10&&r.warn(`Approaching rate limit for ${this.providerName}: ${this.rateLimitInfo.remaining} requests remaining`)}calculateRateLimitWait(e=0,t){let o=t?this.parseRetryAfter(t.headers.get("Retry-After")):null;if(o!==null)return Math.min(Math.max(o,1e3),6e4);if(this.rateLimitInfo.reset){let l=Math.floor(Date.now()/1e3),c=(this.rateLimitInfo.reset-l)*1e3;return Math.min(Math.max(c,1e3),6e4)}let a=5e3*Math.pow(2,e);return Math.min(a,6e4)}parseRetryAfter(e){if(!e)return null;let t=Number(e);if(Number.isFinite(t))return t*1e3;let n=Date.parse(e);return Number.isNaN(n)?null:n-Date.now()}async handleErrorResponse(e){let t=`${this.providerName} API error: ${e.status} ${e.statusText}`,n=t;try{let o=await e.json();if(typeof o=="object"&&o!==null){let s=o;if(typeof s.error=="string")n=`${t} - ${s.error}`;else if(typeof s.message=="string")n=`${t} - ${s.message}`;else if(Array.isArray(s.errors)){let a=s.errors.map(l=>typeof l=="object"&&l!==null&&"message"in l?String(l.message):JSON.stringify(l)).join(", ");n=`${t} - ${a}`}}}catch{}return r.error(n),new Error(n)}isNonRetryableError(e){if(typeof e=="object"&&e!==null&&e.name==="AbortError")return!0;let t=e.message;return typeof t=="string"&&t.includes("API error: 4")?!t.includes("429"):!1}delay(e){return new Promise(t=>setTimeout(t,e))}getRateLimitInfo(){return{...this.rateLimitInfo}}isLargeResponse(e){if(!e||typeof e!="object")return!1;let t=(n,o=0)=>{if(o>10)return!1;for(let[,s]of Object.entries(n))if(Array.isArray(s)&&s.length>1e3||typeof s=="object"&&s!==null&&t(s,o+1))return!0;return!1};return t(e)}validateLargeResponse(e){if(!e||typeof e!="object")return;let t=(o,s)=>{o.length>5e3&&r.warn(`Large array detected at ${s} with ${o.length} items. This may impact performance.`);let a=Math.min(10,o.length),l=o[0];if(l&&typeof l=="object"){let c=Object.keys(l);for(let d=1;d<a;d++){let u=o[d];if(!u||typeof u!="object"){r.warn(`Inconsistent array structure at ${s}[${d}]: expected object, got ${typeof u}`);continue}let p=Object.keys(u),f=c.filter(g=>!p.includes(g));f.length>0&&r.warn(`Inconsistent array structure at ${s}[${d}]: missing keys ${f.join(", ")}`)}}},n=(o,s="root",a=0)=>{if(!(a>10))for(let[l,c]of Object.entries(o)){let d=`${s}.${l}`;Array.isArray(c)?t(c,d):typeof c=="object"&&c!==null&&n(c,d,a+1)}};n(e)}};var un=process.env.DOORMAN_VERCEL_API_BASE_URL||"https://api.vercel.com/v1/security/firewall/config",Ze=class extends _e{constructor(t,n,o){super("","vercel");this.projectId=t;this.teamId=n;this.token=o}projectId;teamId;token;async getAuthHeaders(){return{Authorization:`Bearer ${this.token}`}}getUrl(t){let n=t!==void 0?`${un}/${t}`:un;r.debug("API URL:",n);let o=this.teamId?`&teamId=${this.teamId}`:"";return`${n}?projectId=${this.projectId}${o}`}async fetchFirewallConfig(t,n){let{allowCreate:o=!0}=n??{},s=await this.get(this.getUrl(t));if(r.debug("Config Version:",t??"latest"),r.debug("Fetched Config:",t?s:s.active),t)return s;if(!s||"active"in s&&s.active===null){if(r.warn(ei.default.bold("No firewall configuration found.")),!o)return r.debug("Skipping create-configuration prompt for read-only fetch."),this.emptyConfigPlaceholder();if(await C("Would you like to create one?",{type:"confirm"})){r.debug("Creating new empty firewall configuration...");let l=await this.putEmptyConfig();return r.debug("Empty firewall configuration created successfully"),r.debug(`New configuration version: ${ei.default.yellow(l.version)}`),l}else return s.active}return s.active}emptyConfigPlaceholder(){return{version:0,id:"",firewallEnabled:!0,crs:null,rules:[],ips:[],projectKey:"",ownerId:"",updatedAt:new Date(0).toISOString()}}async putEmptyConfig(){let{$schema:t,...n}=Ut();return r.debug("Empty Config:",n),this.putConfig(n)}async putConfig(t){return(await this.put(this.getUrl(),t)).active}async fetchActiveFirewallRules(){return(await this.fetchFirewallConfig())?.rules}async updateFirewallRule(t){let n=!t.id||t.id==="-",o={action:n?"rules.insert":"rules.update",id:n?null:t.id,value:{name:t.name,description:t.description,action:t.action,conditionGroup:t.conditionGroup,active:t.active}};return this.patch(this.getUrl(),o,n?{retries:0}:void 0)}async createFirewallRule(t){return this.updateFirewallRule({...t,id:"-"})}async deleteFirewallRule(t){let n={action:"rules.remove",id:t.id,value:null};await this.patch(this.getUrl(),n)}async updateIPBlockingRule(t){let n=!t.id||t.id==="-",o={action:n?"ip.insert":"ip.update",id:n?null:t.id,value:{action:t.action,hostname:t.hostname,ip:t.ip,notes:t.notes??null}};return this.patch(this.getUrl(),o,n?{retries:0}:void 0)}async createIPBlockingRule(t){return this.updateIPBlockingRule({...t,id:"-"})}async deleteIPBlockingRule(t){let n={action:"ip.remove",id:t.id,value:null};await this.patch(this.getUrl(),n)}async verifyCredentials(){try{return await this.fetchFirewallConfig(),!0}catch(t){return r.debug("Credential verification failed:",t),!1}}};var fe=O(require("chalk"));_();_();var ke=class{validateConfig(e){let t=[],n=[];if(e||t.push({path:"root",message:"Configuration is required",code:"CONFIG_REQUIRED"}),e&&!e.rules&&t.push({path:"rules",message:"Rules array is required",code:"RULES_REQUIRED"}),e&&e.rules&&!Array.isArray(e.rules)&&t.push({path:"rules",message:"Rules must be an array",code:"RULES_INVALID_TYPE"}),e&&Array.isArray(e.rules)){let o=new Map;e.rules.forEach((s,a)=>{let l=s.id||s.name;if(!l)return;let c=o.get(l);if(c===void 0){o.set(l,a);return}let d=e.rules[c];n.push({path:`rules[${a}]`,message:s.id?`Rule "${s.name}" has the same id ("${s.id}") as rules[${c}] ("${d?.name}") \u2014 they will collide during sync diffing and one may be silently skipped.`:`Rule "${s.name}" has the same name as rules[${c}] and neither has an explicit id \u2014 they will collide during sync diffing and one may be silently skipped. Add unique ids to disambiguate.`,code:"DUPLICATE_RULE_IDENTIFIER"})})}return{valid:t.length===0,errors:t,warnings:n}}getHealthScore(e){let t=[],n=100;if(!e)return{score:0,grade:"poor",issues:[{severity:"error",category:"configuration",message:"No configuration found"}],recommendations:["Create a configuration file"]};if((!e.rules||e.rules.length===0)&&(n-=20,t.push({severity:"warning",category:"rules",message:"No rules defined",suggestion:"Add security rules to protect your application"})),e.rules&&e.rules.length>0){let a=e.rules.filter(l=>!l.description);a.length>0&&(n-=5,t.push({severity:"info",category:"maintainability",message:`${a.length} rule(s) missing descriptions`,suggestion:"Add descriptions to improve maintainability"}))}if(e.rules&&e.rules.length>0){let a=e.rules.filter(l=>l.active===!1||l.enabled===!1);a.length>0&&(n-=5,t.push({severity:"info",category:"maintenance",message:`${a.length} disabled rule(s) found`,suggestion:"Review and remove unused rules"}))}let o=this.calculateGrade(n),s=this.generateRecommendations(e,t);return{score:Math.max(0,n),grade:o,issues:t,recommendations:s}}diffItems(e,t,n,o="id"){let s=[],a=[],l=[],c=d=>d[o];for(let d of e){let u=t.find(p=>c(p)===c(d));u?n(d,u)||a.push(d):s.push(d)}for(let d of t)e.find(p=>c(p)===c(d))||l.push(d);return{toAdd:s,toUpdate:a,toDelete:l}}assertManagedRulesSupported(e){if(!(!e.managedRules||e.managedRules.length===0)&&!this.getSupportedFeatures().supportsManagedRules)throw new Error(`${this.name} does not support managed rule groups, but this config declares ${e.managedRules.length}. Remove \`managedRules\` from the config, or switch to a provider that supports them (currently Cloudflare only).`)}validateRuleCount(e,t){if(t&&e>t)throw new Error(`Rule count (${e}) exceeds provider limit (${t})`);t&&e>t*.9&&r.warn(`Approaching rule limit: ${e}/${t} rules`)}updateMetadata(e,t){return{...e,metadata:{...e.metadata,version:t,updatedAt:new Date().toISOString()}}}calculateGrade(e){return e>=80?"excellent":e>=60?"good":e>=40?"fair":"poor"}generateRecommendations(e,t){let n=[];return t.some(o=>o.category==="rules")&&n.push("Consider using predefined templates to get started"),t.some(o=>o.category==="maintainability")&&n.push("Add descriptions to all rules for better documentation"),t.some(o=>o.category==="maintenance")&&n.push("Remove or enable disabled rules to keep configuration clean"),n}logSyncStats(e){r.info(`Sync complete for ${this.name}:`),r.info(` Rules added: ${e.rulesAdded}`),r.info(` Rules updated: ${e.rulesUpdated}`),r.info(` Rules deleted: ${e.rulesDeleted}`),e.ipsAdded!==void 0&&(r.info(` IPs added: ${e.ipsAdded}`),r.info(` IPs updated: ${e.ipsUpdated}`),r.info(` IPs deleted: ${e.ipsDeleted}`)),e.managedRulesAdded!==void 0&&(r.info(` Managed rule groups added: ${e.managedRulesAdded}`),r.info(` Managed rule groups updated: ${e.managedRulesUpdated}`),r.info(` Managed rule groups deleted: ${e.managedRulesDeleted}`)),e.version&&r.info(` New version: ${e.version}`)}};pe();function pn(i){let e=[],t=[];i.conditionGroup.forEach((s,a)=>{for(let l of s.conditions){let c=dr(l.op);t.push({field:pr(l.type),operator:c,...l.value!==void 0?{value:l.value}:{},...l.neg?{negated:!0}:{},...l.key?{key:l.key}:{},group:a})}}),t.length>10&&e.push(U.createWarning("many_conditions",i.id,void 0,`Rule has ${t.length} conditions which may impact performance`,"Consider splitting complex rules into multiple simpler rules for better performance"));let n={type:i.action.mitigate.action,...i.action.mitigate.rateLimit?{rateLimit:{requests:i.action.mitigate.rateLimit.requests,window:i.action.mitigate.rateLimit.window,...i.action.mitigate.rateLimit.characteristics?{characteristics:i.action.mitigate.rateLimit.characteristics}:{},...i.action.mitigate.rateLimit.mitigationTimeout?{mitigationTimeout:i.action.mitigate.rateLimit.mitigationTimeout}:{},...i.action.mitigate.rateLimit.countingExpression?{countingExpression:i.action.mitigate.rateLimit.countingExpression}:{}}}:{},...i.action.mitigate.redirect?{redirect:{location:i.action.mitigate.redirect.location,...i.action.mitigate.redirect.permanent?{permanent:i.action.mitigate.redirect.permanent}:{}}}:{},...i.action.mitigate.actionDuration?{duration:i.action.mitigate.actionDuration}:{}};return{result:{id:i.id,name:i.name,...i.description?{description:i.description}:{},enabled:i.active,conditions:t,conditionLogic:i.conditionGroup.length>1?"OR":"AND",action:n},warnings:e}}function fn(i){let e=[],t=cr(i.conditions,i.id,e);if(i.action.response&&e.push(U.createUnsupportedFeatureWarning("custom response body","unified config","Vercel",i.id,"action.response")),t.length===0)throw new Error(`Rule "${i.name}" has no conditions Vercel can represent \u2014 every condition field is unsupported by Vercel (e.g. Cloudflare-only fields like referer/port). Cannot sync this rule to Vercel.`);return{result:{id:i.id,name:i.name,description:i.description,conditionGroup:t,action:{mitigate:{action:i.action.type,rateLimit:i.action.rateLimit?{requests:i.action.rateLimit.requests,window:i.action.rateLimit.window,characteristics:i.action.rateLimit.characteristics,mitigationTimeout:i.action.rateLimit.mitigationTimeout,countingExpression:i.action.rateLimit.countingExpression}:null,redirect:i.action.redirect?{location:i.action.redirect.location,permanent:i.action.redirect.permanent}:null,actionDuration:i.action.duration||null}},active:i.enabled},warnings:e}}function gn(i){return{id:i.id,ip:i.ip,...i.hostname?{hostname:i.hostname}:{},...i.notes?{notes:i.notes}:{},action:i.action}}function cr(i,e,t){let n=new Map;for(let s of i){let a=s.group??0,l=n.get(a);l?l.push(s):n.set(a,[s])}let o=[];for(let s of n.values()){let a=[];for(let l of s){let c=gr(l.field);if(!c){t.push(U.createUnsupportedFeatureWarning(`condition field '${l.field}'`,"unified config","Vercel",e,l.field));continue}let d=ur(l.operator);if(!d){t.push(U.createUnsupportedFeatureWarning(`operator '${l.operator}'`,"unified config","Vercel",e,l.field));continue}a.push({op:d.op,neg:d.forceNeg!==!!l.negated,type:c,key:l.key,value:l.value})}a.length>0&&o.push({conditions:a})}return o}function dr(i){return{eq:"eq",pre:"starts_with",suf:"ends_with",inc:"in",sub:"contains",re:"matches",ex:"exists",nex:"not_exists"}[i]||"eq"}function ur(i){return{eq:{op:"eq",forceNeg:!1},ne:{op:"eq",forceNeg:!0},starts_with:{op:"pre",forceNeg:!1},ends_with:{op:"suf",forceNeg:!1},in:{op:"inc",forceNeg:!1},not_in:{op:"inc",forceNeg:!0},contains:{op:"sub",forceNeg:!1},not_contains:{op:"sub",forceNeg:!0},matches:{op:"re",forceNeg:!1},exists:{op:"ex",forceNeg:!1},not_exists:{op:"nex",forceNeg:!1}}[i]??null}function pr(i){return{host:"host",path:"path",method:"method",header:"header",query:"query",cookie:"cookie",ip_address:"ip",user_agent:"user_agent",geo_country:"country",geo_city:"city",geo_as_number:"asn",scheme:"scheme",region:"vercel_region",geo_country_region:"region"}[i]||i}var fr={host:"host",path:"path",method:"method",header:"header",query:"query",cookie:"cookie",ip:"ip_address",user_agent:"user_agent",country:"geo_country",city:"geo_city",asn:"geo_as_number",scheme:"scheme",target_path:"target_path",vercel_region:"region",region:"geo_country_region",protocol:"protocol",environment:"environment",geo_continent:"geo_continent",ja4_digest:"ja4_digest",ja3_digest:"ja3_digest",rate_limit_api_id:"rate_limit_api_id"};function gr(i){return fr[i]??null}pe();_();function Ye(i){return i.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}function mn(i){let e="";for(let t=0;t<i.length;t++)i[t]==="\\"&&(i[t+1]==="\\"||i[t+1]==='"')?(e+=i[t+1],t++):e+=i[t];return e}var pt=class{static vercelToCloudflare={host:"http.host",path:"http.request.uri.path",method:"http.request.method",header:"http.request.headers",query:"http.request.uri.query",cookie:"http.cookie",target_path:"http.request.uri.path",ip_address:"ip.src",region:"ip.geoip.subdivision_1",protocol:"ssl",scheme:"ssl",environment:"",user_agent:"http.user_agent",geo_continent:"ip.geoip.continent",geo_country:"ip.geoip.country",geo_country_region:"ip.geoip.subdivision_1",geo_city:"ip.geoip.city",geo_as_number:"ip.geoip.asnum",ja4_digest:"",ja3_digest:"",rate_limit_api_id:""};static cloudflareToVercel={"http.host":"host","http.request.uri.path":"path","http.request.method":"method","http.request.headers":"header","http.request.uri.query":"query","http.cookie":"cookie","ip.src":"ip_address","ip.geoip.subdivision_1":"region",ssl:"protocol","http.user_agent":"user_agent","ip.geoip.continent":"geo_continent","ip.geoip.country":"geo_country","ip.geoip.city":"geo_city","ip.geoip.asnum":"geo_as_number"};static toCloudflare(e,t){let n=this.vercelToCloudflare[e];if(!n)throw r.warn(`No Cloudflare mapping for Vercel type: ${e}`),new Error(`Unsupported Vercel rule type for Cloudflare: ${e}`);return e==="header"&&t?`${n}["${Ye(t.toLowerCase())}"]`:e==="cookie"&&t?`${n}["${Ye(t)}"]`:n}static toVercel(e){let t=e.match(/http\.request\.headers\["([^"]+)"\]/);if(t)return{type:"header",key:t[1]};let n=e.match(/http\.cookie\["([^"]+)"\]/);if(n)return{type:"cookie",key:n[1]};if(e==="http.referer")return{type:"header",key:"referer"};let o=this.cloudflareToVercel[e];if(o)return{type:o};throw r.warn(`No Vercel mapping for Cloudflare field: ${e}`),new Error(`Unsupported Cloudflare field for Vercel: ${e}`)}static isCloudflareSupported(e){let t=this.vercelToCloudflare[e];return!!t&&t!==""}static isVercelSupported(e){return!!(this.cloudflareToVercel[e]||e==="http.referer"||e.startsWith("http.request.headers[")||e.startsWith("http.cookie["))}static getSupportedCloudflareFields(){return Object.values(this.vercelToCloudflare).filter(e=>e!=="")}static getSupportedVercelTypes(){return Object.keys(this.vercelToCloudflare).filter(e=>this.isCloudflareSupported(e))}};var mr=new Set(["ip.src"]),hr="http.request.uri.args",yr="http.request.headers",Rr="http.request.cookies",Je=class{static fromVercelConditionGroups(e){if(!e||e.length===0)throw new Error("At least one condition group is required");let t=e.map(n=>{let o=n.conditions.map(s=>this.fromVercelCondition(s));if(o.length===0)throw new Error("Condition group must have at least one condition");return o.length>1?`(${o.join(" and ")})`:o[0]});return t.length>1?t.join(" or "):t[0]}static fromVercelCondition(e){let t=pt.toCloudflare(e.type,e.key),n=this.buildExistsOrComparisonExpression(t,e.op,()=>`${this.mapVercelOperator(e.op)} ${this.formatValue(t,e.value)}`);return e.neg&&(n=`not (${n})`),n}static fromUnifiedConditions(e,t="AND"){if(!e||e.length===0)throw new Error("At least one condition is required");if(!e.some(a=>a.group!==void 0)){let a=e.map(c=>this.fromUnifiedCondition(c)),l=t==="AND"?" and ":" or ";return a.length>1?`(${a.join(l)})`:a[0]}let o=new Map;for(let a of e){let l=a.group??0,c=o.get(l);c?c.push(a):o.set(l,[a])}let s=Array.from(o.values()).map(a=>this.combineWithAnd(a.map(l=>this.fromUnifiedCondition(l))));return s.length>1?this.combineWithOr(s):s[0]}static fromUnifiedCondition(e){if(e.key&&e.field==="query")return this.buildKeyedMapExpression(hr,e.key,e);if(e.field==="header"){if(!e.key)throw new Error('A "header" condition requires a key naming the header');return this.buildKeyedMapExpression(yr,e.key.toLowerCase(),e)}if(e.key&&e.field==="cookie")return this.buildKeyedMapExpression(Rr,e.key,e);let t=this.mapUnifiedFieldToCloudflare(e.field);if(t===null)throw new Error(`Unsupported condition field '${e.field}' for Cloudflare \u2014 filter it out with a warning before calling fromUnifiedCondition (see unifiedToCloudflare).`);let n=this.buildUnifiedExpression(t,e.operator,e.value);return e.negated&&(n=`not (${n})`),n}static buildKeyedMapExpression(e,t,n){let o=Ye(t),s=`${e}["${o}"]`,a;if(n.operator==="exists")a=`has_key(${e}, "${o}")`;else if(n.operator==="not_exists")a=`not (has_key(${e}, "${o}"))`;else if(n.operator==="not_contains")a=`not (any(${s}[*] contains ${this.formatValue(e,n.value)}))`;else if(n.operator==="not_in")a=`not (any(${s}[*] in ${this.formatValue(e,n.value)}))`;else{let l=this.mapUnifiedOperator(n.operator);a=`any(${s}[*] ${l} ${this.formatValue(e,n.value)})`}return n.negated?`not (${a})`:a}static buildExistsOrComparisonExpression(e,t,n){return t==="ex"||t==="exists"?`${e} exists`:t==="nex"||t==="not_exists"?`not (${e} exists)`:`${e} ${n()}`}static buildUnifiedExpression(e,t,n){return t==="not_contains"?`not (${e} contains ${this.formatValue(e,n)})`:t==="not_in"?`not (${e} in ${this.formatValue(e,n)})`:this.buildExistsOrComparisonExpression(e,t,()=>`${this.mapUnifiedOperator(t)} ${this.formatValue(e,n)}`)}static mapVercelOperator(e){return{eq:"eq",pre:"starts_with",suf:"ends_with",inc:"in",sub:"contains",re:"matches",ex:"exists"}[e]||e}static mapUnifiedOperator(e){return{eq:"eq",ne:"ne",contains:"contains",starts_with:"starts_with",ends_with:"ends_with",matches:"matches",in:"in",gt:"gt",ge:"ge",lt:"lt",le:"le",exists:"exists"}[e]||e}static mapUnifiedFieldToCloudflare(e){return{ip:"ip.src",country:"ip.geoip.country",region:"ip.geoip.subdivision_1",city:"ip.geoip.city",asn:"ip.geoip.asnum",path:"http.request.uri.path",host:"http.host",method:"http.request.method",header:"http.request.headers",query:"http.request.uri.query",cookie:"http.cookie",user_agent:"http.user_agent",referer:"http.referer",scheme:"ssl",port:"cf.edge.server_port",threat_score:"cf.threat_score"}[e]??null}static isFieldSupported(e){return this.mapUnifiedFieldToCloudflare(e)!==null}static formatValue(e,t){return Array.isArray(t)?`{${t.map(o=>this.formatSingleValue(e,o)).join(" ")}}`:this.formatSingleValue(e,t)}static formatSingleValue(e,t){if(typeof t=="string"){if(mr.has(e)){if(!Pe.safeParse(t).success)throw new Error(`Invalid IP address or CIDR range: ${t}`);return t}return`"${Ye(t)}"`}return typeof t=="number"?String(t):typeof t=="boolean"?t?"true":"false":String(t)}static validate(e){if(!e||e.trim().length===0)return!1;let t=0;for(let n of e)if(n==="("&&t++,n===")"&&t--,t<0)return!1;return t===0}static combineWithAnd(e){if(e.length===0)throw new Error("At least one expression is required");return e.length===1?e[0]:`(${e.join(" and ")})`}static combineWithOr(e){if(e.length===0)throw new Error("At least one expression is required");return e.length===1?e[0]:`(${e.join(" or ")})`}};var j=class extends Error{},Qe=class{constructor(e){this.tokens=e}tokens;pos=0;peek(){return this.tokens[this.pos]}next(){let e=this.tokens[this.pos];if(!e)throw new j("Unexpected end of expression");return this.pos++,e}expect(e){let t=this.next();if(t.type!==e)throw new j(`Expected ${e} but got '${t.value}'`);return t}is(e,t){let n=this.peek();return!!n&&n.type===e&&n.value===t}atEnd(){return this.pos>=this.tokens.length}};function Lt(i,e,t,n){let o=[];i.forEach((l,c)=>{if(e(l)){let d=n(l,c);d&&o.push(d);return}if(t(l))for(let d of l.children){if(!e(d))return;let u=n(d,c);u&&o.push(u)}});let s=i.length;return new Set(o.map(l=>l.group)).size!==s?null:{conditions:o,conditionLogic:"OR"}}var wr={"ip.src":"ip","ip.geoip.country":"country","ip.geoip.subdivision_1":"region","ip.geoip.city":"city","ip.geoip.asnum":"asn","http.request.uri.path":"path","http.host":"host","http.request.method":"method","http.request.headers":"header","http.request.uri.query":"query","http.request.uri.args":"query","http.cookie":"cookie","http.request.cookies":"cookie","http.user_agent":"user_agent","http.referer":"referer",ssl:"scheme","cf.edge.server_port":"port","cf.threat_score":"threat_score"},wn=new Set(["eq","ne","contains","starts_with","ends_with","matches","in","gt","ge","lt","le"]),Cr=new Set(["and","or","not"]);function br(i){let e=[],t=0,n=i.length;for(;t<n;){let o=i[t];if(/\s/.test(o)){t++;continue}if(o===","){t++;continue}if(o==="("){e.push({type:"LPAREN",value:o}),t++;continue}if(o===")"){e.push({type:"RPAREN",value:o}),t++;continue}if(o==="{"){e.push({type:"LBRACE",value:o}),t++;continue}if(o==="}"){e.push({type:"RBRACE",value:o}),t++;continue}if(o==="["){e.push({type:"LBRACKET",value:o}),t++;continue}if(o==="]"){e.push({type:"RBRACKET",value:o}),t++;continue}if(o==='"'){let a=t+1,l="",c=!1;for(;a<n;){let d=i[a];if(d==="\\"&&a+1<n){l+=d+i[a+1],a+=2;continue}if(i[a]==='"'){c=!0,a++;break}l+=i[a],a++}if(!c)throw new j("Unterminated string literal");e.push({type:"STRING",value:mn(l)}),t=a;continue}let s=t;for(;s<n&&!/[\s(){}[\]",]/.test(i[s]);)s++;if(s===t)throw new j(`Unexpected character '${o}' at position ${t}`);e.push({type:"WORD",value:i.slice(t,s)}),t=s}return e}function ni(i){let e=[hn(i)];for(;i.is("WORD","or");)i.next(),e.push(hn(i));return e.length===1?e[0]:{type:"or",children:e}}function hn(i){let e=[yn(i)];for(;i.is("WORD","and");)i.next(),e.push(yn(i));return e.length===1?e[0]:{type:"and",children:e}}function yn(i){if(i.is("WORD","not")){i.next(),i.expect("LPAREN");let e=ni(i);return i.expect("RPAREN"),{type:"not",child:e}}return vr(i)}function vr(i){let e=i.peek();if(e&&e.type==="LPAREN"){i.next();let t=ni(i);return i.expect("RPAREN"),t}return Ar(i)}function Ir(i){let e=i.expect("WORD");if(Cr.has(e.value))throw new j(`Expected a field reference but got keyword '${e.value}'`);if(i.peek()?.type==="LBRACKET"){i.next();let t=i.expect("STRING");return i.expect("RBRACKET"),{field:e.value,key:t.value}}return{field:e.value}}function Rn(i){let e=i.next();if(e.type==="STRING")return e.value;if(e.type==="WORD")return/^-?\d+(\.\d+)?$/.test(e.value)?Number(e.value):e.value;throw new j(`Unexpected token '${e.value}' where a value was expected`)}function Cn(i){if(i.peek()?.type==="LBRACE"){i.next();let e=[];for(;i.peek()?.type!=="RBRACE";)e.push(Rn(i));return i.expect("RBRACE"),e}return Rn(i)}function Er(i){i.next(),i.expect("LPAREN");let e=i.expect("WORD");i.expect("LBRACKET");let t=i.expect("STRING");i.expect("RBRACKET"),i.expect("LBRACKET");let n=i.expect("WORD");if(n.value!=="*")throw new j(`Expected '*' in any(...) array index but got '${n.value}'`);i.expect("RBRACKET");let o=i.next();if(o.type!=="WORD"||!wn.has(o.value))throw new j(`Unsupported or unrecognized operator '${o.value}' inside any(...)`);let s=Cn(i);return i.expect("RPAREN"),{type:"comparison",field:e.value,key:t.value,operator:o.value,value:s}}function Tr(i){i.next(),i.expect("LPAREN");let e=i.expect("WORD"),t=i.expect("STRING");return i.expect("RPAREN"),{type:"exists",field:e.value,key:t.value}}function Ar(i){if(i.is("WORD","any"))return Er(i);if(i.is("WORD","has_key"))return Tr(i);let{field:e,key:t}=Ir(i);if(i.is("WORD","exists"))return i.next(),{type:"exists",field:e,key:t};let n=i.next();if(n.type!=="WORD"||!wn.has(n.value))throw new j(`Unsupported or unrecognized operator '${n.value}'`);let o=Cn(i);return{type:"comparison",field:e,key:t,operator:n.value,value:o}}function $r(i){let e=new Qe(br(i)),t=ni(e);if(!e.atEnd())throw new j("Unexpected trailing tokens");return t}function ti(i){return i.type==="comparison"||i.type==="exists"?!0:i.type==="not"?i.child.type==="comparison"||i.child.type==="exists":!1}function ii(i,e){let t=i.type==="not",n=i.type==="not"?i.child:i;if(n.type!=="comparison"&&n.type!=="exists")return null;let o=wr[n.field];if(!o)return null;if(n.type==="exists")return{field:o,operator:t?"not_exists":"exists",value:"",key:n.key,group:e};let s=n.operator,a;return t&&(n.operator==="contains"?s="not_contains":n.operator==="in"?s="not_in":a=!0),{field:o,operator:s,value:n.value,key:n.key,negated:a,group:e}}function Pr(i){if(ti(i)){let e=ii(i,0);return e?{conditions:[e],conditionLogic:"AND"}:null}if(i.type==="and"){let e=[];for(let t of i.children){if(!ti(t))return null;let n=ii(t,0);if(!n)return null;e.push(n)}return{conditions:e,conditionLogic:"AND"}}return i.type==="or"?Lt(i.children,ti,e=>e.type==="and",ii):null}function oi(i){if(!i||!i.trim())return null;try{let e=$r(i);return Pr(e)}catch{return null}}function In(i){let e=[],t=oi(i.expression),n=[],o;t?(n=t.conditions,o=t.conditionLogic):e.push(U.createWarning("complex_expressions",i.id,"expression","Expression could not be parsed back into structured conditions \u2014 it may be hand-authored or use syntax outside what doorman itself generates.","Review the translated rule and add missing conditions manually if needed."));let s=i.action_parameters&&"response"in i.action_parameters?i.action_parameters.response:void 0,a={type:_r(i.action),rateLimit:i.ratelimit?{requests:i.ratelimit.requests_per_period,window:`${i.ratelimit.period}s`,characteristics:i.ratelimit.characteristics,mitigationTimeout:i.ratelimit.mitigation_timeout,countingExpression:i.ratelimit.counting_expression}:void 0,response:s?{statusCode:s.status_code,content:s.content,contentType:s.content_type}:void 0};return{result:{id:i.id,name:i.description||`Rule ${i.id}`,description:i.description,enabled:i.enabled??!0,conditions:n,conditionLogic:o,action:a},warnings:e}}function En(i){let e=[],t=Sr(i.conditions,i.id,e);if(t.length===0)throw new Error(`Rule "${i.name}" has no conditions Cloudflare can represent \u2014 every condition field is unsupported by Cloudflare (e.g. Vercel-only fields like environment/ja3_digest, or vercel_region). Cannot sync this rule to Cloudflare.`);let n=Je.fromUnifiedConditions(t,i.conditionLogic),o={id:i.id||crypto.randomUUID(),action:kr(i.action.type),expression:n,description:i.description||i.name,enabled:i.enabled};return i.action.rateLimit&&(o.ratelimit={characteristics:i.action.rateLimit.characteristics||["ip.src"],period:Ur(i.action.rateLimit.window),requests_per_period:i.action.rateLimit.requests},i.action.rateLimit.mitigationTimeout?o.ratelimit.mitigation_timeout=i.action.rateLimit.mitigationTimeout:(o.ratelimit.mitigation_timeout=3600,e.push(U.createWarning("rate_limiting_precision",i.id,"action.rateLimit.mitigationTimeout",`No mitigationTimeout set for rate limit rule "${i.name}" \u2014 defaulted to Cloudflare's 3600s (1 hour) block duration.`))),i.action.rateLimit.countingExpression&&(o.ratelimit.counting_expression=i.action.rateLimit.countingExpression)),i.action.type==="redirect"&&i.action.redirect&&(o.action_parameters={from_value:{status_code:i.action.redirect.statusCode??(i.action.redirect.permanent?301:302),target_url:{value:i.action.redirect.location},...i.action.redirect.preserveQueryString!==void 0?{preserve_query_string:i.action.redirect.preserveQueryString}:{}}}),i.action.response&&(o.action!=="block"?e.push(U.createUnsupportedFeatureWarning(`custom response on a '${o.action}' action`,"unified config","Cloudflare",i.id,"action.response")):i.action.response.content?o.action_parameters={response:{status_code:i.action.response.statusCode??403,content:i.action.response.content,content_type:i.action.response.contentType??"text/plain"}}:e.push(U.createWarning("lossy_conversion",i.id,"action.response","Custom response declared without `content`; Cloudflare requires a response body, so it was dropped.","Set action.response.content to the body you want returned."))),{result:o,warnings:e}}function Tn(i){if(!Pe.safeParse(i.ip).success)throw new Error(`Invalid IP address or CIDR range: ${i.ip}`);let t=i.ip.includes("/")?`ip.src in {${i.ip}}`:`ip.src eq ${i.ip}`;return{id:i.id||crypto.randomUUID(),action:i.action==="allow"?"allow":"block",expression:t,description:i.notes||`IP ${i.action}: ${i.ip}${i.hostname?` (${i.hostname})`:""}`,enabled:!0}}function An(i){let e=[],t={};if(i.action){let{action:s,warning:a}=bn(i.action,i.id,"action");a&&e.push(a),s&&(t.action=s)}i.overrides&&i.overrides.length>0&&(t.rules=i.overrides.map(s=>{let a={id:s.ruleId};if(s.enabled!==void 0&&(a.enabled=s.enabled),s.action){let{action:l,warning:c}=bn(s.action,i.id,`overrides[${s.ruleId}].action`);c&&e.push(c),l&&(a.action=l)}return a}));let n={id:i.ruleset,...Object.keys(t).length>0?{overrides:t}:{}};return{result:{id:i.id||crypto.randomUUID(),action:"execute",expression:"true",description:i.name||`Managed ruleset ${i.ruleset}`,enabled:i.enabled,action_parameters:n},warnings:e}}function $n(i){let e=[],t=i.action_parameters,n={id:i.id,ruleset:t?.id??"",enabled:i.enabled??!0,...i.description?{name:i.description}:{}};return t?.overrides?.action&&(n.action=vn(t.overrides.action)),t?.overrides?.rules&&t.overrides.rules.length>0&&(n.overrides=t.overrides.rules.map(o=>({ruleId:o.id,...o.action!==void 0?{action:vn(o.action)}:{},...o.enabled!==void 0?{enabled:o.enabled}:{}}))),{result:n,warnings:e}}function bn(i,e,t){let o={log:"log",deny:"block",block:"block",challenge:"managed_challenge",allow:"allow"}[i];return o?{action:o}:{warning:U.createUnsupportedFeatureWarning(`'${i}' as a managed-ruleset override action`,"unified config","Cloudflare",e,t)}}function vn(i){return{log:"log",block:"deny",challenge:"challenge",managed_challenge:"challenge",js_challenge:"challenge",allow:"allow"}[i]||"deny"}function _r(i){return{block:"deny",challenge:"challenge",managed_challenge:"challenge",js_challenge:"challenge",log:"log",skip:"bypass",allow:"allow",rewrite:"bypass",redirect:"redirect",execute:"log"}[i]||"deny"}function kr(i){return{log:"log",deny:"block",block:"block",challenge:"managed_challenge",bypass:"skip",rate_limit:"block",redirect:"redirect",allow:"allow"}[i]||"block"}function Sr(i,e,t){return i.filter(n=>Je.isFieldSupported(n.field)?!0:(t.push(U.createUnsupportedFeatureWarning(`condition field '${n.field}'`,"unified config","Cloudflare",e,n.field)),!1))}function Ur(i){let e=i.match(/^(\d+)([smhd])$/);if(!e||!e[1]||!e[2])throw new Error(`Invalid window format: ${i}`);let t=parseInt(e[1],10),n=e[2],s={s:1,m:60,h:3600,d:86400}[n];if(s===void 0)throw new Error(`Invalid time unit: ${n}`);return t*s}pe();function we(i,e,t){return U.createWarning("complex_expressions",i,void 0,e,t)}var Lr={ip:"ip",path:"path",domain:"host",method:"method",country:"country",scheme:"scheme",user_agent:"user_agent"},xr={ip:"ip",path:"path",host:"domain",method:"method",country:"country",scheme:"scheme",user_agent:"user_agent"},Dr={header:"request_header",query:"query_parameter",cookie:"request_cookie"},Or={request_header:"header",query_parameter:"query",request_cookie:"cookie",response_header:"response_header",post_parameter:"post_parameter",signal:"signal"};function Fr(i){return Lr[i]??i}function Nr(i){return xr[i]??null}var Vr={equals:{operator:"eq",negated:!1},does_not_equal:{operator:"eq",negated:!0},contains:{operator:"contains",negated:!1},does_not_contain:{operator:"contains",negated:!0},like:{operator:"matches",negated:!1},not_like:{operator:"matches",negated:!0},in_list:{operator:"in",negated:!1},not_in_list:{operator:"in",negated:!0},matches:{operator:"matches",negated:!1},does_not_match:{operator:"matches",negated:!0},greater_equal:{operator:"ge",negated:!1},lesser_equal:{operator:"le",negated:!1}};function Pn(i){return Vr[i]??{operator:"eq",negated:!1}}function _n(i,e,t,n){switch(i){case"eq":return e?"does_not_equal":"equals";case"ne":return"does_not_equal";case"contains":return e?"does_not_contain":"contains";case"not_contains":return"does_not_contain";case"starts_with":case"ends_with":return t.push(U.createWarning("regex_patterns",n,void 0,`Unified operator "${i}" has no direct Fastly equivalent \u2014 mapped to Fastly's wildcard "like" operator. Verify the translated value still matches as a wildcard pattern.`)),e?"not_like":"like";case"matches":return t.push(U.createWarning("regex_patterns",n,void 0,"Regular expression pattern may need adjustment for Fastly Next-Gen WAF regex syntax.")),e?"does_not_match":"matches";case"in":return e?"not_in_list":"in_list";case"not_in":return"not_in_list";case"gt":return t.push(we(n,'Unified operator "gt" (strictly greater than) has no Fastly equivalent \u2014 mapped to "greater_equal", which also matches the boundary value itself.')),"greater_equal";case"ge":return"greater_equal";case"lt":return t.push(we(n,'Unified operator "lt" (strictly less than) has no Fastly equivalent \u2014 mapped to "lesser_equal", which also matches the boundary value itself.')),"lesser_equal";case"le":return"lesser_equal";default:return"equals"}}function Br(i,e){let t=[],n=i.conditions.filter(s=>s.type==="group"),o=i.conditions.filter(s=>s.type!=="group");if(n.length>0&&o.length===0&&i.group_operator==="any")return n.forEach((s,a)=>{s.group_operator!=="all"&&e.push(we(i.id,`A condition group using "any" (OR) inside an outer "any" (OR) rule cannot be represented in doorman's flat AND-within/OR-across condition model \u2014 its members were kept in the same group (effectively AND'd) instead of OR'd. Review the translated rule against the original Fastly logic.`));for(let l of s.conditions)xt(t,l,a,e,i.id)}),{conditions:t,conditionLogic:n.length>1?"OR":"AND"};if(n.length===0)return o.forEach((s,a)=>{let l=i.group_operator==="any"?a:0;xt(t,s,l,e,i.id)}),{conditions:t,conditionLogic:i.group_operator==="any"&&o.length>1?"OR":"AND"};e.push(we(i.id,`Rule mixes top-level conditions with condition groups under group_operator "${i.group_operator}" \u2014 this doesn't fit doorman's flat AND-within/OR-across condition model. All conditions were combined with AND; review the translated rule against the original Fastly rule logic.`));for(let s of o)xt(t,s,0,e,i.id);for(let s of n)for(let a of s.conditions)xt(t,a,0,e,i.id);return{conditions:t,conditionLogic:"AND"}}function xt(i,e,t,n,o){if(e.type==="single"){let{operator:u,negated:p}=Pn(e.operator);i.push({field:Fr(e.field),operator:u,value:e.value,...p?{negated:!0}:{},group:t});return}let s=Or[e.field],a=e.conditions.find(u=>u.field==="name"),l=e.conditions.find(u=>u.field==="value"||u.field==="value_string");if(!l){if(a){i.push({field:s,operator:e.operator==="does_not_exist"?"not_exists":"exists",key:a.value,group:t});return}n.push(we(o,`Multival condition on '${e.field}' has no value or name sub-condition doorman could extract \u2014 skipped.`));return}let{operator:c,negated:d}=Pn(l.operator);i.push({field:s,operator:c,value:l.value,...d?{negated:!0}:{},...a?{key:a.value}:{},group:t})}function Mr(i,e){i.actions.length>1&&e.push(we(i.id,`Rule has ${i.actions.length} actions \u2014 doorman only models one action per rule; only the first ("${i.actions[0]?.type}") was translated.`));let t=i.actions[0];if(i.type==="rate_limit")return{type:"rate_limit",rateLimit:i.rate_limit?{requests:i.rate_limit.threshold,window:`${i.rate_limit.interval}s`,mitigationTimeout:i.rate_limit.duration}:void 0};switch(t?.type){case"block":return{type:"block"};case"allow":return{type:"allow"};case"browser_challenge":case"dynamic_challenge":case"verify_token":return{type:"challenge"};case"redirect":return{type:"redirect",redirect:{location:t.redirect_url??"",...t.response_code!==void 0?{statusCode:t.response_code}:{}}};case"add_signal":case"exclude_signal":case"deception":return e.push(U.createWarning("action_differences",i.id,"action.type",`Fastly action "${t.type}" tags or excludes a WAF signal rather than allowing/blocking the request \u2014 doorman has no matching action type. Mapped to "log" as the closest non-destructive fallback; review this rule.`)),{type:"log"};default:return e.push(we(i.id,`Unrecognised Fastly action type "${String(t?.type)}" \u2014 mapped to "log".`)),{type:"log"}}}function kn(i){let e=[],{conditions:t,conditionLogic:n}=Br(i,e),o=Mr(i,e);return t.length>10&&e.push(U.createWarning("many_conditions",i.id,void 0,`Rule has ${t.length} conditions which may impact performance`)),{result:{id:i.id,name:i.description||i.id,enabled:i.enabled,conditions:t,conditionLogic:n,action:o},warnings:e}}function qr(i,e,t){let n=new Map;for(let a of i){let l=a.group??0,c=n.get(l);c?c.push(a):n.set(l,[a])}let o=a=>{let l=Dr[a.field];if(l){if(a.key===void 0)return t.push(we(e,`Condition on '${a.field}' has no 'key' set \u2014 Fastly needs a specific ${a.field} name to match on (e.g. a header name). Skipped.`)),null;if(a.operator==="exists"||a.operator==="not_exists")return{type:"multival",field:l,operator:a.operator==="exists"?"exists":"does_not_exist",group_operator:"all",conditions:[{type:"single",field:"name",operator:"equals",value:a.key}]};let d=l==="request_header"||l==="response_header"?"value_string":"value";return{type:"multival",field:l,operator:"exists",group_operator:"all",conditions:[{type:"single",field:"name",operator:"equals",value:a.key},{type:"single",field:d,operator:_n(a.operator,a.negated,t,e),value:String(a.value)}]}}if(a.operator==="exists"||a.operator==="not_exists")return t.push(U.createUnsupportedFeatureWarning(`"${a.operator}" on field '${a.field}'`,"unified config","Fastly",e,a.field)),null;let c=Nr(a.field);return c?{type:"single",field:c,operator:_n(a.operator,a.negated,t,e),value:String(a.value)}:(t.push(U.createUnsupportedFeatureWarning(`condition field '${a.field}'`,"unified config","Fastly",e,a.field)),null)};if(n.size<=1){let a=i.map(o).filter(l=>l!==null);return{conditions:a.filter(l=>l.type==="single"),groupConditions:[],multivalConditions:a.filter(l=>l.type==="multival"),groupOperator:"all"}}let s=[];for(let a of n.values()){let l=a.map(o).filter(c=>c!==null);l.length>0&&s.push({type:"group",group_operator:"all",conditions:l})}return{conditions:[],groupConditions:s,multivalConditions:[],groupOperator:"any"}}function Wr(i,e,t){switch(i.type){case"block":case"deny":return{type:"block"};case"allow":return{type:"allow"};case"challenge":return{type:"browser_challenge"};case"bypass":return e.push(we(t,'Unified action "bypass" has no direct Fastly equivalent \u2014 mapped to "allow".')),{type:"allow"};case"log":return e.push(we(t,'Unified action "log" has no dedicated Fastly action \u2014 mapped to "allow" (request_logging is enabled at the rule level regardless).')),{type:"allow"};case"redirect":return i.redirect?{type:"redirect",redirect_url:i.redirect.location,response_code:i.redirect.statusCode??(i.redirect.permanent?301:302)}:(e.push(we(t,'Unified action "redirect" has no redirect target \u2014 mapped to "allow".')),{type:"allow"});case"rate_limit":return{type:"block_signal"};default:return e.push(U.createUnsupportedFeatureWarning(`action type '${i.type}'`,"unified config","Fastly",t,"action.type")),{type:"block"}}}function jr(i,e,t){let n=/^(\d+)\s*(s|m|h)?$/i.exec(i.trim()),o={s:1,m:60,h:3600},s=n?Number(n[1])*o[n[2]?.toLowerCase()||"s"]:NaN,a=[60,600,3600];if(!Number.isFinite(s)||s<=0)return e.push(U.createWarning("rate_limiting_precision",t,void 0,`Rate limit window "${i}" could not be parsed \u2014 defaulted to Fastly's 60-second interval.`)),60;let l=a.reduce((c,d)=>Math.abs(d-s)<Math.abs(c-s)?d:c);return l!==s&&e.push(U.createWarning("rate_limiting_precision",t,void 0,`Rate limit window "${i}" (${s}s) isn't one of Fastly's supported intervals (60s/600s/3600s) \u2014 rounded to ${l}s.`)),l}function zr(i,e,t){if(!i)throw new Error(`Rule "${e??"(unnamed)"}" has action type "rate_limit" but no rateLimit configuration \u2014 Fastly requires a threshold and interval.`);let n=jr(i.window,t,e);return t.push(U.createWarning("rate_limiting_precision",e,void 0,`Fastly rate-limit rules count requests against a named custom signal, which doorman does not create automatically \u2014 create a signal named "doorman-rate-limit-${e??"rule"}" in this workspace before syncing, or the rule will be rejected.`)),{client_identifiers:[{type:"ip"}],duration:i.mitigationTimeout??n,interval:n,signal:`doorman-rate-limit-${e??"rule"}`,threshold:i.requests}}function Sn(i){let e=[];i.description&&i.description!==i.name&&e.push(we(i.id,'Fastly rules have no separate "name" field \u2014 only "description" carries over, and it was set from this rule\'s `name`. The `description` text was dropped rather than merged in, to keep it round-trip-stable on the next sync.'));let{conditions:t,groupConditions:n,multivalConditions:o,groupOperator:s}=qr(i.conditions,i.id,e);if(t.length===0&&n.length===0&&o.length===0)throw new Error(`Rule "${i.name}" has no conditions Fastly can represent \u2014 every condition field is unsupported by Fastly. Cannot sync this rule to Fastly.`);let a=i.action.type==="rate_limit";return{result:{type:a?"rate_limit":"request",description:i.name,enabled:i.enabled,group_operator:s,request_logging:"sampled",conditions:t,group_conditions:n,multival_conditions:o,actions:[Wr(i.action,e,i.id)],rate_limit:a?zr(i.action.rateLimit,i.id,e):null},warnings:e}}function Un(i,e){return i.map(t=>({ip:t,action:e}))}function Ln(i){let e=[],t=[],n=[];for(let o of i)(o.hostname||o.notes)&&e.push(we(o.id,`IP rule "${o.ip}" sets ${o.hostname?"`hostname`":""}${o.hostname&&o.notes?" and ":""}${o.notes?"`notes`":""} \u2014 Fastly IP lists have no equivalent per-entry metadata; only the address was kept.`)),(o.action==="allow"?n:t).push(o.ip);return{result:{denyEntries:t,allowEntries:n},warnings:e}}pe();function Xe(i){return i.replace(/\\/g,"\\\\").replace(/'/g,"\\'")}function xn(i){let e="";for(let t=0;t<i.length;t++)i[t]==="\\"&&(i[t+1]==="\\"||i[t+1]==="'")?(e+=i[t+1],t++):e+=i[t];return e}var Gr=new Set(["region","city","port","scheme"]),Kr={host:"host",user_agent:"user-agent",referer:"referer"},ft=class{static fromUnifiedConditions(e,t="AND"){if(!e||e.length===0)throw new Error("At least one condition is required");if(!e.some(a=>a.group!==void 0)){let a=e.map(c=>this.fromUnifiedCondition(c)),l=t==="AND"?" && ":" || ";return a.length>1?`(${a.join(l)})`:a[0]}let o=new Map;for(let a of e){let l=a.group??0,c=o.get(l);c?c.push(a):o.set(l,[a])}let s=Array.from(o.values()).map(a=>this.combineWithAnd(a.map(l=>this.fromUnifiedCondition(l))));return s.length>1?this.combineWithOr(s):s[0]}static fromUnifiedCondition(e){if(Gr.has(e.field))throw new Error(`Cloud Armor has no CEL equivalent for the "${e.field}" condition field \u2014 this rule cannot be represented and would otherwise be silently dropped.`);if(e.field==="ip")return this.buildIpCondition(e);if(e.field==="cookie")return this.buildCookieCondition(e);let t=Kr[e.field];if(t)return this.buildHeaderCondition(t,e);if(e.field==="header"||e.field==="query"){if(e.field==="header"){if(!e.key)throw new Error('A "header" condition requires a key naming the header');return this.buildHeaderCondition(e.key,e)}return this.buildComparison("request.query",e,!1)}let n=Zr[e.field];if(n)return this.buildComparison(n.path,e,n.numeric);throw new Error(`Cloud Armor CEL builder has no mapping for condition field "${e.field}"`)}static buildIpCondition(e){let t=(Array.isArray(e.value)?e.value:[e.value]).map(String);if(t.length===0||t.some(a=>!a))throw new Error('An "ip" condition requires at least one value');let n=t.map(a=>{if(!Pe.safeParse(a).success)throw new Error(`Invalid IP address or CIDR range: ${a}`);return a.includes("/")?`inIpRange(origin.ip, '${Xe(a)}')`:`origin.ip == '${Xe(a)}'`}),o=this.isNegated(e,e.operator==="not_in"||e.operator==="ne");if(n.length>1){let a=`(${n.join(" || ")})`;return o?`!${a}`:a}let s=n[0];return o?s.startsWith("inIpRange(")?`!${s}`:`!(${s})`:s}static buildHeaderCondition(e,t){let n=`request.headers['${Xe(e)}']`,o=`has(${n})`;return t.operator==="exists"||t.operator==="not_exists"?this.isNegated(t,t.operator==="not_exists")?`!${o}`:o:`(${o} && ${this.buildComparison(n,t,!1)})`}static buildCookieCondition(e){let t="request.headers['cookie']",n=`has(${t})`;if(e.operator==="exists"||e.operator==="not_exists")return this.isNegated(e,e.operator==="not_exists")?`!${n}`:n;if(!e.key)return`(${n} && ${this.buildComparison(t,e,!1)})`;if(!["eq","ne","contains","not_contains"].includes(e.operator))throw new Error(`Cloud Armor can only check a specific cookie's value via substring search (has no parsed cookie map) \u2014 "${e.operator}" on a keyed cookie condition has no unambiguous CEL representation.`);let o=`${e.key}=${String(e.value)}`,s=`${t}.contains('${Xe(o)}')`,a=this.isNegated(e,e.operator==="ne"||e.operator==="not_contains");return`(${n} && ${a?`!${s}`:s})`}static buildComparison(e,t,n){let{value:o}=t;if(t.operator==="exists"||t.operator==="not_exists")throw new Error(`"${t.operator}" is not meaningful for Cloud Armor's "${e}" \u2014 it is always present`);let s=t.operator,a=!1;if(t.negated){let d=Hr[s];d?s=d:a=!0}if(s==="in"||s==="not_in"){let u=(Array.isArray(o)?o:[o]).map(f=>this.formatLiteral(f,n)).join(", "),p=`${e} in [${u}]`;return s==="not_in"?`!(${p})`:p}let l=this.formatLiteral(o,n),c;switch(s){case"eq":c=`${e} == ${l}`;break;case"ne":c=`${e} != ${l}`;break;case"contains":c=`${e}.contains(${l})`;break;case"not_contains":c=`!${e}.contains(${l})`;break;case"starts_with":c=`${e}.startsWith(${l})`;break;case"ends_with":c=`${e}.endsWith(${l})`;break;case"matches":c=`${e}.matches(${l})`;break;case"gt":c=`${e} > ${l}`;break;case"ge":c=`${e} >= ${l}`;break;case"lt":c=`${e} < ${l}`;break;case"le":c=`${e} <= ${l}`;break;default:throw new Error(`Cloud Armor CEL builder has no mapping for operator "${s}"`)}return a?`!(${c})`:c}static isNegated(e,t){return t!==!!e.negated}static formatLiteral(e,t){if(t){let n=Number(e);if(!Number.isFinite(n))throw new Error(`Expected a numeric value, got "${String(e)}"`);return String(n)}return`'${Xe(String(e))}'`}static combineWithAnd(e){if(e.length===0)throw new Error("At least one expression is required");return e.length===1?e[0]:`(${e.join(" && ")})`}static combineWithOr(e){if(e.length===0)throw new Error("At least one expression is required");return e.length===1?e[0]:`(${e.join(" || ")})`}},Hr={eq:"ne",ne:"eq",contains:"not_contains",not_contains:"contains",in:"not_in",not_in:"in"},Zr={country:{path:"origin.region_code",numeric:!1},asn:{path:"origin.asn",numeric:!0},path:{path:"request.path",numeric:!1},method:{path:"request.method",numeric:!1}};var Yr={"origin.region_code":"country","origin.asn":"asn","request.path":"path","request.method":"method","request.query":"query"},Jr={host:"host","user-agent":"user_agent",referer:"referer",cookie:"cookie"},Bn={contains:"contains",startsWith:"starts_with",endsWith:"ends_with",matches:"matches"};function Qr(i){let e=[],t=0,n=i.length,o=(s,a)=>i[t]===s&&i[t+1]===a;for(;t<n;){let s=i[t];if(/\s/.test(s)){t++;continue}if(s==="("){e.push({type:"LPAREN",value:s}),t++;continue}if(s===")"){e.push({type:"RPAREN",value:s}),t++;continue}if(s==="["){e.push({type:"LBRACKET",value:s}),t++;continue}if(s==="]"){e.push({type:"RBRACKET",value:s}),t++;continue}if(s===","){e.push({type:"COMMA",value:s}),t++;continue}if(o("&","&")){e.push({type:"AND",value:"&&"}),t+=2;continue}if(o("|","|")){e.push({type:"OR",value:"||"}),t+=2;continue}if(o("=","=")){e.push({type:"EQ",value:"=="}),t+=2;continue}if(o("!","=")){e.push({type:"NE",value:"!="}),t+=2;continue}if(o(">","=")){e.push({type:"GE",value:">="}),t+=2;continue}if(o("<","=")){e.push({type:"LE",value:"<="}),t+=2;continue}if(s==="!"){e.push({type:"NOT",value:"!"}),t++;continue}if(s===">"){e.push({type:"GT",value:">"}),t++;continue}if(s==="<"){e.push({type:"LT",value:"<"}),t++;continue}if(s==="."){e.push({type:"DOT",value:s}),t++;continue}if(s==="'"){let a=t+1,l="",c=!1;for(;a<n;){let d=i[a];if(d==="\\"&&a+1<n){l+=d+i[a+1],a+=2;continue}if(i[a]==="'"){c=!0,a++;break}l+=i[a],a++}if(!c)throw new j("Unterminated string literal");e.push({type:"STRING",value:xn(l)}),t=a;continue}if(/[0-9]/.test(s)||s==="-"&&/[0-9]/.test(i[t+1]??"")){let a=t+1;for(;a<n&&/[0-9.]/.test(i[a]);)a++;e.push({type:"NUMBER",value:i.slice(t,a)}),t=a;continue}if(/[A-Za-z_]/.test(s)){let a=t;for(;a<n&&/[A-Za-z0-9_]/.test(i[a]);)a++;e.push({type:"IDENT",value:i.slice(t,a)}),t=a;continue}throw new j(`Unexpected character '${s}' at position ${t}`)}return e}function Xr(i){let e=i.expect("IDENT").value;for(;i.peek()?.type==="DOT";){let t=i.pos;i.next();let n=i.expect("IDENT");if(i.peek()?.type==="LPAREN"){i.pos=t;break}e+=`.${n.value}`}return e}function ri(i){let e=Xr(i);if(i.peek()?.type==="LBRACKET"){i.next();let t=i.expect("STRING");return i.expect("RBRACKET"),{field:e,key:t.value}}return{field:e}}function Dn(i){let e=i.next();if(e.type==="STRING")return e.value;if(e.type==="NUMBER")return Number(e.value);throw new j(`Unexpected token '${e.value}' where a literal was expected`)}function es(i){if(i.is("IDENT","has")){i.next(),i.expect("LPAREN");let a=ri(i);return i.expect("RPAREN"),{type:"has",...a}}if(i.is("IDENT","inIpRange")){i.next(),i.expect("LPAREN");let a=ri(i);if(a.field!=="origin.ip")throw new j(`inIpRange must reference origin.ip, got '${a.field}'`);i.expect("COMMA");let l=i.expect("STRING");return i.expect("RPAREN"),{type:"inIpRange",value:l.value}}let e=ri(i);if(i.peek()?.type==="DOT"){i.next();let a=i.expect("IDENT").value;if(!Bn[a])throw new j(`Unrecognized method '${a}'`);i.expect("LPAREN");let l=i.expect("STRING");return i.expect("RPAREN"),{type:"methodCall",...e,method:a,value:l.value}}if(i.is("IDENT","in")){i.next(),i.expect("LBRACKET");let a=[];for(;i.peek()?.type!=="RBRACKET";)a.push(Dn(i)),i.peek()?.type==="COMMA"&&i.next();return i.expect("RBRACKET"),{type:"membership",...e,values:a}}let t=i.next(),o={EQ:"eq",NE:"ne",GT:"gt",GE:"ge",LT:"lt",LE:"le"}[t.type];if(!o)throw new j(`Unexpected token '${t.value}' after field reference`);let s=Dn(i);return{type:"comparison",...e,operator:o,value:s}}function On(i){if(i.peek()?.type==="LPAREN"){i.next();let e=Mn(i);return i.expect("RPAREN"),e}return es(i)}function Fn(i){return i.peek()?.type==="NOT"?(i.next(),{type:"not",child:On(i)}):On(i)}function Nn(i){let e=[Fn(i)];for(;i.peek()?.type==="AND";)i.next(),e.push(Fn(i));return e.length===1?e[0]:{type:"and",children:e}}function Mn(i){let e=[Nn(i)];for(;i.peek()?.type==="OR";)i.next(),e.push(Nn(i));return e.length===1?e[0]:{type:"or",children:e}}function ts(i){let e=new Qe(Qr(i)),t=Mn(e);if(!e.atEnd())throw new j("Unexpected trailing tokens");return t}function is(i){return i.type==="inIpRange"||i.type==="comparison"&&i.field==="origin.ip"&&i.operator==="eq"}function li(i){return i.type==="or"&&i.children.length>0&&i.children.every(is)}function ns(i){return i.type==="inIpRange"?i.value:String(i.value)}function qn(i){if(i.type!=="and"||i.children.length!==2)return!1;let e=i.children[0],t=i.children[1];if(e.type!=="has")return!1;let n=t.type==="not"?t.child:t;return!("field"in n)||n.field!==e.field||n.key!==e.key?!1:n.type==="comparison"||n.type==="methodCall"||n.type==="membership"}function os(i){let e=i.children[0],t=i.children[1],n=t.type==="not",o=n?t.child:t;return{guard:e,comparisonNegated:n,comparison:o}}function si(i){if(i.type==="has"||i.type==="comparison"||i.type==="methodCall"||i.type==="membership"||i.type==="inIpRange"||li(i)||qn(i))return!0;if(i.type==="not"){let e=i.child;return e.type==="has"||e.type==="comparison"||e.type==="methodCall"||e.type==="membership"||e.type==="inIpRange"||li(e)}return!1}function Wn(i,e){if(i==="origin.ip")return{unifiedField:"ip"};let t=Yr[i];if(t)return{unifiedField:t};if(i==="request.headers"&&e!==void 0){let n=Jr[e];return n?{unifiedField:n}:{unifiedField:"header",unifiedKey:e}}return null}function ai(i,e){let t=i.type==="not",n=i.type==="not"?i.child:i;if(li(n)){let o=n.children.map(s=>ns(s));return{field:"ip",operator:t?"not_in":"in",value:o,...e!==void 0?{group:e}:{}}}if(n.type==="inIpRange"||n.type==="comparison"&&n.field==="origin.ip"){let o=n.type==="inIpRange"?n.value:String(n.value);return{field:"ip",operator:t?"ne":"eq",value:o,...e!==void 0?{group:e}:{}}}if(n.type==="and"&&qn(n)){if(t)return null;let{guard:o,comparisonNegated:s,comparison:a}=os(n);return a.type!=="comparison"&&a.type!=="methodCall"&&a.type!=="membership"?null:Vn(a,s,o.field,o.key,e)}if(n.type==="comparison"||n.type==="methodCall"||n.type==="membership")return Vn(n,t,n.field,n.key,e);if(n.type==="has"){let o=Wn(n.field,n.key);return o?{field:o.unifiedField,operator:t?"not_exists":"exists",value:"",...o.unifiedKey!==void 0?{key:o.unifiedKey}:{},...e!==void 0?{group:e}:{}}:null}return null}function Vn(i,e,t,n,o){let s=Wn(t,n);if(!s)return null;let a,l,c=!1;if(i.type==="comparison")e?i.operator==="eq"?a="ne":i.operator==="ne"?a="eq":(a=i.operator,c=!0):a=i.operator,l=i.value;else if(i.type==="methodCall"){let d=Bn[i.method];if(!d)return null;e?d==="contains"?a="not_contains":(a=d,c=!0):a=d,l=i.value}else a=e?"not_in":"in",l=i.values;return{field:s.unifiedField,operator:a,value:l,...c?{negated:!0}:{},...s.unifiedKey!==void 0?{key:s.unifiedKey}:{},...o!==void 0?{group:o}:{}}}function rs(i){if(si(i)){let e=ai(i,void 0);return e?{conditions:[e],conditionLogic:"AND"}:null}if(i.type==="and"){let e=[];for(let t of i.children){if(!si(t))return null;let n=ai(t,void 0);if(!n)return null;e.push(n)}return{conditions:e,conditionLogic:"AND"}}return i.type==="or"?Lt(i.children,si,e=>e.type==="and",ai):null}function Me(i){if(!i||!i.trim())return null;try{let e=ts(i);return rs(e)}catch{return null}}function ci(i){if(!i.match.expr)throw new Error(`Cloud Armor rule at priority ${i.priority} has no CEL match expression (uses the basic versionedExpr/config shape) \u2014 caller must filter these out before translating.`);return i.match.expr.expression}function jn(i){if(i.priority===void 0)throw new Error(`Rule "${i.name}" has no priority assigned \u2014 CloudArmorFirewallService must assign one before calling unifiedToGcp`);let e=[],t=ft.fromUnifiedConditions(i.conditions,i.conditionLogic),{action:n,actionWarning:o}=as(i.action.type,i.name,i.id);o&&e.push(o);let s={priority:i.priority,description:(i.description||i.name).slice(0,64),match:{expr:{expression:t}},action:n,preview:i.action.type==="log"?!0:void 0};if((n==="throttle"||n==="rate_based_ban")&&i.action.rateLimit){let a=ls(i.action.rateLimit.window);s.rateLimitOptions={rateLimitThreshold:{count:i.action.rateLimit.requests,intervalSec:a},conformAction:"allow",exceedAction:"deny(429)",enforceOnKey:"IP"}}else n==="throttle"&&!i.action.rateLimit&&e.push(U.createWarning("lossy_conversion",i.id,"action.rateLimit",`Rule "${i.name}" maps to Cloud Armor's "throttle" action but declares no rateLimit block \u2014 Cloud Armor requires rateLimitOptions for this action; the rule will likely be rejected by the API.`));return n==="redirect"&&(i.action.redirect?s.redirectOptions={type:"EXTERNAL_302",target:i.action.redirect.location}:e.push(U.createWarning("lossy_conversion",i.id,"action.redirect",`Rule "${i.name}" maps to Cloud Armor's "redirect" action but declares no redirect target \u2014 Cloud Armor requires redirectOptions for this action; the rule will likely be rejected by the API.`))),{result:s,warnings:e}}function zn(i,e){let t=[],n=e!==void 0?e:Me(ci(i)),o=[],s;n?(o=n.conditions,s=n.conditionLogic):t.push(U.createWarning("complex_expressions",String(i.priority),"match.expr.expression","CEL expression could not be parsed back into structured conditions \u2014 it may be hand-authored or use syntax outside what doorman itself generates.","Review the translated rule and add missing conditions manually if needed."));let a={type:ss(i.action),...i.rateLimitOptions?{rateLimit:{requests:i.rateLimitOptions.rateLimitThreshold.count,window:`${i.rateLimitOptions.rateLimitThreshold.intervalSec}s`}}:{},...i.redirectOptions?{redirect:{location:i.redirectOptions.target}}:{}};return{result:{id:String(i.priority),name:i.description||`Rule ${i.priority}`,...i.description?{description:i.description}:{},enabled:!i.preview,conditions:o,...s==="OR"?{conditionLogic:s}:{},action:a,priority:i.priority},warnings:t}}function Gn(i,e){if(i.action!=="allow"&&i.action!=="deny(403)"||i.preview)return!1;let t=e!==void 0?e:Me(ci(i));if(!t||t.conditions.length!==1)return!1;let n=t.conditions[0];return n.field==="ip"&&(n.operator==="eq"||n.operator==="in")&&!n.key}function Kn(i,e){let o=(e!==void 0?e:Me(ci(i))).conditions[0].value;return{id:String(i.priority),ip:Array.isArray(o)?String(o[0]):String(o),...i.description?{notes:i.description}:{},action:i.action==="allow"?"allow":"deny"}}function Hn(i,e){if(!Pe.safeParse(i.ip).success)throw new Error(`Invalid IP address or CIDR range: ${i.ip}`);let t=ft.fromUnifiedCondition({field:"ip",operator:"eq",value:i.ip});return{priority:e,description:(i.notes||`IP ${i.action}: ${i.ip}${i.hostname?` (${i.hostname})`:""}`).slice(0,64),match:{expr:{expression:t}},action:i.action==="allow"?"allow":"deny(403)"}}function ss(i){return{allow:"allow","deny(403)":"deny","deny(404)":"deny","deny(502)":"deny",rate_based_ban:"rate_limit",throttle:"rate_limit",redirect:"redirect"}[i]||"deny"}function as(i,e,t){switch(i){case"log":return{action:"allow"};case"deny":case"block":return{action:"deny(403)"};case"challenge":return{action:"deny(403)",actionWarning:U.createUnsupportedFeatureWarning("challenge action","unified config","Cloud Armor",t,"action")};case"bypass":return{action:"allow"};case"rate_limit":return{action:"throttle"};case"redirect":return{action:"redirect"};case"allow":return{action:"allow"};default:return{action:"deny(403)",actionWarning:U.createWarning("lossy_conversion",t,"action",`Rule "${e}" has unrecognized action "${i}" \u2014 defaulted to Cloud Armor's "deny(403)".`)}}}function ls(i){let e=i.match(/^(\d+)([smhd])$/);if(!e||!e[1]||!e[2])throw new Error(`Invalid window format: ${i}`);let t=parseInt(e[1],10),o={s:1,m:60,h:3600,d:86400}[e[2]];if(o===void 0)throw new Error(`Invalid time unit: ${e[2]}`);return t*o}var T=class{static vercelToUnified=pn;static unifiedToVercel=fn;static vercelIPToUnified=gn;static cloudflareToUnified=In;static unifiedToCloudflare=En;static unifiedIPToCloudflare=Tn;static unifiedManagedRuleGroupToCloudflare=An;static cloudflareToUnifiedManagedRuleGroup=$n;static fastlyToUnified=kn;static unifiedToFastly=Sn;static fastlyListEntriesToUnified=Un;static unifiedIPsToFastlyEntries=Ln;static gcpToUnified=zn;static unifiedToGcp=jn;static gcpToUnifiedIP=Kn;static unifiedIPToGcp=Hn;static gcpLooksLikeIpRule=Gn};pe();function Oe(i,e){if(i===e)return!0;if(typeof i!="object"||i===null||typeof e!="object"||e===null||Array.isArray(i)!==Array.isArray(e))return!1;let t=Object.keys(i),n=Object.keys(e);if(t.length!==n.length)return!1;for(let o of t)if(!n.includes(o)||!Oe(i[o],e[o]))return!1;return!0}function Ce(i){let{id:e,...t}=i;return t}function gt(i){return[...i].sort((e,t)=>{let n=e.priority!==void 0,o=t.priority!==void 0;return!n&&!o?0:n?o?e.priority-t.priority:-1:1})}function Dt(i){return i.some(e=>e.priority!==void 0)}var et=class extends ke{constructor(t){super();this.client=t}client;name="vercel";async fetchConfig(t){try{r.debug("Fetching Vercel firewall configuration");let n=await this.client.fetchFirewallConfig(t),o=n.rules.map(a=>{let l=T.vercelToUnified(a);return l.warnings.length>0&&l.warnings.forEach(c=>{let{TranslationWarningSystem:d}=(pe(),Re(Se)),u=d.formatWarning(c);r.warn(`Rule ${a.name}:
25
25
  ${u}`)}),l.result}),s=n.ips.map(a=>T.vercelIPToUnified(a));return{version:"2.0",provider:"vercel",providers:{vercel:{projectId:this.client.projectId,...this.client.teamId?{teamId:this.client.teamId}:{}}},rules:o,ips:s,metadata:{version:n.version,updatedAt:n.updatedAt}}}catch(n){throw r.error("Error fetching Vercel configuration:",n),new Error("Failed to fetch Vercel firewall configuration")}}async syncRules(t,n={}){let{dryRun:o=!1}=n;try{let{OperationSafety:s}=(ht(),Re(mt)),a=await s.performDryRunValidation(t,"sync rules",async y=>await this.getChanges(y,{allowCreate:!o}));if(!a.valid)throw new Error(`Dry-run validation failed: ${a.issues.join(", ")}`);let l=a.changes,{rulesToAdd:c,rulesToUpdate:d,rulesToDelete:u,version:p}=l,f=l.ipsToAdd||[],g=l.ipsToUpdate||[],m=l.ipsToDelete||[];if(o)return r.info("Dry run mode. The following changes would be made:"),r.info(`Custom Rules - Add: ${c.length}, Update: ${d.length}, Delete: ${u.length}`),r.info(`IP Rules - Add: ${f.length}, Update: ${g.length}, Delete: ${m.length}`),{success:!0,rulesAdded:0,rulesUpdated:0,rulesDeleted:0,ipsAdded:0,ipsUpdated:0,ipsDeleted:0,version:p,warnings:a.warnings};let v=s.assessOperationRisk(l,t);if(!await s.confirmDestructiveOperation({operation:"sync rules",target:`Vercel project ${this.client.projectId}`,changes:l,riskLevel:v,skipConfirmation:n.force||!1,dryRun:!1,allowDeletions:n.allowDeletions||!1}))throw new Error("Operation cancelled by user");let x=c.map(y=>T.unifiedToVercel(y).result),S=d.map(y=>T.unifiedToVercel(y).result),q=u.map(y=>T.unifiedToVercel(y).result),W=f.map(y=>({id:y.id||"",ip:y.ip,hostname:y.hostname||"",action:y.action,notes:y.notes})),Q=g.map(y=>({id:y.id||"",ip:y.ip,hostname:y.hostname||"",action:y.action,notes:y.notes})),ue=m.map(y=>({id:y.id||"",ip:y.ip,hostname:y.hostname||"",action:y.action,notes:y.notes})),K=[],ge=[],k=[],Ae=[],Ee=[],A=[],h=[],b=(y,P)=>`${y}: ${P instanceof Error?P.message:String(P)}`,ee=[];Dt(t.rules)&&t.rules.length>c.length&&ee.push("Rule `priority` is best-effort on Vercel: new rules are created in priority order, but rules that already exist remotely cannot be repositioned via the Vercel API. Recreate them (remove and re-add) if exact evaluation order matters.");let me=[];for(let y of q)try{r.debug(`Deleting custom rule: ${y.id}`),await this.client.deleteFirewallRule(y),k.push(y),r.debug(`Custom rule deleted: ${y.id}`)}catch(P){r.error(`Failed to delete custom rule ${y.id}:`,P),h.push(b(`Failed to delete custom rule ${y.id}`,P))}for(let y of ue)try{r.debug(`Deleting IP blocking rule: ${y.id}`),await this.client.deleteIPBlockingRule(y),A.push(y),r.debug(`IP blocking rule deleted: ${y.id}`)}catch(P){r.error(`Failed to delete IP blocking rule ${y.id}:`,P),h.push(b(`Failed to delete IP blocking rule ${y.id}`,P))}for(let y of x)try{r.debug(`Adding new custom rule: ${y.name}`);let P=await this.client.createFirewallRule(y);K.push(P),P.id&&P.id!==y.id&&me.push({oldId:y.id||void 0,newId:P.id,name:y.name}),r.debug(`New custom rule added: ${P.id}`)}catch(P){r.error(`Failed to add custom rule "${y.name}":`,P),h.push(b(`Failed to add custom rule "${y.name}"`,P))}for(let y of W)try{r.debug(`Adding new IP blocking rule: ${y.ip}`);let P=await this.client.createIPBlockingRule(y);Ae.push(P),r.debug(`New IP blocking rule added: (hostname): ${P.hostname} (ip): ${P.ip}`)}catch(P){r.error(`Failed to add IP blocking rule ${y.ip}:`,P),h.push(b(`Failed to add IP blocking rule ${y.ip}`,P))}for(let y of S)try{r.debug(`Updating custom rule: ${y.id}`);let P=await this.client.updateFirewallRule(y);ge.push(P),r.debug(`Custom rule updated: ${P.id}`)}catch(P){r.error(`Failed to update custom rule ${y.id}:`,P),h.push(b(`Failed to update custom rule ${y.id}`,P))}for(let y of Q)try{r.debug(`Updating IP blocking rule: ${y.id}`);let P=await this.client.updateIPBlockingRule(y);Ee.push(P),r.debug(`IP blocking rule updated: ${P.id}`)}catch(P){r.error(`Failed to update IP blocking rule ${y.id}:`,P),h.push(b(`Failed to update IP blocking rule ${y.id}`,P))}r.debug(`${fe.default.underline("Custom Rules:")} ${fe.default.green("Added:")} ${fe.default.green(K.length)}, ${fe.default.cyan("Updated:")} ${fe.default.cyan(ge.length)}, ${fe.default.red("Deleted:")} ${fe.default.red(k.length)}`),r.debug(`${fe.default.underline("IP Rules:")} ${fe.default.green("Added:")} ${fe.default.green(Ae.length)}, ${fe.default.cyan("Updated:")} ${fe.default.cyan(Ee.length)}, ${fe.default.red("Deleted:")} ${fe.default.red(A.length)}`);let lt=p,ct,Ne=[...ee];try{let y=await this.client.fetchFirewallConfig();lt=y.version,ct=y.updatedAt;let P=new Set(y.rules.map(M=>M.id)),ki=new Set(y.ips.map(M=>M.id));for(let M of[...K,...ge])M.id&&!P.has(M.id)&&Ne.push(`Rule "${M.name}" (${M.id}) was synced but is missing from the post-sync remote config \u2014 a subsequent sync may re-create or misdiff it.`);for(let M of k)M.id&&P.has(M.id)&&Ne.push(`Rule "${M.name}" (${M.id}) was deleted but still appears in the post-sync remote config.`);for(let M of[...Ae,...Ee])M.id&&!ki.has(M.id)&&Ne.push(`IP rule "${M.ip}" (${M.id}) was synced but is missing from the post-sync remote config \u2014 a subsequent sync may re-create or misdiff it.`);for(let M of A)M.id&&ki.has(M.id)&&Ne.push(`IP rule "${M.ip}" (${M.id}) was deleted but still appears in the post-sync remote config.`)}catch(y){r.error("Failed to fetch updated firewall configuration version after sync:",y),h.push(b("Failed to fetch updated configuration version after sync",y))}return{success:h.length===0,rulesAdded:K.length,rulesUpdated:ge.length,rulesDeleted:k.length,ipsAdded:Ae.length,ipsUpdated:Ee.length,ipsDeleted:A.length,version:lt,updatedAt:ct,idRemappings:me.length>0?me:void 0,errors:h.length>0?h:void 0,warnings:Ne.length>0?Ne:void 0}}catch(s){throw r.error("Error during sync:",s),new Error(`Failed to synchronize firewall rules: ${s instanceof Error?s.message:String(s)}`,{cause:s})}}async getChanges(t,n){let o=Ue.safeParse(t);if(!o.success)throw new Error(`Invalid firewall configuration: ${o.error.message}`);this.assertManagedRulesSupported(t);try{r.debug("Fetching existing firewall configuration");let s=await this.client.fetchFirewallConfig(void 0,{allowCreate:n?.allowCreate??!0});r.debug(`Fetched ${s.rules.length} custom rules and ${s.ips.length} IP blocking rules`);let a=s.rules.map(m=>{let v=T.vercelToUnified(m);return v.warnings.length>0&&v.warnings.forEach(z=>{let{TranslationWarningSystem:x}=(pe(),Re(Se)),S=x.formatWarning(z);r.warn(`Rule ${m.name}:
26
26
  ${S}`)}),v.result}),{toAdd:l,toUpdate:c,toDelete:d}=this.diffRules(gt(o.data.rules),a),u=s.ips.map(m=>T.vercelIPToUnified(m)),{ipsToAdd:p,ipsToUpdate:f,ipsToDelete:g}=this.diffIPRules(o.data.ips||[],u);return{version:s.version,rulesToAdd:l,rulesToUpdate:c,rulesToDelete:d,ipsToAdd:p,ipsToUpdate:f,ipsToDelete:g,hasChanges:l.length>0||c.length>0||d.length>0||p.length>0||f.length>0||g.length>0}}catch(s){throw r.error("Error fetching existing firewall configuration:",s),new Error("Failed to fetch existing firewall configuration")}}getSupportedFeatures(){return{supportsCustomRules:!0,supportsIPBlocking:!0,supportsRateLimiting:!0,supportsGeoBlocking:!0,supportsManagedRules:!1,supportsRedirect:!0,supportsChallenge:!0}}async verifyCredentials(){return this.client.verifyCredentials()}validateConfig(t){let n=super.validateConfig(t),o=[...n.errors],s=[...n.warnings];t.provider&&t.provider!=="vercel"&&o.push({path:"provider",message:"Provider must be 'vercel' for VercelFirewallService",code:"INVALID_PROVIDER"});try{let a=Ve.safeParse(t);a.success||o.push({path:"root",message:`Schema validation failed: ${a.error.message}`,code:"SCHEMA_VALIDATION_FAILED"})}catch(a){r.debug("Schema validation error:",a)}return{valid:o.length===0,errors:o,warnings:s}}getHealthScore(t){let n=super.getHealthScore(t),o=[...n.issues],s=n.score;return t.rules.filter(l=>l.action?.type==="rate_limit").length===0&&(s-=10,o.push({severity:"info",category:"security",message:"No rate limiting rules configured",suggestion:"Consider adding rate limiting to protect against abuse"})),(!t.ips||t.ips.length===0)&&o.push({severity:"info",category:"security",message:"No IP blocking rules configured",suggestion:"Consider blocking known malicious IPs"}),{score:Math.max(0,s),grade:n.grade,issues:o,recommendations:n.recommendations}}diffRules(t,n){let o=[],s=[],a=[...n];for(let l of t){let c=n.find(d=>d.id===l.id);if(!c)o.push(l);else{Oe(Ce(l),Ce(c))||s.push({...l,id:c.id});let d=a.findIndex(u=>u.id===c.id);d!==-1&&a.splice(d,1)}}return{toAdd:o,toUpdate:s,toDelete:a}}diffIPRules(t,n){let o=[],s=[],a=[...n];for(let l of t){let c=n.find(d=>d.id===l.id);if(!c)r.debug("Rule not found in existing rules:",{configRule:l}),o.push(l);else{Oe(Ce(l),Ce(c))||s.push({...c,...l});let d=a.findIndex(u=>u.id===c.id);d!==-1&&a.splice(d,1)}}return{ipsToAdd:o,ipsToUpdate:s,ipsToDelete:a}}};_();var yt=class{static fromEnv(){let e=process.env.VERCEL_TOKEN,t=process.env.VERCEL_PROJECT_ID,n=process.env.VERCEL_TEAM_ID;if(!e)throw new Error("VERCEL_TOKEN environment variable is required");if(!t)throw new Error("VERCEL_PROJECT_ID environment variable is required");let o=new Ze(t,n,e);return new et(o)}static fromConfig(e){let t=e.token||process.env.VERCEL_TOKEN,n=e.projectId||process.env.VERCEL_PROJECT_ID,o=e.teamId||process.env.VERCEL_TEAM_ID;if(!t)throw new Error("Vercel API token is required (provide token or set VERCEL_TOKEN env var)");if(!n)throw new Error("Vercel project ID is required (provide projectId or set VERCEL_PROJECT_ID env var)");r.debug("Creating Vercel provider with config:",{projectId:n,teamId:o});let s=new Ze(n,o,t);return new et(s)}static create(e,t,n){return this.fromConfig({projectId:e,teamId:t,token:n})}};var Jn=require("crypto");_();var qe=O(require("chalk")),R=class i extends Error{code;suggestion;details;docsUrl;cause;constructor(e){super(e.message),this.name="DoormanError",this.code=e.code,this.suggestion=e.suggestion,this.details=e.details,this.docsUrl=e.docsUrl,this.cause=e.cause,Error.captureStackTrace&&Error.captureStackTrace(this,i)}format(){let e=[];return e.push(qe.default.red.bold(`[${this.code}] ${this.message}`)),e.push(""),this.suggestion&&(e.push(qe.default.yellow.bold("Suggestion:")),e.push(` ${this.suggestion}`),e.push("")),this.details&&Object.keys(this.details).length>0&&(e.push(qe.default.dim("Details:")),Object.entries(this.details).forEach(([t,n])=>{let o=typeof n=="object"?JSON.stringify(n,null,2).split(`
27
27
  `).join(`
package/dist/bin/run.mjs CHANGED
@@ -21,7 +21,7 @@ ${o}`}};var F=class i extends Error{constructor(t,n,o,s=[]){super(t);this.ajvErr
21
21
  Dry run - The following IP rule would be added:`)),Oi(t),r.log(""),r.log(N.dim(JSON.stringify(t,null,2)));return}r.start("Loading configuration...");let o=await H(i.config,"raw"),s={...o,ips:[...o.ips||[],t]};await Z(s,i.config),r.success(N.green(`\u2714 IP rule "${t.ip}" added to configuration`)),Oi(t)}else{let t=i.interactive?await jo():zo(i),n=Dt.safeParse(t);if(n.success||(r.error(N.red("Rule validation failed:")),n.error.errors.forEach(c=>{let d=c.path.join(".")||"rule";r.error(N.red(` - ${d}: ${c.message}`))}),process.exit(1)),i.dryRun){r.info(N.cyan(`
22
22
  Dry run - The following rule would be added:`)),Di(t),r.log(""),r.log(N.dim(JSON.stringify(t,null,2)));return}r.start("Loading configuration...");let o=await H(i.config,"raw"),s=o.rules||[],a=Ho({...o,rules:s},t);if(a){if(r.warn(N.yellow(`\u26A0\uFE0F ${a}`)),i.ci||!process.stdin.isTTY){r.info(N.dim("Skipping \u2014 non-interactive and a conflicting rule already exists. Rule not added."));return}if(!await C("Proceed anyway?",{type:"confirm",initial:!1})){r.info("Cancelled.");return}}let l={...o,rules:[...s,t]};await Z(l,i.config),r.success(N.green(`\u2714 Rule "${t.name}" added to configuration`)),Di(t)}r.log(""),r.log(N.dim(`Run ${N.cyan("doorman sync")} to deploy this rule.`))}catch(e){ne(e,"adding rule")}};var ri={};ie(ri,{builder:()=>_s,command:()=>$s,desc:()=>Ps,handler:()=>ks});_();import ae from"chalk";import{existsSync as kt,mkdirSync as vs,readdirSync as Is,statSync as Es}from"fs";import{isAbsolute as Ts,join as oi}from"path";import{LogLevels as As}from"consola";Ee();import{existsSync as Ni,readFileSync as Yo}from"fs";import{join as Vi}from"path";function Bi(i=process.cwd()){return Ni(Vi(i,".git"))}function Mi(i,e=process.cwd()){let t=Vi(e,".gitignore");if(!Ni(t))return!1;let n=i.replace(/^\.\//,"").replace(/^\//,"").replace(/\/$/,""),o;try{o=Yo(t,"utf8").split(`
23
23
  `)}catch{return!1}return o.some(s=>{let a=s.trim();if(!a||a.startsWith("#"))return!1;let l=a.replace(/^\//,"").replace(/\/$/,"");return l===n||n.startsWith(`${l}/`)})}var oe={type:"string",choices:K,description:"Firewall provider (auto-detected)"};_();import{LogLevels as bs}from"consola";_();Ee();import gs from"chalk";import{emitKeypressEvents as Jo}from"readline";function Qo(i,e){return e?.ctrl&&e.name==="c"?{type:"cancel"}:e?.name==="return"||e?.name==="enter"?{type:"submit"}:e?.name==="backspace"?{type:"backspace"}:i&&!e?.ctrl&&!e?.meta&&i.length===1&&i>=" "?{type:"append",char:i}:{type:"ignore"}}function Xo(i,e){switch(e.type){case"append":return{value:i+e.char,done:!1,cancelled:!1,echo:"add"};case"backspace":return i.length===0?{value:i,done:!1,cancelled:!1,echo:"none"}:{value:i.slice(0,-1),done:!1,cancelled:!1,echo:"remove"};case"submit":return{value:i,done:!0,cancelled:!1,echo:"none"};case"cancel":return{value:i,done:!0,cancelled:!0,echo:"none"};case"ignore":return{value:i,done:!1,cancelled:!1,echo:"none"}}}async function qi(i){if(process.stdout.write(i),!process.stdin.isTTY){let{createInterface:e}=await import("readline"),t=e({input:process.stdin,output:process.stdout,terminal:!1});return new Promise(n=>{t.question("",o=>{t.close(),n(o)})})}return new Promise(e=>{let t=process.stdin,n=t.isRaw;Jo(t),t.setRawMode(!0),t.resume();let o="",s=()=>{t.removeListener("keypress",a),t.setRawMode(n??!1)},a=(l,c)=>{let d=Qo(l,c),u=Xo(o,d);o=u.value,u.echo==="add"?process.stdout.write("*"):u.echo==="remove"&&process.stdout.write("\b \b"),u.done&&(s(),process.stdout.write(`
24
- `),u.cancelled&&process.exit(0),e(o))};t.on("keypress",a)})}_();var Wi={provider:"vercel",fields:[{key:"token",envVar:"VERCEL_TOKEN",label:"Vercel API Token",required:!0,secret:!0,promptMessage:"What is your Vercel API Auth Token? (https://vercel.com/guides/how-do-i-use-a-vercel-api-access-token#creating-an-access-token) "},{key:"projectId",envVar:"VERCEL_PROJECT_ID",label:"Vercel Project ID",required:!0,configKey:"projectId",cliAlias:"p",legacyConfigKey:"projectId",promptMessage:"What is your Vercel Project ID? (See https://vercel.com/docs/projects/project-configuration/general-settings#project-id)"},{key:"teamId",envVar:"VERCEL_TEAM_ID",label:"Vercel Team ID",required:!1,configKey:"teamId",cliAlias:"t",legacyConfigKey:"teamId",promptMessage:"What is your Vercel Team ID? Leave blank to use your Vercel default team (every account, including Hobby, is a team). Only needed if you belong to more than one team and want to target a non-default one. (See https://vercel.com/docs/accounts#find-your-team-id)"}]};var ji={provider:"cloudflare",fields:[{key:"apiToken",envVar:"CLOUDFLARE_API_TOKEN",label:"Cloudflare API Token",required:!0,secret:!0},{key:"zoneId",envVar:"CLOUDFLARE_ZONE_ID",label:"Cloudflare Zone ID",required:!0,configKey:"zoneId"},{key:"accountId",envVar:"CLOUDFLARE_ACCOUNT_ID",label:"Cloudflare Account ID",required:!1,configKey:"accountId"}]};var zi={provider:"fastly",fields:[{key:"apiToken",envVar:"FASTLY_API_TOKEN",label:"Fastly API Token",required:!0,secret:!0},{key:"workspaceId",envVar:"FASTLY_WORKSPACE_ID",label:"Fastly Next-Gen WAF Workspace ID",required:!0,configKey:"workspaceId"}]};var Gi={provider:"gcp",fields:[{key:"serviceAccountKeyPath",envVar:"GOOGLE_APPLICATION_CREDENTIALS",label:"GCP Service Account Key Path",required:!1,promptMessage:"Path to a GCP service account JSON key file (leave blank to use Application Default Credentials \u2014 gcloud auth application-default login, or the GCE/Cloud Run metadata server): "},{key:"projectId",envVar:"GOOGLE_CLOUD_PROJECT",label:"GCP Project ID",required:!0,configKey:"projectId"},{key:"policyName",envVar:"GCP_POLICY_NAME",label:"Cloud Armor Security Policy Name",required:!0,configKey:"policyName"}]};function Ki(i,e={}){let{explicit:t={},config:n={},env:o=process.env}=e,s={};for(let a of i.fields)s[a.key]=t[a.key]||n[a.key]||o[a.envVar];return s}function Hi(i,e){return i.fields.filter(t=>t.required&&!e[t.key])}var qe={vercel:Wi,cloudflare:ji,fastly:zi,gcp:Gi};var It=class{static detectionField(e){return qe[e].fields.find(t=>t.required&&t.configKey)}static detect(e){let t=[];if(e&&"provider"in e){let o=e.provider;if(typeof o=="string"&&this.isValidProvider(o))return t.push(`Explicit provider specified in config: ${e.provider}`),{provider:o,confidence:"high",reasons:t};r.warn(`Invalid provider specified in config: ${String(e.provider)}`)}if(e&&"providers"in e&&typeof e.providers=="object"&&e.providers!==null){let o=e.providers;for(let s of K){let a=this.detectionField(s);if(!a?.configKey)continue;if(typeof(o[s]||{})[a.configKey]=="string")return t.push(`${a.label} found in config`),{provider:s,confidence:"high",reasons:t}}}if(e&&"projectId"in e&&typeof e.projectId=="string")return t.push("Legacy Vercel project ID found in config"),{provider:"vercel",confidence:"high",reasons:t};let n=this.detectFromEnvironment();return n.provider?n:(r.debug("Unable to auto-detect provider"),{provider:null,confidence:"low",reasons:["No provider information found in config or environment"]})}static detectFromEnvironment(){let e=[],t=process.env.DOORMAN_PROVIDER;if(t&&this.isValidProvider(t))return e.push(`Provider set via DOORMAN_PROVIDER environment variable: ${t}`),{provider:t,confidence:"high",reasons:e};for(let n of K){let o=this.detectionField(n);if(!(!o||!process.env[o.envVar]))return e.push(`${o.label} found in environment (${o.envVar})`),{provider:n,confidence:"medium",reasons:e}}return{provider:null,confidence:"low",reasons:["No provider credentials found in environment"]}}static isValidProvider(e){return K.includes(e)}static getProvider(e,t="vercel"){let n=this.detect(e);return n.provider?(r.debug(`Provider detected: ${n.provider} (${n.confidence} confidence)`),n.reasons.length>0&&r.debug(`Detection reasons: ${n.reasons.join(", ")}`),n.provider):(r.debug(`No provider detected, using fallback: ${t}`),t)}static detectAll(e){let t=new Set;if(e&&"provider"in e){let n=e.provider;typeof n=="string"&&this.isValidProvider(n)&&t.add(n)}if(e&&"providers"in e&&typeof e.providers=="object"&&e.providers!==null){let n=e.providers;for(let o of K){let s=this.detectionField(o);if(!s?.configKey)continue;typeof(n[o]||{})[s.configKey]=="string"&&t.add(o)}}e&&"projectId"in e&&typeof e.projectId=="string"&&t.add("vercel");for(let n of K){let o=this.detectionField(n);o&&process.env[o.envVar]&&t.add(n)}return Array.from(t)}};_();Ee();import Zi from"chalk";var Et=i=>({$schema:Ei,firewallEnabled:!0,rules:[],ips:[],...i});_();var Ae=class{baseUrl;providerName;rateLimitInfo={};constructor(e,t){this.baseUrl=e,this.providerName=t}async makeRequest(e,t={}){let n=`${this.baseUrl}${e}`,{timeout:o=3e4,retries:s=3,retryDelay:a=1e3,...l}=t,c=null,d=null,u=null,p=()=>{u&&(clearTimeout(u),u=null),d&&(d.abort(),d=null)};try{for(let f=0;f<=s;f++)try{d=new AbortController,u=setTimeout(()=>{d&&d.abort()},o);let g=await fetch(n,{...l,headers:{"Content-Type":"application/json",...await this.getAuthHeaders(),...l.headers},signal:d.signal});if(u&&(clearTimeout(u),u=null),this.updateRateLimitInfo(g),g.status===429){let v=this.calculateRateLimitWait(f,g);if(f===s)throw new Error(`${this.providerName} API rate limit exceeded after ${s+1} attempt(s) (429 Too Many Requests). Retry after ~${Math.round(v/1e3)}s.`);r.warn(`Rate limit exceeded for ${this.providerName}. Waiting ${v}ms before retry (attempt ${f+1}/${s+1})...`),await this.delay(v);continue}if(!g.ok)throw await this.handleErrorResponse(g);let m;try{let v=await g.text();v.trim()?m=JSON.parse(v):m={}}catch(v){throw r.error(`Failed to parse response from ${n}:`,v),new Error(`Invalid JSON response from ${this.providerName} API: ${v instanceof Error?v.message:String(v)}`)}return this.isLargeResponse(m)&&(r.debug(`Large response detected from ${n}, validating structure...`),this.validateLargeResponse(m)),m}catch(g){if(c=g,u&&(clearTimeout(u),u=null),this.isNonRetryableError(g)||f===s)throw g;let m=a*Math.pow(2,f),v=Math.random()*.1*m,j=Math.min(m+v,3e4);r.debug(`Request failed (attempt ${f+1}/${s+1}). Retrying in ${Math.round(j)}ms...`),r.debug(`Error details: ${g instanceof Error?g.message:String(g)}`),await this.delay(j)}throw c||new Error("Request failed after all retries")}finally{p()}}async get(e,t){return this.makeRequest(e,{...t,method:"GET"})}async post(e,t,n){return this.makeRequest(e,{...n,method:"POST",body:t?JSON.stringify(t):void 0})}async put(e,t,n){return this.makeRequest(e,{...n,method:"PUT",body:t?JSON.stringify(t):void 0})}async patch(e,t,n){return this.makeRequest(e,{...n,method:"PATCH",body:t?JSON.stringify(t):void 0})}async delete(e,t){return this.makeRequest(e,{...t,method:"DELETE"})}updateRateLimitInfo(e){let t=e.headers.get("X-RateLimit-Limit"),n=e.headers.get("X-RateLimit-Remaining"),o=e.headers.get("X-RateLimit-Reset");t&&(this.rateLimitInfo.limit=parseInt(t,10)),n&&(this.rateLimitInfo.remaining=parseInt(n,10)),o&&(this.rateLimitInfo.reset=parseInt(o,10)),this.rateLimitInfo.remaining&&this.rateLimitInfo.remaining<10&&r.warn(`Approaching rate limit for ${this.providerName}: ${this.rateLimitInfo.remaining} requests remaining`)}calculateRateLimitWait(e=0,t){let o=t?this.parseRetryAfter(t.headers.get("Retry-After")):null;if(o!==null)return Math.min(Math.max(o,1e3),6e4);if(this.rateLimitInfo.reset){let l=Math.floor(Date.now()/1e3),c=(this.rateLimitInfo.reset-l)*1e3;return Math.min(Math.max(c,1e3),6e4)}let a=5e3*Math.pow(2,e);return Math.min(a,6e4)}parseRetryAfter(e){if(!e)return null;let t=Number(e);if(Number.isFinite(t))return t*1e3;let n=Date.parse(e);return Number.isNaN(n)?null:n-Date.now()}async handleErrorResponse(e){let t=`${this.providerName} API error: ${e.status} ${e.statusText}`,n=t;try{let o=await e.json();if(typeof o=="object"&&o!==null){let s=o;if(typeof s.error=="string")n=`${t} - ${s.error}`;else if(typeof s.message=="string")n=`${t} - ${s.message}`;else if(Array.isArray(s.errors)){let a=s.errors.map(l=>typeof l=="object"&&l!==null&&"message"in l?String(l.message):JSON.stringify(l)).join(", ");n=`${t} - ${a}`}}}catch{}return r.error(n),new Error(n)}isNonRetryableError(e){if(typeof e=="object"&&e!==null&&e.name==="AbortError")return!0;let t=e.message;return typeof t=="string"&&t.includes("API error: 4")?!t.includes("429"):!1}delay(e){return new Promise(t=>setTimeout(t,e))}getRateLimitInfo(){return{...this.rateLimitInfo}}isLargeResponse(e){if(!e||typeof e!="object")return!1;let t=(n,o=0)=>{if(o>10)return!1;for(let[,s]of Object.entries(n))if(Array.isArray(s)&&s.length>1e3||typeof s=="object"&&s!==null&&t(s,o+1))return!0;return!1};return t(e)}validateLargeResponse(e){if(!e||typeof e!="object")return;let t=(o,s)=>{o.length>5e3&&r.warn(`Large array detected at ${s} with ${o.length} items. This may impact performance.`);let a=Math.min(10,o.length),l=o[0];if(l&&typeof l=="object"){let c=Object.keys(l);for(let d=1;d<a;d++){let u=o[d];if(!u||typeof u!="object"){r.warn(`Inconsistent array structure at ${s}[${d}]: expected object, got ${typeof u}`);continue}let p=Object.keys(u),f=c.filter(g=>!p.includes(g));f.length>0&&r.warn(`Inconsistent array structure at ${s}[${d}]: missing keys ${f.join(", ")}`)}}},n=(o,s="root",a=0)=>{if(!(a>10))for(let[l,c]of Object.entries(o)){let d=`${s}.${l}`;Array.isArray(c)?t(c,d):typeof c=="object"&&c!==null&&n(c,d,a+1)}};n(e)}};var Yi=process.env.DOORMAN_VERCEL_API_BASE_URL||"https://api.vercel.com/v1/security/firewall/config",We=class extends Ae{constructor(t,n,o){super("","vercel");this.projectId=t;this.teamId=n;this.token=o}projectId;teamId;token;async getAuthHeaders(){return{Authorization:`Bearer ${this.token}`}}getUrl(t){let n=t!==void 0?`${Yi}/${t}`:Yi;r.debug("API URL:",n);let o=this.teamId?`&teamId=${this.teamId}`:"";return`${n}?projectId=${this.projectId}${o}`}async fetchFirewallConfig(t,n){let{allowCreate:o=!0}=n??{},s=await this.get(this.getUrl(t));if(r.debug("Config Version:",t??"latest"),r.debug("Fetched Config:",t?s:s.active),t)return s;if(!s||"active"in s&&s.active===null){if(r.warn(Zi.bold("No firewall configuration found.")),!o)return r.debug("Skipping create-configuration prompt for read-only fetch."),this.emptyConfigPlaceholder();if(await C("Would you like to create one?",{type:"confirm"})){r.debug("Creating new empty firewall configuration...");let l=await this.putEmptyConfig();return r.debug("Empty firewall configuration created successfully"),r.debug(`New configuration version: ${Zi.yellow(l.version)}`),l}else return s.active}return s.active}emptyConfigPlaceholder(){return{version:0,id:"",firewallEnabled:!0,crs:null,rules:[],ips:[],projectKey:"",ownerId:"",updatedAt:new Date(0).toISOString()}}async putEmptyConfig(){let{$schema:t,...n}=Et();return r.debug("Empty Config:",n),this.putConfig(n)}async putConfig(t){return(await this.put(this.getUrl(),t)).active}async fetchActiveFirewallRules(){return(await this.fetchFirewallConfig())?.rules}async updateFirewallRule(t){let n=!t.id||t.id==="-",o={action:n?"rules.insert":"rules.update",id:n?null:t.id,value:{name:t.name,description:t.description,action:t.action,conditionGroup:t.conditionGroup,active:t.active}};return this.patch(this.getUrl(),o,n?{retries:0}:void 0)}async createFirewallRule(t){return this.updateFirewallRule({...t,id:"-"})}async deleteFirewallRule(t){let n={action:"rules.remove",id:t.id,value:null};await this.patch(this.getUrl(),n)}async updateIPBlockingRule(t){let n=!t.id||t.id==="-",o={action:n?"ip.insert":"ip.update",id:n?null:t.id,value:{action:t.action,hostname:t.hostname,ip:t.ip,notes:t.notes??null}};return this.patch(this.getUrl(),o,n?{retries:0}:void 0)}async createIPBlockingRule(t){return this.updateIPBlockingRule({...t,id:"-"})}async deleteIPBlockingRule(t){let n={action:"ip.remove",id:t.id,value:null};await this.patch(this.getUrl(),n)}async verifyCredentials(){try{return await this.fetchFirewallConfig(),!0}catch(t){return r.debug("Credential verification failed:",t),!1}}};_();import ge from"chalk";_();var $e=class{validateConfig(e){let t=[],n=[];if(e||t.push({path:"root",message:"Configuration is required",code:"CONFIG_REQUIRED"}),e&&!e.rules&&t.push({path:"rules",message:"Rules array is required",code:"RULES_REQUIRED"}),e&&e.rules&&!Array.isArray(e.rules)&&t.push({path:"rules",message:"Rules must be an array",code:"RULES_INVALID_TYPE"}),e&&Array.isArray(e.rules)){let o=new Map;e.rules.forEach((s,a)=>{let l=s.id||s.name;if(!l)return;let c=o.get(l);if(c===void 0){o.set(l,a);return}let d=e.rules[c];n.push({path:`rules[${a}]`,message:s.id?`Rule "${s.name}" has the same id ("${s.id}") as rules[${c}] ("${d?.name}") \u2014 they will collide during sync diffing and one may be silently skipped.`:`Rule "${s.name}" has the same name as rules[${c}] and neither has an explicit id \u2014 they will collide during sync diffing and one may be silently skipped. Add unique ids to disambiguate.`,code:"DUPLICATE_RULE_IDENTIFIER"})})}return{valid:t.length===0,errors:t,warnings:n}}getHealthScore(e){let t=[],n=100;if(!e)return{score:0,grade:"poor",issues:[{severity:"error",category:"configuration",message:"No configuration found"}],recommendations:["Create a configuration file"]};if((!e.rules||e.rules.length===0)&&(n-=20,t.push({severity:"warning",category:"rules",message:"No rules defined",suggestion:"Add security rules to protect your application"})),e.rules&&e.rules.length>0){let a=e.rules.filter(l=>!l.description);a.length>0&&(n-=5,t.push({severity:"info",category:"maintainability",message:`${a.length} rule(s) missing descriptions`,suggestion:"Add descriptions to improve maintainability"}))}if(e.rules&&e.rules.length>0){let a=e.rules.filter(l=>l.active===!1||l.enabled===!1);a.length>0&&(n-=5,t.push({severity:"info",category:"maintenance",message:`${a.length} disabled rule(s) found`,suggestion:"Review and remove unused rules"}))}let o=this.calculateGrade(n),s=this.generateRecommendations(e,t);return{score:Math.max(0,n),grade:o,issues:t,recommendations:s}}diffItems(e,t,n,o="id"){let s=[],a=[],l=[],c=d=>d[o];for(let d of e){let u=t.find(p=>c(p)===c(d));u?n(d,u)||a.push(d):s.push(d)}for(let d of t)e.find(p=>c(p)===c(d))||l.push(d);return{toAdd:s,toUpdate:a,toDelete:l}}assertManagedRulesSupported(e){if(!(!e.managedRules||e.managedRules.length===0)&&!this.getSupportedFeatures().supportsManagedRules)throw new Error(`${this.name} does not support managed rule groups, but this config declares ${e.managedRules.length}. Remove \`managedRules\` from the config, or switch to a provider that supports them (currently Cloudflare only).`)}validateRuleCount(e,t){if(t&&e>t)throw new Error(`Rule count (${e}) exceeds provider limit (${t})`);t&&e>t*.9&&r.warn(`Approaching rule limit: ${e}/${t} rules`)}updateMetadata(e,t){return{...e,metadata:{...e.metadata,version:t,updatedAt:new Date().toISOString()}}}calculateGrade(e){return e>=80?"excellent":e>=60?"good":e>=40?"fair":"poor"}generateRecommendations(e,t){let n=[];return t.some(o=>o.category==="rules")&&n.push("Consider using predefined templates to get started"),t.some(o=>o.category==="maintainability")&&n.push("Add descriptions to all rules for better documentation"),t.some(o=>o.category==="maintenance")&&n.push("Remove or enable disabled rules to keep configuration clean"),n}logSyncStats(e){r.info(`Sync complete for ${this.name}:`),r.info(` Rules added: ${e.rulesAdded}`),r.info(` Rules updated: ${e.rulesUpdated}`),r.info(` Rules deleted: ${e.rulesDeleted}`),e.ipsAdded!==void 0&&(r.info(` IPs added: ${e.ipsAdded}`),r.info(` IPs updated: ${e.ipsUpdated}`),r.info(` IPs deleted: ${e.ipsDeleted}`)),e.managedRulesAdded!==void 0&&(r.info(` Managed rule groups added: ${e.managedRulesAdded}`),r.info(` Managed rule groups updated: ${e.managedRulesUpdated}`),r.info(` Managed rule groups deleted: ${e.managedRulesDeleted}`)),e.version&&r.info(` New version: ${e.version}`)}};ce();function Ji(i){let e=[],t=[];i.conditionGroup.forEach((s,a)=>{for(let l of s.conditions){let c=tr(l.op);t.push({field:nr(l.type),operator:c,...l.value!==void 0?{value:l.value}:{},...l.neg?{negated:!0}:{},...l.key?{key:l.key}:{},group:a})}}),t.length>10&&e.push(U.createWarning("many_conditions",i.id,void 0,`Rule has ${t.length} conditions which may impact performance`,"Consider splitting complex rules into multiple simpler rules for better performance"));let n={type:i.action.mitigate.action,...i.action.mitigate.rateLimit?{rateLimit:{requests:i.action.mitigate.rateLimit.requests,window:i.action.mitigate.rateLimit.window,...i.action.mitigate.rateLimit.characteristics?{characteristics:i.action.mitigate.rateLimit.characteristics}:{},...i.action.mitigate.rateLimit.mitigationTimeout?{mitigationTimeout:i.action.mitigate.rateLimit.mitigationTimeout}:{},...i.action.mitigate.rateLimit.countingExpression?{countingExpression:i.action.mitigate.rateLimit.countingExpression}:{}}}:{},...i.action.mitigate.redirect?{redirect:{location:i.action.mitigate.redirect.location,...i.action.mitigate.redirect.permanent?{permanent:i.action.mitigate.redirect.permanent}:{}}}:{},...i.action.mitigate.actionDuration?{duration:i.action.mitigate.actionDuration}:{}};return{result:{id:i.id,name:i.name,...i.description?{description:i.description}:{},enabled:i.active,conditions:t,conditionLogic:i.conditionGroup.length>1?"OR":"AND",action:n},warnings:e}}function Qi(i){let e=[],t=er(i.conditions,i.id,e);if(i.action.response&&e.push(U.createUnsupportedFeatureWarning("custom response body","unified config","Vercel",i.id,"action.response")),t.length===0)throw new Error(`Rule "${i.name}" has no conditions Vercel can represent \u2014 every condition field is unsupported by Vercel (e.g. Cloudflare-only fields like referer/port). Cannot sync this rule to Vercel.`);return{result:{id:i.id,name:i.name,description:i.description,conditionGroup:t,action:{mitigate:{action:i.action.type,rateLimit:i.action.rateLimit?{requests:i.action.rateLimit.requests,window:i.action.rateLimit.window,characteristics:i.action.rateLimit.characteristics,mitigationTimeout:i.action.rateLimit.mitigationTimeout,countingExpression:i.action.rateLimit.countingExpression}:null,redirect:i.action.redirect?{location:i.action.redirect.location,permanent:i.action.redirect.permanent}:null,actionDuration:i.action.duration||null}},active:i.enabled},warnings:e}}function Xi(i){return{id:i.id,ip:i.ip,...i.hostname?{hostname:i.hostname}:{},...i.notes?{notes:i.notes}:{},action:i.action}}function er(i,e,t){let n=new Map;for(let s of i){let a=s.group??0,l=n.get(a);l?l.push(s):n.set(a,[s])}let o=[];for(let s of n.values()){let a=[];for(let l of s){let c=rr(l.field);if(!c){t.push(U.createUnsupportedFeatureWarning(`condition field '${l.field}'`,"unified config","Vercel",e,l.field));continue}a.push({op:ir(l.operator),neg:l.negated,type:c,key:l.key,value:l.value})}a.length>0&&o.push({conditions:a})}return o}function tr(i){return{eq:"eq",pre:"starts_with",suf:"ends_with",inc:"in",sub:"contains",re:"matches",ex:"exists",nex:"not_exists"}[i]||"eq"}function ir(i){return{eq:"eq",starts_with:"pre",ends_with:"suf",in:"inc",contains:"sub",matches:"re",exists:"ex",not_exists:"nex"}[i]||"eq"}function nr(i){return{host:"host",path:"path",method:"method",header:"header",query:"query",cookie:"cookie",ip_address:"ip",user_agent:"user_agent",geo_country:"country",geo_city:"city",geo_as_number:"asn",scheme:"scheme",region:"vercel_region",geo_country_region:"region"}[i]||i}var or={host:"host",path:"path",method:"method",header:"header",query:"query",cookie:"cookie",ip:"ip_address",user_agent:"user_agent",country:"geo_country",city:"geo_city",asn:"geo_as_number",scheme:"scheme",target_path:"target_path",vercel_region:"region",region:"geo_country_region",protocol:"protocol",environment:"environment",geo_continent:"geo_continent",ja4_digest:"ja4_digest",ja3_digest:"ja3_digest",rate_limit_api_id:"rate_limit_api_id"};function rr(i){return or[i]??null}ce();_();function je(i){return i.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}function en(i){let e="";for(let t=0;t<i.length;t++)i[t]==="\\"&&(i[t+1]==="\\"||i[t+1]==='"')?(e+=i[t+1],t++):e+=i[t];return e}var rt=class{static vercelToCloudflare={host:"http.host",path:"http.request.uri.path",method:"http.request.method",header:"http.request.headers",query:"http.request.uri.query",cookie:"http.cookie",target_path:"http.request.uri.path",ip_address:"ip.src",region:"ip.geoip.subdivision_1",protocol:"ssl",scheme:"ssl",environment:"",user_agent:"http.user_agent",geo_continent:"ip.geoip.continent",geo_country:"ip.geoip.country",geo_country_region:"ip.geoip.subdivision_1",geo_city:"ip.geoip.city",geo_as_number:"ip.geoip.asnum",ja4_digest:"",ja3_digest:"",rate_limit_api_id:""};static cloudflareToVercel={"http.host":"host","http.request.uri.path":"path","http.request.method":"method","http.request.headers":"header","http.request.uri.query":"query","http.cookie":"cookie","ip.src":"ip_address","ip.geoip.subdivision_1":"region",ssl:"protocol","http.user_agent":"user_agent","ip.geoip.continent":"geo_continent","ip.geoip.country":"geo_country","ip.geoip.city":"geo_city","ip.geoip.asnum":"geo_as_number"};static toCloudflare(e,t){let n=this.vercelToCloudflare[e];if(!n)throw r.warn(`No Cloudflare mapping for Vercel type: ${e}`),new Error(`Unsupported Vercel rule type for Cloudflare: ${e}`);return e==="header"&&t?`${n}["${je(t.toLowerCase())}"]`:e==="cookie"&&t?`${n}["${je(t)}"]`:n}static toVercel(e){let t=e.match(/http\.request\.headers\["([^"]+)"\]/);if(t)return{type:"header",key:t[1]};let n=e.match(/http\.cookie\["([^"]+)"\]/);if(n)return{type:"cookie",key:n[1]};if(e==="http.referer")return{type:"header",key:"referer"};let o=this.cloudflareToVercel[e];if(o)return{type:o};throw r.warn(`No Vercel mapping for Cloudflare field: ${e}`),new Error(`Unsupported Cloudflare field for Vercel: ${e}`)}static isCloudflareSupported(e){let t=this.vercelToCloudflare[e];return!!t&&t!==""}static isVercelSupported(e){return!!(this.cloudflareToVercel[e]||e==="http.referer"||e.startsWith("http.request.headers[")||e.startsWith("http.cookie["))}static getSupportedCloudflareFields(){return Object.values(this.vercelToCloudflare).filter(e=>e!=="")}static getSupportedVercelTypes(){return Object.keys(this.vercelToCloudflare).filter(e=>this.isCloudflareSupported(e))}};var sr=new Set(["ip.src"]),ar="http.request.uri.args",lr="http.request.headers",cr="http.request.cookies",ze=class{static fromVercelConditionGroups(e){if(!e||e.length===0)throw new Error("At least one condition group is required");let t=e.map(n=>{let o=n.conditions.map(s=>this.fromVercelCondition(s));if(o.length===0)throw new Error("Condition group must have at least one condition");return o.length>1?`(${o.join(" and ")})`:o[0]});return t.length>1?t.join(" or "):t[0]}static fromVercelCondition(e){let t=rt.toCloudflare(e.type,e.key),n=this.buildExistsOrComparisonExpression(t,e.op,()=>`${this.mapVercelOperator(e.op)} ${this.formatValue(t,e.value)}`);return e.neg&&(n=`not (${n})`),n}static fromUnifiedConditions(e,t="AND"){if(!e||e.length===0)throw new Error("At least one condition is required");if(!e.some(a=>a.group!==void 0)){let a=e.map(c=>this.fromUnifiedCondition(c)),l=t==="AND"?" and ":" or ";return a.length>1?`(${a.join(l)})`:a[0]}let o=new Map;for(let a of e){let l=a.group??0,c=o.get(l);c?c.push(a):o.set(l,[a])}let s=Array.from(o.values()).map(a=>this.combineWithAnd(a.map(l=>this.fromUnifiedCondition(l))));return s.length>1?this.combineWithOr(s):s[0]}static fromUnifiedCondition(e){if(e.key&&e.field==="query")return this.buildKeyedMapExpression(ar,e.key,e);if(e.field==="header"){if(!e.key)throw new Error('A "header" condition requires a key naming the header');return this.buildKeyedMapExpression(lr,e.key.toLowerCase(),e)}if(e.key&&e.field==="cookie")return this.buildKeyedMapExpression(cr,e.key,e);let t=this.mapUnifiedFieldToCloudflare(e.field);if(t===null)throw new Error(`Unsupported condition field '${e.field}' for Cloudflare \u2014 filter it out with a warning before calling fromUnifiedCondition (see unifiedToCloudflare).`);let n=this.buildUnifiedExpression(t,e.operator,e.value);return e.negated&&(n=`not (${n})`),n}static buildKeyedMapExpression(e,t,n){let o=je(t),s=`${e}["${o}"]`,a;if(n.operator==="exists")a=`has_key(${e}, "${o}")`;else if(n.operator==="not_exists")a=`not (has_key(${e}, "${o}"))`;else if(n.operator==="not_contains")a=`not (any(${s}[*] contains ${this.formatValue(e,n.value)}))`;else if(n.operator==="not_in")a=`not (any(${s}[*] in ${this.formatValue(e,n.value)}))`;else{let l=this.mapUnifiedOperator(n.operator);a=`any(${s}[*] ${l} ${this.formatValue(e,n.value)})`}return n.negated?`not (${a})`:a}static buildExistsOrComparisonExpression(e,t,n){return t==="ex"||t==="exists"?`${e} exists`:t==="nex"||t==="not_exists"?`not (${e} exists)`:`${e} ${n()}`}static buildUnifiedExpression(e,t,n){return t==="not_contains"?`not (${e} contains ${this.formatValue(e,n)})`:t==="not_in"?`not (${e} in ${this.formatValue(e,n)})`:this.buildExistsOrComparisonExpression(e,t,()=>`${this.mapUnifiedOperator(t)} ${this.formatValue(e,n)}`)}static mapVercelOperator(e){return{eq:"eq",pre:"starts_with",suf:"ends_with",inc:"in",sub:"contains",re:"matches",ex:"exists"}[e]||e}static mapUnifiedOperator(e){return{eq:"eq",ne:"ne",contains:"contains",starts_with:"starts_with",ends_with:"ends_with",matches:"matches",in:"in",gt:"gt",ge:"ge",lt:"lt",le:"le",exists:"exists"}[e]||e}static mapUnifiedFieldToCloudflare(e){return{ip:"ip.src",country:"ip.geoip.country",region:"ip.geoip.subdivision_1",city:"ip.geoip.city",asn:"ip.geoip.asnum",path:"http.request.uri.path",host:"http.host",method:"http.request.method",header:"http.request.headers",query:"http.request.uri.query",cookie:"http.cookie",user_agent:"http.user_agent",referer:"http.referer",scheme:"ssl",port:"cf.edge.server_port",threat_score:"cf.threat_score"}[e]??null}static isFieldSupported(e){return this.mapUnifiedFieldToCloudflare(e)!==null}static formatValue(e,t){return Array.isArray(t)?`{${t.map(o=>this.formatSingleValue(e,o)).join(" ")}}`:this.formatSingleValue(e,t)}static formatSingleValue(e,t){if(typeof t=="string"){if(sr.has(e)){if(!Te.safeParse(t).success)throw new Error(`Invalid IP address or CIDR range: ${t}`);return t}return`"${je(t)}"`}return typeof t=="number"?String(t):typeof t=="boolean"?t?"true":"false":String(t)}static validate(e){if(!e||e.trim().length===0)return!1;let t=0;for(let n of e)if(n==="("&&t++,n===")"&&t--,t<0)return!1;return t===0}static combineWithAnd(e){if(e.length===0)throw new Error("At least one expression is required");return e.length===1?e[0]:`(${e.join(" and ")})`}static combineWithOr(e){if(e.length===0)throw new Error("At least one expression is required");return e.length===1?e[0]:`(${e.join(" or ")})`}};var W=class extends Error{},Ge=class{constructor(e){this.tokens=e}tokens;pos=0;peek(){return this.tokens[this.pos]}next(){let e=this.tokens[this.pos];if(!e)throw new W("Unexpected end of expression");return this.pos++,e}expect(e){let t=this.next();if(t.type!==e)throw new W(`Expected ${e} but got '${t.value}'`);return t}is(e,t){let n=this.peek();return!!n&&n.type===e&&n.value===t}atEnd(){return this.pos>=this.tokens.length}};function Tt(i,e,t,n){let o=[];i.forEach((l,c)=>{if(e(l)){let d=n(l,c);d&&o.push(d);return}if(t(l))for(let d of l.children){if(!e(d))return;let u=n(d,c);u&&o.push(u)}});let s=i.length;return new Set(o.map(l=>l.group)).size!==s?null:{conditions:o,conditionLogic:"OR"}}var dr={"ip.src":"ip","ip.geoip.country":"country","ip.geoip.subdivision_1":"region","ip.geoip.city":"city","ip.geoip.asnum":"asn","http.request.uri.path":"path","http.host":"host","http.request.method":"method","http.request.headers":"header","http.request.uri.query":"query","http.request.uri.args":"query","http.cookie":"cookie","http.request.cookies":"cookie","http.user_agent":"user_agent","http.referer":"referer",ssl:"scheme","cf.edge.server_port":"port","cf.threat_score":"threat_score"},rn=new Set(["eq","ne","contains","starts_with","ends_with","matches","in","gt","ge","lt","le"]),ur=new Set(["and","or","not"]);function pr(i){let e=[],t=0,n=i.length;for(;t<n;){let o=i[t];if(/\s/.test(o)){t++;continue}if(o===","){t++;continue}if(o==="("){e.push({type:"LPAREN",value:o}),t++;continue}if(o===")"){e.push({type:"RPAREN",value:o}),t++;continue}if(o==="{"){e.push({type:"LBRACE",value:o}),t++;continue}if(o==="}"){e.push({type:"RBRACE",value:o}),t++;continue}if(o==="["){e.push({type:"LBRACKET",value:o}),t++;continue}if(o==="]"){e.push({type:"RBRACKET",value:o}),t++;continue}if(o==='"'){let a=t+1,l="",c=!1;for(;a<n;){let d=i[a];if(d==="\\"&&a+1<n){l+=d+i[a+1],a+=2;continue}if(i[a]==='"'){c=!0,a++;break}l+=i[a],a++}if(!c)throw new W("Unterminated string literal");e.push({type:"STRING",value:en(l)}),t=a;continue}let s=t;for(;s<n&&!/[\s(){}[\]",]/.test(i[s]);)s++;if(s===t)throw new W(`Unexpected character '${o}' at position ${t}`);e.push({type:"WORD",value:i.slice(t,s)}),t=s}return e}function Kt(i){let e=[tn(i)];for(;i.is("WORD","or");)i.next(),e.push(tn(i));return e.length===1?e[0]:{type:"or",children:e}}function tn(i){let e=[nn(i)];for(;i.is("WORD","and");)i.next(),e.push(nn(i));return e.length===1?e[0]:{type:"and",children:e}}function nn(i){if(i.is("WORD","not")){i.next(),i.expect("LPAREN");let e=Kt(i);return i.expect("RPAREN"),{type:"not",child:e}}return fr(i)}function fr(i){let e=i.peek();if(e&&e.type==="LPAREN"){i.next();let t=Kt(i);return i.expect("RPAREN"),t}return yr(i)}function gr(i){let e=i.expect("WORD");if(ur.has(e.value))throw new W(`Expected a field reference but got keyword '${e.value}'`);if(i.peek()?.type==="LBRACKET"){i.next();let t=i.expect("STRING");return i.expect("RBRACKET"),{field:e.value,key:t.value}}return{field:e.value}}function on(i){let e=i.next();if(e.type==="STRING")return e.value;if(e.type==="WORD")return/^-?\d+(\.\d+)?$/.test(e.value)?Number(e.value):e.value;throw new W(`Unexpected token '${e.value}' where a value was expected`)}function sn(i){if(i.peek()?.type==="LBRACE"){i.next();let e=[];for(;i.peek()?.type!=="RBRACE";)e.push(on(i));return i.expect("RBRACE"),e}return on(i)}function mr(i){i.next(),i.expect("LPAREN");let e=i.expect("WORD");i.expect("LBRACKET");let t=i.expect("STRING");i.expect("RBRACKET"),i.expect("LBRACKET");let n=i.expect("WORD");if(n.value!=="*")throw new W(`Expected '*' in any(...) array index but got '${n.value}'`);i.expect("RBRACKET");let o=i.next();if(o.type!=="WORD"||!rn.has(o.value))throw new W(`Unsupported or unrecognized operator '${o.value}' inside any(...)`);let s=sn(i);return i.expect("RPAREN"),{type:"comparison",field:e.value,key:t.value,operator:o.value,value:s}}function hr(i){i.next(),i.expect("LPAREN");let e=i.expect("WORD"),t=i.expect("STRING");return i.expect("RPAREN"),{type:"exists",field:e.value,key:t.value}}function yr(i){if(i.is("WORD","any"))return mr(i);if(i.is("WORD","has_key"))return hr(i);let{field:e,key:t}=gr(i);if(i.is("WORD","exists"))return i.next(),{type:"exists",field:e,key:t};let n=i.next();if(n.type!=="WORD"||!rn.has(n.value))throw new W(`Unsupported or unrecognized operator '${n.value}'`);let o=sn(i);return{type:"comparison",field:e,key:t,operator:n.value,value:o}}function Rr(i){let e=new Ge(pr(i)),t=Kt(e);if(!e.atEnd())throw new W("Unexpected trailing tokens");return t}function zt(i){return i.type==="comparison"||i.type==="exists"?!0:i.type==="not"?i.child.type==="comparison"||i.child.type==="exists":!1}function Gt(i,e){let t=i.type==="not",n=i.type==="not"?i.child:i;if(n.type!=="comparison"&&n.type!=="exists")return null;let o=dr[n.field];if(!o)return null;if(n.type==="exists")return{field:o,operator:t?"not_exists":"exists",value:"",key:n.key,group:e};let s=n.operator,a;return t&&(n.operator==="contains"?s="not_contains":n.operator==="in"?s="not_in":a=!0),{field:o,operator:s,value:n.value,key:n.key,negated:a,group:e}}function wr(i){if(zt(i)){let e=Gt(i,0);return e?{conditions:[e],conditionLogic:"AND"}:null}if(i.type==="and"){let e=[];for(let t of i.children){if(!zt(t))return null;let n=Gt(t,0);if(!n)return null;e.push(n)}return{conditions:e,conditionLogic:"AND"}}return i.type==="or"?Tt(i.children,zt,e=>e.type==="and",Gt):null}function Ht(i){if(!i||!i.trim())return null;try{let e=Rr(i);return wr(e)}catch{return null}}function cn(i){let e=[],t=Ht(i.expression),n=[],o;t?(n=t.conditions,o=t.conditionLogic):e.push(U.createWarning("complex_expressions",i.id,"expression","Expression could not be parsed back into structured conditions \u2014 it may be hand-authored or use syntax outside what doorman itself generates.","Review the translated rule and add missing conditions manually if needed."));let s=i.action_parameters&&"response"in i.action_parameters?i.action_parameters.response:void 0,a={type:Cr(i.action),rateLimit:i.ratelimit?{requests:i.ratelimit.requests_per_period,window:`${i.ratelimit.period}s`,characteristics:i.ratelimit.characteristics,mitigationTimeout:i.ratelimit.mitigation_timeout,countingExpression:i.ratelimit.counting_expression}:void 0,response:s?{statusCode:s.status_code,content:s.content,contentType:s.content_type}:void 0};return{result:{id:i.id,name:i.description||`Rule ${i.id}`,description:i.description,enabled:i.enabled??!0,conditions:n,conditionLogic:o,action:a},warnings:e}}function dn(i){let e=[],t=vr(i.conditions,i.id,e);if(t.length===0)throw new Error(`Rule "${i.name}" has no conditions Cloudflare can represent \u2014 every condition field is unsupported by Cloudflare (e.g. Vercel-only fields like environment/ja3_digest, or vercel_region). Cannot sync this rule to Cloudflare.`);let n=ze.fromUnifiedConditions(t,i.conditionLogic),o={id:i.id||crypto.randomUUID(),action:br(i.action.type),expression:n,description:i.description||i.name,enabled:i.enabled};return i.action.rateLimit&&(o.ratelimit={characteristics:i.action.rateLimit.characteristics||["ip.src"],period:Ir(i.action.rateLimit.window),requests_per_period:i.action.rateLimit.requests},i.action.rateLimit.mitigationTimeout?o.ratelimit.mitigation_timeout=i.action.rateLimit.mitigationTimeout:(o.ratelimit.mitigation_timeout=3600,e.push(U.createWarning("rate_limiting_precision",i.id,"action.rateLimit.mitigationTimeout",`No mitigationTimeout set for rate limit rule "${i.name}" \u2014 defaulted to Cloudflare's 3600s (1 hour) block duration.`))),i.action.rateLimit.countingExpression&&(o.ratelimit.counting_expression=i.action.rateLimit.countingExpression)),i.action.type==="redirect"&&i.action.redirect&&(o.action_parameters={from_value:{status_code:i.action.redirect.statusCode??(i.action.redirect.permanent?301:302),target_url:{value:i.action.redirect.location},...i.action.redirect.preserveQueryString!==void 0?{preserve_query_string:i.action.redirect.preserveQueryString}:{}}}),i.action.response&&(o.action!=="block"?e.push(U.createUnsupportedFeatureWarning(`custom response on a '${o.action}' action`,"unified config","Cloudflare",i.id,"action.response")):i.action.response.content?o.action_parameters={response:{status_code:i.action.response.statusCode??403,content:i.action.response.content,content_type:i.action.response.contentType??"text/plain"}}:e.push(U.createWarning("lossy_conversion",i.id,"action.response","Custom response declared without `content`; Cloudflare requires a response body, so it was dropped.","Set action.response.content to the body you want returned."))),{result:o,warnings:e}}function un(i){if(!Te.safeParse(i.ip).success)throw new Error(`Invalid IP address or CIDR range: ${i.ip}`);let t=i.ip.includes("/")?`ip.src in {${i.ip}}`:`ip.src eq ${i.ip}`;return{id:i.id||crypto.randomUUID(),action:i.action==="allow"?"allow":"block",expression:t,description:i.notes||`IP ${i.action}: ${i.ip}${i.hostname?` (${i.hostname})`:""}`,enabled:!0}}function pn(i){let e=[],t={};if(i.action){let{action:s,warning:a}=an(i.action,i.id,"action");a&&e.push(a),s&&(t.action=s)}i.overrides&&i.overrides.length>0&&(t.rules=i.overrides.map(s=>{let a={id:s.ruleId};if(s.enabled!==void 0&&(a.enabled=s.enabled),s.action){let{action:l,warning:c}=an(s.action,i.id,`overrides[${s.ruleId}].action`);c&&e.push(c),l&&(a.action=l)}return a}));let n={id:i.ruleset,...Object.keys(t).length>0?{overrides:t}:{}};return{result:{id:i.id||crypto.randomUUID(),action:"execute",expression:"true",description:i.name||`Managed ruleset ${i.ruleset}`,enabled:i.enabled,action_parameters:n},warnings:e}}function fn(i){let e=[],t=i.action_parameters,n={id:i.id,ruleset:t?.id??"",enabled:i.enabled??!0,...i.description?{name:i.description}:{}};return t?.overrides?.action&&(n.action=ln(t.overrides.action)),t?.overrides?.rules&&t.overrides.rules.length>0&&(n.overrides=t.overrides.rules.map(o=>({ruleId:o.id,...o.action!==void 0?{action:ln(o.action)}:{},...o.enabled!==void 0?{enabled:o.enabled}:{}}))),{result:n,warnings:e}}function an(i,e,t){let o={log:"log",deny:"block",block:"block",challenge:"managed_challenge",allow:"allow"}[i];return o?{action:o}:{warning:U.createUnsupportedFeatureWarning(`'${i}' as a managed-ruleset override action`,"unified config","Cloudflare",e,t)}}function ln(i){return{log:"log",block:"deny",challenge:"challenge",managed_challenge:"challenge",js_challenge:"challenge",allow:"allow"}[i]||"deny"}function Cr(i){return{block:"deny",challenge:"challenge",managed_challenge:"challenge",js_challenge:"challenge",log:"log",skip:"bypass",allow:"allow",rewrite:"bypass",redirect:"redirect",execute:"log"}[i]||"deny"}function br(i){return{log:"log",deny:"block",block:"block",challenge:"managed_challenge",bypass:"skip",rate_limit:"block",redirect:"redirect",allow:"allow"}[i]||"block"}function vr(i,e,t){return i.filter(n=>ze.isFieldSupported(n.field)?!0:(t.push(U.createUnsupportedFeatureWarning(`condition field '${n.field}'`,"unified config","Cloudflare",e,n.field)),!1))}function Ir(i){let e=i.match(/^(\d+)([smhd])$/);if(!e||!e[1]||!e[2])throw new Error(`Invalid window format: ${i}`);let t=parseInt(e[1],10),n=e[2],s={s:1,m:60,h:3600,d:86400}[n];if(s===void 0)throw new Error(`Invalid time unit: ${n}`);return t*s}ce();function ye(i,e,t){return U.createWarning("complex_expressions",i,void 0,e,t)}var Er={ip:"ip",path:"path",domain:"host",method:"method",country:"country",scheme:"scheme",user_agent:"user_agent"},Tr={ip:"ip",path:"path",host:"domain",method:"method",country:"country",scheme:"scheme",user_agent:"user_agent"},Ar={header:"request_header",query:"query_parameter",cookie:"request_cookie"},$r={request_header:"header",query_parameter:"query",request_cookie:"cookie",response_header:"response_header",post_parameter:"post_parameter",signal:"signal"};function Pr(i){return Er[i]??i}function _r(i){return Tr[i]??null}var kr={equals:{operator:"eq",negated:!1},does_not_equal:{operator:"eq",negated:!0},contains:{operator:"contains",negated:!1},does_not_contain:{operator:"contains",negated:!0},like:{operator:"matches",negated:!1},not_like:{operator:"matches",negated:!0},in_list:{operator:"in",negated:!1},not_in_list:{operator:"in",negated:!0},matches:{operator:"matches",negated:!1},does_not_match:{operator:"matches",negated:!0},greater_equal:{operator:"ge",negated:!1},lesser_equal:{operator:"le",negated:!1}};function gn(i){return kr[i]??{operator:"eq",negated:!1}}function mn(i,e,t,n){switch(i){case"eq":return e?"does_not_equal":"equals";case"ne":return"does_not_equal";case"contains":return e?"does_not_contain":"contains";case"not_contains":return"does_not_contain";case"starts_with":case"ends_with":return t.push(U.createWarning("regex_patterns",n,void 0,`Unified operator "${i}" has no direct Fastly equivalent \u2014 mapped to Fastly's wildcard "like" operator. Verify the translated value still matches as a wildcard pattern.`)),e?"not_like":"like";case"matches":return t.push(U.createWarning("regex_patterns",n,void 0,"Regular expression pattern may need adjustment for Fastly Next-Gen WAF regex syntax.")),e?"does_not_match":"matches";case"in":return e?"not_in_list":"in_list";case"not_in":return"not_in_list";case"gt":return t.push(ye(n,'Unified operator "gt" (strictly greater than) has no Fastly equivalent \u2014 mapped to "greater_equal", which also matches the boundary value itself.')),"greater_equal";case"ge":return"greater_equal";case"lt":return t.push(ye(n,'Unified operator "lt" (strictly less than) has no Fastly equivalent \u2014 mapped to "lesser_equal", which also matches the boundary value itself.')),"lesser_equal";case"le":return"lesser_equal";default:return"equals"}}function Sr(i,e){let t=[],n=i.conditions.filter(s=>s.type==="group"),o=i.conditions.filter(s=>s.type!=="group");if(n.length>0&&o.length===0&&i.group_operator==="any")return n.forEach((s,a)=>{s.group_operator!=="all"&&e.push(ye(i.id,`A condition group using "any" (OR) inside an outer "any" (OR) rule cannot be represented in doorman's flat AND-within/OR-across condition model \u2014 its members were kept in the same group (effectively AND'd) instead of OR'd. Review the translated rule against the original Fastly logic.`));for(let l of s.conditions)At(t,l,a,e,i.id)}),{conditions:t,conditionLogic:n.length>1?"OR":"AND"};if(n.length===0)return o.forEach((s,a)=>{let l=i.group_operator==="any"?a:0;At(t,s,l,e,i.id)}),{conditions:t,conditionLogic:i.group_operator==="any"&&o.length>1?"OR":"AND"};e.push(ye(i.id,`Rule mixes top-level conditions with condition groups under group_operator "${i.group_operator}" \u2014 this doesn't fit doorman's flat AND-within/OR-across condition model. All conditions were combined with AND; review the translated rule against the original Fastly rule logic.`));for(let s of o)At(t,s,0,e,i.id);for(let s of n)for(let a of s.conditions)At(t,a,0,e,i.id);return{conditions:t,conditionLogic:"AND"}}function At(i,e,t,n,o){if(e.type==="single"){let{operator:u,negated:p}=gn(e.operator);i.push({field:Pr(e.field),operator:u,value:e.value,...p?{negated:!0}:{},group:t});return}let s=$r[e.field],a=e.conditions.find(u=>u.field==="name"),l=e.conditions.find(u=>u.field==="value"||u.field==="value_string");if(!l){if(a){i.push({field:s,operator:e.operator==="does_not_exist"?"not_exists":"exists",key:a.value,group:t});return}n.push(ye(o,`Multival condition on '${e.field}' has no value or name sub-condition doorman could extract \u2014 skipped.`));return}let{operator:c,negated:d}=gn(l.operator);i.push({field:s,operator:c,value:l.value,...d?{negated:!0}:{},...a?{key:a.value}:{},group:t})}function Ur(i,e){i.actions.length>1&&e.push(ye(i.id,`Rule has ${i.actions.length} actions \u2014 doorman only models one action per rule; only the first ("${i.actions[0]?.type}") was translated.`));let t=i.actions[0];if(i.type==="rate_limit")return{type:"rate_limit",rateLimit:i.rate_limit?{requests:i.rate_limit.threshold,window:`${i.rate_limit.interval}s`,mitigationTimeout:i.rate_limit.duration}:void 0};switch(t?.type){case"block":return{type:"block"};case"allow":return{type:"allow"};case"browser_challenge":case"dynamic_challenge":case"verify_token":return{type:"challenge"};case"redirect":return{type:"redirect",redirect:{location:t.redirect_url??"",...t.response_code!==void 0?{statusCode:t.response_code}:{}}};case"add_signal":case"exclude_signal":case"deception":return e.push(U.createWarning("action_differences",i.id,"action.type",`Fastly action "${t.type}" tags or excludes a WAF signal rather than allowing/blocking the request \u2014 doorman has no matching action type. Mapped to "log" as the closest non-destructive fallback; review this rule.`)),{type:"log"};default:return e.push(ye(i.id,`Unrecognised Fastly action type "${String(t?.type)}" \u2014 mapped to "log".`)),{type:"log"}}}function hn(i){let e=[],{conditions:t,conditionLogic:n}=Sr(i,e),o=Ur(i,e);return t.length>10&&e.push(U.createWarning("many_conditions",i.id,void 0,`Rule has ${t.length} conditions which may impact performance`)),{result:{id:i.id,name:i.description||i.id,enabled:i.enabled,conditions:t,conditionLogic:n,action:o},warnings:e}}function Lr(i,e,t){let n=new Map;for(let a of i){let l=a.group??0,c=n.get(l);c?c.push(a):n.set(l,[a])}let o=a=>{let l=Ar[a.field];if(l){if(a.key===void 0)return t.push(ye(e,`Condition on '${a.field}' has no 'key' set \u2014 Fastly needs a specific ${a.field} name to match on (e.g. a header name). Skipped.`)),null;if(a.operator==="exists"||a.operator==="not_exists")return{type:"multival",field:l,operator:a.operator==="exists"?"exists":"does_not_exist",group_operator:"all",conditions:[{type:"single",field:"name",operator:"equals",value:a.key}]};let d=l==="request_header"||l==="response_header"?"value_string":"value";return{type:"multival",field:l,operator:"exists",group_operator:"all",conditions:[{type:"single",field:"name",operator:"equals",value:a.key},{type:"single",field:d,operator:mn(a.operator,a.negated,t,e),value:String(a.value)}]}}if(a.operator==="exists"||a.operator==="not_exists")return t.push(U.createUnsupportedFeatureWarning(`"${a.operator}" on field '${a.field}'`,"unified config","Fastly",e,a.field)),null;let c=_r(a.field);return c?{type:"single",field:c,operator:mn(a.operator,a.negated,t,e),value:String(a.value)}:(t.push(U.createUnsupportedFeatureWarning(`condition field '${a.field}'`,"unified config","Fastly",e,a.field)),null)};if(n.size<=1){let a=i.map(o).filter(l=>l!==null);return{conditions:a.filter(l=>l.type==="single"),groupConditions:[],multivalConditions:a.filter(l=>l.type==="multival"),groupOperator:"all"}}let s=[];for(let a of n.values()){let l=a.map(o).filter(c=>c!==null);l.length>0&&s.push({type:"group",group_operator:"all",conditions:l})}return{conditions:[],groupConditions:s,multivalConditions:[],groupOperator:"any"}}function xr(i,e,t){switch(i.type){case"block":case"deny":return{type:"block"};case"allow":return{type:"allow"};case"challenge":return{type:"browser_challenge"};case"bypass":return e.push(ye(t,'Unified action "bypass" has no direct Fastly equivalent \u2014 mapped to "allow".')),{type:"allow"};case"log":return e.push(ye(t,'Unified action "log" has no dedicated Fastly action \u2014 mapped to "allow" (request_logging is enabled at the rule level regardless).')),{type:"allow"};case"redirect":return i.redirect?{type:"redirect",redirect_url:i.redirect.location,response_code:i.redirect.statusCode??(i.redirect.permanent?301:302)}:(e.push(ye(t,'Unified action "redirect" has no redirect target \u2014 mapped to "allow".')),{type:"allow"});case"rate_limit":return{type:"block_signal"};default:return e.push(U.createUnsupportedFeatureWarning(`action type '${i.type}'`,"unified config","Fastly",t,"action.type")),{type:"block"}}}function Dr(i,e,t){let n=/^(\d+)\s*(s|m|h)?$/i.exec(i.trim()),o={s:1,m:60,h:3600},s=n?Number(n[1])*o[n[2]?.toLowerCase()||"s"]:NaN,a=[60,600,3600];if(!Number.isFinite(s)||s<=0)return e.push(U.createWarning("rate_limiting_precision",t,void 0,`Rate limit window "${i}" could not be parsed \u2014 defaulted to Fastly's 60-second interval.`)),60;let l=a.reduce((c,d)=>Math.abs(d-s)<Math.abs(c-s)?d:c);return l!==s&&e.push(U.createWarning("rate_limiting_precision",t,void 0,`Rate limit window "${i}" (${s}s) isn't one of Fastly's supported intervals (60s/600s/3600s) \u2014 rounded to ${l}s.`)),l}function Or(i,e,t){if(!i)throw new Error(`Rule "${e??"(unnamed)"}" has action type "rate_limit" but no rateLimit configuration \u2014 Fastly requires a threshold and interval.`);let n=Dr(i.window,t,e);return t.push(U.createWarning("rate_limiting_precision",e,void 0,`Fastly rate-limit rules count requests against a named custom signal, which doorman does not create automatically \u2014 create a signal named "doorman-rate-limit-${e??"rule"}" in this workspace before syncing, or the rule will be rejected.`)),{client_identifiers:[{type:"ip"}],duration:i.mitigationTimeout??n,interval:n,signal:`doorman-rate-limit-${e??"rule"}`,threshold:i.requests}}function yn(i){let e=[];i.description&&i.description!==i.name&&e.push(ye(i.id,'Fastly rules have no separate "name" field \u2014 only "description" carries over, and it was set from this rule\'s `name`. The `description` text was dropped rather than merged in, to keep it round-trip-stable on the next sync.'));let{conditions:t,groupConditions:n,multivalConditions:o,groupOperator:s}=Lr(i.conditions,i.id,e);if(t.length===0&&n.length===0&&o.length===0)throw new Error(`Rule "${i.name}" has no conditions Fastly can represent \u2014 every condition field is unsupported by Fastly. Cannot sync this rule to Fastly.`);let a=i.action.type==="rate_limit";return{result:{type:a?"rate_limit":"request",description:i.name,enabled:i.enabled,group_operator:s,request_logging:"sampled",conditions:t,group_conditions:n,multival_conditions:o,actions:[xr(i.action,e,i.id)],rate_limit:a?Or(i.action.rateLimit,i.id,e):null},warnings:e}}function Rn(i,e){return i.map(t=>({ip:t,action:e}))}function wn(i){let e=[],t=[],n=[];for(let o of i)(o.hostname||o.notes)&&e.push(ye(o.id,`IP rule "${o.ip}" sets ${o.hostname?"`hostname`":""}${o.hostname&&o.notes?" and ":""}${o.notes?"`notes`":""} \u2014 Fastly IP lists have no equivalent per-entry metadata; only the address was kept.`)),(o.action==="allow"?n:t).push(o.ip);return{result:{denyEntries:t,allowEntries:n},warnings:e}}ce();function Ke(i){return i.replace(/\\/g,"\\\\").replace(/'/g,"\\'")}function Cn(i){let e="";for(let t=0;t<i.length;t++)i[t]==="\\"&&(i[t+1]==="\\"||i[t+1]==="'")?(e+=i[t+1],t++):e+=i[t];return e}var Fr=new Set(["region","city","port","scheme"]),Nr={host:"host",user_agent:"user-agent",referer:"referer"},st=class{static fromUnifiedConditions(e,t="AND"){if(!e||e.length===0)throw new Error("At least one condition is required");if(!e.some(a=>a.group!==void 0)){let a=e.map(c=>this.fromUnifiedCondition(c)),l=t==="AND"?" && ":" || ";return a.length>1?`(${a.join(l)})`:a[0]}let o=new Map;for(let a of e){let l=a.group??0,c=o.get(l);c?c.push(a):o.set(l,[a])}let s=Array.from(o.values()).map(a=>this.combineWithAnd(a.map(l=>this.fromUnifiedCondition(l))));return s.length>1?this.combineWithOr(s):s[0]}static fromUnifiedCondition(e){if(Fr.has(e.field))throw new Error(`Cloud Armor has no CEL equivalent for the "${e.field}" condition field \u2014 this rule cannot be represented and would otherwise be silently dropped.`);if(e.field==="ip")return this.buildIpCondition(e);if(e.field==="cookie")return this.buildCookieCondition(e);let t=Nr[e.field];if(t)return this.buildHeaderCondition(t,e);if(e.field==="header"||e.field==="query"){if(e.field==="header"){if(!e.key)throw new Error('A "header" condition requires a key naming the header');return this.buildHeaderCondition(e.key,e)}return this.buildComparison("request.query",e,!1)}let n=Br[e.field];if(n)return this.buildComparison(n.path,e,n.numeric);throw new Error(`Cloud Armor CEL builder has no mapping for condition field "${e.field}"`)}static buildIpCondition(e){let t=(Array.isArray(e.value)?e.value:[e.value]).map(String);if(t.length===0||t.some(a=>!a))throw new Error('An "ip" condition requires at least one value');let n=t.map(a=>{if(!Te.safeParse(a).success)throw new Error(`Invalid IP address or CIDR range: ${a}`);return a.includes("/")?`inIpRange(origin.ip, '${Ke(a)}')`:`origin.ip == '${Ke(a)}'`}),o=this.isNegated(e,e.operator==="not_in"||e.operator==="ne");if(n.length>1){let a=`(${n.join(" || ")})`;return o?`!${a}`:a}let s=n[0];return o?s.startsWith("inIpRange(")?`!${s}`:`!(${s})`:s}static buildHeaderCondition(e,t){let n=`request.headers['${Ke(e)}']`,o=`has(${n})`;return t.operator==="exists"||t.operator==="not_exists"?this.isNegated(t,t.operator==="not_exists")?`!${o}`:o:`(${o} && ${this.buildComparison(n,t,!1)})`}static buildCookieCondition(e){let t="request.headers['cookie']",n=`has(${t})`;if(e.operator==="exists"||e.operator==="not_exists")return this.isNegated(e,e.operator==="not_exists")?`!${n}`:n;if(!e.key)return`(${n} && ${this.buildComparison(t,e,!1)})`;if(!["eq","ne","contains","not_contains"].includes(e.operator))throw new Error(`Cloud Armor can only check a specific cookie's value via substring search (has no parsed cookie map) \u2014 "${e.operator}" on a keyed cookie condition has no unambiguous CEL representation.`);let o=`${e.key}=${String(e.value)}`,s=`${t}.contains('${Ke(o)}')`,a=this.isNegated(e,e.operator==="ne"||e.operator==="not_contains");return`(${n} && ${a?`!${s}`:s})`}static buildComparison(e,t,n){let{value:o}=t;if(t.operator==="exists"||t.operator==="not_exists")throw new Error(`"${t.operator}" is not meaningful for Cloud Armor's "${e}" \u2014 it is always present`);let s=t.operator,a=!1;if(t.negated){let d=Vr[s];d?s=d:a=!0}if(s==="in"||s==="not_in"){let u=(Array.isArray(o)?o:[o]).map(f=>this.formatLiteral(f,n)).join(", "),p=`${e} in [${u}]`;return s==="not_in"?`!(${p})`:p}let l=this.formatLiteral(o,n),c;switch(s){case"eq":c=`${e} == ${l}`;break;case"ne":c=`${e} != ${l}`;break;case"contains":c=`${e}.contains(${l})`;break;case"not_contains":c=`!${e}.contains(${l})`;break;case"starts_with":c=`${e}.startsWith(${l})`;break;case"ends_with":c=`${e}.endsWith(${l})`;break;case"matches":c=`${e}.matches(${l})`;break;case"gt":c=`${e} > ${l}`;break;case"ge":c=`${e} >= ${l}`;break;case"lt":c=`${e} < ${l}`;break;case"le":c=`${e} <= ${l}`;break;default:throw new Error(`Cloud Armor CEL builder has no mapping for operator "${s}"`)}return a?`!(${c})`:c}static isNegated(e,t){return t!==!!e.negated}static formatLiteral(e,t){if(t){let n=Number(e);if(!Number.isFinite(n))throw new Error(`Expected a numeric value, got "${String(e)}"`);return String(n)}return`'${Ke(String(e))}'`}static combineWithAnd(e){if(e.length===0)throw new Error("At least one expression is required");return e.length===1?e[0]:`(${e.join(" && ")})`}static combineWithOr(e){if(e.length===0)throw new Error("At least one expression is required");return e.length===1?e[0]:`(${e.join(" || ")})`}},Vr={eq:"ne",ne:"eq",contains:"not_contains",not_contains:"contains",in:"not_in",not_in:"in"},Br={country:{path:"origin.region_code",numeric:!1},asn:{path:"origin.asn",numeric:!0},path:{path:"request.path",numeric:!1},method:{path:"request.method",numeric:!1}};var Mr={"origin.region_code":"country","origin.asn":"asn","request.path":"path","request.method":"method","request.query":"query"},qr={host:"host","user-agent":"user_agent",referer:"referer",cookie:"cookie"},An={contains:"contains",startsWith:"starts_with",endsWith:"ends_with",matches:"matches"};function Wr(i){let e=[],t=0,n=i.length,o=(s,a)=>i[t]===s&&i[t+1]===a;for(;t<n;){let s=i[t];if(/\s/.test(s)){t++;continue}if(s==="("){e.push({type:"LPAREN",value:s}),t++;continue}if(s===")"){e.push({type:"RPAREN",value:s}),t++;continue}if(s==="["){e.push({type:"LBRACKET",value:s}),t++;continue}if(s==="]"){e.push({type:"RBRACKET",value:s}),t++;continue}if(s===","){e.push({type:"COMMA",value:s}),t++;continue}if(o("&","&")){e.push({type:"AND",value:"&&"}),t+=2;continue}if(o("|","|")){e.push({type:"OR",value:"||"}),t+=2;continue}if(o("=","=")){e.push({type:"EQ",value:"=="}),t+=2;continue}if(o("!","=")){e.push({type:"NE",value:"!="}),t+=2;continue}if(o(">","=")){e.push({type:"GE",value:">="}),t+=2;continue}if(o("<","=")){e.push({type:"LE",value:"<="}),t+=2;continue}if(s==="!"){e.push({type:"NOT",value:"!"}),t++;continue}if(s===">"){e.push({type:"GT",value:">"}),t++;continue}if(s==="<"){e.push({type:"LT",value:"<"}),t++;continue}if(s==="."){e.push({type:"DOT",value:s}),t++;continue}if(s==="'"){let a=t+1,l="",c=!1;for(;a<n;){let d=i[a];if(d==="\\"&&a+1<n){l+=d+i[a+1],a+=2;continue}if(i[a]==="'"){c=!0,a++;break}l+=i[a],a++}if(!c)throw new W("Unterminated string literal");e.push({type:"STRING",value:Cn(l)}),t=a;continue}if(/[0-9]/.test(s)||s==="-"&&/[0-9]/.test(i[t+1]??"")){let a=t+1;for(;a<n&&/[0-9.]/.test(i[a]);)a++;e.push({type:"NUMBER",value:i.slice(t,a)}),t=a;continue}if(/[A-Za-z_]/.test(s)){let a=t;for(;a<n&&/[A-Za-z0-9_]/.test(i[a]);)a++;e.push({type:"IDENT",value:i.slice(t,a)}),t=a;continue}throw new W(`Unexpected character '${s}' at position ${t}`)}return e}function jr(i){let e=i.expect("IDENT").value;for(;i.peek()?.type==="DOT";){let t=i.pos;i.next();let n=i.expect("IDENT");if(i.peek()?.type==="LPAREN"){i.pos=t;break}e+=`.${n.value}`}return e}function Zt(i){let e=jr(i);if(i.peek()?.type==="LBRACKET"){i.next();let t=i.expect("STRING");return i.expect("RBRACKET"),{field:e,key:t.value}}return{field:e}}function bn(i){let e=i.next();if(e.type==="STRING")return e.value;if(e.type==="NUMBER")return Number(e.value);throw new W(`Unexpected token '${e.value}' where a literal was expected`)}function zr(i){if(i.is("IDENT","has")){i.next(),i.expect("LPAREN");let a=Zt(i);return i.expect("RPAREN"),{type:"has",...a}}if(i.is("IDENT","inIpRange")){i.next(),i.expect("LPAREN");let a=Zt(i);if(a.field!=="origin.ip")throw new W(`inIpRange must reference origin.ip, got '${a.field}'`);i.expect("COMMA");let l=i.expect("STRING");return i.expect("RPAREN"),{type:"inIpRange",value:l.value}}let e=Zt(i);if(i.peek()?.type==="DOT"){i.next();let a=i.expect("IDENT").value;if(!An[a])throw new W(`Unrecognized method '${a}'`);i.expect("LPAREN");let l=i.expect("STRING");return i.expect("RPAREN"),{type:"methodCall",...e,method:a,value:l.value}}if(i.is("IDENT","in")){i.next(),i.expect("LBRACKET");let a=[];for(;i.peek()?.type!=="RBRACKET";)a.push(bn(i)),i.peek()?.type==="COMMA"&&i.next();return i.expect("RBRACKET"),{type:"membership",...e,values:a}}let t=i.next(),o={EQ:"eq",NE:"ne",GT:"gt",GE:"ge",LT:"lt",LE:"le"}[t.type];if(!o)throw new W(`Unexpected token '${t.value}' after field reference`);let s=bn(i);return{type:"comparison",...e,operator:o,value:s}}function vn(i){if(i.peek()?.type==="LPAREN"){i.next();let e=$n(i);return i.expect("RPAREN"),e}return zr(i)}function In(i){return i.peek()?.type==="NOT"?(i.next(),{type:"not",child:vn(i)}):vn(i)}function En(i){let e=[In(i)];for(;i.peek()?.type==="AND";)i.next(),e.push(In(i));return e.length===1?e[0]:{type:"and",children:e}}function $n(i){let e=[En(i)];for(;i.peek()?.type==="OR";)i.next(),e.push(En(i));return e.length===1?e[0]:{type:"or",children:e}}function Gr(i){let e=new Ge(Wr(i)),t=$n(e);if(!e.atEnd())throw new W("Unexpected trailing tokens");return t}function Kr(i){return i.type==="inIpRange"||i.type==="comparison"&&i.field==="origin.ip"&&i.operator==="eq"}function Qt(i){return i.type==="or"&&i.children.length>0&&i.children.every(Kr)}function Hr(i){return i.type==="inIpRange"?i.value:String(i.value)}function Pn(i){if(i.type!=="and"||i.children.length!==2)return!1;let e=i.children[0],t=i.children[1];if(e.type!=="has")return!1;let n=t.type==="not"?t.child:t;return!("field"in n)||n.field!==e.field||n.key!==e.key?!1:n.type==="comparison"||n.type==="methodCall"||n.type==="membership"}function Zr(i){let e=i.children[0],t=i.children[1],n=t.type==="not",o=n?t.child:t;return{guard:e,comparisonNegated:n,comparison:o}}function Yt(i){if(i.type==="has"||i.type==="comparison"||i.type==="methodCall"||i.type==="membership"||i.type==="inIpRange"||Qt(i)||Pn(i))return!0;if(i.type==="not"){let e=i.child;return e.type==="has"||e.type==="comparison"||e.type==="methodCall"||e.type==="membership"||e.type==="inIpRange"||Qt(e)}return!1}function _n(i,e){if(i==="origin.ip")return{unifiedField:"ip"};let t=Mr[i];if(t)return{unifiedField:t};if(i==="request.headers"&&e!==void 0){let n=qr[e];return n?{unifiedField:n}:{unifiedField:"header",unifiedKey:e}}return null}function Jt(i,e){let t=i.type==="not",n=i.type==="not"?i.child:i;if(Qt(n)){let o=n.children.map(s=>Hr(s));return{field:"ip",operator:t?"not_in":"in",value:o,...e!==void 0?{group:e}:{}}}if(n.type==="inIpRange"||n.type==="comparison"&&n.field==="origin.ip"){let o=n.type==="inIpRange"?n.value:String(n.value);return{field:"ip",operator:t?"ne":"eq",value:o,...e!==void 0?{group:e}:{}}}if(n.type==="and"&&Pn(n)){if(t)return null;let{guard:o,comparisonNegated:s,comparison:a}=Zr(n);return a.type!=="comparison"&&a.type!=="methodCall"&&a.type!=="membership"?null:Tn(a,s,o.field,o.key,e)}if(n.type==="comparison"||n.type==="methodCall"||n.type==="membership")return Tn(n,t,n.field,n.key,e);if(n.type==="has"){let o=_n(n.field,n.key);return o?{field:o.unifiedField,operator:t?"not_exists":"exists",value:"",...o.unifiedKey!==void 0?{key:o.unifiedKey}:{},...e!==void 0?{group:e}:{}}:null}return null}function Tn(i,e,t,n,o){let s=_n(t,n);if(!s)return null;let a,l,c=!1;if(i.type==="comparison")e?i.operator==="eq"?a="ne":i.operator==="ne"?a="eq":(a=i.operator,c=!0):a=i.operator,l=i.value;else if(i.type==="methodCall"){let d=An[i.method];if(!d)return null;e?d==="contains"?a="not_contains":(a=d,c=!0):a=d,l=i.value}else a=e?"not_in":"in",l=i.values;return{field:s.unifiedField,operator:a,value:l,...c?{negated:!0}:{},...s.unifiedKey!==void 0?{key:s.unifiedKey}:{},...o!==void 0?{group:o}:{}}}function Yr(i){if(Yt(i)){let e=Jt(i,void 0);return e?{conditions:[e],conditionLogic:"AND"}:null}if(i.type==="and"){let e=[];for(let t of i.children){if(!Yt(t))return null;let n=Jt(t,void 0);if(!n)return null;e.push(n)}return{conditions:e,conditionLogic:"AND"}}return i.type==="or"?Tt(i.children,Yt,e=>e.type==="and",Jt):null}function Ne(i){if(!i||!i.trim())return null;try{let e=Gr(i);return Yr(e)}catch{return null}}function Xt(i){if(!i.match.expr)throw new Error(`Cloud Armor rule at priority ${i.priority} has no CEL match expression (uses the basic versionedExpr/config shape) \u2014 caller must filter these out before translating.`);return i.match.expr.expression}function kn(i){if(i.priority===void 0)throw new Error(`Rule "${i.name}" has no priority assigned \u2014 CloudArmorFirewallService must assign one before calling unifiedToGcp`);let e=[],t=st.fromUnifiedConditions(i.conditions,i.conditionLogic),{action:n,actionWarning:o}=Qr(i.action.type,i.name,i.id);o&&e.push(o);let s={priority:i.priority,description:(i.description||i.name).slice(0,64),match:{expr:{expression:t}},action:n,preview:i.action.type==="log"?!0:void 0};if((n==="throttle"||n==="rate_based_ban")&&i.action.rateLimit){let a=Xr(i.action.rateLimit.window);s.rateLimitOptions={rateLimitThreshold:{count:i.action.rateLimit.requests,intervalSec:a},conformAction:"allow",exceedAction:"deny(429)",enforceOnKey:"IP"}}else n==="throttle"&&!i.action.rateLimit&&e.push(U.createWarning("lossy_conversion",i.id,"action.rateLimit",`Rule "${i.name}" maps to Cloud Armor's "throttle" action but declares no rateLimit block \u2014 Cloud Armor requires rateLimitOptions for this action; the rule will likely be rejected by the API.`));return n==="redirect"&&(i.action.redirect?s.redirectOptions={type:"EXTERNAL_302",target:i.action.redirect.location}:e.push(U.createWarning("lossy_conversion",i.id,"action.redirect",`Rule "${i.name}" maps to Cloud Armor's "redirect" action but declares no redirect target \u2014 Cloud Armor requires redirectOptions for this action; the rule will likely be rejected by the API.`))),{result:s,warnings:e}}function Sn(i,e){let t=[],n=e!==void 0?e:Ne(Xt(i)),o=[],s;n?(o=n.conditions,s=n.conditionLogic):t.push(U.createWarning("complex_expressions",String(i.priority),"match.expr.expression","CEL expression could not be parsed back into structured conditions \u2014 it may be hand-authored or use syntax outside what doorman itself generates.","Review the translated rule and add missing conditions manually if needed."));let a={type:Jr(i.action),...i.rateLimitOptions?{rateLimit:{requests:i.rateLimitOptions.rateLimitThreshold.count,window:`${i.rateLimitOptions.rateLimitThreshold.intervalSec}s`}}:{},...i.redirectOptions?{redirect:{location:i.redirectOptions.target}}:{}};return{result:{id:String(i.priority),name:i.description||`Rule ${i.priority}`,...i.description?{description:i.description}:{},enabled:!i.preview,conditions:o,...s==="OR"?{conditionLogic:s}:{},action:a,priority:i.priority},warnings:t}}function Un(i,e){if(i.action!=="allow"&&i.action!=="deny(403)"||i.preview)return!1;let t=e!==void 0?e:Ne(Xt(i));if(!t||t.conditions.length!==1)return!1;let n=t.conditions[0];return n.field==="ip"&&(n.operator==="eq"||n.operator==="in")&&!n.key}function Ln(i,e){let o=(e!==void 0?e:Ne(Xt(i))).conditions[0].value;return{id:String(i.priority),ip:Array.isArray(o)?String(o[0]):String(o),...i.description?{notes:i.description}:{},action:i.action==="allow"?"allow":"deny"}}function xn(i,e){if(!Te.safeParse(i.ip).success)throw new Error(`Invalid IP address or CIDR range: ${i.ip}`);let t=st.fromUnifiedCondition({field:"ip",operator:"eq",value:i.ip});return{priority:e,description:(i.notes||`IP ${i.action}: ${i.ip}${i.hostname?` (${i.hostname})`:""}`).slice(0,64),match:{expr:{expression:t}},action:i.action==="allow"?"allow":"deny(403)"}}function Jr(i){return{allow:"allow","deny(403)":"deny","deny(404)":"deny","deny(502)":"deny",rate_based_ban:"rate_limit",throttle:"rate_limit",redirect:"redirect"}[i]||"deny"}function Qr(i,e,t){switch(i){case"log":return{action:"allow"};case"deny":case"block":return{action:"deny(403)"};case"challenge":return{action:"deny(403)",actionWarning:U.createUnsupportedFeatureWarning("challenge action","unified config","Cloud Armor",t,"action")};case"bypass":return{action:"allow"};case"rate_limit":return{action:"throttle"};case"redirect":return{action:"redirect"};case"allow":return{action:"allow"};default:return{action:"deny(403)",actionWarning:U.createWarning("lossy_conversion",t,"action",`Rule "${e}" has unrecognized action "${i}" \u2014 defaulted to Cloud Armor's "deny(403)".`)}}}function Xr(i){let e=i.match(/^(\d+)([smhd])$/);if(!e||!e[1]||!e[2])throw new Error(`Invalid window format: ${i}`);let t=parseInt(e[1],10),o={s:1,m:60,h:3600,d:86400}[e[2]];if(o===void 0)throw new Error(`Invalid time unit: ${e[2]}`);return t*o}var E=class{static vercelToUnified=Ji;static unifiedToVercel=Qi;static vercelIPToUnified=Xi;static cloudflareToUnified=cn;static unifiedToCloudflare=dn;static unifiedIPToCloudflare=un;static unifiedManagedRuleGroupToCloudflare=pn;static cloudflareToUnifiedManagedRuleGroup=fn;static fastlyToUnified=hn;static unifiedToFastly=yn;static fastlyListEntriesToUnified=Rn;static unifiedIPsToFastlyEntries=wn;static gcpToUnified=Sn;static unifiedToGcp=kn;static gcpToUnifiedIP=Ln;static unifiedIPToGcp=xn;static gcpLooksLikeIpRule=Un};ce();function Le(i,e){if(i===e)return!0;if(typeof i!="object"||i===null||typeof e!="object"||e===null||Array.isArray(i)!==Array.isArray(e))return!1;let t=Object.keys(i),n=Object.keys(e);if(t.length!==n.length)return!1;for(let o of t)if(!n.includes(o)||!Le(i[o],e[o]))return!1;return!0}function Re(i){let{id:e,...t}=i;return t}function at(i){return[...i].sort((e,t)=>{let n=e.priority!==void 0,o=t.priority!==void 0;return!n&&!o?0:n?o?e.priority-t.priority:-1:1})}function $t(i){return i.some(e=>e.priority!==void 0)}var He=class extends $e{constructor(t){super();this.client=t}client;name="vercel";async fetchConfig(t){try{r.debug("Fetching Vercel firewall configuration");let n=await this.client.fetchFirewallConfig(t),o=n.rules.map(a=>{let l=E.vercelToUnified(a);return l.warnings.length>0&&l.warnings.forEach(c=>{let{TranslationWarningSystem:d}=(ce(),me(Pe)),u=d.formatWarning(c);r.warn(`Rule ${a.name}:
24
+ `),u.cancelled&&process.exit(0),e(o))};t.on("keypress",a)})}_();var Wi={provider:"vercel",fields:[{key:"token",envVar:"VERCEL_TOKEN",label:"Vercel API Token",required:!0,secret:!0,promptMessage:"What is your Vercel API Auth Token? (https://vercel.com/guides/how-do-i-use-a-vercel-api-access-token#creating-an-access-token) "},{key:"projectId",envVar:"VERCEL_PROJECT_ID",label:"Vercel Project ID",required:!0,configKey:"projectId",cliAlias:"p",legacyConfigKey:"projectId",promptMessage:"What is your Vercel Project ID? (See https://vercel.com/docs/projects/project-configuration/general-settings#project-id)"},{key:"teamId",envVar:"VERCEL_TEAM_ID",label:"Vercel Team ID",required:!1,configKey:"teamId",cliAlias:"t",legacyConfigKey:"teamId",promptMessage:"What is your Vercel Team ID? Leave blank to use your Vercel default team (every account, including Hobby, is a team). Only needed if you belong to more than one team and want to target a non-default one. (See https://vercel.com/docs/accounts#find-your-team-id)"}]};var ji={provider:"cloudflare",fields:[{key:"apiToken",envVar:"CLOUDFLARE_API_TOKEN",label:"Cloudflare API Token",required:!0,secret:!0},{key:"zoneId",envVar:"CLOUDFLARE_ZONE_ID",label:"Cloudflare Zone ID",required:!0,configKey:"zoneId"},{key:"accountId",envVar:"CLOUDFLARE_ACCOUNT_ID",label:"Cloudflare Account ID",required:!1,configKey:"accountId"}]};var zi={provider:"fastly",fields:[{key:"apiToken",envVar:"FASTLY_API_TOKEN",label:"Fastly API Token",required:!0,secret:!0},{key:"workspaceId",envVar:"FASTLY_WORKSPACE_ID",label:"Fastly Next-Gen WAF Workspace ID",required:!0,configKey:"workspaceId"}]};var Gi={provider:"gcp",fields:[{key:"serviceAccountKeyPath",envVar:"GOOGLE_APPLICATION_CREDENTIALS",label:"GCP Service Account Key Path",required:!1,promptMessage:"Path to a GCP service account JSON key file (leave blank to use Application Default Credentials \u2014 gcloud auth application-default login, or the GCE/Cloud Run metadata server): "},{key:"projectId",envVar:"GOOGLE_CLOUD_PROJECT",label:"GCP Project ID",required:!0,configKey:"projectId"},{key:"policyName",envVar:"GCP_POLICY_NAME",label:"Cloud Armor Security Policy Name",required:!0,configKey:"policyName"}]};function Ki(i,e={}){let{explicit:t={},config:n={},env:o=process.env}=e,s={};for(let a of i.fields)s[a.key]=t[a.key]||n[a.key]||o[a.envVar];return s}function Hi(i,e){return i.fields.filter(t=>t.required&&!e[t.key])}var qe={vercel:Wi,cloudflare:ji,fastly:zi,gcp:Gi};var It=class{static detectionField(e){return qe[e].fields.find(t=>t.required&&t.configKey)}static detect(e){let t=[];if(e&&"provider"in e){let o=e.provider;if(typeof o=="string"&&this.isValidProvider(o))return t.push(`Explicit provider specified in config: ${e.provider}`),{provider:o,confidence:"high",reasons:t};r.warn(`Invalid provider specified in config: ${String(e.provider)}`)}if(e&&"providers"in e&&typeof e.providers=="object"&&e.providers!==null){let o=e.providers;for(let s of K){let a=this.detectionField(s);if(!a?.configKey)continue;if(typeof(o[s]||{})[a.configKey]=="string")return t.push(`${a.label} found in config`),{provider:s,confidence:"high",reasons:t}}}if(e&&"projectId"in e&&typeof e.projectId=="string")return t.push("Legacy Vercel project ID found in config"),{provider:"vercel",confidence:"high",reasons:t};let n=this.detectFromEnvironment();return n.provider?n:(r.debug("Unable to auto-detect provider"),{provider:null,confidence:"low",reasons:["No provider information found in config or environment"]})}static detectFromEnvironment(){let e=[],t=process.env.DOORMAN_PROVIDER;if(t&&this.isValidProvider(t))return e.push(`Provider set via DOORMAN_PROVIDER environment variable: ${t}`),{provider:t,confidence:"high",reasons:e};for(let n of K){let o=this.detectionField(n);if(!(!o||!process.env[o.envVar]))return e.push(`${o.label} found in environment (${o.envVar})`),{provider:n,confidence:"medium",reasons:e}}return{provider:null,confidence:"low",reasons:["No provider credentials found in environment"]}}static isValidProvider(e){return K.includes(e)}static getProvider(e,t="vercel"){let n=this.detect(e);return n.provider?(r.debug(`Provider detected: ${n.provider} (${n.confidence} confidence)`),n.reasons.length>0&&r.debug(`Detection reasons: ${n.reasons.join(", ")}`),n.provider):(r.debug(`No provider detected, using fallback: ${t}`),t)}static detectAll(e){let t=new Set;if(e&&"provider"in e){let n=e.provider;typeof n=="string"&&this.isValidProvider(n)&&t.add(n)}if(e&&"providers"in e&&typeof e.providers=="object"&&e.providers!==null){let n=e.providers;for(let o of K){let s=this.detectionField(o);if(!s?.configKey)continue;typeof(n[o]||{})[s.configKey]=="string"&&t.add(o)}}e&&"projectId"in e&&typeof e.projectId=="string"&&t.add("vercel");for(let n of K){let o=this.detectionField(n);o&&process.env[o.envVar]&&t.add(n)}return Array.from(t)}};_();Ee();import Zi from"chalk";var Et=i=>({$schema:Ei,firewallEnabled:!0,rules:[],ips:[],...i});_();var Ae=class{baseUrl;providerName;rateLimitInfo={};constructor(e,t){this.baseUrl=e,this.providerName=t}async makeRequest(e,t={}){let n=`${this.baseUrl}${e}`,{timeout:o=3e4,retries:s=3,retryDelay:a=1e3,...l}=t,c=null,d=null,u=null,p=()=>{u&&(clearTimeout(u),u=null),d&&(d.abort(),d=null)};try{for(let f=0;f<=s;f++)try{d=new AbortController,u=setTimeout(()=>{d&&d.abort()},o);let g=await fetch(n,{...l,headers:{"Content-Type":"application/json",...await this.getAuthHeaders(),...l.headers},signal:d.signal});if(u&&(clearTimeout(u),u=null),this.updateRateLimitInfo(g),g.status===429){let v=this.calculateRateLimitWait(f,g);if(f===s)throw new Error(`${this.providerName} API rate limit exceeded after ${s+1} attempt(s) (429 Too Many Requests). Retry after ~${Math.round(v/1e3)}s.`);r.warn(`Rate limit exceeded for ${this.providerName}. Waiting ${v}ms before retry (attempt ${f+1}/${s+1})...`),await this.delay(v);continue}if(!g.ok)throw await this.handleErrorResponse(g);let m;try{let v=await g.text();v.trim()?m=JSON.parse(v):m={}}catch(v){throw r.error(`Failed to parse response from ${n}:`,v),new Error(`Invalid JSON response from ${this.providerName} API: ${v instanceof Error?v.message:String(v)}`)}return this.isLargeResponse(m)&&(r.debug(`Large response detected from ${n}, validating structure...`),this.validateLargeResponse(m)),m}catch(g){if(c=g,u&&(clearTimeout(u),u=null),this.isNonRetryableError(g)||f===s)throw g;let m=a*Math.pow(2,f),v=Math.random()*.1*m,j=Math.min(m+v,3e4);r.debug(`Request failed (attempt ${f+1}/${s+1}). Retrying in ${Math.round(j)}ms...`),r.debug(`Error details: ${g instanceof Error?g.message:String(g)}`),await this.delay(j)}throw c||new Error("Request failed after all retries")}finally{p()}}async get(e,t){return this.makeRequest(e,{...t,method:"GET"})}async post(e,t,n){return this.makeRequest(e,{...n,method:"POST",body:t?JSON.stringify(t):void 0})}async put(e,t,n){return this.makeRequest(e,{...n,method:"PUT",body:t?JSON.stringify(t):void 0})}async patch(e,t,n){return this.makeRequest(e,{...n,method:"PATCH",body:t?JSON.stringify(t):void 0})}async delete(e,t){return this.makeRequest(e,{...t,method:"DELETE"})}updateRateLimitInfo(e){let t=e.headers.get("X-RateLimit-Limit"),n=e.headers.get("X-RateLimit-Remaining"),o=e.headers.get("X-RateLimit-Reset");t&&(this.rateLimitInfo.limit=parseInt(t,10)),n&&(this.rateLimitInfo.remaining=parseInt(n,10)),o&&(this.rateLimitInfo.reset=parseInt(o,10)),this.rateLimitInfo.remaining&&this.rateLimitInfo.remaining<10&&r.warn(`Approaching rate limit for ${this.providerName}: ${this.rateLimitInfo.remaining} requests remaining`)}calculateRateLimitWait(e=0,t){let o=t?this.parseRetryAfter(t.headers.get("Retry-After")):null;if(o!==null)return Math.min(Math.max(o,1e3),6e4);if(this.rateLimitInfo.reset){let l=Math.floor(Date.now()/1e3),c=(this.rateLimitInfo.reset-l)*1e3;return Math.min(Math.max(c,1e3),6e4)}let a=5e3*Math.pow(2,e);return Math.min(a,6e4)}parseRetryAfter(e){if(!e)return null;let t=Number(e);if(Number.isFinite(t))return t*1e3;let n=Date.parse(e);return Number.isNaN(n)?null:n-Date.now()}async handleErrorResponse(e){let t=`${this.providerName} API error: ${e.status} ${e.statusText}`,n=t;try{let o=await e.json();if(typeof o=="object"&&o!==null){let s=o;if(typeof s.error=="string")n=`${t} - ${s.error}`;else if(typeof s.message=="string")n=`${t} - ${s.message}`;else if(Array.isArray(s.errors)){let a=s.errors.map(l=>typeof l=="object"&&l!==null&&"message"in l?String(l.message):JSON.stringify(l)).join(", ");n=`${t} - ${a}`}}}catch{}return r.error(n),new Error(n)}isNonRetryableError(e){if(typeof e=="object"&&e!==null&&e.name==="AbortError")return!0;let t=e.message;return typeof t=="string"&&t.includes("API error: 4")?!t.includes("429"):!1}delay(e){return new Promise(t=>setTimeout(t,e))}getRateLimitInfo(){return{...this.rateLimitInfo}}isLargeResponse(e){if(!e||typeof e!="object")return!1;let t=(n,o=0)=>{if(o>10)return!1;for(let[,s]of Object.entries(n))if(Array.isArray(s)&&s.length>1e3||typeof s=="object"&&s!==null&&t(s,o+1))return!0;return!1};return t(e)}validateLargeResponse(e){if(!e||typeof e!="object")return;let t=(o,s)=>{o.length>5e3&&r.warn(`Large array detected at ${s} with ${o.length} items. This may impact performance.`);let a=Math.min(10,o.length),l=o[0];if(l&&typeof l=="object"){let c=Object.keys(l);for(let d=1;d<a;d++){let u=o[d];if(!u||typeof u!="object"){r.warn(`Inconsistent array structure at ${s}[${d}]: expected object, got ${typeof u}`);continue}let p=Object.keys(u),f=c.filter(g=>!p.includes(g));f.length>0&&r.warn(`Inconsistent array structure at ${s}[${d}]: missing keys ${f.join(", ")}`)}}},n=(o,s="root",a=0)=>{if(!(a>10))for(let[l,c]of Object.entries(o)){let d=`${s}.${l}`;Array.isArray(c)?t(c,d):typeof c=="object"&&c!==null&&n(c,d,a+1)}};n(e)}};var Yi=process.env.DOORMAN_VERCEL_API_BASE_URL||"https://api.vercel.com/v1/security/firewall/config",We=class extends Ae{constructor(t,n,o){super("","vercel");this.projectId=t;this.teamId=n;this.token=o}projectId;teamId;token;async getAuthHeaders(){return{Authorization:`Bearer ${this.token}`}}getUrl(t){let n=t!==void 0?`${Yi}/${t}`:Yi;r.debug("API URL:",n);let o=this.teamId?`&teamId=${this.teamId}`:"";return`${n}?projectId=${this.projectId}${o}`}async fetchFirewallConfig(t,n){let{allowCreate:o=!0}=n??{},s=await this.get(this.getUrl(t));if(r.debug("Config Version:",t??"latest"),r.debug("Fetched Config:",t?s:s.active),t)return s;if(!s||"active"in s&&s.active===null){if(r.warn(Zi.bold("No firewall configuration found.")),!o)return r.debug("Skipping create-configuration prompt for read-only fetch."),this.emptyConfigPlaceholder();if(await C("Would you like to create one?",{type:"confirm"})){r.debug("Creating new empty firewall configuration...");let l=await this.putEmptyConfig();return r.debug("Empty firewall configuration created successfully"),r.debug(`New configuration version: ${Zi.yellow(l.version)}`),l}else return s.active}return s.active}emptyConfigPlaceholder(){return{version:0,id:"",firewallEnabled:!0,crs:null,rules:[],ips:[],projectKey:"",ownerId:"",updatedAt:new Date(0).toISOString()}}async putEmptyConfig(){let{$schema:t,...n}=Et();return r.debug("Empty Config:",n),this.putConfig(n)}async putConfig(t){return(await this.put(this.getUrl(),t)).active}async fetchActiveFirewallRules(){return(await this.fetchFirewallConfig())?.rules}async updateFirewallRule(t){let n=!t.id||t.id==="-",o={action:n?"rules.insert":"rules.update",id:n?null:t.id,value:{name:t.name,description:t.description,action:t.action,conditionGroup:t.conditionGroup,active:t.active}};return this.patch(this.getUrl(),o,n?{retries:0}:void 0)}async createFirewallRule(t){return this.updateFirewallRule({...t,id:"-"})}async deleteFirewallRule(t){let n={action:"rules.remove",id:t.id,value:null};await this.patch(this.getUrl(),n)}async updateIPBlockingRule(t){let n=!t.id||t.id==="-",o={action:n?"ip.insert":"ip.update",id:n?null:t.id,value:{action:t.action,hostname:t.hostname,ip:t.ip,notes:t.notes??null}};return this.patch(this.getUrl(),o,n?{retries:0}:void 0)}async createIPBlockingRule(t){return this.updateIPBlockingRule({...t,id:"-"})}async deleteIPBlockingRule(t){let n={action:"ip.remove",id:t.id,value:null};await this.patch(this.getUrl(),n)}async verifyCredentials(){try{return await this.fetchFirewallConfig(),!0}catch(t){return r.debug("Credential verification failed:",t),!1}}};_();import ge from"chalk";_();var $e=class{validateConfig(e){let t=[],n=[];if(e||t.push({path:"root",message:"Configuration is required",code:"CONFIG_REQUIRED"}),e&&!e.rules&&t.push({path:"rules",message:"Rules array is required",code:"RULES_REQUIRED"}),e&&e.rules&&!Array.isArray(e.rules)&&t.push({path:"rules",message:"Rules must be an array",code:"RULES_INVALID_TYPE"}),e&&Array.isArray(e.rules)){let o=new Map;e.rules.forEach((s,a)=>{let l=s.id||s.name;if(!l)return;let c=o.get(l);if(c===void 0){o.set(l,a);return}let d=e.rules[c];n.push({path:`rules[${a}]`,message:s.id?`Rule "${s.name}" has the same id ("${s.id}") as rules[${c}] ("${d?.name}") \u2014 they will collide during sync diffing and one may be silently skipped.`:`Rule "${s.name}" has the same name as rules[${c}] and neither has an explicit id \u2014 they will collide during sync diffing and one may be silently skipped. Add unique ids to disambiguate.`,code:"DUPLICATE_RULE_IDENTIFIER"})})}return{valid:t.length===0,errors:t,warnings:n}}getHealthScore(e){let t=[],n=100;if(!e)return{score:0,grade:"poor",issues:[{severity:"error",category:"configuration",message:"No configuration found"}],recommendations:["Create a configuration file"]};if((!e.rules||e.rules.length===0)&&(n-=20,t.push({severity:"warning",category:"rules",message:"No rules defined",suggestion:"Add security rules to protect your application"})),e.rules&&e.rules.length>0){let a=e.rules.filter(l=>!l.description);a.length>0&&(n-=5,t.push({severity:"info",category:"maintainability",message:`${a.length} rule(s) missing descriptions`,suggestion:"Add descriptions to improve maintainability"}))}if(e.rules&&e.rules.length>0){let a=e.rules.filter(l=>l.active===!1||l.enabled===!1);a.length>0&&(n-=5,t.push({severity:"info",category:"maintenance",message:`${a.length} disabled rule(s) found`,suggestion:"Review and remove unused rules"}))}let o=this.calculateGrade(n),s=this.generateRecommendations(e,t);return{score:Math.max(0,n),grade:o,issues:t,recommendations:s}}diffItems(e,t,n,o="id"){let s=[],a=[],l=[],c=d=>d[o];for(let d of e){let u=t.find(p=>c(p)===c(d));u?n(d,u)||a.push(d):s.push(d)}for(let d of t)e.find(p=>c(p)===c(d))||l.push(d);return{toAdd:s,toUpdate:a,toDelete:l}}assertManagedRulesSupported(e){if(!(!e.managedRules||e.managedRules.length===0)&&!this.getSupportedFeatures().supportsManagedRules)throw new Error(`${this.name} does not support managed rule groups, but this config declares ${e.managedRules.length}. Remove \`managedRules\` from the config, or switch to a provider that supports them (currently Cloudflare only).`)}validateRuleCount(e,t){if(t&&e>t)throw new Error(`Rule count (${e}) exceeds provider limit (${t})`);t&&e>t*.9&&r.warn(`Approaching rule limit: ${e}/${t} rules`)}updateMetadata(e,t){return{...e,metadata:{...e.metadata,version:t,updatedAt:new Date().toISOString()}}}calculateGrade(e){return e>=80?"excellent":e>=60?"good":e>=40?"fair":"poor"}generateRecommendations(e,t){let n=[];return t.some(o=>o.category==="rules")&&n.push("Consider using predefined templates to get started"),t.some(o=>o.category==="maintainability")&&n.push("Add descriptions to all rules for better documentation"),t.some(o=>o.category==="maintenance")&&n.push("Remove or enable disabled rules to keep configuration clean"),n}logSyncStats(e){r.info(`Sync complete for ${this.name}:`),r.info(` Rules added: ${e.rulesAdded}`),r.info(` Rules updated: ${e.rulesUpdated}`),r.info(` Rules deleted: ${e.rulesDeleted}`),e.ipsAdded!==void 0&&(r.info(` IPs added: ${e.ipsAdded}`),r.info(` IPs updated: ${e.ipsUpdated}`),r.info(` IPs deleted: ${e.ipsDeleted}`)),e.managedRulesAdded!==void 0&&(r.info(` Managed rule groups added: ${e.managedRulesAdded}`),r.info(` Managed rule groups updated: ${e.managedRulesUpdated}`),r.info(` Managed rule groups deleted: ${e.managedRulesDeleted}`)),e.version&&r.info(` New version: ${e.version}`)}};ce();function Ji(i){let e=[],t=[];i.conditionGroup.forEach((s,a)=>{for(let l of s.conditions){let c=tr(l.op);t.push({field:nr(l.type),operator:c,...l.value!==void 0?{value:l.value}:{},...l.neg?{negated:!0}:{},...l.key?{key:l.key}:{},group:a})}}),t.length>10&&e.push(U.createWarning("many_conditions",i.id,void 0,`Rule has ${t.length} conditions which may impact performance`,"Consider splitting complex rules into multiple simpler rules for better performance"));let n={type:i.action.mitigate.action,...i.action.mitigate.rateLimit?{rateLimit:{requests:i.action.mitigate.rateLimit.requests,window:i.action.mitigate.rateLimit.window,...i.action.mitigate.rateLimit.characteristics?{characteristics:i.action.mitigate.rateLimit.characteristics}:{},...i.action.mitigate.rateLimit.mitigationTimeout?{mitigationTimeout:i.action.mitigate.rateLimit.mitigationTimeout}:{},...i.action.mitigate.rateLimit.countingExpression?{countingExpression:i.action.mitigate.rateLimit.countingExpression}:{}}}:{},...i.action.mitigate.redirect?{redirect:{location:i.action.mitigate.redirect.location,...i.action.mitigate.redirect.permanent?{permanent:i.action.mitigate.redirect.permanent}:{}}}:{},...i.action.mitigate.actionDuration?{duration:i.action.mitigate.actionDuration}:{}};return{result:{id:i.id,name:i.name,...i.description?{description:i.description}:{},enabled:i.active,conditions:t,conditionLogic:i.conditionGroup.length>1?"OR":"AND",action:n},warnings:e}}function Qi(i){let e=[],t=er(i.conditions,i.id,e);if(i.action.response&&e.push(U.createUnsupportedFeatureWarning("custom response body","unified config","Vercel",i.id,"action.response")),t.length===0)throw new Error(`Rule "${i.name}" has no conditions Vercel can represent \u2014 every condition field is unsupported by Vercel (e.g. Cloudflare-only fields like referer/port). Cannot sync this rule to Vercel.`);return{result:{id:i.id,name:i.name,description:i.description,conditionGroup:t,action:{mitigate:{action:i.action.type,rateLimit:i.action.rateLimit?{requests:i.action.rateLimit.requests,window:i.action.rateLimit.window,characteristics:i.action.rateLimit.characteristics,mitigationTimeout:i.action.rateLimit.mitigationTimeout,countingExpression:i.action.rateLimit.countingExpression}:null,redirect:i.action.redirect?{location:i.action.redirect.location,permanent:i.action.redirect.permanent}:null,actionDuration:i.action.duration||null}},active:i.enabled},warnings:e}}function Xi(i){return{id:i.id,ip:i.ip,...i.hostname?{hostname:i.hostname}:{},...i.notes?{notes:i.notes}:{},action:i.action}}function er(i,e,t){let n=new Map;for(let s of i){let a=s.group??0,l=n.get(a);l?l.push(s):n.set(a,[s])}let o=[];for(let s of n.values()){let a=[];for(let l of s){let c=rr(l.field);if(!c){t.push(U.createUnsupportedFeatureWarning(`condition field '${l.field}'`,"unified config","Vercel",e,l.field));continue}let d=ir(l.operator);if(!d){t.push(U.createUnsupportedFeatureWarning(`operator '${l.operator}'`,"unified config","Vercel",e,l.field));continue}a.push({op:d.op,neg:d.forceNeg!==!!l.negated,type:c,key:l.key,value:l.value})}a.length>0&&o.push({conditions:a})}return o}function tr(i){return{eq:"eq",pre:"starts_with",suf:"ends_with",inc:"in",sub:"contains",re:"matches",ex:"exists",nex:"not_exists"}[i]||"eq"}function ir(i){return{eq:{op:"eq",forceNeg:!1},ne:{op:"eq",forceNeg:!0},starts_with:{op:"pre",forceNeg:!1},ends_with:{op:"suf",forceNeg:!1},in:{op:"inc",forceNeg:!1},not_in:{op:"inc",forceNeg:!0},contains:{op:"sub",forceNeg:!1},not_contains:{op:"sub",forceNeg:!0},matches:{op:"re",forceNeg:!1},exists:{op:"ex",forceNeg:!1},not_exists:{op:"nex",forceNeg:!1}}[i]??null}function nr(i){return{host:"host",path:"path",method:"method",header:"header",query:"query",cookie:"cookie",ip_address:"ip",user_agent:"user_agent",geo_country:"country",geo_city:"city",geo_as_number:"asn",scheme:"scheme",region:"vercel_region",geo_country_region:"region"}[i]||i}var or={host:"host",path:"path",method:"method",header:"header",query:"query",cookie:"cookie",ip:"ip_address",user_agent:"user_agent",country:"geo_country",city:"geo_city",asn:"geo_as_number",scheme:"scheme",target_path:"target_path",vercel_region:"region",region:"geo_country_region",protocol:"protocol",environment:"environment",geo_continent:"geo_continent",ja4_digest:"ja4_digest",ja3_digest:"ja3_digest",rate_limit_api_id:"rate_limit_api_id"};function rr(i){return or[i]??null}ce();_();function je(i){return i.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}function en(i){let e="";for(let t=0;t<i.length;t++)i[t]==="\\"&&(i[t+1]==="\\"||i[t+1]==='"')?(e+=i[t+1],t++):e+=i[t];return e}var rt=class{static vercelToCloudflare={host:"http.host",path:"http.request.uri.path",method:"http.request.method",header:"http.request.headers",query:"http.request.uri.query",cookie:"http.cookie",target_path:"http.request.uri.path",ip_address:"ip.src",region:"ip.geoip.subdivision_1",protocol:"ssl",scheme:"ssl",environment:"",user_agent:"http.user_agent",geo_continent:"ip.geoip.continent",geo_country:"ip.geoip.country",geo_country_region:"ip.geoip.subdivision_1",geo_city:"ip.geoip.city",geo_as_number:"ip.geoip.asnum",ja4_digest:"",ja3_digest:"",rate_limit_api_id:""};static cloudflareToVercel={"http.host":"host","http.request.uri.path":"path","http.request.method":"method","http.request.headers":"header","http.request.uri.query":"query","http.cookie":"cookie","ip.src":"ip_address","ip.geoip.subdivision_1":"region",ssl:"protocol","http.user_agent":"user_agent","ip.geoip.continent":"geo_continent","ip.geoip.country":"geo_country","ip.geoip.city":"geo_city","ip.geoip.asnum":"geo_as_number"};static toCloudflare(e,t){let n=this.vercelToCloudflare[e];if(!n)throw r.warn(`No Cloudflare mapping for Vercel type: ${e}`),new Error(`Unsupported Vercel rule type for Cloudflare: ${e}`);return e==="header"&&t?`${n}["${je(t.toLowerCase())}"]`:e==="cookie"&&t?`${n}["${je(t)}"]`:n}static toVercel(e){let t=e.match(/http\.request\.headers\["([^"]+)"\]/);if(t)return{type:"header",key:t[1]};let n=e.match(/http\.cookie\["([^"]+)"\]/);if(n)return{type:"cookie",key:n[1]};if(e==="http.referer")return{type:"header",key:"referer"};let o=this.cloudflareToVercel[e];if(o)return{type:o};throw r.warn(`No Vercel mapping for Cloudflare field: ${e}`),new Error(`Unsupported Cloudflare field for Vercel: ${e}`)}static isCloudflareSupported(e){let t=this.vercelToCloudflare[e];return!!t&&t!==""}static isVercelSupported(e){return!!(this.cloudflareToVercel[e]||e==="http.referer"||e.startsWith("http.request.headers[")||e.startsWith("http.cookie["))}static getSupportedCloudflareFields(){return Object.values(this.vercelToCloudflare).filter(e=>e!=="")}static getSupportedVercelTypes(){return Object.keys(this.vercelToCloudflare).filter(e=>this.isCloudflareSupported(e))}};var sr=new Set(["ip.src"]),ar="http.request.uri.args",lr="http.request.headers",cr="http.request.cookies",ze=class{static fromVercelConditionGroups(e){if(!e||e.length===0)throw new Error("At least one condition group is required");let t=e.map(n=>{let o=n.conditions.map(s=>this.fromVercelCondition(s));if(o.length===0)throw new Error("Condition group must have at least one condition");return o.length>1?`(${o.join(" and ")})`:o[0]});return t.length>1?t.join(" or "):t[0]}static fromVercelCondition(e){let t=rt.toCloudflare(e.type,e.key),n=this.buildExistsOrComparisonExpression(t,e.op,()=>`${this.mapVercelOperator(e.op)} ${this.formatValue(t,e.value)}`);return e.neg&&(n=`not (${n})`),n}static fromUnifiedConditions(e,t="AND"){if(!e||e.length===0)throw new Error("At least one condition is required");if(!e.some(a=>a.group!==void 0)){let a=e.map(c=>this.fromUnifiedCondition(c)),l=t==="AND"?" and ":" or ";return a.length>1?`(${a.join(l)})`:a[0]}let o=new Map;for(let a of e){let l=a.group??0,c=o.get(l);c?c.push(a):o.set(l,[a])}let s=Array.from(o.values()).map(a=>this.combineWithAnd(a.map(l=>this.fromUnifiedCondition(l))));return s.length>1?this.combineWithOr(s):s[0]}static fromUnifiedCondition(e){if(e.key&&e.field==="query")return this.buildKeyedMapExpression(ar,e.key,e);if(e.field==="header"){if(!e.key)throw new Error('A "header" condition requires a key naming the header');return this.buildKeyedMapExpression(lr,e.key.toLowerCase(),e)}if(e.key&&e.field==="cookie")return this.buildKeyedMapExpression(cr,e.key,e);let t=this.mapUnifiedFieldToCloudflare(e.field);if(t===null)throw new Error(`Unsupported condition field '${e.field}' for Cloudflare \u2014 filter it out with a warning before calling fromUnifiedCondition (see unifiedToCloudflare).`);let n=this.buildUnifiedExpression(t,e.operator,e.value);return e.negated&&(n=`not (${n})`),n}static buildKeyedMapExpression(e,t,n){let o=je(t),s=`${e}["${o}"]`,a;if(n.operator==="exists")a=`has_key(${e}, "${o}")`;else if(n.operator==="not_exists")a=`not (has_key(${e}, "${o}"))`;else if(n.operator==="not_contains")a=`not (any(${s}[*] contains ${this.formatValue(e,n.value)}))`;else if(n.operator==="not_in")a=`not (any(${s}[*] in ${this.formatValue(e,n.value)}))`;else{let l=this.mapUnifiedOperator(n.operator);a=`any(${s}[*] ${l} ${this.formatValue(e,n.value)})`}return n.negated?`not (${a})`:a}static buildExistsOrComparisonExpression(e,t,n){return t==="ex"||t==="exists"?`${e} exists`:t==="nex"||t==="not_exists"?`not (${e} exists)`:`${e} ${n()}`}static buildUnifiedExpression(e,t,n){return t==="not_contains"?`not (${e} contains ${this.formatValue(e,n)})`:t==="not_in"?`not (${e} in ${this.formatValue(e,n)})`:this.buildExistsOrComparisonExpression(e,t,()=>`${this.mapUnifiedOperator(t)} ${this.formatValue(e,n)}`)}static mapVercelOperator(e){return{eq:"eq",pre:"starts_with",suf:"ends_with",inc:"in",sub:"contains",re:"matches",ex:"exists"}[e]||e}static mapUnifiedOperator(e){return{eq:"eq",ne:"ne",contains:"contains",starts_with:"starts_with",ends_with:"ends_with",matches:"matches",in:"in",gt:"gt",ge:"ge",lt:"lt",le:"le",exists:"exists"}[e]||e}static mapUnifiedFieldToCloudflare(e){return{ip:"ip.src",country:"ip.geoip.country",region:"ip.geoip.subdivision_1",city:"ip.geoip.city",asn:"ip.geoip.asnum",path:"http.request.uri.path",host:"http.host",method:"http.request.method",header:"http.request.headers",query:"http.request.uri.query",cookie:"http.cookie",user_agent:"http.user_agent",referer:"http.referer",scheme:"ssl",port:"cf.edge.server_port",threat_score:"cf.threat_score"}[e]??null}static isFieldSupported(e){return this.mapUnifiedFieldToCloudflare(e)!==null}static formatValue(e,t){return Array.isArray(t)?`{${t.map(o=>this.formatSingleValue(e,o)).join(" ")}}`:this.formatSingleValue(e,t)}static formatSingleValue(e,t){if(typeof t=="string"){if(sr.has(e)){if(!Te.safeParse(t).success)throw new Error(`Invalid IP address or CIDR range: ${t}`);return t}return`"${je(t)}"`}return typeof t=="number"?String(t):typeof t=="boolean"?t?"true":"false":String(t)}static validate(e){if(!e||e.trim().length===0)return!1;let t=0;for(let n of e)if(n==="("&&t++,n===")"&&t--,t<0)return!1;return t===0}static combineWithAnd(e){if(e.length===0)throw new Error("At least one expression is required");return e.length===1?e[0]:`(${e.join(" and ")})`}static combineWithOr(e){if(e.length===0)throw new Error("At least one expression is required");return e.length===1?e[0]:`(${e.join(" or ")})`}};var W=class extends Error{},Ge=class{constructor(e){this.tokens=e}tokens;pos=0;peek(){return this.tokens[this.pos]}next(){let e=this.tokens[this.pos];if(!e)throw new W("Unexpected end of expression");return this.pos++,e}expect(e){let t=this.next();if(t.type!==e)throw new W(`Expected ${e} but got '${t.value}'`);return t}is(e,t){let n=this.peek();return!!n&&n.type===e&&n.value===t}atEnd(){return this.pos>=this.tokens.length}};function Tt(i,e,t,n){let o=[];i.forEach((l,c)=>{if(e(l)){let d=n(l,c);d&&o.push(d);return}if(t(l))for(let d of l.children){if(!e(d))return;let u=n(d,c);u&&o.push(u)}});let s=i.length;return new Set(o.map(l=>l.group)).size!==s?null:{conditions:o,conditionLogic:"OR"}}var dr={"ip.src":"ip","ip.geoip.country":"country","ip.geoip.subdivision_1":"region","ip.geoip.city":"city","ip.geoip.asnum":"asn","http.request.uri.path":"path","http.host":"host","http.request.method":"method","http.request.headers":"header","http.request.uri.query":"query","http.request.uri.args":"query","http.cookie":"cookie","http.request.cookies":"cookie","http.user_agent":"user_agent","http.referer":"referer",ssl:"scheme","cf.edge.server_port":"port","cf.threat_score":"threat_score"},rn=new Set(["eq","ne","contains","starts_with","ends_with","matches","in","gt","ge","lt","le"]),ur=new Set(["and","or","not"]);function pr(i){let e=[],t=0,n=i.length;for(;t<n;){let o=i[t];if(/\s/.test(o)){t++;continue}if(o===","){t++;continue}if(o==="("){e.push({type:"LPAREN",value:o}),t++;continue}if(o===")"){e.push({type:"RPAREN",value:o}),t++;continue}if(o==="{"){e.push({type:"LBRACE",value:o}),t++;continue}if(o==="}"){e.push({type:"RBRACE",value:o}),t++;continue}if(o==="["){e.push({type:"LBRACKET",value:o}),t++;continue}if(o==="]"){e.push({type:"RBRACKET",value:o}),t++;continue}if(o==='"'){let a=t+1,l="",c=!1;for(;a<n;){let d=i[a];if(d==="\\"&&a+1<n){l+=d+i[a+1],a+=2;continue}if(i[a]==='"'){c=!0,a++;break}l+=i[a],a++}if(!c)throw new W("Unterminated string literal");e.push({type:"STRING",value:en(l)}),t=a;continue}let s=t;for(;s<n&&!/[\s(){}[\]",]/.test(i[s]);)s++;if(s===t)throw new W(`Unexpected character '${o}' at position ${t}`);e.push({type:"WORD",value:i.slice(t,s)}),t=s}return e}function Kt(i){let e=[tn(i)];for(;i.is("WORD","or");)i.next(),e.push(tn(i));return e.length===1?e[0]:{type:"or",children:e}}function tn(i){let e=[nn(i)];for(;i.is("WORD","and");)i.next(),e.push(nn(i));return e.length===1?e[0]:{type:"and",children:e}}function nn(i){if(i.is("WORD","not")){i.next(),i.expect("LPAREN");let e=Kt(i);return i.expect("RPAREN"),{type:"not",child:e}}return fr(i)}function fr(i){let e=i.peek();if(e&&e.type==="LPAREN"){i.next();let t=Kt(i);return i.expect("RPAREN"),t}return yr(i)}function gr(i){let e=i.expect("WORD");if(ur.has(e.value))throw new W(`Expected a field reference but got keyword '${e.value}'`);if(i.peek()?.type==="LBRACKET"){i.next();let t=i.expect("STRING");return i.expect("RBRACKET"),{field:e.value,key:t.value}}return{field:e.value}}function on(i){let e=i.next();if(e.type==="STRING")return e.value;if(e.type==="WORD")return/^-?\d+(\.\d+)?$/.test(e.value)?Number(e.value):e.value;throw new W(`Unexpected token '${e.value}' where a value was expected`)}function sn(i){if(i.peek()?.type==="LBRACE"){i.next();let e=[];for(;i.peek()?.type!=="RBRACE";)e.push(on(i));return i.expect("RBRACE"),e}return on(i)}function mr(i){i.next(),i.expect("LPAREN");let e=i.expect("WORD");i.expect("LBRACKET");let t=i.expect("STRING");i.expect("RBRACKET"),i.expect("LBRACKET");let n=i.expect("WORD");if(n.value!=="*")throw new W(`Expected '*' in any(...) array index but got '${n.value}'`);i.expect("RBRACKET");let o=i.next();if(o.type!=="WORD"||!rn.has(o.value))throw new W(`Unsupported or unrecognized operator '${o.value}' inside any(...)`);let s=sn(i);return i.expect("RPAREN"),{type:"comparison",field:e.value,key:t.value,operator:o.value,value:s}}function hr(i){i.next(),i.expect("LPAREN");let e=i.expect("WORD"),t=i.expect("STRING");return i.expect("RPAREN"),{type:"exists",field:e.value,key:t.value}}function yr(i){if(i.is("WORD","any"))return mr(i);if(i.is("WORD","has_key"))return hr(i);let{field:e,key:t}=gr(i);if(i.is("WORD","exists"))return i.next(),{type:"exists",field:e,key:t};let n=i.next();if(n.type!=="WORD"||!rn.has(n.value))throw new W(`Unsupported or unrecognized operator '${n.value}'`);let o=sn(i);return{type:"comparison",field:e,key:t,operator:n.value,value:o}}function Rr(i){let e=new Ge(pr(i)),t=Kt(e);if(!e.atEnd())throw new W("Unexpected trailing tokens");return t}function zt(i){return i.type==="comparison"||i.type==="exists"?!0:i.type==="not"?i.child.type==="comparison"||i.child.type==="exists":!1}function Gt(i,e){let t=i.type==="not",n=i.type==="not"?i.child:i;if(n.type!=="comparison"&&n.type!=="exists")return null;let o=dr[n.field];if(!o)return null;if(n.type==="exists")return{field:o,operator:t?"not_exists":"exists",value:"",key:n.key,group:e};let s=n.operator,a;return t&&(n.operator==="contains"?s="not_contains":n.operator==="in"?s="not_in":a=!0),{field:o,operator:s,value:n.value,key:n.key,negated:a,group:e}}function wr(i){if(zt(i)){let e=Gt(i,0);return e?{conditions:[e],conditionLogic:"AND"}:null}if(i.type==="and"){let e=[];for(let t of i.children){if(!zt(t))return null;let n=Gt(t,0);if(!n)return null;e.push(n)}return{conditions:e,conditionLogic:"AND"}}return i.type==="or"?Tt(i.children,zt,e=>e.type==="and",Gt):null}function Ht(i){if(!i||!i.trim())return null;try{let e=Rr(i);return wr(e)}catch{return null}}function cn(i){let e=[],t=Ht(i.expression),n=[],o;t?(n=t.conditions,o=t.conditionLogic):e.push(U.createWarning("complex_expressions",i.id,"expression","Expression could not be parsed back into structured conditions \u2014 it may be hand-authored or use syntax outside what doorman itself generates.","Review the translated rule and add missing conditions manually if needed."));let s=i.action_parameters&&"response"in i.action_parameters?i.action_parameters.response:void 0,a={type:Cr(i.action),rateLimit:i.ratelimit?{requests:i.ratelimit.requests_per_period,window:`${i.ratelimit.period}s`,characteristics:i.ratelimit.characteristics,mitigationTimeout:i.ratelimit.mitigation_timeout,countingExpression:i.ratelimit.counting_expression}:void 0,response:s?{statusCode:s.status_code,content:s.content,contentType:s.content_type}:void 0};return{result:{id:i.id,name:i.description||`Rule ${i.id}`,description:i.description,enabled:i.enabled??!0,conditions:n,conditionLogic:o,action:a},warnings:e}}function dn(i){let e=[],t=vr(i.conditions,i.id,e);if(t.length===0)throw new Error(`Rule "${i.name}" has no conditions Cloudflare can represent \u2014 every condition field is unsupported by Cloudflare (e.g. Vercel-only fields like environment/ja3_digest, or vercel_region). Cannot sync this rule to Cloudflare.`);let n=ze.fromUnifiedConditions(t,i.conditionLogic),o={id:i.id||crypto.randomUUID(),action:br(i.action.type),expression:n,description:i.description||i.name,enabled:i.enabled};return i.action.rateLimit&&(o.ratelimit={characteristics:i.action.rateLimit.characteristics||["ip.src"],period:Ir(i.action.rateLimit.window),requests_per_period:i.action.rateLimit.requests},i.action.rateLimit.mitigationTimeout?o.ratelimit.mitigation_timeout=i.action.rateLimit.mitigationTimeout:(o.ratelimit.mitigation_timeout=3600,e.push(U.createWarning("rate_limiting_precision",i.id,"action.rateLimit.mitigationTimeout",`No mitigationTimeout set for rate limit rule "${i.name}" \u2014 defaulted to Cloudflare's 3600s (1 hour) block duration.`))),i.action.rateLimit.countingExpression&&(o.ratelimit.counting_expression=i.action.rateLimit.countingExpression)),i.action.type==="redirect"&&i.action.redirect&&(o.action_parameters={from_value:{status_code:i.action.redirect.statusCode??(i.action.redirect.permanent?301:302),target_url:{value:i.action.redirect.location},...i.action.redirect.preserveQueryString!==void 0?{preserve_query_string:i.action.redirect.preserveQueryString}:{}}}),i.action.response&&(o.action!=="block"?e.push(U.createUnsupportedFeatureWarning(`custom response on a '${o.action}' action`,"unified config","Cloudflare",i.id,"action.response")):i.action.response.content?o.action_parameters={response:{status_code:i.action.response.statusCode??403,content:i.action.response.content,content_type:i.action.response.contentType??"text/plain"}}:e.push(U.createWarning("lossy_conversion",i.id,"action.response","Custom response declared without `content`; Cloudflare requires a response body, so it was dropped.","Set action.response.content to the body you want returned."))),{result:o,warnings:e}}function un(i){if(!Te.safeParse(i.ip).success)throw new Error(`Invalid IP address or CIDR range: ${i.ip}`);let t=i.ip.includes("/")?`ip.src in {${i.ip}}`:`ip.src eq ${i.ip}`;return{id:i.id||crypto.randomUUID(),action:i.action==="allow"?"allow":"block",expression:t,description:i.notes||`IP ${i.action}: ${i.ip}${i.hostname?` (${i.hostname})`:""}`,enabled:!0}}function pn(i){let e=[],t={};if(i.action){let{action:s,warning:a}=an(i.action,i.id,"action");a&&e.push(a),s&&(t.action=s)}i.overrides&&i.overrides.length>0&&(t.rules=i.overrides.map(s=>{let a={id:s.ruleId};if(s.enabled!==void 0&&(a.enabled=s.enabled),s.action){let{action:l,warning:c}=an(s.action,i.id,`overrides[${s.ruleId}].action`);c&&e.push(c),l&&(a.action=l)}return a}));let n={id:i.ruleset,...Object.keys(t).length>0?{overrides:t}:{}};return{result:{id:i.id||crypto.randomUUID(),action:"execute",expression:"true",description:i.name||`Managed ruleset ${i.ruleset}`,enabled:i.enabled,action_parameters:n},warnings:e}}function fn(i){let e=[],t=i.action_parameters,n={id:i.id,ruleset:t?.id??"",enabled:i.enabled??!0,...i.description?{name:i.description}:{}};return t?.overrides?.action&&(n.action=ln(t.overrides.action)),t?.overrides?.rules&&t.overrides.rules.length>0&&(n.overrides=t.overrides.rules.map(o=>({ruleId:o.id,...o.action!==void 0?{action:ln(o.action)}:{},...o.enabled!==void 0?{enabled:o.enabled}:{}}))),{result:n,warnings:e}}function an(i,e,t){let o={log:"log",deny:"block",block:"block",challenge:"managed_challenge",allow:"allow"}[i];return o?{action:o}:{warning:U.createUnsupportedFeatureWarning(`'${i}' as a managed-ruleset override action`,"unified config","Cloudflare",e,t)}}function ln(i){return{log:"log",block:"deny",challenge:"challenge",managed_challenge:"challenge",js_challenge:"challenge",allow:"allow"}[i]||"deny"}function Cr(i){return{block:"deny",challenge:"challenge",managed_challenge:"challenge",js_challenge:"challenge",log:"log",skip:"bypass",allow:"allow",rewrite:"bypass",redirect:"redirect",execute:"log"}[i]||"deny"}function br(i){return{log:"log",deny:"block",block:"block",challenge:"managed_challenge",bypass:"skip",rate_limit:"block",redirect:"redirect",allow:"allow"}[i]||"block"}function vr(i,e,t){return i.filter(n=>ze.isFieldSupported(n.field)?!0:(t.push(U.createUnsupportedFeatureWarning(`condition field '${n.field}'`,"unified config","Cloudflare",e,n.field)),!1))}function Ir(i){let e=i.match(/^(\d+)([smhd])$/);if(!e||!e[1]||!e[2])throw new Error(`Invalid window format: ${i}`);let t=parseInt(e[1],10),n=e[2],s={s:1,m:60,h:3600,d:86400}[n];if(s===void 0)throw new Error(`Invalid time unit: ${n}`);return t*s}ce();function ye(i,e,t){return U.createWarning("complex_expressions",i,void 0,e,t)}var Er={ip:"ip",path:"path",domain:"host",method:"method",country:"country",scheme:"scheme",user_agent:"user_agent"},Tr={ip:"ip",path:"path",host:"domain",method:"method",country:"country",scheme:"scheme",user_agent:"user_agent"},Ar={header:"request_header",query:"query_parameter",cookie:"request_cookie"},$r={request_header:"header",query_parameter:"query",request_cookie:"cookie",response_header:"response_header",post_parameter:"post_parameter",signal:"signal"};function Pr(i){return Er[i]??i}function _r(i){return Tr[i]??null}var kr={equals:{operator:"eq",negated:!1},does_not_equal:{operator:"eq",negated:!0},contains:{operator:"contains",negated:!1},does_not_contain:{operator:"contains",negated:!0},like:{operator:"matches",negated:!1},not_like:{operator:"matches",negated:!0},in_list:{operator:"in",negated:!1},not_in_list:{operator:"in",negated:!0},matches:{operator:"matches",negated:!1},does_not_match:{operator:"matches",negated:!0},greater_equal:{operator:"ge",negated:!1},lesser_equal:{operator:"le",negated:!1}};function gn(i){return kr[i]??{operator:"eq",negated:!1}}function mn(i,e,t,n){switch(i){case"eq":return e?"does_not_equal":"equals";case"ne":return"does_not_equal";case"contains":return e?"does_not_contain":"contains";case"not_contains":return"does_not_contain";case"starts_with":case"ends_with":return t.push(U.createWarning("regex_patterns",n,void 0,`Unified operator "${i}" has no direct Fastly equivalent \u2014 mapped to Fastly's wildcard "like" operator. Verify the translated value still matches as a wildcard pattern.`)),e?"not_like":"like";case"matches":return t.push(U.createWarning("regex_patterns",n,void 0,"Regular expression pattern may need adjustment for Fastly Next-Gen WAF regex syntax.")),e?"does_not_match":"matches";case"in":return e?"not_in_list":"in_list";case"not_in":return"not_in_list";case"gt":return t.push(ye(n,'Unified operator "gt" (strictly greater than) has no Fastly equivalent \u2014 mapped to "greater_equal", which also matches the boundary value itself.')),"greater_equal";case"ge":return"greater_equal";case"lt":return t.push(ye(n,'Unified operator "lt" (strictly less than) has no Fastly equivalent \u2014 mapped to "lesser_equal", which also matches the boundary value itself.')),"lesser_equal";case"le":return"lesser_equal";default:return"equals"}}function Sr(i,e){let t=[],n=i.conditions.filter(s=>s.type==="group"),o=i.conditions.filter(s=>s.type!=="group");if(n.length>0&&o.length===0&&i.group_operator==="any")return n.forEach((s,a)=>{s.group_operator!=="all"&&e.push(ye(i.id,`A condition group using "any" (OR) inside an outer "any" (OR) rule cannot be represented in doorman's flat AND-within/OR-across condition model \u2014 its members were kept in the same group (effectively AND'd) instead of OR'd. Review the translated rule against the original Fastly logic.`));for(let l of s.conditions)At(t,l,a,e,i.id)}),{conditions:t,conditionLogic:n.length>1?"OR":"AND"};if(n.length===0)return o.forEach((s,a)=>{let l=i.group_operator==="any"?a:0;At(t,s,l,e,i.id)}),{conditions:t,conditionLogic:i.group_operator==="any"&&o.length>1?"OR":"AND"};e.push(ye(i.id,`Rule mixes top-level conditions with condition groups under group_operator "${i.group_operator}" \u2014 this doesn't fit doorman's flat AND-within/OR-across condition model. All conditions were combined with AND; review the translated rule against the original Fastly rule logic.`));for(let s of o)At(t,s,0,e,i.id);for(let s of n)for(let a of s.conditions)At(t,a,0,e,i.id);return{conditions:t,conditionLogic:"AND"}}function At(i,e,t,n,o){if(e.type==="single"){let{operator:u,negated:p}=gn(e.operator);i.push({field:Pr(e.field),operator:u,value:e.value,...p?{negated:!0}:{},group:t});return}let s=$r[e.field],a=e.conditions.find(u=>u.field==="name"),l=e.conditions.find(u=>u.field==="value"||u.field==="value_string");if(!l){if(a){i.push({field:s,operator:e.operator==="does_not_exist"?"not_exists":"exists",key:a.value,group:t});return}n.push(ye(o,`Multival condition on '${e.field}' has no value or name sub-condition doorman could extract \u2014 skipped.`));return}let{operator:c,negated:d}=gn(l.operator);i.push({field:s,operator:c,value:l.value,...d?{negated:!0}:{},...a?{key:a.value}:{},group:t})}function Ur(i,e){i.actions.length>1&&e.push(ye(i.id,`Rule has ${i.actions.length} actions \u2014 doorman only models one action per rule; only the first ("${i.actions[0]?.type}") was translated.`));let t=i.actions[0];if(i.type==="rate_limit")return{type:"rate_limit",rateLimit:i.rate_limit?{requests:i.rate_limit.threshold,window:`${i.rate_limit.interval}s`,mitigationTimeout:i.rate_limit.duration}:void 0};switch(t?.type){case"block":return{type:"block"};case"allow":return{type:"allow"};case"browser_challenge":case"dynamic_challenge":case"verify_token":return{type:"challenge"};case"redirect":return{type:"redirect",redirect:{location:t.redirect_url??"",...t.response_code!==void 0?{statusCode:t.response_code}:{}}};case"add_signal":case"exclude_signal":case"deception":return e.push(U.createWarning("action_differences",i.id,"action.type",`Fastly action "${t.type}" tags or excludes a WAF signal rather than allowing/blocking the request \u2014 doorman has no matching action type. Mapped to "log" as the closest non-destructive fallback; review this rule.`)),{type:"log"};default:return e.push(ye(i.id,`Unrecognised Fastly action type "${String(t?.type)}" \u2014 mapped to "log".`)),{type:"log"}}}function hn(i){let e=[],{conditions:t,conditionLogic:n}=Sr(i,e),o=Ur(i,e);return t.length>10&&e.push(U.createWarning("many_conditions",i.id,void 0,`Rule has ${t.length} conditions which may impact performance`)),{result:{id:i.id,name:i.description||i.id,enabled:i.enabled,conditions:t,conditionLogic:n,action:o},warnings:e}}function Lr(i,e,t){let n=new Map;for(let a of i){let l=a.group??0,c=n.get(l);c?c.push(a):n.set(l,[a])}let o=a=>{let l=Ar[a.field];if(l){if(a.key===void 0)return t.push(ye(e,`Condition on '${a.field}' has no 'key' set \u2014 Fastly needs a specific ${a.field} name to match on (e.g. a header name). Skipped.`)),null;if(a.operator==="exists"||a.operator==="not_exists")return{type:"multival",field:l,operator:a.operator==="exists"?"exists":"does_not_exist",group_operator:"all",conditions:[{type:"single",field:"name",operator:"equals",value:a.key}]};let d=l==="request_header"||l==="response_header"?"value_string":"value";return{type:"multival",field:l,operator:"exists",group_operator:"all",conditions:[{type:"single",field:"name",operator:"equals",value:a.key},{type:"single",field:d,operator:mn(a.operator,a.negated,t,e),value:String(a.value)}]}}if(a.operator==="exists"||a.operator==="not_exists")return t.push(U.createUnsupportedFeatureWarning(`"${a.operator}" on field '${a.field}'`,"unified config","Fastly",e,a.field)),null;let c=_r(a.field);return c?{type:"single",field:c,operator:mn(a.operator,a.negated,t,e),value:String(a.value)}:(t.push(U.createUnsupportedFeatureWarning(`condition field '${a.field}'`,"unified config","Fastly",e,a.field)),null)};if(n.size<=1){let a=i.map(o).filter(l=>l!==null);return{conditions:a.filter(l=>l.type==="single"),groupConditions:[],multivalConditions:a.filter(l=>l.type==="multival"),groupOperator:"all"}}let s=[];for(let a of n.values()){let l=a.map(o).filter(c=>c!==null);l.length>0&&s.push({type:"group",group_operator:"all",conditions:l})}return{conditions:[],groupConditions:s,multivalConditions:[],groupOperator:"any"}}function xr(i,e,t){switch(i.type){case"block":case"deny":return{type:"block"};case"allow":return{type:"allow"};case"challenge":return{type:"browser_challenge"};case"bypass":return e.push(ye(t,'Unified action "bypass" has no direct Fastly equivalent \u2014 mapped to "allow".')),{type:"allow"};case"log":return e.push(ye(t,'Unified action "log" has no dedicated Fastly action \u2014 mapped to "allow" (request_logging is enabled at the rule level regardless).')),{type:"allow"};case"redirect":return i.redirect?{type:"redirect",redirect_url:i.redirect.location,response_code:i.redirect.statusCode??(i.redirect.permanent?301:302)}:(e.push(ye(t,'Unified action "redirect" has no redirect target \u2014 mapped to "allow".')),{type:"allow"});case"rate_limit":return{type:"block_signal"};default:return e.push(U.createUnsupportedFeatureWarning(`action type '${i.type}'`,"unified config","Fastly",t,"action.type")),{type:"block"}}}function Dr(i,e,t){let n=/^(\d+)\s*(s|m|h)?$/i.exec(i.trim()),o={s:1,m:60,h:3600},s=n?Number(n[1])*o[n[2]?.toLowerCase()||"s"]:NaN,a=[60,600,3600];if(!Number.isFinite(s)||s<=0)return e.push(U.createWarning("rate_limiting_precision",t,void 0,`Rate limit window "${i}" could not be parsed \u2014 defaulted to Fastly's 60-second interval.`)),60;let l=a.reduce((c,d)=>Math.abs(d-s)<Math.abs(c-s)?d:c);return l!==s&&e.push(U.createWarning("rate_limiting_precision",t,void 0,`Rate limit window "${i}" (${s}s) isn't one of Fastly's supported intervals (60s/600s/3600s) \u2014 rounded to ${l}s.`)),l}function Or(i,e,t){if(!i)throw new Error(`Rule "${e??"(unnamed)"}" has action type "rate_limit" but no rateLimit configuration \u2014 Fastly requires a threshold and interval.`);let n=Dr(i.window,t,e);return t.push(U.createWarning("rate_limiting_precision",e,void 0,`Fastly rate-limit rules count requests against a named custom signal, which doorman does not create automatically \u2014 create a signal named "doorman-rate-limit-${e??"rule"}" in this workspace before syncing, or the rule will be rejected.`)),{client_identifiers:[{type:"ip"}],duration:i.mitigationTimeout??n,interval:n,signal:`doorman-rate-limit-${e??"rule"}`,threshold:i.requests}}function yn(i){let e=[];i.description&&i.description!==i.name&&e.push(ye(i.id,'Fastly rules have no separate "name" field \u2014 only "description" carries over, and it was set from this rule\'s `name`. The `description` text was dropped rather than merged in, to keep it round-trip-stable on the next sync.'));let{conditions:t,groupConditions:n,multivalConditions:o,groupOperator:s}=Lr(i.conditions,i.id,e);if(t.length===0&&n.length===0&&o.length===0)throw new Error(`Rule "${i.name}" has no conditions Fastly can represent \u2014 every condition field is unsupported by Fastly. Cannot sync this rule to Fastly.`);let a=i.action.type==="rate_limit";return{result:{type:a?"rate_limit":"request",description:i.name,enabled:i.enabled,group_operator:s,request_logging:"sampled",conditions:t,group_conditions:n,multival_conditions:o,actions:[xr(i.action,e,i.id)],rate_limit:a?Or(i.action.rateLimit,i.id,e):null},warnings:e}}function Rn(i,e){return i.map(t=>({ip:t,action:e}))}function wn(i){let e=[],t=[],n=[];for(let o of i)(o.hostname||o.notes)&&e.push(ye(o.id,`IP rule "${o.ip}" sets ${o.hostname?"`hostname`":""}${o.hostname&&o.notes?" and ":""}${o.notes?"`notes`":""} \u2014 Fastly IP lists have no equivalent per-entry metadata; only the address was kept.`)),(o.action==="allow"?n:t).push(o.ip);return{result:{denyEntries:t,allowEntries:n},warnings:e}}ce();function Ke(i){return i.replace(/\\/g,"\\\\").replace(/'/g,"\\'")}function Cn(i){let e="";for(let t=0;t<i.length;t++)i[t]==="\\"&&(i[t+1]==="\\"||i[t+1]==="'")?(e+=i[t+1],t++):e+=i[t];return e}var Fr=new Set(["region","city","port","scheme"]),Nr={host:"host",user_agent:"user-agent",referer:"referer"},st=class{static fromUnifiedConditions(e,t="AND"){if(!e||e.length===0)throw new Error("At least one condition is required");if(!e.some(a=>a.group!==void 0)){let a=e.map(c=>this.fromUnifiedCondition(c)),l=t==="AND"?" && ":" || ";return a.length>1?`(${a.join(l)})`:a[0]}let o=new Map;for(let a of e){let l=a.group??0,c=o.get(l);c?c.push(a):o.set(l,[a])}let s=Array.from(o.values()).map(a=>this.combineWithAnd(a.map(l=>this.fromUnifiedCondition(l))));return s.length>1?this.combineWithOr(s):s[0]}static fromUnifiedCondition(e){if(Fr.has(e.field))throw new Error(`Cloud Armor has no CEL equivalent for the "${e.field}" condition field \u2014 this rule cannot be represented and would otherwise be silently dropped.`);if(e.field==="ip")return this.buildIpCondition(e);if(e.field==="cookie")return this.buildCookieCondition(e);let t=Nr[e.field];if(t)return this.buildHeaderCondition(t,e);if(e.field==="header"||e.field==="query"){if(e.field==="header"){if(!e.key)throw new Error('A "header" condition requires a key naming the header');return this.buildHeaderCondition(e.key,e)}return this.buildComparison("request.query",e,!1)}let n=Br[e.field];if(n)return this.buildComparison(n.path,e,n.numeric);throw new Error(`Cloud Armor CEL builder has no mapping for condition field "${e.field}"`)}static buildIpCondition(e){let t=(Array.isArray(e.value)?e.value:[e.value]).map(String);if(t.length===0||t.some(a=>!a))throw new Error('An "ip" condition requires at least one value');let n=t.map(a=>{if(!Te.safeParse(a).success)throw new Error(`Invalid IP address or CIDR range: ${a}`);return a.includes("/")?`inIpRange(origin.ip, '${Ke(a)}')`:`origin.ip == '${Ke(a)}'`}),o=this.isNegated(e,e.operator==="not_in"||e.operator==="ne");if(n.length>1){let a=`(${n.join(" || ")})`;return o?`!${a}`:a}let s=n[0];return o?s.startsWith("inIpRange(")?`!${s}`:`!(${s})`:s}static buildHeaderCondition(e,t){let n=`request.headers['${Ke(e)}']`,o=`has(${n})`;return t.operator==="exists"||t.operator==="not_exists"?this.isNegated(t,t.operator==="not_exists")?`!${o}`:o:`(${o} && ${this.buildComparison(n,t,!1)})`}static buildCookieCondition(e){let t="request.headers['cookie']",n=`has(${t})`;if(e.operator==="exists"||e.operator==="not_exists")return this.isNegated(e,e.operator==="not_exists")?`!${n}`:n;if(!e.key)return`(${n} && ${this.buildComparison(t,e,!1)})`;if(!["eq","ne","contains","not_contains"].includes(e.operator))throw new Error(`Cloud Armor can only check a specific cookie's value via substring search (has no parsed cookie map) \u2014 "${e.operator}" on a keyed cookie condition has no unambiguous CEL representation.`);let o=`${e.key}=${String(e.value)}`,s=`${t}.contains('${Ke(o)}')`,a=this.isNegated(e,e.operator==="ne"||e.operator==="not_contains");return`(${n} && ${a?`!${s}`:s})`}static buildComparison(e,t,n){let{value:o}=t;if(t.operator==="exists"||t.operator==="not_exists")throw new Error(`"${t.operator}" is not meaningful for Cloud Armor's "${e}" \u2014 it is always present`);let s=t.operator,a=!1;if(t.negated){let d=Vr[s];d?s=d:a=!0}if(s==="in"||s==="not_in"){let u=(Array.isArray(o)?o:[o]).map(f=>this.formatLiteral(f,n)).join(", "),p=`${e} in [${u}]`;return s==="not_in"?`!(${p})`:p}let l=this.formatLiteral(o,n),c;switch(s){case"eq":c=`${e} == ${l}`;break;case"ne":c=`${e} != ${l}`;break;case"contains":c=`${e}.contains(${l})`;break;case"not_contains":c=`!${e}.contains(${l})`;break;case"starts_with":c=`${e}.startsWith(${l})`;break;case"ends_with":c=`${e}.endsWith(${l})`;break;case"matches":c=`${e}.matches(${l})`;break;case"gt":c=`${e} > ${l}`;break;case"ge":c=`${e} >= ${l}`;break;case"lt":c=`${e} < ${l}`;break;case"le":c=`${e} <= ${l}`;break;default:throw new Error(`Cloud Armor CEL builder has no mapping for operator "${s}"`)}return a?`!(${c})`:c}static isNegated(e,t){return t!==!!e.negated}static formatLiteral(e,t){if(t){let n=Number(e);if(!Number.isFinite(n))throw new Error(`Expected a numeric value, got "${String(e)}"`);return String(n)}return`'${Ke(String(e))}'`}static combineWithAnd(e){if(e.length===0)throw new Error("At least one expression is required");return e.length===1?e[0]:`(${e.join(" && ")})`}static combineWithOr(e){if(e.length===0)throw new Error("At least one expression is required");return e.length===1?e[0]:`(${e.join(" || ")})`}},Vr={eq:"ne",ne:"eq",contains:"not_contains",not_contains:"contains",in:"not_in",not_in:"in"},Br={country:{path:"origin.region_code",numeric:!1},asn:{path:"origin.asn",numeric:!0},path:{path:"request.path",numeric:!1},method:{path:"request.method",numeric:!1}};var Mr={"origin.region_code":"country","origin.asn":"asn","request.path":"path","request.method":"method","request.query":"query"},qr={host:"host","user-agent":"user_agent",referer:"referer",cookie:"cookie"},An={contains:"contains",startsWith:"starts_with",endsWith:"ends_with",matches:"matches"};function Wr(i){let e=[],t=0,n=i.length,o=(s,a)=>i[t]===s&&i[t+1]===a;for(;t<n;){let s=i[t];if(/\s/.test(s)){t++;continue}if(s==="("){e.push({type:"LPAREN",value:s}),t++;continue}if(s===")"){e.push({type:"RPAREN",value:s}),t++;continue}if(s==="["){e.push({type:"LBRACKET",value:s}),t++;continue}if(s==="]"){e.push({type:"RBRACKET",value:s}),t++;continue}if(s===","){e.push({type:"COMMA",value:s}),t++;continue}if(o("&","&")){e.push({type:"AND",value:"&&"}),t+=2;continue}if(o("|","|")){e.push({type:"OR",value:"||"}),t+=2;continue}if(o("=","=")){e.push({type:"EQ",value:"=="}),t+=2;continue}if(o("!","=")){e.push({type:"NE",value:"!="}),t+=2;continue}if(o(">","=")){e.push({type:"GE",value:">="}),t+=2;continue}if(o("<","=")){e.push({type:"LE",value:"<="}),t+=2;continue}if(s==="!"){e.push({type:"NOT",value:"!"}),t++;continue}if(s===">"){e.push({type:"GT",value:">"}),t++;continue}if(s==="<"){e.push({type:"LT",value:"<"}),t++;continue}if(s==="."){e.push({type:"DOT",value:s}),t++;continue}if(s==="'"){let a=t+1,l="",c=!1;for(;a<n;){let d=i[a];if(d==="\\"&&a+1<n){l+=d+i[a+1],a+=2;continue}if(i[a]==="'"){c=!0,a++;break}l+=i[a],a++}if(!c)throw new W("Unterminated string literal");e.push({type:"STRING",value:Cn(l)}),t=a;continue}if(/[0-9]/.test(s)||s==="-"&&/[0-9]/.test(i[t+1]??"")){let a=t+1;for(;a<n&&/[0-9.]/.test(i[a]);)a++;e.push({type:"NUMBER",value:i.slice(t,a)}),t=a;continue}if(/[A-Za-z_]/.test(s)){let a=t;for(;a<n&&/[A-Za-z0-9_]/.test(i[a]);)a++;e.push({type:"IDENT",value:i.slice(t,a)}),t=a;continue}throw new W(`Unexpected character '${s}' at position ${t}`)}return e}function jr(i){let e=i.expect("IDENT").value;for(;i.peek()?.type==="DOT";){let t=i.pos;i.next();let n=i.expect("IDENT");if(i.peek()?.type==="LPAREN"){i.pos=t;break}e+=`.${n.value}`}return e}function Zt(i){let e=jr(i);if(i.peek()?.type==="LBRACKET"){i.next();let t=i.expect("STRING");return i.expect("RBRACKET"),{field:e,key:t.value}}return{field:e}}function bn(i){let e=i.next();if(e.type==="STRING")return e.value;if(e.type==="NUMBER")return Number(e.value);throw new W(`Unexpected token '${e.value}' where a literal was expected`)}function zr(i){if(i.is("IDENT","has")){i.next(),i.expect("LPAREN");let a=Zt(i);return i.expect("RPAREN"),{type:"has",...a}}if(i.is("IDENT","inIpRange")){i.next(),i.expect("LPAREN");let a=Zt(i);if(a.field!=="origin.ip")throw new W(`inIpRange must reference origin.ip, got '${a.field}'`);i.expect("COMMA");let l=i.expect("STRING");return i.expect("RPAREN"),{type:"inIpRange",value:l.value}}let e=Zt(i);if(i.peek()?.type==="DOT"){i.next();let a=i.expect("IDENT").value;if(!An[a])throw new W(`Unrecognized method '${a}'`);i.expect("LPAREN");let l=i.expect("STRING");return i.expect("RPAREN"),{type:"methodCall",...e,method:a,value:l.value}}if(i.is("IDENT","in")){i.next(),i.expect("LBRACKET");let a=[];for(;i.peek()?.type!=="RBRACKET";)a.push(bn(i)),i.peek()?.type==="COMMA"&&i.next();return i.expect("RBRACKET"),{type:"membership",...e,values:a}}let t=i.next(),o={EQ:"eq",NE:"ne",GT:"gt",GE:"ge",LT:"lt",LE:"le"}[t.type];if(!o)throw new W(`Unexpected token '${t.value}' after field reference`);let s=bn(i);return{type:"comparison",...e,operator:o,value:s}}function vn(i){if(i.peek()?.type==="LPAREN"){i.next();let e=$n(i);return i.expect("RPAREN"),e}return zr(i)}function In(i){return i.peek()?.type==="NOT"?(i.next(),{type:"not",child:vn(i)}):vn(i)}function En(i){let e=[In(i)];for(;i.peek()?.type==="AND";)i.next(),e.push(In(i));return e.length===1?e[0]:{type:"and",children:e}}function $n(i){let e=[En(i)];for(;i.peek()?.type==="OR";)i.next(),e.push(En(i));return e.length===1?e[0]:{type:"or",children:e}}function Gr(i){let e=new Ge(Wr(i)),t=$n(e);if(!e.atEnd())throw new W("Unexpected trailing tokens");return t}function Kr(i){return i.type==="inIpRange"||i.type==="comparison"&&i.field==="origin.ip"&&i.operator==="eq"}function Qt(i){return i.type==="or"&&i.children.length>0&&i.children.every(Kr)}function Hr(i){return i.type==="inIpRange"?i.value:String(i.value)}function Pn(i){if(i.type!=="and"||i.children.length!==2)return!1;let e=i.children[0],t=i.children[1];if(e.type!=="has")return!1;let n=t.type==="not"?t.child:t;return!("field"in n)||n.field!==e.field||n.key!==e.key?!1:n.type==="comparison"||n.type==="methodCall"||n.type==="membership"}function Zr(i){let e=i.children[0],t=i.children[1],n=t.type==="not",o=n?t.child:t;return{guard:e,comparisonNegated:n,comparison:o}}function Yt(i){if(i.type==="has"||i.type==="comparison"||i.type==="methodCall"||i.type==="membership"||i.type==="inIpRange"||Qt(i)||Pn(i))return!0;if(i.type==="not"){let e=i.child;return e.type==="has"||e.type==="comparison"||e.type==="methodCall"||e.type==="membership"||e.type==="inIpRange"||Qt(e)}return!1}function _n(i,e){if(i==="origin.ip")return{unifiedField:"ip"};let t=Mr[i];if(t)return{unifiedField:t};if(i==="request.headers"&&e!==void 0){let n=qr[e];return n?{unifiedField:n}:{unifiedField:"header",unifiedKey:e}}return null}function Jt(i,e){let t=i.type==="not",n=i.type==="not"?i.child:i;if(Qt(n)){let o=n.children.map(s=>Hr(s));return{field:"ip",operator:t?"not_in":"in",value:o,...e!==void 0?{group:e}:{}}}if(n.type==="inIpRange"||n.type==="comparison"&&n.field==="origin.ip"){let o=n.type==="inIpRange"?n.value:String(n.value);return{field:"ip",operator:t?"ne":"eq",value:o,...e!==void 0?{group:e}:{}}}if(n.type==="and"&&Pn(n)){if(t)return null;let{guard:o,comparisonNegated:s,comparison:a}=Zr(n);return a.type!=="comparison"&&a.type!=="methodCall"&&a.type!=="membership"?null:Tn(a,s,o.field,o.key,e)}if(n.type==="comparison"||n.type==="methodCall"||n.type==="membership")return Tn(n,t,n.field,n.key,e);if(n.type==="has"){let o=_n(n.field,n.key);return o?{field:o.unifiedField,operator:t?"not_exists":"exists",value:"",...o.unifiedKey!==void 0?{key:o.unifiedKey}:{},...e!==void 0?{group:e}:{}}:null}return null}function Tn(i,e,t,n,o){let s=_n(t,n);if(!s)return null;let a,l,c=!1;if(i.type==="comparison")e?i.operator==="eq"?a="ne":i.operator==="ne"?a="eq":(a=i.operator,c=!0):a=i.operator,l=i.value;else if(i.type==="methodCall"){let d=An[i.method];if(!d)return null;e?d==="contains"?a="not_contains":(a=d,c=!0):a=d,l=i.value}else a=e?"not_in":"in",l=i.values;return{field:s.unifiedField,operator:a,value:l,...c?{negated:!0}:{},...s.unifiedKey!==void 0?{key:s.unifiedKey}:{},...o!==void 0?{group:o}:{}}}function Yr(i){if(Yt(i)){let e=Jt(i,void 0);return e?{conditions:[e],conditionLogic:"AND"}:null}if(i.type==="and"){let e=[];for(let t of i.children){if(!Yt(t))return null;let n=Jt(t,void 0);if(!n)return null;e.push(n)}return{conditions:e,conditionLogic:"AND"}}return i.type==="or"?Tt(i.children,Yt,e=>e.type==="and",Jt):null}function Ne(i){if(!i||!i.trim())return null;try{let e=Gr(i);return Yr(e)}catch{return null}}function Xt(i){if(!i.match.expr)throw new Error(`Cloud Armor rule at priority ${i.priority} has no CEL match expression (uses the basic versionedExpr/config shape) \u2014 caller must filter these out before translating.`);return i.match.expr.expression}function kn(i){if(i.priority===void 0)throw new Error(`Rule "${i.name}" has no priority assigned \u2014 CloudArmorFirewallService must assign one before calling unifiedToGcp`);let e=[],t=st.fromUnifiedConditions(i.conditions,i.conditionLogic),{action:n,actionWarning:o}=Qr(i.action.type,i.name,i.id);o&&e.push(o);let s={priority:i.priority,description:(i.description||i.name).slice(0,64),match:{expr:{expression:t}},action:n,preview:i.action.type==="log"?!0:void 0};if((n==="throttle"||n==="rate_based_ban")&&i.action.rateLimit){let a=Xr(i.action.rateLimit.window);s.rateLimitOptions={rateLimitThreshold:{count:i.action.rateLimit.requests,intervalSec:a},conformAction:"allow",exceedAction:"deny(429)",enforceOnKey:"IP"}}else n==="throttle"&&!i.action.rateLimit&&e.push(U.createWarning("lossy_conversion",i.id,"action.rateLimit",`Rule "${i.name}" maps to Cloud Armor's "throttle" action but declares no rateLimit block \u2014 Cloud Armor requires rateLimitOptions for this action; the rule will likely be rejected by the API.`));return n==="redirect"&&(i.action.redirect?s.redirectOptions={type:"EXTERNAL_302",target:i.action.redirect.location}:e.push(U.createWarning("lossy_conversion",i.id,"action.redirect",`Rule "${i.name}" maps to Cloud Armor's "redirect" action but declares no redirect target \u2014 Cloud Armor requires redirectOptions for this action; the rule will likely be rejected by the API.`))),{result:s,warnings:e}}function Sn(i,e){let t=[],n=e!==void 0?e:Ne(Xt(i)),o=[],s;n?(o=n.conditions,s=n.conditionLogic):t.push(U.createWarning("complex_expressions",String(i.priority),"match.expr.expression","CEL expression could not be parsed back into structured conditions \u2014 it may be hand-authored or use syntax outside what doorman itself generates.","Review the translated rule and add missing conditions manually if needed."));let a={type:Jr(i.action),...i.rateLimitOptions?{rateLimit:{requests:i.rateLimitOptions.rateLimitThreshold.count,window:`${i.rateLimitOptions.rateLimitThreshold.intervalSec}s`}}:{},...i.redirectOptions?{redirect:{location:i.redirectOptions.target}}:{}};return{result:{id:String(i.priority),name:i.description||`Rule ${i.priority}`,...i.description?{description:i.description}:{},enabled:!i.preview,conditions:o,...s==="OR"?{conditionLogic:s}:{},action:a,priority:i.priority},warnings:t}}function Un(i,e){if(i.action!=="allow"&&i.action!=="deny(403)"||i.preview)return!1;let t=e!==void 0?e:Ne(Xt(i));if(!t||t.conditions.length!==1)return!1;let n=t.conditions[0];return n.field==="ip"&&(n.operator==="eq"||n.operator==="in")&&!n.key}function Ln(i,e){let o=(e!==void 0?e:Ne(Xt(i))).conditions[0].value;return{id:String(i.priority),ip:Array.isArray(o)?String(o[0]):String(o),...i.description?{notes:i.description}:{},action:i.action==="allow"?"allow":"deny"}}function xn(i,e){if(!Te.safeParse(i.ip).success)throw new Error(`Invalid IP address or CIDR range: ${i.ip}`);let t=st.fromUnifiedCondition({field:"ip",operator:"eq",value:i.ip});return{priority:e,description:(i.notes||`IP ${i.action}: ${i.ip}${i.hostname?` (${i.hostname})`:""}`).slice(0,64),match:{expr:{expression:t}},action:i.action==="allow"?"allow":"deny(403)"}}function Jr(i){return{allow:"allow","deny(403)":"deny","deny(404)":"deny","deny(502)":"deny",rate_based_ban:"rate_limit",throttle:"rate_limit",redirect:"redirect"}[i]||"deny"}function Qr(i,e,t){switch(i){case"log":return{action:"allow"};case"deny":case"block":return{action:"deny(403)"};case"challenge":return{action:"deny(403)",actionWarning:U.createUnsupportedFeatureWarning("challenge action","unified config","Cloud Armor",t,"action")};case"bypass":return{action:"allow"};case"rate_limit":return{action:"throttle"};case"redirect":return{action:"redirect"};case"allow":return{action:"allow"};default:return{action:"deny(403)",actionWarning:U.createWarning("lossy_conversion",t,"action",`Rule "${e}" has unrecognized action "${i}" \u2014 defaulted to Cloud Armor's "deny(403)".`)}}}function Xr(i){let e=i.match(/^(\d+)([smhd])$/);if(!e||!e[1]||!e[2])throw new Error(`Invalid window format: ${i}`);let t=parseInt(e[1],10),o={s:1,m:60,h:3600,d:86400}[e[2]];if(o===void 0)throw new Error(`Invalid time unit: ${e[2]}`);return t*o}var E=class{static vercelToUnified=Ji;static unifiedToVercel=Qi;static vercelIPToUnified=Xi;static cloudflareToUnified=cn;static unifiedToCloudflare=dn;static unifiedIPToCloudflare=un;static unifiedManagedRuleGroupToCloudflare=pn;static cloudflareToUnifiedManagedRuleGroup=fn;static fastlyToUnified=hn;static unifiedToFastly=yn;static fastlyListEntriesToUnified=Rn;static unifiedIPsToFastlyEntries=wn;static gcpToUnified=Sn;static unifiedToGcp=kn;static gcpToUnifiedIP=Ln;static unifiedIPToGcp=xn;static gcpLooksLikeIpRule=Un};ce();function Le(i,e){if(i===e)return!0;if(typeof i!="object"||i===null||typeof e!="object"||e===null||Array.isArray(i)!==Array.isArray(e))return!1;let t=Object.keys(i),n=Object.keys(e);if(t.length!==n.length)return!1;for(let o of t)if(!n.includes(o)||!Le(i[o],e[o]))return!1;return!0}function Re(i){let{id:e,...t}=i;return t}function at(i){return[...i].sort((e,t)=>{let n=e.priority!==void 0,o=t.priority!==void 0;return!n&&!o?0:n?o?e.priority-t.priority:-1:1})}function $t(i){return i.some(e=>e.priority!==void 0)}var He=class extends $e{constructor(t){super();this.client=t}client;name="vercel";async fetchConfig(t){try{r.debug("Fetching Vercel firewall configuration");let n=await this.client.fetchFirewallConfig(t),o=n.rules.map(a=>{let l=E.vercelToUnified(a);return l.warnings.length>0&&l.warnings.forEach(c=>{let{TranslationWarningSystem:d}=(ce(),me(Pe)),u=d.formatWarning(c);r.warn(`Rule ${a.name}:
25
25
  ${u}`)}),l.result}),s=n.ips.map(a=>E.vercelIPToUnified(a));return{version:"2.0",provider:"vercel",providers:{vercel:{projectId:this.client.projectId,...this.client.teamId?{teamId:this.client.teamId}:{}}},rules:o,ips:s,metadata:{version:n.version,updatedAt:n.updatedAt}}}catch(n){throw r.error("Error fetching Vercel configuration:",n),new Error("Failed to fetch Vercel firewall configuration")}}async syncRules(t,n={}){let{dryRun:o=!1}=n;try{let{OperationSafety:s}=(ct(),me(lt)),a=await s.performDryRunValidation(t,"sync rules",async y=>await this.getChanges(y,{allowCreate:!o}));if(!a.valid)throw new Error(`Dry-run validation failed: ${a.issues.join(", ")}`);let l=a.changes,{rulesToAdd:c,rulesToUpdate:d,rulesToDelete:u,version:p}=l,f=l.ipsToAdd||[],g=l.ipsToUpdate||[],m=l.ipsToDelete||[];if(o)return r.info("Dry run mode. The following changes would be made:"),r.info(`Custom Rules - Add: ${c.length}, Update: ${d.length}, Delete: ${u.length}`),r.info(`IP Rules - Add: ${f.length}, Update: ${g.length}, Delete: ${m.length}`),{success:!0,rulesAdded:0,rulesUpdated:0,rulesDeleted:0,ipsAdded:0,ipsUpdated:0,ipsDeleted:0,version:p,warnings:a.warnings};let v=s.assessOperationRisk(l,t);if(!await s.confirmDestructiveOperation({operation:"sync rules",target:`Vercel project ${this.client.projectId}`,changes:l,riskLevel:v,skipConfirmation:n.force||!1,dryRun:!1,allowDeletions:n.allowDeletions||!1}))throw new Error("Operation cancelled by user");let x=c.map(y=>E.unifiedToVercel(y).result),S=d.map(y=>E.unifiedToVercel(y).result),M=u.map(y=>E.unifiedToVercel(y).result),q=f.map(y=>({id:y.id||"",ip:y.ip,hostname:y.hostname||"",action:y.action,notes:y.notes})),Y=g.map(y=>({id:y.id||"",ip:y.ip,hostname:y.hostname||"",action:y.action,notes:y.notes})),le=m.map(y=>({id:y.id||"",ip:y.ip,hostname:y.hostname||"",action:y.action,notes:y.notes})),G=[],pe=[],k=[],Ie=[],Ce=[],T=[],h=[],b=(y,P)=>`${y}: ${P instanceof Error?P.message:String(P)}`,X=[];$t(t.rules)&&t.rules.length>c.length&&X.push("Rule `priority` is best-effort on Vercel: new rules are created in priority order, but rules that already exist remotely cannot be repositioned via the Vercel API. Recreate them (remove and re-add) if exact evaluation order matters.");let fe=[];for(let y of M)try{r.debug(`Deleting custom rule: ${y.id}`),await this.client.deleteFirewallRule(y),k.push(y),r.debug(`Custom rule deleted: ${y.id}`)}catch(P){r.error(`Failed to delete custom rule ${y.id}:`,P),h.push(b(`Failed to delete custom rule ${y.id}`,P))}for(let y of le)try{r.debug(`Deleting IP blocking rule: ${y.id}`),await this.client.deleteIPBlockingRule(y),T.push(y),r.debug(`IP blocking rule deleted: ${y.id}`)}catch(P){r.error(`Failed to delete IP blocking rule ${y.id}:`,P),h.push(b(`Failed to delete IP blocking rule ${y.id}`,P))}for(let y of x)try{r.debug(`Adding new custom rule: ${y.name}`);let P=await this.client.createFirewallRule(y);G.push(P),P.id&&P.id!==y.id&&fe.push({oldId:y.id||void 0,newId:P.id,name:y.name}),r.debug(`New custom rule added: ${P.id}`)}catch(P){r.error(`Failed to add custom rule "${y.name}":`,P),h.push(b(`Failed to add custom rule "${y.name}"`,P))}for(let y of q)try{r.debug(`Adding new IP blocking rule: ${y.ip}`);let P=await this.client.createIPBlockingRule(y);Ie.push(P),r.debug(`New IP blocking rule added: (hostname): ${P.hostname} (ip): ${P.ip}`)}catch(P){r.error(`Failed to add IP blocking rule ${y.ip}:`,P),h.push(b(`Failed to add IP blocking rule ${y.ip}`,P))}for(let y of S)try{r.debug(`Updating custom rule: ${y.id}`);let P=await this.client.updateFirewallRule(y);pe.push(P),r.debug(`Custom rule updated: ${P.id}`)}catch(P){r.error(`Failed to update custom rule ${y.id}:`,P),h.push(b(`Failed to update custom rule ${y.id}`,P))}for(let y of Y)try{r.debug(`Updating IP blocking rule: ${y.id}`);let P=await this.client.updateIPBlockingRule(y);Ce.push(P),r.debug(`IP blocking rule updated: ${P.id}`)}catch(P){r.error(`Failed to update IP blocking rule ${y.id}:`,P),h.push(b(`Failed to update IP blocking rule ${y.id}`,P))}r.debug(`${ge.underline("Custom Rules:")} ${ge.green("Added:")} ${ge.green(G.length)}, ${ge.cyan("Updated:")} ${ge.cyan(pe.length)}, ${ge.red("Deleted:")} ${ge.red(k.length)}`),r.debug(`${ge.underline("IP Rules:")} ${ge.green("Added:")} ${ge.green(Ie.length)}, ${ge.cyan("Updated:")} ${ge.cyan(Ce.length)}, ${ge.red("Deleted:")} ${ge.red(T.length)}`);let nt=p,ot,xe=[...X];try{let y=await this.client.fetchFirewallConfig();nt=y.version,ot=y.updatedAt;let P=new Set(y.rules.map(B=>B.id)),vi=new Set(y.ips.map(B=>B.id));for(let B of[...G,...pe])B.id&&!P.has(B.id)&&xe.push(`Rule "${B.name}" (${B.id}) was synced but is missing from the post-sync remote config \u2014 a subsequent sync may re-create or misdiff it.`);for(let B of k)B.id&&P.has(B.id)&&xe.push(`Rule "${B.name}" (${B.id}) was deleted but still appears in the post-sync remote config.`);for(let B of[...Ie,...Ce])B.id&&!vi.has(B.id)&&xe.push(`IP rule "${B.ip}" (${B.id}) was synced but is missing from the post-sync remote config \u2014 a subsequent sync may re-create or misdiff it.`);for(let B of T)B.id&&vi.has(B.id)&&xe.push(`IP rule "${B.ip}" (${B.id}) was deleted but still appears in the post-sync remote config.`)}catch(y){r.error("Failed to fetch updated firewall configuration version after sync:",y),h.push(b("Failed to fetch updated configuration version after sync",y))}return{success:h.length===0,rulesAdded:G.length,rulesUpdated:pe.length,rulesDeleted:k.length,ipsAdded:Ie.length,ipsUpdated:Ce.length,ipsDeleted:T.length,version:nt,updatedAt:ot,idRemappings:fe.length>0?fe:void 0,errors:h.length>0?h:void 0,warnings:xe.length>0?xe:void 0}}catch(s){throw r.error("Error during sync:",s),new Error(`Failed to synchronize firewall rules: ${s instanceof Error?s.message:String(s)}`,{cause:s})}}async getChanges(t,n){let o=_e.safeParse(t);if(!o.success)throw new Error(`Invalid firewall configuration: ${o.error.message}`);this.assertManagedRulesSupported(t);try{r.debug("Fetching existing firewall configuration");let s=await this.client.fetchFirewallConfig(void 0,{allowCreate:n?.allowCreate??!0});r.debug(`Fetched ${s.rules.length} custom rules and ${s.ips.length} IP blocking rules`);let a=s.rules.map(m=>{let v=E.vercelToUnified(m);return v.warnings.length>0&&v.warnings.forEach(j=>{let{TranslationWarningSystem:x}=(ce(),me(Pe)),S=x.formatWarning(j);r.warn(`Rule ${m.name}:
26
26
  ${S}`)}),v.result}),{toAdd:l,toUpdate:c,toDelete:d}=this.diffRules(at(o.data.rules),a),u=s.ips.map(m=>E.vercelIPToUnified(m)),{ipsToAdd:p,ipsToUpdate:f,ipsToDelete:g}=this.diffIPRules(o.data.ips||[],u);return{version:s.version,rulesToAdd:l,rulesToUpdate:c,rulesToDelete:d,ipsToAdd:p,ipsToUpdate:f,ipsToDelete:g,hasChanges:l.length>0||c.length>0||d.length>0||p.length>0||f.length>0||g.length>0}}catch(s){throw r.error("Error fetching existing firewall configuration:",s),new Error("Failed to fetch existing firewall configuration")}}getSupportedFeatures(){return{supportsCustomRules:!0,supportsIPBlocking:!0,supportsRateLimiting:!0,supportsGeoBlocking:!0,supportsManagedRules:!1,supportsRedirect:!0,supportsChallenge:!0}}async verifyCredentials(){return this.client.verifyCredentials()}validateConfig(t){let n=super.validateConfig(t),o=[...n.errors],s=[...n.warnings];t.provider&&t.provider!=="vercel"&&o.push({path:"provider",message:"Provider must be 'vercel' for VercelFirewallService",code:"INVALID_PROVIDER"});try{let a=De.safeParse(t);a.success||o.push({path:"root",message:`Schema validation failed: ${a.error.message}`,code:"SCHEMA_VALIDATION_FAILED"})}catch(a){r.debug("Schema validation error:",a)}return{valid:o.length===0,errors:o,warnings:s}}getHealthScore(t){let n=super.getHealthScore(t),o=[...n.issues],s=n.score;return t.rules.filter(l=>l.action?.type==="rate_limit").length===0&&(s-=10,o.push({severity:"info",category:"security",message:"No rate limiting rules configured",suggestion:"Consider adding rate limiting to protect against abuse"})),(!t.ips||t.ips.length===0)&&o.push({severity:"info",category:"security",message:"No IP blocking rules configured",suggestion:"Consider blocking known malicious IPs"}),{score:Math.max(0,s),grade:n.grade,issues:o,recommendations:n.recommendations}}diffRules(t,n){let o=[],s=[],a=[...n];for(let l of t){let c=n.find(d=>d.id===l.id);if(!c)o.push(l);else{Le(Re(l),Re(c))||s.push({...l,id:c.id});let d=a.findIndex(u=>u.id===c.id);d!==-1&&a.splice(d,1)}}return{toAdd:o,toUpdate:s,toDelete:a}}diffIPRules(t,n){let o=[],s=[],a=[...n];for(let l of t){let c=n.find(d=>d.id===l.id);if(!c)r.debug("Rule not found in existing rules:",{configRule:l}),o.push(l);else{Le(Re(l),Re(c))||s.push({...c,...l});let d=a.findIndex(u=>u.id===c.id);d!==-1&&a.splice(d,1)}}return{ipsToAdd:o,ipsToUpdate:s,ipsToDelete:a}}};_();var dt=class{static fromEnv(){let e=process.env.VERCEL_TOKEN,t=process.env.VERCEL_PROJECT_ID,n=process.env.VERCEL_TEAM_ID;if(!e)throw new Error("VERCEL_TOKEN environment variable is required");if(!t)throw new Error("VERCEL_PROJECT_ID environment variable is required");let o=new We(t,n,e);return new He(o)}static fromConfig(e){let t=e.token||process.env.VERCEL_TOKEN,n=e.projectId||process.env.VERCEL_PROJECT_ID,o=e.teamId||process.env.VERCEL_TEAM_ID;if(!t)throw new Error("Vercel API token is required (provide token or set VERCEL_TOKEN env var)");if(!n)throw new Error("Vercel project ID is required (provide projectId or set VERCEL_PROJECT_ID env var)");r.debug("Creating Vercel provider with config:",{projectId:n,teamId:o});let s=new We(n,o,t);return new He(s)}static create(e,t,n){return this.fromConfig({projectId:e,teamId:t,token:n})}};import{createHash as as}from"crypto";_();import Ze from"chalk";var R=class i extends Error{code;suggestion;details;docsUrl;cause;constructor(e){super(e.message),this.name="DoormanError",this.code=e.code,this.suggestion=e.suggestion,this.details=e.details,this.docsUrl=e.docsUrl,this.cause=e.cause,Error.captureStackTrace&&Error.captureStackTrace(this,i)}format(){let e=[];return e.push(Ze.red.bold(`[${this.code}] ${this.message}`)),e.push(""),this.suggestion&&(e.push(Ze.yellow.bold("Suggestion:")),e.push(` ${this.suggestion}`),e.push("")),this.details&&Object.keys(this.details).length>0&&(e.push(Ze.dim("Details:")),Object.entries(this.details).forEach(([t,n])=>{let o=typeof n=="object"?JSON.stringify(n,null,2).split(`
27
27
  `).join(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gfargo/doorman",
3
- "version": "3.15.2",
3
+ "version": "3.15.3",
4
4
  "description": "Manage firewall rules as code across multiple providers",
5
5
  "publishConfig": {
6
6
  "access": "public"