@gfargo/doorman 3.13.0 → 3.13.1
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 +1 -1
- package/dist/bin/run.mjs +5 -5
- package/package.json +1 -1
package/dist/bin/run.js
CHANGED
|
@@ -21,7 +21,7 @@ ${o}`}};var W=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(B.default.dim(JSON.stringify(t,null,2)));return}r.start("Loading configuration...");let o=await G(i.config,"raw"),s={...o,ips:[...o.ips||[],t]};await K(s,i.config),r.success(B.default.green(`\u2714 IP rule "${t.ip}" added to configuration`)),Ji(t)}else{let t=i.interactive?await Ko():Ho(i),n=jt.safeParse(t);if(n.success||(r.error(B.default.red("Rule validation failed:")),n.error.errors.forEach(c=>{let d=c.path.join(".")||"rule";r.error(B.default.red(` - ${d}: ${c.message}`))}),process.exit(1)),i.dryRun){r.info(B.default.cyan(`
|
|
22
22
|
Dry run - The following rule would be added:`)),Yi(t),r.log(""),r.log(B.default.dim(JSON.stringify(t,null,2)));return}r.start("Loading configuration...");let o=await G(i.config,"raw"),s=o.rules||[],a=Jo({...o,rules:s},t);if(a){if(r.warn(B.default.yellow(`\u26A0\uFE0F ${a}`)),i.ci||!process.stdin.isTTY){r.info(B.default.dim("Skipping \u2014 non-interactive and a conflicting rule already exists. Rule not added."));return}if(!await v("Proceed anyway?",{type:"confirm",initial:!1})){r.info("Cancelled.");return}}let l={...o,rules:[...s,t]};await K(l,i.config),r.success(B.default.green(`\u2714 Rule "${t.name}" added to configuration`)),Yi(t)}r.log(""),r.log(B.default.dim(`Run ${B.default.cyan("doorman sync")} to deploy this rule.`))}catch(e){ee(e,"adding rule")}};var pi={};X(pi,{builder:()=>gs,command:()=>ps,desc:()=>fs,handler:()=>ms});var Q=O(require("chalk")),Ae=require("fs"),rt=require("path"),Xn=require("consola");T();$e();var ct=require("fs"),ti=require("path");function en(i=process.cwd()){return(0,ct.existsSync)((0,ti.join)(i,".git"))}function tn(i,e=process.cwd()){let t=(0,ti.join)(e,".gitignore");if(!(0,ct.existsSync)(t))return!1;let n=i.replace(/^\.\//,"").replace(/^\//,"").replace(/\/$/,""),o;try{o=(0,ct.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 te={type:"string",choices:z,description:"Firewall provider (auto-detected)"};var Qn=require("consola");T();var Yn=O(require("chalk"));T();$e();var nn=require("readline");function Xo(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 er(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=Xo(l,c),u=er(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)})}T();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 Pt=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 z){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 z){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 z.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 z){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 z){let o=this.detectionField(n);o&&process.env[o.envVar]&&t.add(n)}return Array.from(t)}};var ii=O(require("chalk"));T();$e();var _t=i=>({$schema:xi,firewallEnabled:!0,rules:[],ips:[],...i});T();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 y=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(y),y.status===429){let C=this.calculateRateLimitWait(f,y);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(C/1e3)}s.`);r.warn(`Rate limit exceeded for ${this.providerName}. Waiting ${C}ms before retry (attempt ${f+1}/${s+1})...`),await this.delay(C);continue}if(!y.ok)throw await this.handleErrorResponse(y);let R;try{let C=await y.text();C.trim()?R=JSON.parse(C):R={}}catch(C){throw r.error(`Failed to parse response from ${n}:`,C),new Error(`Invalid JSON response from ${this.providerName} API: ${C instanceof Error?C.message:String(C)}`)}return this.isLargeResponse(R)&&(r.debug(`Large response detected from ${n}, validating structure...`),this.validateLargeResponse(R)),R}catch(y){if(c=y,u&&(clearTimeout(u),u=null),this.isNonRetryableError(y)||f===s)throw y;let R=a*Math.pow(2,f),C=Math.random()*.1*R,x=Math.min(R+C,3e4);r.debug(`Request failed (attempt ${f+1}/${s+1}). Retrying in ${Math.round(x)}ms...`),r.debug(`Error details: ${y instanceof Error?y.message:String(y)}`),await this.delay(x)}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(y=>!p.includes(y));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(ii.default.bold("No firewall configuration found.")),!o)return r.debug("Skipping create-configuration prompt for read-only fetch."),this.emptyConfigPlaceholder();if(await v("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: ${ii.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}=_t();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 se=O(require("chalk"));T();T();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}}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.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=ir(l.op);t.push({field:or(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=tr(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 tr(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=sr(l.field);if(!c){t.push(U.createUnsupportedFeatureWarning(`condition field '${l.field}'`,"unified config","Vercel",e,l.field));continue}a.push({op:nr(l.operator),neg:l.negated,type:c,key:l.key,value:l.value})}a.length>0&&o.push({conditions:a})}return o}function ir(i){return{eq:"eq",pre:"starts_with",suf:"ends_with",inc:"in",sub:"contains",re:"matches",ex:"exists",nex:"not_exists"}[i]||"eq"}function nr(i){return{eq:"eq",starts_with:"pre",ends_with:"suf",in:"inc",contains:"sub",matches:"re",exists:"ex",not_exists:"nex"}[i]||"eq"}function or(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"}[i]||i}var rr={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",region:"region",protocol:"protocol",environment:"environment",geo_continent:"geo_continent",geo_country_region:"geo_country_region",ja4_digest:"ja4_digest",ja3_digest:"ja3_digest",rate_limit_api_id:"rate_limit_api_id"};function sr(i){return rr[i]??null}pe();T();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 dt=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 ar=new Set(["ip.src"]),ut=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=dt.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){let t=this.mapUnifiedFieldToCloudflare(e.field),n=e.key&&(e.field==="header"||e.field==="cookie")?`${t}["${Ye(e.key)}"]`:t,o=this.buildUnifiedExpression(n,e.operator,e.value);return e.negated&&(o=`not (${o})`),o}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"}[e]||e}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(ar.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 lr={"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.cookie":"cookie","http.user_agent":"user_agent","http.referer":"referer",ssl:"scheme","cf.edge.server_port":"port"},cr=new Set(["eq","ne","contains","starts_with","ends_with","matches","in","gt","ge","lt","le"]),dr=new Set(["and","or","not"]),Se=class extends Error{};function ur(i){let e=[],t=0,n=i.length;for(;t<n;){let o=i[t];if(/\s/.test(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 Se("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 Se(`Unexpected character '${o}' at position ${t}`);e.push({type:"WORD",value:i.slice(t,s)}),t=s}return e}var ni=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 Se("Unexpected end of expression");return this.pos++,e}expect(e,t){let n=this.next();if(n.type!==e||t!==void 0&&n.value!==t)throw new Se(`Expected ${t??e} but got '${n.value}'`);return n}isWord(e){let t=this.peek();return!!t&&t.type==="WORD"&&t.value===e}atEnd(){return this.pos>=this.tokens.length}};function oi(i){let e=[hn(i)];for(;i.isWord("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.isWord("and");)i.next(),e.push(yn(i));return e.length===1?e[0]:{type:"and",children:e}}function yn(i){if(i.isWord("not")){i.next(),i.expect("LPAREN");let e=oi(i);return i.expect("RPAREN"),{type:"not",child:e}}return pr(i)}function pr(i){let e=i.peek();if(e&&e.type==="LPAREN"){i.next();let t=oi(i);return i.expect("RPAREN"),t}return mr(i)}function fr(i){let e=i.expect("WORD");if(dr.has(e.value))throw new Se(`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 wn(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 Se(`Unexpected token '${e.value}' where a value was expected`)}function gr(i){if(i.peek()?.type==="LBRACE"){i.next();let e=[];for(;i.peek()?.type!=="RBRACE";)e.push(wn(i));return i.expect("RBRACE"),e}return wn(i)}function mr(i){let{field:e,key:t}=fr(i);if(i.isWord("exists"))return i.next(),{type:"exists",field:e,key:t};let n=i.next();if(n.type!=="WORD"||!cr.has(n.value))throw new Se(`Unsupported or unrecognized operator '${n.value}'`);let o=gr(i);return{type:"comparison",field:e,key:t,operator:n.value,value:o}}function hr(i){let e=new ni(ur(i)),t=oi(e);if(!e.atEnd())throw new Se("Unexpected trailing tokens");return t}function kt(i){return i.type==="comparison"||i.type==="exists"?!0:i.type==="not"?i.child.type==="comparison"||i.child.type==="exists":!1}function St(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=lr[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 yr(i){if(kt(i)){let e=St(i,0);return e?{conditions:[e],conditionLogic:"AND"}:null}if(i.type==="and"){let e=[];for(let t of i.children){if(!kt(t))return null;let n=St(t,0);if(!n)return null;e.push(n)}return{conditions:e,conditionLogic:"AND"}}if(i.type==="or"){let e=[];i.children.forEach((o,s)=>{if(kt(o)){let a=St(o,s);a&&e.push(a);return}if(o.type==="and")for(let a of o.children){if(!kt(a))return;let l=St(a,s);l&&e.push(l)}});let t=i.children.length;return new Set(e.map(o=>o.group)).size!==t?null:{conditions:e,conditionLogic:"OR"}}return null}function ri(i){if(!i||!i.trim())return null;try{let e=hr(i);return yr(e)}catch{return null}}function Rn(i){let e=[],t=ri(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:wr(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 Cn(i){let e=[],t=ut.fromUnifiedConditions(i.conditions,i.conditionLogic),n={id:i.id||crypto.randomUUID(),action:Rr(i.action.type),expression:t,description:i.description||i.name,enabled:i.enabled};return i.action.rateLimit&&(n.ratelimit={characteristics:i.action.rateLimit.characteristics||["ip.src"],period:Cr(i.action.rateLimit.window),requests_per_period:i.action.rateLimit.requests},i.action.rateLimit.mitigationTimeout?n.ratelimit.mitigation_timeout=i.action.rateLimit.mitigationTimeout:(n.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&&(n.ratelimit.counting_expression=i.action.rateLimit.countingExpression)),i.action.type==="redirect"&&i.action.redirect&&(n.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&&(n.action!=="block"?e.push(U.createUnsupportedFeatureWarning(`custom response on a '${n.action}' action`,"unified config","Cloudflare",i.id,"action.response")):i.action.response.content?n.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:n,warnings:e}}function vn(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 wr(i){return{block:"deny",challenge:"challenge",managed_challenge:"challenge",js_challenge:"challenge",log:"log",skip:"bypass",allow:"allow",rewrite:"bypass",redirect:"redirect"}[i]||"deny"}function Rr(i){return{log:"log",deny:"block",block:"block",challenge:"managed_challenge",bypass:"skip",rate_limit:"block",redirect:"redirect",allow:"allow"}[i]||"block"}function Cr(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 fe(i,e,t){return U.createWarning("complex_expressions",i,void 0,e,t)}var vr={ip:"ip",path:"path",domain:"host",method:"method",country:"country",scheme:"scheme",user_agent:"user_agent"},br={ip:"ip",path:"path",host:"domain",method:"method",country:"country",scheme:"scheme",user_agent:"user_agent"},Ir={header:"request_header",query:"query_parameter",cookie:"request_cookie"},Er={request_header:"header",query_parameter:"query",request_cookie:"cookie",response_header:"response_header",post_parameter:"post_parameter",signal:"signal"};function Ar(i){return vr[i]??i}function Tr(i){return br[i]??null}var $r={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 bn(i){return $r[i]??{operator:"eq",negated:!1}}function In(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(fe(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(fe(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 Pr(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(fe(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)Ut(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;Ut(t,s,l,e,i.id)}),{conditions:t,conditionLogic:i.group_operator==="any"&&o.length>1?"OR":"AND"};e.push(fe(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)Ut(t,s,0,e,i.id);for(let s of n)for(let a of s.conditions)Ut(t,a,0,e,i.id);return{conditions:t,conditionLogic:"AND"}}function Ut(i,e,t,n,o){if(e.type==="single"){let{operator:u,negated:p}=bn(e.operator);i.push({field:Ar(e.field),operator:u,value:e.value,...p?{negated:!0}:{},group:t});return}let s=Er[e.field],a=e.conditions.find(u=>u.field==="name"),l=e.conditions.find(u=>u.field==="value"||u.field==="value_string");if(!l){n.push(fe(o,`Multival condition on '${e.field}' has no value sub-condition doorman could extract (only a 'name' existence check) \u2014 skipped.`));return}let{operator:c,negated:d}=bn(l.operator);i.push({field:s,operator:c,value:l.value,...d?{negated:!0}:{},...a?{key:a.value}:{},group:t})}function _r(i,e){i.actions.length>1&&e.push(fe(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(fe(i.id,`Unrecognised Fastly action type "${String(t?.type)}" \u2014 mapped to "log".`)),{type:"log"}}}function En(i){let e=[],{conditions:t,conditionLogic:n}=Pr(i,e),o=_r(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 kr(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=Ir[a.field];if(l){if(a.key===void 0)return t.push(fe(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;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:In(a.operator,a.negated,t,e),value:String(a.value)}]}}let c=Tr(a.field);return c?{type:"single",field:c,operator:In(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 Sr(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(fe(t,'Unified action "bypass" has no direct Fastly equivalent \u2014 mapped to "allow".')),{type:"allow"};case"log":return e.push(fe(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(fe(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 Ur(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 Lr(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=Ur(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 An(i){let e=[];i.description&&i.description!==i.name&&e.push(fe(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}=kr(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:[Sr(i.action,e,i.id)],rate_limit:a?Lr(i.action.rateLimit,i.id,e):null},warnings:e}}function Tn(i,e){return i.map(t=>({ip:t,action:e}))}function $n(i){let e=[],t=[],n=[];for(let o of i)(o.hostname||o.notes)&&e.push(fe(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 Je(i){return i.replace(/\\/g,"\\\\").replace(/'/g,"\\'")}function Pn(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 xr=new Set(["region","city","port","scheme"]),Dr={host:"host",user_agent:"user-agent",referer:"referer"},pt=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(xr.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=Dr[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=Or[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, '${Je(a)}')`:`origin.ip == '${Je(a)}'`}),o=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['${Je(e)}']`,o=`has(${n})`;return t.operator==="exists"?o:t.operator==="not_exists"?`!${o}`:`(${o} && ${this.buildComparison(n,t,!1)})`}static buildCookieCondition(e){let t="request.headers['cookie']",n=`has(${t})`;if(e.operator==="exists")return n;if(e.operator==="not_exists")return`!${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('${Je(o)}')`,a=e.operator==="ne"||e.operator==="not_contains";return`(${n} && ${a?`!${s}`:s})`}static buildComparison(e,t,n){let{operator:o,value:s}=t;if(o==="exists"||o==="not_exists")throw new Error(`"${o}" is not meaningful for Cloud Armor's "${e}" \u2014 it is always present`);if(o==="in"||o==="not_in"){let c=(Array.isArray(s)?s:[s]).map(u=>this.formatLiteral(u,n)).join(", "),d=`${e} in [${c}]`;return o==="not_in"?`!(${d})`:d}let a=this.formatLiteral(s,n);switch(o){case"eq":return`${e} == ${a}`;case"ne":return`${e} != ${a}`;case"contains":return`${e}.contains(${a})`;case"not_contains":return`!${e}.contains(${a})`;case"starts_with":return`${e}.startsWith(${a})`;case"ends_with":return`${e}.endsWith(${a})`;case"matches":return`${e}.matches(${a})`;case"gt":return`${e} > ${a}`;case"ge":return`${e} >= ${a}`;case"lt":return`${e} < ${a}`;case"le":return`${e} <= ${a}`;default:throw new Error(`Cloud Armor CEL builder has no mapping for operator "${o}"`)}}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`'${Je(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(" || ")})`}},Or={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 Fr={"origin.region_code":"country","origin.asn":"asn","request.path":"path","request.method":"method","request.query":"query"},Nr={host:"host","user-agent":"user_agent",referer:"referer",cookie:"cookie"},xn={contains:"contains",startsWith:"starts_with",endsWith:"ends_with",matches:"matches"},Re=class extends Error{};function Vr(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 Re("Unterminated string literal");e.push({type:"STRING",value:Pn(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 Re(`Unexpected character '${s}' at position ${t}`)}return e}var ai=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 Re("Unexpected end of expression");return this.pos++,e}expect(e){let t=this.next();if(t.type!==e)throw new Re(`Expected ${e} but got '${t.value}'`);return t}isIdent(e){let t=this.peek();return!!t&&t.type==="IDENT"&&t.value===e}atEnd(){return this.pos>=this.tokens.length}};function Br(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 si(i){let e=Br(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 _n(i){let e=i.next();if(e.type==="STRING")return e.value;if(e.type==="NUMBER")return Number(e.value);throw new Re(`Unexpected token '${e.value}' where a literal was expected`)}function qr(i){if(i.isIdent("has")){i.next(),i.expect("LPAREN");let a=si(i);return i.expect("RPAREN"),{type:"has",...a}}if(i.isIdent("inIpRange")){i.next(),i.expect("LPAREN");let a=si(i);if(a.field!=="origin.ip")throw new Re(`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=si(i);if(i.peek()?.type==="DOT"){i.next();let a=i.expect("IDENT").value;if(!xn[a])throw new Re(`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.isIdent("in")){i.next(),i.expect("LBRACKET");let a=[];for(;i.peek()?.type!=="RBRACKET";)a.push(_n(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 Re(`Unexpected token '${t.value}' after field reference`);let s=_n(i);return{type:"comparison",...e,operator:o,value:s}}function kn(i){if(i.peek()?.type==="LPAREN"){i.next();let e=Dn(i);return i.expect("RPAREN"),e}return qr(i)}function Sn(i){return i.peek()?.type==="NOT"?(i.next(),{type:"not",child:kn(i)}):kn(i)}function Un(i){let e=[Sn(i)];for(;i.peek()?.type==="AND";)i.next(),e.push(Sn(i));return e.length===1?e[0]:{type:"and",children:e}}function Dn(i){let e=[Un(i)];for(;i.peek()?.type==="OR";)i.next(),e.push(Un(i));return e.length===1?e[0]:{type:"or",children:e}}function Wr(i){let e=new ai(Vr(i)),t=Dn(e);if(!e.atEnd())throw new Re("Unexpected trailing tokens");return t}function Mr(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(Mr)}function jr(i){return i.type==="inIpRange"?i.value:String(i.value)}function On(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 Lt(i){if(i.type==="has"||i.type==="comparison"||i.type==="methodCall"||i.type==="membership"||i.type==="inIpRange"||li(i)||On(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 Fn(i,e){if(i==="origin.ip")return{unifiedField:"ip"};let t=Fr[i];if(t)return{unifiedField:t};if(i==="request.headers"&&e!==void 0){let n=Nr[e];return n?{unifiedField:n}:{unifiedField:"header",unifiedKey:e}}return null}function xt(i,e){let t=i.type==="not",n=i.type==="not"?i.child:i;if(li(n)){let o=n.children.map(s=>jr(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"&&On(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:Ln(a,s,o.field,o.key,e)}if(n.type==="comparison"||n.type==="methodCall"||n.type==="membership")return Ln(n,t,n.field,n.key,e);if(n.type==="has"){let o=Fn(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 Ln(i,e,t,n,o){let s=Fn(t,n);if(!s)return null;let a,l;if(i.type==="comparison")a=e?Gr(i.operator):i.operator,l=i.value;else if(i.type==="methodCall"){let c=xn[i.method];if(!c||(a=e?`not_${c}`:c,e&&a!=="not_contains"))return null;l=i.value}else a=e?"not_in":"in",l=i.values;return{field:s.unifiedField,operator:a,value:l,...s.unifiedKey!==void 0?{key:s.unifiedKey}:{},...o!==void 0?{group:o}:{}}}function Gr(i){if(i==="eq")return"ne";if(i==="ne")return"eq";throw new Re(`No negated form for comparison operator '${i}'`)}function Kr(i){if(Lt(i)){let e=xt(i,void 0);return e?{conditions:[e],conditionLogic:"AND"}:null}if(i.type==="and"){let e=[];for(let t of i.children){if(!Lt(t))return null;let n=xt(t,void 0);if(!n)return null;e.push(n)}return{conditions:e,conditionLogic:"AND"}}if(i.type==="or"){let e=[];i.children.forEach((o,s)=>{if(Lt(o)){let a=xt(o,s);a&&e.push(a);return}if(o.type==="and")for(let a of o.children){if(!Lt(a))return;let l=xt(a,s);l&&e.push(l)}});let t=i.children.length;return new Set(e.map(o=>o.group)).size!==t?null:{conditions:e,conditionLogic:"OR"}}return null}function Dt(i){if(!i||!i.trim())return null;try{let e=Wr(i);return Kr(e)}catch{return null}}function Nn(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=pt.fromUnifiedConditions(i.conditions,i.conditionLogic),{action:n,actionWarning:o}=Zr(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=Yr(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 Vn(i){let e=[],t=Dt(i.match.expr.expression),n=[],o;t?(n=t.conditions,o=t.conditionLogic):e.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 s={type:Hr(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:n,...o==="OR"?{conditionLogic:o}:{},action:s,priority:i.priority},warnings:e}}function Bn(i){if(i.action!=="allow"&&i.action!=="deny(403)"||i.preview)return!1;let e=Dt(i.match.expr.expression);if(!e||e.conditions.length!==1)return!1;let t=e.conditions[0];return t.field==="ip"&&(t.operator==="eq"||t.operator==="in")&&!t.key}function qn(i){let t=Dt(i.match.expr.expression).conditions[0].value;return{id:String(i.priority),ip:Array.isArray(t)?String(t[0]):String(t),...i.description?{notes:i.description}:{},action:i.action==="allow"?"allow":"deny"}}function Wn(i,e){if(!Pe.safeParse(i.ip).success)throw new Error(`Invalid IP address or CIDR range: ${i.ip}`);let t=pt.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 Hr(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 Zr(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 Yr(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 A=class{static vercelToUnified=pn;static unifiedToVercel=fn;static vercelIPToUnified=gn;static cloudflareToUnified=Rn;static unifiedToCloudflare=Cn;static unifiedIPToCloudflare=vn;static fastlyToUnified=En;static unifiedToFastly=An;static fastlyListEntriesToUnified=Tn;static unifiedIPsToFastlyEntries=$n;static gcpToUnified=Vn;static unifiedToGcp=Nn;static gcpToUnifiedIP=qn;static unifiedIPToGcp=Wn;static gcpLooksLikeIpRule=Bn};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 ge(i){let{id:e,...t}=i;return t}function ft(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 Ot(i){return i.some(e=>e.priority!==void 0)}var Qe=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=A.vercelToUnified(a);return l.warnings.length>0&&l.warnings.forEach(c=>{let{TranslationWarningSystem:d}=(pe(),Ee(Be)),u=d.formatWarning(c);r.warn(`Rule ${a.name}:
|
|
24
|
+
`),u.cancelled&&process.exit(0),e(o))};t.on("keypress",a)})}T();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 Pt=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 z){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 z){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 z.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 z){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 z){let o=this.detectionField(n);o&&process.env[o.envVar]&&t.add(n)}return Array.from(t)}};var ii=O(require("chalk"));T();$e();var _t=i=>({$schema:xi,firewallEnabled:!0,rules:[],ips:[],...i});T();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 y=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(y),y.status===429){let C=this.calculateRateLimitWait(f,y);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(C/1e3)}s.`);r.warn(`Rate limit exceeded for ${this.providerName}. Waiting ${C}ms before retry (attempt ${f+1}/${s+1})...`),await this.delay(C);continue}if(!y.ok)throw await this.handleErrorResponse(y);let R;try{let C=await y.text();C.trim()?R=JSON.parse(C):R={}}catch(C){throw r.error(`Failed to parse response from ${n}:`,C),new Error(`Invalid JSON response from ${this.providerName} API: ${C instanceof Error?C.message:String(C)}`)}return this.isLargeResponse(R)&&(r.debug(`Large response detected from ${n}, validating structure...`),this.validateLargeResponse(R)),R}catch(y){if(c=y,u&&(clearTimeout(u),u=null),this.isNonRetryableError(y)||f===s)throw y;let R=a*Math.pow(2,f),C=Math.random()*.1*R,x=Math.min(R+C,3e4);r.debug(`Request failed (attempt ${f+1}/${s+1}). Retrying in ${Math.round(x)}ms...`),r.debug(`Error details: ${y instanceof Error?y.message:String(y)}`),await this.delay(x)}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(y=>!p.includes(y));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(ii.default.bold("No firewall configuration found.")),!o)return r.debug("Skipping create-configuration prompt for read-only fetch."),this.emptyConfigPlaceholder();if(await v("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: ${ii.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}=_t();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 se=O(require("chalk"));T();T();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}}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.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=ir(l.op);t.push({field:or(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=tr(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 tr(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=sr(l.field);if(!c){t.push(U.createUnsupportedFeatureWarning(`condition field '${l.field}'`,"unified config","Vercel",e,l.field));continue}a.push({op:nr(l.operator),neg:l.negated,type:c,key:l.key,value:l.value})}a.length>0&&o.push({conditions:a})}return o}function ir(i){return{eq:"eq",pre:"starts_with",suf:"ends_with",inc:"in",sub:"contains",re:"matches",ex:"exists",nex:"not_exists"}[i]||"eq"}function nr(i){return{eq:"eq",starts_with:"pre",ends_with:"suf",in:"inc",contains:"sub",matches:"re",exists:"ex",not_exists:"nex"}[i]||"eq"}function or(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"}[i]||i}var rr={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",region:"region",protocol:"protocol",environment:"environment",geo_continent:"geo_continent",geo_country_region:"geo_country_region",ja4_digest:"ja4_digest",ja3_digest:"ja3_digest",rate_limit_api_id:"rate_limit_api_id"};function sr(i){return rr[i]??null}pe();T();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 dt=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 ar=new Set(["ip.src"]),ut=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=dt.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){let t=this.mapUnifiedFieldToCloudflare(e.field),n=e.key&&(e.field==="header"||e.field==="cookie")?`${t}["${Ye(e.key)}"]`:t,o=this.buildUnifiedExpression(n,e.operator,e.value);return e.negated&&(o=`not (${o})`),o}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"}[e]||e}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(ar.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 lr={"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.cookie":"cookie","http.user_agent":"user_agent","http.referer":"referer",ssl:"scheme","cf.edge.server_port":"port"},cr=new Set(["eq","ne","contains","starts_with","ends_with","matches","in","gt","ge","lt","le"]),dr=new Set(["and","or","not"]),Se=class extends Error{};function ur(i){let e=[],t=0,n=i.length;for(;t<n;){let o=i[t];if(/\s/.test(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 Se("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 Se(`Unexpected character '${o}' at position ${t}`);e.push({type:"WORD",value:i.slice(t,s)}),t=s}return e}var ni=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 Se("Unexpected end of expression");return this.pos++,e}expect(e,t){let n=this.next();if(n.type!==e||t!==void 0&&n.value!==t)throw new Se(`Expected ${t??e} but got '${n.value}'`);return n}isWord(e){let t=this.peek();return!!t&&t.type==="WORD"&&t.value===e}atEnd(){return this.pos>=this.tokens.length}};function oi(i){let e=[hn(i)];for(;i.isWord("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.isWord("and");)i.next(),e.push(yn(i));return e.length===1?e[0]:{type:"and",children:e}}function yn(i){if(i.isWord("not")){i.next(),i.expect("LPAREN");let e=oi(i);return i.expect("RPAREN"),{type:"not",child:e}}return pr(i)}function pr(i){let e=i.peek();if(e&&e.type==="LPAREN"){i.next();let t=oi(i);return i.expect("RPAREN"),t}return mr(i)}function fr(i){let e=i.expect("WORD");if(dr.has(e.value))throw new Se(`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 wn(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 Se(`Unexpected token '${e.value}' where a value was expected`)}function gr(i){if(i.peek()?.type==="LBRACE"){i.next();let e=[];for(;i.peek()?.type!=="RBRACE";)e.push(wn(i));return i.expect("RBRACE"),e}return wn(i)}function mr(i){let{field:e,key:t}=fr(i);if(i.isWord("exists"))return i.next(),{type:"exists",field:e,key:t};let n=i.next();if(n.type!=="WORD"||!cr.has(n.value))throw new Se(`Unsupported or unrecognized operator '${n.value}'`);let o=gr(i);return{type:"comparison",field:e,key:t,operator:n.value,value:o}}function hr(i){let e=new ni(ur(i)),t=oi(e);if(!e.atEnd())throw new Se("Unexpected trailing tokens");return t}function kt(i){return i.type==="comparison"||i.type==="exists"?!0:i.type==="not"?i.child.type==="comparison"||i.child.type==="exists":!1}function St(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=lr[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 yr(i){if(kt(i)){let e=St(i,0);return e?{conditions:[e],conditionLogic:"AND"}:null}if(i.type==="and"){let e=[];for(let t of i.children){if(!kt(t))return null;let n=St(t,0);if(!n)return null;e.push(n)}return{conditions:e,conditionLogic:"AND"}}if(i.type==="or"){let e=[];i.children.forEach((o,s)=>{if(kt(o)){let a=St(o,s);a&&e.push(a);return}if(o.type==="and")for(let a of o.children){if(!kt(a))return;let l=St(a,s);l&&e.push(l)}});let t=i.children.length;return new Set(e.map(o=>o.group)).size!==t?null:{conditions:e,conditionLogic:"OR"}}return null}function ri(i){if(!i||!i.trim())return null;try{let e=hr(i);return yr(e)}catch{return null}}function Rn(i){let e=[],t=ri(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:wr(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 Cn(i){let e=[],t=ut.fromUnifiedConditions(i.conditions,i.conditionLogic),n={id:i.id||crypto.randomUUID(),action:Rr(i.action.type),expression:t,description:i.description||i.name,enabled:i.enabled};return i.action.rateLimit&&(n.ratelimit={characteristics:i.action.rateLimit.characteristics||["ip.src"],period:Cr(i.action.rateLimit.window),requests_per_period:i.action.rateLimit.requests},i.action.rateLimit.mitigationTimeout?n.ratelimit.mitigation_timeout=i.action.rateLimit.mitigationTimeout:(n.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&&(n.ratelimit.counting_expression=i.action.rateLimit.countingExpression)),i.action.type==="redirect"&&i.action.redirect&&(n.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&&(n.action!=="block"?e.push(U.createUnsupportedFeatureWarning(`custom response on a '${n.action}' action`,"unified config","Cloudflare",i.id,"action.response")):i.action.response.content?n.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:n,warnings:e}}function vn(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 wr(i){return{block:"deny",challenge:"challenge",managed_challenge:"challenge",js_challenge:"challenge",log:"log",skip:"bypass",allow:"allow",rewrite:"bypass",redirect:"redirect"}[i]||"deny"}function Rr(i){return{log:"log",deny:"block",block:"block",challenge:"managed_challenge",bypass:"skip",rate_limit:"block",redirect:"redirect",allow:"allow"}[i]||"block"}function Cr(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 fe(i,e,t){return U.createWarning("complex_expressions",i,void 0,e,t)}var vr={ip:"ip",path:"path",domain:"host",method:"method",country:"country",scheme:"scheme",user_agent:"user_agent"},br={ip:"ip",path:"path",host:"domain",method:"method",country:"country",scheme:"scheme",user_agent:"user_agent"},Ir={header:"request_header",query:"query_parameter",cookie:"request_cookie"},Er={request_header:"header",query_parameter:"query",request_cookie:"cookie",response_header:"response_header",post_parameter:"post_parameter",signal:"signal"};function Ar(i){return vr[i]??i}function Tr(i){return br[i]??null}var $r={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 bn(i){return $r[i]??{operator:"eq",negated:!1}}function In(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(fe(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(fe(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 Pr(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(fe(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)Ut(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;Ut(t,s,l,e,i.id)}),{conditions:t,conditionLogic:i.group_operator==="any"&&o.length>1?"OR":"AND"};e.push(fe(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)Ut(t,s,0,e,i.id);for(let s of n)for(let a of s.conditions)Ut(t,a,0,e,i.id);return{conditions:t,conditionLogic:"AND"}}function Ut(i,e,t,n,o){if(e.type==="single"){let{operator:u,negated:p}=bn(e.operator);i.push({field:Ar(e.field),operator:u,value:e.value,...p?{negated:!0}:{},group:t});return}let s=Er[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(fe(o,`Multival condition on '${e.field}' has no value or name sub-condition doorman could extract \u2014 skipped.`));return}let{operator:c,negated:d}=bn(l.operator);i.push({field:s,operator:c,value:l.value,...d?{negated:!0}:{},...a?{key:a.value}:{},group:t})}function _r(i,e){i.actions.length>1&&e.push(fe(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(fe(i.id,`Unrecognised Fastly action type "${String(t?.type)}" \u2014 mapped to "log".`)),{type:"log"}}}function En(i){let e=[],{conditions:t,conditionLogic:n}=Pr(i,e),o=_r(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 kr(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=Ir[a.field];if(l){if(a.key===void 0)return t.push(fe(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:In(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=Tr(a.field);return c?{type:"single",field:c,operator:In(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 Sr(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(fe(t,'Unified action "bypass" has no direct Fastly equivalent \u2014 mapped to "allow".')),{type:"allow"};case"log":return e.push(fe(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(fe(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 Ur(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 Lr(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=Ur(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 An(i){let e=[];i.description&&i.description!==i.name&&e.push(fe(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}=kr(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:[Sr(i.action,e,i.id)],rate_limit:a?Lr(i.action.rateLimit,i.id,e):null},warnings:e}}function Tn(i,e){return i.map(t=>({ip:t,action:e}))}function $n(i){let e=[],t=[],n=[];for(let o of i)(o.hostname||o.notes)&&e.push(fe(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 Je(i){return i.replace(/\\/g,"\\\\").replace(/'/g,"\\'")}function Pn(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 xr=new Set(["region","city","port","scheme"]),Dr={host:"host",user_agent:"user-agent",referer:"referer"},pt=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(xr.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=Dr[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=Or[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, '${Je(a)}')`:`origin.ip == '${Je(a)}'`}),o=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['${Je(e)}']`,o=`has(${n})`;return t.operator==="exists"?o:t.operator==="not_exists"?`!${o}`:`(${o} && ${this.buildComparison(n,t,!1)})`}static buildCookieCondition(e){let t="request.headers['cookie']",n=`has(${t})`;if(e.operator==="exists")return n;if(e.operator==="not_exists")return`!${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('${Je(o)}')`,a=e.operator==="ne"||e.operator==="not_contains";return`(${n} && ${a?`!${s}`:s})`}static buildComparison(e,t,n){let{operator:o,value:s}=t;if(o==="exists"||o==="not_exists")throw new Error(`"${o}" is not meaningful for Cloud Armor's "${e}" \u2014 it is always present`);if(o==="in"||o==="not_in"){let c=(Array.isArray(s)?s:[s]).map(u=>this.formatLiteral(u,n)).join(", "),d=`${e} in [${c}]`;return o==="not_in"?`!(${d})`:d}let a=this.formatLiteral(s,n);switch(o){case"eq":return`${e} == ${a}`;case"ne":return`${e} != ${a}`;case"contains":return`${e}.contains(${a})`;case"not_contains":return`!${e}.contains(${a})`;case"starts_with":return`${e}.startsWith(${a})`;case"ends_with":return`${e}.endsWith(${a})`;case"matches":return`${e}.matches(${a})`;case"gt":return`${e} > ${a}`;case"ge":return`${e} >= ${a}`;case"lt":return`${e} < ${a}`;case"le":return`${e} <= ${a}`;default:throw new Error(`Cloud Armor CEL builder has no mapping for operator "${o}"`)}}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`'${Je(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(" || ")})`}},Or={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 Fr={"origin.region_code":"country","origin.asn":"asn","request.path":"path","request.method":"method","request.query":"query"},Nr={host:"host","user-agent":"user_agent",referer:"referer",cookie:"cookie"},xn={contains:"contains",startsWith:"starts_with",endsWith:"ends_with",matches:"matches"},Re=class extends Error{};function Vr(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 Re("Unterminated string literal");e.push({type:"STRING",value:Pn(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 Re(`Unexpected character '${s}' at position ${t}`)}return e}var ai=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 Re("Unexpected end of expression");return this.pos++,e}expect(e){let t=this.next();if(t.type!==e)throw new Re(`Expected ${e} but got '${t.value}'`);return t}isIdent(e){let t=this.peek();return!!t&&t.type==="IDENT"&&t.value===e}atEnd(){return this.pos>=this.tokens.length}};function Br(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 si(i){let e=Br(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 _n(i){let e=i.next();if(e.type==="STRING")return e.value;if(e.type==="NUMBER")return Number(e.value);throw new Re(`Unexpected token '${e.value}' where a literal was expected`)}function qr(i){if(i.isIdent("has")){i.next(),i.expect("LPAREN");let a=si(i);return i.expect("RPAREN"),{type:"has",...a}}if(i.isIdent("inIpRange")){i.next(),i.expect("LPAREN");let a=si(i);if(a.field!=="origin.ip")throw new Re(`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=si(i);if(i.peek()?.type==="DOT"){i.next();let a=i.expect("IDENT").value;if(!xn[a])throw new Re(`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.isIdent("in")){i.next(),i.expect("LBRACKET");let a=[];for(;i.peek()?.type!=="RBRACKET";)a.push(_n(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 Re(`Unexpected token '${t.value}' after field reference`);let s=_n(i);return{type:"comparison",...e,operator:o,value:s}}function kn(i){if(i.peek()?.type==="LPAREN"){i.next();let e=Dn(i);return i.expect("RPAREN"),e}return qr(i)}function Sn(i){return i.peek()?.type==="NOT"?(i.next(),{type:"not",child:kn(i)}):kn(i)}function Un(i){let e=[Sn(i)];for(;i.peek()?.type==="AND";)i.next(),e.push(Sn(i));return e.length===1?e[0]:{type:"and",children:e}}function Dn(i){let e=[Un(i)];for(;i.peek()?.type==="OR";)i.next(),e.push(Un(i));return e.length===1?e[0]:{type:"or",children:e}}function Wr(i){let e=new ai(Vr(i)),t=Dn(e);if(!e.atEnd())throw new Re("Unexpected trailing tokens");return t}function Mr(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(Mr)}function jr(i){return i.type==="inIpRange"?i.value:String(i.value)}function On(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 Lt(i){if(i.type==="has"||i.type==="comparison"||i.type==="methodCall"||i.type==="membership"||i.type==="inIpRange"||li(i)||On(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 Fn(i,e){if(i==="origin.ip")return{unifiedField:"ip"};let t=Fr[i];if(t)return{unifiedField:t};if(i==="request.headers"&&e!==void 0){let n=Nr[e];return n?{unifiedField:n}:{unifiedField:"header",unifiedKey:e}}return null}function xt(i,e){let t=i.type==="not",n=i.type==="not"?i.child:i;if(li(n)){let o=n.children.map(s=>jr(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"&&On(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:Ln(a,s,o.field,o.key,e)}if(n.type==="comparison"||n.type==="methodCall"||n.type==="membership")return Ln(n,t,n.field,n.key,e);if(n.type==="has"){let o=Fn(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 Ln(i,e,t,n,o){let s=Fn(t,n);if(!s)return null;let a,l;if(i.type==="comparison")a=e?Gr(i.operator):i.operator,l=i.value;else if(i.type==="methodCall"){let c=xn[i.method];if(!c||(a=e?`not_${c}`:c,e&&a!=="not_contains"))return null;l=i.value}else a=e?"not_in":"in",l=i.values;return{field:s.unifiedField,operator:a,value:l,...s.unifiedKey!==void 0?{key:s.unifiedKey}:{},...o!==void 0?{group:o}:{}}}function Gr(i){if(i==="eq")return"ne";if(i==="ne")return"eq";throw new Re(`No negated form for comparison operator '${i}'`)}function Kr(i){if(Lt(i)){let e=xt(i,void 0);return e?{conditions:[e],conditionLogic:"AND"}:null}if(i.type==="and"){let e=[];for(let t of i.children){if(!Lt(t))return null;let n=xt(t,void 0);if(!n)return null;e.push(n)}return{conditions:e,conditionLogic:"AND"}}if(i.type==="or"){let e=[];i.children.forEach((o,s)=>{if(Lt(o)){let a=xt(o,s);a&&e.push(a);return}if(o.type==="and")for(let a of o.children){if(!Lt(a))return;let l=xt(a,s);l&&e.push(l)}});let t=i.children.length;return new Set(e.map(o=>o.group)).size!==t?null:{conditions:e,conditionLogic:"OR"}}return null}function Dt(i){if(!i||!i.trim())return null;try{let e=Wr(i);return Kr(e)}catch{return null}}function Nn(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=pt.fromUnifiedConditions(i.conditions,i.conditionLogic),{action:n,actionWarning:o}=Zr(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=Yr(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 Vn(i){let e=[],t=Dt(i.match.expr.expression),n=[],o;t?(n=t.conditions,o=t.conditionLogic):e.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 s={type:Hr(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:n,...o==="OR"?{conditionLogic:o}:{},action:s,priority:i.priority},warnings:e}}function Bn(i){if(i.action!=="allow"&&i.action!=="deny(403)"||i.preview)return!1;let e=Dt(i.match.expr.expression);if(!e||e.conditions.length!==1)return!1;let t=e.conditions[0];return t.field==="ip"&&(t.operator==="eq"||t.operator==="in")&&!t.key}function qn(i){let t=Dt(i.match.expr.expression).conditions[0].value;return{id:String(i.priority),ip:Array.isArray(t)?String(t[0]):String(t),...i.description?{notes:i.description}:{},action:i.action==="allow"?"allow":"deny"}}function Wn(i,e){if(!Pe.safeParse(i.ip).success)throw new Error(`Invalid IP address or CIDR range: ${i.ip}`);let t=pt.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 Hr(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 Zr(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 Yr(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 A=class{static vercelToUnified=pn;static unifiedToVercel=fn;static vercelIPToUnified=gn;static cloudflareToUnified=Rn;static unifiedToCloudflare=Cn;static unifiedIPToCloudflare=vn;static fastlyToUnified=En;static unifiedToFastly=An;static fastlyListEntriesToUnified=Tn;static unifiedIPsToFastlyEntries=$n;static gcpToUnified=Vn;static unifiedToGcp=Nn;static gcpToUnifiedIP=qn;static unifiedIPToGcp=Wn;static gcpLooksLikeIpRule=Bn};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 ge(i){let{id:e,...t}=i;return t}function ft(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 Ot(i){return i.some(e=>e.priority!==void 0)}var Qe=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=A.vercelToUnified(a);return l.warnings.length>0&&l.warnings.forEach(c=>{let{TranslationWarningSystem:d}=(pe(),Ee(Be)),u=d.formatWarning(c);r.warn(`Rule ${a.name}:
|
|
25
25
|
${u}`)}),l.result}),s=n.ips.map(a=>A.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}=(mt(),Ee(gt)),a=await s.performDryRunValidation(t,"sync rules",async g=>await this.getChanges(g,{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||[],y=l.ipsToUpdate||[],R=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: ${y.length}, Delete: ${R.length}`),{success:!0,rulesAdded:0,rulesUpdated:0,rulesDeleted:0,ipsAdded:0,ipsUpdated:0,ipsDeleted:0,version:p,warnings:a.warnings};let C=s.assessOperationRisk(l,t);if(!await s.confirmDestructiveOperation({operation:"sync rules",target:`Vercel project ${this.client.projectId}`,changes:l,riskLevel:C,skipConfirmation:n.force||!1,dryRun:!1,allowDeletions:n.allowDeletions||!1}))throw new Error("Operation cancelled by user");let N=c.map(g=>A.unifiedToVercel(g).result),V=d.map(g=>A.unifiedToVercel(g).result),ce=u.map(g=>A.unifiedToVercel(g).result),de=f.map(g=>({id:g.id||"",ip:g.ip,hostname:g.hostname||"",action:g.action,notes:g.notes})),me=y.map(g=>({id:g.id||"",ip:g.ip,hostname:g.hostname||"",action:g.action,notes:g.notes})),S=R.map(g=>({id:g.id||"",ip:g.ip,hostname:g.hostname||"",action:g.action,notes:g.notes})),ue=[],he=[],ye=[],Te=[],Ie=[],P=[],m=[],$=(g,I)=>`${g}: ${I instanceof Error?I.message:String(I)}`,D=[];Ot(t.rules)&&t.rules.length>c.length&&D.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 we=[];for(let g of ce)try{r.debug(`Deleting custom rule: ${g.id}`),await this.client.deleteFirewallRule(g),ye.push(g),r.debug(`Custom rule deleted: ${g.id}`)}catch(I){r.error(`Failed to delete custom rule ${g.id}:`,I),m.push($(`Failed to delete custom rule ${g.id}`,I))}for(let g of S)try{r.debug(`Deleting IP blocking rule: ${g.id}`),await this.client.deleteIPBlockingRule(g),P.push(g),r.debug(`IP blocking rule deleted: ${g.id}`)}catch(I){r.error(`Failed to delete IP blocking rule ${g.id}:`,I),m.push($(`Failed to delete IP blocking rule ${g.id}`,I))}for(let g of N)try{r.debug(`Adding new custom rule: ${g.name}`);let I=await this.client.createFirewallRule(g);ue.push(I),I.id&&I.id!==g.id&&we.push({oldId:g.id||void 0,newId:I.id,name:g.name}),r.debug(`New custom rule added: ${I.id}`)}catch(I){r.error(`Failed to add custom rule "${g.name}":`,I),m.push($(`Failed to add custom rule "${g.name}"`,I))}for(let g of de)try{r.debug(`Adding new IP blocking rule: ${g.ip}`);let I=await this.client.createIPBlockingRule(g);Te.push(I),r.debug(`New IP blocking rule added: (hostname): ${I.hostname} (ip): ${I.ip}`)}catch(I){r.error(`Failed to add IP blocking rule ${g.ip}:`,I),m.push($(`Failed to add IP blocking rule ${g.ip}`,I))}for(let g of V)try{r.debug(`Updating custom rule: ${g.id}`);let I=await this.client.updateFirewallRule(g);he.push(I),r.debug(`Custom rule updated: ${I.id}`)}catch(I){r.error(`Failed to update custom rule ${g.id}:`,I),m.push($(`Failed to update custom rule ${g.id}`,I))}for(let g of me)try{r.debug(`Updating IP blocking rule: ${g.id}`);let I=await this.client.updateIPBlockingRule(g);Ie.push(I),r.debug(`IP blocking rule updated: ${I.id}`)}catch(I){r.error(`Failed to update IP blocking rule ${g.id}:`,I),m.push($(`Failed to update IP blocking rule ${g.id}`,I))}r.debug(`${se.default.underline("Custom Rules:")} ${se.default.green("Added:")} ${se.default.green(ue.length)}, ${se.default.cyan("Updated:")} ${se.default.cyan(he.length)}, ${se.default.red("Deleted:")} ${se.default.red(ye.length)}`),r.debug(`${se.default.underline("IP Rules:")} ${se.default.green("Added:")} ${se.default.green(Te.length)}, ${se.default.cyan("Updated:")} ${se.default.cyan(Ie.length)}, ${se.default.red("Deleted:")} ${se.default.red(P.length)}`);let st=p,at,Ne=[...D];try{let g=await this.client.fetchFirewallConfig();st=g.version,at=g.updatedAt;let I=new Set(g.rules.map(M=>M.id)),_i=new Set(g.ips.map(M=>M.id));for(let M of[...ue,...he])M.id&&!I.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 ye)M.id&&I.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[...Te,...Ie])M.id&&!_i.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 P)M.id&&_i.has(M.id)&&Ne.push(`IP rule "${M.ip}" (${M.id}) was deleted but still appears in the post-sync remote config.`)}catch(g){r.error("Failed to fetch updated firewall configuration version after sync:",g),m.push($("Failed to fetch updated configuration version after sync",g))}return{success:m.length===0,rulesAdded:ue.length,rulesUpdated:he.length,rulesDeleted:ye.length,ipsAdded:Te.length,ipsUpdated:Ie.length,ipsDeleted:P.length,version:st,updatedAt:at,idRemappings:we.length>0?we:void 0,errors:m.length>0?m: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}`);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(R=>{let C=A.vercelToUnified(R);return C.warnings.length>0&&C.warnings.forEach(x=>{let{TranslationWarningSystem:N}=(pe(),Ee(Be)),V=N.formatWarning(x);r.warn(`Rule ${R.name}:
|
|
26
26
|
${V}`)}),C.result}),{toAdd:l,toUpdate:c,toDelete:d}=this.diffRules(ft(o.data.rules),a),u=s.ips.map(R=>A.vercelIPToUnified(R)),{ipsToAdd:p,ipsToUpdate:f,ipsToDelete:y}=this.diffIPRules(o.data.ips||[],u);return{version:s.version,rulesToAdd:l,rulesToUpdate:c,rulesToDelete:d,ipsToAdd:p,ipsToUpdate:f,ipsToDelete:y,hasChanges:l.length>0||c.length>0||d.length>0||p.length>0||f.length>0||y.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(ge(l),ge(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(ge(l),ge(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}}};T();var ht=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 Qe(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 Qe(s)}static create(e,t,n){return this.fromConfig({projectId:e,teamId:t,token:n})}};var zn=require("crypto");T();var We=O(require("chalk")),h=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(We.default.red.bold(`[${this.code}] ${this.message}`)),e.push(""),this.suggestion&&(e.push(We.default.yellow.bold("Suggestion:")),e.push(` ${this.suggestion}`),e.push("")),this.details&&Object.keys(this.details).length>0&&(e.push(We.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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var Lt=Object.defineProperty;var Yn=Object.getOwnPropertyDescriptor;var Jn=Object.getOwnPropertyNames;var Qn=Object.prototype.hasOwnProperty;var yt=(i,e)=>()=>(i&&(e=i(i=0)),e);var J=(i,e)=>{for(var t in e)Lt(i,t,{get:e[t],enumerable:!0})},Xn=(i,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of Jn(e))!Qn.call(i,o)&&o!==t&&Lt(i,o,{get:()=>e[o],enumerable:!(n=Yn(e,o))||n.enumerable});return i};var Ce=i=>Xn(Lt({},"__esModule",{value:!0}),i);import{createConsola as eo}from"consola";var r,T=yt(()=>{"use strict";r=eo({formatOptions:{colors:!0,compact:!0,date:!1}})});import po from"consola";var v,Ee=yt(()=>{"use strict";v=async(i,e)=>{if(!process.stdin.isTTY)throw new Error(`Cannot prompt for "${i}" \u2014 no interactive terminal available. Re-run with the appropriate non-interactive flag for this command (e.g. --interactive=false, --ci, --force).`);let t=await po.prompt(i,e);return t!=null&&t.toString()==="Symbol(clack:cancel)"&&process.exit(0),(!e?.type||e.type==="text")&&t===void 0?"":t}});var Oe={};J(Oe,{TranslationWarningSystem:()=>U});var U,de=yt(()=>{"use strict";U=class i{static DOCS_BASE_URL="https://docs.doorman.griffen.codes";static WARNING_CONFIGS={managed_rules:{category:"feature_unsupported",severity:"critical",explanation:"Managed rules are provider-specific security rules that cannot be directly translated between platforms.",suggestion:"Replace with custom rules using equivalent conditions and actions.",alternativeApproach:"Create custom rules that match the same traffic patterns using standard conditions like IP, country, user agent, or request headers.",impact:"Security coverage may be reduced until equivalent custom rules are implemented.",docsUrl:`${i.DOCS_BASE_URL}/migration/managed-rules`},bot_management:{category:"feature_unsupported",severity:"critical",explanation:"Bot management features use provider-specific detection algorithms that cannot be translated.",suggestion:"Configure bot management separately in the target provider.",alternativeApproach:"Use user agent filtering, rate limiting, and challenge actions to implement basic bot protection.",impact:"Bot protection effectiveness may vary between providers.",docsUrl:`${i.DOCS_BASE_URL}/migration/bot-management`},complex_expressions:{category:"lossy_conversion",severity:"warning",explanation:"Complex expressions may not translate perfectly due to differences in syntax and supported operators between providers.",suggestion:"Review translated expressions and test thoroughly before deploying.",alternativeApproach:"Break complex expressions into simpler, more portable conditions.",impact:"Rule behavior may differ slightly from the original configuration.",docsUrl:`${i.DOCS_BASE_URL}/migration/expressions`},geo_blocking:{category:"lossy_conversion",severity:"warning",explanation:"Geographic blocking may use different country codes or geographic databases between providers.",suggestion:"Verify country codes and geographic targeting after translation.",alternativeApproach:"Use ISO country codes when possible for better compatibility.",impact:"Some geographic regions may be blocked or allowed differently than intended.",docsUrl:`${i.DOCS_BASE_URL}/migration/geo-blocking`},rate_limiting_precision:{category:"lossy_conversion",severity:"info",explanation:"Rate limiting configurations may be adjusted to match the target provider's supported time windows and request thresholds.",suggestion:"Review rate limiting settings and adjust if necessary.",alternativeApproach:"Use the closest supported time window and request threshold values.",impact:"Rate limiting may be slightly more or less restrictive than the original configuration.",docsUrl:`${i.DOCS_BASE_URL}/migration/rate-limiting`},regex_patterns:{category:"syntax_limitation",severity:"warning",explanation:"Regular expression patterns may have different syntax requirements or feature support between providers.",suggestion:"Test regex patterns in the target provider and adjust syntax if needed.",alternativeApproach:"Use simpler string matching operations (contains, starts_with, ends_with) when possible.",impact:"Pattern matching may behave differently or fail to match intended traffic.",docsUrl:`${i.DOCS_BASE_URL}/migration/regex-patterns`},header_manipulation:{category:"syntax_limitation",severity:"warning",explanation:"Header manipulation capabilities vary between providers in terms of supported headers and modification types.",suggestion:"Verify header modification support in the target provider.",alternativeApproach:"Use redirect rules or custom error pages to achieve similar functionality.",impact:"Header modifications may not work as expected or may be ignored.",docsUrl:`${i.DOCS_BASE_URL}/migration/headers`},large_ip_lists:{category:"performance_impact",severity:"info",explanation:"Large IP lists may impact rule evaluation performance differently between providers.",suggestion:"Consider using provider-specific IP list features for better performance.",alternativeApproach:"Break large IP lists into smaller, more targeted rules or use CIDR notation to reduce list size.",impact:"Rule evaluation may be slower with large IP lists, potentially affecting site performance.",docsUrl:`${i.DOCS_BASE_URL}/migration/ip-lists`},many_conditions:{category:"performance_impact",severity:"info",explanation:"Rules with many conditions may have different performance characteristics between providers.",suggestion:"Monitor rule performance and consider optimizing complex rules.",alternativeApproach:"Split complex rules into multiple simpler rules or use more efficient condition types.",impact:"Complex rules may impact request processing speed.",docsUrl:`${i.DOCS_BASE_URL}/migration/rule-optimization`},action_differences:{category:"security_consideration",severity:"warning",explanation:"Security actions (block, challenge, etc.) may behave differently between providers in terms of user experience and effectiveness.",suggestion:"Test security actions thoroughly and adjust based on provider capabilities.",alternativeApproach:"Use the most appropriate equivalent action available in the target provider.",impact:"Security effectiveness and user experience may differ from the original configuration.",docsUrl:`${i.DOCS_BASE_URL}/migration/security-actions`},challenge_types:{category:"security_consideration",severity:"info",explanation:"Challenge types (CAPTCHA, JavaScript, etc.) vary between providers and may affect user experience.",suggestion:"Choose challenge types that balance security and user experience for your use case.",alternativeApproach:"Use the provider's recommended challenge type for your security requirements.",impact:"User experience during security challenges may vary.",docsUrl:`${i.DOCS_BASE_URL}/migration/challenges`},version_compatibility:{category:"compatibility_issue",severity:"warning",explanation:"Some features may only be available in specific provider plan tiers or API versions.",suggestion:"Verify feature availability in your target provider plan.",alternativeApproach:"Use alternative features available in your plan tier.",impact:"Some rules may not work as expected if required features are unavailable.",docsUrl:`${i.DOCS_BASE_URL}/migration/compatibility`},plan_limitations:{category:"compatibility_issue",severity:"critical",explanation:"Your current plan may not support all features being translated.",suggestion:"Upgrade your plan or modify rules to use available features.",alternativeApproach:"Implement equivalent functionality using features available in your current plan.",impact:"Rules may be rejected or ignored if they use unavailable features.",docsUrl:`${i.DOCS_BASE_URL}/migration/plan-limits`}};static createWarning(e,t,n,o,s){let a=this.WARNING_CONFIGS[e];return a?{rule:t,field:n,category:a.category,message:o||`${e.replace(/_/g," ")} translation issue`,explanation:a.explanation,severity:a.severity,suggestion:s||a.suggestion,alternativeApproach:a.alternativeApproach,impact:a.impact,docsUrl:a.docsUrl}:{rule:t,field:n,category:"compatibility_issue",message:o||`Unknown translation issue: ${e}`,explanation:"This translation scenario is not fully documented.",severity:"warning",suggestion:s||"Review the translated configuration manually and test thoroughly.",docsUrl:`${this.DOCS_BASE_URL}/migration/troubleshooting`}}static createUnsupportedFeatureWarning(e,t,n,o,s){return{rule:o,field:s,category:"feature_unsupported",message:`${e} is not supported in ${n}`,explanation:`The ${e} feature from ${t} cannot be directly translated to ${n} due to platform differences.`,severity:"critical",suggestion:`Remove or replace ${e} with equivalent functionality supported by ${n}.`,alternativeApproach:`Check ${n} documentation for similar features or use custom rules to achieve equivalent functionality.`,impact:"This rule may not work as expected or may be ignored entirely.",docsUrl:`${this.DOCS_BASE_URL}/migration/unsupported-features`}}static createLossyConversionWarning(e,t,n,o){return{rule:n,field:o,category:"lossy_conversion",message:`${e} translation may be lossy`,explanation:`The ${e} configuration cannot be perfectly translated: ${t}`,severity:"warning",suggestion:"Review the translated configuration and test thoroughly to ensure it behaves as expected.",alternativeApproach:"Consider simplifying the configuration or using more portable alternatives.",impact:"The translated rule may behave differently than the original.",docsUrl:`${this.DOCS_BASE_URL}/migration/lossy-conversions`}}static createSyntaxLimitationWarning(e,t,n,o){return{rule:n,field:o,category:"syntax_limitation",message:`${e} has syntax limitations`,explanation:`The ${e} syntax has been modified due to provider limitations: ${t}`,severity:"warning",suggestion:"Verify the translated syntax works correctly in the target provider.",alternativeApproach:"Use simpler syntax or alternative approaches that are more widely supported.",impact:"The rule may not match traffic as precisely as intended.",docsUrl:`${this.DOCS_BASE_URL}/migration/syntax-limitations`}}static createPerformanceWarning(e,t,n,o){return{rule:n,field:o,category:"performance_impact",message:`${e} may impact performance`,explanation:`The ${e} configuration may have performance implications: ${t}`,severity:"info",suggestion:"Monitor performance after deployment and optimize if necessary.",alternativeApproach:"Consider using more efficient alternatives or breaking complex rules into simpler ones.",impact:"Request processing may be slower than expected.",docsUrl:`${this.DOCS_BASE_URL}/migration/performance`}}static formatWarning(e){let n={critical:"\u{1F6A8}",warning:"\u26A0\uFE0F",info:"\u2139\uFE0F"}[e.severity],o=e.rule?` (Rule: ${e.rule}${e.field?`, Field: ${e.field}`:""})`:"",s=`${n} ${e.severity.toUpperCase()}: ${e.message}${o}
|
|
1
|
+
var Lt=Object.defineProperty;var Yn=Object.getOwnPropertyDescriptor;var Jn=Object.getOwnPropertyNames;var Qn=Object.prototype.hasOwnProperty;var yt=(i,e)=>()=>(i&&(e=i(i=0)),e);var J=(i,e)=>{for(var t in e)Lt(i,t,{get:e[t],enumerable:!0})},Xn=(i,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of Jn(e))!Qn.call(i,o)&&o!==t&&Lt(i,o,{get:()=>e[o],enumerable:!(n=Yn(e,o))||n.enumerable});return i};var Ce=i=>Xn(Lt({},"__esModule",{value:!0}),i);import{createConsola as eo}from"consola";var r,T=yt(()=>{"use strict";r=eo({formatOptions:{colors:!0,compact:!0,date:!1}})});import po from"consola";var v,Ee=yt(()=>{"use strict";v=async(i,e)=>{if(!process.stdin.isTTY)throw new Error(`Cannot prompt for "${i}" \u2014 no interactive terminal available. Re-run with the appropriate non-interactive flag for this command (e.g. --interactive=false, --ci, --force).`);let t=await po.prompt(i,e);return t!=null&&t.toString()==="Symbol(clack:cancel)"&&process.exit(0),(!e?.type||e.type==="text")&&t===void 0?"":t}});var Oe={};J(Oe,{TranslationWarningSystem:()=>S});var S,de=yt(()=>{"use strict";S=class i{static DOCS_BASE_URL="https://docs.doorman.griffen.codes";static WARNING_CONFIGS={managed_rules:{category:"feature_unsupported",severity:"critical",explanation:"Managed rules are provider-specific security rules that cannot be directly translated between platforms.",suggestion:"Replace with custom rules using equivalent conditions and actions.",alternativeApproach:"Create custom rules that match the same traffic patterns using standard conditions like IP, country, user agent, or request headers.",impact:"Security coverage may be reduced until equivalent custom rules are implemented.",docsUrl:`${i.DOCS_BASE_URL}/migration/managed-rules`},bot_management:{category:"feature_unsupported",severity:"critical",explanation:"Bot management features use provider-specific detection algorithms that cannot be translated.",suggestion:"Configure bot management separately in the target provider.",alternativeApproach:"Use user agent filtering, rate limiting, and challenge actions to implement basic bot protection.",impact:"Bot protection effectiveness may vary between providers.",docsUrl:`${i.DOCS_BASE_URL}/migration/bot-management`},complex_expressions:{category:"lossy_conversion",severity:"warning",explanation:"Complex expressions may not translate perfectly due to differences in syntax and supported operators between providers.",suggestion:"Review translated expressions and test thoroughly before deploying.",alternativeApproach:"Break complex expressions into simpler, more portable conditions.",impact:"Rule behavior may differ slightly from the original configuration.",docsUrl:`${i.DOCS_BASE_URL}/migration/expressions`},geo_blocking:{category:"lossy_conversion",severity:"warning",explanation:"Geographic blocking may use different country codes or geographic databases between providers.",suggestion:"Verify country codes and geographic targeting after translation.",alternativeApproach:"Use ISO country codes when possible for better compatibility.",impact:"Some geographic regions may be blocked or allowed differently than intended.",docsUrl:`${i.DOCS_BASE_URL}/migration/geo-blocking`},rate_limiting_precision:{category:"lossy_conversion",severity:"info",explanation:"Rate limiting configurations may be adjusted to match the target provider's supported time windows and request thresholds.",suggestion:"Review rate limiting settings and adjust if necessary.",alternativeApproach:"Use the closest supported time window and request threshold values.",impact:"Rate limiting may be slightly more or less restrictive than the original configuration.",docsUrl:`${i.DOCS_BASE_URL}/migration/rate-limiting`},regex_patterns:{category:"syntax_limitation",severity:"warning",explanation:"Regular expression patterns may have different syntax requirements or feature support between providers.",suggestion:"Test regex patterns in the target provider and adjust syntax if needed.",alternativeApproach:"Use simpler string matching operations (contains, starts_with, ends_with) when possible.",impact:"Pattern matching may behave differently or fail to match intended traffic.",docsUrl:`${i.DOCS_BASE_URL}/migration/regex-patterns`},header_manipulation:{category:"syntax_limitation",severity:"warning",explanation:"Header manipulation capabilities vary between providers in terms of supported headers and modification types.",suggestion:"Verify header modification support in the target provider.",alternativeApproach:"Use redirect rules or custom error pages to achieve similar functionality.",impact:"Header modifications may not work as expected or may be ignored.",docsUrl:`${i.DOCS_BASE_URL}/migration/headers`},large_ip_lists:{category:"performance_impact",severity:"info",explanation:"Large IP lists may impact rule evaluation performance differently between providers.",suggestion:"Consider using provider-specific IP list features for better performance.",alternativeApproach:"Break large IP lists into smaller, more targeted rules or use CIDR notation to reduce list size.",impact:"Rule evaluation may be slower with large IP lists, potentially affecting site performance.",docsUrl:`${i.DOCS_BASE_URL}/migration/ip-lists`},many_conditions:{category:"performance_impact",severity:"info",explanation:"Rules with many conditions may have different performance characteristics between providers.",suggestion:"Monitor rule performance and consider optimizing complex rules.",alternativeApproach:"Split complex rules into multiple simpler rules or use more efficient condition types.",impact:"Complex rules may impact request processing speed.",docsUrl:`${i.DOCS_BASE_URL}/migration/rule-optimization`},action_differences:{category:"security_consideration",severity:"warning",explanation:"Security actions (block, challenge, etc.) may behave differently between providers in terms of user experience and effectiveness.",suggestion:"Test security actions thoroughly and adjust based on provider capabilities.",alternativeApproach:"Use the most appropriate equivalent action available in the target provider.",impact:"Security effectiveness and user experience may differ from the original configuration.",docsUrl:`${i.DOCS_BASE_URL}/migration/security-actions`},challenge_types:{category:"security_consideration",severity:"info",explanation:"Challenge types (CAPTCHA, JavaScript, etc.) vary between providers and may affect user experience.",suggestion:"Choose challenge types that balance security and user experience for your use case.",alternativeApproach:"Use the provider's recommended challenge type for your security requirements.",impact:"User experience during security challenges may vary.",docsUrl:`${i.DOCS_BASE_URL}/migration/challenges`},version_compatibility:{category:"compatibility_issue",severity:"warning",explanation:"Some features may only be available in specific provider plan tiers or API versions.",suggestion:"Verify feature availability in your target provider plan.",alternativeApproach:"Use alternative features available in your plan tier.",impact:"Some rules may not work as expected if required features are unavailable.",docsUrl:`${i.DOCS_BASE_URL}/migration/compatibility`},plan_limitations:{category:"compatibility_issue",severity:"critical",explanation:"Your current plan may not support all features being translated.",suggestion:"Upgrade your plan or modify rules to use available features.",alternativeApproach:"Implement equivalent functionality using features available in your current plan.",impact:"Rules may be rejected or ignored if they use unavailable features.",docsUrl:`${i.DOCS_BASE_URL}/migration/plan-limits`}};static createWarning(e,t,n,o,s){let a=this.WARNING_CONFIGS[e];return a?{rule:t,field:n,category:a.category,message:o||`${e.replace(/_/g," ")} translation issue`,explanation:a.explanation,severity:a.severity,suggestion:s||a.suggestion,alternativeApproach:a.alternativeApproach,impact:a.impact,docsUrl:a.docsUrl}:{rule:t,field:n,category:"compatibility_issue",message:o||`Unknown translation issue: ${e}`,explanation:"This translation scenario is not fully documented.",severity:"warning",suggestion:s||"Review the translated configuration manually and test thoroughly.",docsUrl:`${this.DOCS_BASE_URL}/migration/troubleshooting`}}static createUnsupportedFeatureWarning(e,t,n,o,s){return{rule:o,field:s,category:"feature_unsupported",message:`${e} is not supported in ${n}`,explanation:`The ${e} feature from ${t} cannot be directly translated to ${n} due to platform differences.`,severity:"critical",suggestion:`Remove or replace ${e} with equivalent functionality supported by ${n}.`,alternativeApproach:`Check ${n} documentation for similar features or use custom rules to achieve equivalent functionality.`,impact:"This rule may not work as expected or may be ignored entirely.",docsUrl:`${this.DOCS_BASE_URL}/migration/unsupported-features`}}static createLossyConversionWarning(e,t,n,o){return{rule:n,field:o,category:"lossy_conversion",message:`${e} translation may be lossy`,explanation:`The ${e} configuration cannot be perfectly translated: ${t}`,severity:"warning",suggestion:"Review the translated configuration and test thoroughly to ensure it behaves as expected.",alternativeApproach:"Consider simplifying the configuration or using more portable alternatives.",impact:"The translated rule may behave differently than the original.",docsUrl:`${this.DOCS_BASE_URL}/migration/lossy-conversions`}}static createSyntaxLimitationWarning(e,t,n,o){return{rule:n,field:o,category:"syntax_limitation",message:`${e} has syntax limitations`,explanation:`The ${e} syntax has been modified due to provider limitations: ${t}`,severity:"warning",suggestion:"Verify the translated syntax works correctly in the target provider.",alternativeApproach:"Use simpler syntax or alternative approaches that are more widely supported.",impact:"The rule may not match traffic as precisely as intended.",docsUrl:`${this.DOCS_BASE_URL}/migration/syntax-limitations`}}static createPerformanceWarning(e,t,n,o){return{rule:n,field:o,category:"performance_impact",message:`${e} may impact performance`,explanation:`The ${e} configuration may have performance implications: ${t}`,severity:"info",suggestion:"Monitor performance after deployment and optimize if necessary.",alternativeApproach:"Consider using more efficient alternatives or breaking complex rules into simpler ones.",impact:"Request processing may be slower than expected.",docsUrl:`${this.DOCS_BASE_URL}/migration/performance`}}static formatWarning(e){let n={critical:"\u{1F6A8}",warning:"\u26A0\uFE0F",info:"\u2139\uFE0F"}[e.severity],o=e.rule?` (Rule: ${e.rule}${e.field?`, Field: ${e.field}`:""})`:"",s=`${n} ${e.severity.toUpperCase()}: ${e.message}${o}
|
|
2
2
|
`;return s+=` ${e.explanation}
|
|
3
3
|
`,e.suggestion&&(s+=` \u{1F4A1} Suggestion: ${e.suggestion}
|
|
4
4
|
`),e.alternativeApproach&&(s+=` \u{1F504} Alternative: ${e.alternativeApproach}
|
|
@@ -21,7 +21,7 @@ ${o}`}};var V=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(B.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 G(s,i.config),r.success(B.green(`\u2714 IP rule "${t.ip}" added to configuration`)),Oi(t)}else{let t=i.interactive?await Oo():Fo(i),n=Ot.safeParse(t);if(n.success||(r.error(B.red("Rule validation failed:")),n.error.errors.forEach(c=>{let d=c.path.join(".")||"rule";r.error(B.red(` - ${d}: ${c.message}`))}),process.exit(1)),i.dryRun){r.info(B.cyan(`
|
|
22
22
|
Dry run - The following rule would be added:`)),Di(t),r.log(""),r.log(B.dim(JSON.stringify(t,null,2)));return}r.start("Loading configuration...");let o=await z(i.config,"raw"),s=o.rules||[],a=Bo({...o,rules:s},t);if(a){if(r.warn(B.yellow(`\u26A0\uFE0F ${a}`)),i.ci||!process.stdin.isTTY){r.info(B.dim("Skipping \u2014 non-interactive and a conflicting rule already exists. Rule not added."));return}if(!await v("Proceed anyway?",{type:"confirm",initial:!1})){r.info("Cancelled.");return}}let l={...o,rules:[...s,t]};await G(l,i.config),r.success(B.green(`\u2714 Rule "${t.name}" added to configuration`)),Di(t)}r.log(""),r.log(B.dim(`Run ${B.cyan("doorman sync")} to deploy this rule.`))}catch(e){Q(e,"adding rule")}};var oi={};J(oi,{builder:()=>gs,command:()=>ps,desc:()=>fs,handler:()=>ms});T();import ie from"chalk";import{existsSync as St,mkdirSync as as,readdirSync as ls,statSync as cs}from"fs";import{isAbsolute as ds,join as ni}from"path";import{LogLevels as us}from"consola";Ee();import{existsSync as Ni,readFileSync as Wo}from"fs";import{join as Vi}from"path";function Bi(i=process.cwd()){return Ni(Vi(i,".git"))}function qi(i,e=process.cwd()){let t=Vi(e,".gitignore");if(!Ni(t))return!1;let n=i.replace(/^\.\//,"").replace(/^\//,"").replace(/\/$/,""),o;try{o=Wo(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 X={type:"string",choices:j,description:"Firewall provider (auto-detected)"};T();import{LogLevels as ss}from"consola";T();Ee();import Xr from"chalk";import{emitKeypressEvents as Mo}from"readline";function jo(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 zo(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 Wi(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;Mo(t),t.setRawMode(!0),t.resume();let o="",s=()=>{t.removeListener("keypress",a),t.setRawMode(n??!1)},a=(l,c)=>{let d=jo(l,c),u=zo(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)})}T();var Mi={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 We={vercel:Mi,cloudflare:ji,fastly:zi,gcp:Gi};var Ct=class{static detectionField(e){return We[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 j){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 j){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 j.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 j){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 j){let o=this.detectionField(n);o&&process.env[o.envVar]&&t.add(n)}return Array.from(t)}};T();Ee();import Zi from"chalk";var vt=i=>({$schema:Ii,firewallEnabled:!0,rules:[],ips:[],...i});T();var Te=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 y=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(y),y.status===429){let C=this.calculateRateLimitWait(f,y);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(C/1e3)}s.`);r.warn(`Rate limit exceeded for ${this.providerName}. Waiting ${C}ms before retry (attempt ${f+1}/${s+1})...`),await this.delay(C);continue}if(!y.ok)throw await this.handleErrorResponse(y);let w;try{let C=await y.text();C.trim()?w=JSON.parse(C):w={}}catch(C){throw r.error(`Failed to parse response from ${n}:`,C),new Error(`Invalid JSON response from ${this.providerName} API: ${C instanceof Error?C.message:String(C)}`)}return this.isLargeResponse(w)&&(r.debug(`Large response detected from ${n}, validating structure...`),this.validateLargeResponse(w)),w}catch(y){if(c=y,u&&(clearTimeout(u),u=null),this.isNonRetryableError(y)||f===s)throw y;let w=a*Math.pow(2,f),C=Math.random()*.1*w,x=Math.min(w+C,3e4);r.debug(`Request failed (attempt ${f+1}/${s+1}). Retrying in ${Math.round(x)}ms...`),r.debug(`Error details: ${y instanceof Error?y.message:String(y)}`),await this.delay(x)}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(y=>!p.includes(y));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",Me=class extends Te{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 v("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}=vt();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}}};T();import re from"chalk";T();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}}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.version&&r.info(` New version: ${e.version}`)}};de();function Ji(i){let e=[],t=[];i.conditionGroup.forEach((s,a)=>{for(let l of s.conditions){let c=Ko(l.op);t.push({field:Zo(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=Go(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 Go(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=Jo(l.field);if(!c){t.push(U.createUnsupportedFeatureWarning(`condition field '${l.field}'`,"unified config","Vercel",e,l.field));continue}a.push({op:Ho(l.operator),neg:l.negated,type:c,key:l.key,value:l.value})}a.length>0&&o.push({conditions:a})}return o}function Ko(i){return{eq:"eq",pre:"starts_with",suf:"ends_with",inc:"in",sub:"contains",re:"matches",ex:"exists",nex:"not_exists"}[i]||"eq"}function Ho(i){return{eq:"eq",starts_with:"pre",ends_with:"suf",in:"inc",contains:"sub",matches:"re",exists:"ex",not_exists:"nex"}[i]||"eq"}function Zo(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"}[i]||i}var Yo={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",region:"region",protocol:"protocol",environment:"environment",geo_continent:"geo_continent",geo_country_region:"geo_country_region",ja4_digest:"ja4_digest",ja3_digest:"ja3_digest",rate_limit_api_id:"rate_limit_api_id"};function Jo(i){return Yo[i]??null}de();T();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 nt=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 Qo=new Set(["ip.src"]),ot=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=nt.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){let t=this.mapUnifiedFieldToCloudflare(e.field),n=e.key&&(e.field==="header"||e.field==="cookie")?`${t}["${je(e.key)}"]`:t,o=this.buildUnifiedExpression(n,e.operator,e.value);return e.negated&&(o=`not (${o})`),o}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"}[e]||e}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(Qo.has(e)){if(!Ae.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 Xo={"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.cookie":"cookie","http.user_agent":"user_agent","http.referer":"referer",ssl:"scheme","cf.edge.server_port":"port"},er=new Set(["eq","ne","contains","starts_with","ends_with","matches","in","gt","ge","lt","le"]),tr=new Set(["and","or","not"]),Pe=class extends Error{};function ir(i){let e=[],t=0,n=i.length;for(;t<n;){let o=i[t];if(/\s/.test(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 Pe("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 Pe(`Unexpected character '${o}' at position ${t}`);e.push({type:"WORD",value:i.slice(t,s)}),t=s}return e}var Kt=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 Pe("Unexpected end of expression");return this.pos++,e}expect(e,t){let n=this.next();if(n.type!==e||t!==void 0&&n.value!==t)throw new Pe(`Expected ${t??e} but got '${n.value}'`);return n}isWord(e){let t=this.peek();return!!t&&t.type==="WORD"&&t.value===e}atEnd(){return this.pos>=this.tokens.length}};function Ht(i){let e=[tn(i)];for(;i.isWord("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.isWord("and");)i.next(),e.push(nn(i));return e.length===1?e[0]:{type:"and",children:e}}function nn(i){if(i.isWord("not")){i.next(),i.expect("LPAREN");let e=Ht(i);return i.expect("RPAREN"),{type:"not",child:e}}return nr(i)}function nr(i){let e=i.peek();if(e&&e.type==="LPAREN"){i.next();let t=Ht(i);return i.expect("RPAREN"),t}return sr(i)}function or(i){let e=i.expect("WORD");if(tr.has(e.value))throw new Pe(`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 Pe(`Unexpected token '${e.value}' where a value was expected`)}function rr(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 sr(i){let{field:e,key:t}=or(i);if(i.isWord("exists"))return i.next(),{type:"exists",field:e,key:t};let n=i.next();if(n.type!=="WORD"||!er.has(n.value))throw new Pe(`Unsupported or unrecognized operator '${n.value}'`);let o=rr(i);return{type:"comparison",field:e,key:t,operator:n.value,value:o}}function ar(i){let e=new Kt(ir(i)),t=Ht(e);if(!e.atEnd())throw new Pe("Unexpected trailing tokens");return t}function bt(i){return i.type==="comparison"||i.type==="exists"?!0:i.type==="not"?i.child.type==="comparison"||i.child.type==="exists":!1}function It(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=Xo[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 lr(i){if(bt(i)){let e=It(i,0);return e?{conditions:[e],conditionLogic:"AND"}:null}if(i.type==="and"){let e=[];for(let t of i.children){if(!bt(t))return null;let n=It(t,0);if(!n)return null;e.push(n)}return{conditions:e,conditionLogic:"AND"}}if(i.type==="or"){let e=[];i.children.forEach((o,s)=>{if(bt(o)){let a=It(o,s);a&&e.push(a);return}if(o.type==="and")for(let a of o.children){if(!bt(a))return;let l=It(a,s);l&&e.push(l)}});let t=i.children.length;return new Set(e.map(o=>o.group)).size!==t?null:{conditions:e,conditionLogic:"OR"}}return null}function Zt(i){if(!i||!i.trim())return null;try{let e=ar(i);return lr(e)}catch{return null}}function rn(i){let e=[],t=Zt(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 sn(i){let e=[],t=ot.fromUnifiedConditions(i.conditions,i.conditionLogic),n={id:i.id||crypto.randomUUID(),action:dr(i.action.type),expression:t,description:i.description||i.name,enabled:i.enabled};return i.action.rateLimit&&(n.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?n.ratelimit.mitigation_timeout=i.action.rateLimit.mitigationTimeout:(n.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&&(n.ratelimit.counting_expression=i.action.rateLimit.countingExpression)),i.action.type==="redirect"&&i.action.redirect&&(n.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&&(n.action!=="block"?e.push(U.createUnsupportedFeatureWarning(`custom response on a '${n.action}' action`,"unified config","Cloudflare",i.id,"action.response")):i.action.response.content?n.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:n,warnings:e}}function an(i){if(!Ae.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 cr(i){return{block:"deny",challenge:"challenge",managed_challenge:"challenge",js_challenge:"challenge",log:"log",skip:"bypass",allow:"allow",rewrite:"bypass",redirect:"redirect"}[i]||"deny"}function dr(i){return{log:"log",deny:"block",block:"block",challenge:"managed_challenge",bypass:"skip",rate_limit:"block",redirect:"redirect",allow:"allow"}[i]||"block"}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}de();function ue(i,e,t){return U.createWarning("complex_expressions",i,void 0,e,t)}var pr={ip:"ip",path:"path",domain:"host",method:"method",country:"country",scheme:"scheme",user_agent:"user_agent"},fr={ip:"ip",path:"path",host:"domain",method:"method",country:"country",scheme:"scheme",user_agent:"user_agent"},gr={header:"request_header",query:"query_parameter",cookie:"request_cookie"},mr={request_header:"header",query_parameter:"query",request_cookie:"cookie",response_header:"response_header",post_parameter:"post_parameter",signal:"signal"};function hr(i){return pr[i]??i}function yr(i){return fr[i]??null}var wr={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 ln(i){return wr[i]??{operator:"eq",negated:!1}}function cn(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(ue(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(ue(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 Rr(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(ue(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)Et(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;Et(t,s,l,e,i.id)}),{conditions:t,conditionLogic:i.group_operator==="any"&&o.length>1?"OR":"AND"};e.push(ue(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)Et(t,s,0,e,i.id);for(let s of n)for(let a of s.conditions)Et(t,a,0,e,i.id);return{conditions:t,conditionLogic:"AND"}}function Et(i,e,t,n,o){if(e.type==="single"){let{operator:u,negated:p}=ln(e.operator);i.push({field:hr(e.field),operator:u,value:e.value,...p?{negated:!0}:{},group:t});return}let s=mr[e.field],a=e.conditions.find(u=>u.field==="name"),l=e.conditions.find(u=>u.field==="value"||u.field==="value_string");if(!l){n.push(ue(o,`Multival condition on '${e.field}' has no value sub-condition doorman could extract (only a 'name' existence check) \u2014 skipped.`));return}let{operator:c,negated:d}=ln(l.operator);i.push({field:s,operator:c,value:l.value,...d?{negated:!0}:{},...a?{key:a.value}:{},group:t})}function Cr(i,e){i.actions.length>1&&e.push(ue(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(ue(i.id,`Unrecognised Fastly action type "${String(t?.type)}" \u2014 mapped to "log".`)),{type:"log"}}}function dn(i){let e=[],{conditions:t,conditionLogic:n}=Rr(i,e),o=Cr(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 vr(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=gr[a.field];if(l){if(a.key===void 0)return t.push(ue(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;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:cn(a.operator,a.negated,t,e),value:String(a.value)}]}}let c=yr(a.field);return c?{type:"single",field:c,operator:cn(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 br(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(ue(t,'Unified action "bypass" has no direct Fastly equivalent \u2014 mapped to "allow".')),{type:"allow"};case"log":return e.push(ue(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(ue(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 Ir(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 Er(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=Ir(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 un(i){let e=[];i.description&&i.description!==i.name&&e.push(ue(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}=vr(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:[br(i.action,e,i.id)],rate_limit:a?Er(i.action.rateLimit,i.id,e):null},warnings:e}}function pn(i,e){return i.map(t=>({ip:t,action:e}))}function fn(i){let e=[],t=[],n=[];for(let o of i)(o.hostname||o.notes)&&e.push(ue(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}}de();function ze(i){return i.replace(/\\/g,"\\\\").replace(/'/g,"\\'")}function gn(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 Ar=new Set(["region","city","port","scheme"]),Tr={host:"host",user_agent:"user-agent",referer:"referer"},rt=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(Ar.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=Tr[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=$r[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(!Ae.safeParse(a).success)throw new Error(`Invalid IP address or CIDR range: ${a}`);return a.includes("/")?`inIpRange(origin.ip, '${ze(a)}')`:`origin.ip == '${ze(a)}'`}),o=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['${ze(e)}']`,o=`has(${n})`;return t.operator==="exists"?o:t.operator==="not_exists"?`!${o}`:`(${o} && ${this.buildComparison(n,t,!1)})`}static buildCookieCondition(e){let t="request.headers['cookie']",n=`has(${t})`;if(e.operator==="exists")return n;if(e.operator==="not_exists")return`!${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('${ze(o)}')`,a=e.operator==="ne"||e.operator==="not_contains";return`(${n} && ${a?`!${s}`:s})`}static buildComparison(e,t,n){let{operator:o,value:s}=t;if(o==="exists"||o==="not_exists")throw new Error(`"${o}" is not meaningful for Cloud Armor's "${e}" \u2014 it is always present`);if(o==="in"||o==="not_in"){let c=(Array.isArray(s)?s:[s]).map(u=>this.formatLiteral(u,n)).join(", "),d=`${e} in [${c}]`;return o==="not_in"?`!(${d})`:d}let a=this.formatLiteral(s,n);switch(o){case"eq":return`${e} == ${a}`;case"ne":return`${e} != ${a}`;case"contains":return`${e}.contains(${a})`;case"not_contains":return`!${e}.contains(${a})`;case"starts_with":return`${e}.startsWith(${a})`;case"ends_with":return`${e}.endsWith(${a})`;case"matches":return`${e}.matches(${a})`;case"gt":return`${e} > ${a}`;case"ge":return`${e} >= ${a}`;case"lt":return`${e} < ${a}`;case"le":return`${e} <= ${a}`;default:throw new Error(`Cloud Armor CEL builder has no mapping for operator "${o}"`)}}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`'${ze(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(" || ")})`}},$r={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 Pr={"origin.region_code":"country","origin.asn":"asn","request.path":"path","request.method":"method","request.query":"query"},_r={host:"host","user-agent":"user_agent",referer:"referer",cookie:"cookie"},Cn={contains:"contains",startsWith:"starts_with",endsWith:"ends_with",matches:"matches"},ye=class extends Error{};function kr(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 ye("Unterminated string literal");e.push({type:"STRING",value:gn(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 ye(`Unexpected character '${s}' at position ${t}`)}return e}var Jt=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 ye("Unexpected end of expression");return this.pos++,e}expect(e){let t=this.next();if(t.type!==e)throw new ye(`Expected ${e} but got '${t.value}'`);return t}isIdent(e){let t=this.peek();return!!t&&t.type==="IDENT"&&t.value===e}atEnd(){return this.pos>=this.tokens.length}};function Sr(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 Yt(i){let e=Sr(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 mn(i){let e=i.next();if(e.type==="STRING")return e.value;if(e.type==="NUMBER")return Number(e.value);throw new ye(`Unexpected token '${e.value}' where a literal was expected`)}function Ur(i){if(i.isIdent("has")){i.next(),i.expect("LPAREN");let a=Yt(i);return i.expect("RPAREN"),{type:"has",...a}}if(i.isIdent("inIpRange")){i.next(),i.expect("LPAREN");let a=Yt(i);if(a.field!=="origin.ip")throw new ye(`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=Yt(i);if(i.peek()?.type==="DOT"){i.next();let a=i.expect("IDENT").value;if(!Cn[a])throw new ye(`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.isIdent("in")){i.next(),i.expect("LBRACKET");let a=[];for(;i.peek()?.type!=="RBRACKET";)a.push(mn(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 ye(`Unexpected token '${t.value}' after field reference`);let s=mn(i);return{type:"comparison",...e,operator:o,value:s}}function hn(i){if(i.peek()?.type==="LPAREN"){i.next();let e=vn(i);return i.expect("RPAREN"),e}return Ur(i)}function yn(i){return i.peek()?.type==="NOT"?(i.next(),{type:"not",child:hn(i)}):hn(i)}function wn(i){let e=[yn(i)];for(;i.peek()?.type==="AND";)i.next(),e.push(yn(i));return e.length===1?e[0]:{type:"and",children:e}}function vn(i){let e=[wn(i)];for(;i.peek()?.type==="OR";)i.next(),e.push(wn(i));return e.length===1?e[0]:{type:"or",children:e}}function Lr(i){let e=new Jt(kr(i)),t=vn(e);if(!e.atEnd())throw new ye("Unexpected trailing tokens");return t}function xr(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(xr)}function Dr(i){return i.type==="inIpRange"?i.value:String(i.value)}function bn(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 Or(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 At(i){if(i.type==="has"||i.type==="comparison"||i.type==="methodCall"||i.type==="membership"||i.type==="inIpRange"||Qt(i)||bn(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 In(i,e){if(i==="origin.ip")return{unifiedField:"ip"};let t=Pr[i];if(t)return{unifiedField:t};if(i==="request.headers"&&e!==void 0){let n=_r[e];return n?{unifiedField:n}:{unifiedField:"header",unifiedKey:e}}return null}function Tt(i,e){let t=i.type==="not",n=i.type==="not"?i.child:i;if(Qt(n)){let o=n.children.map(s=>Dr(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"&&bn(n)){if(t)return null;let{guard:o,comparisonNegated:s,comparison:a}=Or(n);return a.type!=="comparison"&&a.type!=="methodCall"&&a.type!=="membership"?null:Rn(a,s,o.field,o.key,e)}if(n.type==="comparison"||n.type==="methodCall"||n.type==="membership")return Rn(n,t,n.field,n.key,e);if(n.type==="has"){let o=In(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 Rn(i,e,t,n,o){let s=In(t,n);if(!s)return null;let a,l;if(i.type==="comparison")a=e?Fr(i.operator):i.operator,l=i.value;else if(i.type==="methodCall"){let c=Cn[i.method];if(!c||(a=e?`not_${c}`:c,e&&a!=="not_contains"))return null;l=i.value}else a=e?"not_in":"in",l=i.values;return{field:s.unifiedField,operator:a,value:l,...s.unifiedKey!==void 0?{key:s.unifiedKey}:{},...o!==void 0?{group:o}:{}}}function Fr(i){if(i==="eq")return"ne";if(i==="ne")return"eq";throw new ye(`No negated form for comparison operator '${i}'`)}function Nr(i){if(At(i)){let e=Tt(i,void 0);return e?{conditions:[e],conditionLogic:"AND"}:null}if(i.type==="and"){let e=[];for(let t of i.children){if(!At(t))return null;let n=Tt(t,void 0);if(!n)return null;e.push(n)}return{conditions:e,conditionLogic:"AND"}}if(i.type==="or"){let e=[];i.children.forEach((o,s)=>{if(At(o)){let a=Tt(o,s);a&&e.push(a);return}if(o.type==="and")for(let a of o.children){if(!At(a))return;let l=Tt(a,s);l&&e.push(l)}});let t=i.children.length;return new Set(e.map(o=>o.group)).size!==t?null:{conditions:e,conditionLogic:"OR"}}return null}function $t(i){if(!i||!i.trim())return null;try{let e=Lr(i);return Nr(e)}catch{return null}}function En(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=rt.fromUnifiedConditions(i.conditions,i.conditionLogic),{action:n,actionWarning:o}=Br(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=qr(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 An(i){let e=[],t=$t(i.match.expr.expression),n=[],o;t?(n=t.conditions,o=t.conditionLogic):e.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 s={type:Vr(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:n,...o==="OR"?{conditionLogic:o}:{},action:s,priority:i.priority},warnings:e}}function Tn(i){if(i.action!=="allow"&&i.action!=="deny(403)"||i.preview)return!1;let e=$t(i.match.expr.expression);if(!e||e.conditions.length!==1)return!1;let t=e.conditions[0];return t.field==="ip"&&(t.operator==="eq"||t.operator==="in")&&!t.key}function $n(i){let t=$t(i.match.expr.expression).conditions[0].value;return{id:String(i.priority),ip:Array.isArray(t)?String(t[0]):String(t),...i.description?{notes:i.description}:{},action:i.action==="allow"?"allow":"deny"}}function Pn(i,e){if(!Ae.safeParse(i.ip).success)throw new Error(`Invalid IP address or CIDR range: ${i.ip}`);let t=rt.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 Vr(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 Br(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 qr(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 A=class{static vercelToUnified=Ji;static unifiedToVercel=Qi;static vercelIPToUnified=Xi;static cloudflareToUnified=rn;static unifiedToCloudflare=sn;static unifiedIPToCloudflare=an;static fastlyToUnified=dn;static unifiedToFastly=un;static fastlyListEntriesToUnified=pn;static unifiedIPsToFastlyEntries=fn;static gcpToUnified=An;static unifiedToGcp=En;static gcpToUnifiedIP=$n;static unifiedIPToGcp=Pn;static gcpLooksLikeIpRule=Tn};de();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 pe(i){let{id:e,...t}=i;return t}function st(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 Pt(i){return i.some(e=>e.priority!==void 0)}var Ge=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=A.vercelToUnified(a);return l.warnings.length>0&&l.warnings.forEach(c=>{let{TranslationWarningSystem:d}=(de(),Ce(Oe)),u=d.formatWarning(c);r.warn(`Rule ${a.name}:
|
|
24
|
+
`),u.cancelled&&process.exit(0),e(o))};t.on("keypress",a)})}T();var Mi={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 We={vercel:Mi,cloudflare:ji,fastly:zi,gcp:Gi};var Ct=class{static detectionField(e){return We[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 j){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 j){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 j.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 j){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 j){let o=this.detectionField(n);o&&process.env[o.envVar]&&t.add(n)}return Array.from(t)}};T();Ee();import Zi from"chalk";var vt=i=>({$schema:Ii,firewallEnabled:!0,rules:[],ips:[],...i});T();var Te=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 y=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(y),y.status===429){let C=this.calculateRateLimitWait(f,y);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(C/1e3)}s.`);r.warn(`Rate limit exceeded for ${this.providerName}. Waiting ${C}ms before retry (attempt ${f+1}/${s+1})...`),await this.delay(C);continue}if(!y.ok)throw await this.handleErrorResponse(y);let w;try{let C=await y.text();C.trim()?w=JSON.parse(C):w={}}catch(C){throw r.error(`Failed to parse response from ${n}:`,C),new Error(`Invalid JSON response from ${this.providerName} API: ${C instanceof Error?C.message:String(C)}`)}return this.isLargeResponse(w)&&(r.debug(`Large response detected from ${n}, validating structure...`),this.validateLargeResponse(w)),w}catch(y){if(c=y,u&&(clearTimeout(u),u=null),this.isNonRetryableError(y)||f===s)throw y;let w=a*Math.pow(2,f),C=Math.random()*.1*w,x=Math.min(w+C,3e4);r.debug(`Request failed (attempt ${f+1}/${s+1}). Retrying in ${Math.round(x)}ms...`),r.debug(`Error details: ${y instanceof Error?y.message:String(y)}`),await this.delay(x)}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(y=>!p.includes(y));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",Me=class extends Te{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 v("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}=vt();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}}};T();import re from"chalk";T();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}}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.version&&r.info(` New version: ${e.version}`)}};de();function Ji(i){let e=[],t=[];i.conditionGroup.forEach((s,a)=>{for(let l of s.conditions){let c=Ko(l.op);t.push({field:Zo(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(S.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=Go(i.conditions,i.id,e);if(i.action.response&&e.push(S.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 Go(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=Jo(l.field);if(!c){t.push(S.createUnsupportedFeatureWarning(`condition field '${l.field}'`,"unified config","Vercel",e,l.field));continue}a.push({op:Ho(l.operator),neg:l.negated,type:c,key:l.key,value:l.value})}a.length>0&&o.push({conditions:a})}return o}function Ko(i){return{eq:"eq",pre:"starts_with",suf:"ends_with",inc:"in",sub:"contains",re:"matches",ex:"exists",nex:"not_exists"}[i]||"eq"}function Ho(i){return{eq:"eq",starts_with:"pre",ends_with:"suf",in:"inc",contains:"sub",matches:"re",exists:"ex",not_exists:"nex"}[i]||"eq"}function Zo(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"}[i]||i}var Yo={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",region:"region",protocol:"protocol",environment:"environment",geo_continent:"geo_continent",geo_country_region:"geo_country_region",ja4_digest:"ja4_digest",ja3_digest:"ja3_digest",rate_limit_api_id:"rate_limit_api_id"};function Jo(i){return Yo[i]??null}de();T();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 nt=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 Qo=new Set(["ip.src"]),ot=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=nt.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){let t=this.mapUnifiedFieldToCloudflare(e.field),n=e.key&&(e.field==="header"||e.field==="cookie")?`${t}["${je(e.key)}"]`:t,o=this.buildUnifiedExpression(n,e.operator,e.value);return e.negated&&(o=`not (${o})`),o}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"}[e]||e}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(Qo.has(e)){if(!Ae.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 Xo={"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.cookie":"cookie","http.user_agent":"user_agent","http.referer":"referer",ssl:"scheme","cf.edge.server_port":"port"},er=new Set(["eq","ne","contains","starts_with","ends_with","matches","in","gt","ge","lt","le"]),tr=new Set(["and","or","not"]),Pe=class extends Error{};function ir(i){let e=[],t=0,n=i.length;for(;t<n;){let o=i[t];if(/\s/.test(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 Pe("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 Pe(`Unexpected character '${o}' at position ${t}`);e.push({type:"WORD",value:i.slice(t,s)}),t=s}return e}var Kt=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 Pe("Unexpected end of expression");return this.pos++,e}expect(e,t){let n=this.next();if(n.type!==e||t!==void 0&&n.value!==t)throw new Pe(`Expected ${t??e} but got '${n.value}'`);return n}isWord(e){let t=this.peek();return!!t&&t.type==="WORD"&&t.value===e}atEnd(){return this.pos>=this.tokens.length}};function Ht(i){let e=[tn(i)];for(;i.isWord("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.isWord("and");)i.next(),e.push(nn(i));return e.length===1?e[0]:{type:"and",children:e}}function nn(i){if(i.isWord("not")){i.next(),i.expect("LPAREN");let e=Ht(i);return i.expect("RPAREN"),{type:"not",child:e}}return nr(i)}function nr(i){let e=i.peek();if(e&&e.type==="LPAREN"){i.next();let t=Ht(i);return i.expect("RPAREN"),t}return sr(i)}function or(i){let e=i.expect("WORD");if(tr.has(e.value))throw new Pe(`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 Pe(`Unexpected token '${e.value}' where a value was expected`)}function rr(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 sr(i){let{field:e,key:t}=or(i);if(i.isWord("exists"))return i.next(),{type:"exists",field:e,key:t};let n=i.next();if(n.type!=="WORD"||!er.has(n.value))throw new Pe(`Unsupported or unrecognized operator '${n.value}'`);let o=rr(i);return{type:"comparison",field:e,key:t,operator:n.value,value:o}}function ar(i){let e=new Kt(ir(i)),t=Ht(e);if(!e.atEnd())throw new Pe("Unexpected trailing tokens");return t}function bt(i){return i.type==="comparison"||i.type==="exists"?!0:i.type==="not"?i.child.type==="comparison"||i.child.type==="exists":!1}function It(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=Xo[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 lr(i){if(bt(i)){let e=It(i,0);return e?{conditions:[e],conditionLogic:"AND"}:null}if(i.type==="and"){let e=[];for(let t of i.children){if(!bt(t))return null;let n=It(t,0);if(!n)return null;e.push(n)}return{conditions:e,conditionLogic:"AND"}}if(i.type==="or"){let e=[];i.children.forEach((o,s)=>{if(bt(o)){let a=It(o,s);a&&e.push(a);return}if(o.type==="and")for(let a of o.children){if(!bt(a))return;let l=It(a,s);l&&e.push(l)}});let t=i.children.length;return new Set(e.map(o=>o.group)).size!==t?null:{conditions:e,conditionLogic:"OR"}}return null}function Zt(i){if(!i||!i.trim())return null;try{let e=ar(i);return lr(e)}catch{return null}}function rn(i){let e=[],t=Zt(i.expression),n=[],o;t?(n=t.conditions,o=t.conditionLogic):e.push(S.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 sn(i){let e=[],t=ot.fromUnifiedConditions(i.conditions,i.conditionLogic),n={id:i.id||crypto.randomUUID(),action:dr(i.action.type),expression:t,description:i.description||i.name,enabled:i.enabled};return i.action.rateLimit&&(n.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?n.ratelimit.mitigation_timeout=i.action.rateLimit.mitigationTimeout:(n.ratelimit.mitigation_timeout=3600,e.push(S.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&&(n.ratelimit.counting_expression=i.action.rateLimit.countingExpression)),i.action.type==="redirect"&&i.action.redirect&&(n.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&&(n.action!=="block"?e.push(S.createUnsupportedFeatureWarning(`custom response on a '${n.action}' action`,"unified config","Cloudflare",i.id,"action.response")):i.action.response.content?n.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(S.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:n,warnings:e}}function an(i){if(!Ae.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 cr(i){return{block:"deny",challenge:"challenge",managed_challenge:"challenge",js_challenge:"challenge",log:"log",skip:"bypass",allow:"allow",rewrite:"bypass",redirect:"redirect"}[i]||"deny"}function dr(i){return{log:"log",deny:"block",block:"block",challenge:"managed_challenge",bypass:"skip",rate_limit:"block",redirect:"redirect",allow:"allow"}[i]||"block"}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}de();function ue(i,e,t){return S.createWarning("complex_expressions",i,void 0,e,t)}var pr={ip:"ip",path:"path",domain:"host",method:"method",country:"country",scheme:"scheme",user_agent:"user_agent"},fr={ip:"ip",path:"path",host:"domain",method:"method",country:"country",scheme:"scheme",user_agent:"user_agent"},gr={header:"request_header",query:"query_parameter",cookie:"request_cookie"},mr={request_header:"header",query_parameter:"query",request_cookie:"cookie",response_header:"response_header",post_parameter:"post_parameter",signal:"signal"};function hr(i){return pr[i]??i}function yr(i){return fr[i]??null}var wr={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 ln(i){return wr[i]??{operator:"eq",negated:!1}}function cn(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(S.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(S.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(ue(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(ue(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 Rr(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(ue(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)Et(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;Et(t,s,l,e,i.id)}),{conditions:t,conditionLogic:i.group_operator==="any"&&o.length>1?"OR":"AND"};e.push(ue(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)Et(t,s,0,e,i.id);for(let s of n)for(let a of s.conditions)Et(t,a,0,e,i.id);return{conditions:t,conditionLogic:"AND"}}function Et(i,e,t,n,o){if(e.type==="single"){let{operator:u,negated:p}=ln(e.operator);i.push({field:hr(e.field),operator:u,value:e.value,...p?{negated:!0}:{},group:t});return}let s=mr[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(ue(o,`Multival condition on '${e.field}' has no value or name sub-condition doorman could extract \u2014 skipped.`));return}let{operator:c,negated:d}=ln(l.operator);i.push({field:s,operator:c,value:l.value,...d?{negated:!0}:{},...a?{key:a.value}:{},group:t})}function Cr(i,e){i.actions.length>1&&e.push(ue(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(S.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(ue(i.id,`Unrecognised Fastly action type "${String(t?.type)}" \u2014 mapped to "log".`)),{type:"log"}}}function dn(i){let e=[],{conditions:t,conditionLogic:n}=Rr(i,e),o=Cr(i,e);return t.length>10&&e.push(S.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 vr(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=gr[a.field];if(l){if(a.key===void 0)return t.push(ue(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:cn(a.operator,a.negated,t,e),value:String(a.value)}]}}if(a.operator==="exists"||a.operator==="not_exists")return t.push(S.createUnsupportedFeatureWarning(`"${a.operator}" on field '${a.field}'`,"unified config","Fastly",e,a.field)),null;let c=yr(a.field);return c?{type:"single",field:c,operator:cn(a.operator,a.negated,t,e),value:String(a.value)}:(t.push(S.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 br(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(ue(t,'Unified action "bypass" has no direct Fastly equivalent \u2014 mapped to "allow".')),{type:"allow"};case"log":return e.push(ue(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(ue(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(S.createUnsupportedFeatureWarning(`action type '${i.type}'`,"unified config","Fastly",t,"action.type")),{type:"block"}}}function Ir(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(S.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(S.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 Er(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=Ir(i.window,t,e);return t.push(S.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 un(i){let e=[];i.description&&i.description!==i.name&&e.push(ue(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}=vr(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:[br(i.action,e,i.id)],rate_limit:a?Er(i.action.rateLimit,i.id,e):null},warnings:e}}function pn(i,e){return i.map(t=>({ip:t,action:e}))}function fn(i){let e=[],t=[],n=[];for(let o of i)(o.hostname||o.notes)&&e.push(ue(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}}de();function ze(i){return i.replace(/\\/g,"\\\\").replace(/'/g,"\\'")}function gn(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 Ar=new Set(["region","city","port","scheme"]),Tr={host:"host",user_agent:"user-agent",referer:"referer"},rt=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(Ar.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=Tr[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=$r[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(!Ae.safeParse(a).success)throw new Error(`Invalid IP address or CIDR range: ${a}`);return a.includes("/")?`inIpRange(origin.ip, '${ze(a)}')`:`origin.ip == '${ze(a)}'`}),o=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['${ze(e)}']`,o=`has(${n})`;return t.operator==="exists"?o:t.operator==="not_exists"?`!${o}`:`(${o} && ${this.buildComparison(n,t,!1)})`}static buildCookieCondition(e){let t="request.headers['cookie']",n=`has(${t})`;if(e.operator==="exists")return n;if(e.operator==="not_exists")return`!${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('${ze(o)}')`,a=e.operator==="ne"||e.operator==="not_contains";return`(${n} && ${a?`!${s}`:s})`}static buildComparison(e,t,n){let{operator:o,value:s}=t;if(o==="exists"||o==="not_exists")throw new Error(`"${o}" is not meaningful for Cloud Armor's "${e}" \u2014 it is always present`);if(o==="in"||o==="not_in"){let c=(Array.isArray(s)?s:[s]).map(u=>this.formatLiteral(u,n)).join(", "),d=`${e} in [${c}]`;return o==="not_in"?`!(${d})`:d}let a=this.formatLiteral(s,n);switch(o){case"eq":return`${e} == ${a}`;case"ne":return`${e} != ${a}`;case"contains":return`${e}.contains(${a})`;case"not_contains":return`!${e}.contains(${a})`;case"starts_with":return`${e}.startsWith(${a})`;case"ends_with":return`${e}.endsWith(${a})`;case"matches":return`${e}.matches(${a})`;case"gt":return`${e} > ${a}`;case"ge":return`${e} >= ${a}`;case"lt":return`${e} < ${a}`;case"le":return`${e} <= ${a}`;default:throw new Error(`Cloud Armor CEL builder has no mapping for operator "${o}"`)}}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`'${ze(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(" || ")})`}},$r={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 Pr={"origin.region_code":"country","origin.asn":"asn","request.path":"path","request.method":"method","request.query":"query"},_r={host:"host","user-agent":"user_agent",referer:"referer",cookie:"cookie"},Cn={contains:"contains",startsWith:"starts_with",endsWith:"ends_with",matches:"matches"},ye=class extends Error{};function kr(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 ye("Unterminated string literal");e.push({type:"STRING",value:gn(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 ye(`Unexpected character '${s}' at position ${t}`)}return e}var Jt=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 ye("Unexpected end of expression");return this.pos++,e}expect(e){let t=this.next();if(t.type!==e)throw new ye(`Expected ${e} but got '${t.value}'`);return t}isIdent(e){let t=this.peek();return!!t&&t.type==="IDENT"&&t.value===e}atEnd(){return this.pos>=this.tokens.length}};function Sr(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 Yt(i){let e=Sr(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 mn(i){let e=i.next();if(e.type==="STRING")return e.value;if(e.type==="NUMBER")return Number(e.value);throw new ye(`Unexpected token '${e.value}' where a literal was expected`)}function Ur(i){if(i.isIdent("has")){i.next(),i.expect("LPAREN");let a=Yt(i);return i.expect("RPAREN"),{type:"has",...a}}if(i.isIdent("inIpRange")){i.next(),i.expect("LPAREN");let a=Yt(i);if(a.field!=="origin.ip")throw new ye(`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=Yt(i);if(i.peek()?.type==="DOT"){i.next();let a=i.expect("IDENT").value;if(!Cn[a])throw new ye(`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.isIdent("in")){i.next(),i.expect("LBRACKET");let a=[];for(;i.peek()?.type!=="RBRACKET";)a.push(mn(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 ye(`Unexpected token '${t.value}' after field reference`);let s=mn(i);return{type:"comparison",...e,operator:o,value:s}}function hn(i){if(i.peek()?.type==="LPAREN"){i.next();let e=vn(i);return i.expect("RPAREN"),e}return Ur(i)}function yn(i){return i.peek()?.type==="NOT"?(i.next(),{type:"not",child:hn(i)}):hn(i)}function wn(i){let e=[yn(i)];for(;i.peek()?.type==="AND";)i.next(),e.push(yn(i));return e.length===1?e[0]:{type:"and",children:e}}function vn(i){let e=[wn(i)];for(;i.peek()?.type==="OR";)i.next(),e.push(wn(i));return e.length===1?e[0]:{type:"or",children:e}}function Lr(i){let e=new Jt(kr(i)),t=vn(e);if(!e.atEnd())throw new ye("Unexpected trailing tokens");return t}function xr(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(xr)}function Dr(i){return i.type==="inIpRange"?i.value:String(i.value)}function bn(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 Or(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 At(i){if(i.type==="has"||i.type==="comparison"||i.type==="methodCall"||i.type==="membership"||i.type==="inIpRange"||Qt(i)||bn(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 In(i,e){if(i==="origin.ip")return{unifiedField:"ip"};let t=Pr[i];if(t)return{unifiedField:t};if(i==="request.headers"&&e!==void 0){let n=_r[e];return n?{unifiedField:n}:{unifiedField:"header",unifiedKey:e}}return null}function Tt(i,e){let t=i.type==="not",n=i.type==="not"?i.child:i;if(Qt(n)){let o=n.children.map(s=>Dr(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"&&bn(n)){if(t)return null;let{guard:o,comparisonNegated:s,comparison:a}=Or(n);return a.type!=="comparison"&&a.type!=="methodCall"&&a.type!=="membership"?null:Rn(a,s,o.field,o.key,e)}if(n.type==="comparison"||n.type==="methodCall"||n.type==="membership")return Rn(n,t,n.field,n.key,e);if(n.type==="has"){let o=In(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 Rn(i,e,t,n,o){let s=In(t,n);if(!s)return null;let a,l;if(i.type==="comparison")a=e?Fr(i.operator):i.operator,l=i.value;else if(i.type==="methodCall"){let c=Cn[i.method];if(!c||(a=e?`not_${c}`:c,e&&a!=="not_contains"))return null;l=i.value}else a=e?"not_in":"in",l=i.values;return{field:s.unifiedField,operator:a,value:l,...s.unifiedKey!==void 0?{key:s.unifiedKey}:{},...o!==void 0?{group:o}:{}}}function Fr(i){if(i==="eq")return"ne";if(i==="ne")return"eq";throw new ye(`No negated form for comparison operator '${i}'`)}function Nr(i){if(At(i)){let e=Tt(i,void 0);return e?{conditions:[e],conditionLogic:"AND"}:null}if(i.type==="and"){let e=[];for(let t of i.children){if(!At(t))return null;let n=Tt(t,void 0);if(!n)return null;e.push(n)}return{conditions:e,conditionLogic:"AND"}}if(i.type==="or"){let e=[];i.children.forEach((o,s)=>{if(At(o)){let a=Tt(o,s);a&&e.push(a);return}if(o.type==="and")for(let a of o.children){if(!At(a))return;let l=Tt(a,s);l&&e.push(l)}});let t=i.children.length;return new Set(e.map(o=>o.group)).size!==t?null:{conditions:e,conditionLogic:"OR"}}return null}function $t(i){if(!i||!i.trim())return null;try{let e=Lr(i);return Nr(e)}catch{return null}}function En(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=rt.fromUnifiedConditions(i.conditions,i.conditionLogic),{action:n,actionWarning:o}=Br(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=qr(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(S.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(S.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 An(i){let e=[],t=$t(i.match.expr.expression),n=[],o;t?(n=t.conditions,o=t.conditionLogic):e.push(S.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 s={type:Vr(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:n,...o==="OR"?{conditionLogic:o}:{},action:s,priority:i.priority},warnings:e}}function Tn(i){if(i.action!=="allow"&&i.action!=="deny(403)"||i.preview)return!1;let e=$t(i.match.expr.expression);if(!e||e.conditions.length!==1)return!1;let t=e.conditions[0];return t.field==="ip"&&(t.operator==="eq"||t.operator==="in")&&!t.key}function $n(i){let t=$t(i.match.expr.expression).conditions[0].value;return{id:String(i.priority),ip:Array.isArray(t)?String(t[0]):String(t),...i.description?{notes:i.description}:{},action:i.action==="allow"?"allow":"deny"}}function Pn(i,e){if(!Ae.safeParse(i.ip).success)throw new Error(`Invalid IP address or CIDR range: ${i.ip}`);let t=rt.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 Vr(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 Br(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:S.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:S.createWarning("lossy_conversion",t,"action",`Rule "${e}" has unrecognized action "${i}" \u2014 defaulted to Cloud Armor's "deny(403)".`)}}}function qr(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 A=class{static vercelToUnified=Ji;static unifiedToVercel=Qi;static vercelIPToUnified=Xi;static cloudflareToUnified=rn;static unifiedToCloudflare=sn;static unifiedIPToCloudflare=an;static fastlyToUnified=dn;static unifiedToFastly=un;static fastlyListEntriesToUnified=pn;static unifiedIPsToFastlyEntries=fn;static gcpToUnified=An;static unifiedToGcp=En;static gcpToUnifiedIP=$n;static unifiedIPToGcp=Pn;static gcpLooksLikeIpRule=Tn};de();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 pe(i){let{id:e,...t}=i;return t}function st(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 Pt(i){return i.some(e=>e.priority!==void 0)}var Ge=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=A.vercelToUnified(a);return l.warnings.length>0&&l.warnings.forEach(c=>{let{TranslationWarningSystem:d}=(de(),Ce(Oe)),u=d.formatWarning(c);r.warn(`Rule ${a.name}:
|
|
25
25
|
${u}`)}),l.result}),s=n.ips.map(a=>A.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}=(lt(),Ce(at)),a=await s.performDryRunValidation(t,"sync rules",async g=>await this.getChanges(g,{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||[],y=l.ipsToUpdate||[],w=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: ${y.length}, Delete: ${w.length}`),{success:!0,rulesAdded:0,rulesUpdated:0,rulesDeleted:0,ipsAdded:0,ipsUpdated:0,ipsDeleted:0,version:p,warnings:a.warnings};let C=s.assessOperationRisk(l,t);if(!await s.confirmDestructiveOperation({operation:"sync rules",target:`Vercel project ${this.client.projectId}`,changes:l,riskLevel:C,skipConfirmation:n.force||!1,dryRun:!1,allowDeletions:n.allowDeletions||!1}))throw new Error("Operation cancelled by user");let O=c.map(g=>A.unifiedToVercel(g).result),F=d.map(g=>A.unifiedToVercel(g).result),se=u.map(g=>A.unifiedToVercel(g).result),ae=f.map(g=>({id:g.id||"",ip:g.ip,hostname:g.hostname||"",action:g.action,notes:g.notes})),fe=y.map(g=>({id:g.id||"",ip:g.ip,hostname:g.hostname||"",action:g.action,notes:g.notes})),k=w.map(g=>({id:g.id||"",ip:g.ip,hostname:g.hostname||"",action:g.action,notes:g.notes})),le=[],ge=[],me=[],Ie=[],Re=[],P=[],m=[],$=(g,I)=>`${g}: ${I instanceof Error?I.message:String(I)}`,D=[];Pt(t.rules)&&t.rules.length>c.length&&D.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 he=[];for(let g of se)try{r.debug(`Deleting custom rule: ${g.id}`),await this.client.deleteFirewallRule(g),me.push(g),r.debug(`Custom rule deleted: ${g.id}`)}catch(I){r.error(`Failed to delete custom rule ${g.id}:`,I),m.push($(`Failed to delete custom rule ${g.id}`,I))}for(let g of k)try{r.debug(`Deleting IP blocking rule: ${g.id}`),await this.client.deleteIPBlockingRule(g),P.push(g),r.debug(`IP blocking rule deleted: ${g.id}`)}catch(I){r.error(`Failed to delete IP blocking rule ${g.id}:`,I),m.push($(`Failed to delete IP blocking rule ${g.id}`,I))}for(let g of O)try{r.debug(`Adding new custom rule: ${g.name}`);let I=await this.client.createFirewallRule(g);le.push(I),I.id&&I.id!==g.id&&he.push({oldId:g.id||void 0,newId:I.id,name:g.name}),r.debug(`New custom rule added: ${I.id}`)}catch(I){r.error(`Failed to add custom rule "${g.name}":`,I),m.push($(`Failed to add custom rule "${g.name}"`,I))}for(let g of ae)try{r.debug(`Adding new IP blocking rule: ${g.ip}`);let I=await this.client.createIPBlockingRule(g);Ie.push(I),r.debug(`New IP blocking rule added: (hostname): ${I.hostname} (ip): ${I.ip}`)}catch(I){r.error(`Failed to add IP blocking rule ${g.ip}:`,I),m.push($(`Failed to add IP blocking rule ${g.ip}`,I))}for(let g of F)try{r.debug(`Updating custom rule: ${g.id}`);let I=await this.client.updateFirewallRule(g);ge.push(I),r.debug(`Custom rule updated: ${I.id}`)}catch(I){r.error(`Failed to update custom rule ${g.id}:`,I),m.push($(`Failed to update custom rule ${g.id}`,I))}for(let g of fe)try{r.debug(`Updating IP blocking rule: ${g.id}`);let I=await this.client.updateIPBlockingRule(g);Re.push(I),r.debug(`IP blocking rule updated: ${I.id}`)}catch(I){r.error(`Failed to update IP blocking rule ${g.id}:`,I),m.push($(`Failed to update IP blocking rule ${g.id}`,I))}r.debug(`${re.underline("Custom Rules:")} ${re.green("Added:")} ${re.green(le.length)}, ${re.cyan("Updated:")} ${re.cyan(ge.length)}, ${re.red("Deleted:")} ${re.red(me.length)}`),r.debug(`${re.underline("IP Rules:")} ${re.green("Added:")} ${re.green(Ie.length)}, ${re.cyan("Updated:")} ${re.cyan(Re.length)}, ${re.red("Deleted:")} ${re.red(P.length)}`);let tt=p,it,xe=[...D];try{let g=await this.client.fetchFirewallConfig();tt=g.version,it=g.updatedAt;let I=new Set(g.rules.map(W=>W.id)),vi=new Set(g.ips.map(W=>W.id));for(let W of[...le,...ge])W.id&&!I.has(W.id)&&xe.push(`Rule "${W.name}" (${W.id}) was synced but is missing from the post-sync remote config \u2014 a subsequent sync may re-create or misdiff it.`);for(let W of me)W.id&&I.has(W.id)&&xe.push(`Rule "${W.name}" (${W.id}) was deleted but still appears in the post-sync remote config.`);for(let W of[...Ie,...Re])W.id&&!vi.has(W.id)&&xe.push(`IP rule "${W.ip}" (${W.id}) was synced but is missing from the post-sync remote config \u2014 a subsequent sync may re-create or misdiff it.`);for(let W of P)W.id&&vi.has(W.id)&&xe.push(`IP rule "${W.ip}" (${W.id}) was deleted but still appears in the post-sync remote config.`)}catch(g){r.error("Failed to fetch updated firewall configuration version after sync:",g),m.push($("Failed to fetch updated configuration version after sync",g))}return{success:m.length===0,rulesAdded:le.length,rulesUpdated:ge.length,rulesDeleted:me.length,ipsAdded:Ie.length,ipsUpdated:Re.length,ipsDeleted:P.length,version:tt,updatedAt:it,idRemappings:he.length>0?he:void 0,errors:m.length>0?m: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}`);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(w=>{let C=A.vercelToUnified(w);return C.warnings.length>0&&C.warnings.forEach(x=>{let{TranslationWarningSystem:O}=(de(),Ce(Oe)),F=O.formatWarning(x);r.warn(`Rule ${w.name}:
|
|
26
26
|
${F}`)}),C.result}),{toAdd:l,toUpdate:c,toDelete:d}=this.diffRules(st(o.data.rules),a),u=s.ips.map(w=>A.vercelIPToUnified(w)),{ipsToAdd:p,ipsToUpdate:f,ipsToDelete:y}=this.diffIPRules(o.data.ips||[],u);return{version:s.version,rulesToAdd:l,rulesToUpdate:c,rulesToDelete:d,ipsToAdd:p,ipsToUpdate:f,ipsToDelete:y,hasChanges:l.length>0||c.length>0||d.length>0||p.length>0||f.length>0||y.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(pe(l),pe(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(pe(l),pe(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}}};T();var ct=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 Me(t,n,e);return new Ge(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 Me(n,o,t);return new Ge(s)}static create(e,t,n){return this.fromConfig({projectId:e,teamId:t,token:n})}};import{createHash as Zr}from"crypto";T();import Ke from"chalk";var h=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(Ke.red.bold(`[${this.code}] ${this.message}`)),e.push(""),this.suggestion&&(e.push(Ke.yellow.bold("Suggestion:")),e.push(` ${this.suggestion}`),e.push("")),this.details&&Object.keys(this.details).length>0&&(e.push(Ke.dim("Details:")),Object.entries(this.details).forEach(([t,n])=>{let o=typeof n=="object"?JSON.stringify(n,null,2).split(`
|
|
27
27
|
`).join(`
|
|
@@ -122,9 +122,9 @@ Did you mean one of these?`)),l.forEach((c,d)=>{let u=c.id?` (${c.id})`:"";r.log
|
|
|
122
122
|
Use --id for exact matching, or --interactive to select from a list.`))),process.exit(1)}}else if(i.id&&i.id.length>0){let l=new Set(i.id);o=n.filter(u=>u.id&&l.has(u.id));let c=new Set(o.map(u=>u.id)),d=i.id.filter(u=>!c.has(u));d.length>0&&r.warn(q.yellow(`No rules found for IDs: ${d.join(", ")}`)),o.length===0&&(r.error(q.red("No matching rules found for the given IDs.")),process.exit(1))}else if(i.disabled){if(o=n.filter(l=>!l.active),o.length===0){r.info("No disabled rules found.");return}}else i.all?o=[...n]:(r.error("Specify --name, --id, --disabled, --all, or --interactive to select rules to remove."),process.exit(1));if(o.length===0){r.info("No rules selected for removal.");return}if(o.length>=5&&!i.dryRun&&!i.force&&r.log(q.yellow(`
|
|
123
123
|
\u{1F4A1} Tip: Run ${q.cyan("doorman backup")} before bulk removal operations.`)),r.log(q.cyan(`
|
|
124
124
|
${i.dryRun?"Would remove":"Removing"} ${o.length} rule(s):`)),di(o),i.dryRun){r.log(q.dim(`
|
|
125
|
-
Dry run \u2014 no changes made.`));return}if(!await qn(o.length,i.force||!1,i.all||!1)){r.info("Cancelled.");return}let s=new Set(o),a={...t,rules:n.filter(l=>!s.has(l))};a.rules.length===0&&r.warn(q.yellow("\u26A0\uFE0F Configuration will have no rules after removal.")),await G(a,i.config),r.success(q.green(`\u2714 Removed ${o.length} rule(s) from configuration`)),di(o)}r.log(""),r.log(q.dim(`Run ${q.cyan("doorman sync")} to deploy these changes.`))}catch(e){Q(e,"removing rule")}};var pi={};J(pi,{builder:()=>oa,command:()=>ia,desc:()=>na,handler:()=>ra});T();import
|
|
126
|
-
`)),r.log(
|
|
127
|
-
`),r.log(
|
|
125
|
+
Dry run \u2014 no changes made.`));return}if(!await qn(o.length,i.force||!1,i.all||!1)){r.info("Cancelled.");return}let s=new Set(o),a={...t,rules:n.filter(l=>!s.has(l))};a.rules.length===0&&r.warn(q.yellow("\u26A0\uFE0F Configuration will have no rules after removal.")),await G(a,i.config),r.success(q.green(`\u2714 Removed ${o.length} rule(s) from configuration`)),di(o)}r.log(""),r.log(q.dim(`Run ${q.cyan("doorman sync")} to deploy these changes.`))}catch(e){Q(e,"removing rule")}};var pi={};J(pi,{builder:()=>oa,command:()=>ia,desc:()=>na,handler:()=>ra});T();import U from"chalk";var ia="setup",na="Show setup instructions and helpful links for Doorman",oa={},ra=async i=>{r.log(""),r.log(U.bold.cyan("\u{1F6AA} Doorman Setup Guide")),r.log(U.dim(`Everything you need to get started with firewall management
|
|
126
|
+
`)),r.log(U.bold("\u{1F4CB} Prerequisites")),r.log("\u2022 Vercel account with a deployed project"),r.log("\u2022 Project with Pro plan or higher (firewall feature requirement)"),r.log(`\u2022 Admin access to the project/team
|
|
127
|
+
`),r.log(U.bold("\u{1F50D} Finding Your Project Information")),r.log(""),r.log(U.cyan("1. Project ID:")),r.log(" \u2022 Go to https://vercel.com/dashboard"),r.log(" \u2022 Select your project"),r.log(" \u2022 Navigate to Settings \u2192 General"),r.log(" \u2022 Copy the Project ID from the top of the page"),r.log(""),r.log(U.cyan("2. Team ID (if using team account):")),r.log(" \u2022 Go to https://vercel.com/dashboard"),r.log(" \u2022 Click on your team name in the top-left"),r.log(" \u2022 Go to Team Settings \u2192 General"),r.log(" \u2022 Copy the Team ID"),r.log(" \u2022 Leave empty to use your Vercel default team (every account has one)"),r.log(" \u2022 Belong to more than one team? Set this explicitly, or doorman will use whichever is your default"),r.log(""),r.log(U.bold("\u{1F511} Creating API Token")),r.log(""),r.log(U.cyan("Steps:")),r.log(" 1. Visit https://vercel.com/account/tokens"),r.log(' 2. Click "Create Token"'),r.log(' 3. Name: "Doorman Firewall Management"'),r.log(" 4. Expiration: Set as needed (90 days recommended)"),r.log(" 5. Scope: Select your project or team"),r.log(" 6. Copy the generated token"),r.log(""),r.log(U.cyan("Required Permissions:")),r.log(" \u2022 Read access to project settings"),r.log(" \u2022 Write access to firewall configuration"),r.log(""),r.log(U.bold("\u{1F30D} Environment Setup")),r.log(""),r.log(U.cyan("Set your API token as environment variable:")),r.log(""),r.log(U.green("# For current session:")),r.log('export VERCEL_TOKEN="your-token-here"'),r.log(""),r.log(U.green("# For permanent setup (add to ~/.bashrc or ~/.zshrc):")),r.log(`echo 'export VERCEL_TOKEN="your-token-here"' >> ~/.bashrc`),r.log(""),r.log(U.green("# Or create a .env file in your project:")),r.log('echo "VERCEL_TOKEN=your-token-here" > .env'),r.log(""),r.log(U.bold("\u{1F680} Quick Start Commands")),r.log(""),r.log(U.cyan("1. Initialize configuration:")),r.log(" doorman init --interactive"),r.log(""),r.log(U.cyan("2. Validate setup:")),r.log(" doorman validate"),r.log(""),r.log(U.cyan("3. Check current status:")),r.log(" doorman status"),r.log(""),r.log(U.cyan("4. View existing rules:")),r.log(" doorman list"),r.log(""),r.log(U.cyan("5. Sync your configuration:")),r.log(" doorman sync"),r.log(""),r.log(U.bold("\u{1F527} Common Issues")),r.log(""),r.log(U.yellow('\u274C "Project not found" error:')),r.log(" \u2022 Double-check your Project ID"),r.log(" \u2022 Ensure your token has access to the project"),r.log(" \u2022 Verify the project has Pro plan or higher"),r.log(""),r.log(U.yellow('\u274C "Unauthorized" error:')),r.log(" \u2022 Check your VERCEL_TOKEN is set correctly"),r.log(" \u2022 Ensure token hasn't expired"),r.log(" \u2022 Verify token has firewall permissions"),r.log(""),r.log(U.yellow('\u274C "Firewall not available" error:')),r.log(" \u2022 Upgrade to Pro plan or higher"),r.log(" \u2022 Contact Vercel support if issue persists"),r.log(""),r.log(U.bold("\u{1F4DA} Additional Resources")),r.log(""),r.log(U.cyan("Documentation:")),r.log(" \u2022 Doorman Docs: https://doorman.griffen.codes"),r.log(" \u2022 Vercel Firewall: https://vercel.com/docs/security/vercel-firewall"),r.log(" \u2022 API Reference: https://vercel.com/docs/rest-api/endpoints/firewall"),r.log(""),r.log(U.cyan("Support:")),r.log(" \u2022 GitHub Issues: https://github.com/gfargo/doorman/issues"),r.log(" \u2022 Vercel Support: https://vercel.com/help"),r.log(""),r.log(U.bold("\u{1F512} Security Best Practices")),r.log(""),r.log("\u2022 Store API tokens securely (use environment variables)"),r.log("\u2022 Set token expiration dates"),r.log("\u2022 Use principle of least privilege for token scopes"),r.log("\u2022 Regularly rotate API tokens"),r.log("\u2022 Test rules in staging before production"),r.log("\u2022 Keep backups of working configurations"),r.log(""),r.log(U.bold.green("\u2705 Ready to get started?")),r.log(U.dim("Run: ")+U.cyan("doorman init --interactive")),r.log("")};var fi={};J(fi,{builder:()=>la,command:()=>sa,desc:()=>aa,handler:()=>ca});T();import N from"chalk";var sa="status",aa="Show sync status between local and remote firewall configuration",la={config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:X,...ee,format:{alias:"f",type:"string",choices:["table","json"],description:"Output format",default:"table"},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}},ca=async i=>{await te({config:i.config,provider:i.provider,...K(i),debug:i.debug,ci:i.ci,errorContext:"checking status"},async({config:e,provider:t})=>{await da(t,we(e),i)})};async function da(i,e,t){let n=t.format==="json";n||r.start("Checking sync status...");let o=await i.getChanges(e),s=i.getHealthScore(e),a=e.metadata?.version,l=o.version!==void 0&&a!==void 0&&a!==o.version,c=!o.hasChanges&&!l;if(n){r.log(JSON.stringify({provider:i.name,inSync:c,version:{local:a??null,remote:o.version??null,matches:o.version!==void 0?!l:null},rules:{toAdd:o.rulesToAdd.length,toUpdate:o.rulesToUpdate.length,toDelete:o.rulesToDelete.length},ips:{toAdd:o.ipsToAdd?.length??0,toUpdate:o.ipsToUpdate?.length??0,toDelete:o.ipsToDelete?.length??0},health:s},null,2));return}r.log(N.bold(`
|
|
128
128
|
\u{1F4CA} ${i.name} Sync Status Summary
|
|
129
129
|
`)),o.version!==void 0&&(r.log(`${N.dim("Local Version:")} ${N.yellow(a??"unknown")}`),r.log(`${N.dim("Remote Version:")} ${N.yellow(o.version)}`),r.log(`${N.dim("Version Status:")} ${l?N.red("Out of sync"):N.green("In sync")}`),r.log("")),r.log(`${N.dim("Rules:")}`),r.log(` ${N.green("+")} ${o.rulesToAdd.length} to add`),r.log(` ${N.cyan("~")} ${o.rulesToUpdate.length} to update`),r.log(` ${N.red("-")} ${o.rulesToDelete.length} to delete`),r.log(`${N.dim("IPs:")}`),r.log(` ${N.green("+")} ${o.ipsToAdd?.length??0} to add`),r.log(` ${N.cyan("~")} ${o.ipsToUpdate?.length??0} to update`),r.log(` ${N.red("-")} ${o.ipsToDelete?.length??0} to delete`),r.log(""),c?r.success(N.green("\u2705 Everything is in sync!")):r.warn(N.yellow("\u26A0\uFE0F Changes detected. Run `sync` to apply changes.")),r.log(`
|
|
130
130
|
`+N.bold("\u{1F3E5} Configuration Health Check")),r.log(`${N.dim("Score:")} ${s.score}/100 (${s.grade})`),s.issues.forEach(d=>{let u=d.severity==="error"?N.red("\u2717"):d.severity==="warning"?N.yellow("\u26A0"):N.dim("\u2139");r.log(` ${u} [${d.category}] ${d.message}`),d.suggestion&&r.log(` ${N.dim(d.suggestion)}`)}),s.recommendations.length>0&&(r.log(N.dim(`
|