diff --git a/.gitea/workflows/default_nottags.yaml b/.gitea/workflows/default_nottags.yaml new file mode 100644 index 0000000..0bae651 --- /dev/null +++ b/.gitea/workflows/default_nottags.yaml @@ -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 diff --git a/.gitea/workflows/default_tags.yaml b/.gitea/workflows/default_tags.yaml new file mode 100644 index 0000000..821d33f --- /dev/null +++ b/.gitea/workflows/default_tags.yaml @@ -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 diff --git a/.gitignore b/.gitignore index ef13c79..0b26089 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ # artifacts coverage/ public/ -pages/ # installs node_modules/ @@ -17,4 +16,4 @@ node_modules/ dist/ dist_*/ -# custom \ No newline at end of file +#------# custom \ No newline at end of file diff --git a/changelog.md b/changelog.md index 58dc264..e5ca7d7 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,13 @@ # Changelog +## 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 diff --git a/package.json b/package.json index bc9d824..822b7bd 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ }, "repository": { "type": "git", - "url": "git+https://gitlab.com/pushrocks/cflare.git" + "url": "https://gitlab.com/mojoio/cloudflare.git" }, "keywords": [ "Cloudflare", @@ -31,9 +31,9 @@ "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", @@ -67,5 +67,8 @@ "browserslist": [ "last 1 chrome versions" ], - "packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6" -} + "packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6", + "pnpm": { + "overrides": {} + } +} \ No newline at end of file diff --git a/readme.md b/readme.md index a57598a..2ff93c7 100644 --- a/readme.md +++ b/readme.md @@ -89,7 +89,7 @@ await myZone.purgeCache(); await myZone.purgeUrls(['https://example.com/css/styles.css', 'https://example.com/js/app.js']); // Enable/disable development mode -await myZone.enableDevelopmentMode(); // Enables dev mode for 3 hours +await myZone.enableDevelopmentMode(); // Enables dev mode for 3 hours await myZone.disableDevelopmentMode(); // Check zone status @@ -126,11 +126,11 @@ await cfAccount.convenience.cleanRecord('example.com', 'TXT'); // Support for ACME DNS challenges (for certificate issuance) await cfAccount.convenience.acmeSetDnsChallenge({ hostName: '_acme-challenge.example.com', - challenge: 'token-validation-string' + challenge: 'token-validation-string', }); await cfAccount.convenience.acmeRemoveDnsChallenge({ hostName: '_acme-challenge.example.com', - challenge: 'token-validation-string' + challenge: 'token-validation-string', }); ``` @@ -157,12 +157,12 @@ 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 @@ -191,19 +191,19 @@ async function manageCloudflare() { try { // Initialize with API token from environment variable const cfAccount = new cflare.CloudflareAccount(process.env.CLOUDFLARE_API_TOKEN); - + // Preselect account if needed await cfAccount.preselectAccountByName('My Company'); - + // Get zone and check status const myZone = await cfAccount.zoneManager.getZoneByName('example.com'); 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'); - + // Create a worker and set up routes const workerCode = ` addEventListener('fetch', event => { @@ -217,15 +217,13 @@ async function manageCloudflare() { event.respondWith(fetch(event.request)); } })`; - + 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']); - + console.log('Configuration completed successfully'); } catch (error) { console.error('Error managing Cloudflare:', error); @@ -244,33 +242,43 @@ The main entry point for all Cloudflare operations. ```typescript class CloudflareAccount { constructor(apiToken: string); - + // Account management async listAccounts(): Promise>; async preselectAccountByName(accountName: string): Promise; - + // Managers readonly zoneManager: ZoneManager; readonly workerManager: WorkerManager; - + // Official Cloudflare client readonly apiAccount: cloudflare.Cloudflare; - + // Convenience namespace with helper methods readonly convenience: { // Zone operations listZones(domainName?: string): Promise; getZoneId(domainName: string): Promise; purgeZone(domainName: string): Promise; - + // DNS operations listRecords(domainName: string): Promise; getRecord(domainName: string, recordType: string): Promise; - createRecord(domainName: string, recordType: string, content: string, ttl?: number): Promise; - updateRecord(domainName: string, recordType: string, content: string, ttl?: number): Promise; + createRecord( + domainName: string, + recordType: string, + content: string, + ttl?: number, + ): Promise; + updateRecord( + domainName: string, + recordType: string, + content: string, + ttl?: number, + ): Promise; removeRecord(domainName: string, recordType: string): Promise; cleanRecord(domainName: string, recordType: string): Promise; - + // ACME operations acmeSetDnsChallenge(dnsChallenge: IDnsChallenge): Promise; acmeRemoveDnsChallenge(dnsChallenge: IDnsChallenge): Promise; @@ -291,7 +299,7 @@ class CloudflareZone { readonly paused: boolean; readonly type: string; readonly nameServers: string[]; - + // Methods async purgeCache(): Promise; async purgeUrls(urls: string[]): Promise; @@ -316,7 +324,7 @@ class CloudflareRecord { readonly content: string; readonly ttl: number; readonly proxied: boolean; - + // Methods async update(content: string, ttl?: number): Promise; async delete(): Promise; @@ -333,7 +341,7 @@ class CloudflareWorker { readonly id: string; readonly script: string; readonly routes: IWorkerRoute[]; - + // Methods async getRoutes(): Promise; async setRoutes(routes: Array): Promise; @@ -396,4 +404,4 @@ pnpm run test ## License -MIT © [Lossless GmbH](https://lossless.gmbh) \ No newline at end of file +MIT © [Lossless GmbH](https://lossless.gmbh) diff --git a/test/test.ts b/test/test.ts index 537791b..82e6349 100644 --- a/test/test.ts +++ b/test/test.ts @@ -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,12 +24,12 @@ 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) => { tools.timeout(600000); - + try { const result = await testCloudflareAccount.convenience.listZones(); // The test expects an array, but the current API might return an object with a result property @@ -66,7 +68,7 @@ tap.test('ZoneManager: should get zone by name', async (tools) => { // DNS record tests tap.test('.listRecords(domainName) -> should list records for domain', async (tools) => { tools.timeout(600000); - + try { const records = await testCloudflareAccount.convenience.listRecords('bleu.de'); // The test expects an array, but the current API might return an object with a result property @@ -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,7 +144,7 @@ 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'); @@ -157,7 +159,7 @@ tap.test('should create A record for nested subdomain', async (tools) => { nestedSubdomain, 'A', '127.0.0.5', - 120 + 120, ); expect(result).toBeTypeOf('object'); console.log(`Created nested A record for ${nestedSubdomain}`); @@ -179,7 +181,7 @@ tap.test('should update A record for nested subdomain', async (tools) => { nestedSubdomain, 'A', '127.0.0.6', - 120 + 120, ); expect(result).toBeTypeOf('object'); expect(result.content).toEqual('127.0.0.6'); @@ -209,14 +211,14 @@ tap.test('should remove A and CNAME records', async (tools) => { tools.timeout(600000); const aSubdomain = `${randomPrefix}-a-test.bleu.de`; const cnameSubdomain = `${randomPrefix}-cname-test.bleu.de`; - + await testCloudflareAccount.convenience.removeRecord(aSubdomain, 'A'); await testCloudflareAccount.convenience.removeRecord(cnameSubdomain, 'CNAME'); - + // Verify records are removed const aRecord = await testCloudflareAccount.convenience.getRecord(aSubdomain, 'A'); const cnameRecord = await testCloudflareAccount.convenience.getRecord(cnameSubdomain, 'CNAME'); - + expect(aRecord).toBeUndefined(); expect(cnameRecord).toBeUndefined(); console.log(`Successfully removed A and CNAME records`); @@ -232,7 +234,7 @@ tap.test('.purgeZone() -> should purge zone cache', async (tools) => { // Worker tests tap.test('should list workers', async (tools) => { tools.timeout(600000); - + try { const workerArray = await testCloudflareAccount.workerManager.listWorkerScripts(); expect(workerArray).toBeTypeOf('array'); @@ -246,7 +248,7 @@ tap.test('should list workers', async (tools) => { tap.test('should create a worker', async (tools) => { tools.timeout(600000); - + try { const worker = await testCloudflareAccount.workerManager.createWorker( testWorkerName, @@ -254,13 +256,13 @@ 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'); expect(worker.id).toEqual(testWorkerName); console.log(`Created worker: ${testWorkerName}`); - + try { // Set routes for the worker await worker.setRoutes([ @@ -269,7 +271,7 @@ tap.test('should create a worker', async (tools) => { pattern: `https://${testWorkerName}.bleu.de/*`, }, ]); - + console.log(`Set routes for worker ${testWorkerName}`); } catch (routeError) { console.error(`Error setting routes: ${routeError.message}`); @@ -284,7 +286,7 @@ tap.test('should create a worker', async (tools) => { tap.test('should get a specific worker by name', async (tools) => { tools.timeout(600000); - + try { // First create a worker to ensure it exists await testCloudflareAccount.workerManager.createWorker( @@ -293,12 +295,12 @@ 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 const worker = await testCloudflareAccount.workerManager.getWorker(testWorkerName); - + expect(worker).toBeTypeOf('object'); expect(worker?.id).toEqual(testWorkerName); console.log(`Successfully retrieved worker: ${testWorkerName}`); @@ -311,17 +313,17 @@ tap.test('should get a specific worker by name', async (tools) => { tap.test('should update worker script', async (tools) => { tools.timeout(600000); - + try { const worker = await testCloudflareAccount.workerManager.getWorker(testWorkerName); - + if (worker) { await worker.updateScript(`addEventListener('fetch', event => { event.respondWith(new Response('Updated Worker Script!', { headers: { 'content-type': 'text/plain' } })) })`); - + console.log(`Updated script for worker ${testWorkerName}`); expect(true).toBeTrue(); } else { @@ -338,10 +340,10 @@ tap.test('should update worker script', async (tools) => { tap.test('should delete the test worker', async (tools) => { tools.timeout(600000); - + try { const worker = await testCloudflareAccount.workerManager.getWorker(testWorkerName); - + if (worker) { const result = await worker.delete(); console.log(`Deleted worker: ${testWorkerName}`); @@ -381,4 +383,4 @@ tap.test('should format TTL values', async () => { expect(cloudflare.CloudflareUtils.formatTtl(999)).toEqual('999 seconds'); }); -tap.start(); \ No newline at end of file +tap.start(); diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 70d33e9..a8ad7af 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@apiclient.xyz/cloudflare', - version: '6.4.0', + version: '6.4.1', description: 'A TypeScript client for managing Cloudflare accounts, zones, DNS records, and workers with ease.' } diff --git a/ts/cloudflare.classes.account.ts b/ts/cloudflare.classes.account.ts index c1c67d7..e3fb60b 100644 --- a/ts/cloudflare.classes.account.ts +++ b/ts/cloudflare.classes.account.ts @@ -39,13 +39,13 @@ export class CloudflareAccount implements plugins.tsclass.network.IConvenientDns method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH', endpoint: string, data?: any, - customHeaders?: Record + customHeaders?: Record, ): Promise { try { const options: plugins.smartrequest.ISmartRequestOptions = { method, headers: { - 'Authorization': `Bearer ${this.authToken}`, + Authorization: `Bearer ${this.authToken}`, 'Content-Type': 'application/json', ...customHeaders, }, @@ -62,13 +62,16 @@ export class CloudflareAccount implements plugins.tsclass.network.IConvenientDns } logger.log('debug', `Making ${method} request to ${endpoint}`); - const response = await plugins.smartrequest.request(`https://api.cloudflare.com/client/v4${endpoint}`, options); - + 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; } - + // Otherwise try to parse as JSON try { if (typeof response.body === 'string' && response.body.trim()) { @@ -80,13 +83,15 @@ export class CloudflareAccount implements plugins.tsclass.network.IConvenientDns } } catch (parseError) { logger.log('warn', `Failed to parse response as JSON: ${parseError.message}`); - + // Create a fake response object to maintain expected structure 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: ${typeof response.body === 'string' ? response.body?.substring(0, 50) : typeof response.body}...`, + ], } as T; } } catch (error) { @@ -115,12 +120,12 @@ export class CloudflareAccount implements plugins.tsclass.network.IConvenientDns listAccounts: async () => { try { const accounts: plugins.ICloudflareTypes['Account'][] = []; - + // Collect all accounts using async iterator for await (const account of this.apiAccount.accounts.list()) { accounts.push(account as interfaces.ICloudflareApiAccountObject); } - + logger.log('info', `Found ${accounts.length} accounts`); return accounts; } catch (error) { @@ -152,21 +157,24 @@ export class CloudflareAccount implements plugins.tsclass.network.IConvenientDns */ getRecord: async ( domainNameArg: string, - typeArg: plugins.tsclass.network.TDnsRecordType + typeArg: plugins.tsclass.network.TDnsRecordType, ): Promise => { 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; } - + const filteredResponse = recordArrayArg.filter((recordArg) => { return recordArg.type === typeArg && recordArg.name === domainNameArg; }); - + return filteredResponse.length > 0 ? filteredResponse[0] : undefined; } catch (error) { logger.log('error', `Error getting record for ${domainNameArg}: ${error.message}`); @@ -180,7 +188,7 @@ export class CloudflareAccount implements plugins.tsclass.network.IConvenientDns domainNameArg: string, typeArg: plugins.tsclass.network.TDnsRecordType, contentArg: string, - ttlArg = 1 + ttlArg = 1, ): Promise => { const domain = new plugins.smartstring.Domain(domainNameArg); const zoneId = await this.convenience.getZoneId(domain.zoneName); @@ -190,7 +198,7 @@ export class CloudflareAccount implements plugins.tsclass.network.IConvenientDns name: domain.fullName, content: contentArg, ttl: ttlArg, - }) + }); return response; }, /** @@ -200,7 +208,7 @@ export class CloudflareAccount implements plugins.tsclass.network.IConvenientDns */ removeRecord: async ( domainNameArg: string, - typeArg: plugins.tsclass.network.TDnsRecordType + typeArg: plugins.tsclass.network.TDnsRecordType, ): Promise => { const domain = new plugins.smartstring.Domain(domainNameArg); const zoneId = await this.convenience.getZoneId(domain.zoneName); @@ -228,22 +236,28 @@ export class CloudflareAccount implements plugins.tsclass.network.IConvenientDns logger.log('info', `Cleaning ${typeArg} records for ${domainNameArg}`); const domain = new plugins.smartstring.Domain(domainNameArg); const zoneId = await this.convenience.getZoneId(domain.zoneName); - + // 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 && 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 { // The official client might have different property locations @@ -253,7 +267,7 @@ export class CloudflareAccount implements plugins.tsclass.network.IConvenientDns logger.log('warn', `Record ID not found for ${domainNameArg} record`); continue; } - + await this.apiAccount.dns.records.delete(recordId, { zone_id: zoneId, }); @@ -263,7 +277,10 @@ export class CloudflareAccount implements plugins.tsclass.network.IConvenientDns } } } catch (error) { - logger.log('error', `Error cleaning ${typeArg} records for ${domainNameArg}: ${error.message}`); + logger.log( + 'error', + `Error cleaning ${typeArg} records for ${domainNameArg}: ${error.message}`, + ); } }, @@ -279,19 +296,22 @@ export class CloudflareAccount implements plugins.tsclass.network.IConvenientDns domainNameArg: string, typeArg: plugins.tsclass.network.TDnsRecordType, contentArg: string, - ttlArg: number = 1 + ttlArg: number = 1, ): Promise => { const domain = new plugins.smartstring.Domain(domainNameArg); const zoneId = await this.convenience.getZoneId(domain.zoneName); - + // Find existing record const record = await this.convenience.getRecord(domainNameArg, typeArg); - + if (!record) { - logger.log('warn', `Record ${domainNameArg} of type ${typeArg} not found for update, creating instead`); + logger.log( + 'warn', + `Record ${domainNameArg} of type ${typeArg} not found for update, creating instead`, + ); return this.convenience.createRecord(domainNameArg, typeArg, contentArg, ttlArg); } - + // Update the record - cast to any to access the id property const recordId = (record as any).id; const updatedRecord = await this.apiAccount.dns.records.edit(recordId, { @@ -299,9 +319,9 @@ export class CloudflareAccount implements plugins.tsclass.network.IConvenientDns type: typeArg as any, name: domain.fullName, content: contentArg, - ttl: ttlArg + ttl: ttlArg, }); - + return updatedRecord; }, /** @@ -313,14 +333,14 @@ export class CloudflareAccount implements plugins.tsclass.network.IConvenientDns const domain = new plugins.smartstring.Domain(domainNameArg); const zoneId = await this.convenience.getZoneId(domain.zoneName); const records: plugins.ICloudflareTypes['Record'][] = []; - + // Collect all records using async iterator for await (const record of this.apiAccount.dns.records.list({ zone_id: zoneId, })) { records.push(record); } - + logger.log('info', `Found ${records.length} DNS records for ${domainNameArg}`); return records; } catch (error) { @@ -338,15 +358,18 @@ export class CloudflareAccount implements plugins.tsclass.network.IConvenientDns if (domainName) { options.name = domainName; } - + const zones: plugins.ICloudflareTypes['Zone'][] = []; - + // Collect all zones using async iterator for await (const zone of this.apiAccount.zones.list(options)) { 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}`); @@ -390,11 +413,11 @@ export class CloudflareAccount implements plugins.tsclass.network.IConvenientDns dnsChallenge.hostName, 'TXT', dnsChallenge.challenge, - 120 + 120, ); }, acmeRemoveDnsChallenge: async (dnsChallenge: plugins.tsclass.network.IDnsChallenge) => { await this.convenience.removeRecord(dnsChallenge.hostName, 'TXT'); }, }; -} \ No newline at end of file +} diff --git a/ts/cloudflare.classes.record.ts b/ts/cloudflare.classes.record.ts index 7fb3844..1b13df6 100644 --- a/ts/cloudflare.classes.record.ts +++ b/ts/cloudflare.classes.record.ts @@ -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,28 +54,28 @@ export class CloudflareRecord { public async update( cloudflareAccount: any, newContent: string, - ttl?: number + ttl?: number, ): Promise { logger.log('info', `Updating record ${this.name} (${this.type}) with new content`); - + const updatedRecord = await cloudflareAccount.apiAccount.dns.records.edit(this.id, { zone_id: this.zone_id, type: this.type as any, name: this.name, content: newContent, ttl: ttl || this.ttl, - proxied: this.proxied + proxied: this.proxied, }); - + // Update this instance this.content = newContent; if (ttl) { this.ttl = ttl; } - + return this; } - + /** * Delete this record * @param cloudflareAccount The Cloudflare account to use @@ -82,15 +84,15 @@ export class CloudflareRecord { public async delete(cloudflareAccount: any): Promise { try { 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; } catch (error) { logger.log('error', `Failed to delete record: ${error.message}`); return false; } } -} \ No newline at end of file +} diff --git a/ts/cloudflare.classes.worker.ts b/ts/cloudflare.classes.worker.ts index 5203052..14a9068 100644 --- a/ts/cloudflare.classes.worker.ts +++ b/ts/cloudflare.classes.worker.ts @@ -16,7 +16,7 @@ export class CloudflareWorker { // STATIC public static async fromApiObject( workerManager: WorkerManager, - apiObject + apiObject, ): Promise { const newWorker = new CloudflareWorker(workerManager); Object.assign(newWorker, apiObject); @@ -46,33 +46,33 @@ export class CloudflareWorker { public async getRoutes() { try { this.routes = []; // Reset routes before fetching - + // Get all zones using the async iterator const zones: plugins.ICloudflareTypes['Zone'][] = []; for await (const zone of this.workerManager.cfAccount.apiAccount.zones.list()) { zones.push(zone); } - + if (zones.length === 0) { logger.log('warn', 'No zones found for the account'); return; } - + for (const zone of zones) { try { if (!zone || !zone.id) { logger.log('warn', 'Zone is missing ID property'); continue; } - + // 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); } - + // Filter for routes that match this worker's ID for (const route of apiRoutes) { if (route.script === this.id) { @@ -81,10 +81,13 @@ 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}`, + ); } } - + logger.log('info', `Found ${this.routes.length} routes for worker ${this.id}`); } catch (error) { logger.log('error', `Failed to get routes for worker ${this.id}: ${error.message}`); @@ -100,60 +103,62 @@ export class CloudflareWorker { public async setRoutes(routeArray: IWorkerRouteDefinition[]) { // First get all existing routes to determine what we need to create/update await this.getRoutes(); - + for (const newRoute of routeArray) { // Determine whether a route is new, needs an update, or is already up to date let routeStatus: 'new' | 'needsUpdate' | 'alreadyUpToDate' = 'new'; let existingRouteId: string; - + for (const existingRoute of this.routes) { if (existingRoute.pattern === newRoute.pattern) { routeStatus = 'needsUpdate'; existingRouteId = existingRoute.id; - + if (existingRoute.script === this.id) { routeStatus = 'alreadyUpToDate'; logger.log('info', `Route ${newRoute.pattern} already exists, no update needed`); } } } - + 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`); continue; } - + // Handle route creation, update, or skip if already up to date if (routeStatus === 'new') { 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}`); } else if (routeStatus === 'needsUpdate') { 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}`); } } catch (error) { logger.log('error', `Failed to set route ${newRoute.pattern}: ${error.message}`); } } - + // Refresh routes after all changes await this.getRoutes(); } - + /** * Upload or update worker script content * @param scriptContent The worker script content @@ -163,10 +168,10 @@ export class CloudflareWorker { if (!this.workerManager.cfAccount.preselectedAccountId) { throw new Error('No account selected. Please select it first on the account.'); } - + try { logger.log('info', `Updating script for worker ${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 = { @@ -175,23 +180,27 @@ export class CloudflareWorker { }; updateParams['CF-WORKER-BODY-PART'] = 'script'; updateParams['script'] = scriptContent; - const updatedWorker = await this.workerManager.cfAccount.apiAccount.workers.scripts.content.update(this.id, updateParams); - + 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') { Object.assign(this, updatedWorker); } - + // Always ensure the script property is updated this.script = scriptContent; - + return this; } catch (error) { logger.log('error', `Failed to update worker script: ${error.message}`); throw error; } } - + /** * Delete this worker script * @returns True if deletion was successful @@ -200,19 +209,19 @@ export class CloudflareWorker { if (!this.workerManager.cfAccount.preselectedAccountId) { throw new Error('No account selected. Please select it first on the account.'); } - + try { logger.log('info', `Deleting worker ${this.id}`); - + // 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; } catch (error) { logger.log('error', `Failed to delete worker: ${error.message}`); return false; } } -} \ No newline at end of file +} diff --git a/ts/cloudflare.classes.workermanager.ts b/ts/cloudflare.classes.workermanager.ts index ea23258..2ae2544 100644 --- a/ts/cloudflare.classes.workermanager.ts +++ b/ts/cloudflare.classes.workermanager.ts @@ -20,7 +20,7 @@ export class WorkerManager { if (!this.cfAccount.preselectedAccountId) { throw new Error('No account selected. Please select it first on the account.'); } - + try { // 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 @@ -31,12 +31,12 @@ export class WorkerManager { 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); worker.id = workerName; worker.script = workerScript; - + // Initialize the worker and get its routes try { await worker.getRoutes(); @@ -44,7 +44,7 @@ export class WorkerManager { logger.log('warn', `Failed to get routes for worker ${workerName}: ${routeError.message}`); // Continue anyway since the worker was created } - + return worker; } catch (error) { logger.log('error', `Failed to create worker ${workerName}: ${error.message}`); @@ -61,22 +61,22 @@ export class WorkerManager { if (!this.cfAccount.preselectedAccountId) { throw new Error('No account selected. Please select it first on the account.'); } - + 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 const worker = new CloudflareWorker(this); worker.id = workerName; - + // Save script content if available if (workerScript && typeof workerScript === 'object') { Object.assign(worker, workerScript); } - + // Initialize the worker and get its routes try { await worker.getRoutes(); @@ -84,7 +84,7 @@ export class WorkerManager { logger.log('warn', `Failed to get routes for worker ${workerName}: ${routeError.message}`); // Continue anyway since we found the worker } - + return worker; } catch (error) { logger.log('warn', `Worker '${workerName}' not found: ${error.message}`); @@ -100,35 +100,35 @@ export class WorkerManager { if (!this.cfAccount.preselectedAccountId) { throw new Error('No account selected. Please select it first on the account.'); } - + try { // Collect all scripts using the new client's async iterator const workerScripts: plugins.ICloudflareTypes['Script'][] = []; - + try { for await (const script of this.cfAccount.apiAccount.workers.scripts.list({ account_id: this.cfAccount.preselectedAccountId, })) { workerScripts.push(script); } - + logger.log('info', `Found ${workerScripts.length} worker scripts`); return workerScripts; } catch (error) { 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)) { logger.log('info', `Found ${result.result.length} worker scripts using direct result`); return result.result; } } - + logger.log('warn', 'Could not retrieve worker scripts'); return []; } catch (error) { @@ -136,7 +136,7 @@ export class WorkerManager { return []; } } - + /** * Deletes a worker script * @param workerName Name of the worker to delete @@ -146,10 +146,10 @@ export class WorkerManager { if (!this.cfAccount.preselectedAccountId) { throw new Error('No account selected. Please select it first on the account.'); } - + try { await this.cfAccount.apiAccount.workers.scripts.delete(workerName, { - account_id: this.cfAccount.preselectedAccountId + account_id: this.cfAccount.preselectedAccountId, }); logger.log('info', `Worker '${workerName}' deleted successfully`); return true; @@ -158,4 +158,4 @@ export class WorkerManager { return false; } } -} \ No newline at end of file +} diff --git a/ts/cloudflare.classes.zone.ts b/ts/cloudflare.classes.zone.ts index 8321901..683d430 100644 --- a/ts/cloudflare.classes.zone.ts +++ b/ts/cloudflare.classes.zone.ts @@ -23,7 +23,7 @@ export class CloudflareZone { public account: interfaces.ICflareZone['account']; public permissions: string[]; public plan: interfaces.ICflareZone['plan']; - + private cfAccount?: CloudflareAccount; // Will be set when created through a manager /** @@ -33,19 +33,19 @@ export class CloudflareZone { * @returns CloudflareZone instance */ public static createFromApiObject( - apiObject: plugins.ICloudflareTypes['Zone'], - cfAccount?: CloudflareAccount + apiObject: plugins.ICloudflareTypes['Zone'], + cfAccount?: CloudflareAccount, ): CloudflareZone { const cloudflareZone = new CloudflareZone(); Object.assign(cloudflareZone, apiObject); - + if (cfAccount) { cloudflareZone.cfAccount = cfAccount; } - + return cloudflareZone; } - + /** * Check if development mode is currently active * @returns True if development mode is active @@ -53,7 +53,7 @@ export class CloudflareZone { public isDevelopmentModeActive(): boolean { return this.development_mode > 0; } - + /** * Enable development mode for the zone * @param cfAccount Cloudflare account to use if not already set @@ -62,23 +62,23 @@ export class CloudflareZone { */ public async enableDevelopmentMode( cfAccount?: CloudflareAccount, - duration: number = 10800 + duration: number = 10800, ): Promise { const account = cfAccount || this.cfAccount; if (!account) { throw new Error('CloudflareAccount is required to enable development mode'); } - + logger.log('info', `Enabling development mode for zone ${this.name}`); - + try { // 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: 'on', - time: duration + time: duration, }); - + this.development_mode = duration; return this; } catch (error) { @@ -86,7 +86,7 @@ export class CloudflareZone { throw error; } } - + /** * Disable development mode for the zone * @param cfAccount Cloudflare account to use if not already set @@ -97,16 +97,16 @@ export class CloudflareZone { if (!account) { throw new Error('CloudflareAccount is required to disable development mode'); } - + logger.log('info', `Disabling development mode for zone ${this.name}`); - + try { // 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; return this; } catch (error) { @@ -114,7 +114,7 @@ export class CloudflareZone { throw error; } } - + /** * Purge all cached content for this zone * @param cfAccount Cloudflare account to use if not already set @@ -125,13 +125,13 @@ export class CloudflareZone { if (!account) { throw new Error('CloudflareAccount is required to purge cache'); } - + logger.log('info', `Purging all cache for zone ${this.name}`); - + try { await account.apiAccount.cache.purge({ zone_id: this.id, - purge_everything: true + purge_everything: true, }); return true; } catch (error) { @@ -139,7 +139,7 @@ export class CloudflareZone { return false; } } - + /** * Purge specific URLs from the cache * @param urls Array of URLs to purge @@ -151,17 +151,17 @@ export class CloudflareZone { if (!account) { throw new Error('CloudflareAccount is required to purge URLs'); } - + if (!urls.length) { return true; } - + logger.log('info', `Purging ${urls.length} URLs from cache for zone ${this.name}`); - + try { await account.apiAccount.cache.purge({ zone_id: this.id, - files: urls + files: urls, }); return true; } catch (error) { @@ -169,7 +169,7 @@ export class CloudflareZone { return false; } } - + /** * Check if the zone is active * @returns True if the zone is active @@ -177,7 +177,7 @@ export class CloudflareZone { public isActive(): boolean { return this.status === 'active' && !this.paused; } - + /** * Check if the zone is using Cloudflare nameservers * @returns True if using Cloudflare nameservers @@ -187,11 +187,11 @@ export class CloudflareZone { if (!this.original_name_servers || !this.name_servers) { return false; } - + // If they're different, and current nameservers are Cloudflare's - return this.name_servers.some(ns => ns.includes('cloudflare')); + return this.name_servers.some((ns) => ns.includes('cloudflare')); } - + /** * Update zone settings * @param settings Settings to update @@ -205,23 +205,23 @@ export class CloudflareZone { vanity_name_servers: string[]; type: 'full' | 'partial' | 'secondary'; }>, - cfAccount?: CloudflareAccount + cfAccount?: CloudflareAccount, ): Promise { const account = cfAccount || this.cfAccount; if (!account) { throw new Error('CloudflareAccount is required to update zone settings'); } - + logger.log('info', `Updating settings for zone ${this.name}`); - + try { // Use the request method instead of zones.edit to avoid type issues const response: { result: interfaces.ICflareZone } = await account.request( 'PATCH', `/zones/${this.id}`, - settings + settings, ); - + Object.assign(this, response.result); return this; } catch (error) { @@ -229,4 +229,4 @@ export class CloudflareZone { throw error; } } -} \ No newline at end of file +} diff --git a/ts/cloudflare.classes.zonemanager.ts b/ts/cloudflare.classes.zonemanager.ts index c22d0ec..f9d7b52 100644 --- a/ts/cloudflare.classes.zonemanager.ts +++ b/ts/cloudflare.classes.zonemanager.ts @@ -19,7 +19,7 @@ export class ZoneManager { public async getZones(zoneName?: string): Promise { try { const options: any = { per_page: 50 }; - + // May be optionally filtered by domain name if (zoneName) { options.name = zoneName; @@ -29,14 +29,14 @@ export class ZoneManager { for await (const zone of this.cfAccount.apiAccount.zones.list(options)) { 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 []; } } - + /** * Get a single zone by name * @param zoneName Zone name to find @@ -44,9 +44,9 @@ export class ZoneManager { */ public async getZoneByName(zoneName: string): Promise { const zones = await this.getZones(zoneName); - return zones.find(zone => zone.name === zoneName); + return zones.find((zone) => zone.name === zoneName); } - + /** * Get a zone by its ID * @param zoneId Zone ID to find @@ -56,17 +56,17 @@ export class ZoneManager { try { // 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}` + 'GET', + `/zones/${zoneId}`, ); - + return CloudflareZone.createFromApiObject(response.result as any, this.cfAccount); } catch (error) { logger.log('error', `Failed to fetch zone with ID ${zoneId}: ${error.message}`); return undefined; } } - + /** * Create a new zone * @param zoneName Name of the zone to create @@ -75,37 +75,37 @@ export class ZoneManager { * @returns The created zone */ public async createZone( - zoneName: string, + zoneName: string, jumpStart: boolean = false, - accountId?: string + accountId?: string, ): Promise { const useAccountId = accountId || this.cfAccount.preselectedAccountId; - + if (!useAccountId) { throw new Error('No account selected. Please select it first on the account.'); } - + try { logger.log('info', `Creating zone ${zoneName}`); - + // Use the request method for more direct control over the parameters const response: { result: interfaces.ICflareZone } = await this.cfAccount.request( - 'POST', - '/zones', + 'POST', + '/zones', { name: zoneName, jump_start: jumpStart, - account: { id: useAccountId } - } + account: { id: useAccountId }, + }, ); - + return CloudflareZone.createFromApiObject(response.result as any, this.cfAccount); } catch (error) { logger.log('error', `Failed to create zone ${zoneName}: ${error.message}`); return undefined; } } - + /** * Delete a zone * @param zoneId ID of the zone to delete @@ -114,7 +114,7 @@ export class ZoneManager { public async deleteZone(zoneId: string): Promise { try { logger.log('info', `Deleting zone with ID ${zoneId}`); - + // Use the request method to avoid type issues await this.cfAccount.request('DELETE', `/zones/${zoneId}`); return true; @@ -123,7 +123,7 @@ export class ZoneManager { return false; } } - + /** * Check if a zone exists * @param zoneName Name of the zone to check @@ -131,9 +131,9 @@ export class ZoneManager { */ public async zoneExists(zoneName: string): Promise { const zones = await this.getZones(zoneName); - return zones.some(zone => zone.name === zoneName); + return zones.some((zone) => zone.name === zoneName); } - + /** * Activate a zone (if it's in pending status) * @param zoneId ID of the zone to activate @@ -142,23 +142,23 @@ export class ZoneManager { public async activateZone(zoneId: string): Promise { try { logger.log('info', `Activating zone with ID ${zoneId}`); - + // Use the request method for better control const response: { result: interfaces.ICflareZone } = await this.cfAccount.request( - 'PATCH', + 'PATCH', `/zones/${zoneId}`, { - status: 'active' - } + status: 'active', + }, ); - + return CloudflareZone.createFromApiObject(response.result as any, this.cfAccount); } catch (error) { logger.log('error', `Failed to activate zone with ID ${zoneId}: ${error.message}`); return undefined; } } - + /** * Check the activation status of a zone * @param zoneId ID of the zone to check @@ -167,17 +167,17 @@ export class ZoneManager { public async checkZoneActivation(zoneId: string): Promise { try { logger.log('info', `Checking activation for zone with ID ${zoneId}`); - + // 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); } catch (error) { logger.log('error', `Failed to check zone activation with ID ${zoneId}: ${error.message}`); return undefined; } } -} \ No newline at end of file +} diff --git a/ts/cloudflare.utils.ts b/ts/cloudflare.utils.ts index f14832f..f90aefc 100644 --- a/ts/cloudflare.utils.ts +++ b/ts/cloudflare.utils.ts @@ -16,7 +16,7 @@ export class CloudflareUtils { return false; } } - + /** * Extracts the zone name (apex domain) from a full domain * @param domainName Domain name to process @@ -31,7 +31,7 @@ export class CloudflareUtils { throw new Error(`Invalid domain name: ${domainName}`); } } - + /** * Checks if a string is a valid Cloudflare API token * @param token API token to validate @@ -41,7 +41,7 @@ export class CloudflareUtils { // Cloudflare API tokens are typically 40+ characters long and start with specific patterns return /^[A-Za-z0-9_-]{40,}$/.test(token); } - + /** * Validates a DNS record type * @param type DNS record type to validate @@ -49,14 +49,28 @@ 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); } - + /** * Formats a URL for cache purging (ensures it starts with http/https) * @param url URL to format @@ -68,7 +82,7 @@ export class CloudflareUtils { } return url; } - + /** * Converts a TTL value in seconds to a human-readable string * @param ttl TTL in seconds @@ -101,20 +115,23 @@ export class CloudflareUtils { return `${ttl} seconds`; } } - + /** * Safely handles API pagination for Cloudflare requests * @param makeRequest Function that makes the API request with page parameters * @returns Combined results from all pages */ public static async paginateResults( - 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 { const perPage = 50; // Cloudflare's maximum let page = 1; let totalPages = 1; const allResults: T[] = []; - + do { try { const response = await makeRequest(page, perPage); @@ -126,7 +143,7 @@ export class CloudflareUtils { break; } } while (page <= totalPages); - + return allResults; } -} \ No newline at end of file +} diff --git a/ts/index.ts b/ts/index.ts index f175380..ddb9997 100644 --- a/ts/index.ts +++ b/ts/index.ts @@ -1,5 +1,9 @@ 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 { CloudflareZone } from './cloudflare.classes.zone.js'; @@ -8,4 +12,4 @@ export { CloudflareUtils } from './cloudflare.utils.js'; export { commitinfo } from './00_commitinfo_data.js'; // Re-export interfaces -export * from './interfaces/index.js'; \ No newline at end of file +export * from './interfaces/index.js'; diff --git a/ts/interfaces/cloudflare.api.account.ts b/ts/interfaces/cloudflare.api.account.ts index 6be78f2..3c34a6a 100644 --- a/ts/interfaces/cloudflare.api.account.ts +++ b/ts/interfaces/cloudflare.api.account.ts @@ -17,4 +17,4 @@ export interface ICloudflareApiAccountObject { }; }; created_on: string; // Assuming ISO date string -} \ No newline at end of file +} diff --git a/ts/interfaces/cloudflare.api.zone.ts b/ts/interfaces/cloudflare.api.zone.ts index b412be3..7970a78 100644 --- a/ts/interfaces/cloudflare.api.zone.ts +++ b/ts/interfaces/cloudflare.api.zone.ts @@ -42,4 +42,4 @@ export interface ICflareZone { legacy_discount: boolean; externally_managed: boolean; }; -} \ No newline at end of file +} diff --git a/tsconfig.json b/tsconfig.json index dfe5a55..2413b93 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,9 +6,11 @@ "module": "NodeNext", "moduleResolution": "NodeNext", "esModuleInterop": true, - "verbatimModuleSyntax": true + "verbatimModuleSyntax": true, + "baseUrl": ".", + "paths": {} }, "exclude": [ "dist_*/**/*.d.ts" ] -} +} \ No newline at end of file