Skip to content

Commit

Permalink
build: improve node compatibility (#8)
Browse files Browse the repository at this point in the history
* build: enable type checking for npm build

* build: make npm build to run integration tests too

* ci: run npm build in ci
  • Loading branch information
ycmjason authored Oct 14, 2024
1 parent d7481a3 commit d09726f
Show file tree
Hide file tree
Showing 22 changed files with 205 additions and 103 deletions.
46 changes: 46 additions & 0 deletions .github/workflows/npm-build.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: "Node: Build + Integration"

on:
push:
branches:
- main
pull_request:

jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3

- uses: denoland/setup-deno@v2
with:
deno-version: v2.x # Run with latest stable Deno.

- name: Start Pebble and pebble-challtestsrv
run: deno task pebble:start --detach

- name: Wait for Pebble to be ready
run: |
echo "⏳ Waiting for Pebble... 🪨"
# Loop until the port 14000 is open
until nc -zv localhost 14000 2>/dev/null; do
sleep 1
done
echo "✅ Pebble is running!"
- name: Wait for pebble-challtestsrv to be ready
run: |
echo "⏳ Waiting for pebble-challtestsrv... 🪨"
until nc -zv localhost 8055 2>/dev/null; do
sleep 1
done
echo "✅ pebble-challtestsrv is running!"
- run: deno task build:npm:integration

- name: Stop Pebble
run: deno task pebble:stop
10 changes: 7 additions & 3 deletions .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
name: Publish
on:
push:
branches:
- main
workflow_run:
workflows: ["CI", "E2E", "Integration", "Node: Build + Integration"]
branches: [main]
types:
- completed

jobs:
publish-jsr:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest

permissions:
Expand All @@ -19,6 +22,7 @@ jobs:
run: npx jsr publish

publish-npm:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest

permissions:
Expand Down
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,9 @@ application.

This is built primarily for Deno.

But since this is built in 100% TypeScript with no dependencies, it should be
But since this is built in 100% TypeScript with no dependencies, it is
compatible with all modern JavaScript platforms that support the [WebCrypto] and
[fetch]. (One exception being `DnsUtils.pollDnsTxtRecord()` which make use of
`Deno.resolveDns`, perhaps we can make this take in a `resolveDns` function
later to make this platform agnostic.)
[fetch].

## Features

Expand Down
3 changes: 2 additions & 1 deletion deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"test:integration": "deno test --unsafely-ignore-certificate-errors=localhost -A integration",
"pebble:start": "docker compose -f integration/docker-compose.yaml up",
"pebble:stop": "docker compose -f integration/docker-compose.yaml down",
"build:npm": "deno run -A scripts/build-npm.ts"
"build:npm": "deno run -A scripts/build-npm.ts",
"build:npm:integration": "deno run -A scripts/build-npm-integration.ts"
},
"compilerOptions": {
"strict": true,
Expand Down
2 changes: 1 addition & 1 deletion e2e/create-certificate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
AcmeOrder,
Dns01Challenge,
DnsUtils,
} from "@fishballpkg/acme";
} from "../src/mod.ts";
import { expect, it } from "../test_deps.ts";
import { CloudflareZone } from "./utils/cloudflare.ts";
import { expectToBeDefined } from "./utils/expectToBeDefined.ts";
Expand Down
2 changes: 1 addition & 1 deletion e2e/workflows.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
AcmeClient,
AcmeOrder,
AcmeWorkflows,
} from "@fishballpkg/acme";
} from "../src/mod.ts";
import { describe, expect, it } from "../test_deps.ts";
import { CloudflareZone } from "./utils/cloudflare.ts";
import { randomFishballTestingSubdomain } from "./utils/randomFishballTestingSubdomain.ts";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { AcmeClient, AcmeOrder, AcmeWorkflows } from "@fishballpkg/acme";
// 01 prefix to this file because of https://github.com/denoland/dnt/issues/432
import { AcmeClient, AcmeOrder, AcmeWorkflows } from "../src/mod.ts";
import { describe, expect, it } from "../test_deps.ts";
import { EMAIL, PEBBLE_DIRECTORY_URL } from "./CONSTANTS.ts";
import { generateRandomDomain } from "./utils/generateRandomDomain.ts";
import { PebbleChallTestSrv } from "./utils/PebbleChallTestSrv.ts";
import { resolveDns } from "./utils/resolveDns.ts";
import { setupNode } from "./utils/setupNode.ts";

setupNode();

const DOMAINS = [
generateRandomDomain(),
Expand All @@ -27,14 +32,7 @@ describe("requestCertificates", () => {
updateDnsRecords: async (dnsRecords) => {
await pebbleChallTestSrv.createDnsRecords(dnsRecords);
},
resolveDns: async (query, recordType) => {
return await Deno.resolveDns(query, recordType, {
nameServer: {
ipAddr: "127.0.0.1",
port: 8053,
},
});
},
resolveDns,
});

expect(certKeyPair.privateKey).toBeInstanceOf(CryptoKey);
Expand Down
5 changes: 4 additions & 1 deletion integration/account.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { AcmeClient } from "@fishballpkg/acme";
import { AcmeClient } from "../src/mod.ts";
import { expect, it } from "../test_deps.ts";
import { EMAIL, PEBBLE_DIRECTORY_URL } from "./CONSTANTS.ts";
import { setupNode } from "./utils/setupNode.ts";

setupNode();

it("should create the account successfully and the key pair should allow login", async () => {
const client = await AcmeClient.init(PEBBLE_DIRECTORY_URL);
Expand Down
5 changes: 4 additions & 1 deletion integration/order.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { AcmeClient } from "@fishballpkg/acme";
import { AcmeClient } from "../src/mod.ts";
import { expect, it } from "../test_deps.ts";
import { EMAIL, PEBBLE_DIRECTORY_URL } from "./CONSTANTS.ts";
import { generateRandomDomain } from "./utils/generateRandomDomain.ts";
import { setupNode } from "./utils/setupNode.ts";

setupNode();

it("should place an order correctly and get the corresponding authorizations", async () => {
const client = await AcmeClient.init(PEBBLE_DIRECTORY_URL);
Expand Down
2 changes: 1 addition & 1 deletion integration/utils/PebbleChallTestSrv.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { DnsTxtRecord } from "@fishballpkg/acme";
import type { DnsTxtRecord } from "../../src/mod.ts";
import { afterEach } from "../../test_deps.ts";

const PEBBLE_CHALLTESTSRV_URL = "http://localhost:8055";
Expand Down
19 changes: 19 additions & 0 deletions integration/utils/resolveDns.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {
defaultResolveDns,
type ResolveDnsFunction,
} from "../../src/DnsUtils/resolveDns.ts";

/**
* A resolveDns function specifically for integration tests to allow TXT lookups to be done via pebble-testchallsrv
*/
export const resolveDns: ResolveDnsFunction = async (query, recordType) => {
return (await defaultResolveDns(query, recordType, {
nameServer: recordType === "TXT"
? {
ipAddr: "127.0.0.1",
port: 8053,
} // only lookup via pebble-challtestsrv for txt records
: undefined,
// deno-lint-ignore no-explicit-any -- typescript is hard
})) as any;
};
6 changes: 6 additions & 0 deletions integration/utils/setupNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const setupNode = () => {
if ("process" in globalThis) {
// @ts-ignore: node specific
globalThis["process"]["env"]["NODE_TLS_REJECT_UNAUTHORIZED"] = 0;
}
};
10 changes: 10 additions & 0 deletions scripts/build-npm-integration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { build, type BuildOptions } from "jsr:@deno/dnt";
import { dntConfig } from "./build-npm.ts";

const config: BuildOptions = {
...dntConfig,
testPattern: "integration/**/*.test.ts",
test: true,
};

await build(config);
30 changes: 23 additions & 7 deletions scripts/build-npm.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { build, emptyDir } from "jsr:@deno/dnt";
import { build, type BuildOptions, emptyDir } from "jsr:@deno/dnt";
import { join } from "jsr:@std/path";
import DENO_JSON from "../deno.json" with { type: "json" };

Expand All @@ -14,20 +14,24 @@ const OUT_DIR = join(
"./dist-npm",
);

await emptyDir(OUT_DIR);

const GITHUB_REPO = "https://github.com/fishballapp/acme";

await build({
export const dntConfig: BuildOptions = {
entryPoints: Object.entries(DENO_JSON.exports).map(([name, path]) => ({
kind: "export",
name,
path: join(PROJECT_ROOT, path),
})),
outDir: OUT_DIR,
test: false,
typeCheck: false,
shims: {},
shims: {
deno: "dev",
undici: "dev",
},
compilerOptions: {
lib: ["ESNext", "DOM"],
target: "ES2022",
},
package: {
name: DENO_JSON.name,
version: DENO_JSON.version,
Expand All @@ -43,6 +47,13 @@ await build({
},
keywords: ["acme"],
homepage: "https://jsr.io/@fishballpkg/acme/doc",
devDependencies: {
"@types/node": "latest",
},
},
mappings: {
[`${PROJECT_ROOT}/src/DnsUtils/resolveDns.deno.ts`]:
`${PROJECT_ROOT}/src/DnsUtils/resolveDns.node.ts`,
},
postBuild() {
// steps to run after building and before running the tests
Expand All @@ -53,4 +64,9 @@ await build({
);
}
},
});
};

if (import.meta.main) {
await emptyDir(OUT_DIR);
await build(dntConfig);
}
32 changes: 0 additions & 32 deletions src/DnsUtils/ResolveDnsFunction.ts

This file was deleted.

20 changes: 15 additions & 5 deletions src/DnsUtils/_helpers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { defaultResolveDns, type ResolveDnsFunction } from "./resolveDns.ts";

type IpVersion = "ipv4" | "ipv6";

const QUAD9_DNS_IPS: Record<IpVersion, string> = {
Expand All @@ -12,29 +14,37 @@ const cache: Record<IpVersion, Promise<boolean> | undefined> = {

const isIpVersionSupported = async (
version: IpVersion,
{
resolveDns = defaultResolveDns,
}: {
resolveDns?: ResolveDnsFunction;
} = {},
): Promise<boolean> => {
cache[version] ??= (async () => {
try {
await Deno.resolveDns("example.com", "NS", {
await resolveDns("example.com", "NS", {
nameServer: {
ipAddr: QUAD9_DNS_IPS[version],
},
});
return true;
} catch {
} catch (e) {
console.error(e);
return false;
}
})();

return await cache[version];
};

export const getSupportedIpVersions = async (): Promise<
export const getSupportedIpVersions = async (
opts: { resolveDns?: ResolveDnsFunction } = {},
): Promise<
readonly IpVersion[]
> => {
const [ipv4Supported, ipv6Supported] = await Promise.all([
isIpVersionSupported("ipv4"),
isIpVersionSupported("ipv6"),
isIpVersionSupported("ipv4", opts),
isIpVersionSupported("ipv6", opts),
]);

if (ipv4Supported && ipv6Supported) {
Expand Down
6 changes: 2 additions & 4 deletions src/DnsUtils/findAuthoritativeNameServerIps.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ResolveDnsFunction } from "./ResolveDnsFunction.ts";
import { defaultResolveDns, type ResolveDnsFunction } from "./resolveDns.ts";
export type FindAuthoritativeNameServerIpsConfig = {
/**
* A function to resolve DNS record.
Expand All @@ -24,9 +24,7 @@ export const findAuthoritativeNameServerIps = async (
domain: string,
config: FindAuthoritativeNameServerIpsConfig = {},
): Promise<string[]> => {
const resolveDns =
(config.resolveDns ?? Deno.resolveDns) as typeof Deno.resolveDns;

const { resolveDns = defaultResolveDns } = config;
while (domain.includes(".")) { // continue the loop if we haven't reached TLD
const nameServers = await (async () => {
try {
Expand Down
2 changes: 1 addition & 1 deletion src/DnsUtils/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@

export * from "./findAuthoritativeNameServerIps.ts";
export * from "./pollDnsTxtRecord.ts";
export * from "./ResolveDnsFunction.ts";
export * from "./resolveDns.ts";
Loading

0 comments on commit d09726f

Please sign in to comment.