@gfargo/doorman 3.11.7 → 3.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/run.js +3 -3
- package/dist/bin/run.mjs +3 -3
- package/package.json +1 -1
package/dist/bin/run.js
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 g=this.getConfirmationMessage(s,t);return await R(g,{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 $t.default.green("LOW");case"medium":return $t.default.yellow("MEDIUM");case"high":return $t.default.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(", ")}`)}}});var Wn=require("dotenv"),qn=S(require("yargs"));var jt={};J(jt,{builder:()=>go,command:()=>po,desc:()=>fo,generateRuleId:()=>Mt,handler:()=>Co});var L=S(require("chalk")),Vi=require("consola");T();var b=require("zod"),wt=b.z.number().int().positive().optional(),Kn=b.z.string().ip().or(b.z.string().regex(/^(?:\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/)),Ri=b.z.string().optional(),Zn=b.z.enum(["eq","pre","suf","inc","sub","re","ex","nex"]),Yn=b.z.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"]),Jn=b.z.object({op:Zn,neg:b.z.boolean().optional(),type:Yn,value:b.z.union([b.z.string(),b.z.array(b.z.string()),b.z.array(b.z.number())]).optional(),key:b.z.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"),Ut=b.z.object({conditions:b.z.array(Jn).min(1,"Condition group must have at least one condition")}),Qn=b.z.string().regex(/^\d+[smhd]$|^permanent$/),Lt=b.z.object({requests:b.z.number().positive().int().refine(i=>i>0,"requests must be positive"),window:b.z.string().regex(/^\d+[smhd]$/).refine(i=>parseInt(i)>0,"window duration must be positive")}),Xn=b.z.object({location:b.z.string(),permanent:b.z.boolean().optional()}),eo=b.z.enum(["log","deny","challenge","bypass","rate_limit","redirect"]),to=b.z.object({action:eo,rateLimit:Lt.nullable().optional(),redirect:Xn.nullable().optional(),actionDuration:Qn.nullable().optional()}),io=b.z.object({mitigate:to}),Dt=b.z.object({id:Ri,name:b.z.string(),description:b.z.string().optional(),conditionGroup:b.z.array(Ut),action:io,active:b.z.boolean()});var xt=b.z.object({id:Ri,ip:Kn,hostname:b.z.string().optional(),notes:b.z.string().optional(),action:b.z.literal("deny")}),no=b.z.object({projectId:b.z.string().optional(),teamId:b.z.string().optional()}),Pe=no.extend({rules:b.z.array(Dt),ips:b.z.array(xt).optional(),version:b.z.number().optional(),updatedAt:b.z.string().optional()});ye();var ae=require("fs"),Di=require("crypto"),xi=require("path");T();var Si=require("net"),Ui=S(require("ajv"));var bi="https://doorman.griffen.codes/schema.json",vi={$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}}};var $=require("zod");var E=require("zod");var q=["vercel","cloudflare","fastly"];var Ot=E.z.string().optional(),Ge=E.z.string().ip().or(E.z.string().regex(/^(?:\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/)),Rt=E.z.string().datetime().or(E.z.string().optional()),Ii=E.z.enum(["log","deny","challenge","bypass","rate_limit","redirect","allow","block"]),Ei=E.z.enum(["eq","ne","contains","not_contains","starts_with","ends_with","matches","in","not_in","gt","ge","lt","le","exists","not_exists"]),Ti=E.z.enum(["ip","country","region","city","asn","path","host","method","header","query","cookie","user_agent","referer","scheme","port"]),Fs=E.z.string().regex(/^\d+[smhd]$|^permanent$/),$i=E.z.object({requests:E.z.number().positive().int(),window:E.z.string().regex(/^\d+[smhd]$/),characteristics:E.z.array(E.z.string()).optional()}),Ai=E.z.object({location:E.z.string().refine(i=>i.startsWith("/")||E.z.string().url().safeParse(i).success,'location must be an absolute URL or a path starting with "/"'),statusCode:E.z.number().int().min(300).max(399).optional(),permanent:E.z.boolean().optional(),preserveQueryString:E.z.boolean().optional()}),Ft=E.z.object({version:E.z.number().int().positive().optional(),updatedAt:Rt,createdAt:Rt,lastSyncedAt:Rt,migratedFrom:E.z.string().optional(),migratedAt:Rt}),oo=E.z.enum(q),Pi=E.z.object({vercel:E.z.object({projectId:E.z.string().optional(),teamId:E.z.string().optional()}).optional(),cloudflare:E.z.object({zoneId:E.z.string().optional(),accountId:E.z.string().optional()}).optional(),fastly:E.z.object({workspaceId:E.z.string().optional()}).optional()}),_i=E.z.object({$schema:E.z.string().url().optional(),version:E.z.string().optional(),provider:oo.optional(),metadata:Ft.optional()});var ro=$.z.object({field:Ti.or($.z.string()),operator:Ei,value:$.z.union([$.z.string(),$.z.number(),$.z.array($.z.string()),$.z.array($.z.number())]).optional(),negated:$.z.boolean().optional(),key:$.z.string().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"]}),so=$.z.object({type:Ii,rateLimit:$i.optional(),redirect:Ai.optional(),response:$.z.object({statusCode:$.z.number().int().min(100).max(599).optional(),content:$.z.string().optional(),contentType:$.z.string().optional()}).optional(),duration:$.z.string().optional()}),ao=$.z.object({id:Ot,name:$.z.string().min(1,"Rule name is required"),description:$.z.string().optional(),enabled:$.z.boolean(),conditions:$.z.array(ro).min(1,"At least one condition is required"),conditionLogic:$.z.enum(["AND","OR"]).optional().default("AND"),action:so,priority:$.z.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:$.z.array($.z.string()).optional()}),lo=$.z.object({id:Ot,ip:Ge,hostname:$.z.string().optional(),notes:$.z.string().optional(),action:$.z.enum(["deny","allow"])}),De=_i.extend({providers:Pi.optional(),rules:$.z.array(ao),ips:$.z.array(lo).optional(),metadata:Ft.optional()}),ki=i=>{let e=De.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 Ce(i){if(!i||typeof i!="object")return!1;let e=i;return typeof e.provider=="string"||typeof e.providers=="object"&&e.providers!==null}var se=S(require("chalk")),be=class{static formatValidationError(e){let t=e.instancePath||"/",n=se.default.cyan(t);switch(e.keyword){case"type":return`${n}: Expected ${se.default.green(e.schema)}, got ${se.default.red(typeof e.data)}`;case"enum":{let o=e.params.allowedValues;return`${n}: Value must be one of: ${se.default.green(o.join(", "))}`}case"required":{let o=e.params;return`${n}: Missing required property ${se.default.yellow(o.missingProperty)}`}case"additionalProperties":{let o=e.params;return`${n}: Unknown property ${se.default.red(o.additionalProperty)}`}default:return`${n}: ${e.message}`}}static formatCustomError(e,t){return`${se.default.cyan(e)}: ${t}`}static wrapErrorBlock(e){let t=se.default.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(", ")}`)}}});var Wn=require("dotenv"),qn=S(require("yargs"));var jt={};J(jt,{builder:()=>go,command:()=>po,desc:()=>fo,generateRuleId:()=>Mt,handler:()=>Co});var L=S(require("chalk")),Vi=require("consola");T();var b=require("zod"),wt=b.z.number().int().positive().optional(),Kn=b.z.string().ip().or(b.z.string().regex(/^(?:\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/)),Ri=b.z.string().optional(),Zn=b.z.enum(["eq","pre","suf","inc","sub","re","ex","nex"]),Yn=b.z.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"]),Jn=b.z.object({op:Zn,neg:b.z.boolean().optional(),type:Yn,value:b.z.union([b.z.string(),b.z.array(b.z.string()),b.z.array(b.z.number())]).optional(),key:b.z.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"),Ut=b.z.object({conditions:b.z.array(Jn).min(1,"Condition group must have at least one condition")}),Qn=b.z.string().regex(/^\d+[smhd]$|^permanent$/),Lt=b.z.object({requests:b.z.number().positive().int().refine(i=>i>0,"requests must be positive"),window:b.z.string().regex(/^\d+[smhd]$/).refine(i=>parseInt(i)>0,"window duration must be positive")}),Xn=b.z.object({location:b.z.string(),permanent:b.z.boolean().optional()}),eo=b.z.enum(["log","deny","challenge","bypass","rate_limit","redirect"]),to=b.z.object({action:eo,rateLimit:Lt.nullable().optional(),redirect:Xn.nullable().optional(),actionDuration:Qn.nullable().optional()}),io=b.z.object({mitigate:to}),Dt=b.z.object({id:Ri,name:b.z.string(),description:b.z.string().optional(),conditionGroup:b.z.array(Ut),action:io,active:b.z.boolean()});var xt=b.z.object({id:Ri,ip:Kn,hostname:b.z.string().optional(),notes:b.z.string().optional(),action:b.z.literal("deny")}),no=b.z.object({projectId:b.z.string().optional(),teamId:b.z.string().optional()}),Pe=no.extend({rules:b.z.array(Dt),ips:b.z.array(xt).optional(),version:b.z.number().optional(),updatedAt:b.z.string().optional()});ye();var ae=require("fs"),Di=require("crypto"),xi=require("path");T();var Si=require("net"),Ui=S(require("ajv"));var bi="https://doorman.griffen.codes/schema.json",vi={$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}}};var $=require("zod");var E=require("zod");var q=["vercel","cloudflare","fastly"];var Ot=E.z.string().optional(),Ge=E.z.string().ip().or(E.z.string().regex(/^(?:\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/)),Rt=E.z.string().datetime().or(E.z.string().optional()),Ii=E.z.enum(["log","deny","challenge","bypass","rate_limit","redirect","allow","block"]),Ei=E.z.enum(["eq","ne","contains","not_contains","starts_with","ends_with","matches","in","not_in","gt","ge","lt","le","exists","not_exists"]),Ti=E.z.enum(["ip","country","region","city","asn","path","host","method","header","query","cookie","user_agent","referer","scheme","port"]),Fs=E.z.string().regex(/^\d+[smhd]$|^permanent$/),$i=E.z.object({requests:E.z.number().positive().int(),window:E.z.string().regex(/^\d+[smhd]$/),characteristics:E.z.array(E.z.string()).optional()}),Ai=E.z.object({location:E.z.string().refine(i=>i.startsWith("/")||E.z.string().url().safeParse(i).success,'location must be an absolute URL or a path starting with "/"'),statusCode:E.z.number().int().min(300).max(399).optional(),permanent:E.z.boolean().optional(),preserveQueryString:E.z.boolean().optional()}),Ft=E.z.object({version:E.z.number().int().positive().optional(),updatedAt:Rt,createdAt:Rt,lastSyncedAt:Rt,migratedFrom:E.z.string().optional(),migratedAt:Rt}),oo=E.z.enum(q),Pi=E.z.object({vercel:E.z.object({projectId:E.z.string().optional(),teamId:E.z.string().optional()}).optional(),cloudflare:E.z.object({zoneId:E.z.string().optional(),accountId:E.z.string().optional()}).optional(),fastly:E.z.object({workspaceId:E.z.string().optional()}).optional()}),_i=E.z.object({$schema:E.z.string().url().optional(),version:E.z.string().optional(),provider:oo.optional(),metadata:Ft.optional()});var ro=$.z.object({field:Ti.or($.z.string()),operator:Ei,value:$.z.union([$.z.string(),$.z.number(),$.z.array($.z.string()),$.z.array($.z.number())]).optional(),negated:$.z.boolean().optional(),key:$.z.string().optional(),group:$.z.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"]}),so=$.z.object({type:Ii,rateLimit:$i.optional(),redirect:Ai.optional(),response:$.z.object({statusCode:$.z.number().int().min(100).max(599).optional(),content:$.z.string().optional(),contentType:$.z.string().optional()}).optional(),duration:$.z.string().optional()}),ao=$.z.object({id:Ot,name:$.z.string().min(1,"Rule name is required"),description:$.z.string().optional(),enabled:$.z.boolean(),conditions:$.z.array(ro).min(1,"At least one condition is required"),conditionLogic:$.z.enum(["AND","OR"]).optional().default("AND"),action:so,priority:$.z.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:$.z.array($.z.string()).optional()}),lo=$.z.object({id:Ot,ip:Ge,hostname:$.z.string().optional(),notes:$.z.string().optional(),action:$.z.enum(["deny","allow"])}),De=_i.extend({providers:Pi.optional(),rules:$.z.array(ao),ips:$.z.array(lo).optional(),metadata:Ft.optional()}),ki=i=>{let e=De.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 Ce(i){if(!i||typeof i!="object")return!1;let e=i;return typeof e.provider=="string"||typeof e.providers=="object"&&e.providers!==null}var se=S(require("chalk")),be=class{static formatValidationError(e){let t=e.instancePath||"/",n=se.default.cyan(t);switch(e.keyword){case"type":return`${n}: Expected ${se.default.green(e.schema)}, got ${se.default.red(typeof e.data)}`;case"enum":{let o=e.params.allowedValues;return`${n}: Value must be one of: ${se.default.green(o.join(", "))}`}case"required":{let o=e.params;return`${n}: Missing required property ${se.default.yellow(o.missingProperty)}`}case"additionalProperties":{let o=e.params;return`${n}: Unknown property ${se.default.red(o.additionalProperty)}`}default:return`${n}: ${e.message}`}}static formatCustomError(e,t){return`${se.default.cyan(e)}: ${t}`}static wrapErrorBlock(e){let t=se.default.red.bold("Validation Errors:"),n=e.map(o=>` ${o}`).join(`
|
|
15
15
|
`);return`${t}
|
|
16
16
|
${n}`}static formatDetailedRuleValidation(e,t){let n=se.default.cyan(`Rule: ${e}`),o=t.map(s=>{let a=s.passed?se.default.green("\u2713"):se.default.red("\u2717"),l=s.passed?se.default.green(s.name):se.default.red(s.name);return` ${a} ${l}`}).join(`
|
|
17
17
|
`);return`${n}
|
|
@@ -21,9 +21,9 @@ ${o}`}};var x=class i extends Error{constructor(t,n,o,s=[]){super(t);this.ajvErr
|
|
|
21
21
|
Dry run - The following IP rule would be added:`)),Ni(t),r.log(""),r.log(L.default.dim(JSON.stringify(t,null,2)));return}r.start("Loading configuration...");let o=await M(i.config,"raw"),s={...o,ips:[...o.ips||[],t]};await j(s,i.config),r.success(L.default.green(`\u2714 IP rule "${t.ip}" added to configuration`)),Ni(t)}else{let t=i.interactive?await mo():ho(i),n=Dt.safeParse(t);if(n.success||(r.error(L.default.red("Rule validation failed:")),n.error.errors.forEach(c=>{let d=c.path.join(".")||"rule";r.error(L.default.red(` - ${d}: ${c.message}`))}),process.exit(1)),i.dryRun){r.info(L.default.cyan(`
|
|
22
22
|
Dry run - The following rule would be added:`)),Fi(t),r.log(""),r.log(L.default.dim(JSON.stringify(t,null,2)));return}r.start("Loading configuration...");let o=await M(i.config,"raw"),s=o.rules||[],a=Ro({...o,rules:s},t);if(a){if(r.warn(L.default.yellow(`\u26A0\uFE0F ${a}`)),i.ci||!process.stdin.isTTY){r.info(L.default.dim("Skipping \u2014 non-interactive and a conflicting rule already exists. Rule not added."));return}if(!await R("Proceed anyway?",{type:"confirm",initial:!1})){r.info("Cancelled.");return}}let l={...o,rules:[...s,t]};await j(l,i.config),r.success(L.default.green(`\u2714 Rule "${t.name}" added to configuration`)),Fi(t)}r.log(""),r.log(L.default.dim(`Run ${L.default.cyan("doorman sync")} to deploy this rule.`))}catch(e){Q(e,"adding rule")}};var Xt={};J(Xt,{builder:()=>yr,command:()=>mr,desc:()=>hr,handler:()=>wr});var Z=S(require("chalk")),me=require("fs"),it=require("path"),bn=require("consola");T();ye();var st=require("fs"),zt=require("path");function Wi(i=process.cwd()){return(0,st.existsSync)((0,zt.join)(i,".git"))}function qi(i,e=process.cwd()){let t=(0,zt.join)(e,".gitignore");if(!(0,st.existsSync)(t))return!1;let n=i.replace(/^\.\//,"").replace(/^\//,"").replace(/\/$/,""),o;try{o=(0,st.readFileSync)(t,"utf8").split(`
|
|
23
23
|
`)}catch{return!1}return o.some(s=>{let a=s.trim();if(!a||a.startsWith("#"))return!1;let l=a.replace(/^\//,"").replace(/\/$/,"");return l===n||n.startsWith(`${l}/`)})}var X={type:"string",choices:q,description:"Firewall provider (auto-detected)"};var Cn=require("consola");T();var wn=S(require("chalk"));T();ye();var Mi=require("readline");function bo(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 vo(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 ji(i){if(process.stdout.write(i),!process.stdin.isTTY){let{createInterface:e}=await import("readline"),t=e({input:process.stdin,output:process.stdout,terminal:!1});return new Promise(n=>{t.question("",o=>{t.close(),n(o)})})}return new Promise(e=>{let t=process.stdin,n=t.isRaw;(0,Mi.emitKeypressEvents)(t),t.setRawMode(!0),t.resume();let o="",s=()=>{t.removeListener("keypress",a),t.setRawMode(n??!1)},a=(l,c)=>{let d=bo(l,c),u=vo(o,d);o=u.value,u.echo==="add"?process.stdout.write("*"):u.echo==="remove"&&process.stdout.write("\b \b"),u.done&&(s(),process.stdout.write(`
|
|
24
|
-
`),u.cancelled&&process.exit(0),e(o))};t.on("keypress",a)})}T();var zi={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 Gi={provider:"cloudflare",fields:[{key:"apiToken",envVar:"CLOUDFLARE_API_TOKEN",label:"Cloudflare API Token",required:!0,secret:!0},{key:"zoneId",envVar:"CLOUDFLARE_ZONE_ID",label:"Cloudflare Zone ID",required:!0,configKey:"zoneId"},{key:"accountId",envVar:"CLOUDFLARE_ACCOUNT_ID",label:"Cloudflare Account ID",required:!1,configKey:"accountId"}]};var Hi={provider:"fastly",fields:[{key:"apiToken",envVar:"FASTLY_API_TOKEN",label:"Fastly API Token",required:!0,secret:!0},{key:"workspaceId",envVar:"FASTLY_WORKSPACE_ID",label:"Fastly Next-Gen WAF Workspace ID",required:!0,configKey:"workspaceId"}]};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 Zi(i,e){return i.fields.filter(t=>t.required&&!e[t.key])}var He={vercel:zi,cloudflare:Gi,fastly:Hi};var Ct=class{static detectionField(e){return He[e].fields.find(t=>t.required&&t.configKey)}static detect(e){let t=[];if(e&&"provider"in e){let o=e.provider;if(typeof o=="string"&&this.isValidProvider(o))return t.push(`Explicit provider specified in config: ${e.provider}`),{provider:o,confidence:"high",reasons:t};r.warn(`Invalid provider specified in config: ${String(e.provider)}`)}if(e&&"providers"in e&&typeof e.providers=="object"&&e.providers!==null){let o=e.providers;for(let s of q){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 q){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 q.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 q){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 q){let o=this.detectionField(n);o&&process.env[o.envVar]&&t.add(n)}return Array.from(t)}};var Gt=S(require("chalk"));T();ye();var bt=i=>({$schema:bi,firewallEnabled:!0,rules:[],ips:[],...i});T();var _e=class{baseUrl;providerName;rateLimitInfo={};constructor(e,t){this.baseUrl=e,this.providerName=t}async makeRequest(e,t={}){let n=`${this.baseUrl}${e}`,{timeout:o=3e4,retries:s=3,retryDelay:a=1e3,...l}=t,c=null,d=null,u=null,p=()=>{u&&(clearTimeout(u),u=null),d&&(d.abort(),d=null)};try{for(let g=0;g<=s;g++)try{d=new AbortController,u=setTimeout(()=>{d&&d.abort()},o);let h=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(h),h.status===429){let C=this.calculateRateLimitWait(g,h);if(g===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 ${g+1}/${s+1})...`),await this.delay(C);continue}if(!h.ok)throw await this.handleErrorResponse(h);let w;try{let C=await h.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(h){if(c=h,u&&(clearTimeout(u),u=null),this.isNonRetryableError(h)||g===s)throw h;let w=a*Math.pow(2,g),C=Math.random()*.1*w,N=Math.min(w+C,3e4);r.debug(`Request failed (attempt ${g+1}/${s+1}). Retrying in ${Math.round(N)}ms...`),r.debug(`Error details: ${h instanceof Error?h.message:String(h)}`),await this.delay(N)}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),g=c.filter(h=>!p.includes(h));g.length>0&&r.warn(`Inconsistent array structure at ${s}[${d}]: missing keys ${g.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",Ke=class extends _e{constructor(t,n,o){super("","vercel");this.projectId=t;this.teamId=n;this.token=o}projectId;teamId;token;async getAuthHeaders(){return{Authorization:`Bearer ${this.token}`}}getUrl(t){let n=t!==void 0?`${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(Gt.default.bold("No firewall configuration found.")),!o)return r.debug("Skipping create-configuration prompt for read-only fetch."),this.emptyConfigPlaceholder();if(await R("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: ${Gt.default.yellow(l.version)}`),l}else return s.active}return s.active}emptyConfigPlaceholder(){return{version:0,id:"",firewallEnabled:!0,crs:null,rules:[],ips:[],projectKey:"",ownerId:"",updatedAt:new Date(0).toISOString()}}async putEmptyConfig(){let{$schema:t,...n}=bt();return r.debug("Empty Config:",n),this.putConfig(n)}async putConfig(t){return(await this.put(this.getUrl(),t)).active}async fetchActiveFirewallRules(){return(await this.fetchFirewallConfig())?.rules}async updateFirewallRule(t){let n=!t.id||t.id==="-",o={action:n?"rules.insert":"rules.update",id:n?null:t.id,value:{name:t.name,description:t.description,action:t.action,conditionGroup:t.conditionGroup,active:t.active}};return this.patch(this.getUrl(),o,n?{retries:0}:void 0)}async createFirewallRule(t){return this.updateFirewallRule({...t,id:"-"})}async deleteFirewallRule(t){let n={action:"rules.remove",id:t.id,value:null};await this.patch(this.getUrl(),n)}async updateIPBlockingRule(t){let n=!t.id||t.id==="-",o={action:n?"ip.insert":"ip.update",id:n?null:t.id,value:{action:t.action,hostname:t.hostname,ip:t.ip,notes:t.notes??null}};return this.patch(this.getUrl(),o,n?{retries:0}:void 0)}async createIPBlockingRule(t){return this.updateIPBlockingRule({...t,id:"-"})}async deleteIPBlockingRule(t){let n={action:"ip.remove",id:t.id,value:null};await this.patch(this.getUrl(),n)}async verifyCredentials(){try{return await this.fetchFirewallConfig(),!0}catch(t){return r.debug("Credential verification failed:",t),!1}}};var oe=S(require("chalk"));T();T();var ke=class{validateConfig(e){let t=[],n=[];if(e||t.push({path:"root",message:"Configuration is required",code:"CONFIG_REQUIRED"}),e&&!e.rules&&t.push({path:"rules",message:"Rules array is required",code:"RULES_REQUIRED"}),e&&e.rules&&!Array.isArray(e.rules)&&t.push({path:"rules",message:"Rules must be an array",code:"RULES_INVALID_TYPE"}),e&&Array.isArray(e.rules)){let o=new Map;e.rules.forEach((s,a)=>{let l=s.id||s.name;if(!l)return;let c=o.get(l);if(c===void 0){o.set(l,a);return}let d=e.rules[c];n.push({path:`rules[${a}]`,message:s.id?`Rule "${s.name}" has the same id ("${s.id}") as rules[${c}] ("${d?.name}") \u2014 they will collide during sync diffing and one may be silently skipped.`:`Rule "${s.name}" has the same name as rules[${c}] and neither has an explicit id \u2014 they will collide during sync diffing and one may be silently skipped. Add unique ids to disambiguate.`,code:"DUPLICATE_RULE_IDENTIFIER"})})}return{valid:t.length===0,errors:t,warnings:n}}getHealthScore(e){let t=[],n=100;if(!e)return{score:0,grade:"poor",issues:[{severity:"error",category:"configuration",message:"No configuration found"}],recommendations:["Create a configuration file"]};if((!e.rules||e.rules.length===0)&&(n-=20,t.push({severity:"warning",category:"rules",message:"No rules defined",suggestion:"Add security rules to protect your application"})),e.rules&&e.rules.length>0){let a=e.rules.filter(l=>!l.description);a.length>0&&(n-=5,t.push({severity:"info",category:"maintainability",message:`${a.length} rule(s) missing descriptions`,suggestion:"Add descriptions to improve maintainability"}))}if(e.rules&&e.rules.length>0){let a=e.rules.filter(l=>l.active===!1||l.enabled===!1);a.length>0&&(n-=5,t.push({severity:"info",category:"maintenance",message:`${a.length} disabled rule(s) found`,suggestion:"Review and remove unused rules"}))}let o=this.calculateGrade(n),s=this.generateRecommendations(e,t);return{score:Math.max(0,n),grade:o,issues:t,recommendations:s}}diffItems(e,t,n,o="id"){let s=[],a=[],l=[],c=d=>d[o];for(let d of e){let u=t.find(p=>c(p)===c(d));u?n(d,u)||a.push(d):s.push(d)}for(let d of t)e.find(p=>c(p)===c(d))||l.push(d);return{toAdd:s,toUpdate:a,toDelete:l}}validateRuleCount(e,t){if(t&&e>t)throw new Error(`Rule count (${e}) exceeds provider limit (${t})`);t&&e>t*.9&&r.warn(`Approaching rule limit: ${e}/${t} rules`)}updateMetadata(e,t){return{...e,metadata:{...e.metadata,version:t,updatedAt:new Date().toISOString()}}}calculateGrade(e){return e>=80?"excellent":e>=60?"good":e>=40?"fair":"poor"}generateRecommendations(e,t){let n=[];return t.some(o=>o.category==="rules")&&n.push("Consider using predefined templates to get started"),t.some(o=>o.category==="maintainability")&&n.push("Add descriptions to all rules for better documentation"),t.some(o=>o.category==="maintenance")&&n.push("Remove or enable disabled rules to keep configuration clean"),n}logSyncStats(e){r.info(`Sync complete for ${this.name}:`),r.info(` Rules added: ${e.rulesAdded}`),r.info(` Rules updated: ${e.rulesUpdated}`),r.info(` Rules deleted: ${e.rulesDeleted}`),e.ipsAdded!==void 0&&(r.info(` IPs added: ${e.ipsAdded}`),r.info(` IPs updated: ${e.ipsUpdated}`),r.info(` IPs deleted: ${e.ipsDeleted}`)),e.version&&r.info(` New version: ${e.version}`)}};ge();function Ji(i){let e=[],t=[];i.conditionGroup.forEach((s,a)=>{for(let l of s.conditions){let c=Eo(l.op);t.push({field:$o(l.type),operator:c,value:l.value,...l.neg?{negated:!0}:{},...l.key?{key:l.key}:{},group:a})}}),t.length>10&&e.push(O.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=Io(i.conditions,i.id,e);if(i.action.response&&e.push(O.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 Io(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=Po(l.field);if(!c){t.push(O.createUnsupportedFeatureWarning(`condition field '${l.field}'`,"unified config","Vercel",e,l.field));continue}a.push({op:To(l.operator),neg:l.negated,type:c,key:l.key,value:l.value})}a.length>0&&o.push({conditions:a})}return o}function Eo(i){return{eq:"eq",pre:"starts_with",suf:"ends_with",inc:"in",sub:"contains",re:"matches",ex:"exists",nex:"not_exists"}[i]||"eq"}function To(i){return{eq:"eq",starts_with:"pre",ends_with:"suf",in:"inc",contains:"sub",matches:"re",exists:"ex",not_exists:"nex"}[i]||"eq"}function $o(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 Ao={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 Po(i){return Ao[i]??null}ge();T();function Ze(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 at=class{static vercelToCloudflare={host:"http.host",path:"http.request.uri.path",method:"http.request.method",header:"http.request.headers",query:"http.request.uri.query",cookie:"http.cookie",target_path:"http.request.uri.path",ip_address:"ip.src",region:"ip.geoip.subdivision_1",protocol:"ssl",scheme:"ssl",environment:"",user_agent:"http.user_agent",geo_continent:"ip.geoip.continent",geo_country:"ip.geoip.country",geo_country_region:"ip.geoip.subdivision_1",geo_city:"ip.geoip.city",geo_as_number:"ip.geoip.asnum",ja4_digest:"",ja3_digest:"",rate_limit_api_id:""};static cloudflareToVercel={"http.host":"host","http.request.uri.path":"path","http.request.method":"method","http.request.headers":"header","http.request.uri.query":"query","http.cookie":"cookie","ip.src":"ip_address","ip.geoip.subdivision_1":"region",ssl:"protocol","http.user_agent":"user_agent","ip.geoip.continent":"geo_continent","ip.geoip.country":"geo_country","ip.geoip.city":"geo_city","ip.geoip.asnum":"geo_as_number"};static toCloudflare(e,t){let n=this.vercelToCloudflare[e];if(!n)throw r.warn(`No Cloudflare mapping for Vercel type: ${e}`),new Error(`Unsupported Vercel rule type for Cloudflare: ${e}`);return e==="header"&&t?`${n}["${Ze(t.toLowerCase())}"]`:e==="cookie"&&t?`${n}["${Ze(t)}"]`:n}static toVercel(e){let t=e.match(/http\.request\.headers\["([^"]+)"\]/);if(t)return{type:"header",key:t[1]};let n=e.match(/http\.cookie\["([^"]+)"\]/);if(n)return{type:"cookie",key:n[1]};if(e==="http.referer")return{type:"header",key:"referer"};let o=this.cloudflareToVercel[e];if(o)return{type:o};throw r.warn(`No Vercel mapping for Cloudflare field: ${e}`),new Error(`Unsupported Cloudflare field for Vercel: ${e}`)}static isCloudflareSupported(e){let t=this.vercelToCloudflare[e];return!!t&&t!==""}static isVercelSupported(e){return!!(this.cloudflareToVercel[e]||e==="http.referer"||e.startsWith("http.request.headers[")||e.startsWith("http.cookie["))}static getSupportedCloudflareFields(){return Object.values(this.vercelToCloudflare).filter(e=>e!=="")}static getSupportedVercelTypes(){return Object.keys(this.vercelToCloudflare).filter(e=>this.isCloudflareSupported(e))}};var _o=new Set(["ip.src"]),lt=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=at.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}["${Ze(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(_o.has(e)){if(!Ge.safeParse(t).success)throw new Error(`Invalid IP address or CIDR range: ${t}`);return t}return`"${Ze(t)}"`}return typeof t=="number"?String(t):typeof t=="boolean"?t?"true":"false":String(t)}static validate(e){if(!e||e.trim().length===0)return!1;let t=0;for(let n of e)if(n==="("&&t++,n===")"&&t--,t<0)return!1;return t===0}static combineWithAnd(e){if(e.length===0)throw new Error("At least one expression is required");return e.length===1?e[0]:`(${e.join(" and ")})`}static combineWithOr(e){if(e.length===0)throw new Error("At least one expression is required");return e.length===1?e[0]:`(${e.join(" or ")})`}};var ko={"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"},So=new Set(["eq","ne","contains","starts_with","ends_with","matches","in","gt","ge","lt","le"]),Uo=new Set(["and","or","not"]),we=class extends Error{};function Lo(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 we("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 we(`Unexpected character '${o}' at position ${t}`);e.push({type:"WORD",value:i.slice(t,s)}),t=s}return e}var Ht=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 we("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 we(`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 Kt(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=Kt(i);return i.expect("RPAREN"),{type:"not",child:e}}return Do(i)}function Do(i){let e=i.peek();if(e&&e.type==="LPAREN"){i.next();let t=Kt(i);return i.expect("RPAREN"),t}return Fo(i)}function xo(i){let e=i.expect("WORD");if(Uo.has(e.value))throw new we(`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 we(`Unexpected token '${e.value}' where a value was expected`)}function Oo(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 Fo(i){let{field:e,key:t}=xo(i);if(i.isWord("exists"))return i.next(),{type:"exists",field:e,key:t};let n=i.next();if(n.type!=="WORD"||!So.has(n.value))throw new we(`Unsupported or unrecognized operator '${n.value}'`);let o=Oo(i);return{type:"comparison",field:e,key:t,operator:n.value,value:o}}function No(i){let e=new Ht(Lo(i)),t=Kt(e);if(!e.atEnd())throw new we("Unexpected trailing tokens");return t}function vt(i){return i.type==="comparison"||i.type==="exists"?!0:i.type==="not"?i.child.type==="comparison"||i.child.type==="exists":!1}function It(i,e){let t=i.type==="not",n=i.type==="not"?i.child:i;if(n.type!=="comparison"&&n.type!=="exists")return null;let o=ko[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 Vo(i){if(vt(i)){let e=It(i,0);return e?{conditions:[e],conditionLogic:"AND"}:null}if(i.type==="and"){let e=[];for(let t of i.children){if(!vt(t))return null;let n=It(t,0);if(!n)return null;e.push(n)}return{conditions:e,conditionLogic:"AND"}}if(i.type==="or"){let e=[];i.children.forEach((o,s)=>{if(vt(o)){let a=It(o,s);a&&e.push(a);return}if(o.type==="and")for(let a of o.children){if(!vt(a))return;let l=It(a,s);l&&e.push(l)}});let t=i.children.length;return new Set(e.map(o=>o.group)).size!==t?null:{conditions:e,conditionLogic:"OR"}}return null}function Zt(i){if(!i||!i.trim())return null;try{let e=No(i);return Vo(e)}catch{return null}}function rn(i){let e=[],t=Zt(i.expression),n=[],o;t?(n=t.conditions,o=t.conditionLogic):e.push(O.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:Bo(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=lt.fromUnifiedConditions(i.conditions,i.conditionLogic),n={id:i.id||crypto.randomUUID(),action:Wo(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:qo(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(O.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(O.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(O.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(!Ge.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 Bo(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 Wo(i){return{log:"log",deny:"block",block:"block",challenge:"managed_challenge",bypass:"skip",rate_limit:"block",redirect:"redirect",allow:"allow"}[i]||"block"}function qo(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}ge();function le(i,e,t){return O.createWarning("complex_expressions",i,void 0,e,t)}var Mo={ip:"ip",path:"path",domain:"host",method:"method",country:"country",scheme:"scheme",user_agent:"user_agent"},jo={ip:"ip",path:"path",host:"domain",method:"method",country:"country",scheme:"scheme",user_agent:"user_agent"},zo={header:"request_header",query:"query_parameter",cookie:"request_cookie"},Go={request_header:"header",query_parameter:"query",request_cookie:"cookie",response_header:"response_header",post_parameter:"post_parameter",signal:"signal"};function Ho(i){return Mo[i]??i}function Ko(i){return jo[i]??null}var Zo={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 Zo[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(O.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(O.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(le(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(le(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 Yo(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(le(i.id,`A condition group using "any" (OR) inside an outer "any" (OR) rule cannot be represented in doorman's flat AND-within/OR-across condition model \u2014 its members were kept in the same group (effectively AND'd) instead of OR'd. Review the translated rule against the original Fastly logic.`));for(let l of s.conditions)Et(t,l,a,e,i.id)}),{conditions:t,conditionLogic:n.length>1?"OR":"AND"};if(n.length===0)return o.forEach((s,a)=>{let l=i.group_operator==="any"?a:0;Et(t,s,l,e,i.id)}),{conditions:t,conditionLogic:i.group_operator==="any"&&o.length>1?"OR":"AND"};e.push(le(i.id,`Rule mixes top-level conditions with condition groups under group_operator "${i.group_operator}" \u2014 this doesn't fit doorman's flat AND-within/OR-across condition model. All conditions were combined with AND; review the translated rule against the original Fastly rule logic.`));for(let s of o)Et(t,s,0,e,i.id);for(let s of n)for(let a of s.conditions)Et(t,a,0,e,i.id);return{conditions:t,conditionLogic:"AND"}}function Et(i,e,t,n,o){if(e.type==="single"){let{operator:u,negated:p}=ln(e.operator);i.push({field:Ho(e.field),operator:u,value:e.value,...p?{negated:!0}:{},group:t});return}let s=Go[e.field],a=e.conditions.find(u=>u.field==="name"),l=e.conditions.find(u=>u.field==="value"||u.field==="value_string");if(!l){n.push(le(o,`Multival condition on '${e.field}' has no value sub-condition doorman could extract (only a 'name' existence check) \u2014 skipped.`));return}let{operator:c,negated:d}=ln(l.operator);i.push({field:s,operator:c,value:l.value,...d?{negated:!0}:{},...a?{key:a.value}:{},group:t})}function Jo(i,e){i.actions.length>1&&e.push(le(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(O.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(le(i.id,`Unrecognised Fastly action type "${String(t?.type)}" \u2014 mapped to "log".`)),{type:"log"}}}function dn(i){let e=[],{conditions:t,conditionLogic:n}=Yo(i,e),o=Jo(i,e);return t.length>10&&e.push(O.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 Qo(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=zo[a.field];if(l){if(a.key===void 0)return t.push(le(e,`Condition on '${a.field}' has no 'key' set \u2014 Fastly needs a specific ${a.field} name to match on (e.g. a header name). Skipped.`)),null;let d=l==="request_header"||l==="response_header"?"value_string":"value";return{type:"multival",field:l,operator:"exists",group_operator:"all",conditions:[{type:"single",field:"name",operator:"equals",value:a.key},{type:"single",field:d,operator:cn(a.operator,a.negated,t,e),value:String(a.value)}]}}let c=Ko(a.field);return c?{type:"single",field:c,operator:cn(a.operator,a.negated,t,e),value:String(a.value)}:(t.push(O.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 Xo(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(le(t,'Unified action "bypass" has no direct Fastly equivalent \u2014 mapped to "allow".')),{type:"allow"};case"log":return e.push(le(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(le(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(O.createUnsupportedFeatureWarning(`action type '${i.type}'`,"unified config","Fastly",t,"action.type")),{type:"block"}}}function er(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(O.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(O.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 tr(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=er(i.window,t,e);return t.push(O.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(le(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}=Qo(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:[Xo(i.action,e,i.id)],rate_limit:a?tr(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(le(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}}var P=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};ge();function Ye(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)||!Ye(i[o],e[o]))return!1;return!0}function Se(i){let{id:e,...t}=i;return t}function ct(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 Tt(i){return i.some(e=>e.priority!==void 0)}var Je=class extends ke{constructor(t){super();this.client=t}client;name="vercel";async fetchConfig(t){try{r.debug("Fetching Vercel firewall configuration");let n=await this.client.fetchFirewallConfig(t),o=n.rules.map(a=>{let l=P.vercelToUnified(a);return l.warnings.length>0&&l.warnings.forEach(c=>{let{TranslationWarningSystem:d}=(ge(),Re(Oe)),u=d.formatWarning(c);r.warn(`Rule ${a.name}:
|
|
24
|
+
`),u.cancelled&&process.exit(0),e(o))};t.on("keypress",a)})}T();var zi={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 Gi={provider:"cloudflare",fields:[{key:"apiToken",envVar:"CLOUDFLARE_API_TOKEN",label:"Cloudflare API Token",required:!0,secret:!0},{key:"zoneId",envVar:"CLOUDFLARE_ZONE_ID",label:"Cloudflare Zone ID",required:!0,configKey:"zoneId"},{key:"accountId",envVar:"CLOUDFLARE_ACCOUNT_ID",label:"Cloudflare Account ID",required:!1,configKey:"accountId"}]};var Hi={provider:"fastly",fields:[{key:"apiToken",envVar:"FASTLY_API_TOKEN",label:"Fastly API Token",required:!0,secret:!0},{key:"workspaceId",envVar:"FASTLY_WORKSPACE_ID",label:"Fastly Next-Gen WAF Workspace ID",required:!0,configKey:"workspaceId"}]};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 Zi(i,e){return i.fields.filter(t=>t.required&&!e[t.key])}var He={vercel:zi,cloudflare:Gi,fastly:Hi};var Ct=class{static detectionField(e){return He[e].fields.find(t=>t.required&&t.configKey)}static detect(e){let t=[];if(e&&"provider"in e){let o=e.provider;if(typeof o=="string"&&this.isValidProvider(o))return t.push(`Explicit provider specified in config: ${e.provider}`),{provider:o,confidence:"high",reasons:t};r.warn(`Invalid provider specified in config: ${String(e.provider)}`)}if(e&&"providers"in e&&typeof e.providers=="object"&&e.providers!==null){let o=e.providers;for(let s of q){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 q){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 q.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 q){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 q){let o=this.detectionField(n);o&&process.env[o.envVar]&&t.add(n)}return Array.from(t)}};var Gt=S(require("chalk"));T();ye();var bt=i=>({$schema:bi,firewallEnabled:!0,rules:[],ips:[],...i});T();var _e=class{baseUrl;providerName;rateLimitInfo={};constructor(e,t){this.baseUrl=e,this.providerName=t}async makeRequest(e,t={}){let n=`${this.baseUrl}${e}`,{timeout:o=3e4,retries:s=3,retryDelay:a=1e3,...l}=t,c=null,d=null,u=null,p=()=>{u&&(clearTimeout(u),u=null),d&&(d.abort(),d=null)};try{for(let g=0;g<=s;g++)try{d=new AbortController,u=setTimeout(()=>{d&&d.abort()},o);let h=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(h),h.status===429){let C=this.calculateRateLimitWait(g,h);if(g===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 ${g+1}/${s+1})...`),await this.delay(C);continue}if(!h.ok)throw await this.handleErrorResponse(h);let w;try{let C=await h.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(h){if(c=h,u&&(clearTimeout(u),u=null),this.isNonRetryableError(h)||g===s)throw h;let w=a*Math.pow(2,g),C=Math.random()*.1*w,N=Math.min(w+C,3e4);r.debug(`Request failed (attempt ${g+1}/${s+1}). Retrying in ${Math.round(N)}ms...`),r.debug(`Error details: ${h instanceof Error?h.message:String(h)}`),await this.delay(N)}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),g=c.filter(h=>!p.includes(h));g.length>0&&r.warn(`Inconsistent array structure at ${s}[${d}]: missing keys ${g.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",Ke=class extends _e{constructor(t,n,o){super("","vercel");this.projectId=t;this.teamId=n;this.token=o}projectId;teamId;token;async getAuthHeaders(){return{Authorization:`Bearer ${this.token}`}}getUrl(t){let n=t!==void 0?`${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(Gt.default.bold("No firewall configuration found.")),!o)return r.debug("Skipping create-configuration prompt for read-only fetch."),this.emptyConfigPlaceholder();if(await R("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: ${Gt.default.yellow(l.version)}`),l}else return s.active}return s.active}emptyConfigPlaceholder(){return{version:0,id:"",firewallEnabled:!0,crs:null,rules:[],ips:[],projectKey:"",ownerId:"",updatedAt:new Date(0).toISOString()}}async putEmptyConfig(){let{$schema:t,...n}=bt();return r.debug("Empty Config:",n),this.putConfig(n)}async putConfig(t){return(await this.put(this.getUrl(),t)).active}async fetchActiveFirewallRules(){return(await this.fetchFirewallConfig())?.rules}async updateFirewallRule(t){let n=!t.id||t.id==="-",o={action:n?"rules.insert":"rules.update",id:n?null:t.id,value:{name:t.name,description:t.description,action:t.action,conditionGroup:t.conditionGroup,active:t.active}};return this.patch(this.getUrl(),o,n?{retries:0}:void 0)}async createFirewallRule(t){return this.updateFirewallRule({...t,id:"-"})}async deleteFirewallRule(t){let n={action:"rules.remove",id:t.id,value:null};await this.patch(this.getUrl(),n)}async updateIPBlockingRule(t){let n=!t.id||t.id==="-",o={action:n?"ip.insert":"ip.update",id:n?null:t.id,value:{action:t.action,hostname:t.hostname,ip:t.ip,notes:t.notes??null}};return this.patch(this.getUrl(),o,n?{retries:0}:void 0)}async createIPBlockingRule(t){return this.updateIPBlockingRule({...t,id:"-"})}async deleteIPBlockingRule(t){let n={action:"ip.remove",id:t.id,value:null};await this.patch(this.getUrl(),n)}async verifyCredentials(){try{return await this.fetchFirewallConfig(),!0}catch(t){return r.debug("Credential verification failed:",t),!1}}};var oe=S(require("chalk"));T();T();var ke=class{validateConfig(e){let t=[],n=[];if(e||t.push({path:"root",message:"Configuration is required",code:"CONFIG_REQUIRED"}),e&&!e.rules&&t.push({path:"rules",message:"Rules array is required",code:"RULES_REQUIRED"}),e&&e.rules&&!Array.isArray(e.rules)&&t.push({path:"rules",message:"Rules must be an array",code:"RULES_INVALID_TYPE"}),e&&Array.isArray(e.rules)){let o=new Map;e.rules.forEach((s,a)=>{let l=s.id||s.name;if(!l)return;let c=o.get(l);if(c===void 0){o.set(l,a);return}let d=e.rules[c];n.push({path:`rules[${a}]`,message:s.id?`Rule "${s.name}" has the same id ("${s.id}") as rules[${c}] ("${d?.name}") \u2014 they will collide during sync diffing and one may be silently skipped.`:`Rule "${s.name}" has the same name as rules[${c}] and neither has an explicit id \u2014 they will collide during sync diffing and one may be silently skipped. Add unique ids to disambiguate.`,code:"DUPLICATE_RULE_IDENTIFIER"})})}return{valid:t.length===0,errors:t,warnings:n}}getHealthScore(e){let t=[],n=100;if(!e)return{score:0,grade:"poor",issues:[{severity:"error",category:"configuration",message:"No configuration found"}],recommendations:["Create a configuration file"]};if((!e.rules||e.rules.length===0)&&(n-=20,t.push({severity:"warning",category:"rules",message:"No rules defined",suggestion:"Add security rules to protect your application"})),e.rules&&e.rules.length>0){let a=e.rules.filter(l=>!l.description);a.length>0&&(n-=5,t.push({severity:"info",category:"maintainability",message:`${a.length} rule(s) missing descriptions`,suggestion:"Add descriptions to improve maintainability"}))}if(e.rules&&e.rules.length>0){let a=e.rules.filter(l=>l.active===!1||l.enabled===!1);a.length>0&&(n-=5,t.push({severity:"info",category:"maintenance",message:`${a.length} disabled rule(s) found`,suggestion:"Review and remove unused rules"}))}let o=this.calculateGrade(n),s=this.generateRecommendations(e,t);return{score:Math.max(0,n),grade:o,issues:t,recommendations:s}}diffItems(e,t,n,o="id"){let s=[],a=[],l=[],c=d=>d[o];for(let d of e){let u=t.find(p=>c(p)===c(d));u?n(d,u)||a.push(d):s.push(d)}for(let d of t)e.find(p=>c(p)===c(d))||l.push(d);return{toAdd:s,toUpdate:a,toDelete:l}}validateRuleCount(e,t){if(t&&e>t)throw new Error(`Rule count (${e}) exceeds provider limit (${t})`);t&&e>t*.9&&r.warn(`Approaching rule limit: ${e}/${t} rules`)}updateMetadata(e,t){return{...e,metadata:{...e.metadata,version:t,updatedAt:new Date().toISOString()}}}calculateGrade(e){return e>=80?"excellent":e>=60?"good":e>=40?"fair":"poor"}generateRecommendations(e,t){let n=[];return t.some(o=>o.category==="rules")&&n.push("Consider using predefined templates to get started"),t.some(o=>o.category==="maintainability")&&n.push("Add descriptions to all rules for better documentation"),t.some(o=>o.category==="maintenance")&&n.push("Remove or enable disabled rules to keep configuration clean"),n}logSyncStats(e){r.info(`Sync complete for ${this.name}:`),r.info(` Rules added: ${e.rulesAdded}`),r.info(` Rules updated: ${e.rulesUpdated}`),r.info(` Rules deleted: ${e.rulesDeleted}`),e.ipsAdded!==void 0&&(r.info(` IPs added: ${e.ipsAdded}`),r.info(` IPs updated: ${e.ipsUpdated}`),r.info(` IPs deleted: ${e.ipsDeleted}`)),e.version&&r.info(` New version: ${e.version}`)}};ge();function Ji(i){let e=[],t=[];i.conditionGroup.forEach((s,a)=>{for(let l of s.conditions){let c=Eo(l.op);t.push({field:$o(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(O.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=Io(i.conditions,i.id,e);if(i.action.response&&e.push(O.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 Io(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=Po(l.field);if(!c){t.push(O.createUnsupportedFeatureWarning(`condition field '${l.field}'`,"unified config","Vercel",e,l.field));continue}a.push({op:To(l.operator),neg:l.negated,type:c,key:l.key,value:l.value})}a.length>0&&o.push({conditions:a})}return o}function Eo(i){return{eq:"eq",pre:"starts_with",suf:"ends_with",inc:"in",sub:"contains",re:"matches",ex:"exists",nex:"not_exists"}[i]||"eq"}function To(i){return{eq:"eq",starts_with:"pre",ends_with:"suf",in:"inc",contains:"sub",matches:"re",exists:"ex",not_exists:"nex"}[i]||"eq"}function $o(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 Ao={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 Po(i){return Ao[i]??null}ge();T();function Ze(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 at=class{static vercelToCloudflare={host:"http.host",path:"http.request.uri.path",method:"http.request.method",header:"http.request.headers",query:"http.request.uri.query",cookie:"http.cookie",target_path:"http.request.uri.path",ip_address:"ip.src",region:"ip.geoip.subdivision_1",protocol:"ssl",scheme:"ssl",environment:"",user_agent:"http.user_agent",geo_continent:"ip.geoip.continent",geo_country:"ip.geoip.country",geo_country_region:"ip.geoip.subdivision_1",geo_city:"ip.geoip.city",geo_as_number:"ip.geoip.asnum",ja4_digest:"",ja3_digest:"",rate_limit_api_id:""};static cloudflareToVercel={"http.host":"host","http.request.uri.path":"path","http.request.method":"method","http.request.headers":"header","http.request.uri.query":"query","http.cookie":"cookie","ip.src":"ip_address","ip.geoip.subdivision_1":"region",ssl:"protocol","http.user_agent":"user_agent","ip.geoip.continent":"geo_continent","ip.geoip.country":"geo_country","ip.geoip.city":"geo_city","ip.geoip.asnum":"geo_as_number"};static toCloudflare(e,t){let n=this.vercelToCloudflare[e];if(!n)throw r.warn(`No Cloudflare mapping for Vercel type: ${e}`),new Error(`Unsupported Vercel rule type for Cloudflare: ${e}`);return e==="header"&&t?`${n}["${Ze(t.toLowerCase())}"]`:e==="cookie"&&t?`${n}["${Ze(t)}"]`:n}static toVercel(e){let t=e.match(/http\.request\.headers\["([^"]+)"\]/);if(t)return{type:"header",key:t[1]};let n=e.match(/http\.cookie\["([^"]+)"\]/);if(n)return{type:"cookie",key:n[1]};if(e==="http.referer")return{type:"header",key:"referer"};let o=this.cloudflareToVercel[e];if(o)return{type:o};throw r.warn(`No Vercel mapping for Cloudflare field: ${e}`),new Error(`Unsupported Cloudflare field for Vercel: ${e}`)}static isCloudflareSupported(e){let t=this.vercelToCloudflare[e];return!!t&&t!==""}static isVercelSupported(e){return!!(this.cloudflareToVercel[e]||e==="http.referer"||e.startsWith("http.request.headers[")||e.startsWith("http.cookie["))}static getSupportedCloudflareFields(){return Object.values(this.vercelToCloudflare).filter(e=>e!=="")}static getSupportedVercelTypes(){return Object.keys(this.vercelToCloudflare).filter(e=>this.isCloudflareSupported(e))}};var _o=new Set(["ip.src"]),lt=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=at.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}["${Ze(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(_o.has(e)){if(!Ge.safeParse(t).success)throw new Error(`Invalid IP address or CIDR range: ${t}`);return t}return`"${Ze(t)}"`}return typeof t=="number"?String(t):typeof t=="boolean"?t?"true":"false":String(t)}static validate(e){if(!e||e.trim().length===0)return!1;let t=0;for(let n of e)if(n==="("&&t++,n===")"&&t--,t<0)return!1;return t===0}static combineWithAnd(e){if(e.length===0)throw new Error("At least one expression is required");return e.length===1?e[0]:`(${e.join(" and ")})`}static combineWithOr(e){if(e.length===0)throw new Error("At least one expression is required");return e.length===1?e[0]:`(${e.join(" or ")})`}};var ko={"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"},So=new Set(["eq","ne","contains","starts_with","ends_with","matches","in","gt","ge","lt","le"]),Uo=new Set(["and","or","not"]),we=class extends Error{};function Lo(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 we("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 we(`Unexpected character '${o}' at position ${t}`);e.push({type:"WORD",value:i.slice(t,s)}),t=s}return e}var Ht=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 we("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 we(`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 Kt(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=Kt(i);return i.expect("RPAREN"),{type:"not",child:e}}return Do(i)}function Do(i){let e=i.peek();if(e&&e.type==="LPAREN"){i.next();let t=Kt(i);return i.expect("RPAREN"),t}return Fo(i)}function xo(i){let e=i.expect("WORD");if(Uo.has(e.value))throw new we(`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 we(`Unexpected token '${e.value}' where a value was expected`)}function Oo(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 Fo(i){let{field:e,key:t}=xo(i);if(i.isWord("exists"))return i.next(),{type:"exists",field:e,key:t};let n=i.next();if(n.type!=="WORD"||!So.has(n.value))throw new we(`Unsupported or unrecognized operator '${n.value}'`);let o=Oo(i);return{type:"comparison",field:e,key:t,operator:n.value,value:o}}function No(i){let e=new Ht(Lo(i)),t=Kt(e);if(!e.atEnd())throw new we("Unexpected trailing tokens");return t}function vt(i){return i.type==="comparison"||i.type==="exists"?!0:i.type==="not"?i.child.type==="comparison"||i.child.type==="exists":!1}function It(i,e){let t=i.type==="not",n=i.type==="not"?i.child:i;if(n.type!=="comparison"&&n.type!=="exists")return null;let o=ko[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 Vo(i){if(vt(i)){let e=It(i,0);return e?{conditions:[e],conditionLogic:"AND"}:null}if(i.type==="and"){let e=[];for(let t of i.children){if(!vt(t))return null;let n=It(t,0);if(!n)return null;e.push(n)}return{conditions:e,conditionLogic:"AND"}}if(i.type==="or"){let e=[];i.children.forEach((o,s)=>{if(vt(o)){let a=It(o,s);a&&e.push(a);return}if(o.type==="and")for(let a of o.children){if(!vt(a))return;let l=It(a,s);l&&e.push(l)}});let t=i.children.length;return new Set(e.map(o=>o.group)).size!==t?null:{conditions:e,conditionLogic:"OR"}}return null}function Zt(i){if(!i||!i.trim())return null;try{let e=No(i);return Vo(e)}catch{return null}}function rn(i){let e=[],t=Zt(i.expression),n=[],o;t?(n=t.conditions,o=t.conditionLogic):e.push(O.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:Bo(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=lt.fromUnifiedConditions(i.conditions,i.conditionLogic),n={id:i.id||crypto.randomUUID(),action:Wo(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:qo(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(O.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(O.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(O.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(!Ge.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 Bo(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 Wo(i){return{log:"log",deny:"block",block:"block",challenge:"managed_challenge",bypass:"skip",rate_limit:"block",redirect:"redirect",allow:"allow"}[i]||"block"}function qo(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}ge();function le(i,e,t){return O.createWarning("complex_expressions",i,void 0,e,t)}var Mo={ip:"ip",path:"path",domain:"host",method:"method",country:"country",scheme:"scheme",user_agent:"user_agent"},jo={ip:"ip",path:"path",host:"domain",method:"method",country:"country",scheme:"scheme",user_agent:"user_agent"},zo={header:"request_header",query:"query_parameter",cookie:"request_cookie"},Go={request_header:"header",query_parameter:"query",request_cookie:"cookie",response_header:"response_header",post_parameter:"post_parameter",signal:"signal"};function Ho(i){return Mo[i]??i}function Ko(i){return jo[i]??null}var Zo={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 Zo[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(O.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(O.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(le(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(le(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 Yo(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(le(i.id,`A condition group using "any" (OR) inside an outer "any" (OR) rule cannot be represented in doorman's flat AND-within/OR-across condition model \u2014 its members were kept in the same group (effectively AND'd) instead of OR'd. Review the translated rule against the original Fastly logic.`));for(let l of s.conditions)Et(t,l,a,e,i.id)}),{conditions:t,conditionLogic:n.length>1?"OR":"AND"};if(n.length===0)return o.forEach((s,a)=>{let l=i.group_operator==="any"?a:0;Et(t,s,l,e,i.id)}),{conditions:t,conditionLogic:i.group_operator==="any"&&o.length>1?"OR":"AND"};e.push(le(i.id,`Rule mixes top-level conditions with condition groups under group_operator "${i.group_operator}" \u2014 this doesn't fit doorman's flat AND-within/OR-across condition model. All conditions were combined with AND; review the translated rule against the original Fastly rule logic.`));for(let s of o)Et(t,s,0,e,i.id);for(let s of n)for(let a of s.conditions)Et(t,a,0,e,i.id);return{conditions:t,conditionLogic:"AND"}}function Et(i,e,t,n,o){if(e.type==="single"){let{operator:u,negated:p}=ln(e.operator);i.push({field:Ho(e.field),operator:u,value:e.value,...p?{negated:!0}:{},group:t});return}let s=Go[e.field],a=e.conditions.find(u=>u.field==="name"),l=e.conditions.find(u=>u.field==="value"||u.field==="value_string");if(!l){n.push(le(o,`Multival condition on '${e.field}' has no value sub-condition doorman could extract (only a 'name' existence check) \u2014 skipped.`));return}let{operator:c,negated:d}=ln(l.operator);i.push({field:s,operator:c,value:l.value,...d?{negated:!0}:{},...a?{key:a.value}:{},group:t})}function Jo(i,e){i.actions.length>1&&e.push(le(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(O.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(le(i.id,`Unrecognised Fastly action type "${String(t?.type)}" \u2014 mapped to "log".`)),{type:"log"}}}function dn(i){let e=[],{conditions:t,conditionLogic:n}=Yo(i,e),o=Jo(i,e);return t.length>10&&e.push(O.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 Qo(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=zo[a.field];if(l){if(a.key===void 0)return t.push(le(e,`Condition on '${a.field}' has no 'key' set \u2014 Fastly needs a specific ${a.field} name to match on (e.g. a header name). Skipped.`)),null;let d=l==="request_header"||l==="response_header"?"value_string":"value";return{type:"multival",field:l,operator:"exists",group_operator:"all",conditions:[{type:"single",field:"name",operator:"equals",value:a.key},{type:"single",field:d,operator:cn(a.operator,a.negated,t,e),value:String(a.value)}]}}let c=Ko(a.field);return c?{type:"single",field:c,operator:cn(a.operator,a.negated,t,e),value:String(a.value)}:(t.push(O.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 Xo(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(le(t,'Unified action "bypass" has no direct Fastly equivalent \u2014 mapped to "allow".')),{type:"allow"};case"log":return e.push(le(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(le(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(O.createUnsupportedFeatureWarning(`action type '${i.type}'`,"unified config","Fastly",t,"action.type")),{type:"block"}}}function er(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(O.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(O.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 tr(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=er(i.window,t,e);return t.push(O.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(le(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}=Qo(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:[Xo(i.action,e,i.id)],rate_limit:a?tr(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(le(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}}var P=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};ge();function Ye(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)||!Ye(i[o],e[o]))return!1;return!0}function Se(i){let{id:e,...t}=i;return t}function ct(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 Tt(i){return i.some(e=>e.priority!==void 0)}var Je=class extends ke{constructor(t){super();this.client=t}client;name="vercel";async fetchConfig(t){try{r.debug("Fetching Vercel firewall configuration");let n=await this.client.fetchFirewallConfig(t),o=n.rules.map(a=>{let l=P.vercelToUnified(a);return l.warnings.length>0&&l.warnings.forEach(c=>{let{TranslationWarningSystem:d}=(ge(),Re(Oe)),u=d.formatWarning(c);r.warn(`Rule ${a.name}:
|
|
25
25
|
${u}`)}),l.result}),s=n.ips.map(a=>P.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}=(Pt(),Re(At)),a=await s.performDryRunValidation(t,"sync rules",async f=>await this.getChanges(f,{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,g=l.ipsToAdd||[],h=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: ${g.length}, Update: ${h.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 Y=c.map(f=>P.unifiedToVercel(f).result),W=d.map(f=>P.unifiedToVercel(f).result),fe=u.map(f=>P.unifiedToVercel(f).result),he=g.map(f=>({id:f.id||"",ip:f.ip,hostname:f.hostname||"",action:f.action,notes:f.notes})),Ue=h.map(f=>({id:f.id||"",ip:f.ip,hostname:f.hostname||"",action:f.action,notes:f.notes})),k=w.map(f=>({id:f.id||"",ip:f.ip,hostname:f.hostname||"",action:f.action,notes:f.notes})),Ee=[],Te=[],$e=[],je=[],Le=[],ze=[],v=[],V=(f,I)=>`${f}: ${I instanceof Error?I.message:String(I)}`,re=[];Tt(t.rules)&&t.rules.length>c.length&&re.push("Rule `priority` is best-effort on Vercel: new rules are created in priority order, but rules that already exist remotely cannot be repositioned via the Vercel API. Recreate them (remove and re-add) if exact evaluation order matters.");let ce=[];for(let f of fe)try{r.debug(`Deleting custom rule: ${f.id}`),await this.client.deleteFirewallRule(f),$e.push(f),r.debug(`Custom rule deleted: ${f.id}`)}catch(I){r.error(`Failed to delete custom rule ${f.id}:`,I),v.push(V(`Failed to delete custom rule ${f.id}`,I))}for(let f of k)try{r.debug(`Deleting IP blocking rule: ${f.id}`),await this.client.deleteIPBlockingRule(f),ze.push(f),r.debug(`IP blocking rule deleted: ${f.id}`)}catch(I){r.error(`Failed to delete IP blocking rule ${f.id}:`,I),v.push(V(`Failed to delete IP blocking rule ${f.id}`,I))}for(let f of Y)try{r.debug(`Adding new custom rule: ${f.name}`);let I=await this.client.createFirewallRule(f);Ee.push(I),I.id&&I.id!==f.id&&ce.push({oldId:f.id||void 0,newId:I.id,name:f.name}),r.debug(`New custom rule added: ${I.id}`)}catch(I){r.error(`Failed to add custom rule "${f.name}":`,I),v.push(V(`Failed to add custom rule "${f.name}"`,I))}for(let f of he)try{r.debug(`Adding new IP blocking rule: ${f.ip}`);let I=await this.client.createIPBlockingRule(f);je.push(I),r.debug(`New IP blocking rule added: (hostname): ${I.hostname} (ip): ${I.ip}`)}catch(I){r.error(`Failed to add IP blocking rule ${f.ip}:`,I),v.push(V(`Failed to add IP blocking rule ${f.ip}`,I))}for(let f of W)try{r.debug(`Updating custom rule: ${f.id}`);let I=await this.client.updateFirewallRule(f);Te.push(I),r.debug(`Custom rule updated: ${I.id}`)}catch(I){r.error(`Failed to update custom rule ${f.id}:`,I),v.push(V(`Failed to update custom rule ${f.id}`,I))}for(let f of Ue)try{r.debug(`Updating IP blocking rule: ${f.id}`);let I=await this.client.updateIPBlockingRule(f);Le.push(I),r.debug(`IP blocking rule updated: ${I.id}`)}catch(I){r.error(`Failed to update IP blocking rule ${f.id}:`,I),v.push(V(`Failed to update IP blocking rule ${f.id}`,I))}r.debug(`${oe.default.underline("Custom Rules:")} ${oe.default.green("Added:")} ${oe.default.green(Ee.length)}, ${oe.default.cyan("Updated:")} ${oe.default.cyan(Te.length)}, ${oe.default.red("Deleted:")} ${oe.default.red($e.length)}`),r.debug(`${oe.default.underline("IP Rules:")} ${oe.default.green("Added:")} ${oe.default.green(je.length)}, ${oe.default.cyan("Updated:")} ${oe.default.cyan(Le.length)}, ${oe.default.red("Deleted:")} ${oe.default.red(ze.length)}`);let nt=p,ot,Ae=[...re];try{let f=await this.client.fetchFirewallConfig();nt=f.version,ot=f.updatedAt;let I=new Set(f.rules.map(F=>F.id)),hi=new Set(f.ips.map(F=>F.id));for(let F of[...Ee,...Te])F.id&&!I.has(F.id)&&Ae.push(`Rule "${F.name}" (${F.id}) was synced but is missing from the post-sync remote config \u2014 a subsequent sync may re-create or misdiff it.`);for(let F of $e)F.id&&I.has(F.id)&&Ae.push(`Rule "${F.name}" (${F.id}) was deleted but still appears in the post-sync remote config.`);for(let F of[...je,...Le])F.id&&!hi.has(F.id)&&Ae.push(`IP rule "${F.ip}" (${F.id}) was synced but is missing from the post-sync remote config \u2014 a subsequent sync may re-create or misdiff it.`);for(let F of ze)F.id&&hi.has(F.id)&&Ae.push(`IP rule "${F.ip}" (${F.id}) was deleted but still appears in the post-sync remote config.`)}catch(f){r.error("Failed to fetch updated firewall configuration version after sync:",f),v.push(V("Failed to fetch updated configuration version after sync",f))}return{success:v.length===0,rulesAdded:Ee.length,rulesUpdated:Te.length,rulesDeleted:$e.length,ipsAdded:je.length,ipsUpdated:Le.length,ipsDeleted:ze.length,version:nt,updatedAt:ot,idRemappings:ce.length>0?ce:void 0,errors:v.length>0?v:void 0,warnings:Ae.length>0?Ae: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=De.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=P.vercelToUnified(w);return C.warnings.length>0&&C.warnings.forEach(N=>{let{TranslationWarningSystem:Y}=(ge(),Re(Oe)),W=Y.formatWarning(N);r.warn(`Rule ${w.name}:
|
|
26
|
-
${W}`)}),C.result}),{toAdd:l,toUpdate:c,toDelete:d}=this.diffRules(ct(
|
|
26
|
+
${W}`)}),C.result}),{toAdd:l,toUpdate:c,toDelete:d}=this.diffRules(ct(o.data.rules),a),u=s.ips.map(w=>P.vercelIPToUnified(w)),{ipsToAdd:p,ipsToUpdate:g,ipsToDelete:h}=this.diffIPRules(o.data.ips||[],u);return{version:s.version,rulesToAdd:l,rulesToUpdate:c,rulesToDelete:d,ipsToAdd:p,ipsToUpdate:g,ipsToDelete:h,hasChanges:l.length>0||c.length>0||d.length>0||p.length>0||g.length>0||h.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=Pe.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{Ye(Se(l),Se(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{Ye(Se(l),Se(c))||s.push({...c,...l});let d=a.findIndex(u=>u.id===c.id);d!==-1&&a.splice(d,1)}}return{ipsToAdd:o,ipsToUpdate:s,ipsToDelete:a}}};T();var 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 Ke(t,n,e);return new Je(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 Ke(n,o,t);return new Je(s)}static create(e,t,n){return this.fromConfig({projectId:e,teamId:t,token:n})}};var hn=require("crypto");T();var Fe=S(require("chalk")),m=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(Fe.default.red.bold(`[${this.code}] ${this.message}`)),e.push(""),this.suggestion&&(e.push(Fe.default.yellow.bold("Suggestion:")),e.push(` ${this.suggestion}`),e.push("")),this.details&&Object.keys(this.details).length>0&&(e.push(Fe.default.dim("Details:")),Object.entries(this.details).forEach(([t,n])=>{let o=typeof n=="object"?JSON.stringify(n,null,2).split(`
|
|
27
27
|
`).join(`
|
|
28
28
|
`):n;e.push(` ${Fe.default.cyan(t)}: ${o}`)}),e.push("")),this.cause&&(e.push(Fe.default.dim("Caused by:")),e.push(` ${this.cause.message}`),e.push("")),this.docsUrl&&(e.push(Fe.default.cyan.bold("Documentation:")),e.push(` ${this.docsUrl}`),e.push("")),e.join(`
|
|
29
29
|
`)}toPlainText(){let e=[];return e.push(`[${this.code}] ${this.message}`),this.suggestion&&e.push(`Suggestion: ${this.suggestion}`),this.details&&Object.keys(this.details).length>0&&(e.push("Details:"),Object.entries(this.details).forEach(([t,n])=>{e.push(` ${t}: ${n}`)})),this.cause&&e.push(`Caused by: ${this.cause.message}`),this.docsUrl&&e.push(`Documentation: ${this.docsUrl}`),e.join(`
|
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 g=this.getConfirmationMessage(s,t);return await R(g,{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 Bt.green("LOW");case"medium":return Bt.yellow("MEDIUM");case"high":return Bt.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 Fs}from"dotenv";import Ns from"yargs";var Ot={};Z(Ot,{builder:()=>no,command:()=>to,desc:()=>io,generateRuleId:()=>xt,handler:()=>co});T();import L from"chalk";import{LogLevels as eo}from"consola";import{z as v}from"zod";var dt=v.number().int().positive().optional(),En=v.string().ip().or(v.string().regex(/^(?:\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/)),ci=v.string().optional(),Tn=v.enum(["eq","pre","suf","inc","sub","re","ex","nex"]),$n=v.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"]),An=v.object({op:Tn,neg:v.boolean().optional(),type:$n,value:v.union([v.string(),v.array(v.string()),v.array(v.number())]).optional(),key:v.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"),Et=v.object({conditions:v.array(An).min(1,"Condition group must have at least one condition")}),Pn=v.string().regex(/^\d+[smhd]$|^permanent$/),Tt=v.object({requests:v.number().positive().int().refine(i=>i>0,"requests must be positive"),window:v.string().regex(/^\d+[smhd]$/).refine(i=>parseInt(i)>0,"window duration must be positive")}),_n=v.object({location:v.string(),permanent:v.boolean().optional()}),kn=v.enum(["log","deny","challenge","bypass","rate_limit","redirect"]),Sn=v.object({action:kn,rateLimit:Tt.nullable().optional(),redirect:_n.nullable().optional(),actionDuration:Pn.nullable().optional()}),Un=v.object({mitigate:Sn}),$t=v.object({id:ci,name:v.string(),description:v.string().optional(),conditionGroup:v.array(Et),action:Un,active:v.boolean()});var At=v.object({id:ci,ip:En,hostname:v.string().optional(),notes:v.string().optional(),action:v.literal("deny")}),Ln=v.object({projectId:v.string().optional(),teamId:v.string().optional()}),Ee=Ln.extend({rules:v.array($t),ips:v.array(At).optional(),version:v.number().optional(),updatedAt:v.string().optional()});ge();T();import{existsSync as St,mkdirSync as zn,readFileSync as Gn,renameSync as Hn,unlinkSync as Kn,writeFileSync as Zn}from"fs";import{randomBytes as Yn}from"crypto";import{dirname as Jn}from"path";import{isIPv6 as Bn}from"net";import Wn from"ajv";var di="https://doorman.griffen.codes/schema.json",ui={$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 E}from"zod";var W=["vercel","cloudflare","fastly"];var Pt=E.string().optional(),Be=E.string().ip().or(E.string().regex(/^(?:\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/)),ut=E.string().datetime().or(E.string().optional()),pi=E.enum(["log","deny","challenge","bypass","rate_limit","redirect","allow","block"]),fi=E.enum(["eq","ne","contains","not_contains","starts_with","ends_with","matches","in","not_in","gt","ge","lt","le","exists","not_exists"]),gi=E.enum(["ip","country","region","city","asn","path","host","method","header","query","cookie","user_agent","referer","scheme","port"]),Js=E.string().regex(/^\d+[smhd]$|^permanent$/),mi=E.object({requests:E.number().positive().int(),window:E.string().regex(/^\d+[smhd]$/),characteristics:E.array(E.string()).optional()}),hi=E.object({location:E.string().refine(i=>i.startsWith("/")||E.string().url().safeParse(i).success,'location must be an absolute URL or a path starting with "/"'),statusCode:E.number().int().min(300).max(399).optional(),permanent:E.boolean().optional(),preserveQueryString:E.boolean().optional()}),_t=E.object({version:E.number().int().positive().optional(),updatedAt:ut,createdAt:ut,lastSyncedAt:ut,migratedFrom:E.string().optional(),migratedAt:ut}),xn=E.enum(W),yi=E.object({vercel:E.object({projectId:E.string().optional(),teamId:E.string().optional()}).optional(),cloudflare:E.object({zoneId:E.string().optional(),accountId:E.string().optional()}).optional(),fastly:E.object({workspaceId:E.string().optional()}).optional()}),wi=E.object({$schema:E.string().url().optional(),version:E.string().optional(),provider:xn.optional(),metadata:_t.optional()});var On=$.object({field:gi.or($.string()),operator:fi,value:$.union([$.string(),$.number(),$.array($.string()),$.array($.number())]).optional(),negated:$.boolean().optional(),key:$.string().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"]}),Fn=$.object({type:pi,rateLimit:mi.optional(),redirect:hi.optional(),response:$.object({statusCode:$.number().int().min(100).max(599).optional(),content:$.string().optional(),contentType:$.string().optional()}).optional(),duration:$.string().optional()}),Nn=$.object({id:Pt,name:$.string().min(1,"Rule name is required"),description:$.string().optional(),enabled:$.boolean(),conditions:$.array(On).min(1,"At least one condition is required"),conditionLogic:$.enum(["AND","OR"]).optional().default("AND"),action:Fn,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()}),Vn=$.object({id:Pt,ip:Be,hostname:$.string().optional(),notes:$.string().optional(),action:$.enum(["deny","allow"])}),Se=wi.extend({providers:yi.optional(),rules:$.array(Nn),ips:$.array(Vn).optional(),metadata:_t.optional()}),Ri=i=>{let e=Se.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 ye(i){if(!i||typeof i!="object")return!1;let e=i;return typeof e.provider=="string"||typeof e.providers=="object"&&e.providers!==null}import re from"chalk";var we=class{static formatValidationError(e){let t=e.instancePath||"/",n=re.cyan(t);switch(e.keyword){case"type":return`${n}: Expected ${re.green(e.schema)}, got ${re.red(typeof e.data)}`;case"enum":{let o=e.params.allowedValues;return`${n}: Value must be one of: ${re.green(o.join(", "))}`}case"required":{let o=e.params;return`${n}: Missing required property ${re.yellow(o.missingProperty)}`}case"additionalProperties":{let o=e.params;return`${n}: Unknown property ${re.red(o.additionalProperty)}`}default:return`${n}: ${e.message}`}}static formatCustomError(e,t){return`${re.cyan(e)}: ${t}`}static wrapErrorBlock(e){let t=re.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 Fs}from"dotenv";import Ns from"yargs";var Ot={};Z(Ot,{builder:()=>no,command:()=>to,desc:()=>io,generateRuleId:()=>xt,handler:()=>co});T();import L from"chalk";import{LogLevels as eo}from"consola";import{z as v}from"zod";var dt=v.number().int().positive().optional(),En=v.string().ip().or(v.string().regex(/^(?:\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/)),ci=v.string().optional(),Tn=v.enum(["eq","pre","suf","inc","sub","re","ex","nex"]),$n=v.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"]),An=v.object({op:Tn,neg:v.boolean().optional(),type:$n,value:v.union([v.string(),v.array(v.string()),v.array(v.number())]).optional(),key:v.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"),Et=v.object({conditions:v.array(An).min(1,"Condition group must have at least one condition")}),Pn=v.string().regex(/^\d+[smhd]$|^permanent$/),Tt=v.object({requests:v.number().positive().int().refine(i=>i>0,"requests must be positive"),window:v.string().regex(/^\d+[smhd]$/).refine(i=>parseInt(i)>0,"window duration must be positive")}),_n=v.object({location:v.string(),permanent:v.boolean().optional()}),kn=v.enum(["log","deny","challenge","bypass","rate_limit","redirect"]),Sn=v.object({action:kn,rateLimit:Tt.nullable().optional(),redirect:_n.nullable().optional(),actionDuration:Pn.nullable().optional()}),Un=v.object({mitigate:Sn}),$t=v.object({id:ci,name:v.string(),description:v.string().optional(),conditionGroup:v.array(Et),action:Un,active:v.boolean()});var At=v.object({id:ci,ip:En,hostname:v.string().optional(),notes:v.string().optional(),action:v.literal("deny")}),Ln=v.object({projectId:v.string().optional(),teamId:v.string().optional()}),Ee=Ln.extend({rules:v.array($t),ips:v.array(At).optional(),version:v.number().optional(),updatedAt:v.string().optional()});ge();T();import{existsSync as St,mkdirSync as zn,readFileSync as Gn,renameSync as Hn,unlinkSync as Kn,writeFileSync as Zn}from"fs";import{randomBytes as Yn}from"crypto";import{dirname as Jn}from"path";import{isIPv6 as Bn}from"net";import Wn from"ajv";var di="https://doorman.griffen.codes/schema.json",ui={$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 E}from"zod";var W=["vercel","cloudflare","fastly"];var Pt=E.string().optional(),Be=E.string().ip().or(E.string().regex(/^(?:\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/)),ut=E.string().datetime().or(E.string().optional()),pi=E.enum(["log","deny","challenge","bypass","rate_limit","redirect","allow","block"]),fi=E.enum(["eq","ne","contains","not_contains","starts_with","ends_with","matches","in","not_in","gt","ge","lt","le","exists","not_exists"]),gi=E.enum(["ip","country","region","city","asn","path","host","method","header","query","cookie","user_agent","referer","scheme","port"]),Js=E.string().regex(/^\d+[smhd]$|^permanent$/),mi=E.object({requests:E.number().positive().int(),window:E.string().regex(/^\d+[smhd]$/),characteristics:E.array(E.string()).optional()}),hi=E.object({location:E.string().refine(i=>i.startsWith("/")||E.string().url().safeParse(i).success,'location must be an absolute URL or a path starting with "/"'),statusCode:E.number().int().min(300).max(399).optional(),permanent:E.boolean().optional(),preserveQueryString:E.boolean().optional()}),_t=E.object({version:E.number().int().positive().optional(),updatedAt:ut,createdAt:ut,lastSyncedAt:ut,migratedFrom:E.string().optional(),migratedAt:ut}),xn=E.enum(W),yi=E.object({vercel:E.object({projectId:E.string().optional(),teamId:E.string().optional()}).optional(),cloudflare:E.object({zoneId:E.string().optional(),accountId:E.string().optional()}).optional(),fastly:E.object({workspaceId:E.string().optional()}).optional()}),wi=E.object({$schema:E.string().url().optional(),version:E.string().optional(),provider:xn.optional(),metadata:_t.optional()});var On=$.object({field:gi.or($.string()),operator:fi,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"]}),Fn=$.object({type:pi,rateLimit:mi.optional(),redirect:hi.optional(),response:$.object({statusCode:$.number().int().min(100).max(599).optional(),content:$.string().optional(),contentType:$.string().optional()}).optional(),duration:$.string().optional()}),Nn=$.object({id:Pt,name:$.string().min(1,"Rule name is required"),description:$.string().optional(),enabled:$.boolean(),conditions:$.array(On).min(1,"At least one condition is required"),conditionLogic:$.enum(["AND","OR"]).optional().default("AND"),action:Fn,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()}),Vn=$.object({id:Pt,ip:Be,hostname:$.string().optional(),notes:$.string().optional(),action:$.enum(["deny","allow"])}),Se=wi.extend({providers:yi.optional(),rules:$.array(Nn),ips:$.array(Vn).optional(),metadata:_t.optional()}),Ri=i=>{let e=Se.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 ye(i){if(!i||typeof i!="object")return!1;let e=i;return typeof e.provider=="string"||typeof e.providers=="object"&&e.providers!==null}import re from"chalk";var we=class{static formatValidationError(e){let t=e.instancePath||"/",n=re.cyan(t);switch(e.keyword){case"type":return`${n}: Expected ${re.green(e.schema)}, got ${re.red(typeof e.data)}`;case"enum":{let o=e.params.allowedValues;return`${n}: Value must be one of: ${re.green(o.join(", "))}`}case"required":{let o=e.params;return`${n}: Missing required property ${re.yellow(o.missingProperty)}`}case"additionalProperties":{let o=e.params;return`${n}: Unknown property ${re.red(o.additionalProperty)}`}default:return`${n}: ${e.message}`}}static formatCustomError(e,t){return`${re.cyan(e)}: ${t}`}static wrapErrorBlock(e){let t=re.red.bold("Validation Errors:"),n=e.map(o=>` ${o}`).join(`
|
|
15
15
|
`);return`${t}
|
|
16
16
|
${n}`}static formatDetailedRuleValidation(e,t){let n=re.cyan(`Rule: ${e}`),o=t.map(s=>{let a=s.passed?re.green("\u2713"):re.red("\u2717"),l=s.passed?re.green(s.name):re.red(s.name);return` ${a} ${l}`}).join(`
|
|
17
17
|
`);return`${n}
|
|
@@ -21,9 +21,9 @@ ${o}`}};var U=class i extends Error{constructor(t,n,o,s=[]){super(t);this.ajvErr
|
|
|
21
21
|
Dry run - The following IP rule would be added:`)),Ii(t),r.log(""),r.log(L.dim(JSON.stringify(t,null,2)));return}r.start("Loading configuration...");let o=await q(i.config,"raw"),s={...o,ips:[...o.ips||[],t]};await M(s,i.config),r.success(L.green(`\u2714 IP rule "${t.ip}" added to configuration`)),Ii(t)}else{let t=i.interactive?await oo():ro(i),n=$t.safeParse(t);if(n.success||(r.error(L.red("Rule validation failed:")),n.error.errors.forEach(c=>{let d=c.path.join(".")||"rule";r.error(L.red(` - ${d}: ${c.message}`))}),process.exit(1)),i.dryRun){r.info(L.cyan(`
|
|
22
22
|
Dry run - The following rule would be added:`)),vi(t),r.log(""),r.log(L.dim(JSON.stringify(t,null,2)));return}r.start("Loading configuration...");let o=await q(i.config,"raw"),s=o.rules||[],a=lo({...o,rules:s},t);if(a){if(r.warn(L.yellow(`\u26A0\uFE0F ${a}`)),i.ci||!process.stdin.isTTY){r.info(L.dim("Skipping \u2014 non-interactive and a conflicting rule already exists. Rule not added."));return}if(!await R("Proceed anyway?",{type:"confirm",initial:!1})){r.info("Cancelled.");return}}let l={...o,rules:[...s,t]};await M(l,i.config),r.success(L.green(`\u2714 Rule "${t.name}" added to configuration`)),vi(t)}r.log(""),r.log(L.dim(`Run ${L.cyan("doorman sync")} to deploy this rule.`))}catch(e){Y(e,"adding rule")}};var zt={};Z(zt,{builder:()=>yr,command:()=>mr,desc:()=>hr,handler:()=>wr});T();import ee from"chalk";import{existsSync as bt,mkdirSync as dr,readdirSync as ur,statSync as pr}from"fs";import{isAbsolute as fr,join as jt}from"path";import{LogLevels as gr}from"consola";ge();import{existsSync as Ti,readFileSync as uo}from"fs";import{join as $i}from"path";function Ai(i=process.cwd()){return Ti($i(i,".git"))}function Pi(i,e=process.cwd()){let t=$i(e,".gitignore");if(!Ti(t))return!1;let n=i.replace(/^\.\//,"").replace(/^\//,"").replace(/\/$/,""),o;try{o=uo(t,"utf8").split(`
|
|
23
23
|
`)}catch{return!1}return o.some(s=>{let a=s.trim();if(!a||a.startsWith("#"))return!1;let l=a.replace(/^\//,"").replace(/\/$/,"");return l===n||n.startsWith(`${l}/`)})}var J={type:"string",choices:W,description:"Firewall provider (auto-detected)"};T();import{LogLevels as cr}from"consola";T();ge();import ir from"chalk";import{emitKeypressEvents as po}from"readline";function fo(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 go(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 _i(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;po(t),t.setRawMode(!0),t.resume();let o="",s=()=>{t.removeListener("keypress",a),t.setRawMode(n??!1)},a=(l,c)=>{let d=fo(l,c),u=go(o,d);o=u.value,u.echo==="add"?process.stdout.write("*"):u.echo==="remove"&&process.stdout.write("\b \b"),u.done&&(s(),process.stdout.write(`
|
|
24
|
-
`),u.cancelled&&process.exit(0),e(o))};t.on("keypress",a)})}T();var ki={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 Si={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 Ui={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"}]};function Li(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 Di(i,e){return i.fields.filter(t=>t.required&&!e[t.key])}var We={vercel:ki,cloudflare:Si,fastly:Ui};var pt=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 W){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 W){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 W.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 W){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 W){let o=this.detectionField(n);o&&process.env[o.envVar]&&t.add(n)}return Array.from(t)}};T();ge();import xi from"chalk";var ft=i=>({$schema:di,firewallEnabled:!0,rules:[],ips:[],...i});T();var Te=class{baseUrl;providerName;rateLimitInfo={};constructor(e,t){this.baseUrl=e,this.providerName=t}async makeRequest(e,t={}){let n=`${this.baseUrl}${e}`,{timeout:o=3e4,retries:s=3,retryDelay:a=1e3,...l}=t,c=null,d=null,u=null,p=()=>{u&&(clearTimeout(u),u=null),d&&(d.abort(),d=null)};try{for(let g=0;g<=s;g++)try{d=new AbortController,u=setTimeout(()=>{d&&d.abort()},o);let h=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(h),h.status===429){let C=this.calculateRateLimitWait(g,h);if(g===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 ${g+1}/${s+1})...`),await this.delay(C);continue}if(!h.ok)throw await this.handleErrorResponse(h);let w;try{let C=await h.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(h){if(c=h,u&&(clearTimeout(u),u=null),this.isNonRetryableError(h)||g===s)throw h;let w=a*Math.pow(2,g),C=Math.random()*.1*w,F=Math.min(w+C,3e4);r.debug(`Request failed (attempt ${g+1}/${s+1}). Retrying in ${Math.round(F)}ms...`),r.debug(`Error details: ${h instanceof Error?h.message:String(h)}`),await this.delay(F)}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),g=c.filter(h=>!p.includes(h));g.length>0&&r.warn(`Inconsistent array structure at ${s}[${d}]: missing keys ${g.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 Oi=process.env.DOORMAN_VERCEL_API_BASE_URL||"https://api.vercel.com/v1/security/firewall/config",qe=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?`${Oi}/${t}`:Oi;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(xi.bold("No firewall configuration found.")),!o)return r.debug("Skipping create-configuration prompt for read-only fetch."),this.emptyConfigPlaceholder();if(await R("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: ${xi.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}=ft();return r.debug("Empty Config:",n),this.putConfig(n)}async putConfig(t){return(await this.put(this.getUrl(),t)).active}async fetchActiveFirewallRules(){return(await this.fetchFirewallConfig())?.rules}async updateFirewallRule(t){let n=!t.id||t.id==="-",o={action:n?"rules.insert":"rules.update",id:n?null:t.id,value:{name:t.name,description:t.description,action:t.action,conditionGroup:t.conditionGroup,active:t.active}};return this.patch(this.getUrl(),o,n?{retries:0}:void 0)}async createFirewallRule(t){return this.updateFirewallRule({...t,id:"-"})}async deleteFirewallRule(t){let n={action:"rules.remove",id:t.id,value:null};await this.patch(this.getUrl(),n)}async updateIPBlockingRule(t){let n=!t.id||t.id==="-",o={action:n?"ip.insert":"ip.update",id:n?null:t.id,value:{action:t.action,hostname:t.hostname,ip:t.ip,notes:t.notes??null}};return this.patch(this.getUrl(),o,n?{retries:0}:void 0)}async createIPBlockingRule(t){return this.updateIPBlockingRule({...t,id:"-"})}async deleteIPBlockingRule(t){let n={action:"ip.remove",id:t.id,value:null};await this.patch(this.getUrl(),n)}async verifyCredentials(){try{return await this.fetchFirewallConfig(),!0}catch(t){return r.debug("Credential verification failed:",t),!1}}};T();import oe from"chalk";T();var $e=class{validateConfig(e){let t=[],n=[];if(e||t.push({path:"root",message:"Configuration is required",code:"CONFIG_REQUIRED"}),e&&!e.rules&&t.push({path:"rules",message:"Rules array is required",code:"RULES_REQUIRED"}),e&&e.rules&&!Array.isArray(e.rules)&&t.push({path:"rules",message:"Rules must be an array",code:"RULES_INVALID_TYPE"}),e&&Array.isArray(e.rules)){let o=new Map;e.rules.forEach((s,a)=>{let l=s.id||s.name;if(!l)return;let c=o.get(l);if(c===void 0){o.set(l,a);return}let d=e.rules[c];n.push({path:`rules[${a}]`,message:s.id?`Rule "${s.name}" has the same id ("${s.id}") as rules[${c}] ("${d?.name}") \u2014 they will collide during sync diffing and one may be silently skipped.`:`Rule "${s.name}" has the same name as rules[${c}] and neither has an explicit id \u2014 they will collide during sync diffing and one may be silently skipped. Add unique ids to disambiguate.`,code:"DUPLICATE_RULE_IDENTIFIER"})})}return{valid:t.length===0,errors:t,warnings:n}}getHealthScore(e){let t=[],n=100;if(!e)return{score:0,grade:"poor",issues:[{severity:"error",category:"configuration",message:"No configuration found"}],recommendations:["Create a configuration file"]};if((!e.rules||e.rules.length===0)&&(n-=20,t.push({severity:"warning",category:"rules",message:"No rules defined",suggestion:"Add security rules to protect your application"})),e.rules&&e.rules.length>0){let a=e.rules.filter(l=>!l.description);a.length>0&&(n-=5,t.push({severity:"info",category:"maintainability",message:`${a.length} rule(s) missing descriptions`,suggestion:"Add descriptions to improve maintainability"}))}if(e.rules&&e.rules.length>0){let a=e.rules.filter(l=>l.active===!1||l.enabled===!1);a.length>0&&(n-=5,t.push({severity:"info",category:"maintenance",message:`${a.length} disabled rule(s) found`,suggestion:"Review and remove unused rules"}))}let o=this.calculateGrade(n),s=this.generateRecommendations(e,t);return{score:Math.max(0,n),grade:o,issues:t,recommendations:s}}diffItems(e,t,n,o="id"){let s=[],a=[],l=[],c=d=>d[o];for(let d of e){let u=t.find(p=>c(p)===c(d));u?n(d,u)||a.push(d):s.push(d)}for(let d of t)e.find(p=>c(p)===c(d))||l.push(d);return{toAdd:s,toUpdate:a,toDelete:l}}validateRuleCount(e,t){if(t&&e>t)throw new Error(`Rule count (${e}) exceeds provider limit (${t})`);t&&e>t*.9&&r.warn(`Approaching rule limit: ${e}/${t} rules`)}updateMetadata(e,t){return{...e,metadata:{...e.metadata,version:t,updatedAt:new Date().toISOString()}}}calculateGrade(e){return e>=80?"excellent":e>=60?"good":e>=40?"fair":"poor"}generateRecommendations(e,t){let n=[];return t.some(o=>o.category==="rules")&&n.push("Consider using predefined templates to get started"),t.some(o=>o.category==="maintainability")&&n.push("Add descriptions to all rules for better documentation"),t.some(o=>o.category==="maintenance")&&n.push("Remove or enable disabled rules to keep configuration clean"),n}logSyncStats(e){r.info(`Sync complete for ${this.name}:`),r.info(` Rules added: ${e.rulesAdded}`),r.info(` Rules updated: ${e.rulesUpdated}`),r.info(` Rules deleted: ${e.rulesDeleted}`),e.ipsAdded!==void 0&&(r.info(` IPs added: ${e.ipsAdded}`),r.info(` IPs updated: ${e.ipsUpdated}`),r.info(` IPs deleted: ${e.ipsDeleted}`)),e.version&&r.info(` New version: ${e.version}`)}};de();function Fi(i){let e=[],t=[];i.conditionGroup.forEach((s,a)=>{for(let l of s.conditions){let c=ho(l.op);t.push({field:wo(l.type),operator:c,value:l.value,...l.neg?{negated:!0}:{},...l.key?{key:l.key}:{},group:a})}}),t.length>10&&e.push(D.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 Ni(i){let e=[],t=mo(i.conditions,i.id,e);if(i.action.response&&e.push(D.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 Vi(i){return{id:i.id,ip:i.ip,...i.hostname?{hostname:i.hostname}:{},...i.notes?{notes:i.notes}:{},action:i.action}}function mo(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=Co(l.field);if(!c){t.push(D.createUnsupportedFeatureWarning(`condition field '${l.field}'`,"unified config","Vercel",e,l.field));continue}a.push({op:yo(l.operator),neg:l.negated,type:c,key:l.key,value:l.value})}a.length>0&&o.push({conditions:a})}return o}function ho(i){return{eq:"eq",pre:"starts_with",suf:"ends_with",inc:"in",sub:"contains",re:"matches",ex:"exists",nex:"not_exists"}[i]||"eq"}function yo(i){return{eq:"eq",starts_with:"pre",ends_with:"suf",in:"inc",contains:"sub",matches:"re",exists:"ex",not_exists:"nex"}[i]||"eq"}function wo(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 Ro={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 Co(i){return Ro[i]??null}de();T();function Me(i){return i.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}function Bi(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 et=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}["${Me(t.toLowerCase())}"]`:e==="cookie"&&t?`${n}["${Me(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 bo=new Set(["ip.src"]),tt=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=et.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}["${Me(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(bo.has(e)){if(!Be.safeParse(t).success)throw new Error(`Invalid IP address or CIDR range: ${t}`);return t}return`"${Me(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 vo={"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"},Io=new Set(["eq","ne","contains","starts_with","ends_with","matches","in","gt","ge","lt","le"]),Eo=new Set(["and","or","not"]),me=class extends Error{};function To(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 me("Unterminated string literal");e.push({type:"STRING",value:Bi(l)}),t=a;continue}let s=t;for(;s<n&&!/[\s(){}[\]"]/.test(i[s]);)s++;if(s===t)throw new me(`Unexpected character '${o}' at position ${t}`);e.push({type:"WORD",value:i.slice(t,s)}),t=s}return e}var Ft=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 me("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 me(`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 Nt(i){let e=[Wi(i)];for(;i.isWord("or");)i.next(),e.push(Wi(i));return e.length===1?e[0]:{type:"or",children:e}}function Wi(i){let e=[qi(i)];for(;i.isWord("and");)i.next(),e.push(qi(i));return e.length===1?e[0]:{type:"and",children:e}}function qi(i){if(i.isWord("not")){i.next(),i.expect("LPAREN");let e=Nt(i);return i.expect("RPAREN"),{type:"not",child:e}}return $o(i)}function $o(i){let e=i.peek();if(e&&e.type==="LPAREN"){i.next();let t=Nt(i);return i.expect("RPAREN"),t}return _o(i)}function Ao(i){let e=i.expect("WORD");if(Eo.has(e.value))throw new me(`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 Mi(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 me(`Unexpected token '${e.value}' where a value was expected`)}function Po(i){if(i.peek()?.type==="LBRACE"){i.next();let e=[];for(;i.peek()?.type!=="RBRACE";)e.push(Mi(i));return i.expect("RBRACE"),e}return Mi(i)}function _o(i){let{field:e,key:t}=Ao(i);if(i.isWord("exists"))return i.next(),{type:"exists",field:e,key:t};let n=i.next();if(n.type!=="WORD"||!Io.has(n.value))throw new me(`Unsupported or unrecognized operator '${n.value}'`);let o=Po(i);return{type:"comparison",field:e,key:t,operator:n.value,value:o}}function ko(i){let e=new Ft(To(i)),t=Nt(e);if(!e.atEnd())throw new me("Unexpected trailing tokens");return t}function gt(i){return i.type==="comparison"||i.type==="exists"?!0:i.type==="not"?i.child.type==="comparison"||i.child.type==="exists":!1}function mt(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=vo[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 So(i){if(gt(i)){let e=mt(i,0);return e?{conditions:[e],conditionLogic:"AND"}:null}if(i.type==="and"){let e=[];for(let t of i.children){if(!gt(t))return null;let n=mt(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(gt(o)){let a=mt(o,s);a&&e.push(a);return}if(o.type==="and")for(let a of o.children){if(!gt(a))return;let l=mt(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 Vt(i){if(!i||!i.trim())return null;try{let e=ko(i);return So(e)}catch{return null}}function ji(i){let e=[],t=Vt(i.expression),n=[],o;t?(n=t.conditions,o=t.conditionLogic):e.push(D.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:Uo(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 zi(i){let e=[],t=tt.fromUnifiedConditions(i.conditions,i.conditionLogic),n={id:i.id||crypto.randomUUID(),action:Lo(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:Do(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(D.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(D.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(D.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 Gi(i){if(!Be.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 Uo(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 Lo(i){return{log:"log",deny:"block",block:"block",challenge:"managed_challenge",bypass:"skip",rate_limit:"block",redirect:"redirect",allow:"allow"}[i]||"block"}function Do(i){let e=i.match(/^(\d+)([smhd])$/);if(!e||!e[1]||!e[2])throw new Error(`Invalid window format: ${i}`);let t=parseInt(e[1],10),n=e[2],s={s:1,m:60,h:3600,d:86400}[n];if(s===void 0)throw new Error(`Invalid time unit: ${n}`);return t*s}de();function se(i,e,t){return D.createWarning("complex_expressions",i,void 0,e,t)}var xo={ip:"ip",path:"path",domain:"host",method:"method",country:"country",scheme:"scheme",user_agent:"user_agent"},Oo={ip:"ip",path:"path",host:"domain",method:"method",country:"country",scheme:"scheme",user_agent:"user_agent"},Fo={header:"request_header",query:"query_parameter",cookie:"request_cookie"},No={request_header:"header",query_parameter:"query",request_cookie:"cookie",response_header:"response_header",post_parameter:"post_parameter",signal:"signal"};function Vo(i){return xo[i]??i}function Bo(i){return Oo[i]??null}var Wo={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 Hi(i){return Wo[i]??{operator:"eq",negated:!1}}function Ki(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(D.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(D.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(se(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(se(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 qo(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(se(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)ht(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;ht(t,s,l,e,i.id)}),{conditions:t,conditionLogic:i.group_operator==="any"&&o.length>1?"OR":"AND"};e.push(se(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)ht(t,s,0,e,i.id);for(let s of n)for(let a of s.conditions)ht(t,a,0,e,i.id);return{conditions:t,conditionLogic:"AND"}}function ht(i,e,t,n,o){if(e.type==="single"){let{operator:u,negated:p}=Hi(e.operator);i.push({field:Vo(e.field),operator:u,value:e.value,...p?{negated:!0}:{},group:t});return}let s=No[e.field],a=e.conditions.find(u=>u.field==="name"),l=e.conditions.find(u=>u.field==="value"||u.field==="value_string");if(!l){n.push(se(o,`Multival condition on '${e.field}' has no value sub-condition doorman could extract (only a 'name' existence check) \u2014 skipped.`));return}let{operator:c,negated:d}=Hi(l.operator);i.push({field:s,operator:c,value:l.value,...d?{negated:!0}:{},...a?{key:a.value}:{},group:t})}function Mo(i,e){i.actions.length>1&&e.push(se(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(D.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(se(i.id,`Unrecognised Fastly action type "${String(t?.type)}" \u2014 mapped to "log".`)),{type:"log"}}}function Zi(i){let e=[],{conditions:t,conditionLogic:n}=qo(i,e),o=Mo(i,e);return t.length>10&&e.push(D.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 jo(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=Fo[a.field];if(l){if(a.key===void 0)return t.push(se(e,`Condition on '${a.field}' has no 'key' set \u2014 Fastly needs a specific ${a.field} name to match on (e.g. a header name). Skipped.`)),null;let d=l==="request_header"||l==="response_header"?"value_string":"value";return{type:"multival",field:l,operator:"exists",group_operator:"all",conditions:[{type:"single",field:"name",operator:"equals",value:a.key},{type:"single",field:d,operator:Ki(a.operator,a.negated,t,e),value:String(a.value)}]}}let c=Bo(a.field);return c?{type:"single",field:c,operator:Ki(a.operator,a.negated,t,e),value:String(a.value)}:(t.push(D.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 zo(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(se(t,'Unified action "bypass" has no direct Fastly equivalent \u2014 mapped to "allow".')),{type:"allow"};case"log":return e.push(se(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(se(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(D.createUnsupportedFeatureWarning(`action type '${i.type}'`,"unified config","Fastly",t,"action.type")),{type:"block"}}}function Go(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(D.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(D.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 Ho(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=Go(i.window,t,e);return t.push(D.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 Yi(i){let e=[];i.description&&i.description!==i.name&&e.push(se(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}=jo(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:[zo(i.action,e,i.id)],rate_limit:a?Ho(i.action.rateLimit,i.id,e):null},warnings:e}}function Ji(i,e){return i.map(t=>({ip:t,action:e}))}function Qi(i){let e=[],t=[],n=[];for(let o of i)(o.hostname||o.notes)&&e.push(se(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}}var A=class{static vercelToUnified=Fi;static unifiedToVercel=Ni;static vercelIPToUnified=Vi;static cloudflareToUnified=ji;static unifiedToCloudflare=zi;static unifiedIPToCloudflare=Gi;static fastlyToUnified=Zi;static unifiedToFastly=Yi;static fastlyListEntriesToUnified=Ji;static unifiedIPsToFastlyEntries=Qi};de();function je(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)||!je(i[o],e[o]))return!1;return!0}function Ae(i){let{id:e,...t}=i;return t}function it(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 yt(i){return i.some(e=>e.priority!==void 0)}var ze=class extends $e{constructor(t){super();this.client=t}client;name="vercel";async fetchConfig(t){try{r.debug("Fetching Vercel firewall configuration");let n=await this.client.fetchFirewallConfig(t),o=n.rules.map(a=>{let l=A.vercelToUnified(a);return l.warnings.length>0&&l.warnings.forEach(c=>{let{TranslationWarningSystem:d}=(de(),he(Le)),u=d.formatWarning(c);r.warn(`Rule ${a.name}:
|
|
24
|
+
`),u.cancelled&&process.exit(0),e(o))};t.on("keypress",a)})}T();var ki={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 Si={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 Ui={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"}]};function Li(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 Di(i,e){return i.fields.filter(t=>t.required&&!e[t.key])}var We={vercel:ki,cloudflare:Si,fastly:Ui};var pt=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 W){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 W){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 W.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 W){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 W){let o=this.detectionField(n);o&&process.env[o.envVar]&&t.add(n)}return Array.from(t)}};T();ge();import xi from"chalk";var ft=i=>({$schema:di,firewallEnabled:!0,rules:[],ips:[],...i});T();var Te=class{baseUrl;providerName;rateLimitInfo={};constructor(e,t){this.baseUrl=e,this.providerName=t}async makeRequest(e,t={}){let n=`${this.baseUrl}${e}`,{timeout:o=3e4,retries:s=3,retryDelay:a=1e3,...l}=t,c=null,d=null,u=null,p=()=>{u&&(clearTimeout(u),u=null),d&&(d.abort(),d=null)};try{for(let g=0;g<=s;g++)try{d=new AbortController,u=setTimeout(()=>{d&&d.abort()},o);let h=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(h),h.status===429){let C=this.calculateRateLimitWait(g,h);if(g===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 ${g+1}/${s+1})...`),await this.delay(C);continue}if(!h.ok)throw await this.handleErrorResponse(h);let w;try{let C=await h.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(h){if(c=h,u&&(clearTimeout(u),u=null),this.isNonRetryableError(h)||g===s)throw h;let w=a*Math.pow(2,g),C=Math.random()*.1*w,F=Math.min(w+C,3e4);r.debug(`Request failed (attempt ${g+1}/${s+1}). Retrying in ${Math.round(F)}ms...`),r.debug(`Error details: ${h instanceof Error?h.message:String(h)}`),await this.delay(F)}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),g=c.filter(h=>!p.includes(h));g.length>0&&r.warn(`Inconsistent array structure at ${s}[${d}]: missing keys ${g.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 Oi=process.env.DOORMAN_VERCEL_API_BASE_URL||"https://api.vercel.com/v1/security/firewall/config",qe=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?`${Oi}/${t}`:Oi;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(xi.bold("No firewall configuration found.")),!o)return r.debug("Skipping create-configuration prompt for read-only fetch."),this.emptyConfigPlaceholder();if(await R("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: ${xi.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}=ft();return r.debug("Empty Config:",n),this.putConfig(n)}async putConfig(t){return(await this.put(this.getUrl(),t)).active}async fetchActiveFirewallRules(){return(await this.fetchFirewallConfig())?.rules}async updateFirewallRule(t){let n=!t.id||t.id==="-",o={action:n?"rules.insert":"rules.update",id:n?null:t.id,value:{name:t.name,description:t.description,action:t.action,conditionGroup:t.conditionGroup,active:t.active}};return this.patch(this.getUrl(),o,n?{retries:0}:void 0)}async createFirewallRule(t){return this.updateFirewallRule({...t,id:"-"})}async deleteFirewallRule(t){let n={action:"rules.remove",id:t.id,value:null};await this.patch(this.getUrl(),n)}async updateIPBlockingRule(t){let n=!t.id||t.id==="-",o={action:n?"ip.insert":"ip.update",id:n?null:t.id,value:{action:t.action,hostname:t.hostname,ip:t.ip,notes:t.notes??null}};return this.patch(this.getUrl(),o,n?{retries:0}:void 0)}async createIPBlockingRule(t){return this.updateIPBlockingRule({...t,id:"-"})}async deleteIPBlockingRule(t){let n={action:"ip.remove",id:t.id,value:null};await this.patch(this.getUrl(),n)}async verifyCredentials(){try{return await this.fetchFirewallConfig(),!0}catch(t){return r.debug("Credential verification failed:",t),!1}}};T();import oe from"chalk";T();var $e=class{validateConfig(e){let t=[],n=[];if(e||t.push({path:"root",message:"Configuration is required",code:"CONFIG_REQUIRED"}),e&&!e.rules&&t.push({path:"rules",message:"Rules array is required",code:"RULES_REQUIRED"}),e&&e.rules&&!Array.isArray(e.rules)&&t.push({path:"rules",message:"Rules must be an array",code:"RULES_INVALID_TYPE"}),e&&Array.isArray(e.rules)){let o=new Map;e.rules.forEach((s,a)=>{let l=s.id||s.name;if(!l)return;let c=o.get(l);if(c===void 0){o.set(l,a);return}let d=e.rules[c];n.push({path:`rules[${a}]`,message:s.id?`Rule "${s.name}" has the same id ("${s.id}") as rules[${c}] ("${d?.name}") \u2014 they will collide during sync diffing and one may be silently skipped.`:`Rule "${s.name}" has the same name as rules[${c}] and neither has an explicit id \u2014 they will collide during sync diffing and one may be silently skipped. Add unique ids to disambiguate.`,code:"DUPLICATE_RULE_IDENTIFIER"})})}return{valid:t.length===0,errors:t,warnings:n}}getHealthScore(e){let t=[],n=100;if(!e)return{score:0,grade:"poor",issues:[{severity:"error",category:"configuration",message:"No configuration found"}],recommendations:["Create a configuration file"]};if((!e.rules||e.rules.length===0)&&(n-=20,t.push({severity:"warning",category:"rules",message:"No rules defined",suggestion:"Add security rules to protect your application"})),e.rules&&e.rules.length>0){let a=e.rules.filter(l=>!l.description);a.length>0&&(n-=5,t.push({severity:"info",category:"maintainability",message:`${a.length} rule(s) missing descriptions`,suggestion:"Add descriptions to improve maintainability"}))}if(e.rules&&e.rules.length>0){let a=e.rules.filter(l=>l.active===!1||l.enabled===!1);a.length>0&&(n-=5,t.push({severity:"info",category:"maintenance",message:`${a.length} disabled rule(s) found`,suggestion:"Review and remove unused rules"}))}let o=this.calculateGrade(n),s=this.generateRecommendations(e,t);return{score:Math.max(0,n),grade:o,issues:t,recommendations:s}}diffItems(e,t,n,o="id"){let s=[],a=[],l=[],c=d=>d[o];for(let d of e){let u=t.find(p=>c(p)===c(d));u?n(d,u)||a.push(d):s.push(d)}for(let d of t)e.find(p=>c(p)===c(d))||l.push(d);return{toAdd:s,toUpdate:a,toDelete:l}}validateRuleCount(e,t){if(t&&e>t)throw new Error(`Rule count (${e}) exceeds provider limit (${t})`);t&&e>t*.9&&r.warn(`Approaching rule limit: ${e}/${t} rules`)}updateMetadata(e,t){return{...e,metadata:{...e.metadata,version:t,updatedAt:new Date().toISOString()}}}calculateGrade(e){return e>=80?"excellent":e>=60?"good":e>=40?"fair":"poor"}generateRecommendations(e,t){let n=[];return t.some(o=>o.category==="rules")&&n.push("Consider using predefined templates to get started"),t.some(o=>o.category==="maintainability")&&n.push("Add descriptions to all rules for better documentation"),t.some(o=>o.category==="maintenance")&&n.push("Remove or enable disabled rules to keep configuration clean"),n}logSyncStats(e){r.info(`Sync complete for ${this.name}:`),r.info(` Rules added: ${e.rulesAdded}`),r.info(` Rules updated: ${e.rulesUpdated}`),r.info(` Rules deleted: ${e.rulesDeleted}`),e.ipsAdded!==void 0&&(r.info(` IPs added: ${e.ipsAdded}`),r.info(` IPs updated: ${e.ipsUpdated}`),r.info(` IPs deleted: ${e.ipsDeleted}`)),e.version&&r.info(` New version: ${e.version}`)}};de();function Fi(i){let e=[],t=[];i.conditionGroup.forEach((s,a)=>{for(let l of s.conditions){let c=ho(l.op);t.push({field:wo(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(D.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 Ni(i){let e=[],t=mo(i.conditions,i.id,e);if(i.action.response&&e.push(D.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 Vi(i){return{id:i.id,ip:i.ip,...i.hostname?{hostname:i.hostname}:{},...i.notes?{notes:i.notes}:{},action:i.action}}function mo(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=Co(l.field);if(!c){t.push(D.createUnsupportedFeatureWarning(`condition field '${l.field}'`,"unified config","Vercel",e,l.field));continue}a.push({op:yo(l.operator),neg:l.negated,type:c,key:l.key,value:l.value})}a.length>0&&o.push({conditions:a})}return o}function ho(i){return{eq:"eq",pre:"starts_with",suf:"ends_with",inc:"in",sub:"contains",re:"matches",ex:"exists",nex:"not_exists"}[i]||"eq"}function yo(i){return{eq:"eq",starts_with:"pre",ends_with:"suf",in:"inc",contains:"sub",matches:"re",exists:"ex",not_exists:"nex"}[i]||"eq"}function wo(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 Ro={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 Co(i){return Ro[i]??null}de();T();function Me(i){return i.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}function Bi(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 et=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}["${Me(t.toLowerCase())}"]`:e==="cookie"&&t?`${n}["${Me(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 bo=new Set(["ip.src"]),tt=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=et.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}["${Me(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(bo.has(e)){if(!Be.safeParse(t).success)throw new Error(`Invalid IP address or CIDR range: ${t}`);return t}return`"${Me(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 vo={"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"},Io=new Set(["eq","ne","contains","starts_with","ends_with","matches","in","gt","ge","lt","le"]),Eo=new Set(["and","or","not"]),me=class extends Error{};function To(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 me("Unterminated string literal");e.push({type:"STRING",value:Bi(l)}),t=a;continue}let s=t;for(;s<n&&!/[\s(){}[\]"]/.test(i[s]);)s++;if(s===t)throw new me(`Unexpected character '${o}' at position ${t}`);e.push({type:"WORD",value:i.slice(t,s)}),t=s}return e}var Ft=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 me("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 me(`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 Nt(i){let e=[Wi(i)];for(;i.isWord("or");)i.next(),e.push(Wi(i));return e.length===1?e[0]:{type:"or",children:e}}function Wi(i){let e=[qi(i)];for(;i.isWord("and");)i.next(),e.push(qi(i));return e.length===1?e[0]:{type:"and",children:e}}function qi(i){if(i.isWord("not")){i.next(),i.expect("LPAREN");let e=Nt(i);return i.expect("RPAREN"),{type:"not",child:e}}return $o(i)}function $o(i){let e=i.peek();if(e&&e.type==="LPAREN"){i.next();let t=Nt(i);return i.expect("RPAREN"),t}return _o(i)}function Ao(i){let e=i.expect("WORD");if(Eo.has(e.value))throw new me(`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 Mi(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 me(`Unexpected token '${e.value}' where a value was expected`)}function Po(i){if(i.peek()?.type==="LBRACE"){i.next();let e=[];for(;i.peek()?.type!=="RBRACE";)e.push(Mi(i));return i.expect("RBRACE"),e}return Mi(i)}function _o(i){let{field:e,key:t}=Ao(i);if(i.isWord("exists"))return i.next(),{type:"exists",field:e,key:t};let n=i.next();if(n.type!=="WORD"||!Io.has(n.value))throw new me(`Unsupported or unrecognized operator '${n.value}'`);let o=Po(i);return{type:"comparison",field:e,key:t,operator:n.value,value:o}}function ko(i){let e=new Ft(To(i)),t=Nt(e);if(!e.atEnd())throw new me("Unexpected trailing tokens");return t}function gt(i){return i.type==="comparison"||i.type==="exists"?!0:i.type==="not"?i.child.type==="comparison"||i.child.type==="exists":!1}function mt(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=vo[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 So(i){if(gt(i)){let e=mt(i,0);return e?{conditions:[e],conditionLogic:"AND"}:null}if(i.type==="and"){let e=[];for(let t of i.children){if(!gt(t))return null;let n=mt(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(gt(o)){let a=mt(o,s);a&&e.push(a);return}if(o.type==="and")for(let a of o.children){if(!gt(a))return;let l=mt(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 Vt(i){if(!i||!i.trim())return null;try{let e=ko(i);return So(e)}catch{return null}}function ji(i){let e=[],t=Vt(i.expression),n=[],o;t?(n=t.conditions,o=t.conditionLogic):e.push(D.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:Uo(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 zi(i){let e=[],t=tt.fromUnifiedConditions(i.conditions,i.conditionLogic),n={id:i.id||crypto.randomUUID(),action:Lo(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:Do(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(D.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(D.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(D.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 Gi(i){if(!Be.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 Uo(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 Lo(i){return{log:"log",deny:"block",block:"block",challenge:"managed_challenge",bypass:"skip",rate_limit:"block",redirect:"redirect",allow:"allow"}[i]||"block"}function Do(i){let e=i.match(/^(\d+)([smhd])$/);if(!e||!e[1]||!e[2])throw new Error(`Invalid window format: ${i}`);let t=parseInt(e[1],10),n=e[2],s={s:1,m:60,h:3600,d:86400}[n];if(s===void 0)throw new Error(`Invalid time unit: ${n}`);return t*s}de();function se(i,e,t){return D.createWarning("complex_expressions",i,void 0,e,t)}var xo={ip:"ip",path:"path",domain:"host",method:"method",country:"country",scheme:"scheme",user_agent:"user_agent"},Oo={ip:"ip",path:"path",host:"domain",method:"method",country:"country",scheme:"scheme",user_agent:"user_agent"},Fo={header:"request_header",query:"query_parameter",cookie:"request_cookie"},No={request_header:"header",query_parameter:"query",request_cookie:"cookie",response_header:"response_header",post_parameter:"post_parameter",signal:"signal"};function Vo(i){return xo[i]??i}function Bo(i){return Oo[i]??null}var Wo={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 Hi(i){return Wo[i]??{operator:"eq",negated:!1}}function Ki(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(D.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(D.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(se(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(se(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 qo(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(se(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)ht(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;ht(t,s,l,e,i.id)}),{conditions:t,conditionLogic:i.group_operator==="any"&&o.length>1?"OR":"AND"};e.push(se(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)ht(t,s,0,e,i.id);for(let s of n)for(let a of s.conditions)ht(t,a,0,e,i.id);return{conditions:t,conditionLogic:"AND"}}function ht(i,e,t,n,o){if(e.type==="single"){let{operator:u,negated:p}=Hi(e.operator);i.push({field:Vo(e.field),operator:u,value:e.value,...p?{negated:!0}:{},group:t});return}let s=No[e.field],a=e.conditions.find(u=>u.field==="name"),l=e.conditions.find(u=>u.field==="value"||u.field==="value_string");if(!l){n.push(se(o,`Multival condition on '${e.field}' has no value sub-condition doorman could extract (only a 'name' existence check) \u2014 skipped.`));return}let{operator:c,negated:d}=Hi(l.operator);i.push({field:s,operator:c,value:l.value,...d?{negated:!0}:{},...a?{key:a.value}:{},group:t})}function Mo(i,e){i.actions.length>1&&e.push(se(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(D.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(se(i.id,`Unrecognised Fastly action type "${String(t?.type)}" \u2014 mapped to "log".`)),{type:"log"}}}function Zi(i){let e=[],{conditions:t,conditionLogic:n}=qo(i,e),o=Mo(i,e);return t.length>10&&e.push(D.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 jo(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=Fo[a.field];if(l){if(a.key===void 0)return t.push(se(e,`Condition on '${a.field}' has no 'key' set \u2014 Fastly needs a specific ${a.field} name to match on (e.g. a header name). Skipped.`)),null;let d=l==="request_header"||l==="response_header"?"value_string":"value";return{type:"multival",field:l,operator:"exists",group_operator:"all",conditions:[{type:"single",field:"name",operator:"equals",value:a.key},{type:"single",field:d,operator:Ki(a.operator,a.negated,t,e),value:String(a.value)}]}}let c=Bo(a.field);return c?{type:"single",field:c,operator:Ki(a.operator,a.negated,t,e),value:String(a.value)}:(t.push(D.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 zo(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(se(t,'Unified action "bypass" has no direct Fastly equivalent \u2014 mapped to "allow".')),{type:"allow"};case"log":return e.push(se(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(se(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(D.createUnsupportedFeatureWarning(`action type '${i.type}'`,"unified config","Fastly",t,"action.type")),{type:"block"}}}function Go(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(D.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(D.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 Ho(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=Go(i.window,t,e);return t.push(D.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 Yi(i){let e=[];i.description&&i.description!==i.name&&e.push(se(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}=jo(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:[zo(i.action,e,i.id)],rate_limit:a?Ho(i.action.rateLimit,i.id,e):null},warnings:e}}function Ji(i,e){return i.map(t=>({ip:t,action:e}))}function Qi(i){let e=[],t=[],n=[];for(let o of i)(o.hostname||o.notes)&&e.push(se(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}}var A=class{static vercelToUnified=Fi;static unifiedToVercel=Ni;static vercelIPToUnified=Vi;static cloudflareToUnified=ji;static unifiedToCloudflare=zi;static unifiedIPToCloudflare=Gi;static fastlyToUnified=Zi;static unifiedToFastly=Yi;static fastlyListEntriesToUnified=Ji;static unifiedIPsToFastlyEntries=Qi};de();function je(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)||!je(i[o],e[o]))return!1;return!0}function Ae(i){let{id:e,...t}=i;return t}function it(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 yt(i){return i.some(e=>e.priority!==void 0)}var ze=class extends $e{constructor(t){super();this.client=t}client;name="vercel";async fetchConfig(t){try{r.debug("Fetching Vercel firewall configuration");let n=await this.client.fetchFirewallConfig(t),o=n.rules.map(a=>{let l=A.vercelToUnified(a);return l.warnings.length>0&&l.warnings.forEach(c=>{let{TranslationWarningSystem:d}=(de(),he(Le)),u=d.formatWarning(c);r.warn(`Rule ${a.name}:
|
|
25
25
|
${u}`)}),l.result}),s=n.ips.map(a=>A.vercelIPToUnified(a));return{version:"2.0",provider:"vercel",providers:{vercel:{projectId:this.client.projectId,...this.client.teamId?{teamId:this.client.teamId}:{}}},rules:o,ips:s,metadata:{version:n.version,updatedAt:n.updatedAt}}}catch(n){throw r.error("Error fetching Vercel configuration:",n),new Error("Failed to fetch Vercel firewall configuration")}}async syncRules(t,n={}){let{dryRun:o=!1}=n;try{let{OperationSafety:s}=(Rt(),he(wt)),a=await s.performDryRunValidation(t,"sync rules",async f=>await this.getChanges(f,{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,g=l.ipsToAdd||[],h=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: ${g.length}, Update: ${h.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 K=c.map(f=>A.unifiedToVercel(f).result),B=d.map(f=>A.unifiedToVercel(f).result),ce=u.map(f=>A.unifiedToVercel(f).result),fe=g.map(f=>({id:f.id||"",ip:f.ip,hostname:f.hostname||"",action:f.action,notes:f.notes})),_e=h.map(f=>({id:f.id||"",ip:f.ip,hostname:f.hostname||"",action:f.action,notes:f.notes})),k=w.map(f=>({id:f.id||"",ip:f.ip,hostname:f.hostname||"",action:f.action,notes:f.notes})),Ce=[],be=[],ve=[],Ne=[],ke=[],Ve=[],b=[],N=(f,I)=>`${f}: ${I instanceof Error?I.message:String(I)}`,ne=[];yt(t.rules)&&t.rules.length>c.length&&ne.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 ae=[];for(let f of ce)try{r.debug(`Deleting custom rule: ${f.id}`),await this.client.deleteFirewallRule(f),ve.push(f),r.debug(`Custom rule deleted: ${f.id}`)}catch(I){r.error(`Failed to delete custom rule ${f.id}:`,I),b.push(N(`Failed to delete custom rule ${f.id}`,I))}for(let f of k)try{r.debug(`Deleting IP blocking rule: ${f.id}`),await this.client.deleteIPBlockingRule(f),Ve.push(f),r.debug(`IP blocking rule deleted: ${f.id}`)}catch(I){r.error(`Failed to delete IP blocking rule ${f.id}:`,I),b.push(N(`Failed to delete IP blocking rule ${f.id}`,I))}for(let f of K)try{r.debug(`Adding new custom rule: ${f.name}`);let I=await this.client.createFirewallRule(f);Ce.push(I),I.id&&I.id!==f.id&&ae.push({oldId:f.id||void 0,newId:I.id,name:f.name}),r.debug(`New custom rule added: ${I.id}`)}catch(I){r.error(`Failed to add custom rule "${f.name}":`,I),b.push(N(`Failed to add custom rule "${f.name}"`,I))}for(let f of fe)try{r.debug(`Adding new IP blocking rule: ${f.ip}`);let I=await this.client.createIPBlockingRule(f);Ne.push(I),r.debug(`New IP blocking rule added: (hostname): ${I.hostname} (ip): ${I.ip}`)}catch(I){r.error(`Failed to add IP blocking rule ${f.ip}:`,I),b.push(N(`Failed to add IP blocking rule ${f.ip}`,I))}for(let f of B)try{r.debug(`Updating custom rule: ${f.id}`);let I=await this.client.updateFirewallRule(f);be.push(I),r.debug(`Custom rule updated: ${I.id}`)}catch(I){r.error(`Failed to update custom rule ${f.id}:`,I),b.push(N(`Failed to update custom rule ${f.id}`,I))}for(let f of _e)try{r.debug(`Updating IP blocking rule: ${f.id}`);let I=await this.client.updateIPBlockingRule(f);ke.push(I),r.debug(`IP blocking rule updated: ${I.id}`)}catch(I){r.error(`Failed to update IP blocking rule ${f.id}:`,I),b.push(N(`Failed to update IP blocking rule ${f.id}`,I))}r.debug(`${oe.underline("Custom Rules:")} ${oe.green("Added:")} ${oe.green(Ce.length)}, ${oe.cyan("Updated:")} ${oe.cyan(be.length)}, ${oe.red("Deleted:")} ${oe.red(ve.length)}`),r.debug(`${oe.underline("IP Rules:")} ${oe.green("Added:")} ${oe.green(Ne.length)}, ${oe.cyan("Updated:")} ${oe.cyan(ke.length)}, ${oe.red("Deleted:")} ${oe.red(Ve.length)}`);let Qe=p,Xe,Ie=[...ne];try{let f=await this.client.fetchFirewallConfig();Qe=f.version,Xe=f.updatedAt;let I=new Set(f.rules.map(O=>O.id)),li=new Set(f.ips.map(O=>O.id));for(let O of[...Ce,...be])O.id&&!I.has(O.id)&&Ie.push(`Rule "${O.name}" (${O.id}) was synced but is missing from the post-sync remote config \u2014 a subsequent sync may re-create or misdiff it.`);for(let O of ve)O.id&&I.has(O.id)&&Ie.push(`Rule "${O.name}" (${O.id}) was deleted but still appears in the post-sync remote config.`);for(let O of[...Ne,...ke])O.id&&!li.has(O.id)&&Ie.push(`IP rule "${O.ip}" (${O.id}) was synced but is missing from the post-sync remote config \u2014 a subsequent sync may re-create or misdiff it.`);for(let O of Ve)O.id&&li.has(O.id)&&Ie.push(`IP rule "${O.ip}" (${O.id}) was deleted but still appears in the post-sync remote config.`)}catch(f){r.error("Failed to fetch updated firewall configuration version after sync:",f),b.push(N("Failed to fetch updated configuration version after sync",f))}return{success:b.length===0,rulesAdded:Ce.length,rulesUpdated:be.length,rulesDeleted:ve.length,ipsAdded:Ne.length,ipsUpdated:ke.length,ipsDeleted:Ve.length,version:Qe,updatedAt:Xe,idRemappings:ae.length>0?ae:void 0,errors:b.length>0?b:void 0,warnings:Ie.length>0?Ie: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=Se.safeParse(t);if(!o.success)throw new Error(`Invalid firewall configuration: ${o.error.message}`);try{r.debug("Fetching existing firewall configuration");let s=await this.client.fetchFirewallConfig(void 0,{allowCreate:n?.allowCreate??!0});r.debug(`Fetched ${s.rules.length} custom rules and ${s.ips.length} IP blocking rules`);let a=s.rules.map(w=>{let C=A.vercelToUnified(w);return C.warnings.length>0&&C.warnings.forEach(F=>{let{TranslationWarningSystem:K}=(de(),he(Le)),B=K.formatWarning(F);r.warn(`Rule ${w.name}:
|
|
26
|
-
${B}`)}),C.result}),{toAdd:l,toUpdate:c,toDelete:d}=this.diffRules(it(
|
|
26
|
+
${B}`)}),C.result}),{toAdd:l,toUpdate:c,toDelete:d}=this.diffRules(it(o.data.rules),a),u=s.ips.map(w=>A.vercelIPToUnified(w)),{ipsToAdd:p,ipsToUpdate:g,ipsToDelete:h}=this.diffIPRules(o.data.ips||[],u);return{version:s.version,rulesToAdd:l,rulesToUpdate:c,rulesToDelete:d,ipsToAdd:p,ipsToUpdate:g,ipsToDelete:h,hasChanges:l.length>0||c.length>0||d.length>0||p.length>0||g.length>0||h.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=Ee.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{je(Ae(l),Ae(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{je(Ae(l),Ae(c))||s.push({...c,...l});let d=a.findIndex(u=>u.id===c.id);d!==-1&&a.splice(d,1)}}return{ipsToAdd:o,ipsToUpdate:s,ipsToDelete:a}}};T();var nt=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 qe(t,n,e);return new ze(o)}static fromConfig(e){let t=e.token||process.env.VERCEL_TOKEN,n=e.projectId||process.env.VERCEL_PROJECT_ID,o=e.teamId||process.env.VERCEL_TEAM_ID;if(!t)throw new Error("Vercel API token is required (provide token or set VERCEL_TOKEN env var)");if(!n)throw new Error("Vercel project ID is required (provide projectId or set VERCEL_PROJECT_ID env var)");r.debug("Creating Vercel provider with config:",{projectId:n,teamId:o});let s=new qe(n,o,t);return new ze(s)}static create(e,t,n){return this.fromConfig({projectId:e,teamId:t,token:n})}};import{createHash as tr}from"crypto";T();import Ge from"chalk";var m=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(Ge.red.bold(`[${this.code}] ${this.message}`)),e.push(""),this.suggestion&&(e.push(Ge.yellow.bold("Suggestion:")),e.push(` ${this.suggestion}`),e.push("")),this.details&&Object.keys(this.details).length>0&&(e.push(Ge.dim("Details:")),Object.entries(this.details).forEach(([t,n])=>{let o=typeof n=="object"?JSON.stringify(n,null,2).split(`
|
|
27
27
|
`).join(`
|
|
28
28
|
`):n;e.push(` ${Ge.cyan(t)}: ${o}`)}),e.push("")),this.cause&&(e.push(Ge.dim("Caused by:")),e.push(` ${this.cause.message}`),e.push("")),this.docsUrl&&(e.push(Ge.cyan.bold("Documentation:")),e.push(` ${this.docsUrl}`),e.push("")),e.join(`
|
|
29
29
|
`)}toPlainText(){let e=[];return e.push(`[${this.code}] ${this.message}`),this.suggestion&&e.push(`Suggestion: ${this.suggestion}`),this.details&&Object.keys(this.details).length>0&&(e.push("Details:"),Object.entries(this.details).forEach(([t,n])=>{e.push(` ${t}: ${n}`)})),this.cause&&e.push(`Caused by: ${this.cause.message}`),this.docsUrl&&e.push(`Documentation: ${this.docsUrl}`),e.join(`
|