Compare commits

...

6 Commits

Author SHA1 Message Date
18afafd3b3 v7.0.0
Some checks failed
Default (tags) / security (push) Failing after 1m35s
Default (tags) / test (push) Failing after 41s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-11-18 20:39:08 +00:00
5ce1520e2b BREAKING CHANGE(core): Introduce RecordManager and ConvenientDnsProvider; rename list/get methods for consistent API and deprecate convenience namespace 2025-11-18 20:39:08 +00:00
39d53da4e6 v6.4.3
Some checks failed
Default (tags) / security (push) Successful in 34s
Default (tags) / test (push) Failing after 44s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-11-17 23:22:00 +00:00
002ac3ae01 fix(cloudflare.plugins): Switch to smartrequest namespace export and improve request typing and JSON parsing 2025-11-17 23:22:00 +00:00
0184371635 v6.4.2
Some checks failed
Default (tags) / security (push) Successful in 42s
Default (tags) / test (push) Failing after 37s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-11-17 23:10:24 +00:00
038f56b0ce fix(core): Switch to SmartRequest fluent API and improve Cloudflare API request handling 2025-11-17 23:10:24 +00:00
14 changed files with 11091 additions and 4804 deletions

View File

@@ -1,5 +1,36 @@
# Changelog # Changelog
## 2025-11-18 - 7.0.0 - BREAKING CHANGE(core)
Introduce RecordManager and ConvenientDnsProvider; rename list/get methods for consistent API and deprecate convenience namespace
- Add RecordManager with listRecords, getRecord, createRecord, updateRecord, deleteRecord and cleanRecords to centralize DNS record operations
- Add ConvenientDnsProvider adapter and CloudflareAccount.getConvenientDnsProvider() to provide IConvenientDnsProvider compatibility for third-party modules
- Rename methods to consistent list* naming: worker.getRoutes -> worker.listRoutes, WorkerManager.listWorkerScripts -> WorkerManager.listWorkers, ZoneManager.getZones -> ZoneManager.listZones, convenience.listRecords -> recordManager.listRecords
- Add ZoneManager.getZoneId() and ZoneManager.purgeZone() (zone cache purge helper)
- Deprecate the legacy convenience.* methods (getZoneId, getRecord, createRecord, removeRecord, cleanRecord, updateRecord, listRecords, listZones, isDomainSupported, purgeZone, acmeSetDnsChallenge, acmeRemoveDnsChallenge) — kept for backward compatibility but marked deprecated
- Export RecordManager and ConvenientDnsProvider from ts/index.ts and expose cfAccount.recordManager on CloudflareAccount
- Update tests to use new method names (listWorkers) and extend test runner timeout; package.json test script updated
- Documentation (readme) updated to describe the new manager-based API and migration guide; prepares project for major version 7.0.0
## 2025-11-17 - 6.4.3 - fix(cloudflare.plugins)
Switch to smartrequest namespace export and improve request typing and JSON parsing
- Export smartrequest as a namespace from cloudflare.plugins (replaced named SmartRequest/CoreResponse exports)
- Use plugins.smartrequest.SmartRequest.create() when building HTTP requests
- Type response as InstanceType<typeof plugins.smartrequest.CoreResponse> to match the new smartrequest export shape
- Safer JSON parsing: cast result of response.json() to the expected generic instead of relying on a generic json<T>() call and provide a text fallback when parsing fails
- Adjust imports/usages to align with @push.rocks/smartrequest namespace usage
## 2025-11-17 - 6.4.2 - fix(core)
Switch to SmartRequest fluent API and improve Cloudflare API request handling
- Upgrade runtime dependencies: @push.rocks/smartlog -> ^3.1.10, @push.rocks/smartrequest -> ^5.0.1, @push.rocks/smartstring -> ^4.1.0, @tsclass/tsclass -> ^9.3.0, cloudflare -> ^5.2.0
- Upgrade devDependencies: @git.zone/tsbuild -> ^3.1.0, @git.zone/tsrun -> ^2.0.0, @git.zone/tstest -> ^2.8.2, @push.rocks/qenv -> ^6.1.3, openapi-typescript -> ^7.10.1
- Export SmartRequest and CoreResponse from cloudflare.plugins to align with smartrequest v5 API
- Refactor CloudflareAccount.request to use SmartRequest fluent builder, add detailed logging, default JSON Content-Type, support multipart/form-data via formData(), and use appropriate HTTP method helpers
- Improve response parsing: return a safe fallback when JSON parsing fails by reading response.text() and include a concise message; better HTTP error logging including response body text
- Update usages to rely on the new request behavior (zones/workers managers use account.request for endpoints not covered by the official client)
## 2025-04-30 - 6.4.1 - fix(ci) ## 2025-04-30 - 6.4.1 - fix(ci)
Update CI workflows, repository URL, and apply minor code formatting fixes Update CI workflows, repository URL, and apply minor code formatting fixes

7414
deno.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,13 @@
{ {
"name": "@apiclient.xyz/cloudflare", "name": "@apiclient.xyz/cloudflare",
"version": "6.4.1", "version": "7.0.0",
"private": false, "private": false,
"description": "A TypeScript client for managing Cloudflare accounts, zones, DNS records, and workers with ease.", "description": "A TypeScript client for managing Cloudflare accounts, zones, DNS records, and workers with ease.",
"main": "dist_ts/index.js", "main": "dist_ts/index.js",
"typings": "dist_ts/index.d.ts", "typings": "dist_ts/index.d.ts",
"type": "module", "type": "module",
"scripts": { "scripts": {
"test": "(tstest test/)", "test": "(tstest test/ --verbose --timeout 600)",
"build": "(tsbuild --web --allowimplicitany)", "build": "(tsbuild --web --allowimplicitany)",
"buildDocs": "tsdoc", "buildDocs": "tsdoc",
"updateOpenapi": "openapi-typescript https://raw.githubusercontent.com/cloudflare/api-schemas/main/openapi.yaml --output ts/openapi.spec.ts" "updateOpenapi": "openapi-typescript https://raw.githubusercontent.com/cloudflare/api-schemas/main/openapi.yaml --output ts/openapi.spec.ts"
@@ -36,21 +36,20 @@
"homepage": "https://gitlab.com/mojoio/cloudflare#readme", "homepage": "https://gitlab.com/mojoio/cloudflare#readme",
"dependencies": { "dependencies": {
"@push.rocks/smartdelay": "^3.0.1", "@push.rocks/smartdelay": "^3.0.1",
"@push.rocks/smartlog": "^3.0.2", "@push.rocks/smartlog": "^3.1.10",
"@push.rocks/smartpromise": "^4.2.3", "@push.rocks/smartpromise": "^4.2.3",
"@push.rocks/smartrequest": "^2.1.0", "@push.rocks/smartrequest": "^5.0.1",
"@push.rocks/smartstring": "^4.0.5", "@push.rocks/smartstring": "^4.1.0",
"@tsclass/tsclass": "^9.1.0", "@tsclass/tsclass": "^9.3.0",
"cloudflare": "^4.2.0" "cloudflare": "^5.2.0"
}, },
"devDependencies": { "devDependencies": {
"@git.zone/tsbuild": "^2.3.2", "@git.zone/tsbuild": "^3.1.0",
"@git.zone/tsrun": "^1.3.3", "@git.zone/tsrun": "^2.0.0",
"@git.zone/tstest": "^1.0.96", "@git.zone/tstest": "^2.8.2",
"@push.rocks/qenv": "^6.1.0", "@push.rocks/qenv": "^6.1.3",
"@push.rocks/tapbundle": "^6.0.0",
"@types/node": "^22.15.3", "@types/node": "^22.15.3",
"openapi-typescript": "^7.6.1" "openapi-typescript": "^7.10.1"
}, },
"files": [ "files": [
"ts/**/*", "ts/**/*",

7654
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

213
readme.md
View File

@@ -8,10 +8,10 @@ An elegant, class-based TypeScript client for the Cloudflare API that makes mana
## Features ## Features
- **Comprehensive coverage** of the Cloudflare API including zones, DNS records, and Workers - **Comprehensive coverage** of the Cloudflare API including zones, DNS records, and Workers
- **Class-based design** with intuitive methods for all Cloudflare operations - **Clean manager-based architecture** with intuitive methods for all Cloudflare operations
- **Strong TypeScript typing** for excellent IDE autocompletion and type safety - **Strong TypeScript typing** for excellent IDE autocompletion and type safety
- **Fully integrated with the official Cloudflare client** using modern async iterators - **Fully integrated with the official Cloudflare client** using modern async iterators
- **Convenience methods** for common operations to reduce boilerplate code - **IConvenientDnsProvider compatibility** for seamless integration with third-party modules
- **Promise-based API** for easy async/await usage - **Promise-based API** for easy async/await usage
- **ESM compatible** for modern JavaScript projects - **ESM compatible** for modern JavaScript projects
- **Comprehensive error handling** for robust applications - **Comprehensive error handling** for robust applications
@@ -37,12 +37,13 @@ import * as cflare from '@apiclient.xyz/cloudflare';
// Initialize with your API token // Initialize with your API token
const cfAccount = new cflare.CloudflareAccount('your-cloudflare-api-token'); const cfAccount = new cflare.CloudflareAccount('your-cloudflare-api-token');
// Use convenience methods for quick operations // Use the clean manager-based API
await cfAccount.convenience.createRecord('subdomain.example.com', 'A', '192.0.2.1', 3600); await cfAccount.recordManager.createRecord('subdomain.example.com', 'A', '192.0.2.1', 3600);
await cfAccount.zoneManager.purgeZone('example.com');
// Or work with the powerful class-based API // Or use the IConvenientDnsProvider interface for third-party modules
const zone = await cfAccount.zoneManager.getZoneByName('example.com'); const dnsProvider = cfAccount.getConvenientDnsProvider();
await zone.purgeCache(); await dnsProvider.createRecord('subdomain.example.com', 'A', '192.0.2.1');
``` ```
## Usage Guide ## Usage Guide
@@ -68,20 +69,20 @@ const myAccounts = await cfAccount.listAccounts();
Zones represent your domains in Cloudflare. Zones represent your domains in Cloudflare.
```typescript ```typescript
// Get all zones in your account // List all zones in your account
const allZones = await cfAccount.convenience.listZones(); const allZones = await cfAccount.zoneManager.listZones();
// Get a specific zone by domain name // Get a specific zone by domain name
const myZone = await cfAccount.zoneManager.getZoneByName('example.com'); const myZone = await cfAccount.zoneManager.getZoneByName('example.com');
// Get zone ID directly // Get zone ID directly
const zoneId = await cfAccount.convenience.getZoneId('example.com'); const zoneId = await cfAccount.zoneManager.getZoneId('example.com');
// Create a new zone // Create a new zone
const newZone = await cfAccount.zoneManager.createZone('newdomain.com'); const newZone = await cfAccount.zoneManager.createZone('newdomain.com');
// Purge cache for an entire zone // Purge cache for an entire zone
await cfAccount.convenience.purgeZone('example.com'); await cfAccount.zoneManager.purgeZone('example.com');
// Or using the zone object // Or using the zone object
await myZone.purgeCache(); await myZone.purgeCache();
@@ -99,36 +100,40 @@ const usingCfNameservers = await myZone.isUsingCloudflareNameservers();
### DNS Record Management ### DNS Record Management
Manage DNS records for your domains with ease. Manage DNS records for your domains with ease using the RecordManager.
```typescript ```typescript
// List all DNS records for a domain // List all DNS records for a domain
const allRecords = await cfAccount.convenience.listRecords('example.com'); const allRecords = await cfAccount.recordManager.listRecords('example.com');
// Create a new DNS record // Create a new DNS record
await cfAccount.convenience.createRecord('api.example.com', 'A', '192.0.2.1', 3600); await cfAccount.recordManager.createRecord('api.example.com', 'A', '192.0.2.1', 3600);
// Create a CNAME record // Create a CNAME record
await cfAccount.convenience.createRecord('www.example.com', 'CNAME', 'example.com', 3600); await cfAccount.recordManager.createRecord('www.example.com', 'CNAME', 'example.com', 3600);
// Get a specific DNS record // Get a specific DNS record
const record = await cfAccount.convenience.getRecord('api.example.com', 'A'); const record = await cfAccount.recordManager.getRecord('api.example.com', 'A');
// Update a DNS record (automatically creates it if it doesn't exist) // Update a DNS record (automatically creates it if it doesn't exist)
await cfAccount.convenience.updateRecord('api.example.com', 'A', '192.0.2.2', 3600); await cfAccount.recordManager.updateRecord('api.example.com', 'A', '192.0.2.2', 3600);
// Remove a specific DNS record // Delete a specific DNS record
await cfAccount.convenience.removeRecord('api.example.com', 'A'); await cfAccount.recordManager.deleteRecord('api.example.com', 'A');
// Clean (remove) all records of a specific type // Clean (remove) all records of a specific type for a domain
await cfAccount.convenience.cleanRecord('example.com', 'TXT'); await cfAccount.recordManager.cleanRecords('example.com', 'TXT');
// For third-party modules requiring IConvenientDnsProvider interface
const dnsProvider = cfAccount.getConvenientDnsProvider();
await dnsProvider.createRecord('api.example.com', 'A', '192.0.2.1');
// Support for ACME DNS challenges (for certificate issuance) // Support for ACME DNS challenges (for certificate issuance)
await cfAccount.convenience.acmeSetDnsChallenge({ await dnsProvider.acmeSetDnsChallenge({
hostName: '_acme-challenge.example.com', hostName: '_acme-challenge.example.com',
challenge: 'token-validation-string', challenge: 'token-validation-string',
}); });
await cfAccount.convenience.acmeRemoveDnsChallenge({ await dnsProvider.acmeRemoveDnsChallenge({
hostName: '_acme-challenge.example.com', hostName: '_acme-challenge.example.com',
challenge: 'token-validation-string', challenge: 'token-validation-string',
}); });
@@ -148,7 +153,7 @@ addEventListener('fetch', event => {
const worker = await cfAccount.workerManager.createWorker('my-worker', workerScript); const worker = await cfAccount.workerManager.createWorker('my-worker', workerScript);
// List all workers // List all workers
const allWorkers = await cfAccount.workerManager.listWorkerScripts(); const allWorkers = await cfAccount.workerManager.listWorkers();
// Get an existing worker // Get an existing worker
const existingWorker = await cfAccount.workerManager.getWorker('my-worker'); const existingWorker = await cfAccount.workerManager.getWorker('my-worker');
@@ -165,8 +170,8 @@ await worker.setRoutes([
}, },
]); ]);
// Get all routes for a worker // List all routes for a worker
const routes = await worker.getRoutes(); const routes = await worker.listRoutes();
// Update a worker's script // Update a worker's script
await worker.updateScript(` await worker.updateScript(`
@@ -200,9 +205,9 @@ async function manageCloudflare() {
console.log(`Zone active: ${await myZone.isActive()}`); console.log(`Zone active: ${await myZone.isActive()}`);
console.log(`Using CF nameservers: ${await myZone.isUsingCloudflareNameservers()}`); console.log(`Using CF nameservers: ${await myZone.isUsingCloudflareNameservers()}`);
// Configure DNS // Configure DNS using RecordManager
await cfAccount.convenience.createRecord('api.example.com', 'A', '192.0.2.1'); await cfAccount.recordManager.createRecord('api.example.com', 'A', '192.0.2.1');
await cfAccount.convenience.createRecord('www.example.com', 'CNAME', 'example.com'); await cfAccount.recordManager.createRecord('www.example.com', 'CNAME', 'example.com');
// Create a worker and set up routes // Create a worker and set up routes
const workerCode = ` const workerCode = `
@@ -247,42 +252,78 @@ class CloudflareAccount {
async listAccounts(): Promise<Array<ICloudflareTypes['Account']>>; async listAccounts(): Promise<Array<ICloudflareTypes['Account']>>;
async preselectAccountByName(accountName: string): Promise<void>; async preselectAccountByName(accountName: string): Promise<void>;
// Managers // Managers - Clean, logical API
readonly zoneManager: ZoneManager; readonly zoneManager: ZoneManager;
readonly workerManager: WorkerManager; readonly workerManager: WorkerManager;
readonly recordManager: RecordManager;
// Get IConvenientDnsProvider adapter for third-party modules
getConvenientDnsProvider(): ConvenientDnsProvider;
// Official Cloudflare client // Official Cloudflare client
readonly apiAccount: cloudflare.Cloudflare; readonly apiAccount: cloudflare.Cloudflare;
// Convenience namespace with helper methods // ⚠️ Deprecated: convenience namespace (kept for backward compatibility)
readonly convenience: { // Use the managers instead: recordManager, zoneManager, workerManager
// Zone operations readonly convenience: { /* deprecated methods */ };
listZones(domainName?: string): Promise<CloudflareZone[]>; }
getZoneId(domainName: string): Promise<string>; ```
purgeZone(domainName: string): Promise<void>;
// DNS operations ### RecordManager
listRecords(domainName: string): Promise<CloudflareRecord[]>;
getRecord(domainName: string, recordType: string): Promise<CloudflareRecord | undefined>;
createRecord(
domainName: string,
recordType: string,
content: string,
ttl?: number,
): Promise<any>;
updateRecord(
domainName: string,
recordType: string,
content: string,
ttl?: number,
): Promise<any>;
removeRecord(domainName: string, recordType: string): Promise<any>;
cleanRecord(domainName: string, recordType: string): Promise<void>;
// ACME operations Clean DNS record management (recommended over deprecated convenience methods).
acmeSetDnsChallenge(dnsChallenge: IDnsChallenge): Promise<any>;
acmeRemoveDnsChallenge(dnsChallenge: IDnsChallenge): Promise<any>; ```typescript
}; class RecordManager {
async listRecords(domainName: string): Promise<CloudflareRecord[]>;
async getRecord(domainName: string, recordType: string): Promise<CloudflareRecord | undefined>;
async createRecord(domainName: string, recordType: string, content: string, ttl?: number): Promise<CloudflareRecord>;
async updateRecord(domainName: string, recordType: string, content: string, ttl?: number): Promise<CloudflareRecord>;
async deleteRecord(domainName: string, recordType: string): Promise<void>;
async cleanRecords(domainName: string, recordType: string): Promise<void>;
}
```
### ZoneManager
```typescript
class ZoneManager {
async listZones(zoneName?: string): Promise<CloudflareZone[]>;
async getZoneById(zoneId: string): Promise<CloudflareZone | undefined>;
async getZoneByName(zoneName: string): Promise<CloudflareZone | undefined>;
async getZoneId(domainName: string): Promise<string>;
async createZone(zoneName: string): Promise<CloudflareZone | undefined>;
async deleteZone(zoneId: string): Promise<boolean>;
async purgeZone(domainName: string): Promise<void>;
}
```
### WorkerManager
```typescript
class WorkerManager {
async listWorkers(): Promise<Array<ICloudflareTypes['Script']>>;
async getWorker(workerName: string): Promise<CloudflareWorker | undefined>;
async createWorker(workerName: string, workerScript: string): Promise<CloudflareWorker>;
async deleteWorker(workerName: string): Promise<boolean>;
}
```
### ConvenientDnsProvider
Adapter for third-party modules requiring `IConvenientDnsProvider` interface.
```typescript
class ConvenientDnsProvider implements IConvenientDnsProvider {
async createRecord(domainName: string, recordType: string, content: string, ttl?: number): Promise<any>;
async updateRecord(domainName: string, recordType: string, content: string, ttl?: number): Promise<any>;
async removeRecord(domainName: string, recordType: string): Promise<any>;
async getRecord(domainName: string, recordType: string): Promise<any | undefined>;
async listRecords(domainName: string): Promise<any[]>;
async cleanRecord(domainName: string, recordType: string): Promise<void>;
async isDomainSupported(domainName: string): Promise<boolean>;
async acmeSetDnsChallenge(dnsChallenge: IDnsChallenge): Promise<void>;
async acmeRemoveDnsChallenge(dnsChallenge: IDnsChallenge): Promise<void>;
} }
``` ```
@@ -343,7 +384,7 @@ class CloudflareWorker {
readonly routes: IWorkerRoute[]; readonly routes: IWorkerRoute[];
// Methods // Methods
async getRoutes(): Promise<IWorkerRoute[]>; async listRoutes(): Promise<void>; // Populates the routes property
async setRoutes(routes: Array<IWorkerRouteDefinition>): Promise<void>; async setRoutes(routes: Array<IWorkerRouteDefinition>): Promise<void>;
async updateScript(scriptContent: string): Promise<CloudflareWorker>; async updateScript(scriptContent: string): Promise<CloudflareWorker>;
async delete(): Promise<boolean>; async delete(): Promise<boolean>;
@@ -376,13 +417,57 @@ CloudflareUtils.formatUrlForPurge('example.com/page'); // 'https://example.com/p
CloudflareUtils.formatTtl(3600); // '1 hour' CloudflareUtils.formatTtl(3600); // '1 hour'
``` ```
## What's New in 6.2.0 ## What's New in 7.0.0
- **Improved async iterator support**: Fully leverages the official Cloudflare client's async iterator pattern - **🎨 Clean Manager-Based Architecture**: New RecordManager, improved ZoneManager and WorkerManager with consistent naming
- **Enhanced error handling**: Better error detection and recovery - **🔌 IConvenientDnsProvider Compatibility**: New ConvenientDnsProvider adapter for seamless third-party module integration
- **Simplified API**: More consistent method signatures and return types - **📝 Consistent Method Naming**:
- **Better type safety**: Improved TypeScript typing throughout the library - `listZones()`, `listWorkers()`, `listRecords()` - consistent list* pattern
- **Detailed logging**: More informative logging for easier debugging - `deleteRecord()` instead of `removeRecord()` - clearer semantics
- `listRoutes()` instead of `getRoutes()` - consistent with other list methods
- **⚠️ Deprecated convenience Namespace**: Old methods still work but are deprecated - use managers instead
- **✅ Backward Compatible**: All existing code continues to work with deprecation warnings
## Migration Guide (6.x → 7.0)
### DNS Record Operations
```typescript
// Old (deprecated):
await cfAccount.convenience.createRecord('example.com', 'A', '1.2.3.4');
await cfAccount.convenience.listRecords('example.com');
await cfAccount.convenience.removeRecord('example.com', 'A');
// New (recommended):
await cfAccount.recordManager.createRecord('example.com', 'A', '1.2.3.4');
await cfAccount.recordManager.listRecords('example.com');
await cfAccount.recordManager.deleteRecord('example.com', 'A');
// For third-party modules:
const dnsProvider = cfAccount.getConvenientDnsProvider();
await dnsProvider.createRecord('example.com', 'A', '1.2.3.4');
```
### Zone Operations
```typescript
// Old (deprecated):
await cfAccount.convenience.listZones();
await cfAccount.convenience.purgeZone('example.com');
// New (recommended):
await cfAccount.zoneManager.listZones();
await cfAccount.zoneManager.purgeZone('example.com');
```
### Worker Operations
```typescript
// Old:
await cfAccount.workerManager.listWorkerScripts();
await worker.getRoutes();
// New:
await cfAccount.workerManager.listWorkers();
await worker.listRoutes();
```
## Development & Testing ## Development & Testing

View File

@@ -1,5 +1,5 @@
// tslint:disable-next-line: no-implicit-dependencies // tslint:disable-next-line: no-implicit-dependencies
import { expect, tap } from '@push.rocks/tapbundle'; import { expect, tap } from '@git.zone/tstest/tapbundle';
// tslint:disable-next-line: no-implicit-dependencies // tslint:disable-next-line: no-implicit-dependencies
import { Qenv } from '@push.rocks/qenv'; import { Qenv } from '@push.rocks/qenv';
@@ -236,7 +236,7 @@ tap.test('should list workers', async (tools) => {
tools.timeout(600000); tools.timeout(600000);
try { try {
const workerArray = await testCloudflareAccount.workerManager.listWorkerScripts(); const workerArray = await testCloudflareAccount.workerManager.listWorkers();
expect(workerArray).toBeTypeOf('array'); expect(workerArray).toBeTypeOf('array');
console.log(`Found ${workerArray.length} workers in account`); console.log(`Found ${workerArray.length} workers in account`);
} catch (error) { } catch (error) {

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@apiclient.xyz/cloudflare', name: '@apiclient.xyz/cloudflare',
version: '6.4.1', version: '7.0.0',
description: 'A TypeScript client for managing Cloudflare accounts, zones, DNS records, and workers with ease.' description: 'A TypeScript client for managing Cloudflare accounts, zones, DNS records, and workers with ease.'
} }

View File

@@ -5,6 +5,8 @@ import * as interfaces from './interfaces/index.js';
// interfaces // interfaces
import { WorkerManager } from './cloudflare.classes.workermanager.js'; import { WorkerManager } from './cloudflare.classes.workermanager.js';
import { ZoneManager } from './cloudflare.classes.zonemanager.js'; import { ZoneManager } from './cloudflare.classes.zonemanager.js';
import { RecordManager } from './cloudflare.classes.recordmanager.js';
import { ConvenientDnsProvider } from './cloudflare.classes.convenientdnsprovider.js';
export class CloudflareAccount implements plugins.tsclass.network.IConvenientDnsProvider { export class CloudflareAccount implements plugins.tsclass.network.IConvenientDnsProvider {
private authToken: string; private authToken: string;
@@ -12,6 +14,7 @@ export class CloudflareAccount implements plugins.tsclass.network.IConvenientDns
public workerManager = new WorkerManager(this); public workerManager = new WorkerManager(this);
public zoneManager = new ZoneManager(this); public zoneManager = new ZoneManager(this);
public recordManager = new RecordManager(this);
public apiAccount: plugins.cloudflare.Cloudflare; public apiAccount: plugins.cloudflare.Cloudflare;
@@ -42,56 +45,77 @@ export class CloudflareAccount implements plugins.tsclass.network.IConvenientDns
customHeaders?: Record<string, string>, customHeaders?: Record<string, string>,
): Promise<T> { ): Promise<T> {
try { try {
const options: plugins.smartrequest.ISmartRequestOptions = { logger.log('debug', `Making ${method} request to ${endpoint}`);
method,
headers: {
Authorization: `Bearer ${this.authToken}`,
'Content-Type': 'application/json',
...customHeaders,
},
};
// Build the request using fluent API
let requestBuilder = plugins.smartrequest.SmartRequest.create()
.url(`https://api.cloudflare.com/client/v4${endpoint}`)
.header('Authorization', `Bearer ${this.authToken}`);
// Add custom headers
if (customHeaders) {
for (const [key, value] of Object.entries(customHeaders)) {
requestBuilder = requestBuilder.header(key, value);
}
} else {
// Default to JSON content type if no custom headers
requestBuilder = requestBuilder.header('Content-Type', 'application/json');
}
// Add request body if provided
if (data) { if (data) {
if (customHeaders && customHeaders['Content-Type']?.includes('multipart/form-data')) { if (customHeaders && customHeaders['Content-Type']?.includes('multipart/form-data')) {
// For multipart form data, use the data directly as the request body // For multipart form data, use formData method
options.requestBody = data; requestBuilder = requestBuilder.formData(data);
} else { } else {
// For JSON requests, stringify the data // For JSON requests, use json method
options.requestBody = JSON.stringify(data); requestBuilder = requestBuilder.json(data);
} }
} }
logger.log('debug', `Making ${method} request to ${endpoint}`); // Execute the request with the appropriate method
const response = await plugins.smartrequest.request( let response: InstanceType<typeof plugins.smartrequest.CoreResponse>;
`https://api.cloudflare.com/client/v4${endpoint}`, switch (method) {
options, case 'GET':
); response = await requestBuilder.get();
break;
// Check if response is already an object (might happen with newer smartrequest versions) case 'POST':
if (typeof response.body === 'object' && response.body !== null) { response = await requestBuilder.post();
return response.body; break;
case 'PUT':
response = await requestBuilder.put();
break;
case 'DELETE':
response = await requestBuilder.delete();
break;
case 'PATCH':
response = await requestBuilder.patch();
break;
default:
throw new Error(`Unsupported HTTP method: ${method}`);
} }
// Otherwise try to parse as JSON // Check response status
if (!response.ok) {
const errorBody = await response.text();
logger.log('error', `HTTP ${response.status}: ${errorBody}`);
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
// Parse the response body
try { try {
if (typeof response.body === 'string' && response.body.trim()) { return (await response.json()) as T;
return JSON.parse(response.body);
} else {
// If body is empty or not a string, return an empty result
logger.log('warn', `Empty or invalid response body: ${typeof response.body}`);
return { result: [] } as T;
}
} catch (parseError) { } catch (parseError) {
logger.log('warn', `Failed to parse response as JSON: ${parseError.message}`); logger.log('warn', `Failed to parse response as JSON: ${parseError.message}`);
// Create a fake response object to maintain expected structure // Try to get as text and create a fallback response
const textBody = await response.text().catch(() => '');
return { return {
result: [], result: [],
success: true, success: true,
errors: [], errors: [],
messages: [ messages: [`Failed to parse: ${textBody.substring(0, 50)}...`],
`Failed to parse: ${typeof response.body === 'string' ? response.body?.substring(0, 50) : typeof response.body}...`,
],
} as T; } as T;
} }
} catch (error) { } catch (error) {
@@ -112,6 +136,16 @@ export class CloudflareAccount implements plugins.tsclass.network.IConvenientDns
} }
} }
/**
* Returns a ConvenientDnsProvider instance that implements IConvenientDnsProvider
* This allows third-party modules to use the standard DNS provider interface
* while internally delegating to the clean RecordManager and ZoneManager structure
* @returns ConvenientDnsProvider instance
*/
public getConvenientDnsProvider(): ConvenientDnsProvider {
return new ConvenientDnsProvider(this);
}
public convenience = { public convenience = {
/** /**
* Lists all accounts accessible with the current API token * Lists all accounts accessible with the current API token
@@ -136,6 +170,7 @@ export class CloudflareAccount implements plugins.tsclass.network.IConvenientDns
/** /**
* gets a zone id of a domain from cloudflare * gets a zone id of a domain from cloudflare
* @param domainName * @param domainName
* @deprecated Use zoneManager.getZoneId() instead
*/ */
getZoneId: async (domainName: string) => { getZoneId: async (domainName: string) => {
const domain = new plugins.smartstring.Domain(domainName); const domain = new plugins.smartstring.Domain(domainName);
@@ -154,6 +189,7 @@ export class CloudflareAccount implements plugins.tsclass.network.IConvenientDns
* gets a record * gets a record
* @param domainNameArg * @param domainNameArg
* @param typeArg * @param typeArg
* @deprecated Use recordManager.getRecord() or getConvenientDnsProvider().getRecord() instead
*/ */
getRecord: async ( getRecord: async (
domainNameArg: string, domainNameArg: string,
@@ -183,6 +219,7 @@ export class CloudflareAccount implements plugins.tsclass.network.IConvenientDns
}, },
/** /**
* creates a record * creates a record
* @deprecated Use recordManager.createRecord() or getConvenientDnsProvider().createRecord() instead
*/ */
createRecord: async ( createRecord: async (
domainNameArg: string, domainNameArg: string,
@@ -205,6 +242,7 @@ export class CloudflareAccount implements plugins.tsclass.network.IConvenientDns
* removes a record from Cloudflare * removes a record from Cloudflare
* @param domainNameArg * @param domainNameArg
* @param typeArg * @param typeArg
* @deprecated Use recordManager.deleteRecord() or getConvenientDnsProvider().removeRecord() instead
*/ */
removeRecord: async ( removeRecord: async (
domainNameArg: string, domainNameArg: string,
@@ -230,6 +268,7 @@ export class CloudflareAccount implements plugins.tsclass.network.IConvenientDns
/** /**
* cleanrecord allows the cleaning of any previous records to avoid unwanted sideeffects * cleanrecord allows the cleaning of any previous records to avoid unwanted sideeffects
* @deprecated Use recordManager.cleanRecords() or getConvenientDnsProvider().cleanRecord() instead
*/ */
cleanRecord: async (domainNameArg: string, typeArg: plugins.tsclass.network.TDnsRecordType) => { cleanRecord: async (domainNameArg: string, typeArg: plugins.tsclass.network.TDnsRecordType) => {
try { try {
@@ -291,6 +330,7 @@ export class CloudflareAccount implements plugins.tsclass.network.IConvenientDns
* @param contentArg New content for the record * @param contentArg New content for the record
* @param ttlArg Time to live in seconds (optional) * @param ttlArg Time to live in seconds (optional)
* @returns Updated record * @returns Updated record
* @deprecated Use recordManager.updateRecord() or getConvenientDnsProvider().updateRecord() instead
*/ */
updateRecord: async ( updateRecord: async (
domainNameArg: string, domainNameArg: string,
@@ -327,6 +367,7 @@ export class CloudflareAccount implements plugins.tsclass.network.IConvenientDns
/** /**
* list all records of a specified domain name * list all records of a specified domain name
* @param domainNameArg - the domain name that you want to get the records from * @param domainNameArg - the domain name that you want to get the records from
* @deprecated Use recordManager.listRecords() or getConvenientDnsProvider().listRecords() instead
*/ */
listRecords: async (domainNameArg: string) => { listRecords: async (domainNameArg: string) => {
try { try {
@@ -351,6 +392,7 @@ export class CloudflareAccount implements plugins.tsclass.network.IConvenientDns
/** /**
* list all zones in the associated authenticated account * list all zones in the associated authenticated account
* @param domainName optional filter by domain name * @param domainName optional filter by domain name
* @deprecated Use zoneManager.listZones() instead
*/ */
listZones: async (domainName?: string) => { listZones: async (domainName?: string) => {
try { try {
@@ -380,6 +422,7 @@ export class CloudflareAccount implements plugins.tsclass.network.IConvenientDns
* Determines whether the given domain can be managed by this account * Determines whether the given domain can be managed by this account
* @param domainName Full domain name to check (e.g., "sub.example.com") * @param domainName Full domain name to check (e.g., "sub.example.com")
* @returns True if the zone for the domain exists in the account, false otherwise * @returns True if the zone for the domain exists in the account, false otherwise
* @deprecated Use getConvenientDnsProvider().isDomainSupported() instead
*/ */
isDomainSupported: async (domainName: string): Promise<boolean> => { isDomainSupported: async (domainName: string): Promise<boolean> => {
try { try {
@@ -396,6 +439,7 @@ export class CloudflareAccount implements plugins.tsclass.network.IConvenientDns
}, },
/** /**
* purges a zone * purges a zone
* @deprecated Use zoneManager.purgeZone() instead
*/ */
purgeZone: async (domainName: string): Promise<void> => { purgeZone: async (domainName: string): Promise<void> => {
const domain = new plugins.smartstring.Domain(domainName); const domain = new plugins.smartstring.Domain(domainName);
@@ -407,6 +451,9 @@ export class CloudflareAccount implements plugins.tsclass.network.IConvenientDns
}, },
// acme convenience functions // acme convenience functions
/**
* @deprecated Use getConvenientDnsProvider().acmeSetDnsChallenge() instead
*/
acmeSetDnsChallenge: async (dnsChallenge: plugins.tsclass.network.IDnsChallenge) => { acmeSetDnsChallenge: async (dnsChallenge: plugins.tsclass.network.IDnsChallenge) => {
await this.convenience.cleanRecord(dnsChallenge.hostName, 'TXT'); await this.convenience.cleanRecord(dnsChallenge.hostName, 'TXT');
await this.convenience.createRecord( await this.convenience.createRecord(
@@ -416,6 +463,9 @@ export class CloudflareAccount implements plugins.tsclass.network.IConvenientDns
120, 120,
); );
}, },
/**
* @deprecated Use getConvenientDnsProvider().acmeRemoveDnsChallenge() instead
*/
acmeRemoveDnsChallenge: async (dnsChallenge: plugins.tsclass.network.IDnsChallenge) => { acmeRemoveDnsChallenge: async (dnsChallenge: plugins.tsclass.network.IDnsChallenge) => {
await this.convenience.removeRecord(dnsChallenge.hostName, 'TXT'); await this.convenience.removeRecord(dnsChallenge.hostName, 'TXT');
}, },

View File

@@ -0,0 +1,178 @@
import * as plugins from './cloudflare.plugins.js';
import { logger } from './cloudflare.logger.js';
/**
* Adapter class that implements IConvenientDnsProvider interface
* Delegates to RecordManager and ZoneManager internally for clean architecture
* This allows third-party modules to use the standard DNS provider interface
*/
export class ConvenientDnsProvider implements plugins.tsclass.network.IConvenientDnsProvider {
/**
* The convenience property is required by IConvenientDnsProvider interface
* It returns this instance to maintain interface compatibility
*/
public convenience = this;
constructor(private cfAccount: any) {}
/**
* Creates a new DNS record
* @param domainNameArg - The domain name for the record
* @param typeArg - The DNS record type
* @param contentArg - The record content (IP address, CNAME target, etc.)
* @param ttlArg - Time to live in seconds (default: 1 = automatic)
* @returns Created record as raw API object
*/
public async createRecord(
domainNameArg: string,
typeArg: plugins.tsclass.network.TDnsRecordType,
contentArg: string,
ttlArg: number = 1,
): Promise<any> {
const record = await this.cfAccount.recordManager.createRecord(
domainNameArg,
typeArg,
contentArg,
ttlArg,
);
// Return raw API object format for interface compatibility
return this.recordToApiObject(record);
}
/**
* Updates an existing DNS record, or creates it if it doesn't exist
* @param domainNameArg - The domain name
* @param typeArg - The DNS record type
* @param contentArg - The new record content
* @param ttlArg - Time to live in seconds (default: 1 = automatic)
* @returns Updated record as raw API object
*/
public async updateRecord(
domainNameArg: string,
typeArg: plugins.tsclass.network.TDnsRecordType,
contentArg: string,
ttlArg: number = 1,
): Promise<any> {
const record = await this.cfAccount.recordManager.updateRecord(
domainNameArg,
typeArg,
contentArg,
ttlArg,
);
return this.recordToApiObject(record);
}
/**
* Removes a DNS record
* @param domainNameArg - The domain name
* @param typeArg - The DNS record type
*/
public async removeRecord(
domainNameArg: string,
typeArg: plugins.tsclass.network.TDnsRecordType,
): Promise<any> {
await this.cfAccount.recordManager.deleteRecord(domainNameArg, typeArg);
}
/**
* Gets a specific DNS record by domain and type
* @param domainNameArg - The domain name
* @param typeArg - The DNS record type
* @returns Record as raw API object or undefined if not found
*/
public async getRecord(
domainNameArg: string,
typeArg: plugins.tsclass.network.TDnsRecordType,
): Promise<any | undefined> {
const record = await this.cfAccount.recordManager.getRecord(domainNameArg, typeArg);
return record ? this.recordToApiObject(record) : undefined;
}
/**
* Lists all DNS records for a domain
* @param domainNameArg - The domain name to list records for
* @returns Array of records as raw API objects
*/
public async listRecords(domainNameArg: string): Promise<any[]> {
const records = await this.cfAccount.recordManager.listRecords(domainNameArg);
return records.map((record: any) => this.recordToApiObject(record));
}
/**
* Removes all DNS records of a specific type for a domain
* @param domainNameArg - The domain name
* @param typeArg - The DNS record type to clean
*/
public async cleanRecord(
domainNameArg: string,
typeArg: plugins.tsclass.network.TDnsRecordType,
): Promise<void> {
await this.cfAccount.recordManager.cleanRecords(domainNameArg, typeArg);
}
/**
* Determines whether the given domain can be managed by this account
* @param domainName - Full domain name to check (e.g., "sub.example.com")
* @returns True if the zone for the domain exists in the account, false otherwise
*/
public async isDomainSupported(domainName: string): Promise<boolean> {
try {
// Parse out the apex/zone name from the full domain
const domain = new plugins.smartstring.Domain(domainName);
// List zones filtered by the zone name
const zones = await this.cfAccount.zoneManager.listZones(domain.zoneName);
// If any zone matches, we can manage this domain
return Array.isArray(zones) && zones.length > 0;
} catch (error) {
logger.log('error', `Error checking domain support for ${domainName}: ${error.message}`);
return false;
}
}
/**
* Sets an ACME DNS challenge for domain verification
* @param dnsChallenge - The DNS challenge object
*/
public async acmeSetDnsChallenge(
dnsChallenge: plugins.tsclass.network.IDnsChallenge,
): Promise<void> {
await this.cfAccount.recordManager.cleanRecords(dnsChallenge.hostName, 'TXT');
await this.cfAccount.recordManager.createRecord(
dnsChallenge.hostName,
'TXT',
dnsChallenge.challenge,
120,
);
}
/**
* Removes an ACME DNS challenge
* @param dnsChallenge - The DNS challenge object
*/
public async acmeRemoveDnsChallenge(
dnsChallenge: plugins.tsclass.network.IDnsChallenge,
): Promise<void> {
await this.cfAccount.recordManager.deleteRecord(dnsChallenge.hostName, 'TXT');
}
/**
* Helper method to convert CloudflareRecord instance to raw API object format
* This ensures compatibility with the IConvenientDnsProvider interface
*/
private recordToApiObject(record: any): any {
return {
id: record.id,
type: record.type,
name: record.name,
content: record.content,
proxiable: record.proxiable,
proxied: record.proxied,
ttl: record.ttl,
locked: record.locked,
zone_id: record.zone_id,
zone_name: record.zone_name,
created_on: record.created_on,
modified_on: record.modified_on,
};
}
}

View File

@@ -0,0 +1,198 @@
import * as plugins from './cloudflare.plugins.js';
import { logger } from './cloudflare.logger.js';
import { CloudflareRecord } from './cloudflare.classes.record.js';
export class RecordManager {
constructor(private cfAccount: any) {}
/**
* Lists all DNS records for a domain
* @param domainNameArg - The domain name to list records for
* @returns Array of CloudflareRecord instances
*/
public async listRecords(domainNameArg: string): Promise<CloudflareRecord[]> {
try {
const domain = new plugins.smartstring.Domain(domainNameArg);
const zoneId = await this.cfAccount.zoneManager.getZoneId(domain.zoneName);
const records: plugins.ICloudflareTypes['Record'][] = [];
// Collect all records using async iterator
for await (const record of this.cfAccount.apiAccount.dns.records.list({
zone_id: zoneId,
})) {
records.push(record);
}
logger.log('info', `Found ${records.length} DNS records for ${domainNameArg}`);
// Convert to CloudflareRecord instances
return records.map(record => CloudflareRecord.createFromApiObject(record));
} catch (error) {
logger.log('error', `Failed to list records for ${domainNameArg}: ${error.message}`);
return [];
}
}
/**
* Gets a specific DNS record by domain and type
* @param domainNameArg - The domain name
* @param typeArg - The DNS record type (A, AAAA, CNAME, TXT, etc.)
* @returns CloudflareRecord instance or undefined if not found
*/
public async getRecord(
domainNameArg: string,
typeArg: plugins.tsclass.network.TDnsRecordType,
): Promise<CloudflareRecord | undefined> {
try {
const domain = new plugins.smartstring.Domain(domainNameArg);
const recordArray = await this.listRecords(domain.zoneName);
const filteredRecords = recordArray.filter((recordArg) => {
return recordArg.type === typeArg && recordArg.name === domainNameArg;
});
return filteredRecords.length > 0 ? filteredRecords[0] : undefined;
} catch (error) {
logger.log('error', `Error getting record for ${domainNameArg}: ${error.message}`);
return undefined;
}
}
/**
* Creates a new DNS record
* @param domainNameArg - The domain name for the record
* @param typeArg - The DNS record type
* @param contentArg - The record content (IP address, CNAME target, etc.)
* @param ttlArg - Time to live in seconds (default: 1 = automatic)
* @returns Created CloudflareRecord instance
*/
public async createRecord(
domainNameArg: string,
typeArg: plugins.tsclass.network.TDnsRecordType,
contentArg: string,
ttlArg: number = 1,
): Promise<CloudflareRecord> {
const domain = new plugins.smartstring.Domain(domainNameArg);
const zoneId = await this.cfAccount.zoneManager.getZoneId(domain.zoneName);
const response = await this.cfAccount.apiAccount.dns.records.create({
zone_id: zoneId,
type: typeArg as any,
name: domain.fullName,
content: contentArg,
ttl: ttlArg,
});
logger.log('info', `Created ${typeArg} record for ${domainNameArg}`);
return CloudflareRecord.createFromApiObject(response);
}
/**
* Updates an existing DNS record, or creates it if it doesn't exist
* @param domainNameArg - The domain name
* @param typeArg - The DNS record type
* @param contentArg - The new record content
* @param ttlArg - Time to live in seconds (default: 1 = automatic)
* @returns Updated CloudflareRecord instance
*/
public async updateRecord(
domainNameArg: string,
typeArg: plugins.tsclass.network.TDnsRecordType,
contentArg: string,
ttlArg: number = 1,
): Promise<CloudflareRecord> {
const domain = new plugins.smartstring.Domain(domainNameArg);
const zoneId = await this.cfAccount.zoneManager.getZoneId(domain.zoneName);
// Find existing record
const existingRecord = await this.getRecord(domainNameArg, typeArg);
if (!existingRecord) {
logger.log(
'warn',
`Record ${domainNameArg} of type ${typeArg} not found for update, creating instead`,
);
return this.createRecord(domainNameArg, typeArg, contentArg, ttlArg);
}
// Update the record
const updatedRecord = await this.cfAccount.apiAccount.dns.records.edit(existingRecord.id, {
zone_id: zoneId,
type: typeArg as any,
name: domain.fullName,
content: contentArg,
ttl: ttlArg,
});
logger.log('info', `Updated ${typeArg} record for ${domainNameArg}`);
return CloudflareRecord.createFromApiObject(updatedRecord);
}
/**
* Deletes a DNS record
* @param domainNameArg - The domain name
* @param typeArg - The DNS record type
*/
public async deleteRecord(
domainNameArg: string,
typeArg: plugins.tsclass.network.TDnsRecordType,
): Promise<void> {
const domain = new plugins.smartstring.Domain(domainNameArg);
const zoneId = await this.cfAccount.zoneManager.getZoneId(domain.zoneName);
const record = await this.getRecord(domainNameArg, typeArg);
if (record) {
await this.cfAccount.apiAccount.dns.records.delete(record.id, {
zone_id: zoneId,
});
logger.log('info', `Deleted ${typeArg} record for ${domainNameArg}`);
} else {
logger.log('warn', `Record ${domainNameArg} of type ${typeArg} not found for deletion`);
}
}
/**
* Removes all DNS records of a specific type for a domain
* @param domainNameArg - The domain name
* @param typeArg - The DNS record type to clean
*/
public async cleanRecords(
domainNameArg: string,
typeArg: plugins.tsclass.network.TDnsRecordType,
): Promise<void> {
try {
logger.log('info', `Cleaning ${typeArg} records for ${domainNameArg}`);
const domain = new plugins.smartstring.Domain(domainNameArg);
const zoneId = await this.cfAccount.zoneManager.getZoneId(domain.zoneName);
// List all records in the zone for this domain
const records = await this.listRecords(domain.zoneName);
// Only delete records matching the specified name and type
const recordsToDelete = records.filter((recordArg) => {
return recordArg.type === typeArg && recordArg.name === domainNameArg;
});
logger.log(
'info',
`Found ${recordsToDelete.length} ${typeArg} records to delete for ${domainNameArg}`,
);
for (const recordToDelete of recordsToDelete) {
try {
await this.cfAccount.apiAccount.dns.records.delete(recordToDelete.id, {
zone_id: zoneId,
});
logger.log('info', `Deleted ${typeArg} record ${recordToDelete.id} for ${domainNameArg}`);
} catch (deleteError) {
logger.log('error', `Failed to delete record: ${deleteError.message}`);
}
}
} catch (error) {
logger.log(
'error',
`Error cleaning ${typeArg} records for ${domainNameArg}: ${error.message}`,
);
}
}
}

View File

@@ -20,7 +20,7 @@ export class CloudflareWorker {
): Promise<CloudflareWorker> { ): Promise<CloudflareWorker> {
const newWorker = new CloudflareWorker(workerManager); const newWorker = new CloudflareWorker(workerManager);
Object.assign(newWorker, apiObject); Object.assign(newWorker, apiObject);
await newWorker.getRoutes(); await newWorker.listRoutes();
return newWorker; return newWorker;
} }
@@ -41,9 +41,9 @@ export class CloudflareWorker {
} }
/** /**
* gets all routes for a worker * Lists all routes for this worker
*/ */
public async getRoutes() { public async listRoutes() {
try { try {
this.routes = []; // Reset routes before fetching this.routes = []; // Reset routes before fetching
@@ -102,7 +102,7 @@ export class CloudflareWorker {
*/ */
public async setRoutes(routeArray: IWorkerRouteDefinition[]) { public async setRoutes(routeArray: IWorkerRouteDefinition[]) {
// First get all existing routes to determine what we need to create/update // First get all existing routes to determine what we need to create/update
await this.getRoutes(); await this.listRoutes();
for (const newRoute of routeArray) { for (const newRoute of routeArray) {
// Determine whether a route is new, needs an update, or is already up to date // Determine whether a route is new, needs an update, or is already up to date
@@ -156,7 +156,7 @@ export class CloudflareWorker {
} }
// Refresh routes after all changes // Refresh routes after all changes
await this.getRoutes(); await this.listRoutes();
} }
/** /**

View File

@@ -39,7 +39,7 @@ export class WorkerManager {
// Initialize the worker and get its routes // Initialize the worker and get its routes
try { try {
await worker.getRoutes(); await worker.listRoutes();
} catch (routeError) { } catch (routeError) {
logger.log('warn', `Failed to get routes for worker ${workerName}: ${routeError.message}`); logger.log('warn', `Failed to get routes for worker ${workerName}: ${routeError.message}`);
// Continue anyway since the worker was created // Continue anyway since the worker was created
@@ -79,7 +79,7 @@ export class WorkerManager {
// Initialize the worker and get its routes // Initialize the worker and get its routes
try { try {
await worker.getRoutes(); await worker.listRoutes();
} catch (routeError) { } catch (routeError) {
logger.log('warn', `Failed to get routes for worker ${workerName}: ${routeError.message}`); logger.log('warn', `Failed to get routes for worker ${workerName}: ${routeError.message}`);
// Continue anyway since we found the worker // Continue anyway since we found the worker
@@ -96,7 +96,7 @@ export class WorkerManager {
* Lists all worker scripts * Lists all worker scripts
* @returns Array of worker scripts * @returns Array of worker scripts
*/ */
public async listWorkerScripts() { public async listWorkers() {
if (!this.cfAccount.preselectedAccountId) { if (!this.cfAccount.preselectedAccountId) {
throw new Error('No account selected. Please select it first on the account.'); throw new Error('No account selected. Please select it first on the account.');
} }

View File

@@ -12,11 +12,11 @@ export class ZoneManager {
} }
/** /**
* Get all zones, optionally filtered by name * Lists all zones, optionally filtered by name
* @param zoneName Optional zone name to filter by * @param zoneName Optional zone name to filter by
* @returns Array of CloudflareZone instances * @returns Array of CloudflareZone instances
*/ */
public async getZones(zoneName?: string): Promise<CloudflareZone[]> { public async listZones(zoneName?: string): Promise<CloudflareZone[]> {
try { try {
const options: any = { per_page: 50 }; const options: any = { per_page: 50 };
@@ -37,13 +37,33 @@ export class ZoneManager {
} }
} }
/**
* Gets the zone ID for a domain name
* @param domainName Domain name to get the zone ID for
* @returns Zone ID string
* @throws Error if domain is not found in this account
*/
public async getZoneId(domainName: string): Promise<string> {
const domain = new plugins.smartstring.Domain(domainName);
const zoneArray = await this.listZones(domain.zoneName);
const filteredResponse = zoneArray.filter((zoneArg) => {
return zoneArg.name === domainName;
});
if (filteredResponse.length >= 1) {
return filteredResponse[0].id;
} else {
logger.log('error', `the domain ${domainName} does not appear to be in this account!`);
throw new Error(`the domain ${domainName} does not appear to be in this account!`);
}
}
/** /**
* Get a single zone by name * Get a single zone by name
* @param zoneName Zone name to find * @param zoneName Zone name to find
* @returns CloudflareZone instance or undefined if not found * @returns CloudflareZone instance or undefined if not found
*/ */
public async getZoneByName(zoneName: string): Promise<CloudflareZone | undefined> { public async getZoneByName(zoneName: string): Promise<CloudflareZone | undefined> {
const zones = await this.getZones(zoneName); const zones = await this.listZones(zoneName);
return zones.find((zone) => zone.name === zoneName); return zones.find((zone) => zone.name === zoneName);
} }
@@ -130,7 +150,7 @@ export class ZoneManager {
* @returns True if the zone exists * @returns True if the zone exists
*/ */
public async zoneExists(zoneName: string): Promise<boolean> { public async zoneExists(zoneName: string): Promise<boolean> {
const zones = await this.getZones(zoneName); const zones = await this.listZones(zoneName);
return zones.some((zone) => zone.name === zoneName); return zones.some((zone) => zone.name === zoneName);
} }
@@ -180,4 +200,18 @@ export class ZoneManager {
return undefined; return undefined;
} }
} }
/**
* Purges all cached files for a zone
* @param domainName Domain name to purge cache for
*/
public async purgeZone(domainName: string): Promise<void> {
const domain = new plugins.smartstring.Domain(domainName);
const zoneId = await this.getZoneId(domain.zoneName);
await this.cfAccount.apiAccount.cache.purge({
zone_id: zoneId,
purge_everything: true,
});
logger.log('info', `Purged cache for zone ${domainName}`);
}
} }

View File

@@ -6,8 +6,10 @@ export {
} from './cloudflare.classes.worker.js'; } from './cloudflare.classes.worker.js';
export { WorkerManager } from './cloudflare.classes.workermanager.js'; export { WorkerManager } from './cloudflare.classes.workermanager.js';
export { CloudflareRecord, type ICloudflareRecordInfo } from './cloudflare.classes.record.js'; export { CloudflareRecord, type ICloudflareRecordInfo } from './cloudflare.classes.record.js';
export { RecordManager } from './cloudflare.classes.recordmanager.js';
export { CloudflareZone } from './cloudflare.classes.zone.js'; export { CloudflareZone } from './cloudflare.classes.zone.js';
export { ZoneManager } from './cloudflare.classes.zonemanager.js'; export { ZoneManager } from './cloudflare.classes.zonemanager.js';
export { ConvenientDnsProvider } from './cloudflare.classes.convenientdnsprovider.js';
export { CloudflareUtils } from './cloudflare.utils.js'; export { CloudflareUtils } from './cloudflare.utils.js';
export { commitinfo } from './00_commitinfo_data.js'; export { commitinfo } from './00_commitinfo_data.js';