fix(core): Improve logging consistency, record update functionality, and API error handling in Cloudflare modules
This commit is contained in:
parent
4600749442
commit
ba49c42dd8
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
|
||||
@ -208,4 +264,4 @@ export class CloudflareAccount {
|
||||
await this.convenience.removeRecord(dnsChallenge.hostName, 'TXT');
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
@ -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';
|
||||
@ -90,4 +98,4 @@ export class CloudflareWorker {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
const cloudflareZoneArray = [];
|
||||
for (const apiObject of apiObjects) {
|
||||
cloudflareZoneArray.push(CloudflareZone.createFromApiObject(apiObject));
|
||||
try {
|
||||
const response: { result: interfaces.ICflareZone[] } = await this.cfAccount.request('GET', requestRoute);
|
||||
|
||||
return response.result.map(apiObject =>
|
||||
CloudflareZone.createFromApiObject(apiObject as any, this.cfAccount)
|
||||
);
|
||||
} catch (error) {
|
||||
logger.log('error', `Failed to fetch zones: ${error.message}`);
|
||||
return [];
|
||||
}
|
||||
|
||||
return cloudflareZoneArray;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.');
|
||||
}
|
||||
|
||||
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';
|
||||
|
Loading…
x
Reference in New Issue
Block a user