Compare commits

...

7 Commits

Author SHA1 Message Date
4600749442 6.0.5 2024-06-17 01:36:40 +02:00
b0f8d1e4d0 fix(start supporting workers again): update 2024-06-17 01:36:39 +02:00
902ca30529 update license info 2024-06-15 19:56:32 +02:00
5731150157 update readme 2024-06-15 19:55:48 +02:00
f39f8cd33c switch to official cloudflare api client while keeping class based approach 2024-06-15 19:47:09 +02:00
1d2e0974b2 6.0.4 2024-06-15 17:08:04 +02:00
801f86fede fix(core): update 2024-06-15 17:08:03 +02:00
19 changed files with 5807 additions and 16477 deletions

View File

@ -1,128 +0,0 @@
# gitzone ci_default
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
cache:
paths:
- .npmci_cache/
key: '$CI_BUILD_STAGE'
stages:
- security
- test
- release
- metadata
before_script:
- npm install -g @shipzone/npmci
# ====================
# security stage
# ====================
auditProductionDependencies:
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
stage: security
script:
- npmci npm prepare
- npmci command npm install --production --ignore-scripts
- npmci command npm config set registry https://registry.npmjs.org
- npmci command npm audit --audit-level=high --only=prod --production
tags:
- docker
allow_failure: true
auditDevDependencies:
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
stage: security
script:
- npmci npm prepare
- npmci command npm install --ignore-scripts
- npmci command npm config set registry https://registry.npmjs.org
- npmci command npm audit --audit-level=high --only=dev
tags:
- docker
allow_failure: true
# ====================
# test stage
# ====================
testStable:
stage: test
script:
- npmci npm prepare
- npmci node install stable
- npmci npm install
- npmci npm test
coverage: /\d+.?\d+?\%\s*coverage/
tags:
- docker
testBuild:
stage: test
script:
- npmci npm prepare
- npmci node install stable
- npmci npm install
- npmci command npm run build
coverage: /\d+.?\d+?\%\s*coverage/
tags:
- docker
release:
stage: release
script:
- npmci node install stable
- npmci npm publish
only:
- tags
tags:
- lossless
- docker
- notpriv
# ====================
# metadata stage
# ====================
codequality:
stage: metadata
allow_failure: true
only:
- tags
script:
- npmci command npm install -g typescript
- npmci npm prepare
- npmci npm install
tags:
- lossless
- docker
- priv
trigger:
stage: metadata
script:
- npmci trigger
only:
- tags
tags:
- lossless
- docker
- notpriv
pages:
stage: metadata
script:
- npmci node install stable
- npmci npm prepare
- npmci npm install
- npmci command npm run buildDocs
tags:
- lossless
- docker
- notpriv
only:
- tags
artifacts:
expire_in: 1 week
paths:
- public
allow_failure: true

View File

@ -1,6 +1,4 @@
The MIT License (MIT)
Copyright (c) 2016 Lossless GmbH
Copyright (c) 2014 Task Venture Capital GmbH (hello@task.vc)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -18,4 +16,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.

View File

@ -9,9 +9,21 @@
"githost": "gitlab.com",
"gitscope": "mojoio",
"gitrepo": "cloudflare",
"description": "easy cloudflare management",
"description": "A TypeScript client for managing Cloudflare accounts, zones, DNS records, and workers with ease.",
"npmPackagename": "@apiclient.xyz/cloudflare",
"license": "MIT"
"license": "MIT",
"keywords": [
"Cloudflare",
"DNS management",
"zone management",
"worker management",
"TypeScript",
"API client",
"cloud infrastructure",
"automated DNS",
"CDN management",
"open source"
]
}
}
}

13094
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +1,32 @@
{
"name": "@apiclient.xyz/cloudflare",
"version": "6.0.3",
"version": "6.0.5",
"private": false,
"description": "easy cloudflare management",
"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/)",
"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"
},
"repository": {
"type": "git",
"url": "git+https://gitlab.com/pushrocks/cflare.git"
},
"keywords": [
"Push.Rocks",
"cloudflare"
"Cloudflare",
"DNS management",
"zone management",
"worker management",
"TypeScript",
"API client",
"cloud infrastructure",
"automated DNS",
"CDN management",
"open source"
],
"author": "Lossless GmbH",
"license": "MIT",
@ -26,20 +35,22 @@
},
"homepage": "https://gitlab.com/pushrocks/cflare#readme",
"dependencies": {
"@pushrocks/smartdelay": "^3.0.1",
"@pushrocks/smartlog": "^3.0.2",
"@pushrocks/smartpromise": "^4.0.2",
"@pushrocks/smartrequest": "^2.0.15",
"@pushrocks/smartstring": "^4.0.5",
"@tsclass/tsclass": "^4.0.42"
"@push.rocks/smartdelay": "^3.0.1",
"@push.rocks/smartlog": "^3.0.2",
"@push.rocks/smartpromise": "^4.0.2",
"@push.rocks/smartrequest": "^2.0.15",
"@push.rocks/smartstring": "^4.0.5",
"@tsclass/tsclass": "^4.0.58",
"cloudflare": "^3.2.0"
},
"devDependencies": {
"@gitzone/tsbuild": "^2.1.66",
"@gitzone/tsrun": "^1.2.42",
"@gitzone/tstest": "^1.0.74",
"@pushrocks/qenv": "^5.0.2",
"@pushrocks/tapbundle": "^5.0.4",
"@types/node": "^20.3.1"
"@git.zone/tsbuild": "^2.1.66",
"@git.zone/tsrun": "^1.2.42",
"@git.zone/tstest": "^1.0.74",
"@push.rocks/qenv": "^6.0.5",
"@push.rocks/tapbundle": "^5.0.4",
"@types/node": "^20.3.1",
"openapi-typescript": "^6.7.6"
},
"files": [
"ts/**/*",

8287
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

1
readme.hints.md Normal file
View File

@ -0,0 +1 @@
- unofficial TypeScript cloudflare api client coming with a lot of convenience.

264
readme.md
View File

@ -1,52 +1,250 @@
# @apiclient.xyz/cloudflare
easy cloudflare management
## Availabililty and Links
* [npmjs.org (npm package)](https://www.npmjs.com/package/@apiclient.xyz/cloudflare)
* [gitlab.com (source)](https://gitlab.com/mojoio/cloudflare)
* [github.com (source mirror)](https://github.com/mojoio/cloudflare)
* [docs (typedoc)](https://mojoio.gitlab.io/cloudflare/)
## Install
To install the `@apiclient.xyz/cloudflare` package, you can use npm. Simply run the following command:
## Status for master
```bash
npm install @apiclient.xyz/cloudflare
```
Status Category | Status Badge
-- | --
GitLab Pipelines | [![pipeline status](https://gitlab.com/mojoio/cloudflare/badges/master/pipeline.svg)](https://lossless.cloud)
GitLab Pipline Test Coverage | [![coverage report](https://gitlab.com/mojoio/cloudflare/badges/master/coverage.svg)](https://lossless.cloud)
npm | [![npm downloads per month](https://badgen.net/npm/dy/@apiclient.xyz/cloudflare)](https://lossless.cloud)
Snyk | [![Known Vulnerabilities](https://badgen.net/snyk/mojoio/cloudflare)](https://lossless.cloud)
TypeScript Support | [![TypeScript](https://badgen.net/badge/TypeScript/>=%203.x/blue?icon=typescript)](https://lossless.cloud)
node Support | [![node](https://img.shields.io/badge/node->=%2010.x.x-blue.svg)](https://nodejs.org/dist/latest-v10.x/docs/api/)
Code Style | [![Code Style](https://badgen.net/badge/style/prettier/purple)](https://lossless.cloud)
PackagePhobia (total standalone install weight) | [![PackagePhobia](https://badgen.net/packagephobia/install/@apiclient.xyz/cloudflare)](https://lossless.cloud)
PackagePhobia (package size on registry) | [![PackagePhobia](https://badgen.net/packagephobia/publish/@apiclient.xyz/cloudflare)](https://lossless.cloud)
BundlePhobia (total size when bundled) | [![BundlePhobia](https://badgen.net/bundlephobia/minzip/@apiclient.xyz/cloudflare)](https://lossless.cloud)
Platform support | [![Supports Windows 10](https://badgen.net/badge/supports%20Windows%2010/yes/green?icon=windows)](https://lossless.cloud) [![Supports Mac OS X](https://badgen.net/badge/supports%20Mac%20OS%20X/yes/green?icon=apple)](https://lossless.cloud)
Make sure to include it in your `dependencies` in `package.json`.
## Usage
Use TypeScript for best in class instellisense.
### Initial Setup
```javascript
First, let's start by importing the required modules and setting up an instance of `CloudflareAccount` with your API token. This instance will be used to interact with the Cloudflare API.
```typescript
import * as cflare from '@apiclient.xyz/cloudflare';
const myCflareAccount = new cflare.CflareAccount('mySuperAwesomeAccountToken');
// Initialize Cloudflare Account
const myCflareAccount = new cflare.CloudflareAccount('mySuperAwesomeAccountToken');
```
const myAsyncCflareManagement = async () => {
// get things
const myZones = await myCflareAccount.listZones(); // zones are fully typed
const myIdForADomain = await myCflareAccount.getZoneId('example.com'); // type number
const myRecordsForADomain = await myCflareAccount.listRecords('example.com'); // records are fully typed
### Managing Zones
#### List All Zones
To list all zones in your Cloudflare account, you can use the `listZones` method:
```typescript
const listAllZones = async () => {
const myZones = await myCflareAccount.convenience.listZones();
console.log(myZones);
};
```
## Contribution
#### Get Zone ID
We are always happy for code contributions. If you are not the code contributing type that is ok. Still, maintaining Open Source repositories takes considerable time and thought. If you like the quality of what we do and our modules are useful to you we would appreciate a little monthly contribution: You can [contribute one time](https://lossless.link/contribute-onetime) or [contribute monthly](https://lossless.link/contribute). :)
To get the ID of a specific zone (domain), use the `getZoneId` method:
For further information read the linked docs at the top of this readme.
```typescript
const getZoneId = async (domainName: string) => {
try {
const zoneId = await myCflareAccount.convenience.getZoneId(domainName);
console.log(`Zone ID for ${domainName}:`, zoneId);
} catch (error) {
console.error('Error getting zone ID:', error);
}
};
```
> MIT licensed | **©** [Lossless GmbH](https://lossless.gmbh)
| By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy)
#### Purge Cache for a Zone
[![repo-footer](https://lossless.gitlab.io/publicrelations/repofooter.svg)](https://maintainedby.lossless.com)
To purge all cache for a specific zone:
```typescript
const purgeZoneCache = async (domainName: string) => {
await myCflareAccount.convenience.purgeZone(domainName);
console.log(`Purged cache for ${domainName}`);
};
```
### Managing DNS Records
#### List DNS Records
To list all DNS records for a specific zone:
```typescript
const listDnsRecords = async (domainName: string) => {
try {
const records = await myCflareAccount.convenience.listRecords(domainName);
console.log(`DNS Records for ${domainName}:`, records);
} catch (error) {
console.error('Error listing DNS records:', error);
}
};
```
#### Get a Specific Record
To get a specific DNS record by type (e.g., A, AAAA, CNAME, etc.):
```typescript
const getDnsRecord = async (domainName: string, recordType: string) => {
try {
const record = await myCflareAccount.convenience.getRecord(domainName, recordType);
console.log(`DNS Record (${recordType}) for ${domainName}:`, record);
} catch (error) {
console.error('Error getting DNS record:', error);
}
};
```
#### Create a DNS Record
To create a new DNS record:
```typescript
const createDnsRecord = async (domainName: string, recordType: string, content: string) => {
try {
const response = await myCflareAccount.convenience.createRecord(domainName, recordType, content, 120);
console.log(`Created DNS record (${recordType}) for ${domainName}:`, response);
} catch (error) {
console.error('Error creating DNS record:', error);
}
};
```
#### Remove a DNS Record
To remove a DNS record:
```typescript
const removeDnsRecord = async (domainName: string, recordType: string) => {
try {
await myCflareAccount.convenience.removeRecord(domainName, recordType);
console.log(`Removed DNS record (${recordType}) for ${domainName}`);
} catch (error) {
console.error('Error removing DNS record:', error);
}
};
```
#### Clean a DNS Record
To clean (remove) all records of a specific type for a domain:
```typescript
const cleanDnsRecord = async (domainName: string, recordType: string) => {
try {
await myCflareAccount.convenience.cleanRecord(domainName, recordType);
console.log(`Cleaned DNS records (${recordType}) for ${domainName}`);
} catch (error) {
console.error('Error cleaning DNS record:', error);
}
};
```
### Managing Workers
#### Create a Worker
To create a new Cloudflare Worker:
```typescript
const createWorker = async (workerName: string, workerScript: string) => {
try {
const worker = await myCflareAccount.workerManager.createWorker(workerName, workerScript);
console.log(`Created Worker (${workerName}):`, worker);
} catch (error) {
console.error('Error creating Worker:', error);
}
};
```
#### List Workers
To list all workers in your Cloudflare account:
```typescript
const listWorkers = async () => {
try {
const workers = await myCflareAccount.workerManager.listWorkers();
console.log('Workers:', workers);
} catch (error) {
console.error('Error listing workers:', error);
}
};
```
#### Set Worker Routes
To set routes for a Cloudflare Worker:
```typescript
import { CloudflareWorker } from '@apiclient.xyz/cloudflare';
const setWorkerRoutes = async (worker: CloudflareWorker, routes: Array<{ zoneName: string, pattern: string }>) => {
try {
await worker.setRoutes(routes);
console.log('Routes set successfully for Worker:', worker.id);
} catch (error) {
console.error('Error setting routes for Worker:', error);
}
};
```
#### Sample Complete Workflow
Below is a sample workflow that includes all the above features:
```typescript
import * as cflare from '@apiclient.xyz/cloudflare';
const myCflareAccount = new cflare.CloudflareAccount('mySuperAwesomeAccountToken');
const manageCloudflare = async () => {
try {
// List all zones
const myZones = await myCflareAccount.convenience.listZones();
console.log('Zones:', myZones);
// Get Zone ID for a specific domain
const myZoneId = await myCflareAccount.convenience.getZoneId('example.com');
console.log('Zone ID:', myZoneId);
// Purge cache for a zone
await myCflareAccount.convenience.purgeZone('example.com');
console.log('Cache purged for example.com');
// List DNS records for a domain
const myRecords = await myCflareAccount.convenience.listRecords('example.com');
console.log('DNS Records:', myRecords);
// Get a specific DNS record
const myRecord = await myCflareAccount.convenience.getRecord('sub.example.com', 'A');
console.log('Specific DNS Record:', myRecord);
// Create a DNS record
const createResponse = await myCflareAccount.convenience.createRecord('sub.example.com', 'A', '127.0.0.1');
console.log('Created DNS Record:', createResponse);
// Clean DNS records
await myCflareAccount.convenience.cleanRecord('sub.example.com', 'A');
console.log('Cleaned DNS Records for sub.example.com');
// Create a Cloudflare Worker
const myWorker = await myCflareAccount.workerManager.createWorker('myWorker', `addEventListener('fetch', event => { event.respondWith(fetch(event.request)) })`);
console.log('Created Worker:', myWorker);
// Set routes for the Worker
await myWorker.setRoutes([{ zoneName: 'example.com', pattern: 'https://*example.com/*' }]);
console.log('Routes set for Worker');
// List all Workers
const workers = await myCflareAccount.workerManager.listWorkers();
console.log('Workers:', workers);
} catch (error) {
console.error('Error managing Cloudflare:', error);
}
};
manageCloudflare();
```
This complete guide covers initialization, managing Cloudflare zones, DNS records, and Cloudflare Workers comprehensively using TypeScript for enhanced type safety and intellisense. Always ensure to keep your API keys secure and avoid hardcoding them directly in your scripts.
undefined

View File

@ -1,7 +1,7 @@
// tslint:disable-next-line: no-implicit-dependencies
import { expect, tap } from '@pushrocks/tapbundle';
import { expect, tap } from '@push.rocks/tapbundle';
// tslint:disable-next-line: no-implicit-dependencies
import { Qenv } from '@pushrocks/qenv';
import { Qenv } from '@push.rocks/qenv';
import * as cloudflare from '../ts/index.js';
@ -11,20 +11,26 @@ const randomPrefix = Math.floor(Math.random() * 2000);
let testCloudflareAccount: cloudflare.CloudflareAccount;
tap.test('should create a valid instance of CloudflareAccount', async () => {
testCloudflareAccount = new cloudflare.CloudflareAccount(testQenv.getEnvVarOnDemand('CF_KEY'));
testCloudflareAccount = new cloudflare.CloudflareAccount(await testQenv.getEnvVarOnDemand('CF_KEY'));
});
tap.test('should preselect an account', async () => {
await testCloudflareAccount.preselectAccountByName('Sandbox Account');
})
tap.test('.listZones() -> should display an entire account', async (tools) => {
tools.timeout(600000);
const result = await testCloudflareAccount.convenience.listZones();
console.log(result);
// await tools.delayFor(10000);
});
tap.test(
'.getZoneId(domainName) -> should get an Cloudflare Id for a domain string',
async (tools) => {
tools.timeout(600000);
await testCloudflareAccount.convenience.getZoneId('bleu.de');
const id = await testCloudflareAccount.convenience.getZoneId('bleu.de');
console.log(`The account id for bleu.de is: ${id}`);
}
);
@ -70,6 +76,11 @@ tap.test('.purge(some.domain) -> should purge everything', async () => {
await testCloudflareAccount.convenience.purgeZone('bleu.de');
});
tap.test('should list workers', async () => {
const workerArray = await testCloudflareAccount.workerManager.listWorkerScripts();
console.log(workerArray);
});
// WORKERS
tap.test('should create a worker', async () => {
const worker = await testCloudflareAccount.workerManager.createWorker(
@ -85,8 +96,8 @@ tap.test('should create a worker', async () => {
console.log(worker);
});
tap.test('should get workers', async () => {
const workerArray = await testCloudflareAccount.workerManager.listWorkers();
tap.test('should get workers again', async () => {
const workerArray = await testCloudflareAccount.workerManager.listWorkerScripts();
console.log(workerArray);
});

View File

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

View File

@ -8,32 +8,47 @@ import { ZoneManager } from './cloudflare.classes.zonemanager.js';
export class CloudflareAccount {
private authToken: string;
private accountIdentifier: string;
public preselectedAccountId: string;
public workerManager = new WorkerManager(this);
public zoneManager = new ZoneManager(this);
public apiAccount: plugins.cloudflare.Cloudflare;
/**
* constructor sets auth information on the CloudflareAccountInstance
* @param optionsArg
*/
constructor(authTokenArg: string) {
this.authToken = authTokenArg;
this.apiAccount = new plugins.cloudflare.Cloudflare({
apiToken: this.authToken,
});
}
/**
* gets you the account identifier
*/
public async getAccountIdentifier() {
if (!this.accountIdentifier) {
const route = `/accounts?page=1&per_page=20&direction=desc`;
const response: any = await this.request('GET', route);
this.accountIdentifier = response.result[0].id;
public async preselectAccountByName(nameArg: string) {
const accounts = await this.convenience.listAccounts();
const account = accounts.find((accountArg) => {
return accountArg.name === nameArg;
});
if (account) {
this.preselectedAccountId = account.id;
} else {
throw new Error(`account with name ${nameArg} not found`);
}
return this.accountIdentifier;
}
public convenience = {
/**
* listAccounts
*/
listAccounts: async () => {
const accounts: plugins.ICloudflareTypes['Account'][] = [];
for await (const account of this.apiAccount.accounts.list()) {
accounts.push(account as interfaces.ICloudflareApiAccountObject);
}
return accounts;
},
/**
* gets a zone id of a domain from cloudflare
* @param domainName
@ -59,7 +74,7 @@ export class CloudflareAccount {
getRecord: async (
domainNameArg: string,
typeArg: plugins.tsclass.network.TDnsRecordType
): Promise<interfaces.ICflareRecord> => {
): Promise<plugins.ICloudflareTypes['Record']> => {
const domain = new plugins.smartstring.Domain(domainNameArg);
const recordArrayArg = await this.convenience.listRecords(domain.zoneName);
const filteredResponse = recordArrayArg.filter((recordArg) => {
@ -77,18 +92,14 @@ export class CloudflareAccount {
ttlArg = 1
): Promise<any> => {
const domain = new plugins.smartstring.Domain(domainNameArg);
const domainIdArg = await this.convenience.getZoneId(domain.zoneName);
const dataObject = {
const zoneId = await this.convenience.getZoneId(domain.zoneName);
const response = await this.apiAccount.dns.records.create({
zone_id: zoneId,
type: typeArg as any,
name: domain.fullName,
type: typeArg,
content: contentArg,
ttl: ttlArg,
};
const response = await this.request(
'POST',
'/zones/' + domainIdArg + '/dns_records',
dataObject
);
})
return response;
},
/**
@ -101,20 +112,36 @@ export class CloudflareAccount {
typeArg: plugins.tsclass.network.TDnsRecordType
): Promise<any> => {
const domain = new plugins.smartstring.Domain(domainNameArg);
const cflareRecord = await this.convenience.getRecord(domain.fullName, typeArg);
if (cflareRecord) {
const requestRoute: string = `/zones/${cflareRecord.zone_id}/dns_records/${cflareRecord.id}`;
return await this.request('DELETE', requestRoute);
const zoneId = await this.convenience.getZoneId(domain.zoneName);
const records = await this.convenience.listRecords(domain.zoneName);
const recordToDelete = records.find((recordArg) => {
return recordArg.name === domainNameArg && recordArg.type === typeArg;
});
if (recordToDelete) {
await this.apiAccount.dns.records.delete(recordToDelete.id, {
zone_id: zoneId,
});
} else {
throw new Error(`could not remove record for ${domainNameArg} with type ${typeArg}`);
logger.log('warn', `record ${domainNameArg} of type ${typeArg} not found`);
}
},
/**
* cleanrecord allows the cleaning of any previous records to avoid unwanted sideeffects
*/
cleanRecord: async (domainNameArg: string, typeArg: plugins.tsclass.network.TDnsRecordType) => {
console.log(`cleaning record for ${domainNameArg}`);
const records = await this.convenience.listRecords(domainNameArg);
const recordsToDelete = records.filter((recordArg) => {
return recordArg.type === typeArg;
});
for (const recordToDelete of recordsToDelete) {
await this.apiAccount.dns.records.delete(recordToDelete.id, {
zone_id: recordToDelete.zone_id,
});
}
},
/**
* updates a record
* @param domainNameArg
@ -133,45 +160,40 @@ export class CloudflareAccount {
* list all records of a specified domain name
* @param domainNameArg - the domain name that you want to get the records from
*/
listRecords: async (domainNameArg: string): Promise<interfaces.ICflareRecord[]> => {
listRecords: async (domainNameArg: string) => {
const domain = new plugins.smartstring.Domain(domainNameArg);
const domainId = await this.convenience.getZoneId(domain.zoneName);
const responseArg: any = await this.request(
'GET',
'/zones/' + domainId + '/dns_records?per_page=100'
);
const result: interfaces.ICflareRecord[] = responseArg.result;
return result;
const zoneId = await this.convenience.getZoneId(domain.zoneName);
const records: plugins.ICloudflareTypes['Record'][] = [];
for await (const record of this.apiAccount.dns.records.list({
zone_id: zoneId,
})) {
records.push(record);
}
return records;
},
/**
* list all zones in the associated authenticated account
* @param domainName
*/
listZones: async (domainName?: string): Promise<interfaces.ICflareZone[]> => {
// TODO: handle pagination
let requestRoute = `/zones?per_page=50`;
// may be optionally filtered by domain name
if (domainName) {
requestRoute = `${requestRoute}&name=${domainName}`;
listZones: async (domainName?: string) => {
const zones: plugins.ICloudflareTypes['Zone'][] = [];
for await (const zone of this.apiAccount.zones.list()) {
zones.push(zone);
}
const response: any = await this.request('GET', requestRoute);
const result = response.result;
return result;
return zones;
},
/**
* purges a zone
*/
purgeZone: async (domainName: string): Promise<void> => {
const domain = new plugins.smartstring.Domain(domainName);
const domainId = await this.convenience.getZoneId(domain.zoneName);
const requestUrl = `/zones/${domainId}/purge_cache`;
const payload = {
const zoneId = await this.convenience.getZoneId(domain.zoneName);
await this.apiAccount.cache.purge({
zone_id: zoneId,
purge_everything: true,
};
const respone = await this.request('DELETE', requestUrl, payload);
});
},
// acme convenience functions
acmeSetDnsChallenge: async (dnsChallenge: plugins.tsclass.network.IDnsChallenge) => {
await this.convenience.cleanRecord(dnsChallenge.hostName, 'TXT');
@ -186,105 +208,4 @@ export class CloudflareAccount {
await this.convenience.removeRecord(dnsChallenge.hostName, 'TXT');
},
};
public async request(
methodArg: string,
routeArg: string,
dataArg: any = {},
requestHeadersArg = {}
): Promise<any> {
const options: plugins.smartrequest.ISmartRequestOptions = {
method: methodArg,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.authToken}`,
'Content-Length': Buffer.byteLength(JSON.stringify(dataArg)),
...requestHeadersArg,
},
requestBody: dataArg,
};
// route analysis
const routeWithoutQuery = routeArg.split('?')[0];
let queryParams: string[] = [];
if (routeArg.split('?').length > 1) {
queryParams = routeArg.split('?')[1].split('&');
}
// console.log(options);
let retryCount = 0; // count the amount of retries
let pageCount = 1;
const getQueryParams = () => {
let result = '';
if (queryParams.length > 0) {
result += '?';
} else {
return result;
}
let isFirst = true;
for (const queryParam of queryParams) {
if (!isFirst) {
result += '&';
}
isFirst = false;
const queryParamSerialized = queryParam.split('=');
if (queryParam === 'page') {
result += `page=${pageCount}`;
} else {
result += queryParam;
}
}
return result;
};
const makeRequest = async (): Promise<plugins.smartrequest.IExtendedIncomingMessage> => {
const requestUrl = `https://api.cloudflare.com/client/v4${routeWithoutQuery}${getQueryParams()}`;
const response = await plugins.smartrequest.request(requestUrl, options);
if (response.statusCode === 200) {
if (response.body.result_info) {
const rI = response.body.result_info;
if (rI.total_count / rI.per_page > pageCount) {
pageCount++;
const subresponse = await makeRequest();
response.body.result = response.body.result.concat(subresponse.body.result);
return response;
} else {
return response;
}
} else {
return response;
}
} else if (response.statusCode === 429) {
console.log('rate limited! Waiting for retry!');
return await retryRequest();
} else if (response.statusCode === 400) {
console.log(`bad request for route ${requestUrl}!`);
console.log(response.body);
throw new Error(`request failed for ${requestUrl}`);
} else {
console.log(response.body);
throw new Error(`request failed for ${requestUrl} with status${response.statusCode}}`);
}
};
const retryRequest = async (
delayTimeArg = Math.floor(Math.random() * (60000 - 8000) + 8000)
) => {
console.log(`retry started and waiting for ${delayTimeArg} ms`);
await plugins.smartdelay.delayFor(delayTimeArg);
if (retryCount < 10) {
retryCount++;
return await makeRequest();
}
};
const response = await makeRequest();
return response.body;
}
private authCheck() {
return !!this.authToken; // check if auth is available
}
}

View File

@ -9,28 +9,30 @@ export class WorkerManager {
this.cfAccount = cfAccountArg;
}
public async createWorker(workerName: string, workerScript: string): Promise<CloudflareWorker> {
const accountIdentifier = await this.cfAccount.getAccountIdentifier();
const route = `/accounts/${accountIdentifier}/workers/scripts/${workerName}`;
const responseBody = await this.cfAccount.request('PUT', route, workerScript, {
'Content-Type': 'application/javascript',
'Content-Length': Buffer.byteLength(workerScript),
public async createWorker(workerName: string, workerScript: string): Promise<plugins.ICloudflareTypes['Script']> {
if (!this.cfAccount.preselectedAccountId) {
throw new Error('No account selected. Please select it first on the account.');
}
const worker = await this.cfAccount.apiAccount.workers.scripts.content.update(workerName, {
account_id: this.cfAccount.preselectedAccountId,
"CF-WORKER-BODY-PART": workerScript,
});
return CloudflareWorker.fromApiObject(this, responseBody.result);
return worker;
}
/**
* lists workers
*/
public async listWorkers() {
const accountIdentifier = await this.cfAccount.getAccountIdentifier();
const route = `/accounts/${accountIdentifier}/workers/scripts`;
const response = await this.cfAccount.request('GET', route);
const results = response.result;
const workers: CloudflareWorker[] = [];
for (const apiObject of results) {
workers.push(await CloudflareWorker.fromApiObject(this, apiObject));
public async listWorkerScripts() {
if (!this.cfAccount.preselectedAccountId) {
throw new Error('No account selected. Please select it first on the account.');
}
return workers;
const workerScripts: plugins.ICloudflareTypes['Script'][] = [];
for await (const scriptArg of this.cfAccount.apiAccount.workers.scripts.list({
account_id: this.cfAccount.preselectedAccountId,
})) {
workerScripts.push(scriptArg);
}
return workerScripts;
}
}

View File

@ -1,50 +1,9 @@
import * as plugins from './cloudflare.plugins.js';
import * as interfaces from './interfaces/index.js';
export class CloudflareZone implements interfaces.ICflareZone {
public static createFromApiObject(apiObject: interfaces.ICflareZone) {
export class CloudflareZone {
public static createFromApiObject(apiObject: plugins.ICloudflareTypes['Zone']) {
const cloudflareZone = new CloudflareZone();
Object.assign(cloudflareZone, apiObject);
return cloudflareZone;
}
id: string;
name: string;
development_mode: number;
original_name_servers: string[];
original_registrar: string;
original_dnshost: string;
created_on: string;
modified_on: string;
name_servers: string[];
owner: {
id: string;
email: string;
owner_type: string;
};
permissions: string[];
plan: {
id: string;
name: string;
price: number;
currency: string;
frequency: string;
legacy_id: string;
is_subscribed: boolean;
can_subscribe: boolean;
};
plan_pending: {
id: string;
name: string;
price: number;
currency: string;
frequency: string;
legacy_id: string;
is_subscribed: string;
can_subscribe: string;
};
status: string;
paused: boolean;
type: string;
checked_on: string;
}

View File

@ -1,8 +1,24 @@
import * as smartlog from '@pushrocks/smartlog';
import * as smartpromise from '@pushrocks/smartpromise';
import * as smartdelay from '@pushrocks/smartdelay';
import * as smartrequest from '@pushrocks/smartrequest';
import * as smartstring from '@pushrocks/smartstring';
import * as smartlog from '@push.rocks/smartlog';
import * as smartpromise from '@push.rocks/smartpromise';
import * as smartdelay from '@push.rocks/smartdelay';
import * as smartrequest from '@push.rocks/smartrequest';
import * as smartstring from '@push.rocks/smartstring';
import * as tsclass from '@tsclass/tsclass';
export { smartlog, smartpromise, smartdelay, smartrequest, smartstring, tsclass };
// third party
import * as cloudflare from 'cloudflare';
import * as interfaces from './interfaces/index.js';
import type { Zone } from 'cloudflare/resources/zones/zones.js';
import type { Record } from 'cloudflare/resources/dns/records.js';
import type { Script } from 'cloudflare/resources/workers/scripts/index.js';
export interface ICloudflareTypes {
Account: interfaces.ICloudflareApiAccountObject;
Record: Record;
Zone: Zone;
Script: Script;
}
export { cloudflare };

View File

@ -0,0 +1,20 @@
export interface ICloudflareApiAccountObject {
id: string;
name: string;
type: 'standard' | 'enterprise' | 'pro' | 'free'; // Assuming other possible types
settings: {
enforce_twofactor: boolean;
api_access_enabled: boolean | null;
access_approval_expiry: string | null; // Assuming ISO date string or null
use_account_custom_ns_by_default: boolean;
default_nameservers: string;
};
legacy_flags: {
enterprise_zone_quota: {
maximum: number;
current: number;
available: number;
};
};
created_on: string; // Assuming ISO date string
}

View File

@ -1,15 +0,0 @@
export interface ICflareRecord {
id: string;
type: string;
name: string;
content: string;
proxiable: boolean;
proxied: boolean;
ttl: number;
locked: boolean;
zone_id: string;
zone_name: string;
created_on: string;
modified_on: string;
data: any;
}

View File

@ -1,41 +0,0 @@
export interface ICflareZone {
id: string;
name: string;
development_mode: number;
original_name_servers: string[];
original_registrar: string;
original_dnshost: string;
created_on: string;
modified_on: string;
name_servers: string[];
owner: {
id: string;
email: string;
owner_type: string;
};
permissions: string[];
plan: {
id: string;
name: string;
price: number;
currency: string;
frequency: string;
legacy_id: string;
is_subscribed: boolean;
can_subscribe: boolean;
};
plan_pending: {
id: string;
name: string;
price: number;
currency: string;
frequency: string;
legacy_id: string;
is_subscribed: string;
can_subscribe: string;
};
status: string;
paused: boolean;
type: string;
checked_on: string;
}

View File

@ -1,3 +1,2 @@
export * from './cloudflare.api.record.js';
export * from './cloudflare.api.zone.js';
export * from './cloudflare.api.account.js';
export * from './cloudflare.api.workerroute.js';

View File

@ -3,9 +3,12 @@
"experimentalDecorators": true,
"useDefineForClassFields": false,
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "nodenext",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"verbatimModuleSyntax": true,
}
"verbatimModuleSyntax": true
},
"exclude": [
"dist_*/**/*.d.ts"
]
}