Skip to content

Commit 77c62d3

Browse files
authored
Merge pull request #239 from dahlia/cloudflare-workers
2 parents f218880 + 05827ff commit 77c62d3

51 files changed

Lines changed: 1430 additions & 222 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build.yaml

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,28 @@ jobs:
108108
- run: deno task test:bun
109109
working-directory: ${{ github.workspace }}/fedify/
110110

111+
test-cfworkers:
112+
runs-on: ubuntu-latest
113+
steps:
114+
- if: github.event_name == 'push'
115+
uses: actions/checkout@v4
116+
- if: github.event_name == 'pull_request_target'
117+
uses: actions/checkout@v4
118+
with:
119+
repository: ${{ github.event.pull_request.head.repo.full_name }}
120+
ref: ${{ github.event.pull_request.head.sha }}
121+
- uses: denoland/setup-deno@v2
122+
with:
123+
deno-version: v2.x
124+
- uses: actions/setup-node@v4
125+
with:
126+
node-version: lts/*
127+
- uses: pnpm/action-setup@v4
128+
with:
129+
version: 10
130+
- run: deno task test:cfworkers
131+
working-directory: ${{ github.workspace }}/fedify/
132+
111133
lint:
112134
runs-on: ubuntu-latest
113135
steps:
@@ -159,7 +181,7 @@ jobs:
159181
working-directory: ${{ github.workspace }}/cli/
160182

161183
publish:
162-
needs: [test, test-node, test-bun, lint, release-test]
184+
needs: [test, test-node, test-bun, test-cfworkers, lint, release-test]
163185
runs-on: ubuntu-latest
164186
permissions:
165187
id-token: write

cspell.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"btos",
1414
"callouts",
1515
"cfworker",
16+
"cfworkers",
1617
"codegen",
1718
"compactable",
1819
"cryptosuite",

fedify/.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
.dnt-import-map.json
44
.test-report.xml
55
apidoc/
6+
cfworkers/dist/
7+
cfworkers/fixtures/
8+
cfworkers/imports.ts
9+
cfworkers/README.md
10+
cfworkers/server.js
11+
cfworkers/server.js.map
612
coverage/
713
dist/
814
fedify-fedify-*.tgz

fedify/cfworkers/client.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { Miniflare } from "miniflare";
2+
import { join } from "node:path";
3+
import process from "node:process";
4+
import { styleText } from "node:util";
5+
6+
const filters = process.argv.slice(2).map((f) => f.toLowerCase());
7+
8+
const mf = new Miniflare({
9+
// @ts-ignore: scriptPath is not recognized in the type definitions
10+
scriptPath: join(import.meta.dirname ?? ".", "server.js"),
11+
modules: [
12+
{ type: "ESModule", path: join(import.meta.dirname ?? ".", "server.js") },
13+
],
14+
async outboundService(request: Request) {
15+
const url = new URL(request.url);
16+
if (url.hostname.endsWith(".test")) {
17+
const host = url.hostname.slice(0, -5);
18+
try {
19+
const { default: document } = await import(
20+
"../testing/fixtures/" + host + url.pathname + ".json"
21+
);
22+
return new Response(JSON.stringify(document), {
23+
headers: {
24+
"Content-Type": "application/json",
25+
},
26+
});
27+
} catch (e) {
28+
return new Response(String(e), { status: 404 });
29+
}
30+
}
31+
return await fetch(request);
32+
},
33+
compatibilityDate: "2025-05-23",
34+
compatibilityFlags: ["nodejs_compat"],
35+
});
36+
const url = await mf.ready;
37+
const response = await mf.dispatchFetch(url);
38+
const tests = await response.json() as string[];
39+
let passed = 0;
40+
let failed = 0;
41+
let skipped = 0;
42+
for (const test of tests) {
43+
const testLower = test.toLowerCase();
44+
if (filters.length > 0 && !filters.some((f) => testLower.includes(f))) {
45+
continue;
46+
}
47+
const resp = await mf.dispatchFetch(url, {
48+
method: "POST",
49+
body: test,
50+
headers: { "Content-Type": "text/plain" },
51+
});
52+
if (resp.ok) {
53+
console.log(styleText("green", `PASS: ${test}`));
54+
passed++;
55+
} else if (resp.status === 404) {
56+
console.log(styleText("yellow", `SKIP: ${test}`));
57+
skipped++;
58+
} else {
59+
const text = await resp.text();
60+
console.log(styleText("red", `FAIL: ${test}`));
61+
console.log(text);
62+
failed++;
63+
}
64+
}
65+
await mf.dispose();
66+
console.log(
67+
`Tests completed: ${styleText("green", `${passed} passed`)}, ${
68+
styleText("red", `${failed} failed`)
69+
}, ${styleText("yellow", `${skipped} skipped`)}.`,
70+
);
71+
72+
// cSpell: ignore Miniflare

fedify/cfworkers/server.ts

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import {
2+
ansiColorFormatter,
3+
configure,
4+
type LogRecord,
5+
} from "@logtape/logtape";
6+
import { AsyncLocalStorage } from "node:async_hooks";
7+
// @ts-ignore: The following code is generated
8+
import { testDefinitions } from "./dist/testing/mod.js";
9+
// @ts-ignore: The following code is generated
10+
import "./imports.ts";
11+
12+
interface TestDefinition {
13+
name: string;
14+
ignore?: boolean;
15+
fn: (
16+
// deno-lint-ignore no-explicit-any
17+
ctx: { name: string; origin: string; step: any },
18+
) => void | Promise<void>;
19+
}
20+
21+
// @ts-ignore: testDefinitions is untyped
22+
const tests: TestDefinition[] = testDefinitions;
23+
const logs: LogRecord[] = [];
24+
25+
await configure({
26+
sinks: {
27+
buffer: logs.push.bind(logs),
28+
},
29+
loggers: [
30+
{ category: [], sinks: ["buffer"], lowestLevel: "debug" },
31+
],
32+
contextLocalStorage: new AsyncLocalStorage(),
33+
});
34+
35+
export default {
36+
async fetch(request: Request): Promise<Response> {
37+
if (request.method === "GET") {
38+
return new Response(
39+
JSON.stringify(tests.map(({ name }) => name)),
40+
{
41+
headers: { "Content-Type": "application/json" },
42+
},
43+
);
44+
}
45+
const testName = await request.text();
46+
for (const def of tests) {
47+
const { name, fn, ignore } = def;
48+
if (testName !== name) continue;
49+
if (ignore) {
50+
return new Response(
51+
"",
52+
{
53+
status: 404,
54+
headers: { "Content-Type": "text/plain" },
55+
},
56+
);
57+
}
58+
let failed: unknown = undefined;
59+
let capturedLogs: LogRecord[] | undefined = undefined;
60+
// deno-lint-ignore no-inner-declarations
61+
async function step(
62+
arg: string | {
63+
name: string;
64+
ignore?: boolean;
65+
// deno-lint-ignore no-explicit-any
66+
fn: (def: any) => void | Promise<void>;
67+
// deno-lint-ignore no-explicit-any
68+
} | ((ctx: any) => void | Promise<void>),
69+
// deno-lint-ignore no-explicit-any
70+
fn?: (ctx: any) => void | Promise<void>,
71+
) {
72+
let def: {
73+
name: string;
74+
ignore?: boolean;
75+
// deno-lint-ignore no-explicit-any
76+
fn: (def: any) => void | Promise<void>;
77+
};
78+
if (typeof arg === "string") {
79+
def = { name: arg, fn: fn! };
80+
} else if (typeof arg === "function") {
81+
def = { name: arg.name, fn: arg };
82+
} else {
83+
def = arg;
84+
}
85+
if (def.ignore) return;
86+
try {
87+
await def.fn({
88+
name: def.name,
89+
origin: "",
90+
step,
91+
});
92+
} catch (e) {
93+
failed ??= e;
94+
capturedLogs ??= [...logs];
95+
return false;
96+
}
97+
return true;
98+
}
99+
logs.splice(0, logs.length); // Clear logs
100+
try {
101+
await fn({ name, origin: "", step });
102+
} catch (e) {
103+
failed ??= e;
104+
}
105+
capturedLogs ??= [...logs];
106+
if (typeof failed === "undefined") {
107+
return new Response(
108+
"",
109+
{ status: 200, headers: { "Content-Type": "text/plain" } },
110+
);
111+
} else {
112+
return new Response(
113+
`${
114+
failed instanceof Error
115+
? `${failed.message}\n${failed.stack ?? ""}`
116+
: String(failed)
117+
}\n${capturedLogs.map(ansiColorFormatter).join("")}`,
118+
{
119+
status: 500,
120+
headers: { "Content-Type": "text/plain" },
121+
},
122+
);
123+
}
124+
}
125+
return new Response(
126+
"Test not found",
127+
{
128+
status: 404,
129+
headers: { "Content-Type": "text/plain" },
130+
},
131+
);
132+
},
133+
};

fedify/codegen/schema.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,11 +241,12 @@ async function loadSchemaValidator(): Promise<Validator> {
241241
return new Validator(schemaObject as JsonSchema);
242242
}
243243

244-
const schemaValidator: Validator = await loadSchemaValidator();
244+
let schemaValidator: Validator | undefined = undefined;
245245

246246
async function loadSchema(path: string): Promise<TypeSchema> {
247247
const content = await readFile(path, { encoding: "utf-8" });
248248
const schema = parse(content);
249+
if (schemaValidator == null) schemaValidator = await loadSchemaValidator();
249250
const result = schemaValidator.validate(schema);
250251
const errors: SchemaError[] = [];
251252
if (result.valid) return schema as TypeSchema;

fedify/deno.json

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"asn1js": "npm:asn1js@^3.0.5",
3333
"byte-encodings": "npm:byte-encodings@^1.0.11",
3434
"fast-check": "npm:fast-check@^3.22.0",
35+
"fetch-mock": "npm:fetch-mock@^12.5.2",
3536
"json-canon": "npm:json-canon@^1.0.1",
3637
"jsonld": "npm:jsonld@^8.3.2",
3738
"multicodec": "npm:multicodec@^3.2.1",
@@ -46,6 +47,13 @@
4647
],
4748
"exclude": [
4849
"apidoc/",
50+
"cfworkers/dist/",
51+
"cfworkers/fixtures/",
52+
"cfworkers/imports.ts",
53+
"cfworkers/README.md",
54+
"cfworkers/server.ts",
55+
"cfworkers/server.js",
56+
"cfworkers/server.js.map",
4957
"codegen/schema.yaml",
5058
"dist/",
5159
"node_modules/",
@@ -124,12 +132,19 @@
124132
"pnpm:build"
125133
]
126134
},
135+
"test:cfworkers": {
136+
"command": "pnpm exec wrangler deploy --dry-run --outdir cfworkers && node --import=tsx cfworkers/client.ts",
137+
"dependencies": [
138+
"pnpm:build"
139+
]
140+
},
127141
"test-all": {
128142
"dependencies": [
129143
"check",
130144
"test",
131145
"test:node",
132-
"test:bun"
146+
"test:bun",
147+
"test:cfworkers"
133148
]
134149
}
135150
}

0 commit comments

Comments
 (0)