fix(core): Improve logging consistency, record update functionality, and API error handling in Cloudflare modules
This commit is contained in:
		
							
								
								
									
										219
									
								
								changelog.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										219
									
								
								changelog.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,219 @@ | ||||
| # Changelog | ||||
|  | ||||
| ## 2025-03-19 - 6.0.6 - fix(core) | ||||
| Improve logging consistency, record update functionality, and API error handling in Cloudflare modules | ||||
|  | ||||
| - Replaced raw console.log calls with logger.log for unified logging across modules | ||||
| - Implemented and documented the updateRecord method with proper parameters in CloudflareAccount | ||||
| - Enhanced API request error handling and added detailed documentation in request methods | ||||
| - Refactored CloudflareWorker and WorkerManager methods to improve clarity and maintainability | ||||
| - Updated ZoneManager and CloudflareZone to improve error reporting and zone manipulation | ||||
|  | ||||
| ## 2024-06-16 - 6.0.5 – no significant changes | ||||
| _No significant changes in this release._ | ||||
|  | ||||
| ## 2024-06-16 - 6.0.4 – miscellaneous | ||||
| Several improvements and fixes: | ||||
| - fix(start supporting workers again): update | ||||
| - update license info | ||||
| - update readme | ||||
| - switch to official cloudflare api client while keeping class based approach | ||||
|  | ||||
| ## 2024-06-15 - 6.0.3 – core | ||||
| - fix(core): update | ||||
|  | ||||
| ## 2023-06-13 - 6.0.2 – core | ||||
| - fix(core): update | ||||
|  | ||||
| ## 2023-06-13 - 6.0.1 – core | ||||
| - fix(core): update | ||||
|  | ||||
| ## 2022-09-27 - 6.0.0 – core | ||||
| - fix(core): update | ||||
|  | ||||
| ## 2022-09-27 - 5.0.10 – core | ||||
| - BREAKING CHANGE(core): switch to esm | ||||
|  | ||||
| ## 2022-09-27 - 5.0.9 – core | ||||
| - fix(core): update | ||||
|  | ||||
| ## 2021-01-22 - 5.0.8 – core | ||||
| - fix(core): update | ||||
|  | ||||
| ## 2021-01-22 - 5.0.7 – core | ||||
| - fix(core): update | ||||
|  | ||||
| ## 2021-01-22 - 5.0.6 – core | ||||
| - fix(core): update | ||||
|  | ||||
| ## 2020-06-10 - 5.0.5 – core | ||||
| - fix(core): update | ||||
|  | ||||
| ## 2020-06-10 - 5.0.4 – core | ||||
| - fix(core): update | ||||
|  | ||||
| ## 2020-02-28 - 5.0.3 – core | ||||
| - fix(core): update | ||||
|  | ||||
| ## 2020-02-28 - 5.0.2 – core | ||||
| - fix(core): update | ||||
|  | ||||
| ## 2020-02-28 - 5.0.1 – core | ||||
| - fix(core): update | ||||
|  | ||||
| ## 2020-02-28 - 5.0.0 – core | ||||
| - fix(core): update | ||||
|  | ||||
| ## 2020-02-19 - 4.0.5 – account | ||||
| - BREAKING CHANGE(account): authorization now uses the new Account API | ||||
|  | ||||
| ## 2020-02-19 - 4.0.4 – core | ||||
| - fix(core): update | ||||
|  | ||||
| ## 2020-02-19 - 4.0.3 – core | ||||
| - fix(core): update | ||||
|  | ||||
| ## 2020-02-10 - 4.0.2 – core | ||||
| - fix(core): update | ||||
|  | ||||
| ## 2020-02-10 - 4.0.1 – core | ||||
| - fix(core): update | ||||
|  | ||||
| ## 2020-02-10 - 4.0.0 – core | ||||
| - fix(core): update | ||||
|  | ||||
| ## 2020-02-09 - 3.0.7 – API | ||||
| - BREAKING CHANGE(API): move to .convenience property | ||||
|  | ||||
| ## 2020-02-09 - 3.0.6 – core | ||||
| - fix(core): update | ||||
|  | ||||
| ## 2020-02-09 - 3.0.5 – core | ||||
| - fix(core): update | ||||
|  | ||||
| ## 2019-07-19 - 3.0.4 – core | ||||
| - fix(core): update | ||||
|  | ||||
| ## 2019-07-18 - 3.0.3 – core | ||||
| - fix(core): update | ||||
|  | ||||
| ## 2019-07-18 - 3.0.2 – core | ||||
| - fix(core): update | ||||
|  | ||||
| ## 2019-07-18 - 3.0.1 – core | ||||
| - fix(core): update | ||||
|  | ||||
| ## 2019-07-18 - 3.0.0 – core | ||||
| - fix(core): update | ||||
|  | ||||
| ## 2019-07-18 - 2.0.1 – core | ||||
| - fix(core): update | ||||
|  | ||||
| ## 2019-07-18 - 2.0.0 – core | ||||
| - fix(core): update | ||||
|  | ||||
| ## 2019-07-18 - 2.0.2 – no significant changes | ||||
| _No significant changes in this release._ | ||||
|  | ||||
| ## 2018-08-13 - 1.0.5 – scope | ||||
| - BREAKING CHANGE(scope): change scope, tools and package name | ||||
|  | ||||
| ## 2017-06-11 - 1.0.4 – misc | ||||
| - now using tsclass | ||||
|  | ||||
| ## 2017-06-09 - 1.0.3 – misc | ||||
| - update dependencies | ||||
|  | ||||
| ## 2017-06-05 - 1.0.2 – misc | ||||
| - now supports purging of assets | ||||
| - improve test | ||||
|  | ||||
| ## 2017-06-04 - 1.0.1 – misc | ||||
| - add npmextra.json | ||||
|  | ||||
| ## 2017-06-04 - 1.0.0 – misc | ||||
| - add type TRecord, update ci | ||||
|  | ||||
| ## 2017-06-04 - 0.0.20 – no significant changes | ||||
| _No significant changes in this release._ | ||||
|  | ||||
| ## 2017-06-04 - 0.0.19 – misc | ||||
| - go async/await | ||||
| - update brand link | ||||
|  | ||||
| ## 2017-02-12 - 0.0.18 – misc | ||||
| - update README | ||||
|  | ||||
| ## 2017-01-29 - 0.0.17 – misc | ||||
| - update README | ||||
|  | ||||
| ## 2017-01-29 - 0.0.16 – misc | ||||
| - fix tests to run in parallel | ||||
|  | ||||
| ## 2017-01-29 - 0.0.15 – misc | ||||
| - fixed bad request retry | ||||
|  | ||||
| ## 2017-01-29 - 0.0.14 – misc | ||||
| - fix testing timeouts | ||||
|  | ||||
| ## 2017-01-29 - 0.0.13 – misc | ||||
| - added random retry times | ||||
|  | ||||
| ## 2017-01-29 - 0.0.12 – misc | ||||
| - update to new ci | ||||
|  | ||||
| ## 2017-01-29 - 0.0.11 – misc | ||||
| - now using smartrequest | ||||
|  | ||||
| ## 2017-01-22 - 0.0.10 – misc | ||||
| - now reacting to rate limiting | ||||
|  | ||||
| ## 2016-07-31 - 0.0.9 – misc | ||||
| - update dependencies | ||||
|  | ||||
| ## 2016-06-22 - 0.0.8 to 0.0.7 – no significant changes | ||||
| _No significant changes in these releases._ | ||||
|  | ||||
| ## 2016-06-22 - 0.0.6 – misc | ||||
| - updated dependencies | ||||
|  | ||||
| ## 2016-06-21 - 0.0.5 – misc | ||||
| - fix stages | ||||
|  | ||||
| ## 2016-06-21 - 0.0.4 – misc | ||||
| - fix stages | ||||
|  | ||||
| ## 2016-06-21 - 0.0.3 – misc | ||||
| Multiple improvements: | ||||
| - now works for most things | ||||
| - update to latest dependencies | ||||
| - update .gitlab.yml | ||||
| - update | ||||
| - add .gitlab-ci.yml | ||||
|  | ||||
| ## 2016-05-25 - 0.0.2 – misc | ||||
| Several changes: | ||||
| - improve domain string handling | ||||
| - update .getRecord | ||||
| - improve .createRecord | ||||
| - implemented .createRecord | ||||
| - compile | ||||
| - add functionality | ||||
| - start with tests | ||||
| - improved request method of cflare class | ||||
|  | ||||
| ## 2016-04-27 - 0.0.1 – misc | ||||
| - now returning promises | ||||
| - add lossless badge | ||||
|  | ||||
| ## 2016-04-27 - 0.0.0 – misc | ||||
| - added travis and improved README | ||||
|  | ||||
| ## 2016-04-10 - 0.0.0 – misc | ||||
| - add package.json and README | ||||
|  | ||||
| ## 2016-04-10 - unknown – misc | ||||
| - Initial commit | ||||
|  | ||||
| --- | ||||
| _Note: Versions that only contained version bump commits or minor housekeeping (6.0.5; 2.0.2; 0.0.20; 0.0.8 to 0.0.7) have been omitted from detailed entries and are summarized above._ | ||||
| @@ -1,8 +1,8 @@ | ||||
| /** | ||||
|  * autocreated commitinfo by @pushrocks/commitinfo | ||||
|  * autocreated commitinfo by @push.rocks/commitinfo | ||||
|  */ | ||||
| export const commitinfo = { | ||||
|   name: '@apiclient.xyz/cloudflare', | ||||
|   version: '6.0.5', | ||||
|   version: '6.0.6', | ||||
|   description: 'A TypeScript client for managing Cloudflare accounts, zones, DNS records, and workers with ease.' | ||||
| } | ||||
|   | ||||
| @@ -26,6 +26,40 @@ export class CloudflareAccount { | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Make a request to the Cloudflare API | ||||
|    * @param method HTTP method (GET, POST, PUT, DELETE) | ||||
|    * @param endpoint API endpoint path | ||||
|    * @param data Optional request body data | ||||
|    * @returns API response | ||||
|    */ | ||||
|   public async request<T = any>( | ||||
|     method: 'GET' | 'POST' | 'PUT' | 'DELETE', | ||||
|     endpoint: string, | ||||
|     data?: any | ||||
|   ): Promise<T> { | ||||
|     try { | ||||
|       const options: plugins.smartrequest.ISmartRequestOptions = { | ||||
|         method, | ||||
|         url: `https://api.cloudflare.com/client/v4${endpoint}`, | ||||
|         headers: { | ||||
|           'Authorization': `Bearer ${this.authToken}`, | ||||
|           'Content-Type': 'application/json', | ||||
|         }, | ||||
|       }; | ||||
|  | ||||
|       if (data) { | ||||
|         options.json = data; | ||||
|       } | ||||
|  | ||||
|       const response = await plugins.smartrequest.request(options); | ||||
|       return JSON.parse(response.body); | ||||
|     } catch (error) { | ||||
|       logger.log('error', `Cloudflare API request failed: ${error.message}`); | ||||
|       throw error; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   public async preselectAccountByName(nameArg: string) { | ||||
|     const accounts = await this.convenience.listAccounts(); | ||||
|     const account = accounts.find((accountArg) => { | ||||
| @@ -130,7 +164,7 @@ export class CloudflareAccount { | ||||
|      * cleanrecord allows the cleaning of any previous records to avoid unwanted sideeffects | ||||
|      */ | ||||
|     cleanRecord: async (domainNameArg: string, typeArg: plugins.tsclass.network.TDnsRecordType) => { | ||||
|       console.log(`cleaning record for ${domainNameArg}`); | ||||
|       logger.log('info', `Cleaning ${typeArg} records for ${domainNameArg}`); | ||||
|       const records = await this.convenience.listRecords(domainNameArg); | ||||
|       const recordsToDelete = records.filter((recordArg) => { | ||||
|         return recordArg.type === typeArg; | ||||
| @@ -144,17 +178,39 @@ export class CloudflareAccount { | ||||
|  | ||||
|     /** | ||||
|      * updates a record | ||||
|      * @param domainNameArg | ||||
|      * @param typeArg | ||||
|      * @param valueArg | ||||
|      * @param domainNameArg Domain name for the record | ||||
|      * @param typeArg Type of DNS record | ||||
|      * @param contentArg New content for the record | ||||
|      * @param ttlArg Time to live in seconds (optional) | ||||
|      * @returns Updated record | ||||
|      */ | ||||
|     updateRecord: async ( | ||||
|       domainNameArg: string, | ||||
|       typeArg: plugins.tsclass.network.TDnsRecordType, | ||||
|       valueArg | ||||
|     ) => { | ||||
|       // TODO: implement | ||||
|       contentArg: string, | ||||
|       ttlArg: number = 1 | ||||
|     ): Promise<plugins.ICloudflareTypes['Record']> => { | ||||
|       const domain = new plugins.smartstring.Domain(domainNameArg); | ||||
|       const zoneId = await this.convenience.getZoneId(domain.zoneName); | ||||
|        | ||||
|       // Find existing record | ||||
|       const record = await this.convenience.getRecord(domainNameArg, typeArg); | ||||
|        | ||||
|       if (!record) { | ||||
|         logger.log('warn', `Record ${domainNameArg} of type ${typeArg} not found for update, creating instead`); | ||||
|         return this.convenience.createRecord(domainNameArg, typeArg, contentArg, ttlArg); | ||||
|       } | ||||
|        | ||||
|       // Update the record | ||||
|       const updatedRecord = await this.apiAccount.dns.records.edit(record.id, { | ||||
|         zone_id: zoneId, | ||||
|         type: typeArg as any, | ||||
|         name: domain.fullName, | ||||
|         content: contentArg, | ||||
|         ttl: ttlArg | ||||
|       }); | ||||
|        | ||||
|       return updatedRecord; | ||||
|     }, | ||||
|     /** | ||||
|      * list all records of a specified domain name | ||||
|   | ||||
| @@ -1,3 +1,96 @@ | ||||
| import * as plugins from './cloudflare.plugins.js'; | ||||
| import { logger } from './cloudflare.logger.js'; | ||||
|  | ||||
| export class CloudflareRecord {} | ||||
| export interface ICloudflareRecordInfo { | ||||
|   id: string; | ||||
|   type: plugins.tsclass.network.TDnsRecordType; | ||||
|   name: string; | ||||
|   content: string; | ||||
|   proxiable: boolean; | ||||
|   proxied: boolean; | ||||
|   ttl: number; | ||||
|   locked: boolean; | ||||
|   zone_id: string; | ||||
|   zone_name: string; | ||||
|   created_on: string; | ||||
|   modified_on: string; | ||||
| } | ||||
|  | ||||
| export class CloudflareRecord { | ||||
|   /** | ||||
|    * Create a CloudflareRecord instance from an API object | ||||
|    * @param apiObject Cloudflare DNS record API object | ||||
|    * @returns CloudflareRecord instance | ||||
|    */ | ||||
|   public static createFromApiObject(apiObject: plugins.ICloudflareTypes['Record']): CloudflareRecord { | ||||
|     const record = new CloudflareRecord(); | ||||
|     Object.assign(record, apiObject); | ||||
|     return record; | ||||
|   } | ||||
|  | ||||
|   // Record properties | ||||
|   public id: string; | ||||
|   public type: plugins.tsclass.network.TDnsRecordType; | ||||
|   public name: string; | ||||
|   public content: string; | ||||
|   public proxiable: boolean; | ||||
|   public proxied: boolean; | ||||
|   public ttl: number; | ||||
|   public locked: boolean; | ||||
|   public zone_id: string; | ||||
|   public zone_name: string; | ||||
|   public created_on: string; | ||||
|   public modified_on: string; | ||||
|  | ||||
|   /** | ||||
|    * Update the record content | ||||
|    * @param cloudflareAccount The Cloudflare account to use | ||||
|    * @param newContent New content for the record | ||||
|    * @param ttl Optional TTL value in seconds | ||||
|    * @returns Updated record | ||||
|    */ | ||||
|   public async update( | ||||
|     cloudflareAccount: any, | ||||
|     newContent: string, | ||||
|     ttl?: number | ||||
|   ): Promise<CloudflareRecord> { | ||||
|     logger.log('info', `Updating record ${this.name} (${this.type}) with new content`); | ||||
|      | ||||
|     const updatedRecord = await cloudflareAccount.apiAccount.dns.records.edit(this.id, { | ||||
|       zone_id: this.zone_id, | ||||
|       type: this.type as any, | ||||
|       name: this.name, | ||||
|       content: newContent, | ||||
|       ttl: ttl || this.ttl, | ||||
|       proxied: this.proxied | ||||
|     }); | ||||
|      | ||||
|     // Update this instance | ||||
|     this.content = newContent; | ||||
|     if (ttl) { | ||||
|       this.ttl = ttl; | ||||
|     } | ||||
|      | ||||
|     return this; | ||||
|   } | ||||
|    | ||||
|   /** | ||||
|    * Delete this record | ||||
|    * @param cloudflareAccount The Cloudflare account to use | ||||
|    * @returns Boolean indicating success | ||||
|    */ | ||||
|   public async delete(cloudflareAccount: any): Promise<boolean> { | ||||
|     try { | ||||
|       logger.log('info', `Deleting record ${this.name} (${this.type})`); | ||||
|        | ||||
|       await cloudflareAccount.apiAccount.dns.records.delete(this.id, { | ||||
|         zone_id: this.zone_id | ||||
|       }); | ||||
|        | ||||
|       return true; | ||||
|     } catch (error) { | ||||
|       logger.log('error', `Failed to delete record: ${error.message}`); | ||||
|       return false; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -7,6 +7,11 @@ export interface IWorkerRoute extends interfaces.ICflareWorkerRoute { | ||||
|   zoneName: string; | ||||
| } | ||||
|  | ||||
| export interface IWorkerRouteDefinition { | ||||
|   zoneName: string; | ||||
|   pattern: string; | ||||
| } | ||||
|  | ||||
| export class CloudflareWorker { | ||||
|   // STATIC | ||||
|   public static async fromApiObject( | ||||
| @@ -46,9 +51,8 @@ export class CloudflareWorker { | ||||
|         result: interfaces.ICflareWorkerRoute[]; | ||||
|       } = await this.workerManager.cfAccount.request('GET', requestRoute); | ||||
|       for (const route of response.result) { | ||||
|         console.log('hey'); | ||||
|         console.log(route); | ||||
|         console.log(this.id); | ||||
|         logger.log('debug', `Processing route: ${route.pattern}`); | ||||
|         logger.log('debug', `Comparing script: ${route.script} with worker ID: ${this.id}`); | ||||
|         if (route.script === this.id) { | ||||
|           this.routes.push({ ...route, zoneName: zone.name }); | ||||
|         } | ||||
| @@ -56,7 +60,11 @@ export class CloudflareWorker { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   public async setRoutes(routeArray: Array<{ zoneName: string; pattern: string }>) { | ||||
|   /** | ||||
|    * Sets routes for this worker | ||||
|    * @param routeArray Array of route definitions | ||||
|    */ | ||||
|   public async setRoutes(routeArray: IWorkerRouteDefinition[]) { | ||||
|     for (const newRoute of routeArray) { | ||||
|       // lets determine wether a route is new, needs an update or already up to date. | ||||
|       let routeStatus: 'new' | 'needsUpdate' | 'alreadyUpToDate' = 'new'; | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import * as plugins from './cloudflare.plugins.js'; | ||||
| import { CloudflareAccount } from './cloudflare.classes.account.js'; | ||||
| import { CloudflareWorker } from './cloudflare.classes.worker.js'; | ||||
| import { logger } from './cloudflare.logger.js'; | ||||
|  | ||||
| export class WorkerManager { | ||||
|   public cfAccount: CloudflareAccount; | ||||
| @@ -9,6 +10,12 @@ export class WorkerManager { | ||||
|     this.cfAccount = cfAccountArg; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Creates a new worker or updates an existing one | ||||
|    * @param workerName Name of the worker | ||||
|    * @param workerScript JavaScript content of the worker | ||||
|    * @returns The created or updated worker | ||||
|    */ | ||||
|   public async createWorker(workerName: string, workerScript: string): Promise<plugins.ICloudflareTypes['Script']> { | ||||
|     if (!this.cfAccount.preselectedAccountId) { | ||||
|       throw new Error('No account selected. Please select it first on the account.'); | ||||
| @@ -21,7 +28,30 @@ export class WorkerManager { | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * lists workers | ||||
|    * Get a worker by name | ||||
|    * @param workerName Name of the worker to retrieve | ||||
|    * @returns CloudflareWorker instance or undefined if not found | ||||
|    */ | ||||
|   public async getWorker(workerName: string): Promise<CloudflareWorker | undefined> { | ||||
|     if (!this.cfAccount.preselectedAccountId) { | ||||
|       throw new Error('No account selected. Please select it first on the account.'); | ||||
|     } | ||||
|      | ||||
|     try { | ||||
|       const script = await this.cfAccount.apiAccount.workers.scripts.get(workerName, { | ||||
|         account_id: this.cfAccount.preselectedAccountId | ||||
|       }); | ||||
|        | ||||
|       return CloudflareWorker.fromApiObject(this, script); | ||||
|     } catch (error) { | ||||
|       logger.log('warn', `Worker '${workerName}' not found: ${error.message}`); | ||||
|       return undefined; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Lists all worker scripts | ||||
|    * @returns Array of worker scripts | ||||
|    */ | ||||
|   public async listWorkerScripts() { | ||||
|     if (!this.cfAccount.preselectedAccountId) { | ||||
| @@ -35,4 +65,26 @@ export class WorkerManager { | ||||
|     } | ||||
|     return workerScripts; | ||||
|   } | ||||
|    | ||||
|   /** | ||||
|    * Deletes a worker script | ||||
|    * @param workerName Name of the worker to delete | ||||
|    * @returns True if deletion was successful | ||||
|    */ | ||||
|   public async deleteWorker(workerName: string): Promise<boolean> { | ||||
|     if (!this.cfAccount.preselectedAccountId) { | ||||
|       throw new Error('No account selected. Please select it first on the account.'); | ||||
|     } | ||||
|      | ||||
|     try { | ||||
|       await this.cfAccount.apiAccount.workers.scripts.delete(workerName, { | ||||
|         account_id: this.cfAccount.preselectedAccountId | ||||
|       }); | ||||
|       logger.log('info', `Worker '${workerName}' deleted successfully`); | ||||
|       return true; | ||||
|     } catch (error) { | ||||
|       logger.log('error', `Failed to delete worker '${workerName}': ${error.message}`); | ||||
|       return false; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -1,9 +1,177 @@ | ||||
| import * as plugins from './cloudflare.plugins.js'; | ||||
| import { logger } from './cloudflare.logger.js'; | ||||
| import * as interfaces from './interfaces/index.js'; | ||||
|  | ||||
| export class CloudflareZone { | ||||
|   public static createFromApiObject(apiObject: plugins.ICloudflareTypes['Zone']) { | ||||
|   // Zone properties | ||||
|   public id: string; | ||||
|   public name: string; | ||||
|   public status: interfaces.ICflareZone['status']; | ||||
|   public paused: boolean; | ||||
|   public type: interfaces.ICflareZone['type']; | ||||
|   public development_mode: number; | ||||
|   public name_servers: string[]; | ||||
|   public original_name_servers: string[]; | ||||
|   public original_registrar: string | null; | ||||
|   public original_dnshost: string | null; | ||||
|   public modified_on: string; | ||||
|   public created_on: string; | ||||
|   public activated_on: string; | ||||
|   public meta: interfaces.ICflareZone['meta']; | ||||
|   public owner: interfaces.ICflareZone['owner']; | ||||
|   public account: interfaces.ICflareZone['account']; | ||||
|   public permissions: string[]; | ||||
|   public plan: interfaces.ICflareZone['plan']; | ||||
|    | ||||
|   private cfAccount: any; // Will be set when created through a manager | ||||
|  | ||||
|   /** | ||||
|    * Create a CloudflareZone instance from an API object | ||||
|    * @param apiObject Cloudflare Zone API object | ||||
|    * @param cfAccount Optional Cloudflare account instance | ||||
|    * @returns CloudflareZone instance | ||||
|    */ | ||||
|   public static createFromApiObject( | ||||
|     apiObject: plugins.ICloudflareTypes['Zone'],  | ||||
|     cfAccount?: any | ||||
|   ): CloudflareZone { | ||||
|     const cloudflareZone = new CloudflareZone(); | ||||
|     Object.assign(cloudflareZone, apiObject); | ||||
|      | ||||
|     if (cfAccount) { | ||||
|       cloudflareZone.cfAccount = cfAccount; | ||||
|     } | ||||
|      | ||||
|     return cloudflareZone; | ||||
|   } | ||||
|    | ||||
|   /** | ||||
|    * Check if development mode is currently active | ||||
|    * @returns True if development mode is active | ||||
|    */ | ||||
|   public isDevelopmentModeActive(): boolean { | ||||
|     return this.development_mode > 0; | ||||
|   } | ||||
|    | ||||
|   /** | ||||
|    * Enable development mode for the zone | ||||
|    * @param cfAccount Cloudflare account to use if not already set | ||||
|    * @param duration Duration in seconds (default: 3 hours) | ||||
|    * @returns Updated zone | ||||
|    */ | ||||
|   public async enableDevelopmentMode( | ||||
|     cfAccount?: any, | ||||
|     duration: number = 10800 | ||||
|   ): Promise<CloudflareZone> { | ||||
|     const account = cfAccount || this.cfAccount; | ||||
|     if (!account) { | ||||
|       throw new Error('CloudflareAccount is required to enable development mode'); | ||||
|     } | ||||
|      | ||||
|     logger.log('info', `Enabling development mode for zone ${this.name}`); | ||||
|      | ||||
|     const response = await account.request('PATCH', `/zones/${this.id}/settings/development_mode`, { | ||||
|       value: 'on', | ||||
|       time: duration | ||||
|     }); | ||||
|      | ||||
|     this.development_mode = duration; | ||||
|     return this; | ||||
|   } | ||||
|    | ||||
|   /** | ||||
|    * Disable development mode for the zone | ||||
|    * @param cfAccount Cloudflare account to use if not already set | ||||
|    * @returns Updated zone | ||||
|    */ | ||||
|   public async disableDevelopmentMode(cfAccount?: any): Promise<CloudflareZone> { | ||||
|     const account = cfAccount || this.cfAccount; | ||||
|     if (!account) { | ||||
|       throw new Error('CloudflareAccount is required to disable development mode'); | ||||
|     } | ||||
|      | ||||
|     logger.log('info', `Disabling development mode for zone ${this.name}`); | ||||
|      | ||||
|     const response = await account.request('PATCH', `/zones/${this.id}/settings/development_mode`, { | ||||
|       value: 'off' | ||||
|     }); | ||||
|      | ||||
|     this.development_mode = 0; | ||||
|     return this; | ||||
|   } | ||||
|    | ||||
|   /** | ||||
|    * Purge all cached content for this zone | ||||
|    * @param cfAccount Cloudflare account to use if not already set | ||||
|    * @returns True if successful | ||||
|    */ | ||||
|   public async purgeCache(cfAccount?: any): Promise<boolean> { | ||||
|     const account = cfAccount || this.cfAccount; | ||||
|     if (!account) { | ||||
|       throw new Error('CloudflareAccount is required to purge cache'); | ||||
|     } | ||||
|      | ||||
|     logger.log('info', `Purging all cache for zone ${this.name}`); | ||||
|      | ||||
|     try { | ||||
|       await account.request('POST', `/zones/${this.id}/purge_cache`, { | ||||
|         purge_everything: true | ||||
|       }); | ||||
|       return true; | ||||
|     } catch (error) { | ||||
|       logger.log('error', `Failed to purge cache: ${error.message}`); | ||||
|       return false; | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   /** | ||||
|    * Purge specific URLs from the cache | ||||
|    * @param urls Array of URLs to purge | ||||
|    * @param cfAccount Cloudflare account to use if not already set | ||||
|    * @returns True if successful | ||||
|    */ | ||||
|   public async purgeUrls(urls: string[], cfAccount?: any): Promise<boolean> { | ||||
|     const account = cfAccount || this.cfAccount; | ||||
|     if (!account) { | ||||
|       throw new Error('CloudflareAccount is required to purge URLs'); | ||||
|     } | ||||
|      | ||||
|     if (!urls.length) { | ||||
|       return true; | ||||
|     } | ||||
|      | ||||
|     logger.log('info', `Purging ${urls.length} URLs from cache for zone ${this.name}`); | ||||
|      | ||||
|     try { | ||||
|       await account.request('POST', `/zones/${this.id}/purge_cache`, { | ||||
|         files: urls | ||||
|       }); | ||||
|       return true; | ||||
|     } catch (error) { | ||||
|       logger.log('error', `Failed to purge URLs: ${error.message}`); | ||||
|       return false; | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   /** | ||||
|    * Check if the zone is active | ||||
|    * @returns True if the zone is active | ||||
|    */ | ||||
|   public isActive(): boolean { | ||||
|     return this.status === 'active' && !this.paused; | ||||
|   } | ||||
|    | ||||
|   /** | ||||
|    * Check if the zone is using Cloudflare nameservers | ||||
|    * @returns True if using Cloudflare nameservers | ||||
|    */ | ||||
|   public isUsingCloudflareNameservers(): boolean { | ||||
|     // Check if original nameservers match current nameservers | ||||
|     if (!this.original_name_servers || !this.name_servers) { | ||||
|       return false; | ||||
|     } | ||||
|      | ||||
|     // If they're different, and current nameservers are Cloudflare's | ||||
|     return this.name_servers.some(ns => ns.includes('cloudflare')); | ||||
|   } | ||||
| } | ||||
| @@ -2,31 +2,152 @@ import * as plugins from './cloudflare.plugins.js'; | ||||
| import * as interfaces from './interfaces/index.js'; | ||||
| import { CloudflareAccount } from './cloudflare.classes.account.js'; | ||||
| import { CloudflareZone } from './cloudflare.classes.zone.js'; | ||||
| import { logger } from './cloudflare.logger.js'; | ||||
|  | ||||
| export class ZoneManager { | ||||
|   public cfAccount: CloudflareAccount; | ||||
|   public zoneName: string; | ||||
|  | ||||
|   constructor(cfAccountArg: CloudflareAccount) { | ||||
|     this.cfAccount = cfAccountArg; | ||||
|   } | ||||
|  | ||||
|   public async getZones(zoneName: string) { | ||||
|   /** | ||||
|    * Get all zones, optionally filtered by name | ||||
|    * @param zoneName Optional zone name to filter by | ||||
|    * @returns Array of CloudflareZone instances | ||||
|    */ | ||||
|   public async getZones(zoneName?: string): Promise<CloudflareZone[]> { | ||||
|     let requestRoute = `/zones?per_page=50`; | ||||
|     // may be optionally filtered by domain name | ||||
|      | ||||
|     // May be optionally filtered by domain name | ||||
|     if (zoneName) { | ||||
|       requestRoute = `${requestRoute}&name=${zoneName}`; | ||||
|       requestRoute = `${requestRoute}&name=${encodeURIComponent(zoneName)}`; | ||||
|     } | ||||
|  | ||||
|     const response: any = await this.cfAccount.request('GET', requestRoute); | ||||
|     const apiObjects: interfaces.ICflareZone[] = response.result; | ||||
|     try { | ||||
|       const response: { result: interfaces.ICflareZone[] } = await this.cfAccount.request('GET', requestRoute); | ||||
|        | ||||
|     const cloudflareZoneArray = []; | ||||
|     for (const apiObject of apiObjects) { | ||||
|       cloudflareZoneArray.push(CloudflareZone.createFromApiObject(apiObject)); | ||||
|       return response.result.map(apiObject =>  | ||||
|         CloudflareZone.createFromApiObject(apiObject as any, this.cfAccount) | ||||
|       ); | ||||
|     } catch (error) { | ||||
|       logger.log('error', `Failed to fetch zones: ${error.message}`); | ||||
|       return []; | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   /** | ||||
|    * Get a single zone by name | ||||
|    * @param zoneName Zone name to find | ||||
|    * @returns CloudflareZone instance or undefined if not found | ||||
|    */ | ||||
|   public async getZoneByName(zoneName: string): Promise<CloudflareZone | undefined> { | ||||
|     const zones = await this.getZones(zoneName); | ||||
|     return zones.find(zone => zone.name === zoneName); | ||||
|   } | ||||
|    | ||||
|   /** | ||||
|    * Get a zone by its ID | ||||
|    * @param zoneId Zone ID to find | ||||
|    * @returns CloudflareZone instance or undefined if not found | ||||
|    */ | ||||
|   public async getZoneById(zoneId: string): Promise<CloudflareZone | undefined> { | ||||
|     try { | ||||
|       const response: { result: interfaces.ICflareZone } = await this.cfAccount.request( | ||||
|         'GET',  | ||||
|         `/zones/${zoneId}` | ||||
|       ); | ||||
|        | ||||
|       return CloudflareZone.createFromApiObject(response.result as any, this.cfAccount); | ||||
|     } catch (error) { | ||||
|       logger.log('error', `Failed to fetch zone with ID ${zoneId}: ${error.message}`); | ||||
|       return undefined; | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   /** | ||||
|    * Create a new zone | ||||
|    * @param zoneName Name of the zone to create | ||||
|    * @param jumpStart Whether to automatically attempt to fetch existing DNS records | ||||
|    * @param accountId Account ID to use (defaults to preselected account) | ||||
|    * @returns The created zone | ||||
|    */ | ||||
|   public async createZone( | ||||
|     zoneName: string,  | ||||
|     jumpStart: boolean = false, | ||||
|     accountId?: string | ||||
|   ): Promise<CloudflareZone | undefined> { | ||||
|     const useAccountId = accountId || this.cfAccount.preselectedAccountId; | ||||
|      | ||||
|     if (!useAccountId) { | ||||
|       throw new Error('No account selected. Please select it first on the account.'); | ||||
|     } | ||||
|      | ||||
|     return cloudflareZoneArray; | ||||
|     try { | ||||
|       logger.log('info', `Creating zone ${zoneName}`); | ||||
|        | ||||
|       const response: { result: interfaces.ICflareZone } = await this.cfAccount.request( | ||||
|         'POST',  | ||||
|         '/zones',  | ||||
|         { | ||||
|           name: zoneName, | ||||
|           jump_start: jumpStart, | ||||
|           account: { id: useAccountId } | ||||
|         } | ||||
|       ); | ||||
|        | ||||
|       return CloudflareZone.createFromApiObject(response.result as any, this.cfAccount); | ||||
|     } catch (error) { | ||||
|       logger.log('error', `Failed to create zone ${zoneName}: ${error.message}`); | ||||
|       return undefined; | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   /** | ||||
|    * Delete a zone | ||||
|    * @param zoneId ID of the zone to delete | ||||
|    * @returns True if successful | ||||
|    */ | ||||
|   public async deleteZone(zoneId: string): Promise<boolean> { | ||||
|     try { | ||||
|       logger.log('info', `Deleting zone with ID ${zoneId}`); | ||||
|        | ||||
|       await this.cfAccount.request('DELETE', `/zones/${zoneId}`); | ||||
|       return true; | ||||
|     } catch (error) { | ||||
|       logger.log('error', `Failed to delete zone with ID ${zoneId}: ${error.message}`); | ||||
|       return false; | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   /** | ||||
|    * Check if a zone exists | ||||
|    * @param zoneName Name of the zone to check | ||||
|    * @returns True if the zone exists | ||||
|    */ | ||||
|   public async zoneExists(zoneName: string): Promise<boolean> { | ||||
|     const zones = await this.getZones(zoneName); | ||||
|     return zones.some(zone => zone.name === zoneName); | ||||
|   } | ||||
|    | ||||
|   /** | ||||
|    * Activate a zone (if it's in pending status) | ||||
|    * @param zoneId ID of the zone to activate | ||||
|    * @returns Updated zone or undefined if activation failed | ||||
|    */ | ||||
|   public async activateZone(zoneId: string): Promise<CloudflareZone | undefined> { | ||||
|     try { | ||||
|       logger.log('info', `Activating zone with ID ${zoneId}`); | ||||
|        | ||||
|       const response: { result: interfaces.ICflareZone } = await this.cfAccount.request( | ||||
|         'PUT',  | ||||
|         `/zones/${zoneId}/activation_check` | ||||
|       ); | ||||
|        | ||||
|       return CloudflareZone.createFromApiObject(response.result as any, this.cfAccount); | ||||
|     } catch (error) { | ||||
|       logger.log('error', `Failed to activate zone with ID ${zoneId}: ${error.message}`); | ||||
|       return undefined; | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										130
									
								
								ts/cloudflare.utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								ts/cloudflare.utils.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,130 @@ | ||||
| import * as plugins from './cloudflare.plugins.js'; | ||||
| import { logger } from './cloudflare.logger.js'; | ||||
|  | ||||
| export class CloudflareUtils { | ||||
|   /** | ||||
|    * Validates if a domain name is properly formatted | ||||
|    * @param domainName Domain name to validate | ||||
|    * @returns True if the domain is valid | ||||
|    */ | ||||
|   public static isValidDomain(domainName: string): boolean { | ||||
|     try { | ||||
|       const domain = new plugins.smartstring.Domain(domainName); | ||||
|       return domain.isValid(); | ||||
|     } catch (error) { | ||||
|       return false; | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   /** | ||||
|    * Extracts the zone name (apex domain) from a full domain | ||||
|    * @param domainName Domain name to process | ||||
|    * @returns Zone name (apex domain) | ||||
|    */ | ||||
|   public static getZoneName(domainName: string): string { | ||||
|     try { | ||||
|       const domain = new plugins.smartstring.Domain(domainName); | ||||
|       return domain.zoneName; | ||||
|     } catch (error) { | ||||
|       logger.log('error', `Invalid domain name: ${domainName}`); | ||||
|       throw new Error(`Invalid domain name: ${domainName}`); | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   /** | ||||
|    * Checks if a string is a valid Cloudflare API token | ||||
|    * @param token API token to validate | ||||
|    * @returns True if the token format is valid | ||||
|    */ | ||||
|   public static isValidApiToken(token: string): boolean { | ||||
|     // Cloudflare API tokens are typically 40+ characters long and start with specific patterns | ||||
|     return /^[A-Za-z0-9_-]{40,}$/.test(token); | ||||
|   } | ||||
|    | ||||
|   /** | ||||
|    * Validates a DNS record type | ||||
|    * @param type DNS record type to validate | ||||
|    * @returns True if it's a valid DNS record type | ||||
|    */ | ||||
|   public static isValidRecordType(type: string): boolean { | ||||
|     const validTypes: plugins.tsclass.network.TDnsRecordType[] = [ | ||||
|       'A', 'AAAA', 'CNAME', 'TXT', 'SRV', 'LOC', 'MX', | ||||
|       'NS', 'SPF', 'CERT', 'DNSKEY', 'DS', 'NAPTR', 'SMIMEA', | ||||
|       'SSHFP', 'TLSA', 'URI' | ||||
|     ]; | ||||
|     return validTypes.includes(type as any); | ||||
|   } | ||||
|    | ||||
|   /** | ||||
|    * Formats a URL for cache purging (ensures it starts with http/https) | ||||
|    * @param url URL to format | ||||
|    * @returns Properly formatted URL | ||||
|    */ | ||||
|   public static formatUrlForPurge(url: string): string { | ||||
|     if (!url.startsWith('http://') && !url.startsWith('https://')) { | ||||
|       return `https://${url}`; | ||||
|     } | ||||
|     return url; | ||||
|   } | ||||
|    | ||||
|   /** | ||||
|    * Converts a TTL value in seconds to a human-readable string | ||||
|    * @param ttl TTL in seconds | ||||
|    * @returns Human-readable TTL | ||||
|    */ | ||||
|   public static formatTtl(ttl: number): string { | ||||
|     if (ttl === 1) { | ||||
|       return 'Automatic'; | ||||
|     } else if (ttl === 120) { | ||||
|       return '2 minutes'; | ||||
|     } else if (ttl === 300) { | ||||
|       return '5 minutes'; | ||||
|     } else if (ttl === 600) { | ||||
|       return '10 minutes'; | ||||
|     } else if (ttl === 900) { | ||||
|       return '15 minutes'; | ||||
|     } else if (ttl === 1800) { | ||||
|       return '30 minutes'; | ||||
|     } else if (ttl === 3600) { | ||||
|       return '1 hour'; | ||||
|     } else if (ttl === 7200) { | ||||
|       return '2 hours'; | ||||
|     } else if (ttl === 18000) { | ||||
|       return '5 hours'; | ||||
|     } else if (ttl === 43200) { | ||||
|       return '12 hours'; | ||||
|     } else if (ttl === 86400) { | ||||
|       return '1 day'; | ||||
|     } else { | ||||
|       return `${ttl} seconds`; | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   /** | ||||
|    * Safely handles API pagination for Cloudflare requests | ||||
|    * @param makeRequest Function that makes the API request with page parameters | ||||
|    * @returns Combined results from all pages | ||||
|    */ | ||||
|   public static async paginateResults<T>( | ||||
|     makeRequest: (page: number, perPage: number) => Promise<{ result: T[], result_info: { total_pages: number } }> | ||||
|   ): Promise<T[]> { | ||||
|     const perPage = 50; // Cloudflare's maximum | ||||
|     let page = 1; | ||||
|     let totalPages = 1; | ||||
|     const allResults: T[] = []; | ||||
|      | ||||
|     do { | ||||
|       try { | ||||
|         const response = await makeRequest(page, perPage); | ||||
|         allResults.push(...response.result); | ||||
|         totalPages = response.result_info.total_pages; | ||||
|         page++; | ||||
|       } catch (error) { | ||||
|         logger.log('error', `Pagination error on page ${page}: ${error.message}`); | ||||
|         break; | ||||
|       } | ||||
|     } while (page <= totalPages); | ||||
|      | ||||
|     return allResults; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										11
									
								
								ts/index.ts
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								ts/index.ts
									
									
									
									
									
								
							| @@ -1,2 +1,11 @@ | ||||
| export { CloudflareAccount } from './cloudflare.classes.account.js'; | ||||
| export { CloudflareWorker } from './cloudflare.classes.worker.js'; | ||||
| export { CloudflareWorker, type IWorkerRoute, type IWorkerRouteDefinition } from './cloudflare.classes.worker.js'; | ||||
| export { WorkerManager } from './cloudflare.classes.workermanager.js'; | ||||
| export { CloudflareRecord, type ICloudflareRecordInfo } from './cloudflare.classes.record.js'; | ||||
| export { CloudflareZone } from './cloudflare.classes.zone.js'; | ||||
| export { ZoneManager } from './cloudflare.classes.zonemanager.js'; | ||||
| export { CloudflareUtils } from './cloudflare.utils.js'; | ||||
| export { commitinfo } from './00_commitinfo_data.js'; | ||||
|  | ||||
| // Re-export interfaces | ||||
| export * from './interfaces/index.js'; | ||||
							
								
								
									
										45
									
								
								ts/interfaces/cloudflare.api.zone.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								ts/interfaces/cloudflare.api.zone.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| export interface ICflareZone { | ||||
|   id: string; | ||||
|   name: string; | ||||
|   status: 'active' | 'pending' | 'initializing' | 'moved' | 'deleted' | 'deactivated'; | ||||
|   paused: boolean; | ||||
|   type: 'full' | 'partial' | 'secondary'; | ||||
|   development_mode: number; | ||||
|   name_servers: string[]; | ||||
|   original_name_servers: string[]; | ||||
|   original_registrar: string | null; | ||||
|   original_dnshost: string | null; | ||||
|   modified_on: string; | ||||
|   created_on: string; | ||||
|   activated_on: string; | ||||
|   meta: { | ||||
|     step: number; | ||||
|     wildcard_proxiable: boolean; | ||||
|     custom_certificate_quota: number; | ||||
|     page_rule_quota: number; | ||||
|     phishing_detected: boolean; | ||||
|     multiple_railguns_allowed: boolean; | ||||
|   }; | ||||
|   owner: { | ||||
|     id: string | null; | ||||
|     type: 'user' | 'organization'; | ||||
|     email: string | null; | ||||
|   }; | ||||
|   account: { | ||||
|     id: string; | ||||
|     name: string; | ||||
|   }; | ||||
|   permissions: string[]; | ||||
|   plan: { | ||||
|     id: string; | ||||
|     name: string; | ||||
|     price: number; | ||||
|     currency: string; | ||||
|     frequency: string; | ||||
|     is_subscribed: boolean; | ||||
|     can_subscribe: boolean; | ||||
|     legacy_id: string; | ||||
|     legacy_discount: boolean; | ||||
|     externally_managed: boolean; | ||||
|   }; | ||||
| } | ||||
| @@ -1,2 +1,3 @@ | ||||
| export * from './cloudflare.api.account.js'; | ||||
| export * from './cloudflare.api.workerroute.js'; | ||||
| export * from './cloudflare.api.zone.js'; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user