@gfargo/doorman 3.10.1 → 3.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/run.js CHANGED
@@ -37,9 +37,9 @@ ${W}`)}),R.result}),{toAdd:l,toUpdate:c,toDelete:d}=this.diffRules(lt(t.rules),a
37
37
  ${l.formatWarning(a)}`)}),o.push(s.result)}return o}async syncRules(t,o={}){let{dryRun:n=!1}=o;try{let{OperationSafety:s}=(bt(),ye(vt)),a=await s.performDryRunValidation(t,"sync rules",async v=>this.getChanges(v));if(!a.valid)throw new Error(`Dry-run validation failed: ${a.issues.join(", ")}`);let l=a.changes,{rulesToAdd:c,rulesToUpdate:d,rulesToDelete:u}=l,p=l.ipsToAdd||[],g=l.ipsToDelete||[];if(n)return r.info("Dry run mode. The following changes would be made:"),r.info(`Custom Rules - Add: ${c.length}, Update: ${d.length}, Delete: ${u.length}`),r.info(`IP Rules - Add: ${p.length}, Delete: ${g.length}`),{success:!0,rulesAdded:0,rulesUpdated:0,rulesDeleted:0,ipsAdded:0,ipsUpdated:0,ipsDeleted:0,warnings:a.warnings};let h=s.assessOperationRisk(l,t);if(!await s.confirmDestructiveOperation({operation:"sync rules",target:`Fastly workspace ${this.client.workspaceId}`,changes:l,riskLevel:h,skipConfirmation:o.force||!1,dryRun:!1,allowDeletions:o.allowDeletions||!1}))throw new Error("Operation cancelled by user");let R=[];Rt(t.rules)&&R.push("Rule `priority` has no effect on Fastly: Next-Gen WAF rules are evaluated independently and have no API-exposed ordering concept to honour.");let N=c.map(v=>({rule:v,translated:k.unifiedToFastly(v)})),H=d.map(v=>({rule:v,translated:k.unifiedToFastly(v)})),W=[],ue=(v,V)=>`${v}: ${V instanceof Error?V.message:String(V)}`,me=[],_e=[],_=[],Ce=[];for(let v of u)if(v.id)try{r.debug(`Deleting Fastly rule: ${v.id}`),await this.client.deleteRule(v.id),_.push(v.id)}catch(V){r.error(`Failed to delete Fastly rule ${v.id}:`,V),W.push(ue(`Failed to delete rule ${v.id}`,V))}for(let{rule:v,translated:V}of N){let ie=V.result;try{r.debug(`Adding new Fastly rule: ${ie.description}`);let ae=await this.client.createRule(ie);me.push(ae),Ce.push({oldId:v.id||void 0,newId:ae.id,name:ie.description})}catch(ae){r.error(`Failed to add Fastly rule "${ie.description}":`,ae),W.push(ue(`Failed to add rule "${ie.description}"`,ae))}}for(let{rule:v,translated:V}of H)if(v.id)try{r.debug(`Updating Fastly rule: ${v.id}`);let ie=await this.client.updateRule(v.id,V.result);_e.push(ie)}catch(ie){r.error(`Failed to update Fastly rule ${v.id}:`,ie),W.push(ue(`Failed to update rule ${v.id}`,ie))}let ve=0,be=0,We=t.ips||[],{result:Se,warnings:je}=k.unifiedIPsToFastlyEntries(We);je.forEach(v=>R.push(v.message));try{for(let[v,V]of[[dt,Se.denyEntries],[ut,Se.allowEntries]]){let ae=(await this.client.findList(v))?.entries??[],it=V.filter(f=>!ae.includes(f)),ot=ae.filter(f=>!V.includes(f));if(it.length===0&&ot.length===0)continue;let Ee=await this.client.getOrCreateList(v);await this.client.updateListEntries(Ee.id,V),ve+=it.length,be+=ot.length}}catch(v){r.error("Failed to sync Fastly IP lists:",v),W.push(ue("Failed to sync IP lists",v))}return r.debug(`Custom Rules: Added: ${me.length}, Updated: ${_e.length}, Deleted: ${_.length}`),r.debug(`IP Rules: Added: ${ve}, Deleted: ${be}`),{success:W.length===0,rulesAdded:me.length,rulesUpdated:_e.length,rulesDeleted:_.length,ipsAdded:ve,ipsUpdated:0,ipsDeleted:be,idRemappings:Ce.length>0?Ce:void 0,errors:W.length>0?W:void 0,warnings:R.length>0?R:void 0}}catch(s){throw r.error("Error during sync:",s),new Error(`Failed to synchronize firewall rules: ${s instanceof Error?s.message:String(s)}`,{cause:s})}}async getChanges(t){let o=Le.safeParse(t);if(!o.success)throw new Error(`Invalid firewall configuration: ${o.error.message}`);try{r.debug("Fetching existing Fastly configuration");let[n,s,a]=await Promise.all([this.client.fetchRules(),this.client.findList(dt),this.client.findList(ut)]),l=this.translateFastlyRules(n),{toAdd:c,toUpdate:d,toDelete:u}=this.diffRules(t.rules,l),p=s?.entries??[],g=a?.entries??[],h=[...k.fastlyListEntriesToUnified(p,"deny"),...k.fastlyListEntriesToUnified(g,"allow")],{ipsToAdd:I,ipsToDelete:R}=this.diffIPs(t.ips||[],h);return{rulesToAdd:c,rulesToUpdate:d,rulesToDelete:u,ipsToAdd:I,ipsToUpdate:[],ipsToDelete:R,hasChanges:c.length>0||d.length>0||u.length>0||I.length>0||R.length>0}}catch(n){throw r.error("Error fetching existing Fastly configuration:",n),new Error("Failed to fetch existing Fastly configuration")}}getSupportedFeatures(){return{supportsCustomRules:!0,supportsIPBlocking:!0,supportsRateLimiting:!0,supportsManagedRules:!1,supportsGeoBlocking:!0,supportsRedirect:!0,supportsChallenge:!0}}async verifyCredentials(){return this.client.verifyCredentials()}validateConfig(t){let o=super.validateConfig(t),n=[...o.errors],s=[...o.warnings];return t.provider&&t.provider!=="fastly"&&n.push({path:"provider",message:"Provider must be 'fastly' for FastlyFirewallService",code:"INVALID_PROVIDER"}),{valid:n.length===0,errors:n,warnings:s}}getHealthScore(t){let o=super.getHealthScore(t),n=[...o.issues],s=o.score;return t.rules.filter(l=>l.action?.type==="rate_limit").length===0&&(s-=10,n.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)&&n.push({severity:"info",category:"security",message:"No IP blocking rules configured",suggestion:"Consider blocking known malicious IPs"}),{score:Math.max(0,s),grade:o.grade,issues:n,recommendations:o.recommendations}}diffRules(t,o){let n=[],s=[],a=[...o];for(let l of t){let c=o.find(d=>d.id===l.id);if(!c)n.push(l);else{Ke(ke(l),ke(c))||s.push({...l,id:c.id});let d=a.findIndex(u=>u.id===c.id);d!==-1&&a.splice(d,1)}}return{toAdd:n,toUpdate:s,toDelete:a}}diffIPs(t,o){let n=l=>`${l.action}:${l.ip}`,s=new Set(o.map(n)),a=new Set(t.map(n));return{ipsToAdd:t.filter(l=>!s.has(n(l))),ipsToDelete:o.filter(l=>!a.has(n(l)))}}};T();var tt=class{static fromEnv(){let e=process.env.FASTLY_API_TOKEN,t=process.env.FASTLY_WORKSPACE_ID;if(!e)throw new Error("FASTLY_API_TOKEN environment variable is required");if(!t)throw new Error("FASTLY_WORKSPACE_ID environment variable is required");let o=new Xe(t,e);return new et(o)}static fromConfig(e){let t=e.apiToken||process.env.FASTLY_API_TOKEN,o=e.workspaceId||process.env.FASTLY_WORKSPACE_ID;if(!t)throw new Error("Fastly API token is required (provide apiToken or set FASTLY_API_TOKEN env var)");if(!o)throw new Error("Fastly workspace ID is required (provide workspaceId or set FASTLY_WORKSPACE_ID env var)");r.debug("Creating Fastly provider with config:",{workspaceId:o});let n=new Xe(o,t);return new et(n)}static create(e,t){return this.fromConfig({workspaceId:e,apiToken:t})}};var Zt={provider:"vercel",fields:[{key:"token",envVar:"VERCEL_TOKEN",label:"Vercel API Token",required:!0,secret:!0},{key:"projectId",envVar:"VERCEL_PROJECT_ID",label:"Vercel Project ID",required:!0,configKey:"projectId"},{key:"teamId",envVar:"VERCEL_TEAM_ID",label:"Vercel Team ID",required:!1,configKey:"teamId"}]};var Yt={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 Jt={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 Tt(i,e={}){let{explicit:t={},config:o={},env:n=process.env}=e,s={};for(let a of i.fields)s[a.key]=t[a.key]||o[a.key]||n[a.envVar];return s}async function fo(i){let e;if(i.provider)e=i.provider,r.debug(`Using explicitly specified provider: ${e}`);else{let t=mt.detect(i.config);t.provider?(e=t.provider,r.debug(`Auto-detected provider: ${e} (${t.confidence} confidence)`),t.reasons.length>0&&r.debug(`Reasons: ${t.reasons.join(", ")}`)):i.interactive!==!1?e=await ar():(e="vercel",r.warn("No provider detected, defaulting to Vercel"))}try{if(e==="vercel")return await nr(i);if(e==="cloudflare")return{provider:await rr(i)};if(e==="fastly")return{provider:await sr(i)};throw new Error(`Unknown provider: ${e}`)}catch(t){throw r.error(`Failed to initialize ${e} provider:`,t),t}}async function nr(i){let e=i.config,{token:t,projectId:o,teamId:n}=Tt(Zt,{explicit:{token:i.token,projectId:i.projectId,teamId:i.teamId},config:{projectId:e?.providers?.vercel?.projectId??e?.projectId,teamId:e?.providers?.vercel?.teamId??e?.teamId}});if(!t||!o){if(i.interactive===!1)throw new Error("Vercel credentials missing. Provide token and projectId.");r.warn("Missing Vercel credentials"),r.info("Please provide your Vercel credentials:");let{promptForCredentials:a}=await Promise.resolve().then(()=>(co(),lo)),l=await a({token:t,projectId:o,teamId:n});return{provider:Ze.fromConfig({token:l.token,projectId:l.projectId,teamId:l.teamId}),vercelCredentials:l}}return{provider:Ze.fromConfig({token:t,projectId:o,teamId:n}),vercelCredentials:{token:t,projectId:o,teamId:n}}}async function rr(i){let e=i.config,{apiToken:t,zoneId:o,accountId:n}=Tt(Yt,{explicit:{apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId},config:{zoneId:e?.providers?.cloudflare?.zoneId,accountId:e?.providers?.cloudflare?.accountId}});if(!t||!o){if(i.interactive===!1)throw new Error("Cloudflare credentials missing. Provide apiToken and zoneId.");r.warn("Missing Cloudflare credentials"),r.info("Please provide your Cloudflare credentials:");let s=t||await rt("Cloudflare API Token: "),a=o||await w("Cloudflare Zone ID:",{type:"text"}),l=n||await w("Cloudflare Account ID (optional):",{type:"text"});return Qe.fromConfig({apiToken:s,zoneId:a,accountId:l||void 0})}return Qe.fromConfig({apiToken:t,zoneId:o,accountId:n})}async function sr(i){let e=i.config,{apiToken:t,workspaceId:o}=Tt(Jt,{explicit:{apiToken:i.apiToken,workspaceId:i.workspaceId},config:{workspaceId:e?.providers?.fastly?.workspaceId}});if(!t||!o){if(i.interactive===!1)throw new Error("Fastly credentials missing. Provide apiToken and workspaceId.");r.warn("Missing Fastly credentials"),r.info("Please provide your Fastly credentials:");let n=t||await rt("Fastly API Token: "),s=o||await w("Fastly Next-Gen WAF Workspace ID:",{type:"text"});return tt.fromConfig({apiToken:n,workspaceId:s})}return tt.fromConfig({apiToken:t,workspaceId:o})}async function ar(){r.info(po.default.yellow("Unable to auto-detect firewall provider.")),r.info(`Please select a provider:
38
38
  `),re.forEach((s,a)=>{r.info(` ${a+1}. ${uo(s)}`)}),r.info("");let i=await w(`Select provider (1-${re.length}):`,{type:"text"}),e=String(i).trim().toLowerCase(),t=re.find(s=>s===e);if(t)return t;let o=Number(e);if(Number.isInteger(o)&&o>=1&&o<=re.length)return re[o-1];let n=re[0];return r.warn(`Invalid choice: ${i}, defaulting to ${uo(n)}`),n}function uo(i){switch(i){case"vercel":return"Vercel Firewall";case"cloudflare":return"Cloudflare WAF";case"fastly":return"Fastly Next-Gen WAF";default:return i}}async function J(i,e){try{i.debug&&(r.level=go.LogLevels.debug);let t;if(i.optionalConfig)try{t=await j(i.config,"optional")}catch{t={}}else i.skipValidation?t=await j(i.config,"raw"):t=await j(i.config,"required");let{provider:o,vercelCredentials:n}=await fo({provider:i.provider,config:t,interactive:!i.ci,token:i.token,projectId:i.projectId,teamId:i.teamId,apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId,workspaceId:i.workspaceId}),s=n?.token??"",a=n?.projectId??"",l=n?.teamId??"";await e({config:t,provider:o,token:s,projectId:a,teamId:l})}catch(t){Z(t,i.errorContext)}}var lr="backup",cr="Backup or restore firewall configurations",dr={config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:Y,projectId:{alias:"p",type:"string",description:"Vercel Project ID (can be set in config file)"},teamId:{alias:"t",type:"string",description:"Vercel Team ID (can be set in config file)"},token:{type:"string",description:"Vercel API token (defaults to VERCEL_TOKEN env var)"},apiToken:{type:"string",description:"Cloudflare API token (defaults to CLOUDFLARE_API_TOKEN env var)"},zoneId:{type:"string",description:"Cloudflare Zone ID (defaults to CLOUDFLARE_ZONE_ID env var)"},accountId:{type:"string",description:"Cloudflare Account ID (optional)"},workspaceId:{type:"string",description:"Fastly Next-Gen WAF Workspace ID (defaults to FASTLY_WORKSPACE_ID env var)"},output:{alias:"o",type:"string",description:"Output directory for backups",default:"./backups"},restore:{alias:"r",type:"string",description:"Restore from backup file"},list:{alias:"l",type:"boolean",description:"List available backups",default:!1},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}},ur=async i=>{try{i.debug&&(r.level=mo.LogLevels.debug);let e=i.output||"./backups";if(i.list){if(!(0,ge.existsSync)(e)){r.info(Q.default.yellow("No backup directory found."));return}let t=(0,ge.readdirSync)(e).filter(o=>o.endsWith(".json")).map(o=>{let n=(0,At.join)(e,o),s=(0,ge.statSync)(n);return{name:o,size:s.size,created:s.mtime}}).sort((o,n)=>n.created.getTime()-o.created.getTime());if(t.length===0){r.info(Q.default.yellow("No backups found."));return}r.log(Q.default.bold(`
39
39
  \u{1F4E6} Available Backups:
40
- `)),t.forEach(o=>{r.log(`${Q.default.cyan(o.name)}`),r.log(` ${Q.default.dim("Created:")} ${o.created.toLocaleString()}`),r.log(` ${Q.default.dim("Size:")} ${(o.size/1024).toFixed(1)} KB`),r.log("")});return}if(i.restore){let t=i.restore.startsWith("/")?i.restore:(0,At.join)(e,i.restore);(0,ge.existsSync)(t)||(r.error(`Backup file not found: ${t}`),process.exit(1));let o=await j(t,"raw"),n=i.config||".doorman.json";if((0,ge.existsSync)(n)&&!await w(`Config file ${n} already exists. Do you want to overwrite it?`,{type:"confirm"})){r.info(Q.default.yellow("Restore cancelled."));return}let{backup:s,...a}=o;await q(a,n),r.success(Q.default.green(`\u2705 Restored configuration from ${t} to ${n}`));return}await J({config:i.config,provider:i.provider,projectId:i.projectId,teamId:i.teamId,token:i.token,apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId,workspaceId:i.workspaceId,debug:i.debug,ci:i.ci,errorContext:"creating backup"},async({provider:t,projectId:o,teamId:n})=>{r.start("Fetching current remote configuration...");let s=await t.fetchConfig(),a=s.metadata?.version??s.version;Re.getInstance().validateConfig(s),(0,ge.existsSync)(e)||(0,ge.mkdirSync)(e,{recursive:!0});let c=new Date,d=c.toISOString().split("T")[0]??"unknown-date",u=(c.toISOString().split("T")[1]??"").split(".")[0]?.replace(/:/g,"-")??"unknown-time",g=`firewall-backup-${`${d}_${u}`}.json`,h=(0,At.join)(e,g),I={...s,backup:{createdAt:new Date().toISOString(),source:"remote",provider:t.name,...t.name==="vercel"?{projectId:o,teamId:n}:{},originalVersion:a}};await q(I,h,{validate:!1}),r.success(Q.default.green(`\u2705 Backup created: ${h}`)),r.log(""),r.log(Q.default.bold("Backup Details:")),r.log(`${Q.default.dim("Version:")} ${a??"unknown"}`),r.log(`${Q.default.dim("Rules:")} ${s.rules.length} custom, ${s.ips?.length??0} IP blocking`),r.log(`${Q.default.dim("Created:")} ${new Date().toLocaleString()}`),r.log(""),r.log(Q.default.dim("To restore this backup later, run:")),r.log(Q.default.cyan(`doorman backup --restore ${g}`))})}catch(e){Z(e,"managing backup")}};var Xt={};M(Xt,{builder:()=>gr,command:()=>pr,desc:()=>fr,handler:()=>mr});var X=S(require("chalk"));T();function le(i){if(we(i))return i;let e=i,t=e.rules.map(n=>k.vercelToUnified(n).result),o=(e.ips??[]).map(n=>k.vercelIPToUnified(n));return{version:"2.0",provider:"vercel",providers:{vercel:{projectId:e.projectId,teamId:e.teamId}},rules:t,ips:o,metadata:{version:e.version,updatedAt:e.updatedAt}}}function Pt(i,e){let t=!1,o=we(i),n=o?{...i,metadata:{...i.metadata}}:{...i};if(e.version!==void 0)if(o){let s=n;s.metadata?.version!==e.version&&(s.metadata={...s.metadata,version:e.version,updatedAt:e.updatedAt??new Date().toISOString()},t=!0)}else{let s=n;s.version!==e.version&&(s.version=e.version,s.updatedAt=e.updatedAt??new Date().toISOString(),t=!0)}if(e.idRemappings&&e.idRemappings.length>0){let s=new Map(e.idRemappings.filter(d=>d.oldId).map(d=>[d.oldId,d.newId])),a=new Map(e.idRemappings.filter(d=>!d.oldId).map(d=>[d.name,d.newId])),l=!1,c=n.rules.map(d=>{let u=d.id?s.get(d.id):a.get(d.name);return u&&u!==d.id?(l=!0,{...d,id:u}):d});l&&(n={...n,rules:c},t=!0)}return{config:n,changed:t}}function ho(i,e){if(we(e))return{...e,...i};let t=e,o=i.rules.map(s=>k.unifiedToVercel(s).result),n=(i.ips??[]).map(s=>({id:s.id||"",ip:s.ip,hostname:s.hostname||"",action:s.action,notes:s.notes}));return{...t,projectId:i.providers?.vercel?.projectId??t.projectId,teamId:i.providers?.vercel?.teamId??t.teamId,version:i.metadata?.version,updatedAt:i.metadata?.updatedAt,rules:o,ips:n}}var pr="diff",fr="Show detailed differences between local and remote firewall configuration",gr={config:{alias:"c",type:"string",description:"Path to firewall config file"},provider:Y,projectId:{alias:"p",type:"string",description:"Vercel Project ID"},teamId:{alias:"t",type:"string",description:"Vercel Team ID"},token:{type:"string",description:"Vercel API token (defaults to VERCEL_TOKEN env var)"},apiToken:{type:"string",description:"Cloudflare API token (defaults to CLOUDFLARE_API_TOKEN env var)"},zoneId:{type:"string",description:"Cloudflare Zone ID (defaults to CLOUDFLARE_ZONE_ID env var)"},accountId:{type:"string",description:"Cloudflare Account ID (optional)"},workspaceId:{type:"string",description:"Fastly Next-Gen WAF Workspace ID (defaults to FASTLY_WORKSPACE_ID env var)"},format:{alias:"f",type:"string",choices:["table","json"],description:"Output format",default:"table"},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}},mr=async i=>{await J({config:i.config,provider:i.provider,projectId:i.projectId,teamId:i.teamId,token:i.token,apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId,workspaceId:i.workspaceId,debug:i.debug,ci:i.ci,errorContext:"calculating diff"},async({config:e,provider:t})=>{await hr(t,le(e),i)})};async function hr(i,e,t){r.start("Calculating differences...");let o=await i.getChanges(e);if(!o.hasChanges){r.success(X.default.green("No differences found. Local and remote configurations are in sync."));return}let n=o.ipsToAdd??[],s=o.ipsToUpdate??[],a=o.ipsToDelete??[];if(t.format==="json"){r.log(JSON.stringify({rules:{toAdd:o.rulesToAdd,toUpdate:o.rulesToUpdate,toDelete:o.rulesToDelete},ips:{toAdd:n,toUpdate:s,toDelete:a},summary:{hasChanges:o.hasChanges,ruleChanges:o.rulesToAdd.length+o.rulesToUpdate.length+o.rulesToDelete.length,ipChanges:n.length+s.length+a.length}},null,2));return}r.log(X.default.bold(`
40
+ `)),t.forEach(o=>{r.log(`${Q.default.cyan(o.name)}`),r.log(` ${Q.default.dim("Created:")} ${o.created.toLocaleString()}`),r.log(` ${Q.default.dim("Size:")} ${(o.size/1024).toFixed(1)} KB`),r.log("")});return}if(i.restore){let t=i.restore.startsWith("/")?i.restore:(0,At.join)(e,i.restore);(0,ge.existsSync)(t)||(r.error(`Backup file not found: ${t}`),process.exit(1));let o=await j(t,"raw"),n=i.config||".doorman.json";if((0,ge.existsSync)(n)&&!await w(`Config file ${n} already exists. Do you want to overwrite it?`,{type:"confirm"})){r.info(Q.default.yellow("Restore cancelled."));return}let{backup:s,...a}=o;await q(a,n),r.success(Q.default.green(`\u2705 Restored configuration from ${t} to ${n}`));return}await J({config:i.config,provider:i.provider,projectId:i.projectId,teamId:i.teamId,token:i.token,apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId,workspaceId:i.workspaceId,debug:i.debug,ci:i.ci,errorContext:"creating backup"},async({provider:t,projectId:o,teamId:n})=>{r.start("Fetching current remote configuration...");let s=await t.fetchConfig(),a=s.metadata?.version??s.version;Re.getInstance().validateConfig(s),(0,ge.existsSync)(e)||(0,ge.mkdirSync)(e,{recursive:!0});let c=new Date,d=c.toISOString().split("T")[0]??"unknown-date",u=(c.toISOString().split("T")[1]??"").split(".")[0]?.replace(/:/g,"-")??"unknown-time",g=`firewall-backup-${`${d}_${u}`}.json`,h=(0,At.join)(e,g),I={...s,backup:{createdAt:new Date().toISOString(),source:"remote",provider:t.name,...t.name==="vercel"?{projectId:o,teamId:n}:{},originalVersion:a}};await q(I,h,{validate:!1}),r.success(Q.default.green(`\u2705 Backup created: ${h}`)),r.log(""),r.log(Q.default.bold("Backup Details:")),r.log(`${Q.default.dim("Version:")} ${a??"unknown"}`),r.log(`${Q.default.dim("Rules:")} ${s.rules.length} custom, ${s.ips?.length??0} IP blocking`),r.log(`${Q.default.dim("Created:")} ${new Date().toLocaleString()}`),r.log(""),r.log(Q.default.dim("To restore this backup later, run:")),r.log(Q.default.cyan(`doorman backup --restore ${g}`))})}catch(e){Z(e,"managing backup")}};var Xt={};M(Xt,{builder:()=>gr,command:()=>pr,desc:()=>fr,handler:()=>mr});var X=S(require("chalk"));T();function le(i){if(we(i))return i;let e=i,t=e.rules.map(n=>k.vercelToUnified(n).result),o=(e.ips??[]).map(n=>k.vercelIPToUnified(n));return{version:"2.0",provider:"vercel",providers:{vercel:{projectId:e.projectId,teamId:e.teamId}},rules:t,ips:o,metadata:{version:e.version,updatedAt:e.updatedAt}}}function Pt(i,e){let t=!1,o=we(i),n=o?{...i,metadata:{...i.metadata}}:{...i};if(e.version!==void 0)if(o){let s=n;s.metadata?.version!==e.version&&(s.metadata={...s.metadata,version:e.version,updatedAt:e.updatedAt??new Date().toISOString()},t=!0)}else{let s=n;s.version!==e.version&&(s.version=e.version,s.updatedAt=e.updatedAt??new Date().toISOString(),t=!0)}if(e.idRemappings&&e.idRemappings.length>0){let s=new Map(e.idRemappings.filter(d=>d.oldId).map(d=>[d.oldId,d.newId])),a=new Map(e.idRemappings.filter(d=>!d.oldId).map(d=>[d.name,d.newId])),l=!1,c=n.rules.map(d=>{let u=d.id?s.get(d.id):a.get(d.name);return u&&u!==d.id?(l=!0,{...d,id:u}):d});l&&(n={...n,rules:c},t=!0)}return{config:n,changed:t}}function ho(i,e){if(we(e))return{...e,...i};let t=e,o=i.rules.map(s=>k.unifiedToVercel(s).result),n=(i.ips??[]).map(s=>({id:s.id||"",ip:s.ip,hostname:s.hostname||"",action:s.action,notes:s.notes}));return{...t,projectId:i.providers?.vercel?.projectId??t.projectId,teamId:i.providers?.vercel?.teamId??t.teamId,version:i.metadata?.version,updatedAt:i.metadata?.updatedAt,rules:o,ips:n}}var pr="diff",fr="Show detailed differences between local and remote firewall configuration",gr={config:{alias:"c",type:"string",description:"Path to firewall config file"},provider:Y,projectId:{alias:"p",type:"string",description:"Vercel Project ID"},teamId:{alias:"t",type:"string",description:"Vercel Team ID"},token:{type:"string",description:"Vercel API token (defaults to VERCEL_TOKEN env var)"},apiToken:{type:"string",description:"Cloudflare API token (defaults to CLOUDFLARE_API_TOKEN env var)"},zoneId:{type:"string",description:"Cloudflare Zone ID (defaults to CLOUDFLARE_ZONE_ID env var)"},accountId:{type:"string",description:"Cloudflare Account ID (optional)"},workspaceId:{type:"string",description:"Fastly Next-Gen WAF Workspace ID (defaults to FASTLY_WORKSPACE_ID env var)"},format:{alias:"f",type:"string",choices:["table","json"],description:"Output format",default:"table"},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}},mr=async i=>{await J({config:i.config,provider:i.provider,projectId:i.projectId,teamId:i.teamId,token:i.token,apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId,workspaceId:i.workspaceId,debug:i.debug,ci:i.ci,errorContext:"calculating diff"},async({config:e,provider:t})=>{await hr(t,le(e),i)})};async function hr(i,e,t){let o=t.format==="json";o||r.start("Calculating differences...");let n=await i.getChanges(e),s=n.ipsToAdd??[],a=n.ipsToUpdate??[],l=n.ipsToDelete??[];if(o){r.log(JSON.stringify({rules:{toAdd:n.rulesToAdd,toUpdate:n.rulesToUpdate,toDelete:n.rulesToDelete},ips:{toAdd:s,toUpdate:a,toDelete:l},summary:{hasChanges:n.hasChanges,ruleChanges:n.rulesToAdd.length+n.rulesToUpdate.length+n.rulesToDelete.length,ipChanges:s.length+a.length+l.length}},null,2));return}if(!n.hasChanges){r.success(X.default.green("No differences found. Local and remote configurations are in sync."));return}r.log(X.default.bold(`
41
41
  \u{1F50D} ${i.name} Configuration Differences
42
- `)),r.log(X.default.bold("Rule Changes:")),r.log(` ${X.default.green("+")} ${o.rulesToAdd.length} to add`),o.rulesToAdd.forEach(c=>r.log(` ${X.default.green("+")} ${c.name}`)),r.log(` ${X.default.cyan("~")} ${o.rulesToUpdate.length} to update`),o.rulesToUpdate.forEach(c=>r.log(` ${X.default.cyan("~")} ${c.name}`)),r.log(` ${X.default.red("-")} ${o.rulesToDelete.length} to delete`),o.rulesToDelete.forEach(c=>r.log(` ${X.default.red("-")} ${c.name}`)),(n.length||s.length||a.length)&&(r.log(""),r.log(X.default.bold("IP Rule Changes:")),r.log(` ${X.default.green("+")} ${n.length} to add`),r.log(` ${X.default.cyan("~")} ${s.length} to update`),r.log(` ${X.default.red("-")} ${a.length} to delete`));let l=o.rulesToAdd.length+o.rulesToUpdate.length+o.rulesToDelete.length+n.length+s.length+a.length;r.log(""),r.log(X.default.bold(`Summary: ${l} total changes detected`)),r.log(X.default.dim("Run `sync` to apply these changes to the remote configuration."))}var ei={};M(ei,{builder:()=>Ir,command:()=>yr,desc:()=>wr,handler:()=>Rr});var Ve=S(require("chalk")),yo=require("zod");T();pe();var yr="download [configVersion]",wr="Download remote Vercel Firewall rules and update local config, optionally for a specific configuration version",Ir={configVersion:{type:"number",description:"Specific configuration version to download (defaults to latest)"},config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:Y,projectId:{alias:"p",type:"string",description:"Vercel Project ID (can be set in config file)"},teamId:{alias:"t",type:"string",description:"Vercel Team ID (can be set in config file)"},token:{type:"string",description:"Vercel API token (defaults to VERCEL_TOKEN env var)"},apiToken:{type:"string",description:"Cloudflare API token (defaults to CLOUDFLARE_API_TOKEN env var)"},zoneId:{type:"string",description:"Cloudflare Zone ID (defaults to CLOUDFLARE_ZONE_ID env var)"},accountId:{type:"string",description:"Cloudflare Account ID (optional)"},workspaceId:{type:"string",description:"Fastly Next-Gen WAF Workspace ID (defaults to FASTLY_WORKSPACE_ID env var)"},dryRun:{alias:"d",type:"boolean",description:"Show rules that would be downloaded without making changes",default:!1},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}},Rr=async i=>{await J({config:i.config,provider:i.provider,projectId:i.projectId,teamId:i.teamId,token:i.token,apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId,workspaceId:i.workspaceId,debug:i.debug,ci:i.ci,optionalConfig:!0,skipValidation:!0,errorContext:"downloading firewall rules"},async({config:e,provider:t})=>{if(i.configVersion!==void 0)try{ft.parse(i.configVersion)}catch(o){throw o instanceof yo.z.ZodError&&(r.error("Invalid configuration version number. Version must be a positive integer."),process.exit(1)),o}await Cr(t,e,i)})};async function Cr(i,e,t){r.start(`Fetching remote firewall configuration${t.configVersion?` version ${t.configVersion}`:""} ...`);let o=await i.fetchConfig(t.configVersion),n=o.rules,s=o.ips??[];if(n.length>0?(r.log(Ve.default.bold(`
42
+ `)),r.log(X.default.bold("Rule Changes:")),r.log(` ${X.default.green("+")} ${n.rulesToAdd.length} to add`),n.rulesToAdd.forEach(d=>r.log(` ${X.default.green("+")} ${d.name}`)),r.log(` ${X.default.cyan("~")} ${n.rulesToUpdate.length} to update`),n.rulesToUpdate.forEach(d=>r.log(` ${X.default.cyan("~")} ${d.name}`)),r.log(` ${X.default.red("-")} ${n.rulesToDelete.length} to delete`),n.rulesToDelete.forEach(d=>r.log(` ${X.default.red("-")} ${d.name}`)),(s.length||a.length||l.length)&&(r.log(""),r.log(X.default.bold("IP Rule Changes:")),r.log(` ${X.default.green("+")} ${s.length} to add`),r.log(` ${X.default.cyan("~")} ${a.length} to update`),r.log(` ${X.default.red("-")} ${l.length} to delete`));let c=n.rulesToAdd.length+n.rulesToUpdate.length+n.rulesToDelete.length+s.length+a.length+l.length;r.log(""),r.log(X.default.bold(`Summary: ${c} total changes detected`)),r.log(X.default.dim("Run `sync` to apply these changes to the remote configuration."))}var ei={};M(ei,{builder:()=>Ir,command:()=>yr,desc:()=>wr,handler:()=>Rr});var Ve=S(require("chalk")),yo=require("zod");T();pe();var yr="download [configVersion]",wr="Download remote Vercel Firewall rules and update local config, optionally for a specific configuration version",Ir={configVersion:{type:"number",description:"Specific configuration version to download (defaults to latest)"},config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:Y,projectId:{alias:"p",type:"string",description:"Vercel Project ID (can be set in config file)"},teamId:{alias:"t",type:"string",description:"Vercel Team ID (can be set in config file)"},token:{type:"string",description:"Vercel API token (defaults to VERCEL_TOKEN env var)"},apiToken:{type:"string",description:"Cloudflare API token (defaults to CLOUDFLARE_API_TOKEN env var)"},zoneId:{type:"string",description:"Cloudflare Zone ID (defaults to CLOUDFLARE_ZONE_ID env var)"},accountId:{type:"string",description:"Cloudflare Account ID (optional)"},workspaceId:{type:"string",description:"Fastly Next-Gen WAF Workspace ID (defaults to FASTLY_WORKSPACE_ID env var)"},dryRun:{alias:"d",type:"boolean",description:"Show rules that would be downloaded without making changes",default:!1},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}},Rr=async i=>{await J({config:i.config,provider:i.provider,projectId:i.projectId,teamId:i.teamId,token:i.token,apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId,workspaceId:i.workspaceId,debug:i.debug,ci:i.ci,optionalConfig:!0,skipValidation:!0,errorContext:"downloading firewall rules"},async({config:e,provider:t})=>{if(i.configVersion!==void 0)try{ft.parse(i.configVersion)}catch(o){throw o instanceof yo.z.ZodError&&(r.error("Invalid configuration version number. Version must be a positive integer."),process.exit(1)),o}await Cr(t,e,i)})};async function Cr(i,e,t){r.start(`Fetching remote firewall configuration${t.configVersion?` version ${t.configVersion}`:""} ...`);let o=await i.fetchConfig(t.configVersion),n=o.rules,s=o.ips??[];if(n.length>0?(r.log(Ve.default.bold(`
43
43
  Remote Custom Rules to Download (${n.length}):
44
44
  `)),n.forEach(c=>r.log(` - ${c.name}${c.enabled?"":Ve.default.dim(" (disabled)")}`))):r.info("No custom rules to download..."),s.length>0?(r.log(Ve.default.bold(`
45
45
  Remote IP Blocking Rules to Download (${s.length}):
@@ -94,24 +94,24 @@ Remote IP Blocking Rules to Download (${s.length}):
94
94
  `,o+=` action = "${n.action}"
95
95
  `,o+=`}
96
96
 
97
- `}),o},kr=async i=>{try{let e;i.source==="remote"?await J({config:i.config,provider:i.provider,projectId:i.projectId,teamId:i.teamId,token:i.token,apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId,workspaceId:i.workspaceId,debug:i.debug,ci:i.ci,errorContext:"exporting configuration"},async({provider:s})=>{r.start("Fetching remote configuration..."),e=await s.fetchConfig()}):e=await j(i.config),r.start(`Exporting configuration in ${i.format} format...`);let t,o;switch(i.format){case"json":t=JSON.stringify(e,null,2),o="json";break;case"yaml":{let s=le(e);t=`# Firewall Configuration (${s.provider??"unknown"})
98
- `,t+=`version: ${s.metadata?.version??"unknown"}
99
- `,t+=`updatedAt: "${s.metadata?.updatedAt??""}"
100
- `,t+=`rules:
101
- `,s.rules.forEach(a=>{t+=` - name: "${a.name}"
102
- `,t+=` id: "${a.id??""}"
103
- `,t+=` enabled: ${a.enabled}
104
- `,t+=` action: "${a.action.type}"
105
- `}),o="yaml";break}case"terraform":t=Pr(le(e)),o="tf";break;case"markdown":t=Ar(le(e)),o="md";break;default:throw new Error(`Unsupported format: ${i.format}`)}let n=i.output||`firewall-export.${o}`;i.output==="-"||!i.output?r.log(t):((0,wo.writeFileSync)(n,t,"utf8"),r.success(Be.default.green(`\u2705 Exported to ${n}`)),r.log(""),r.log(Be.default.bold("Export Summary:")),r.log(`${Be.default.dim("Format:")} ${i.format}`),r.log(`${Be.default.dim("Source:")} ${i.source}`),r.log(`${Be.default.dim("Rules:")} ${e.rules.length} custom, ${(e.ips||[]).length} IP blocking`),r.log(`${Be.default.dim("Size:")} ${(t.length/1024).toFixed(1)} KB`))}catch(e){Z(e,"exporting configuration")}};var ii={};M(ii,{BASIC_TEMPLATE_RULES:()=>Ro,SECURITY_FOCUSED_TEMPLATE_RULES:()=>Co,builder:()=>Sr,command:()=>$r,desc:()=>_r,handler:()=>xr});var y=S(require("chalk")),Io=require("fs");T();pe();var Ro=[{id:"rule_block_bad_bots",name:"Block Bad Bots",description:"Block known malicious bots and crawlers based on user agent patterns",conditionGroup:[{conditions:[{type:"user_agent",op:"sub",value:"bot"}]}],action:{mitigate:{action:"deny"}},active:!1},{id:"rule_block_scrapers",name:"Block Scrapers",description:"Block common scraping tools and automated requests",conditionGroup:[{conditions:[{type:"user_agent",op:"sub",value:"scraper"}]}],action:{mitigate:{action:"deny"}},active:!1}],Co=[{id:"rule_rate_limit_api",name:"Rate Limit API",description:"Rate limit API endpoints to prevent abuse and DoS attacks",conditionGroup:[{conditions:[{type:"path",op:"pre",value:"/api/"}]}],action:{mitigate:{action:"rate_limit",rateLimit:{requests:100,window:"1m"}}},active:!1},{id:"rule_block_suspicious_ips",name:"Block Suspicious IPs",description:"Block requests from a known suspicious IP range",conditionGroup:[{conditions:[{type:"ip_address",op:"eq",value:"10.0.0.0/24"}]}],action:{mitigate:{action:"deny"}},active:!1},{id:"rule_protect_admin",name:"Protect Admin Routes",description:"Add extra protection for admin and sensitive routes",conditionGroup:[{conditions:[{type:"path",op:"pre",value:"/admin"}]}],action:{mitigate:{action:"rate_limit",rateLimit:{requests:10,window:"1m"}}},active:!1}],$r="init [template]",_r="Initialize a new Doorman configuration",Sr={template:{type:"string",description:"Template to use for initialization (empty, basic, or security-focused)",choices:["empty","basic","security-focused"],default:"empty"},config:{alias:"c",type:"string",description:"Path for the new config file",default:".doorman.json"},force:{alias:"f",type:"boolean",description:"Overwrite existing config file",default:!1},interactive:{alias:"i",type:"boolean",description:"Interactive setup with guided prompts",default:!0},projectId:{alias:"p",type:"string",description:"Vercel Project ID"},teamId:{alias:"t",type:"string",description:"Vercel Team ID"}},Lr=()=>{r.log(""),r.log(y.default.bold.cyan("\u{1F6AA} Welcome to Doorman!")),r.log(y.default.dim(`Let's set up your firewall configuration.
97
+ `}),o},kr=async i=>{try{let e=i.output==="-"||!i.output,t;i.source==="remote"?await J({config:i.config,provider:i.provider,projectId:i.projectId,teamId:i.teamId,token:i.token,apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId,workspaceId:i.workspaceId,debug:i.debug,ci:i.ci,errorContext:"exporting configuration"},async({provider:a})=>{e||r.start("Fetching remote configuration..."),t=await a.fetchConfig()}):t=await j(i.config),e||r.start(`Exporting configuration in ${i.format} format...`);let o,n;switch(i.format){case"json":o=JSON.stringify(t,null,2),n="json";break;case"yaml":{let a=le(t);o=`# Firewall Configuration (${a.provider??"unknown"})
98
+ `,o+=`version: ${a.metadata?.version??"unknown"}
99
+ `,o+=`updatedAt: "${a.metadata?.updatedAt??""}"
100
+ `,o+=`rules:
101
+ `,a.rules.forEach(l=>{o+=` - name: "${l.name}"
102
+ `,o+=` id: "${l.id??""}"
103
+ `,o+=` enabled: ${l.enabled}
104
+ `,o+=` action: "${l.action.type}"
105
+ `}),n="yaml";break}case"terraform":o=Pr(le(t)),n="tf";break;case"markdown":o=Ar(le(t)),n="md";break;default:throw new Error(`Unsupported format: ${i.format}`)}let s=i.output||`firewall-export.${n}`;e?r.log(o):((0,wo.writeFileSync)(s,o,"utf8"),r.success(Be.default.green(`\u2705 Exported to ${s}`)),r.log(""),r.log(Be.default.bold("Export Summary:")),r.log(`${Be.default.dim("Format:")} ${i.format}`),r.log(`${Be.default.dim("Source:")} ${i.source}`),r.log(`${Be.default.dim("Rules:")} ${t.rules.length} custom, ${(t.ips||[]).length} IP blocking`),r.log(`${Be.default.dim("Size:")} ${(o.length/1024).toFixed(1)} KB`))}catch(e){Z(e,"exporting configuration")}};var ii={};M(ii,{BASIC_TEMPLATE_RULES:()=>Ro,SECURITY_FOCUSED_TEMPLATE_RULES:()=>Co,builder:()=>Sr,command:()=>$r,desc:()=>_r,handler:()=>xr});var y=S(require("chalk")),Io=require("fs");T();pe();var Ro=[{id:"rule_block_bad_bots",name:"Block Bad Bots",description:"Block known malicious bots and crawlers based on user agent patterns",conditionGroup:[{conditions:[{type:"user_agent",op:"sub",value:"bot"}]}],action:{mitigate:{action:"deny"}},active:!1},{id:"rule_block_scrapers",name:"Block Scrapers",description:"Block common scraping tools and automated requests",conditionGroup:[{conditions:[{type:"user_agent",op:"sub",value:"scraper"}]}],action:{mitigate:{action:"deny"}},active:!1}],Co=[{id:"rule_rate_limit_api",name:"Rate Limit API",description:"Rate limit API endpoints to prevent abuse and DoS attacks",conditionGroup:[{conditions:[{type:"path",op:"pre",value:"/api/"}]}],action:{mitigate:{action:"rate_limit",rateLimit:{requests:100,window:"1m"}}},active:!1},{id:"rule_block_suspicious_ips",name:"Block Suspicious IPs",description:"Block requests from a known suspicious IP range",conditionGroup:[{conditions:[{type:"ip_address",op:"eq",value:"10.0.0.0/24"}]}],action:{mitigate:{action:"deny"}},active:!1},{id:"rule_protect_admin",name:"Protect Admin Routes",description:"Add extra protection for admin and sensitive routes",conditionGroup:[{conditions:[{type:"path",op:"pre",value:"/admin"}]}],action:{mitigate:{action:"rate_limit",rateLimit:{requests:10,window:"1m"}}},active:!1}],$r="init [template]",_r="Initialize a new Doorman configuration",Sr={template:{type:"string",description:"Template to use for initialization (empty, basic, or security-focused)",choices:["empty","basic","security-focused"],default:"empty"},config:{alias:"c",type:"string",description:"Path for the new config file",default:".doorman.json"},force:{alias:"f",type:"boolean",description:"Overwrite existing config file",default:!1},interactive:{alias:"i",type:"boolean",description:"Interactive setup with guided prompts",default:!0},projectId:{alias:"p",type:"string",description:"Vercel Project ID"},teamId:{alias:"t",type:"string",description:"Vercel Team ID"}},Lr=()=>{r.log(""),r.log(y.default.bold.cyan("\u{1F6AA} Welcome to Doorman!")),r.log(y.default.dim(`Let's set up your firewall configuration.
106
106
  `))},Ur=()=>{r.log(y.default.bold(`
107
107
  \u{1F4DA} Helpful Resources:`)),r.log(""),r.log(y.default.cyan("\u{1F517} Find your Project ID:")),r.log(y.default.dim(" https://vercel.com/dashboard \u2192 Select your project \u2192 Settings \u2192 General")),r.log(""),r.log(y.default.cyan("\u{1F517} Find your Team ID:")),r.log(y.default.dim(" https://vercel.com/dashboard \u2192 Team Settings \u2192 General")),r.log(y.default.dim(" (Leave empty if using personal account)")),r.log(""),r.log(y.default.cyan("\u{1F517} Create API Token:")),r.log(y.default.dim(" https://vercel.com/account/tokens \u2192 Create Token")),r.log(y.default.dim(" Scopes needed: Read & Write for your project")),r.log(""),r.log(y.default.cyan("\u{1F517} Documentation:")),r.log(y.default.dim(" https://doorman.griffen.codes/getting-started")),r.log("")},Dr=async i=>{let e=i.projectId,t=i.teamId;return i.interactive&&(r.log(y.default.bold("\u{1F4CB} Project Configuration")),r.log(y.default.dim(`We need your Vercel project details to configure the firewall.
108
108
  `)),e||(e=await w("Enter your Vercel Project ID:",{type:"text"})),t||await w("Are you using a Vercel team account?",{type:"confirm"})&&(t=await w("Enter your Vercel Team ID:",{type:"text"})),process.env.VERCEL_TOKEN?r.log(y.default.green("\u2705 VERCEL_TOKEN environment variable found")):(r.log(""),r.log(y.default.yellow("\u26A0\uFE0F VERCEL_TOKEN environment variable not found")),r.log(y.default.dim("You'll need to set this before running sync commands.")),await w("Would you like to see how to create an API token?",{type:"confirm"})&&(r.log(""),r.log(y.default.bold("\u{1F511} Creating a Vercel API Token:")),r.log("1. Go to https://vercel.com/account/tokens"),r.log('2. Click "Create Token"'),r.log('3. Give it a descriptive name (e.g., "Doorman Firewall")'),r.log("4. Set expiration as needed"),r.log("5. Copy the token and set it as an environment variable:"),r.log(""),r.log(y.default.cyan(' export VERCEL_TOKEN="your-token-here"')),r.log(y.default.dim(" # Add this to your ~/.bashrc, ~/.zshrc, or .env file")),r.log("")))),{projectId:e,teamId:t}},xr=async i=>{try{let e=i.config||".doorman.json";if(i.interactive&&Lr(),(0,Io.existsSync)(e)&&!i.force&&!await w(`Config file ${e} already exists. Do you want to overwrite it?`,{type:"confirm"})){r.info(y.default.yellow("Initialization cancelled."));return}let{projectId:t,teamId:o}=await Dr(i);i.interactive&&(r.log(""),r.log(y.default.bold("\u{1F3A8} Template Selection")),r.log(y.default.dim(`Choose a starting template for your firewall configuration:
109
- `)),r.log(y.default.cyan("\u{1F4DD} empty")+y.default.dim(" - Minimal config, add your own rules")),r.log(y.default.cyan("\u{1F6E1}\uFE0F basic")+y.default.dim(" - Includes basic bot protection")),r.log(y.default.cyan("\u{1F512} security-focused")+y.default.dim(" - Comprehensive security rules")),r.log(""));let n=i.template;i.interactive&&!i.template&&(n=await w("Select a template:",{type:"select",options:["empty","basic","security-focused"],initial:"empty"})),r.start(`Creating ${n} configuration...`);let s=ht();switch(t&&(s.projectId=t),o&&(s.teamId=o),n){case"basic":s={...s,rules:Ro};break;case"security-focused":s={...s,rules:Co};break;default:break}await q(s,e),r.success(y.default.green(`\u2705 Created ${e}`)),r.log(""),r.log(y.default.bold("\u{1F4CB} Configuration Summary:")),r.log(`${y.default.dim("File:")} ${e}`),r.log(`${y.default.dim("Template:")} ${n}`),r.log(`${y.default.dim("Project ID:")} ${t||y.default.yellow("Not set - add manually")}`),r.log(`${y.default.dim("Team ID:")} ${o||y.default.dim("None (personal account)")}`),r.log(`${y.default.dim("Rules:")} ${s.rules.length} (all disabled for safety)`),r.log(""),r.log(y.default.bold("\u{1F680} Next Steps:")),t||r.log(`1. ${y.default.yellow("Add your projectId")} to ${e}`),process.env.VERCEL_TOKEN||r.log(`${t?"1":"2"}. ${y.default.yellow("Set VERCEL_TOKEN")} environment variable`);let a=t?process.env.VERCEL_TOKEN?1:2:3;r.log(`${a}. ${y.default.dim("Review and enable rules in")} ${e}`),r.log(`${a+1}. ${y.default.dim("Run")} ${y.default.cyan("doorman validate")} ${y.default.dim("to check your config")}`),r.log(`${a+2}. ${y.default.dim("Run")} ${y.default.cyan("doorman status")} ${y.default.dim("to see sync status")}`),r.log(`${a+3}. ${y.default.dim("Run")} ${y.default.cyan("doorman sync")} ${y.default.dim("to deploy your rules")}`),n!=="empty"&&(r.log(""),r.log(y.default.yellow("\u26A0\uFE0F All template rules are disabled by default for safety.")),r.log(y.default.dim(" Review each rule and set active: true when ready."))),i.interactive&&(!t||!process.env.VERCEL_TOKEN)&&Ur()}catch(e){Z(e,"initializing configuration")}};var oi={};M(oi,{builder:()=>Nr,command:()=>Or,desc:()=>Fr,handler:()=>Vr});var ce=S(require("chalk")),vo=require("zod");T();var Or="list [configVersion]",Fr="List Vercel Firewall rules, optionally for a specific configuration version",Nr={configVersion:{type:"number",description:"Specific configuration version to fetch (defaults to latest)"},provider:Y,projectId:{alias:"p",type:"string",description:"Vercel Project ID (can be set in config file or VERCEL_PROJECT_ID environment variable)"},teamId:{alias:"t",type:"string",description:"Vercel Team ID (can be set in config file or VERCEL_TEAM_ID environment variable)"},token:{type:"string",description:"Vercel API token (defaults to VERCEL_TOKEN env var)"},apiToken:{type:"string",description:"Cloudflare API token (defaults to CLOUDFLARE_API_TOKEN env var)"},zoneId:{type:"string",description:"Cloudflare Zone ID (defaults to CLOUDFLARE_ZONE_ID env var)"},accountId:{type:"string",description:"Cloudflare Account ID (optional)"},workspaceId:{type:"string",description:"Fastly Next-Gen WAF Workspace ID (defaults to FASTLY_WORKSPACE_ID env var)"},format:{alias:"f",type:"string",description:"Output format (json or table)",choices:["json","table"],default:"table"},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}},Vr=async i=>{await J({provider:i.provider,projectId:i.projectId,teamId:i.teamId,token:i.token,apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId,workspaceId:i.workspaceId,debug:i.debug,ci:i.ci,optionalConfig:!0,skipValidation:!0,errorContext:"listing firewall rules"},async({provider:e})=>{if(i.configVersion!==void 0)try{ft.parse(i.configVersion)}catch(t){throw t instanceof vo.z.ZodError&&(r.error("Invalid configuration version number. Version must be a positive integer."),process.exit(1)),t}await Br(e,i)})};async function Br(i,e){r.start(`Fetching firewall configuration${e.configVersion?` version ${e.configVersion}`:""} ...`);let t=await i.fetchConfig(e.configVersion),o=t.rules,n=t.ips??[],s=t.metadata?.version??t.version,a=t.metadata?.updatedAt;if(r.info(`Found ${ce.default.cyan(o.length)} custom rules and ${ce.default.cyan(n.length)} IP blocking rules
110
- `+ce.default.dim(`Version: ${ce.default.yellow(s??"unknown")}${a?` \u2022 Last Updated: ${ce.default.yellow(a)}`:""}`)),e.format==="json"){r.info(JSON.stringify({version:s,updatedAt:a,rules:o,ips:n},null,2));return}if(o.length===0&&n.length===0){r.info(ce.default.yellow("No rules found"));return}o.length>0?(r.log(ce.default.bold.underline(`
109
+ `)),r.log(y.default.cyan("\u{1F4DD} empty")+y.default.dim(" - Minimal config, add your own rules")),r.log(y.default.cyan("\u{1F6E1}\uFE0F basic")+y.default.dim(" - Includes basic bot protection")),r.log(y.default.cyan("\u{1F512} security-focused")+y.default.dim(" - Comprehensive security rules")),r.log(""));let n=i.template;i.interactive&&!i.template&&(n=await w("Select a template:",{type:"select",options:["empty","basic","security-focused"],initial:"empty"})),r.start(`Creating ${n} configuration...`);let s=ht();switch(t&&(s.projectId=t),o&&(s.teamId=o),n){case"basic":s={...s,rules:Ro};break;case"security-focused":s={...s,rules:Co};break;default:break}await q(s,e),r.success(y.default.green(`\u2705 Created ${e}`)),r.log(""),r.log(y.default.bold("\u{1F4CB} Configuration Summary:")),r.log(`${y.default.dim("File:")} ${e}`),r.log(`${y.default.dim("Template:")} ${n}`),r.log(`${y.default.dim("Project ID:")} ${t||y.default.yellow("Not set - add manually")}`),r.log(`${y.default.dim("Team ID:")} ${o||y.default.dim("None (personal account)")}`),r.log(`${y.default.dim("Rules:")} ${s.rules.length} (all disabled for safety)`),r.log(""),r.log(y.default.bold("\u{1F680} Next Steps:")),t||r.log(`1. ${y.default.yellow("Add your projectId")} to ${e}`),process.env.VERCEL_TOKEN||r.log(`${t?"1":"2"}. ${y.default.yellow("Set VERCEL_TOKEN")} environment variable`);let a=t?process.env.VERCEL_TOKEN?1:2:3;r.log(`${a}. ${y.default.dim("Review and enable rules in")} ${e}`),r.log(`${a+1}. ${y.default.dim("Run")} ${y.default.cyan("doorman validate")} ${y.default.dim("to check your config")}`),r.log(`${a+2}. ${y.default.dim("Run")} ${y.default.cyan("doorman status")} ${y.default.dim("to see sync status")}`),r.log(`${a+3}. ${y.default.dim("Run")} ${y.default.cyan("doorman sync")} ${y.default.dim("to deploy your rules")}`),n!=="empty"&&(r.log(""),r.log(y.default.yellow("\u26A0\uFE0F All template rules are disabled by default for safety.")),r.log(y.default.dim(" Review each rule and set active: true when ready."))),i.interactive&&(!t||!process.env.VERCEL_TOKEN)&&Ur()}catch(e){Z(e,"initializing configuration")}};var oi={};M(oi,{builder:()=>Nr,command:()=>Or,desc:()=>Fr,handler:()=>Vr});var ce=S(require("chalk")),vo=require("zod");T();var Or="list [configVersion]",Fr="List Vercel Firewall rules, optionally for a specific configuration version",Nr={configVersion:{type:"number",description:"Specific configuration version to fetch (defaults to latest)"},provider:Y,projectId:{alias:"p",type:"string",description:"Vercel Project ID (can be set in config file or VERCEL_PROJECT_ID environment variable)"},teamId:{alias:"t",type:"string",description:"Vercel Team ID (can be set in config file or VERCEL_TEAM_ID environment variable)"},token:{type:"string",description:"Vercel API token (defaults to VERCEL_TOKEN env var)"},apiToken:{type:"string",description:"Cloudflare API token (defaults to CLOUDFLARE_API_TOKEN env var)"},zoneId:{type:"string",description:"Cloudflare Zone ID (defaults to CLOUDFLARE_ZONE_ID env var)"},accountId:{type:"string",description:"Cloudflare Account ID (optional)"},workspaceId:{type:"string",description:"Fastly Next-Gen WAF Workspace ID (defaults to FASTLY_WORKSPACE_ID env var)"},format:{alias:"f",type:"string",description:"Output format (json or table)",choices:["json","table"],default:"table"},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}},Vr=async i=>{await J({provider:i.provider,projectId:i.projectId,teamId:i.teamId,token:i.token,apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId,workspaceId:i.workspaceId,debug:i.debug,ci:i.ci,optionalConfig:!0,skipValidation:!0,errorContext:"listing firewall rules"},async({provider:e})=>{if(i.configVersion!==void 0)try{ft.parse(i.configVersion)}catch(t){throw t instanceof vo.z.ZodError&&(r.error("Invalid configuration version number. Version must be a positive integer."),process.exit(1)),t}await Br(e,i)})};async function Br(i,e){let t=e.format==="json";t||r.start(`Fetching firewall configuration${e.configVersion?` version ${e.configVersion}`:""} ...`);let o=await i.fetchConfig(e.configVersion),n=o.rules,s=o.ips??[],a=o.metadata?.version??o.version,l=o.metadata?.updatedAt;if(t){r.log(JSON.stringify({version:a,updatedAt:l,rules:n,ips:s},null,2));return}if(r.info(`Found ${ce.default.cyan(n.length)} custom rules and ${ce.default.cyan(s.length)} IP blocking rules
110
+ `+ce.default.dim(`Version: ${ce.default.yellow(a??"unknown")}${l?` \u2022 Last Updated: ${ce.default.yellow(l)}`:""}`)),n.length===0&&s.length===0){r.info(ce.default.yellow("No rules found"));return}n.length>0?(r.log(ce.default.bold.underline(`
111
111
  Custom Rules:`),`
112
- `),o.forEach(l=>r.log(` - ${l.name}${l.enabled?"":ce.default.dim(" (disabled)")}`))):r.info(ce.default.cyan("No custom rules found")),n.length>0?(r.log(ce.default.bold.underline(`
112
+ `),n.forEach(c=>r.log(` - ${c.name}${c.enabled?"":ce.default.dim(" (disabled)")}`))):r.info(ce.default.cyan("No custom rules found")),s.length>0?(r.log(ce.default.bold.underline(`
113
113
  IP Blocking Rules:`),`
114
- `),n.forEach(l=>r.log(` - ${l.ip} (${l.action})`))):r.info(ce.default.cyan("No IP blocking rules found"))}var ri={};M(ri,{aliases:()=>jr,builder:()=>zr,command:()=>Wr,desc:()=>qr,findFuzzyMatches:()=>Po,handler:()=>Mr,levenshtein:()=>Ao});var U=S(require("chalk")),To=require("consola");T();pe();var Wr="remove [type]",jr=["rm","delete"],qr="Remove a firewall rule or IP entry from your configuration",zr={type:{type:"string",description:'What to remove: "rule" (default) or "ip"',default:"rule",choices:["rule","ip"]},interactive:{alias:"i",type:"boolean",description:"Select rules to remove from a list",default:!1},name:{alias:"n",type:"string",description:"Remove rule by name (exact match)"},id:{type:"array",string:!0,description:"Remove rule by ID (supports multiple)"},ip:{type:"string",description:"Remove IP rule by IP address"},disabled:{type:"boolean",description:"Remove all disabled (active: false) rules",default:!1},all:{type:"boolean",description:"Remove all rules (requires confirmation)",default:!1},force:{alias:"f",type:"boolean",description:"Skip confirmation prompt",default:!1},config:{alias:"c",type:"string",description:"Config file path"},dryRun:{alias:"d",type:"boolean",description:"Show what would be removed without writing",default:!1},debug:{type:"boolean",description:"Enable debug logging",default:!1}};function Ao(i,e){let t=i.length,o=e.length;if(t===0)return o;if(o===0)return t;let n=Array.from({length:t+1},(a,l)=>l),s=new Array(t+1).fill(0);for(let a=1;a<=o;a++){s[0]=a;for(let l=1;l<=t;l++)e.charAt(a-1)===i.charAt(l-1)?s[l]=n[l-1]:s[l]=Math.min(n[l-1]+1,s[l-1]+1,n[l]+1);for(let l=0;l<=t;l++)n[l]=s[l]}return n[t]}function Po(i,e,t=3){let o=i.toLowerCase(),n=e.filter(l=>l.name.toLowerCase().includes(o)||o.includes(l.name.toLowerCase()));if(n.length>0)return n.slice(0,t);let s=e.map(l=>({rule:l,distance:Ao(o,l.name.toLowerCase())})).sort((l,c)=>l.distance-c.distance),a=Math.max(i.length*.6,3);return s.filter(l=>l.distance<=a).slice(0,t).map(l=>l.rule)}function ni(i){for(let e of i){let t=e.id?` (${e.id})`:"",o=e.active?"":U.default.dim(" [disabled]"),n=e.action.mitigate.action;r.log(U.default.dim(` - ${e.name}${t} \u2014 ${n}${o}`))}}function bo(i){for(let e of i){let t=e.notes?` \u2014 ${e.notes}`:"";r.log(U.default.dim(` - ${e.ip}${t}`))}}async function Eo(i,e,t,o="rule"){if(e)return!0;if(t)return await w(U.default.red(`\u26A0\uFE0F Are you sure you want to remove ALL ${o}s? Type "YES" to confirm:`),{type:"text"})==="YES";let n=i===1?`Confirm removal of 1 ${o}?`:`Confirm removal of ${i} ${o}s?`;return await w(n,{type:"confirm",initial:!1})}var Mr=async i=>{try{i.debug&&(r.level=To.LogLevels.debug),r.debug("Remove command arguments:",i);let e=i.type||"rule";r.start("Loading configuration...");let t=await j(i.config);if(e==="ip"){let o=t.ips||[];if(o.length===0){r.info("No IP rules in configuration.");return}let n=[];if(i.interactive){let l=o.map(d=>{let u=d.notes?` \u2014 ${d.notes}`:"";return`${d.ip}${u}`}),c=await w("Select IP rules to remove:",{type:"multiselect",options:l});n=o.filter(d=>{let u=`${d.ip}${d.notes?` \u2014 ${d.notes}`:""}`;return c.includes(u)})}else i.ip?(n=o.filter(l=>l.ip===i.ip),n.length===0&&(r.error(U.default.red(`No IP rule found matching "${i.ip}"`)),o.length>0&&(r.log(U.default.dim(`
114
+ `),s.forEach(c=>r.log(` - ${c.ip} (${c.action})`))):r.info(ce.default.cyan("No IP blocking rules found"))}var ri={};M(ri,{aliases:()=>jr,builder:()=>zr,command:()=>Wr,desc:()=>qr,findFuzzyMatches:()=>Po,handler:()=>Mr,levenshtein:()=>Ao});var U=S(require("chalk")),To=require("consola");T();pe();var Wr="remove [type]",jr=["rm","delete"],qr="Remove a firewall rule or IP entry from your configuration",zr={type:{type:"string",description:'What to remove: "rule" (default) or "ip"',default:"rule",choices:["rule","ip"]},interactive:{alias:"i",type:"boolean",description:"Select rules to remove from a list",default:!1},name:{alias:"n",type:"string",description:"Remove rule by name (exact match)"},id:{type:"array",string:!0,description:"Remove rule by ID (supports multiple)"},ip:{type:"string",description:"Remove IP rule by IP address"},disabled:{type:"boolean",description:"Remove all disabled (active: false) rules",default:!1},all:{type:"boolean",description:"Remove all rules (requires confirmation)",default:!1},force:{alias:"f",type:"boolean",description:"Skip confirmation prompt",default:!1},config:{alias:"c",type:"string",description:"Config file path"},dryRun:{alias:"d",type:"boolean",description:"Show what would be removed without writing",default:!1},debug:{type:"boolean",description:"Enable debug logging",default:!1}};function Ao(i,e){let t=i.length,o=e.length;if(t===0)return o;if(o===0)return t;let n=Array.from({length:t+1},(a,l)=>l),s=new Array(t+1).fill(0);for(let a=1;a<=o;a++){s[0]=a;for(let l=1;l<=t;l++)e.charAt(a-1)===i.charAt(l-1)?s[l]=n[l-1]:s[l]=Math.min(n[l-1]+1,s[l-1]+1,n[l]+1);for(let l=0;l<=t;l++)n[l]=s[l]}return n[t]}function Po(i,e,t=3){let o=i.toLowerCase(),n=e.filter(l=>l.name.toLowerCase().includes(o)||o.includes(l.name.toLowerCase()));if(n.length>0)return n.slice(0,t);let s=e.map(l=>({rule:l,distance:Ao(o,l.name.toLowerCase())})).sort((l,c)=>l.distance-c.distance),a=Math.max(i.length*.6,3);return s.filter(l=>l.distance<=a).slice(0,t).map(l=>l.rule)}function ni(i){for(let e of i){let t=e.id?` (${e.id})`:"",o=e.active?"":U.default.dim(" [disabled]"),n=e.action.mitigate.action;r.log(U.default.dim(` - ${e.name}${t} \u2014 ${n}${o}`))}}function bo(i){for(let e of i){let t=e.notes?` \u2014 ${e.notes}`:"";r.log(U.default.dim(` - ${e.ip}${t}`))}}async function Eo(i,e,t,o="rule"){if(e)return!0;if(t)return await w(U.default.red(`\u26A0\uFE0F Are you sure you want to remove ALL ${o}s? Type "YES" to confirm:`),{type:"text"})==="YES";let n=i===1?`Confirm removal of 1 ${o}?`:`Confirm removal of ${i} ${o}s?`;return await w(n,{type:"confirm",initial:!1})}var Mr=async i=>{try{i.debug&&(r.level=To.LogLevels.debug),r.debug("Remove command arguments:",i);let e=i.type||"rule";r.start("Loading configuration...");let t=await j(i.config);if(e==="ip"){let o=t.ips||[];if(o.length===0){r.info("No IP rules in configuration.");return}let n=[];if(i.interactive){let l=o.map(d=>{let u=d.notes?` \u2014 ${d.notes}`:"";return`${d.ip}${u}`}),c=await w("Select IP rules to remove:",{type:"multiselect",options:l});n=o.filter(d=>{let u=`${d.ip}${d.notes?` \u2014 ${d.notes}`:""}`;return c.includes(u)})}else i.ip?(n=o.filter(l=>l.ip===i.ip),n.length===0&&(r.error(U.default.red(`No IP rule found matching "${i.ip}"`)),o.length>0&&(r.log(U.default.dim(`
115
115
  Current IP rules:`)),bo(o)),process.exit(1))):i.all?n=[...o]:(r.error("Specify --ip, --interactive, or --all to select IP rules to remove."),process.exit(1));if(n.length===0){r.info("No IP rules selected for removal.");return}if(r.log(U.default.cyan(`
116
116
  ${i.dryRun?"Would remove":"Removing"} ${n.length} IP rule(s):`)),bo(n),i.dryRun){r.log(U.default.dim(`
117
117
  Dry run \u2014 no changes made.`));return}if(!await Eo(n.length,i.force||!1,i.all||!1,"IP rule")){r.info("Cancelled.");return}let s=new Set(n.map(l=>l.ip)),a={...t,ips:o.filter(l=>!s.has(l.ip))};await q(a,i.config),r.success(U.default.green(`\u2714 Removed ${n.length} IP rule(s) from configuration`))}else{let o=t.rules||[];if(o.length===0){r.info("No rules in configuration.");return}let n=[];if(i.interactive){let l=o.map((d,u)=>{let p=d.id?` (${d.id})`:"",g=d.active?"":" [disabled]",h=d.action.mitigate.action;return`[${u}] ${d.name}${p} \u2014 ${h}${g}`}),c=await w("Select rules to remove:",{type:"multiselect",options:l});n=o.filter((d,u)=>c.some(p=>p.startsWith(`[${u}]`)))}else if(i.name){if(n=o.filter(l=>l.name===i.name),n.length>1&&(r.warn(U.default.yellow(`
@@ -122,11 +122,11 @@ Use --id for exact matching, or --interactive to select from a list.`))),process
122
122
  ${i.dryRun?"Would remove":"Removing"} ${n.length} rule(s):`)),ni(n),i.dryRun){r.log(U.default.dim(`
123
123
  Dry run \u2014 no changes made.`));return}if(!await Eo(n.length,i.force||!1,i.all||!1)){r.info("Cancelled.");return}let s=new Set(n),a={...t,rules:o.filter(l=>!s.has(l))};a.rules.length===0&&r.warn(U.default.yellow("\u26A0\uFE0F Configuration will have no rules after removal.")),await q(a,i.config),r.success(U.default.green(`\u2714 Removed ${n.length} rule(s) from configuration`)),ni(n)}r.log(""),r.log(U.default.dim(`Run ${U.default.cyan("doorman sync")} to deploy these changes.`))}catch(e){Z(e,"removing rule")}};var si={};M(si,{builder:()=>Hr,command:()=>Gr,desc:()=>Kr,handler:()=>Zr});var P=S(require("chalk"));T();var Gr="setup",Kr="Show setup instructions and helpful links for Doorman",Hr={},Zr=async i=>{r.log(""),r.log(P.default.bold.cyan("\u{1F6AA} Doorman Setup Guide")),r.log(P.default.dim(`Everything you need to get started with firewall management
124
124
  `)),r.log(P.default.bold("\u{1F4CB} Prerequisites")),r.log("\u2022 Vercel account with a deployed project"),r.log("\u2022 Project with Pro plan or higher (firewall feature requirement)"),r.log(`\u2022 Admin access to the project/team
125
- `),r.log(P.default.bold("\u{1F50D} Finding Your Project Information")),r.log(""),r.log(P.default.cyan("1. Project ID:")),r.log(" \u2022 Go to https://vercel.com/dashboard"),r.log(" \u2022 Select your project"),r.log(" \u2022 Navigate to Settings \u2192 General"),r.log(" \u2022 Copy the Project ID from the top of the page"),r.log(""),r.log(P.default.cyan("2. Team ID (if using team account):")),r.log(" \u2022 Go to https://vercel.com/dashboard"),r.log(" \u2022 Click on your team name in the top-left"),r.log(" \u2022 Go to Team Settings \u2192 General"),r.log(" \u2022 Copy the Team ID"),r.log(" \u2022 Leave empty to use your Vercel default team (every account has one)"),r.log(" \u2022 Belong to more than one team? Set this explicitly, or doorman will use whichever is your default"),r.log(""),r.log(P.default.bold("\u{1F511} Creating API Token")),r.log(""),r.log(P.default.cyan("Steps:")),r.log(" 1. Visit https://vercel.com/account/tokens"),r.log(' 2. Click "Create Token"'),r.log(' 3. Name: "Doorman Firewall Management"'),r.log(" 4. Expiration: Set as needed (90 days recommended)"),r.log(" 5. Scope: Select your project or team"),r.log(" 6. Copy the generated token"),r.log(""),r.log(P.default.cyan("Required Permissions:")),r.log(" \u2022 Read access to project settings"),r.log(" \u2022 Write access to firewall configuration"),r.log(""),r.log(P.default.bold("\u{1F30D} Environment Setup")),r.log(""),r.log(P.default.cyan("Set your API token as environment variable:")),r.log(""),r.log(P.default.green("# For current session:")),r.log('export VERCEL_TOKEN="your-token-here"'),r.log(""),r.log(P.default.green("# For permanent setup (add to ~/.bashrc or ~/.zshrc):")),r.log(`echo 'export VERCEL_TOKEN="your-token-here"' >> ~/.bashrc`),r.log(""),r.log(P.default.green("# Or create a .env file in your project:")),r.log('echo "VERCEL_TOKEN=your-token-here" > .env'),r.log(""),r.log(P.default.bold("\u{1F680} Quick Start Commands")),r.log(""),r.log(P.default.cyan("1. Initialize configuration:")),r.log(" doorman init --interactive"),r.log(""),r.log(P.default.cyan("2. Validate setup:")),r.log(" doorman validate"),r.log(""),r.log(P.default.cyan("3. Check current status:")),r.log(" doorman status"),r.log(""),r.log(P.default.cyan("4. View existing rules:")),r.log(" doorman list"),r.log(""),r.log(P.default.cyan("5. Sync your configuration:")),r.log(" doorman sync"),r.log(""),r.log(P.default.bold("\u{1F527} Common Issues")),r.log(""),r.log(P.default.yellow('\u274C "Project not found" error:')),r.log(" \u2022 Double-check your Project ID"),r.log(" \u2022 Ensure your token has access to the project"),r.log(" \u2022 Verify the project has Pro plan or higher"),r.log(""),r.log(P.default.yellow('\u274C "Unauthorized" error:')),r.log(" \u2022 Check your VERCEL_TOKEN is set correctly"),r.log(" \u2022 Ensure token hasn't expired"),r.log(" \u2022 Verify token has firewall permissions"),r.log(""),r.log(P.default.yellow('\u274C "Firewall not available" error:')),r.log(" \u2022 Upgrade to Pro plan or higher"),r.log(" \u2022 Contact Vercel support if issue persists"),r.log(""),r.log(P.default.bold("\u{1F4DA} Additional Resources")),r.log(""),r.log(P.default.cyan("Documentation:")),r.log(" \u2022 Doorman Docs: https://doorman.griffen.codes"),r.log(" \u2022 Vercel Firewall: https://vercel.com/docs/security/vercel-firewall"),r.log(" \u2022 API Reference: https://vercel.com/docs/rest-api/endpoints/firewall"),r.log(""),r.log(P.default.cyan("Support:")),r.log(" \u2022 GitHub Issues: https://github.com/gfargo/doorman/issues"),r.log(" \u2022 Vercel Support: https://vercel.com/help"),r.log(""),r.log(P.default.bold("\u{1F512} Security Best Practices")),r.log(""),r.log("\u2022 Store API tokens securely (use environment variables)"),r.log("\u2022 Set token expiration dates"),r.log("\u2022 Use principle of least privilege for token scopes"),r.log("\u2022 Regularly rotate API tokens"),r.log("\u2022 Test rules in staging before production"),r.log("\u2022 Keep backups of working configurations"),r.log(""),r.log(P.default.bold.green("\u2705 Ready to get started?")),r.log(P.default.dim("Run: ")+P.default.cyan("doorman init --interactive")),r.log("")};var ai={};M(ai,{builder:()=>Qr,command:()=>Yr,desc:()=>Jr,handler:()=>Xr});var L=S(require("chalk"));T();var Yr="status",Jr="Show sync status between local and remote firewall configuration",Qr={config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:Y,projectId:{alias:"p",type:"string",description:"Vercel Project ID (can be set in config file)"},teamId:{alias:"t",type:"string",description:"Vercel Team ID (can be set in config file)"},token:{type:"string",description:"Vercel API token (defaults to VERCEL_TOKEN env var)"},apiToken:{type:"string",description:"Cloudflare API token (defaults to CLOUDFLARE_API_TOKEN env var)"},zoneId:{type:"string",description:"Cloudflare Zone ID (defaults to CLOUDFLARE_ZONE_ID env var)"},accountId:{type:"string",description:"Cloudflare Account ID (optional)"},workspaceId:{type:"string",description:"Fastly Next-Gen WAF Workspace ID (defaults to FASTLY_WORKSPACE_ID env var)"},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}},Xr=async i=>{await J({config:i.config,provider:i.provider,projectId:i.projectId,teamId:i.teamId,token:i.token,apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId,workspaceId:i.workspaceId,debug:i.debug,ci:i.ci,errorContext:"checking status"},async({config:e,provider:t})=>{await es(t,le(e))})};async function es(i,e){r.start("Checking sync status...");let t=await i.getChanges(e),o=e.metadata?.version,n=t.version!==void 0&&o!==void 0&&o!==t.version;r.log(L.default.bold(`
125
+ `),r.log(P.default.bold("\u{1F50D} Finding Your Project Information")),r.log(""),r.log(P.default.cyan("1. Project ID:")),r.log(" \u2022 Go to https://vercel.com/dashboard"),r.log(" \u2022 Select your project"),r.log(" \u2022 Navigate to Settings \u2192 General"),r.log(" \u2022 Copy the Project ID from the top of the page"),r.log(""),r.log(P.default.cyan("2. Team ID (if using team account):")),r.log(" \u2022 Go to https://vercel.com/dashboard"),r.log(" \u2022 Click on your team name in the top-left"),r.log(" \u2022 Go to Team Settings \u2192 General"),r.log(" \u2022 Copy the Team ID"),r.log(" \u2022 Leave empty to use your Vercel default team (every account has one)"),r.log(" \u2022 Belong to more than one team? Set this explicitly, or doorman will use whichever is your default"),r.log(""),r.log(P.default.bold("\u{1F511} Creating API Token")),r.log(""),r.log(P.default.cyan("Steps:")),r.log(" 1. Visit https://vercel.com/account/tokens"),r.log(' 2. Click "Create Token"'),r.log(' 3. Name: "Doorman Firewall Management"'),r.log(" 4. Expiration: Set as needed (90 days recommended)"),r.log(" 5. Scope: Select your project or team"),r.log(" 6. Copy the generated token"),r.log(""),r.log(P.default.cyan("Required Permissions:")),r.log(" \u2022 Read access to project settings"),r.log(" \u2022 Write access to firewall configuration"),r.log(""),r.log(P.default.bold("\u{1F30D} Environment Setup")),r.log(""),r.log(P.default.cyan("Set your API token as environment variable:")),r.log(""),r.log(P.default.green("# For current session:")),r.log('export VERCEL_TOKEN="your-token-here"'),r.log(""),r.log(P.default.green("# For permanent setup (add to ~/.bashrc or ~/.zshrc):")),r.log(`echo 'export VERCEL_TOKEN="your-token-here"' >> ~/.bashrc`),r.log(""),r.log(P.default.green("# Or create a .env file in your project:")),r.log('echo "VERCEL_TOKEN=your-token-here" > .env'),r.log(""),r.log(P.default.bold("\u{1F680} Quick Start Commands")),r.log(""),r.log(P.default.cyan("1. Initialize configuration:")),r.log(" doorman init --interactive"),r.log(""),r.log(P.default.cyan("2. Validate setup:")),r.log(" doorman validate"),r.log(""),r.log(P.default.cyan("3. Check current status:")),r.log(" doorman status"),r.log(""),r.log(P.default.cyan("4. View existing rules:")),r.log(" doorman list"),r.log(""),r.log(P.default.cyan("5. Sync your configuration:")),r.log(" doorman sync"),r.log(""),r.log(P.default.bold("\u{1F527} Common Issues")),r.log(""),r.log(P.default.yellow('\u274C "Project not found" error:')),r.log(" \u2022 Double-check your Project ID"),r.log(" \u2022 Ensure your token has access to the project"),r.log(" \u2022 Verify the project has Pro plan or higher"),r.log(""),r.log(P.default.yellow('\u274C "Unauthorized" error:')),r.log(" \u2022 Check your VERCEL_TOKEN is set correctly"),r.log(" \u2022 Ensure token hasn't expired"),r.log(" \u2022 Verify token has firewall permissions"),r.log(""),r.log(P.default.yellow('\u274C "Firewall not available" error:')),r.log(" \u2022 Upgrade to Pro plan or higher"),r.log(" \u2022 Contact Vercel support if issue persists"),r.log(""),r.log(P.default.bold("\u{1F4DA} Additional Resources")),r.log(""),r.log(P.default.cyan("Documentation:")),r.log(" \u2022 Doorman Docs: https://doorman.griffen.codes"),r.log(" \u2022 Vercel Firewall: https://vercel.com/docs/security/vercel-firewall"),r.log(" \u2022 API Reference: https://vercel.com/docs/rest-api/endpoints/firewall"),r.log(""),r.log(P.default.cyan("Support:")),r.log(" \u2022 GitHub Issues: https://github.com/gfargo/doorman/issues"),r.log(" \u2022 Vercel Support: https://vercel.com/help"),r.log(""),r.log(P.default.bold("\u{1F512} Security Best Practices")),r.log(""),r.log("\u2022 Store API tokens securely (use environment variables)"),r.log("\u2022 Set token expiration dates"),r.log("\u2022 Use principle of least privilege for token scopes"),r.log("\u2022 Regularly rotate API tokens"),r.log("\u2022 Test rules in staging before production"),r.log("\u2022 Keep backups of working configurations"),r.log(""),r.log(P.default.bold.green("\u2705 Ready to get started?")),r.log(P.default.dim("Run: ")+P.default.cyan("doorman init --interactive")),r.log("")};var ai={};M(ai,{builder:()=>Qr,command:()=>Yr,desc:()=>Jr,handler:()=>Xr});var L=S(require("chalk"));T();var Yr="status",Jr="Show sync status between local and remote firewall configuration",Qr={config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:Y,projectId:{alias:"p",type:"string",description:"Vercel Project ID (can be set in config file)"},teamId:{alias:"t",type:"string",description:"Vercel Team ID (can be set in config file)"},token:{type:"string",description:"Vercel API token (defaults to VERCEL_TOKEN env var)"},apiToken:{type:"string",description:"Cloudflare API token (defaults to CLOUDFLARE_API_TOKEN env var)"},zoneId:{type:"string",description:"Cloudflare Zone ID (defaults to CLOUDFLARE_ZONE_ID env var)"},accountId:{type:"string",description:"Cloudflare Account ID (optional)"},workspaceId:{type:"string",description:"Fastly Next-Gen WAF Workspace ID (defaults to FASTLY_WORKSPACE_ID env var)"},format:{alias:"f",type:"string",choices:["table","json"],description:"Output format",default:"table"},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}},Xr=async i=>{await J({config:i.config,provider:i.provider,projectId:i.projectId,teamId:i.teamId,token:i.token,apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId,workspaceId:i.workspaceId,debug:i.debug,ci:i.ci,errorContext:"checking status"},async({config:e,provider:t})=>{await es(t,le(e),i)})};async function es(i,e,t){let o=t.format==="json";o||r.start("Checking sync status...");let n=await i.getChanges(e),s=i.getHealthScore(e),a=e.metadata?.version,l=n.version!==void 0&&a!==void 0&&a!==n.version,c=!n.hasChanges&&!l;if(o){r.log(JSON.stringify({provider:i.name,inSync:c,version:{local:a??null,remote:n.version??null,matches:n.version!==void 0?!l:null},rules:{toAdd:n.rulesToAdd.length,toUpdate:n.rulesToUpdate.length,toDelete:n.rulesToDelete.length},ips:{toAdd:n.ipsToAdd?.length??0,toUpdate:n.ipsToUpdate?.length??0,toDelete:n.ipsToDelete?.length??0},health:s},null,2));return}r.log(L.default.bold(`
126
126
  \u{1F4CA} ${i.name} Sync Status Summary
127
- `)),t.version!==void 0&&(r.log(`${L.default.dim("Local Version:")} ${L.default.yellow(o??"unknown")}`),r.log(`${L.default.dim("Remote Version:")} ${L.default.yellow(t.version)}`),r.log(`${L.default.dim("Version Status:")} ${n?L.default.red("Out of sync"):L.default.green("In sync")}`),r.log("")),r.log(`${L.default.dim("Rules:")}`),r.log(` ${L.default.green("+")} ${t.rulesToAdd.length} to add`),r.log(` ${L.default.cyan("~")} ${t.rulesToUpdate.length} to update`),r.log(` ${L.default.red("-")} ${t.rulesToDelete.length} to delete`),r.log(`${L.default.dim("IPs:")}`),r.log(` ${L.default.green("+")} ${t.ipsToAdd?.length??0} to add`),r.log(` ${L.default.cyan("~")} ${t.ipsToUpdate?.length??0} to update`),r.log(` ${L.default.red("-")} ${t.ipsToDelete?.length??0} to delete`),r.log(""),!t.hasChanges&&!n?r.success(L.default.green("\u2705 Everything is in sync!")):r.warn(L.default.yellow("\u26A0\uFE0F Changes detected. Run `sync` to apply changes.")),r.log(`
128
- `+L.default.bold("\u{1F3E5} Configuration Health Check"));let s=i.getHealthScore(e);r.log(`${L.default.dim("Score:")} ${s.score}/100 (${s.grade})`),s.issues.forEach(a=>{let l=a.severity==="error"?L.default.red("\u2717"):a.severity==="warning"?L.default.yellow("\u26A0"):L.default.dim("\u2139");r.log(` ${l} [${a.category}] ${a.message}`),a.suggestion&&r.log(` ${L.default.dim(a.suggestion)}`)}),s.recommendations.length>0&&(r.log(L.default.dim(`
129
- Recommendations:`)),s.recommendations.forEach(a=>r.log(` - ${a}`)))}var di={};M(di,{builder:()=>os,command:()=>ts,desc:()=>is,handler:()=>ns});var z=S(require("chalk"));T();var ts="sync",is="Sync Vercel Firewall rules with config file",os={config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:Y,projectId:{alias:"p",type:"string",description:"Vercel Project ID (can be set in config file)"},teamId:{alias:"t",type:"string",description:"Vercel Team ID (can be set in config file)"},token:{type:"string",description:"Vercel API token (defaults to VERCEL_TOKEN env var)"},apiToken:{type:"string",description:"Cloudflare API token (defaults to CLOUDFLARE_API_TOKEN env var)"},zoneId:{type:"string",description:"Cloudflare Zone ID (defaults to CLOUDFLARE_ZONE_ID env var)"},accountId:{type:"string",description:"Cloudflare Account ID (optional)"},workspaceId:{type:"string",description:"Fastly Next-Gen WAF Workspace ID (defaults to FASTLY_WORKSPACE_ID env var)"},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1},allowDeletions:{type:"boolean",description:"Required together with --ci to let a non-interactive sync delete existing rules/IPs. Without it, a --ci sync that would delete anything is refused rather than applied silently.",default:!1}},ns=async i=>{await J({config:i.config,provider:i.provider,projectId:i.projectId,teamId:i.teamId,token:i.token,apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId,workspaceId:i.workspaceId,debug:i.debug,ci:i.ci,errorContext:"syncing firewall rules"},async e=>{await rs(e.provider,e.config,i)})};async function rs(i,e,t){let o=le(e);r.start(z.default.magenta(`Calculating ${i.name} firewall configuration changes...`));let n=await i.getChanges(o),s=o.metadata?.version,a=n.version!==void 0&&s!==n.version;if(!n.hasChanges&&!a){r.success(z.default.green("No changes detected. Firewall rules are in sync."));return}r.log(z.default.bold(`
127
+ `)),n.version!==void 0&&(r.log(`${L.default.dim("Local Version:")} ${L.default.yellow(a??"unknown")}`),r.log(`${L.default.dim("Remote Version:")} ${L.default.yellow(n.version)}`),r.log(`${L.default.dim("Version Status:")} ${l?L.default.red("Out of sync"):L.default.green("In sync")}`),r.log("")),r.log(`${L.default.dim("Rules:")}`),r.log(` ${L.default.green("+")} ${n.rulesToAdd.length} to add`),r.log(` ${L.default.cyan("~")} ${n.rulesToUpdate.length} to update`),r.log(` ${L.default.red("-")} ${n.rulesToDelete.length} to delete`),r.log(`${L.default.dim("IPs:")}`),r.log(` ${L.default.green("+")} ${n.ipsToAdd?.length??0} to add`),r.log(` ${L.default.cyan("~")} ${n.ipsToUpdate?.length??0} to update`),r.log(` ${L.default.red("-")} ${n.ipsToDelete?.length??0} to delete`),r.log(""),c?r.success(L.default.green("\u2705 Everything is in sync!")):r.warn(L.default.yellow("\u26A0\uFE0F Changes detected. Run `sync` to apply changes.")),r.log(`
128
+ `+L.default.bold("\u{1F3E5} Configuration Health Check")),r.log(`${L.default.dim("Score:")} ${s.score}/100 (${s.grade})`),s.issues.forEach(d=>{let u=d.severity==="error"?L.default.red("\u2717"):d.severity==="warning"?L.default.yellow("\u26A0"):L.default.dim("\u2139");r.log(` ${u} [${d.category}] ${d.message}`),d.suggestion&&r.log(` ${L.default.dim(d.suggestion)}`)}),s.recommendations.length>0&&(r.log(L.default.dim(`
129
+ Recommendations:`)),s.recommendations.forEach(d=>r.log(` - ${d}`)))}var di={};M(di,{builder:()=>os,command:()=>ts,desc:()=>is,handler:()=>ns});var z=S(require("chalk"));T();var ts="sync",is="Sync Vercel Firewall rules with config file",os={config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:Y,projectId:{alias:"p",type:"string",description:"Vercel Project ID (can be set in config file)"},teamId:{alias:"t",type:"string",description:"Vercel Team ID (can be set in config file)"},token:{type:"string",description:"Vercel API token (defaults to VERCEL_TOKEN env var)"},apiToken:{type:"string",description:"Cloudflare API token (defaults to CLOUDFLARE_API_TOKEN env var)"},zoneId:{type:"string",description:"Cloudflare Zone ID (defaults to CLOUDFLARE_ZONE_ID env var)"},accountId:{type:"string",description:"Cloudflare Account ID (optional)"},workspaceId:{type:"string",description:"Fastly Next-Gen WAF Workspace ID (defaults to FASTLY_WORKSPACE_ID env var)"},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1},allowDeletions:{type:"boolean",description:"Required together with --ci to let a non-interactive sync delete existing rules/IPs. Without it, a --ci sync that would delete anything is refused rather than applied silently.",default:!1}},ns=async i=>{await J({config:i.config,provider:i.provider,projectId:i.projectId,teamId:i.teamId,token:i.token,apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId,workspaceId:i.workspaceId,debug:i.debug,ci:i.ci,errorContext:"syncing firewall rules"},async e=>{await rs(e.provider,e.config,i)})};async function rs(i,e,t){let o=le(e);r.start(z.default.magenta(`Calculating ${i.name} firewall configuration changes...`));let n=await i.getChanges(o),s=o.metadata?.version,a=n.version!==void 0&&s!==n.version;if(!n.hasChanges&&!a){r.success(z.default.green("No changes detected. Firewall rules are in sync."));return}r.log(z.default.bold(`
130
130
  Proposed Changes:
131
131
  `)),li("Rules to add",n.rulesToAdd,z.default.green("+")),li("Rules to update",n.rulesToUpdate,z.default.cyan("~")),li("Rules to delete",n.rulesToDelete,z.default.red("-"));let l=n.ipsToAdd??[],c=n.ipsToUpdate??[],d=n.ipsToDelete??[];(l.length||c.length||d.length)&&(ci("IPs to add",l,z.default.green("+")),ci("IPs to update",c,z.default.cyan("~")),ci("IPs to delete",d,z.default.red("-"))),a&&r.log(` Remote config version: ${z.default.red(s??"unknown")} ${z.default.dim("->")} ${z.default.green(n.version)}`),r.start("Starting firewall rules sync...");let u=await i.syncRules(o,{force:t.ci,allowDeletions:t.allowDeletions});if(!u.success)throw new Error(`Sync failed: ${u.errors?.join(", ")||"unknown error"}`);r.success(z.default.green("Firewall rules sync completed successfully")),r.log(` Rules: ${u.rulesAdded} added, ${u.rulesUpdated} updated, ${u.rulesDeleted} deleted`),(u.ipsAdded||u.ipsUpdated||u.ipsDeleted)&&r.log(` IPs: ${u.ipsAdded??0} added, ${u.ipsUpdated??0} updated, ${u.ipsDeleted??0} deleted`),u.warnings?.forEach(g=>r.warn(g)),u.idRemappings?.length&&(r.info(z.default.yellow("Rule IDs were reassigned by the provider and written back to the local config:")),u.idRemappings.forEach(g=>{r.log(` - ${g.name}: ${z.default.red(g.oldId||"none")} ${z.default.dim("->")} ${z.default.green(g.newId)}`)}));let p=Pt(e,u);p.changed&&(await q(p.config,t.config),r.success(z.default.green("Updated local config with new version/metadata")))}function li(i,e,t){e.length!==0&&(r.log(` ${t} ${i} (${e.length}):`),e.forEach(o=>r.log(` - ${o.name}`)))}function ci(i,e,t){e.length!==0&&(r.log(` ${t} ${i} (${e.length}):`),e.forEach(o=>r.log(` - ${o.ip}${o.hostname?` (${o.hostname})`:""}`)))}var pi={};M(pi,{builder:()=>ls,command:()=>ss,desc:()=>as,handler:()=>us});var $e=S(require("chalk")),Uo=require("consola");T();var ko={metadata:{title:"Block AI Bots Firewall Rule",reference:"https://vercel.com/templates/other/block-ai-bots-firewall-rule"},config:{rules:[{id:"rule_detect_ai_bots",name:"Detect AI Bots",description:"",conditionGroup:[{conditions:[{type:"user_agent",op:"re",value:"AI2Bot|Ai2Bot-Dolma|Amazonbot|Applebot|Applebot-Extended|Bytespider|CCBot|ChatGPT-User|Claude-Web|ClaudeBot|Diffbot|FacebookBot|FriendlyCrawler|GPTBot|Google-Extended|GoogleOther|GoogleOther-Image|GoogleOther-Video|ICC-Crawler|ImagesiftBot|Meta-ExternalAgent|Meta-ExternalFetcher|OAI-SearchBot|PerplexityBot|PetalBot|Scrapy|Timpibot|VelenPublicWebCrawler|Webzio-Extended|YouBot|anthropic-ai|cohere-ai|facebookexternalhit|img2dataset|omgili|omgilibot"}]}],action:{mitigate:{action:"log"}},active:!0}]}};var $o={metadata:{title:"Block Bad Bots Firewall Rule",reference:"https://vercel.com/templates/other/block-bad-bots-firewall-rule"},config:{rules:[{id:"rule_detect_bad_bots",name:"Detect Bad Bots",description:"",conditionGroup:[{conditions:[{type:"user_agent",op:"re",value:"01h4x.com|360Spider|404checker|404enemy|80legs|ADmantX|AIBOT|ALittle Client|ASPSeek|Abonti|Aboundex|Aboundexbot|Acunetix|AdsTxtCrawlerTP|AfD-Verbotsverfahren|AhrefsBot|AiHitBot|Aipbot|Alexibot|AllSubmitter|Alligator|AlphaBot|Anarchie|Anarchy|Anarchy99|Ankit|Anthill|Apexoo|Aspiegel|Asterias|Atomseobot|Attach|AwarioBot|AwarioRssBot|AwarioSmartBot|BBBike|BDCbot|BDFetch|BLEXBot|BackDoorBot|BackStreet|BackWeb|Backlink-Ceck|BacklinkCrawler|BacklinksExtendedBot|Badass|Bandit|Barkrowler|BatchFTP|Battleztar Bazinga|BetaBot|Bigfoot|Bitacle|BlackWidow|Black Hole|Blackboard|Blow|BlowFish|Boardreader|Bolt|BotALot|Brandprotect|Brandwatch|Buck|Buddy|BuiltBotTough|BuiltWith|Bullseye|BunnySlippers|BuzzSumo|Bytespider|CATExplorador|CCBot|CODE87|CSHttp|Calculon|CazoodleBot|Cegbfeieh|CensysInspect|ChatGPT-User|CheTeam|CheeseBot|CherryPicker|ChinaClaw|Chlooe|Citoid|Claritybot|ClaudeBot|Cliqzbot|Cloud mapping|Cocolyzebot|Cogentbot|Collector|Copier|CopyRightCheck|Copyscape|Cosmos|Craftbot|Crawling at Home Project|CrazyWebCrawler|Crescent|CrunchBot|Curious|Custo|CyotekWebCopy|DBLBot|DIIbot|DSearch|DTS Agent|DataCha0s|DatabaseDriverMysqli|Demon|Deusu|Devil|Digincore|DigitalPebble|Dirbuster|Disco|Discobot|Discoverybot|Dispatch|DittoSpyder|DnBCrawler-Analytics|DnyzBot|DomCopBot|DomainAppender|DomainCrawler|DomainSigmaCrawler|DomainStatsBot|Domains Project|Dotbot|Download Wonder|Dragonfly|Drip|ECCP/1.0|EMail Siphon|EMail Wolf|EasyDL|Ebingbong|Ecxi|EirGrabber|EroCrawler|Evil|Exabot|Express WebPictures|ExtLinksBot|Extractor|ExtractorPro|Extreme Picture Finder|EyeNetIE|Ezooms|FDM|FHscan|FacebookBot|FemtosearchBot|Fimap|Firefox/7.0|FlashGet|Flunky|Foobot|Freeuploader|FrontPage|Fuzz|FyberSpider|Fyrebot|G-i-g-a-b-o-t|GPTBot|GT::WWW|GalaxyBot|Genieo|GermCrawler|GetRight|GetWeb|Getintent|Gigabot|Go!Zilla|Go-Ahead-Got-It|GoZilla|Gotit|GrabNet|Grabber|Grafula|GrapeFX|GrapeshotCrawler|GridBot|HEADMasterSEO|HMView|HTMLparser|HTTP::Lite|HTTrack|Haansoft|HaosouSpider|Harvest|Havij|Heritrix|Hloader|HonoluluBot|Humanlinks|HybridBot|IDBTE4M|IDBot|IRLbot|Iblog|Id-search|IlseBot|Image Fetch|Image Sucker|ImagesiftBot|IndeedBot|Indy Library|InfoNaviRobot|InfoTekies|Information Security Team InfraSec Scanner|InfraSec Scanner|Intelliseek|InterGET|InternetMeasurement|InternetSeer|Internet Ninja|Iria|Iskanie|IstellaBot|JOC Web Spider|JamesBOT|Jbrofuzz|JennyBot|JetCar|Jetty|JikeSpider|Joomla|Jorgee|JustView|Jyxobot|Kenjin Spider|Keybot Translation-Search-Machine|Keyword Density|Kinza|Kozmosbot|LNSpiderguy|LWP::Simple|Lanshanbot|Larbin|Leap|LeechFTP|LeechGet|LexiBot|Lftp|LibWeb|Libwhisker|LieBaoFast|Lightspeedsystems|Likse|LinkScan|LinkWalker|Linkbot|LinkextractorPro|LinkpadBot|LinksManager|LinqiaMetadataDownloaderBot|LinqiaRSSBot|LinqiaScrapeBot|Lipperhey|Lipperhey Spider|Litemage_walker|Lmspider|Ltx71|MFC_Tear_Sample|MIDown tool|MIIxpc|MJ12bot|MQQBrowser|MSFrontPage|MSIECrawler|MTRobot|Mag-Net|Magnet|Mail.RU_Bot|Majestic-SEO|Majestic12|Majestic SEO|MarkMonitor|MarkWatch|Mass Downloader|Masscan|Mata Hari|MauiBot|Mb2345Browser|MeanPath Bot|Meanpathbot|Mediatoolkitbot|MegaIndex.ru|Metauri|MicroMessenger|Microsoft Data Access|Microsoft URL Control|Minefield|Mister PiX|Moblie Safari|Mojeek|Mojolicious|MolokaiBot|Morfeus Fucking Scanner|Mozlila|Mr.4x3|Msrabot|Musobot|NICErsPRO|NPbot|Name Intelligence|Nameprotect|Navroad|NearSite|Needle|Nessus|NetAnts|NetLyzer|NetMechanic|NetSpider|NetZIP|Net Vampire|Netcraft|Nettrack|Netvibes|NextGenSearchBot|Nibbler|Niki-bot|Nikto|NimbleCrawler|Nimbostratus|Ninja|Nmap|Nuclei|Nutch|Octopus|Offline Explorer|Offline Navigator|OnCrawl|OpenLinkProfiler|OpenVAS|Openfind|Openvas|OrangeBot|OrangeSpider|OutclicksBot|OutfoxBot|PECL::HTTP|PHPCrawl|POE-Component-Client-HTTP|PageAnalyzer|PageGrabber|PageScorer|PageThing.com|Page Analyzer|Pandalytics|Panscient|Papa Foto|Pavuk|PeoplePal|Petalbot|Pi-Monster|Picscout|Picsearch|PictureFinder|Piepmatz|Pimonster|Pixray|PleaseCrawl|Pockey|ProPowerBot|ProWebWalker|Probethenet|Proximic|Psbot|Pu_iN|Pump|PxBroker|PyCurl|QueryN Metasearch|Quick-Crawler|RSSingBot|Rainbot|RankActive|RankActiveLinkBot|RankFlex|RankingBot|RankingBot2|Rankivabot|RankurBot|Re-re|ReGet|RealDownload|Reaper|RebelMouse|Recorder|RedesScrapy|RepoMonkey|Ripper|RocketCrawler|Rogerbot|SBIder|SEOkicks|SEOkicks-Robot|SEOlyt|SEOlyticsCrawler|SEOprofiler|SEOstats|SISTRIX|SMTBot|SalesIntelligent|ScanAlert|Scanbot|ScoutJet|Scrapy|Screaming|ScreenerBot|ScrepyBot|Searchestate|SearchmetricsBot|Seekport|SeekportBot|SemanticJuice|Semrush|SemrushBot|SentiBot|SenutoBot|SeoSiteCheckup|SeobilityBot|Seomoz|Shodan|Siphon|SiteCheckerBotCrawler|SiteExplorer|SiteLockSpider|SiteSnagger|SiteSucker|Site Sucker|Sitebeam|Siteimprove|Sitevigil|SlySearch|SmartDownload|Snake|Snapbot|Snoopy|SocialRankIOBot|Sociscraper|Sogou web spider|Sosospider|Sottopop|SpaceBison|Spammen|SpankBot|Spanner|Spbot|Spider_Bot|Spider_Bot/3.0|Spinn3r|SputnikBot|Sqlmap|Sqlworm|Sqworm|Steeler|Stripper|Sucker|Sucuri|SuperBot|SuperHTTP|Surfbot|SurveyBot|Suzuran|Swiftbot|Szukacz|T0PHackTeam|T8Abot|Teleport|TeleportPro|Telesoft|Telesphoreo|Telesphorep|TheNomad|The Intraformant|Thumbor|TightTwatBot|TinyTestBot|Titan|Toata|Toweyabot|Tracemyfile|Trendiction|Trendictionbot|True_Robot|Turingos|Turnitin|TurnitinBot|TwengaBot|Twice|Typhoeus|URLy.Warning|URLy Warning|UnisterBot|Upflow|V-BOT|VB Project|VCI|Vacuum|Vagabondo|VelenPublicWebCrawler|VeriCiteCrawler|VidibleScraper|Virusdie|VoidEYE|Voil|Voltron|WASALive-Bot|WBSearchBot|WEBDAV|WISENutbot|WPScan|WWW-Collector-E|WWW-Mechanize|WWW::Mechanize|WWWOFFLE|Wallpapers|Wallpapers/3.0|WallpapersHD|WeSEE|WebAuto|WebBandit|WebCollage|WebCopier|WebEnhancer|WebFetch|WebFuck|WebGo IS|WebImageCollector|WebLeacher|WebPix|WebReaper|WebSauger|WebStripper|WebSucker|WebWhacker|WebZIP|Web Auto|Web Collage|Web Enhancer|Web Fetch|Web Fuck|Web Pix|Web Sauger|Web Sucker|Webalta|WebmasterWorldForumBot|Webshag|WebsiteExtractor|WebsiteQuester|Website Quester|Webster|Whack|Whacker|Whatweb|Who.is Bot|Widow|WinHTTrack|WiseGuys Robot|Wonderbot|Woobot|Wotbox|Wprecon|Xaldon WebSpider|Xaldon_WebSpider|Xenu|YaK|YoudaoBot|Zade|Zauba|Zermelo|Zeus|Zitebot|ZmEu|ZoomBot|ZoominfoBot|ZumBot|ZyBorg|adscanner|anthropic-ai|archive.org_bot|arquivo-web-crawler|arquivo.pt|autoemailspider|awario.com|backlink-check|cah.io.community|check1.exe|clark-crawler|coccocbot|cognitiveseo|cohere-ai|com.plumanalytics|crawl.sogou.com|crawler.feedback|crawler4j|dataforseo.com|dataforseobot|demandbase-bot|domainsproject.org|eCatch|evc-batch|facebookscraper|gopher|heritrix|imagesift.com|instabid|internetVista monitor|ips-agent|isitwp.com|iubenda-radar|linkdexbot|linkfluence|lwp-request|lwp-trivial|magpie-crawler|meanpathbot|mediawords|muhstik-scan|netEstate NE Crawler|oBot|omgili|openai|openai.com|page scorer|pcBrowser|plumanalytics|polaris version|probe-image-size|ripz|s1z.ru|satoristudio.net|scalaj-http|scan.lol|seobility|seocompany.store|seoscanners|seostar|serpstatbot|sexsearcher|sitechecker.pro|siteripz|sogouspider|sp_auditbot|spyfu|sysscan|tAkeOut|trendiction.com|trendiction.de|ubermetrics-technologies.com|voyagerx.com|webgains-bot|webmeup-crawler|webpros.com|webprosbot|x09Mozilla|x22Mozilla|xpymep1.exe|zauba.io|zgrab"}]}],action:{mitigate:{action:"log"}},active:!0}]}};var _o={metadata:{title:"Block OFAC-Sanctioned Countries",reference:"https://vercel.com/templates/other/block-ofac-sanctioned-countries-firewall-rule"},config:{rules:[{id:"rule_block_traffic_from_ofac_sanctioned_countries",name:"Block traffic from OFAC-sanctioned countries",description:"Blocks traffic from OFAC-sanctioned countries and enforces a one-hour persistent block after the first violation.",conditionGroup:[{conditions:[{type:"geo_country",op:"inc",value:["SY","IR","RU","CU","KP"]}]}],action:{mitigate:{action:"deny",actionDuration:"1h"}},active:!0}]}};var So={metadata:{title:"Deny Common WordPress URLs Firewall Rule",reference:"https://vercel.com/templates/other/block-wordpress-urls-firewall-rule"},config:{rules:[{name:"Deny WordPress URLs",description:"",conditionGroup:[{conditions:[{type:"path",op:"re",value:"/(wp-admin|wp-login\\.php|xmlrpc\\.php|wp-content|wp-includes|wp-signup\\.php|wp-activate\\.php|register\\.php|wp-register\\.php)"}]}],action:{mitigate:{action:"deny"}},active:!0}]}};var ui={"ai-bots":ko,"bad-bots":$o,"block-ofac-sanctioned-countries":_o,wordpress:So};var Lo=i=>ui[i]?.config;pe();var ss="template [name]",as="Add a firewall rule template to your configuration",ls={name:{type:"string",description:"Name of the template to add"},config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},dryRun:{alias:"d",type:"boolean",description:"Preview changes without applying them",default:!1},debug:{type:"boolean",description:"Enable debug logging",default:!1}};function cs(i,e){let t=new Set(i.map(n=>n.name.toLowerCase())),o=e.filter(n=>t.has(n.name.toLowerCase())).map(n=>n.name);return[...new Set(o)]}var ds=()=>Object.keys(ui),us=async i=>{try{i.debug&&(r.level=Uo.LogLevels.debug),r.debug("Template command arguments:",i);let e=i.name;if(!e){let c=ds();e=await w("Select a template to add:",{type:"select",options:c,initial:c[0]})}let t=Lo(e);t||(r.error(`Template not found: ${e}`),process.exit(1)),r.debug("Template content:",t),r.start("Loading current configuration...");let o=await j(i.config,"raw"),n=o.rules||[],s=cs(n,t.rules);if(i.dryRun){r.info($e.default.cyan(`
132
132
  Dry run - The following rules would be added:`)),r.log(JSON.stringify(t.rules,null,2)),s.length>0&&r.warn($e.default.yellow(`\u26A0\uFE0F Rule name(s) already exist in the configuration: ${s.join(", ")}`));return}if(s.length>0&&(r.warn($e.default.yellow(`\u26A0\uFE0F Rule name(s) already exist in the configuration: ${s.join(", ")}`)),!await w("Proceed anyway?",{type:"confirm",initial:!1}))){r.info("Cancelled.");return}let a={...o,rules:[...n,...t.rules]},l=Te.safeParse(a);l.success||(r.error($e.default.red("The resulting configuration would be invalid:")),l.error.errors.forEach(c=>{let d=c.path.join(".");r.error($e.default.red(` - ${d}: ${c.message}`))}),r.info($e.default.dim("Template was not applied. Fix the issues above and try again.")),process.exit(1)),r.start("Saving updated configuration..."),await q(a,i.config,{validate:!1}),r.success($e.default.green(`Successfully added template '${e}' to configuration`))}catch(e){Z(e,"adding template")}};var fi={};M(fi,{builder:()=>gs,command:()=>ps,desc:()=>fs,handler:()=>ms});var ee=S(require("chalk"));T();var ps="validate",fs="Validate firewall configuration file",gs={config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},verbose:{alias:"v",type:"boolean",description:"Show detailed validation results",default:!1}},ms=async i=>{try{let e=await j(i.config,"raw"),t=Re.getInstance(),o=we(e);i.verbose&&r.start(`Validating configuration file...
package/dist/bin/run.mjs CHANGED
@@ -37,9 +37,9 @@ ${B}`)}),R.result}),{toAdd:l,toUpdate:c,toDelete:d}=this.diffRules(ot(t.rules),a
37
37
  ${l.formatWarning(a)}`)}),o.push(s.result)}return o}async syncRules(t,o={}){let{dryRun:n=!1}=o;try{let{OperationSafety:s}=(ht(),ge(mt)),a=await s.performDryRunValidation(t,"sync rules",async C=>this.getChanges(C));if(!a.valid)throw new Error(`Dry-run validation failed: ${a.issues.join(", ")}`);let l=a.changes,{rulesToAdd:c,rulesToUpdate:d,rulesToDelete:u}=l,p=l.ipsToAdd||[],g=l.ipsToDelete||[];if(n)return r.info("Dry run mode. The following changes would be made:"),r.info(`Custom Rules - Add: ${c.length}, Update: ${d.length}, Delete: ${u.length}`),r.info(`IP Rules - Add: ${p.length}, Delete: ${g.length}`),{success:!0,rulesAdded:0,rulesUpdated:0,rulesDeleted:0,ipsAdded:0,ipsUpdated:0,ipsDeleted:0,warnings:a.warnings};let h=s.assessOperationRisk(l,t);if(!await s.confirmDestructiveOperation({operation:"sync rules",target:`Fastly workspace ${this.client.workspaceId}`,changes:l,riskLevel:h,skipConfirmation:o.force||!1,dryRun:!1,allowDeletions:o.allowDeletions||!1}))throw new Error("Operation cancelled by user");let R=[];gt(t.rules)&&R.push("Rule `priority` has no effect on Fastly: Next-Gen WAF rules are evaluated independently and have no API-exposed ordering concept to honour.");let F=c.map(C=>({rule:C,translated:P.unifiedToFastly(C)})),K=d.map(C=>({rule:C,translated:P.unifiedToFastly(C)})),B=[],ae=(C,N)=>`${C}: ${N instanceof Error?N.message:String(N)}`,pe=[],Ae=[],_=[],we=[];for(let C of u)if(C.id)try{r.debug(`Deleting Fastly rule: ${C.id}`),await this.client.deleteRule(C.id),_.push(C.id)}catch(N){r.error(`Failed to delete Fastly rule ${C.id}:`,N),B.push(ae(`Failed to delete rule ${C.id}`,N))}for(let{rule:C,translated:N}of F){let ee=N.result;try{r.debug(`Adding new Fastly rule: ${ee.description}`);let re=await this.client.createRule(ee);pe.push(re),we.push({oldId:C.id||void 0,newId:re.id,name:ee.description})}catch(re){r.error(`Failed to add Fastly rule "${ee.description}":`,re),B.push(ae(`Failed to add rule "${ee.description}"`,re))}}for(let{rule:C,translated:N}of K)if(C.id)try{r.debug(`Updating Fastly rule: ${C.id}`);let ee=await this.client.updateRule(C.id,N.result);Ae.push(ee)}catch(ee){r.error(`Failed to update Fastly rule ${C.id}:`,ee),B.push(ae(`Failed to update rule ${C.id}`,ee))}let Ie=0,Re=0,xe=t.ips||[],{result:Pe,warnings:Oe}=P.unifiedIPsToFastlyEntries(xe);Oe.forEach(C=>R.push(C.message));try{for(let[C,N]of[[rt,Pe.denyEntries],[st,Pe.allowEntries]]){let re=(await this.client.findList(C))?.entries??[],Qe=N.filter(f=>!re.includes(f)),Xe=re.filter(f=>!N.includes(f));if(Qe.length===0&&Xe.length===0)continue;let Ce=await this.client.getOrCreateList(C);await this.client.updateListEntries(Ce.id,N),Ie+=Qe.length,Re+=Xe.length}}catch(C){r.error("Failed to sync Fastly IP lists:",C),B.push(ae("Failed to sync IP lists",C))}return r.debug(`Custom Rules: Added: ${pe.length}, Updated: ${Ae.length}, Deleted: ${_.length}`),r.debug(`IP Rules: Added: ${Ie}, Deleted: ${Re}`),{success:B.length===0,rulesAdded:pe.length,rulesUpdated:Ae.length,rulesDeleted:_.length,ipsAdded:Ie,ipsUpdated:0,ipsDeleted:Re,idRemappings:we.length>0?we:void 0,errors:B.length>0?B:void 0,warnings:R.length>0?R:void 0}}catch(s){throw r.error("Error during sync:",s),new Error(`Failed to synchronize firewall rules: ${s instanceof Error?s.message:String(s)}`,{cause:s})}}async getChanges(t){let o=ke.safeParse(t);if(!o.success)throw new Error(`Invalid firewall configuration: ${o.error.message}`);try{r.debug("Fetching existing Fastly configuration");let[n,s,a]=await Promise.all([this.client.fetchRules(),this.client.findList(rt),this.client.findList(st)]),l=this.translateFastlyRules(n),{toAdd:c,toUpdate:d,toDelete:u}=this.diffRules(t.rules,l),p=s?.entries??[],g=a?.entries??[],h=[...P.fastlyListEntriesToUnified(p,"deny"),...P.fastlyListEntriesToUnified(g,"allow")],{ipsToAdd:I,ipsToDelete:R}=this.diffIPs(t.ips||[],h);return{rulesToAdd:c,rulesToUpdate:d,rulesToDelete:u,ipsToAdd:I,ipsToUpdate:[],ipsToDelete:R,hasChanges:c.length>0||d.length>0||u.length>0||I.length>0||R.length>0}}catch(n){throw r.error("Error fetching existing Fastly configuration:",n),new Error("Failed to fetch existing Fastly configuration")}}getSupportedFeatures(){return{supportsCustomRules:!0,supportsIPBlocking:!0,supportsRateLimiting:!0,supportsManagedRules:!1,supportsGeoBlocking:!0,supportsRedirect:!0,supportsChallenge:!0}}async verifyCredentials(){return this.client.verifyCredentials()}validateConfig(t){let o=super.validateConfig(t),n=[...o.errors],s=[...o.warnings];return t.provider&&t.provider!=="fastly"&&n.push({path:"provider",message:"Provider must be 'fastly' for FastlyFirewallService",code:"INVALID_PROVIDER"}),{valid:n.length===0,errors:n,warnings:s}}getHealthScore(t){let o=super.getHealthScore(t),n=[...o.issues],s=o.score;return t.rules.filter(l=>l.action?.type==="rate_limit").length===0&&(s-=10,n.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)&&n.push({severity:"info",category:"security",message:"No IP blocking rules configured",suggestion:"Consider blocking known malicious IPs"}),{score:Math.max(0,s),grade:o.grade,issues:n,recommendations:o.recommendations}}diffRules(t,o){let n=[],s=[],a=[...o];for(let l of t){let c=o.find(d=>d.id===l.id);if(!c)n.push(l);else{We(Te(l),Te(c))||s.push({...l,id:c.id});let d=a.findIndex(u=>u.id===c.id);d!==-1&&a.splice(d,1)}}return{toAdd:n,toUpdate:s,toDelete:a}}diffIPs(t,o){let n=l=>`${l.action}:${l.ip}`,s=new Set(o.map(n)),a=new Set(t.map(n));return{ipsToAdd:t.filter(l=>!s.has(n(l))),ipsToDelete:o.filter(l=>!a.has(n(l)))}}};E();var Ze=class{static fromEnv(){let e=process.env.FASTLY_API_TOKEN,t=process.env.FASTLY_WORKSPACE_ID;if(!e)throw new Error("FASTLY_API_TOKEN environment variable is required");if(!t)throw new Error("FASTLY_WORKSPACE_ID environment variable is required");let o=new Ke(t,e);return new He(o)}static fromConfig(e){let t=e.apiToken||process.env.FASTLY_API_TOKEN,o=e.workspaceId||process.env.FASTLY_WORKSPACE_ID;if(!t)throw new Error("Fastly API token is required (provide apiToken or set FASTLY_API_TOKEN env var)");if(!o)throw new Error("Fastly workspace ID is required (provide workspaceId or set FASTLY_WORKSPACE_ID env var)");r.debug("Creating Fastly provider with config:",{workspaceId:o});let n=new Ke(o,t);return new He(n)}static create(e,t){return this.fromConfig({workspaceId:e,apiToken:t})}};var jt={provider:"vercel",fields:[{key:"token",envVar:"VERCEL_TOKEN",label:"Vercel API Token",required:!0,secret:!0},{key:"projectId",envVar:"VERCEL_PROJECT_ID",label:"Vercel Project ID",required:!0,configKey:"projectId"},{key:"teamId",envVar:"VERCEL_TEAM_ID",label:"Vercel Team ID",required:!1,configKey:"teamId"}]};var qt={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 zt={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 wt(i,e={}){let{explicit:t={},config:o={},env:n=process.env}=e,s={};for(let a of i.fields)s[a.key]=t[a.key]||o[a.key]||n[a.envVar];return s}async function Qi(i){let e;if(i.provider)e=i.provider,r.debug(`Using explicitly specified provider: ${e}`);else{let t=ct.detect(i.config);t.provider?(e=t.provider,r.debug(`Auto-detected provider: ${e} (${t.confidence} confidence)`),t.reasons.length>0&&r.debug(`Reasons: ${t.reasons.join(", ")}`)):i.interactive!==!1?e=await er():(e="vercel",r.warn("No provider detected, defaulting to Vercel"))}try{if(e==="vercel")return await Jn(i);if(e==="cloudflare")return{provider:await Qn(i)};if(e==="fastly")return{provider:await Xn(i)};throw new Error(`Unknown provider: ${e}`)}catch(t){throw r.error(`Failed to initialize ${e} provider:`,t),t}}async function Jn(i){let e=i.config,{token:t,projectId:o,teamId:n}=wt(jt,{explicit:{token:i.token,projectId:i.projectId,teamId:i.teamId},config:{projectId:e?.providers?.vercel?.projectId??e?.projectId,teamId:e?.providers?.vercel?.teamId??e?.teamId}});if(!t||!o){if(i.interactive===!1)throw new Error("Vercel credentials missing. Provide token and projectId.");r.warn("Missing Vercel credentials"),r.info("Please provide your Vercel credentials:");let{promptForCredentials:a}=await Promise.resolve().then(()=>(Yi(),Zi)),l=await a({token:t,projectId:o,teamId:n});return{provider:qe.fromConfig({token:l.token,projectId:l.projectId,teamId:l.teamId}),vercelCredentials:l}}return{provider:qe.fromConfig({token:t,projectId:o,teamId:n}),vercelCredentials:{token:t,projectId:o,teamId:n}}}async function Qn(i){let e=i.config,{apiToken:t,zoneId:o,accountId:n}=wt(qt,{explicit:{apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId},config:{zoneId:e?.providers?.cloudflare?.zoneId,accountId:e?.providers?.cloudflare?.accountId}});if(!t||!o){if(i.interactive===!1)throw new Error("Cloudflare credentials missing. Provide apiToken and zoneId.");r.warn("Missing Cloudflare credentials"),r.info("Please provide your Cloudflare credentials:");let s=t||await et("Cloudflare API Token: "),a=o||await w("Cloudflare Zone ID:",{type:"text"}),l=n||await w("Cloudflare Account ID (optional):",{type:"text"});return Ge.fromConfig({apiToken:s,zoneId:a,accountId:l||void 0})}return Ge.fromConfig({apiToken:t,zoneId:o,accountId:n})}async function Xn(i){let e=i.config,{apiToken:t,workspaceId:o}=wt(zt,{explicit:{apiToken:i.apiToken,workspaceId:i.workspaceId},config:{workspaceId:e?.providers?.fastly?.workspaceId}});if(!t||!o){if(i.interactive===!1)throw new Error("Fastly credentials missing. Provide apiToken and workspaceId.");r.warn("Missing Fastly credentials"),r.info("Please provide your Fastly credentials:");let n=t||await et("Fastly API Token: "),s=o||await w("Fastly Next-Gen WAF Workspace ID:",{type:"text"});return Ze.fromConfig({apiToken:n,workspaceId:s})}return Ze.fromConfig({apiToken:t,workspaceId:o})}async function er(){r.info(Yn.yellow("Unable to auto-detect firewall provider.")),r.info(`Please select a provider:
38
38
  `),ie.forEach((s,a)=>{r.info(` ${a+1}. ${Ji(s)}`)}),r.info("");let i=await w(`Select provider (1-${ie.length}):`,{type:"text"}),e=String(i).trim().toLowerCase(),t=ie.find(s=>s===e);if(t)return t;let o=Number(e);if(Number.isInteger(o)&&o>=1&&o<=ie.length)return ie[o-1];let n=ie[0];return r.warn(`Invalid choice: ${i}, defaulting to ${Ji(n)}`),n}function Ji(i){switch(i){case"vercel":return"Vercel Firewall";case"cloudflare":return"Cloudflare WAF";case"fastly":return"Fastly Next-Gen WAF";default:return i}}async function Y(i,e){try{i.debug&&(r.level=tr.debug);let t;if(i.optionalConfig)try{t=await W(i.config,"optional")}catch{t={}}else i.skipValidation?t=await W(i.config,"raw"):t=await W(i.config,"required");let{provider:o,vercelCredentials:n}=await Qi({provider:i.provider,config:t,interactive:!i.ci,token:i.token,projectId:i.projectId,teamId:i.teamId,apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId,workspaceId:i.workspaceId}),s=n?.token??"",a=n?.projectId??"",l=n?.teamId??"";await e({config:t,provider:o,token:s,projectId:a,teamId:l})}catch(t){H(t,i.errorContext)}}var sr="backup",ar="Backup or restore firewall configurations",lr={config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:Z,projectId:{alias:"p",type:"string",description:"Vercel Project ID (can be set in config file)"},teamId:{alias:"t",type:"string",description:"Vercel Team ID (can be set in config file)"},token:{type:"string",description:"Vercel API token (defaults to VERCEL_TOKEN env var)"},apiToken:{type:"string",description:"Cloudflare API token (defaults to CLOUDFLARE_API_TOKEN env var)"},zoneId:{type:"string",description:"Cloudflare Zone ID (defaults to CLOUDFLARE_ZONE_ID env var)"},accountId:{type:"string",description:"Cloudflare Account ID (optional)"},workspaceId:{type:"string",description:"Fastly Next-Gen WAF Workspace ID (defaults to FASTLY_WORKSPACE_ID env var)"},output:{alias:"o",type:"string",description:"Output directory for backups",default:"./backups"},restore:{alias:"r",type:"string",description:"Restore from backup file"},list:{alias:"l",type:"boolean",description:"List available backups",default:!1},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}},cr=async i=>{try{i.debug&&(r.level=rr.debug);let e=i.output||"./backups";if(i.list){if(!It(e)){r.info(J.yellow("No backup directory found."));return}let t=or(e).filter(o=>o.endsWith(".json")).map(o=>{let n=Mt(e,o),s=nr(n);return{name:o,size:s.size,created:s.mtime}}).sort((o,n)=>n.created.getTime()-o.created.getTime());if(t.length===0){r.info(J.yellow("No backups found."));return}r.log(J.bold(`
39
39
  \u{1F4E6} Available Backups:
40
- `)),t.forEach(o=>{r.log(`${J.cyan(o.name)}`),r.log(` ${J.dim("Created:")} ${o.created.toLocaleString()}`),r.log(` ${J.dim("Size:")} ${(o.size/1024).toFixed(1)} KB`),r.log("")});return}if(i.restore){let t=i.restore.startsWith("/")?i.restore:Mt(e,i.restore);It(t)||(r.error(`Backup file not found: ${t}`),process.exit(1));let o=await W(t,"raw"),n=i.config||".doorman.json";if(It(n)&&!await w(`Config file ${n} already exists. Do you want to overwrite it?`,{type:"confirm"})){r.info(J.yellow("Restore cancelled."));return}let{backup:s,...a}=o;await j(a,n),r.success(J.green(`\u2705 Restored configuration from ${t} to ${n}`));return}await Y({config:i.config,provider:i.provider,projectId:i.projectId,teamId:i.teamId,token:i.token,apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId,workspaceId:i.workspaceId,debug:i.debug,ci:i.ci,errorContext:"creating backup"},async({provider:t,projectId:o,teamId:n})=>{r.start("Fetching current remote configuration...");let s=await t.fetchConfig(),a=s.metadata?.version??s.version;ye.getInstance().validateConfig(s),It(e)||ir(e,{recursive:!0});let c=new Date,d=c.toISOString().split("T")[0]??"unknown-date",u=(c.toISOString().split("T")[1]??"").split(".")[0]?.replace(/:/g,"-")??"unknown-time",g=`firewall-backup-${`${d}_${u}`}.json`,h=Mt(e,g),I={...s,backup:{createdAt:new Date().toISOString(),source:"remote",provider:t.name,...t.name==="vercel"?{projectId:o,teamId:n}:{},originalVersion:a}};await j(I,h,{validate:!1}),r.success(J.green(`\u2705 Backup created: ${h}`)),r.log(""),r.log(J.bold("Backup Details:")),r.log(`${J.dim("Version:")} ${a??"unknown"}`),r.log(`${J.dim("Rules:")} ${s.rules.length} custom, ${s.ips?.length??0} IP blocking`),r.log(`${J.dim("Created:")} ${new Date().toLocaleString()}`),r.log(""),r.log(J.dim("To restore this backup later, run:")),r.log(J.cyan(`doorman backup --restore ${g}`))})}catch(e){H(e,"managing backup")}};var Kt={};z(Kt,{builder:()=>pr,command:()=>dr,desc:()=>ur,handler:()=>fr});E();import Q from"chalk";function se(i){if(me(i))return i;let e=i,t=e.rules.map(n=>P.vercelToUnified(n).result),o=(e.ips??[]).map(n=>P.vercelIPToUnified(n));return{version:"2.0",provider:"vercel",providers:{vercel:{projectId:e.projectId,teamId:e.teamId}},rules:t,ips:o,metadata:{version:e.version,updatedAt:e.updatedAt}}}function Rt(i,e){let t=!1,o=me(i),n=o?{...i,metadata:{...i.metadata}}:{...i};if(e.version!==void 0)if(o){let s=n;s.metadata?.version!==e.version&&(s.metadata={...s.metadata,version:e.version,updatedAt:e.updatedAt??new Date().toISOString()},t=!0)}else{let s=n;s.version!==e.version&&(s.version=e.version,s.updatedAt=e.updatedAt??new Date().toISOString(),t=!0)}if(e.idRemappings&&e.idRemappings.length>0){let s=new Map(e.idRemappings.filter(d=>d.oldId).map(d=>[d.oldId,d.newId])),a=new Map(e.idRemappings.filter(d=>!d.oldId).map(d=>[d.name,d.newId])),l=!1,c=n.rules.map(d=>{let u=d.id?s.get(d.id):a.get(d.name);return u&&u!==d.id?(l=!0,{...d,id:u}):d});l&&(n={...n,rules:c},t=!0)}return{config:n,changed:t}}function Xi(i,e){if(me(e))return{...e,...i};let t=e,o=i.rules.map(s=>P.unifiedToVercel(s).result),n=(i.ips??[]).map(s=>({id:s.id||"",ip:s.ip,hostname:s.hostname||"",action:s.action,notes:s.notes}));return{...t,projectId:i.providers?.vercel?.projectId??t.projectId,teamId:i.providers?.vercel?.teamId??t.teamId,version:i.metadata?.version,updatedAt:i.metadata?.updatedAt,rules:o,ips:n}}var dr="diff",ur="Show detailed differences between local and remote firewall configuration",pr={config:{alias:"c",type:"string",description:"Path to firewall config file"},provider:Z,projectId:{alias:"p",type:"string",description:"Vercel Project ID"},teamId:{alias:"t",type:"string",description:"Vercel Team ID"},token:{type:"string",description:"Vercel API token (defaults to VERCEL_TOKEN env var)"},apiToken:{type:"string",description:"Cloudflare API token (defaults to CLOUDFLARE_API_TOKEN env var)"},zoneId:{type:"string",description:"Cloudflare Zone ID (defaults to CLOUDFLARE_ZONE_ID env var)"},accountId:{type:"string",description:"Cloudflare Account ID (optional)"},workspaceId:{type:"string",description:"Fastly Next-Gen WAF Workspace ID (defaults to FASTLY_WORKSPACE_ID env var)"},format:{alias:"f",type:"string",choices:["table","json"],description:"Output format",default:"table"},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}},fr=async i=>{await Y({config:i.config,provider:i.provider,projectId:i.projectId,teamId:i.teamId,token:i.token,apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId,workspaceId:i.workspaceId,debug:i.debug,ci:i.ci,errorContext:"calculating diff"},async({config:e,provider:t})=>{await gr(t,se(e),i)})};async function gr(i,e,t){r.start("Calculating differences...");let o=await i.getChanges(e);if(!o.hasChanges){r.success(Q.green("No differences found. Local and remote configurations are in sync."));return}let n=o.ipsToAdd??[],s=o.ipsToUpdate??[],a=o.ipsToDelete??[];if(t.format==="json"){r.log(JSON.stringify({rules:{toAdd:o.rulesToAdd,toUpdate:o.rulesToUpdate,toDelete:o.rulesToDelete},ips:{toAdd:n,toUpdate:s,toDelete:a},summary:{hasChanges:o.hasChanges,ruleChanges:o.rulesToAdd.length+o.rulesToUpdate.length+o.rulesToDelete.length,ipChanges:n.length+s.length+a.length}},null,2));return}r.log(Q.bold(`
40
+ `)),t.forEach(o=>{r.log(`${J.cyan(o.name)}`),r.log(` ${J.dim("Created:")} ${o.created.toLocaleString()}`),r.log(` ${J.dim("Size:")} ${(o.size/1024).toFixed(1)} KB`),r.log("")});return}if(i.restore){let t=i.restore.startsWith("/")?i.restore:Mt(e,i.restore);It(t)||(r.error(`Backup file not found: ${t}`),process.exit(1));let o=await W(t,"raw"),n=i.config||".doorman.json";if(It(n)&&!await w(`Config file ${n} already exists. Do you want to overwrite it?`,{type:"confirm"})){r.info(J.yellow("Restore cancelled."));return}let{backup:s,...a}=o;await j(a,n),r.success(J.green(`\u2705 Restored configuration from ${t} to ${n}`));return}await Y({config:i.config,provider:i.provider,projectId:i.projectId,teamId:i.teamId,token:i.token,apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId,workspaceId:i.workspaceId,debug:i.debug,ci:i.ci,errorContext:"creating backup"},async({provider:t,projectId:o,teamId:n})=>{r.start("Fetching current remote configuration...");let s=await t.fetchConfig(),a=s.metadata?.version??s.version;ye.getInstance().validateConfig(s),It(e)||ir(e,{recursive:!0});let c=new Date,d=c.toISOString().split("T")[0]??"unknown-date",u=(c.toISOString().split("T")[1]??"").split(".")[0]?.replace(/:/g,"-")??"unknown-time",g=`firewall-backup-${`${d}_${u}`}.json`,h=Mt(e,g),I={...s,backup:{createdAt:new Date().toISOString(),source:"remote",provider:t.name,...t.name==="vercel"?{projectId:o,teamId:n}:{},originalVersion:a}};await j(I,h,{validate:!1}),r.success(J.green(`\u2705 Backup created: ${h}`)),r.log(""),r.log(J.bold("Backup Details:")),r.log(`${J.dim("Version:")} ${a??"unknown"}`),r.log(`${J.dim("Rules:")} ${s.rules.length} custom, ${s.ips?.length??0} IP blocking`),r.log(`${J.dim("Created:")} ${new Date().toLocaleString()}`),r.log(""),r.log(J.dim("To restore this backup later, run:")),r.log(J.cyan(`doorman backup --restore ${g}`))})}catch(e){H(e,"managing backup")}};var Kt={};z(Kt,{builder:()=>pr,command:()=>dr,desc:()=>ur,handler:()=>fr});E();import Q from"chalk";function se(i){if(me(i))return i;let e=i,t=e.rules.map(n=>P.vercelToUnified(n).result),o=(e.ips??[]).map(n=>P.vercelIPToUnified(n));return{version:"2.0",provider:"vercel",providers:{vercel:{projectId:e.projectId,teamId:e.teamId}},rules:t,ips:o,metadata:{version:e.version,updatedAt:e.updatedAt}}}function Rt(i,e){let t=!1,o=me(i),n=o?{...i,metadata:{...i.metadata}}:{...i};if(e.version!==void 0)if(o){let s=n;s.metadata?.version!==e.version&&(s.metadata={...s.metadata,version:e.version,updatedAt:e.updatedAt??new Date().toISOString()},t=!0)}else{let s=n;s.version!==e.version&&(s.version=e.version,s.updatedAt=e.updatedAt??new Date().toISOString(),t=!0)}if(e.idRemappings&&e.idRemappings.length>0){let s=new Map(e.idRemappings.filter(d=>d.oldId).map(d=>[d.oldId,d.newId])),a=new Map(e.idRemappings.filter(d=>!d.oldId).map(d=>[d.name,d.newId])),l=!1,c=n.rules.map(d=>{let u=d.id?s.get(d.id):a.get(d.name);return u&&u!==d.id?(l=!0,{...d,id:u}):d});l&&(n={...n,rules:c},t=!0)}return{config:n,changed:t}}function Xi(i,e){if(me(e))return{...e,...i};let t=e,o=i.rules.map(s=>P.unifiedToVercel(s).result),n=(i.ips??[]).map(s=>({id:s.id||"",ip:s.ip,hostname:s.hostname||"",action:s.action,notes:s.notes}));return{...t,projectId:i.providers?.vercel?.projectId??t.projectId,teamId:i.providers?.vercel?.teamId??t.teamId,version:i.metadata?.version,updatedAt:i.metadata?.updatedAt,rules:o,ips:n}}var dr="diff",ur="Show detailed differences between local and remote firewall configuration",pr={config:{alias:"c",type:"string",description:"Path to firewall config file"},provider:Z,projectId:{alias:"p",type:"string",description:"Vercel Project ID"},teamId:{alias:"t",type:"string",description:"Vercel Team ID"},token:{type:"string",description:"Vercel API token (defaults to VERCEL_TOKEN env var)"},apiToken:{type:"string",description:"Cloudflare API token (defaults to CLOUDFLARE_API_TOKEN env var)"},zoneId:{type:"string",description:"Cloudflare Zone ID (defaults to CLOUDFLARE_ZONE_ID env var)"},accountId:{type:"string",description:"Cloudflare Account ID (optional)"},workspaceId:{type:"string",description:"Fastly Next-Gen WAF Workspace ID (defaults to FASTLY_WORKSPACE_ID env var)"},format:{alias:"f",type:"string",choices:["table","json"],description:"Output format",default:"table"},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}},fr=async i=>{await Y({config:i.config,provider:i.provider,projectId:i.projectId,teamId:i.teamId,token:i.token,apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId,workspaceId:i.workspaceId,debug:i.debug,ci:i.ci,errorContext:"calculating diff"},async({config:e,provider:t})=>{await gr(t,se(e),i)})};async function gr(i,e,t){let o=t.format==="json";o||r.start("Calculating differences...");let n=await i.getChanges(e),s=n.ipsToAdd??[],a=n.ipsToUpdate??[],l=n.ipsToDelete??[];if(o){r.log(JSON.stringify({rules:{toAdd:n.rulesToAdd,toUpdate:n.rulesToUpdate,toDelete:n.rulesToDelete},ips:{toAdd:s,toUpdate:a,toDelete:l},summary:{hasChanges:n.hasChanges,ruleChanges:n.rulesToAdd.length+n.rulesToUpdate.length+n.rulesToDelete.length,ipChanges:s.length+a.length+l.length}},null,2));return}if(!n.hasChanges){r.success(Q.green("No differences found. Local and remote configurations are in sync."));return}r.log(Q.bold(`
41
41
  \u{1F50D} ${i.name} Configuration Differences
42
- `)),r.log(Q.bold("Rule Changes:")),r.log(` ${Q.green("+")} ${o.rulesToAdd.length} to add`),o.rulesToAdd.forEach(c=>r.log(` ${Q.green("+")} ${c.name}`)),r.log(` ${Q.cyan("~")} ${o.rulesToUpdate.length} to update`),o.rulesToUpdate.forEach(c=>r.log(` ${Q.cyan("~")} ${c.name}`)),r.log(` ${Q.red("-")} ${o.rulesToDelete.length} to delete`),o.rulesToDelete.forEach(c=>r.log(` ${Q.red("-")} ${c.name}`)),(n.length||s.length||a.length)&&(r.log(""),r.log(Q.bold("IP Rule Changes:")),r.log(` ${Q.green("+")} ${n.length} to add`),r.log(` ${Q.cyan("~")} ${s.length} to update`),r.log(` ${Q.red("-")} ${a.length} to delete`));let l=o.rulesToAdd.length+o.rulesToUpdate.length+o.rulesToDelete.length+n.length+s.length+a.length;r.log(""),r.log(Q.bold(`Summary: ${l} total changes detected`)),r.log(Q.dim("Run `sync` to apply these changes to the remote configuration."))}var Ht={};z(Ht,{builder:()=>wr,command:()=>hr,desc:()=>yr,handler:()=>Ir});E();import Ye from"chalk";import{z as mr}from"zod";le();var hr="download [configVersion]",yr="Download remote Vercel Firewall rules and update local config, optionally for a specific configuration version",wr={configVersion:{type:"number",description:"Specific configuration version to download (defaults to latest)"},config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:Z,projectId:{alias:"p",type:"string",description:"Vercel Project ID (can be set in config file)"},teamId:{alias:"t",type:"string",description:"Vercel Team ID (can be set in config file)"},token:{type:"string",description:"Vercel API token (defaults to VERCEL_TOKEN env var)"},apiToken:{type:"string",description:"Cloudflare API token (defaults to CLOUDFLARE_API_TOKEN env var)"},zoneId:{type:"string",description:"Cloudflare Zone ID (defaults to CLOUDFLARE_ZONE_ID env var)"},accountId:{type:"string",description:"Cloudflare Account ID (optional)"},workspaceId:{type:"string",description:"Fastly Next-Gen WAF Workspace ID (defaults to FASTLY_WORKSPACE_ID env var)"},dryRun:{alias:"d",type:"boolean",description:"Show rules that would be downloaded without making changes",default:!1},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}},Ir=async i=>{await Y({config:i.config,provider:i.provider,projectId:i.projectId,teamId:i.teamId,token:i.token,apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId,workspaceId:i.workspaceId,debug:i.debug,ci:i.ci,optionalConfig:!0,skipValidation:!0,errorContext:"downloading firewall rules"},async({config:e,provider:t})=>{if(i.configVersion!==void 0)try{at.parse(i.configVersion)}catch(o){throw o instanceof mr.ZodError&&(r.error("Invalid configuration version number. Version must be a positive integer."),process.exit(1)),o}await Rr(t,e,i)})};async function Rr(i,e,t){r.start(`Fetching remote firewall configuration${t.configVersion?` version ${t.configVersion}`:""} ...`);let o=await i.fetchConfig(t.configVersion),n=o.rules,s=o.ips??[];if(n.length>0?(r.log(Ye.bold(`
42
+ `)),r.log(Q.bold("Rule Changes:")),r.log(` ${Q.green("+")} ${n.rulesToAdd.length} to add`),n.rulesToAdd.forEach(d=>r.log(` ${Q.green("+")} ${d.name}`)),r.log(` ${Q.cyan("~")} ${n.rulesToUpdate.length} to update`),n.rulesToUpdate.forEach(d=>r.log(` ${Q.cyan("~")} ${d.name}`)),r.log(` ${Q.red("-")} ${n.rulesToDelete.length} to delete`),n.rulesToDelete.forEach(d=>r.log(` ${Q.red("-")} ${d.name}`)),(s.length||a.length||l.length)&&(r.log(""),r.log(Q.bold("IP Rule Changes:")),r.log(` ${Q.green("+")} ${s.length} to add`),r.log(` ${Q.cyan("~")} ${a.length} to update`),r.log(` ${Q.red("-")} ${l.length} to delete`));let c=n.rulesToAdd.length+n.rulesToUpdate.length+n.rulesToDelete.length+s.length+a.length+l.length;r.log(""),r.log(Q.bold(`Summary: ${c} total changes detected`)),r.log(Q.dim("Run `sync` to apply these changes to the remote configuration."))}var Ht={};z(Ht,{builder:()=>wr,command:()=>hr,desc:()=>yr,handler:()=>Ir});E();import Ye from"chalk";import{z as mr}from"zod";le();var hr="download [configVersion]",yr="Download remote Vercel Firewall rules and update local config, optionally for a specific configuration version",wr={configVersion:{type:"number",description:"Specific configuration version to download (defaults to latest)"},config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:Z,projectId:{alias:"p",type:"string",description:"Vercel Project ID (can be set in config file)"},teamId:{alias:"t",type:"string",description:"Vercel Team ID (can be set in config file)"},token:{type:"string",description:"Vercel API token (defaults to VERCEL_TOKEN env var)"},apiToken:{type:"string",description:"Cloudflare API token (defaults to CLOUDFLARE_API_TOKEN env var)"},zoneId:{type:"string",description:"Cloudflare Zone ID (defaults to CLOUDFLARE_ZONE_ID env var)"},accountId:{type:"string",description:"Cloudflare Account ID (optional)"},workspaceId:{type:"string",description:"Fastly Next-Gen WAF Workspace ID (defaults to FASTLY_WORKSPACE_ID env var)"},dryRun:{alias:"d",type:"boolean",description:"Show rules that would be downloaded without making changes",default:!1},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}},Ir=async i=>{await Y({config:i.config,provider:i.provider,projectId:i.projectId,teamId:i.teamId,token:i.token,apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId,workspaceId:i.workspaceId,debug:i.debug,ci:i.ci,optionalConfig:!0,skipValidation:!0,errorContext:"downloading firewall rules"},async({config:e,provider:t})=>{if(i.configVersion!==void 0)try{at.parse(i.configVersion)}catch(o){throw o instanceof mr.ZodError&&(r.error("Invalid configuration version number. Version must be a positive integer."),process.exit(1)),o}await Rr(t,e,i)})};async function Rr(i,e,t){r.start(`Fetching remote firewall configuration${t.configVersion?` version ${t.configVersion}`:""} ...`);let o=await i.fetchConfig(t.configVersion),n=o.rules,s=o.ips??[];if(n.length>0?(r.log(Ye.bold(`
43
43
  Remote Custom Rules to Download (${n.length}):
44
44
  `)),n.forEach(c=>r.log(` - ${c.name}${c.enabled?"":Ye.dim(" (disabled)")}`))):r.info("No custom rules to download..."),s.length>0?(r.log(Ye.bold(`
45
45
  Remote IP Blocking Rules to Download (${s.length}):
@@ -94,24 +94,24 @@ Remote IP Blocking Rules to Download (${s.length}):
94
94
  `,o+=` action = "${n.action}"
95
95
  `,o+=`}
96
96
 
97
- `}),o},kr=async i=>{try{let e;i.source==="remote"?await Y({config:i.config,provider:i.provider,projectId:i.projectId,teamId:i.teamId,token:i.token,apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId,workspaceId:i.workspaceId,debug:i.debug,ci:i.ci,errorContext:"exporting configuration"},async({provider:s})=>{r.start("Fetching remote configuration..."),e=await s.fetchConfig()}):e=await W(i.config),r.start(`Exporting configuration in ${i.format} format...`);let t,o;switch(i.format){case"json":t=JSON.stringify(e,null,2),o="json";break;case"yaml":{let s=se(e);t=`# Firewall Configuration (${s.provider??"unknown"})
98
- `,t+=`version: ${s.metadata?.version??"unknown"}
99
- `,t+=`updatedAt: "${s.metadata?.updatedAt??""}"
100
- `,t+=`rules:
101
- `,s.rules.forEach(a=>{t+=` - name: "${a.name}"
102
- `,t+=` id: "${a.id??""}"
103
- `,t+=` enabled: ${a.enabled}
104
- `,t+=` action: "${a.action.type}"
105
- `}),o="yaml";break}case"terraform":t=Pr(se(e)),o="tf";break;case"markdown":t=Ar(se(e)),o="md";break;default:throw new Error(`Unsupported format: ${i.format}`)}let n=i.output||`firewall-export.${o}`;i.output==="-"||!i.output?r.log(t):(Cr(n,t,"utf8"),r.success(Je.green(`\u2705 Exported to ${n}`)),r.log(""),r.log(Je.bold("Export Summary:")),r.log(`${Je.dim("Format:")} ${i.format}`),r.log(`${Je.dim("Source:")} ${i.source}`),r.log(`${Je.dim("Rules:")} ${e.rules.length} custom, ${(e.ips||[]).length} IP blocking`),r.log(`${Je.dim("Size:")} ${(t.length/1024).toFixed(1)} KB`))}catch(e){H(e,"exporting configuration")}};var Yt={};z(Yt,{BASIC_TEMPLATE_RULES:()=>eo,SECURITY_FOCUSED_TEMPLATE_RULES:()=>to,builder:()=>Lr,command:()=>_r,desc:()=>Sr,handler:()=>Or});E();le();import y from"chalk";import{existsSync as $r}from"fs";var eo=[{id:"rule_block_bad_bots",name:"Block Bad Bots",description:"Block known malicious bots and crawlers based on user agent patterns",conditionGroup:[{conditions:[{type:"user_agent",op:"sub",value:"bot"}]}],action:{mitigate:{action:"deny"}},active:!1},{id:"rule_block_scrapers",name:"Block Scrapers",description:"Block common scraping tools and automated requests",conditionGroup:[{conditions:[{type:"user_agent",op:"sub",value:"scraper"}]}],action:{mitigate:{action:"deny"}},active:!1}],to=[{id:"rule_rate_limit_api",name:"Rate Limit API",description:"Rate limit API endpoints to prevent abuse and DoS attacks",conditionGroup:[{conditions:[{type:"path",op:"pre",value:"/api/"}]}],action:{mitigate:{action:"rate_limit",rateLimit:{requests:100,window:"1m"}}},active:!1},{id:"rule_block_suspicious_ips",name:"Block Suspicious IPs",description:"Block requests from a known suspicious IP range",conditionGroup:[{conditions:[{type:"ip_address",op:"eq",value:"10.0.0.0/24"}]}],action:{mitigate:{action:"deny"}},active:!1},{id:"rule_protect_admin",name:"Protect Admin Routes",description:"Add extra protection for admin and sensitive routes",conditionGroup:[{conditions:[{type:"path",op:"pre",value:"/admin"}]}],action:{mitigate:{action:"rate_limit",rateLimit:{requests:10,window:"1m"}}},active:!1}],_r="init [template]",Sr="Initialize a new Doorman configuration",Lr={template:{type:"string",description:"Template to use for initialization (empty, basic, or security-focused)",choices:["empty","basic","security-focused"],default:"empty"},config:{alias:"c",type:"string",description:"Path for the new config file",default:".doorman.json"},force:{alias:"f",type:"boolean",description:"Overwrite existing config file",default:!1},interactive:{alias:"i",type:"boolean",description:"Interactive setup with guided prompts",default:!0},projectId:{alias:"p",type:"string",description:"Vercel Project ID"},teamId:{alias:"t",type:"string",description:"Vercel Team ID"}},Ur=()=>{r.log(""),r.log(y.bold.cyan("\u{1F6AA} Welcome to Doorman!")),r.log(y.dim(`Let's set up your firewall configuration.
97
+ `}),o},kr=async i=>{try{let e=i.output==="-"||!i.output,t;i.source==="remote"?await Y({config:i.config,provider:i.provider,projectId:i.projectId,teamId:i.teamId,token:i.token,apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId,workspaceId:i.workspaceId,debug:i.debug,ci:i.ci,errorContext:"exporting configuration"},async({provider:a})=>{e||r.start("Fetching remote configuration..."),t=await a.fetchConfig()}):t=await W(i.config),e||r.start(`Exporting configuration in ${i.format} format...`);let o,n;switch(i.format){case"json":o=JSON.stringify(t,null,2),n="json";break;case"yaml":{let a=se(t);o=`# Firewall Configuration (${a.provider??"unknown"})
98
+ `,o+=`version: ${a.metadata?.version??"unknown"}
99
+ `,o+=`updatedAt: "${a.metadata?.updatedAt??""}"
100
+ `,o+=`rules:
101
+ `,a.rules.forEach(l=>{o+=` - name: "${l.name}"
102
+ `,o+=` id: "${l.id??""}"
103
+ `,o+=` enabled: ${l.enabled}
104
+ `,o+=` action: "${l.action.type}"
105
+ `}),n="yaml";break}case"terraform":o=Pr(se(t)),n="tf";break;case"markdown":o=Ar(se(t)),n="md";break;default:throw new Error(`Unsupported format: ${i.format}`)}let s=i.output||`firewall-export.${n}`;e?r.log(o):(Cr(s,o,"utf8"),r.success(Je.green(`\u2705 Exported to ${s}`)),r.log(""),r.log(Je.bold("Export Summary:")),r.log(`${Je.dim("Format:")} ${i.format}`),r.log(`${Je.dim("Source:")} ${i.source}`),r.log(`${Je.dim("Rules:")} ${t.rules.length} custom, ${(t.ips||[]).length} IP blocking`),r.log(`${Je.dim("Size:")} ${(o.length/1024).toFixed(1)} KB`))}catch(e){H(e,"exporting configuration")}};var Yt={};z(Yt,{BASIC_TEMPLATE_RULES:()=>eo,SECURITY_FOCUSED_TEMPLATE_RULES:()=>to,builder:()=>Lr,command:()=>_r,desc:()=>Sr,handler:()=>Or});E();le();import y from"chalk";import{existsSync as $r}from"fs";var eo=[{id:"rule_block_bad_bots",name:"Block Bad Bots",description:"Block known malicious bots and crawlers based on user agent patterns",conditionGroup:[{conditions:[{type:"user_agent",op:"sub",value:"bot"}]}],action:{mitigate:{action:"deny"}},active:!1},{id:"rule_block_scrapers",name:"Block Scrapers",description:"Block common scraping tools and automated requests",conditionGroup:[{conditions:[{type:"user_agent",op:"sub",value:"scraper"}]}],action:{mitigate:{action:"deny"}},active:!1}],to=[{id:"rule_rate_limit_api",name:"Rate Limit API",description:"Rate limit API endpoints to prevent abuse and DoS attacks",conditionGroup:[{conditions:[{type:"path",op:"pre",value:"/api/"}]}],action:{mitigate:{action:"rate_limit",rateLimit:{requests:100,window:"1m"}}},active:!1},{id:"rule_block_suspicious_ips",name:"Block Suspicious IPs",description:"Block requests from a known suspicious IP range",conditionGroup:[{conditions:[{type:"ip_address",op:"eq",value:"10.0.0.0/24"}]}],action:{mitigate:{action:"deny"}},active:!1},{id:"rule_protect_admin",name:"Protect Admin Routes",description:"Add extra protection for admin and sensitive routes",conditionGroup:[{conditions:[{type:"path",op:"pre",value:"/admin"}]}],action:{mitigate:{action:"rate_limit",rateLimit:{requests:10,window:"1m"}}},active:!1}],_r="init [template]",Sr="Initialize a new Doorman configuration",Lr={template:{type:"string",description:"Template to use for initialization (empty, basic, or security-focused)",choices:["empty","basic","security-focused"],default:"empty"},config:{alias:"c",type:"string",description:"Path for the new config file",default:".doorman.json"},force:{alias:"f",type:"boolean",description:"Overwrite existing config file",default:!1},interactive:{alias:"i",type:"boolean",description:"Interactive setup with guided prompts",default:!0},projectId:{alias:"p",type:"string",description:"Vercel Project ID"},teamId:{alias:"t",type:"string",description:"Vercel Team ID"}},Ur=()=>{r.log(""),r.log(y.bold.cyan("\u{1F6AA} Welcome to Doorman!")),r.log(y.dim(`Let's set up your firewall configuration.
106
106
  `))},Dr=()=>{r.log(y.bold(`
107
107
  \u{1F4DA} Helpful Resources:`)),r.log(""),r.log(y.cyan("\u{1F517} Find your Project ID:")),r.log(y.dim(" https://vercel.com/dashboard \u2192 Select your project \u2192 Settings \u2192 General")),r.log(""),r.log(y.cyan("\u{1F517} Find your Team ID:")),r.log(y.dim(" https://vercel.com/dashboard \u2192 Team Settings \u2192 General")),r.log(y.dim(" (Leave empty if using personal account)")),r.log(""),r.log(y.cyan("\u{1F517} Create API Token:")),r.log(y.dim(" https://vercel.com/account/tokens \u2192 Create Token")),r.log(y.dim(" Scopes needed: Read & Write for your project")),r.log(""),r.log(y.cyan("\u{1F517} Documentation:")),r.log(y.dim(" https://doorman.griffen.codes/getting-started")),r.log("")},xr=async i=>{let e=i.projectId,t=i.teamId;return i.interactive&&(r.log(y.bold("\u{1F4CB} Project Configuration")),r.log(y.dim(`We need your Vercel project details to configure the firewall.
108
108
  `)),e||(e=await w("Enter your Vercel Project ID:",{type:"text"})),t||await w("Are you using a Vercel team account?",{type:"confirm"})&&(t=await w("Enter your Vercel Team ID:",{type:"text"})),process.env.VERCEL_TOKEN?r.log(y.green("\u2705 VERCEL_TOKEN environment variable found")):(r.log(""),r.log(y.yellow("\u26A0\uFE0F VERCEL_TOKEN environment variable not found")),r.log(y.dim("You'll need to set this before running sync commands.")),await w("Would you like to see how to create an API token?",{type:"confirm"})&&(r.log(""),r.log(y.bold("\u{1F511} Creating a Vercel API Token:")),r.log("1. Go to https://vercel.com/account/tokens"),r.log('2. Click "Create Token"'),r.log('3. Give it a descriptive name (e.g., "Doorman Firewall")'),r.log("4. Set expiration as needed"),r.log("5. Copy the token and set it as an environment variable:"),r.log(""),r.log(y.cyan(' export VERCEL_TOKEN="your-token-here"')),r.log(y.dim(" # Add this to your ~/.bashrc, ~/.zshrc, or .env file")),r.log("")))),{projectId:e,teamId:t}},Or=async i=>{try{let e=i.config||".doorman.json";if(i.interactive&&Ur(),$r(e)&&!i.force&&!await w(`Config file ${e} already exists. Do you want to overwrite it?`,{type:"confirm"})){r.info(y.yellow("Initialization cancelled."));return}let{projectId:t,teamId:o}=await xr(i);i.interactive&&(r.log(""),r.log(y.bold("\u{1F3A8} Template Selection")),r.log(y.dim(`Choose a starting template for your firewall configuration:
109
- `)),r.log(y.cyan("\u{1F4DD} empty")+y.dim(" - Minimal config, add your own rules")),r.log(y.cyan("\u{1F6E1}\uFE0F basic")+y.dim(" - Includes basic bot protection")),r.log(y.cyan("\u{1F512} security-focused")+y.dim(" - Comprehensive security rules")),r.log(""));let n=i.template;i.interactive&&!i.template&&(n=await w("Select a template:",{type:"select",options:["empty","basic","security-focused"],initial:"empty"})),r.start(`Creating ${n} configuration...`);let s=dt();switch(t&&(s.projectId=t),o&&(s.teamId=o),n){case"basic":s={...s,rules:eo};break;case"security-focused":s={...s,rules:to};break;default:break}await j(s,e),r.success(y.green(`\u2705 Created ${e}`)),r.log(""),r.log(y.bold("\u{1F4CB} Configuration Summary:")),r.log(`${y.dim("File:")} ${e}`),r.log(`${y.dim("Template:")} ${n}`),r.log(`${y.dim("Project ID:")} ${t||y.yellow("Not set - add manually")}`),r.log(`${y.dim("Team ID:")} ${o||y.dim("None (personal account)")}`),r.log(`${y.dim("Rules:")} ${s.rules.length} (all disabled for safety)`),r.log(""),r.log(y.bold("\u{1F680} Next Steps:")),t||r.log(`1. ${y.yellow("Add your projectId")} to ${e}`),process.env.VERCEL_TOKEN||r.log(`${t?"1":"2"}. ${y.yellow("Set VERCEL_TOKEN")} environment variable`);let a=t?process.env.VERCEL_TOKEN?1:2:3;r.log(`${a}. ${y.dim("Review and enable rules in")} ${e}`),r.log(`${a+1}. ${y.dim("Run")} ${y.cyan("doorman validate")} ${y.dim("to check your config")}`),r.log(`${a+2}. ${y.dim("Run")} ${y.cyan("doorman status")} ${y.dim("to see sync status")}`),r.log(`${a+3}. ${y.dim("Run")} ${y.cyan("doorman sync")} ${y.dim("to deploy your rules")}`),n!=="empty"&&(r.log(""),r.log(y.yellow("\u26A0\uFE0F All template rules are disabled by default for safety.")),r.log(y.dim(" Review each rule and set active: true when ready."))),i.interactive&&(!t||!process.env.VERCEL_TOKEN)&&Dr()}catch(e){H(e,"initializing configuration")}};var Jt={};z(Jt,{builder:()=>Br,command:()=>Nr,desc:()=>Vr,handler:()=>Wr});E();import de from"chalk";import{z as Fr}from"zod";var Nr="list [configVersion]",Vr="List Vercel Firewall rules, optionally for a specific configuration version",Br={configVersion:{type:"number",description:"Specific configuration version to fetch (defaults to latest)"},provider:Z,projectId:{alias:"p",type:"string",description:"Vercel Project ID (can be set in config file or VERCEL_PROJECT_ID environment variable)"},teamId:{alias:"t",type:"string",description:"Vercel Team ID (can be set in config file or VERCEL_TEAM_ID environment variable)"},token:{type:"string",description:"Vercel API token (defaults to VERCEL_TOKEN env var)"},apiToken:{type:"string",description:"Cloudflare API token (defaults to CLOUDFLARE_API_TOKEN env var)"},zoneId:{type:"string",description:"Cloudflare Zone ID (defaults to CLOUDFLARE_ZONE_ID env var)"},accountId:{type:"string",description:"Cloudflare Account ID (optional)"},workspaceId:{type:"string",description:"Fastly Next-Gen WAF Workspace ID (defaults to FASTLY_WORKSPACE_ID env var)"},format:{alias:"f",type:"string",description:"Output format (json or table)",choices:["json","table"],default:"table"},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}},Wr=async i=>{await Y({provider:i.provider,projectId:i.projectId,teamId:i.teamId,token:i.token,apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId,workspaceId:i.workspaceId,debug:i.debug,ci:i.ci,optionalConfig:!0,skipValidation:!0,errorContext:"listing firewall rules"},async({provider:e})=>{if(i.configVersion!==void 0)try{at.parse(i.configVersion)}catch(t){throw t instanceof Fr.ZodError&&(r.error("Invalid configuration version number. Version must be a positive integer."),process.exit(1)),t}await jr(e,i)})};async function jr(i,e){r.start(`Fetching firewall configuration${e.configVersion?` version ${e.configVersion}`:""} ...`);let t=await i.fetchConfig(e.configVersion),o=t.rules,n=t.ips??[],s=t.metadata?.version??t.version,a=t.metadata?.updatedAt;if(r.info(`Found ${de.cyan(o.length)} custom rules and ${de.cyan(n.length)} IP blocking rules
110
- `+de.dim(`Version: ${de.yellow(s??"unknown")}${a?` \u2022 Last Updated: ${de.yellow(a)}`:""}`)),e.format==="json"){r.info(JSON.stringify({version:s,updatedAt:a,rules:o,ips:n},null,2));return}if(o.length===0&&n.length===0){r.info(de.yellow("No rules found"));return}o.length>0?(r.log(de.bold.underline(`
109
+ `)),r.log(y.cyan("\u{1F4DD} empty")+y.dim(" - Minimal config, add your own rules")),r.log(y.cyan("\u{1F6E1}\uFE0F basic")+y.dim(" - Includes basic bot protection")),r.log(y.cyan("\u{1F512} security-focused")+y.dim(" - Comprehensive security rules")),r.log(""));let n=i.template;i.interactive&&!i.template&&(n=await w("Select a template:",{type:"select",options:["empty","basic","security-focused"],initial:"empty"})),r.start(`Creating ${n} configuration...`);let s=dt();switch(t&&(s.projectId=t),o&&(s.teamId=o),n){case"basic":s={...s,rules:eo};break;case"security-focused":s={...s,rules:to};break;default:break}await j(s,e),r.success(y.green(`\u2705 Created ${e}`)),r.log(""),r.log(y.bold("\u{1F4CB} Configuration Summary:")),r.log(`${y.dim("File:")} ${e}`),r.log(`${y.dim("Template:")} ${n}`),r.log(`${y.dim("Project ID:")} ${t||y.yellow("Not set - add manually")}`),r.log(`${y.dim("Team ID:")} ${o||y.dim("None (personal account)")}`),r.log(`${y.dim("Rules:")} ${s.rules.length} (all disabled for safety)`),r.log(""),r.log(y.bold("\u{1F680} Next Steps:")),t||r.log(`1. ${y.yellow("Add your projectId")} to ${e}`),process.env.VERCEL_TOKEN||r.log(`${t?"1":"2"}. ${y.yellow("Set VERCEL_TOKEN")} environment variable`);let a=t?process.env.VERCEL_TOKEN?1:2:3;r.log(`${a}. ${y.dim("Review and enable rules in")} ${e}`),r.log(`${a+1}. ${y.dim("Run")} ${y.cyan("doorman validate")} ${y.dim("to check your config")}`),r.log(`${a+2}. ${y.dim("Run")} ${y.cyan("doorman status")} ${y.dim("to see sync status")}`),r.log(`${a+3}. ${y.dim("Run")} ${y.cyan("doorman sync")} ${y.dim("to deploy your rules")}`),n!=="empty"&&(r.log(""),r.log(y.yellow("\u26A0\uFE0F All template rules are disabled by default for safety.")),r.log(y.dim(" Review each rule and set active: true when ready."))),i.interactive&&(!t||!process.env.VERCEL_TOKEN)&&Dr()}catch(e){H(e,"initializing configuration")}};var Jt={};z(Jt,{builder:()=>Br,command:()=>Nr,desc:()=>Vr,handler:()=>Wr});E();import de from"chalk";import{z as Fr}from"zod";var Nr="list [configVersion]",Vr="List Vercel Firewall rules, optionally for a specific configuration version",Br={configVersion:{type:"number",description:"Specific configuration version to fetch (defaults to latest)"},provider:Z,projectId:{alias:"p",type:"string",description:"Vercel Project ID (can be set in config file or VERCEL_PROJECT_ID environment variable)"},teamId:{alias:"t",type:"string",description:"Vercel Team ID (can be set in config file or VERCEL_TEAM_ID environment variable)"},token:{type:"string",description:"Vercel API token (defaults to VERCEL_TOKEN env var)"},apiToken:{type:"string",description:"Cloudflare API token (defaults to CLOUDFLARE_API_TOKEN env var)"},zoneId:{type:"string",description:"Cloudflare Zone ID (defaults to CLOUDFLARE_ZONE_ID env var)"},accountId:{type:"string",description:"Cloudflare Account ID (optional)"},workspaceId:{type:"string",description:"Fastly Next-Gen WAF Workspace ID (defaults to FASTLY_WORKSPACE_ID env var)"},format:{alias:"f",type:"string",description:"Output format (json or table)",choices:["json","table"],default:"table"},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}},Wr=async i=>{await Y({provider:i.provider,projectId:i.projectId,teamId:i.teamId,token:i.token,apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId,workspaceId:i.workspaceId,debug:i.debug,ci:i.ci,optionalConfig:!0,skipValidation:!0,errorContext:"listing firewall rules"},async({provider:e})=>{if(i.configVersion!==void 0)try{at.parse(i.configVersion)}catch(t){throw t instanceof Fr.ZodError&&(r.error("Invalid configuration version number. Version must be a positive integer."),process.exit(1)),t}await jr(e,i)})};async function jr(i,e){let t=e.format==="json";t||r.start(`Fetching firewall configuration${e.configVersion?` version ${e.configVersion}`:""} ...`);let o=await i.fetchConfig(e.configVersion),n=o.rules,s=o.ips??[],a=o.metadata?.version??o.version,l=o.metadata?.updatedAt;if(t){r.log(JSON.stringify({version:a,updatedAt:l,rules:n,ips:s},null,2));return}if(r.info(`Found ${de.cyan(n.length)} custom rules and ${de.cyan(s.length)} IP blocking rules
110
+ `+de.dim(`Version: ${de.yellow(a??"unknown")}${l?` \u2022 Last Updated: ${de.yellow(l)}`:""}`)),n.length===0&&s.length===0){r.info(de.yellow("No rules found"));return}n.length>0?(r.log(de.bold.underline(`
111
111
  Custom Rules:`),`
112
- `),o.forEach(l=>r.log(` - ${l.name}${l.enabled?"":de.dim(" (disabled)")}`))):r.info(de.cyan("No custom rules found")),n.length>0?(r.log(de.bold.underline(`
112
+ `),n.forEach(c=>r.log(` - ${c.name}${c.enabled?"":de.dim(" (disabled)")}`))):r.info(de.cyan("No custom rules found")),s.length>0?(r.log(de.bold.underline(`
113
113
  IP Blocking Rules:`),`
114
- `),n.forEach(l=>r.log(` - ${l.ip} (${l.action})`))):r.info(de.cyan("No IP blocking rules found"))}var Xt={};z(Xt,{aliases:()=>Mr,builder:()=>Kr,command:()=>zr,desc:()=>Gr,findFuzzyMatches:()=>ro,handler:()=>Hr,levenshtein:()=>no});E();le();import D from"chalk";import{LogLevels as qr}from"consola";var zr="remove [type]",Mr=["rm","delete"],Gr="Remove a firewall rule or IP entry from your configuration",Kr={type:{type:"string",description:'What to remove: "rule" (default) or "ip"',default:"rule",choices:["rule","ip"]},interactive:{alias:"i",type:"boolean",description:"Select rules to remove from a list",default:!1},name:{alias:"n",type:"string",description:"Remove rule by name (exact match)"},id:{type:"array",string:!0,description:"Remove rule by ID (supports multiple)"},ip:{type:"string",description:"Remove IP rule by IP address"},disabled:{type:"boolean",description:"Remove all disabled (active: false) rules",default:!1},all:{type:"boolean",description:"Remove all rules (requires confirmation)",default:!1},force:{alias:"f",type:"boolean",description:"Skip confirmation prompt",default:!1},config:{alias:"c",type:"string",description:"Config file path"},dryRun:{alias:"d",type:"boolean",description:"Show what would be removed without writing",default:!1},debug:{type:"boolean",description:"Enable debug logging",default:!1}};function no(i,e){let t=i.length,o=e.length;if(t===0)return o;if(o===0)return t;let n=Array.from({length:t+1},(a,l)=>l),s=new Array(t+1).fill(0);for(let a=1;a<=o;a++){s[0]=a;for(let l=1;l<=t;l++)e.charAt(a-1)===i.charAt(l-1)?s[l]=n[l-1]:s[l]=Math.min(n[l-1]+1,s[l-1]+1,n[l]+1);for(let l=0;l<=t;l++)n[l]=s[l]}return n[t]}function ro(i,e,t=3){let o=i.toLowerCase(),n=e.filter(l=>l.name.toLowerCase().includes(o)||o.includes(l.name.toLowerCase()));if(n.length>0)return n.slice(0,t);let s=e.map(l=>({rule:l,distance:no(o,l.name.toLowerCase())})).sort((l,c)=>l.distance-c.distance),a=Math.max(i.length*.6,3);return s.filter(l=>l.distance<=a).slice(0,t).map(l=>l.rule)}function Qt(i){for(let e of i){let t=e.id?` (${e.id})`:"",o=e.active?"":D.dim(" [disabled]"),n=e.action.mitigate.action;r.log(D.dim(` - ${e.name}${t} \u2014 ${n}${o}`))}}function io(i){for(let e of i){let t=e.notes?` \u2014 ${e.notes}`:"";r.log(D.dim(` - ${e.ip}${t}`))}}async function oo(i,e,t,o="rule"){if(e)return!0;if(t)return await w(D.red(`\u26A0\uFE0F Are you sure you want to remove ALL ${o}s? Type "YES" to confirm:`),{type:"text"})==="YES";let n=i===1?`Confirm removal of 1 ${o}?`:`Confirm removal of ${i} ${o}s?`;return await w(n,{type:"confirm",initial:!1})}var Hr=async i=>{try{i.debug&&(r.level=qr.debug),r.debug("Remove command arguments:",i);let e=i.type||"rule";r.start("Loading configuration...");let t=await W(i.config);if(e==="ip"){let o=t.ips||[];if(o.length===0){r.info("No IP rules in configuration.");return}let n=[];if(i.interactive){let l=o.map(d=>{let u=d.notes?` \u2014 ${d.notes}`:"";return`${d.ip}${u}`}),c=await w("Select IP rules to remove:",{type:"multiselect",options:l});n=o.filter(d=>{let u=`${d.ip}${d.notes?` \u2014 ${d.notes}`:""}`;return c.includes(u)})}else i.ip?(n=o.filter(l=>l.ip===i.ip),n.length===0&&(r.error(D.red(`No IP rule found matching "${i.ip}"`)),o.length>0&&(r.log(D.dim(`
114
+ `),s.forEach(c=>r.log(` - ${c.ip} (${c.action})`))):r.info(de.cyan("No IP blocking rules found"))}var Xt={};z(Xt,{aliases:()=>Mr,builder:()=>Kr,command:()=>zr,desc:()=>Gr,findFuzzyMatches:()=>ro,handler:()=>Hr,levenshtein:()=>no});E();le();import D from"chalk";import{LogLevels as qr}from"consola";var zr="remove [type]",Mr=["rm","delete"],Gr="Remove a firewall rule or IP entry from your configuration",Kr={type:{type:"string",description:'What to remove: "rule" (default) or "ip"',default:"rule",choices:["rule","ip"]},interactive:{alias:"i",type:"boolean",description:"Select rules to remove from a list",default:!1},name:{alias:"n",type:"string",description:"Remove rule by name (exact match)"},id:{type:"array",string:!0,description:"Remove rule by ID (supports multiple)"},ip:{type:"string",description:"Remove IP rule by IP address"},disabled:{type:"boolean",description:"Remove all disabled (active: false) rules",default:!1},all:{type:"boolean",description:"Remove all rules (requires confirmation)",default:!1},force:{alias:"f",type:"boolean",description:"Skip confirmation prompt",default:!1},config:{alias:"c",type:"string",description:"Config file path"},dryRun:{alias:"d",type:"boolean",description:"Show what would be removed without writing",default:!1},debug:{type:"boolean",description:"Enable debug logging",default:!1}};function no(i,e){let t=i.length,o=e.length;if(t===0)return o;if(o===0)return t;let n=Array.from({length:t+1},(a,l)=>l),s=new Array(t+1).fill(0);for(let a=1;a<=o;a++){s[0]=a;for(let l=1;l<=t;l++)e.charAt(a-1)===i.charAt(l-1)?s[l]=n[l-1]:s[l]=Math.min(n[l-1]+1,s[l-1]+1,n[l]+1);for(let l=0;l<=t;l++)n[l]=s[l]}return n[t]}function ro(i,e,t=3){let o=i.toLowerCase(),n=e.filter(l=>l.name.toLowerCase().includes(o)||o.includes(l.name.toLowerCase()));if(n.length>0)return n.slice(0,t);let s=e.map(l=>({rule:l,distance:no(o,l.name.toLowerCase())})).sort((l,c)=>l.distance-c.distance),a=Math.max(i.length*.6,3);return s.filter(l=>l.distance<=a).slice(0,t).map(l=>l.rule)}function Qt(i){for(let e of i){let t=e.id?` (${e.id})`:"",o=e.active?"":D.dim(" [disabled]"),n=e.action.mitigate.action;r.log(D.dim(` - ${e.name}${t} \u2014 ${n}${o}`))}}function io(i){for(let e of i){let t=e.notes?` \u2014 ${e.notes}`:"";r.log(D.dim(` - ${e.ip}${t}`))}}async function oo(i,e,t,o="rule"){if(e)return!0;if(t)return await w(D.red(`\u26A0\uFE0F Are you sure you want to remove ALL ${o}s? Type "YES" to confirm:`),{type:"text"})==="YES";let n=i===1?`Confirm removal of 1 ${o}?`:`Confirm removal of ${i} ${o}s?`;return await w(n,{type:"confirm",initial:!1})}var Hr=async i=>{try{i.debug&&(r.level=qr.debug),r.debug("Remove command arguments:",i);let e=i.type||"rule";r.start("Loading configuration...");let t=await W(i.config);if(e==="ip"){let o=t.ips||[];if(o.length===0){r.info("No IP rules in configuration.");return}let n=[];if(i.interactive){let l=o.map(d=>{let u=d.notes?` \u2014 ${d.notes}`:"";return`${d.ip}${u}`}),c=await w("Select IP rules to remove:",{type:"multiselect",options:l});n=o.filter(d=>{let u=`${d.ip}${d.notes?` \u2014 ${d.notes}`:""}`;return c.includes(u)})}else i.ip?(n=o.filter(l=>l.ip===i.ip),n.length===0&&(r.error(D.red(`No IP rule found matching "${i.ip}"`)),o.length>0&&(r.log(D.dim(`
115
115
  Current IP rules:`)),io(o)),process.exit(1))):i.all?n=[...o]:(r.error("Specify --ip, --interactive, or --all to select IP rules to remove."),process.exit(1));if(n.length===0){r.info("No IP rules selected for removal.");return}if(r.log(D.cyan(`
116
116
  ${i.dryRun?"Would remove":"Removing"} ${n.length} IP rule(s):`)),io(n),i.dryRun){r.log(D.dim(`
117
117
  Dry run \u2014 no changes made.`));return}if(!await oo(n.length,i.force||!1,i.all||!1,"IP rule")){r.info("Cancelled.");return}let s=new Set(n.map(l=>l.ip)),a={...t,ips:o.filter(l=>!s.has(l.ip))};await j(a,i.config),r.success(D.green(`\u2714 Removed ${n.length} IP rule(s) from configuration`))}else{let o=t.rules||[];if(o.length===0){r.info("No rules in configuration.");return}let n=[];if(i.interactive){let l=o.map((d,u)=>{let p=d.id?` (${d.id})`:"",g=d.active?"":" [disabled]",h=d.action.mitigate.action;return`[${u}] ${d.name}${p} \u2014 ${h}${g}`}),c=await w("Select rules to remove:",{type:"multiselect",options:l});n=o.filter((d,u)=>c.some(p=>p.startsWith(`[${u}]`)))}else if(i.name){if(n=o.filter(l=>l.name===i.name),n.length>1&&(r.warn(D.yellow(`
@@ -122,11 +122,11 @@ Use --id for exact matching, or --interactive to select from a list.`))),process
122
122
  ${i.dryRun?"Would remove":"Removing"} ${n.length} rule(s):`)),Qt(n),i.dryRun){r.log(D.dim(`
123
123
  Dry run \u2014 no changes made.`));return}if(!await oo(n.length,i.force||!1,i.all||!1)){r.info("Cancelled.");return}let s=new Set(n),a={...t,rules:o.filter(l=>!s.has(l))};a.rules.length===0&&r.warn(D.yellow("\u26A0\uFE0F Configuration will have no rules after removal.")),await j(a,i.config),r.success(D.green(`\u2714 Removed ${n.length} rule(s) from configuration`)),Qt(n)}r.log(""),r.log(D.dim(`Run ${D.cyan("doorman sync")} to deploy these changes.`))}catch(e){H(e,"removing rule")}};var ei={};z(ei,{builder:()=>Jr,command:()=>Zr,desc:()=>Yr,handler:()=>Qr});E();import k from"chalk";var Zr="setup",Yr="Show setup instructions and helpful links for Doorman",Jr={},Qr=async i=>{r.log(""),r.log(k.bold.cyan("\u{1F6AA} Doorman Setup Guide")),r.log(k.dim(`Everything you need to get started with firewall management
124
124
  `)),r.log(k.bold("\u{1F4CB} Prerequisites")),r.log("\u2022 Vercel account with a deployed project"),r.log("\u2022 Project with Pro plan or higher (firewall feature requirement)"),r.log(`\u2022 Admin access to the project/team
125
- `),r.log(k.bold("\u{1F50D} Finding Your Project Information")),r.log(""),r.log(k.cyan("1. Project ID:")),r.log(" \u2022 Go to https://vercel.com/dashboard"),r.log(" \u2022 Select your project"),r.log(" \u2022 Navigate to Settings \u2192 General"),r.log(" \u2022 Copy the Project ID from the top of the page"),r.log(""),r.log(k.cyan("2. Team ID (if using team account):")),r.log(" \u2022 Go to https://vercel.com/dashboard"),r.log(" \u2022 Click on your team name in the top-left"),r.log(" \u2022 Go to Team Settings \u2192 General"),r.log(" \u2022 Copy the Team ID"),r.log(" \u2022 Leave empty to use your Vercel default team (every account has one)"),r.log(" \u2022 Belong to more than one team? Set this explicitly, or doorman will use whichever is your default"),r.log(""),r.log(k.bold("\u{1F511} Creating API Token")),r.log(""),r.log(k.cyan("Steps:")),r.log(" 1. Visit https://vercel.com/account/tokens"),r.log(' 2. Click "Create Token"'),r.log(' 3. Name: "Doorman Firewall Management"'),r.log(" 4. Expiration: Set as needed (90 days recommended)"),r.log(" 5. Scope: Select your project or team"),r.log(" 6. Copy the generated token"),r.log(""),r.log(k.cyan("Required Permissions:")),r.log(" \u2022 Read access to project settings"),r.log(" \u2022 Write access to firewall configuration"),r.log(""),r.log(k.bold("\u{1F30D} Environment Setup")),r.log(""),r.log(k.cyan("Set your API token as environment variable:")),r.log(""),r.log(k.green("# For current session:")),r.log('export VERCEL_TOKEN="your-token-here"'),r.log(""),r.log(k.green("# For permanent setup (add to ~/.bashrc or ~/.zshrc):")),r.log(`echo 'export VERCEL_TOKEN="your-token-here"' >> ~/.bashrc`),r.log(""),r.log(k.green("# Or create a .env file in your project:")),r.log('echo "VERCEL_TOKEN=your-token-here" > .env'),r.log(""),r.log(k.bold("\u{1F680} Quick Start Commands")),r.log(""),r.log(k.cyan("1. Initialize configuration:")),r.log(" doorman init --interactive"),r.log(""),r.log(k.cyan("2. Validate setup:")),r.log(" doorman validate"),r.log(""),r.log(k.cyan("3. Check current status:")),r.log(" doorman status"),r.log(""),r.log(k.cyan("4. View existing rules:")),r.log(" doorman list"),r.log(""),r.log(k.cyan("5. Sync your configuration:")),r.log(" doorman sync"),r.log(""),r.log(k.bold("\u{1F527} Common Issues")),r.log(""),r.log(k.yellow('\u274C "Project not found" error:')),r.log(" \u2022 Double-check your Project ID"),r.log(" \u2022 Ensure your token has access to the project"),r.log(" \u2022 Verify the project has Pro plan or higher"),r.log(""),r.log(k.yellow('\u274C "Unauthorized" error:')),r.log(" \u2022 Check your VERCEL_TOKEN is set correctly"),r.log(" \u2022 Ensure token hasn't expired"),r.log(" \u2022 Verify token has firewall permissions"),r.log(""),r.log(k.yellow('\u274C "Firewall not available" error:')),r.log(" \u2022 Upgrade to Pro plan or higher"),r.log(" \u2022 Contact Vercel support if issue persists"),r.log(""),r.log(k.bold("\u{1F4DA} Additional Resources")),r.log(""),r.log(k.cyan("Documentation:")),r.log(" \u2022 Doorman Docs: https://doorman.griffen.codes"),r.log(" \u2022 Vercel Firewall: https://vercel.com/docs/security/vercel-firewall"),r.log(" \u2022 API Reference: https://vercel.com/docs/rest-api/endpoints/firewall"),r.log(""),r.log(k.cyan("Support:")),r.log(" \u2022 GitHub Issues: https://github.com/gfargo/doorman/issues"),r.log(" \u2022 Vercel Support: https://vercel.com/help"),r.log(""),r.log(k.bold("\u{1F512} Security Best Practices")),r.log(""),r.log("\u2022 Store API tokens securely (use environment variables)"),r.log("\u2022 Set token expiration dates"),r.log("\u2022 Use principle of least privilege for token scopes"),r.log("\u2022 Regularly rotate API tokens"),r.log("\u2022 Test rules in staging before production"),r.log("\u2022 Keep backups of working configurations"),r.log(""),r.log(k.bold.green("\u2705 Ready to get started?")),r.log(k.dim("Run: ")+k.cyan("doorman init --interactive")),r.log("")};var ti={};z(ti,{builder:()=>ts,command:()=>Xr,desc:()=>es,handler:()=>is});E();import S from"chalk";var Xr="status",es="Show sync status between local and remote firewall configuration",ts={config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:Z,projectId:{alias:"p",type:"string",description:"Vercel Project ID (can be set in config file)"},teamId:{alias:"t",type:"string",description:"Vercel Team ID (can be set in config file)"},token:{type:"string",description:"Vercel API token (defaults to VERCEL_TOKEN env var)"},apiToken:{type:"string",description:"Cloudflare API token (defaults to CLOUDFLARE_API_TOKEN env var)"},zoneId:{type:"string",description:"Cloudflare Zone ID (defaults to CLOUDFLARE_ZONE_ID env var)"},accountId:{type:"string",description:"Cloudflare Account ID (optional)"},workspaceId:{type:"string",description:"Fastly Next-Gen WAF Workspace ID (defaults to FASTLY_WORKSPACE_ID env var)"},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}},is=async i=>{await Y({config:i.config,provider:i.provider,projectId:i.projectId,teamId:i.teamId,token:i.token,apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId,workspaceId:i.workspaceId,debug:i.debug,ci:i.ci,errorContext:"checking status"},async({config:e,provider:t})=>{await os(t,se(e))})};async function os(i,e){r.start("Checking sync status...");let t=await i.getChanges(e),o=e.metadata?.version,n=t.version!==void 0&&o!==void 0&&o!==t.version;r.log(S.bold(`
125
+ `),r.log(k.bold("\u{1F50D} Finding Your Project Information")),r.log(""),r.log(k.cyan("1. Project ID:")),r.log(" \u2022 Go to https://vercel.com/dashboard"),r.log(" \u2022 Select your project"),r.log(" \u2022 Navigate to Settings \u2192 General"),r.log(" \u2022 Copy the Project ID from the top of the page"),r.log(""),r.log(k.cyan("2. Team ID (if using team account):")),r.log(" \u2022 Go to https://vercel.com/dashboard"),r.log(" \u2022 Click on your team name in the top-left"),r.log(" \u2022 Go to Team Settings \u2192 General"),r.log(" \u2022 Copy the Team ID"),r.log(" \u2022 Leave empty to use your Vercel default team (every account has one)"),r.log(" \u2022 Belong to more than one team? Set this explicitly, or doorman will use whichever is your default"),r.log(""),r.log(k.bold("\u{1F511} Creating API Token")),r.log(""),r.log(k.cyan("Steps:")),r.log(" 1. Visit https://vercel.com/account/tokens"),r.log(' 2. Click "Create Token"'),r.log(' 3. Name: "Doorman Firewall Management"'),r.log(" 4. Expiration: Set as needed (90 days recommended)"),r.log(" 5. Scope: Select your project or team"),r.log(" 6. Copy the generated token"),r.log(""),r.log(k.cyan("Required Permissions:")),r.log(" \u2022 Read access to project settings"),r.log(" \u2022 Write access to firewall configuration"),r.log(""),r.log(k.bold("\u{1F30D} Environment Setup")),r.log(""),r.log(k.cyan("Set your API token as environment variable:")),r.log(""),r.log(k.green("# For current session:")),r.log('export VERCEL_TOKEN="your-token-here"'),r.log(""),r.log(k.green("# For permanent setup (add to ~/.bashrc or ~/.zshrc):")),r.log(`echo 'export VERCEL_TOKEN="your-token-here"' >> ~/.bashrc`),r.log(""),r.log(k.green("# Or create a .env file in your project:")),r.log('echo "VERCEL_TOKEN=your-token-here" > .env'),r.log(""),r.log(k.bold("\u{1F680} Quick Start Commands")),r.log(""),r.log(k.cyan("1. Initialize configuration:")),r.log(" doorman init --interactive"),r.log(""),r.log(k.cyan("2. Validate setup:")),r.log(" doorman validate"),r.log(""),r.log(k.cyan("3. Check current status:")),r.log(" doorman status"),r.log(""),r.log(k.cyan("4. View existing rules:")),r.log(" doorman list"),r.log(""),r.log(k.cyan("5. Sync your configuration:")),r.log(" doorman sync"),r.log(""),r.log(k.bold("\u{1F527} Common Issues")),r.log(""),r.log(k.yellow('\u274C "Project not found" error:')),r.log(" \u2022 Double-check your Project ID"),r.log(" \u2022 Ensure your token has access to the project"),r.log(" \u2022 Verify the project has Pro plan or higher"),r.log(""),r.log(k.yellow('\u274C "Unauthorized" error:')),r.log(" \u2022 Check your VERCEL_TOKEN is set correctly"),r.log(" \u2022 Ensure token hasn't expired"),r.log(" \u2022 Verify token has firewall permissions"),r.log(""),r.log(k.yellow('\u274C "Firewall not available" error:')),r.log(" \u2022 Upgrade to Pro plan or higher"),r.log(" \u2022 Contact Vercel support if issue persists"),r.log(""),r.log(k.bold("\u{1F4DA} Additional Resources")),r.log(""),r.log(k.cyan("Documentation:")),r.log(" \u2022 Doorman Docs: https://doorman.griffen.codes"),r.log(" \u2022 Vercel Firewall: https://vercel.com/docs/security/vercel-firewall"),r.log(" \u2022 API Reference: https://vercel.com/docs/rest-api/endpoints/firewall"),r.log(""),r.log(k.cyan("Support:")),r.log(" \u2022 GitHub Issues: https://github.com/gfargo/doorman/issues"),r.log(" \u2022 Vercel Support: https://vercel.com/help"),r.log(""),r.log(k.bold("\u{1F512} Security Best Practices")),r.log(""),r.log("\u2022 Store API tokens securely (use environment variables)"),r.log("\u2022 Set token expiration dates"),r.log("\u2022 Use principle of least privilege for token scopes"),r.log("\u2022 Regularly rotate API tokens"),r.log("\u2022 Test rules in staging before production"),r.log("\u2022 Keep backups of working configurations"),r.log(""),r.log(k.bold.green("\u2705 Ready to get started?")),r.log(k.dim("Run: ")+k.cyan("doorman init --interactive")),r.log("")};var ti={};z(ti,{builder:()=>ts,command:()=>Xr,desc:()=>es,handler:()=>is});E();import S from"chalk";var Xr="status",es="Show sync status between local and remote firewall configuration",ts={config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:Z,projectId:{alias:"p",type:"string",description:"Vercel Project ID (can be set in config file)"},teamId:{alias:"t",type:"string",description:"Vercel Team ID (can be set in config file)"},token:{type:"string",description:"Vercel API token (defaults to VERCEL_TOKEN env var)"},apiToken:{type:"string",description:"Cloudflare API token (defaults to CLOUDFLARE_API_TOKEN env var)"},zoneId:{type:"string",description:"Cloudflare Zone ID (defaults to CLOUDFLARE_ZONE_ID env var)"},accountId:{type:"string",description:"Cloudflare Account ID (optional)"},workspaceId:{type:"string",description:"Fastly Next-Gen WAF Workspace ID (defaults to FASTLY_WORKSPACE_ID env var)"},format:{alias:"f",type:"string",choices:["table","json"],description:"Output format",default:"table"},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1}},is=async i=>{await Y({config:i.config,provider:i.provider,projectId:i.projectId,teamId:i.teamId,token:i.token,apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId,workspaceId:i.workspaceId,debug:i.debug,ci:i.ci,errorContext:"checking status"},async({config:e,provider:t})=>{await os(t,se(e),i)})};async function os(i,e,t){let o=t.format==="json";o||r.start("Checking sync status...");let n=await i.getChanges(e),s=i.getHealthScore(e),a=e.metadata?.version,l=n.version!==void 0&&a!==void 0&&a!==n.version,c=!n.hasChanges&&!l;if(o){r.log(JSON.stringify({provider:i.name,inSync:c,version:{local:a??null,remote:n.version??null,matches:n.version!==void 0?!l:null},rules:{toAdd:n.rulesToAdd.length,toUpdate:n.rulesToUpdate.length,toDelete:n.rulesToDelete.length},ips:{toAdd:n.ipsToAdd?.length??0,toUpdate:n.ipsToUpdate?.length??0,toDelete:n.ipsToDelete?.length??0},health:s},null,2));return}r.log(S.bold(`
126
126
  \u{1F4CA} ${i.name} Sync Status Summary
127
- `)),t.version!==void 0&&(r.log(`${S.dim("Local Version:")} ${S.yellow(o??"unknown")}`),r.log(`${S.dim("Remote Version:")} ${S.yellow(t.version)}`),r.log(`${S.dim("Version Status:")} ${n?S.red("Out of sync"):S.green("In sync")}`),r.log("")),r.log(`${S.dim("Rules:")}`),r.log(` ${S.green("+")} ${t.rulesToAdd.length} to add`),r.log(` ${S.cyan("~")} ${t.rulesToUpdate.length} to update`),r.log(` ${S.red("-")} ${t.rulesToDelete.length} to delete`),r.log(`${S.dim("IPs:")}`),r.log(` ${S.green("+")} ${t.ipsToAdd?.length??0} to add`),r.log(` ${S.cyan("~")} ${t.ipsToUpdate?.length??0} to update`),r.log(` ${S.red("-")} ${t.ipsToDelete?.length??0} to delete`),r.log(""),!t.hasChanges&&!n?r.success(S.green("\u2705 Everything is in sync!")):r.warn(S.yellow("\u26A0\uFE0F Changes detected. Run `sync` to apply changes.")),r.log(`
128
- `+S.bold("\u{1F3E5} Configuration Health Check"));let s=i.getHealthScore(e);r.log(`${S.dim("Score:")} ${s.score}/100 (${s.grade})`),s.issues.forEach(a=>{let l=a.severity==="error"?S.red("\u2717"):a.severity==="warning"?S.yellow("\u26A0"):S.dim("\u2139");r.log(` ${l} [${a.category}] ${a.message}`),a.suggestion&&r.log(` ${S.dim(a.suggestion)}`)}),s.recommendations.length>0&&(r.log(S.dim(`
129
- Recommendations:`)),s.recommendations.forEach(a=>r.log(` - ${a}`)))}var ni={};z(ni,{builder:()=>ss,command:()=>ns,desc:()=>rs,handler:()=>as});E();import q from"chalk";var ns="sync",rs="Sync Vercel Firewall rules with config file",ss={config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:Z,projectId:{alias:"p",type:"string",description:"Vercel Project ID (can be set in config file)"},teamId:{alias:"t",type:"string",description:"Vercel Team ID (can be set in config file)"},token:{type:"string",description:"Vercel API token (defaults to VERCEL_TOKEN env var)"},apiToken:{type:"string",description:"Cloudflare API token (defaults to CLOUDFLARE_API_TOKEN env var)"},zoneId:{type:"string",description:"Cloudflare Zone ID (defaults to CLOUDFLARE_ZONE_ID env var)"},accountId:{type:"string",description:"Cloudflare Account ID (optional)"},workspaceId:{type:"string",description:"Fastly Next-Gen WAF Workspace ID (defaults to FASTLY_WORKSPACE_ID env var)"},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1},allowDeletions:{type:"boolean",description:"Required together with --ci to let a non-interactive sync delete existing rules/IPs. Without it, a --ci sync that would delete anything is refused rather than applied silently.",default:!1}},as=async i=>{await Y({config:i.config,provider:i.provider,projectId:i.projectId,teamId:i.teamId,token:i.token,apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId,workspaceId:i.workspaceId,debug:i.debug,ci:i.ci,errorContext:"syncing firewall rules"},async e=>{await ls(e.provider,e.config,i)})};async function ls(i,e,t){let o=se(e);r.start(q.magenta(`Calculating ${i.name} firewall configuration changes...`));let n=await i.getChanges(o),s=o.metadata?.version,a=n.version!==void 0&&s!==n.version;if(!n.hasChanges&&!a){r.success(q.green("No changes detected. Firewall rules are in sync."));return}r.log(q.bold(`
127
+ `)),n.version!==void 0&&(r.log(`${S.dim("Local Version:")} ${S.yellow(a??"unknown")}`),r.log(`${S.dim("Remote Version:")} ${S.yellow(n.version)}`),r.log(`${S.dim("Version Status:")} ${l?S.red("Out of sync"):S.green("In sync")}`),r.log("")),r.log(`${S.dim("Rules:")}`),r.log(` ${S.green("+")} ${n.rulesToAdd.length} to add`),r.log(` ${S.cyan("~")} ${n.rulesToUpdate.length} to update`),r.log(` ${S.red("-")} ${n.rulesToDelete.length} to delete`),r.log(`${S.dim("IPs:")}`),r.log(` ${S.green("+")} ${n.ipsToAdd?.length??0} to add`),r.log(` ${S.cyan("~")} ${n.ipsToUpdate?.length??0} to update`),r.log(` ${S.red("-")} ${n.ipsToDelete?.length??0} to delete`),r.log(""),c?r.success(S.green("\u2705 Everything is in sync!")):r.warn(S.yellow("\u26A0\uFE0F Changes detected. Run `sync` to apply changes.")),r.log(`
128
+ `+S.bold("\u{1F3E5} Configuration Health Check")),r.log(`${S.dim("Score:")} ${s.score}/100 (${s.grade})`),s.issues.forEach(d=>{let u=d.severity==="error"?S.red("\u2717"):d.severity==="warning"?S.yellow("\u26A0"):S.dim("\u2139");r.log(` ${u} [${d.category}] ${d.message}`),d.suggestion&&r.log(` ${S.dim(d.suggestion)}`)}),s.recommendations.length>0&&(r.log(S.dim(`
129
+ Recommendations:`)),s.recommendations.forEach(d=>r.log(` - ${d}`)))}var ni={};z(ni,{builder:()=>ss,command:()=>ns,desc:()=>rs,handler:()=>as});E();import q from"chalk";var ns="sync",rs="Sync Vercel Firewall rules with config file",ss={config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},provider:Z,projectId:{alias:"p",type:"string",description:"Vercel Project ID (can be set in config file)"},teamId:{alias:"t",type:"string",description:"Vercel Team ID (can be set in config file)"},token:{type:"string",description:"Vercel API token (defaults to VERCEL_TOKEN env var)"},apiToken:{type:"string",description:"Cloudflare API token (defaults to CLOUDFLARE_API_TOKEN env var)"},zoneId:{type:"string",description:"Cloudflare Zone ID (defaults to CLOUDFLARE_ZONE_ID env var)"},accountId:{type:"string",description:"Cloudflare Account ID (optional)"},workspaceId:{type:"string",description:"Fastly Next-Gen WAF Workspace ID (defaults to FASTLY_WORKSPACE_ID env var)"},debug:{type:"boolean",description:"Enable debug logging",default:!1},ci:{type:"boolean",description:"Run in CI mode (non-interactive)",default:!1},allowDeletions:{type:"boolean",description:"Required together with --ci to let a non-interactive sync delete existing rules/IPs. Without it, a --ci sync that would delete anything is refused rather than applied silently.",default:!1}},as=async i=>{await Y({config:i.config,provider:i.provider,projectId:i.projectId,teamId:i.teamId,token:i.token,apiToken:i.apiToken,zoneId:i.zoneId,accountId:i.accountId,workspaceId:i.workspaceId,debug:i.debug,ci:i.ci,errorContext:"syncing firewall rules"},async e=>{await ls(e.provider,e.config,i)})};async function ls(i,e,t){let o=se(e);r.start(q.magenta(`Calculating ${i.name} firewall configuration changes...`));let n=await i.getChanges(o),s=o.metadata?.version,a=n.version!==void 0&&s!==n.version;if(!n.hasChanges&&!a){r.success(q.green("No changes detected. Firewall rules are in sync."));return}r.log(q.bold(`
130
130
  Proposed Changes:
131
131
  `)),ii("Rules to add",n.rulesToAdd,q.green("+")),ii("Rules to update",n.rulesToUpdate,q.cyan("~")),ii("Rules to delete",n.rulesToDelete,q.red("-"));let l=n.ipsToAdd??[],c=n.ipsToUpdate??[],d=n.ipsToDelete??[];(l.length||c.length||d.length)&&(oi("IPs to add",l,q.green("+")),oi("IPs to update",c,q.cyan("~")),oi("IPs to delete",d,q.red("-"))),a&&r.log(` Remote config version: ${q.red(s??"unknown")} ${q.dim("->")} ${q.green(n.version)}`),r.start("Starting firewall rules sync...");let u=await i.syncRules(o,{force:t.ci,allowDeletions:t.allowDeletions});if(!u.success)throw new Error(`Sync failed: ${u.errors?.join(", ")||"unknown error"}`);r.success(q.green("Firewall rules sync completed successfully")),r.log(` Rules: ${u.rulesAdded} added, ${u.rulesUpdated} updated, ${u.rulesDeleted} deleted`),(u.ipsAdded||u.ipsUpdated||u.ipsDeleted)&&r.log(` IPs: ${u.ipsAdded??0} added, ${u.ipsUpdated??0} updated, ${u.ipsDeleted??0} deleted`),u.warnings?.forEach(g=>r.warn(g)),u.idRemappings?.length&&(r.info(q.yellow("Rule IDs were reassigned by the provider and written back to the local config:")),u.idRemappings.forEach(g=>{r.log(` - ${g.name}: ${q.red(g.oldId||"none")} ${q.dim("->")} ${q.green(g.newId)}`)}));let p=Rt(e,u);p.changed&&(await j(p.config,t.config),r.success(q.green("Updated local config with new version/metadata")))}function ii(i,e,t){e.length!==0&&(r.log(` ${t} ${i} (${e.length}):`),e.forEach(o=>r.log(` - ${o.name}`)))}function oi(i,e,t){e.length!==0&&(r.log(` ${t} ${i} (${e.length}):`),e.forEach(o=>r.log(` - ${o.ip}${o.hostname?` (${o.hostname})`:""}`)))}var si={};z(si,{builder:()=>ps,command:()=>ds,desc:()=>us,handler:()=>ms});E();import De from"chalk";import{LogLevels as cs}from"consola";var so={metadata:{title:"Block AI Bots Firewall Rule",reference:"https://vercel.com/templates/other/block-ai-bots-firewall-rule"},config:{rules:[{id:"rule_detect_ai_bots",name:"Detect AI Bots",description:"",conditionGroup:[{conditions:[{type:"user_agent",op:"re",value:"AI2Bot|Ai2Bot-Dolma|Amazonbot|Applebot|Applebot-Extended|Bytespider|CCBot|ChatGPT-User|Claude-Web|ClaudeBot|Diffbot|FacebookBot|FriendlyCrawler|GPTBot|Google-Extended|GoogleOther|GoogleOther-Image|GoogleOther-Video|ICC-Crawler|ImagesiftBot|Meta-ExternalAgent|Meta-ExternalFetcher|OAI-SearchBot|PerplexityBot|PetalBot|Scrapy|Timpibot|VelenPublicWebCrawler|Webzio-Extended|YouBot|anthropic-ai|cohere-ai|facebookexternalhit|img2dataset|omgili|omgilibot"}]}],action:{mitigate:{action:"log"}},active:!0}]}};var ao={metadata:{title:"Block Bad Bots Firewall Rule",reference:"https://vercel.com/templates/other/block-bad-bots-firewall-rule"},config:{rules:[{id:"rule_detect_bad_bots",name:"Detect Bad Bots",description:"",conditionGroup:[{conditions:[{type:"user_agent",op:"re",value:"01h4x.com|360Spider|404checker|404enemy|80legs|ADmantX|AIBOT|ALittle Client|ASPSeek|Abonti|Aboundex|Aboundexbot|Acunetix|AdsTxtCrawlerTP|AfD-Verbotsverfahren|AhrefsBot|AiHitBot|Aipbot|Alexibot|AllSubmitter|Alligator|AlphaBot|Anarchie|Anarchy|Anarchy99|Ankit|Anthill|Apexoo|Aspiegel|Asterias|Atomseobot|Attach|AwarioBot|AwarioRssBot|AwarioSmartBot|BBBike|BDCbot|BDFetch|BLEXBot|BackDoorBot|BackStreet|BackWeb|Backlink-Ceck|BacklinkCrawler|BacklinksExtendedBot|Badass|Bandit|Barkrowler|BatchFTP|Battleztar Bazinga|BetaBot|Bigfoot|Bitacle|BlackWidow|Black Hole|Blackboard|Blow|BlowFish|Boardreader|Bolt|BotALot|Brandprotect|Brandwatch|Buck|Buddy|BuiltBotTough|BuiltWith|Bullseye|BunnySlippers|BuzzSumo|Bytespider|CATExplorador|CCBot|CODE87|CSHttp|Calculon|CazoodleBot|Cegbfeieh|CensysInspect|ChatGPT-User|CheTeam|CheeseBot|CherryPicker|ChinaClaw|Chlooe|Citoid|Claritybot|ClaudeBot|Cliqzbot|Cloud mapping|Cocolyzebot|Cogentbot|Collector|Copier|CopyRightCheck|Copyscape|Cosmos|Craftbot|Crawling at Home Project|CrazyWebCrawler|Crescent|CrunchBot|Curious|Custo|CyotekWebCopy|DBLBot|DIIbot|DSearch|DTS Agent|DataCha0s|DatabaseDriverMysqli|Demon|Deusu|Devil|Digincore|DigitalPebble|Dirbuster|Disco|Discobot|Discoverybot|Dispatch|DittoSpyder|DnBCrawler-Analytics|DnyzBot|DomCopBot|DomainAppender|DomainCrawler|DomainSigmaCrawler|DomainStatsBot|Domains Project|Dotbot|Download Wonder|Dragonfly|Drip|ECCP/1.0|EMail Siphon|EMail Wolf|EasyDL|Ebingbong|Ecxi|EirGrabber|EroCrawler|Evil|Exabot|Express WebPictures|ExtLinksBot|Extractor|ExtractorPro|Extreme Picture Finder|EyeNetIE|Ezooms|FDM|FHscan|FacebookBot|FemtosearchBot|Fimap|Firefox/7.0|FlashGet|Flunky|Foobot|Freeuploader|FrontPage|Fuzz|FyberSpider|Fyrebot|G-i-g-a-b-o-t|GPTBot|GT::WWW|GalaxyBot|Genieo|GermCrawler|GetRight|GetWeb|Getintent|Gigabot|Go!Zilla|Go-Ahead-Got-It|GoZilla|Gotit|GrabNet|Grabber|Grafula|GrapeFX|GrapeshotCrawler|GridBot|HEADMasterSEO|HMView|HTMLparser|HTTP::Lite|HTTrack|Haansoft|HaosouSpider|Harvest|Havij|Heritrix|Hloader|HonoluluBot|Humanlinks|HybridBot|IDBTE4M|IDBot|IRLbot|Iblog|Id-search|IlseBot|Image Fetch|Image Sucker|ImagesiftBot|IndeedBot|Indy Library|InfoNaviRobot|InfoTekies|Information Security Team InfraSec Scanner|InfraSec Scanner|Intelliseek|InterGET|InternetMeasurement|InternetSeer|Internet Ninja|Iria|Iskanie|IstellaBot|JOC Web Spider|JamesBOT|Jbrofuzz|JennyBot|JetCar|Jetty|JikeSpider|Joomla|Jorgee|JustView|Jyxobot|Kenjin Spider|Keybot Translation-Search-Machine|Keyword Density|Kinza|Kozmosbot|LNSpiderguy|LWP::Simple|Lanshanbot|Larbin|Leap|LeechFTP|LeechGet|LexiBot|Lftp|LibWeb|Libwhisker|LieBaoFast|Lightspeedsystems|Likse|LinkScan|LinkWalker|Linkbot|LinkextractorPro|LinkpadBot|LinksManager|LinqiaMetadataDownloaderBot|LinqiaRSSBot|LinqiaScrapeBot|Lipperhey|Lipperhey Spider|Litemage_walker|Lmspider|Ltx71|MFC_Tear_Sample|MIDown tool|MIIxpc|MJ12bot|MQQBrowser|MSFrontPage|MSIECrawler|MTRobot|Mag-Net|Magnet|Mail.RU_Bot|Majestic-SEO|Majestic12|Majestic SEO|MarkMonitor|MarkWatch|Mass Downloader|Masscan|Mata Hari|MauiBot|Mb2345Browser|MeanPath Bot|Meanpathbot|Mediatoolkitbot|MegaIndex.ru|Metauri|MicroMessenger|Microsoft Data Access|Microsoft URL Control|Minefield|Mister PiX|Moblie Safari|Mojeek|Mojolicious|MolokaiBot|Morfeus Fucking Scanner|Mozlila|Mr.4x3|Msrabot|Musobot|NICErsPRO|NPbot|Name Intelligence|Nameprotect|Navroad|NearSite|Needle|Nessus|NetAnts|NetLyzer|NetMechanic|NetSpider|NetZIP|Net Vampire|Netcraft|Nettrack|Netvibes|NextGenSearchBot|Nibbler|Niki-bot|Nikto|NimbleCrawler|Nimbostratus|Ninja|Nmap|Nuclei|Nutch|Octopus|Offline Explorer|Offline Navigator|OnCrawl|OpenLinkProfiler|OpenVAS|Openfind|Openvas|OrangeBot|OrangeSpider|OutclicksBot|OutfoxBot|PECL::HTTP|PHPCrawl|POE-Component-Client-HTTP|PageAnalyzer|PageGrabber|PageScorer|PageThing.com|Page Analyzer|Pandalytics|Panscient|Papa Foto|Pavuk|PeoplePal|Petalbot|Pi-Monster|Picscout|Picsearch|PictureFinder|Piepmatz|Pimonster|Pixray|PleaseCrawl|Pockey|ProPowerBot|ProWebWalker|Probethenet|Proximic|Psbot|Pu_iN|Pump|PxBroker|PyCurl|QueryN Metasearch|Quick-Crawler|RSSingBot|Rainbot|RankActive|RankActiveLinkBot|RankFlex|RankingBot|RankingBot2|Rankivabot|RankurBot|Re-re|ReGet|RealDownload|Reaper|RebelMouse|Recorder|RedesScrapy|RepoMonkey|Ripper|RocketCrawler|Rogerbot|SBIder|SEOkicks|SEOkicks-Robot|SEOlyt|SEOlyticsCrawler|SEOprofiler|SEOstats|SISTRIX|SMTBot|SalesIntelligent|ScanAlert|Scanbot|ScoutJet|Scrapy|Screaming|ScreenerBot|ScrepyBot|Searchestate|SearchmetricsBot|Seekport|SeekportBot|SemanticJuice|Semrush|SemrushBot|SentiBot|SenutoBot|SeoSiteCheckup|SeobilityBot|Seomoz|Shodan|Siphon|SiteCheckerBotCrawler|SiteExplorer|SiteLockSpider|SiteSnagger|SiteSucker|Site Sucker|Sitebeam|Siteimprove|Sitevigil|SlySearch|SmartDownload|Snake|Snapbot|Snoopy|SocialRankIOBot|Sociscraper|Sogou web spider|Sosospider|Sottopop|SpaceBison|Spammen|SpankBot|Spanner|Spbot|Spider_Bot|Spider_Bot/3.0|Spinn3r|SputnikBot|Sqlmap|Sqlworm|Sqworm|Steeler|Stripper|Sucker|Sucuri|SuperBot|SuperHTTP|Surfbot|SurveyBot|Suzuran|Swiftbot|Szukacz|T0PHackTeam|T8Abot|Teleport|TeleportPro|Telesoft|Telesphoreo|Telesphorep|TheNomad|The Intraformant|Thumbor|TightTwatBot|TinyTestBot|Titan|Toata|Toweyabot|Tracemyfile|Trendiction|Trendictionbot|True_Robot|Turingos|Turnitin|TurnitinBot|TwengaBot|Twice|Typhoeus|URLy.Warning|URLy Warning|UnisterBot|Upflow|V-BOT|VB Project|VCI|Vacuum|Vagabondo|VelenPublicWebCrawler|VeriCiteCrawler|VidibleScraper|Virusdie|VoidEYE|Voil|Voltron|WASALive-Bot|WBSearchBot|WEBDAV|WISENutbot|WPScan|WWW-Collector-E|WWW-Mechanize|WWW::Mechanize|WWWOFFLE|Wallpapers|Wallpapers/3.0|WallpapersHD|WeSEE|WebAuto|WebBandit|WebCollage|WebCopier|WebEnhancer|WebFetch|WebFuck|WebGo IS|WebImageCollector|WebLeacher|WebPix|WebReaper|WebSauger|WebStripper|WebSucker|WebWhacker|WebZIP|Web Auto|Web Collage|Web Enhancer|Web Fetch|Web Fuck|Web Pix|Web Sauger|Web Sucker|Webalta|WebmasterWorldForumBot|Webshag|WebsiteExtractor|WebsiteQuester|Website Quester|Webster|Whack|Whacker|Whatweb|Who.is Bot|Widow|WinHTTrack|WiseGuys Robot|Wonderbot|Woobot|Wotbox|Wprecon|Xaldon WebSpider|Xaldon_WebSpider|Xenu|YaK|YoudaoBot|Zade|Zauba|Zermelo|Zeus|Zitebot|ZmEu|ZoomBot|ZoominfoBot|ZumBot|ZyBorg|adscanner|anthropic-ai|archive.org_bot|arquivo-web-crawler|arquivo.pt|autoemailspider|awario.com|backlink-check|cah.io.community|check1.exe|clark-crawler|coccocbot|cognitiveseo|cohere-ai|com.plumanalytics|crawl.sogou.com|crawler.feedback|crawler4j|dataforseo.com|dataforseobot|demandbase-bot|domainsproject.org|eCatch|evc-batch|facebookscraper|gopher|heritrix|imagesift.com|instabid|internetVista monitor|ips-agent|isitwp.com|iubenda-radar|linkdexbot|linkfluence|lwp-request|lwp-trivial|magpie-crawler|meanpathbot|mediawords|muhstik-scan|netEstate NE Crawler|oBot|omgili|openai|openai.com|page scorer|pcBrowser|plumanalytics|polaris version|probe-image-size|ripz|s1z.ru|satoristudio.net|scalaj-http|scan.lol|seobility|seocompany.store|seoscanners|seostar|serpstatbot|sexsearcher|sitechecker.pro|siteripz|sogouspider|sp_auditbot|spyfu|sysscan|tAkeOut|trendiction.com|trendiction.de|ubermetrics-technologies.com|voyagerx.com|webgains-bot|webmeup-crawler|webpros.com|webprosbot|x09Mozilla|x22Mozilla|xpymep1.exe|zauba.io|zgrab"}]}],action:{mitigate:{action:"log"}},active:!0}]}};var lo={metadata:{title:"Block OFAC-Sanctioned Countries",reference:"https://vercel.com/templates/other/block-ofac-sanctioned-countries-firewall-rule"},config:{rules:[{id:"rule_block_traffic_from_ofac_sanctioned_countries",name:"Block traffic from OFAC-sanctioned countries",description:"Blocks traffic from OFAC-sanctioned countries and enforces a one-hour persistent block after the first violation.",conditionGroup:[{conditions:[{type:"geo_country",op:"inc",value:["SY","IR","RU","CU","KP"]}]}],action:{mitigate:{action:"deny",actionDuration:"1h"}},active:!0}]}};var co={metadata:{title:"Deny Common WordPress URLs Firewall Rule",reference:"https://vercel.com/templates/other/block-wordpress-urls-firewall-rule"},config:{rules:[{name:"Deny WordPress URLs",description:"",conditionGroup:[{conditions:[{type:"path",op:"re",value:"/(wp-admin|wp-login\\.php|xmlrpc\\.php|wp-content|wp-includes|wp-signup\\.php|wp-activate\\.php|register\\.php|wp-register\\.php)"}]}],action:{mitigate:{action:"deny"}},active:!0}]}};var ri={"ai-bots":so,"bad-bots":ao,"block-ofac-sanctioned-countries":lo,wordpress:co};var uo=i=>ri[i]?.config;le();var ds="template [name]",us="Add a firewall rule template to your configuration",ps={name:{type:"string",description:"Name of the template to add"},config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},dryRun:{alias:"d",type:"boolean",description:"Preview changes without applying them",default:!1},debug:{type:"boolean",description:"Enable debug logging",default:!1}};function fs(i,e){let t=new Set(i.map(n=>n.name.toLowerCase())),o=e.filter(n=>t.has(n.name.toLowerCase())).map(n=>n.name);return[...new Set(o)]}var gs=()=>Object.keys(ri),ms=async i=>{try{i.debug&&(r.level=cs.debug),r.debug("Template command arguments:",i);let e=i.name;if(!e){let c=gs();e=await w("Select a template to add:",{type:"select",options:c,initial:c[0]})}let t=uo(e);t||(r.error(`Template not found: ${e}`),process.exit(1)),r.debug("Template content:",t),r.start("Loading current configuration...");let o=await W(i.config,"raw"),n=o.rules||[],s=fs(n,t.rules);if(i.dryRun){r.info(De.cyan(`
132
132
  Dry run - The following rules would be added:`)),r.log(JSON.stringify(t.rules,null,2)),s.length>0&&r.warn(De.yellow(`\u26A0\uFE0F Rule name(s) already exist in the configuration: ${s.join(", ")}`));return}if(s.length>0&&(r.warn(De.yellow(`\u26A0\uFE0F Rule name(s) already exist in the configuration: ${s.join(", ")}`)),!await w("Proceed anyway?",{type:"confirm",initial:!1}))){r.info("Cancelled.");return}let a={...o,rules:[...n,...t.rules]},l=ve.safeParse(a);l.success||(r.error(De.red("The resulting configuration would be invalid:")),l.error.errors.forEach(c=>{let d=c.path.join(".");r.error(De.red(` - ${d}: ${c.message}`))}),r.info(De.dim("Template was not applied. Fix the issues above and try again.")),process.exit(1)),r.start("Saving updated configuration..."),await j(a,i.config,{validate:!1}),r.success(De.green(`Successfully added template '${e}' to configuration`))}catch(e){H(e,"adding template")}};var ai={};z(ai,{builder:()=>ws,command:()=>hs,desc:()=>ys,handler:()=>Is});E();import X from"chalk";var hs="validate",ys="Validate firewall configuration file",ws={config:{alias:"c",type:"string",description:"Path to firewall config file (defaults to .doorman.json)"},verbose:{alias:"v",type:"boolean",description:"Show detailed validation results",default:!1}},Is=async i=>{try{let e=await W(i.config,"raw"),t=ye.getInstance(),o=me(e);i.verbose&&r.start(`Validating configuration file...
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gfargo/doorman",
3
- "version": "3.10.1",
3
+ "version": "3.11.0",
4
4
  "description": "Manage firewall rules as code across multiple providers",
5
5
  "publishConfig": {
6
6
  "access": "public"