@gfargo/doorman 3.15.6 → 3.15.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/run.js CHANGED
@@ -22,7 +22,7 @@ Dry run - The following IP rule would be added:`)),en(t),r.log(""),r.log(V.defau
22
22
  Dry run - The following rule would be added:`)),Xi(t),r.log(""),r.log(V.default.dim(JSON.stringify(t,null,2)));return}r.start("Loading configuration...");let o=await Z(i.config,"raw"),s=o.rules||[],a=cr({...o,rules:s},t);if(a){if(r.warn(V.default.yellow(`\u26A0\uFE0F ${a}`)),i.ci||!process.stdin.isTTY){r.info(V.default.dim("Skipping \u2014 non-interactive and a conflicting rule already exists. Rule not added."));return}if(!await C("Proceed anyway?",{type:"confirm",initial:!1})){r.info("Cancelled.");return}}let l={...o,rules:[...s,t]};await Y(l,i.config),r.success(V.default.green(`\u2714 Rule "${t.name}" added to configuration`)),Xi(t)}r.log(""),r.log(V.default.dim(`Run ${V.default.cyan("doorman sync")} to deploy this rule.`))}catch(e){re(e,"adding rule")}};var hi={};oe(hi,{builder:()=>xs,command:()=>Us,desc:()=>Ls,handler:()=>Ds});var ne=F(require("chalk")),Ae=require("fs"),lt=require("path"),lo=require("consola");_();Pe();var pt=require("fs"),ii=require("path");function on(i=process.cwd()){return(0,pt.existsSync)((0,ii.join)(i,".git"))}function rn(i,e=process.cwd()){let t=(0,ii.join)(e,".gitignore");if(!(0,pt.existsSync)(t))return!1;let n=i.replace(/^\.\//,"").replace(/^\//,"").replace(/\/$/,""),o;try{o=(0,pt.readFileSync)(t,"utf8").split(`
23
23
  `)}catch{return!1}return o.some(s=>{let a=s.trim();if(!a||a.startsWith("#"))return!1;let l=a.replace(/^\//,"").replace(/\/$/,"");return l===n||n.startsWith(`${l}/`)})}var se={type:"string",choices:H,description:"Firewall provider (auto-detected)"};var ao=require("consola");_();var ro=F(require("chalk"));_();Pe();var sn=require("readline");function ur(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 pr(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 an(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,sn.emitKeypressEvents)(t),t.setRawMode(!0),t.resume();let o="",s=()=>{t.removeListener("keypress",a),t.setRawMode(n??!1)},a=(l,c)=>{let d=ur(l,c),u=pr(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
24
  `),u.cancelled&&process.exit(0),e(o))};t.on("keypress",a)})}_();var ln={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 cn={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 dn={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 un={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 pn(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 fn(i,e){return i.fields.filter(t=>t.required&&!e[t.key])}var Ze={vercel:ln,cloudflare:cn,fastly:dn,gcp:un};var Lt=class{static detectionField(e){return Ze[e].fields.find(t=>t.required&&t.configKey)}static detect(e){let t=[];if(e&&"provider"in e){let o=e.provider;if(typeof o=="string"&&this.isValidProvider(o))return t.push(`Explicit provider specified in config: ${e.provider}`),{provider:o,confidence:"high",reasons:t};r.warn(`Invalid provider specified in config: ${String(e.provider)}`)}if(e&&"providers"in e&&typeof e.providers=="object"&&e.providers!==null){let o=e.providers;for(let s of H){let a=this.detectionField(s);if(!a?.configKey)continue;if(typeof(o[s]||{})[a.configKey]=="string")return t.push(`${a.label} found in config`),{provider:s,confidence:"high",reasons:t}}}if(e&&"projectId"in e&&typeof e.projectId=="string")return t.push("Legacy Vercel project ID found in config"),{provider:"vercel",confidence:"high",reasons:t};let n=this.detectFromEnvironment();return n.provider?n:(r.debug("Unable to auto-detect provider"),{provider:null,confidence:"low",reasons:["No provider information found in config or environment"]})}static detectFromEnvironment(){let e=[],t=process.env.DOORMAN_PROVIDER;if(t&&this.isValidProvider(t))return e.push(`Provider set via DOORMAN_PROVIDER environment variable: ${t}`),{provider:t,confidence:"high",reasons:e};for(let n of H){let o=this.detectionField(n);if(!(!o||!process.env[o.envVar]))return e.push(`${o.label} found in environment (${o.envVar})`),{provider:n,confidence:"medium",reasons:e}}return{provider:null,confidence:"low",reasons:["No provider credentials found in environment"]}}static isValidProvider(e){return H.includes(e)}static getProvider(e,t="vercel"){let n=this.detect(e);return n.provider?(r.debug(`Provider detected: ${n.provider} (${n.confidence} confidence)`),n.reasons.length>0&&r.debug(`Detection reasons: ${n.reasons.join(", ")}`),n.provider):(r.debug(`No provider detected, using fallback: ${t}`),t)}static detectAll(e){let t=new Set;if(e&&"provider"in e){let n=e.provider;typeof n=="string"&&this.isValidProvider(n)&&t.add(n)}if(e&&"providers"in e&&typeof e.providers=="object"&&e.providers!==null){let n=e.providers;for(let o of H){let s=this.detectionField(o);if(!s?.configKey)continue;typeof(n[o]||{})[s.configKey]=="string"&&t.add(o)}}e&&"projectId"in e&&typeof e.projectId=="string"&&t.add("vercel");for(let n of H){let o=this.detectionField(n);o&&process.env[o.envVar]&&t.add(n)}return Array.from(t)}};var ni=F(require("chalk"));_();Pe();var xt=i=>({$schema:Ni,firewallEnabled:!0,rules:[],ips:[],...i});_();var ke=class{baseUrl;providerName;rateLimitInfo={};constructor(e,t){this.baseUrl=e,this.providerName=t}async makeRequest(e,t={}){let n=`${this.baseUrl}${e}`,{timeout:o=3e4,retries:s=3,retryDelay:a=1e3,...l}=t,c=null,d=null,u=null,p=()=>{u&&(clearTimeout(u),u=null),d&&(d.abort(),d=null)};try{for(let f=0;f<=s;f++)try{d=new AbortController,u=setTimeout(()=>{d&&d.abort()},o);let g=await fetch(n,{...l,headers:{"Content-Type":"application/json",...await this.getAuthHeaders(),...l.headers},signal:d.signal});if(u&&(clearTimeout(u),u=null),this.updateRateLimitInfo(g),g.status===429){let v=this.calculateRateLimitWait(f,g);if(f===s)throw new Error(`${this.providerName} API rate limit exceeded after ${s+1} attempt(s) (429 Too Many Requests). Retry after ~${Math.round(v/1e3)}s.`);r.warn(`Rate limit exceeded for ${this.providerName}. Waiting ${v}ms before retry (attempt ${f+1}/${s+1})...`),await this.delay(v);continue}if(!g.ok)throw await this.handleErrorResponse(g);let m;try{let v=await g.text();v.trim()?m=JSON.parse(v):m={}}catch(v){throw r.error(`Failed to parse response from ${n}:`,v),new Error(`Invalid JSON response from ${this.providerName} API: ${v instanceof Error?v.message:String(v)}`)}return this.isLargeResponse(m)&&(r.debug(`Large response detected from ${n}, validating structure...`),this.validateLargeResponse(m)),m}catch(g){if(c=g,u&&(clearTimeout(u),u=null),this.isNonRetryableError(g)||f===s)throw g;let m=a*Math.pow(2,f),v=Math.random()*.1*m,z=Math.min(m+v,3e4);r.debug(`Request failed (attempt ${f+1}/${s+1}). Retrying in ${Math.round(z)}ms...`),r.debug(`Error details: ${g instanceof Error?g.message:String(g)}`),await this.delay(z)}throw c||new Error("Request failed after all retries")}finally{p()}}async get(e,t){return this.makeRequest(e,{...t,method:"GET"})}async post(e,t,n){return this.makeRequest(e,{...n,method:"POST",body:t?JSON.stringify(t):void 0})}async put(e,t,n){return this.makeRequest(e,{...n,method:"PUT",body:t?JSON.stringify(t):void 0})}async patch(e,t,n){return this.makeRequest(e,{...n,method:"PATCH",body:t?JSON.stringify(t):void 0})}async delete(e,t){return this.makeRequest(e,{...t,method:"DELETE"})}updateRateLimitInfo(e){let t=e.headers.get("X-RateLimit-Limit"),n=e.headers.get("X-RateLimit-Remaining"),o=e.headers.get("X-RateLimit-Reset");t&&(this.rateLimitInfo.limit=parseInt(t,10)),n&&(this.rateLimitInfo.remaining=parseInt(n,10)),o&&(this.rateLimitInfo.reset=parseInt(o,10)),this.rateLimitInfo.remaining&&this.rateLimitInfo.remaining<10&&r.warn(`Approaching rate limit for ${this.providerName}: ${this.rateLimitInfo.remaining} requests remaining`)}calculateRateLimitWait(e=0,t){let o=t?this.parseRetryAfter(t.headers.get("Retry-After")):null;if(o!==null)return Math.min(Math.max(o,1e3),6e4);if(this.rateLimitInfo.reset){let l=Math.floor(Date.now()/1e3),c=(this.rateLimitInfo.reset-l)*1e3;return Math.min(Math.max(c,1e3),6e4)}let a=5e3*Math.pow(2,e);return Math.min(a,6e4)}parseRetryAfter(e){if(!e)return null;let t=Number(e);if(Number.isFinite(t))return t*1e3;let n=Date.parse(e);return Number.isNaN(n)?null:n-Date.now()}async handleErrorResponse(e){let t=`${this.providerName} API error: ${e.status} ${e.statusText}`,n=t;try{let o=await e.json();if(typeof o=="object"&&o!==null){let s=o;if(typeof s.error=="string")n=`${t} - ${s.error}`;else if(typeof s.message=="string")n=`${t} - ${s.message}`;else if(Array.isArray(s.errors)){let a=s.errors.map(l=>typeof l=="object"&&l!==null&&"message"in l?String(l.message):JSON.stringify(l)).join(", ");n=`${t} - ${a}`}}}catch{}return r.error(n),new Error(n)}isNonRetryableError(e){if(typeof e=="object"&&e!==null&&e.name==="AbortError")return!0;let t=e.message;return typeof t=="string"&&t.includes("API error: 4")?!t.includes("429"):!1}delay(e){return new Promise(t=>setTimeout(t,e))}getRateLimitInfo(){return{...this.rateLimitInfo}}isLargeResponse(e){if(!e||typeof e!="object")return!1;let t=(n,o=0)=>{if(o>10)return!1;for(let[,s]of Object.entries(n))if(Array.isArray(s)&&s.length>1e3||typeof s=="object"&&s!==null&&t(s,o+1))return!0;return!1};return t(e)}validateLargeResponse(e){if(!e||typeof e!="object")return;let t=(o,s)=>{o.length>5e3&&r.warn(`Large array detected at ${s} with ${o.length} items. This may impact performance.`);let a=Math.min(10,o.length),l=o[0];if(l&&typeof l=="object"){let c=Object.keys(l);for(let d=1;d<a;d++){let u=o[d];if(!u||typeof u!="object"){r.warn(`Inconsistent array structure at ${s}[${d}]: expected object, got ${typeof u}`);continue}let p=Object.keys(u),f=c.filter(g=>!p.includes(g));f.length>0&&r.warn(`Inconsistent array structure at ${s}[${d}]: missing keys ${f.join(", ")}`)}}},n=(o,s="root",a=0)=>{if(!(a>10))for(let[l,c]of Object.entries(o)){let d=`${s}.${l}`;Array.isArray(c)?t(c,d):typeof c=="object"&&c!==null&&n(c,d,a+1)}};n(e)}};var gn=process.env.DOORMAN_VERCEL_API_BASE_URL||"https://api.vercel.com/v1/security/firewall/config",Ye=class extends ke{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?`${gn}/${t}`:gn;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,assumeYes:s=!1}=n??{},a=await this.get(this.getUrl(t));if(r.debug("Config Version:",t??"latest"),r.debug("Fetched Config:",t?a:a.active),t)return a;if(!a||"active"in a&&a.active===null){if(r.warn(ni.default.bold("No firewall configuration found.")),!o)return r.debug("Skipping create-configuration prompt for read-only fetch."),this.emptyConfigPlaceholder();if(s||await C("Would you like to create one?",{type:"confirm"})){r.debug("Creating new empty firewall configuration...");let c=await this.putEmptyConfig();return r.debug("Empty firewall configuration created successfully"),r.debug(`New configuration version: ${ni.default.yellow(c.version)}`),c}else return a.active}return a.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}=xt();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 ge=F(require("chalk"));_();_();var Se=class{validateConfig(e){let t=[],n=[];if(e||t.push({path:"root",message:"Configuration is required",code:"CONFIG_REQUIRED"}),e&&!e.rules&&t.push({path:"rules",message:"Rules array is required",code:"RULES_REQUIRED"}),e&&e.rules&&!Array.isArray(e.rules)&&t.push({path:"rules",message:"Rules must be an array",code:"RULES_INVALID_TYPE"}),e&&Array.isArray(e.rules)){let o=new Map;e.rules.forEach((s,a)=>{let l=s.id||s.name;if(!l)return;let c=o.get(l);if(c===void 0){o.set(l,a);return}let d=e.rules[c];n.push({path:`rules[${a}]`,message:s.id?`Rule "${s.name}" has the same id ("${s.id}") as rules[${c}] ("${d?.name}") \u2014 they will collide during sync diffing and one may be silently skipped.`:`Rule "${s.name}" has the same name as rules[${c}] and neither has an explicit id \u2014 they will collide during sync diffing and one may be silently skipped. Add unique ids to disambiguate.`,code:"DUPLICATE_RULE_IDENTIFIER"})})}return{valid:t.length===0,errors:t,warnings:n}}getHealthScore(e){let t=[],n=100;if(!e)return{score:0,grade:"poor",issues:[{severity:"error",category:"configuration",message:"No configuration found"}],recommendations:["Create a configuration file"]};if((!e.rules||e.rules.length===0)&&(n-=20,t.push({severity:"warning",category:"rules",message:"No rules defined",suggestion:"Add security rules to protect your application"})),e.rules&&e.rules.length>0){let a=e.rules.filter(l=>!l.description);a.length>0&&(n-=5,t.push({severity:"info",category:"maintainability",message:`${a.length} rule(s) missing descriptions`,suggestion:"Add descriptions to improve maintainability"}))}if(e.rules&&e.rules.length>0){let a=e.rules.filter(l=>l.active===!1||l.enabled===!1);a.length>0&&(n-=5,t.push({severity:"info",category:"maintenance",message:`${a.length} disabled rule(s) found`,suggestion:"Review and remove unused rules"}))}let o=this.calculateGrade(n),s=this.generateRecommendations(e,t);return{score:Math.max(0,n),grade:o,issues:t,recommendations:s}}diffItems(e,t,n,o="id"){let s=[],a=[],l=[],c=d=>d[o];for(let d of e){let u=t.find(p=>c(p)===c(d));u?n(d,u)||a.push(d):s.push(d)}for(let d of t)e.find(p=>c(p)===c(d))||l.push(d);return{toAdd:s,toUpdate:a,toDelete:l}}assertManagedRulesSupported(e){if(!(!e.managedRules||e.managedRules.length===0)&&!this.getSupportedFeatures().supportsManagedRules)throw new Error(`${this.name} does not support managed rule groups, but this config declares ${e.managedRules.length}. Remove \`managedRules\` from the config, or switch to a provider that supports them (currently Cloudflare only).`)}validateRuleCount(e,t){if(t&&e>t)throw new Error(`Rule count (${e}) exceeds provider limit (${t})`);t&&e>t*.9&&r.warn(`Approaching rule limit: ${e}/${t} rules`)}updateMetadata(e,t){return{...e,metadata:{...e.metadata,version:t,updatedAt:new Date().toISOString()}}}calculateGrade(e){return e>=80?"excellent":e>=60?"good":e>=40?"fair":"poor"}generateRecommendations(e,t){let n=[];return t.some(o=>o.category==="rules")&&n.push("Consider using predefined templates to get started"),t.some(o=>o.category==="maintainability")&&n.push("Add descriptions to all rules for better documentation"),t.some(o=>o.category==="maintenance")&&n.push("Remove or enable disabled rules to keep configuration clean"),n}logSyncStats(e){r.info(`Sync complete for ${this.name}:`),r.info(` Rules added: ${e.rulesAdded}`),r.info(` Rules updated: ${e.rulesUpdated}`),r.info(` Rules deleted: ${e.rulesDeleted}`),e.ipsAdded!==void 0&&(r.info(` IPs added: ${e.ipsAdded}`),r.info(` IPs updated: ${e.ipsUpdated}`),r.info(` IPs deleted: ${e.ipsDeleted}`)),e.managedRulesAdded!==void 0&&(r.info(` Managed rule groups added: ${e.managedRulesAdded}`),r.info(` Managed rule groups updated: ${e.managedRulesUpdated}`),r.info(` Managed rule groups deleted: ${e.managedRulesDeleted}`)),e.version&&r.info(` New version: ${e.version}`)}};fe();function mn(i){let e=[],t=[];i.conditionGroup.forEach((s,a)=>{for(let l of s.conditions){let c=gr(l.op);t.push({field:yr(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 hn(i){let e=[],t=fr(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:hr(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 yn(i){return{id:i.id,ip:i.ip,...i.hostname?{hostname:i.hostname}:{},...i.notes?{notes:i.notes}:{},action:i.action}}function fr(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=wr(l.field);if(!c){t.push(U.createUnsupportedFeatureWarning(`condition field '${l.field}'`,"unified config","Vercel",e,l.field));continue}let d=mr(l.operator);if(!d){t.push(U.createUnsupportedFeatureWarning(`operator '${l.operator}'`,"unified config","Vercel",e,l.field));continue}a.push({op:d.op,neg:d.forceNeg!==!!l.negated,type:c,key:l.key,value:l.value})}a.length>0&&o.push({conditions:a})}return o}function gr(i){return{eq:"eq",pre:"starts_with",suf:"ends_with",inc:"in",sub:"contains",re:"matches",ex:"exists",nex:"not_exists"}[i]||"eq"}function mr(i){return{eq:{op:"eq",forceNeg:!1},ne:{op:"eq",forceNeg:!0},starts_with:{op:"pre",forceNeg:!1},ends_with:{op:"suf",forceNeg:!1},in:{op:"inc",forceNeg:!1},not_in:{op:"inc",forceNeg:!0},contains:{op:"sub",forceNeg:!1},not_contains:{op:"sub",forceNeg:!0},matches:{op:"re",forceNeg:!1},exists:{op:"ex",forceNeg:!1},not_exists:{op:"nex",forceNeg:!1}}[i]??null}function hr(i){return{log:"log",deny:"deny",challenge:"challenge",bypass:"bypass",rate_limit:"rate_limit",redirect:"redirect",allow:"bypass",block:"deny"}[i]}function yr(i){return{host:"host",path:"path",method:"method",header:"header",query:"query",cookie:"cookie",ip_address:"ip",user_agent:"user_agent",geo_country:"country",geo_city:"city",geo_as_number:"asn",scheme:"scheme",region:"vercel_region",geo_country_region:"region"}[i]||i}var 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",vercel_region:"region",region:"geo_country_region",protocol:"protocol",environment:"environment",geo_continent:"geo_continent",ja4_digest:"ja4_digest",ja3_digest:"ja3_digest",rate_limit_api_id:"rate_limit_api_id"};function wr(i){return Rr[i]??null}fe();_();function Je(i){return i.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}function Rn(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 ft=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 Cr=new Set(["ip.src"]),br="http.request.uri.args",vr="http.request.headers",Ir="http.request.cookies",Qe=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=ft.toCloudflare(e.type,e.key),n=this.buildExistsOrComparisonExpression(t,e.op,()=>`${this.mapVercelOperator(e.op)} ${this.formatValue(t,e.value)}`);return e.neg&&(n=`not (${n})`),n}static fromUnifiedConditions(e,t="AND"){if(!e||e.length===0)throw new Error("At least one condition is required");if(!e.some(a=>a.group!==void 0)){let a=e.map(c=>this.fromUnifiedCondition(c)),l=t==="AND"?" and ":" or ";return a.length>1?`(${a.join(l)})`:a[0]}let o=new Map;for(let a of e){let l=a.group??0,c=o.get(l);c?c.push(a):o.set(l,[a])}let s=Array.from(o.values()).map(a=>this.combineWithAnd(a.map(l=>this.fromUnifiedCondition(l))));return s.length>1?this.combineWithOr(s):s[0]}static fromUnifiedCondition(e){if(e.key&&e.field==="query")return this.buildKeyedMapExpression(br,e.key,e);if(e.field==="header"){if(!e.key)throw new Error('A "header" condition requires a key naming the header');return this.buildKeyedMapExpression(vr,e.key.toLowerCase(),e)}if(e.key&&e.field==="cookie")return this.buildKeyedMapExpression(Ir,e.key,e);let t=this.mapUnifiedFieldToCloudflare(e.field);if(t===null)throw new Error(`Unsupported condition field '${e.field}' for Cloudflare \u2014 filter it out with a warning before calling fromUnifiedCondition (see unifiedToCloudflare).`);let n=this.buildUnifiedExpression(t,e.operator,e.value);return e.negated&&(n=`not (${n})`),n}static buildKeyedMapExpression(e,t,n){let o=Je(t),s=`${e}["${o}"]`,a;if(n.operator==="exists")a=`has_key(${e}, "${o}")`;else if(n.operator==="not_exists")a=`not (has_key(${e}, "${o}"))`;else if(n.operator==="not_contains")a=`not (any(${s}[*] contains ${this.formatValue(e,n.value)}))`;else if(n.operator==="not_in")a=`not (any(${s}[*] in ${this.formatValue(e,n.value)}))`;else{let l=this.mapUnifiedOperator(n.operator);a=`any(${s}[*] ${l} ${this.formatValue(e,n.value)})`}return n.negated?`not (${a})`:a}static buildExistsOrComparisonExpression(e,t,n){return t==="ex"||t==="exists"?`${e} exists`:t==="nex"||t==="not_exists"?`not (${e} exists)`:`${e} ${n()}`}static buildUnifiedExpression(e,t,n){return t==="not_contains"?`not (${e} contains ${this.formatValue(e,n)})`:t==="not_in"?`not (${e} in ${this.formatValue(e,n)})`:this.buildExistsOrComparisonExpression(e,t,()=>`${this.mapUnifiedOperator(t)} ${this.formatValue(e,n)}`)}static mapVercelOperator(e){return{eq:"eq",pre:"starts_with",suf:"ends_with",inc:"in",sub:"contains",re:"matches",ex:"exists"}[e]||e}static mapUnifiedOperator(e){return{eq:"eq",ne:"ne",contains:"contains",starts_with:"starts_with",ends_with:"ends_with",matches:"matches",in:"in",gt:"gt",ge:"ge",lt:"lt",le:"le",exists:"exists"}[e]||e}static mapUnifiedFieldToCloudflare(e){return{ip:"ip.src",country:"ip.geoip.country",region:"ip.geoip.subdivision_1",city:"ip.geoip.city",asn:"ip.geoip.asnum",path:"http.request.uri.path",host:"http.host",method:"http.request.method",header:"http.request.headers",query:"http.request.uri.query",cookie:"http.cookie",user_agent:"http.user_agent",referer:"http.referer",scheme:"ssl",port:"cf.edge.server_port",threat_score:"cf.threat_score"}[e]??null}static isFieldSupported(e){return this.mapUnifiedFieldToCloudflare(e)!==null}static formatValue(e,t){return Array.isArray(t)?`{${t.map(o=>this.formatSingleValue(e,o)).join(" ")}}`:this.formatSingleValue(e,t)}static formatSingleValue(e,t){if(typeof t=="string"){if(Cr.has(e)){if(!_e.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 j=class extends Error{},Xe=class{constructor(e){this.tokens=e}tokens;pos=0;peek(){return this.tokens[this.pos]}next(){let e=this.tokens[this.pos];if(!e)throw new j("Unexpected end of expression");return this.pos++,e}expect(e){let t=this.next();if(t.type!==e)throw new j(`Expected ${e} but got '${t.value}'`);return t}is(e,t){let n=this.peek();return!!n&&n.type===e&&n.value===t}atEnd(){return this.pos>=this.tokens.length}};function Dt(i,e,t,n){let o=[];i.forEach((l,c)=>{if(e(l)){let d=n(l,c);d&&o.push(d);return}if(t(l))for(let d of l.children){if(!e(d))return;let u=n(d,c);u&&o.push(u)}});let s=i.length;return new Set(o.map(l=>l.group)).size!==s?null:{conditions:o,conditionLogic:"OR"}}var Er={"ip.src":"ip","ip.geoip.country":"country","ip.geoip.subdivision_1":"region","ip.geoip.city":"city","ip.geoip.asnum":"asn","http.request.uri.path":"path","http.host":"host","http.request.method":"method","http.request.headers":"header","http.request.uri.query":"query","http.request.uri.args":"query","http.cookie":"cookie","http.request.cookies":"cookie","http.user_agent":"user_agent","http.referer":"referer",ssl:"scheme","cf.edge.server_port":"port","cf.threat_score":"threat_score"},vn=new Set(["eq","ne","contains","starts_with","ends_with","matches","in","gt","ge","lt","le"]),Tr=new Set(["and","or","not"]);function Ar(i){let e=[],t=0,n=i.length;for(;t<n;){let o=i[t];if(/\s/.test(o)){t++;continue}if(o===","){t++;continue}if(o==="("){e.push({type:"LPAREN",value:o}),t++;continue}if(o===")"){e.push({type:"RPAREN",value:o}),t++;continue}if(o==="{"){e.push({type:"LBRACE",value:o}),t++;continue}if(o==="}"){e.push({type:"RBRACE",value:o}),t++;continue}if(o==="["){e.push({type:"LBRACKET",value:o}),t++;continue}if(o==="]"){e.push({type:"RBRACKET",value:o}),t++;continue}if(o==='"'){let a=t+1,l="",c=!1;for(;a<n;){let d=i[a];if(d==="\\"&&a+1<n){l+=d+i[a+1],a+=2;continue}if(i[a]==='"'){c=!0,a++;break}l+=i[a],a++}if(!c)throw new j("Unterminated string literal");e.push({type:"STRING",value:Rn(l)}),t=a;continue}let s=t;for(;s<n&&!/[\s(){}[\]",]/.test(i[s]);)s++;if(s===t)throw new j(`Unexpected character '${o}' at position ${t}`);e.push({type:"WORD",value:i.slice(t,s)}),t=s}return e}function si(i){let e=[wn(i)];for(;i.is("WORD","or");)i.next(),e.push(wn(i));return e.length===1?e[0]:{type:"or",children:e}}function wn(i){let e=[Cn(i)];for(;i.is("WORD","and");)i.next(),e.push(Cn(i));return e.length===1?e[0]:{type:"and",children:e}}function Cn(i){if(i.is("WORD","not")){i.next(),i.expect("LPAREN");let e=si(i);return i.expect("RPAREN"),{type:"not",child:e}}return $r(i)}function $r(i){let e=i.peek();if(e&&e.type==="LPAREN"){i.next();let t=si(i);return i.expect("RPAREN"),t}return Sr(i)}function Pr(i){let e=i.expect("WORD");if(Tr.has(e.value))throw new j(`Expected a field reference but got keyword '${e.value}'`);if(i.peek()?.type==="LBRACKET"){i.next();let t=i.expect("STRING");return i.expect("RBRACKET"),{field:e.value,key:t.value}}return{field:e.value}}function bn(i){let e=i.next();if(e.type==="STRING")return e.value;if(e.type==="WORD")return/^-?\d+(\.\d+)?$/.test(e.value)?Number(e.value):e.value;throw new j(`Unexpected token '${e.value}' where a value was expected`)}function In(i){if(i.peek()?.type==="LBRACE"){i.next();let e=[];for(;i.peek()?.type!=="RBRACE";)e.push(bn(i));return i.expect("RBRACE"),e}return bn(i)}function _r(i){i.next(),i.expect("LPAREN");let e=i.expect("WORD");i.expect("LBRACKET");let t=i.expect("STRING");i.expect("RBRACKET"),i.expect("LBRACKET");let n=i.expect("WORD");if(n.value!=="*")throw new j(`Expected '*' in any(...) array index but got '${n.value}'`);i.expect("RBRACKET");let o=i.next();if(o.type!=="WORD"||!vn.has(o.value))throw new j(`Unsupported or unrecognized operator '${o.value}' inside any(...)`);let s=In(i);return i.expect("RPAREN"),{type:"comparison",field:e.value,key:t.value,operator:o.value,value:s}}function kr(i){i.next(),i.expect("LPAREN");let e=i.expect("WORD"),t=i.expect("STRING");return i.expect("RPAREN"),{type:"exists",field:e.value,key:t.value}}function Sr(i){if(i.is("WORD","any"))return _r(i);if(i.is("WORD","has_key"))return kr(i);let{field:e,key:t}=Pr(i);if(i.is("WORD","exists"))return i.next(),{type:"exists",field:e,key:t};let n=i.next();if(n.type!=="WORD"||!vn.has(n.value))throw new j(`Unsupported or unrecognized operator '${n.value}'`);let o=In(i);return{type:"comparison",field:e,key:t,operator:n.value,value:o}}function Ur(i){let e=new Xe(Ar(i)),t=si(e);if(!e.atEnd())throw new j("Unexpected trailing tokens");return t}function oi(i){return i.type==="comparison"||i.type==="exists"?!0:i.type==="not"?i.child.type==="comparison"||i.child.type==="exists":!1}function ri(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=Er[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(oi(i)){let e=ri(i,0);return e?{conditions:[e],conditionLogic:"AND"}:null}if(i.type==="and"){let e=[];for(let t of i.children){if(!oi(t))return null;let n=ri(t,0);if(!n)return null;e.push(n)}return{conditions:e,conditionLogic:"AND"}}return i.type==="or"?Dt(i.children,oi,e=>e.type==="and",ri):null}function ai(i){if(!i||!i.trim())return null;try{let e=Ur(i);return Lr(e)}catch{return null}}function An(i){let e=[],t=ai(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:xr(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 $n(i){let e=[],t=Or(i.conditions,i.id,e);if(t.length===0)throw new Error(`Rule "${i.name}" has no conditions Cloudflare can represent \u2014 every condition field is unsupported by Cloudflare (e.g. Vercel-only fields like environment/ja3_digest, or vercel_region). Cannot sync this rule to Cloudflare.`);let n=Qe.fromUnifiedConditions(t,i.conditionLogic),o={id:i.id||crypto.randomUUID(),action:Dr(i.action.type),expression:n,description:i.description||i.name,enabled:i.enabled};return i.action.rateLimit&&(o.ratelimit={characteristics:i.action.rateLimit.characteristics||["ip.src"],period:Fr(i.action.rateLimit.window),requests_per_period:i.action.rateLimit.requests},i.action.rateLimit.mitigationTimeout?o.ratelimit.mitigation_timeout=i.action.rateLimit.mitigationTimeout:(o.ratelimit.mitigation_timeout=3600,e.push(U.createWarning("rate_limiting_precision",i.id,"action.rateLimit.mitigationTimeout",`No mitigationTimeout set for rate limit rule "${i.name}" \u2014 defaulted to Cloudflare's 3600s (1 hour) block duration.`))),i.action.rateLimit.countingExpression&&(o.ratelimit.counting_expression=i.action.rateLimit.countingExpression)),i.action.type==="redirect"&&i.action.redirect&&(o.action_parameters={from_value:{status_code:i.action.redirect.statusCode??(i.action.redirect.permanent?301:302),target_url:{value:i.action.redirect.location},...i.action.redirect.preserveQueryString!==void 0?{preserve_query_string:i.action.redirect.preserveQueryString}:{}}}),i.action.response&&(o.action!=="block"?e.push(U.createUnsupportedFeatureWarning(`custom response on a '${o.action}' action`,"unified config","Cloudflare",i.id,"action.response")):i.action.response.content?o.action_parameters={response:{status_code:i.action.response.statusCode??403,content:i.action.response.content,content_type:i.action.response.contentType??"text/plain"}}:e.push(U.createWarning("lossy_conversion",i.id,"action.response","Custom response declared without `content`; Cloudflare requires a response body, so it was dropped.","Set action.response.content to the body you want returned."))),{result:o,warnings:e}}function Pn(i){if(!_e.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 _n(i){let e=[],t={};if(i.action){let{action:s,warning:a}=En(i.action,i.id,"action");a&&e.push(a),s&&(t.action=s)}i.overrides&&i.overrides.length>0&&(t.rules=i.overrides.map(s=>{let a={id:s.ruleId};if(s.enabled!==void 0&&(a.enabled=s.enabled),s.action){let{action:l,warning:c}=En(s.action,i.id,`overrides[${s.ruleId}].action`);c&&e.push(c),l&&(a.action=l)}return a}));let n={id:i.ruleset,...Object.keys(t).length>0?{overrides:t}:{}};return{result:{id:i.id||crypto.randomUUID(),action:"execute",expression:"true",description:i.name||`Managed ruleset ${i.ruleset}`,enabled:i.enabled,action_parameters:n},warnings:e}}function kn(i){let e=[],t=i.action_parameters,n={id:i.id,ruleset:t?.id??"",enabled:i.enabled??!0,...i.description?{name:i.description}:{}};return t?.overrides?.action&&(n.action=Tn(t.overrides.action)),t?.overrides?.rules&&t.overrides.rules.length>0&&(n.overrides=t.overrides.rules.map(o=>({ruleId:o.id,...o.action!==void 0?{action:Tn(o.action)}:{},...o.enabled!==void 0?{enabled:o.enabled}:{}}))),{result:n,warnings:e}}function En(i,e,t){let o={log:"log",deny:"block",block:"block",challenge:"managed_challenge",allow:"allow"}[i];return o?{action:o}:{warning:U.createUnsupportedFeatureWarning(`'${i}' as a managed-ruleset override action`,"unified config","Cloudflare",e,t)}}function Tn(i){return{log:"log",block:"deny",challenge:"challenge",managed_challenge:"challenge",js_challenge:"challenge",allow:"allow"}[i]||"deny"}function xr(i){return{block:"deny",challenge:"challenge",managed_challenge:"challenge",js_challenge:"challenge",log:"log",skip:"bypass",allow:"allow",rewrite:"bypass",redirect:"redirect",execute:"log"}[i]||"deny"}function Dr(i){return{log:"log",deny:"block",block:"block",challenge:"managed_challenge",bypass:"skip",rate_limit:"block",redirect:"redirect",allow:"allow"}[i]||"block"}function Or(i,e,t){return i.filter(n=>Qe.isFieldSupported(n.field)?!0:(t.push(U.createUnsupportedFeatureWarning(`condition field '${n.field}'`,"unified config","Cloudflare",e,n.field)),!1))}function Fr(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}fe();function we(i,e,t){return U.createWarning("complex_expressions",i,void 0,e,t)}var Nr={ip:"ip",path:"path",domain:"host",method:"method",country:"country",scheme:"scheme",user_agent:"user_agent"},Vr={ip:"ip",path:"path",host:"domain",method:"method",country:"country",scheme:"scheme",user_agent:"user_agent"},Br={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 qr(i){return Nr[i]??i}function Wr(i){return Vr[i]??null}var jr={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 Sn(i){return jr[i]??{operator:"eq",negated:!1}}function Un(i,e,t,n){switch(i){case"eq":return e?"does_not_equal":"equals";case"ne":return"does_not_equal";case"contains":return e?"does_not_contain":"contains";case"not_contains":return"does_not_contain";case"starts_with":case"ends_with":return t.push(U.createWarning("regex_patterns",n,void 0,`Unified operator "${i}" has no direct Fastly equivalent \u2014 mapped to Fastly's wildcard "like" operator. Verify the translated value still matches as a wildcard pattern.`)),e?"not_like":"like";case"matches":return t.push(U.createWarning("regex_patterns",n,void 0,"Regular expression pattern may need adjustment for Fastly Next-Gen WAF regex syntax.")),e?"does_not_match":"matches";case"in":return e?"not_in_list":"in_list";case"not_in":return"not_in_list";case"gt":return t.push(we(n,'Unified operator "gt" (strictly greater than) has no Fastly equivalent \u2014 mapped to "greater_equal", which also matches the boundary value itself.')),"greater_equal";case"ge":return"greater_equal";case"lt":return t.push(we(n,'Unified operator "lt" (strictly less than) has no Fastly equivalent \u2014 mapped to "lesser_equal", which also matches the boundary value itself.')),"lesser_equal";case"le":return"lesser_equal";default:return"equals"}}function zr(i,e){let t=[],n=i.conditions.filter(s=>s.type==="group"),o=i.conditions.filter(s=>s.type!=="group");if(n.length>0&&o.length===0&&i.group_operator==="any")return n.forEach((s,a)=>{s.group_operator!=="all"&&e.push(we(i.id,`A condition group using "any" (OR) inside an outer "any" (OR) rule cannot be represented in doorman's flat AND-within/OR-across condition model \u2014 its members were kept in the same group (effectively AND'd) instead of OR'd. Review the translated rule against the original Fastly logic.`));for(let l of s.conditions)Ot(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;Ot(t,s,l,e,i.id)}),{conditions:t,conditionLogic:i.group_operator==="any"&&o.length>1?"OR":"AND"};e.push(we(i.id,`Rule mixes top-level conditions with condition groups under group_operator "${i.group_operator}" \u2014 this doesn't fit doorman's flat AND-within/OR-across condition model. All conditions were combined with AND; review the translated rule against the original Fastly rule logic.`));for(let s of o)Ot(t,s,0,e,i.id);for(let s of n)for(let a of s.conditions)Ot(t,a,0,e,i.id);return{conditions:t,conditionLogic:"AND"}}function Ot(i,e,t,n,o){if(e.type==="single"){let{operator:u,negated:p}=Sn(e.operator);i.push({field:qr(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(we(o,`Multival condition on '${e.field}' has no value or name sub-condition doorman could extract \u2014 skipped.`));return}let{operator:c,negated:d}=Sn(l.operator);i.push({field:s,operator:c,value:l.value,...d?{negated:!0}:{},...a?{key:a.value}:{},group:t})}function Gr(i,e){i.actions.length>1&&e.push(we(i.id,`Rule has ${i.actions.length} actions \u2014 doorman only models one action per rule; only the first ("${i.actions[0]?.type}") was translated.`));let t=i.actions[0];if(i.type==="rate_limit")return{type:"rate_limit",rateLimit:i.rate_limit?{requests:i.rate_limit.threshold,window:`${i.rate_limit.interval}s`,mitigationTimeout:i.rate_limit.duration}:void 0};switch(t?.type){case"block":return{type:"block"};case"allow":return{type:"allow"};case"browser_challenge":case"dynamic_challenge":case"verify_token":return{type:"challenge"};case"redirect":return{type:"redirect",redirect:{location:t.redirect_url??"",...t.response_code!==void 0?{statusCode:t.response_code}:{}}};case"add_signal":case"exclude_signal":case"deception":return e.push(U.createWarning("action_differences",i.id,"action.type",`Fastly action "${t.type}" tags or excludes a WAF signal rather than allowing/blocking the request \u2014 doorman has no matching action type. Mapped to "log" as the closest non-destructive fallback; review this rule.`)),{type:"log"};default:return e.push(we(i.id,`Unrecognised Fastly action type "${String(t?.type)}" \u2014 mapped to "log".`)),{type:"log"}}}function Ln(i){let e=[],{conditions:t,conditionLogic:n}=zr(i,e),o=Gr(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=Br[a.field];if(l){if(a.key===void 0)return t.push(we(e,`Condition on '${a.field}' has no 'key' set \u2014 Fastly needs a specific ${a.field} name to match on (e.g. a header name). Skipped.`)),null;if(a.operator==="exists"||a.operator==="not_exists")return{type:"multival",field:l,operator:a.operator==="exists"?"exists":"does_not_exist",group_operator:"all",conditions:[{type:"single",field:"name",operator:"equals",value:a.key}]};let d=l==="request_header"||l==="response_header"?"value_string":"value";return{type:"multival",field:l,operator:"exists",group_operator:"all",conditions:[{type:"single",field:"name",operator:"equals",value:a.key},{type:"single",field:d,operator:Un(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=Wr(a.field);return c?{type:"single",field:c,operator:Un(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 Hr(i,e,t){switch(i.type){case"block":case"deny":return{type:"block"};case"allow":return{type:"allow"};case"challenge":return{type:"browser_challenge"};case"bypass":return e.push(we(t,'Unified action "bypass" has no direct Fastly equivalent \u2014 mapped to "allow".')),{type:"allow"};case"log":return e.push(we(t,'Unified action "log" has no dedicated Fastly action \u2014 mapped to "allow" (request_logging is enabled at the rule level regardless).')),{type:"allow"};case"redirect":return i.redirect?{type:"redirect",redirect_url:i.redirect.location,response_code:i.redirect.statusCode??(i.redirect.permanent?301:302)}:(e.push(we(t,'Unified action "redirect" has no redirect target \u2014 mapped to "allow".')),{type:"allow"});case"rate_limit":return{type:"block_signal"};default:return e.push(U.createUnsupportedFeatureWarning(`action type '${i.type}'`,"unified config","Fastly",t,"action.type")),{type:"block"}}}function Zr(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 Yr(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=Zr(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 xn(i){let e=[];i.description&&i.description!==i.name&&e.push(we(i.id,'Fastly rules have no separate "name" field \u2014 only "description" carries over, and it was set from this rule\'s `name`. The `description` text was dropped rather than merged in, to keep it round-trip-stable on the next sync.'));let{conditions:t,groupConditions:n,multivalConditions:o,groupOperator:s}=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:[Hr(i.action,e,i.id)],rate_limit:a?Yr(i.action.rateLimit,i.id,e):null},warnings:e}}function Dn(i,e){return i.map(t=>({ip:t,action:e}))}function On(i){let e=[],t=[],n=[];for(let o of i)(o.hostname||o.notes)&&e.push(we(o.id,`IP rule "${o.ip}" sets ${o.hostname?"`hostname`":""}${o.hostname&&o.notes?" and ":""}${o.notes?"`notes`":""} \u2014 Fastly IP lists have no equivalent per-entry metadata; only the address was kept.`)),(o.action==="allow"?n:t).push(o.ip);return{result:{denyEntries:t,allowEntries:n},warnings:e}}fe();function et(i){return i.replace(/\\/g,"\\\\").replace(/'/g,"\\'")}function Fn(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 Jr=new Set(["region","city","port","scheme"]),Qr={host:"host",user_agent:"user-agent",referer:"referer"},gt=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(Jr.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=Qr[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=es[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(!_e.safeParse(a).success)throw new Error(`Invalid IP address or CIDR range: ${a}`);return a.includes("/")?`inIpRange(origin.ip, '${et(a)}')`:`origin.ip == '${et(a)}'`}),o=this.isNegated(e,e.operator==="not_in"||e.operator==="ne");if(n.length>1){let a=`(${n.join(" || ")})`;return o?`!${a}`:a}let s=n[0];return o?s.startsWith("inIpRange(")?`!${s}`:`!(${s})`:s}static buildHeaderCondition(e,t){let n=`request.headers['${et(e)}']`,o=`has(${n})`;return t.operator==="exists"||t.operator==="not_exists"?this.isNegated(t,t.operator==="not_exists")?`!${o}`:o:`(${o} && ${this.buildComparison(n,t,!1)})`}static buildCookieCondition(e){let t="request.headers['cookie']",n=`has(${t})`;if(e.operator==="exists"||e.operator==="not_exists")return this.isNegated(e,e.operator==="not_exists")?`!${n}`:n;if(!e.key)return`(${n} && ${this.buildComparison(t,e,!1)})`;if(!["eq","ne","contains","not_contains"].includes(e.operator))throw new Error(`Cloud Armor can only check a specific cookie's value via substring search (has no parsed cookie map) \u2014 "${e.operator}" on a keyed cookie condition has no unambiguous CEL representation.`);let o=`${e.key}=${String(e.value)}`,s=`${t}.contains('${et(o)}')`,a=this.isNegated(e,e.operator==="ne"||e.operator==="not_contains");return`(${n} && ${a?`!${s}`:s})`}static buildComparison(e,t,n){let{value:o}=t;if(t.operator==="exists"||t.operator==="not_exists")throw new Error(`"${t.operator}" is not meaningful for Cloud Armor's "${e}" \u2014 it is always present`);let s=t.operator,a=!1;if(t.negated){let d=Xr[s];d?s=d:a=!0}if(s==="in"||s==="not_in"){let u=(Array.isArray(o)?o:[o]).map(f=>this.formatLiteral(f,n)).join(", "),p=`${e} in [${u}]`;return s==="not_in"?`!(${p})`:p}let l=this.formatLiteral(o,n),c;switch(s){case"eq":c=`${e} == ${l}`;break;case"ne":c=`${e} != ${l}`;break;case"contains":c=`${e}.contains(${l})`;break;case"not_contains":c=`!${e}.contains(${l})`;break;case"starts_with":c=`${e}.startsWith(${l})`;break;case"ends_with":c=`${e}.endsWith(${l})`;break;case"matches":c=`${e}.matches(${l})`;break;case"gt":c=`${e} > ${l}`;break;case"ge":c=`${e} >= ${l}`;break;case"lt":c=`${e} < ${l}`;break;case"le":c=`${e} <= ${l}`;break;default:throw new Error(`Cloud Armor CEL builder has no mapping for operator "${s}"`)}return a?`!(${c})`:c}static isNegated(e,t){return t!==!!e.negated}static formatLiteral(e,t){if(t){let n=Number(e);if(!Number.isFinite(n))throw new Error(`Expected a numeric value, got "${String(e)}"`);return String(n)}return`'${et(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(" || ")})`}},Xr={eq:"ne",ne:"eq",contains:"not_contains",not_contains:"contains",in:"not_in",not_in:"in"},es={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 ts={"origin.region_code":"country","origin.asn":"asn","request.path":"path","request.method":"method","request.query":"query"},is={host:"host","user-agent":"user_agent",referer:"referer",cookie:"cookie"},Wn={contains:"contains",startsWith:"starts_with",endsWith:"ends_with",matches:"matches"};function ns(i){let e=[],t=0,n=i.length,o=(s,a)=>i[t]===s&&i[t+1]===a;for(;t<n;){let s=i[t];if(/\s/.test(s)){t++;continue}if(s==="("){e.push({type:"LPAREN",value:s}),t++;continue}if(s===")"){e.push({type:"RPAREN",value:s}),t++;continue}if(s==="["){e.push({type:"LBRACKET",value:s}),t++;continue}if(s==="]"){e.push({type:"RBRACKET",value:s}),t++;continue}if(s===","){e.push({type:"COMMA",value:s}),t++;continue}if(o("&","&")){e.push({type:"AND",value:"&&"}),t+=2;continue}if(o("|","|")){e.push({type:"OR",value:"||"}),t+=2;continue}if(o("=","=")){e.push({type:"EQ",value:"=="}),t+=2;continue}if(o("!","=")){e.push({type:"NE",value:"!="}),t+=2;continue}if(o(">","=")){e.push({type:"GE",value:">="}),t+=2;continue}if(o("<","=")){e.push({type:"LE",value:"<="}),t+=2;continue}if(s==="!"){e.push({type:"NOT",value:"!"}),t++;continue}if(s===">"){e.push({type:"GT",value:">"}),t++;continue}if(s==="<"){e.push({type:"LT",value:"<"}),t++;continue}if(s==="."){e.push({type:"DOT",value:s}),t++;continue}if(s==="'"){let a=t+1,l="",c=!1;for(;a<n;){let d=i[a];if(d==="\\"&&a+1<n){l+=d+i[a+1],a+=2;continue}if(i[a]==="'"){c=!0,a++;break}l+=i[a],a++}if(!c)throw new j("Unterminated string literal");e.push({type:"STRING",value:Fn(l)}),t=a;continue}if(/[0-9]/.test(s)||s==="-"&&/[0-9]/.test(i[t+1]??"")){let a=t+1;for(;a<n&&/[0-9.]/.test(i[a]);)a++;e.push({type:"NUMBER",value:i.slice(t,a)}),t=a;continue}if(/[A-Za-z_]/.test(s)){let a=t;for(;a<n&&/[A-Za-z0-9_]/.test(i[a]);)a++;e.push({type:"IDENT",value:i.slice(t,a)}),t=a;continue}throw new j(`Unexpected character '${s}' at position ${t}`)}return e}function os(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 li(i){let e=os(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 Nn(i){let e=i.next();if(e.type==="STRING")return e.value;if(e.type==="NUMBER")return Number(e.value);throw new j(`Unexpected token '${e.value}' where a literal was expected`)}function rs(i){if(i.is("IDENT","has")){i.next(),i.expect("LPAREN");let a=li(i);return i.expect("RPAREN"),{type:"has",...a}}if(i.is("IDENT","inIpRange")){i.next(),i.expect("LPAREN");let a=li(i);if(a.field!=="origin.ip")throw new j(`inIpRange must reference origin.ip, got '${a.field}'`);i.expect("COMMA");let l=i.expect("STRING");return i.expect("RPAREN"),{type:"inIpRange",value:l.value}}let e=li(i);if(i.peek()?.type==="DOT"){i.next();let a=i.expect("IDENT").value;if(!Wn[a])throw new j(`Unrecognized method '${a}'`);i.expect("LPAREN");let l=i.expect("STRING");return i.expect("RPAREN"),{type:"methodCall",...e,method:a,value:l.value}}if(i.is("IDENT","in")){i.next(),i.expect("LBRACKET");let a=[];for(;i.peek()?.type!=="RBRACKET";)a.push(Nn(i)),i.peek()?.type==="COMMA"&&i.next();return i.expect("RBRACKET"),{type:"membership",...e,values:a}}let t=i.next(),o={EQ:"eq",NE:"ne",GT:"gt",GE:"ge",LT:"lt",LE:"le"}[t.type];if(!o)throw new j(`Unexpected token '${t.value}' after field reference`);let s=Nn(i);return{type:"comparison",...e,operator:o,value:s}}function Vn(i){if(i.peek()?.type==="LPAREN"){i.next();let e=jn(i);return i.expect("RPAREN"),e}return rs(i)}function Bn(i){return i.peek()?.type==="NOT"?(i.next(),{type:"not",child:Vn(i)}):Vn(i)}function Mn(i){let e=[Bn(i)];for(;i.peek()?.type==="AND";)i.next(),e.push(Bn(i));return e.length===1?e[0]:{type:"and",children:e}}function jn(i){let e=[Mn(i)];for(;i.peek()?.type==="OR";)i.next(),e.push(Mn(i));return e.length===1?e[0]:{type:"or",children:e}}function ss(i){let e=new Xe(ns(i)),t=jn(e);if(!e.atEnd())throw new j("Unexpected trailing tokens");return t}function as(i){return i.type==="inIpRange"||i.type==="comparison"&&i.field==="origin.ip"&&i.operator==="eq"}function ui(i){return i.type==="or"&&i.children.length>0&&i.children.every(as)}function ls(i){return i.type==="inIpRange"?i.value:String(i.value)}function zn(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 cs(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 ci(i){if(i.type==="has"||i.type==="comparison"||i.type==="methodCall"||i.type==="membership"||i.type==="inIpRange"||ui(i)||zn(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"||ui(e)}return!1}function Gn(i,e){if(i==="origin.ip")return{unifiedField:"ip"};let t=ts[i];if(t)return{unifiedField:t};if(i==="request.headers"&&e!==void 0){let n=is[e];return n?{unifiedField:n}:{unifiedField:"header",unifiedKey:e}}return null}function di(i,e){let t=i.type==="not",n=i.type==="not"?i.child:i;if(ui(n)){let o=n.children.map(s=>ls(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"&&zn(n)){if(t)return null;let{guard:o,comparisonNegated:s,comparison:a}=cs(n);return a.type!=="comparison"&&a.type!=="methodCall"&&a.type!=="membership"?null:qn(a,s,o.field,o.key,e)}if(n.type==="comparison"||n.type==="methodCall"||n.type==="membership")return qn(n,t,n.field,n.key,e);if(n.type==="has"){let o=Gn(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 qn(i,e,t,n,o){let s=Gn(t,n);if(!s)return null;let a,l,c=!1;if(i.type==="comparison")e?i.operator==="eq"?a="ne":i.operator==="ne"?a="eq":(a=i.operator,c=!0):a=i.operator,l=i.value;else if(i.type==="methodCall"){let d=Wn[i.method];if(!d)return null;e?d==="contains"?a="not_contains":(a=d,c=!0):a=d,l=i.value}else a=e?"not_in":"in",l=i.values;return{field:s.unifiedField,operator:a,value:l,...c?{negated:!0}:{},...s.unifiedKey!==void 0?{key:s.unifiedKey}:{},...o!==void 0?{group:o}:{}}}function ds(i){if(ci(i)){let e=di(i,void 0);return e?{conditions:[e],conditionLogic:"AND"}:null}if(i.type==="and"){let e=[];for(let t of i.children){if(!ci(t))return null;let n=di(t,void 0);if(!n)return null;e.push(n)}return{conditions:e,conditionLogic:"AND"}}return i.type==="or"?Dt(i.children,ci,e=>e.type==="and",di):null}function Me(i){if(!i||!i.trim())return null;try{let e=ss(i);return ds(e)}catch{return null}}function pi(i){if(!i.match.expr)throw new Error(`Cloud Armor rule at priority ${i.priority} has no CEL match expression (uses the basic versionedExpr/config shape) \u2014 caller must filter these out before translating.`);return i.match.expr.expression}function Kn(i){if(i.priority===void 0)throw new Error(`Rule "${i.name}" has no priority assigned \u2014 CloudArmorFirewallService must assign one before calling unifiedToGcp`);let e=[],t=gt.fromUnifiedConditions(i.conditions,i.conditionLogic),{action:n,actionWarning:o}=ps(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=fs(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 Hn(i,e){let t=[],n=e!==void 0?e:Me(pi(i)),o=[],s;n?(o=n.conditions,s=n.conditionLogic):t.push(U.createWarning("complex_expressions",String(i.priority),"match.expr.expression","CEL expression could not be parsed back into structured conditions \u2014 it may be hand-authored or use syntax outside what doorman itself generates.","Review the translated rule and add missing conditions manually if needed."));let a={type:us(i.action),...i.rateLimitOptions?{rateLimit:{requests:i.rateLimitOptions.rateLimitThreshold.count,window:`${i.rateLimitOptions.rateLimitThreshold.intervalSec}s`}}:{},...i.redirectOptions?{redirect:{location:i.redirectOptions.target}}:{}};return{result:{id:String(i.priority),name:i.description||`Rule ${i.priority}`,...i.description?{description:i.description}:{},enabled:!i.preview,conditions:o,...s==="OR"?{conditionLogic:s}:{},action:a,priority:i.priority},warnings:t}}function Zn(i,e){if(i.action!=="allow"&&i.action!=="deny(403)"||i.preview)return!1;let t=e!==void 0?e:Me(pi(i));if(!t||t.conditions.length!==1)return!1;let n=t.conditions[0];return n.field==="ip"&&(n.operator==="eq"||n.operator==="in")&&!n.key}function Yn(i,e){let o=(e!==void 0?e:Me(pi(i))).conditions[0].value;return{id:String(i.priority),ip:Array.isArray(o)?String(o[0]):String(o),...i.description?{notes:i.description}:{},action:i.action==="allow"?"allow":"deny"}}function Jn(i,e){if(!_e.safeParse(i.ip).success)throw new Error(`Invalid IP address or CIDR range: ${i.ip}`);let t=gt.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 us(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 ps(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 fs(i){let e=i.match(/^(\d+)([smhd])$/);if(!e||!e[1]||!e[2])throw new Error(`Invalid window format: ${i}`);let t=parseInt(e[1],10),o={s:1,m:60,h:3600,d:86400}[e[2]];if(o===void 0)throw new Error(`Invalid time unit: ${e[2]}`);return t*o}var T=class{static vercelToUnified=mn;static unifiedToVercel=hn;static vercelIPToUnified=yn;static cloudflareToUnified=An;static unifiedToCloudflare=$n;static unifiedIPToCloudflare=Pn;static unifiedManagedRuleGroupToCloudflare=_n;static cloudflareToUnifiedManagedRuleGroup=kn;static fastlyToUnified=Ln;static unifiedToFastly=xn;static fastlyListEntriesToUnified=Dn;static unifiedIPsToFastlyEntries=On;static gcpToUnified=Hn;static unifiedToGcp=Kn;static gcpToUnifiedIP=Yn;static unifiedIPToGcp=Jn;static gcpLooksLikeIpRule=Zn};fe();function Fe(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)||!Fe(i[o],e[o]))return!1;return!0}function Ce(i){let{id:e,...t}=i;return t}function mt(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 Ft(i){return i.some(e=>e.priority!==void 0)}var tt=class extends Se{constructor(t){super();this.client=t}client;name="vercel";async fetchConfig(t){try{r.debug("Fetching Vercel firewall configuration");let n=await this.client.fetchFirewallConfig(t),o=n.rules.map(a=>{let l=T.vercelToUnified(a);return l.warnings.length>0&&l.warnings.forEach(c=>{let{TranslationWarningSystem:d}=(fe(),Re(Ue)),u=d.formatWarning(c);r.warn(`Rule ${a.name}:
25
- ${u}`)}),l.result}),s=n.ips.map(a=>T.vercelIPToUnified(a));return{version:"2.0",provider:"vercel",providers:{vercel:{projectId:this.client.projectId,...this.client.teamId?{teamId:this.client.teamId}:{}}},rules:o,ips:s,metadata:{version:n.version,updatedAt:n.updatedAt}}}catch(n){throw r.error("Error fetching Vercel configuration:",n),new Error("Failed to fetch Vercel firewall configuration")}}async syncRules(t,n={}){let{dryRun:o=!1}=n;try{let{OperationSafety:s}=(yt(),Re(ht)),a=await s.performDryRunValidation(t,"sync rules",async y=>await this.getChanges(y,{allowCreate:!o,assumeYes:n.assumeYes}));if(!a.valid)throw new Error(`Dry-run validation failed: ${a.issues.join(", ")}`);let l=a.changes,{rulesToAdd:c,rulesToUpdate:d,rulesToDelete:u,version:p}=l,f=l.ipsToAdd||[],g=l.ipsToUpdate||[],m=l.ipsToDelete||[];if(o)return r.info("Dry run mode. The following changes would be made:"),r.info(`Custom Rules - Add: ${c.length}, Update: ${d.length}, Delete: ${u.length}`),r.info(`IP Rules - Add: ${f.length}, Update: ${g.length}, Delete: ${m.length}`),{success:!0,rulesAdded:0,rulesUpdated:0,rulesDeleted:0,ipsAdded:0,ipsUpdated:0,ipsDeleted:0,version:p,warnings:a.warnings};let v=s.assessOperationRisk(l,t);if(!await s.confirmDestructiveOperation({operation:"sync rules",target:`Vercel project ${this.client.projectId}`,changes:l,riskLevel:v,skipConfirmation:n.force||!1,dryRun:!1,allowDeletions:n.allowDeletions||!1}))throw new Error("Operation cancelled by user");let x=c.map(y=>T.unifiedToVercel(y).result),S=d.map(y=>T.unifiedToVercel(y).result),q=u.map(y=>T.unifiedToVercel(y).result),W=f.map(y=>({id:y.id||"",ip:y.ip,hostname:y.hostname||"",action:y.action,notes:y.notes})),Q=g.map(y=>({id:y.id||"",ip:y.ip,hostname:y.hostname||"",action:y.action,notes:y.notes})),ue=m.map(y=>({id:y.id||"",ip:y.ip,hostname:y.hostname||"",action:y.action,notes:y.notes})),K=[],me=[],k=[],$e=[],Ee=[],A=[],h=[],b=(y,P)=>`${y}: ${P instanceof Error?P.message:String(P)}`,ee=[];Ft(t.rules)&&t.rules.length>c.length&&ee.push("Rule `priority` is best-effort on Vercel: new rules are created in priority order, but rules that already exist remotely cannot be repositioned via the Vercel API. Recreate them (remove and re-add) if exact evaluation order matters.");let pe=[],ct=[];for(let y of q)try{r.debug(`Deleting custom rule: ${y.id}`),await this.client.deleteFirewallRule(y),k.push(y),r.debug(`Custom rule deleted: ${y.id}`)}catch(P){r.error(`Failed to delete custom rule ${y.id}:`,P),h.push(b(`Failed to delete custom rule ${y.id}`,P))}for(let y of ue)try{r.debug(`Deleting IP blocking rule: ${y.id}`),await this.client.deleteIPBlockingRule(y),A.push(y),r.debug(`IP blocking rule deleted: ${y.id}`)}catch(P){r.error(`Failed to delete IP blocking rule ${y.id}:`,P),h.push(b(`Failed to delete IP blocking rule ${y.id}`,P))}for(let y of x)try{r.debug(`Adding new custom rule: ${y.name}`);let P=await this.client.createFirewallRule(y);K.push(P),P.id?P.id!==y.id&&pe.push({oldId:y.id||void 0,newId:P.id,name:y.name}):ct.push({rule:y,created:P}),r.debug(`New custom rule added: ${P.id}`)}catch(P){r.error(`Failed to add custom rule "${y.name}":`,P),h.push(b(`Failed to add custom rule "${y.name}"`,P))}for(let y of W)try{r.debug(`Adding new IP blocking rule: ${y.ip}`);let P=await this.client.createIPBlockingRule(y);$e.push(P),r.debug(`New IP blocking rule added: (hostname): ${P.hostname} (ip): ${P.ip}`)}catch(P){r.error(`Failed to add IP blocking rule ${y.ip}:`,P),h.push(b(`Failed to add IP blocking rule ${y.ip}`,P))}for(let y of S)try{r.debug(`Updating custom rule: ${y.id}`);let P=await this.client.updateFirewallRule(y);me.push(P),r.debug(`Custom rule updated: ${P.id}`)}catch(P){r.error(`Failed to update custom rule ${y.id}:`,P),h.push(b(`Failed to update custom rule ${y.id}`,P))}for(let y of Q)try{r.debug(`Updating IP blocking rule: ${y.id}`);let P=await this.client.updateIPBlockingRule(y);Ee.push(P),r.debug(`IP blocking rule updated: ${P.id}`)}catch(P){r.error(`Failed to update IP blocking rule ${y.id}:`,P),h.push(b(`Failed to update IP blocking rule ${y.id}`,P))}r.debug(`${ge.default.underline("Custom Rules:")} ${ge.default.green("Added:")} ${ge.default.green(K.length)}, ${ge.default.cyan("Updated:")} ${ge.default.cyan(me.length)}, ${ge.default.red("Deleted:")} ${ge.default.red(k.length)}`),r.debug(`${ge.default.underline("IP Rules:")} ${ge.default.green("Added:")} ${ge.default.green($e.length)}, ${ge.default.cyan("Updated:")} ${ge.default.cyan(Ee.length)}, ${ge.default.red("Deleted:")} ${ge.default.red(A.length)}`);let dt=p,At,Te=[...ee];try{let y=await this.client.fetchFirewallConfig();dt=y.version,At=y.updatedAt;for(let{rule:O,created:So}of ct){let Wt=y.rules.filter(He=>He.name===O.name);if(Wt.length===1){let He=Wt[0].id;So.id=He,He&&He!==O.id&&pe.push({oldId:O.id||void 0,newId:He,name:O.name})}else Te.push(`Could not determine the Vercel-assigned id for new rule "${O.name}" (${Wt.length} remote rules share that name); its local id may not match the remote one, so a later sync may re-create it. Run \`doorman download\` to reconcile.`)}let P=new Set(y.rules.map(O=>O.id)),Li=new Set(y.ips.map(O=>O.id));for(let O of[...K,...me])O.id&&!P.has(O.id)&&Te.push(`Rule "${O.name}" (${O.id}) was synced but is missing from the post-sync remote config \u2014 a subsequent sync may re-create or misdiff it.`);for(let O of k)O.id&&P.has(O.id)&&Te.push(`Rule "${O.name}" (${O.id}) was deleted but still appears in the post-sync remote config.`);for(let O of[...$e,...Ee])O.id&&!Li.has(O.id)&&Te.push(`IP rule "${O.ip}" (${O.id}) was synced but is missing from the post-sync remote config \u2014 a subsequent sync may re-create or misdiff it.`);for(let O of A)O.id&&Li.has(O.id)&&Te.push(`IP rule "${O.ip}" (${O.id}) was deleted but still appears in the post-sync remote config.`)}catch(y){r.error("Failed to fetch updated firewall configuration version after sync:",y),h.push(b("Failed to fetch updated configuration version after sync",y))}return{success:h.length===0,rulesAdded:K.length,rulesUpdated:me.length,rulesDeleted:k.length,ipsAdded:$e.length,ipsUpdated:Ee.length,ipsDeleted:A.length,version:dt,updatedAt:At,idRemappings:pe.length>0?pe:void 0,errors:h.length>0?h:void 0,warnings:Te.length>0?Te: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=Le.safeParse(t);if(!o.success)throw new Error(`Invalid firewall configuration: ${o.error.message}`);this.assertManagedRulesSupported(t);try{r.debug("Fetching existing firewall configuration");let s=await this.client.fetchFirewallConfig(void 0,{allowCreate:n?.allowCreate??!0,assumeYes:n?.assumeYes});r.debug(`Fetched ${s.rules.length} custom rules and ${s.ips.length} IP blocking rules`);let a=s.rules.map(m=>{let v=T.vercelToUnified(m);return v.warnings.length>0&&v.warnings.forEach(z=>{let{TranslationWarningSystem:x}=(fe(),Re(Ue)),S=x.formatWarning(z);r.warn(`Rule ${m.name}:
25
+ ${u}`)}),l.result}),s=n.ips.map(a=>T.vercelIPToUnified(a));return{version:"2.0",provider:"vercel",providers:{vercel:{projectId:this.client.projectId,...this.client.teamId?{teamId:this.client.teamId}:{}}},rules:o,ips:s,metadata:{version:n.version,updatedAt:n.updatedAt}}}catch(n){throw r.error("Error fetching Vercel configuration:",n),new Error("Failed to fetch Vercel firewall configuration")}}async syncRules(t,n={}){let{dryRun:o=!1}=n;try{let{OperationSafety:s}=(yt(),Re(ht)),a=await s.performDryRunValidation(t,"sync rules",async y=>await this.getChanges(y,{allowCreate:!o,assumeYes:n.assumeYes}));if(!a.valid)throw new Error(`Dry-run validation failed: ${a.issues.join(", ")}`);let l=a.changes,{rulesToAdd:c,rulesToUpdate:d,rulesToDelete:u,version:p}=l,f=l.ipsToAdd||[],g=l.ipsToUpdate||[],m=l.ipsToDelete||[];if(o)return r.info("Dry run mode. The following changes would be made:"),r.info(`Custom Rules - Add: ${c.length}, Update: ${d.length}, Delete: ${u.length}`),r.info(`IP Rules - Add: ${f.length}, Update: ${g.length}, Delete: ${m.length}`),{success:!0,rulesAdded:0,rulesUpdated:0,rulesDeleted:0,ipsAdded:0,ipsUpdated:0,ipsDeleted:0,version:p,warnings:a.warnings};let v=s.assessOperationRisk(l,t);if(!await s.confirmDestructiveOperation({operation:"sync rules",target:`Vercel project ${this.client.projectId}`,changes:l,riskLevel:v,skipConfirmation:n.force||!1,dryRun:!1,allowDeletions:n.allowDeletions||!1}))throw new Error("Operation cancelled by user");let x=c.map(y=>T.unifiedToVercel(y).result),S=d.map(y=>T.unifiedToVercel(y).result),q=u.map(y=>T.unifiedToVercel(y).result),W=f.map(y=>({id:y.id||"",ip:y.ip,hostname:y.hostname||"",action:y.action,notes:y.notes})),Q=g.map(y=>({id:y.id||"",ip:y.ip,hostname:y.hostname||"",action:y.action,notes:y.notes})),ue=m.map(y=>({id:y.id||"",ip:y.ip,hostname:y.hostname||"",action:y.action,notes:y.notes})),K=[],me=[],k=[],$e=[],Ee=[],A=[],h=[],b=(y,P)=>`${y}: ${P instanceof Error?P.message:String(P)}`,ee=[];Ft(t.rules)&&t.rules.length>c.length&&ee.push("Rule `priority` is best-effort on Vercel: new rules are created in priority order, but rules that already exist remotely cannot be repositioned via the Vercel API. Recreate them (remove and re-add) if exact evaluation order matters.");let pe=[],ct=[];for(let y of q)try{r.debug(`Deleting custom rule: ${y.id}`),await this.client.deleteFirewallRule(y),k.push(y),r.debug(`Custom rule deleted: ${y.id}`)}catch(P){r.error(`Failed to delete custom rule ${y.id}:`,P),h.push(b(`Failed to delete custom rule ${y.id}`,P))}for(let y of ue)try{r.debug(`Deleting IP blocking rule: ${y.id}`),await this.client.deleteIPBlockingRule(y),A.push(y),r.debug(`IP blocking rule deleted: ${y.id}`)}catch(P){r.error(`Failed to delete IP blocking rule ${y.id}:`,P),h.push(b(`Failed to delete IP blocking rule ${y.id}`,P))}for(let y of x)try{r.debug(`Adding new custom rule: ${y.name}`);let P=await this.client.createFirewallRule(y);K.push(P),P.id?P.id!==y.id&&pe.push({oldId:y.id||void 0,newId:P.id,name:y.name}):ct.push({rule:y,created:P}),r.debug(`New custom rule added: ${P.id}`)}catch(P){r.error(`Failed to add custom rule "${y.name}":`,P),h.push(b(`Failed to add custom rule "${y.name}"`,P))}for(let y of W)try{r.debug(`Adding new IP blocking rule: ${y.ip}`);let P=await this.client.createIPBlockingRule(y);$e.push(P),r.debug(`New IP blocking rule added: (hostname): ${P.hostname} (ip): ${P.ip}`)}catch(P){r.error(`Failed to add IP blocking rule ${y.ip}:`,P),h.push(b(`Failed to add IP blocking rule ${y.ip}`,P))}for(let y of S)try{r.debug(`Updating custom rule: ${y.id}`);let P=await this.client.updateFirewallRule(y);me.push(P),r.debug(`Custom rule updated: ${P.id}`)}catch(P){r.error(`Failed to update custom rule ${y.id}:`,P),h.push(b(`Failed to update custom rule ${y.id}`,P))}for(let y of Q)try{r.debug(`Updating IP blocking rule: ${y.id}`);let P=await this.client.updateIPBlockingRule(y);Ee.push(P),r.debug(`IP blocking rule updated: ${P.id}`)}catch(P){r.error(`Failed to update IP blocking rule ${y.id}:`,P),h.push(b(`Failed to update IP blocking rule ${y.id}`,P))}r.debug(`${ge.default.underline("Custom Rules:")} ${ge.default.green("Added:")} ${ge.default.green(K.length)}, ${ge.default.cyan("Updated:")} ${ge.default.cyan(me.length)}, ${ge.default.red("Deleted:")} ${ge.default.red(k.length)}`),r.debug(`${ge.default.underline("IP Rules:")} ${ge.default.green("Added:")} ${ge.default.green($e.length)}, ${ge.default.cyan("Updated:")} ${ge.default.cyan(Ee.length)}, ${ge.default.red("Deleted:")} ${ge.default.red(A.length)}`);let dt=p,At,Te=[...ee];try{let y=await this.client.fetchFirewallConfig();dt=y.version,At=y.updatedAt;for(let{rule:O,created:So}of ct){let Wt=y.rules.filter(He=>He.name===O.name);if(Wt.length===1){let He=Wt[0].id;So.id=He,He&&He!==O.id&&pe.push({oldId:O.id||void 0,newId:He,name:O.name})}else Te.push(`Could not determine the Vercel-assigned id for new rule "${O.name}" (${Wt.length} remote rules share that name); its local id may not match the remote one, so a later sync may re-create it. Run \`doorman download\` to reconcile.`)}let P=new Set(y.rules.map(O=>O.id)),Li=new Set(y.ips.map(O=>O.id));for(let O of[...K,...me])O.id&&!P.has(O.id)&&Te.push(`Rule "${O.name}" (${O.id}) was synced but is missing from the post-sync remote config \u2014 a subsequent sync may re-create or misdiff it.`);for(let O of k)O.id&&P.has(O.id)&&Te.push(`Rule "${O.name}" (${O.id}) was deleted but still appears in the post-sync remote config.`);for(let O of[...$e,...Ee])O.id&&!Li.has(O.id)&&Te.push(`IP rule "${O.ip}" (${O.id}) was synced but is missing from the post-sync remote config \u2014 a subsequent sync may re-create or misdiff it.`);for(let O of A)O.id&&Li.has(O.id)&&Te.push(`IP rule "${O.ip}" (${O.id}) was deleted but still appears in the post-sync remote config.`)}catch(y){r.error("Failed to fetch updated firewall configuration version after sync:",y),h.push(b("Failed to fetch updated configuration version after sync",y))}return{success:h.length===0,rulesAdded:K.length,rulesUpdated:me.length,rulesDeleted:k.length,ipsAdded:$e.length,ipsUpdated:Ee.length,ipsDeleted:A.length,version:dt,updatedAt:At,idRemappings:pe.length>0?pe:void 0,errors:h.length>0?h:void 0,warnings:Te.length>0?Te: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=Le.safeParse(t);if(!o.success)throw new Error(`Invalid firewall configuration: ${o.error.message}`);this.assertManagedRulesSupported(t);try{r.debug("Fetching existing firewall configuration");let s=await this.client.fetchFirewallConfig(void 0,{allowCreate:n?.readOnly?!1:n?.allowCreate??!0,assumeYes:n?.assumeYes});r.debug(`Fetched ${s.rules.length} custom rules and ${s.ips.length} IP blocking rules`);let a=s.rules.map(m=>{let v=T.vercelToUnified(m);return v.warnings.length>0&&v.warnings.forEach(z=>{let{TranslationWarningSystem:x}=(fe(),Re(Ue)),S=x.formatWarning(z);r.warn(`Rule ${m.name}:
26
26
  ${S}`)}),v.result}),{toAdd:l,toUpdate:c,toDelete:d}=this.diffRules(mt(o.data.rules),a),u=s.ips.map(m=>T.vercelIPToUnified(m)),{ipsToAdd:p,ipsToUpdate:f,ipsToDelete:g}=this.diffIPRules(o.data.ips||[],u);return{version:s.version,rulesToAdd:l,rulesToUpdate:c,rulesToDelete:d,ipsToAdd:p,ipsToUpdate:f,ipsToDelete:g,hasChanges:l.length>0||c.length>0||d.length>0||p.length>0||f.length>0||g.length>0}}catch(s){throw r.error("Error fetching existing firewall configuration:",s),new Error("Failed to fetch existing firewall configuration")}}getSupportedFeatures(){return{supportsCustomRules:!0,supportsIPBlocking:!0,supportsRateLimiting:!0,supportsGeoBlocking:!0,supportsManagedRules:!1,supportsRedirect:!0,supportsChallenge:!0}}async verifyCredentials(){return this.client.verifyCredentials()}validateConfig(t){let n=super.validateConfig(t),o=[...n.errors],s=[...n.warnings];t.provider&&t.provider!=="vercel"&&o.push({path:"provider",message:"Provider must be 'vercel' for VercelFirewallService",code:"INVALID_PROVIDER"});try{let a=Ve.safeParse(t);a.success||o.push({path:"root",message:`Schema validation failed: ${a.error.message}`,code:"SCHEMA_VALIDATION_FAILED"})}catch(a){r.debug("Schema validation error:",a)}return{valid:o.length===0,errors:o,warnings:s}}getHealthScore(t){let n=super.getHealthScore(t),o=[...n.issues],s=n.score;return t.rules.filter(l=>l.action?.type==="rate_limit").length===0&&(s-=10,o.push({severity:"info",category:"security",message:"No rate limiting rules configured",suggestion:"Consider adding rate limiting to protect against abuse"})),(!t.ips||t.ips.length===0)&&o.push({severity:"info",category:"security",message:"No IP blocking rules configured",suggestion:"Consider blocking known malicious IPs"}),{score:Math.max(0,s),grade:n.grade,issues:o,recommendations:n.recommendations}}diffRules(t,n){let o=[],s=[],a=[...n];for(let l of t){let c=n.find(d=>d.id===l.id);if(!c)o.push(l);else{Fe(Ce(l),Ce(c))||s.push({...l,id:c.id});let d=a.findIndex(u=>u.id===c.id);d!==-1&&a.splice(d,1)}}return{toAdd:o,toUpdate:s,toDelete:a}}diffIPRules(t,n){let o=[],s=[],a=[...n];for(let l of t){let c=n.find(d=>d.id===l.id);if(!c)r.debug("Rule not found in existing rules:",{configRule:l}),o.push(l);else{Fe(Ce(l),Ce(c))||s.push({...c,...l});let d=a.findIndex(u=>u.id===c.id);d!==-1&&a.splice(d,1)}}return{ipsToAdd:o,ipsToUpdate:s,ipsToDelete:a}}};_();var Rt=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 Ye(t,n,e);return new tt(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 Ye(n,o,t);return new tt(s)}static create(e,t,n){return this.fromConfig({projectId:e,teamId:t,token:n})}};var eo=require("crypto");_();var qe=F(require("chalk")),R=class i extends Error{code;suggestion;details;docsUrl;cause;constructor(e){super(e.message),this.name="DoormanError",this.code=e.code,this.suggestion=e.suggestion,this.details=e.details,this.docsUrl=e.docsUrl,this.cause=e.cause,Error.captureStackTrace&&Error.captureStackTrace(this,i)}format(){let e=[];return e.push(qe.default.red.bold(`[${this.code}] ${this.message}`)),e.push(""),this.suggestion&&(e.push(qe.default.yellow.bold("Suggestion:")),e.push(` ${this.suggestion}`),e.push("")),this.details&&Object.keys(this.details).length>0&&(e.push(qe.default.dim("Details:")),Object.entries(this.details).forEach(([t,n])=>{let o=typeof n=="object"?JSON.stringify(n,null,2).split(`
27
27
  `).join(`
28
28
  `):n;e.push(` ${qe.default.cyan(t)}: ${o}`)}),e.push("")),this.cause&&(e.push(qe.default.dim("Caused by:")),e.push(` ${this.cause.message}`),e.push("")),this.docsUrl&&(e.push(qe.default.cyan.bold("Documentation:")),e.push(` ${this.docsUrl}`),e.push("")),e.join(`
@@ -39,7 +39,7 @@ ${l.formatWarning(a)}`)}),n.push(s.result)}return n}async syncRules(t,n={}){let{
39
39
  ${p.formatWarning(u)}`)}),o.push(d.result)}return{rules:o,ips:s}}async syncRules(t,n={}){let{dryRun:o=!1}=n;try{let{OperationSafety:s}=(yt(),Re(ht)),a=await s.performDryRunValidation(t,"sync rules",async A=>this.getChanges(A));if(!a.valid)throw new Error(`Dry-run validation failed: ${a.issues.join(", ")}`);let l=a.changes,{rulesToAdd:c,rulesToUpdate:d,rulesToDelete:u}=l,p=l.ipsToAdd||[],f=l.ipsToUpdate||[],g=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: ${p.length}, Update: ${f.length}, Delete: ${g.length}`),{success:!0,rulesAdded:0,rulesUpdated:0,rulesDeleted:0,ipsAdded:0,ipsUpdated:0,ipsDeleted:0,warnings:a.warnings};let m=s.assessOperationRisk(l,t);if(!await s.confirmDestructiveOperation({operation:"sync rules",target:`Cloud Armor policy ${this.client.policyName}`,changes:l,riskLevel:m,skipConfirmation:n.force||!1,dryRun:!1,allowDeletions:n.allowDeletions||!1}))throw new Error("Operation cancelled by user");let z=[],x=[],S=(A,h)=>`${A}: ${h instanceof Error?h.message:String(h)}`,q=await this.client.getPolicy(),W=new Set(q.rules.map(A=>A.priority)),Q=1e3,ue=()=>{for(;W.has(Q);)Q+=1e3;return W.add(Q),Q},K=[];for(let A of[...u,...g]){let h=Bt(A.id);if(h!==void 0)try{r.debug(`Removing Cloud Armor rule at priority ${h}`),await this.client.removeRule(h)}catch(b){r.error(`Failed to remove Cloud Armor rule at priority ${h}:`,b),x.push(S(`Failed to delete rule at priority ${h}`,b))}}let me=0;for(let A of c){let h=A.priority??ue();try{let b=T.unifiedToGcp({...A,priority:h});b.warnings.forEach(ee=>z.push(ee.message)),r.debug(`Adding Cloud Armor rule at priority ${h}: ${b.result.description}`),await this.client.addRule(b.result),me++,K.push({oldId:A.id,newId:String(h),name:A.name})}catch(b){r.error(`Failed to add Cloud Armor rule "${A.name}":`,b),x.push(S(`Failed to add rule "${A.name}"`,b))}}let k=0;for(let A of d){let h=Bt(A.id);if(h!==void 0)try{let b=T.unifiedToGcp(A);b.warnings.forEach(pe=>z.push(pe.message)),A.priority!==void 0&&A.priority!==h?(r.debug(`Relocating Cloud Armor rule from priority ${h} to ${A.priority}`),await this.client.removeRule(h),await this.client.addRule(b.result),K.push({oldId:A.id,newId:String(A.priority),name:A.name})):(r.debug(`Updating Cloud Armor rule at priority ${h}`),await this.client.patchRule(h,b.result)),k++}catch(b){r.error(`Failed to update Cloud Armor rule at priority ${h}:`,b),x.push(S(`Failed to update rule at priority ${h}`,b))}}let $e=0;for(let A of p){let h=Bt(A.id)??ue();try{let b=T.unifiedIPToGcp(A,h);r.debug(`Adding Cloud Armor IP rule at priority ${h}: ${A.ip}`),await this.client.addRule(b),$e++,K.push({oldId:A.id,newId:String(h),name:A.ip})}catch(b){r.error(`Failed to add Cloud Armor IP rule for ${A.ip}:`,b),x.push(S(`Failed to add IP rule for ${A.ip}`,b))}}let Ee=0;for(let A of f){let h=Bt(A.id);if(h!==void 0)try{r.debug(`Updating Cloud Armor IP rule at priority ${h}: ${A.ip}`),await this.client.patchRule(h,T.unifiedIPToGcp(A,h)),Ee++}catch(b){r.error(`Failed to update Cloud Armor IP rule at priority ${h}:`,b),x.push(S(`Failed to update IP rule at priority ${h}`,b))}}return r.debug(`Custom Rules: Added: ${me}, Updated: ${k}, Deleted: ${u.length}`),r.debug(`IP Rules: Added: ${$e}, Updated: ${Ee}, Deleted: ${g.length}`),{success:x.length===0,rulesAdded:me,rulesUpdated:k,rulesDeleted:u.length,ipsAdded:$e,ipsUpdated:Ee,ipsDeleted:g.length,idRemappings:K.length>0?K:void 0,errors:x.length>0?x:void 0,warnings:z.length>0?z: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){let n=Le.safeParse(t);if(!n.success)throw new Error(`Invalid firewall configuration: ${n.error.message}`);this.assertManagedRulesSupported(t);try{r.debug("Fetching existing Cloud Armor configuration");let o=await this.client.getPolicy(),s=new Set(t.rules.map(m=>m.id).filter(m=>m!==void 0)),{rules:a,ips:l}=this.translatePolicy(o,s),{toAdd:c,toUpdate:d,toDelete:u}=this.diffItems(t.rules,a,(m,v)=>Fe(Ce(m),Ce(v))),{toAdd:p,toUpdate:f,toDelete:g}=this.diffItems(t.ips||[],l,(m,v)=>Fe(Ce(m),Ce(v)));return{rulesToAdd:c,rulesToUpdate:d,rulesToDelete:u,ipsToAdd:p,ipsToUpdate:f,ipsToDelete:g,hasChanges:c.length>0||d.length>0||u.length>0||p.length>0||f.length>0||g.length>0}}catch(o){throw r.error("Error fetching existing Cloud Armor configuration:",o),new Error("Failed to fetch existing Cloud Armor configuration")}}getSupportedFeatures(){return{supportsCustomRules:!0,supportsIPBlocking:!0,supportsRateLimiting:!0,supportsManagedRules:!1,supportsGeoBlocking:!0,supportsRedirect:!0,supportsChallenge:!1}}async verifyCredentials(){return this.client.verifyCredentials()}validateConfig(t){let n=super.validateConfig(t),o=[...n.errors],s=[...n.warnings];return t.provider&&t.provider!=="gcp"&&o.push({path:"provider",message:"Provider must be 'gcp' for CloudArmorFirewallService",code:"INVALID_PROVIDER"}),{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}}};_();var Et=class{static fromEnv(){let e=process.env.GOOGLE_CLOUD_PROJECT,t=process.env.GCP_POLICY_NAME,n=process.env.GOOGLE_APPLICATION_CREDENTIALS;if(!e)throw new Error("GOOGLE_CLOUD_PROJECT environment variable is required");if(!t)throw new Error("GCP_POLICY_NAME environment variable is required");let o=new st(e,t,n);return new at(o)}static fromConfig(e){let t=e.projectId||process.env.GOOGLE_CLOUD_PROJECT,n=e.policyName||process.env.GCP_POLICY_NAME,o=e.serviceAccountKeyPath||process.env.GOOGLE_APPLICATION_CREDENTIALS;if(!t)throw new Error("GCP project ID is required (provide projectId or set GOOGLE_CLOUD_PROJECT env var)");if(!n)throw new Error("Cloud Armor policy name is required (provide policyName or set GCP_POLICY_NAME env var)");r.debug("Creating Cloud Armor provider with config:",{projectId:t,policyName:n});let s=new st(t,n,o);return new at(s)}static create(e,t,n){return this.fromConfig({projectId:e,policyName:t,serviceAccountKeyPath:n})}};async function so(i){let e;if(i.provider)e=i.provider,r.debug(`Using explicitly specified provider: ${e}`);else{let t=Lt.detect(i.config);t.provider?(e=t.provider,r.debug(`Auto-detected provider: ${e} (${t.confidence} confidence)`),t.reasons.length>0&&r.debug(`Reasons: ${t.reasons.join(", ")}`)):i.interactive!==!1?e=await ks():(e="vercel",r.warn("No provider detected, defaulting to Vercel"))}try{let t=await $s(e,i);return _s(e,t)}catch(t){throw r.error(`Failed to initialize ${e} provider:`,t),t}}function As(i,e,t){if(!t.configKey)return;let o=i?.providers?.[e]?.[t.configKey];if(typeof o=="string")return o;if(t.legacyConfigKey){let s=i?.[t.legacyConfigKey];if(typeof s=="string")return s}}async function $s(i,e){let t=Ze[i],n=e.config,o={};for(let l of t.fields)o[l.key]=As(n,i,l);let s=pn(t,{explicit:e.credentials,config:o}),a=fn(t,s);if(a.length===0)return s;if(e.interactive===!1){let l=a.map(c=>`--${c.key}`).join(", ");throw new Error(`${Tt(i)} credentials missing. Provide ${l}.`)}r.warn(`Missing ${Tt(i)} credentials`),r.info(`Please provide your ${Tt(i)} credentials:`);for(let l of t.fields){if(s[l.key])continue;let c=l.promptMessage??Ps(l),d=l.secret?await an(c):await C(c,{type:"text"});s[l.key]=d||void 0}return s}function Ps(i){let e=i.required?"":" (optional)",t=i.secret?" ":"";return`${i.label}${e}:${t}`}function _s(i,e){switch(i){case"vercel":{let t={token:e.token,projectId:e.projectId,teamId:e.teamId};return{provider:Rt.fromConfig(t),vercelCredentials:t}}case"cloudflare":return{provider:Ct.fromConfig({apiToken:e.apiToken,zoneId:e.zoneId,accountId:e.accountId})};case"fastly":return{provider:It.fromConfig({apiToken:e.apiToken,workspaceId:e.workspaceId})};case"gcp":return{provider:Et.fromConfig({projectId:e.projectId,policyName:e.policyName,serviceAccountKeyPath:e.serviceAccountKeyPath})};default:throw new Error(`Unknown provider: ${i}`)}}async function ks(){r.info(ro.default.yellow("Unable to auto-detect firewall provider.")),r.info(`Please select a provider:
40
40
  `),H.forEach((s,a)=>{r.info(` ${a+1}. ${Tt(s)}`)}),r.info("");let i=await C(`Select provider (1-${H.length}):`,{type:"text"}),e=String(i).trim().toLowerCase(),t=H.find(s=>s===e);if(t)return t;let n=Number(e);if(Number.isInteger(n)&&n>=1&&n<=H.length)return H[n-1];let o=H[0];return r.warn(`Invalid choice: ${i}, defaulting to ${Tt(o)}`),o}function Tt(i){switch(i){case"vercel":return"Vercel Firewall";case"cloudflare":return"Cloudflare WAF";case"fastly":return"Fastly Next-Gen WAF";case"gcp":return"Google Cloud Armor";default:return i}}function Ss(i){if(i.length===1){let{field:t}=i[0],n=t.required?"":" (optional)",o=t.configKey?" or config file":"";return`${t.label}${n} (defaults to ${t.envVar} env var${o})`}return`Resolved against whichever provider is active: ${i.map(t=>`${t.field.label} (${t.field.envVar})`).join(" / ")}`}var mi=new Map;for(let i of H)for(let e of Ze[i].fields){let t=mi.get(e.key)??[];t.push({provider:i,field:e}),mi.set(e.key,t)}var ae=Object.fromEntries(Array.from(mi.entries()).map(([i,e])=>{let t=e.map(n=>n.field.cliAlias).find(n=>!!n);return[i,{type:"string",...t?{alias:t}:{},description:Ss(e)}]}));function X(i){return{token:i.token,projectId:i.projectId,teamId:i.teamId,apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId,workspaceId:i.workspaceId,policyName:i.policyName,serviceAccountKeyPath:i.serviceAccountKeyPath}}async function le(i,e){try{i.debug&&(r.level=ao.LogLevels.debug);let t;if(i.optionalConfig)try{t=await Z(i.config,"optional")}catch{t={}}else i.skipValidation?t=await Z(i.config,"raw"):t=await Z(i.config,"required");let{provider:n,vercelCredentials:o}=await so({provider:i.provider,config:t,interactive:!i.ci,credentials:X(i)}),s=o?.token??"",a=o?.projectId??"",l=o?.teamId??"";await e({config:t,provider:n,token:s,projectId:a,teamId:l})}catch(t){re(t,i.errorContext)}}var Us="backup",Ls="Backup or restore firewall configurations",xs={config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:se,...ae,output:{alias:"o",type:"string",description:"Output directory for backups",default:"./backups"},restore:{alias:"r",type:"string",description:"Restore from backup file"},list:{alias:"l",type:"boolean",description:"List available backups",default:!1},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}},Ds=async i=>{try{i.debug&&(r.level=lo.LogLevels.debug);let e=i.output||"./backups";if(i.list){if(!(0,Ae.existsSync)(e)){r.info(ne.default.yellow("No backup directory found."));return}let t=(0,Ae.readdirSync)(e).filter(n=>n.endsWith(".json")).map(n=>{let o=(0,lt.join)(e,n),s=(0,Ae.statSync)(o);return{name:n,size:s.size,created:s.mtime}}).sort((n,o)=>o.created.getTime()-n.created.getTime());if(t.length===0){r.info(ne.default.yellow("No backups found."));return}r.log(ne.default.bold(`
41
41
  \u{1F4E6} Available Backups:
42
- `)),t.forEach(n=>{r.log(`${ne.default.cyan(n.name)}`),r.log(` ${ne.default.dim("Created:")} ${n.created.toLocaleString()}`),r.log(` ${ne.default.dim("Size:")} ${(n.size/1024).toFixed(1)} KB`),r.log("")});return}if(i.restore){let t=i.restore.startsWith("/")?i.restore:(0,lt.join)(e,i.restore);(0,Ae.existsSync)(t)||(r.error(`Backup file not found: ${t}`),process.exit(1));let n=await Z(t,"raw"),o=i.config||".doorman.json";if((0,Ae.existsSync)(o)&&!await C(`Config file ${o} already exists. Do you want to overwrite it?`,{type:"confirm"})){r.info(ne.default.yellow("Restore cancelled."));return}let{backup:s,...a}=n;await Y(a,o),r.success(ne.default.green(`\u2705 Restored configuration from ${t} to ${o}`));return}await le({config:i.config,provider:i.provider,...X(i),debug:i.debug,ci:i.ci,errorContext:"creating backup"},async({provider:t,projectId:n,teamId:o})=>{r.start("Fetching current remote configuration...");let s=await t.fetchConfig(),a=s.metadata?.version??s.version;Oe.getInstance().validateConfig(s),(0,Ae.existsSync)(e)||(0,Ae.mkdirSync)(e,{recursive:!0});let c=new Date,d=c.toISOString().split("T")[0]??"unknown-date",u=(c.toISOString().split("T")[1]??"").split(".")[0]?.replace(/:/g,"-")??"unknown-time",f=`firewall-backup-${`${d}_${u}`}.json`,g=(0,lt.join)(e,f),m={...s,backup:{createdAt:new Date().toISOString(),source:"remote",provider:t.name,...t.name==="vercel"?{projectId:n,teamId:o}:{},originalVersion:a}};await Y(m,g,{validate:!1}),r.success(ne.default.green(`\u2705 Backup created: ${g}`)),r.log(""),r.log(ne.default.bold("Backup Details:")),r.log(`${ne.default.dim("Version:")} ${a??"unknown"}`),r.log(`${ne.default.dim("Rules:")} ${s.rules.length} custom, ${s.ips?.length??0} IP blocking`),r.log(`${ne.default.dim("Created:")} ${new Date().toLocaleString()}`),!(0,lt.isAbsolute)(e)&&on()&&!rn(e)&&r.warn(ne.default.yellow(`\u{1F4A1} ${e} isn't in .gitignore \u2014 firewall backups may contain rule details you don't want in git history.`)),r.log(""),r.log(ne.default.dim("To restore this backup later, run:")),r.log(ne.default.cyan(`doorman backup --restore ${f}`))})}catch(e){re(e,"managing backup")}};var yi={};oe(yi,{builder:()=>Ns,command:()=>Os,desc:()=>Fs,handler:()=>Vs});var ce=F(require("chalk"));_();function be(i){if(xe(i))return i;let e=i,t=e.rules.map(o=>T.vercelToUnified(o).result),n=(e.ips??[]).map(o=>T.vercelIPToUnified(o));return{version:"2.0",provider:"vercel",providers:{vercel:{projectId:e.projectId,teamId:e.teamId}},rules:t,ips:n,metadata:{version:e.version,updatedAt:e.updatedAt}}}function Mt(i,e){let t=!1,n=xe(i),o=n?{...i,metadata:{...i.metadata}}:{...i};if(e.version!==void 0)if(n){let s=o;s.metadata?.version!==e.version&&(s.metadata={...s.metadata,version:e.version,updatedAt:e.updatedAt??new Date().toISOString()},t=!0)}else{let s=o;s.version!==e.version&&(s.version=e.version,s.updatedAt=e.updatedAt??new Date().toISOString(),t=!0)}if(e.idRemappings&&e.idRemappings.length>0){let s=new Map(e.idRemappings.filter(f=>f.oldId).map(f=>[f.oldId,f.newId])),a=new Map(e.idRemappings.filter(f=>!f.oldId).map(f=>[f.name,f.newId])),l=(f,g)=>{let m=f.id?s.get(f.id):a.get(g);return m&&m!==f.id?{...f,id:m}:f},c=!1,d=o.rules.map(f=>{let g=l(f,f.name);return g!==f&&(c=!0),g}),u=!1,p=(o.ips??[]).map(f=>{let g=l(f,f.ip);return g!==f&&(u=!0),g});(c||u)&&(o={...o,rules:d,...u?{ips:p}:{}},t=!0)}return{config:o,changed:t}}function co(i,e){if(xe(e))return{...e,...i};let t=e,n=i.rules.map(s=>T.unifiedToVercel(s).result),o=(i.ips??[]).map(s=>({id:s.id||"",ip:s.ip,hostname:s.hostname||"",action:s.action,notes:s.notes}));return{...t,projectId:i.providers?.vercel?.projectId??t.projectId,teamId:i.providers?.vercel?.teamId??t.teamId,version:i.metadata?.version,updatedAt:i.metadata?.updatedAt,rules:n,ips:o}}var Os="diff",Fs="Show detailed differences between local and remote firewall configuration",Ns={config:{alias:"c",type:"string",description:"Path to firewall config file"},provider:se,...ae,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}},Vs=async i=>{await le({config:i.config,provider:i.provider,...X(i),debug:i.debug,ci:i.ci,errorContext:"calculating diff"},async({config:e,provider:t})=>{await Bs(t,be(e),i)})};async function Bs(i,e,t){let n=t.format==="json";n||r.start("Calculating differences...");let o=await i.getChanges(e),s=o.ipsToAdd??[],a=o.ipsToUpdate??[],l=o.ipsToDelete??[];if(n){r.log(JSON.stringify({rules:{toAdd:o.rulesToAdd,toUpdate:o.rulesToUpdate,toDelete:o.rulesToDelete},ips:{toAdd:s,toUpdate:a,toDelete:l},summary:{hasChanges:o.hasChanges,ruleChanges:o.rulesToAdd.length+o.rulesToUpdate.length+o.rulesToDelete.length,ipChanges:s.length+a.length+l.length}},null,2));return}if(!o.hasChanges){r.success(ce.default.green("No differences found. Local and remote configurations are in sync."));return}r.log(ce.default.bold(`
42
+ `)),t.forEach(n=>{r.log(`${ne.default.cyan(n.name)}`),r.log(` ${ne.default.dim("Created:")} ${n.created.toLocaleString()}`),r.log(` ${ne.default.dim("Size:")} ${(n.size/1024).toFixed(1)} KB`),r.log("")});return}if(i.restore){let t=i.restore.startsWith("/")?i.restore:(0,lt.join)(e,i.restore);(0,Ae.existsSync)(t)||(r.error(`Backup file not found: ${t}`),process.exit(1));let n=await Z(t,"raw"),o=i.config||".doorman.json";if((0,Ae.existsSync)(o)&&!await C(`Config file ${o} already exists. Do you want to overwrite it?`,{type:"confirm"})){r.info(ne.default.yellow("Restore cancelled."));return}let{backup:s,...a}=n;await Y(a,o),r.success(ne.default.green(`\u2705 Restored configuration from ${t} to ${o}`));return}await le({config:i.config,provider:i.provider,...X(i),debug:i.debug,ci:i.ci,errorContext:"creating backup"},async({provider:t,projectId:n,teamId:o})=>{r.start("Fetching current remote configuration...");let s=await t.fetchConfig(),a=s.metadata?.version??s.version;Oe.getInstance().validateConfig(s),(0,Ae.existsSync)(e)||(0,Ae.mkdirSync)(e,{recursive:!0});let c=new Date,d=c.toISOString().split("T")[0]??"unknown-date",u=(c.toISOString().split("T")[1]??"").split(".")[0]?.replace(/:/g,"-")??"unknown-time",f=`firewall-backup-${`${d}_${u}`}.json`,g=(0,lt.join)(e,f),m={...s,backup:{createdAt:new Date().toISOString(),source:"remote",provider:t.name,...t.name==="vercel"?{projectId:n,teamId:o}:{},originalVersion:a}};await Y(m,g,{validate:!1}),r.success(ne.default.green(`\u2705 Backup created: ${g}`)),r.log(""),r.log(ne.default.bold("Backup Details:")),r.log(`${ne.default.dim("Version:")} ${a??"unknown"}`),r.log(`${ne.default.dim("Rules:")} ${s.rules.length} custom, ${s.ips?.length??0} IP blocking`),r.log(`${ne.default.dim("Created:")} ${new Date().toLocaleString()}`),!(0,lt.isAbsolute)(e)&&on()&&!rn(e)&&r.warn(ne.default.yellow(`\u{1F4A1} ${e} isn't in .gitignore \u2014 firewall backups may contain rule details you don't want in git history.`)),r.log(""),r.log(ne.default.dim("To restore this backup later, run:")),r.log(ne.default.cyan(`doorman backup --restore ${f}`))})}catch(e){re(e,"managing backup")}};var yi={};oe(yi,{builder:()=>Ns,command:()=>Os,desc:()=>Fs,handler:()=>Vs});var ce=F(require("chalk"));_();function be(i){if(xe(i))return i;let e=i,t=e.rules.map(o=>T.vercelToUnified(o).result),n=(e.ips??[]).map(o=>T.vercelIPToUnified(o));return{version:"2.0",provider:"vercel",providers:{vercel:{projectId:e.projectId,teamId:e.teamId}},rules:t,ips:n,metadata:{version:e.version,updatedAt:e.updatedAt}}}function Mt(i,e){let t=!1,n=xe(i),o=n?{...i,metadata:{...i.metadata}}:{...i};if(e.version!==void 0)if(n){let s=o;s.metadata?.version!==e.version&&(s.metadata={...s.metadata,version:e.version,updatedAt:e.updatedAt??new Date().toISOString()},t=!0)}else{let s=o;s.version!==e.version&&(s.version=e.version,s.updatedAt=e.updatedAt??new Date().toISOString(),t=!0)}if(e.idRemappings&&e.idRemappings.length>0){let s=new Map(e.idRemappings.filter(f=>f.oldId).map(f=>[f.oldId,f.newId])),a=new Map(e.idRemappings.filter(f=>!f.oldId).map(f=>[f.name,f.newId])),l=(f,g)=>{let m=f.id?s.get(f.id):a.get(g);return m&&m!==f.id?{...f,id:m}:f},c=!1,d=o.rules.map(f=>{let g=l(f,f.name);return g!==f&&(c=!0),g}),u=!1,p=(o.ips??[]).map(f=>{let g=l(f,f.ip);return g!==f&&(u=!0),g});(c||u)&&(o={...o,rules:d,...u?{ips:p}:{}},t=!0)}return{config:o,changed:t}}function co(i,e){if(xe(e))return{...e,...i};let t=e,n=i.rules.map(s=>T.unifiedToVercel(s).result),o=(i.ips??[]).map(s=>({id:s.id||"",ip:s.ip,hostname:s.hostname||"",action:s.action,notes:s.notes}));return{...t,projectId:i.providers?.vercel?.projectId??t.projectId,teamId:i.providers?.vercel?.teamId??t.teamId,version:i.metadata?.version,updatedAt:i.metadata?.updatedAt,rules:n,ips:o}}var Os="diff",Fs="Show detailed differences between local and remote firewall configuration",Ns={config:{alias:"c",type:"string",description:"Path to firewall config file"},provider:se,...ae,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}},Vs=async i=>{await le({config:i.config,provider:i.provider,...X(i),debug:i.debug,ci:i.ci,errorContext:"calculating diff"},async({config:e,provider:t})=>{await Bs(t,be(e),i)})};async function Bs(i,e,t){let n=t.format==="json";n||r.start("Calculating differences...");let o=await i.getChanges(e,{readOnly:!0}),s=o.ipsToAdd??[],a=o.ipsToUpdate??[],l=o.ipsToDelete??[];if(n){r.log(JSON.stringify({rules:{toAdd:o.rulesToAdd,toUpdate:o.rulesToUpdate,toDelete:o.rulesToDelete},ips:{toAdd:s,toUpdate:a,toDelete:l},summary:{hasChanges:o.hasChanges,ruleChanges:o.rulesToAdd.length+o.rulesToUpdate.length+o.rulesToDelete.length,ipChanges:s.length+a.length+l.length}},null,2));return}if(!o.hasChanges){r.success(ce.default.green("No differences found. Local and remote configurations are in sync."));return}r.log(ce.default.bold(`
43
43
  \u{1F50D} ${i.name} Configuration Differences
44
44
  `)),r.log(ce.default.bold("Rule Changes:")),r.log(` ${ce.default.green("+")} ${o.rulesToAdd.length} to add`),o.rulesToAdd.forEach(d=>r.log(` ${ce.default.green("+")} ${d.name}`)),r.log(` ${ce.default.cyan("~")} ${o.rulesToUpdate.length} to update`),o.rulesToUpdate.forEach(d=>r.log(` ${ce.default.cyan("~")} ${d.name}`)),r.log(` ${ce.default.red("-")} ${o.rulesToDelete.length} to delete`),o.rulesToDelete.forEach(d=>r.log(` ${ce.default.red("-")} ${d.name}`)),(s.length||a.length||l.length)&&(r.log(""),r.log(ce.default.bold("IP Rule Changes:")),r.log(` ${ce.default.green("+")} ${s.length} to add`),r.log(` ${ce.default.cyan("~")} ${a.length} to update`),r.log(` ${ce.default.red("-")} ${l.length} to delete`));let c=o.rulesToAdd.length+o.rulesToUpdate.length+o.rulesToDelete.length+s.length+a.length+l.length;r.log(""),r.log(ce.default.bold(`Summary: ${c} total changes detected`)),r.log(ce.default.dim("Run `sync` to apply these changes to the remote configuration."))}var Ri={};oe(Ri,{builder:()=>Ws,command:()=>Ms,desc:()=>qs,handler:()=>js});var Ge=F(require("chalk")),uo=require("zod");_();Pe();var Ms="download [configVersion]",qs="Download remote Vercel Firewall rules and update local config, optionally for a specific configuration version",Ws={configVersion:{type:"number",description:"Specific configuration version to download (defaults to latest)"},config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:se,...ae,dryRun:{alias:"d",type:"boolean",description:"Show rules that would be downloaded without making changes",default:!1},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}},js=async i=>{await le({config:i.config,provider:i.provider,...X(i),debug:i.debug,ci:i.ci,optionalConfig:!0,skipValidation:!0,errorContext:"downloading firewall rules"},async({config:e,provider:t})=>{if(i.configVersion!==void 0)try{_t.parse(i.configVersion)}catch(n){throw n instanceof uo.z.ZodError&&(r.error("Invalid configuration version number. Version must be a positive integer."),process.exit(1)),n}await zs(t,e,i)})};async function zs(i,e,t){r.start(`Fetching remote firewall configuration${t.configVersion?` version ${t.configVersion}`:""} ...`);let n=await i.fetchConfig(t.configVersion),o=n.rules,s=n.ips??[];if(o.length>0?(r.log(Ge.default.bold(`
45
45
  Remote Custom Rules to Download (${o.length}):
@@ -124,7 +124,7 @@ Use --id for exact matching, or --interactive to select from a list.`))),process
124
124
  ${i.dryRun?"Would remove":"Removing"} ${o.length} rule(s):`)),vi(o),i.dryRun){r.log(B.default.dim(`
125
125
  Dry run \u2014 no changes made.`));return}if(!await Ro(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(B.default.yellow("\u26A0\uFE0F Configuration will have no rules after removal.")),await Y(a,i.config),r.success(B.default.green(`\u2714 Removed ${o.length} rule(s) from configuration`)),vi(o)}r.log(""),r.log(B.default.dim(`Run ${B.default.cyan("doorman sync")} to deploy these changes.`))}catch(e){re(e,"removing rule")}};var Ei={};oe(Ei,{builder:()=>wa,command:()=>ya,desc:()=>Ra,handler:()=>Ca});var L=F(require("chalk"));_();var ya="setup",Ra="Show setup instructions and helpful links for Doorman",wa={},Ca=async i=>{r.log(""),r.log(L.default.bold.cyan("\u{1F6AA} Doorman Setup Guide")),r.log(L.default.dim(`Everything you need to get started with firewall management
126
126
  `)),r.log(L.default.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(L.default.bold("\u{1F50D} Finding Your Project Information")),r.log(""),r.log(L.default.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(L.default.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(L.default.bold("\u{1F511} Creating API Token")),r.log(""),r.log(L.default.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(L.default.cyan("Required Permissions:")),r.log(" \u2022 Read access to project settings"),r.log(" \u2022 Write access to firewall configuration"),r.log(""),r.log(L.default.bold("\u{1F30D} Environment Setup")),r.log(""),r.log(L.default.cyan("Set your API token as environment variable:")),r.log(""),r.log(L.default.green("# For current session:")),r.log('export VERCEL_TOKEN="your-token-here"'),r.log(""),r.log(L.default.green("# For permanent setup (add to ~/.bashrc or ~/.zshrc):")),r.log(`echo 'export VERCEL_TOKEN="your-token-here"' >> ~/.bashrc`),r.log(""),r.log(L.default.green("# Or create a .env file in your project:")),r.log('echo "VERCEL_TOKEN=your-token-here" > .env'),r.log(""),r.log(L.default.bold("\u{1F680} Quick Start Commands")),r.log(""),r.log(L.default.cyan("1. Initialize configuration:")),r.log(" doorman init --interactive"),r.log(""),r.log(L.default.cyan("2. Validate setup:")),r.log(" doorman validate"),r.log(""),r.log(L.default.cyan("3. Check current status:")),r.log(" doorman status"),r.log(""),r.log(L.default.cyan("4. View existing rules:")),r.log(" doorman list"),r.log(""),r.log(L.default.cyan("5. Sync your configuration:")),r.log(" doorman sync"),r.log(""),r.log(L.default.bold("\u{1F527} Common Issues")),r.log(""),r.log(L.default.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(L.default.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(L.default.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(L.default.bold("\u{1F4DA} Additional Resources")),r.log(""),r.log(L.default.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(L.default.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(L.default.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(L.default.bold.green("\u2705 Ready to get started?")),r.log(L.default.dim("Run: ")+L.default.cyan("doorman init --interactive")),r.log("")};var Ti={};oe(Ti,{builder:()=>Ia,command:()=>ba,desc:()=>va,handler:()=>Ea});var N=F(require("chalk"));_();var ba="status",va="Show sync status between local and remote firewall configuration",Ia={config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:se,...ae,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}},Ea=async i=>{await le({config:i.config,provider:i.provider,...X(i),debug:i.debug,ci:i.ci,errorContext:"checking status"},async({config:e,provider:t})=>{await Ta(t,be(e),i)})};async function Ta(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.default.bold(`
127
+ `),r.log(L.default.bold("\u{1F50D} Finding Your Project Information")),r.log(""),r.log(L.default.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(L.default.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(L.default.bold("\u{1F511} Creating API Token")),r.log(""),r.log(L.default.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(L.default.cyan("Required Permissions:")),r.log(" \u2022 Read access to project settings"),r.log(" \u2022 Write access to firewall configuration"),r.log(""),r.log(L.default.bold("\u{1F30D} Environment Setup")),r.log(""),r.log(L.default.cyan("Set your API token as environment variable:")),r.log(""),r.log(L.default.green("# For current session:")),r.log('export VERCEL_TOKEN="your-token-here"'),r.log(""),r.log(L.default.green("# For permanent setup (add to ~/.bashrc or ~/.zshrc):")),r.log(`echo 'export VERCEL_TOKEN="your-token-here"' >> ~/.bashrc`),r.log(""),r.log(L.default.green("# Or create a .env file in your project:")),r.log('echo "VERCEL_TOKEN=your-token-here" > .env'),r.log(""),r.log(L.default.bold("\u{1F680} Quick Start Commands")),r.log(""),r.log(L.default.cyan("1. Initialize configuration:")),r.log(" doorman init --interactive"),r.log(""),r.log(L.default.cyan("2. Validate setup:")),r.log(" doorman validate"),r.log(""),r.log(L.default.cyan("3. Check current status:")),r.log(" doorman status"),r.log(""),r.log(L.default.cyan("4. View existing rules:")),r.log(" doorman list"),r.log(""),r.log(L.default.cyan("5. Sync your configuration:")),r.log(" doorman sync"),r.log(""),r.log(L.default.bold("\u{1F527} Common Issues")),r.log(""),r.log(L.default.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(L.default.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(L.default.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(L.default.bold("\u{1F4DA} Additional Resources")),r.log(""),r.log(L.default.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(L.default.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(L.default.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(L.default.bold.green("\u2705 Ready to get started?")),r.log(L.default.dim("Run: ")+L.default.cyan("doorman init --interactive")),r.log("")};var Ti={};oe(Ti,{builder:()=>Ia,command:()=>ba,desc:()=>va,handler:()=>Ea});var N=F(require("chalk"));_();var ba="status",va="Show sync status between local and remote firewall configuration",Ia={config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:se,...ae,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}},Ea=async i=>{await le({config:i.config,provider:i.provider,...X(i),debug:i.debug,ci:i.ci,errorContext:"checking status"},async({config:e,provider:t})=>{await Ta(t,be(e),i)})};async function Ta(i,e,t){let n=t.format==="json";n||r.start("Checking sync status...");let o=await i.getChanges(e,{readOnly:!0}),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.default.bold(`
128
128
  \u{1F4CA} ${i.name} Sync Status Summary
129
129
  `)),o.version!==void 0&&(r.log(`${N.default.dim("Local Version:")} ${N.default.yellow(a??"unknown")}`),r.log(`${N.default.dim("Remote Version:")} ${N.default.yellow(o.version)}`),r.log(`${N.default.dim("Version Status:")} ${l?N.default.red("Out of sync"):N.default.green("In sync")}`),r.log("")),r.log(`${N.default.dim("Rules:")}`),r.log(` ${N.default.green("+")} ${o.rulesToAdd.length} to add`),r.log(` ${N.default.cyan("~")} ${o.rulesToUpdate.length} to update`),r.log(` ${N.default.red("-")} ${o.rulesToDelete.length} to delete`),r.log(`${N.default.dim("IPs:")}`),r.log(` ${N.default.green("+")} ${o.ipsToAdd?.length??0} to add`),r.log(` ${N.default.cyan("~")} ${o.ipsToUpdate?.length??0} to update`),r.log(` ${N.default.red("-")} ${o.ipsToDelete?.length??0} to delete`),r.log(""),c?r.success(N.default.green("\u2705 Everything is in sync!")):r.warn(N.default.yellow("\u26A0\uFE0F Changes detected. Run `sync` to apply changes.")),r.log(`
130
130
  `+N.default.bold("\u{1F3E5} Configuration Health Check")),r.log(`${N.default.dim("Score:")} ${s.score}/100 (${s.grade})`),s.issues.forEach(d=>{let u=d.severity==="error"?N.default.red("\u2717"):d.severity==="warning"?N.default.yellow("\u26A0"):N.default.dim("\u2139");r.log(` ${u} [${d.category}] ${d.message}`),d.suggestion&&r.log(` ${N.default.dim(d.suggestion)}`)}),s.recommendations.length>0&&(r.log(N.default.dim(`
package/dist/bin/run.mjs CHANGED
@@ -22,7 +22,7 @@ Dry run - The following IP rule would be added:`)),Vi(t),r.log(""),r.log(V.dim(J
22
22
  Dry run - The following rule would be added:`)),Ni(t),r.log(""),r.log(V.dim(JSON.stringify(t,null,2)));return}r.start("Loading configuration...");let o=await H(i.config,"raw"),s=o.rules||[],a=Qo({...o,rules:s},t);if(a){if(r.warn(V.yellow(`\u26A0\uFE0F ${a}`)),i.ci||!process.stdin.isTTY){r.info(V.dim("Skipping \u2014 non-interactive and a conflicting rule already exists. Rule not added."));return}if(!await C("Proceed anyway?",{type:"confirm",initial:!1})){r.info("Cancelled.");return}}let l={...o,rules:[...s,t]};await Z(l,i.config),r.success(V.green(`\u2714 Rule "${t.name}" added to configuration`)),Ni(t)}r.log(""),r.log(V.dim(`Run ${V.cyan("doorman sync")} to deploy this rule.`))}catch(e){ne(e,"adding rule")}};var li={};ie(li,{builder:()=>xs,command:()=>Us,desc:()=>Ls,handler:()=>Ds});_();import ae from"chalk";import{existsSync as Ut,mkdirSync as $s,readdirSync as Ps,statSync as _s}from"fs";import{isAbsolute as ks,join as ai}from"path";import{LogLevels as Ss}from"consola";Te();import{existsSync as Mi,readFileSync as er}from"fs";import{join as qi}from"path";function Wi(i=process.cwd()){return Mi(qi(i,".git"))}function ji(i,e=process.cwd()){let t=qi(e,".gitignore");if(!Mi(t))return!1;let n=i.replace(/^\.\//,"").replace(/^\//,"").replace(/\/$/,""),o;try{o=er(t,"utf8").split(`
23
23
  `)}catch{return!1}return o.some(s=>{let a=s.trim();if(!a||a.startsWith("#"))return!1;let l=a.replace(/^\//,"").replace(/\/$/,"");return l===n||n.startsWith(`${l}/`)})}var oe={type:"string",choices:K,description:"Firewall provider (auto-detected)"};_();import{LogLevels as As}from"consola";_();Te();import ws from"chalk";import{emitKeypressEvents as tr}from"readline";function ir(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 nr(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 zi(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;tr(t),t.setRawMode(!0),t.resume();let o="",s=()=>{t.removeListener("keypress",a),t.setRawMode(n??!1)},a=(l,c)=>{let d=ir(l,c),u=nr(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
24
  `),u.cancelled&&process.exit(0),e(o))};t.on("keypress",a)})}_();var Gi={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 Ki={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 Hi={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 Zi={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 Yi(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 Ji(i,e){return i.fields.filter(t=>t.required&&!e[t.key])}var We={vercel:Gi,cloudflare:Ki,fastly:Hi,gcp:Zi};var Tt=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 K){let a=this.detectionField(s);if(!a?.configKey)continue;if(typeof(o[s]||{})[a.configKey]=="string")return t.push(`${a.label} found in config`),{provider:s,confidence:"high",reasons:t}}}if(e&&"projectId"in e&&typeof e.projectId=="string")return t.push("Legacy Vercel project ID found in config"),{provider:"vercel",confidence:"high",reasons:t};let n=this.detectFromEnvironment();return n.provider?n:(r.debug("Unable to auto-detect provider"),{provider:null,confidence:"low",reasons:["No provider information found in config or environment"]})}static detectFromEnvironment(){let e=[],t=process.env.DOORMAN_PROVIDER;if(t&&this.isValidProvider(t))return e.push(`Provider set via DOORMAN_PROVIDER environment variable: ${t}`),{provider:t,confidence:"high",reasons:e};for(let n of K){let o=this.detectionField(n);if(!(!o||!process.env[o.envVar]))return e.push(`${o.label} found in environment (${o.envVar})`),{provider:n,confidence:"medium",reasons:e}}return{provider:null,confidence:"low",reasons:["No provider credentials found in environment"]}}static isValidProvider(e){return K.includes(e)}static getProvider(e,t="vercel"){let n=this.detect(e);return n.provider?(r.debug(`Provider detected: ${n.provider} (${n.confidence} confidence)`),n.reasons.length>0&&r.debug(`Detection reasons: ${n.reasons.join(", ")}`),n.provider):(r.debug(`No provider detected, using fallback: ${t}`),t)}static detectAll(e){let t=new Set;if(e&&"provider"in e){let n=e.provider;typeof n=="string"&&this.isValidProvider(n)&&t.add(n)}if(e&&"providers"in e&&typeof e.providers=="object"&&e.providers!==null){let n=e.providers;for(let o of K){let s=this.detectionField(o);if(!s?.configKey)continue;typeof(n[o]||{})[s.configKey]=="string"&&t.add(o)}}e&&"projectId"in e&&typeof e.projectId=="string"&&t.add("vercel");for(let n of K){let o=this.detectionField(n);o&&process.env[o.envVar]&&t.add(n)}return Array.from(t)}};_();Te();import Qi from"chalk";var At=i=>({$schema:$i,firewallEnabled:!0,rules:[],ips:[],...i});_();var $e=class{baseUrl;providerName;rateLimitInfo={};constructor(e,t){this.baseUrl=e,this.providerName=t}async makeRequest(e,t={}){let n=`${this.baseUrl}${e}`,{timeout:o=3e4,retries:s=3,retryDelay:a=1e3,...l}=t,c=null,d=null,u=null,p=()=>{u&&(clearTimeout(u),u=null),d&&(d.abort(),d=null)};try{for(let f=0;f<=s;f++)try{d=new AbortController,u=setTimeout(()=>{d&&d.abort()},o);let g=await fetch(n,{...l,headers:{"Content-Type":"application/json",...await this.getAuthHeaders(),...l.headers},signal:d.signal});if(u&&(clearTimeout(u),u=null),this.updateRateLimitInfo(g),g.status===429){let v=this.calculateRateLimitWait(f,g);if(f===s)throw new Error(`${this.providerName} API rate limit exceeded after ${s+1} attempt(s) (429 Too Many Requests). Retry after ~${Math.round(v/1e3)}s.`);r.warn(`Rate limit exceeded for ${this.providerName}. Waiting ${v}ms before retry (attempt ${f+1}/${s+1})...`),await this.delay(v);continue}if(!g.ok)throw await this.handleErrorResponse(g);let m;try{let v=await g.text();v.trim()?m=JSON.parse(v):m={}}catch(v){throw r.error(`Failed to parse response from ${n}:`,v),new Error(`Invalid JSON response from ${this.providerName} API: ${v instanceof Error?v.message:String(v)}`)}return this.isLargeResponse(m)&&(r.debug(`Large response detected from ${n}, validating structure...`),this.validateLargeResponse(m)),m}catch(g){if(c=g,u&&(clearTimeout(u),u=null),this.isNonRetryableError(g)||f===s)throw g;let m=a*Math.pow(2,f),v=Math.random()*.1*m,j=Math.min(m+v,3e4);r.debug(`Request failed (attempt ${f+1}/${s+1}). Retrying in ${Math.round(j)}ms...`),r.debug(`Error details: ${g instanceof Error?g.message:String(g)}`),await this.delay(j)}throw c||new Error("Request failed after all retries")}finally{p()}}async get(e,t){return this.makeRequest(e,{...t,method:"GET"})}async post(e,t,n){return this.makeRequest(e,{...n,method:"POST",body:t?JSON.stringify(t):void 0})}async put(e,t,n){return this.makeRequest(e,{...n,method:"PUT",body:t?JSON.stringify(t):void 0})}async patch(e,t,n){return this.makeRequest(e,{...n,method:"PATCH",body:t?JSON.stringify(t):void 0})}async delete(e,t){return this.makeRequest(e,{...t,method:"DELETE"})}updateRateLimitInfo(e){let t=e.headers.get("X-RateLimit-Limit"),n=e.headers.get("X-RateLimit-Remaining"),o=e.headers.get("X-RateLimit-Reset");t&&(this.rateLimitInfo.limit=parseInt(t,10)),n&&(this.rateLimitInfo.remaining=parseInt(n,10)),o&&(this.rateLimitInfo.reset=parseInt(o,10)),this.rateLimitInfo.remaining&&this.rateLimitInfo.remaining<10&&r.warn(`Approaching rate limit for ${this.providerName}: ${this.rateLimitInfo.remaining} requests remaining`)}calculateRateLimitWait(e=0,t){let o=t?this.parseRetryAfter(t.headers.get("Retry-After")):null;if(o!==null)return Math.min(Math.max(o,1e3),6e4);if(this.rateLimitInfo.reset){let l=Math.floor(Date.now()/1e3),c=(this.rateLimitInfo.reset-l)*1e3;return Math.min(Math.max(c,1e3),6e4)}let a=5e3*Math.pow(2,e);return Math.min(a,6e4)}parseRetryAfter(e){if(!e)return null;let t=Number(e);if(Number.isFinite(t))return t*1e3;let n=Date.parse(e);return Number.isNaN(n)?null:n-Date.now()}async handleErrorResponse(e){let t=`${this.providerName} API error: ${e.status} ${e.statusText}`,n=t;try{let o=await e.json();if(typeof o=="object"&&o!==null){let s=o;if(typeof s.error=="string")n=`${t} - ${s.error}`;else if(typeof s.message=="string")n=`${t} - ${s.message}`;else if(Array.isArray(s.errors)){let a=s.errors.map(l=>typeof l=="object"&&l!==null&&"message"in l?String(l.message):JSON.stringify(l)).join(", ");n=`${t} - ${a}`}}}catch{}return r.error(n),new Error(n)}isNonRetryableError(e){if(typeof e=="object"&&e!==null&&e.name==="AbortError")return!0;let t=e.message;return typeof t=="string"&&t.includes("API error: 4")?!t.includes("429"):!1}delay(e){return new Promise(t=>setTimeout(t,e))}getRateLimitInfo(){return{...this.rateLimitInfo}}isLargeResponse(e){if(!e||typeof e!="object")return!1;let t=(n,o=0)=>{if(o>10)return!1;for(let[,s]of Object.entries(n))if(Array.isArray(s)&&s.length>1e3||typeof s=="object"&&s!==null&&t(s,o+1))return!0;return!1};return t(e)}validateLargeResponse(e){if(!e||typeof e!="object")return;let t=(o,s)=>{o.length>5e3&&r.warn(`Large array detected at ${s} with ${o.length} items. This may impact performance.`);let a=Math.min(10,o.length),l=o[0];if(l&&typeof l=="object"){let c=Object.keys(l);for(let d=1;d<a;d++){let u=o[d];if(!u||typeof u!="object"){r.warn(`Inconsistent array structure at ${s}[${d}]: expected object, got ${typeof u}`);continue}let p=Object.keys(u),f=c.filter(g=>!p.includes(g));f.length>0&&r.warn(`Inconsistent array structure at ${s}[${d}]: missing keys ${f.join(", ")}`)}}},n=(o,s="root",a=0)=>{if(!(a>10))for(let[l,c]of Object.entries(o)){let d=`${s}.${l}`;Array.isArray(c)?t(c,d):typeof c=="object"&&c!==null&&n(c,d,a+1)}};n(e)}};var Xi=process.env.DOORMAN_VERCEL_API_BASE_URL||"https://api.vercel.com/v1/security/firewall/config",je=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?`${Xi}/${t}`:Xi;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,assumeYes:s=!1}=n??{},a=await this.get(this.getUrl(t));if(r.debug("Config Version:",t??"latest"),r.debug("Fetched Config:",t?a:a.active),t)return a;if(!a||"active"in a&&a.active===null){if(r.warn(Qi.bold("No firewall configuration found.")),!o)return r.debug("Skipping create-configuration prompt for read-only fetch."),this.emptyConfigPlaceholder();if(s||await C("Would you like to create one?",{type:"confirm"})){r.debug("Creating new empty firewall configuration...");let c=await this.putEmptyConfig();return r.debug("Empty firewall configuration created successfully"),r.debug(`New configuration version: ${Qi.yellow(c.version)}`),c}else return a.active}return a.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}=At();return r.debug("Empty Config:",n),this.putConfig(n)}async putConfig(t){return(await this.put(this.getUrl(),t)).active}async fetchActiveFirewallRules(){return(await this.fetchFirewallConfig())?.rules}async updateFirewallRule(t){let n=!t.id||t.id==="-",o={action:n?"rules.insert":"rules.update",id:n?null:t.id,value:{name:t.name,description:t.description,action:t.action,conditionGroup:t.conditionGroup,active:t.active}};return this.patch(this.getUrl(),o,n?{retries:0}:void 0)}async createFirewallRule(t){return this.updateFirewallRule({...t,id:"-"})}async deleteFirewallRule(t){let n={action:"rules.remove",id:t.id,value:null};await this.patch(this.getUrl(),n)}async updateIPBlockingRule(t){let n=!t.id||t.id==="-",o={action:n?"ip.insert":"ip.update",id:n?null:t.id,value:{action:t.action,hostname:t.hostname,ip:t.ip,notes:t.notes??null}};return this.patch(this.getUrl(),o,n?{retries:0}:void 0)}async createIPBlockingRule(t){return this.updateIPBlockingRule({...t,id:"-"})}async deleteIPBlockingRule(t){let n={action:"ip.remove",id:t.id,value:null};await this.patch(this.getUrl(),n)}async verifyCredentials(){try{return await this.fetchFirewallConfig(),!0}catch(t){return r.debug("Credential verification failed:",t),!1}}};_();import ge from"chalk";_();var Pe=class{validateConfig(e){let t=[],n=[];if(e||t.push({path:"root",message:"Configuration is required",code:"CONFIG_REQUIRED"}),e&&!e.rules&&t.push({path:"rules",message:"Rules array is required",code:"RULES_REQUIRED"}),e&&e.rules&&!Array.isArray(e.rules)&&t.push({path:"rules",message:"Rules must be an array",code:"RULES_INVALID_TYPE"}),e&&Array.isArray(e.rules)){let o=new Map;e.rules.forEach((s,a)=>{let l=s.id||s.name;if(!l)return;let c=o.get(l);if(c===void 0){o.set(l,a);return}let d=e.rules[c];n.push({path:`rules[${a}]`,message:s.id?`Rule "${s.name}" has the same id ("${s.id}") as rules[${c}] ("${d?.name}") \u2014 they will collide during sync diffing and one may be silently skipped.`:`Rule "${s.name}" has the same name as rules[${c}] and neither has an explicit id \u2014 they will collide during sync diffing and one may be silently skipped. Add unique ids to disambiguate.`,code:"DUPLICATE_RULE_IDENTIFIER"})})}return{valid:t.length===0,errors:t,warnings:n}}getHealthScore(e){let t=[],n=100;if(!e)return{score:0,grade:"poor",issues:[{severity:"error",category:"configuration",message:"No configuration found"}],recommendations:["Create a configuration file"]};if((!e.rules||e.rules.length===0)&&(n-=20,t.push({severity:"warning",category:"rules",message:"No rules defined",suggestion:"Add security rules to protect your application"})),e.rules&&e.rules.length>0){let a=e.rules.filter(l=>!l.description);a.length>0&&(n-=5,t.push({severity:"info",category:"maintainability",message:`${a.length} rule(s) missing descriptions`,suggestion:"Add descriptions to improve maintainability"}))}if(e.rules&&e.rules.length>0){let a=e.rules.filter(l=>l.active===!1||l.enabled===!1);a.length>0&&(n-=5,t.push({severity:"info",category:"maintenance",message:`${a.length} disabled rule(s) found`,suggestion:"Review and remove unused rules"}))}let o=this.calculateGrade(n),s=this.generateRecommendations(e,t);return{score:Math.max(0,n),grade:o,issues:t,recommendations:s}}diffItems(e,t,n,o="id"){let s=[],a=[],l=[],c=d=>d[o];for(let d of e){let u=t.find(p=>c(p)===c(d));u?n(d,u)||a.push(d):s.push(d)}for(let d of t)e.find(p=>c(p)===c(d))||l.push(d);return{toAdd:s,toUpdate:a,toDelete:l}}assertManagedRulesSupported(e){if(!(!e.managedRules||e.managedRules.length===0)&&!this.getSupportedFeatures().supportsManagedRules)throw new Error(`${this.name} does not support managed rule groups, but this config declares ${e.managedRules.length}. Remove \`managedRules\` from the config, or switch to a provider that supports them (currently Cloudflare only).`)}validateRuleCount(e,t){if(t&&e>t)throw new Error(`Rule count (${e}) exceeds provider limit (${t})`);t&&e>t*.9&&r.warn(`Approaching rule limit: ${e}/${t} rules`)}updateMetadata(e,t){return{...e,metadata:{...e.metadata,version:t,updatedAt:new Date().toISOString()}}}calculateGrade(e){return e>=80?"excellent":e>=60?"good":e>=40?"fair":"poor"}generateRecommendations(e,t){let n=[];return t.some(o=>o.category==="rules")&&n.push("Consider using predefined templates to get started"),t.some(o=>o.category==="maintainability")&&n.push("Add descriptions to all rules for better documentation"),t.some(o=>o.category==="maintenance")&&n.push("Remove or enable disabled rules to keep configuration clean"),n}logSyncStats(e){r.info(`Sync complete for ${this.name}:`),r.info(` Rules added: ${e.rulesAdded}`),r.info(` Rules updated: ${e.rulesUpdated}`),r.info(` Rules deleted: ${e.rulesDeleted}`),e.ipsAdded!==void 0&&(r.info(` IPs added: ${e.ipsAdded}`),r.info(` IPs updated: ${e.ipsUpdated}`),r.info(` IPs deleted: ${e.ipsDeleted}`)),e.managedRulesAdded!==void 0&&(r.info(` Managed rule groups added: ${e.managedRulesAdded}`),r.info(` Managed rule groups updated: ${e.managedRulesUpdated}`),r.info(` Managed rule groups deleted: ${e.managedRulesDeleted}`)),e.version&&r.info(` New version: ${e.version}`)}};de();function en(i){let e=[],t=[];i.conditionGroup.forEach((s,a)=>{for(let l of s.conditions){let c=rr(l.op);t.push({field:lr(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 tn(i){let e=[],t=or(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:ar(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 nn(i){return{id:i.id,ip:i.ip,...i.hostname?{hostname:i.hostname}:{},...i.notes?{notes:i.notes}:{},action:i.action}}function or(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=dr(l.field);if(!c){t.push(U.createUnsupportedFeatureWarning(`condition field '${l.field}'`,"unified config","Vercel",e,l.field));continue}let d=sr(l.operator);if(!d){t.push(U.createUnsupportedFeatureWarning(`operator '${l.operator}'`,"unified config","Vercel",e,l.field));continue}a.push({op:d.op,neg:d.forceNeg!==!!l.negated,type:c,key:l.key,value:l.value})}a.length>0&&o.push({conditions:a})}return o}function rr(i){return{eq:"eq",pre:"starts_with",suf:"ends_with",inc:"in",sub:"contains",re:"matches",ex:"exists",nex:"not_exists"}[i]||"eq"}function sr(i){return{eq:{op:"eq",forceNeg:!1},ne:{op:"eq",forceNeg:!0},starts_with:{op:"pre",forceNeg:!1},ends_with:{op:"suf",forceNeg:!1},in:{op:"inc",forceNeg:!1},not_in:{op:"inc",forceNeg:!0},contains:{op:"sub",forceNeg:!1},not_contains:{op:"sub",forceNeg:!0},matches:{op:"re",forceNeg:!1},exists:{op:"ex",forceNeg:!1},not_exists:{op:"nex",forceNeg:!1}}[i]??null}function ar(i){return{log:"log",deny:"deny",challenge:"challenge",bypass:"bypass",rate_limit:"rate_limit",redirect:"redirect",allow:"bypass",block:"deny"}[i]}function lr(i){return{host:"host",path:"path",method:"method",header:"header",query:"query",cookie:"cookie",ip_address:"ip",user_agent:"user_agent",geo_country:"country",geo_city:"city",geo_as_number:"asn",scheme:"scheme",region:"vercel_region",geo_country_region:"region"}[i]||i}var cr={host:"host",path:"path",method:"method",header:"header",query:"query",cookie:"cookie",ip:"ip_address",user_agent:"user_agent",country:"geo_country",city:"geo_city",asn:"geo_as_number",scheme:"scheme",target_path:"target_path",vercel_region:"region",region:"geo_country_region",protocol:"protocol",environment:"environment",geo_continent:"geo_continent",ja4_digest:"ja4_digest",ja3_digest:"ja3_digest",rate_limit_api_id:"rate_limit_api_id"};function dr(i){return cr[i]??null}de();_();function ze(i){return i.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}function on(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 st=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}["${ze(t.toLowerCase())}"]`:e==="cookie"&&t?`${n}["${ze(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 ur=new Set(["ip.src"]),pr="http.request.uri.args",fr="http.request.headers",gr="http.request.cookies",Ge=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=st.toCloudflare(e.type,e.key),n=this.buildExistsOrComparisonExpression(t,e.op,()=>`${this.mapVercelOperator(e.op)} ${this.formatValue(t,e.value)}`);return e.neg&&(n=`not (${n})`),n}static fromUnifiedConditions(e,t="AND"){if(!e||e.length===0)throw new Error("At least one condition is required");if(!e.some(a=>a.group!==void 0)){let a=e.map(c=>this.fromUnifiedCondition(c)),l=t==="AND"?" and ":" or ";return a.length>1?`(${a.join(l)})`:a[0]}let o=new Map;for(let a of e){let l=a.group??0,c=o.get(l);c?c.push(a):o.set(l,[a])}let s=Array.from(o.values()).map(a=>this.combineWithAnd(a.map(l=>this.fromUnifiedCondition(l))));return s.length>1?this.combineWithOr(s):s[0]}static fromUnifiedCondition(e){if(e.key&&e.field==="query")return this.buildKeyedMapExpression(pr,e.key,e);if(e.field==="header"){if(!e.key)throw new Error('A "header" condition requires a key naming the header');return this.buildKeyedMapExpression(fr,e.key.toLowerCase(),e)}if(e.key&&e.field==="cookie")return this.buildKeyedMapExpression(gr,e.key,e);let t=this.mapUnifiedFieldToCloudflare(e.field);if(t===null)throw new Error(`Unsupported condition field '${e.field}' for Cloudflare \u2014 filter it out with a warning before calling fromUnifiedCondition (see unifiedToCloudflare).`);let n=this.buildUnifiedExpression(t,e.operator,e.value);return e.negated&&(n=`not (${n})`),n}static buildKeyedMapExpression(e,t,n){let o=ze(t),s=`${e}["${o}"]`,a;if(n.operator==="exists")a=`has_key(${e}, "${o}")`;else if(n.operator==="not_exists")a=`not (has_key(${e}, "${o}"))`;else if(n.operator==="not_contains")a=`not (any(${s}[*] contains ${this.formatValue(e,n.value)}))`;else if(n.operator==="not_in")a=`not (any(${s}[*] in ${this.formatValue(e,n.value)}))`;else{let l=this.mapUnifiedOperator(n.operator);a=`any(${s}[*] ${l} ${this.formatValue(e,n.value)})`}return n.negated?`not (${a})`:a}static buildExistsOrComparisonExpression(e,t,n){return t==="ex"||t==="exists"?`${e} exists`:t==="nex"||t==="not_exists"?`not (${e} exists)`:`${e} ${n()}`}static buildUnifiedExpression(e,t,n){return t==="not_contains"?`not (${e} contains ${this.formatValue(e,n)})`:t==="not_in"?`not (${e} in ${this.formatValue(e,n)})`:this.buildExistsOrComparisonExpression(e,t,()=>`${this.mapUnifiedOperator(t)} ${this.formatValue(e,n)}`)}static mapVercelOperator(e){return{eq:"eq",pre:"starts_with",suf:"ends_with",inc:"in",sub:"contains",re:"matches",ex:"exists"}[e]||e}static mapUnifiedOperator(e){return{eq:"eq",ne:"ne",contains:"contains",starts_with:"starts_with",ends_with:"ends_with",matches:"matches",in:"in",gt:"gt",ge:"ge",lt:"lt",le:"le",exists:"exists"}[e]||e}static mapUnifiedFieldToCloudflare(e){return{ip:"ip.src",country:"ip.geoip.country",region:"ip.geoip.subdivision_1",city:"ip.geoip.city",asn:"ip.geoip.asnum",path:"http.request.uri.path",host:"http.host",method:"http.request.method",header:"http.request.headers",query:"http.request.uri.query",cookie:"http.cookie",user_agent:"http.user_agent",referer:"http.referer",scheme:"ssl",port:"cf.edge.server_port",threat_score:"cf.threat_score"}[e]??null}static isFieldSupported(e){return this.mapUnifiedFieldToCloudflare(e)!==null}static formatValue(e,t){return Array.isArray(t)?`{${t.map(o=>this.formatSingleValue(e,o)).join(" ")}}`:this.formatSingleValue(e,t)}static formatSingleValue(e,t){if(typeof t=="string"){if(ur.has(e)){if(!Ae.safeParse(t).success)throw new Error(`Invalid IP address or CIDR range: ${t}`);return t}return`"${ze(t)}"`}return typeof t=="number"?String(t):typeof t=="boolean"?t?"true":"false":String(t)}static validate(e){if(!e||e.trim().length===0)return!1;let t=0;for(let n of e)if(n==="("&&t++,n===")"&&t--,t<0)return!1;return t===0}static combineWithAnd(e){if(e.length===0)throw new Error("At least one expression is required");return e.length===1?e[0]:`(${e.join(" and ")})`}static combineWithOr(e){if(e.length===0)throw new Error("At least one expression is required");return e.length===1?e[0]:`(${e.join(" or ")})`}};var W=class extends Error{},Ke=class{constructor(e){this.tokens=e}tokens;pos=0;peek(){return this.tokens[this.pos]}next(){let e=this.tokens[this.pos];if(!e)throw new W("Unexpected end of expression");return this.pos++,e}expect(e){let t=this.next();if(t.type!==e)throw new W(`Expected ${e} but got '${t.value}'`);return t}is(e,t){let n=this.peek();return!!n&&n.type===e&&n.value===t}atEnd(){return this.pos>=this.tokens.length}};function $t(i,e,t,n){let o=[];i.forEach((l,c)=>{if(e(l)){let d=n(l,c);d&&o.push(d);return}if(t(l))for(let d of l.children){if(!e(d))return;let u=n(d,c);u&&o.push(u)}});let s=i.length;return new Set(o.map(l=>l.group)).size!==s?null:{conditions:o,conditionLogic:"OR"}}var mr={"ip.src":"ip","ip.geoip.country":"country","ip.geoip.subdivision_1":"region","ip.geoip.city":"city","ip.geoip.asnum":"asn","http.request.uri.path":"path","http.host":"host","http.request.method":"method","http.request.headers":"header","http.request.uri.query":"query","http.request.uri.args":"query","http.cookie":"cookie","http.request.cookies":"cookie","http.user_agent":"user_agent","http.referer":"referer",ssl:"scheme","cf.edge.server_port":"port","cf.threat_score":"threat_score"},ln=new Set(["eq","ne","contains","starts_with","ends_with","matches","in","gt","ge","lt","le"]),hr=new Set(["and","or","not"]);function yr(i){let e=[],t=0,n=i.length;for(;t<n;){let o=i[t];if(/\s/.test(o)){t++;continue}if(o===","){t++;continue}if(o==="("){e.push({type:"LPAREN",value:o}),t++;continue}if(o===")"){e.push({type:"RPAREN",value:o}),t++;continue}if(o==="{"){e.push({type:"LBRACE",value:o}),t++;continue}if(o==="}"){e.push({type:"RBRACE",value:o}),t++;continue}if(o==="["){e.push({type:"LBRACKET",value:o}),t++;continue}if(o==="]"){e.push({type:"RBRACKET",value:o}),t++;continue}if(o==='"'){let a=t+1,l="",c=!1;for(;a<n;){let d=i[a];if(d==="\\"&&a+1<n){l+=d+i[a+1],a+=2;continue}if(i[a]==='"'){c=!0,a++;break}l+=i[a],a++}if(!c)throw new W("Unterminated string literal");e.push({type:"STRING",value:on(l)}),t=a;continue}let s=t;for(;s<n&&!/[\s(){}[\]",]/.test(i[s]);)s++;if(s===t)throw new W(`Unexpected character '${o}' at position ${t}`);e.push({type:"WORD",value:i.slice(t,s)}),t=s}return e}function Yt(i){let e=[rn(i)];for(;i.is("WORD","or");)i.next(),e.push(rn(i));return e.length===1?e[0]:{type:"or",children:e}}function rn(i){let e=[sn(i)];for(;i.is("WORD","and");)i.next(),e.push(sn(i));return e.length===1?e[0]:{type:"and",children:e}}function sn(i){if(i.is("WORD","not")){i.next(),i.expect("LPAREN");let e=Yt(i);return i.expect("RPAREN"),{type:"not",child:e}}return Rr(i)}function Rr(i){let e=i.peek();if(e&&e.type==="LPAREN"){i.next();let t=Yt(i);return i.expect("RPAREN"),t}return vr(i)}function wr(i){let e=i.expect("WORD");if(hr.has(e.value))throw new W(`Expected a field reference but got keyword '${e.value}'`);if(i.peek()?.type==="LBRACKET"){i.next();let t=i.expect("STRING");return i.expect("RBRACKET"),{field:e.value,key:t.value}}return{field:e.value}}function an(i){let e=i.next();if(e.type==="STRING")return e.value;if(e.type==="WORD")return/^-?\d+(\.\d+)?$/.test(e.value)?Number(e.value):e.value;throw new W(`Unexpected token '${e.value}' where a value was expected`)}function cn(i){if(i.peek()?.type==="LBRACE"){i.next();let e=[];for(;i.peek()?.type!=="RBRACE";)e.push(an(i));return i.expect("RBRACE"),e}return an(i)}function Cr(i){i.next(),i.expect("LPAREN");let e=i.expect("WORD");i.expect("LBRACKET");let t=i.expect("STRING");i.expect("RBRACKET"),i.expect("LBRACKET");let n=i.expect("WORD");if(n.value!=="*")throw new W(`Expected '*' in any(...) array index but got '${n.value}'`);i.expect("RBRACKET");let o=i.next();if(o.type!=="WORD"||!ln.has(o.value))throw new W(`Unsupported or unrecognized operator '${o.value}' inside any(...)`);let s=cn(i);return i.expect("RPAREN"),{type:"comparison",field:e.value,key:t.value,operator:o.value,value:s}}function br(i){i.next(),i.expect("LPAREN");let e=i.expect("WORD"),t=i.expect("STRING");return i.expect("RPAREN"),{type:"exists",field:e.value,key:t.value}}function vr(i){if(i.is("WORD","any"))return Cr(i);if(i.is("WORD","has_key"))return br(i);let{field:e,key:t}=wr(i);if(i.is("WORD","exists"))return i.next(),{type:"exists",field:e,key:t};let n=i.next();if(n.type!=="WORD"||!ln.has(n.value))throw new W(`Unsupported or unrecognized operator '${n.value}'`);let o=cn(i);return{type:"comparison",field:e,key:t,operator:n.value,value:o}}function Ir(i){let e=new Ke(yr(i)),t=Yt(e);if(!e.atEnd())throw new W("Unexpected trailing tokens");return t}function Ht(i){return i.type==="comparison"||i.type==="exists"?!0:i.type==="not"?i.child.type==="comparison"||i.child.type==="exists":!1}function Zt(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=mr[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 Er(i){if(Ht(i)){let e=Zt(i,0);return e?{conditions:[e],conditionLogic:"AND"}:null}if(i.type==="and"){let e=[];for(let t of i.children){if(!Ht(t))return null;let n=Zt(t,0);if(!n)return null;e.push(n)}return{conditions:e,conditionLogic:"AND"}}return i.type==="or"?$t(i.children,Ht,e=>e.type==="and",Zt):null}function Jt(i){if(!i||!i.trim())return null;try{let e=Ir(i);return Er(e)}catch{return null}}function pn(i){let e=[],t=Jt(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:Tr(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 fn(i){let e=[],t=$r(i.conditions,i.id,e);if(t.length===0)throw new Error(`Rule "${i.name}" has no conditions Cloudflare can represent \u2014 every condition field is unsupported by Cloudflare (e.g. Vercel-only fields like environment/ja3_digest, or vercel_region). Cannot sync this rule to Cloudflare.`);let n=Ge.fromUnifiedConditions(t,i.conditionLogic),o={id:i.id||crypto.randomUUID(),action:Ar(i.action.type),expression:n,description:i.description||i.name,enabled:i.enabled};return i.action.rateLimit&&(o.ratelimit={characteristics:i.action.rateLimit.characteristics||["ip.src"],period:Pr(i.action.rateLimit.window),requests_per_period:i.action.rateLimit.requests},i.action.rateLimit.mitigationTimeout?o.ratelimit.mitigation_timeout=i.action.rateLimit.mitigationTimeout:(o.ratelimit.mitigation_timeout=3600,e.push(U.createWarning("rate_limiting_precision",i.id,"action.rateLimit.mitigationTimeout",`No mitigationTimeout set for rate limit rule "${i.name}" \u2014 defaulted to Cloudflare's 3600s (1 hour) block duration.`))),i.action.rateLimit.countingExpression&&(o.ratelimit.counting_expression=i.action.rateLimit.countingExpression)),i.action.type==="redirect"&&i.action.redirect&&(o.action_parameters={from_value:{status_code:i.action.redirect.statusCode??(i.action.redirect.permanent?301:302),target_url:{value:i.action.redirect.location},...i.action.redirect.preserveQueryString!==void 0?{preserve_query_string:i.action.redirect.preserveQueryString}:{}}}),i.action.response&&(o.action!=="block"?e.push(U.createUnsupportedFeatureWarning(`custom response on a '${o.action}' action`,"unified config","Cloudflare",i.id,"action.response")):i.action.response.content?o.action_parameters={response:{status_code:i.action.response.statusCode??403,content:i.action.response.content,content_type:i.action.response.contentType??"text/plain"}}:e.push(U.createWarning("lossy_conversion",i.id,"action.response","Custom response declared without `content`; Cloudflare requires a response body, so it was dropped.","Set action.response.content to the body you want returned."))),{result:o,warnings:e}}function gn(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 mn(i){let e=[],t={};if(i.action){let{action:s,warning:a}=dn(i.action,i.id,"action");a&&e.push(a),s&&(t.action=s)}i.overrides&&i.overrides.length>0&&(t.rules=i.overrides.map(s=>{let a={id:s.ruleId};if(s.enabled!==void 0&&(a.enabled=s.enabled),s.action){let{action:l,warning:c}=dn(s.action,i.id,`overrides[${s.ruleId}].action`);c&&e.push(c),l&&(a.action=l)}return a}));let n={id:i.ruleset,...Object.keys(t).length>0?{overrides:t}:{}};return{result:{id:i.id||crypto.randomUUID(),action:"execute",expression:"true",description:i.name||`Managed ruleset ${i.ruleset}`,enabled:i.enabled,action_parameters:n},warnings:e}}function hn(i){let e=[],t=i.action_parameters,n={id:i.id,ruleset:t?.id??"",enabled:i.enabled??!0,...i.description?{name:i.description}:{}};return t?.overrides?.action&&(n.action=un(t.overrides.action)),t?.overrides?.rules&&t.overrides.rules.length>0&&(n.overrides=t.overrides.rules.map(o=>({ruleId:o.id,...o.action!==void 0?{action:un(o.action)}:{},...o.enabled!==void 0?{enabled:o.enabled}:{}}))),{result:n,warnings:e}}function dn(i,e,t){let o={log:"log",deny:"block",block:"block",challenge:"managed_challenge",allow:"allow"}[i];return o?{action:o}:{warning:U.createUnsupportedFeatureWarning(`'${i}' as a managed-ruleset override action`,"unified config","Cloudflare",e,t)}}function un(i){return{log:"log",block:"deny",challenge:"challenge",managed_challenge:"challenge",js_challenge:"challenge",allow:"allow"}[i]||"deny"}function Tr(i){return{block:"deny",challenge:"challenge",managed_challenge:"challenge",js_challenge:"challenge",log:"log",skip:"bypass",allow:"allow",rewrite:"bypass",redirect:"redirect",execute:"log"}[i]||"deny"}function Ar(i){return{log:"log",deny:"block",block:"block",challenge:"managed_challenge",bypass:"skip",rate_limit:"block",redirect:"redirect",allow:"allow"}[i]||"block"}function $r(i,e,t){return i.filter(n=>Ge.isFieldSupported(n.field)?!0:(t.push(U.createUnsupportedFeatureWarning(`condition field '${n.field}'`,"unified config","Cloudflare",e,n.field)),!1))}function Pr(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 ye(i,e,t){return U.createWarning("complex_expressions",i,void 0,e,t)}var _r={ip:"ip",path:"path",domain:"host",method:"method",country:"country",scheme:"scheme",user_agent:"user_agent"},kr={ip:"ip",path:"path",host:"domain",method:"method",country:"country",scheme:"scheme",user_agent:"user_agent"},Sr={header:"request_header",query:"query_parameter",cookie:"request_cookie"},Ur={request_header:"header",query_parameter:"query",request_cookie:"cookie",response_header:"response_header",post_parameter:"post_parameter",signal:"signal"};function Lr(i){return _r[i]??i}function xr(i){return kr[i]??null}var Dr={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 yn(i){return Dr[i]??{operator:"eq",negated:!1}}function Rn(i,e,t,n){switch(i){case"eq":return e?"does_not_equal":"equals";case"ne":return"does_not_equal";case"contains":return e?"does_not_contain":"contains";case"not_contains":return"does_not_contain";case"starts_with":case"ends_with":return t.push(U.createWarning("regex_patterns",n,void 0,`Unified operator "${i}" has no direct Fastly equivalent \u2014 mapped to Fastly's wildcard "like" operator. Verify the translated value still matches as a wildcard pattern.`)),e?"not_like":"like";case"matches":return t.push(U.createWarning("regex_patterns",n,void 0,"Regular expression pattern may need adjustment for Fastly Next-Gen WAF regex syntax.")),e?"does_not_match":"matches";case"in":return e?"not_in_list":"in_list";case"not_in":return"not_in_list";case"gt":return t.push(ye(n,'Unified operator "gt" (strictly greater than) has no Fastly equivalent \u2014 mapped to "greater_equal", which also matches the boundary value itself.')),"greater_equal";case"ge":return"greater_equal";case"lt":return t.push(ye(n,'Unified operator "lt" (strictly less than) has no Fastly equivalent \u2014 mapped to "lesser_equal", which also matches the boundary value itself.')),"lesser_equal";case"le":return"lesser_equal";default:return"equals"}}function Or(i,e){let t=[],n=i.conditions.filter(s=>s.type==="group"),o=i.conditions.filter(s=>s.type!=="group");if(n.length>0&&o.length===0&&i.group_operator==="any")return n.forEach((s,a)=>{s.group_operator!=="all"&&e.push(ye(i.id,`A condition group using "any" (OR) inside an outer "any" (OR) rule cannot be represented in doorman's flat AND-within/OR-across condition model \u2014 its members were kept in the same group (effectively AND'd) instead of OR'd. Review the translated rule against the original Fastly logic.`));for(let l of s.conditions)Pt(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;Pt(t,s,l,e,i.id)}),{conditions:t,conditionLogic:i.group_operator==="any"&&o.length>1?"OR":"AND"};e.push(ye(i.id,`Rule mixes top-level conditions with condition groups under group_operator "${i.group_operator}" \u2014 this doesn't fit doorman's flat AND-within/OR-across condition model. All conditions were combined with AND; review the translated rule against the original Fastly rule logic.`));for(let s of o)Pt(t,s,0,e,i.id);for(let s of n)for(let a of s.conditions)Pt(t,a,0,e,i.id);return{conditions:t,conditionLogic:"AND"}}function Pt(i,e,t,n,o){if(e.type==="single"){let{operator:u,negated:p}=yn(e.operator);i.push({field:Lr(e.field),operator:u,value:e.value,...p?{negated:!0}:{},group:t});return}let s=Ur[e.field],a=e.conditions.find(u=>u.field==="name"),l=e.conditions.find(u=>u.field==="value"||u.field==="value_string");if(!l){if(a){i.push({field:s,operator:e.operator==="does_not_exist"?"not_exists":"exists",key:a.value,group:t});return}n.push(ye(o,`Multival condition on '${e.field}' has no value or name sub-condition doorman could extract \u2014 skipped.`));return}let{operator:c,negated:d}=yn(l.operator);i.push({field:s,operator:c,value:l.value,...d?{negated:!0}:{},...a?{key:a.value}:{},group:t})}function Fr(i,e){i.actions.length>1&&e.push(ye(i.id,`Rule has ${i.actions.length} actions \u2014 doorman only models one action per rule; only the first ("${i.actions[0]?.type}") was translated.`));let t=i.actions[0];if(i.type==="rate_limit")return{type:"rate_limit",rateLimit:i.rate_limit?{requests:i.rate_limit.threshold,window:`${i.rate_limit.interval}s`,mitigationTimeout:i.rate_limit.duration}:void 0};switch(t?.type){case"block":return{type:"block"};case"allow":return{type:"allow"};case"browser_challenge":case"dynamic_challenge":case"verify_token":return{type:"challenge"};case"redirect":return{type:"redirect",redirect:{location:t.redirect_url??"",...t.response_code!==void 0?{statusCode:t.response_code}:{}}};case"add_signal":case"exclude_signal":case"deception":return e.push(U.createWarning("action_differences",i.id,"action.type",`Fastly action "${t.type}" tags or excludes a WAF signal rather than allowing/blocking the request \u2014 doorman has no matching action type. Mapped to "log" as the closest non-destructive fallback; review this rule.`)),{type:"log"};default:return e.push(ye(i.id,`Unrecognised Fastly action type "${String(t?.type)}" \u2014 mapped to "log".`)),{type:"log"}}}function wn(i){let e=[],{conditions:t,conditionLogic:n}=Or(i,e),o=Fr(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 Nr(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=Sr[a.field];if(l){if(a.key===void 0)return t.push(ye(e,`Condition on '${a.field}' has no 'key' set \u2014 Fastly needs a specific ${a.field} name to match on (e.g. a header name). Skipped.`)),null;if(a.operator==="exists"||a.operator==="not_exists")return{type:"multival",field:l,operator:a.operator==="exists"?"exists":"does_not_exist",group_operator:"all",conditions:[{type:"single",field:"name",operator:"equals",value:a.key}]};let d=l==="request_header"||l==="response_header"?"value_string":"value";return{type:"multival",field:l,operator:"exists",group_operator:"all",conditions:[{type:"single",field:"name",operator:"equals",value:a.key},{type:"single",field:d,operator:Rn(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=xr(a.field);return c?{type:"single",field:c,operator:Rn(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 Vr(i,e,t){switch(i.type){case"block":case"deny":return{type:"block"};case"allow":return{type:"allow"};case"challenge":return{type:"browser_challenge"};case"bypass":return e.push(ye(t,'Unified action "bypass" has no direct Fastly equivalent \u2014 mapped to "allow".')),{type:"allow"};case"log":return e.push(ye(t,'Unified action "log" has no dedicated Fastly action \u2014 mapped to "allow" (request_logging is enabled at the rule level regardless).')),{type:"allow"};case"redirect":return i.redirect?{type:"redirect",redirect_url:i.redirect.location,response_code:i.redirect.statusCode??(i.redirect.permanent?301:302)}:(e.push(ye(t,'Unified action "redirect" has no redirect target \u2014 mapped to "allow".')),{type:"allow"});case"rate_limit":return{type:"block_signal"};default:return e.push(U.createUnsupportedFeatureWarning(`action type '${i.type}'`,"unified config","Fastly",t,"action.type")),{type:"block"}}}function Br(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 Mr(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=Br(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 Cn(i){let e=[];i.description&&i.description!==i.name&&e.push(ye(i.id,'Fastly rules have no separate "name" field \u2014 only "description" carries over, and it was set from this rule\'s `name`. The `description` text was dropped rather than merged in, to keep it round-trip-stable on the next sync.'));let{conditions:t,groupConditions:n,multivalConditions:o,groupOperator:s}=Nr(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:[Vr(i.action,e,i.id)],rate_limit:a?Mr(i.action.rateLimit,i.id,e):null},warnings:e}}function bn(i,e){return i.map(t=>({ip:t,action:e}))}function vn(i){let e=[],t=[],n=[];for(let o of i)(o.hostname||o.notes)&&e.push(ye(o.id,`IP rule "${o.ip}" sets ${o.hostname?"`hostname`":""}${o.hostname&&o.notes?" and ":""}${o.notes?"`notes`":""} \u2014 Fastly IP lists have no equivalent per-entry metadata; only the address was kept.`)),(o.action==="allow"?n:t).push(o.ip);return{result:{denyEntries:t,allowEntries:n},warnings:e}}de();function He(i){return i.replace(/\\/g,"\\\\").replace(/'/g,"\\'")}function In(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 qr=new Set(["region","city","port","scheme"]),Wr={host:"host",user_agent:"user-agent",referer:"referer"},at=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(qr.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=Wr[e.field];if(t)return this.buildHeaderCondition(t,e);if(e.field==="header"||e.field==="query"){if(e.field==="header"){if(!e.key)throw new Error('A "header" condition requires a key naming the header');return this.buildHeaderCondition(e.key,e)}return this.buildComparison("request.query",e,!1)}let n=zr[e.field];if(n)return this.buildComparison(n.path,e,n.numeric);throw new Error(`Cloud Armor CEL builder has no mapping for condition field "${e.field}"`)}static buildIpCondition(e){let t=(Array.isArray(e.value)?e.value:[e.value]).map(String);if(t.length===0||t.some(a=>!a))throw new Error('An "ip" condition requires at least one value');let n=t.map(a=>{if(!Ae.safeParse(a).success)throw new Error(`Invalid IP address or CIDR range: ${a}`);return a.includes("/")?`inIpRange(origin.ip, '${He(a)}')`:`origin.ip == '${He(a)}'`}),o=this.isNegated(e,e.operator==="not_in"||e.operator==="ne");if(n.length>1){let a=`(${n.join(" || ")})`;return o?`!${a}`:a}let s=n[0];return o?s.startsWith("inIpRange(")?`!${s}`:`!(${s})`:s}static buildHeaderCondition(e,t){let n=`request.headers['${He(e)}']`,o=`has(${n})`;return t.operator==="exists"||t.operator==="not_exists"?this.isNegated(t,t.operator==="not_exists")?`!${o}`:o:`(${o} && ${this.buildComparison(n,t,!1)})`}static buildCookieCondition(e){let t="request.headers['cookie']",n=`has(${t})`;if(e.operator==="exists"||e.operator==="not_exists")return this.isNegated(e,e.operator==="not_exists")?`!${n}`:n;if(!e.key)return`(${n} && ${this.buildComparison(t,e,!1)})`;if(!["eq","ne","contains","not_contains"].includes(e.operator))throw new Error(`Cloud Armor can only check a specific cookie's value via substring search (has no parsed cookie map) \u2014 "${e.operator}" on a keyed cookie condition has no unambiguous CEL representation.`);let o=`${e.key}=${String(e.value)}`,s=`${t}.contains('${He(o)}')`,a=this.isNegated(e,e.operator==="ne"||e.operator==="not_contains");return`(${n} && ${a?`!${s}`:s})`}static buildComparison(e,t,n){let{value:o}=t;if(t.operator==="exists"||t.operator==="not_exists")throw new Error(`"${t.operator}" is not meaningful for Cloud Armor's "${e}" \u2014 it is always present`);let s=t.operator,a=!1;if(t.negated){let d=jr[s];d?s=d:a=!0}if(s==="in"||s==="not_in"){let u=(Array.isArray(o)?o:[o]).map(f=>this.formatLiteral(f,n)).join(", "),p=`${e} in [${u}]`;return s==="not_in"?`!(${p})`:p}let l=this.formatLiteral(o,n),c;switch(s){case"eq":c=`${e} == ${l}`;break;case"ne":c=`${e} != ${l}`;break;case"contains":c=`${e}.contains(${l})`;break;case"not_contains":c=`!${e}.contains(${l})`;break;case"starts_with":c=`${e}.startsWith(${l})`;break;case"ends_with":c=`${e}.endsWith(${l})`;break;case"matches":c=`${e}.matches(${l})`;break;case"gt":c=`${e} > ${l}`;break;case"ge":c=`${e} >= ${l}`;break;case"lt":c=`${e} < ${l}`;break;case"le":c=`${e} <= ${l}`;break;default:throw new Error(`Cloud Armor CEL builder has no mapping for operator "${s}"`)}return a?`!(${c})`:c}static isNegated(e,t){return t!==!!e.negated}static formatLiteral(e,t){if(t){let n=Number(e);if(!Number.isFinite(n))throw new Error(`Expected a numeric value, got "${String(e)}"`);return String(n)}return`'${He(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(" || ")})`}},jr={eq:"ne",ne:"eq",contains:"not_contains",not_contains:"contains",in:"not_in",not_in:"in"},zr={country:{path:"origin.region_code",numeric:!1},asn:{path:"origin.asn",numeric:!0},path:{path:"request.path",numeric:!1},method:{path:"request.method",numeric:!1}};var Gr={"origin.region_code":"country","origin.asn":"asn","request.path":"path","request.method":"method","request.query":"query"},Kr={host:"host","user-agent":"user_agent",referer:"referer",cookie:"cookie"},_n={contains:"contains",startsWith:"starts_with",endsWith:"ends_with",matches:"matches"};function Hr(i){let e=[],t=0,n=i.length,o=(s,a)=>i[t]===s&&i[t+1]===a;for(;t<n;){let s=i[t];if(/\s/.test(s)){t++;continue}if(s==="("){e.push({type:"LPAREN",value:s}),t++;continue}if(s===")"){e.push({type:"RPAREN",value:s}),t++;continue}if(s==="["){e.push({type:"LBRACKET",value:s}),t++;continue}if(s==="]"){e.push({type:"RBRACKET",value:s}),t++;continue}if(s===","){e.push({type:"COMMA",value:s}),t++;continue}if(o("&","&")){e.push({type:"AND",value:"&&"}),t+=2;continue}if(o("|","|")){e.push({type:"OR",value:"||"}),t+=2;continue}if(o("=","=")){e.push({type:"EQ",value:"=="}),t+=2;continue}if(o("!","=")){e.push({type:"NE",value:"!="}),t+=2;continue}if(o(">","=")){e.push({type:"GE",value:">="}),t+=2;continue}if(o("<","=")){e.push({type:"LE",value:"<="}),t+=2;continue}if(s==="!"){e.push({type:"NOT",value:"!"}),t++;continue}if(s===">"){e.push({type:"GT",value:">"}),t++;continue}if(s==="<"){e.push({type:"LT",value:"<"}),t++;continue}if(s==="."){e.push({type:"DOT",value:s}),t++;continue}if(s==="'"){let a=t+1,l="",c=!1;for(;a<n;){let d=i[a];if(d==="\\"&&a+1<n){l+=d+i[a+1],a+=2;continue}if(i[a]==="'"){c=!0,a++;break}l+=i[a],a++}if(!c)throw new W("Unterminated string literal");e.push({type:"STRING",value:In(l)}),t=a;continue}if(/[0-9]/.test(s)||s==="-"&&/[0-9]/.test(i[t+1]??"")){let a=t+1;for(;a<n&&/[0-9.]/.test(i[a]);)a++;e.push({type:"NUMBER",value:i.slice(t,a)}),t=a;continue}if(/[A-Za-z_]/.test(s)){let a=t;for(;a<n&&/[A-Za-z0-9_]/.test(i[a]);)a++;e.push({type:"IDENT",value:i.slice(t,a)}),t=a;continue}throw new W(`Unexpected character '${s}' at position ${t}`)}return e}function Zr(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 Qt(i){let e=Zr(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 En(i){let e=i.next();if(e.type==="STRING")return e.value;if(e.type==="NUMBER")return Number(e.value);throw new W(`Unexpected token '${e.value}' where a literal was expected`)}function Yr(i){if(i.is("IDENT","has")){i.next(),i.expect("LPAREN");let a=Qt(i);return i.expect("RPAREN"),{type:"has",...a}}if(i.is("IDENT","inIpRange")){i.next(),i.expect("LPAREN");let a=Qt(i);if(a.field!=="origin.ip")throw new W(`inIpRange must reference origin.ip, got '${a.field}'`);i.expect("COMMA");let l=i.expect("STRING");return i.expect("RPAREN"),{type:"inIpRange",value:l.value}}let e=Qt(i);if(i.peek()?.type==="DOT"){i.next();let a=i.expect("IDENT").value;if(!_n[a])throw new W(`Unrecognized method '${a}'`);i.expect("LPAREN");let l=i.expect("STRING");return i.expect("RPAREN"),{type:"methodCall",...e,method:a,value:l.value}}if(i.is("IDENT","in")){i.next(),i.expect("LBRACKET");let a=[];for(;i.peek()?.type!=="RBRACKET";)a.push(En(i)),i.peek()?.type==="COMMA"&&i.next();return i.expect("RBRACKET"),{type:"membership",...e,values:a}}let t=i.next(),o={EQ:"eq",NE:"ne",GT:"gt",GE:"ge",LT:"lt",LE:"le"}[t.type];if(!o)throw new W(`Unexpected token '${t.value}' after field reference`);let s=En(i);return{type:"comparison",...e,operator:o,value:s}}function Tn(i){if(i.peek()?.type==="LPAREN"){i.next();let e=kn(i);return i.expect("RPAREN"),e}return Yr(i)}function An(i){return i.peek()?.type==="NOT"?(i.next(),{type:"not",child:Tn(i)}):Tn(i)}function $n(i){let e=[An(i)];for(;i.peek()?.type==="AND";)i.next(),e.push(An(i));return e.length===1?e[0]:{type:"and",children:e}}function kn(i){let e=[$n(i)];for(;i.peek()?.type==="OR";)i.next(),e.push($n(i));return e.length===1?e[0]:{type:"or",children:e}}function Jr(i){let e=new Ke(Hr(i)),t=kn(e);if(!e.atEnd())throw new W("Unexpected trailing tokens");return t}function Qr(i){return i.type==="inIpRange"||i.type==="comparison"&&i.field==="origin.ip"&&i.operator==="eq"}function ti(i){return i.type==="or"&&i.children.length>0&&i.children.every(Qr)}function Xr(i){return i.type==="inIpRange"?i.value:String(i.value)}function Sn(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 es(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 Xt(i){if(i.type==="has"||i.type==="comparison"||i.type==="methodCall"||i.type==="membership"||i.type==="inIpRange"||ti(i)||Sn(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"||ti(e)}return!1}function Un(i,e){if(i==="origin.ip")return{unifiedField:"ip"};let t=Gr[i];if(t)return{unifiedField:t};if(i==="request.headers"&&e!==void 0){let n=Kr[e];return n?{unifiedField:n}:{unifiedField:"header",unifiedKey:e}}return null}function ei(i,e){let t=i.type==="not",n=i.type==="not"?i.child:i;if(ti(n)){let o=n.children.map(s=>Xr(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"&&Sn(n)){if(t)return null;let{guard:o,comparisonNegated:s,comparison:a}=es(n);return a.type!=="comparison"&&a.type!=="methodCall"&&a.type!=="membership"?null:Pn(a,s,o.field,o.key,e)}if(n.type==="comparison"||n.type==="methodCall"||n.type==="membership")return Pn(n,t,n.field,n.key,e);if(n.type==="has"){let o=Un(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 Pn(i,e,t,n,o){let s=Un(t,n);if(!s)return null;let a,l,c=!1;if(i.type==="comparison")e?i.operator==="eq"?a="ne":i.operator==="ne"?a="eq":(a=i.operator,c=!0):a=i.operator,l=i.value;else if(i.type==="methodCall"){let d=_n[i.method];if(!d)return null;e?d==="contains"?a="not_contains":(a=d,c=!0):a=d,l=i.value}else a=e?"not_in":"in",l=i.values;return{field:s.unifiedField,operator:a,value:l,...c?{negated:!0}:{},...s.unifiedKey!==void 0?{key:s.unifiedKey}:{},...o!==void 0?{group:o}:{}}}function ts(i){if(Xt(i)){let e=ei(i,void 0);return e?{conditions:[e],conditionLogic:"AND"}:null}if(i.type==="and"){let e=[];for(let t of i.children){if(!Xt(t))return null;let n=ei(t,void 0);if(!n)return null;e.push(n)}return{conditions:e,conditionLogic:"AND"}}return i.type==="or"?$t(i.children,Xt,e=>e.type==="and",ei):null}function Ne(i){if(!i||!i.trim())return null;try{let e=Jr(i);return ts(e)}catch{return null}}function ii(i){if(!i.match.expr)throw new Error(`Cloud Armor rule at priority ${i.priority} has no CEL match expression (uses the basic versionedExpr/config shape) \u2014 caller must filter these out before translating.`);return i.match.expr.expression}function Ln(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=at.fromUnifiedConditions(i.conditions,i.conditionLogic),{action:n,actionWarning:o}=ns(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=os(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 xn(i,e){let t=[],n=e!==void 0?e:Ne(ii(i)),o=[],s;n?(o=n.conditions,s=n.conditionLogic):t.push(U.createWarning("complex_expressions",String(i.priority),"match.expr.expression","CEL expression could not be parsed back into structured conditions \u2014 it may be hand-authored or use syntax outside what doorman itself generates.","Review the translated rule and add missing conditions manually if needed."));let a={type:is(i.action),...i.rateLimitOptions?{rateLimit:{requests:i.rateLimitOptions.rateLimitThreshold.count,window:`${i.rateLimitOptions.rateLimitThreshold.intervalSec}s`}}:{},...i.redirectOptions?{redirect:{location:i.redirectOptions.target}}:{}};return{result:{id:String(i.priority),name:i.description||`Rule ${i.priority}`,...i.description?{description:i.description}:{},enabled:!i.preview,conditions:o,...s==="OR"?{conditionLogic:s}:{},action:a,priority:i.priority},warnings:t}}function Dn(i,e){if(i.action!=="allow"&&i.action!=="deny(403)"||i.preview)return!1;let t=e!==void 0?e:Ne(ii(i));if(!t||t.conditions.length!==1)return!1;let n=t.conditions[0];return n.field==="ip"&&(n.operator==="eq"||n.operator==="in")&&!n.key}function On(i,e){let o=(e!==void 0?e:Ne(ii(i))).conditions[0].value;return{id:String(i.priority),ip:Array.isArray(o)?String(o[0]):String(o),...i.description?{notes:i.description}:{},action:i.action==="allow"?"allow":"deny"}}function Fn(i,e){if(!Ae.safeParse(i.ip).success)throw new Error(`Invalid IP address or CIDR range: ${i.ip}`);let t=at.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 is(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 ns(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 os(i){let e=i.match(/^(\d+)([smhd])$/);if(!e||!e[1]||!e[2])throw new Error(`Invalid window format: ${i}`);let t=parseInt(e[1],10),o={s:1,m:60,h:3600,d:86400}[e[2]];if(o===void 0)throw new Error(`Invalid time unit: ${e[2]}`);return t*o}var E=class{static vercelToUnified=en;static unifiedToVercel=tn;static vercelIPToUnified=nn;static cloudflareToUnified=pn;static unifiedToCloudflare=fn;static unifiedIPToCloudflare=gn;static unifiedManagedRuleGroupToCloudflare=mn;static cloudflareToUnifiedManagedRuleGroup=hn;static fastlyToUnified=wn;static unifiedToFastly=Cn;static fastlyListEntriesToUnified=bn;static unifiedIPsToFastlyEntries=vn;static gcpToUnified=xn;static unifiedToGcp=Ln;static gcpToUnifiedIP=On;static unifiedIPToGcp=Fn;static gcpLooksLikeIpRule=Dn};de();function xe(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)||!xe(i[o],e[o]))return!1;return!0}function Re(i){let{id:e,...t}=i;return t}function lt(i){return[...i].sort((e,t)=>{let n=e.priority!==void 0,o=t.priority!==void 0;return!n&&!o?0:n?o?e.priority-t.priority:-1:1})}function _t(i){return i.some(e=>e.priority!==void 0)}var Ze=class extends Pe{constructor(t){super();this.client=t}client;name="vercel";async fetchConfig(t){try{r.debug("Fetching Vercel firewall configuration");let n=await this.client.fetchFirewallConfig(t),o=n.rules.map(a=>{let l=E.vercelToUnified(a);return l.warnings.length>0&&l.warnings.forEach(c=>{let{TranslationWarningSystem:d}=(de(),me(_e)),u=d.formatWarning(c);r.warn(`Rule ${a.name}:
25
- ${u}`)}),l.result}),s=n.ips.map(a=>E.vercelIPToUnified(a));return{version:"2.0",provider:"vercel",providers:{vercel:{projectId:this.client.projectId,...this.client.teamId?{teamId:this.client.teamId}:{}}},rules:o,ips:s,metadata:{version:n.version,updatedAt:n.updatedAt}}}catch(n){throw r.error("Error fetching Vercel configuration:",n),new Error("Failed to fetch Vercel firewall configuration")}}async syncRules(t,n={}){let{dryRun:o=!1}=n;try{let{OperationSafety:s}=(dt(),me(ct)),a=await s.performDryRunValidation(t,"sync rules",async y=>await this.getChanges(y,{allowCreate:!o,assumeYes:n.assumeYes}));if(!a.valid)throw new Error(`Dry-run validation failed: ${a.issues.join(", ")}`);let l=a.changes,{rulesToAdd:c,rulesToUpdate:d,rulesToDelete:u,version:p}=l,f=l.ipsToAdd||[],g=l.ipsToUpdate||[],m=l.ipsToDelete||[];if(o)return r.info("Dry run mode. The following changes would be made:"),r.info(`Custom Rules - Add: ${c.length}, Update: ${d.length}, Delete: ${u.length}`),r.info(`IP Rules - Add: ${f.length}, Update: ${g.length}, Delete: ${m.length}`),{success:!0,rulesAdded:0,rulesUpdated:0,rulesDeleted:0,ipsAdded:0,ipsUpdated:0,ipsDeleted:0,version:p,warnings:a.warnings};let v=s.assessOperationRisk(l,t);if(!await s.confirmDestructiveOperation({operation:"sync rules",target:`Vercel project ${this.client.projectId}`,changes:l,riskLevel:v,skipConfirmation:n.force||!1,dryRun:!1,allowDeletions:n.allowDeletions||!1}))throw new Error("Operation cancelled by user");let x=c.map(y=>E.unifiedToVercel(y).result),S=d.map(y=>E.unifiedToVercel(y).result),M=u.map(y=>E.unifiedToVercel(y).result),q=f.map(y=>({id:y.id||"",ip:y.ip,hostname:y.hostname||"",action:y.action,notes:y.notes})),Y=g.map(y=>({id:y.id||"",ip:y.ip,hostname:y.hostname||"",action:y.action,notes:y.notes})),le=m.map(y=>({id:y.id||"",ip:y.ip,hostname:y.hostname||"",action:y.action,notes:y.notes})),G=[],fe=[],k=[],Ee=[],Ce=[],T=[],h=[],b=(y,$)=>`${y}: ${$ instanceof Error?$.message:String($)}`,X=[];_t(t.rules)&&t.rules.length>c.length&&X.push("Rule `priority` is best-effort on Vercel: new rules are created in priority order, but rules that already exist remotely cannot be repositioned via the Vercel API. Recreate them (remove and re-add) if exact evaluation order matters.");let ce=[],ot=[];for(let y of M)try{r.debug(`Deleting custom rule: ${y.id}`),await this.client.deleteFirewallRule(y),k.push(y),r.debug(`Custom rule deleted: ${y.id}`)}catch($){r.error(`Failed to delete custom rule ${y.id}:`,$),h.push(b(`Failed to delete custom rule ${y.id}`,$))}for(let y of le)try{r.debug(`Deleting IP blocking rule: ${y.id}`),await this.client.deleteIPBlockingRule(y),T.push(y),r.debug(`IP blocking rule deleted: ${y.id}`)}catch($){r.error(`Failed to delete IP blocking rule ${y.id}:`,$),h.push(b(`Failed to delete IP blocking rule ${y.id}`,$))}for(let y of x)try{r.debug(`Adding new custom rule: ${y.name}`);let $=await this.client.createFirewallRule(y);G.push($),$.id?$.id!==y.id&&ce.push({oldId:y.id||void 0,newId:$.id,name:y.name}):ot.push({rule:y,created:$}),r.debug(`New custom rule added: ${$.id}`)}catch($){r.error(`Failed to add custom rule "${y.name}":`,$),h.push(b(`Failed to add custom rule "${y.name}"`,$))}for(let y of q)try{r.debug(`Adding new IP blocking rule: ${y.ip}`);let $=await this.client.createIPBlockingRule(y);Ee.push($),r.debug(`New IP blocking rule added: (hostname): ${$.hostname} (ip): ${$.ip}`)}catch($){r.error(`Failed to add IP blocking rule ${y.ip}:`,$),h.push(b(`Failed to add IP blocking rule ${y.ip}`,$))}for(let y of S)try{r.debug(`Updating custom rule: ${y.id}`);let $=await this.client.updateFirewallRule(y);fe.push($),r.debug(`Custom rule updated: ${$.id}`)}catch($){r.error(`Failed to update custom rule ${y.id}:`,$),h.push(b(`Failed to update custom rule ${y.id}`,$))}for(let y of Y)try{r.debug(`Updating IP blocking rule: ${y.id}`);let $=await this.client.updateIPBlockingRule(y);Ce.push($),r.debug(`IP blocking rule updated: ${$.id}`)}catch($){r.error(`Failed to update IP blocking rule ${y.id}:`,$),h.push(b(`Failed to update IP blocking rule ${y.id}`,$))}r.debug(`${ge.underline("Custom Rules:")} ${ge.green("Added:")} ${ge.green(G.length)}, ${ge.cyan("Updated:")} ${ge.cyan(fe.length)}, ${ge.red("Deleted:")} ${ge.red(k.length)}`),r.debug(`${ge.underline("IP Rules:")} ${ge.green("Added:")} ${ge.green(Ee.length)}, ${ge.cyan("Updated:")} ${ge.cyan(Ce.length)}, ${ge.red("Deleted:")} ${ge.red(T.length)}`);let rt=p,wt,be=[...X];try{let y=await this.client.fetchFirewallConfig();rt=y.version,wt=y.updatedAt;for(let{rule:O,created:ro}of ot){let xt=y.rules.filter(qe=>qe.name===O.name);if(xt.length===1){let qe=xt[0].id;ro.id=qe,qe&&qe!==O.id&&ce.push({oldId:O.id||void 0,newId:qe,name:O.name})}else be.push(`Could not determine the Vercel-assigned id for new rule "${O.name}" (${xt.length} remote rules share that name); its local id may not match the remote one, so a later sync may re-create it. Run \`doorman download\` to reconcile.`)}let $=new Set(y.rules.map(O=>O.id)),Ti=new Set(y.ips.map(O=>O.id));for(let O of[...G,...fe])O.id&&!$.has(O.id)&&be.push(`Rule "${O.name}" (${O.id}) was synced but is missing from the post-sync remote config \u2014 a subsequent sync may re-create or misdiff it.`);for(let O of k)O.id&&$.has(O.id)&&be.push(`Rule "${O.name}" (${O.id}) was deleted but still appears in the post-sync remote config.`);for(let O of[...Ee,...Ce])O.id&&!Ti.has(O.id)&&be.push(`IP rule "${O.ip}" (${O.id}) was synced but is missing from the post-sync remote config \u2014 a subsequent sync may re-create or misdiff it.`);for(let O of T)O.id&&Ti.has(O.id)&&be.push(`IP rule "${O.ip}" (${O.id}) was deleted but still appears in the post-sync remote config.`)}catch(y){r.error("Failed to fetch updated firewall configuration version after sync:",y),h.push(b("Failed to fetch updated configuration version after sync",y))}return{success:h.length===0,rulesAdded:G.length,rulesUpdated:fe.length,rulesDeleted:k.length,ipsAdded:Ee.length,ipsUpdated:Ce.length,ipsDeleted:T.length,version:rt,updatedAt:wt,idRemappings:ce.length>0?ce:void 0,errors:h.length>0?h:void 0,warnings:be.length>0?be: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=ke.safeParse(t);if(!o.success)throw new Error(`Invalid firewall configuration: ${o.error.message}`);this.assertManagedRulesSupported(t);try{r.debug("Fetching existing firewall configuration");let s=await this.client.fetchFirewallConfig(void 0,{allowCreate:n?.allowCreate??!0,assumeYes:n?.assumeYes});r.debug(`Fetched ${s.rules.length} custom rules and ${s.ips.length} IP blocking rules`);let a=s.rules.map(m=>{let v=E.vercelToUnified(m);return v.warnings.length>0&&v.warnings.forEach(j=>{let{TranslationWarningSystem:x}=(de(),me(_e)),S=x.formatWarning(j);r.warn(`Rule ${m.name}:
25
+ ${u}`)}),l.result}),s=n.ips.map(a=>E.vercelIPToUnified(a));return{version:"2.0",provider:"vercel",providers:{vercel:{projectId:this.client.projectId,...this.client.teamId?{teamId:this.client.teamId}:{}}},rules:o,ips:s,metadata:{version:n.version,updatedAt:n.updatedAt}}}catch(n){throw r.error("Error fetching Vercel configuration:",n),new Error("Failed to fetch Vercel firewall configuration")}}async syncRules(t,n={}){let{dryRun:o=!1}=n;try{let{OperationSafety:s}=(dt(),me(ct)),a=await s.performDryRunValidation(t,"sync rules",async y=>await this.getChanges(y,{allowCreate:!o,assumeYes:n.assumeYes}));if(!a.valid)throw new Error(`Dry-run validation failed: ${a.issues.join(", ")}`);let l=a.changes,{rulesToAdd:c,rulesToUpdate:d,rulesToDelete:u,version:p}=l,f=l.ipsToAdd||[],g=l.ipsToUpdate||[],m=l.ipsToDelete||[];if(o)return r.info("Dry run mode. The following changes would be made:"),r.info(`Custom Rules - Add: ${c.length}, Update: ${d.length}, Delete: ${u.length}`),r.info(`IP Rules - Add: ${f.length}, Update: ${g.length}, Delete: ${m.length}`),{success:!0,rulesAdded:0,rulesUpdated:0,rulesDeleted:0,ipsAdded:0,ipsUpdated:0,ipsDeleted:0,version:p,warnings:a.warnings};let v=s.assessOperationRisk(l,t);if(!await s.confirmDestructiveOperation({operation:"sync rules",target:`Vercel project ${this.client.projectId}`,changes:l,riskLevel:v,skipConfirmation:n.force||!1,dryRun:!1,allowDeletions:n.allowDeletions||!1}))throw new Error("Operation cancelled by user");let x=c.map(y=>E.unifiedToVercel(y).result),S=d.map(y=>E.unifiedToVercel(y).result),M=u.map(y=>E.unifiedToVercel(y).result),q=f.map(y=>({id:y.id||"",ip:y.ip,hostname:y.hostname||"",action:y.action,notes:y.notes})),Y=g.map(y=>({id:y.id||"",ip:y.ip,hostname:y.hostname||"",action:y.action,notes:y.notes})),le=m.map(y=>({id:y.id||"",ip:y.ip,hostname:y.hostname||"",action:y.action,notes:y.notes})),G=[],fe=[],k=[],Ee=[],Ce=[],T=[],h=[],b=(y,$)=>`${y}: ${$ instanceof Error?$.message:String($)}`,X=[];_t(t.rules)&&t.rules.length>c.length&&X.push("Rule `priority` is best-effort on Vercel: new rules are created in priority order, but rules that already exist remotely cannot be repositioned via the Vercel API. Recreate them (remove and re-add) if exact evaluation order matters.");let ce=[],ot=[];for(let y of M)try{r.debug(`Deleting custom rule: ${y.id}`),await this.client.deleteFirewallRule(y),k.push(y),r.debug(`Custom rule deleted: ${y.id}`)}catch($){r.error(`Failed to delete custom rule ${y.id}:`,$),h.push(b(`Failed to delete custom rule ${y.id}`,$))}for(let y of le)try{r.debug(`Deleting IP blocking rule: ${y.id}`),await this.client.deleteIPBlockingRule(y),T.push(y),r.debug(`IP blocking rule deleted: ${y.id}`)}catch($){r.error(`Failed to delete IP blocking rule ${y.id}:`,$),h.push(b(`Failed to delete IP blocking rule ${y.id}`,$))}for(let y of x)try{r.debug(`Adding new custom rule: ${y.name}`);let $=await this.client.createFirewallRule(y);G.push($),$.id?$.id!==y.id&&ce.push({oldId:y.id||void 0,newId:$.id,name:y.name}):ot.push({rule:y,created:$}),r.debug(`New custom rule added: ${$.id}`)}catch($){r.error(`Failed to add custom rule "${y.name}":`,$),h.push(b(`Failed to add custom rule "${y.name}"`,$))}for(let y of q)try{r.debug(`Adding new IP blocking rule: ${y.ip}`);let $=await this.client.createIPBlockingRule(y);Ee.push($),r.debug(`New IP blocking rule added: (hostname): ${$.hostname} (ip): ${$.ip}`)}catch($){r.error(`Failed to add IP blocking rule ${y.ip}:`,$),h.push(b(`Failed to add IP blocking rule ${y.ip}`,$))}for(let y of S)try{r.debug(`Updating custom rule: ${y.id}`);let $=await this.client.updateFirewallRule(y);fe.push($),r.debug(`Custom rule updated: ${$.id}`)}catch($){r.error(`Failed to update custom rule ${y.id}:`,$),h.push(b(`Failed to update custom rule ${y.id}`,$))}for(let y of Y)try{r.debug(`Updating IP blocking rule: ${y.id}`);let $=await this.client.updateIPBlockingRule(y);Ce.push($),r.debug(`IP blocking rule updated: ${$.id}`)}catch($){r.error(`Failed to update IP blocking rule ${y.id}:`,$),h.push(b(`Failed to update IP blocking rule ${y.id}`,$))}r.debug(`${ge.underline("Custom Rules:")} ${ge.green("Added:")} ${ge.green(G.length)}, ${ge.cyan("Updated:")} ${ge.cyan(fe.length)}, ${ge.red("Deleted:")} ${ge.red(k.length)}`),r.debug(`${ge.underline("IP Rules:")} ${ge.green("Added:")} ${ge.green(Ee.length)}, ${ge.cyan("Updated:")} ${ge.cyan(Ce.length)}, ${ge.red("Deleted:")} ${ge.red(T.length)}`);let rt=p,wt,be=[...X];try{let y=await this.client.fetchFirewallConfig();rt=y.version,wt=y.updatedAt;for(let{rule:O,created:ro}of ot){let xt=y.rules.filter(qe=>qe.name===O.name);if(xt.length===1){let qe=xt[0].id;ro.id=qe,qe&&qe!==O.id&&ce.push({oldId:O.id||void 0,newId:qe,name:O.name})}else be.push(`Could not determine the Vercel-assigned id for new rule "${O.name}" (${xt.length} remote rules share that name); its local id may not match the remote one, so a later sync may re-create it. Run \`doorman download\` to reconcile.`)}let $=new Set(y.rules.map(O=>O.id)),Ti=new Set(y.ips.map(O=>O.id));for(let O of[...G,...fe])O.id&&!$.has(O.id)&&be.push(`Rule "${O.name}" (${O.id}) was synced but is missing from the post-sync remote config \u2014 a subsequent sync may re-create or misdiff it.`);for(let O of k)O.id&&$.has(O.id)&&be.push(`Rule "${O.name}" (${O.id}) was deleted but still appears in the post-sync remote config.`);for(let O of[...Ee,...Ce])O.id&&!Ti.has(O.id)&&be.push(`IP rule "${O.ip}" (${O.id}) was synced but is missing from the post-sync remote config \u2014 a subsequent sync may re-create or misdiff it.`);for(let O of T)O.id&&Ti.has(O.id)&&be.push(`IP rule "${O.ip}" (${O.id}) was deleted but still appears in the post-sync remote config.`)}catch(y){r.error("Failed to fetch updated firewall configuration version after sync:",y),h.push(b("Failed to fetch updated configuration version after sync",y))}return{success:h.length===0,rulesAdded:G.length,rulesUpdated:fe.length,rulesDeleted:k.length,ipsAdded:Ee.length,ipsUpdated:Ce.length,ipsDeleted:T.length,version:rt,updatedAt:wt,idRemappings:ce.length>0?ce:void 0,errors:h.length>0?h:void 0,warnings:be.length>0?be: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=ke.safeParse(t);if(!o.success)throw new Error(`Invalid firewall configuration: ${o.error.message}`);this.assertManagedRulesSupported(t);try{r.debug("Fetching existing firewall configuration");let s=await this.client.fetchFirewallConfig(void 0,{allowCreate:n?.readOnly?!1:n?.allowCreate??!0,assumeYes:n?.assumeYes});r.debug(`Fetched ${s.rules.length} custom rules and ${s.ips.length} IP blocking rules`);let a=s.rules.map(m=>{let v=E.vercelToUnified(m);return v.warnings.length>0&&v.warnings.forEach(j=>{let{TranslationWarningSystem:x}=(de(),me(_e)),S=x.formatWarning(j);r.warn(`Rule ${m.name}:
26
26
  ${S}`)}),v.result}),{toAdd:l,toUpdate:c,toDelete:d}=this.diffRules(lt(o.data.rules),a),u=s.ips.map(m=>E.vercelIPToUnified(m)),{ipsToAdd:p,ipsToUpdate:f,ipsToDelete:g}=this.diffIPRules(o.data.ips||[],u);return{version:s.version,rulesToAdd:l,rulesToUpdate:c,rulesToDelete:d,ipsToAdd:p,ipsToUpdate:f,ipsToDelete:g,hasChanges:l.length>0||c.length>0||d.length>0||p.length>0||f.length>0||g.length>0}}catch(s){throw r.error("Error fetching existing firewall configuration:",s),new Error("Failed to fetch existing firewall configuration")}}getSupportedFeatures(){return{supportsCustomRules:!0,supportsIPBlocking:!0,supportsRateLimiting:!0,supportsGeoBlocking:!0,supportsManagedRules:!1,supportsRedirect:!0,supportsChallenge:!0}}async verifyCredentials(){return this.client.verifyCredentials()}validateConfig(t){let n=super.validateConfig(t),o=[...n.errors],s=[...n.warnings];t.provider&&t.provider!=="vercel"&&o.push({path:"provider",message:"Provider must be 'vercel' for VercelFirewallService",code:"INVALID_PROVIDER"});try{let a=De.safeParse(t);a.success||o.push({path:"root",message:`Schema validation failed: ${a.error.message}`,code:"SCHEMA_VALIDATION_FAILED"})}catch(a){r.debug("Schema validation error:",a)}return{valid:o.length===0,errors:o,warnings:s}}getHealthScore(t){let n=super.getHealthScore(t),o=[...n.issues],s=n.score;return t.rules.filter(l=>l.action?.type==="rate_limit").length===0&&(s-=10,o.push({severity:"info",category:"security",message:"No rate limiting rules configured",suggestion:"Consider adding rate limiting to protect against abuse"})),(!t.ips||t.ips.length===0)&&o.push({severity:"info",category:"security",message:"No IP blocking rules configured",suggestion:"Consider blocking known malicious IPs"}),{score:Math.max(0,s),grade:n.grade,issues:o,recommendations:n.recommendations}}diffRules(t,n){let o=[],s=[],a=[...n];for(let l of t){let c=n.find(d=>d.id===l.id);if(!c)o.push(l);else{xe(Re(l),Re(c))||s.push({...l,id:c.id});let d=a.findIndex(u=>u.id===c.id);d!==-1&&a.splice(d,1)}}return{toAdd:o,toUpdate:s,toDelete:a}}diffIPRules(t,n){let o=[],s=[],a=[...n];for(let l of t){let c=n.find(d=>d.id===l.id);if(!c)r.debug("Rule not found in existing rules:",{configRule:l}),o.push(l);else{xe(Re(l),Re(c))||s.push({...c,...l});let d=a.findIndex(u=>u.id===c.id);d!==-1&&a.splice(d,1)}}return{ipsToAdd:o,ipsToUpdate:s,ipsToDelete:a}}};_();var ut=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 je(t,n,e);return new Ze(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 je(n,o,t);return new Ze(s)}static create(e,t,n){return this.fromConfig({projectId:e,teamId:t,token:n})}};import{createHash as ps}from"crypto";_();import Ye from"chalk";var R=class i extends Error{code;suggestion;details;docsUrl;cause;constructor(e){super(e.message),this.name="DoormanError",this.code=e.code,this.suggestion=e.suggestion,this.details=e.details,this.docsUrl=e.docsUrl,this.cause=e.cause,Error.captureStackTrace&&Error.captureStackTrace(this,i)}format(){let e=[];return e.push(Ye.red.bold(`[${this.code}] ${this.message}`)),e.push(""),this.suggestion&&(e.push(Ye.yellow.bold("Suggestion:")),e.push(` ${this.suggestion}`),e.push("")),this.details&&Object.keys(this.details).length>0&&(e.push(Ye.dim("Details:")),Object.entries(this.details).forEach(([t,n])=>{let o=typeof n=="object"?JSON.stringify(n,null,2).split(`
27
27
  `).join(`
28
28
  `):n;e.push(` ${Ye.cyan(t)}: ${o}`)}),e.push("")),this.cause&&(e.push(Ye.dim("Caused by:")),e.push(` ${this.cause.message}`),e.push("")),this.docsUrl&&(e.push(Ye.cyan.bold("Documentation:")),e.push(` ${this.docsUrl}`),e.push("")),e.join(`
@@ -39,7 +39,7 @@ ${l.formatWarning(a)}`)}),n.push(s.result)}return n}async syncRules(t,n={}){let{
39
39
  ${p.formatWarning(u)}`)}),o.push(d.result)}return{rules:o,ips:s}}async syncRules(t,n={}){let{dryRun:o=!1}=n;try{let{OperationSafety:s}=(dt(),me(ct)),a=await s.performDryRunValidation(t,"sync rules",async T=>this.getChanges(T));if(!a.valid)throw new Error(`Dry-run validation failed: ${a.issues.join(", ")}`);let l=a.changes,{rulesToAdd:c,rulesToUpdate:d,rulesToDelete:u}=l,p=l.ipsToAdd||[],f=l.ipsToUpdate||[],g=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: ${p.length}, Update: ${f.length}, Delete: ${g.length}`),{success:!0,rulesAdded:0,rulesUpdated:0,rulesDeleted:0,ipsAdded:0,ipsUpdated:0,ipsDeleted:0,warnings:a.warnings};let m=s.assessOperationRisk(l,t);if(!await s.confirmDestructiveOperation({operation:"sync rules",target:`Cloud Armor policy ${this.client.policyName}`,changes:l,riskLevel:m,skipConfirmation:n.force||!1,dryRun:!1,allowDeletions:n.allowDeletions||!1}))throw new Error("Operation cancelled by user");let j=[],x=[],S=(T,h)=>`${T}: ${h instanceof Error?h.message:String(h)}`,M=await this.client.getPolicy(),q=new Set(M.rules.map(T=>T.priority)),Y=1e3,le=()=>{for(;q.has(Y);)Y+=1e3;return q.add(Y),Y},G=[];for(let T of[...u,...g]){let h=St(T.id);if(h!==void 0)try{r.debug(`Removing Cloud Armor rule at priority ${h}`),await this.client.removeRule(h)}catch(b){r.error(`Failed to remove Cloud Armor rule at priority ${h}:`,b),x.push(S(`Failed to delete rule at priority ${h}`,b))}}let fe=0;for(let T of c){let h=T.priority??le();try{let b=E.unifiedToGcp({...T,priority:h});b.warnings.forEach(X=>j.push(X.message)),r.debug(`Adding Cloud Armor rule at priority ${h}: ${b.result.description}`),await this.client.addRule(b.result),fe++,G.push({oldId:T.id,newId:String(h),name:T.name})}catch(b){r.error(`Failed to add Cloud Armor rule "${T.name}":`,b),x.push(S(`Failed to add rule "${T.name}"`,b))}}let k=0;for(let T of d){let h=St(T.id);if(h!==void 0)try{let b=E.unifiedToGcp(T);b.warnings.forEach(ce=>j.push(ce.message)),T.priority!==void 0&&T.priority!==h?(r.debug(`Relocating Cloud Armor rule from priority ${h} to ${T.priority}`),await this.client.removeRule(h),await this.client.addRule(b.result),G.push({oldId:T.id,newId:String(T.priority),name:T.name})):(r.debug(`Updating Cloud Armor rule at priority ${h}`),await this.client.patchRule(h,b.result)),k++}catch(b){r.error(`Failed to update Cloud Armor rule at priority ${h}:`,b),x.push(S(`Failed to update rule at priority ${h}`,b))}}let Ee=0;for(let T of p){let h=St(T.id)??le();try{let b=E.unifiedIPToGcp(T,h);r.debug(`Adding Cloud Armor IP rule at priority ${h}: ${T.ip}`),await this.client.addRule(b),Ee++,G.push({oldId:T.id,newId:String(h),name:T.ip})}catch(b){r.error(`Failed to add Cloud Armor IP rule for ${T.ip}:`,b),x.push(S(`Failed to add IP rule for ${T.ip}`,b))}}let Ce=0;for(let T of f){let h=St(T.id);if(h!==void 0)try{r.debug(`Updating Cloud Armor IP rule at priority ${h}: ${T.ip}`),await this.client.patchRule(h,E.unifiedIPToGcp(T,h)),Ce++}catch(b){r.error(`Failed to update Cloud Armor IP rule at priority ${h}:`,b),x.push(S(`Failed to update IP rule at priority ${h}`,b))}}return r.debug(`Custom Rules: Added: ${fe}, Updated: ${k}, Deleted: ${u.length}`),r.debug(`IP Rules: Added: ${Ee}, Updated: ${Ce}, Deleted: ${g.length}`),{success:x.length===0,rulesAdded:fe,rulesUpdated:k,rulesDeleted:u.length,ipsAdded:Ee,ipsUpdated:Ce,ipsDeleted:g.length,idRemappings:G.length>0?G:void 0,errors:x.length>0?x:void 0,warnings:j.length>0?j: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){let n=ke.safeParse(t);if(!n.success)throw new Error(`Invalid firewall configuration: ${n.error.message}`);this.assertManagedRulesSupported(t);try{r.debug("Fetching existing Cloud Armor configuration");let o=await this.client.getPolicy(),s=new Set(t.rules.map(m=>m.id).filter(m=>m!==void 0)),{rules:a,ips:l}=this.translatePolicy(o,s),{toAdd:c,toUpdate:d,toDelete:u}=this.diffItems(t.rules,a,(m,v)=>xe(Re(m),Re(v))),{toAdd:p,toUpdate:f,toDelete:g}=this.diffItems(t.ips||[],l,(m,v)=>xe(Re(m),Re(v)));return{rulesToAdd:c,rulesToUpdate:d,rulesToDelete:u,ipsToAdd:p,ipsToUpdate:f,ipsToDelete:g,hasChanges:c.length>0||d.length>0||u.length>0||p.length>0||f.length>0||g.length>0}}catch(o){throw r.error("Error fetching existing Cloud Armor configuration:",o),new Error("Failed to fetch existing Cloud Armor configuration")}}getSupportedFeatures(){return{supportsCustomRules:!0,supportsIPBlocking:!0,supportsRateLimiting:!0,supportsManagedRules:!1,supportsGeoBlocking:!0,supportsRedirect:!0,supportsChallenge:!1}}async verifyCredentials(){return this.client.verifyCredentials()}validateConfig(t){let n=super.validateConfig(t),o=[...n.errors],s=[...n.warnings];return t.provider&&t.provider!=="gcp"&&o.push({path:"provider",message:"Provider must be 'gcp' for CloudArmorFirewallService",code:"INVALID_PROVIDER"}),{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}}};_();var yt=class{static fromEnv(){let e=process.env.GOOGLE_CLOUD_PROJECT,t=process.env.GCP_POLICY_NAME,n=process.env.GOOGLE_APPLICATION_CREDENTIALS;if(!e)throw new Error("GOOGLE_CLOUD_PROJECT environment variable is required");if(!t)throw new Error("GCP_POLICY_NAME environment variable is required");let o=new et(e,t,n);return new tt(o)}static fromConfig(e){let t=e.projectId||process.env.GOOGLE_CLOUD_PROJECT,n=e.policyName||process.env.GCP_POLICY_NAME,o=e.serviceAccountKeyPath||process.env.GOOGLE_APPLICATION_CREDENTIALS;if(!t)throw new Error("GCP project ID is required (provide projectId or set GOOGLE_CLOUD_PROJECT env var)");if(!n)throw new Error("Cloud Armor policy name is required (provide policyName or set GCP_POLICY_NAME env var)");r.debug("Creating Cloud Armor provider with config:",{projectId:t,policyName:n});let s=new et(t,n,o);return new tt(s)}static create(e,t,n){return this.fromConfig({projectId:e,policyName:t,serviceAccountKeyPath:n})}};async function zn(i){let e;if(i.provider)e=i.provider,r.debug(`Using explicitly specified provider: ${e}`);else{let t=Tt.detect(i.config);t.provider?(e=t.provider,r.debug(`Auto-detected provider: ${e} (${t.confidence} confidence)`),t.reasons.length>0&&r.debug(`Reasons: ${t.reasons.join(", ")}`)):i.interactive!==!1?e=await Es():(e="vercel",r.warn("No provider detected, defaulting to Vercel"))}try{let t=await bs(e,i);return Is(e,t)}catch(t){throw r.error(`Failed to initialize ${e} provider:`,t),t}}function Cs(i,e,t){if(!t.configKey)return;let o=i?.providers?.[e]?.[t.configKey];if(typeof o=="string")return o;if(t.legacyConfigKey){let s=i?.[t.legacyConfigKey];if(typeof s=="string")return s}}async function bs(i,e){let t=We[i],n=e.config,o={};for(let l of t.fields)o[l.key]=Cs(n,i,l);let s=Yi(t,{explicit:e.credentials,config:o}),a=Ji(t,s);if(a.length===0)return s;if(e.interactive===!1){let l=a.map(c=>`--${c.key}`).join(", ");throw new Error(`${Rt(i)} credentials missing. Provide ${l}.`)}r.warn(`Missing ${Rt(i)} credentials`),r.info(`Please provide your ${Rt(i)} credentials:`);for(let l of t.fields){if(s[l.key])continue;let c=l.promptMessage??vs(l),d=l.secret?await zi(c):await C(c,{type:"text"});s[l.key]=d||void 0}return s}function vs(i){let e=i.required?"":" (optional)",t=i.secret?" ":"";return`${i.label}${e}:${t}`}function Is(i,e){switch(i){case"vercel":{let t={token:e.token,projectId:e.projectId,teamId:e.teamId};return{provider:ut.fromConfig(t),vercelCredentials:t}}case"cloudflare":return{provider:ft.fromConfig({apiToken:e.apiToken,zoneId:e.zoneId,accountId:e.accountId})};case"fastly":return{provider:ht.fromConfig({apiToken:e.apiToken,workspaceId:e.workspaceId})};case"gcp":return{provider:yt.fromConfig({projectId:e.projectId,policyName:e.policyName,serviceAccountKeyPath:e.serviceAccountKeyPath})};default:throw new Error(`Unknown provider: ${i}`)}}async function Es(){r.info(ws.yellow("Unable to auto-detect firewall provider.")),r.info(`Please select a provider:
40
40
  `),K.forEach((s,a)=>{r.info(` ${a+1}. ${Rt(s)}`)}),r.info("");let i=await C(`Select provider (1-${K.length}):`,{type:"text"}),e=String(i).trim().toLowerCase(),t=K.find(s=>s===e);if(t)return t;let n=Number(e);if(Number.isInteger(n)&&n>=1&&n<=K.length)return K[n-1];let o=K[0];return r.warn(`Invalid choice: ${i}, defaulting to ${Rt(o)}`),o}function Rt(i){switch(i){case"vercel":return"Vercel Firewall";case"cloudflare":return"Cloudflare WAF";case"fastly":return"Fastly Next-Gen WAF";case"gcp":return"Google Cloud Armor";default:return i}}function Ts(i){if(i.length===1){let{field:t}=i[0],n=t.required?"":" (optional)",o=t.configKey?" or config file":"";return`${t.label}${n} (defaults to ${t.envVar} env var${o})`}return`Resolved against whichever provider is active: ${i.map(t=>`${t.field.label} (${t.field.envVar})`).join(" / ")}`}var si=new Map;for(let i of K)for(let e of We[i].fields){let t=si.get(e.key)??[];t.push({provider:i,field:e}),si.set(e.key,t)}var re=Object.fromEntries(Array.from(si.entries()).map(([i,e])=>{let t=e.map(n=>n.field.cliAlias).find(n=>!!n);return[i,{type:"string",...t?{alias:t}:{},description:Ts(e)}]}));function J(i){return{token:i.token,projectId:i.projectId,teamId:i.teamId,apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId,workspaceId:i.workspaceId,policyName:i.policyName,serviceAccountKeyPath:i.serviceAccountKeyPath}}async function se(i,e){try{i.debug&&(r.level=As.debug);let t;if(i.optionalConfig)try{t=await H(i.config,"optional")}catch{t={}}else i.skipValidation?t=await H(i.config,"raw"):t=await H(i.config,"required");let{provider:n,vercelCredentials:o}=await zn({provider:i.provider,config:t,interactive:!i.ci,credentials:J(i)}),s=o?.token??"",a=o?.projectId??"",l=o?.teamId??"";await e({config:t,provider:n,token:s,projectId:a,teamId:l})}catch(t){ne(t,i.errorContext)}}var Us="backup",Ls="Backup or restore firewall configurations",xs={config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:oe,...re,output:{alias:"o",type:"string",description:"Output directory for backups",default:"./backups"},restore:{alias:"r",type:"string",description:"Restore from backup file"},list:{alias:"l",type:"boolean",description:"List available backups",default:!1},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}},Ds=async i=>{try{i.debug&&(r.level=Ss.debug);let e=i.output||"./backups";if(i.list){if(!Ut(e)){r.info(ae.yellow("No backup directory found."));return}let t=Ps(e).filter(n=>n.endsWith(".json")).map(n=>{let o=ai(e,n),s=_s(o);return{name:n,size:s.size,created:s.mtime}}).sort((n,o)=>o.created.getTime()-n.created.getTime());if(t.length===0){r.info(ae.yellow("No backups found."));return}r.log(ae.bold(`
41
41
  \u{1F4E6} Available Backups:
42
- `)),t.forEach(n=>{r.log(`${ae.cyan(n.name)}`),r.log(` ${ae.dim("Created:")} ${n.created.toLocaleString()}`),r.log(` ${ae.dim("Size:")} ${(n.size/1024).toFixed(1)} KB`),r.log("")});return}if(i.restore){let t=i.restore.startsWith("/")?i.restore:ai(e,i.restore);Ut(t)||(r.error(`Backup file not found: ${t}`),process.exit(1));let n=await H(t,"raw"),o=i.config||".doorman.json";if(Ut(o)&&!await C(`Config file ${o} already exists. Do you want to overwrite it?`,{type:"confirm"})){r.info(ae.yellow("Restore cancelled."));return}let{backup:s,...a}=n;await Z(a,o),r.success(ae.green(`\u2705 Restored configuration from ${t} to ${o}`));return}await se({config:i.config,provider:i.provider,...J(i),debug:i.debug,ci:i.ci,errorContext:"creating backup"},async({provider:t,projectId:n,teamId:o})=>{r.start("Fetching current remote configuration...");let s=await t.fetchConfig(),a=s.metadata?.version??s.version;Le.getInstance().validateConfig(s),Ut(e)||$s(e,{recursive:!0});let c=new Date,d=c.toISOString().split("T")[0]??"unknown-date",u=(c.toISOString().split("T")[1]??"").split(".")[0]?.replace(/:/g,"-")??"unknown-time",f=`firewall-backup-${`${d}_${u}`}.json`,g=ai(e,f),m={...s,backup:{createdAt:new Date().toISOString(),source:"remote",provider:t.name,...t.name==="vercel"?{projectId:n,teamId:o}:{},originalVersion:a}};await Z(m,g,{validate:!1}),r.success(ae.green(`\u2705 Backup created: ${g}`)),r.log(""),r.log(ae.bold("Backup Details:")),r.log(`${ae.dim("Version:")} ${a??"unknown"}`),r.log(`${ae.dim("Rules:")} ${s.rules.length} custom, ${s.ips?.length??0} IP blocking`),r.log(`${ae.dim("Created:")} ${new Date().toLocaleString()}`),!ks(e)&&Wi()&&!ji(e)&&r.warn(ae.yellow(`\u{1F4A1} ${e} isn't in .gitignore \u2014 firewall backups may contain rule details you don't want in git history.`)),r.log(""),r.log(ae.dim("To restore this backup later, run:")),r.log(ae.cyan(`doorman backup --restore ${f}`))})}catch(e){ne(e,"managing backup")}};var ci={};ie(ci,{builder:()=>Ns,command:()=>Os,desc:()=>Fs,handler:()=>Vs});_();import ue from"chalk";function we(i){if(Se(i))return i;let e=i,t=e.rules.map(o=>E.vercelToUnified(o).result),n=(e.ips??[]).map(o=>E.vercelIPToUnified(o));return{version:"2.0",provider:"vercel",providers:{vercel:{projectId:e.projectId,teamId:e.teamId}},rules:t,ips:n,metadata:{version:e.version,updatedAt:e.updatedAt}}}function Lt(i,e){let t=!1,n=Se(i),o=n?{...i,metadata:{...i.metadata}}:{...i};if(e.version!==void 0)if(n){let s=o;s.metadata?.version!==e.version&&(s.metadata={...s.metadata,version:e.version,updatedAt:e.updatedAt??new Date().toISOString()},t=!0)}else{let s=o;s.version!==e.version&&(s.version=e.version,s.updatedAt=e.updatedAt??new Date().toISOString(),t=!0)}if(e.idRemappings&&e.idRemappings.length>0){let s=new Map(e.idRemappings.filter(f=>f.oldId).map(f=>[f.oldId,f.newId])),a=new Map(e.idRemappings.filter(f=>!f.oldId).map(f=>[f.name,f.newId])),l=(f,g)=>{let m=f.id?s.get(f.id):a.get(g);return m&&m!==f.id?{...f,id:m}:f},c=!1,d=o.rules.map(f=>{let g=l(f,f.name);return g!==f&&(c=!0),g}),u=!1,p=(o.ips??[]).map(f=>{let g=l(f,f.ip);return g!==f&&(u=!0),g});(c||u)&&(o={...o,rules:d,...u?{ips:p}:{}},t=!0)}return{config:o,changed:t}}function Gn(i,e){if(Se(e))return{...e,...i};let t=e,n=i.rules.map(s=>E.unifiedToVercel(s).result),o=(i.ips??[]).map(s=>({id:s.id||"",ip:s.ip,hostname:s.hostname||"",action:s.action,notes:s.notes}));return{...t,projectId:i.providers?.vercel?.projectId??t.projectId,teamId:i.providers?.vercel?.teamId??t.teamId,version:i.metadata?.version,updatedAt:i.metadata?.updatedAt,rules:n,ips:o}}var Os="diff",Fs="Show detailed differences between local and remote firewall configuration",Ns={config:{alias:"c",type:"string",description:"Path to firewall config file"},provider:oe,...re,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}},Vs=async i=>{await se({config:i.config,provider:i.provider,...J(i),debug:i.debug,ci:i.ci,errorContext:"calculating diff"},async({config:e,provider:t})=>{await Bs(t,we(e),i)})};async function Bs(i,e,t){let n=t.format==="json";n||r.start("Calculating differences...");let o=await i.getChanges(e),s=o.ipsToAdd??[],a=o.ipsToUpdate??[],l=o.ipsToDelete??[];if(n){r.log(JSON.stringify({rules:{toAdd:o.rulesToAdd,toUpdate:o.rulesToUpdate,toDelete:o.rulesToDelete},ips:{toAdd:s,toUpdate:a,toDelete:l},summary:{hasChanges:o.hasChanges,ruleChanges:o.rulesToAdd.length+o.rulesToUpdate.length+o.rulesToDelete.length,ipChanges:s.length+a.length+l.length}},null,2));return}if(!o.hasChanges){r.success(ue.green("No differences found. Local and remote configurations are in sync."));return}r.log(ue.bold(`
42
+ `)),t.forEach(n=>{r.log(`${ae.cyan(n.name)}`),r.log(` ${ae.dim("Created:")} ${n.created.toLocaleString()}`),r.log(` ${ae.dim("Size:")} ${(n.size/1024).toFixed(1)} KB`),r.log("")});return}if(i.restore){let t=i.restore.startsWith("/")?i.restore:ai(e,i.restore);Ut(t)||(r.error(`Backup file not found: ${t}`),process.exit(1));let n=await H(t,"raw"),o=i.config||".doorman.json";if(Ut(o)&&!await C(`Config file ${o} already exists. Do you want to overwrite it?`,{type:"confirm"})){r.info(ae.yellow("Restore cancelled."));return}let{backup:s,...a}=n;await Z(a,o),r.success(ae.green(`\u2705 Restored configuration from ${t} to ${o}`));return}await se({config:i.config,provider:i.provider,...J(i),debug:i.debug,ci:i.ci,errorContext:"creating backup"},async({provider:t,projectId:n,teamId:o})=>{r.start("Fetching current remote configuration...");let s=await t.fetchConfig(),a=s.metadata?.version??s.version;Le.getInstance().validateConfig(s),Ut(e)||$s(e,{recursive:!0});let c=new Date,d=c.toISOString().split("T")[0]??"unknown-date",u=(c.toISOString().split("T")[1]??"").split(".")[0]?.replace(/:/g,"-")??"unknown-time",f=`firewall-backup-${`${d}_${u}`}.json`,g=ai(e,f),m={...s,backup:{createdAt:new Date().toISOString(),source:"remote",provider:t.name,...t.name==="vercel"?{projectId:n,teamId:o}:{},originalVersion:a}};await Z(m,g,{validate:!1}),r.success(ae.green(`\u2705 Backup created: ${g}`)),r.log(""),r.log(ae.bold("Backup Details:")),r.log(`${ae.dim("Version:")} ${a??"unknown"}`),r.log(`${ae.dim("Rules:")} ${s.rules.length} custom, ${s.ips?.length??0} IP blocking`),r.log(`${ae.dim("Created:")} ${new Date().toLocaleString()}`),!ks(e)&&Wi()&&!ji(e)&&r.warn(ae.yellow(`\u{1F4A1} ${e} isn't in .gitignore \u2014 firewall backups may contain rule details you don't want in git history.`)),r.log(""),r.log(ae.dim("To restore this backup later, run:")),r.log(ae.cyan(`doorman backup --restore ${f}`))})}catch(e){ne(e,"managing backup")}};var ci={};ie(ci,{builder:()=>Ns,command:()=>Os,desc:()=>Fs,handler:()=>Vs});_();import ue from"chalk";function we(i){if(Se(i))return i;let e=i,t=e.rules.map(o=>E.vercelToUnified(o).result),n=(e.ips??[]).map(o=>E.vercelIPToUnified(o));return{version:"2.0",provider:"vercel",providers:{vercel:{projectId:e.projectId,teamId:e.teamId}},rules:t,ips:n,metadata:{version:e.version,updatedAt:e.updatedAt}}}function Lt(i,e){let t=!1,n=Se(i),o=n?{...i,metadata:{...i.metadata}}:{...i};if(e.version!==void 0)if(n){let s=o;s.metadata?.version!==e.version&&(s.metadata={...s.metadata,version:e.version,updatedAt:e.updatedAt??new Date().toISOString()},t=!0)}else{let s=o;s.version!==e.version&&(s.version=e.version,s.updatedAt=e.updatedAt??new Date().toISOString(),t=!0)}if(e.idRemappings&&e.idRemappings.length>0){let s=new Map(e.idRemappings.filter(f=>f.oldId).map(f=>[f.oldId,f.newId])),a=new Map(e.idRemappings.filter(f=>!f.oldId).map(f=>[f.name,f.newId])),l=(f,g)=>{let m=f.id?s.get(f.id):a.get(g);return m&&m!==f.id?{...f,id:m}:f},c=!1,d=o.rules.map(f=>{let g=l(f,f.name);return g!==f&&(c=!0),g}),u=!1,p=(o.ips??[]).map(f=>{let g=l(f,f.ip);return g!==f&&(u=!0),g});(c||u)&&(o={...o,rules:d,...u?{ips:p}:{}},t=!0)}return{config:o,changed:t}}function Gn(i,e){if(Se(e))return{...e,...i};let t=e,n=i.rules.map(s=>E.unifiedToVercel(s).result),o=(i.ips??[]).map(s=>({id:s.id||"",ip:s.ip,hostname:s.hostname||"",action:s.action,notes:s.notes}));return{...t,projectId:i.providers?.vercel?.projectId??t.projectId,teamId:i.providers?.vercel?.teamId??t.teamId,version:i.metadata?.version,updatedAt:i.metadata?.updatedAt,rules:n,ips:o}}var Os="diff",Fs="Show detailed differences between local and remote firewall configuration",Ns={config:{alias:"c",type:"string",description:"Path to firewall config file"},provider:oe,...re,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}},Vs=async i=>{await se({config:i.config,provider:i.provider,...J(i),debug:i.debug,ci:i.ci,errorContext:"calculating diff"},async({config:e,provider:t})=>{await Bs(t,we(e),i)})};async function Bs(i,e,t){let n=t.format==="json";n||r.start("Calculating differences...");let o=await i.getChanges(e,{readOnly:!0}),s=o.ipsToAdd??[],a=o.ipsToUpdate??[],l=o.ipsToDelete??[];if(n){r.log(JSON.stringify({rules:{toAdd:o.rulesToAdd,toUpdate:o.rulesToUpdate,toDelete:o.rulesToDelete},ips:{toAdd:s,toUpdate:a,toDelete:l},summary:{hasChanges:o.hasChanges,ruleChanges:o.rulesToAdd.length+o.rulesToUpdate.length+o.rulesToDelete.length,ipChanges:s.length+a.length+l.length}},null,2));return}if(!o.hasChanges){r.success(ue.green("No differences found. Local and remote configurations are in sync."));return}r.log(ue.bold(`
43
43
  \u{1F50D} ${i.name} Configuration Differences
44
44
  `)),r.log(ue.bold("Rule Changes:")),r.log(` ${ue.green("+")} ${o.rulesToAdd.length} to add`),o.rulesToAdd.forEach(d=>r.log(` ${ue.green("+")} ${d.name}`)),r.log(` ${ue.cyan("~")} ${o.rulesToUpdate.length} to update`),o.rulesToUpdate.forEach(d=>r.log(` ${ue.cyan("~")} ${d.name}`)),r.log(` ${ue.red("-")} ${o.rulesToDelete.length} to delete`),o.rulesToDelete.forEach(d=>r.log(` ${ue.red("-")} ${d.name}`)),(s.length||a.length||l.length)&&(r.log(""),r.log(ue.bold("IP Rule Changes:")),r.log(` ${ue.green("+")} ${s.length} to add`),r.log(` ${ue.cyan("~")} ${a.length} to update`),r.log(` ${ue.red("-")} ${l.length} to delete`));let c=o.rulesToAdd.length+o.rulesToUpdate.length+o.rulesToDelete.length+s.length+a.length+l.length;r.log(""),r.log(ue.bold(`Summary: ${c} total changes detected`)),r.log(ue.dim("Run `sync` to apply these changes to the remote configuration."))}var di={};ie(di,{builder:()=>js,command:()=>qs,desc:()=>Ws,handler:()=>zs});_();import it from"chalk";import{z as Ms}from"zod";Te();var qs="download [configVersion]",Ws="Download remote Vercel Firewall rules and update local config, optionally for a specific configuration version",js={configVersion:{type:"number",description:"Specific configuration version to download (defaults to latest)"},config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:oe,...re,dryRun:{alias:"d",type:"boolean",description:"Show rules that would be downloaded without making changes",default:!1},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}},zs=async i=>{await se({config:i.config,provider:i.provider,...J(i),debug:i.debug,ci:i.ci,optionalConfig:!0,skipValidation:!0,errorContext:"downloading firewall rules"},async({config:e,provider:t})=>{if(i.configVersion!==void 0)try{bt.parse(i.configVersion)}catch(n){throw n instanceof Ms.ZodError&&(r.error("Invalid configuration version number. Version must be a positive integer."),process.exit(1)),n}await Gs(t,e,i)})};async function Gs(i,e,t){r.start(`Fetching remote firewall configuration${t.configVersion?` version ${t.configVersion}`:""} ...`);let n=await i.fetchConfig(t.configVersion),o=n.rules,s=n.ips??[];if(o.length>0?(r.log(it.bold(`
45
45
  Remote Custom Rules to Download (${o.length}):
@@ -124,7 +124,7 @@ Use --id for exact matching, or --interactive to select from a list.`))),process
124
124
  ${i.dryRun?"Would remove":"Removing"} ${o.length} rule(s):`)),gi(o),i.dryRun){r.log(B.dim(`
125
125
  Dry run \u2014 no changes made.`));return}if(!await Yn(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(B.yellow("\u26A0\uFE0F Configuration will have no rules after removal.")),await Z(a,i.config),r.success(B.green(`\u2714 Removed ${o.length} rule(s) from configuration`)),gi(o)}r.log(""),r.log(B.dim(`Run ${B.cyan("doorman sync")} to deploy these changes.`))}catch(e){ne(e,"removing rule")}};var hi={};ie(hi,{builder:()=>Ea,command:()=>va,desc:()=>Ia,handler:()=>Ta});_();import L from"chalk";var va="setup",Ia="Show setup instructions and helpful links for Doorman",Ea={},Ta=async i=>{r.log(""),r.log(L.bold.cyan("\u{1F6AA} Doorman Setup Guide")),r.log(L.dim(`Everything you need to get started with firewall management
126
126
  `)),r.log(L.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(L.bold("\u{1F50D} Finding Your Project Information")),r.log(""),r.log(L.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(L.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(L.bold("\u{1F511} Creating API Token")),r.log(""),r.log(L.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(L.cyan("Required Permissions:")),r.log(" \u2022 Read access to project settings"),r.log(" \u2022 Write access to firewall configuration"),r.log(""),r.log(L.bold("\u{1F30D} Environment Setup")),r.log(""),r.log(L.cyan("Set your API token as environment variable:")),r.log(""),r.log(L.green("# For current session:")),r.log('export VERCEL_TOKEN="your-token-here"'),r.log(""),r.log(L.green("# For permanent setup (add to ~/.bashrc or ~/.zshrc):")),r.log(`echo 'export VERCEL_TOKEN="your-token-here"' >> ~/.bashrc`),r.log(""),r.log(L.green("# Or create a .env file in your project:")),r.log('echo "VERCEL_TOKEN=your-token-here" > .env'),r.log(""),r.log(L.bold("\u{1F680} Quick Start Commands")),r.log(""),r.log(L.cyan("1. Initialize configuration:")),r.log(" doorman init --interactive"),r.log(""),r.log(L.cyan("2. Validate setup:")),r.log(" doorman validate"),r.log(""),r.log(L.cyan("3. Check current status:")),r.log(" doorman status"),r.log(""),r.log(L.cyan("4. View existing rules:")),r.log(" doorman list"),r.log(""),r.log(L.cyan("5. Sync your configuration:")),r.log(" doorman sync"),r.log(""),r.log(L.bold("\u{1F527} Common Issues")),r.log(""),r.log(L.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(L.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(L.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(L.bold("\u{1F4DA} Additional Resources")),r.log(""),r.log(L.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(L.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(L.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(L.bold.green("\u2705 Ready to get started?")),r.log(L.dim("Run: ")+L.cyan("doorman init --interactive")),r.log("")};var yi={};ie(yi,{builder:()=>Pa,command:()=>Aa,desc:()=>$a,handler:()=>_a});_();import F from"chalk";var Aa="status",$a="Show sync status between local and remote firewall configuration",Pa={config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:oe,...re,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}},_a=async i=>{await se({config:i.config,provider:i.provider,...J(i),debug:i.debug,ci:i.ci,errorContext:"checking status"},async({config:e,provider:t})=>{await ka(t,we(e),i)})};async function ka(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(F.bold(`
127
+ `),r.log(L.bold("\u{1F50D} Finding Your Project Information")),r.log(""),r.log(L.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(L.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(L.bold("\u{1F511} Creating API Token")),r.log(""),r.log(L.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(L.cyan("Required Permissions:")),r.log(" \u2022 Read access to project settings"),r.log(" \u2022 Write access to firewall configuration"),r.log(""),r.log(L.bold("\u{1F30D} Environment Setup")),r.log(""),r.log(L.cyan("Set your API token as environment variable:")),r.log(""),r.log(L.green("# For current session:")),r.log('export VERCEL_TOKEN="your-token-here"'),r.log(""),r.log(L.green("# For permanent setup (add to ~/.bashrc or ~/.zshrc):")),r.log(`echo 'export VERCEL_TOKEN="your-token-here"' >> ~/.bashrc`),r.log(""),r.log(L.green("# Or create a .env file in your project:")),r.log('echo "VERCEL_TOKEN=your-token-here" > .env'),r.log(""),r.log(L.bold("\u{1F680} Quick Start Commands")),r.log(""),r.log(L.cyan("1. Initialize configuration:")),r.log(" doorman init --interactive"),r.log(""),r.log(L.cyan("2. Validate setup:")),r.log(" doorman validate"),r.log(""),r.log(L.cyan("3. Check current status:")),r.log(" doorman status"),r.log(""),r.log(L.cyan("4. View existing rules:")),r.log(" doorman list"),r.log(""),r.log(L.cyan("5. Sync your configuration:")),r.log(" doorman sync"),r.log(""),r.log(L.bold("\u{1F527} Common Issues")),r.log(""),r.log(L.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(L.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(L.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(L.bold("\u{1F4DA} Additional Resources")),r.log(""),r.log(L.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(L.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(L.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(L.bold.green("\u2705 Ready to get started?")),r.log(L.dim("Run: ")+L.cyan("doorman init --interactive")),r.log("")};var yi={};ie(yi,{builder:()=>Pa,command:()=>Aa,desc:()=>$a,handler:()=>_a});_();import F from"chalk";var Aa="status",$a="Show sync status between local and remote firewall configuration",Pa={config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:oe,...re,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}},_a=async i=>{await se({config:i.config,provider:i.provider,...J(i),debug:i.debug,ci:i.ci,errorContext:"checking status"},async({config:e,provider:t})=>{await ka(t,we(e),i)})};async function ka(i,e,t){let n=t.format==="json";n||r.start("Checking sync status...");let o=await i.getChanges(e,{readOnly:!0}),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(F.bold(`
128
128
  \u{1F4CA} ${i.name} Sync Status Summary
129
129
  `)),o.version!==void 0&&(r.log(`${F.dim("Local Version:")} ${F.yellow(a??"unknown")}`),r.log(`${F.dim("Remote Version:")} ${F.yellow(o.version)}`),r.log(`${F.dim("Version Status:")} ${l?F.red("Out of sync"):F.green("In sync")}`),r.log("")),r.log(`${F.dim("Rules:")}`),r.log(` ${F.green("+")} ${o.rulesToAdd.length} to add`),r.log(` ${F.cyan("~")} ${o.rulesToUpdate.length} to update`),r.log(` ${F.red("-")} ${o.rulesToDelete.length} to delete`),r.log(`${F.dim("IPs:")}`),r.log(` ${F.green("+")} ${o.ipsToAdd?.length??0} to add`),r.log(` ${F.cyan("~")} ${o.ipsToUpdate?.length??0} to update`),r.log(` ${F.red("-")} ${o.ipsToDelete?.length??0} to delete`),r.log(""),c?r.success(F.green("\u2705 Everything is in sync!")):r.warn(F.yellow("\u26A0\uFE0F Changes detected. Run `sync` to apply changes.")),r.log(`
130
130
  `+F.bold("\u{1F3E5} Configuration Health Check")),r.log(`${F.dim("Score:")} ${s.score}/100 (${s.grade})`),s.issues.forEach(d=>{let u=d.severity==="error"?F.red("\u2717"):d.severity==="warning"?F.yellow("\u26A0"):F.dim("\u2139");r.log(` ${u} [${d.category}] ${d.message}`),d.suggestion&&r.log(` ${F.dim(d.suggestion)}`)}),s.recommendations.length>0&&(r.log(F.dim(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gfargo/doorman",
3
- "version": "3.15.6",
3
+ "version": "3.15.7",
4
4
  "description": "Manage firewall rules as code across multiple providers",
5
5
  "publishConfig": {
6
6
  "access": "public"