Loading .github/workflows/build.yaml +23 −1 Original line number Diff line number Diff line Loading @@ -108,6 +108,28 @@ jobs: - run: deno task test:bun working-directory: ${{ github.workspace }}/fedify/ test-cfworkers: runs-on: ubuntu-latest steps: - if: github.event_name == 'push' uses: actions/checkout@v4 - if: github.event_name == 'pull_request_target' uses: actions/checkout@v4 with: repository: ${{ github.event.pull_request.head.repo.full_name }} ref: ${{ github.event.pull_request.head.sha }} - uses: denoland/setup-deno@v2 with: deno-version: v2.x - uses: actions/setup-node@v4 with: node-version: lts/* - uses: pnpm/action-setup@v4 with: version: 10 - run: deno task test:cfworkers working-directory: ${{ github.workspace }}/fedify/ lint: runs-on: ubuntu-latest steps: Loading Loading @@ -159,7 +181,7 @@ jobs: working-directory: ${{ github.workspace }}/cli/ publish: needs: [test, test-node, test-bun, lint, release-test] needs: [test, test-node, test-bun, test-cfworkers, lint, release-test] runs-on: ubuntu-latest permissions: id-token: write Loading cspell.json +1 −0 Original line number Diff line number Diff line Loading @@ -13,6 +13,7 @@ "btos", "callouts", "cfworker", "cfworkers", "codegen", "compactable", "cryptosuite", Loading fedify/.gitignore +6 −0 Original line number Diff line number Diff line Loading @@ -3,6 +3,12 @@ .dnt-import-map.json .test-report.xml apidoc/ cfworkers/dist/ cfworkers/fixtures/ cfworkers/imports.ts cfworkers/README.md cfworkers/server.js cfworkers/server.js.map coverage/ dist/ fedify-fedify-*.tgz Loading fedify/cfworkers/client.ts 0 → 100644 +72 −0 Original line number Diff line number Diff line import { Miniflare } from "miniflare"; import { join } from "node:path"; import process from "node:process"; import { styleText } from "node:util"; const filters = process.argv.slice(2).map((f) => f.toLowerCase()); const mf = new Miniflare({ // @ts-ignore: scriptPath is not recognized in the type definitions scriptPath: join(import.meta.dirname ?? ".", "server.js"), modules: [ { type: "ESModule", path: join(import.meta.dirname ?? ".", "server.js") }, ], async outboundService(request: Request) { const url = new URL(request.url); if (url.hostname.endsWith(".test")) { const host = url.hostname.slice(0, -5); try { const { default: document } = await import( "../testing/fixtures/" + host + url.pathname + ".json" ); return new Response(JSON.stringify(document), { headers: { "Content-Type": "application/json", }, }); } catch (e) { return new Response(String(e), { status: 404 }); } } return await fetch(request); }, compatibilityDate: "2025-05-23", compatibilityFlags: ["nodejs_compat"], }); const url = await mf.ready; const response = await mf.dispatchFetch(url); const tests = await response.json() as string[]; let passed = 0; let failed = 0; let skipped = 0; for (const test of tests) { const testLower = test.toLowerCase(); if (filters.length > 0 && !filters.some((f) => testLower.includes(f))) { continue; } const resp = await mf.dispatchFetch(url, { method: "POST", body: test, headers: { "Content-Type": "text/plain" }, }); if (resp.ok) { console.log(styleText("green", `PASS: ${test}`)); passed++; } else if (resp.status === 404) { console.log(styleText("yellow", `SKIP: ${test}`)); skipped++; } else { const text = await resp.text(); console.log(styleText("red", `FAIL: ${test}`)); console.log(text); failed++; } } await mf.dispose(); console.log( `Tests completed: ${styleText("green", `${passed} passed`)}, ${ styleText("red", `${failed} failed`) }, ${styleText("yellow", `${skipped} skipped`)}.`, ); // cSpell: ignore Miniflare fedify/cfworkers/server.ts 0 → 100644 +133 −0 Original line number Diff line number Diff line import { ansiColorFormatter, configure, type LogRecord, } from "@logtape/logtape"; import { AsyncLocalStorage } from "node:async_hooks"; // @ts-ignore: The following code is generated import { testDefinitions } from "./dist/testing/mod.js"; // @ts-ignore: The following code is generated import "./imports.ts"; interface TestDefinition { name: string; ignore?: boolean; fn: ( // deno-lint-ignore no-explicit-any ctx: { name: string; origin: string; step: any }, ) => void | Promise<void>; } // @ts-ignore: testDefinitions is untyped const tests: TestDefinition[] = testDefinitions; const logs: LogRecord[] = []; await configure({ sinks: { buffer: logs.push.bind(logs), }, loggers: [ { category: [], sinks: ["buffer"], lowestLevel: "debug" }, ], contextLocalStorage: new AsyncLocalStorage(), }); export default { async fetch(request: Request): Promise<Response> { if (request.method === "GET") { return new Response( JSON.stringify(tests.map(({ name }) => name)), { headers: { "Content-Type": "application/json" }, }, ); } const testName = await request.text(); for (const def of tests) { const { name, fn, ignore } = def; if (testName !== name) continue; if (ignore) { return new Response( "", { status: 404, headers: { "Content-Type": "text/plain" }, }, ); } let failed: unknown = undefined; let capturedLogs: LogRecord[] | undefined = undefined; // deno-lint-ignore no-inner-declarations async function step( arg: string | { name: string; ignore?: boolean; // deno-lint-ignore no-explicit-any fn: (def: any) => void | Promise<void>; // deno-lint-ignore no-explicit-any } | ((ctx: any) => void | Promise<void>), // deno-lint-ignore no-explicit-any fn?: (ctx: any) => void | Promise<void>, ) { let def: { name: string; ignore?: boolean; // deno-lint-ignore no-explicit-any fn: (def: any) => void | Promise<void>; }; if (typeof arg === "string") { def = { name: arg, fn: fn! }; } else if (typeof arg === "function") { def = { name: arg.name, fn: arg }; } else { def = arg; } if (def.ignore) return; try { await def.fn({ name: def.name, origin: "", step, }); } catch (e) { failed ??= e; capturedLogs ??= [...logs]; return false; } return true; } logs.splice(0, logs.length); // Clear logs try { await fn({ name, origin: "", step }); } catch (e) { failed ??= e; } capturedLogs ??= [...logs]; if (typeof failed === "undefined") { return new Response( "", { status: 200, headers: { "Content-Type": "text/plain" } }, ); } else { return new Response( `${ failed instanceof Error ? `${failed.message}\n${failed.stack ?? ""}` : String(failed) }\n${capturedLogs.map(ansiColorFormatter).join("")}`, { status: 500, headers: { "Content-Type": "text/plain" }, }, ); } } return new Response( "Test not found", { status: 404, headers: { "Content-Type": "text/plain" }, }, ); }, }; Loading
.github/workflows/build.yaml +23 −1 Original line number Diff line number Diff line Loading @@ -108,6 +108,28 @@ jobs: - run: deno task test:bun working-directory: ${{ github.workspace }}/fedify/ test-cfworkers: runs-on: ubuntu-latest steps: - if: github.event_name == 'push' uses: actions/checkout@v4 - if: github.event_name == 'pull_request_target' uses: actions/checkout@v4 with: repository: ${{ github.event.pull_request.head.repo.full_name }} ref: ${{ github.event.pull_request.head.sha }} - uses: denoland/setup-deno@v2 with: deno-version: v2.x - uses: actions/setup-node@v4 with: node-version: lts/* - uses: pnpm/action-setup@v4 with: version: 10 - run: deno task test:cfworkers working-directory: ${{ github.workspace }}/fedify/ lint: runs-on: ubuntu-latest steps: Loading Loading @@ -159,7 +181,7 @@ jobs: working-directory: ${{ github.workspace }}/cli/ publish: needs: [test, test-node, test-bun, lint, release-test] needs: [test, test-node, test-bun, test-cfworkers, lint, release-test] runs-on: ubuntu-latest permissions: id-token: write Loading
cspell.json +1 −0 Original line number Diff line number Diff line Loading @@ -13,6 +13,7 @@ "btos", "callouts", "cfworker", "cfworkers", "codegen", "compactable", "cryptosuite", Loading
fedify/.gitignore +6 −0 Original line number Diff line number Diff line Loading @@ -3,6 +3,12 @@ .dnt-import-map.json .test-report.xml apidoc/ cfworkers/dist/ cfworkers/fixtures/ cfworkers/imports.ts cfworkers/README.md cfworkers/server.js cfworkers/server.js.map coverage/ dist/ fedify-fedify-*.tgz Loading
fedify/cfworkers/client.ts 0 → 100644 +72 −0 Original line number Diff line number Diff line import { Miniflare } from "miniflare"; import { join } from "node:path"; import process from "node:process"; import { styleText } from "node:util"; const filters = process.argv.slice(2).map((f) => f.toLowerCase()); const mf = new Miniflare({ // @ts-ignore: scriptPath is not recognized in the type definitions scriptPath: join(import.meta.dirname ?? ".", "server.js"), modules: [ { type: "ESModule", path: join(import.meta.dirname ?? ".", "server.js") }, ], async outboundService(request: Request) { const url = new URL(request.url); if (url.hostname.endsWith(".test")) { const host = url.hostname.slice(0, -5); try { const { default: document } = await import( "../testing/fixtures/" + host + url.pathname + ".json" ); return new Response(JSON.stringify(document), { headers: { "Content-Type": "application/json", }, }); } catch (e) { return new Response(String(e), { status: 404 }); } } return await fetch(request); }, compatibilityDate: "2025-05-23", compatibilityFlags: ["nodejs_compat"], }); const url = await mf.ready; const response = await mf.dispatchFetch(url); const tests = await response.json() as string[]; let passed = 0; let failed = 0; let skipped = 0; for (const test of tests) { const testLower = test.toLowerCase(); if (filters.length > 0 && !filters.some((f) => testLower.includes(f))) { continue; } const resp = await mf.dispatchFetch(url, { method: "POST", body: test, headers: { "Content-Type": "text/plain" }, }); if (resp.ok) { console.log(styleText("green", `PASS: ${test}`)); passed++; } else if (resp.status === 404) { console.log(styleText("yellow", `SKIP: ${test}`)); skipped++; } else { const text = await resp.text(); console.log(styleText("red", `FAIL: ${test}`)); console.log(text); failed++; } } await mf.dispose(); console.log( `Tests completed: ${styleText("green", `${passed} passed`)}, ${ styleText("red", `${failed} failed`) }, ${styleText("yellow", `${skipped} skipped`)}.`, ); // cSpell: ignore Miniflare
fedify/cfworkers/server.ts 0 → 100644 +133 −0 Original line number Diff line number Diff line import { ansiColorFormatter, configure, type LogRecord, } from "@logtape/logtape"; import { AsyncLocalStorage } from "node:async_hooks"; // @ts-ignore: The following code is generated import { testDefinitions } from "./dist/testing/mod.js"; // @ts-ignore: The following code is generated import "./imports.ts"; interface TestDefinition { name: string; ignore?: boolean; fn: ( // deno-lint-ignore no-explicit-any ctx: { name: string; origin: string; step: any }, ) => void | Promise<void>; } // @ts-ignore: testDefinitions is untyped const tests: TestDefinition[] = testDefinitions; const logs: LogRecord[] = []; await configure({ sinks: { buffer: logs.push.bind(logs), }, loggers: [ { category: [], sinks: ["buffer"], lowestLevel: "debug" }, ], contextLocalStorage: new AsyncLocalStorage(), }); export default { async fetch(request: Request): Promise<Response> { if (request.method === "GET") { return new Response( JSON.stringify(tests.map(({ name }) => name)), { headers: { "Content-Type": "application/json" }, }, ); } const testName = await request.text(); for (const def of tests) { const { name, fn, ignore } = def; if (testName !== name) continue; if (ignore) { return new Response( "", { status: 404, headers: { "Content-Type": "text/plain" }, }, ); } let failed: unknown = undefined; let capturedLogs: LogRecord[] | undefined = undefined; // deno-lint-ignore no-inner-declarations async function step( arg: string | { name: string; ignore?: boolean; // deno-lint-ignore no-explicit-any fn: (def: any) => void | Promise<void>; // deno-lint-ignore no-explicit-any } | ((ctx: any) => void | Promise<void>), // deno-lint-ignore no-explicit-any fn?: (ctx: any) => void | Promise<void>, ) { let def: { name: string; ignore?: boolean; // deno-lint-ignore no-explicit-any fn: (def: any) => void | Promise<void>; }; if (typeof arg === "string") { def = { name: arg, fn: fn! }; } else if (typeof arg === "function") { def = { name: arg.name, fn: arg }; } else { def = arg; } if (def.ignore) return; try { await def.fn({ name: def.name, origin: "", step, }); } catch (e) { failed ??= e; capturedLogs ??= [...logs]; return false; } return true; } logs.splice(0, logs.length); // Clear logs try { await fn({ name, origin: "", step }); } catch (e) { failed ??= e; } capturedLogs ??= [...logs]; if (typeof failed === "undefined") { return new Response( "", { status: 200, headers: { "Content-Type": "text/plain" } }, ); } else { return new Response( `${ failed instanceof Error ? `${failed.message}\n${failed.stack ?? ""}` : String(failed) }\n${capturedLogs.map(ansiColorFormatter).join("")}`, { status: 500, headers: { "Content-Type": "text/plain" }, }, ); } } return new Response( "Test not found", { status: 404, headers: { "Content-Type": "text/plain" }, }, ); }, };