Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 18afafd3b3 | |||
| 5ce1520e2b | |||
| 39d53da4e6 | |||
| 002ac3ae01 | |||
| 0184371635 | |||
| 038f56b0ce | |||
| 1c0a20ac99 | |||
| 36d9db4332 | |||
| 5d77214222 | |||
| f27eaa0e82 | |||
| 4c16e0263a | |||
| d8ca3dc115 | |||
| 6cd5aa2913 | |||
| 4b82cfbaae |
66
.gitea/workflows/default_nottags.yaml
Normal file
66
.gitea/workflows/default_nottags.yaml
Normal file
@@ -0,0 +1,66 @@
|
||||
name: Default (not tags)
|
||||
|
||||
on:
|
||||
push:
|
||||
tags-ignore:
|
||||
- '**'
|
||||
|
||||
env:
|
||||
IMAGE: code.foss.global/host.today/ht-docker-node:npmci
|
||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@/${{gitea.repository}}.git
|
||||
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
||||
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
||||
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
|
||||
|
||||
jobs:
|
||||
security:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install pnpm and npmci
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @ship.zone/npmci
|
||||
|
||||
- name: Run npm prepare
|
||||
run: npmci npm prepare
|
||||
|
||||
- name: Audit production dependencies
|
||||
run: |
|
||||
npmci command npm config set registry https://registry.npmjs.org
|
||||
npmci command pnpm audit --audit-level=high --prod
|
||||
continue-on-error: true
|
||||
|
||||
- name: Audit development dependencies
|
||||
run: |
|
||||
npmci command npm config set registry https://registry.npmjs.org
|
||||
npmci command pnpm audit --audit-level=high --dev
|
||||
continue-on-error: true
|
||||
|
||||
test:
|
||||
if: ${{ always() }}
|
||||
needs: security
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Test stable
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm install
|
||||
npmci npm test
|
||||
|
||||
- name: Test build
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm install
|
||||
npmci npm build
|
||||
124
.gitea/workflows/default_tags.yaml
Normal file
124
.gitea/workflows/default_tags.yaml
Normal file
@@ -0,0 +1,124 @@
|
||||
name: Default (tags)
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
env:
|
||||
IMAGE: code.foss.global/host.today/ht-docker-node:npmci
|
||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@/${{gitea.repository}}.git
|
||||
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
||||
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
||||
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
|
||||
|
||||
jobs:
|
||||
security:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @ship.zone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Audit production dependencies
|
||||
run: |
|
||||
npmci command npm config set registry https://registry.npmjs.org
|
||||
npmci command pnpm audit --audit-level=high --prod
|
||||
continue-on-error: true
|
||||
|
||||
- name: Audit development dependencies
|
||||
run: |
|
||||
npmci command npm config set registry https://registry.npmjs.org
|
||||
npmci command pnpm audit --audit-level=high --dev
|
||||
continue-on-error: true
|
||||
|
||||
test:
|
||||
if: ${{ always() }}
|
||||
needs: security
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @ship.zone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Test stable
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm install
|
||||
npmci npm test
|
||||
|
||||
- name: Test build
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm install
|
||||
npmci npm build
|
||||
|
||||
release:
|
||||
needs: test
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @ship.zone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Release
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm publish
|
||||
|
||||
metadata:
|
||||
needs: test
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
continue-on-error: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @ship.zone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Code quality
|
||||
run: |
|
||||
npmci command npm install -g typescript
|
||||
npmci npm install
|
||||
|
||||
- name: Trigger
|
||||
run: npmci trigger
|
||||
|
||||
- name: Build docs and upload artifacts
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm install
|
||||
pnpm install -g @git.zone/tsdoc
|
||||
npmci command tsdoc
|
||||
continue-on-error: true
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -3,7 +3,6 @@
|
||||
# artifacts
|
||||
coverage/
|
||||
public/
|
||||
pages/
|
||||
|
||||
# installs
|
||||
node_modules/
|
||||
@@ -17,4 +16,4 @@ node_modules/
|
||||
dist/
|
||||
dist_*/
|
||||
|
||||
# custom
|
||||
#------# custom
|
||||
58
changelog.md
58
changelog.md
@@ -1,5 +1,63 @@
|
||||
# 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)
|
||||
Update CI workflows, repository URL, and apply minor code formatting fixes
|
||||
|
||||
- Add new Gitea workflows for both tagged and non-tagged push events with security, test, release, and metadata jobs
|
||||
- Update repository URL in package.json from pushrocks/cflare to mojoio/cloudflare
|
||||
- Refine .gitignore custom comments
|
||||
- Apply minor formatting improvements in source code and documentation
|
||||
|
||||
## 2025-04-30 - 6.4.0 - feat(CloudflareAccount)
|
||||
Bump dependency versions and add domain support check in CloudflareAccount
|
||||
|
||||
- Upgrade dependencies: @push.rocks/smartrequest, @tsclass/tsclass, @git.zone/tsbuild, @push.rocks/tapbundle, and @types/node
|
||||
- Implement the isDomainSupported convenience method in CloudflareAccount for validating domain management
|
||||
|
||||
## 2025-04-26 - 6.3.2 - fix(worker)
|
||||
Refactor worker script update and creation to use intermediate parameter objects
|
||||
|
||||
- Build updateParams in CloudflareWorker for proper multipart form handling when updating scripts
|
||||
- Use contentParams in WorkerManager to improve clarity and consistency in worker creation
|
||||
|
||||
## 2025-04-26 - 6.3.1 - fix(core)
|
||||
Improve nested DNS record management and worker script multipart handling
|
||||
|
||||
- Add tests for creating, updating, and removing nested subdomain A records
|
||||
- Refine TXT record cleaning by filtering records with matching name and type
|
||||
- Clarify multipart form data handling for worker script updates and creation
|
||||
|
||||
## 2025-04-26 - 6.3.0 - feat(core)
|
||||
Release 6.2.0: Improved async iterator support, enhanced error handling and refined API interfaces for better type safety and consistent behavior.
|
||||
|
||||
|
||||
38
package.json
38
package.json
@@ -1,20 +1,20 @@
|
||||
{
|
||||
"name": "@apiclient.xyz/cloudflare",
|
||||
"version": "6.3.0",
|
||||
"version": "7.0.0",
|
||||
"private": false,
|
||||
"description": "A TypeScript client for managing Cloudflare accounts, zones, DNS records, and workers with ease.",
|
||||
"main": "dist_ts/index.js",
|
||||
"typings": "dist_ts/index.d.ts",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "(tstest test/)",
|
||||
"test": "(tstest test/ --verbose --timeout 600)",
|
||||
"build": "(tsbuild --web --allowimplicitany)",
|
||||
"buildDocs": "tsdoc",
|
||||
"updateOpenapi": "openapi-typescript https://raw.githubusercontent.com/cloudflare/api-schemas/main/openapi.yaml --output ts/openapi.spec.ts"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://gitlab.com/pushrocks/cflare.git"
|
||||
"url": "https://gitlab.com/mojoio/cloudflare.git"
|
||||
},
|
||||
"keywords": [
|
||||
"Cloudflare",
|
||||
@@ -31,26 +31,25 @@
|
||||
"author": "Lossless GmbH",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://gitlab.com/pushrocks/cflare/issues"
|
||||
"url": "https://gitlab.com/mojoio/cloudflare/issues"
|
||||
},
|
||||
"homepage": "https://gitlab.com/pushrocks/cflare#readme",
|
||||
"homepage": "https://gitlab.com/mojoio/cloudflare#readme",
|
||||
"dependencies": {
|
||||
"@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/smartrequest": "^2.0.23",
|
||||
"@push.rocks/smartstring": "^4.0.5",
|
||||
"@tsclass/tsclass": "^5.0.0",
|
||||
"cloudflare": "^4.2.0"
|
||||
"@push.rocks/smartrequest": "^5.0.1",
|
||||
"@push.rocks/smartstring": "^4.1.0",
|
||||
"@tsclass/tsclass": "^9.3.0",
|
||||
"cloudflare": "^5.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@git.zone/tsbuild": "^2.2.7",
|
||||
"@git.zone/tsrun": "^1.3.3",
|
||||
"@git.zone/tstest": "^1.0.96",
|
||||
"@push.rocks/qenv": "^6.1.0",
|
||||
"@push.rocks/tapbundle": "^5.6.0",
|
||||
"@types/node": "^22.13.10",
|
||||
"openapi-typescript": "^7.6.1"
|
||||
"@git.zone/tsbuild": "^3.1.0",
|
||||
"@git.zone/tsrun": "^2.0.0",
|
||||
"@git.zone/tstest": "^2.8.2",
|
||||
"@push.rocks/qenv": "^6.1.3",
|
||||
"@types/node": "^22.15.3",
|
||||
"openapi-typescript": "^7.10.1"
|
||||
},
|
||||
"files": [
|
||||
"ts/**/*",
|
||||
@@ -67,5 +66,8 @@
|
||||
"browserslist": [
|
||||
"last 1 chrome versions"
|
||||
],
|
||||
"packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6"
|
||||
"packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6",
|
||||
"pnpm": {
|
||||
"overrides": {}
|
||||
}
|
||||
}
|
||||
|
||||
7608
pnpm-lock.yaml
generated
7608
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
217
readme.md
217
readme.md
@@ -8,10 +8,10 @@ An elegant, class-based TypeScript client for the Cloudflare API that makes mana
|
||||
## Features
|
||||
|
||||
- **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
|
||||
- **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
|
||||
- **ESM compatible** for modern JavaScript projects
|
||||
- **Comprehensive error handling** for robust applications
|
||||
@@ -37,12 +37,13 @@ import * as cflare from '@apiclient.xyz/cloudflare';
|
||||
// Initialize with your API token
|
||||
const cfAccount = new cflare.CloudflareAccount('your-cloudflare-api-token');
|
||||
|
||||
// Use convenience methods for quick operations
|
||||
await cfAccount.convenience.createRecord('subdomain.example.com', 'A', '192.0.2.1', 3600);
|
||||
// Use the clean manager-based API
|
||||
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
|
||||
const zone = await cfAccount.zoneManager.getZoneByName('example.com');
|
||||
await zone.purgeCache();
|
||||
// Or use the IConvenientDnsProvider interface for third-party modules
|
||||
const dnsProvider = cfAccount.getConvenientDnsProvider();
|
||||
await dnsProvider.createRecord('subdomain.example.com', 'A', '192.0.2.1');
|
||||
```
|
||||
|
||||
## Usage Guide
|
||||
@@ -68,20 +69,20 @@ const myAccounts = await cfAccount.listAccounts();
|
||||
Zones represent your domains in Cloudflare.
|
||||
|
||||
```typescript
|
||||
// Get all zones in your account
|
||||
const allZones = await cfAccount.convenience.listZones();
|
||||
// List all zones in your account
|
||||
const allZones = await cfAccount.zoneManager.listZones();
|
||||
|
||||
// Get a specific zone by domain name
|
||||
const myZone = await cfAccount.zoneManager.getZoneByName('example.com');
|
||||
|
||||
// Get zone ID directly
|
||||
const zoneId = await cfAccount.convenience.getZoneId('example.com');
|
||||
const zoneId = await cfAccount.zoneManager.getZoneId('example.com');
|
||||
|
||||
// Create a new zone
|
||||
const newZone = await cfAccount.zoneManager.createZone('newdomain.com');
|
||||
|
||||
// Purge cache for an entire zone
|
||||
await cfAccount.convenience.purgeZone('example.com');
|
||||
await cfAccount.zoneManager.purgeZone('example.com');
|
||||
// Or using the zone object
|
||||
await myZone.purgeCache();
|
||||
|
||||
@@ -99,38 +100,42 @@ const usingCfNameservers = await myZone.isUsingCloudflareNameservers();
|
||||
|
||||
### DNS Record Management
|
||||
|
||||
Manage DNS records for your domains with ease.
|
||||
Manage DNS records for your domains with ease using the RecordManager.
|
||||
|
||||
```typescript
|
||||
// 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
|
||||
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
|
||||
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
|
||||
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)
|
||||
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
|
||||
await cfAccount.convenience.removeRecord('api.example.com', 'A');
|
||||
// Delete a specific DNS record
|
||||
await cfAccount.recordManager.deleteRecord('api.example.com', 'A');
|
||||
|
||||
// Clean (remove) all records of a specific type
|
||||
await cfAccount.convenience.cleanRecord('example.com', 'TXT');
|
||||
// Clean (remove) all records of a specific type for a domain
|
||||
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)
|
||||
await cfAccount.convenience.acmeSetDnsChallenge({
|
||||
await dnsProvider.acmeSetDnsChallenge({
|
||||
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',
|
||||
challenge: 'token-validation-string'
|
||||
challenge: 'token-validation-string',
|
||||
});
|
||||
```
|
||||
|
||||
@@ -148,7 +153,7 @@ addEventListener('fetch', event => {
|
||||
const worker = await cfAccount.workerManager.createWorker('my-worker', workerScript);
|
||||
|
||||
// List all workers
|
||||
const allWorkers = await cfAccount.workerManager.listWorkerScripts();
|
||||
const allWorkers = await cfAccount.workerManager.listWorkers();
|
||||
|
||||
// Get an existing worker
|
||||
const existingWorker = await cfAccount.workerManager.getWorker('my-worker');
|
||||
@@ -157,16 +162,16 @@ const existingWorker = await cfAccount.workerManager.getWorker('my-worker');
|
||||
await worker.setRoutes([
|
||||
{
|
||||
zoneName: 'example.com',
|
||||
pattern: 'https://api.example.com/*'
|
||||
pattern: 'https://api.example.com/*',
|
||||
},
|
||||
{
|
||||
zoneName: 'example.com',
|
||||
pattern: 'https://app.example.com/api/*'
|
||||
}
|
||||
pattern: 'https://app.example.com/api/*',
|
||||
},
|
||||
]);
|
||||
|
||||
// Get all routes for a worker
|
||||
const routes = await worker.getRoutes();
|
||||
// List all routes for a worker
|
||||
const routes = await worker.listRoutes();
|
||||
|
||||
// Update a worker's script
|
||||
await worker.updateScript(`
|
||||
@@ -200,9 +205,9 @@ async function manageCloudflare() {
|
||||
console.log(`Zone active: ${await myZone.isActive()}`);
|
||||
console.log(`Using CF nameservers: ${await myZone.isUsingCloudflareNameservers()}`);
|
||||
|
||||
// Configure DNS
|
||||
await cfAccount.convenience.createRecord('api.example.com', 'A', '192.0.2.1');
|
||||
await cfAccount.convenience.createRecord('www.example.com', 'CNAME', 'example.com');
|
||||
// Configure DNS using RecordManager
|
||||
await cfAccount.recordManager.createRecord('api.example.com', 'A', '192.0.2.1');
|
||||
await cfAccount.recordManager.createRecord('www.example.com', 'CNAME', 'example.com');
|
||||
|
||||
// Create a worker and set up routes
|
||||
const workerCode = `
|
||||
@@ -219,9 +224,7 @@ async function manageCloudflare() {
|
||||
})`;
|
||||
|
||||
const worker = await cfAccount.workerManager.createWorker('api-handler', workerCode);
|
||||
await worker.setRoutes([
|
||||
{ zoneName: 'example.com', pattern: 'https://api.example.com/*' }
|
||||
]);
|
||||
await worker.setRoutes([{ zoneName: 'example.com', pattern: 'https://api.example.com/*' }]);
|
||||
|
||||
// Purge cache for specific URLs
|
||||
await myZone.purgeUrls(['https://example.com/css/styles.css']);
|
||||
@@ -249,32 +252,78 @@ class CloudflareAccount {
|
||||
async listAccounts(): Promise<Array<ICloudflareTypes['Account']>>;
|
||||
async preselectAccountByName(accountName: string): Promise<void>;
|
||||
|
||||
// Managers
|
||||
// Managers - Clean, logical API
|
||||
readonly zoneManager: ZoneManager;
|
||||
readonly workerManager: WorkerManager;
|
||||
readonly recordManager: RecordManager;
|
||||
|
||||
// Get IConvenientDnsProvider adapter for third-party modules
|
||||
getConvenientDnsProvider(): ConvenientDnsProvider;
|
||||
|
||||
// Official Cloudflare client
|
||||
readonly apiAccount: cloudflare.Cloudflare;
|
||||
|
||||
// Convenience namespace with helper methods
|
||||
readonly convenience: {
|
||||
// Zone operations
|
||||
listZones(domainName?: string): Promise<CloudflareZone[]>;
|
||||
getZoneId(domainName: string): Promise<string>;
|
||||
purgeZone(domainName: string): Promise<void>;
|
||||
// ⚠️ Deprecated: convenience namespace (kept for backward compatibility)
|
||||
// Use the managers instead: recordManager, zoneManager, workerManager
|
||||
readonly convenience: { /* deprecated methods */ };
|
||||
}
|
||||
```
|
||||
|
||||
// DNS operations
|
||||
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>;
|
||||
### RecordManager
|
||||
|
||||
// ACME operations
|
||||
acmeSetDnsChallenge(dnsChallenge: IDnsChallenge): Promise<any>;
|
||||
acmeRemoveDnsChallenge(dnsChallenge: IDnsChallenge): Promise<any>;
|
||||
};
|
||||
Clean DNS record management (recommended over deprecated convenience methods).
|
||||
|
||||
```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>;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -335,7 +384,7 @@ class CloudflareWorker {
|
||||
readonly routes: IWorkerRoute[];
|
||||
|
||||
// Methods
|
||||
async getRoutes(): Promise<IWorkerRoute[]>;
|
||||
async listRoutes(): Promise<void>; // Populates the routes property
|
||||
async setRoutes(routes: Array<IWorkerRouteDefinition>): Promise<void>;
|
||||
async updateScript(scriptContent: string): Promise<CloudflareWorker>;
|
||||
async delete(): Promise<boolean>;
|
||||
@@ -368,13 +417,57 @@ CloudflareUtils.formatUrlForPurge('example.com/page'); // 'https://example.com/p
|
||||
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
|
||||
- **Enhanced error handling**: Better error detection and recovery
|
||||
- **Simplified API**: More consistent method signatures and return types
|
||||
- **Better type safety**: Improved TypeScript typing throughout the library
|
||||
- **Detailed logging**: More informative logging for easier debugging
|
||||
- **🎨 Clean Manager-Based Architecture**: New RecordManager, improved ZoneManager and WorkerManager with consistent naming
|
||||
- **🔌 IConvenientDnsProvider Compatibility**: New ConvenientDnsProvider adapter for seamless third-party module integration
|
||||
- **📝 Consistent Method Naming**:
|
||||
- `listZones()`, `listWorkers()`, `listRecords()` - consistent list* pattern
|
||||
- `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
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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
|
||||
import { Qenv } from '@push.rocks/qenv';
|
||||
|
||||
@@ -14,7 +14,9 @@ let testZoneName = `test-zone-${randomPrefix}.com`;
|
||||
|
||||
// Basic initialization tests
|
||||
tap.test('should create a valid instance of CloudflareAccount', async () => {
|
||||
testCloudflareAccount = new cloudflare.CloudflareAccount(await testQenv.getEnvVarOnDemand('CF_KEY'));
|
||||
testCloudflareAccount = new cloudflare.CloudflareAccount(
|
||||
await testQenv.getEnvVarOnDemand('CF_KEY'),
|
||||
);
|
||||
expect(testCloudflareAccount).toBeTypeOf('object');
|
||||
expect(testCloudflareAccount.apiAccount).toBeTypeOf('object');
|
||||
});
|
||||
@@ -22,7 +24,7 @@ tap.test('should create a valid instance of CloudflareAccount', async () => {
|
||||
tap.test('should preselect an account', async () => {
|
||||
await testCloudflareAccount.preselectAccountByName('Sandbox Account');
|
||||
expect(testCloudflareAccount.preselectedAccountId).toBeTypeOf('string');
|
||||
})
|
||||
});
|
||||
|
||||
// Zone management tests
|
||||
tap.test('.listZones() -> should list zones in account', async (tools) => {
|
||||
@@ -94,7 +96,7 @@ tap.test('should create A record for subdomain', async (tools) => {
|
||||
subdomain,
|
||||
'A',
|
||||
'127.0.0.1',
|
||||
120
|
||||
120,
|
||||
);
|
||||
expect(result).toBeTypeOf('object');
|
||||
console.log(`Created A record for ${subdomain}`);
|
||||
@@ -107,7 +109,7 @@ tap.test('should create CNAME record for subdomain', async (tools) => {
|
||||
subdomain,
|
||||
'CNAME',
|
||||
'example.com',
|
||||
120
|
||||
120,
|
||||
);
|
||||
expect(result).toBeTypeOf('object');
|
||||
console.log(`Created CNAME record for ${subdomain}`);
|
||||
@@ -120,7 +122,7 @@ tap.test('should create TXT record for subdomain', async (tools) => {
|
||||
subdomain,
|
||||
'TXT',
|
||||
'v=spf1 include:_spf.example.com ~all',
|
||||
120
|
||||
120,
|
||||
);
|
||||
expect(result).toBeTypeOf('object');
|
||||
console.log(`Created TXT record for ${subdomain}`);
|
||||
@@ -142,13 +144,59 @@ tap.test('should update A record content', async (tools) => {
|
||||
subdomain,
|
||||
'A',
|
||||
'192.168.1.1',
|
||||
120
|
||||
120,
|
||||
);
|
||||
expect(result).toBeTypeOf('object');
|
||||
expect(result.content).toEqual('192.168.1.1');
|
||||
console.log(`Updated A record for ${subdomain} to 192.168.1.1`);
|
||||
});
|
||||
|
||||
// Nested subdomain DNS record tests
|
||||
tap.test('should create A record for nested subdomain', async (tools) => {
|
||||
tools.timeout(600000);
|
||||
const nestedSubdomain = `${randomPrefix}-nested.sub.bleu.de`;
|
||||
const result = await testCloudflareAccount.convenience.createRecord(
|
||||
nestedSubdomain,
|
||||
'A',
|
||||
'127.0.0.5',
|
||||
120,
|
||||
);
|
||||
expect(result).toBeTypeOf('object');
|
||||
console.log(`Created nested A record for ${nestedSubdomain}`);
|
||||
});
|
||||
|
||||
tap.test('should get A record for nested subdomain', async (tools) => {
|
||||
tools.timeout(600000);
|
||||
const nestedSubdomain = `${randomPrefix}-nested.sub.bleu.de`;
|
||||
const record = await testCloudflareAccount.convenience.getRecord(nestedSubdomain, 'A');
|
||||
expect(record).toBeTypeOf('object');
|
||||
expect(record.content).toEqual('127.0.0.5');
|
||||
console.log(`Successfully retrieved nested A record for ${nestedSubdomain}`);
|
||||
});
|
||||
|
||||
tap.test('should update A record for nested subdomain', async (tools) => {
|
||||
tools.timeout(600000);
|
||||
const nestedSubdomain = `${randomPrefix}-nested.sub.bleu.de`;
|
||||
const result = await testCloudflareAccount.convenience.updateRecord(
|
||||
nestedSubdomain,
|
||||
'A',
|
||||
'127.0.0.6',
|
||||
120,
|
||||
);
|
||||
expect(result).toBeTypeOf('object');
|
||||
expect(result.content).toEqual('127.0.0.6');
|
||||
console.log(`Updated nested A record for ${nestedSubdomain}`);
|
||||
});
|
||||
|
||||
tap.test('should remove nested subdomain A record', async (tools) => {
|
||||
tools.timeout(600000);
|
||||
const nestedSubdomain = `${randomPrefix}-nested.sub.bleu.de`;
|
||||
await testCloudflareAccount.convenience.removeRecord(nestedSubdomain, 'A');
|
||||
const record = await testCloudflareAccount.convenience.getRecord(nestedSubdomain, 'A');
|
||||
expect(record).toBeUndefined();
|
||||
console.log(`Successfully removed nested A record for ${nestedSubdomain}`);
|
||||
});
|
||||
|
||||
tap.test('should clean TXT records', async (tools) => {
|
||||
tools.timeout(600000);
|
||||
const subdomain = `${randomPrefix}-txt-test.bleu.de`;
|
||||
@@ -188,7 +236,7 @@ tap.test('should list workers', async (tools) => {
|
||||
tools.timeout(600000);
|
||||
|
||||
try {
|
||||
const workerArray = await testCloudflareAccount.workerManager.listWorkerScripts();
|
||||
const workerArray = await testCloudflareAccount.workerManager.listWorkers();
|
||||
expect(workerArray).toBeTypeOf('array');
|
||||
console.log(`Found ${workerArray.length} workers in account`);
|
||||
} catch (error) {
|
||||
@@ -208,7 +256,7 @@ tap.test('should create a worker', async (tools) => {
|
||||
event.respondWith(new Response('Hello from Cloudflare Workers!', {
|
||||
headers: { 'content-type': 'text/plain' }
|
||||
}))
|
||||
})`
|
||||
})`,
|
||||
);
|
||||
|
||||
expect(worker).toBeTypeOf('object');
|
||||
@@ -247,7 +295,7 @@ tap.test('should get a specific worker by name', async (tools) => {
|
||||
event.respondWith(new Response('Hello from Cloudflare Workers!', {
|
||||
headers: { 'content-type': 'text/plain' }
|
||||
}))
|
||||
})`
|
||||
})`,
|
||||
);
|
||||
|
||||
// Now get the worker
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@apiclient.xyz/cloudflare',
|
||||
version: '6.3.0',
|
||||
version: '7.0.0',
|
||||
description: 'A TypeScript client for managing Cloudflare accounts, zones, DNS records, and workers with ease.'
|
||||
}
|
||||
|
||||
@@ -5,13 +5,16 @@ import * as interfaces from './interfaces/index.js';
|
||||
// interfaces
|
||||
import { WorkerManager } from './cloudflare.classes.workermanager.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 {
|
||||
export class CloudflareAccount implements plugins.tsclass.network.IConvenientDnsProvider {
|
||||
private authToken: string;
|
||||
public preselectedAccountId: string;
|
||||
|
||||
public workerManager = new WorkerManager(this);
|
||||
public zoneManager = new ZoneManager(this);
|
||||
public recordManager = new RecordManager(this);
|
||||
|
||||
public apiAccount: plugins.cloudflare.Cloudflare;
|
||||
|
||||
@@ -39,54 +42,80 @@ export class CloudflareAccount {
|
||||
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH',
|
||||
endpoint: string,
|
||||
data?: any,
|
||||
customHeaders?: Record<string, string>
|
||||
customHeaders?: Record<string, string>,
|
||||
): Promise<T> {
|
||||
try {
|
||||
const options: plugins.smartrequest.ISmartRequestOptions = {
|
||||
method,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${this.authToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
...customHeaders,
|
||||
},
|
||||
};
|
||||
logger.log('debug', `Making ${method} request to ${endpoint}`);
|
||||
|
||||
// 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 (customHeaders && customHeaders['Content-Type']?.includes('multipart/form-data')) {
|
||||
// For multipart form data, use the data directly as the request body
|
||||
options.requestBody = data;
|
||||
// For multipart form data, use formData method
|
||||
requestBuilder = requestBuilder.formData(data);
|
||||
} else {
|
||||
// For JSON requests, stringify the data
|
||||
options.requestBody = JSON.stringify(data);
|
||||
// For JSON requests, use json method
|
||||
requestBuilder = requestBuilder.json(data);
|
||||
}
|
||||
}
|
||||
|
||||
logger.log('debug', `Making ${method} request to ${endpoint}`);
|
||||
const response = await plugins.smartrequest.request(`https://api.cloudflare.com/client/v4${endpoint}`, options);
|
||||
|
||||
// Check if response is already an object (might happen with newer smartrequest versions)
|
||||
if (typeof response.body === 'object' && response.body !== null) {
|
||||
return response.body;
|
||||
// Execute the request with the appropriate method
|
||||
let response: InstanceType<typeof plugins.smartrequest.CoreResponse>;
|
||||
switch (method) {
|
||||
case 'GET':
|
||||
response = await requestBuilder.get();
|
||||
break;
|
||||
case 'POST':
|
||||
response = await requestBuilder.post();
|
||||
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 {
|
||||
if (typeof response.body === 'string' && response.body.trim()) {
|
||||
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;
|
||||
}
|
||||
return (await response.json()) as T;
|
||||
} catch (parseError) {
|
||||
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 {
|
||||
result: [],
|
||||
success: true,
|
||||
errors: [],
|
||||
messages: [`Failed to parse: ${typeof response.body === 'string' ? response.body?.substring(0, 50) : typeof response.body}...`]
|
||||
messages: [`Failed to parse: ${textBody.substring(0, 50)}...`],
|
||||
} as T;
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -107,6 +136,16 @@ export class CloudflareAccount {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = {
|
||||
/**
|
||||
* Lists all accounts accessible with the current API token
|
||||
@@ -131,6 +170,7 @@ export class CloudflareAccount {
|
||||
/**
|
||||
* gets a zone id of a domain from cloudflare
|
||||
* @param domainName
|
||||
* @deprecated Use zoneManager.getZoneId() instead
|
||||
*/
|
||||
getZoneId: async (domainName: string) => {
|
||||
const domain = new plugins.smartstring.Domain(domainName);
|
||||
@@ -149,17 +189,21 @@ export class CloudflareAccount {
|
||||
* gets a record
|
||||
* @param domainNameArg
|
||||
* @param typeArg
|
||||
* @deprecated Use recordManager.getRecord() or getConvenientDnsProvider().getRecord() instead
|
||||
*/
|
||||
getRecord: async (
|
||||
domainNameArg: string,
|
||||
typeArg: plugins.tsclass.network.TDnsRecordType
|
||||
typeArg: plugins.tsclass.network.TDnsRecordType,
|
||||
): Promise<plugins.ICloudflareTypes['Record'] | undefined> => {
|
||||
try {
|
||||
const domain = new plugins.smartstring.Domain(domainNameArg);
|
||||
const recordArrayArg = await this.convenience.listRecords(domain.zoneName);
|
||||
|
||||
if (!Array.isArray(recordArrayArg)) {
|
||||
logger.log('warn', `Expected records array for ${domainNameArg} but got ${typeof recordArrayArg}`);
|
||||
logger.log(
|
||||
'warn',
|
||||
`Expected records array for ${domainNameArg} but got ${typeof recordArrayArg}`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -175,12 +219,13 @@ export class CloudflareAccount {
|
||||
},
|
||||
/**
|
||||
* creates a record
|
||||
* @deprecated Use recordManager.createRecord() or getConvenientDnsProvider().createRecord() instead
|
||||
*/
|
||||
createRecord: async (
|
||||
domainNameArg: string,
|
||||
typeArg: plugins.tsclass.network.TDnsRecordType,
|
||||
contentArg: string,
|
||||
ttlArg = 1
|
||||
ttlArg = 1,
|
||||
): Promise<any> => {
|
||||
const domain = new plugins.smartstring.Domain(domainNameArg);
|
||||
const zoneId = await this.convenience.getZoneId(domain.zoneName);
|
||||
@@ -190,17 +235,18 @@ export class CloudflareAccount {
|
||||
name: domain.fullName,
|
||||
content: contentArg,
|
||||
ttl: ttlArg,
|
||||
})
|
||||
});
|
||||
return response;
|
||||
},
|
||||
/**
|
||||
* removes a record from Cloudflare
|
||||
* @param domainNameArg
|
||||
* @param typeArg
|
||||
* @deprecated Use recordManager.deleteRecord() or getConvenientDnsProvider().removeRecord() instead
|
||||
*/
|
||||
removeRecord: async (
|
||||
domainNameArg: string,
|
||||
typeArg: plugins.tsclass.network.TDnsRecordType
|
||||
typeArg: plugins.tsclass.network.TDnsRecordType,
|
||||
): Promise<any> => {
|
||||
const domain = new plugins.smartstring.Domain(domainNameArg);
|
||||
const zoneId = await this.convenience.getZoneId(domain.zoneName);
|
||||
@@ -222,6 +268,7 @@ export class CloudflareAccount {
|
||||
|
||||
/**
|
||||
* 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) => {
|
||||
try {
|
||||
@@ -229,18 +276,26 @@ export class CloudflareAccount {
|
||||
const domain = new plugins.smartstring.Domain(domainNameArg);
|
||||
const zoneId = await this.convenience.getZoneId(domain.zoneName);
|
||||
|
||||
const records = await this.convenience.listRecords(domainNameArg);
|
||||
// List all records in the zone for this domain
|
||||
const records = await this.convenience.listRecords(domain.zoneName);
|
||||
|
||||
if (!Array.isArray(records)) {
|
||||
logger.log('warn', `Expected records array for ${domainNameArg} but got ${typeof records}`);
|
||||
logger.log(
|
||||
'warn',
|
||||
`Expected records array for ${domainNameArg} but got ${typeof records}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Only delete records matching the specified name and type
|
||||
const recordsToDelete = records.filter((recordArg) => {
|
||||
return recordArg.type === typeArg;
|
||||
return recordArg.type === typeArg && recordArg.name === domainNameArg;
|
||||
});
|
||||
|
||||
logger.log('info', `Found ${recordsToDelete.length} ${typeArg} records to delete for ${domainNameArg}`);
|
||||
logger.log(
|
||||
'info',
|
||||
`Found ${recordsToDelete.length} ${typeArg} records to delete for ${domainNameArg}`,
|
||||
);
|
||||
|
||||
for (const recordToDelete of recordsToDelete) {
|
||||
try {
|
||||
@@ -261,7 +316,10 @@ export class CloudflareAccount {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.log('error', `Error cleaning ${typeArg} records for ${domainNameArg}: ${error.message}`);
|
||||
logger.log(
|
||||
'error',
|
||||
`Error cleaning ${typeArg} records for ${domainNameArg}: ${error.message}`,
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -272,12 +330,13 @@ export class CloudflareAccount {
|
||||
* @param contentArg New content for the record
|
||||
* @param ttlArg Time to live in seconds (optional)
|
||||
* @returns Updated record
|
||||
* @deprecated Use recordManager.updateRecord() or getConvenientDnsProvider().updateRecord() instead
|
||||
*/
|
||||
updateRecord: async (
|
||||
domainNameArg: string,
|
||||
typeArg: plugins.tsclass.network.TDnsRecordType,
|
||||
contentArg: string,
|
||||
ttlArg: number = 1
|
||||
ttlArg: number = 1,
|
||||
): Promise<plugins.ICloudflareTypes['Record']> => {
|
||||
const domain = new plugins.smartstring.Domain(domainNameArg);
|
||||
const zoneId = await this.convenience.getZoneId(domain.zoneName);
|
||||
@@ -286,7 +345,10 @@ export class CloudflareAccount {
|
||||
const record = await this.convenience.getRecord(domainNameArg, typeArg);
|
||||
|
||||
if (!record) {
|
||||
logger.log('warn', `Record ${domainNameArg} of type ${typeArg} not found for update, creating instead`);
|
||||
logger.log(
|
||||
'warn',
|
||||
`Record ${domainNameArg} of type ${typeArg} not found for update, creating instead`,
|
||||
);
|
||||
return this.convenience.createRecord(domainNameArg, typeArg, contentArg, ttlArg);
|
||||
}
|
||||
|
||||
@@ -297,7 +359,7 @@ export class CloudflareAccount {
|
||||
type: typeArg as any,
|
||||
name: domain.fullName,
|
||||
content: contentArg,
|
||||
ttl: ttlArg
|
||||
ttl: ttlArg,
|
||||
});
|
||||
|
||||
return updatedRecord;
|
||||
@@ -305,6 +367,7 @@ export class CloudflareAccount {
|
||||
/**
|
||||
* list all records of a specified domain name
|
||||
* @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) => {
|
||||
try {
|
||||
@@ -329,6 +392,7 @@ export class CloudflareAccount {
|
||||
/**
|
||||
* list all zones in the associated authenticated account
|
||||
* @param domainName optional filter by domain name
|
||||
* @deprecated Use zoneManager.listZones() instead
|
||||
*/
|
||||
listZones: async (domainName?: string) => {
|
||||
try {
|
||||
@@ -344,15 +408,38 @@ export class CloudflareAccount {
|
||||
zones.push(zone);
|
||||
}
|
||||
|
||||
logger.log('info', `Found ${zones.length} zones${domainName ? ` matching ${domainName}` : ''}`);
|
||||
logger.log(
|
||||
'info',
|
||||
`Found ${zones.length} zones${domainName ? ` matching ${domainName}` : ''}`,
|
||||
);
|
||||
return zones;
|
||||
} catch (error) {
|
||||
logger.log('error', `Failed to list zones: ${error.message}`);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 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
|
||||
* @deprecated Use getConvenientDnsProvider().isDomainSupported() instead
|
||||
*/
|
||||
isDomainSupported: async (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.convenience.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;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* purges a zone
|
||||
* @deprecated Use zoneManager.purgeZone() instead
|
||||
*/
|
||||
purgeZone: async (domainName: string): Promise<void> => {
|
||||
const domain = new plugins.smartstring.Domain(domainName);
|
||||
@@ -364,15 +451,21 @@ export class CloudflareAccount {
|
||||
},
|
||||
|
||||
// acme convenience functions
|
||||
/**
|
||||
* @deprecated Use getConvenientDnsProvider().acmeSetDnsChallenge() instead
|
||||
*/
|
||||
acmeSetDnsChallenge: async (dnsChallenge: plugins.tsclass.network.IDnsChallenge) => {
|
||||
await this.convenience.cleanRecord(dnsChallenge.hostName, 'TXT');
|
||||
await this.convenience.createRecord(
|
||||
dnsChallenge.hostName,
|
||||
'TXT',
|
||||
dnsChallenge.challenge,
|
||||
120
|
||||
120,
|
||||
);
|
||||
},
|
||||
/**
|
||||
* @deprecated Use getConvenientDnsProvider().acmeRemoveDnsChallenge() instead
|
||||
*/
|
||||
acmeRemoveDnsChallenge: async (dnsChallenge: plugins.tsclass.network.IDnsChallenge) => {
|
||||
await this.convenience.removeRecord(dnsChallenge.hostName, 'TXT');
|
||||
},
|
||||
|
||||
178
ts/cloudflare.classes.convenientdnsprovider.ts
Normal file
178
ts/cloudflare.classes.convenientdnsprovider.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,9 @@ export class CloudflareRecord {
|
||||
* @param apiObject Cloudflare DNS record API object
|
||||
* @returns CloudflareRecord instance
|
||||
*/
|
||||
public static createFromApiObject(apiObject: plugins.ICloudflareTypes['Record']): CloudflareRecord {
|
||||
public static createFromApiObject(
|
||||
apiObject: plugins.ICloudflareTypes['Record'],
|
||||
): CloudflareRecord {
|
||||
const record = new CloudflareRecord();
|
||||
Object.assign(record, apiObject);
|
||||
return record;
|
||||
@@ -52,7 +54,7 @@ export class CloudflareRecord {
|
||||
public async update(
|
||||
cloudflareAccount: any,
|
||||
newContent: string,
|
||||
ttl?: number
|
||||
ttl?: number,
|
||||
): Promise<CloudflareRecord> {
|
||||
logger.log('info', `Updating record ${this.name} (${this.type}) with new content`);
|
||||
|
||||
@@ -62,7 +64,7 @@ export class CloudflareRecord {
|
||||
name: this.name,
|
||||
content: newContent,
|
||||
ttl: ttl || this.ttl,
|
||||
proxied: this.proxied
|
||||
proxied: this.proxied,
|
||||
});
|
||||
|
||||
// Update this instance
|
||||
@@ -84,7 +86,7 @@ export class CloudflareRecord {
|
||||
logger.log('info', `Deleting record ${this.name} (${this.type})`);
|
||||
|
||||
await cloudflareAccount.apiAccount.dns.records.delete(this.id, {
|
||||
zone_id: this.zone_id
|
||||
zone_id: this.zone_id,
|
||||
});
|
||||
|
||||
return true;
|
||||
|
||||
198
ts/cloudflare.classes.recordmanager.ts
Normal file
198
ts/cloudflare.classes.recordmanager.ts
Normal 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}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,11 +16,11 @@ export class CloudflareWorker {
|
||||
// STATIC
|
||||
public static async fromApiObject(
|
||||
workerManager: WorkerManager,
|
||||
apiObject
|
||||
apiObject,
|
||||
): Promise<CloudflareWorker> {
|
||||
const newWorker = new CloudflareWorker(workerManager);
|
||||
Object.assign(newWorker, apiObject);
|
||||
await newWorker.getRoutes();
|
||||
await newWorker.listRoutes();
|
||||
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 {
|
||||
this.routes = []; // Reset routes before fetching
|
||||
|
||||
@@ -68,7 +68,7 @@ export class CloudflareWorker {
|
||||
// Get worker routes for this zone
|
||||
const apiRoutes = [];
|
||||
for await (const route of this.workerManager.cfAccount.apiAccount.workers.routes.list({
|
||||
zone_id: zone.id
|
||||
zone_id: zone.id,
|
||||
})) {
|
||||
apiRoutes.push(route);
|
||||
}
|
||||
@@ -81,7 +81,10 @@ export class CloudflareWorker {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.log('error', `Failed to get worker routes for zone ${zone.name || zone.id}: ${error.message}`);
|
||||
logger.log(
|
||||
'error',
|
||||
`Failed to get worker routes for zone ${zone.name || zone.id}: ${error.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +102,7 @@ export class CloudflareWorker {
|
||||
*/
|
||||
public async setRoutes(routeArray: IWorkerRouteDefinition[]) {
|
||||
// First get all existing routes to determine what we need to create/update
|
||||
await this.getRoutes();
|
||||
await this.listRoutes();
|
||||
|
||||
for (const newRoute of routeArray) {
|
||||
// Determine whether a route is new, needs an update, or is already up to date
|
||||
@@ -120,7 +123,9 @@ export class CloudflareWorker {
|
||||
|
||||
try {
|
||||
// Get the zone ID
|
||||
const zone = await this.workerManager.cfAccount.zoneManager.getZoneByName(newRoute.zoneName);
|
||||
const zone = await this.workerManager.cfAccount.zoneManager.getZoneByName(
|
||||
newRoute.zoneName,
|
||||
);
|
||||
|
||||
if (!zone) {
|
||||
logger.log('error', `Zone ${newRoute.zoneName} not found`);
|
||||
@@ -132,7 +137,7 @@ export class CloudflareWorker {
|
||||
await this.workerManager.cfAccount.apiAccount.workers.routes.create({
|
||||
zone_id: zone.id,
|
||||
pattern: newRoute.pattern,
|
||||
script: this.id
|
||||
script: this.id,
|
||||
});
|
||||
|
||||
logger.log('info', `Created new route ${newRoute.pattern} for worker ${this.id}`);
|
||||
@@ -140,7 +145,7 @@ export class CloudflareWorker {
|
||||
await this.workerManager.cfAccount.apiAccount.workers.routes.update(existingRouteId, {
|
||||
zone_id: zone.id,
|
||||
pattern: newRoute.pattern,
|
||||
script: this.id
|
||||
script: this.id,
|
||||
});
|
||||
|
||||
logger.log('info', `Updated route ${newRoute.pattern} for worker ${this.id}`);
|
||||
@@ -151,7 +156,7 @@ export class CloudflareWorker {
|
||||
}
|
||||
|
||||
// Refresh routes after all changes
|
||||
await this.getRoutes();
|
||||
await this.listRoutes();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,12 +172,19 @@ export class CloudflareWorker {
|
||||
try {
|
||||
logger.log('info', `Updating script for worker ${this.id}`);
|
||||
|
||||
// Use the official client to update the script
|
||||
const updatedWorker = await this.workerManager.cfAccount.apiAccount.workers.scripts.content.update(this.id, {
|
||||
// Use the official client to update the script (upload new content)
|
||||
// Build params as any to include the script form part without TS errors
|
||||
const updateParams: any = {
|
||||
account_id: this.workerManager.cfAccount.preselectedAccountId,
|
||||
"CF-WORKER-BODY-PART": scriptContent,
|
||||
metadata: {}
|
||||
});
|
||||
metadata: { body_part: 'script' },
|
||||
};
|
||||
updateParams['CF-WORKER-BODY-PART'] = 'script';
|
||||
updateParams['script'] = scriptContent;
|
||||
const updatedWorker =
|
||||
await this.workerManager.cfAccount.apiAccount.workers.scripts.content.update(
|
||||
this.id,
|
||||
updateParams,
|
||||
);
|
||||
|
||||
// Update this instance with new data
|
||||
if (updatedWorker && typeof updatedWorker === 'object') {
|
||||
@@ -203,7 +215,7 @@ export class CloudflareWorker {
|
||||
|
||||
// Use the official client to delete the worker
|
||||
await this.workerManager.cfAccount.apiAccount.workers.scripts.delete(this.id, {
|
||||
account_id: this.workerManager.cfAccount.preselectedAccountId
|
||||
account_id: this.workerManager.cfAccount.preselectedAccountId,
|
||||
});
|
||||
|
||||
return true;
|
||||
|
||||
@@ -22,12 +22,15 @@ export class WorkerManager {
|
||||
}
|
||||
|
||||
try {
|
||||
// Use the official client to create/update the worker
|
||||
await this.cfAccount.apiAccount.workers.scripts.content.update(workerName, {
|
||||
// Use the official client to create/update the worker (upload script content)
|
||||
// Build params as any to include the script form part without TS errors
|
||||
const contentParams: any = {
|
||||
account_id: this.cfAccount.preselectedAccountId,
|
||||
"CF-WORKER-BODY-PART": workerScript,
|
||||
metadata: {}
|
||||
});
|
||||
metadata: { body_part: 'script' },
|
||||
};
|
||||
contentParams['CF-WORKER-BODY-PART'] = 'script';
|
||||
contentParams['script'] = workerScript;
|
||||
await this.cfAccount.apiAccount.workers.scripts.content.update(workerName, contentParams);
|
||||
|
||||
// Create a new worker instance
|
||||
const worker = new CloudflareWorker(this);
|
||||
@@ -36,7 +39,7 @@ export class WorkerManager {
|
||||
|
||||
// Initialize the worker and get its routes
|
||||
try {
|
||||
await worker.getRoutes();
|
||||
await worker.listRoutes();
|
||||
} catch (routeError) {
|
||||
logger.log('warn', `Failed to get routes for worker ${workerName}: ${routeError.message}`);
|
||||
// Continue anyway since the worker was created
|
||||
@@ -62,7 +65,7 @@ export class WorkerManager {
|
||||
try {
|
||||
// Get the worker script using the official client
|
||||
const workerScript = await this.cfAccount.apiAccount.workers.scripts.get(workerName, {
|
||||
account_id: this.cfAccount.preselectedAccountId
|
||||
account_id: this.cfAccount.preselectedAccountId,
|
||||
});
|
||||
|
||||
// Create a new worker instance
|
||||
@@ -76,7 +79,7 @@ export class WorkerManager {
|
||||
|
||||
// Initialize the worker and get its routes
|
||||
try {
|
||||
await worker.getRoutes();
|
||||
await worker.listRoutes();
|
||||
} catch (routeError) {
|
||||
logger.log('warn', `Failed to get routes for worker ${workerName}: ${routeError.message}`);
|
||||
// Continue anyway since we found the worker
|
||||
@@ -93,7 +96,7 @@ export class WorkerManager {
|
||||
* Lists all worker scripts
|
||||
* @returns Array of worker scripts
|
||||
*/
|
||||
public async listWorkerScripts() {
|
||||
public async listWorkers() {
|
||||
if (!this.cfAccount.preselectedAccountId) {
|
||||
throw new Error('No account selected. Please select it first on the account.');
|
||||
}
|
||||
@@ -115,9 +118,9 @@ export class WorkerManager {
|
||||
logger.log('warn', `Error while listing workers with async iterator: ${error.message}`);
|
||||
|
||||
// Try alternative approach if the async iterator fails
|
||||
const result = await this.cfAccount.apiAccount.workers.scripts.list({
|
||||
const result = (await this.cfAccount.apiAccount.workers.scripts.list({
|
||||
account_id: this.cfAccount.preselectedAccountId,
|
||||
}) as any;
|
||||
})) as any;
|
||||
|
||||
// Check if the result has a 'result' property (older API response format)
|
||||
if (result && result.result && Array.isArray(result.result)) {
|
||||
@@ -146,7 +149,7 @@ export class WorkerManager {
|
||||
|
||||
try {
|
||||
await this.cfAccount.apiAccount.workers.scripts.delete(workerName, {
|
||||
account_id: this.cfAccount.preselectedAccountId
|
||||
account_id: this.cfAccount.preselectedAccountId,
|
||||
});
|
||||
logger.log('info', `Worker '${workerName}' deleted successfully`);
|
||||
return true;
|
||||
|
||||
@@ -34,7 +34,7 @@ export class CloudflareZone {
|
||||
*/
|
||||
public static createFromApiObject(
|
||||
apiObject: plugins.ICloudflareTypes['Zone'],
|
||||
cfAccount?: CloudflareAccount
|
||||
cfAccount?: CloudflareAccount,
|
||||
): CloudflareZone {
|
||||
const cloudflareZone = new CloudflareZone();
|
||||
Object.assign(cloudflareZone, apiObject);
|
||||
@@ -62,7 +62,7 @@ export class CloudflareZone {
|
||||
*/
|
||||
public async enableDevelopmentMode(
|
||||
cfAccount?: CloudflareAccount,
|
||||
duration: number = 10800
|
||||
duration: number = 10800,
|
||||
): Promise<CloudflareZone> {
|
||||
const account = cfAccount || this.cfAccount;
|
||||
if (!account) {
|
||||
@@ -76,7 +76,7 @@ export class CloudflareZone {
|
||||
// We'll use the request method for this specific case
|
||||
await account.request('PATCH', `/zones/${this.id}/settings/development_mode`, {
|
||||
value: 'on',
|
||||
time: duration
|
||||
time: duration,
|
||||
});
|
||||
|
||||
this.development_mode = duration;
|
||||
@@ -104,7 +104,7 @@ export class CloudflareZone {
|
||||
// The official client doesn't have a direct method for development mode
|
||||
// We'll use the request method for this specific case
|
||||
await account.request('PATCH', `/zones/${this.id}/settings/development_mode`, {
|
||||
value: 'off'
|
||||
value: 'off',
|
||||
});
|
||||
|
||||
this.development_mode = 0;
|
||||
@@ -131,7 +131,7 @@ export class CloudflareZone {
|
||||
try {
|
||||
await account.apiAccount.cache.purge({
|
||||
zone_id: this.id,
|
||||
purge_everything: true
|
||||
purge_everything: true,
|
||||
});
|
||||
return true;
|
||||
} catch (error) {
|
||||
@@ -161,7 +161,7 @@ export class CloudflareZone {
|
||||
try {
|
||||
await account.apiAccount.cache.purge({
|
||||
zone_id: this.id,
|
||||
files: urls
|
||||
files: urls,
|
||||
});
|
||||
return true;
|
||||
} catch (error) {
|
||||
@@ -189,7 +189,7 @@ export class CloudflareZone {
|
||||
}
|
||||
|
||||
// If they're different, and current nameservers are Cloudflare's
|
||||
return this.name_servers.some(ns => ns.includes('cloudflare'));
|
||||
return this.name_servers.some((ns) => ns.includes('cloudflare'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -205,7 +205,7 @@ export class CloudflareZone {
|
||||
vanity_name_servers: string[];
|
||||
type: 'full' | 'partial' | 'secondary';
|
||||
}>,
|
||||
cfAccount?: CloudflareAccount
|
||||
cfAccount?: CloudflareAccount,
|
||||
): Promise<CloudflareZone> {
|
||||
const account = cfAccount || this.cfAccount;
|
||||
if (!account) {
|
||||
@@ -219,7 +219,7 @@ export class CloudflareZone {
|
||||
const response: { result: interfaces.ICflareZone } = await account.request(
|
||||
'PATCH',
|
||||
`/zones/${this.id}`,
|
||||
settings
|
||||
settings,
|
||||
);
|
||||
|
||||
Object.assign(this, response.result);
|
||||
|
||||
@@ -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
|
||||
* @returns Array of CloudflareZone instances
|
||||
*/
|
||||
public async getZones(zoneName?: string): Promise<CloudflareZone[]> {
|
||||
public async listZones(zoneName?: string): Promise<CloudflareZone[]> {
|
||||
try {
|
||||
const options: any = { per_page: 50 };
|
||||
|
||||
@@ -30,21 +30,41 @@ export class ZoneManager {
|
||||
zones.push(zone);
|
||||
}
|
||||
|
||||
return zones.map(zone => CloudflareZone.createFromApiObject(zone, this.cfAccount));
|
||||
return zones.map((zone) => CloudflareZone.createFromApiObject(zone, this.cfAccount));
|
||||
} catch (error) {
|
||||
logger.log('error', `Failed to fetch zones: ${error.message}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @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);
|
||||
const zones = await this.listZones(zoneName);
|
||||
return zones.find((zone) => zone.name === zoneName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +77,7 @@ export class ZoneManager {
|
||||
// Use the request method instead of the zones.get method to avoid type issues
|
||||
const response: { result: interfaces.ICflareZone } = await this.cfAccount.request(
|
||||
'GET',
|
||||
`/zones/${zoneId}`
|
||||
`/zones/${zoneId}`,
|
||||
);
|
||||
|
||||
return CloudflareZone.createFromApiObject(response.result as any, this.cfAccount);
|
||||
@@ -77,7 +97,7 @@ export class ZoneManager {
|
||||
public async createZone(
|
||||
zoneName: string,
|
||||
jumpStart: boolean = false,
|
||||
accountId?: string
|
||||
accountId?: string,
|
||||
): Promise<CloudflareZone | undefined> {
|
||||
const useAccountId = accountId || this.cfAccount.preselectedAccountId;
|
||||
|
||||
@@ -95,8 +115,8 @@ export class ZoneManager {
|
||||
{
|
||||
name: zoneName,
|
||||
jump_start: jumpStart,
|
||||
account: { id: useAccountId }
|
||||
}
|
||||
account: { id: useAccountId },
|
||||
},
|
||||
);
|
||||
|
||||
return CloudflareZone.createFromApiObject(response.result as any, this.cfAccount);
|
||||
@@ -130,8 +150,8 @@ export class ZoneManager {
|
||||
* @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);
|
||||
const zones = await this.listZones(zoneName);
|
||||
return zones.some((zone) => zone.name === zoneName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -148,8 +168,8 @@ export class ZoneManager {
|
||||
'PATCH',
|
||||
`/zones/${zoneId}`,
|
||||
{
|
||||
status: 'active'
|
||||
}
|
||||
status: 'active',
|
||||
},
|
||||
);
|
||||
|
||||
return CloudflareZone.createFromApiObject(response.result as any, this.cfAccount);
|
||||
@@ -171,7 +191,7 @@ export class ZoneManager {
|
||||
// For this specific endpoint, we'll use the request method
|
||||
const response: { result: interfaces.ICflareZone } = await this.cfAccount.request(
|
||||
'PUT',
|
||||
`/zones/${zoneId}/activation_check`
|
||||
`/zones/${zoneId}/activation_check`,
|
||||
);
|
||||
|
||||
return CloudflareZone.createFromApiObject(response.result as any, this.cfAccount);
|
||||
@@ -180,4 +200,18 @@ export class ZoneManager {
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
@@ -49,9 +49,23 @@ export class CloudflareUtils {
|
||||
*/
|
||||
public static isValidRecordType(type: string): boolean {
|
||||
const validTypes: plugins.tsclass.network.TDnsRecordType[] = [
|
||||
'A', 'AAAA', 'CNAME', 'TXT', 'SRV', 'LOC', 'MX',
|
||||
'NS', 'CAA', 'CERT', 'DNSKEY', 'DS', 'NAPTR', 'SMIMEA',
|
||||
'SSHFP', 'TLSA', 'URI'
|
||||
'A',
|
||||
'AAAA',
|
||||
'CNAME',
|
||||
'TXT',
|
||||
'SRV',
|
||||
'LOC',
|
||||
'MX',
|
||||
'NS',
|
||||
'CAA',
|
||||
'CERT',
|
||||
'DNSKEY',
|
||||
'DS',
|
||||
'NAPTR',
|
||||
'SMIMEA',
|
||||
'SSHFP',
|
||||
'TLSA',
|
||||
'URI',
|
||||
// Note: SPF has been removed as it's not in TDnsRecordType
|
||||
];
|
||||
return validTypes.includes(type as any);
|
||||
@@ -108,7 +122,10 @@ export class CloudflareUtils {
|
||||
* @returns Combined results from all pages
|
||||
*/
|
||||
public static async paginateResults<T>(
|
||||
makeRequest: (page: number, perPage: number) => Promise<{ result: T[], result_info: { total_pages: number } }>
|
||||
makeRequest: (
|
||||
page: number,
|
||||
perPage: number,
|
||||
) => Promise<{ result: T[]; result_info: { total_pages: number } }>,
|
||||
): Promise<T[]> {
|
||||
const perPage = 50; // Cloudflare's maximum
|
||||
let page = 1;
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
export { CloudflareAccount } from './cloudflare.classes.account.js';
|
||||
export { CloudflareWorker, type IWorkerRoute, type IWorkerRouteDefinition } 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 { RecordManager } from './cloudflare.classes.recordmanager.js';
|
||||
export { CloudflareZone } from './cloudflare.classes.zone.js';
|
||||
export { ZoneManager } from './cloudflare.classes.zonemanager.js';
|
||||
export { ConvenientDnsProvider } from './cloudflare.classes.convenientdnsprovider.js';
|
||||
export { CloudflareUtils } from './cloudflare.utils.js';
|
||||
export { commitinfo } from './00_commitinfo_data.js';
|
||||
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"esModuleInterop": true,
|
||||
"verbatimModuleSyntax": true
|
||||
"verbatimModuleSyntax": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {}
|
||||
},
|
||||
"exclude": [
|
||||
"dist_*/**/*.d.ts"
|
||||
|
||||
Reference in New Issue
Block a user