From 9623273d56b4dad8ac4f4f848e89dd0ab8d4423a Mon Sep 17 00:00:00 2001 From: ChanHaeng Lee <2chanhaeng@gmail.com> Date: Wed, 15 Apr 2026 08:03:35 +0000 Subject: [PATCH 1/6] Fix typo --- .agents/skills/commit/SKILL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.agents/skills/commit/SKILL.md b/.agents/skills/commit/SKILL.md index a8b1f30c8..4e91282c9 100644 --- a/.agents/skills/commit/SKILL.md +++ b/.agents/skills/commit/SKILL.md @@ -42,8 +42,8 @@ EOF Don't put `Generated with [Claude Code](https://claude.ai/code)`, or `Co-authored-by` trailers at the end of the commit message. Instead, -use the `Assiged-by` trailer to indicate that the commit was generated by -an AI assistant, if necessary. The format of the `Assiged-by` trailer should +use the `Assisted-by` trailer to indicate that the commit was generated by +an AI assistant, if necessary. The format of the `Assisted-by` trailer should be: ~~~~ From 6b4805323a413ea87bed99fa119a63619b7dde5d Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Mon, 20 Apr 2026 00:01:15 +0900 Subject: [PATCH 2/6] Fix Astro npm entrypoints Restore the published npm entrypoint contract for @fedify/astro by making tsdown emit dist/*.js and dist/*.d.ts files that match the package metadata again. Add a self-reference regression test that exercises the public API through ESM import and CommonJS require so the packaged entrypoints are validated the way consumers load them. Update CHANGES.md with the packaging fix details. Fixes https://github.com/fedify-dev/fedify/issues/699 Assisted-by: Codex:gpt-5.4 --- CHANGES.md | 11 +++ packages/astro/package.json | 1 + packages/astro/src/package.test.ts | 106 +++++++++++++++++++++++++++++ packages/astro/tsdown.config.ts | 6 ++ 4 files changed, 124 insertions(+) create mode 100644 packages/astro/src/package.test.ts diff --git a/CHANGES.md b/CHANGES.md index bd1c15f0b..3d07f9c5e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,17 @@ Version 2.1.6 To be released. +### @fedify/astro + + - Restored the npm entrypoint contract for `@fedify/astro` by making the + build emit _dist/\*.js_ and _dist/\*.d.ts_ files that match the published + package metadata again. This fixes package resolution failures caused by + _package.json_ exporting files that did not exist in the npm tarball. + [[#699], [#701]] + +[#699]: https://github.com/fedify-dev/fedify/issues/699 +[#701]: https://github.com/fedify-dev/fedify/pull/701 + ### @fedify/cli - Fixed `fedify lookup` failing to look up URLs on private or localhost diff --git a/packages/astro/package.json b/packages/astro/package.json index 12bd75454..29ca4c860 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -61,6 +61,7 @@ "build": "pnpm --filter @fedify/astro... run build:self", "prepack": "pnpm build", "prepublish": "pnpm build", + "pretest": "pnpm build", "test": "node --experimental-transform-types --test" } } diff --git a/packages/astro/src/package.test.ts b/packages/astro/src/package.test.ts new file mode 100644 index 000000000..5ae399e96 --- /dev/null +++ b/packages/astro/src/package.test.ts @@ -0,0 +1,106 @@ +import { deepStrictEqual, strictEqual } from "node:assert/strict"; +import { createRequire } from "node:module"; +import test from "node:test"; + +const require = createRequire(import.meta.url); + +function expectResponse(response: void | Response): Response { + if (!(response instanceof Response)) { + throw new TypeError("Expected middleware to return a Response"); + } + return response; +} + +test("self-reference ESM import exposes working Astro integration API", async () => { + const mod = await import("@fedify/astro"); + + strictEqual(typeof mod.fedifyIntegration, "function"); + strictEqual(typeof mod.fedifyMiddleware, "function"); + + const integration = mod.fedifyIntegration(); + strictEqual(integration.name, "@fedify/astro"); + + let capturedConfig: unknown; + ( + integration.hooks as Record< + "astro:config:setup", + (args: { updateConfig(config: unknown): void }) => void + > + )["astro:config:setup"]({ + updateConfig(config) { + capturedConfig = config; + }, + }); + deepStrictEqual(capturedConfig, { + vite: { + ssr: { + noExternal: ["@fedify/fedify", "@fedify/vocab"], + }, + }, + }); + + let capturedRequest: Request | undefined; + let capturedContextData: unknown; + const middleware = mod.fedifyMiddleware( + { + fetch( + request: Request, + options: { + contextData: string; + onNotAcceptable(request: Request): Promise; + }, + ) { + capturedRequest = request; + capturedContextData = options.contextData; + return options.onNotAcceptable(request); + }, + } as never, + () => "test-context", + ); + + const request = new Request("https://example.com/"); + const response = expectResponse(await middleware( + { request } as never, + () => Promise.resolve(new Response("Not found", { status: 404 })), + )); + strictEqual(capturedRequest, request); + strictEqual(capturedContextData, "test-context"); + strictEqual(response.status, 406); + strictEqual(response.headers.get("Vary"), "Accept"); +}); + +test( + "self-reference CommonJS require exposes working Astro middleware API", + { skip: "Deno" in globalThis }, + async () => { + const mod = require("@fedify/astro") as typeof import("@fedify/astro"); + + strictEqual(typeof mod.fedifyIntegration, "function"); + strictEqual(typeof mod.fedifyMiddleware, "function"); + + let nextCalled = false; + const middleware = mod.fedifyMiddleware( + { + fetch( + _request: Request, + options: { onNotFound(request: Request): Promise }, + ) { + return options.onNotFound(new Request("https://example.com/actor")); + }, + } as never, + () => undefined, + ); + + const response = expectResponse(await middleware( + { request: new Request("https://example.com/inbox") } as never, + () => { + nextCalled = true; + return Promise.resolve(new Response("Handled by Astro")); + }, + )); + + strictEqual(nextCalled, true); + strictEqual(response.status, 200); + strictEqual(await response.text(), "Handled by Astro"); + }, +); diff --git a/packages/astro/tsdown.config.ts b/packages/astro/tsdown.config.ts index 27a91233e..bf33f512d 100644 --- a/packages/astro/tsdown.config.ts +++ b/packages/astro/tsdown.config.ts @@ -5,4 +5,10 @@ export default defineConfig({ dts: true, format: ["esm", "cjs"], platform: "node", + outExtensions({ format }) { + return { + js: format === "cjs" ? ".cjs" : ".js", + dts: format === "cjs" ? ".d.cts" : ".d.ts", + }; + }, }); From cb59fb3e7885147e8e2e0039e5d8a6ed2325975f Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Mon, 20 Apr 2026 00:18:34 +0900 Subject: [PATCH 3/6] Tighten Astro package test typing Replace the test's `as never` shortcuts with explicit mock helper functions and concrete `FederationFetchOptions` / `APIContext` annotations. This keeps the self-import regression test readable while preserving plain `deno test` in *packages/astro/* and the existing Node packaging checks. --- packages/astro/src/package.test.ts | 68 ++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 22 deletions(-) diff --git a/packages/astro/src/package.test.ts b/packages/astro/src/package.test.ts index 5ae399e96..7a04310c9 100644 --- a/packages/astro/src/package.test.ts +++ b/packages/astro/src/package.test.ts @@ -1,9 +1,26 @@ import { deepStrictEqual, strictEqual } from "node:assert/strict"; import { createRequire } from "node:module"; import test from "node:test"; +import type { + Federation, + FederationFetchOptions, +} from "@fedify/fedify/federation"; +import type { APIContext } from "astro"; const require = createRequire(import.meta.url); +type MockFederation = Pick, "fetch">; + +function toFederation( + federation: MockFederation, +): Federation { + return federation as Federation; +} + +function toApiContext(context: Pick): APIContext { + return context as APIContext; +} + function expectResponse(response: void | Response): Response { if (!(response instanceof Response)) { throw new TypeError("Expected middleware to return a Response"); @@ -42,27 +59,29 @@ test("self-reference ESM import exposes working Astro integration API", async () let capturedRequest: Request | undefined; let capturedContextData: unknown; const middleware = mod.fedifyMiddleware( - { - fetch( + toFederation({ + async fetch( request: Request, - options: { - contextData: string; - onNotAcceptable(request: Request): Promise; - }, + options: FederationFetchOptions, ) { capturedRequest = request; capturedContextData = options.contextData; + if (options.onNotAcceptable == null) { + throw new TypeError("Expected onNotAcceptable to be defined"); + } return options.onNotAcceptable(request); }, - } as never, + }), () => "test-context", ); const request = new Request("https://example.com/"); - const response = expectResponse(await middleware( - { request } as never, - () => Promise.resolve(new Response("Not found", { status: 404 })), - )); + const response = expectResponse( + await middleware( + toApiContext({ request }), + () => Promise.resolve(new Response("Not found", { status: 404 })), + ), + ); strictEqual(capturedRequest, request); strictEqual(capturedContextData, "test-context"); strictEqual(response.status, 406); @@ -80,24 +99,29 @@ test( let nextCalled = false; const middleware = mod.fedifyMiddleware( - { - fetch( + toFederation({ + async fetch( _request: Request, - options: { onNotFound(request: Request): Promise }, + options: FederationFetchOptions, ) { + if (options.onNotFound == null) { + throw new TypeError("Expected onNotFound to be defined"); + } return options.onNotFound(new Request("https://example.com/actor")); }, - } as never, + }), () => undefined, ); - const response = expectResponse(await middleware( - { request: new Request("https://example.com/inbox") } as never, - () => { - nextCalled = true; - return Promise.resolve(new Response("Handled by Astro")); - }, - )); + const response = expectResponse( + await middleware( + toApiContext({ request: new Request("https://example.com/inbox") }), + () => { + nextCalled = true; + return Promise.resolve(new Response("Handled by Astro")); + }, + ), + ); strictEqual(nextCalled, true); strictEqual(response.status, 200); From 7d916703cb6d47a353de6c34a1b4505754eeef1e Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Mon, 20 Apr 2026 01:19:47 +0900 Subject: [PATCH 4/6] Satisfy lint in Astro package test Make the mock federation fetch methods await their callback results so the test continues to return Promise while satisfying Deno's require-await lint rule. Assisted-by: Codex:gpt-5.4 --- packages/astro/src/package.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/astro/src/package.test.ts b/packages/astro/src/package.test.ts index 7a04310c9..e3a4f6437 100644 --- a/packages/astro/src/package.test.ts +++ b/packages/astro/src/package.test.ts @@ -69,7 +69,7 @@ test("self-reference ESM import exposes working Astro integration API", async () if (options.onNotAcceptable == null) { throw new TypeError("Expected onNotAcceptable to be defined"); } - return options.onNotAcceptable(request); + return await options.onNotAcceptable(request); }, }), () => "test-context", @@ -107,7 +107,9 @@ test( if (options.onNotFound == null) { throw new TypeError("Expected onNotFound to be defined"); } - return options.onNotFound(new Request("https://example.com/actor")); + return await options.onNotFound( + new Request("https://example.com/actor"), + ); }, }), () => undefined, From e43ca81be758439271e2c1992a1baab899be3f1d Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Mon, 20 Apr 2026 01:22:26 +0900 Subject: [PATCH 5/6] Cover Astro package entrypoints Extend the Astro package regression test to assert that the JavaScript and declaration entrypoints declared in package.json exist on disk. This keeps the test aligned with the original packaging regression so missing type outputs are caught before publication. --- packages/astro/src/package.test.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/astro/src/package.test.ts b/packages/astro/src/package.test.ts index e3a4f6437..9172d8f3f 100644 --- a/packages/astro/src/package.test.ts +++ b/packages/astro/src/package.test.ts @@ -1,6 +1,9 @@ import { deepStrictEqual, strictEqual } from "node:assert/strict"; +import { access, readFile } from "node:fs/promises"; import { createRequire } from "node:module"; +import { dirname, resolve } from "node:path"; import test from "node:test"; +import { fileURLToPath } from "node:url"; import type { Federation, FederationFetchOptions, @@ -8,6 +11,7 @@ import type { import type { APIContext } from "astro"; const require = createRequire(import.meta.url); +const packageDir = resolve(dirname(fileURLToPath(import.meta.url)), ".."); type MockFederation = Pick, "fetch">; @@ -28,6 +32,10 @@ function expectResponse(response: void | Response): Response { return response; } +async function assertTargetExists(path: string): Promise { + await access(resolve(packageDir, path)); +} + test("self-reference ESM import exposes working Astro integration API", async () => { const mod = await import("@fedify/astro"); @@ -92,6 +100,24 @@ test( "self-reference CommonJS require exposes working Astro middleware API", { skip: "Deno" in globalThis }, async () => { + const packageJson = JSON.parse( + await readFile(resolve(packageDir, "package.json"), "utf8"), + ); + const exportMap = packageJson.exports["."]; + const targets = [ + packageJson.main, + packageJson.module, + packageJson.types, + exportMap.require.types, + exportMap.require.default, + exportMap.import.types, + exportMap.import.default, + ] as string[]; + + for (const target of new Set(targets)) { + await assertTargetExists(target); + } + const mod = require("@fedify/astro") as typeof import("@fedify/astro"); strictEqual(typeof mod.fedifyIntegration, "function"); From 82b694f6863e278abe0929379527255b097af53c Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Mon, 20 Apr 2026 02:25:34 +0900 Subject: [PATCH 6/6] Validate Astro package targets Make the Astro package test assert each package.json entrypoint explicitly instead of relying on a cast to string[]. This prevents missing metadata fields from slipping through as false positives while keeping the regression coverage focused on the published package contract. --- packages/astro/src/package.test.ts | 37 +++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/packages/astro/src/package.test.ts b/packages/astro/src/package.test.ts index 9172d8f3f..43ea977ce 100644 --- a/packages/astro/src/package.test.ts +++ b/packages/astro/src/package.test.ts @@ -32,6 +32,11 @@ function expectResponse(response: void | Response): Response { return response; } +function expectTarget(actual: unknown, expected: string, key: string): string { + strictEqual(actual, expected, `Expected ${key} to be ${expected}`); + return actual; +} + async function assertTargetExists(path: string): Promise { await access(resolve(packageDir, path)); } @@ -105,14 +110,30 @@ test( ); const exportMap = packageJson.exports["."]; const targets = [ - packageJson.main, - packageJson.module, - packageJson.types, - exportMap.require.types, - exportMap.require.default, - exportMap.import.types, - exportMap.import.default, - ] as string[]; + expectTarget(packageJson.main, "./dist/mod.cjs", "package.json main"), + expectTarget(packageJson.module, "./dist/mod.js", "package.json module"), + expectTarget(packageJson.types, "./dist/mod.d.ts", "package.json types"), + expectTarget( + exportMap.require.types, + "./dist/mod.d.cts", + 'package.json exports["."].require.types', + ), + expectTarget( + exportMap.require.default, + "./dist/mod.cjs", + 'package.json exports["."].require.default', + ), + expectTarget( + exportMap.import.types, + "./dist/mod.d.ts", + 'package.json exports["."].import.types', + ), + expectTarget( + exportMap.import.default, + "./dist/mod.js", + 'package.json exports["."].import.default', + ), + ]; for (const target of new Set(targets)) { await assertTargetExists(target);