Skip to content

Commit

Permalink
updated: publish package action
Browse files Browse the repository at this point in the history
  • Loading branch information
AndreiIgna committed Jan 23, 2025
1 parent 019ca27 commit 0ef52fc
Show file tree
Hide file tree
Showing 20 changed files with 422 additions and 159 deletions.
8 changes: 6 additions & 2 deletions .github/workflows/publish-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,23 @@ name: Publish package

on:
release:
types: [created]
types: [published]

jobs:
publish-npm:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: latest
registry-url: https://registry.npmjs.org/
- run: npm ci
- run: npm publish
- run: npm build
- run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}

Expand Down
2 changes: 1 addition & 1 deletion dist/dns-resolvers.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DnsRecord } from './index.js';
import { type DnsRecord } from './index.js';
export declare function dnsRecordsCloudflare(name: string, type?: string): Promise<DnsRecord[]>;
export declare function dnsRecordsGoogle(name: string, type?: string): Promise<DnsRecord[]>;
/**
Expand Down
53 changes: 25 additions & 28 deletions dist/dns-resolvers.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { toASCII } from 'punycode';
import {} from './index.js';
const dnsTypeNumbers = {
1: 'A',
2: 'NS',
Expand All @@ -13,8 +14,17 @@ const dnsTypeNumbers = {
33: 'SRV',
257: 'CAA',
};
function prepareDnsRecord(record) {
if (record.name.endsWith('.')) {
record.name = record.name.slice(0, -1);
}
if (['CNAME', 'NS'].includes(record.type) && record.data.endsWith('.')) {
record.data = record.data.slice(0, -1);
}
return record;
}
export async function dnsRecordsCloudflare(name, type = 'A') {
const re = await fetch(`https://cloudflare-dns.com/dns-query?name=${toASCII(name)}&type=${type}&cd=1`, {
const re = await fetch(`https://cloudflare-dns.com/dns-query?name=${toASCII(name)}&type=${type}`, {
headers: {
accept: 'application/dns-json',
}
Expand All @@ -23,34 +33,25 @@ export async function dnsRecordsCloudflare(name, type = 'A') {
throw new Error(`Error fetching DNS records for ${name}: ${re.status} ${re.statusText}`);
}
const json = await re.json();
const records = (json.Answer || []).map((record) => {
const records = (json.Answer || json.Authority || []).map((record) => {
const type = dnsTypeNumbers[record.type] || String(record.type);
let data = record.data;
if (['CNAME', 'NS'].includes(type) && data.endsWith('.')) {
data = data.slice(0, -1);
}
return { name: record.name, type, ttl: record.TTL, data };
return prepareDnsRecord({ name: record.name, type, ttl: record.TTL, data: record.data });
});
return records;
}
export async function dnsRecordsGoogle(name, type = 'A') {
const re = await fetch(`https://dns.google/resolve?name=${toASCII(name)}&type=${type}&cd=1`);
const re = await fetch(`https://dns.google/resolve?name=${toASCII(name)}&type=${type}`);
if (!re.ok) {
throw new Error(`Error fetching DNS records for ${name}: ${re.status} ${re.statusText}`);
}
const json = await re.json();
const records = (json.Answer || []).map((record) => {
const type = dnsTypeNumbers[record.type] || String(record.type);
let data = record.data;
if (['CNAME', 'NS'].includes(type) && data.endsWith('.')) {
data = data.slice(0, -1);
}
return {
const records = (json.Answer || json.Authority || []).map((record) => {
return prepareDnsRecord({
name: record.name,
type,
type: dnsTypeNumbers[record.type] || String(record.type),
ttl: record.TTL,
data,
};
data: record.data,
});
});
return records;
}
Expand Down Expand Up @@ -103,16 +104,12 @@ export async function dnsRecordsNodeDig(names, types = 'A', server) {
.forEach(line => {
// replace tab(s) with space, then split by space
const parts = line.replace(/[\t]+/g, " ").split(" ");
let name = String(parts[0]);
const type = String(parts[3]);
let data = parts.slice(4).join(" ");
if (name.endsWith('.')) {
name = name.slice(0, -1);
}
if (['CNAME', 'NS'].includes(type) && data.endsWith('.')) {
data = data.slice(0, -1);
}
dnsRecords.push({ name, ttl: Number(parts[1]), type, data });
dnsRecords.push(prepareDnsRecord({
name: String(parts[0]),
ttl: Number(parts[1]),
type: String(parts[3]),
data: parts.slice(4).join(" "),
}));
});
return dnsRecords;
}
Expand Down
1 change: 1 addition & 0 deletions dist/dns-resolvers.test.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {};
98 changes: 98 additions & 0 deletions dist/dns-resolvers.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { strict as assert } from 'node:assert';
import { test, suite } from 'node:test';
import { isIPv4, isIPv6 } from 'node:net';
import { dnsRecordsCloudflare, dnsRecordsGoogle, dnsRecordsNodeDns } from './dns-resolvers.js';
suite('Cloudflare DNS resolver', () => {
test('NS records', async () => {
const nsRecords = await dnsRecordsCloudflare('cloudflare.com', 'NS');
assert.ok(nsRecords.length > 1);
assert.equal(nsRecords[0].name, 'cloudflare.com');
assert.equal(nsRecords[0].type, 'NS');
assert.ok(Number.isSafeInteger(nsRecords[0].ttl));
assert.ok(nsRecords[0].data.length);
});
test('A records', async () => {
const aRecords = await dnsRecordsCloudflare('cloudflare.com');
assert.notEqual(aRecords.length, 0);
assert.equal(aRecords[0].name, 'cloudflare.com');
assert.equal(aRecords[0].type, 'A');
assert.ok(Number.isSafeInteger(aRecords[0].ttl));
assert.ok(isIPv4(aRecords[0].data));
});
test('AAAA records', async () => {
const aaaaRecords = await dnsRecordsCloudflare('cloudflare.com', 'AAAA');
assert.notEqual(aaaaRecords.length, 0);
assert.equal(aaaaRecords[0].name, 'cloudflare.com');
assert.equal(aaaaRecords[0].type, 'AAAA');
assert.ok(Number.isSafeInteger(aaaaRecords[0].ttl));
assert.ok(isIPv6(aaaaRecords[0].data));
});
test('MX records', async () => {
const mxRecords = await dnsRecordsCloudflare('cloudflare.com', 'MX');
assert.notEqual(mxRecords.length, 0);
assert.equal(mxRecords[0].name, 'cloudflare.com');
assert.equal(mxRecords[0].type, 'MX');
assert.ok(Number.isSafeInteger(mxRecords[0].ttl));
assert.ok(mxRecords[0].data);
});
test('TXT records', async () => {
const txtRecords = await dnsRecordsCloudflare('cloudflare.com', 'txt');
assert.notEqual(txtRecords.length, 0);
assert.equal(txtRecords[0].name, 'cloudflare.com');
assert.equal(txtRecords[0].type, 'TXT');
assert.ok(Number.isSafeInteger(txtRecords[0].ttl));
assert.ok(txtRecords[0].data);
});
});
suite('Google DNS resolver', () => {
test('A records', async () => {
const aRecords = await dnsRecordsGoogle('google.com');
assert.notEqual(aRecords.length, 0);
assert.equal(aRecords[0].name, 'google.com');
assert.equal(aRecords[0].type, 'A');
assert.ok(Number.isSafeInteger(aRecords[0].ttl));
assert.ok(isIPv4(aRecords[0].data));
});
test('TXT records', async () => {
const txtRecords = await dnsRecordsGoogle('google.com', 'txt');
assert.notEqual(txtRecords.length, 0);
assert.equal(txtRecords[0].name, 'google.com');
assert.equal(txtRecords[0].type, 'TXT');
assert.ok(Number.isSafeInteger(txtRecords[0].ttl));
assert.ok(txtRecords[0].data);
});
});
suite('Node DNS resolver', () => {
test('NS resolver', async () => {
const nsRecords = await dnsRecordsNodeDns('nodejs.org', 'NS');
assert.notEqual(nsRecords.length, 0);
assert.equal(nsRecords[0].name, 'nodejs.org');
assert.equal(nsRecords[0].type, 'NS');
assert.ok(Number.isSafeInteger(nsRecords[0].ttl));
assert.ok(nsRecords[0].data.length);
});
test('A resolver', async () => {
const aRecords = await dnsRecordsNodeDns('nodejs.org');
assert.notEqual(aRecords.length, 0);
assert.equal(aRecords[0].name, 'nodejs.org');
assert.equal(aRecords[0].type, 'A');
assert.ok(Number.isSafeInteger(aRecords[0].ttl));
assert.ok(isIPv4(aRecords[0].data));
});
test('AAAA resolver', async () => {
const aaaaRecords = await dnsRecordsNodeDns('nodejs.org', 'AAAA');
assert.notEqual(aaaaRecords.length, 0);
assert.equal(aaaaRecords[0].name, 'nodejs.org');
assert.equal(aaaaRecords[0].type, 'AAAA');
assert.ok(Number.isSafeInteger(aaaaRecords[0].ttl));
assert.ok(isIPv6(aaaaRecords[0].data));
});
test('TXT resolver', async () => {
const txtRecords = await dnsRecordsNodeDns('nodejs.org', 'txt');
assert.notEqual(txtRecords.length, 0);
assert.equal(txtRecords[0].name, 'nodejs.org');
assert.equal(txtRecords[0].type, 'TXT');
assert.ok(Number.isSafeInteger(txtRecords[0].ttl));
assert.ok(txtRecords[0].data);
});
});
24 changes: 24 additions & 0 deletions dist/get-dns-records.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { type DnsRecord } from './index.js';
/**
* Get DNS records of a given type for a FQDN.
*
* @param name Fully qualified domain name.
* @param type DNS record type: A, AAAA, TXT, CNAME, MX, etc.
* @param resolver Which DNS resolver to use. If not specified, the best DNS resolver for this runtime will be used.
* @returns Array of discovered `DnsRecord` objects.
*
* @example Get TXT records for example.com
* ```js
* import { getDnsRecords } from '@layered/dns-records'
*
* const txtRecords = await getDnsRecords('example.com', 'TXT')
* ```
*
* @example Get MX records for android.com from Google DNS resolver
* ```js
* import { getDnsRecords } from '@layered/dns-records'
*
* const mxRecords = await getDnsRecords('android.com', 'MX', 'google-dns')
* ```
*/
export declare function getDnsRecords(name: string, type?: string, resolver?: string): Promise<DnsRecord[]>;
60 changes: 60 additions & 0 deletions dist/get-dns-records.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { dnsRecordsCloudflare, dnsRecordsGoogle, dnsRecordsNodeDig, dnsRecordsNodeDns } from './dns-resolvers.js';
import {} from './index.js';
import { isDomain } from './utils.js';
function bestDnsResolverForThisRuntime() {
if (globalThis.process?.release?.name === 'node') {
return 'node-dns';
}
else if (globalThis.navigator?.userAgent === 'Cloudflare-Workers') {
return 'cloudflare-dns';
}
else {
return 'google-dns';
}
}
/**
* Get DNS records of a given type for a FQDN.
*
* @param name Fully qualified domain name.
* @param type DNS record type: A, AAAA, TXT, CNAME, MX, etc.
* @param resolver Which DNS resolver to use. If not specified, the best DNS resolver for this runtime will be used.
* @returns Array of discovered `DnsRecord` objects.
*
* @example Get TXT records for example.com
* ```js
* import { getDnsRecords } from '@layered/dns-records'
*
* const txtRecords = await getDnsRecords('example.com', 'TXT')
* ```
*
* @example Get MX records for android.com from Google DNS resolver
* ```js
* import { getDnsRecords } from '@layered/dns-records'
*
* const mxRecords = await getDnsRecords('android.com', 'MX', 'google-dns')
* ```
*/
export async function getDnsRecords(name, type = 'A', resolver) {
if (!isDomain(name)) {
throw new Error(`"${name}" is not a valid domain name`);
}
if (!resolver) {
resolver = bestDnsResolverForThisRuntime();
}
if (resolver === 'cloudflare-dns') {
return dnsRecordsCloudflare(name, type);
}
else if (resolver === 'google-dns') {
return dnsRecordsGoogle(name, type);
}
else if (resolver === 'node-dig') {
return dnsRecordsNodeDig(name, type);
}
else if (resolver === 'node-dns') {
return dnsRecordsNodeDns(name, type);
}
else if (resolver === 'deno-dns') {
throw new Error('Deno DNS not yet implemented');
}
throw new Error(`Invalid DNS resolver: ${resolver}`);
}
1 change: 1 addition & 0 deletions dist/get-dns-records.test.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {};
83 changes: 83 additions & 0 deletions dist/get-dns-records.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { strict as assert } from 'node:assert';
import { test, suite } from 'node:test';
import { getDnsRecords } from './get-dns-records.js';
import { isIPv4 } from 'node:net';
suite('NS for google.com', async () => {
const expectedNs = ['ns1.google.com', 'ns2.google.com', 'ns3.google.com', 'ns4.google.com'];
const [nsRecordsWithCloudflareDns, nsRecordsWithGoogleDns, nsRecordsWithNodeDns, nsRecordsWithNodeDig] = await Promise.all([
getDnsRecords('google.com', 'NS', 'cloudflare-dns'),
getDnsRecords('google.com', 'NS', 'google-dns'),
getDnsRecords('google.com', 'NS', 'node-dns'),
getDnsRecords('google.com', 'NS', 'node-dig'),
]);
test('same number of NS from all resolvers', () => {
assert.equal(nsRecordsWithCloudflareDns.length, expectedNs.length, 'Number of NameServers doesn\'t match');
assert.equal(nsRecordsWithGoogleDns.length, expectedNs.length, 'Number of NameServers doesn\'t match');
assert.equal(nsRecordsWithNodeDns.length, expectedNs.length, 'Number of NameServers doesn\'t match');
assert.equal(nsRecordsWithNodeDig.length, expectedNs.length, 'Number of NameServers doesn\'t match');
});
test('validate NS from `cloudflare-dns`', () => {
assert.equal(nsRecordsWithCloudflareDns[0].name, 'google.com', 'Returned NS doesn\'t match');
assert.equal(nsRecordsWithCloudflareDns[0].type, 'NS', 'Returned record type is not NS');
assert.ok(expectedNs.some(ns => ns === nsRecordsWithCloudflareDns[0].data), 'Returned NS doesn\'t match');
assert.ok(expectedNs.some(ns => ns === nsRecordsWithCloudflareDns[1].data), 'Returned NS doesn\'t match');
});
test('validate NS from `google-dns`', () => {
assert.equal(nsRecordsWithGoogleDns[0].name, 'google.com', 'Returned NS doesn\'t match');
assert.equal(nsRecordsWithGoogleDns[0].type, 'NS', 'Returned record type is not NS');
assert.ok(expectedNs.some(ns => ns === nsRecordsWithGoogleDns[0].data), 'Returned NS doesn\'t match');
assert.ok(expectedNs.some(ns => ns === nsRecordsWithGoogleDns[1].data), 'Returned NS doesn\'t match');
});
test('validate NS from `node-dns`', () => {
assert.equal(nsRecordsWithNodeDns[0].name, 'google.com', 'Returned NS doesn\'t match');
assert.equal(nsRecordsWithNodeDns[0].type, 'NS', 'Returned record type is not NS');
assert.ok(expectedNs.some(ns => ns === nsRecordsWithNodeDns[0].data), 'Returned NS doesn\'t match');
assert.ok(expectedNs.some(ns => ns === nsRecordsWithNodeDns[1].data), 'Returned NS doesn\'t match');
});
test('validate NS from `node-dig`', () => {
assert.equal(nsRecordsWithNodeDig[0].name, 'google.com', 'Returned NS doesn\'t match');
assert.equal(nsRecordsWithNodeDig[0].type, 'NS', 'Returned record type is not NS');
assert.ok(expectedNs.some(ns => ns === nsRecordsWithNodeDig[0].data), 'Returned NS doesn\'t match');
assert.ok(expectedNs.some(ns => ns === nsRecordsWithNodeDig[1].data), 'Returned NS doesn\'t match');
});
});
suite('A records for "mañana.com" (IDN)', async () => {
const [aRecordsWithCloudflareDns, aRecordsWithGoogleDns, aRecordsWithNodeDns, aRecordsWithNodeDig] = await Promise.all([
getDnsRecords('mañana.com', 'A', 'cloudflare-dns'),
getDnsRecords('mañana.com', 'A', 'google-dns'),
getDnsRecords('mañana.com', 'A', 'node-dns'),
getDnsRecords('mañana.com', 'A', 'node-dig'),
]);
test('validate length of records', () => {
assert.notEqual(aRecordsWithCloudflareDns.length, 0);
assert.equal(aRecordsWithCloudflareDns.length, aRecordsWithGoogleDns.length);
assert.equal(aRecordsWithGoogleDns.length, aRecordsWithNodeDns.length);
assert.equal(aRecordsWithNodeDns.length, aRecordsWithNodeDig.length);
});
test('validate returned data', () => {
assert.ok(isIPv4(aRecordsWithCloudflareDns[0].data));
assert.equal(aRecordsWithCloudflareDns[0].data, aRecordsWithGoogleDns[0].data);
assert.equal(aRecordsWithGoogleDns[0].data, aRecordsWithNodeDns[0].data);
assert.equal(aRecordsWithNodeDns[0].data, aRecordsWithNodeDig[0].data);
});
});
suite('TXT records for "cloudflare.com"', async () => {
const [txtRecordsWithCloudflareDns, txtRecordsWithGoogleDns, txtRecordsWithNodeDns, txtRecordsWithNodeDig] = await Promise.all([
getDnsRecords('cloudflare.com', 'TXT', 'cloudflare-dns'),
getDnsRecords('cloudflare.com', 'TXT', 'google-dns'),
getDnsRecords('cloudflare.com', 'TXT', 'node-dns'),
getDnsRecords('cloudflare.com', 'TXT', 'node-dig'),
]);
test('validate number of records', () => {
assert.notEqual(txtRecordsWithCloudflareDns.length, 0);
assert.equal(txtRecordsWithCloudflareDns.length, txtRecordsWithGoogleDns.length, 'TXT records length between `google-dns` and `cloudflare-dns` doesn\'t match');
assert.equal(txtRecordsWithGoogleDns.length, txtRecordsWithNodeDns.length, 'TXT records length between `cloudflare-dns` and `node-dns` doesn\'t match');
assert.equal(txtRecordsWithNodeDns.length, txtRecordsWithNodeDig.length);
});
test('find spf record (cloudflare.com must have one)', () => {
assert.ok(txtRecordsWithCloudflareDns.some(record => record.data.includes('v=spf1')));
assert.ok(txtRecordsWithGoogleDns.some(record => record.data.includes('v=spf1')));
assert.ok(txtRecordsWithNodeDns.some(record => record.data.includes('v=spf1')));
assert.ok(txtRecordsWithNodeDig.some(record => record.data.includes('v=spf1')));
});
});
Loading

0 comments on commit 0ef52fc

Please sign in to comment.