@gfargo/doorman 3.13.10 → 3.13.11

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.mjs CHANGED
@@ -11,7 +11,7 @@ If this is intended, re-run with --allow-deletions. Run "doorman diff" first if
11
11
  \u{1F4BE} Backup Recommended: ${u.reason}`),u.instructions.forEach(w=>{r.info(` \u2022 ${w}`)}));let p=this.getRollbackGuidance(t);p.available&&(r.info(`
12
12
  \u{1F504} Rollback Available: ${p.method}`),p.timeWindow&&r.info(` Time Window: ${p.timeWindow}`));let f=this.getConfirmationMessage(s,t);return await b(f,{type:"confirm"})?(r.info("\u2705 Operation confirmed by user"),!0):(r.info("Operation cancelled by user"),!1)}static async performDryRunValidation(e,t,n){r.info(`\u{1F50D} Performing dry-run validation for ${t}...`);let o=[],s=[],a={rulesToAdd:[],rulesToUpdate:[],rulesToDelete:[],ipsToAdd:[],ipsToUpdate:[],ipsToDelete:[],hasChanges:!1};try{this.validateConfigurationStructure(e,o),a=await n(e),this.validateChanges(a,s),this.checkForPotentialIssues(e,a,s);let l=o.length===0;return l?r.info("\u2705 Dry-run validation passed"):(r.warn(`\u26A0\uFE0F Dry-run validation found ${o.length} issues:`),o.forEach(c=>r.warn(` \u2022 ${c}`))),s.length>0&&(r.warn(`\u26A0\uFE0F Dry-run validation found ${s.length} warning(s):`),s.forEach(c=>r.warn(` \u2022 ${c}`))),{valid:l,changes:a,issues:o,warnings:s}}catch(l){let c=`Dry-run validation failed: ${l instanceof Error?l.message:String(l)}`;return o.push(c),r.error(c),{valid:!1,changes:a,issues:o,warnings:s}}}static assessOperationRisk(e,t){let n=t.rules.length,o=t.ips?.length||0,s=(e.rulesToDelete?.length||0)+(e.ipsToDelete?.length||0),a=(e.rulesToAdd?.length||0)+(e.rulesToUpdate?.length||0)+(e.ipsToAdd?.length||0)+(e.ipsToUpdate?.length||0)+s;return s===n&&n>0||s>0&&s/Math.max(n+o,1)>.5||a>50||t.rules.some(c=>c.action.type==="deny"&&c.conditions.some(d=>d.field==="path"&&(d.value==="/"||d.value==="*")))?"high":s>0||a>10||e.rulesToUpdate&&e.rulesToUpdate.length>0?"medium":"low"}static getBackupRecommendation(e,t){return{"sync rules":{recommended:t!=="low",reason:"Rule synchronization can overwrite existing firewall configuration",instructions:['Run "doorman download" to backup current Cloudflare rules',"Save the downloaded configuration file with a timestamp","Consider using version control for configuration files"]},"delete ruleset":{recommended:!0,reason:"Ruleset deletion is irreversible and removes all associated rules",instructions:['Export current ruleset with "doorman export"',"Document the ruleset ID and configuration","Ensure you have the original configuration files"]},"update rules":{recommended:t==="high",reason:"Rule updates can affect traffic flow and security posture",instructions:["Download current configuration as backup","Test changes in a staging environment if possible","Have rollback plan ready"]},"clear all rules":{recommended:!0,reason:"Clearing all rules removes all firewall protection",instructions:['Export complete configuration with "doorman export"',"Save configuration to version control","Document business justification for clearing rules"]}}[e]||{recommended:t!=="low",reason:"Operation may modify existing configuration",instructions:["Download current configuration as backup","Document the changes being made"]}}static getRollbackGuidance(e){return{"sync rules":{available:!0,method:"Restore from backup configuration",instructions:['Use "doorman sync" with your backup configuration file',"Or manually restore rules in Cloudflare dashboard","Check that all rules are functioning as expected"],timeWindow:"No time limit - can rollback anytime"},"delete ruleset":{available:!1,method:"Recreation required",instructions:["Deleted rulesets cannot be restored","Must recreate ruleset and rules from backup",'Use "doorman sync" with backup configuration']},"update rules":{available:!0,method:"Restore previous rule version",instructions:["Sync with previous configuration version","Or use Cloudflare dashboard to revert changes","Verify rule functionality after rollback"],timeWindow:"Immediate - no time restrictions"},"clear all rules":{available:!0,method:"Restore from backup",instructions:["Sync with backup configuration file","Verify all rules are restored correctly","Test firewall functionality"],timeWindow:"No time limit"}}[e]||{available:!0,method:"Restore from backup",instructions:["Use backup configuration to restore previous state","Verify changes are reverted correctly"]}}static formatRiskLevel(e){switch(e){case"low":return Xt.green("LOW");case"medium":return Xt.yellow("MEDIUM");case"high":return Xt.red("HIGH");default:return String(e).toUpperCase()}}static displayChangeSummary(e){r.info(`
13
13
  \u{1F4CA} Change Summary:`),e.rulesToAdd?.length&&r.info(` \u2022 Rules to add: ${e.rulesToAdd.length}`),e.rulesToUpdate?.length&&r.info(` \u2022 Rules to update: ${e.rulesToUpdate.length}`),e.rulesToDelete?.length&&r.info(` \u2022 Rules to delete: ${e.rulesToDelete.length}`),e.ipsToAdd?.length&&r.info(` \u2022 IPs to add: ${e.ipsToAdd.length}`),e.ipsToUpdate?.length&&r.info(` \u2022 IPs to update: ${e.ipsToUpdate.length}`),e.ipsToDelete?.length&&r.info(` \u2022 IPs to delete: ${e.ipsToDelete.length}`),e.hasChanges||r.info(" \u2022 No changes detected")}static displayRiskWarnings(e,t){let n={low:["This operation has minimal risk of disrupting service"],medium:["This operation may temporarily affect traffic filtering","Monitor your application after the change"],high:["\u26A0\uFE0F This operation can significantly impact security and traffic flow","\u26A0\uFE0F Ensure you have tested the configuration in a safe environment","\u26A0\uFE0F Have a rollback plan ready before proceeding"]},o={"clear all rules":["\u{1F6A8} This will remove ALL firewall protection","\u{1F6A8} Your application will be unprotected until new rules are added"],"delete ruleset":["\u{1F6A8} This action is IRREVERSIBLE","\u{1F6A8} All rules in the ruleset will be permanently deleted"]};r.info(`
14
- \u26A0\uFE0F Warnings:`),n[e]?.forEach(s=>{r.warn(` ${s}`)}),o[t]?.forEach(s=>{r.warn(` ${s}`)})}static getConfirmationMessage(e,t){let n=`Do you want to proceed with ${t}?`;switch(e){case"high":return`${n} (Type 'yes' to confirm high-risk operation)`;case"medium":return`${n} (Proceed with caution)`;default:return n}}static validateConfigurationStructure(e,t){if(!e){t.push("Configuration is null or undefined");return}e.version||t.push("Configuration missing version field"),e.provider||t.push("Configuration missing provider field"),Array.isArray(e.rules)||t.push("Configuration rules field is not an array"),e.ips&&!Array.isArray(e.ips)&&t.push("Configuration ips field is not an array")}static validateChanges(e,t){let n=(e.rulesToDelete?.length||0)+(e.ipsToDelete?.length||0),o=(e.rulesToAdd?.length||0)+(e.rulesToUpdate?.length||0)+(e.ipsToAdd?.length||0)+(e.ipsToUpdate?.length||0)+n;if(n>0&&o>0){let s=n/o;s>.5&&t.push(`High deletion ratio detected: ${Math.round(s*100)}% of changes are deletions`)}o>100&&t.push(`Large number of changes detected: ${o} total changes`)}static checkForPotentialIssues(e,t,n){t.rulesToDelete?.length===e.rules.length&&e.rules.length>0&&n.push("All existing rules will be deleted - this removes all firewall protection");let o=e.rules.filter(l=>l.action.type==="deny"&&l.conditions.some(c=>c.field==="path"&&(c.value==="/"||c.value==="*")));o.length>0&&n.push(`Found ${o.length} rules that may block all traffic`),e.ips&&e.ips.length>1e3&&n.push(`Large IP list detected: ${e.ips.length} IPs may impact performance`);let s=e.rules.map(l=>l.name),a=s.filter((l,c)=>s.indexOf(l)!==c);a.length>0&&n.push(`Duplicate rule names found: ${[...new Set(a)].join(", ")}`)}}});import{config as Da}from"dotenv";import Oa from"yargs";var Gt={};J(Gt,{builder:()=>Do,command:()=>Lo,desc:()=>xo,generateRuleId:()=>zt,handler:()=>qo});P();import V from"chalk";import{LogLevels as Uo}from"consola";import{z as I}from"zod";var Rt=I.number().int().positive().optional(),to=I.string().ip().or(I.string().regex(/^(?:\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/)),vi=I.string().optional(),io=I.enum(["eq","pre","suf","inc","sub","re","ex","nex"]),no=I.enum(["host","path","method","header","query","cookie","target_path","ip_address","region","protocol","scheme","environment","user_agent","geo_continent","geo_country","geo_country_region","geo_city","geo_as_number","ja4_digest","ja3_digest","rate_limit_api_id"]),oo=I.object({op:io,neg:I.boolean().optional(),type:no,value:I.union([I.string(),I.array(I.string()),I.array(I.number())]).optional(),key:I.string().optional()}).refine(i=>{switch(i.type){case"ip_address":case"method":case"environment":case"protocol":return i.op==="eq"||i.op==="inc";default:return!0}},"Invalid operator for the given condition type").refine(i=>{let e=["header","cookie"];return!(e.includes(i.type)&&!i.key||e.includes(i.type)&&i.op!=="ex"&&i.op!=="nex"&&!i.value)},"Missing key from condition").refine(i=>!(["ex","nex"].includes(i.op)&&i.neg),"Negation is not supported for the given operator").refine(i=>!(i.op==="inc"&&!Array.isArray(i.value)),"When using `inc` operator, value must be an array"),xt=I.object({conditions:I.array(oo).min(1,"Condition group must have at least one condition")}),ro=I.string().regex(/^\d+[smhd]$|^permanent$/),Dt=I.object({requests:I.number().positive().int().refine(i=>i>0,"requests must be positive"),window:I.string().regex(/^\d+[smhd]$/).refine(i=>parseInt(i)>0,"window duration must be positive")}),so=I.object({location:I.string(),permanent:I.boolean().optional()}),ao=I.enum(["log","deny","challenge","bypass","rate_limit","redirect"]),lo=I.object({action:ao,rateLimit:Dt.nullable().optional(),redirect:so.nullable().optional(),actionDuration:ro.nullable().optional()}),co=I.object({mitigate:lo}),Ot=I.object({id:vi,name:I.string(),description:I.string().optional(),conditionGroup:I.array(xt),action:co,active:I.boolean()});var Ft=I.object({id:vi,ip:to,hostname:I.string().optional(),notes:I.string().optional(),action:I.literal("deny")}),uo=I.object({projectId:I.string().optional(),teamId:I.string().optional()}),De=uo.extend({rules:I.array(Ot),ips:I.array(Ft).optional(),version:I.number().optional(),updatedAt:I.string().optional()});Ee();P();import{existsSync as qt,mkdirSync as Io,readFileSync as Eo,renameSync as Ao,unlinkSync as To,writeFileSync as $o}from"fs";import{randomBytes as Po}from"crypto";import{dirname as _o}from"path";import{isIPv6 as wo}from"net";import Ro from"ajv";var Ii="https://doorman.griffen.codes/schema.json",Ei={$schema:"http://json-schema.org/draft-07/schema#",title:"Doorman Config",description:"Schema for doorman project configuration files",$ref:"#/definitions/FirewallConfig",definitions:{FirewallConfig:{type:"object",properties:{projectId:{type:"string"},teamId:{type:"string"},$schema:{type:"string"},version:{type:"number"},firewallEnabled:{type:"boolean"},rules:{type:"array",items:{$ref:"#/definitions/CustomRule"}},ips:{type:"array",items:{$ref:"#/definitions/IPBlockingRule"}},updatedAt:{type:"string"}},required:["rules"],additionalProperties:!1,description:"The main configuration type for Doorman"},CustomRule:{type:"object",properties:{id:{type:"string"},name:{type:"string"},description:{type:"string"},conditionGroup:{type:"array",items:{$ref:"#/definitions/ConditionGroup"}},action:{$ref:"#/definitions/RuleAction"},active:{type:"boolean"}},required:["name","conditionGroup","action","active"],additionalProperties:!1,description:"Rule Types"},ConditionGroup:{type:"object",properties:{conditions:{type:"array",items:{$ref:"#/definitions/RuleCondition"}}},required:["conditions"],additionalProperties:!1},RuleCondition:{type:"object",properties:{op:{$ref:"#/definitions/RuleOperator"},neg:{type:"boolean"},type:{$ref:"#/definitions/RuleType"},key:{type:"string"},value:{anyOf:[{type:"string"},{type:"number"},{type:"array",items:{type:"string"}},{type:"array",items:{type:"number"}}]}},required:["op","type"],additionalProperties:!1,description:"Rule Condition Types"},RuleOperator:{type:"string",enum:["eq","pre","suf","inc","sub","re","ex","nex"]},RuleType:{type:"string",enum:["host","path","method","header","query","cookie","target_path","ip_address","region","protocol","scheme","environment","user_agent","geo_continent","geo_country","geo_country_region","geo_city","geo_as_number","ja4_digest","ja3_digest","rate_limit_api_id"]},RuleAction:{type:"object",properties:{mitigate:{$ref:"#/definitions/MitigationAction"}},required:["mitigate"],additionalProperties:!1},MitigationAction:{type:"object",properties:{action:{$ref:"#/definitions/ActionType"},rateLimit:{anyOf:[{$ref:"#/definitions/RateLimit"},{type:"null"}]},redirect:{anyOf:[{$ref:"#/definitions/Redirect"},{type:"null"}]},actionDuration:{type:["string","null"]}},required:["action"],additionalProperties:!1},ActionType:{type:"string",enum:["log","deny","challenge","bypass","rate_limit","redirect"],description:"Core Types"},RateLimit:{type:"object",properties:{requests:{type:"number"},window:{type:"string"}},required:["requests","window"],additionalProperties:!1,description:"Action Types"},Redirect:{type:"object",properties:{location:{type:"string"},permanent:{type:"boolean"}},required:["location"],additionalProperties:!1},IPBlockingRule:{type:"object",properties:{id:{type:"string"},ip:{type:"string"},hostname:{type:"string"},notes:{type:"string"},action:{type:"string",const:"deny"}},required:["ip","action"],additionalProperties:!1}}};import{z as _}from"zod";import{z as T}from"zod";var M=["vercel","cloudflare","fastly","gcp"];var Nt=T.string().optional(),Ae=T.string().ip().or(T.string().regex(/^(?:\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/)),Ct=T.string().datetime().or(T.string().optional()),Ai=T.enum(["log","deny","challenge","bypass","rate_limit","redirect","allow","block"]),Ti=T.enum(["eq","ne","contains","not_contains","starts_with","ends_with","matches","in","not_in","gt","ge","lt","le","exists","not_exists"]),$i=T.enum(["ip","country","region","city","asn","path","host","method","header","query","cookie","user_agent","referer","scheme","port"]),Za=T.string().regex(/^\d+[smhd]$|^permanent$/),Pi=T.object({requests:T.number().positive().int(),window:T.string().regex(/^\d+[smhd]$/),characteristics:T.array(T.string()).optional()}),_i=T.object({location:T.string().refine(i=>i.startsWith("/")||T.string().url().safeParse(i).success,'location must be an absolute URL or a path starting with "/"'),statusCode:T.number().int().min(300).max(399).optional(),permanent:T.boolean().optional(),preserveQueryString:T.boolean().optional()}),Vt=T.object({version:T.number().int().positive().optional(),updatedAt:Ct,createdAt:Ct,lastSyncedAt:Ct,migratedFrom:T.string().optional(),migratedAt:Ct}),fo=T.enum(M),ki=T.object({vercel:T.object({projectId:T.string().optional(),teamId:T.string().optional()}).optional(),cloudflare:T.object({zoneId:T.string().optional(),accountId:T.string().optional()}).optional(),fastly:T.object({workspaceId:T.string().optional()}).optional(),gcp:T.object({projectId:T.string().optional(),policyName:T.string().optional()}).optional()}),Si=T.object({$schema:T.string().url().optional(),version:T.string().optional(),provider:fo.optional(),metadata:Vt.optional()});var go=_.object({field:$i.or(_.string()),operator:Ti,value:_.union([_.string(),_.number(),_.array(_.string()),_.array(_.number())]).optional(),negated:_.boolean().optional(),key:_.string().optional(),group:_.number().optional()}).refine(i=>i.operator==="exists"||i.operator==="not_exists"||i.value!==void 0,{message:'value is required unless operator is "exists" or "not_exists"',path:["value"]}),mo=_.object({type:Ai,rateLimit:Pi.optional(),redirect:_i.optional(),response:_.object({statusCode:_.number().int().min(100).max(599).optional(),content:_.string().optional(),contentType:_.string().optional()}).optional(),duration:_.string().optional()}),ho=_.object({id:Nt,name:_.string().min(1,"Rule name is required"),description:_.string().optional(),enabled:_.boolean(),conditions:_.array(go).min(1,"At least one condition is required"),conditionLogic:_.enum(["AND","OR"]).optional().default("AND"),action:mo,priority:_.number().int().optional().describe("Rule evaluation order. Lower numbers are evaluated first. Rules without a priority keep their config order and run after all prioritised rules. Fully honoured on Cloudflare; best-effort on Vercel, which cannot reposition rules that already exist remotely."),categories:_.array(_.string()).optional()}),yo=_.object({id:Nt,ip:Ae,hostname:_.string().optional(),notes:_.string().optional(),action:_.enum(["deny","allow"])}),_e=Si.extend({providers:ki.optional(),rules:_.array(ho),ips:_.array(yo).optional(),metadata:Vt.optional()}),Ui=i=>{let e=_e.safeParse(i);if(!e.success)throw new Error(`Invalid unified configuration: ${e.error.message}`);if(e.data.provider&&!e.data.providers?.[e.data.provider])throw new Error(`Provider '${e.data.provider}' specified but no configuration found in providers section`);return e.data};function ke(i){if(!i||typeof i!="object")return!1;let e=i;return typeof e.provider=="string"||typeof e.providers=="object"&&e.providers!==null}import de from"chalk";var Se=class{static formatValidationError(e){let t=e.instancePath||"/",n=de.cyan(t);switch(e.keyword){case"type":return`${n}: Expected ${de.green(e.schema)}, got ${de.red(typeof e.data)}`;case"enum":{let o=e.params.allowedValues;return`${n}: Value must be one of: ${de.green(o.join(", "))}`}case"required":{let o=e.params;return`${n}: Missing required property ${de.yellow(o.missingProperty)}`}case"additionalProperties":{let o=e.params;return`${n}: Unknown property ${de.red(o.additionalProperty)}`}default:return`${n}: ${e.message}`}}static formatCustomError(e,t){return`${de.cyan(e)}: ${t}`}static wrapErrorBlock(e){let t=de.red.bold("Validation Errors:"),n=e.map(o=>` ${o}`).join(`
14
+ \u26A0\uFE0F Warnings:`),n[e]?.forEach(s=>{r.warn(` ${s}`)}),o[t]?.forEach(s=>{r.warn(` ${s}`)})}static getConfirmationMessage(e,t){let n=`Do you want to proceed with ${t}?`;switch(e){case"high":return`${n} (Type 'yes' to confirm high-risk operation)`;case"medium":return`${n} (Proceed with caution)`;default:return n}}static validateConfigurationStructure(e,t){if(!e){t.push("Configuration is null or undefined");return}e.version||t.push("Configuration missing version field"),e.provider||t.push("Configuration missing provider field"),Array.isArray(e.rules)||t.push("Configuration rules field is not an array"),e.ips&&!Array.isArray(e.ips)&&t.push("Configuration ips field is not an array")}static validateChanges(e,t){let n=(e.rulesToDelete?.length||0)+(e.ipsToDelete?.length||0),o=(e.rulesToAdd?.length||0)+(e.rulesToUpdate?.length||0)+(e.ipsToAdd?.length||0)+(e.ipsToUpdate?.length||0)+n;if(n>0&&o>0){let s=n/o;s>.5&&t.push(`High deletion ratio detected: ${Math.round(s*100)}% of changes are deletions`)}o>100&&t.push(`Large number of changes detected: ${o} total changes`)}static checkForPotentialIssues(e,t,n){t.rulesToDelete?.length===e.rules.length&&e.rules.length>0&&n.push("All existing rules will be deleted - this removes all firewall protection");let o=e.rules.filter(l=>l.action.type==="deny"&&l.conditions.some(c=>c.field==="path"&&(c.value==="/"||c.value==="*")));o.length>0&&n.push(`Found ${o.length} rules that may block all traffic`),e.ips&&e.ips.length>1e3&&n.push(`Large IP list detected: ${e.ips.length} IPs may impact performance`);let s=e.rules.map(l=>l.name),a=s.filter((l,c)=>s.indexOf(l)!==c);a.length>0&&n.push(`Duplicate rule names found: ${[...new Set(a)].join(", ")}`)}}});import{config as Oa}from"dotenv";import Fa from"yargs";var Gt={};J(Gt,{builder:()=>Do,command:()=>Lo,desc:()=>xo,generateRuleId:()=>zt,handler:()=>qo});P();import V from"chalk";import{LogLevels as Uo}from"consola";import{z as I}from"zod";var Rt=I.number().int().positive().optional(),to=I.string().ip().or(I.string().regex(/^(?:\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/)),vi=I.string().optional(),io=I.enum(["eq","pre","suf","inc","sub","re","ex","nex"]),no=I.enum(["host","path","method","header","query","cookie","target_path","ip_address","region","protocol","scheme","environment","user_agent","geo_continent","geo_country","geo_country_region","geo_city","geo_as_number","ja4_digest","ja3_digest","rate_limit_api_id"]),oo=I.object({op:io,neg:I.boolean().optional(),type:no,value:I.union([I.string(),I.array(I.string()),I.array(I.number())]).optional(),key:I.string().optional()}).refine(i=>{switch(i.type){case"ip_address":case"method":case"environment":case"protocol":return i.op==="eq"||i.op==="inc";default:return!0}},"Invalid operator for the given condition type").refine(i=>{let e=["header","cookie"];return!(e.includes(i.type)&&!i.key||e.includes(i.type)&&i.op!=="ex"&&i.op!=="nex"&&!i.value)},"Missing key from condition").refine(i=>!(["ex","nex"].includes(i.op)&&i.neg),"Negation is not supported for the given operator").refine(i=>!(i.op==="inc"&&!Array.isArray(i.value)),"When using `inc` operator, value must be an array"),xt=I.object({conditions:I.array(oo).min(1,"Condition group must have at least one condition")}),ro=I.string().regex(/^\d+[smhd]$|^permanent$/),Dt=I.object({requests:I.number().positive().int().refine(i=>i>0,"requests must be positive"),window:I.string().regex(/^\d+[smhd]$/).refine(i=>parseInt(i)>0,"window duration must be positive")}),so=I.object({location:I.string(),permanent:I.boolean().optional()}),ao=I.enum(["log","deny","challenge","bypass","rate_limit","redirect"]),lo=I.object({action:ao,rateLimit:Dt.nullable().optional(),redirect:so.nullable().optional(),actionDuration:ro.nullable().optional()}),co=I.object({mitigate:lo}),Ot=I.object({id:vi,name:I.string(),description:I.string().optional(),conditionGroup:I.array(xt),action:co,active:I.boolean()});var Ft=I.object({id:vi,ip:to,hostname:I.string().optional(),notes:I.string().optional(),action:I.literal("deny")}),uo=I.object({projectId:I.string().optional(),teamId:I.string().optional()}),De=uo.extend({rules:I.array(Ot),ips:I.array(Ft).optional(),version:I.number().optional(),updatedAt:I.string().optional()});Ee();P();import{existsSync as qt,mkdirSync as Io,readFileSync as Eo,renameSync as Ao,unlinkSync as To,writeFileSync as $o}from"fs";import{randomBytes as Po}from"crypto";import{dirname as _o}from"path";import{isIPv6 as wo}from"net";import Ro from"ajv";var Ii="https://doorman.griffen.codes/schema.json",Ei={$schema:"http://json-schema.org/draft-07/schema#",title:"Doorman Config",description:"Schema for doorman project configuration files",$ref:"#/definitions/FirewallConfig",definitions:{FirewallConfig:{type:"object",properties:{projectId:{type:"string"},teamId:{type:"string"},$schema:{type:"string"},version:{type:"number"},firewallEnabled:{type:"boolean"},rules:{type:"array",items:{$ref:"#/definitions/CustomRule"}},ips:{type:"array",items:{$ref:"#/definitions/IPBlockingRule"}},updatedAt:{type:"string"}},required:["rules"],additionalProperties:!1,description:"The main configuration type for Doorman"},CustomRule:{type:"object",properties:{id:{type:"string"},name:{type:"string"},description:{type:"string"},conditionGroup:{type:"array",items:{$ref:"#/definitions/ConditionGroup"}},action:{$ref:"#/definitions/RuleAction"},active:{type:"boolean"}},required:["name","conditionGroup","action","active"],additionalProperties:!1,description:"Rule Types"},ConditionGroup:{type:"object",properties:{conditions:{type:"array",items:{$ref:"#/definitions/RuleCondition"}}},required:["conditions"],additionalProperties:!1},RuleCondition:{type:"object",properties:{op:{$ref:"#/definitions/RuleOperator"},neg:{type:"boolean"},type:{$ref:"#/definitions/RuleType"},key:{type:"string"},value:{anyOf:[{type:"string"},{type:"number"},{type:"array",items:{type:"string"}},{type:"array",items:{type:"number"}}]}},required:["op","type"],additionalProperties:!1,description:"Rule Condition Types"},RuleOperator:{type:"string",enum:["eq","pre","suf","inc","sub","re","ex","nex"]},RuleType:{type:"string",enum:["host","path","method","header","query","cookie","target_path","ip_address","region","protocol","scheme","environment","user_agent","geo_continent","geo_country","geo_country_region","geo_city","geo_as_number","ja4_digest","ja3_digest","rate_limit_api_id"]},RuleAction:{type:"object",properties:{mitigate:{$ref:"#/definitions/MitigationAction"}},required:["mitigate"],additionalProperties:!1},MitigationAction:{type:"object",properties:{action:{$ref:"#/definitions/ActionType"},rateLimit:{anyOf:[{$ref:"#/definitions/RateLimit"},{type:"null"}]},redirect:{anyOf:[{$ref:"#/definitions/Redirect"},{type:"null"}]},actionDuration:{type:["string","null"]}},required:["action"],additionalProperties:!1},ActionType:{type:"string",enum:["log","deny","challenge","bypass","rate_limit","redirect"],description:"Core Types"},RateLimit:{type:"object",properties:{requests:{type:"number"},window:{type:"string"}},required:["requests","window"],additionalProperties:!1,description:"Action Types"},Redirect:{type:"object",properties:{location:{type:"string"},permanent:{type:"boolean"}},required:["location"],additionalProperties:!1},IPBlockingRule:{type:"object",properties:{id:{type:"string"},ip:{type:"string"},hostname:{type:"string"},notes:{type:"string"},action:{type:"string",const:"deny"}},required:["ip","action"],additionalProperties:!1}}};import{z as _}from"zod";import{z as T}from"zod";var M=["vercel","cloudflare","fastly","gcp"];var Nt=T.string().optional(),Ae=T.string().ip().or(T.string().regex(/^(?:\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/)),Ct=T.string().datetime().or(T.string().optional()),Ai=T.enum(["log","deny","challenge","bypass","rate_limit","redirect","allow","block"]),Ti=T.enum(["eq","ne","contains","not_contains","starts_with","ends_with","matches","in","not_in","gt","ge","lt","le","exists","not_exists"]),$i=T.enum(["ip","country","region","city","asn","path","host","method","header","query","cookie","user_agent","referer","scheme","port"]),Ya=T.string().regex(/^\d+[smhd]$|^permanent$/),Pi=T.object({requests:T.number().positive().int(),window:T.string().regex(/^\d+[smhd]$/),characteristics:T.array(T.string()).optional()}),_i=T.object({location:T.string().refine(i=>i.startsWith("/")||T.string().url().safeParse(i).success,'location must be an absolute URL or a path starting with "/"'),statusCode:T.number().int().min(300).max(399).optional(),permanent:T.boolean().optional(),preserveQueryString:T.boolean().optional()}),Vt=T.object({version:T.number().int().positive().optional(),updatedAt:Ct,createdAt:Ct,lastSyncedAt:Ct,migratedFrom:T.string().optional(),migratedAt:Ct}),fo=T.enum(M),ki=T.object({vercel:T.object({projectId:T.string().optional(),teamId:T.string().optional()}).optional(),cloudflare:T.object({zoneId:T.string().optional(),accountId:T.string().optional()}).optional(),fastly:T.object({workspaceId:T.string().optional()}).optional(),gcp:T.object({projectId:T.string().optional(),policyName:T.string().optional()}).optional()}),Si=T.object({$schema:T.string().url().optional(),version:T.string().optional(),provider:fo.optional(),metadata:Vt.optional()});var go=_.object({field:$i.or(_.string()),operator:Ti,value:_.union([_.string(),_.number(),_.array(_.string()),_.array(_.number())]).optional(),negated:_.boolean().optional(),key:_.string().optional(),group:_.number().optional()}).refine(i=>i.operator==="exists"||i.operator==="not_exists"||i.value!==void 0,{message:'value is required unless operator is "exists" or "not_exists"',path:["value"]}),mo=_.object({type:Ai,rateLimit:Pi.optional(),redirect:_i.optional(),response:_.object({statusCode:_.number().int().min(100).max(599).optional(),content:_.string().optional(),contentType:_.string().optional()}).optional(),duration:_.string().optional()}),ho=_.object({id:Nt,name:_.string().min(1,"Rule name is required"),description:_.string().optional(),enabled:_.boolean(),conditions:_.array(go).min(1,"At least one condition is required"),conditionLogic:_.enum(["AND","OR"]).optional().default("AND"),action:mo,priority:_.number().int().optional().describe("Rule evaluation order. Lower numbers are evaluated first. Rules without a priority keep their config order and run after all prioritised rules. Fully honoured on Cloudflare; best-effort on Vercel, which cannot reposition rules that already exist remotely."),categories:_.array(_.string()).optional()}),yo=_.object({id:Nt,ip:Ae,hostname:_.string().optional(),notes:_.string().optional(),action:_.enum(["deny","allow"])}),_e=Si.extend({providers:ki.optional(),rules:_.array(ho),ips:_.array(yo).optional(),metadata:Vt.optional()}),Ui=i=>{let e=_e.safeParse(i);if(!e.success)throw new Error(`Invalid unified configuration: ${e.error.message}`);if(e.data.provider&&!e.data.providers?.[e.data.provider])throw new Error(`Provider '${e.data.provider}' specified but no configuration found in providers section`);return e.data};function ke(i){if(!i||typeof i!="object")return!1;let e=i;return typeof e.provider=="string"||typeof e.providers=="object"&&e.providers!==null}import de from"chalk";var Se=class{static formatValidationError(e){let t=e.instancePath||"/",n=de.cyan(t);switch(e.keyword){case"type":return`${n}: Expected ${de.green(e.schema)}, got ${de.red(typeof e.data)}`;case"enum":{let o=e.params.allowedValues;return`${n}: Value must be one of: ${de.green(o.join(", "))}`}case"required":{let o=e.params;return`${n}: Missing required property ${de.yellow(o.missingProperty)}`}case"additionalProperties":{let o=e.params;return`${n}: Unknown property ${de.red(o.additionalProperty)}`}default:return`${n}: ${e.message}`}}static formatCustomError(e,t){return`${de.cyan(e)}: ${t}`}static wrapErrorBlock(e){let t=de.red.bold("Validation Errors:"),n=e.map(o=>` ${o}`).join(`
15
15
  `);return`${t}
16
16
  ${n}`}static formatDetailedRuleValidation(e,t){let n=de.cyan(`Rule: ${e}`),o=t.map(s=>{let a=s.passed?de.green("\u2713"):de.red("\u2717"),l=s.passed?de.green(s.name):de.red(s.name);return` ${a} ${l}`}).join(`
17
17
  `);return`${n}
@@ -19,8 +19,8 @@ ${o}`}};var N=class i extends Error{constructor(t,n,o,s=[]){super(t);this.ajvErr
19
19
  `+(c instanceof Error?c.message:String(c)),null)}let t=this.ajv.compile(this.schema),n=t(e),o=t.errors,s=De.safeParse(e),a;s.success||(a=s.error);let l=[];if(n&&s.success){try{this.validateRuleNames(e),this.validateRuleConditionGroup(e),this.validateRuleAction(e)}catch(c){if(c instanceof N)l.push(c.message,...c.customErrors||[]);else throw c}for(let c of e.rules){if(c.action.mitigate?.rateLimit){let d=Dt.safeParse(c.action.mitigate.rateLimit);d.success||l.push(...d.error.errors.map(u=>u.message))}if(c.conditionGroup)for(let d of c.conditionGroup){let u=xt.safeParse(d);u.success||l.push(...u.error.errors.map(p=>p.message))}}}if(!n||!s.success||l.length>0)throw new N(`Invalid firewall configuration:
20
20
  `+N.formatErrors(o,a,l),o,a,l)}validateRuleNames(e){let t=new Set;for(let n of e.rules){if(t.has(n.name))throw new N(`Duplicate rule name found: "${n.name}"`,null);t.add(n.name)}}validateRuleConditionGroup(e){for(let t of e.rules)if(t.conditionGroup)for(let n of t.conditionGroup){if(!n.conditions||n.conditions.length===0)throw new N(`Rule "${t.name}" has an empty condition group`,null);for(let o of n.conditions){if(!o.type||!o.op)throw new N(`Rule "${t.name}" has an invalid condition`,null);let s=o.type==="header"||o.type==="cookie",a=o.op==="ex"||o.op==="nex";if(s){if(!o.key||typeof o.key!="string")throw new N(`Rule "${t.name}" has an invalid condition`,null);if(!a&&(o.value===void 0||o.value===null))throw new N(`Rule "${t.name}" has an invalid condition`,null)}else if(o.value===void 0||o.value===null)throw new N(`Rule "${t.name}" has an invalid condition`,null);let l=Array.isArray(o.value)?o.value:o.value!==void 0?[o.value]:[];if(Array.isArray(o.value)&&l.length===0)throw new N(`Rule "${t.name}" has an invalid value in condition`,null);if(o.type==="ip_address"){for(let c of l)if(typeof c!="string"||!this.isValidIP(c))throw new N(`Rule "${t.name}" has an invalid IP address in condition`,null)}if(o.type==="geo_as_number")for(let c of l){let d=typeof c=="number"?c:parseInt(String(c),10);if(!this.isValidASN(String(d)))throw new N(`Rule "${t.name}" has an invalid ASN in condition`,null)}if(o.type==="path"&&(o.op==="eq"||o.op==="pre")){for(let c of l)if(typeof c!="string"||!this.isValidPath(c))throw new N(`Rule "${t.name}" has an invalid path in condition`,null)}}}else throw new N("Either conditionGroup or type+values must be provided",null)}validateRuleAction(e){for(let t of e.rules)if(typeof t.action=="object"&&"mitigate"in t.action){let n=t.action.mitigate;if(n?.rateLimit){if(n.rateLimit.requests<=0)throw new N("Invalid rate limit configuration: requests must be positive",null);if(!n.rateLimit.window.match(/^\d+[smhd]$/))throw new N("Invalid rate limit configuration: invalid window format",null);if(parseInt(n.rateLimit.window)<=0)throw new N("Invalid rate limit configuration: window duration must be positive",null)}if(n?.actionDuration&&!n.actionDuration.match(/^\d+[smhd]$|^permanent$/))throw new N("Invalid action duration format: "+n.actionDuration,null);if(n?.redirect&&!n.redirect.location)throw new N("Invalid redirect configuration: location is required",null)}}isValidIP(e){let t=e.split("/"),n=t[0]??"",o=t[1],s=d=>{let u=d.split(".");return u.length!==4?!1:u.every(p=>{if(!/^\d{1,3}$/.test(p))return!1;let f=parseInt(p,10);return f>=0&&f<=255})},a=d=>wo(d),l=s(n),c=a(n);if(!l&&!c)return!1;if(o!==void 0){let d=parseInt(o,10);if(Number.isNaN(d)||l&&(d<0||d>32)||c&&(d<0||d>128))return!1}return!0}isValidASN(e){let t=parseInt(e,10);return!isNaN(t)&&t>=1&&t<=4294967295}isValidPath(e){return e.startsWith("/")&&!e.includes(" ")}};import{existsSync as Co}from"fs";import{dirname as bo,resolve as Li}from"path";var Bt=[".doorman.json","vercel-firewall.config.json"],vo=Bt[0],Ne=class{static async findConfig(e){let{findUp:t}=await import("find-up");for(let n of Bt){let o=await t(n,{cwd:e||process.cwd()});if(o)return o}}static async findProjectRoot(e){let t=await this.findConfig(e);return t?bo(t):null}static configExists(e){return Co(Li(e))}static getDefaultConfigPath(){return Li(process.cwd(),vo)}static getSupportedFileNames(){return Bt}};async function j(i,e="required"){let t=ko(e),n=i||await Ne.findConfig();if(!n||!qt(n)){if(t==="optional")return r.debug("No config file found, returning empty config"),{};let s=Ne.getDefaultConfigPath();throw new Error(`No config file found. Run \`doorman init\` to create one at ${s}, or use --config to specify a custom path.`)}let o;try{let s=Eo(n,"utf8");o=JSON.parse(s)}catch(s){throw s instanceof SyntaxError?new Error(`Invalid JSON in config file (${n}): ${s.message}`):s}if(t!=="raw")try{Ue.getInstance().validateConfig(o)}catch(s){if(t==="lenient")r.warn("Config validation failed:",s);else throw s}return o}async function z(i,e,t={}){let{validate:n=!0,throwOnError:o=!0}=t,s=e||await Ne.findConfig()||Ne.getDefaultConfigPath();if(n)try{Ue.getInstance().validateConfig(i)}catch(c){if(r.error("Config validation failed:",c),o)throw c}let a=_o(s);qt(a)||Io(a,{recursive:!0});let l=`${s}.${process.pid}.${Po(4).toString("hex")}.tmp`;try{$o(l,JSON.stringify(i,null,2)),Ao(l,s)}catch(c){try{qt(l)&&To(l)}catch{}throw c}r.debug(`Config saved to ${s}`)}function ko(i){if(typeof i=="string")return i;let{validate:e=!0,throwOnError:t=!0}=i;return e?t?"required":"lenient":"raw"}P();import xi from"chalk";import{ZodError as So}from"zod";function Q(i,e){i instanceof SyntaxError?r.log(Se.wrapErrorBlock(["Invalid JSON format in config file:",` ${i.message}`])):i instanceof So?(r.error(xi.red("Schema validation failed:")),i.errors.forEach(t=>{let n=t.path.join(".");r.error(xi.red(` - ${n}: ${t.message}`))})):i instanceof Error&&i.name==="ValidationError"?r.error(i):i instanceof Error&&i.message.includes("Forbidden")?r.log(Se.wrapErrorBlock([`Error ${e}:`," Access denied. Check that your token has the correct scope."," If using a team, ensure the token is scoped to that team."])):r.error(Se.wrapErrorBlock([`Error ${e}:`,` ${i instanceof Error?i.message:String(i)}`])),process.exit(1)}var Lo="add [type]",xo="Add a new firewall rule to your configuration",Do={type:{type:"string",description:'Rule type: "rule" (default) or "ip"',default:"rule",choices:["rule","ip"]},interactive:{alias:"i",type:"boolean",description:"Guided prompts for rule creation",default:!1},name:{alias:"n",type:"string",description:"Rule name"},description:{type:"string",description:"Rule description"},field:{type:"string",description:"Condition field type (path, method, user_agent, etc.)"},op:{type:"string",description:"Operator (eq, pre, suf, sub, inc, re, ex, nex)"},value:{type:"string",description:"Match value (string or comma-separated for arrays)"},key:{type:"string",description:"Header/query/cookie key (required for header, cookie types)"},neg:{type:"boolean",description:"Negate the condition",default:!1},action:{alias:"a",type:"string",description:"Action type (deny, challenge, rate_limit, redirect, log, bypass)",default:"deny"},active:{type:"boolean",description:"Enable rule immediately",default:!0},requests:{type:"number",description:"Rate limit: max requests"},window:{type:"string",description:'Rate limit: time window (e.g., "60s", "5m")'},duration:{type:"string",description:'Action duration (e.g., "1h", "permanent")'},location:{type:"string",description:"Redirect URL"},permanent:{type:"boolean",description:"Redirect: use 301 instead of 302"},ip:{type:"string",description:"IP address or CIDR for IP blocking rules"},hostname:{type:"string",description:"Hostname for IP blocking rules",default:"*"},notes:{type:"string",description:"Notes for IP blocking rules"},config:{alias:"c",type:"string",description:"Config file path"},dryRun:{alias:"d",type:"boolean",description:"Show what would be added without writing",default:!1},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}},Wt=["host","path","method","header","query","cookie","target_path","ip_address","region","protocol","scheme","environment","user_agent","geo_continent","geo_country","geo_country_region","geo_city","geo_as_number","ja4_digest","ja3_digest","rate_limit_api_id"],Mt=["eq","pre","suf","inc","sub","re","ex","nex"],jt=["log","deny","challenge","bypass","rate_limit","redirect"];function zt(i){let e=i.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_|_$/g,"");if(!e)throw new Error("Cannot generate rule ID: name must contain at least one alphanumeric character");return"rule_"+e}function Fi(i,e){return e==="inc"?i.split(",").map(t=>t.trim()):i}async function Oo(){let i=await b("Rule name:",{type:"text"});if(!i.trim())throw new Error("Rule name cannot be empty");let e=await b("Description (optional):",{type:"text"}),t=[],n=!0;for(;n;){let u=[],p=!0;for(;p;){let f=await b("Condition type:",{type:"select",options:Wt,initial:"path"}),y=await b("Operator:",{type:"select",options:Mt,initial:"eq"}),w;(f==="header"||f==="cookie"||f==="query")&&(w=await b(`${f} key:`,{type:"text"}));let C;if(y!=="ex"&&y!=="nex"){let O=await b("Value:",{type:"text"});C=Fi(O,y)}let x=y!=="ex"&&y!=="nex"?await b("Negate this condition?",{type:"confirm",initial:!1}):!1,D={type:f,op:y,...C!==void 0&&{value:C},...w&&{key:w},...x&&{neg:x}};u.push(D),p=await b("Add another condition to this group? (AND logic)",{type:"confirm",initial:!1})}t.push({conditions:u}),n=await b("Add another condition group? (OR logic)",{type:"confirm",initial:!1})}let o=await b("Action:",{type:"select",options:jt,initial:"deny"}),s;if(o==="rate_limit"){let u=parseInt(await b("Max requests:",{type:"text",initial:"100"}),10),p=await b('Time window (e.g., "60s", "1m", "1h"):',{type:"text",initial:"60s"});s={requests:u,window:p}}let a;if(o==="redirect"){let u=await b("Redirect URL:",{type:"text"}),p=await b("Permanent redirect (301)?",{type:"confirm",initial:!1});a={location:u,...p&&{permanent:p}}}let l;(o==="deny"||o==="challenge")&&await b("Set action duration? (default: permanent)",{type:"confirm",initial:!1})&&(l=await b('Duration (e.g., "1h", "1d", "permanent"):',{type:"text",initial:"permanent"}));let c=await b("Enable rule immediately?",{type:"confirm",initial:!0});return{id:zt(i),name:i.trim(),...e.trim()&&{description:e.trim()},conditionGroup:t,action:{mitigate:{action:o,...s&&{rateLimit:s},...a&&{redirect:a},...l&&{actionDuration:l}}},active:c}}function Fo(i){if(!i.name)throw new Error("--name is required in inline mode. Use --interactive for guided prompts.");if(!i.field)throw new Error("--field is required in inline mode (e.g., --field path)");if(!i.op)throw new Error("--op is required in inline mode (e.g., --op eq)");let e=i.op;if(!Mt.includes(e))throw new Error(`Invalid operator "${i.op}". Valid operators: ${Mt.join(", ")}`);let t=i.field;if(!Wt.includes(t))throw new Error(`Invalid field type "${i.field}". Valid types: ${Wt.join(", ")}`);let n=i.action||"deny";if(!jt.includes(n))throw new Error(`Invalid action "${i.action}". Valid actions: ${jt.join(", ")}`);if(e!=="ex"&&e!=="nex"&&!i.value)throw new Error("--value is required for the given operator");if((t==="header"||t==="cookie"||t==="query")&&!i.key)throw new Error(`--key is required for "${t}" condition type`);if(n==="rate_limit"&&(!i.requests||!i.window))throw new Error("--requests and --window are required when action is rate_limit");if(n==="redirect"&&!i.location)throw new Error("--location is required when action is redirect");let o=i.value?Fi(i.value,e):void 0,s={type:t,op:e,...o!==void 0&&{value:o},...i.key&&{key:i.key},...i.neg&&{neg:!0}};return{id:zt(i.name),name:i.name.trim(),...i.description&&{description:i.description},conditionGroup:[{conditions:[s]}],action:{mitigate:{action:n,...n==="rate_limit"&&i.requests&&i.window&&{rateLimit:{requests:i.requests,window:i.window}},...n==="redirect"&&i.location&&{redirect:{location:i.location,...i.permanent&&{permanent:!0}}},...i.duration&&{actionDuration:i.duration}}},active:i.active!==!1}}async function No(){let i=await b("IP address or CIDR (e.g., 192.168.1.100/32):",{type:"text"});if(!i.trim())throw new Error("IP address cannot be empty");let e=await b("Hostname (use * for all):",{type:"text",initial:"*"}),t=await b("Notes (optional):",{type:"text"});return{ip:i.trim(),hostname:e.trim()||"*",...t.trim()&&{notes:t.trim()},action:"deny"}}function Vo(i){if(!i.ip)throw new Error("--ip is required for IP blocking rules");return{ip:i.ip.trim(),hostname:i.hostname||"*",...i.notes&&{notes:i.notes},action:"deny"}}function Bo(i,e){if(i.rules.map(o=>o.name.toLowerCase()).includes(e.name.toLowerCase()))return`A rule named "${e.name}" already exists`;let n=i.rules.map(o=>o.id).filter(Boolean);return e.id&&n.includes(e.id)?`A rule with ID "${e.id}" already exists`:null}function Di(i){r.log(""),r.log(V.bold(` Rule: ${i.name}`)),i.description&&r.log(V.dim(` Description: ${i.description}`)),r.log(V.dim(` ID: ${i.id}`));let e=i.conditionGroup.map(o=>o.conditions.map(s=>{let a=s.neg?"NOT ":"",l=s.key?`[${s.key}] `:"",c=Array.isArray(s.value)?s.value.join(", "):s.value||"";return`${a}${s.type} ${l}${s.op} "${c}"`}).join(" AND "));r.log(V.dim(` Conditions: ${e.join(" OR ")}`));let t=i.action.mitigate.action,n=[];i.action.mitigate.rateLimit&&n.push(`${i.action.mitigate.rateLimit.requests} req/${i.action.mitigate.rateLimit.window}`),i.action.mitigate.redirect&&n.push(`\u2192 ${i.action.mitigate.redirect.location}`),i.action.mitigate.actionDuration&&n.push(`duration: ${i.action.mitigate.actionDuration}`),r.log(V.dim(` Action: ${t}${n.length?` (${n.join(", ")})`:""}`)),r.log(V.dim(` Active: ${i.active?"\u2705":"\u274C"}`))}function Oi(i){r.log(""),r.log(V.bold(` IP Rule: ${i.ip}`)),r.log(V.dim(` Hostname: ${i.hostname}`)),i.notes&&r.log(V.dim(` Notes: ${i.notes}`)),r.log(V.dim(` Action: ${i.action}`))}var qo=async i=>{try{if(i.debug&&(r.level=Uo.debug),r.debug("Add command arguments:",i),(i.type||"rule")==="ip"){let t=i.interactive?await No():Vo(i),n=Ft.safeParse(t);if(n.success||(r.error(V.red("Rule validation failed:")),n.error.errors.forEach(a=>{let l=a.path.join(".");r.error(V.red(` - ${l||"ip"}: ${a.message}`))}),process.exit(1)),i.dryRun){r.info(V.cyan(`
21
21
  Dry run - The following IP rule would be added:`)),Oi(t),r.log(""),r.log(V.dim(JSON.stringify(t,null,2)));return}r.start("Loading configuration...");let o=await j(i.config,"raw"),s={...o,ips:[...o.ips||[],t]};await z(s,i.config),r.success(V.green(`\u2714 IP rule "${t.ip}" added to configuration`)),Oi(t)}else{let t=i.interactive?await Oo():Fo(i),n=Ot.safeParse(t);if(n.success||(r.error(V.red("Rule validation failed:")),n.error.errors.forEach(c=>{let d=c.path.join(".")||"rule";r.error(V.red(` - ${d}: ${c.message}`))}),process.exit(1)),i.dryRun){r.info(V.cyan(`
22
- Dry run - The following rule would be added:`)),Di(t),r.log(""),r.log(V.dim(JSON.stringify(t,null,2)));return}r.start("Loading configuration...");let o=await j(i.config,"raw"),s=o.rules||[],a=Bo({...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 b("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`)),Di(t)}r.log(""),r.log(V.dim(`Run ${V.cyan("doorman sync")} to deploy this rule.`))}catch(e){Q(e,"adding rule")}};var oi={};J(oi,{builder:()=>ms,command:()=>fs,desc:()=>gs,handler:()=>hs});P();import ie from"chalk";import{existsSync as St,mkdirSync as ls,readdirSync as cs,statSync as ds}from"fs";import{isAbsolute as us,join as ni}from"path";import{LogLevels as ps}from"consola";Ee();import{existsSync as Ni,readFileSync as Wo}from"fs";import{join as Vi}from"path";function Bi(i=process.cwd()){return Ni(Vi(i,".git"))}function qi(i,e=process.cwd()){let t=Vi(e,".gitignore");if(!Ni(t))return!1;let n=i.replace(/^\.\//,"").replace(/^\//,"").replace(/\/$/,""),o;try{o=Wo(t,"utf8").split(`
23
- `)}catch{return!1}return o.some(s=>{let a=s.trim();if(!a||a.startsWith("#"))return!1;let l=a.replace(/^\//,"").replace(/\/$/,"");return l===n||n.startsWith(`${l}/`)})}var X={type:"string",choices:M,description:"Firewall provider (auto-detected)"};P();import{LogLevels as as}from"consola";P();Ee();import es from"chalk";import{emitKeypressEvents as Mo}from"readline";function jo(i,e){return e?.ctrl&&e.name==="c"?{type:"cancel"}:e?.name==="return"||e?.name==="enter"?{type:"submit"}:e?.name==="backspace"?{type:"backspace"}:i&&!e?.ctrl&&!e?.meta&&i.length===1&&i>=" "?{type:"append",char:i}:{type:"ignore"}}function zo(i,e){switch(e.type){case"append":return{value:i+e.char,done:!1,cancelled:!1,echo:"add"};case"backspace":return i.length===0?{value:i,done:!1,cancelled:!1,echo:"none"}:{value:i.slice(0,-1),done:!1,cancelled:!1,echo:"remove"};case"submit":return{value:i,done:!0,cancelled:!1,echo:"none"};case"cancel":return{value:i,done:!0,cancelled:!0,echo:"none"};case"ignore":return{value:i,done:!1,cancelled:!1,echo:"none"}}}async function Wi(i){if(process.stdout.write(i),!process.stdin.isTTY){let{createInterface:e}=await import("readline"),t=e({input:process.stdin,output:process.stdout,terminal:!1});return new Promise(n=>{t.question("",o=>{t.close(),n(o)})})}return new Promise(e=>{let t=process.stdin,n=t.isRaw;Mo(t),t.setRawMode(!0),t.resume();let o="",s=()=>{t.removeListener("keypress",a),t.setRawMode(n??!1)},a=(l,c)=>{let d=jo(l,c),u=zo(o,d);o=u.value,u.echo==="add"?process.stdout.write("*"):u.echo==="remove"&&process.stdout.write("\b \b"),u.done&&(s(),process.stdout.write(`
22
+ Dry run - The following rule would be added:`)),Di(t),r.log(""),r.log(V.dim(JSON.stringify(t,null,2)));return}r.start("Loading configuration...");let o=await j(i.config,"raw"),s=o.rules||[],a=Bo({...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 b("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`)),Di(t)}r.log(""),r.log(V.dim(`Run ${V.cyan("doorman sync")} to deploy this rule.`))}catch(e){Q(e,"adding rule")}};var oi={};J(oi,{builder:()=>hs,command:()=>gs,desc:()=>ms,handler:()=>ys});P();import ie from"chalk";import{existsSync as St,mkdirSync as cs,readdirSync as ds,statSync as us}from"fs";import{isAbsolute as ps,join as ni}from"path";import{LogLevels as fs}from"consola";Ee();import{existsSync as Ni,readFileSync as Wo}from"fs";import{join as Vi}from"path";function Bi(i=process.cwd()){return Ni(Vi(i,".git"))}function qi(i,e=process.cwd()){let t=Vi(e,".gitignore");if(!Ni(t))return!1;let n=i.replace(/^\.\//,"").replace(/^\//,"").replace(/\/$/,""),o;try{o=Wo(t,"utf8").split(`
23
+ `)}catch{return!1}return o.some(s=>{let a=s.trim();if(!a||a.startsWith("#"))return!1;let l=a.replace(/^\//,"").replace(/\/$/,"");return l===n||n.startsWith(`${l}/`)})}var X={type:"string",choices:M,description:"Firewall provider (auto-detected)"};P();import{LogLevels as ls}from"consola";P();Ee();import ts from"chalk";import{emitKeypressEvents as Mo}from"readline";function jo(i,e){return e?.ctrl&&e.name==="c"?{type:"cancel"}:e?.name==="return"||e?.name==="enter"?{type:"submit"}:e?.name==="backspace"?{type:"backspace"}:i&&!e?.ctrl&&!e?.meta&&i.length===1&&i>=" "?{type:"append",char:i}:{type:"ignore"}}function zo(i,e){switch(e.type){case"append":return{value:i+e.char,done:!1,cancelled:!1,echo:"add"};case"backspace":return i.length===0?{value:i,done:!1,cancelled:!1,echo:"none"}:{value:i.slice(0,-1),done:!1,cancelled:!1,echo:"remove"};case"submit":return{value:i,done:!0,cancelled:!1,echo:"none"};case"cancel":return{value:i,done:!0,cancelled:!0,echo:"none"};case"ignore":return{value:i,done:!1,cancelled:!1,echo:"none"}}}async function Wi(i){if(process.stdout.write(i),!process.stdin.isTTY){let{createInterface:e}=await import("readline"),t=e({input:process.stdin,output:process.stdout,terminal:!1});return new Promise(n=>{t.question("",o=>{t.close(),n(o)})})}return new Promise(e=>{let t=process.stdin,n=t.isRaw;Mo(t),t.setRawMode(!0),t.resume();let o="",s=()=>{t.removeListener("keypress",a),t.setRawMode(n??!1)},a=(l,c)=>{let d=jo(l,c),u=zo(o,d);o=u.value,u.echo==="add"?process.stdout.write("*"):u.echo==="remove"&&process.stdout.write("\b \b"),u.done&&(s(),process.stdout.write(`
24
24
  `),u.cancelled&&process.exit(0),e(o))};t.on("keypress",a)})}P();var Mi={provider:"vercel",fields:[{key:"token",envVar:"VERCEL_TOKEN",label:"Vercel API Token",required:!0,secret:!0,promptMessage:"What is your Vercel API Auth Token? (https://vercel.com/guides/how-do-i-use-a-vercel-api-access-token#creating-an-access-token) "},{key:"projectId",envVar:"VERCEL_PROJECT_ID",label:"Vercel Project ID",required:!0,configKey:"projectId",cliAlias:"p",legacyConfigKey:"projectId",promptMessage:"What is your Vercel Project ID? (See https://vercel.com/docs/projects/project-configuration/general-settings#project-id)"},{key:"teamId",envVar:"VERCEL_TEAM_ID",label:"Vercel Team ID",required:!1,configKey:"teamId",cliAlias:"t",legacyConfigKey:"teamId",promptMessage:"What is your Vercel Team ID? Leave blank to use your Vercel default team (every account, including Hobby, is a team). Only needed if you belong to more than one team and want to target a non-default one. (See https://vercel.com/docs/accounts#find-your-team-id)"}]};var ji={provider:"cloudflare",fields:[{key:"apiToken",envVar:"CLOUDFLARE_API_TOKEN",label:"Cloudflare API Token",required:!0,secret:!0},{key:"zoneId",envVar:"CLOUDFLARE_ZONE_ID",label:"Cloudflare Zone ID",required:!0,configKey:"zoneId"},{key:"accountId",envVar:"CLOUDFLARE_ACCOUNT_ID",label:"Cloudflare Account ID",required:!1,configKey:"accountId"}]};var zi={provider:"fastly",fields:[{key:"apiToken",envVar:"FASTLY_API_TOKEN",label:"Fastly API Token",required:!0,secret:!0},{key:"workspaceId",envVar:"FASTLY_WORKSPACE_ID",label:"Fastly Next-Gen WAF Workspace ID",required:!0,configKey:"workspaceId"}]};var Gi={provider:"gcp",fields:[{key:"serviceAccountKeyPath",envVar:"GOOGLE_APPLICATION_CREDENTIALS",label:"GCP Service Account Key Path",required:!1,promptMessage:"Path to a GCP service account JSON key file (leave blank to use Application Default Credentials \u2014 gcloud auth application-default login, or the GCE/Cloud Run metadata server): "},{key:"projectId",envVar:"GOOGLE_CLOUD_PROJECT",label:"GCP Project ID",required:!0,configKey:"projectId"},{key:"policyName",envVar:"GCP_POLICY_NAME",label:"Cloud Armor Security Policy Name",required:!0,configKey:"policyName"}]};function Ki(i,e={}){let{explicit:t={},config:n={},env:o=process.env}=e,s={};for(let a of i.fields)s[a.key]=t[a.key]||n[a.key]||o[a.envVar];return s}function Hi(i,e){return i.fields.filter(t=>t.required&&!e[t.key])}var We={vercel:Mi,cloudflare:ji,fastly:zi,gcp:Gi};var bt=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 M){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 M){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 M.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 M){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 M){let o=this.detectionField(n);o&&process.env[o.envVar]&&t.add(n)}return Array.from(t)}};P();Ee();import Zi from"chalk";var vt=i=>({$schema:Ii,firewallEnabled:!0,rules:[],ips:[],...i});P();var Te=class{baseUrl;providerName;rateLimitInfo={};constructor(e,t){this.baseUrl=e,this.providerName=t}async makeRequest(e,t={}){let n=`${this.baseUrl}${e}`,{timeout:o=3e4,retries:s=3,retryDelay:a=1e3,...l}=t,c=null,d=null,u=null,p=()=>{u&&(clearTimeout(u),u=null),d&&(d.abort(),d=null)};try{for(let f=0;f<=s;f++)try{d=new AbortController,u=setTimeout(()=>{d&&d.abort()},o);let y=await fetch(n,{...l,headers:{"Content-Type":"application/json",...await this.getAuthHeaders(),...l.headers},signal:d.signal});if(u&&(clearTimeout(u),u=null),this.updateRateLimitInfo(y),y.status===429){let C=this.calculateRateLimitWait(f,y);if(f===s)throw new Error(`${this.providerName} API rate limit exceeded after ${s+1} attempt(s) (429 Too Many Requests). Retry after ~${Math.round(C/1e3)}s.`);r.warn(`Rate limit exceeded for ${this.providerName}. Waiting ${C}ms before retry (attempt ${f+1}/${s+1})...`),await this.delay(C);continue}if(!y.ok)throw await this.handleErrorResponse(y);let w;try{let C=await y.text();C.trim()?w=JSON.parse(C):w={}}catch(C){throw r.error(`Failed to parse response from ${n}:`,C),new Error(`Invalid JSON response from ${this.providerName} API: ${C instanceof Error?C.message:String(C)}`)}return this.isLargeResponse(w)&&(r.debug(`Large response detected from ${n}, validating structure...`),this.validateLargeResponse(w)),w}catch(y){if(c=y,u&&(clearTimeout(u),u=null),this.isNonRetryableError(y)||f===s)throw y;let w=a*Math.pow(2,f),C=Math.random()*.1*w,x=Math.min(w+C,3e4);r.debug(`Request failed (attempt ${f+1}/${s+1}). Retrying in ${Math.round(x)}ms...`),r.debug(`Error details: ${y instanceof Error?y.message:String(y)}`),await this.delay(x)}throw c||new Error("Request failed after all retries")}finally{p()}}async get(e,t){return this.makeRequest(e,{...t,method:"GET"})}async post(e,t,n){return this.makeRequest(e,{...n,method:"POST",body:t?JSON.stringify(t):void 0})}async put(e,t,n){return this.makeRequest(e,{...n,method:"PUT",body:t?JSON.stringify(t):void 0})}async patch(e,t,n){return this.makeRequest(e,{...n,method:"PATCH",body:t?JSON.stringify(t):void 0})}async delete(e,t){return this.makeRequest(e,{...t,method:"DELETE"})}updateRateLimitInfo(e){let t=e.headers.get("X-RateLimit-Limit"),n=e.headers.get("X-RateLimit-Remaining"),o=e.headers.get("X-RateLimit-Reset");t&&(this.rateLimitInfo.limit=parseInt(t,10)),n&&(this.rateLimitInfo.remaining=parseInt(n,10)),o&&(this.rateLimitInfo.reset=parseInt(o,10)),this.rateLimitInfo.remaining&&this.rateLimitInfo.remaining<10&&r.warn(`Approaching rate limit for ${this.providerName}: ${this.rateLimitInfo.remaining} requests remaining`)}calculateRateLimitWait(e=0,t){let o=t?this.parseRetryAfter(t.headers.get("Retry-After")):null;if(o!==null)return Math.min(Math.max(o,1e3),6e4);if(this.rateLimitInfo.reset){let l=Math.floor(Date.now()/1e3),c=(this.rateLimitInfo.reset-l)*1e3;return Math.min(Math.max(c,1e3),6e4)}let a=5e3*Math.pow(2,e);return Math.min(a,6e4)}parseRetryAfter(e){if(!e)return null;let t=Number(e);if(Number.isFinite(t))return t*1e3;let n=Date.parse(e);return Number.isNaN(n)?null:n-Date.now()}async handleErrorResponse(e){let t=`${this.providerName} API error: ${e.status} ${e.statusText}`,n=t;try{let o=await e.json();if(typeof o=="object"&&o!==null){let s=o;if(typeof s.error=="string")n=`${t} - ${s.error}`;else if(typeof s.message=="string")n=`${t} - ${s.message}`;else if(Array.isArray(s.errors)){let a=s.errors.map(l=>typeof l=="object"&&l!==null&&"message"in l?String(l.message):JSON.stringify(l)).join(", ");n=`${t} - ${a}`}}}catch{}return r.error(n),new Error(n)}isNonRetryableError(e){if(typeof e=="object"&&e!==null&&e.name==="AbortError")return!0;let t=e.message;return typeof t=="string"&&t.includes("API error: 4")?!t.includes("429"):!1}delay(e){return new Promise(t=>setTimeout(t,e))}getRateLimitInfo(){return{...this.rateLimitInfo}}isLargeResponse(e){if(!e||typeof e!="object")return!1;let t=(n,o=0)=>{if(o>10)return!1;for(let[,s]of Object.entries(n))if(Array.isArray(s)&&s.length>1e3||typeof s=="object"&&s!==null&&t(s,o+1))return!0;return!1};return t(e)}validateLargeResponse(e){if(!e||typeof e!="object")return;let t=(o,s)=>{o.length>5e3&&r.warn(`Large array detected at ${s} with ${o.length} items. This may impact performance.`);let a=Math.min(10,o.length),l=o[0];if(l&&typeof l=="object"){let c=Object.keys(l);for(let d=1;d<a;d++){let u=o[d];if(!u||typeof u!="object"){r.warn(`Inconsistent array structure at ${s}[${d}]: expected object, got ${typeof u}`);continue}let p=Object.keys(u),f=c.filter(y=>!p.includes(y));f.length>0&&r.warn(`Inconsistent array structure at ${s}[${d}]: missing keys ${f.join(", ")}`)}}},n=(o,s="root",a=0)=>{if(!(a>10))for(let[l,c]of Object.entries(o)){let d=`${s}.${l}`;Array.isArray(c)?t(c,d):typeof c=="object"&&c!==null&&n(c,d,a+1)}};n(e)}};var Yi=process.env.DOORMAN_VERCEL_API_BASE_URL||"https://api.vercel.com/v1/security/firewall/config",Me=class extends Te{constructor(t,n,o){super("","vercel");this.projectId=t;this.teamId=n;this.token=o}projectId;teamId;token;async getAuthHeaders(){return{Authorization:`Bearer ${this.token}`}}getUrl(t){let n=t!==void 0?`${Yi}/${t}`:Yi;r.debug("API URL:",n);let o=this.teamId?`&teamId=${this.teamId}`:"";return`${n}?projectId=${this.projectId}${o}`}async fetchFirewallConfig(t,n){let{allowCreate:o=!0}=n??{},s=await this.get(this.getUrl(t));if(r.debug("Config Version:",t??"latest"),r.debug("Fetched Config:",t?s:s.active),t)return s;if(!s||"active"in s&&s.active===null){if(r.warn(Zi.bold("No firewall configuration found.")),!o)return r.debug("Skipping create-configuration prompt for read-only fetch."),this.emptyConfigPlaceholder();if(await b("Would you like to create one?",{type:"confirm"})){r.debug("Creating new empty firewall configuration...");let l=await this.putEmptyConfig();return r.debug("Empty firewall configuration created successfully"),r.debug(`New configuration version: ${Zi.yellow(l.version)}`),l}else return s.active}return s.active}emptyConfigPlaceholder(){return{version:0,id:"",firewallEnabled:!0,crs:null,rules:[],ips:[],projectKey:"",ownerId:"",updatedAt:new Date(0).toISOString()}}async putEmptyConfig(){let{$schema:t,...n}=vt();return r.debug("Empty Config:",n),this.putConfig(n)}async putConfig(t){return(await this.put(this.getUrl(),t)).active}async fetchActiveFirewallRules(){return(await this.fetchFirewallConfig())?.rules}async updateFirewallRule(t){let n=!t.id||t.id==="-",o={action:n?"rules.insert":"rules.update",id:n?null:t.id,value:{name:t.name,description:t.description,action:t.action,conditionGroup:t.conditionGroup,active:t.active}};return this.patch(this.getUrl(),o,n?{retries:0}:void 0)}async createFirewallRule(t){return this.updateFirewallRule({...t,id:"-"})}async deleteFirewallRule(t){let n={action:"rules.remove",id:t.id,value:null};await this.patch(this.getUrl(),n)}async updateIPBlockingRule(t){let n=!t.id||t.id==="-",o={action:n?"ip.insert":"ip.update",id:n?null:t.id,value:{action:t.action,hostname:t.hostname,ip:t.ip,notes:t.notes??null}};return this.patch(this.getUrl(),o,n?{retries:0}:void 0)}async createIPBlockingRule(t){return this.updateIPBlockingRule({...t,id:"-"})}async deleteIPBlockingRule(t){let n={action:"ip.remove",id:t.id,value:null};await this.patch(this.getUrl(),n)}async verifyCredentials(){try{return await this.fetchFirewallConfig(),!0}catch(t){return r.debug("Credential verification failed:",t),!1}}};P();import ae from"chalk";P();var $e=class{validateConfig(e){let t=[],n=[];if(e||t.push({path:"root",message:"Configuration is required",code:"CONFIG_REQUIRED"}),e&&!e.rules&&t.push({path:"rules",message:"Rules array is required",code:"RULES_REQUIRED"}),e&&e.rules&&!Array.isArray(e.rules)&&t.push({path:"rules",message:"Rules must be an array",code:"RULES_INVALID_TYPE"}),e&&Array.isArray(e.rules)){let o=new Map;e.rules.forEach((s,a)=>{let l=s.id||s.name;if(!l)return;let c=o.get(l);if(c===void 0){o.set(l,a);return}let d=e.rules[c];n.push({path:`rules[${a}]`,message:s.id?`Rule "${s.name}" has the same id ("${s.id}") as rules[${c}] ("${d?.name}") \u2014 they will collide during sync diffing and one may be silently skipped.`:`Rule "${s.name}" has the same name as rules[${c}] and neither has an explicit id \u2014 they will collide during sync diffing and one may be silently skipped. Add unique ids to disambiguate.`,code:"DUPLICATE_RULE_IDENTIFIER"})})}return{valid:t.length===0,errors:t,warnings:n}}getHealthScore(e){let t=[],n=100;if(!e)return{score:0,grade:"poor",issues:[{severity:"error",category:"configuration",message:"No configuration found"}],recommendations:["Create a configuration file"]};if((!e.rules||e.rules.length===0)&&(n-=20,t.push({severity:"warning",category:"rules",message:"No rules defined",suggestion:"Add security rules to protect your application"})),e.rules&&e.rules.length>0){let a=e.rules.filter(l=>!l.description);a.length>0&&(n-=5,t.push({severity:"info",category:"maintainability",message:`${a.length} rule(s) missing descriptions`,suggestion:"Add descriptions to improve maintainability"}))}if(e.rules&&e.rules.length>0){let a=e.rules.filter(l=>l.active===!1||l.enabled===!1);a.length>0&&(n-=5,t.push({severity:"info",category:"maintenance",message:`${a.length} disabled rule(s) found`,suggestion:"Review and remove unused rules"}))}let o=this.calculateGrade(n),s=this.generateRecommendations(e,t);return{score:Math.max(0,n),grade:o,issues:t,recommendations:s}}diffItems(e,t,n,o="id"){let s=[],a=[],l=[],c=d=>d[o];for(let d of e){let u=t.find(p=>c(p)===c(d));u?n(d,u)||a.push(d):s.push(d)}for(let d of t)e.find(p=>c(p)===c(d))||l.push(d);return{toAdd:s,toUpdate:a,toDelete:l}}validateRuleCount(e,t){if(t&&e>t)throw new Error(`Rule count (${e}) exceeds provider limit (${t})`);t&&e>t*.9&&r.warn(`Approaching rule limit: ${e}/${t} rules`)}updateMetadata(e,t){return{...e,metadata:{...e.metadata,version:t,updatedAt:new Date().toISOString()}}}calculateGrade(e){return e>=80?"excellent":e>=60?"good":e>=40?"fair":"poor"}generateRecommendations(e,t){let n=[];return t.some(o=>o.category==="rules")&&n.push("Consider using predefined templates to get started"),t.some(o=>o.category==="maintainability")&&n.push("Add descriptions to all rules for better documentation"),t.some(o=>o.category==="maintenance")&&n.push("Remove or enable disabled rules to keep configuration clean"),n}logSyncStats(e){r.info(`Sync complete for ${this.name}:`),r.info(` Rules added: ${e.rulesAdded}`),r.info(` Rules updated: ${e.rulesUpdated}`),r.info(` Rules deleted: ${e.rulesDeleted}`),e.ipsAdded!==void 0&&(r.info(` IPs added: ${e.ipsAdded}`),r.info(` IPs updated: ${e.ipsUpdated}`),r.info(` IPs deleted: ${e.ipsDeleted}`)),e.version&&r.info(` New version: ${e.version}`)}};ue();function Ji(i){let e=[],t=[];i.conditionGroup.forEach((s,a)=>{for(let l of s.conditions){let c=Ko(l.op);t.push({field:Zo(l.type),operator:c,...l.value!==void 0?{value:l.value}:{},...l.neg?{negated:!0}:{},...l.key?{key:l.key}:{},group:a})}}),t.length>10&&e.push(S.createWarning("many_conditions",i.id,void 0,`Rule has ${t.length} conditions which may impact performance`,"Consider splitting complex rules into multiple simpler rules for better performance"));let n={type:i.action.mitigate.action,...i.action.mitigate.rateLimit?{rateLimit:{requests:i.action.mitigate.rateLimit.requests,window:i.action.mitigate.rateLimit.window,...i.action.mitigate.rateLimit.characteristics?{characteristics:i.action.mitigate.rateLimit.characteristics}:{},...i.action.mitigate.rateLimit.mitigationTimeout?{mitigationTimeout:i.action.mitigate.rateLimit.mitigationTimeout}:{},...i.action.mitigate.rateLimit.countingExpression?{countingExpression:i.action.mitigate.rateLimit.countingExpression}:{}}}:{},...i.action.mitigate.redirect?{redirect:{location:i.action.mitigate.redirect.location,...i.action.mitigate.redirect.permanent?{permanent:i.action.mitigate.redirect.permanent}:{}}}:{},...i.action.mitigate.actionDuration?{duration:i.action.mitigate.actionDuration}:{}};return{result:{id:i.id,name:i.name,...i.description?{description:i.description}:{},enabled:i.active,conditions:t,conditionLogic:i.conditionGroup.length>1?"OR":"AND",action:n},warnings:e}}function Qi(i){let e=[],t=Go(i.conditions,i.id,e);if(i.action.response&&e.push(S.createUnsupportedFeatureWarning("custom response body","unified config","Vercel",i.id,"action.response")),t.length===0)throw new Error(`Rule "${i.name}" has no conditions Vercel can represent \u2014 every condition field is unsupported by Vercel (e.g. Cloudflare-only fields like referer/port). Cannot sync this rule to Vercel.`);return{result:{id:i.id,name:i.name,description:i.description,conditionGroup:t,action:{mitigate:{action:i.action.type,rateLimit:i.action.rateLimit?{requests:i.action.rateLimit.requests,window:i.action.rateLimit.window,characteristics:i.action.rateLimit.characteristics,mitigationTimeout:i.action.rateLimit.mitigationTimeout,countingExpression:i.action.rateLimit.countingExpression}:null,redirect:i.action.redirect?{location:i.action.redirect.location,permanent:i.action.redirect.permanent}:null,actionDuration:i.action.duration||null}},active:i.enabled},warnings:e}}function Xi(i){return{id:i.id,ip:i.ip,...i.hostname?{hostname:i.hostname}:{},...i.notes?{notes:i.notes}:{},action:i.action}}function Go(i,e,t){let n=new Map;for(let s of i){let a=s.group??0,l=n.get(a);l?l.push(s):n.set(a,[s])}let o=[];for(let s of n.values()){let a=[];for(let l of s){let c=Jo(l.field);if(!c){t.push(S.createUnsupportedFeatureWarning(`condition field '${l.field}'`,"unified config","Vercel",e,l.field));continue}a.push({op:Ho(l.operator),neg:l.negated,type:c,key:l.key,value:l.value})}a.length>0&&o.push({conditions:a})}return o}function Ko(i){return{eq:"eq",pre:"starts_with",suf:"ends_with",inc:"in",sub:"contains",re:"matches",ex:"exists",nex:"not_exists"}[i]||"eq"}function Ho(i){return{eq:"eq",starts_with:"pre",ends_with:"suf",in:"inc",contains:"sub",matches:"re",exists:"ex",not_exists:"nex"}[i]||"eq"}function Zo(i){return{host:"host",path:"path",method:"method",header:"header",query:"query",cookie:"cookie",ip_address:"ip",user_agent:"user_agent",geo_country:"country",geo_city:"city",geo_as_number:"asn",scheme:"scheme"}[i]||i}var Yo={host:"host",path:"path",method:"method",header:"header",query:"query",cookie:"cookie",ip:"ip_address",user_agent:"user_agent",country:"geo_country",city:"geo_city",asn:"geo_as_number",scheme:"scheme",target_path:"target_path",region:"region",protocol:"protocol",environment:"environment",geo_continent:"geo_continent",geo_country_region:"geo_country_region",ja4_digest:"ja4_digest",ja3_digest:"ja3_digest",rate_limit_api_id:"rate_limit_api_id"};function Jo(i){return Yo[i]??null}ue();P();function je(i){return i.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}function en(i){let e="";for(let t=0;t<i.length;t++)i[t]==="\\"&&(i[t+1]==="\\"||i[t+1]==='"')?(e+=i[t+1],t++):e+=i[t];return e}var ot=class{static vercelToCloudflare={host:"http.host",path:"http.request.uri.path",method:"http.request.method",header:"http.request.headers",query:"http.request.uri.query",cookie:"http.cookie",target_path:"http.request.uri.path",ip_address:"ip.src",region:"ip.geoip.subdivision_1",protocol:"ssl",scheme:"ssl",environment:"",user_agent:"http.user_agent",geo_continent:"ip.geoip.continent",geo_country:"ip.geoip.country",geo_country_region:"ip.geoip.subdivision_1",geo_city:"ip.geoip.city",geo_as_number:"ip.geoip.asnum",ja4_digest:"",ja3_digest:"",rate_limit_api_id:""};static cloudflareToVercel={"http.host":"host","http.request.uri.path":"path","http.request.method":"method","http.request.headers":"header","http.request.uri.query":"query","http.cookie":"cookie","ip.src":"ip_address","ip.geoip.subdivision_1":"region",ssl:"protocol","http.user_agent":"user_agent","ip.geoip.continent":"geo_continent","ip.geoip.country":"geo_country","ip.geoip.city":"geo_city","ip.geoip.asnum":"geo_as_number"};static toCloudflare(e,t){let n=this.vercelToCloudflare[e];if(!n)throw r.warn(`No Cloudflare mapping for Vercel type: ${e}`),new Error(`Unsupported Vercel rule type for Cloudflare: ${e}`);return e==="header"&&t?`${n}["${je(t.toLowerCase())}"]`:e==="cookie"&&t?`${n}["${je(t)}"]`:n}static toVercel(e){let t=e.match(/http\.request\.headers\["([^"]+)"\]/);if(t)return{type:"header",key:t[1]};let n=e.match(/http\.cookie\["([^"]+)"\]/);if(n)return{type:"cookie",key:n[1]};if(e==="http.referer")return{type:"header",key:"referer"};let o=this.cloudflareToVercel[e];if(o)return{type:o};throw r.warn(`No Vercel mapping for Cloudflare field: ${e}`),new Error(`Unsupported Cloudflare field for Vercel: ${e}`)}static isCloudflareSupported(e){let t=this.vercelToCloudflare[e];return!!t&&t!==""}static isVercelSupported(e){return!!(this.cloudflareToVercel[e]||e==="http.referer"||e.startsWith("http.request.headers[")||e.startsWith("http.cookie["))}static getSupportedCloudflareFields(){return Object.values(this.vercelToCloudflare).filter(e=>e!=="")}static getSupportedVercelTypes(){return Object.keys(this.vercelToCloudflare).filter(e=>this.isCloudflareSupported(e))}};var Qo=new Set(["ip.src"]),rt=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=ot.toCloudflare(e.type,e.key),n=this.buildExistsOrComparisonExpression(t,e.op,()=>`${this.mapVercelOperator(e.op)} ${this.formatValue(t,e.value)}`);return e.neg&&(n=`not (${n})`),n}static fromUnifiedConditions(e,t="AND"){if(!e||e.length===0)throw new Error("At least one condition is required");if(!e.some(a=>a.group!==void 0)){let a=e.map(c=>this.fromUnifiedCondition(c)),l=t==="AND"?" and ":" or ";return a.length>1?`(${a.join(l)})`:a[0]}let o=new Map;for(let a of e){let l=a.group??0,c=o.get(l);c?c.push(a):o.set(l,[a])}let s=Array.from(o.values()).map(a=>this.combineWithAnd(a.map(l=>this.fromUnifiedCondition(l))));return s.length>1?this.combineWithOr(s):s[0]}static fromUnifiedCondition(e){let t=this.mapUnifiedFieldToCloudflare(e.field),n=e.key&&(e.field==="header"||e.field==="cookie")?`${t}["${je(e.key)}"]`:t,o=this.buildUnifiedExpression(n,e.operator,e.value);return e.negated&&(o=`not (${o})`),o}static buildExistsOrComparisonExpression(e,t,n){return t==="ex"||t==="exists"?`${e} exists`:t==="nex"||t==="not_exists"?`not (${e} exists)`:`${e} ${n()}`}static buildUnifiedExpression(e,t,n){return t==="not_contains"?`not (${e} contains ${this.formatValue(e,n)})`:t==="not_in"?`not (${e} in ${this.formatValue(e,n)})`:this.buildExistsOrComparisonExpression(e,t,()=>`${this.mapUnifiedOperator(t)} ${this.formatValue(e,n)}`)}static mapVercelOperator(e){return{eq:"eq",pre:"starts_with",suf:"ends_with",inc:"in",sub:"contains",re:"matches",ex:"exists"}[e]||e}static mapUnifiedOperator(e){return{eq:"eq",ne:"ne",contains:"contains",starts_with:"starts_with",ends_with:"ends_with",matches:"matches",in:"in",gt:"gt",ge:"ge",lt:"lt",le:"le",exists:"exists"}[e]||e}static mapUnifiedFieldToCloudflare(e){return{ip:"ip.src",country:"ip.geoip.country",region:"ip.geoip.subdivision_1",city:"ip.geoip.city",asn:"ip.geoip.asnum",path:"http.request.uri.path",host:"http.host",method:"http.request.method",header:"http.request.headers",query:"http.request.uri.query",cookie:"http.cookie",user_agent:"http.user_agent",referer:"http.referer",scheme:"ssl",port:"cf.edge.server_port"}[e]||e}static formatValue(e,t){return Array.isArray(t)?`{${t.map(o=>this.formatSingleValue(e,o)).join(" ")}}`:this.formatSingleValue(e,t)}static formatSingleValue(e,t){if(typeof t=="string"){if(Qo.has(e)){if(!Ae.safeParse(t).success)throw new Error(`Invalid IP address or CIDR range: ${t}`);return t}return`"${je(t)}"`}return typeof t=="number"?String(t):typeof t=="boolean"?t?"true":"false":String(t)}static validate(e){if(!e||e.trim().length===0)return!1;let t=0;for(let n of e)if(n==="("&&t++,n===")"&&t--,t<0)return!1;return t===0}static combineWithAnd(e){if(e.length===0)throw new Error("At least one expression is required");return e.length===1?e[0]:`(${e.join(" and ")})`}static combineWithOr(e){if(e.length===0)throw new Error("At least one expression is required");return e.length===1?e[0]:`(${e.join(" or ")})`}};var Xo={"ip.src":"ip","ip.geoip.country":"country","ip.geoip.subdivision_1":"region","ip.geoip.city":"city","ip.geoip.asnum":"asn","http.request.uri.path":"path","http.host":"host","http.request.method":"method","http.request.headers":"header","http.request.uri.query":"query","http.cookie":"cookie","http.user_agent":"user_agent","http.referer":"referer",ssl:"scheme","cf.edge.server_port":"port"},er=new Set(["eq","ne","contains","starts_with","ends_with","matches","in","gt","ge","lt","le"]),tr=new Set(["and","or","not"]),Pe=class extends Error{};function ir(i){let e=[],t=0,n=i.length;for(;t<n;){let o=i[t];if(/\s/.test(o)){t++;continue}if(o==="("){e.push({type:"LPAREN",value:o}),t++;continue}if(o===")"){e.push({type:"RPAREN",value:o}),t++;continue}if(o==="{"){e.push({type:"LBRACE",value:o}),t++;continue}if(o==="}"){e.push({type:"RBRACE",value:o}),t++;continue}if(o==="["){e.push({type:"LBRACKET",value:o}),t++;continue}if(o==="]"){e.push({type:"RBRACKET",value:o}),t++;continue}if(o==='"'){let a=t+1,l="",c=!1;for(;a<n;){let d=i[a];if(d==="\\"&&a+1<n){l+=d+i[a+1],a+=2;continue}if(i[a]==='"'){c=!0,a++;break}l+=i[a],a++}if(!c)throw new Pe("Unterminated string literal");e.push({type:"STRING",value:en(l)}),t=a;continue}let s=t;for(;s<n&&!/[\s(){}[\]"]/.test(i[s]);)s++;if(s===t)throw new Pe(`Unexpected character '${o}' at position ${t}`);e.push({type:"WORD",value:i.slice(t,s)}),t=s}return e}var Kt=class{constructor(e){this.tokens=e}tokens;pos=0;peek(){return this.tokens[this.pos]}next(){let e=this.tokens[this.pos];if(!e)throw new Pe("Unexpected end of expression");return this.pos++,e}expect(e,t){let n=this.next();if(n.type!==e||t!==void 0&&n.value!==t)throw new Pe(`Expected ${t??e} but got '${n.value}'`);return n}isWord(e){let t=this.peek();return!!t&&t.type==="WORD"&&t.value===e}atEnd(){return this.pos>=this.tokens.length}};function Ht(i){let e=[tn(i)];for(;i.isWord("or");)i.next(),e.push(tn(i));return e.length===1?e[0]:{type:"or",children:e}}function tn(i){let e=[nn(i)];for(;i.isWord("and");)i.next(),e.push(nn(i));return e.length===1?e[0]:{type:"and",children:e}}function nn(i){if(i.isWord("not")){i.next(),i.expect("LPAREN");let e=Ht(i);return i.expect("RPAREN"),{type:"not",child:e}}return nr(i)}function nr(i){let e=i.peek();if(e&&e.type==="LPAREN"){i.next();let t=Ht(i);return i.expect("RPAREN"),t}return sr(i)}function or(i){let e=i.expect("WORD");if(tr.has(e.value))throw new Pe(`Expected a field reference but got keyword '${e.value}'`);if(i.peek()?.type==="LBRACKET"){i.next();let t=i.expect("STRING");return i.expect("RBRACKET"),{field:e.value,key:t.value}}return{field:e.value}}function on(i){let e=i.next();if(e.type==="STRING")return e.value;if(e.type==="WORD")return/^-?\d+(\.\d+)?$/.test(e.value)?Number(e.value):e.value;throw new Pe(`Unexpected token '${e.value}' where a value was expected`)}function rr(i){if(i.peek()?.type==="LBRACE"){i.next();let e=[];for(;i.peek()?.type!=="RBRACE";)e.push(on(i));return i.expect("RBRACE"),e}return on(i)}function sr(i){let{field:e,key:t}=or(i);if(i.isWord("exists"))return i.next(),{type:"exists",field:e,key:t};let n=i.next();if(n.type!=="WORD"||!er.has(n.value))throw new Pe(`Unsupported or unrecognized operator '${n.value}'`);let o=rr(i);return{type:"comparison",field:e,key:t,operator:n.value,value:o}}function ar(i){let e=new Kt(ir(i)),t=Ht(e);if(!e.atEnd())throw new Pe("Unexpected trailing tokens");return t}function It(i){return i.type==="comparison"||i.type==="exists"?!0:i.type==="not"?i.child.type==="comparison"||i.child.type==="exists":!1}function Et(i,e){let t=i.type==="not",n=i.type==="not"?i.child:i;if(n.type!=="comparison"&&n.type!=="exists")return null;let o=Xo[n.field];if(!o)return null;if(n.type==="exists")return{field:o,operator:t?"not_exists":"exists",value:"",key:n.key,group:e};let s=n.operator,a;return t&&(n.operator==="contains"?s="not_contains":n.operator==="in"?s="not_in":a=!0),{field:o,operator:s,value:n.value,key:n.key,negated:a,group:e}}function lr(i){if(It(i)){let e=Et(i,0);return e?{conditions:[e],conditionLogic:"AND"}:null}if(i.type==="and"){let e=[];for(let t of i.children){if(!It(t))return null;let n=Et(t,0);if(!n)return null;e.push(n)}return{conditions:e,conditionLogic:"AND"}}if(i.type==="or"){let e=[];i.children.forEach((o,s)=>{if(It(o)){let a=Et(o,s);a&&e.push(a);return}if(o.type==="and")for(let a of o.children){if(!It(a))return;let l=Et(a,s);l&&e.push(l)}});let t=i.children.length;return new Set(e.map(o=>o.group)).size!==t?null:{conditions:e,conditionLogic:"OR"}}return null}function Zt(i){if(!i||!i.trim())return null;try{let e=ar(i);return lr(e)}catch{return null}}function rn(i){let e=[],t=Zt(i.expression),n=[],o;t?(n=t.conditions,o=t.conditionLogic):e.push(S.createWarning("complex_expressions",i.id,"expression","Expression could not be parsed back into structured conditions \u2014 it may be hand-authored or use syntax outside what doorman itself generates.","Review the translated rule and add missing conditions manually if needed."));let s=i.action_parameters&&"response"in i.action_parameters?i.action_parameters.response:void 0,a={type:cr(i.action),rateLimit:i.ratelimit?{requests:i.ratelimit.requests_per_period,window:`${i.ratelimit.period}s`,characteristics:i.ratelimit.characteristics,mitigationTimeout:i.ratelimit.mitigation_timeout,countingExpression:i.ratelimit.counting_expression}:void 0,response:s?{statusCode:s.status_code,content:s.content,contentType:s.content_type}:void 0};return{result:{id:i.id,name:i.description||`Rule ${i.id}`,description:i.description,enabled:i.enabled??!0,conditions:n,conditionLogic:o,action:a},warnings:e}}function sn(i){let e=[],t=rt.fromUnifiedConditions(i.conditions,i.conditionLogic),n={id:i.id||crypto.randomUUID(),action:dr(i.action.type),expression:t,description:i.description||i.name,enabled:i.enabled};return i.action.rateLimit&&(n.ratelimit={characteristics:i.action.rateLimit.characteristics||["ip.src"],period:ur(i.action.rateLimit.window),requests_per_period:i.action.rateLimit.requests},i.action.rateLimit.mitigationTimeout?n.ratelimit.mitigation_timeout=i.action.rateLimit.mitigationTimeout:(n.ratelimit.mitigation_timeout=3600,e.push(S.createWarning("rate_limiting_precision",i.id,"action.rateLimit.mitigationTimeout",`No mitigationTimeout set for rate limit rule "${i.name}" \u2014 defaulted to Cloudflare's 3600s (1 hour) block duration.`))),i.action.rateLimit.countingExpression&&(n.ratelimit.counting_expression=i.action.rateLimit.countingExpression)),i.action.type==="redirect"&&i.action.redirect&&(n.action_parameters={from_value:{status_code:i.action.redirect.statusCode??(i.action.redirect.permanent?301:302),target_url:{value:i.action.redirect.location},...i.action.redirect.preserveQueryString!==void 0?{preserve_query_string:i.action.redirect.preserveQueryString}:{}}}),i.action.response&&(n.action!=="block"?e.push(S.createUnsupportedFeatureWarning(`custom response on a '${n.action}' action`,"unified config","Cloudflare",i.id,"action.response")):i.action.response.content?n.action_parameters={response:{status_code:i.action.response.statusCode??403,content:i.action.response.content,content_type:i.action.response.contentType??"text/plain"}}:e.push(S.createWarning("lossy_conversion",i.id,"action.response","Custom response declared without `content`; Cloudflare requires a response body, so it was dropped.","Set action.response.content to the body you want returned."))),{result:n,warnings:e}}function an(i){if(!Ae.safeParse(i.ip).success)throw new Error(`Invalid IP address or CIDR range: ${i.ip}`);let t=i.ip.includes("/")?`ip.src in {${i.ip}}`:`ip.src eq ${i.ip}`;return{id:i.id||crypto.randomUUID(),action:i.action==="allow"?"allow":"block",expression:t,description:i.notes||`IP ${i.action}: ${i.ip}${i.hostname?` (${i.hostname})`:""}`,enabled:!0}}function cr(i){return{block:"deny",challenge:"challenge",managed_challenge:"challenge",js_challenge:"challenge",log:"log",skip:"bypass",allow:"allow",rewrite:"bypass",redirect:"redirect"}[i]||"deny"}function dr(i){return{log:"log",deny:"block",block:"block",challenge:"managed_challenge",bypass:"skip",rate_limit:"block",redirect:"redirect",allow:"allow"}[i]||"block"}function ur(i){let e=i.match(/^(\d+)([smhd])$/);if(!e||!e[1]||!e[2])throw new Error(`Invalid window format: ${i}`);let t=parseInt(e[1],10),n=e[2],s={s:1,m:60,h:3600,d:86400}[n];if(s===void 0)throw new Error(`Invalid time unit: ${n}`);return t*s}ue();function pe(i,e,t){return S.createWarning("complex_expressions",i,void 0,e,t)}var pr={ip:"ip",path:"path",domain:"host",method:"method",country:"country",scheme:"scheme",user_agent:"user_agent"},fr={ip:"ip",path:"path",host:"domain",method:"method",country:"country",scheme:"scheme",user_agent:"user_agent"},gr={header:"request_header",query:"query_parameter",cookie:"request_cookie"},mr={request_header:"header",query_parameter:"query",request_cookie:"cookie",response_header:"response_header",post_parameter:"post_parameter",signal:"signal"};function hr(i){return pr[i]??i}function yr(i){return fr[i]??null}var wr={equals:{operator:"eq",negated:!1},does_not_equal:{operator:"eq",negated:!0},contains:{operator:"contains",negated:!1},does_not_contain:{operator:"contains",negated:!0},like:{operator:"matches",negated:!1},not_like:{operator:"matches",negated:!0},in_list:{operator:"in",negated:!1},not_in_list:{operator:"in",negated:!0},matches:{operator:"matches",negated:!1},does_not_match:{operator:"matches",negated:!0},greater_equal:{operator:"ge",negated:!1},lesser_equal:{operator:"le",negated:!1}};function ln(i){return wr[i]??{operator:"eq",negated:!1}}function cn(i,e,t,n){switch(i){case"eq":return e?"does_not_equal":"equals";case"ne":return"does_not_equal";case"contains":return e?"does_not_contain":"contains";case"not_contains":return"does_not_contain";case"starts_with":case"ends_with":return t.push(S.createWarning("regex_patterns",n,void 0,`Unified operator "${i}" has no direct Fastly equivalent \u2014 mapped to Fastly's wildcard "like" operator. Verify the translated value still matches as a wildcard pattern.`)),e?"not_like":"like";case"matches":return t.push(S.createWarning("regex_patterns",n,void 0,"Regular expression pattern may need adjustment for Fastly Next-Gen WAF regex syntax.")),e?"does_not_match":"matches";case"in":return e?"not_in_list":"in_list";case"not_in":return"not_in_list";case"gt":return t.push(pe(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(pe(n,'Unified operator "lt" (strictly less than) has no Fastly equivalent \u2014 mapped to "lesser_equal", which also matches the boundary value itself.')),"lesser_equal";case"le":return"lesser_equal";default:return"equals"}}function Rr(i,e){let t=[],n=i.conditions.filter(s=>s.type==="group"),o=i.conditions.filter(s=>s.type!=="group");if(n.length>0&&o.length===0&&i.group_operator==="any")return n.forEach((s,a)=>{s.group_operator!=="all"&&e.push(pe(i.id,`A condition group using "any" (OR) inside an outer "any" (OR) rule cannot be represented in doorman's flat AND-within/OR-across condition model \u2014 its members were kept in the same group (effectively AND'd) instead of OR'd. Review the translated rule against the original Fastly logic.`));for(let l of s.conditions)At(t,l,a,e,i.id)}),{conditions:t,conditionLogic:n.length>1?"OR":"AND"};if(n.length===0)return o.forEach((s,a)=>{let l=i.group_operator==="any"?a:0;At(t,s,l,e,i.id)}),{conditions:t,conditionLogic:i.group_operator==="any"&&o.length>1?"OR":"AND"};e.push(pe(i.id,`Rule mixes top-level conditions with condition groups under group_operator "${i.group_operator}" \u2014 this doesn't fit doorman's flat AND-within/OR-across condition model. All conditions were combined with AND; review the translated rule against the original Fastly rule logic.`));for(let s of o)At(t,s,0,e,i.id);for(let s of n)for(let a of s.conditions)At(t,a,0,e,i.id);return{conditions:t,conditionLogic:"AND"}}function At(i,e,t,n,o){if(e.type==="single"){let{operator:u,negated:p}=ln(e.operator);i.push({field:hr(e.field),operator:u,value:e.value,...p?{negated:!0}:{},group:t});return}let s=mr[e.field],a=e.conditions.find(u=>u.field==="name"),l=e.conditions.find(u=>u.field==="value"||u.field==="value_string");if(!l){if(a){i.push({field:s,operator:e.operator==="does_not_exist"?"not_exists":"exists",key:a.value,group:t});return}n.push(pe(o,`Multival condition on '${e.field}' has no value or name sub-condition doorman could extract \u2014 skipped.`));return}let{operator:c,negated:d}=ln(l.operator);i.push({field:s,operator:c,value:l.value,...d?{negated:!0}:{},...a?{key:a.value}:{},group:t})}function Cr(i,e){i.actions.length>1&&e.push(pe(i.id,`Rule has ${i.actions.length} actions \u2014 doorman only models one action per rule; only the first ("${i.actions[0]?.type}") was translated.`));let t=i.actions[0];if(i.type==="rate_limit")return{type:"rate_limit",rateLimit:i.rate_limit?{requests:i.rate_limit.threshold,window:`${i.rate_limit.interval}s`,mitigationTimeout:i.rate_limit.duration}:void 0};switch(t?.type){case"block":return{type:"block"};case"allow":return{type:"allow"};case"browser_challenge":case"dynamic_challenge":case"verify_token":return{type:"challenge"};case"redirect":return{type:"redirect",redirect:{location:t.redirect_url??"",...t.response_code!==void 0?{statusCode:t.response_code}:{}}};case"add_signal":case"exclude_signal":case"deception":return e.push(S.createWarning("action_differences",i.id,"action.type",`Fastly action "${t.type}" tags or excludes a WAF signal rather than allowing/blocking the request \u2014 doorman has no matching action type. Mapped to "log" as the closest non-destructive fallback; review this rule.`)),{type:"log"};default:return e.push(pe(i.id,`Unrecognised Fastly action type "${String(t?.type)}" \u2014 mapped to "log".`)),{type:"log"}}}function dn(i){let e=[],{conditions:t,conditionLogic:n}=Rr(i,e),o=Cr(i,e);return t.length>10&&e.push(S.createWarning("many_conditions",i.id,void 0,`Rule has ${t.length} conditions which may impact performance`)),{result:{id:i.id,name:i.description||i.id,enabled:i.enabled,conditions:t,conditionLogic:n,action:o},warnings:e}}function br(i,e,t){let n=new Map;for(let a of i){let l=a.group??0,c=n.get(l);c?c.push(a):n.set(l,[a])}let o=a=>{let l=gr[a.field];if(l){if(a.key===void 0)return t.push(pe(e,`Condition on '${a.field}' has no 'key' set \u2014 Fastly needs a specific ${a.field} name to match on (e.g. a header name). Skipped.`)),null;if(a.operator==="exists"||a.operator==="not_exists")return{type:"multival",field:l,operator:a.operator==="exists"?"exists":"does_not_exist",group_operator:"all",conditions:[{type:"single",field:"name",operator:"equals",value:a.key}]};let d=l==="request_header"||l==="response_header"?"value_string":"value";return{type:"multival",field:l,operator:"exists",group_operator:"all",conditions:[{type:"single",field:"name",operator:"equals",value:a.key},{type:"single",field:d,operator:cn(a.operator,a.negated,t,e),value:String(a.value)}]}}if(a.operator==="exists"||a.operator==="not_exists")return t.push(S.createUnsupportedFeatureWarning(`"${a.operator}" on field '${a.field}'`,"unified config","Fastly",e,a.field)),null;let c=yr(a.field);return c?{type:"single",field:c,operator:cn(a.operator,a.negated,t,e),value:String(a.value)}:(t.push(S.createUnsupportedFeatureWarning(`condition field '${a.field}'`,"unified config","Fastly",e,a.field)),null)};if(n.size<=1){let a=i.map(o).filter(l=>l!==null);return{conditions:a.filter(l=>l.type==="single"),groupConditions:[],multivalConditions:a.filter(l=>l.type==="multival"),groupOperator:"all"}}let s=[];for(let a of n.values()){let l=a.map(o).filter(c=>c!==null);l.length>0&&s.push({type:"group",group_operator:"all",conditions:l})}return{conditions:[],groupConditions:s,multivalConditions:[],groupOperator:"any"}}function 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(pe(t,'Unified action "bypass" has no direct Fastly equivalent \u2014 mapped to "allow".')),{type:"allow"};case"log":return e.push(pe(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(pe(t,'Unified action "redirect" has no redirect target \u2014 mapped to "allow".')),{type:"allow"});case"rate_limit":return{type:"block_signal"};default:return e.push(S.createUnsupportedFeatureWarning(`action type '${i.type}'`,"unified config","Fastly",t,"action.type")),{type:"block"}}}function Ir(i,e,t){let n=/^(\d+)\s*(s|m|h)?$/i.exec(i.trim()),o={s:1,m:60,h:3600},s=n?Number(n[1])*o[n[2]?.toLowerCase()||"s"]:NaN,a=[60,600,3600];if(!Number.isFinite(s)||s<=0)return e.push(S.createWarning("rate_limiting_precision",t,void 0,`Rate limit window "${i}" could not be parsed \u2014 defaulted to Fastly's 60-second interval.`)),60;let l=a.reduce((c,d)=>Math.abs(d-s)<Math.abs(c-s)?d:c);return l!==s&&e.push(S.createWarning("rate_limiting_precision",t,void 0,`Rate limit window "${i}" (${s}s) isn't one of Fastly's supported intervals (60s/600s/3600s) \u2014 rounded to ${l}s.`)),l}function Er(i,e,t){if(!i)throw new Error(`Rule "${e??"(unnamed)"}" has action type "rate_limit" but no rateLimit configuration \u2014 Fastly requires a threshold and interval.`);let n=Ir(i.window,t,e);return t.push(S.createWarning("rate_limiting_precision",e,void 0,`Fastly rate-limit rules count requests against a named custom signal, which doorman does not create automatically \u2014 create a signal named "doorman-rate-limit-${e??"rule"}" in this workspace before syncing, or the rule will be rejected.`)),{client_identifiers:[{type:"ip"}],duration:i.mitigationTimeout??n,interval:n,signal:`doorman-rate-limit-${e??"rule"}`,threshold:i.requests}}function un(i){let e=[];i.description&&i.description!==i.name&&e.push(pe(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}=br(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?Er(i.action.rateLimit,i.id,e):null},warnings:e}}function pn(i,e){return i.map(t=>({ip:t,action:e}))}function fn(i){let e=[],t=[],n=[];for(let o of i)(o.hostname||o.notes)&&e.push(pe(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}}ue();function ze(i){return i.replace(/\\/g,"\\\\").replace(/'/g,"\\'")}function gn(i){let e="";for(let t=0;t<i.length;t++)i[t]==="\\"&&(i[t+1]==="\\"||i[t+1]==="'")?(e+=i[t+1],t++):e+=i[t];return e}var Ar=new Set(["region","city","port","scheme"]),Tr={host:"host",user_agent:"user-agent",referer:"referer"},st=class{static fromUnifiedConditions(e,t="AND"){if(!e||e.length===0)throw new Error("At least one condition is required");if(!e.some(a=>a.group!==void 0)){let a=e.map(c=>this.fromUnifiedCondition(c)),l=t==="AND"?" && ":" || ";return a.length>1?`(${a.join(l)})`:a[0]}let o=new Map;for(let a of e){let l=a.group??0,c=o.get(l);c?c.push(a):o.set(l,[a])}let s=Array.from(o.values()).map(a=>this.combineWithAnd(a.map(l=>this.fromUnifiedCondition(l))));return s.length>1?this.combineWithOr(s):s[0]}static fromUnifiedCondition(e){if(Ar.has(e.field))throw new Error(`Cloud Armor has no CEL equivalent for the "${e.field}" condition field \u2014 this rule cannot be represented and would otherwise be silently dropped.`);if(e.field==="ip")return this.buildIpCondition(e);if(e.field==="cookie")return this.buildCookieCondition(e);let t=Tr[e.field];if(t)return this.buildHeaderCondition(t,e);if(e.field==="header"||e.field==="query"){if(e.field==="header"){if(!e.key)throw new Error('A "header" condition requires a key naming the header');return this.buildHeaderCondition(e.key,e)}return this.buildComparison("request.query",e,!1)}let n=Pr[e.field];if(n)return this.buildComparison(n.path,e,n.numeric);throw new Error(`Cloud Armor CEL builder has no mapping for condition field "${e.field}"`)}static buildIpCondition(e){let t=(Array.isArray(e.value)?e.value:[e.value]).map(String);if(t.length===0||t.some(a=>!a))throw new Error('An "ip" condition requires at least one value');let n=t.map(a=>{if(!Ae.safeParse(a).success)throw new Error(`Invalid IP address or CIDR range: ${a}`);return a.includes("/")?`inIpRange(origin.ip, '${ze(a)}')`:`origin.ip == '${ze(a)}'`}),o=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['${ze(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('${ze(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=$r[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`'${ze(String(e))}'`}static combineWithAnd(e){if(e.length===0)throw new Error("At least one expression is required");return e.length===1?e[0]:`(${e.join(" && ")})`}static combineWithOr(e){if(e.length===0)throw new Error("At least one expression is required");return e.length===1?e[0]:`(${e.join(" || ")})`}},$r={eq:"ne",ne:"eq",contains:"not_contains",not_contains:"contains",in:"not_in",not_in:"in"},Pr={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 _r={"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"},Cn={contains:"contains",startsWith:"starts_with",endsWith:"ends_with",matches:"matches"},Ce=class extends Error{};function Sr(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 Ce("Unterminated string literal");e.push({type:"STRING",value:gn(l)}),t=a;continue}if(/[0-9]/.test(s)||s==="-"&&/[0-9]/.test(i[t+1]??"")){let a=t+1;for(;a<n&&/[0-9.]/.test(i[a]);)a++;e.push({type:"NUMBER",value:i.slice(t,a)}),t=a;continue}if(/[A-Za-z_]/.test(s)){let a=t;for(;a<n&&/[A-Za-z0-9_]/.test(i[a]);)a++;e.push({type:"IDENT",value:i.slice(t,a)}),t=a;continue}throw new Ce(`Unexpected character '${s}' at position ${t}`)}return e}var Jt=class{constructor(e){this.tokens=e}tokens;pos=0;peek(){return this.tokens[this.pos]}next(){let e=this.tokens[this.pos];if(!e)throw new Ce("Unexpected end of expression");return this.pos++,e}expect(e){let t=this.next();if(t.type!==e)throw new Ce(`Expected ${e} but got '${t.value}'`);return t}isIdent(e){let t=this.peek();return!!t&&t.type==="IDENT"&&t.value===e}atEnd(){return this.pos>=this.tokens.length}};function Ur(i){let e=i.expect("IDENT").value;for(;i.peek()?.type==="DOT";){let t=i.pos;i.next();let n=i.expect("IDENT");if(i.peek()?.type==="LPAREN"){i.pos=t;break}e+=`.${n.value}`}return e}function Yt(i){let e=Ur(i);if(i.peek()?.type==="LBRACKET"){i.next();let t=i.expect("STRING");return i.expect("RBRACKET"),{field:e,key:t.value}}return{field:e}}function mn(i){let e=i.next();if(e.type==="STRING")return e.value;if(e.type==="NUMBER")return Number(e.value);throw new Ce(`Unexpected token '${e.value}' where a literal was expected`)}function Lr(i){if(i.isIdent("has")){i.next(),i.expect("LPAREN");let a=Yt(i);return i.expect("RPAREN"),{type:"has",...a}}if(i.isIdent("inIpRange")){i.next(),i.expect("LPAREN");let a=Yt(i);if(a.field!=="origin.ip")throw new Ce(`inIpRange must reference origin.ip, got '${a.field}'`);i.expect("COMMA");let l=i.expect("STRING");return i.expect("RPAREN"),{type:"inIpRange",value:l.value}}let e=Yt(i);if(i.peek()?.type==="DOT"){i.next();let a=i.expect("IDENT").value;if(!Cn[a])throw new Ce(`Unrecognized method '${a}'`);i.expect("LPAREN");let l=i.expect("STRING");return i.expect("RPAREN"),{type:"methodCall",...e,method:a,value:l.value}}if(i.isIdent("in")){i.next(),i.expect("LBRACKET");let a=[];for(;i.peek()?.type!=="RBRACKET";)a.push(mn(i)),i.peek()?.type==="COMMA"&&i.next();return i.expect("RBRACKET"),{type:"membership",...e,values:a}}let t=i.next(),o={EQ:"eq",NE:"ne",GT:"gt",GE:"ge",LT:"lt",LE:"le"}[t.type];if(!o)throw new Ce(`Unexpected token '${t.value}' after field reference`);let s=mn(i);return{type:"comparison",...e,operator:o,value:s}}function hn(i){if(i.peek()?.type==="LPAREN"){i.next();let e=bn(i);return i.expect("RPAREN"),e}return Lr(i)}function yn(i){return i.peek()?.type==="NOT"?(i.next(),{type:"not",child:hn(i)}):hn(i)}function wn(i){let e=[yn(i)];for(;i.peek()?.type==="AND";)i.next(),e.push(yn(i));return e.length===1?e[0]:{type:"and",children:e}}function bn(i){let e=[wn(i)];for(;i.peek()?.type==="OR";)i.next(),e.push(wn(i));return e.length===1?e[0]:{type:"or",children:e}}function xr(i){let e=new Jt(Sr(i)),t=bn(e);if(!e.atEnd())throw new Ce("Unexpected trailing tokens");return t}function Dr(i){return i.type==="inIpRange"||i.type==="comparison"&&i.field==="origin.ip"&&i.operator==="eq"}function Qt(i){return i.type==="or"&&i.children.length>0&&i.children.every(Dr)}function Or(i){return i.type==="inIpRange"?i.value:String(i.value)}function vn(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 Fr(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 Tt(i){if(i.type==="has"||i.type==="comparison"||i.type==="methodCall"||i.type==="membership"||i.type==="inIpRange"||Qt(i)||vn(i))return!0;if(i.type==="not"){let e=i.child;return e.type==="has"||e.type==="comparison"||e.type==="methodCall"||e.type==="membership"||e.type==="inIpRange"||Qt(e)}return!1}function In(i,e){if(i==="origin.ip")return{unifiedField:"ip"};let t=_r[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 $t(i,e){let t=i.type==="not",n=i.type==="not"?i.child:i;if(Qt(n)){let o=n.children.map(s=>Or(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"&&vn(n)){if(t)return null;let{guard:o,comparisonNegated:s,comparison:a}=Fr(n);return a.type!=="comparison"&&a.type!=="methodCall"&&a.type!=="membership"?null:Rn(a,s,o.field,o.key,e)}if(n.type==="comparison"||n.type==="methodCall"||n.type==="membership")return Rn(n,t,n.field,n.key,e);if(n.type==="has"){let o=In(n.field,n.key);return o?{field:o.unifiedField,operator:t?"not_exists":"exists",value:"",...o.unifiedKey!==void 0?{key:o.unifiedKey}:{},...e!==void 0?{group:e}:{}}:null}return null}function Rn(i,e,t,n,o){let s=In(t,n);if(!s)return null;let a,l,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=Cn[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 Nr(i){if(Tt(i)){let e=$t(i,void 0);return e?{conditions:[e],conditionLogic:"AND"}:null}if(i.type==="and"){let e=[];for(let t of i.children){if(!Tt(t))return null;let n=$t(t,void 0);if(!n)return null;e.push(n)}return{conditions:e,conditionLogic:"AND"}}if(i.type==="or"){let e=[];i.children.forEach((o,s)=>{if(Tt(o)){let a=$t(o,s);a&&e.push(a);return}if(o.type==="and")for(let a of o.children){if(!Tt(a))return;let l=$t(a,s);l&&e.push(l)}});let t=i.children.length;return new Set(e.map(o=>o.group)).size!==t?null:{conditions:e,conditionLogic:"OR"}}return null}function Ge(i){if(!i||!i.trim())return null;try{let e=xr(i);return Nr(e)}catch{return null}}function En(i){if(i.priority===void 0)throw new Error(`Rule "${i.name}" has no priority assigned \u2014 CloudArmorFirewallService must assign one before calling unifiedToGcp`);let e=[],t=st.fromUnifiedConditions(i.conditions,i.conditionLogic),{action:n,actionWarning:o}=Br(i.action.type,i.name,i.id);o&&e.push(o);let s={priority:i.priority,description:(i.description||i.name).slice(0,64),match:{expr:{expression:t}},action:n,preview:i.action.type==="log"?!0:void 0};if((n==="throttle"||n==="rate_based_ban")&&i.action.rateLimit){let a=qr(i.action.rateLimit.window);s.rateLimitOptions={rateLimitThreshold:{count:i.action.rateLimit.requests,intervalSec:a},conformAction:"allow",exceedAction:"deny(429)",enforceOnKey:"IP"}}else n==="throttle"&&!i.action.rateLimit&&e.push(S.createWarning("lossy_conversion",i.id,"action.rateLimit",`Rule "${i.name}" maps to Cloud Armor's "throttle" action but declares no rateLimit block \u2014 Cloud Armor requires rateLimitOptions for this action; the rule will likely be rejected by the API.`));return n==="redirect"&&(i.action.redirect?s.redirectOptions={type:"EXTERNAL_302",target:i.action.redirect.location}:e.push(S.createWarning("lossy_conversion",i.id,"action.redirect",`Rule "${i.name}" maps to Cloud Armor's "redirect" action but declares no redirect target \u2014 Cloud Armor requires redirectOptions for this action; the rule will likely be rejected by the API.`))),{result:s,warnings:e}}function An(i,e){let t=[],n=e!==void 0?e:Ge(i.match.expr.expression),o=[],s;n?(o=n.conditions,s=n.conditionLogic):t.push(S.createWarning("complex_expressions",String(i.priority),"match.expr.expression","CEL expression could not be parsed back into structured conditions \u2014 it may be hand-authored or use syntax outside what doorman itself generates.","Review the translated rule and add missing conditions manually if needed."));let a={type:Vr(i.action),...i.rateLimitOptions?{rateLimit:{requests:i.rateLimitOptions.rateLimitThreshold.count,window:`${i.rateLimitOptions.rateLimitThreshold.intervalSec}s`}}:{},...i.redirectOptions?{redirect:{location:i.redirectOptions.target}}:{}};return{result:{id:String(i.priority),name:i.description||`Rule ${i.priority}`,...i.description?{description:i.description}:{},enabled:!i.preview,conditions:o,...s==="OR"?{conditionLogic:s}:{},action:a,priority:i.priority},warnings:t}}function Tn(i,e){if(i.action!=="allow"&&i.action!=="deny(403)"||i.preview)return!1;let t=e!==void 0?e:Ge(i.match.expr.expression);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 $n(i,e){let o=(e!==void 0?e:Ge(i.match.expr.expression)).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 Pn(i,e){if(!Ae.safeParse(i.ip).success)throw new Error(`Invalid IP address or CIDR range: ${i.ip}`);let t=st.fromUnifiedCondition({field:"ip",operator:"eq",value:i.ip});return{priority:e,description:(i.notes||`IP ${i.action}: ${i.ip}${i.hostname?` (${i.hostname})`:""}`).slice(0,64),match:{expr:{expression:t}},action:i.action==="allow"?"allow":"deny(403)"}}function Vr(i){return{allow:"allow","deny(403)":"deny","deny(404)":"deny","deny(502)":"deny",rate_based_ban:"rate_limit",throttle:"rate_limit",redirect:"redirect"}[i]||"deny"}function Br(i,e,t){switch(i){case"log":return{action:"allow"};case"deny":case"block":return{action:"deny(403)"};case"challenge":return{action:"deny(403)",actionWarning:S.createUnsupportedFeatureWarning("challenge action","unified config","Cloud Armor",t,"action")};case"bypass":return{action:"allow"};case"rate_limit":return{action:"throttle"};case"redirect":return{action:"redirect"};case"allow":return{action:"allow"};default:return{action:"deny(403)",actionWarning:S.createWarning("lossy_conversion",t,"action",`Rule "${e}" has unrecognized action "${i}" \u2014 defaulted to Cloud Armor's "deny(403)".`)}}}function qr(i){let e=i.match(/^(\d+)([smhd])$/);if(!e||!e[1]||!e[2])throw new Error(`Invalid window format: ${i}`);let t=parseInt(e[1],10),o={s:1,m:60,h:3600,d:86400}[e[2]];if(o===void 0)throw new Error(`Invalid time unit: ${e[2]}`);return t*o}var $=class{static vercelToUnified=Ji;static unifiedToVercel=Qi;static vercelIPToUnified=Xi;static cloudflareToUnified=rn;static unifiedToCloudflare=sn;static unifiedIPToCloudflare=an;static fastlyToUnified=dn;static unifiedToFastly=un;static fastlyListEntriesToUnified=pn;static unifiedIPsToFastlyEntries=fn;static gcpToUnified=An;static unifiedToGcp=En;static gcpToUnifiedIP=$n;static unifiedIPToGcp=Pn;static gcpLooksLikeIpRule=Tn};ue();function Le(i,e){if(i===e)return!0;if(typeof i!="object"||i===null||typeof e!="object"||e===null||Array.isArray(i)!==Array.isArray(e))return!1;let t=Object.keys(i),n=Object.keys(e);if(t.length!==n.length)return!1;for(let o of t)if(!n.includes(o)||!Le(i[o],e[o]))return!1;return!0}function fe(i){let{id:e,...t}=i;return t}function at(i){return[...i].sort((e,t)=>{let n=e.priority!==void 0,o=t.priority!==void 0;return!n&&!o?0:n?o?e.priority-t.priority:-1:1})}function Pt(i){return i.some(e=>e.priority!==void 0)}var Ke=class extends $e{constructor(t){super();this.client=t}client;name="vercel";async fetchConfig(t){try{r.debug("Fetching Vercel firewall configuration");let n=await this.client.fetchFirewallConfig(t),o=n.rules.map(a=>{let l=$.vercelToUnified(a);return l.warnings.length>0&&l.warnings.forEach(c=>{let{TranslationWarningSystem:d}=(ue(),Re(Oe)),u=d.formatWarning(c);r.warn(`Rule ${a.name}:
25
25
  ${u}`)}),l.result}),s=n.ips.map(a=>$.vercelIPToUnified(a));return{version:"2.0",provider:"vercel",providers:{vercel:{projectId:this.client.projectId,...this.client.teamId?{teamId:this.client.teamId}:{}}},rules:o,ips:s,metadata:{version:n.version,updatedAt:n.updatedAt}}}catch(n){throw r.error("Error fetching Vercel configuration:",n),new Error("Failed to fetch Vercel firewall configuration")}}async syncRules(t,n={}){let{dryRun:o=!1}=n;try{let{OperationSafety:s}=(ct(),Re(lt)),a=await s.performDryRunValidation(t,"sync rules",async m=>await this.getChanges(m,{allowCreate:!o}));if(!a.valid)throw new Error(`Dry-run validation failed: ${a.issues.join(", ")}`);let l=a.changes,{rulesToAdd:c,rulesToUpdate:d,rulesToDelete:u,version:p}=l,f=l.ipsToAdd||[],y=l.ipsToUpdate||[],w=l.ipsToDelete||[];if(o)return r.info("Dry run mode. The following changes would be made:"),r.info(`Custom Rules - Add: ${c.length}, Update: ${d.length}, Delete: ${u.length}`),r.info(`IP Rules - Add: ${f.length}, Update: ${y.length}, Delete: ${w.length}`),{success:!0,rulesAdded:0,rulesUpdated:0,rulesDeleted:0,ipsAdded:0,ipsUpdated:0,ipsDeleted:0,version:p,warnings:a.warnings};let C=s.assessOperationRisk(l,t);if(!await s.confirmDestructiveOperation({operation:"sync rules",target:`Vercel project ${this.client.projectId}`,changes:l,riskLevel:C,skipConfirmation:n.force||!1,dryRun:!1,allowDeletions:n.allowDeletions||!1}))throw new Error("Operation cancelled by user");let D=c.map(m=>$.unifiedToVercel(m).result),O=d.map(m=>$.unifiedToVercel(m).result),le=u.map(m=>$.unifiedToVercel(m).result),ce=f.map(m=>({id:m.id||"",ip:m.ip,hostname:m.hostname||"",action:m.action,notes:m.notes})),ge=y.map(m=>({id:m.id||"",ip:m.ip,hostname:m.hostname||"",action:m.action,notes:m.notes})),k=w.map(m=>({id:m.id||"",ip:m.ip,hostname:m.hostname||"",action:m.action,notes:m.notes})),re=[],me=[],he=[],Ie=[],we=[],E=[],g=[],v=(m,A)=>`${m}: ${A instanceof Error?A.message:String(A)}`,H=[];Pt(t.rules)&&t.rules.length>c.length&&H.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 se=[];for(let m of le)try{r.debug(`Deleting custom rule: ${m.id}`),await this.client.deleteFirewallRule(m),he.push(m),r.debug(`Custom rule deleted: ${m.id}`)}catch(A){r.error(`Failed to delete custom rule ${m.id}:`,A),g.push(v(`Failed to delete custom rule ${m.id}`,A))}for(let m of k)try{r.debug(`Deleting IP blocking rule: ${m.id}`),await this.client.deleteIPBlockingRule(m),E.push(m),r.debug(`IP blocking rule deleted: ${m.id}`)}catch(A){r.error(`Failed to delete IP blocking rule ${m.id}:`,A),g.push(v(`Failed to delete IP blocking rule ${m.id}`,A))}for(let m of D)try{r.debug(`Adding new custom rule: ${m.name}`);let A=await this.client.createFirewallRule(m);re.push(A),A.id&&A.id!==m.id&&se.push({oldId:m.id||void 0,newId:A.id,name:m.name}),r.debug(`New custom rule added: ${A.id}`)}catch(A){r.error(`Failed to add custom rule "${m.name}":`,A),g.push(v(`Failed to add custom rule "${m.name}"`,A))}for(let m of ce)try{r.debug(`Adding new IP blocking rule: ${m.ip}`);let A=await this.client.createIPBlockingRule(m);Ie.push(A),r.debug(`New IP blocking rule added: (hostname): ${A.hostname} (ip): ${A.ip}`)}catch(A){r.error(`Failed to add IP blocking rule ${m.ip}:`,A),g.push(v(`Failed to add IP blocking rule ${m.ip}`,A))}for(let m of O)try{r.debug(`Updating custom rule: ${m.id}`);let A=await this.client.updateFirewallRule(m);me.push(A),r.debug(`Custom rule updated: ${A.id}`)}catch(A){r.error(`Failed to update custom rule ${m.id}:`,A),g.push(v(`Failed to update custom rule ${m.id}`,A))}for(let m of ge)try{r.debug(`Updating IP blocking rule: ${m.id}`);let A=await this.client.updateIPBlockingRule(m);we.push(A),r.debug(`IP blocking rule updated: ${A.id}`)}catch(A){r.error(`Failed to update IP blocking rule ${m.id}:`,A),g.push(v(`Failed to update IP blocking rule ${m.id}`,A))}r.debug(`${ae.underline("Custom Rules:")} ${ae.green("Added:")} ${ae.green(re.length)}, ${ae.cyan("Updated:")} ${ae.cyan(me.length)}, ${ae.red("Deleted:")} ${ae.red(he.length)}`),r.debug(`${ae.underline("IP Rules:")} ${ae.green("Added:")} ${ae.green(Ie.length)}, ${ae.cyan("Updated:")} ${ae.cyan(we.length)}, ${ae.red("Deleted:")} ${ae.red(E.length)}`);let it=p,nt,xe=[...H];try{let m=await this.client.fetchFirewallConfig();it=m.version,nt=m.updatedAt;let A=new Set(m.rules.map(q=>q.id)),bi=new Set(m.ips.map(q=>q.id));for(let q of[...re,...me])q.id&&!A.has(q.id)&&xe.push(`Rule "${q.name}" (${q.id}) was synced but is missing from the post-sync remote config \u2014 a subsequent sync may re-create or misdiff it.`);for(let q of he)q.id&&A.has(q.id)&&xe.push(`Rule "${q.name}" (${q.id}) was deleted but still appears in the post-sync remote config.`);for(let q of[...Ie,...we])q.id&&!bi.has(q.id)&&xe.push(`IP rule "${q.ip}" (${q.id}) was synced but is missing from the post-sync remote config \u2014 a subsequent sync may re-create or misdiff it.`);for(let q of E)q.id&&bi.has(q.id)&&xe.push(`IP rule "${q.ip}" (${q.id}) was deleted but still appears in the post-sync remote config.`)}catch(m){r.error("Failed to fetch updated firewall configuration version after sync:",m),g.push(v("Failed to fetch updated configuration version after sync",m))}return{success:g.length===0,rulesAdded:re.length,rulesUpdated:me.length,rulesDeleted:he.length,ipsAdded:Ie.length,ipsUpdated:we.length,ipsDeleted:E.length,version:it,updatedAt:nt,idRemappings:se.length>0?se:void 0,errors:g.length>0?g:void 0,warnings:xe.length>0?xe:void 0}}catch(s){throw r.error("Error during sync:",s),new Error(`Failed to synchronize firewall rules: ${s instanceof Error?s.message:String(s)}`,{cause:s})}}async getChanges(t,n){let o=_e.safeParse(t);if(!o.success)throw new Error(`Invalid firewall configuration: ${o.error.message}`);try{r.debug("Fetching existing firewall configuration");let s=await this.client.fetchFirewallConfig(void 0,{allowCreate:n?.allowCreate??!0});r.debug(`Fetched ${s.rules.length} custom rules and ${s.ips.length} IP blocking rules`);let a=s.rules.map(w=>{let C=$.vercelToUnified(w);return C.warnings.length>0&&C.warnings.forEach(x=>{let{TranslationWarningSystem:D}=(ue(),Re(Oe)),O=D.formatWarning(x);r.warn(`Rule ${w.name}:
26
26
  ${O}`)}),C.result}),{toAdd:l,toUpdate:c,toDelete:d}=this.diffRules(at(o.data.rules),a),u=s.ips.map(w=>$.vercelIPToUnified(w)),{ipsToAdd:p,ipsToUpdate:f,ipsToDelete:y}=this.diffIPRules(o.data.ips||[],u);return{version:s.version,rulesToAdd:l,rulesToUpdate:c,rulesToDelete:d,ipsToAdd:p,ipsToUpdate:f,ipsToDelete:y,hasChanges:l.length>0||c.length>0||d.length>0||p.length>0||f.length>0||y.length>0}}catch(s){throw r.error("Error fetching existing firewall configuration:",s),new Error("Failed to fetch existing firewall configuration")}}getSupportedFeatures(){return{supportsCustomRules:!0,supportsIPBlocking:!0,supportsRateLimiting:!0,supportsGeoBlocking:!0,supportsManagedRules:!1,supportsRedirect:!0,supportsChallenge:!0}}async verifyCredentials(){return this.client.verifyCredentials()}validateConfig(t){let n=super.validateConfig(t),o=[...n.errors],s=[...n.warnings];t.provider&&t.provider!=="vercel"&&o.push({path:"provider",message:"Provider must be 'vercel' for VercelFirewallService",code:"INVALID_PROVIDER"});try{let a=De.safeParse(t);a.success||o.push({path:"root",message:`Schema validation failed: ${a.error.message}`,code:"SCHEMA_VALIDATION_FAILED"})}catch(a){r.debug("Schema validation error:",a)}return{valid:o.length===0,errors:o,warnings:s}}getHealthScore(t){let n=super.getHealthScore(t),o=[...n.issues],s=n.score;return t.rules.filter(l=>l.action?.type==="rate_limit").length===0&&(s-=10,o.push({severity:"info",category:"security",message:"No rate limiting rules configured",suggestion:"Consider adding rate limiting to protect against abuse"})),(!t.ips||t.ips.length===0)&&o.push({severity:"info",category:"security",message:"No IP blocking rules configured",suggestion:"Consider blocking known malicious IPs"}),{score:Math.max(0,s),grade:n.grade,issues:o,recommendations:n.recommendations}}diffRules(t,n){let o=[],s=[],a=[...n];for(let l of t){let c=n.find(d=>d.id===l.id);if(!c)o.push(l);else{Le(fe(l),fe(c))||s.push({...l,id:c.id});let d=a.findIndex(u=>u.id===c.id);d!==-1&&a.splice(d,1)}}return{toAdd:o,toUpdate:s,toDelete:a}}diffIPRules(t,n){let o=[],s=[],a=[...n];for(let l of t){let c=n.find(d=>d.id===l.id);if(!c)r.debug("Rule not found in existing rules:",{configRule:l}),o.push(l);else{Le(fe(l),fe(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}}};P();var dt=class{static fromEnv(){let e=process.env.VERCEL_TOKEN,t=process.env.VERCEL_PROJECT_ID,n=process.env.VERCEL_TEAM_ID;if(!e)throw new Error("VERCEL_TOKEN environment variable is required");if(!t)throw new Error("VERCEL_PROJECT_ID environment variable is required");let o=new Me(t,n,e);return new Ke(o)}static fromConfig(e){let t=e.token||process.env.VERCEL_TOKEN,n=e.projectId||process.env.VERCEL_PROJECT_ID,o=e.teamId||process.env.VERCEL_TEAM_ID;if(!t)throw new Error("Vercel API token is required (provide token or set VERCEL_TOKEN env var)");if(!n)throw new Error("Vercel project ID is required (provide projectId or set VERCEL_PROJECT_ID env var)");r.debug("Creating Vercel provider with config:",{projectId:n,teamId:o});let s=new Me(n,o,t);return new Ke(s)}static create(e,t,n){return this.fromConfig({projectId:e,teamId:t,token:n})}};import{createHash as Zr}from"crypto";P();import He from"chalk";var h=class i extends Error{code;suggestion;details;docsUrl;cause;constructor(e){super(e.message),this.name="DoormanError",this.code=e.code,this.suggestion=e.suggestion,this.details=e.details,this.docsUrl=e.docsUrl,this.cause=e.cause,Error.captureStackTrace&&Error.captureStackTrace(this,i)}format(){let e=[];return e.push(He.red.bold(`[${this.code}] ${this.message}`)),e.push(""),this.suggestion&&(e.push(He.yellow.bold("Suggestion:")),e.push(` ${this.suggestion}`),e.push("")),this.details&&Object.keys(this.details).length>0&&(e.push(He.dim("Details:")),Object.entries(this.details).forEach(([t,n])=>{let o=typeof n=="object"?JSON.stringify(n,null,2).split(`
@@ -35,17 +35,17 @@ ${O}`)}),C.result}),{toAdd:l,toUpdate:c,toDelete:d}=this.diffRules(at(o.data.rul
35
35
  \u2022 Ensure your API token has Account:Read permissions
36
36
  \u2022 Verify the account ID is correct
37
37
  \u2022 Check token permissions in Cloudflare dashboard`)):n.includes("not found")?(r.warn(`\u{1F6AB} Lists API resource not found during ${t}. Falling back to individual IP rules.`),r.info("\u{1F4A1} This may be temporary - the List will be recreated on next sync if Lists API is available.")):n.includes("rate limit")||n.includes("quota")?(r.warn(`\u{1F6AB} Lists API rate limited during ${t}. Falling back to individual IP rules.`),r.info("\u{1F4A1} Lists API rate limit reached. Individual rules will be used this time. Consider retrying later or upgrading your Cloudflare plan.")):(r.warn(`\u{1F6AB} Lists API error during ${t}: ${e.message}. Falling back to individual IP rules.`),r.info("\u{1F4A1} Lists API temporarily unavailable. Using individual IP rules as fallback. Check Cloudflare status if this persists."))}else r.warn(`\u{1F6AB} Unknown Lists API error during ${t}. Falling back to individual IP rules.`)}};var pt=class{static create(e,t,n){let o=e||process.env.CLOUDFLARE_API_TOKEN,s=t||process.env.CLOUDFLARE_ZONE_ID,a=n||process.env.CLOUDFLARE_ACCOUNT_ID;if(!o)throw new Error("Cloudflare API token is required. Set CLOUDFLARE_API_TOKEN environment variable or pass as parameter.");if(!s)throw new Error("Cloudflare Zone ID is required. Set CLOUDFLARE_ZONE_ID environment variable or pass as parameter.");return new ut(o,s,a)}static fromEnv(){return this.create()}static fromConfig(e){return this.create(e.apiToken,e.zoneId,e.accountId)}};P();P();var Ln=process.env.DOORMAN_FASTLY_API_BASE_URL||"https://api.fastly.com",ft="doorman-managed-deny",gt="doorman-managed-allow",Ye=class extends Te{constructor(t,n){super(Ln,"fastly");this.workspaceId=t;this.apiToken=n}workspaceId;apiToken;async getAuthHeaders(){return{"Fastly-Key":this.apiToken}}workspacePath(...t){return["/ngwaf/v1/workspaces",this.workspaceId,...t].join("/")}async fetchRules(){return(await this.get(this.workspacePath("rules"))).data}async createRule(t){return this.post(this.workspacePath("rules"),t,{retries:0})}async updateRule(t,n){return this.patch(this.workspacePath("rules",t),n)}async deleteRule(t){await this.delete(this.workspacePath("rules",t))}async fetchLists(){return(await this.get(this.workspacePath("lists"))).data}async createList(t,n){return this.post(this.workspacePath("lists"),{name:t,type:"ip",description:"Managed by doorman \u2014 do not edit entries directly, they will be overwritten on the next sync.",entries:n},{retries:0})}async updateListEntries(t,n){return this.patch(this.workspacePath("lists",t),{entries:n})}async findList(t){return(await this.fetchLists()).find(o=>o.name===t&&o.type==="ip")??null}async getOrCreateList(t){let n=await this.findList(t);return n||(r.debug(`Creating Fastly list "${t}" in workspace ${this.workspaceId}`),this.createList(t,[]))}async fetchWorkspace(){return this.get(this.workspacePath())}async verifyCredentials(){try{return await this.fetchWorkspace(),!0}catch(t){return r.debug("Credential verification failed:",t),!1}}};P();var Je=class extends $e{constructor(t){super();this.client=t}client;name="fastly";async fetchConfig(){try{r.debug("Fetching Fastly Next-Gen WAF configuration");let[t,n,o]=await Promise.all([this.client.fetchRules(),this.client.findList(ft),this.client.findList(gt)]),s=this.translateFastlyRules(t),a=[...$.fastlyListEntriesToUnified(n?.entries??[],"deny"),...$.fastlyListEntriesToUnified(o?.entries??[],"allow")];return{version:"2.0",provider:"fastly",providers:{fastly:{workspaceId:this.client.workspaceId}},rules:s,ips:a}}catch(t){throw r.error("Error fetching Fastly configuration:",t),new Error("Failed to fetch Fastly Next-Gen WAF configuration")}}translateFastlyRules(t){let n=[];for(let o of t){if(o.type!=="request"&&o.type!=="rate_limit"){r.debug(`Skipping Fastly rule ${o.id} \u2014 type "${o.type}" is not a request/rate_limit rule doorman manages`);continue}let s=$.fastlyToUnified(o);s.warnings.length>0&&s.warnings.forEach(a=>{let{TranslationWarningSystem:l}=(ue(),Re(Oe));r.warn(`Rule ${o.description||o.id}:
38
- ${l.formatWarning(a)}`)}),n.push(s.result)}return n}async syncRules(t,n={}){let{dryRun:o=!1}=n;try{let{OperationSafety:s}=(ct(),Re(lt)),a=await s.performDryRunValidation(t,"sync rules",async g=>this.getChanges(g));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.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}, Delete: ${f.length}`),{success:!0,rulesAdded:0,rulesUpdated:0,rulesDeleted:0,ipsAdded:0,ipsUpdated:0,ipsDeleted:0,warnings:a.warnings};let y=s.assessOperationRisk(l,t);if(!await s.confirmDestructiveOperation({operation:"sync rules",target:`Fastly workspace ${this.client.workspaceId}`,changes:l,riskLevel:y,skipConfirmation:n.force||!1,dryRun:!1,allowDeletions:n.allowDeletions||!1}))throw new Error("Operation cancelled by user");let C=[];Pt(t.rules)&&C.push("Rule `priority` has no effect on Fastly: Next-Gen WAF rules are evaluated independently and have no API-exposed ordering concept to honour.");let x=c.map(g=>({rule:g,translated:$.unifiedToFastly(g)})),D=d.map(g=>({rule:g,translated:$.unifiedToFastly(g)})),O=[],le=(g,v)=>`${g}: ${v instanceof Error?v.message:String(v)}`,ce=[],ge=[],k=[],re=[];for(let g of u)if(g.id)try{r.debug(`Deleting Fastly rule: ${g.id}`),await this.client.deleteRule(g.id),k.push(g.id)}catch(v){r.error(`Failed to delete Fastly rule ${g.id}:`,v),O.push(le(`Failed to delete rule ${g.id}`,v))}for(let{rule:g,translated:v}of x){let H=v.result;try{r.debug(`Adding new Fastly rule: ${H.description}`);let se=await this.client.createRule(H);ce.push(se),re.push({oldId:g.id||void 0,newId:se.id,name:H.description})}catch(se){r.error(`Failed to add Fastly rule "${H.description}":`,se),O.push(le(`Failed to add rule "${H.description}"`,se))}}for(let{rule:g,translated:v}of D)if(g.id)try{r.debug(`Updating Fastly rule: ${g.id}`);let H=await this.client.updateRule(g.id,v.result);ge.push(H)}catch(H){r.error(`Failed to update Fastly rule ${g.id}:`,H),O.push(le(`Failed to update rule ${g.id}`,H))}let me=0,he=0,Ie=t.ips||[],{result:we,warnings:E}=$.unifiedIPsToFastlyEntries(Ie);E.forEach(g=>C.push(g.message));try{for(let[g,v]of[[ft,we.denyEntries],[gt,we.allowEntries]]){let se=(await this.client.findList(g))?.entries??[],it=v.filter(m=>!se.includes(m)),nt=se.filter(m=>!v.includes(m));if(it.length===0&&nt.length===0)continue;let xe=await this.client.getOrCreateList(g);await this.client.updateListEntries(xe.id,v),me+=it.length,he+=nt.length}}catch(g){r.error("Failed to sync Fastly IP lists:",g),O.push(le("Failed to sync IP lists",g))}return r.debug(`Custom Rules: Added: ${ce.length}, Updated: ${ge.length}, Deleted: ${k.length}`),r.debug(`IP Rules: Added: ${me}, Deleted: ${he}`),{success:O.length===0,rulesAdded:ce.length,rulesUpdated:ge.length,rulesDeleted:k.length,ipsAdded:me,ipsUpdated:0,ipsDeleted:he,idRemappings:re.length>0?re:void 0,errors:O.length>0?O:void 0,warnings:C.length>0?C: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=_e.safeParse(t);if(!n.success)throw new Error(`Invalid firewall configuration: ${n.error.message}`);try{r.debug("Fetching existing Fastly configuration");let[o,s,a]=await Promise.all([this.client.fetchRules(),this.client.findList(ft),this.client.findList(gt)]),l=this.translateFastlyRules(o),{toAdd:c,toUpdate:d,toDelete:u}=this.diffRules(n.data.rules,l),p=s?.entries??[],f=a?.entries??[],y=[...$.fastlyListEntriesToUnified(p,"deny"),...$.fastlyListEntriesToUnified(f,"allow")],{ipsToAdd:w,ipsToDelete:C}=this.diffIPs(n.data.ips||[],y);return{rulesToAdd:c,rulesToUpdate:d,rulesToDelete:u,ipsToAdd:w,ipsToUpdate:[],ipsToDelete:C,hasChanges:c.length>0||d.length>0||u.length>0||w.length>0||C.length>0}}catch(o){throw r.error("Error fetching existing Fastly configuration:",o),new Error("Failed to fetch existing Fastly configuration")}}getSupportedFeatures(){return{supportsCustomRules:!0,supportsIPBlocking:!0,supportsRateLimiting:!0,supportsManagedRules:!1,supportsGeoBlocking:!0,supportsRedirect:!0,supportsChallenge:!0}}async verifyCredentials(){return this.client.verifyCredentials()}validateConfig(t){let n=super.validateConfig(t),o=[...n.errors],s=[...n.warnings];return t.provider&&t.provider!=="fastly"&&o.push({path:"provider",message:"Provider must be 'fastly' for FastlyFirewallService",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}}diffRules(t,n){let o=[],s=[],a=[...n];for(let l of t){let c=n.find(d=>d.id===l.id);if(!c)o.push(l);else{Le(fe(l),fe(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}}diffIPs(t,n){let o=l=>`${l.action}:${l.ip}`,s=new Set(n.map(o)),a=new Set(t.map(o));return{ipsToAdd:t.filter(l=>!s.has(o(l))),ipsToDelete:n.filter(l=>!a.has(o(l)))}}};P();var mt=class{static fromEnv(){let e=process.env.FASTLY_API_TOKEN,t=process.env.FASTLY_WORKSPACE_ID;if(!e)throw new Error("FASTLY_API_TOKEN environment variable is required");if(!t)throw new Error("FASTLY_WORKSPACE_ID environment variable is required");let n=new Ye(t,e);return new Je(n)}static fromConfig(e){let t=e.apiToken||process.env.FASTLY_API_TOKEN,n=e.workspaceId||process.env.FASTLY_WORKSPACE_ID;if(!t)throw new Error("Fastly API token is required (provide apiToken or set FASTLY_API_TOKEN env var)");if(!n)throw new Error("Fastly workspace ID is required (provide workspaceId or set FASTLY_WORKSPACE_ID env var)");r.debug("Creating Fastly provider with config:",{workspaceId:n});let o=new Ye(n,t);return new Je(o)}static create(e,t){return this.fromConfig({workspaceId:e,apiToken:t})}};P();import{GoogleAuth as Jr}from"google-auth-library";var Dn=process.env.DOORMAN_GCP_API_BASE_URL||"https://compute.googleapis.com/compute/v1",Qr="https://www.googleapis.com/auth/compute",Xr=1e3,xn=6e4,Qe=class extends Te{auth;projectId;policyName;constructor(e,t,n){super(`${Dn}/projects/${e}/global`,"gcp"),this.projectId=e,this.policyName=t,this.auth=new Jr({scopes:[Qr],...n?{keyFile:n}:{}})}async getAuthHeaders(){let t=await(await this.auth.getClient()).getRequestHeaders();return Object.fromEntries(t.entries())}policyPath(...e){return["/securityPolicies",this.policyName,...e].join("/")}async getPolicy(){return this.get(this.policyPath())}async addRule(e){let t=await this.post(this.policyPath("addRule"),e);await this.waitForOperation(t)}async patchRule(e,t){let n=await this.post(`${this.policyPath("patchRule")}?priority=${e}`,t);await this.waitForOperation(n)}async removeRule(e){let t=await this.post(`${this.policyPath("removeRule")}?priority=${e}`);await this.waitForOperation(t)}async waitForOperation(e){let t=Date.now()+xn,n=e;for(;n.status!=="DONE";){if(Date.now()>=t)throw new Error(`gcp operation "${n.name}" did not complete within ${xn}ms (last status: ${n.status})`);await this.delay(Xr),n=await this.get(`/operations/${n.name}`)}if(n.httpErrorStatusCode||n.error){let o=n.error?.errors?.map(s=>s.message).join(", ")||n.httpErrorMessage;throw new Error(`gcp operation "${n.name}" failed: ${o||"unknown error"}`)}n.warnings?.length&&n.warnings.forEach(o=>r.warn(`gcp operation "${n.name}" warning: ${o.message}`))}async verifyCredentials(){try{return await this.getPolicy(),!0}catch(e){return r.debug("Credential verification failed:",e),!1}}};P();function kt(i){return i!==void 0&&/^\d+$/.test(i)?Number(i):void 0}var Xe=class extends $e{constructor(t){super();this.client=t}client;name="gcp";async fetchConfig(){try{r.debug("Fetching Cloud Armor security policy");let t=await this.client.getPolicy(),{rules:n,ips:o}=this.translatePolicy(t);return{version:"2.0",provider:"gcp",providers:{gcp:{projectId:this.client.projectId,policyName:this.client.policyName}},rules:n,ips:o}}catch(t){throw r.error("Error fetching Cloud Armor configuration:",t),new Error("Failed to fetch Cloud Armor security policy")}}translatePolicy(t,n){let o=[],s=[];for(let a of t.rules){let l=Ge(a.match.expr.expression);if(!(n?.has(String(a.priority))??!1)&&$.gcpLooksLikeIpRule(a,l)){s.push($.gcpToUnifiedIP(a,l));continue}let d=$.gcpToUnified(a,l);d.warnings.length>0&&d.warnings.forEach(u=>{let{TranslationWarningSystem:p}=(ue(),Re(Oe));r.warn(`Rule ${a.description||a.priority}:
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}=(ct(),Re(lt)),a=await s.performDryRunValidation(t,"sync rules",async E=>this.getChanges(E));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||[],y=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: ${y.length}`),{success:!0,rulesAdded:0,rulesUpdated:0,rulesDeleted:0,ipsAdded:0,ipsUpdated:0,ipsDeleted:0,warnings:a.warnings};let w=s.assessOperationRisk(l,t);if(!await s.confirmDestructiveOperation({operation:"sync rules",target:`Cloud Armor policy ${this.client.policyName}`,changes:l,riskLevel:w,skipConfirmation:n.force||!1,dryRun:!1,allowDeletions:n.allowDeletions||!1}))throw new Error("Operation cancelled by user");let x=[],D=[],O=(E,g)=>`${E}: ${g instanceof Error?g.message:String(g)}`,le=await this.client.getPolicy(),ce=new Set(le.rules.map(E=>E.priority)),ge=1e3,k=()=>{for(;ce.has(ge);)ge+=1e3;return ce.add(ge),ge},re=[];for(let E of[...u,...y]){let g=kt(E.id);if(g!==void 0)try{r.debug(`Removing Cloud Armor rule at priority ${g}`),await this.client.removeRule(g)}catch(v){r.error(`Failed to remove Cloud Armor rule at priority ${g}:`,v),D.push(O(`Failed to delete rule at priority ${g}`,v))}}let me=0;for(let E of c){let g=E.priority??k();try{let v=$.unifiedToGcp({...E,priority:g});v.warnings.forEach(H=>x.push(H.message)),r.debug(`Adding Cloud Armor rule at priority ${g}: ${v.result.description}`),await this.client.addRule(v.result),me++,re.push({oldId:E.id,newId:String(g),name:E.name})}catch(v){r.error(`Failed to add Cloud Armor rule "${E.name}":`,v),D.push(O(`Failed to add rule "${E.name}"`,v))}}let he=0;for(let E of d){let g=kt(E.id);if(g!==void 0)try{let v=$.unifiedToGcp(E);v.warnings.forEach(se=>x.push(se.message)),E.priority!==void 0&&E.priority!==g?(r.debug(`Relocating Cloud Armor rule from priority ${g} to ${E.priority}`),await this.client.removeRule(g),await this.client.addRule(v.result),re.push({oldId:E.id,newId:String(E.priority),name:E.name})):(r.debug(`Updating Cloud Armor rule at priority ${g}`),await this.client.patchRule(g,v.result)),he++}catch(v){r.error(`Failed to update Cloud Armor rule at priority ${g}:`,v),D.push(O(`Failed to update rule at priority ${g}`,v))}}let Ie=0;for(let E of p){let g=kt(E.id)??k();try{let v=$.unifiedIPToGcp(E,g);r.debug(`Adding Cloud Armor IP rule at priority ${g}: ${E.ip}`),await this.client.addRule(v),Ie++,re.push({oldId:E.id,newId:String(g),name:E.ip})}catch(v){r.error(`Failed to add Cloud Armor IP rule for ${E.ip}:`,v),D.push(O(`Failed to add IP rule for ${E.ip}`,v))}}let we=0;for(let E of f){let g=kt(E.id);if(g!==void 0)try{r.debug(`Updating Cloud Armor IP rule at priority ${g}: ${E.ip}`),await this.client.patchRule(g,$.unifiedIPToGcp(E,g)),we++}catch(v){r.error(`Failed to update Cloud Armor IP rule at priority ${g}:`,v),D.push(O(`Failed to update IP rule at priority ${g}`,v))}}return r.debug(`Custom Rules: Added: ${me}, Updated: ${he}, Deleted: ${u.length}`),r.debug(`IP Rules: Added: ${Ie}, Updated: ${we}, Deleted: ${y.length}`),{success:D.length===0,rulesAdded:me,rulesUpdated:he,rulesDeleted:u.length,ipsAdded:Ie,ipsUpdated:we,ipsDeleted:y.length,idRemappings:re.length>0?re:void 0,errors:D.length>0?D:void 0,warnings:x.length>0?x: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=_e.safeParse(t);if(!n.success)throw new Error(`Invalid firewall configuration: ${n.error.message}`);try{r.debug("Fetching existing Cloud Armor configuration");let o=await this.client.getPolicy(),s=new Set(t.rules.map(w=>w.id).filter(w=>w!==void 0)),{rules:a,ips:l}=this.translatePolicy(o,s),{toAdd:c,toUpdate:d,toDelete:u}=this.diffItems(t.rules,a,(w,C)=>Le(fe(w),fe(C))),{toAdd:p,toUpdate:f,toDelete:y}=this.diffItems(t.ips||[],l,(w,C)=>Le(fe(w),fe(C)));return{rulesToAdd:c,rulesToUpdate:d,rulesToDelete:u,ipsToAdd:p,ipsToUpdate:f,ipsToDelete:y,hasChanges:c.length>0||d.length>0||u.length>0||p.length>0||f.length>0||y.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}}};P();var ht=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 Qe(e,t,n);return new Xe(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 Qe(t,n,o);return new Xe(s)}static create(e,t,n){return this.fromConfig({projectId:e,policyName:t,serviceAccountKeyPath:n})}};async function On(i){let e;if(i.provider)e=i.provider,r.debug(`Using explicitly specified provider: ${e}`);else{let t=bt.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 rs():(e="vercel",r.warn("No provider detected, defaulting to Vercel"))}try{let t=await is(e,i);return os(e,t)}catch(t){throw r.error(`Failed to initialize ${e} provider:`,t),t}}function ts(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 is(i,e){let t=We[i],n=e.config,o={};for(let l of t.fields)o[l.key]=ts(n,i,l);let s=Ki(t,{explicit:e.credentials,config:o}),a=Hi(t,s);if(a.length===0)return s;if(e.interactive===!1){let l=a.map(c=>`--${c.key}`).join(", ");throw new Error(`${yt(i)} credentials missing. Provide ${l}.`)}r.warn(`Missing ${yt(i)} credentials`),r.info(`Please provide your ${yt(i)} credentials:`);for(let l of t.fields){if(s[l.key])continue;let c=l.promptMessage??ns(l),d=l.secret?await Wi(c):await b(c,{type:"text"});s[l.key]=d||void 0}return s}function ns(i){let e=i.required?"":" (optional)",t=i.secret?" ":"";return`${i.label}${e}:${t}`}function os(i,e){switch(i){case"vercel":{let t={token:e.token,projectId:e.projectId,teamId:e.teamId};return{provider:dt.fromConfig(t),vercelCredentials:t}}case"cloudflare":return{provider:pt.fromConfig({apiToken:e.apiToken,zoneId:e.zoneId,accountId:e.accountId})};case"fastly":return{provider:mt.fromConfig({apiToken:e.apiToken,workspaceId:e.workspaceId})};case"gcp":return{provider:ht.fromConfig({projectId:e.projectId,policyName:e.policyName,serviceAccountKeyPath:e.serviceAccountKeyPath})};default:throw new Error(`Unknown provider: ${i}`)}}async function rs(){r.info(es.yellow("Unable to auto-detect firewall provider.")),r.info(`Please select a provider:
40
- `),M.forEach((s,a)=>{r.info(` ${a+1}. ${yt(s)}`)}),r.info("");let i=await b(`Select provider (1-${M.length}):`,{type:"text"}),e=String(i).trim().toLowerCase(),t=M.find(s=>s===e);if(t)return t;let n=Number(e);if(Number.isInteger(n)&&n>=1&&n<=M.length)return M[n-1];let o=M[0];return r.warn(`Invalid choice: ${i}, defaulting to ${yt(o)}`),o}function yt(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 ii=new Map;for(let i of M)for(let e of We[i].fields){let t=ii.get(e.key)??[];t.push({provider:i,field:e}),ii.set(e.key,t)}var ee=Object.fromEntries(Array.from(ii.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 G(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 te(i,e){try{i.debug&&(r.level=as.debug);let t;if(i.optionalConfig)try{t=await j(i.config,"optional")}catch{t={}}else i.skipValidation?t=await j(i.config,"raw"):t=await j(i.config,"required");let{provider:n,vercelCredentials:o}=await On({provider:i.provider,config:t,interactive:!i.ci,credentials:G(i)}),s=o?.token??"",a=o?.projectId??"",l=o?.teamId??"";await e({config:t,provider:n,token:s,projectId:a,teamId:l})}catch(t){Q(t,i.errorContext)}}var fs="backup",gs="Backup or restore firewall configurations",ms={config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:X,...ee,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}},hs=async i=>{try{i.debug&&(r.level=ps.debug);let e=i.output||"./backups";if(i.list){if(!St(e)){r.info(ie.yellow("No backup directory found."));return}let t=cs(e).filter(n=>n.endsWith(".json")).map(n=>{let o=ni(e,n),s=ds(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(ie.yellow("No backups found."));return}r.log(ie.bold(`
38
+ ${l.formatWarning(a)}`)}),n.push(s.result)}return n}async syncRules(t,n={}){let{dryRun:o=!1}=n;try{let{OperationSafety:s}=(ct(),Re(lt)),a=await s.performDryRunValidation(t,"sync rules",async g=>this.getChanges(g));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.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}, Delete: ${f.length}`),{success:!0,rulesAdded:0,rulesUpdated:0,rulesDeleted:0,ipsAdded:0,ipsUpdated:0,ipsDeleted:0,warnings:a.warnings};let y=s.assessOperationRisk(l,t);if(!await s.confirmDestructiveOperation({operation:"sync rules",target:`Fastly workspace ${this.client.workspaceId}`,changes:l,riskLevel:y,skipConfirmation:n.force||!1,dryRun:!1,allowDeletions:n.allowDeletions||!1}))throw new Error("Operation cancelled by user");let C=[];Pt(t.rules)&&C.push("Rule `priority` has no effect on Fastly: Next-Gen WAF rules are evaluated independently and have no API-exposed ordering concept to honour.");let x=c.map(g=>({rule:g,translated:$.unifiedToFastly(g)})),D=d.map(g=>({rule:g,translated:$.unifiedToFastly(g)})),O=[],le=(g,v)=>`${g}: ${v instanceof Error?v.message:String(v)}`,ce=[],ge=[],k=[],re=[];for(let g of u)if(g.id)try{r.debug(`Deleting Fastly rule: ${g.id}`),await this.client.deleteRule(g.id),k.push(g.id)}catch(v){r.error(`Failed to delete Fastly rule ${g.id}:`,v),O.push(le(`Failed to delete rule ${g.id}`,v))}for(let{rule:g,translated:v}of x){let H=v.result;try{r.debug(`Adding new Fastly rule: ${H.description}`);let se=await this.client.createRule(H);ce.push(se),re.push({oldId:g.id||void 0,newId:se.id,name:H.description})}catch(se){r.error(`Failed to add Fastly rule "${H.description}":`,se),O.push(le(`Failed to add rule "${H.description}"`,se))}}for(let{rule:g,translated:v}of D)if(g.id)try{r.debug(`Updating Fastly rule: ${g.id}`);let H=await this.client.updateRule(g.id,v.result);ge.push(H)}catch(H){r.error(`Failed to update Fastly rule ${g.id}:`,H),O.push(le(`Failed to update rule ${g.id}`,H))}let me=0,he=0,Ie=t.ips||[],{result:we,warnings:E}=$.unifiedIPsToFastlyEntries(Ie);E.forEach(g=>C.push(g.message));try{for(let[g,v]of[[ft,we.denyEntries],[gt,we.allowEntries]]){let se=(await this.client.findList(g))?.entries??[],it=v.filter(m=>!se.includes(m)),nt=se.filter(m=>!v.includes(m));if(it.length===0&&nt.length===0)continue;let xe=await this.client.getOrCreateList(g);await this.client.updateListEntries(xe.id,v),me+=it.length,he+=nt.length}}catch(g){r.error("Failed to sync Fastly IP lists:",g),O.push(le("Failed to sync IP lists",g))}return r.debug(`Custom Rules: Added: ${ce.length}, Updated: ${ge.length}, Deleted: ${k.length}`),r.debug(`IP Rules: Added: ${me}, Deleted: ${he}`),{success:O.length===0,rulesAdded:ce.length,rulesUpdated:ge.length,rulesDeleted:k.length,ipsAdded:me,ipsUpdated:0,ipsDeleted:he,idRemappings:re.length>0?re:void 0,errors:O.length>0?O:void 0,warnings:C.length>0?C: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=_e.safeParse(t);if(!n.success)throw new Error(`Invalid firewall configuration: ${n.error.message}`);try{r.debug("Fetching existing Fastly configuration");let[o,s,a]=await Promise.all([this.client.fetchRules(),this.client.findList(ft),this.client.findList(gt)]),l=this.translateFastlyRules(o),{toAdd:c,toUpdate:d,toDelete:u}=this.diffRules(n.data.rules,l),p=s?.entries??[],f=a?.entries??[],y=[...$.fastlyListEntriesToUnified(p,"deny"),...$.fastlyListEntriesToUnified(f,"allow")],{ipsToAdd:w,ipsToDelete:C}=this.diffIPs(n.data.ips||[],y);return{rulesToAdd:c,rulesToUpdate:d,rulesToDelete:u,ipsToAdd:w,ipsToUpdate:[],ipsToDelete:C,hasChanges:c.length>0||d.length>0||u.length>0||w.length>0||C.length>0}}catch(o){throw r.error("Error fetching existing Fastly configuration:",o),new Error("Failed to fetch existing Fastly configuration")}}getSupportedFeatures(){return{supportsCustomRules:!0,supportsIPBlocking:!0,supportsRateLimiting:!0,supportsManagedRules:!1,supportsGeoBlocking:!0,supportsRedirect:!0,supportsChallenge:!0}}async verifyCredentials(){return this.client.verifyCredentials()}validateConfig(t){let n=super.validateConfig(t),o=[...n.errors],s=[...n.warnings];return t.provider&&t.provider!=="fastly"&&o.push({path:"provider",message:"Provider must be 'fastly' for FastlyFirewallService",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}}diffRules(t,n){let o=[],s=[],a=[...n];for(let l of t){let c=n.find(d=>d.id===l.id);if(!c)o.push(l);else{Le(fe(l),fe(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}}diffIPs(t,n){let o=l=>`${l.action}:${l.ip}`,s=new Set(n.map(o)),a=new Set(t.map(o));return{ipsToAdd:t.filter(l=>!s.has(o(l))),ipsToDelete:n.filter(l=>!a.has(o(l)))}}};P();var mt=class{static fromEnv(){let e=process.env.FASTLY_API_TOKEN,t=process.env.FASTLY_WORKSPACE_ID;if(!e)throw new Error("FASTLY_API_TOKEN environment variable is required");if(!t)throw new Error("FASTLY_WORKSPACE_ID environment variable is required");let n=new Ye(t,e);return new Je(n)}static fromConfig(e){let t=e.apiToken||process.env.FASTLY_API_TOKEN,n=e.workspaceId||process.env.FASTLY_WORKSPACE_ID;if(!t)throw new Error("Fastly API token is required (provide apiToken or set FASTLY_API_TOKEN env var)");if(!n)throw new Error("Fastly workspace ID is required (provide workspaceId or set FASTLY_WORKSPACE_ID env var)");r.debug("Creating Fastly provider with config:",{workspaceId:n});let o=new Ye(n,t);return new Je(o)}static create(e,t){return this.fromConfig({workspaceId:e,apiToken:t})}};P();import{GoogleAuth as Jr}from"google-auth-library";var Dn=process.env.DOORMAN_GCP_API_BASE_URL||"https://compute.googleapis.com/compute/v1",Qr="https://www.googleapis.com/auth/compute",Xr=250,es=1e3,xn=6e4,Qe=class extends Te{auth;projectId;policyName;constructor(e,t,n){super(`${Dn}/projects/${e}/global`,"gcp"),this.projectId=e,this.policyName=t,this.auth=new Jr({scopes:[Qr],...n?{keyFile:n}:{}})}async getAuthHeaders(){let t=await(await this.auth.getClient()).getRequestHeaders();return Object.fromEntries(t.entries())}policyPath(...e){return["/securityPolicies",this.policyName,...e].join("/")}async getPolicy(){return this.get(this.policyPath())}async addRule(e){let t=await this.post(this.policyPath("addRule"),e);await this.waitForOperation(t)}async patchRule(e,t){let n=await this.post(`${this.policyPath("patchRule")}?priority=${e}`,t);await this.waitForOperation(n)}async removeRule(e){let t=await this.post(`${this.policyPath("removeRule")}?priority=${e}`);await this.waitForOperation(t)}async waitForOperation(e){let t=Date.now()+xn,n=e,o=Xr;for(;n.status!=="DONE";){if(Date.now()>=t)throw new Error(`gcp operation "${n.name}" did not complete within ${xn}ms (last status: ${n.status})`);let s=Math.random()*.1*o;await this.delay(o+s),n=await this.get(`/operations/${n.name}`),o=Math.min(o*2,es)}if(n.httpErrorStatusCode||n.error){let s=n.error?.errors?.map(a=>a.message).join(", ")||n.httpErrorMessage;throw new Error(`gcp operation "${n.name}" failed: ${s||"unknown error"}`)}n.warnings?.length&&n.warnings.forEach(s=>r.warn(`gcp operation "${n.name}" warning: ${s.message}`))}async verifyCredentials(){try{return await this.getPolicy(),!0}catch(e){return r.debug("Credential verification failed:",e),!1}}};P();function kt(i){return i!==void 0&&/^\d+$/.test(i)?Number(i):void 0}var Xe=class extends $e{constructor(t){super();this.client=t}client;name="gcp";async fetchConfig(){try{r.debug("Fetching Cloud Armor security policy");let t=await this.client.getPolicy(),{rules:n,ips:o}=this.translatePolicy(t);return{version:"2.0",provider:"gcp",providers:{gcp:{projectId:this.client.projectId,policyName:this.client.policyName}},rules:n,ips:o}}catch(t){throw r.error("Error fetching Cloud Armor configuration:",t),new Error("Failed to fetch Cloud Armor security policy")}}translatePolicy(t,n){let o=[],s=[];for(let a of t.rules){let l=Ge(a.match.expr.expression);if(!(n?.has(String(a.priority))??!1)&&$.gcpLooksLikeIpRule(a,l)){s.push($.gcpToUnifiedIP(a,l));continue}let d=$.gcpToUnified(a,l);d.warnings.length>0&&d.warnings.forEach(u=>{let{TranslationWarningSystem:p}=(ue(),Re(Oe));r.warn(`Rule ${a.description||a.priority}:
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}=(ct(),Re(lt)),a=await s.performDryRunValidation(t,"sync rules",async E=>this.getChanges(E));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||[],y=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: ${y.length}`),{success:!0,rulesAdded:0,rulesUpdated:0,rulesDeleted:0,ipsAdded:0,ipsUpdated:0,ipsDeleted:0,warnings:a.warnings};let w=s.assessOperationRisk(l,t);if(!await s.confirmDestructiveOperation({operation:"sync rules",target:`Cloud Armor policy ${this.client.policyName}`,changes:l,riskLevel:w,skipConfirmation:n.force||!1,dryRun:!1,allowDeletions:n.allowDeletions||!1}))throw new Error("Operation cancelled by user");let x=[],D=[],O=(E,g)=>`${E}: ${g instanceof Error?g.message:String(g)}`,le=await this.client.getPolicy(),ce=new Set(le.rules.map(E=>E.priority)),ge=1e3,k=()=>{for(;ce.has(ge);)ge+=1e3;return ce.add(ge),ge},re=[];for(let E of[...u,...y]){let g=kt(E.id);if(g!==void 0)try{r.debug(`Removing Cloud Armor rule at priority ${g}`),await this.client.removeRule(g)}catch(v){r.error(`Failed to remove Cloud Armor rule at priority ${g}:`,v),D.push(O(`Failed to delete rule at priority ${g}`,v))}}let me=0;for(let E of c){let g=E.priority??k();try{let v=$.unifiedToGcp({...E,priority:g});v.warnings.forEach(H=>x.push(H.message)),r.debug(`Adding Cloud Armor rule at priority ${g}: ${v.result.description}`),await this.client.addRule(v.result),me++,re.push({oldId:E.id,newId:String(g),name:E.name})}catch(v){r.error(`Failed to add Cloud Armor rule "${E.name}":`,v),D.push(O(`Failed to add rule "${E.name}"`,v))}}let he=0;for(let E of d){let g=kt(E.id);if(g!==void 0)try{let v=$.unifiedToGcp(E);v.warnings.forEach(se=>x.push(se.message)),E.priority!==void 0&&E.priority!==g?(r.debug(`Relocating Cloud Armor rule from priority ${g} to ${E.priority}`),await this.client.removeRule(g),await this.client.addRule(v.result),re.push({oldId:E.id,newId:String(E.priority),name:E.name})):(r.debug(`Updating Cloud Armor rule at priority ${g}`),await this.client.patchRule(g,v.result)),he++}catch(v){r.error(`Failed to update Cloud Armor rule at priority ${g}:`,v),D.push(O(`Failed to update rule at priority ${g}`,v))}}let Ie=0;for(let E of p){let g=kt(E.id)??k();try{let v=$.unifiedIPToGcp(E,g);r.debug(`Adding Cloud Armor IP rule at priority ${g}: ${E.ip}`),await this.client.addRule(v),Ie++,re.push({oldId:E.id,newId:String(g),name:E.ip})}catch(v){r.error(`Failed to add Cloud Armor IP rule for ${E.ip}:`,v),D.push(O(`Failed to add IP rule for ${E.ip}`,v))}}let we=0;for(let E of f){let g=kt(E.id);if(g!==void 0)try{r.debug(`Updating Cloud Armor IP rule at priority ${g}: ${E.ip}`),await this.client.patchRule(g,$.unifiedIPToGcp(E,g)),we++}catch(v){r.error(`Failed to update Cloud Armor IP rule at priority ${g}:`,v),D.push(O(`Failed to update IP rule at priority ${g}`,v))}}return r.debug(`Custom Rules: Added: ${me}, Updated: ${he}, Deleted: ${u.length}`),r.debug(`IP Rules: Added: ${Ie}, Updated: ${we}, Deleted: ${y.length}`),{success:D.length===0,rulesAdded:me,rulesUpdated:he,rulesDeleted:u.length,ipsAdded:Ie,ipsUpdated:we,ipsDeleted:y.length,idRemappings:re.length>0?re:void 0,errors:D.length>0?D:void 0,warnings:x.length>0?x: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=_e.safeParse(t);if(!n.success)throw new Error(`Invalid firewall configuration: ${n.error.message}`);try{r.debug("Fetching existing Cloud Armor configuration");let o=await this.client.getPolicy(),s=new Set(t.rules.map(w=>w.id).filter(w=>w!==void 0)),{rules:a,ips:l}=this.translatePolicy(o,s),{toAdd:c,toUpdate:d,toDelete:u}=this.diffItems(t.rules,a,(w,C)=>Le(fe(w),fe(C))),{toAdd:p,toUpdate:f,toDelete:y}=this.diffItems(t.ips||[],l,(w,C)=>Le(fe(w),fe(C)));return{rulesToAdd:c,rulesToUpdate:d,rulesToDelete:u,ipsToAdd:p,ipsToUpdate:f,ipsToDelete:y,hasChanges:c.length>0||d.length>0||u.length>0||p.length>0||f.length>0||y.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}}};P();var ht=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 Qe(e,t,n);return new Xe(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 Qe(t,n,o);return new Xe(s)}static create(e,t,n){return this.fromConfig({projectId:e,policyName:t,serviceAccountKeyPath:n})}};async function On(i){let e;if(i.provider)e=i.provider,r.debug(`Using explicitly specified provider: ${e}`);else{let t=bt.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 ss():(e="vercel",r.warn("No provider detected, defaulting to Vercel"))}try{let t=await ns(e,i);return rs(e,t)}catch(t){throw r.error(`Failed to initialize ${e} provider:`,t),t}}function is(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 ns(i,e){let t=We[i],n=e.config,o={};for(let l of t.fields)o[l.key]=is(n,i,l);let s=Ki(t,{explicit:e.credentials,config:o}),a=Hi(t,s);if(a.length===0)return s;if(e.interactive===!1){let l=a.map(c=>`--${c.key}`).join(", ");throw new Error(`${yt(i)} credentials missing. Provide ${l}.`)}r.warn(`Missing ${yt(i)} credentials`),r.info(`Please provide your ${yt(i)} credentials:`);for(let l of t.fields){if(s[l.key])continue;let c=l.promptMessage??os(l),d=l.secret?await Wi(c):await b(c,{type:"text"});s[l.key]=d||void 0}return s}function os(i){let e=i.required?"":" (optional)",t=i.secret?" ":"";return`${i.label}${e}:${t}`}function rs(i,e){switch(i){case"vercel":{let t={token:e.token,projectId:e.projectId,teamId:e.teamId};return{provider:dt.fromConfig(t),vercelCredentials:t}}case"cloudflare":return{provider:pt.fromConfig({apiToken:e.apiToken,zoneId:e.zoneId,accountId:e.accountId})};case"fastly":return{provider:mt.fromConfig({apiToken:e.apiToken,workspaceId:e.workspaceId})};case"gcp":return{provider:ht.fromConfig({projectId:e.projectId,policyName:e.policyName,serviceAccountKeyPath:e.serviceAccountKeyPath})};default:throw new Error(`Unknown provider: ${i}`)}}async function ss(){r.info(ts.yellow("Unable to auto-detect firewall provider.")),r.info(`Please select a provider:
40
+ `),M.forEach((s,a)=>{r.info(` ${a+1}. ${yt(s)}`)}),r.info("");let i=await b(`Select provider (1-${M.length}):`,{type:"text"}),e=String(i).trim().toLowerCase(),t=M.find(s=>s===e);if(t)return t;let n=Number(e);if(Number.isInteger(n)&&n>=1&&n<=M.length)return M[n-1];let o=M[0];return r.warn(`Invalid choice: ${i}, defaulting to ${yt(o)}`),o}function yt(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 as(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 ii=new Map;for(let i of M)for(let e of We[i].fields){let t=ii.get(e.key)??[];t.push({provider:i,field:e}),ii.set(e.key,t)}var ee=Object.fromEntries(Array.from(ii.entries()).map(([i,e])=>{let t=e.map(n=>n.field.cliAlias).find(n=>!!n);return[i,{type:"string",...t?{alias:t}:{},description:as(e)}]}));function G(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 te(i,e){try{i.debug&&(r.level=ls.debug);let t;if(i.optionalConfig)try{t=await j(i.config,"optional")}catch{t={}}else i.skipValidation?t=await j(i.config,"raw"):t=await j(i.config,"required");let{provider:n,vercelCredentials:o}=await On({provider:i.provider,config:t,interactive:!i.ci,credentials:G(i)}),s=o?.token??"",a=o?.projectId??"",l=o?.teamId??"";await e({config:t,provider:n,token:s,projectId:a,teamId:l})}catch(t){Q(t,i.errorContext)}}var gs="backup",ms="Backup or restore firewall configurations",hs={config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:X,...ee,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}},ys=async i=>{try{i.debug&&(r.level=fs.debug);let e=i.output||"./backups";if(i.list){if(!St(e)){r.info(ie.yellow("No backup directory found."));return}let t=ds(e).filter(n=>n.endsWith(".json")).map(n=>{let o=ni(e,n),s=us(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(ie.yellow("No backups found."));return}r.log(ie.bold(`
41
41
  \u{1F4E6} Available Backups:
42
- `)),t.forEach(n=>{r.log(`${ie.cyan(n.name)}`),r.log(` ${ie.dim("Created:")} ${n.created.toLocaleString()}`),r.log(` ${ie.dim("Size:")} ${(n.size/1024).toFixed(1)} KB`),r.log("")});return}if(i.restore){let t=i.restore.startsWith("/")?i.restore:ni(e,i.restore);St(t)||(r.error(`Backup file not found: ${t}`),process.exit(1));let n=await j(t,"raw"),o=i.config||".doorman.json";if(St(o)&&!await b(`Config file ${o} already exists. Do you want to overwrite it?`,{type:"confirm"})){r.info(ie.yellow("Restore cancelled."));return}let{backup:s,...a}=n;await z(a,o),r.success(ie.green(`\u2705 Restored configuration from ${t} to ${o}`));return}await te({config:i.config,provider:i.provider,...G(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;Ue.getInstance().validateConfig(s),St(e)||ls(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`,y=ni(e,f),w={...s,backup:{createdAt:new Date().toISOString(),source:"remote",provider:t.name,...t.name==="vercel"?{projectId:n,teamId:o}:{},originalVersion:a}};await z(w,y,{validate:!1}),r.success(ie.green(`\u2705 Backup created: ${y}`)),r.log(""),r.log(ie.bold("Backup Details:")),r.log(`${ie.dim("Version:")} ${a??"unknown"}`),r.log(`${ie.dim("Rules:")} ${s.rules.length} custom, ${s.ips?.length??0} IP blocking`),r.log(`${ie.dim("Created:")} ${new Date().toLocaleString()}`),!us(e)&&Bi()&&!qi(e)&&r.warn(ie.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(ie.dim("To restore this backup later, run:")),r.log(ie.cyan(`doorman backup --restore ${f}`))})}catch(e){Q(e,"managing backup")}};var ri={};J(ri,{builder:()=>Rs,command:()=>ys,desc:()=>ws,handler:()=>Cs});P();import ne from"chalk";function ye(i){if(ke(i))return i;let e=i,t=e.rules.map(o=>$.vercelToUnified(o).result),n=(e.ips??[]).map(o=>$.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 Ut(i,e){let t=!1,n=ke(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(d=>d.oldId).map(d=>[d.oldId,d.newId])),a=new Map(e.idRemappings.filter(d=>!d.oldId).map(d=>[d.name,d.newId])),l=!1,c=o.rules.map(d=>{let u=d.id?s.get(d.id):a.get(d.name);return u&&u!==d.id?(l=!0,{...d,id:u}):d});l&&(o={...o,rules:c},t=!0)}return{config:o,changed:t}}function Fn(i,e){if(ke(e))return{...e,...i};let t=e,n=i.rules.map(s=>$.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 ys="diff",ws="Show detailed differences between local and remote firewall configuration",Rs={config:{alias:"c",type:"string",description:"Path to firewall config file"},provider:X,...ee,format:{alias:"f",type:"string",choices:["table","json"],description:"Output format",default:"table"},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}},Cs=async i=>{await te({config:i.config,provider:i.provider,...G(i),debug:i.debug,ci:i.ci,errorContext:"calculating diff"},async({config:e,provider:t})=>{await bs(t,ye(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(ne.green("No differences found. Local and remote configurations are in sync."));return}r.log(ne.bold(`
42
+ `)),t.forEach(n=>{r.log(`${ie.cyan(n.name)}`),r.log(` ${ie.dim("Created:")} ${n.created.toLocaleString()}`),r.log(` ${ie.dim("Size:")} ${(n.size/1024).toFixed(1)} KB`),r.log("")});return}if(i.restore){let t=i.restore.startsWith("/")?i.restore:ni(e,i.restore);St(t)||(r.error(`Backup file not found: ${t}`),process.exit(1));let n=await j(t,"raw"),o=i.config||".doorman.json";if(St(o)&&!await b(`Config file ${o} already exists. Do you want to overwrite it?`,{type:"confirm"})){r.info(ie.yellow("Restore cancelled."));return}let{backup:s,...a}=n;await z(a,o),r.success(ie.green(`\u2705 Restored configuration from ${t} to ${o}`));return}await te({config:i.config,provider:i.provider,...G(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;Ue.getInstance().validateConfig(s),St(e)||cs(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`,y=ni(e,f),w={...s,backup:{createdAt:new Date().toISOString(),source:"remote",provider:t.name,...t.name==="vercel"?{projectId:n,teamId:o}:{},originalVersion:a}};await z(w,y,{validate:!1}),r.success(ie.green(`\u2705 Backup created: ${y}`)),r.log(""),r.log(ie.bold("Backup Details:")),r.log(`${ie.dim("Version:")} ${a??"unknown"}`),r.log(`${ie.dim("Rules:")} ${s.rules.length} custom, ${s.ips?.length??0} IP blocking`),r.log(`${ie.dim("Created:")} ${new Date().toLocaleString()}`),!ps(e)&&Bi()&&!qi(e)&&r.warn(ie.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(ie.dim("To restore this backup later, run:")),r.log(ie.cyan(`doorman backup --restore ${f}`))})}catch(e){Q(e,"managing backup")}};var ri={};J(ri,{builder:()=>Cs,command:()=>ws,desc:()=>Rs,handler:()=>bs});P();import ne from"chalk";function ye(i){if(ke(i))return i;let e=i,t=e.rules.map(o=>$.vercelToUnified(o).result),n=(e.ips??[]).map(o=>$.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 Ut(i,e){let t=!1,n=ke(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(d=>d.oldId).map(d=>[d.oldId,d.newId])),a=new Map(e.idRemappings.filter(d=>!d.oldId).map(d=>[d.name,d.newId])),l=!1,c=o.rules.map(d=>{let u=d.id?s.get(d.id):a.get(d.name);return u&&u!==d.id?(l=!0,{...d,id:u}):d});l&&(o={...o,rules:c},t=!0)}return{config:o,changed:t}}function Fn(i,e){if(ke(e))return{...e,...i};let t=e,n=i.rules.map(s=>$.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 ws="diff",Rs="Show detailed differences between local and remote firewall configuration",Cs={config:{alias:"c",type:"string",description:"Path to firewall config file"},provider:X,...ee,format:{alias:"f",type:"string",choices:["table","json"],description:"Output format",default:"table"},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}},bs=async i=>{await te({config:i.config,provider:i.provider,...G(i),debug:i.debug,ci:i.ci,errorContext:"calculating diff"},async({config:e,provider:t})=>{await vs(t,ye(e),i)})};async function vs(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(ne.green("No differences found. Local and remote configurations are in sync."));return}r.log(ne.bold(`
43
43
  \u{1F50D} ${i.name} Configuration Differences
44
- `)),r.log(ne.bold("Rule Changes:")),r.log(` ${ne.green("+")} ${o.rulesToAdd.length} to add`),o.rulesToAdd.forEach(d=>r.log(` ${ne.green("+")} ${d.name}`)),r.log(` ${ne.cyan("~")} ${o.rulesToUpdate.length} to update`),o.rulesToUpdate.forEach(d=>r.log(` ${ne.cyan("~")} ${d.name}`)),r.log(` ${ne.red("-")} ${o.rulesToDelete.length} to delete`),o.rulesToDelete.forEach(d=>r.log(` ${ne.red("-")} ${d.name}`)),(s.length||a.length||l.length)&&(r.log(""),r.log(ne.bold("IP Rule Changes:")),r.log(` ${ne.green("+")} ${s.length} to add`),r.log(` ${ne.cyan("~")} ${a.length} to update`),r.log(` ${ne.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(ne.bold(`Summary: ${c} total changes detected`)),r.log(ne.dim("Run `sync` to apply these changes to the remote configuration."))}var si={};J(si,{builder:()=>As,command:()=>Is,desc:()=>Es,handler:()=>Ts});P();import et from"chalk";import{z as vs}from"zod";Ee();var Is="download [configVersion]",Es="Download remote Vercel Firewall rules and update local config, optionally for a specific configuration version",As={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:X,...ee,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}},Ts=async i=>{await te({config:i.config,provider:i.provider,...G(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{Rt.parse(i.configVersion)}catch(n){throw n instanceof vs.ZodError&&(r.error("Invalid configuration version number. Version must be a positive integer."),process.exit(1)),n}await $s(t,e,i)})};async function $s(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(et.bold(`
44
+ `)),r.log(ne.bold("Rule Changes:")),r.log(` ${ne.green("+")} ${o.rulesToAdd.length} to add`),o.rulesToAdd.forEach(d=>r.log(` ${ne.green("+")} ${d.name}`)),r.log(` ${ne.cyan("~")} ${o.rulesToUpdate.length} to update`),o.rulesToUpdate.forEach(d=>r.log(` ${ne.cyan("~")} ${d.name}`)),r.log(` ${ne.red("-")} ${o.rulesToDelete.length} to delete`),o.rulesToDelete.forEach(d=>r.log(` ${ne.red("-")} ${d.name}`)),(s.length||a.length||l.length)&&(r.log(""),r.log(ne.bold("IP Rule Changes:")),r.log(` ${ne.green("+")} ${s.length} to add`),r.log(` ${ne.cyan("~")} ${a.length} to update`),r.log(` ${ne.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(ne.bold(`Summary: ${c} total changes detected`)),r.log(ne.dim("Run `sync` to apply these changes to the remote configuration."))}var si={};J(si,{builder:()=>Ts,command:()=>Es,desc:()=>As,handler:()=>$s});P();import et from"chalk";import{z as Is}from"zod";Ee();var Es="download [configVersion]",As="Download remote Vercel Firewall rules and update local config, optionally for a specific configuration version",Ts={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:X,...ee,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}},$s=async i=>{await te({config:i.config,provider:i.provider,...G(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{Rt.parse(i.configVersion)}catch(n){throw n instanceof Is.ZodError&&(r.error("Invalid configuration version number. Version must be a positive integer."),process.exit(1)),n}await Ps(t,e,i)})};async function Ps(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(et.bold(`
45
45
  Remote Custom Rules to Download (${o.length}):
46
46
  `)),o.forEach(c=>r.log(` - ${c.name}${c.enabled?"":et.dim(" (disabled)")}`))):r.info("No custom rules to download..."),s.length>0?(r.log(et.bold(`
47
47
  Remote IP Blocking Rules to Download (${s.length}):
48
- `)),s.forEach(c=>r.log(` - ${c.ip} (${c.action})`))):r.info("No IP blocking rules to download..."),t.dryRun){r.info(et.cyan("Dry run completed. No changes made."));return}if(!await b(`Do you want to download${t.configVersion?` version ${t.configVersion}`:" the latest version"} of these rules? This will overwrite your local configuration.`,{type:"confirm"})){r.info(et.yellow("Download cancelled."));return}let l=Fn(n,e);r.start("Saving configuration..."),await z(l,t.config),r.success(et.green("Successfully downloaded and updated configuration"))}var ai={};J(ai,{builder:()=>Ss,command:()=>_s,desc:()=>ks,handler:()=>Ds});P();import tt from"chalk";import{writeFileSync as Ps}from"fs";var _s="export",ks="Export firewall configuration in various formats",Ss={config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:X,...ee,format:{alias:"f",type:"string",choices:["json","yaml","terraform","markdown"],description:"Export format",default:"json"},output:{alias:"o",type:"string",description:"Output file path"},source:{alias:"s",type:"string",choices:["local","remote"],description:"Export from local config or remote Vercel",default:"local"},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}};function Us(i){let e=new Map;for(let t of i){let n=t.group??0,o=e.get(n);o?o.push(t):e.set(n,[t])}return[...e.values()]}var Ls=i=>{let{rules:e,ips:t=[]}=i,n=i.metadata?.version,o=i.metadata?.updatedAt,s=`# Firewall Configuration Report
48
+ `)),s.forEach(c=>r.log(` - ${c.ip} (${c.action})`))):r.info("No IP blocking rules to download..."),t.dryRun){r.info(et.cyan("Dry run completed. No changes made."));return}if(!await b(`Do you want to download${t.configVersion?` version ${t.configVersion}`:" the latest version"} of these rules? This will overwrite your local configuration.`,{type:"confirm"})){r.info(et.yellow("Download cancelled."));return}let l=Fn(n,e);r.start("Saving configuration..."),await z(l,t.config),r.success(et.green("Successfully downloaded and updated configuration"))}var ai={};J(ai,{builder:()=>Us,command:()=>ks,desc:()=>Ss,handler:()=>Os});P();import tt from"chalk";import{writeFileSync as _s}from"fs";var ks="export",Ss="Export firewall configuration in various formats",Us={config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:X,...ee,format:{alias:"f",type:"string",choices:["json","yaml","terraform","markdown"],description:"Export format",default:"json"},output:{alias:"o",type:"string",description:"Output file path"},source:{alias:"s",type:"string",choices:["local","remote"],description:"Export from local config or remote Vercel",default:"local"},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}};function Ls(i){let e=new Map;for(let t of i){let n=t.group??0,o=e.get(n);o?o.push(t):e.set(n,[t])}return[...e.values()]}var xs=i=>{let{rules:e,ips:t=[]}=i,n=i.metadata?.version,o=i.metadata?.updatedAt,s=`# Firewall Configuration Report
49
49
 
50
50
  `;return s+=`**Provider:** ${i.provider??"unknown"}
51
51
  `,s+=`**Version:** ${n??"unknown"}
@@ -62,7 +62,7 @@ Remote IP Blocking Rules to Download (${s.length}):
62
62
  `,s+=`- **Enabled:** ${a.enabled?"\u2705 Yes":"\u274C No"}
63
63
  `,a.action.rateLimit&&(s+=`- **Rate Limit:** ${a.action.rateLimit.requests} requests per ${a.action.rateLimit.window}
64
64
  `),s+=`- **Conditions:**
65
- `,Us(a.conditions).forEach((c,d)=>{s+=` - Group ${d+1}:
65
+ `,Ls(a.conditions).forEach((c,d)=>{s+=` - Group ${d+1}:
66
66
  `,c.forEach(u=>{s+=` - ${u.field} ${u.operator} \`${u.value}\`
67
67
  `})}),s+=`
68
68
  `}):s+=`No custom rules configured.
@@ -73,7 +73,7 @@ Remote IP Blocking Rules to Download (${s.length}):
73
73
  `,s+=`|------------|----------|--------|
74
74
  `,t.forEach(a=>{s+=`| \`${a.ip}\` | ${a.hostname||"N/A"} | ${a.action} |
75
75
  `})):s+=`No IP blocking rules configured.
76
- `,s},xs=i=>{let{rules:e,ips:t=[]}=i,n=`# Firewall Configuration (${i.provider??"unknown"})
76
+ `,s},Ds=i=>{let{rules:e,ips:t=[]}=i,n=`# Firewall Configuration (${i.provider??"unknown"})
77
77
  `;return n+=`# Generated on ${new Date().toISOString()}
78
78
 
79
79
  `,n+=`# Note: This is a conceptual Terraform configuration
@@ -96,7 +96,7 @@ Remote IP Blocking Rules to Download (${s.length}):
96
96
  `,n+=` action = "${o.action}"
97
97
  `,n+=`}
98
98
 
99
- `}),n},Ds=async i=>{try{let e=i.output==="-"||!i.output,t;i.source==="remote"?await te({config:i.config,provider:i.provider,...G(i),debug:i.debug,ci:i.ci,errorContext:"exporting configuration"},async({provider:a})=>{e||r.start("Fetching remote configuration..."),t=await a.fetchConfig()}):t=await j(i.config),e||r.start(`Exporting configuration in ${i.format} format...`);let n,o;switch(i.format){case"json":n=JSON.stringify(t,null,2),o="json";break;case"yaml":{let a=ye(t);n=`# Firewall Configuration (${a.provider??"unknown"})
99
+ `}),n},Os=async i=>{try{let e=i.output==="-"||!i.output,t;i.source==="remote"?await te({config:i.config,provider:i.provider,...G(i),debug:i.debug,ci:i.ci,errorContext:"exporting configuration"},async({provider:a})=>{e||r.start("Fetching remote configuration..."),t=await a.fetchConfig()}):t=await j(i.config),e||r.start(`Exporting configuration in ${i.format} format...`);let n,o;switch(i.format){case"json":n=JSON.stringify(t,null,2),o="json";break;case"yaml":{let a=ye(t);n=`# Firewall Configuration (${a.provider??"unknown"})
100
100
  `,n+=`version: ${a.metadata?.version??"unknown"}
101
101
  `,n+=`updatedAt: "${a.metadata?.updatedAt??""}"
102
102
  `,n+=`rules:
@@ -104,16 +104,16 @@ Remote IP Blocking Rules to Download (${s.length}):
104
104
  `,n+=` id: "${l.id??""}"
105
105
  `,n+=` enabled: ${l.enabled}
106
106
  `,n+=` action: "${l.action.type}"
107
- `}),o="yaml";break}case"terraform":n=xs(ye(t)),o="tf";break;case"markdown":n=Ls(ye(t)),o="md";break;default:throw new Error(`Unsupported format: ${i.format}`)}let s=i.output||`firewall-export.${o}`;e?r.log(n):(Ps(s,n,"utf8"),r.success(tt.green(`\u2705 Exported to ${s}`)),r.log(""),r.log(tt.bold("Export Summary:")),r.log(`${tt.dim("Format:")} ${i.format}`),r.log(`${tt.dim("Source:")} ${i.source}`),r.log(`${tt.dim("Rules:")} ${t.rules.length} custom, ${(t.ips||[]).length} IP blocking`),r.log(`${tt.dim("Size:")} ${(n.length/1024).toFixed(1)} KB`))}catch(e){Q(e,"exporting configuration")}};var li={};J(li,{BASIC_TEMPLATE_RULES:()=>Nn,SECURITY_FOCUSED_TEMPLATE_RULES:()=>Vn,builder:()=>Vs,command:()=>Fs,desc:()=>Ns,handler:()=>js});P();Ee();import R from"chalk";import{existsSync as Os}from"fs";var Nn=[{id:"rule_block_bad_bots",name:"Block Bad Bots",description:"Block known malicious bots and crawlers based on user agent patterns",conditionGroup:[{conditions:[{type:"user_agent",op:"sub",value:"bot"}]}],action:{mitigate:{action:"deny"}},active:!1},{id:"rule_block_scrapers",name:"Block Scrapers",description:"Block common scraping tools and automated requests",conditionGroup:[{conditions:[{type:"user_agent",op:"sub",value:"scraper"}]}],action:{mitigate:{action:"deny"}},active:!1}],Vn=[{id:"rule_rate_limit_api",name:"Rate Limit API",description:"Rate limit API endpoints to prevent abuse and DoS attacks",conditionGroup:[{conditions:[{type:"path",op:"pre",value:"/api/"}]}],action:{mitigate:{action:"rate_limit",rateLimit:{requests:100,window:"1m"}}},active:!1},{id:"rule_block_suspicious_ips",name:"Block Suspicious IPs",description:"Block requests from a known suspicious IP range",conditionGroup:[{conditions:[{type:"ip_address",op:"eq",value:"10.0.0.0/24"}]}],action:{mitigate:{action:"deny"}},active:!1},{id:"rule_protect_admin",name:"Protect Admin Routes",description:"Add extra protection for admin and sensitive routes",conditionGroup:[{conditions:[{type:"path",op:"pre",value:"/admin"}]}],action:{mitigate:{action:"rate_limit",rateLimit:{requests:10,window:"1m"}}},active:!1}],Fs="init [template]",Ns="Initialize a new Doorman configuration",Vs={template:{type:"string",description:"Template to use for initialization (empty, basic, or security-focused)",choices:["empty","basic","security-focused"]},config:{alias:"c",type:"string",description:"Path for the new config file",default:".doorman.json"},force:{alias:"f",type:"boolean",description:"Overwrite existing config file",default:!1},interactive:{alias:"i",type:"boolean",description:"Interactive setup with guided prompts (default: on unless a template is given positionally)"},projectId:{alias:"p",type:"string",description:"Vercel Project ID"},teamId:{alias:"t",type:"string",description:"Vercel Team ID"}},Bs=()=>{r.log(""),r.log(R.bold.cyan("\u{1F6AA} Welcome to Doorman!")),r.log(R.dim(`Let's set up your firewall configuration.
108
- `))},qs=()=>{r.log(R.bold(`
109
- \u{1F4DA} Helpful Resources:`)),r.log(""),r.log(R.cyan("\u{1F517} Find your Project ID:")),r.log(R.dim(" https://vercel.com/dashboard \u2192 Select your project \u2192 Settings \u2192 General")),r.log(""),r.log(R.cyan("\u{1F517} Find your Team ID:")),r.log(R.dim(" https://vercel.com/dashboard \u2192 Team Settings \u2192 General")),r.log(R.dim(" (Leave empty if using personal account)")),r.log(""),r.log(R.cyan("\u{1F517} Create API Token:")),r.log(R.dim(" https://vercel.com/account/tokens \u2192 Create Token")),r.log(R.dim(" Scopes needed: Read & Write for your project")),r.log(""),r.log(R.cyan("\u{1F517} Documentation:")),r.log(R.dim(" https://doorman.griffen.codes/getting-started")),r.log("")};function Ws(i){return i.interactive??i.template===void 0}var Ms=async(i,e)=>{let t=i.projectId,n=i.teamId;return e&&(r.log(R.bold("\u{1F4CB} Project Configuration")),r.log(R.dim(`We need your Vercel project details to configure the firewall.
110
- `)),t||(t=await b("Enter your Vercel Project ID:",{type:"text"})),n||await b("Are you using a Vercel team account?",{type:"confirm"})&&(n=await b("Enter your Vercel Team ID:",{type:"text"})),process.env.VERCEL_TOKEN?r.log(R.green("\u2705 VERCEL_TOKEN environment variable found")):(r.log(""),r.log(R.yellow("\u26A0\uFE0F VERCEL_TOKEN environment variable not found")),r.log(R.dim("You'll need to set this before running sync commands.")),await b("Would you like to see how to create an API token?",{type:"confirm"})&&(r.log(""),r.log(R.bold("\u{1F511} Creating a Vercel API Token:")),r.log("1. Go to https://vercel.com/account/tokens"),r.log('2. Click "Create Token"'),r.log('3. Give it a descriptive name (e.g., "Doorman Firewall")'),r.log("4. Set expiration as needed"),r.log("5. Copy the token and set it as an environment variable:"),r.log(""),r.log(R.cyan(' export VERCEL_TOKEN="your-token-here"')),r.log(R.dim(" # Add this to your ~/.bashrc, ~/.zshrc, or .env file")),r.log("")))),{projectId:t,teamId:n}},js=async i=>{try{let e=i.config||".doorman.json",t=Ws(i);if(t&&Bs(),Os(e)&&!i.force&&!await b(`Config file ${e} already exists. Do you want to overwrite it?`,{type:"confirm"})){r.info(R.yellow("Initialization cancelled."));return}let{projectId:n,teamId:o}=await Ms(i,t);t&&(r.log(""),r.log(R.bold("\u{1F3A8} Template Selection")),r.log(R.dim(`Choose a starting template for your firewall configuration:
111
- `)),r.log(R.cyan("\u{1F4DD} empty")+R.dim(" - Minimal config, add your own rules")),r.log(R.cyan("\u{1F6E1}\uFE0F basic")+R.dim(" - Includes basic bot protection")),r.log(R.cyan("\u{1F512} security-focused")+R.dim(" - Comprehensive security rules")),r.log(""));let s=i.template??"empty";t&&!i.template&&(s=await b("Select a template:",{type:"select",options:["empty","basic","security-focused"],initial:"empty"})),r.start(`Creating ${s} configuration...`);let a=vt();switch(n&&(a.projectId=n),o&&(a.teamId=o),s){case"basic":a={...a,rules:Nn};break;case"security-focused":a={...a,rules:Vn};break;default:break}await z(a,e),r.success(R.green(`\u2705 Created ${e}`)),r.log(""),r.log(R.bold("\u{1F4CB} Configuration Summary:")),r.log(`${R.dim("File:")} ${e}`),r.log(`${R.dim("Template:")} ${s}`),r.log(`${R.dim("Project ID:")} ${n||R.yellow("Not set - add manually")}`),r.log(`${R.dim("Team ID:")} ${o||R.dim("None (personal account)")}`),r.log(`${R.dim("Rules:")} ${a.rules.length} (all disabled for safety)`),r.log(""),r.log(R.bold("\u{1F680} Next Steps:")),n||r.log(`1. ${R.yellow("Add your projectId")} to ${e}`),process.env.VERCEL_TOKEN||r.log(`${n?"1":"2"}. ${R.yellow("Set VERCEL_TOKEN")} environment variable`);let l=n?process.env.VERCEL_TOKEN?1:2:3;r.log(`${l}. ${R.dim("Review and enable rules in")} ${e}`),r.log(`${l+1}. ${R.dim("Run")} ${R.cyan("doorman validate")} ${R.dim("to check your config")}`),r.log(`${l+2}. ${R.dim("Run")} ${R.cyan("doorman status")} ${R.dim("to see sync status")}`),r.log(`${l+3}. ${R.dim("Run")} ${R.cyan("doorman sync")} ${R.dim("to deploy your rules")}`),s!=="empty"&&(r.log(""),r.log(R.yellow("\u26A0\uFE0F All template rules are disabled by default for safety.")),r.log(R.dim(" Review each rule and set active: true when ready."))),t&&(!n||!process.env.VERCEL_TOKEN)&&qs()}catch(e){Q(e,"initializing configuration")}};var ci={};J(ci,{builder:()=>Hs,command:()=>Gs,desc:()=>Ks,handler:()=>Zs});P();import be from"chalk";import{z as zs}from"zod";var Gs="list [configVersion]",Ks="List Vercel Firewall rules, optionally for a specific configuration version",Hs={configVersion:{type:"number",description:"Specific configuration version to fetch (defaults to latest)"},provider:X,...ee,format:{alias:"f",type:"string",description:"Output format (json or table)",choices:["json","table"],default:"table"},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 te({provider:i.provider,...G(i),debug:i.debug,ci:i.ci,optionalConfig:!0,skipValidation:!0,errorContext:"listing firewall rules"},async({provider:e})=>{if(i.configVersion!==void 0)try{Rt.parse(i.configVersion)}catch(t){throw t instanceof zs.ZodError&&(r.error("Invalid configuration version number. Version must be a positive integer."),process.exit(1)),t}await Ys(e,i)})};async function Ys(i,e){let t=e.format==="json";t||r.start(`Fetching firewall configuration${e.configVersion?` version ${e.configVersion}`:""} ...`);let n=await i.fetchConfig(e.configVersion),o=n.rules,s=n.ips??[],a=n.metadata?.version??n.version,l=n.metadata?.updatedAt;if(t){r.log(JSON.stringify({version:a,updatedAt:l,rules:o,ips:s},null,2));return}if(r.info(`Found ${be.cyan(o.length)} custom rules and ${be.cyan(s.length)} IP blocking rules
107
+ `}),o="yaml";break}case"terraform":n=Ds(ye(t)),o="tf";break;case"markdown":n=xs(ye(t)),o="md";break;default:throw new Error(`Unsupported format: ${i.format}`)}let s=i.output||`firewall-export.${o}`;e?r.log(n):(_s(s,n,"utf8"),r.success(tt.green(`\u2705 Exported to ${s}`)),r.log(""),r.log(tt.bold("Export Summary:")),r.log(`${tt.dim("Format:")} ${i.format}`),r.log(`${tt.dim("Source:")} ${i.source}`),r.log(`${tt.dim("Rules:")} ${t.rules.length} custom, ${(t.ips||[]).length} IP blocking`),r.log(`${tt.dim("Size:")} ${(n.length/1024).toFixed(1)} KB`))}catch(e){Q(e,"exporting configuration")}};var li={};J(li,{BASIC_TEMPLATE_RULES:()=>Nn,SECURITY_FOCUSED_TEMPLATE_RULES:()=>Vn,builder:()=>Bs,command:()=>Ns,desc:()=>Vs,handler:()=>zs});P();Ee();import R from"chalk";import{existsSync as Fs}from"fs";var Nn=[{id:"rule_block_bad_bots",name:"Block Bad Bots",description:"Block known malicious bots and crawlers based on user agent patterns",conditionGroup:[{conditions:[{type:"user_agent",op:"sub",value:"bot"}]}],action:{mitigate:{action:"deny"}},active:!1},{id:"rule_block_scrapers",name:"Block Scrapers",description:"Block common scraping tools and automated requests",conditionGroup:[{conditions:[{type:"user_agent",op:"sub",value:"scraper"}]}],action:{mitigate:{action:"deny"}},active:!1}],Vn=[{id:"rule_rate_limit_api",name:"Rate Limit API",description:"Rate limit API endpoints to prevent abuse and DoS attacks",conditionGroup:[{conditions:[{type:"path",op:"pre",value:"/api/"}]}],action:{mitigate:{action:"rate_limit",rateLimit:{requests:100,window:"1m"}}},active:!1},{id:"rule_block_suspicious_ips",name:"Block Suspicious IPs",description:"Block requests from a known suspicious IP range",conditionGroup:[{conditions:[{type:"ip_address",op:"eq",value:"10.0.0.0/24"}]}],action:{mitigate:{action:"deny"}},active:!1},{id:"rule_protect_admin",name:"Protect Admin Routes",description:"Add extra protection for admin and sensitive routes",conditionGroup:[{conditions:[{type:"path",op:"pre",value:"/admin"}]}],action:{mitigate:{action:"rate_limit",rateLimit:{requests:10,window:"1m"}}},active:!1}],Ns="init [template]",Vs="Initialize a new Doorman configuration",Bs={template:{type:"string",description:"Template to use for initialization (empty, basic, or security-focused)",choices:["empty","basic","security-focused"]},config:{alias:"c",type:"string",description:"Path for the new config file",default:".doorman.json"},force:{alias:"f",type:"boolean",description:"Overwrite existing config file",default:!1},interactive:{alias:"i",type:"boolean",description:"Interactive setup with guided prompts (default: on unless a template is given positionally)"},projectId:{alias:"p",type:"string",description:"Vercel Project ID"},teamId:{alias:"t",type:"string",description:"Vercel Team ID"}},qs=()=>{r.log(""),r.log(R.bold.cyan("\u{1F6AA} Welcome to Doorman!")),r.log(R.dim(`Let's set up your firewall configuration.
108
+ `))},Ws=()=>{r.log(R.bold(`
109
+ \u{1F4DA} Helpful Resources:`)),r.log(""),r.log(R.cyan("\u{1F517} Find your Project ID:")),r.log(R.dim(" https://vercel.com/dashboard \u2192 Select your project \u2192 Settings \u2192 General")),r.log(""),r.log(R.cyan("\u{1F517} Find your Team ID:")),r.log(R.dim(" https://vercel.com/dashboard \u2192 Team Settings \u2192 General")),r.log(R.dim(" (Leave empty if using personal account)")),r.log(""),r.log(R.cyan("\u{1F517} Create API Token:")),r.log(R.dim(" https://vercel.com/account/tokens \u2192 Create Token")),r.log(R.dim(" Scopes needed: Read & Write for your project")),r.log(""),r.log(R.cyan("\u{1F517} Documentation:")),r.log(R.dim(" https://doorman.griffen.codes/getting-started")),r.log("")};function Ms(i){return i.interactive??i.template===void 0}var js=async(i,e)=>{let t=i.projectId,n=i.teamId;return e&&(r.log(R.bold("\u{1F4CB} Project Configuration")),r.log(R.dim(`We need your Vercel project details to configure the firewall.
110
+ `)),t||(t=await b("Enter your Vercel Project ID:",{type:"text"})),n||await b("Are you using a Vercel team account?",{type:"confirm"})&&(n=await b("Enter your Vercel Team ID:",{type:"text"})),process.env.VERCEL_TOKEN?r.log(R.green("\u2705 VERCEL_TOKEN environment variable found")):(r.log(""),r.log(R.yellow("\u26A0\uFE0F VERCEL_TOKEN environment variable not found")),r.log(R.dim("You'll need to set this before running sync commands.")),await b("Would you like to see how to create an API token?",{type:"confirm"})&&(r.log(""),r.log(R.bold("\u{1F511} Creating a Vercel API Token:")),r.log("1. Go to https://vercel.com/account/tokens"),r.log('2. Click "Create Token"'),r.log('3. Give it a descriptive name (e.g., "Doorman Firewall")'),r.log("4. Set expiration as needed"),r.log("5. Copy the token and set it as an environment variable:"),r.log(""),r.log(R.cyan(' export VERCEL_TOKEN="your-token-here"')),r.log(R.dim(" # Add this to your ~/.bashrc, ~/.zshrc, or .env file")),r.log("")))),{projectId:t,teamId:n}},zs=async i=>{try{let e=i.config||".doorman.json",t=Ms(i);if(t&&qs(),Fs(e)&&!i.force&&!await b(`Config file ${e} already exists. Do you want to overwrite it?`,{type:"confirm"})){r.info(R.yellow("Initialization cancelled."));return}let{projectId:n,teamId:o}=await js(i,t);t&&(r.log(""),r.log(R.bold("\u{1F3A8} Template Selection")),r.log(R.dim(`Choose a starting template for your firewall configuration:
111
+ `)),r.log(R.cyan("\u{1F4DD} empty")+R.dim(" - Minimal config, add your own rules")),r.log(R.cyan("\u{1F6E1}\uFE0F basic")+R.dim(" - Includes basic bot protection")),r.log(R.cyan("\u{1F512} security-focused")+R.dim(" - Comprehensive security rules")),r.log(""));let s=i.template??"empty";t&&!i.template&&(s=await b("Select a template:",{type:"select",options:["empty","basic","security-focused"],initial:"empty"})),r.start(`Creating ${s} configuration...`);let a=vt();switch(n&&(a.projectId=n),o&&(a.teamId=o),s){case"basic":a={...a,rules:Nn};break;case"security-focused":a={...a,rules:Vn};break;default:break}await z(a,e),r.success(R.green(`\u2705 Created ${e}`)),r.log(""),r.log(R.bold("\u{1F4CB} Configuration Summary:")),r.log(`${R.dim("File:")} ${e}`),r.log(`${R.dim("Template:")} ${s}`),r.log(`${R.dim("Project ID:")} ${n||R.yellow("Not set - add manually")}`),r.log(`${R.dim("Team ID:")} ${o||R.dim("None (personal account)")}`),r.log(`${R.dim("Rules:")} ${a.rules.length} (all disabled for safety)`),r.log(""),r.log(R.bold("\u{1F680} Next Steps:")),n||r.log(`1. ${R.yellow("Add your projectId")} to ${e}`),process.env.VERCEL_TOKEN||r.log(`${n?"1":"2"}. ${R.yellow("Set VERCEL_TOKEN")} environment variable`);let l=n?process.env.VERCEL_TOKEN?1:2:3;r.log(`${l}. ${R.dim("Review and enable rules in")} ${e}`),r.log(`${l+1}. ${R.dim("Run")} ${R.cyan("doorman validate")} ${R.dim("to check your config")}`),r.log(`${l+2}. ${R.dim("Run")} ${R.cyan("doorman status")} ${R.dim("to see sync status")}`),r.log(`${l+3}. ${R.dim("Run")} ${R.cyan("doorman sync")} ${R.dim("to deploy your rules")}`),s!=="empty"&&(r.log(""),r.log(R.yellow("\u26A0\uFE0F All template rules are disabled by default for safety.")),r.log(R.dim(" Review each rule and set active: true when ready."))),t&&(!n||!process.env.VERCEL_TOKEN)&&Ws()}catch(e){Q(e,"initializing configuration")}};var ci={};J(ci,{builder:()=>Zs,command:()=>Ks,desc:()=>Hs,handler:()=>Ys});P();import be from"chalk";import{z as Gs}from"zod";var Ks="list [configVersion]",Hs="List Vercel Firewall rules, optionally for a specific configuration version",Zs={configVersion:{type:"number",description:"Specific configuration version to fetch (defaults to latest)"},provider:X,...ee,format:{alias:"f",type:"string",description:"Output format (json or table)",choices:["json","table"],default:"table"},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}},Ys=async i=>{await te({provider:i.provider,...G(i),debug:i.debug,ci:i.ci,optionalConfig:!0,skipValidation:!0,errorContext:"listing firewall rules"},async({provider:e})=>{if(i.configVersion!==void 0)try{Rt.parse(i.configVersion)}catch(t){throw t instanceof Gs.ZodError&&(r.error("Invalid configuration version number. Version must be a positive integer."),process.exit(1)),t}await Js(e,i)})};async function Js(i,e){let t=e.format==="json";t||r.start(`Fetching firewall configuration${e.configVersion?` version ${e.configVersion}`:""} ...`);let n=await i.fetchConfig(e.configVersion),o=n.rules,s=n.ips??[],a=n.metadata?.version??n.version,l=n.metadata?.updatedAt;if(t){r.log(JSON.stringify({version:a,updatedAt:l,rules:o,ips:s},null,2));return}if(r.info(`Found ${be.cyan(o.length)} custom rules and ${be.cyan(s.length)} IP blocking rules
112
112
  `+be.dim(`Version: ${be.yellow(a??"unknown")}${l?` \u2022 Last Updated: ${be.yellow(l)}`:""}`)),o.length===0&&s.length===0){r.info(be.yellow("No rules found"));return}o.length>0?(r.log(be.bold.underline(`
113
113
  Custom Rules:`),`
114
114
  `),o.forEach(c=>r.log(` - ${c.name}${c.enabled?"":be.dim(" (disabled)")}`))):r.info(be.cyan("No custom rules found")),s.length>0?(r.log(be.bold.underline(`
115
115
  IP Blocking Rules:`),`
116
- `),s.forEach(c=>r.log(` - ${c.ip} (${c.action})`))):r.info(be.cyan("No IP blocking rules found"))}var ui={};J(ui,{aliases:()=>Xs,builder:()=>ta,command:()=>Qs,desc:()=>ea,findFuzzyMatches:()=>Mn,handler:()=>ia,levenshtein:()=>Wn});P();Ee();import B from"chalk";import{LogLevels as Js}from"consola";var Qs="remove [type]",Xs=["rm","delete"],ea="Remove a firewall rule or IP entry from your configuration",ta={type:{type:"string",description:'What to remove: "rule" (default) or "ip"',default:"rule",choices:["rule","ip"]},interactive:{alias:"i",type:"boolean",description:"Select rules to remove from a list",default:!1},name:{alias:"n",type:"string",description:"Remove rule by name (exact match)"},id:{type:"array",string:!0,description:"Remove rule by ID (supports multiple)"},ip:{type:"string",description:"Remove IP rule by IP address"},disabled:{type:"boolean",description:"Remove all disabled (active: false) rules",default:!1},all:{type:"boolean",description:"Remove all rules (requires confirmation)",default:!1},force:{alias:"f",type:"boolean",description:"Skip confirmation prompt",default:!1},config:{alias:"c",type:"string",description:"Config file path"},dryRun:{alias:"d",type:"boolean",description:"Show what would be removed without writing",default:!1},debug:{type:"boolean",description:"Enable debug logging",default:!1}};function Wn(i,e){let t=i.length,n=e.length;if(t===0)return n;if(n===0)return t;let o=Array.from({length:t+1},(a,l)=>l),s=new Array(t+1).fill(0);for(let a=1;a<=n;a++){s[0]=a;for(let l=1;l<=t;l++)e.charAt(a-1)===i.charAt(l-1)?s[l]=o[l-1]:s[l]=Math.min(o[l-1]+1,s[l-1]+1,o[l]+1);for(let l=0;l<=t;l++)o[l]=s[l]}return o[t]}function Mn(i,e,t=3){let n=i.toLowerCase(),o=e.filter(l=>l.name.toLowerCase().includes(n)||n.includes(l.name.toLowerCase()));if(o.length>0)return o.slice(0,t);let s=e.map(l=>({rule:l,distance:Wn(n,l.name.toLowerCase())})).sort((l,c)=>l.distance-c.distance),a=Math.max(i.length*.6,3);return s.filter(l=>l.distance<=a).slice(0,t).map(l=>l.rule)}function di(i){for(let e of i){let t=e.id?` (${e.id})`:"",n=e.active?"":B.dim(" [disabled]"),o=e.action.mitigate.action;r.log(B.dim(` - ${e.name}${t} \u2014 ${o}${n}`))}}function Bn(i){for(let e of i){let t=e.notes?` \u2014 ${e.notes}`:"";r.log(B.dim(` - ${e.ip}${t}`))}}async function qn(i,e,t,n="rule"){if(e)return!0;if(t)return await b(B.red(`\u26A0\uFE0F Are you sure you want to remove ALL ${n}s? Type "YES" to confirm:`),{type:"text"})==="YES";let o=i===1?`Confirm removal of 1 ${n}?`:`Confirm removal of ${i} ${n}s?`;return await b(o,{type:"confirm",initial:!1})}var ia=async i=>{try{i.debug&&(r.level=Js.debug),r.debug("Remove command arguments:",i);let e=i.type||"rule";r.start("Loading configuration...");let t=await j(i.config);if(e==="ip"){let n=t.ips||[];if(n.length===0){r.info("No IP rules in configuration.");return}let o=[];if(i.interactive){let l=n.map(d=>{let u=d.notes?` \u2014 ${d.notes}`:"";return`${d.ip}${u}`}),c=await b("Select IP rules to remove:",{type:"multiselect",options:l});o=n.filter(d=>{let u=`${d.ip}${d.notes?` \u2014 ${d.notes}`:""}`;return c.includes(u)})}else i.ip?(o=n.filter(l=>l.ip===i.ip),o.length===0&&(r.error(B.red(`No IP rule found matching "${i.ip}"`)),n.length>0&&(r.log(B.dim(`
116
+ `),s.forEach(c=>r.log(` - ${c.ip} (${c.action})`))):r.info(be.cyan("No IP blocking rules found"))}var ui={};J(ui,{aliases:()=>ea,builder:()=>ia,command:()=>Xs,desc:()=>ta,findFuzzyMatches:()=>Mn,handler:()=>na,levenshtein:()=>Wn});P();Ee();import B from"chalk";import{LogLevels as Qs}from"consola";var Xs="remove [type]",ea=["rm","delete"],ta="Remove a firewall rule or IP entry from your configuration",ia={type:{type:"string",description:'What to remove: "rule" (default) or "ip"',default:"rule",choices:["rule","ip"]},interactive:{alias:"i",type:"boolean",description:"Select rules to remove from a list",default:!1},name:{alias:"n",type:"string",description:"Remove rule by name (exact match)"},id:{type:"array",string:!0,description:"Remove rule by ID (supports multiple)"},ip:{type:"string",description:"Remove IP rule by IP address"},disabled:{type:"boolean",description:"Remove all disabled (active: false) rules",default:!1},all:{type:"boolean",description:"Remove all rules (requires confirmation)",default:!1},force:{alias:"f",type:"boolean",description:"Skip confirmation prompt",default:!1},config:{alias:"c",type:"string",description:"Config file path"},dryRun:{alias:"d",type:"boolean",description:"Show what would be removed without writing",default:!1},debug:{type:"boolean",description:"Enable debug logging",default:!1}};function Wn(i,e){let t=i.length,n=e.length;if(t===0)return n;if(n===0)return t;let o=Array.from({length:t+1},(a,l)=>l),s=new Array(t+1).fill(0);for(let a=1;a<=n;a++){s[0]=a;for(let l=1;l<=t;l++)e.charAt(a-1)===i.charAt(l-1)?s[l]=o[l-1]:s[l]=Math.min(o[l-1]+1,s[l-1]+1,o[l]+1);for(let l=0;l<=t;l++)o[l]=s[l]}return o[t]}function Mn(i,e,t=3){let n=i.toLowerCase(),o=e.filter(l=>l.name.toLowerCase().includes(n)||n.includes(l.name.toLowerCase()));if(o.length>0)return o.slice(0,t);let s=e.map(l=>({rule:l,distance:Wn(n,l.name.toLowerCase())})).sort((l,c)=>l.distance-c.distance),a=Math.max(i.length*.6,3);return s.filter(l=>l.distance<=a).slice(0,t).map(l=>l.rule)}function di(i){for(let e of i){let t=e.id?` (${e.id})`:"",n=e.active?"":B.dim(" [disabled]"),o=e.action.mitigate.action;r.log(B.dim(` - ${e.name}${t} \u2014 ${o}${n}`))}}function Bn(i){for(let e of i){let t=e.notes?` \u2014 ${e.notes}`:"";r.log(B.dim(` - ${e.ip}${t}`))}}async function qn(i,e,t,n="rule"){if(e)return!0;if(t)return await b(B.red(`\u26A0\uFE0F Are you sure you want to remove ALL ${n}s? Type "YES" to confirm:`),{type:"text"})==="YES";let o=i===1?`Confirm removal of 1 ${n}?`:`Confirm removal of ${i} ${n}s?`;return await b(o,{type:"confirm",initial:!1})}var na=async i=>{try{i.debug&&(r.level=Qs.debug),r.debug("Remove command arguments:",i);let e=i.type||"rule";r.start("Loading configuration...");let t=await j(i.config);if(e==="ip"){let n=t.ips||[];if(n.length===0){r.info("No IP rules in configuration.");return}let o=[];if(i.interactive){let l=n.map(d=>{let u=d.notes?` \u2014 ${d.notes}`:"";return`${d.ip}${u}`}),c=await b("Select IP rules to remove:",{type:"multiselect",options:l});o=n.filter(d=>{let u=`${d.ip}${d.notes?` \u2014 ${d.notes}`:""}`;return c.includes(u)})}else i.ip?(o=n.filter(l=>l.ip===i.ip),o.length===0&&(r.error(B.red(`No IP rule found matching "${i.ip}"`)),n.length>0&&(r.log(B.dim(`
117
117
  Current IP rules:`)),Bn(n)),process.exit(1))):i.all?o=[...n]:(r.error("Specify --ip, --interactive, or --all to select IP rules to remove."),process.exit(1));if(o.length===0){r.info("No IP rules selected for removal.");return}if(r.log(B.cyan(`
118
118
  ${i.dryRun?"Would remove":"Removing"} ${o.length} IP rule(s):`)),Bn(o),i.dryRun){r.log(B.dim(`
119
119
  Dry run \u2014 no changes made.`));return}if(!await qn(o.length,i.force||!1,i.all||!1,"IP rule")){r.info("Cancelled.");return}let s=new Set(o.map(l=>l.ip)),a={...t,ips:n.filter(l=>!s.has(l.ip))};await z(a,i.config),r.success(B.green(`\u2714 Removed ${o.length} IP rule(s) from configuration`))}else{let n=t.rules||[];if(n.length===0){r.info("No rules in configuration.");return}let o=[];if(i.interactive){let l=n.map((d,u)=>{let p=d.id?` (${d.id})`:"",f=d.active?"":" [disabled]",y=d.action.mitigate.action;return`[${u}] ${d.name}${p} \u2014 ${y}${f}`}),c=await b("Select rules to remove:",{type:"multiselect",options:l});o=n.filter((d,u)=>c.some(p=>p.startsWith(`[${u}]`)))}else if(i.name){if(o=n.filter(l=>l.name===i.name),o.length>1&&(r.warn(B.yellow(`
@@ -122,23 +122,23 @@ Did you mean one of these?`)),l.forEach((c,d)=>{let u=c.id?` (${c.id})`:"";r.log
122
122
  Use --id for exact matching, or --interactive to select from a list.`))),process.exit(1)}}else if(i.id&&i.id.length>0){let l=new Set(i.id);o=n.filter(u=>u.id&&l.has(u.id));let c=new Set(o.map(u=>u.id)),d=i.id.filter(u=>!c.has(u));d.length>0&&r.warn(B.yellow(`No rules found for IDs: ${d.join(", ")}`)),o.length===0&&(r.error(B.red("No matching rules found for the given IDs.")),process.exit(1))}else if(i.disabled){if(o=n.filter(l=>!l.active),o.length===0){r.info("No disabled rules found.");return}}else i.all?o=[...n]:(r.error("Specify --name, --id, --disabled, --all, or --interactive to select rules to remove."),process.exit(1));if(o.length===0){r.info("No rules selected for removal.");return}if(o.length>=5&&!i.dryRun&&!i.force&&r.log(B.yellow(`
123
123
  \u{1F4A1} Tip: Run ${B.cyan("doorman backup")} before bulk removal operations.`)),r.log(B.cyan(`
124
124
  ${i.dryRun?"Would remove":"Removing"} ${o.length} rule(s):`)),di(o),i.dryRun){r.log(B.dim(`
125
- Dry run \u2014 no changes made.`));return}if(!await qn(o.length,i.force||!1,i.all||!1)){r.info("Cancelled.");return}let s=new Set(o),a={...t,rules:n.filter(l=>!s.has(l))};a.rules.length===0&&r.warn(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`)),di(o)}r.log(""),r.log(B.dim(`Run ${B.cyan("doorman sync")} to deploy these changes.`))}catch(e){Q(e,"removing rule")}};var pi={};J(pi,{builder:()=>ra,command:()=>na,desc:()=>oa,handler:()=>sa});P();import U from"chalk";var na="setup",oa="Show setup instructions and helpful links for Doorman",ra={},sa=async i=>{r.log(""),r.log(U.bold.cyan("\u{1F6AA} Doorman Setup Guide")),r.log(U.dim(`Everything you need to get started with firewall management
125
+ Dry run \u2014 no changes made.`));return}if(!await qn(o.length,i.force||!1,i.all||!1)){r.info("Cancelled.");return}let s=new Set(o),a={...t,rules:n.filter(l=>!s.has(l))};a.rules.length===0&&r.warn(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`)),di(o)}r.log(""),r.log(B.dim(`Run ${B.cyan("doorman sync")} to deploy these changes.`))}catch(e){Q(e,"removing rule")}};var pi={};J(pi,{builder:()=>sa,command:()=>oa,desc:()=>ra,handler:()=>aa});P();import U from"chalk";var oa="setup",ra="Show setup instructions and helpful links for Doorman",sa={},aa=async i=>{r.log(""),r.log(U.bold.cyan("\u{1F6AA} Doorman Setup Guide")),r.log(U.dim(`Everything you need to get started with firewall management
126
126
  `)),r.log(U.bold("\u{1F4CB} Prerequisites")),r.log("\u2022 Vercel account with a deployed project"),r.log("\u2022 Project with Pro plan or higher (firewall feature requirement)"),r.log(`\u2022 Admin access to the project/team
127
- `),r.log(U.bold("\u{1F50D} Finding Your Project Information")),r.log(""),r.log(U.cyan("1. Project ID:")),r.log(" \u2022 Go to https://vercel.com/dashboard"),r.log(" \u2022 Select your project"),r.log(" \u2022 Navigate to Settings \u2192 General"),r.log(" \u2022 Copy the Project ID from the top of the page"),r.log(""),r.log(U.cyan("2. Team ID (if using team account):")),r.log(" \u2022 Go to https://vercel.com/dashboard"),r.log(" \u2022 Click on your team name in the top-left"),r.log(" \u2022 Go to Team Settings \u2192 General"),r.log(" \u2022 Copy the Team ID"),r.log(" \u2022 Leave empty to use your Vercel default team (every account has one)"),r.log(" \u2022 Belong to more than one team? Set this explicitly, or doorman will use whichever is your default"),r.log(""),r.log(U.bold("\u{1F511} Creating API Token")),r.log(""),r.log(U.cyan("Steps:")),r.log(" 1. Visit https://vercel.com/account/tokens"),r.log(' 2. Click "Create Token"'),r.log(' 3. Name: "Doorman Firewall Management"'),r.log(" 4. Expiration: Set as needed (90 days recommended)"),r.log(" 5. Scope: Select your project or team"),r.log(" 6. Copy the generated token"),r.log(""),r.log(U.cyan("Required Permissions:")),r.log(" \u2022 Read access to project settings"),r.log(" \u2022 Write access to firewall configuration"),r.log(""),r.log(U.bold("\u{1F30D} Environment Setup")),r.log(""),r.log(U.cyan("Set your API token as environment variable:")),r.log(""),r.log(U.green("# For current session:")),r.log('export VERCEL_TOKEN="your-token-here"'),r.log(""),r.log(U.green("# For permanent setup (add to ~/.bashrc or ~/.zshrc):")),r.log(`echo 'export VERCEL_TOKEN="your-token-here"' >> ~/.bashrc`),r.log(""),r.log(U.green("# Or create a .env file in your project:")),r.log('echo "VERCEL_TOKEN=your-token-here" > .env'),r.log(""),r.log(U.bold("\u{1F680} Quick Start Commands")),r.log(""),r.log(U.cyan("1. Initialize configuration:")),r.log(" doorman init --interactive"),r.log(""),r.log(U.cyan("2. Validate setup:")),r.log(" doorman validate"),r.log(""),r.log(U.cyan("3. Check current status:")),r.log(" doorman status"),r.log(""),r.log(U.cyan("4. View existing rules:")),r.log(" doorman list"),r.log(""),r.log(U.cyan("5. Sync your configuration:")),r.log(" doorman sync"),r.log(""),r.log(U.bold("\u{1F527} Common Issues")),r.log(""),r.log(U.yellow('\u274C "Project not found" error:')),r.log(" \u2022 Double-check your Project ID"),r.log(" \u2022 Ensure your token has access to the project"),r.log(" \u2022 Verify the project has Pro plan or higher"),r.log(""),r.log(U.yellow('\u274C "Unauthorized" error:')),r.log(" \u2022 Check your VERCEL_TOKEN is set correctly"),r.log(" \u2022 Ensure token hasn't expired"),r.log(" \u2022 Verify token has firewall permissions"),r.log(""),r.log(U.yellow('\u274C "Firewall not available" error:')),r.log(" \u2022 Upgrade to Pro plan or higher"),r.log(" \u2022 Contact Vercel support if issue persists"),r.log(""),r.log(U.bold("\u{1F4DA} Additional Resources")),r.log(""),r.log(U.cyan("Documentation:")),r.log(" \u2022 Doorman Docs: https://doorman.griffen.codes"),r.log(" \u2022 Vercel Firewall: https://vercel.com/docs/security/vercel-firewall"),r.log(" \u2022 API Reference: https://vercel.com/docs/rest-api/endpoints/firewall"),r.log(""),r.log(U.cyan("Support:")),r.log(" \u2022 GitHub Issues: https://github.com/gfargo/doorman/issues"),r.log(" \u2022 Vercel Support: https://vercel.com/help"),r.log(""),r.log(U.bold("\u{1F512} Security Best Practices")),r.log(""),r.log("\u2022 Store API tokens securely (use environment variables)"),r.log("\u2022 Set token expiration dates"),r.log("\u2022 Use principle of least privilege for token scopes"),r.log("\u2022 Regularly rotate API tokens"),r.log("\u2022 Test rules in staging before production"),r.log("\u2022 Keep backups of working configurations"),r.log(""),r.log(U.bold.green("\u2705 Ready to get started?")),r.log(U.dim("Run: ")+U.cyan("doorman init --interactive")),r.log("")};var fi={};J(fi,{builder:()=>ca,command:()=>aa,desc:()=>la,handler:()=>da});P();import F from"chalk";var aa="status",la="Show sync status between local and remote firewall configuration",ca={config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:X,...ee,format:{alias:"f",type:"string",choices:["table","json"],description:"Output format",default:"table"},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}},da=async i=>{await te({config:i.config,provider:i.provider,...G(i),debug:i.debug,ci:i.ci,errorContext:"checking status"},async({config:e,provider:t})=>{await ua(t,ye(e),i)})};async function ua(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(U.bold("\u{1F50D} Finding Your Project Information")),r.log(""),r.log(U.cyan("1. Project ID:")),r.log(" \u2022 Go to https://vercel.com/dashboard"),r.log(" \u2022 Select your project"),r.log(" \u2022 Navigate to Settings \u2192 General"),r.log(" \u2022 Copy the Project ID from the top of the page"),r.log(""),r.log(U.cyan("2. Team ID (if using team account):")),r.log(" \u2022 Go to https://vercel.com/dashboard"),r.log(" \u2022 Click on your team name in the top-left"),r.log(" \u2022 Go to Team Settings \u2192 General"),r.log(" \u2022 Copy the Team ID"),r.log(" \u2022 Leave empty to use your Vercel default team (every account has one)"),r.log(" \u2022 Belong to more than one team? Set this explicitly, or doorman will use whichever is your default"),r.log(""),r.log(U.bold("\u{1F511} Creating API Token")),r.log(""),r.log(U.cyan("Steps:")),r.log(" 1. Visit https://vercel.com/account/tokens"),r.log(' 2. Click "Create Token"'),r.log(' 3. Name: "Doorman Firewall Management"'),r.log(" 4. Expiration: Set as needed (90 days recommended)"),r.log(" 5. Scope: Select your project or team"),r.log(" 6. Copy the generated token"),r.log(""),r.log(U.cyan("Required Permissions:")),r.log(" \u2022 Read access to project settings"),r.log(" \u2022 Write access to firewall configuration"),r.log(""),r.log(U.bold("\u{1F30D} Environment Setup")),r.log(""),r.log(U.cyan("Set your API token as environment variable:")),r.log(""),r.log(U.green("# For current session:")),r.log('export VERCEL_TOKEN="your-token-here"'),r.log(""),r.log(U.green("# For permanent setup (add to ~/.bashrc or ~/.zshrc):")),r.log(`echo 'export VERCEL_TOKEN="your-token-here"' >> ~/.bashrc`),r.log(""),r.log(U.green("# Or create a .env file in your project:")),r.log('echo "VERCEL_TOKEN=your-token-here" > .env'),r.log(""),r.log(U.bold("\u{1F680} Quick Start Commands")),r.log(""),r.log(U.cyan("1. Initialize configuration:")),r.log(" doorman init --interactive"),r.log(""),r.log(U.cyan("2. Validate setup:")),r.log(" doorman validate"),r.log(""),r.log(U.cyan("3. Check current status:")),r.log(" doorman status"),r.log(""),r.log(U.cyan("4. View existing rules:")),r.log(" doorman list"),r.log(""),r.log(U.cyan("5. Sync your configuration:")),r.log(" doorman sync"),r.log(""),r.log(U.bold("\u{1F527} Common Issues")),r.log(""),r.log(U.yellow('\u274C "Project not found" error:')),r.log(" \u2022 Double-check your Project ID"),r.log(" \u2022 Ensure your token has access to the project"),r.log(" \u2022 Verify the project has Pro plan or higher"),r.log(""),r.log(U.yellow('\u274C "Unauthorized" error:')),r.log(" \u2022 Check your VERCEL_TOKEN is set correctly"),r.log(" \u2022 Ensure token hasn't expired"),r.log(" \u2022 Verify token has firewall permissions"),r.log(""),r.log(U.yellow('\u274C "Firewall not available" error:')),r.log(" \u2022 Upgrade to Pro plan or higher"),r.log(" \u2022 Contact Vercel support if issue persists"),r.log(""),r.log(U.bold("\u{1F4DA} Additional Resources")),r.log(""),r.log(U.cyan("Documentation:")),r.log(" \u2022 Doorman Docs: https://doorman.griffen.codes"),r.log(" \u2022 Vercel Firewall: https://vercel.com/docs/security/vercel-firewall"),r.log(" \u2022 API Reference: https://vercel.com/docs/rest-api/endpoints/firewall"),r.log(""),r.log(U.cyan("Support:")),r.log(" \u2022 GitHub Issues: https://github.com/gfargo/doorman/issues"),r.log(" \u2022 Vercel Support: https://vercel.com/help"),r.log(""),r.log(U.bold("\u{1F512} Security Best Practices")),r.log(""),r.log("\u2022 Store API tokens securely (use environment variables)"),r.log("\u2022 Set token expiration dates"),r.log("\u2022 Use principle of least privilege for token scopes"),r.log("\u2022 Regularly rotate API tokens"),r.log("\u2022 Test rules in staging before production"),r.log("\u2022 Keep backups of working configurations"),r.log(""),r.log(U.bold.green("\u2705 Ready to get started?")),r.log(U.dim("Run: ")+U.cyan("doorman init --interactive")),r.log("")};var fi={};J(fi,{builder:()=>da,command:()=>la,desc:()=>ca,handler:()=>ua});P();import F from"chalk";var la="status",ca="Show sync status between local and remote firewall configuration",da={config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:X,...ee,format:{alias:"f",type:"string",choices:["table","json"],description:"Output format",default:"table"},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}},ua=async i=>{await te({config:i.config,provider:i.provider,...G(i),debug:i.debug,ci:i.ci,errorContext:"checking status"},async({config:e,provider:t})=>{await pa(t,ye(e),i)})};async function pa(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(`
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(`
131
- Recommendations:`)),s.recommendations.forEach(d=>r.log(` - ${d}`)))}var hi={};J(hi,{builder:()=>ga,command:()=>pa,desc:()=>fa,handler:()=>ma});P();import K from"chalk";var pa="sync",fa="Sync Vercel Firewall rules with config file",ga={config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:X,...ee,debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1},allowDeletions:{type:"boolean",description:"Required together with --ci to let a non-interactive sync delete existing rules/IPs. Without it, a --ci sync that would delete anything is refused rather than applied silently.",default:!1}},ma=async i=>{await te({config:i.config,provider:i.provider,...G(i),debug:i.debug,ci:i.ci,errorContext:"syncing firewall rules"},async e=>{await ha(e.provider,e.config,i)})};async function ha(i,e,t){let n=ye(e);r.start(K.magenta(`Calculating ${i.name} firewall configuration changes...`));let o=await i.getChanges(n),s=n.metadata?.version,a=o.version!==void 0&&s!==o.version;if(!o.hasChanges&&!a){r.success(K.green("No changes detected. Firewall rules are in sync."));return}r.log(K.bold(`
131
+ Recommendations:`)),s.recommendations.forEach(d=>r.log(` - ${d}`)))}var hi={};J(hi,{builder:()=>ma,command:()=>fa,desc:()=>ga,handler:()=>ha});P();import K from"chalk";var fa="sync",ga="Sync Vercel Firewall rules with config file",ma={config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:X,...ee,debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1},allowDeletions:{type:"boolean",description:"Required together with --ci to let a non-interactive sync delete existing rules/IPs. Without it, a --ci sync that would delete anything is refused rather than applied silently.",default:!1}},ha=async i=>{await te({config:i.config,provider:i.provider,...G(i),debug:i.debug,ci:i.ci,errorContext:"syncing firewall rules"},async e=>{await ya(e.provider,e.config,i)})};async function ya(i,e,t){let n=ye(e);r.start(K.magenta(`Calculating ${i.name} firewall configuration changes...`));let o=await i.getChanges(n),s=n.metadata?.version,a=o.version!==void 0&&s!==o.version;if(!o.hasChanges&&!a){r.success(K.green("No changes detected. Firewall rules are in sync."));return}r.log(K.bold(`
132
132
  Proposed Changes:
133
- `)),gi("Rules to add",o.rulesToAdd,K.green("+")),gi("Rules to update",o.rulesToUpdate,K.cyan("~")),gi("Rules to delete",o.rulesToDelete,K.red("-"));let l=o.ipsToAdd??[],c=o.ipsToUpdate??[],d=o.ipsToDelete??[];(l.length||c.length||d.length)&&(mi("IPs to add",l,K.green("+")),mi("IPs to update",c,K.cyan("~")),mi("IPs to delete",d,K.red("-"))),a&&r.log(` Remote config version: ${K.red(s??"unknown")} ${K.dim("->")} ${K.green(o.version)}`),r.start("Starting firewall rules sync...");let u=await i.syncRules(n,{force:t.ci,allowDeletions:t.allowDeletions});if(!u.success)throw new Error(`Sync failed: ${u.errors?.join(", ")||"unknown error"}`);r.success(K.green("Firewall rules sync completed successfully")),r.log(` Rules: ${u.rulesAdded} added, ${u.rulesUpdated} updated, ${u.rulesDeleted} deleted`),(u.ipsAdded||u.ipsUpdated||u.ipsDeleted)&&r.log(` IPs: ${u.ipsAdded??0} added, ${u.ipsUpdated??0} updated, ${u.ipsDeleted??0} deleted`),u.warnings?.forEach(f=>r.warn(f)),u.idRemappings?.length&&(r.info(K.yellow("Rule IDs were reassigned by the provider and written back to the local config:")),u.idRemappings.forEach(f=>{r.log(` - ${f.name}: ${K.red(f.oldId||"none")} ${K.dim("->")} ${K.green(f.newId)}`)}));let p=Ut(e,u);p.changed&&(await z(p.config,t.config),r.success(K.green("Updated local config with new version/metadata")))}function gi(i,e,t){e.length!==0&&(r.log(` ${t} ${i} (${e.length}):`),e.forEach(n=>r.log(` - ${n.name}`)))}function mi(i,e,t){e.length!==0&&(r.log(` ${t} ${i} (${e.length}):`),e.forEach(n=>r.log(` - ${n.ip}${n.hostname?` (${n.hostname})`:""}`)))}var wi={};J(wi,{builder:()=>Ca,command:()=>wa,desc:()=>Ra,handler:()=>Ia});P();import Fe from"chalk";import{LogLevels as ya}from"consola";var jn={metadata:{title:"Block AI Bots Firewall Rule",reference:"https://vercel.com/templates/other/block-ai-bots-firewall-rule"},config:{rules:[{id:"rule_detect_ai_bots",name:"Detect AI Bots",description:"",conditionGroup:[{conditions:[{type:"user_agent",op:"re",value:"AI2Bot|Ai2Bot-Dolma|Amazonbot|Applebot|Applebot-Extended|Bytespider|CCBot|ChatGPT-User|Claude-Web|ClaudeBot|Diffbot|FacebookBot|FriendlyCrawler|GPTBot|Google-Extended|GoogleOther|GoogleOther-Image|GoogleOther-Video|ICC-Crawler|ImagesiftBot|Meta-ExternalAgent|Meta-ExternalFetcher|OAI-SearchBot|PerplexityBot|PetalBot|Scrapy|Timpibot|VelenPublicWebCrawler|Webzio-Extended|YouBot|anthropic-ai|cohere-ai|facebookexternalhit|img2dataset|omgili|omgilibot"}]}],action:{mitigate:{action:"log"}},active:!0}]}};var zn={metadata:{title:"Block Bad Bots Firewall Rule",reference:"https://vercel.com/templates/other/block-bad-bots-firewall-rule"},config:{rules:[{id:"rule_detect_bad_bots",name:"Detect Bad Bots",description:"",conditionGroup:[{conditions:[{type:"user_agent",op:"re",value:"01h4x.com|360Spider|404checker|404enemy|80legs|ADmantX|AIBOT|ALittle Client|ASPSeek|Abonti|Aboundex|Aboundexbot|Acunetix|AdsTxtCrawlerTP|AfD-Verbotsverfahren|AhrefsBot|AiHitBot|Aipbot|Alexibot|AllSubmitter|Alligator|AlphaBot|Anarchie|Anarchy|Anarchy99|Ankit|Anthill|Apexoo|Aspiegel|Asterias|Atomseobot|Attach|AwarioBot|AwarioRssBot|AwarioSmartBot|BBBike|BDCbot|BDFetch|BLEXBot|BackDoorBot|BackStreet|BackWeb|Backlink-Ceck|BacklinkCrawler|BacklinksExtendedBot|Badass|Bandit|Barkrowler|BatchFTP|Battleztar Bazinga|BetaBot|Bigfoot|Bitacle|BlackWidow|Black Hole|Blackboard|Blow|BlowFish|Boardreader|Bolt|BotALot|Brandprotect|Brandwatch|Buck|Buddy|BuiltBotTough|BuiltWith|Bullseye|BunnySlippers|BuzzSumo|Bytespider|CATExplorador|CCBot|CODE87|CSHttp|Calculon|CazoodleBot|Cegbfeieh|CensysInspect|ChatGPT-User|CheTeam|CheeseBot|CherryPicker|ChinaClaw|Chlooe|Citoid|Claritybot|ClaudeBot|Cliqzbot|Cloud mapping|Cocolyzebot|Cogentbot|Collector|Copier|CopyRightCheck|Copyscape|Cosmos|Craftbot|Crawling at Home Project|CrazyWebCrawler|Crescent|CrunchBot|Curious|Custo|CyotekWebCopy|DBLBot|DIIbot|DSearch|DTS Agent|DataCha0s|DatabaseDriverMysqli|Demon|Deusu|Devil|Digincore|DigitalPebble|Dirbuster|Disco|Discobot|Discoverybot|Dispatch|DittoSpyder|DnBCrawler-Analytics|DnyzBot|DomCopBot|DomainAppender|DomainCrawler|DomainSigmaCrawler|DomainStatsBot|Domains Project|Dotbot|Download Wonder|Dragonfly|Drip|ECCP/1.0|EMail Siphon|EMail Wolf|EasyDL|Ebingbong|Ecxi|EirGrabber|EroCrawler|Evil|Exabot|Express WebPictures|ExtLinksBot|Extractor|ExtractorPro|Extreme Picture Finder|EyeNetIE|Ezooms|FDM|FHscan|FacebookBot|FemtosearchBot|Fimap|Firefox/7.0|FlashGet|Flunky|Foobot|Freeuploader|FrontPage|Fuzz|FyberSpider|Fyrebot|G-i-g-a-b-o-t|GPTBot|GT::WWW|GalaxyBot|Genieo|GermCrawler|GetRight|GetWeb|Getintent|Gigabot|Go!Zilla|Go-Ahead-Got-It|GoZilla|Gotit|GrabNet|Grabber|Grafula|GrapeFX|GrapeshotCrawler|GridBot|HEADMasterSEO|HMView|HTMLparser|HTTP::Lite|HTTrack|Haansoft|HaosouSpider|Harvest|Havij|Heritrix|Hloader|HonoluluBot|Humanlinks|HybridBot|IDBTE4M|IDBot|IRLbot|Iblog|Id-search|IlseBot|Image Fetch|Image Sucker|ImagesiftBot|IndeedBot|Indy Library|InfoNaviRobot|InfoTekies|Information Security Team InfraSec Scanner|InfraSec Scanner|Intelliseek|InterGET|InternetMeasurement|InternetSeer|Internet Ninja|Iria|Iskanie|IstellaBot|JOC Web Spider|JamesBOT|Jbrofuzz|JennyBot|JetCar|Jetty|JikeSpider|Joomla|Jorgee|JustView|Jyxobot|Kenjin Spider|Keybot Translation-Search-Machine|Keyword Density|Kinza|Kozmosbot|LNSpiderguy|LWP::Simple|Lanshanbot|Larbin|Leap|LeechFTP|LeechGet|LexiBot|Lftp|LibWeb|Libwhisker|LieBaoFast|Lightspeedsystems|Likse|LinkScan|LinkWalker|Linkbot|LinkextractorPro|LinkpadBot|LinksManager|LinqiaMetadataDownloaderBot|LinqiaRSSBot|LinqiaScrapeBot|Lipperhey|Lipperhey Spider|Litemage_walker|Lmspider|Ltx71|MFC_Tear_Sample|MIDown tool|MIIxpc|MJ12bot|MQQBrowser|MSFrontPage|MSIECrawler|MTRobot|Mag-Net|Magnet|Mail.RU_Bot|Majestic-SEO|Majestic12|Majestic SEO|MarkMonitor|MarkWatch|Mass Downloader|Masscan|Mata Hari|MauiBot|Mb2345Browser|MeanPath Bot|Meanpathbot|Mediatoolkitbot|MegaIndex.ru|Metauri|MicroMessenger|Microsoft Data Access|Microsoft URL Control|Minefield|Mister PiX|Moblie Safari|Mojeek|Mojolicious|MolokaiBot|Morfeus Fucking Scanner|Mozlila|Mr.4x3|Msrabot|Musobot|NICErsPRO|NPbot|Name Intelligence|Nameprotect|Navroad|NearSite|Needle|Nessus|NetAnts|NetLyzer|NetMechanic|NetSpider|NetZIP|Net Vampire|Netcraft|Nettrack|Netvibes|NextGenSearchBot|Nibbler|Niki-bot|Nikto|NimbleCrawler|Nimbostratus|Ninja|Nmap|Nuclei|Nutch|Octopus|Offline Explorer|Offline Navigator|OnCrawl|OpenLinkProfiler|OpenVAS|Openfind|Openvas|OrangeBot|OrangeSpider|OutclicksBot|OutfoxBot|PECL::HTTP|PHPCrawl|POE-Component-Client-HTTP|PageAnalyzer|PageGrabber|PageScorer|PageThing.com|Page Analyzer|Pandalytics|Panscient|Papa Foto|Pavuk|PeoplePal|Petalbot|Pi-Monster|Picscout|Picsearch|PictureFinder|Piepmatz|Pimonster|Pixray|PleaseCrawl|Pockey|ProPowerBot|ProWebWalker|Probethenet|Proximic|Psbot|Pu_iN|Pump|PxBroker|PyCurl|QueryN Metasearch|Quick-Crawler|RSSingBot|Rainbot|RankActive|RankActiveLinkBot|RankFlex|RankingBot|RankingBot2|Rankivabot|RankurBot|Re-re|ReGet|RealDownload|Reaper|RebelMouse|Recorder|RedesScrapy|RepoMonkey|Ripper|RocketCrawler|Rogerbot|SBIder|SEOkicks|SEOkicks-Robot|SEOlyt|SEOlyticsCrawler|SEOprofiler|SEOstats|SISTRIX|SMTBot|SalesIntelligent|ScanAlert|Scanbot|ScoutJet|Scrapy|Screaming|ScreenerBot|ScrepyBot|Searchestate|SearchmetricsBot|Seekport|SeekportBot|SemanticJuice|Semrush|SemrushBot|SentiBot|SenutoBot|SeoSiteCheckup|SeobilityBot|Seomoz|Shodan|Siphon|SiteCheckerBotCrawler|SiteExplorer|SiteLockSpider|SiteSnagger|SiteSucker|Site Sucker|Sitebeam|Siteimprove|Sitevigil|SlySearch|SmartDownload|Snake|Snapbot|Snoopy|SocialRankIOBot|Sociscraper|Sogou web spider|Sosospider|Sottopop|SpaceBison|Spammen|SpankBot|Spanner|Spbot|Spider_Bot|Spider_Bot/3.0|Spinn3r|SputnikBot|Sqlmap|Sqlworm|Sqworm|Steeler|Stripper|Sucker|Sucuri|SuperBot|SuperHTTP|Surfbot|SurveyBot|Suzuran|Swiftbot|Szukacz|T0PHackTeam|T8Abot|Teleport|TeleportPro|Telesoft|Telesphoreo|Telesphorep|TheNomad|The Intraformant|Thumbor|TightTwatBot|TinyTestBot|Titan|Toata|Toweyabot|Tracemyfile|Trendiction|Trendictionbot|True_Robot|Turingos|Turnitin|TurnitinBot|TwengaBot|Twice|Typhoeus|URLy.Warning|URLy Warning|UnisterBot|Upflow|V-BOT|VB Project|VCI|Vacuum|Vagabondo|VelenPublicWebCrawler|VeriCiteCrawler|VidibleScraper|Virusdie|VoidEYE|Voil|Voltron|WASALive-Bot|WBSearchBot|WEBDAV|WISENutbot|WPScan|WWW-Collector-E|WWW-Mechanize|WWW::Mechanize|WWWOFFLE|Wallpapers|Wallpapers/3.0|WallpapersHD|WeSEE|WebAuto|WebBandit|WebCollage|WebCopier|WebEnhancer|WebFetch|WebFuck|WebGo IS|WebImageCollector|WebLeacher|WebPix|WebReaper|WebSauger|WebStripper|WebSucker|WebWhacker|WebZIP|Web Auto|Web Collage|Web Enhancer|Web Fetch|Web Fuck|Web Pix|Web Sauger|Web Sucker|Webalta|WebmasterWorldForumBot|Webshag|WebsiteExtractor|WebsiteQuester|Website Quester|Webster|Whack|Whacker|Whatweb|Who.is Bot|Widow|WinHTTrack|WiseGuys Robot|Wonderbot|Woobot|Wotbox|Wprecon|Xaldon WebSpider|Xaldon_WebSpider|Xenu|YaK|YoudaoBot|Zade|Zauba|Zermelo|Zeus|Zitebot|ZmEu|ZoomBot|ZoominfoBot|ZumBot|ZyBorg|adscanner|anthropic-ai|archive.org_bot|arquivo-web-crawler|arquivo.pt|autoemailspider|awario.com|backlink-check|cah.io.community|check1.exe|clark-crawler|coccocbot|cognitiveseo|cohere-ai|com.plumanalytics|crawl.sogou.com|crawler.feedback|crawler4j|dataforseo.com|dataforseobot|demandbase-bot|domainsproject.org|eCatch|evc-batch|facebookscraper|gopher|heritrix|imagesift.com|instabid|internetVista monitor|ips-agent|isitwp.com|iubenda-radar|linkdexbot|linkfluence|lwp-request|lwp-trivial|magpie-crawler|meanpathbot|mediawords|muhstik-scan|netEstate NE Crawler|oBot|omgili|openai|openai.com|page scorer|pcBrowser|plumanalytics|polaris version|probe-image-size|ripz|s1z.ru|satoristudio.net|scalaj-http|scan.lol|seobility|seocompany.store|seoscanners|seostar|serpstatbot|sexsearcher|sitechecker.pro|siteripz|sogouspider|sp_auditbot|spyfu|sysscan|tAkeOut|trendiction.com|trendiction.de|ubermetrics-technologies.com|voyagerx.com|webgains-bot|webmeup-crawler|webpros.com|webprosbot|x09Mozilla|x22Mozilla|xpymep1.exe|zauba.io|zgrab"}]}],action:{mitigate:{action:"log"}},active:!0}]}};var Gn={metadata:{title:"Block OFAC-Sanctioned Countries",reference:"https://vercel.com/templates/other/block-ofac-sanctioned-countries-firewall-rule"},config:{rules:[{id:"rule_block_traffic_from_ofac_sanctioned_countries",name:"Block traffic from OFAC-sanctioned countries",description:"Blocks traffic from OFAC-sanctioned countries and enforces a one-hour persistent block after the first violation.",conditionGroup:[{conditions:[{type:"geo_country",op:"inc",value:["SY","IR","RU","CU","KP"]}]}],action:{mitigate:{action:"deny",actionDuration:"1h"}},active:!0}]}};var Kn={metadata:{title:"Deny Common WordPress URLs Firewall Rule",reference:"https://vercel.com/templates/other/block-wordpress-urls-firewall-rule"},config:{rules:[{id:"rule_deny_word_press_ur_ls",name:"Deny WordPress URLs",description:"",conditionGroup:[{conditions:[{type:"path",op:"re",value:"/(wp-admin|wp-login\\.php|xmlrpc\\.php|wp-content|wp-includes|wp-signup\\.php|wp-activate\\.php|register\\.php|wp-register\\.php)"}]}],action:{mitigate:{action:"deny"}},active:!0}]}};var yi={"ai-bots":jn,"bad-bots":zn,"block-ofac-sanctioned-countries":Gn,wordpress:Kn};var Hn=i=>yi[i]?.config;Ee();var wa="template [name]",Ra="Add a firewall rule template to your configuration",Ca={name:{type:"string",description:"Name of the template to add"},config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},dryRun:{alias:"d",type:"boolean",description:"Preview changes without applying them",default:!1},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}};function ba(i,e){let t=new Set(i.map(o=>o.name.toLowerCase())),n=e.filter(o=>t.has(o.name.toLowerCase())).map(o=>o.name);return[...new Set(n)]}var va=()=>Object.keys(yi),Ia=async i=>{try{i.debug&&(r.level=ya.debug),r.debug("Template command arguments:",i);let e=i.name;if(!e){let c=va();e=await b("Select a template to add:",{type:"select",options:c,initial:c[0]})}let t=Hn(e);t||(r.error(`Template not found: ${e}`),process.exit(1)),r.debug("Template content:",t),r.start("Loading current configuration...");let n=await j(i.config,"raw"),o=n.rules||[],s=ba(o,t.rules);if(i.dryRun){r.info(Fe.cyan(`
134
- Dry run - The following rules would be added:`)),r.log(JSON.stringify(t.rules,null,2)),s.length>0&&r.warn(Fe.yellow(`\u26A0\uFE0F Rule name(s) already exist in the configuration: ${s.join(", ")}`));return}if(s.length>0){if(r.warn(Fe.yellow(`\u26A0\uFE0F Rule name(s) already exist in the configuration: ${s.join(", ")}`)),i.ci||!process.stdin.isTTY){r.info(Fe.dim("Skipping \u2014 non-interactive and rule name(s) already exist. Template not applied."));return}if(!await b("Proceed anyway?",{type:"confirm",initial:!1})){r.info("Cancelled.");return}}let a={...n,rules:[...o,...t.rules]},l=De.safeParse(a);l.success||(r.error(Fe.red("The resulting configuration would be invalid:")),l.error.errors.forEach(c=>{let d=c.path.join(".");r.error(Fe.red(` - ${d}: ${c.message}`))}),r.info(Fe.dim("Template was not applied. Fix the issues above and try again.")),process.exit(1)),r.start("Saving updated configuration..."),await z(a,i.config,{validate:!1}),r.success(Fe.green(`Successfully added template '${e}' to configuration`))}catch(e){Q(e,"adding template")}};var Ri={};J(Ri,{builder:()=>Ta,command:()=>Ea,desc:()=>Aa,handler:()=>$a});P();import oe from"chalk";var Ea="validate",Aa="Validate firewall configuration file",Ta={config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},verbose:{alias:"v",type:"boolean",description:"Show detailed validation results",default:!1}},$a=async i=>{try{let e=await j(i.config,"raw"),t=Ue.getInstance(),n=ke(e);i.verbose&&r.start(`Validating configuration file...
133
+ `)),gi("Rules to add",o.rulesToAdd,K.green("+")),gi("Rules to update",o.rulesToUpdate,K.cyan("~")),gi("Rules to delete",o.rulesToDelete,K.red("-"));let l=o.ipsToAdd??[],c=o.ipsToUpdate??[],d=o.ipsToDelete??[];(l.length||c.length||d.length)&&(mi("IPs to add",l,K.green("+")),mi("IPs to update",c,K.cyan("~")),mi("IPs to delete",d,K.red("-"))),a&&r.log(` Remote config version: ${K.red(s??"unknown")} ${K.dim("->")} ${K.green(o.version)}`),r.start("Starting firewall rules sync...");let u=await i.syncRules(n,{force:t.ci,allowDeletions:t.allowDeletions});if(!u.success)throw new Error(`Sync failed: ${u.errors?.join(", ")||"unknown error"}`);r.success(K.green("Firewall rules sync completed successfully")),r.log(` Rules: ${u.rulesAdded} added, ${u.rulesUpdated} updated, ${u.rulesDeleted} deleted`),(u.ipsAdded||u.ipsUpdated||u.ipsDeleted)&&r.log(` IPs: ${u.ipsAdded??0} added, ${u.ipsUpdated??0} updated, ${u.ipsDeleted??0} deleted`),u.warnings?.forEach(f=>r.warn(f)),u.idRemappings?.length&&(r.info(K.yellow("Rule IDs were reassigned by the provider and written back to the local config:")),u.idRemappings.forEach(f=>{r.log(` - ${f.name}: ${K.red(f.oldId||"none")} ${K.dim("->")} ${K.green(f.newId)}`)}));let p=Ut(e,u);p.changed&&(await z(p.config,t.config),r.success(K.green("Updated local config with new version/metadata")))}function gi(i,e,t){e.length!==0&&(r.log(` ${t} ${i} (${e.length}):`),e.forEach(n=>r.log(` - ${n.name}`)))}function mi(i,e,t){e.length!==0&&(r.log(` ${t} ${i} (${e.length}):`),e.forEach(n=>r.log(` - ${n.ip}${n.hostname?` (${n.hostname})`:""}`)))}var wi={};J(wi,{builder:()=>ba,command:()=>Ra,desc:()=>Ca,handler:()=>Ea});P();import Fe from"chalk";import{LogLevels as wa}from"consola";var jn={metadata:{title:"Block AI Bots Firewall Rule",reference:"https://vercel.com/templates/other/block-ai-bots-firewall-rule"},config:{rules:[{id:"rule_detect_ai_bots",name:"Detect AI Bots",description:"",conditionGroup:[{conditions:[{type:"user_agent",op:"re",value:"AI2Bot|Ai2Bot-Dolma|Amazonbot|Applebot|Applebot-Extended|Bytespider|CCBot|ChatGPT-User|Claude-Web|ClaudeBot|Diffbot|FacebookBot|FriendlyCrawler|GPTBot|Google-Extended|GoogleOther|GoogleOther-Image|GoogleOther-Video|ICC-Crawler|ImagesiftBot|Meta-ExternalAgent|Meta-ExternalFetcher|OAI-SearchBot|PerplexityBot|PetalBot|Scrapy|Timpibot|VelenPublicWebCrawler|Webzio-Extended|YouBot|anthropic-ai|cohere-ai|facebookexternalhit|img2dataset|omgili|omgilibot"}]}],action:{mitigate:{action:"log"}},active:!0}]}};var zn={metadata:{title:"Block Bad Bots Firewall Rule",reference:"https://vercel.com/templates/other/block-bad-bots-firewall-rule"},config:{rules:[{id:"rule_detect_bad_bots",name:"Detect Bad Bots",description:"",conditionGroup:[{conditions:[{type:"user_agent",op:"re",value:"01h4x.com|360Spider|404checker|404enemy|80legs|ADmantX|AIBOT|ALittle Client|ASPSeek|Abonti|Aboundex|Aboundexbot|Acunetix|AdsTxtCrawlerTP|AfD-Verbotsverfahren|AhrefsBot|AiHitBot|Aipbot|Alexibot|AllSubmitter|Alligator|AlphaBot|Anarchie|Anarchy|Anarchy99|Ankit|Anthill|Apexoo|Aspiegel|Asterias|Atomseobot|Attach|AwarioBot|AwarioRssBot|AwarioSmartBot|BBBike|BDCbot|BDFetch|BLEXBot|BackDoorBot|BackStreet|BackWeb|Backlink-Ceck|BacklinkCrawler|BacklinksExtendedBot|Badass|Bandit|Barkrowler|BatchFTP|Battleztar Bazinga|BetaBot|Bigfoot|Bitacle|BlackWidow|Black Hole|Blackboard|Blow|BlowFish|Boardreader|Bolt|BotALot|Brandprotect|Brandwatch|Buck|Buddy|BuiltBotTough|BuiltWith|Bullseye|BunnySlippers|BuzzSumo|Bytespider|CATExplorador|CCBot|CODE87|CSHttp|Calculon|CazoodleBot|Cegbfeieh|CensysInspect|ChatGPT-User|CheTeam|CheeseBot|CherryPicker|ChinaClaw|Chlooe|Citoid|Claritybot|ClaudeBot|Cliqzbot|Cloud mapping|Cocolyzebot|Cogentbot|Collector|Copier|CopyRightCheck|Copyscape|Cosmos|Craftbot|Crawling at Home Project|CrazyWebCrawler|Crescent|CrunchBot|Curious|Custo|CyotekWebCopy|DBLBot|DIIbot|DSearch|DTS Agent|DataCha0s|DatabaseDriverMysqli|Demon|Deusu|Devil|Digincore|DigitalPebble|Dirbuster|Disco|Discobot|Discoverybot|Dispatch|DittoSpyder|DnBCrawler-Analytics|DnyzBot|DomCopBot|DomainAppender|DomainCrawler|DomainSigmaCrawler|DomainStatsBot|Domains Project|Dotbot|Download Wonder|Dragonfly|Drip|ECCP/1.0|EMail Siphon|EMail Wolf|EasyDL|Ebingbong|Ecxi|EirGrabber|EroCrawler|Evil|Exabot|Express WebPictures|ExtLinksBot|Extractor|ExtractorPro|Extreme Picture Finder|EyeNetIE|Ezooms|FDM|FHscan|FacebookBot|FemtosearchBot|Fimap|Firefox/7.0|FlashGet|Flunky|Foobot|Freeuploader|FrontPage|Fuzz|FyberSpider|Fyrebot|G-i-g-a-b-o-t|GPTBot|GT::WWW|GalaxyBot|Genieo|GermCrawler|GetRight|GetWeb|Getintent|Gigabot|Go!Zilla|Go-Ahead-Got-It|GoZilla|Gotit|GrabNet|Grabber|Grafula|GrapeFX|GrapeshotCrawler|GridBot|HEADMasterSEO|HMView|HTMLparser|HTTP::Lite|HTTrack|Haansoft|HaosouSpider|Harvest|Havij|Heritrix|Hloader|HonoluluBot|Humanlinks|HybridBot|IDBTE4M|IDBot|IRLbot|Iblog|Id-search|IlseBot|Image Fetch|Image Sucker|ImagesiftBot|IndeedBot|Indy Library|InfoNaviRobot|InfoTekies|Information Security Team InfraSec Scanner|InfraSec Scanner|Intelliseek|InterGET|InternetMeasurement|InternetSeer|Internet Ninja|Iria|Iskanie|IstellaBot|JOC Web Spider|JamesBOT|Jbrofuzz|JennyBot|JetCar|Jetty|JikeSpider|Joomla|Jorgee|JustView|Jyxobot|Kenjin Spider|Keybot Translation-Search-Machine|Keyword Density|Kinza|Kozmosbot|LNSpiderguy|LWP::Simple|Lanshanbot|Larbin|Leap|LeechFTP|LeechGet|LexiBot|Lftp|LibWeb|Libwhisker|LieBaoFast|Lightspeedsystems|Likse|LinkScan|LinkWalker|Linkbot|LinkextractorPro|LinkpadBot|LinksManager|LinqiaMetadataDownloaderBot|LinqiaRSSBot|LinqiaScrapeBot|Lipperhey|Lipperhey Spider|Litemage_walker|Lmspider|Ltx71|MFC_Tear_Sample|MIDown tool|MIIxpc|MJ12bot|MQQBrowser|MSFrontPage|MSIECrawler|MTRobot|Mag-Net|Magnet|Mail.RU_Bot|Majestic-SEO|Majestic12|Majestic SEO|MarkMonitor|MarkWatch|Mass Downloader|Masscan|Mata Hari|MauiBot|Mb2345Browser|MeanPath Bot|Meanpathbot|Mediatoolkitbot|MegaIndex.ru|Metauri|MicroMessenger|Microsoft Data Access|Microsoft URL Control|Minefield|Mister PiX|Moblie Safari|Mojeek|Mojolicious|MolokaiBot|Morfeus Fucking Scanner|Mozlila|Mr.4x3|Msrabot|Musobot|NICErsPRO|NPbot|Name Intelligence|Nameprotect|Navroad|NearSite|Needle|Nessus|NetAnts|NetLyzer|NetMechanic|NetSpider|NetZIP|Net Vampire|Netcraft|Nettrack|Netvibes|NextGenSearchBot|Nibbler|Niki-bot|Nikto|NimbleCrawler|Nimbostratus|Ninja|Nmap|Nuclei|Nutch|Octopus|Offline Explorer|Offline Navigator|OnCrawl|OpenLinkProfiler|OpenVAS|Openfind|Openvas|OrangeBot|OrangeSpider|OutclicksBot|OutfoxBot|PECL::HTTP|PHPCrawl|POE-Component-Client-HTTP|PageAnalyzer|PageGrabber|PageScorer|PageThing.com|Page Analyzer|Pandalytics|Panscient|Papa Foto|Pavuk|PeoplePal|Petalbot|Pi-Monster|Picscout|Picsearch|PictureFinder|Piepmatz|Pimonster|Pixray|PleaseCrawl|Pockey|ProPowerBot|ProWebWalker|Probethenet|Proximic|Psbot|Pu_iN|Pump|PxBroker|PyCurl|QueryN Metasearch|Quick-Crawler|RSSingBot|Rainbot|RankActive|RankActiveLinkBot|RankFlex|RankingBot|RankingBot2|Rankivabot|RankurBot|Re-re|ReGet|RealDownload|Reaper|RebelMouse|Recorder|RedesScrapy|RepoMonkey|Ripper|RocketCrawler|Rogerbot|SBIder|SEOkicks|SEOkicks-Robot|SEOlyt|SEOlyticsCrawler|SEOprofiler|SEOstats|SISTRIX|SMTBot|SalesIntelligent|ScanAlert|Scanbot|ScoutJet|Scrapy|Screaming|ScreenerBot|ScrepyBot|Searchestate|SearchmetricsBot|Seekport|SeekportBot|SemanticJuice|Semrush|SemrushBot|SentiBot|SenutoBot|SeoSiteCheckup|SeobilityBot|Seomoz|Shodan|Siphon|SiteCheckerBotCrawler|SiteExplorer|SiteLockSpider|SiteSnagger|SiteSucker|Site Sucker|Sitebeam|Siteimprove|Sitevigil|SlySearch|SmartDownload|Snake|Snapbot|Snoopy|SocialRankIOBot|Sociscraper|Sogou web spider|Sosospider|Sottopop|SpaceBison|Spammen|SpankBot|Spanner|Spbot|Spider_Bot|Spider_Bot/3.0|Spinn3r|SputnikBot|Sqlmap|Sqlworm|Sqworm|Steeler|Stripper|Sucker|Sucuri|SuperBot|SuperHTTP|Surfbot|SurveyBot|Suzuran|Swiftbot|Szukacz|T0PHackTeam|T8Abot|Teleport|TeleportPro|Telesoft|Telesphoreo|Telesphorep|TheNomad|The Intraformant|Thumbor|TightTwatBot|TinyTestBot|Titan|Toata|Toweyabot|Tracemyfile|Trendiction|Trendictionbot|True_Robot|Turingos|Turnitin|TurnitinBot|TwengaBot|Twice|Typhoeus|URLy.Warning|URLy Warning|UnisterBot|Upflow|V-BOT|VB Project|VCI|Vacuum|Vagabondo|VelenPublicWebCrawler|VeriCiteCrawler|VidibleScraper|Virusdie|VoidEYE|Voil|Voltron|WASALive-Bot|WBSearchBot|WEBDAV|WISENutbot|WPScan|WWW-Collector-E|WWW-Mechanize|WWW::Mechanize|WWWOFFLE|Wallpapers|Wallpapers/3.0|WallpapersHD|WeSEE|WebAuto|WebBandit|WebCollage|WebCopier|WebEnhancer|WebFetch|WebFuck|WebGo IS|WebImageCollector|WebLeacher|WebPix|WebReaper|WebSauger|WebStripper|WebSucker|WebWhacker|WebZIP|Web Auto|Web Collage|Web Enhancer|Web Fetch|Web Fuck|Web Pix|Web Sauger|Web Sucker|Webalta|WebmasterWorldForumBot|Webshag|WebsiteExtractor|WebsiteQuester|Website Quester|Webster|Whack|Whacker|Whatweb|Who.is Bot|Widow|WinHTTrack|WiseGuys Robot|Wonderbot|Woobot|Wotbox|Wprecon|Xaldon WebSpider|Xaldon_WebSpider|Xenu|YaK|YoudaoBot|Zade|Zauba|Zermelo|Zeus|Zitebot|ZmEu|ZoomBot|ZoominfoBot|ZumBot|ZyBorg|adscanner|anthropic-ai|archive.org_bot|arquivo-web-crawler|arquivo.pt|autoemailspider|awario.com|backlink-check|cah.io.community|check1.exe|clark-crawler|coccocbot|cognitiveseo|cohere-ai|com.plumanalytics|crawl.sogou.com|crawler.feedback|crawler4j|dataforseo.com|dataforseobot|demandbase-bot|domainsproject.org|eCatch|evc-batch|facebookscraper|gopher|heritrix|imagesift.com|instabid|internetVista monitor|ips-agent|isitwp.com|iubenda-radar|linkdexbot|linkfluence|lwp-request|lwp-trivial|magpie-crawler|meanpathbot|mediawords|muhstik-scan|netEstate NE Crawler|oBot|omgili|openai|openai.com|page scorer|pcBrowser|plumanalytics|polaris version|probe-image-size|ripz|s1z.ru|satoristudio.net|scalaj-http|scan.lol|seobility|seocompany.store|seoscanners|seostar|serpstatbot|sexsearcher|sitechecker.pro|siteripz|sogouspider|sp_auditbot|spyfu|sysscan|tAkeOut|trendiction.com|trendiction.de|ubermetrics-technologies.com|voyagerx.com|webgains-bot|webmeup-crawler|webpros.com|webprosbot|x09Mozilla|x22Mozilla|xpymep1.exe|zauba.io|zgrab"}]}],action:{mitigate:{action:"log"}},active:!0}]}};var Gn={metadata:{title:"Block OFAC-Sanctioned Countries",reference:"https://vercel.com/templates/other/block-ofac-sanctioned-countries-firewall-rule"},config:{rules:[{id:"rule_block_traffic_from_ofac_sanctioned_countries",name:"Block traffic from OFAC-sanctioned countries",description:"Blocks traffic from OFAC-sanctioned countries and enforces a one-hour persistent block after the first violation.",conditionGroup:[{conditions:[{type:"geo_country",op:"inc",value:["SY","IR","RU","CU","KP"]}]}],action:{mitigate:{action:"deny",actionDuration:"1h"}},active:!0}]}};var Kn={metadata:{title:"Deny Common WordPress URLs Firewall Rule",reference:"https://vercel.com/templates/other/block-wordpress-urls-firewall-rule"},config:{rules:[{id:"rule_deny_word_press_ur_ls",name:"Deny WordPress URLs",description:"",conditionGroup:[{conditions:[{type:"path",op:"re",value:"/(wp-admin|wp-login\\.php|xmlrpc\\.php|wp-content|wp-includes|wp-signup\\.php|wp-activate\\.php|register\\.php|wp-register\\.php)"}]}],action:{mitigate:{action:"deny"}},active:!0}]}};var yi={"ai-bots":jn,"bad-bots":zn,"block-ofac-sanctioned-countries":Gn,wordpress:Kn};var Hn=i=>yi[i]?.config;Ee();var Ra="template [name]",Ca="Add a firewall rule template to your configuration",ba={name:{type:"string",description:"Name of the template to add"},config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},dryRun:{alias:"d",type:"boolean",description:"Preview changes without applying them",default:!1},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}};function va(i,e){let t=new Set(i.map(o=>o.name.toLowerCase())),n=e.filter(o=>t.has(o.name.toLowerCase())).map(o=>o.name);return[...new Set(n)]}var Ia=()=>Object.keys(yi),Ea=async i=>{try{i.debug&&(r.level=wa.debug),r.debug("Template command arguments:",i);let e=i.name;if(!e){let c=Ia();e=await b("Select a template to add:",{type:"select",options:c,initial:c[0]})}let t=Hn(e);t||(r.error(`Template not found: ${e}`),process.exit(1)),r.debug("Template content:",t),r.start("Loading current configuration...");let n=await j(i.config,"raw"),o=n.rules||[],s=va(o,t.rules);if(i.dryRun){r.info(Fe.cyan(`
134
+ Dry run - The following rules would be added:`)),r.log(JSON.stringify(t.rules,null,2)),s.length>0&&r.warn(Fe.yellow(`\u26A0\uFE0F Rule name(s) already exist in the configuration: ${s.join(", ")}`));return}if(s.length>0){if(r.warn(Fe.yellow(`\u26A0\uFE0F Rule name(s) already exist in the configuration: ${s.join(", ")}`)),i.ci||!process.stdin.isTTY){r.info(Fe.dim("Skipping \u2014 non-interactive and rule name(s) already exist. Template not applied."));return}if(!await b("Proceed anyway?",{type:"confirm",initial:!1})){r.info("Cancelled.");return}}let a={...n,rules:[...o,...t.rules]},l=De.safeParse(a);l.success||(r.error(Fe.red("The resulting configuration would be invalid:")),l.error.errors.forEach(c=>{let d=c.path.join(".");r.error(Fe.red(` - ${d}: ${c.message}`))}),r.info(Fe.dim("Template was not applied. Fix the issues above and try again.")),process.exit(1)),r.start("Saving updated configuration..."),await z(a,i.config,{validate:!1}),r.success(Fe.green(`Successfully added template '${e}' to configuration`))}catch(e){Q(e,"adding template")}};var Ri={};J(Ri,{builder:()=>$a,command:()=>Aa,desc:()=>Ta,handler:()=>Pa});P();import oe from"chalk";var Aa="validate",Ta="Validate firewall configuration file",$a={config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},verbose:{alias:"v",type:"boolean",description:"Show detailed validation results",default:!1}},Pa=async i=>{try{let e=await j(i.config,"raw"),t=Ue.getInstance(),n=ke(e);i.verbose&&r.start(`Validating configuration file...
135
135
  `);let o=n?_e.safeParse(e):De.safeParse(e);i.verbose&&(r.log(oe.bold.underline("Zod Schema Validation:")),o.success?r.log(oe.green("\u2713 Schema validation passed")):(r.error(oe.red("\u2717 Schema validation failed:")),o.error.errors.forEach(l=>{let c=l.path.join(".");r.error(oe.red(` - ${c}: ${l.message}`))})));let s=!0,a=[];try{t.validateConfig(e),i.verbose&&(r.log(oe.bold.underline(`
136
136
  AJV Schema Validation:`)),r.log(oe.green("\u2713 JSON Schema validation passed")))}catch(l){if(s=!1,l instanceof N)a=l.ajvErrors||[],i.verbose&&(r.log(oe.bold.underline(`
137
137
  AJV Schema Validation:`)),r.error(oe.red("\u2717 JSON Schema validation failed:")),a.forEach(c=>{r.error(oe.red(` - ${c.instancePath}: ${c.message}`))}));else throw l}if(o.success&&s&&i.verbose){r.log(oe.bold.underline(`
138
138
  Custom Validations:`));let l=o.data,c=new Set,d=new Set;for(let u of l.rules)c.has(u.name)&&d.add(u.name),c.add(u.name);r.log(`${oe.cyan.dim(`
139
139
  Rule Names:`)}`),d.size>0?(r.error(oe.red("\u2717 Duplicate rule names found:")),d.forEach(u=>r.error(oe.red(` - "${u}"`)))):r.log(oe.green("\u2713 All rule names are unique")),r.log("")}if(o.success&&s)r.success(oe.green("Configuration is valid"));else{if(i.verbose)throw new Error("Configuration validation failed");{let l=[...o.success?[]:o.error.errors.map(c=>`${c.path.join(".")||"(root)"}: ${c.message}`),...s?[]:a.map(c=>`${c.instancePath||"(root)"}: ${c.message}`)];throw new Error(`Configuration validation failed:
140
140
  ${l.join(`
141
- `)}`)}}}catch(e){Q(e,"validating configuration")}};var Ci={};J(Ci,{builder:()=>Ua,command:()=>ka,desc:()=>Sa,handler:()=>La});P();import ve from"chalk";import{watchFile as Pa,unwatchFile as _a}from"fs";var ka="watch",Sa="Watch config file for changes and auto-sync",Ua={config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:X,...ee,interval:{alias:"i",type:"number",description:"Watch interval in milliseconds",default:1e3},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}},La=async i=>{let e=i.config||".doorman.json";await te({config:e,provider:i.provider,...G(i),debug:i.debug,ci:i.ci,errorContext:"setting up watch mode"},async({provider:t})=>{let n=!1,o=0;r.success(ve.green(`\u{1F440} Watching ${e} for changes...`)),r.log(ve.dim("Press Ctrl+C to stop watching")),r.log("");let s=async(c,d)=>{if(!(n||c.mtime.getTime()===o)){n=!0,o=c.mtime.getTime();try{r.log(ve.yellow(`\u{1F4DD} Config file changed at ${new Date().toLocaleTimeString()}`)),r.start("Validating and syncing changes...");let u=await j(e);await xa(t,u,e),r.log(ve.dim("Watching for more changes..."))}catch(u){r.error(ve.red(`\u274C Sync failed: ${u instanceof Error?u.message:String(u)}`)),r.log(ve.dim("Continuing to watch for changes..."))}finally{n=!1}}};Pa(e,{interval:i.interval},s);let a=()=>{r.log(`
142
- `),r.info(ve.yellow("Stopping watch mode...")),_a(e),process.exit(0)};process.on("SIGINT",a),process.on("SIGTERM",a);let l=()=>{setTimeout(l,1e3)};l()})};async function xa(i,e,t){let n=ye(e),o=await i.getChanges(n),s=n.metadata?.version,a=o.version!==void 0&&s!==o.version;if(!o.hasChanges&&!a){r.info(ve.blue("No changes detected, skipping sync"));return}let l=o.rulesToAdd.length+o.rulesToUpdate.length+o.rulesToDelete.length+(o.ipsToAdd?.length??0)+(o.ipsToUpdate?.length??0)+(o.ipsToDelete?.length??0);r.log(ve.cyan(`Syncing ${l} changes...`));let c=await i.syncRules(n,{force:!0,allowDeletions:!0});if(!c.success)throw new Error(`Sync failed: ${c.errors?.join(", ")||"unknown error"}`);r.success(ve.green(`\u2705 Sync completed at ${new Date().toLocaleTimeString()}`)),c.warnings?.forEach(u=>r.warn(u)),c.idRemappings?.length&&c.idRemappings.forEach(u=>{r.info(ve.dim(` Rule "${u.name}" ID updated: ${u.oldId||"none"} -> ${u.newId}`))});let d=Ut(e,c);d.changed&&await z(d.config,t)}var Zn=[pi,li,Gt,ci,fi,ri,hi,Ri,si,wi,ui,oi,ai,Ci];Da();async function Fa(){let i=(await import("chalk")).default,e=Oa(process.argv.slice(2));e.usage(`${i.bold("\u{1F6AA} Doorman")}
141
+ `)}`)}}}catch(e){Q(e,"validating configuration")}};var Ci={};J(Ci,{builder:()=>La,command:()=>Sa,desc:()=>Ua,handler:()=>xa});P();import ve from"chalk";import{watchFile as _a,unwatchFile as ka}from"fs";var Sa="watch",Ua="Watch config file for changes and auto-sync",La={config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:X,...ee,interval:{alias:"i",type:"number",description:"Watch interval in milliseconds",default:1e3},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}},xa=async i=>{let e=i.config||".doorman.json";await te({config:e,provider:i.provider,...G(i),debug:i.debug,ci:i.ci,errorContext:"setting up watch mode"},async({provider:t})=>{let n=!1,o=0;r.success(ve.green(`\u{1F440} Watching ${e} for changes...`)),r.log(ve.dim("Press Ctrl+C to stop watching")),r.log("");let s=async(c,d)=>{if(!(n||c.mtime.getTime()===o)){n=!0,o=c.mtime.getTime();try{r.log(ve.yellow(`\u{1F4DD} Config file changed at ${new Date().toLocaleTimeString()}`)),r.start("Validating and syncing changes...");let u=await j(e);await Da(t,u,e),r.log(ve.dim("Watching for more changes..."))}catch(u){r.error(ve.red(`\u274C Sync failed: ${u instanceof Error?u.message:String(u)}`)),r.log(ve.dim("Continuing to watch for changes..."))}finally{n=!1}}};_a(e,{interval:i.interval},s);let a=()=>{r.log(`
142
+ `),r.info(ve.yellow("Stopping watch mode...")),ka(e),process.exit(0)};process.on("SIGINT",a),process.on("SIGTERM",a);let l=()=>{setTimeout(l,1e3)};l()})};async function Da(i,e,t){let n=ye(e),o=await i.getChanges(n),s=n.metadata?.version,a=o.version!==void 0&&s!==o.version;if(!o.hasChanges&&!a){r.info(ve.blue("No changes detected, skipping sync"));return}let l=o.rulesToAdd.length+o.rulesToUpdate.length+o.rulesToDelete.length+(o.ipsToAdd?.length??0)+(o.ipsToUpdate?.length??0)+(o.ipsToDelete?.length??0);r.log(ve.cyan(`Syncing ${l} changes...`));let c=await i.syncRules(n,{force:!0,allowDeletions:!0});if(!c.success)throw new Error(`Sync failed: ${c.errors?.join(", ")||"unknown error"}`);r.success(ve.green(`\u2705 Sync completed at ${new Date().toLocaleTimeString()}`)),c.warnings?.forEach(u=>r.warn(u)),c.idRemappings?.length&&c.idRemappings.forEach(u=>{r.info(ve.dim(` Rule "${u.name}" ID updated: ${u.oldId||"none"} -> ${u.newId}`))});let d=Ut(e,c);d.changed&&await z(d.config,t)}var Zn=[pi,li,Gt,ci,fi,ri,hi,Ri,si,wi,ui,oi,ai,Ci];Oa();async function Na(){let i=(await import("chalk")).default,e=Fa(process.argv.slice(2));e.usage(`${i.bold("\u{1F6AA} Doorman")}
143
143
 
144
- ${i.dim("Manage firewall rules as code across multiple providers")}`);for(let t of Zn)e.command(t);e.demandCommand(1,"You need at least one command before moving on").help().epilogue(i.dim(`See ${i.bold("https://doorman.griffen.codes/getting-started")} for more info`)).argv}Fa().catch(console.error);
144
+ ${i.dim("Manage firewall rules as code across multiple providers")}`);for(let t of Zn)e.command(t);e.demandCommand(1,"You need at least one command before moving on").help().epilogue(i.dim(`See ${i.bold("https://doorman.griffen.codes/getting-started")} for more info`)).argv}Na().catch(console.error);