Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
be21695
Define create-integration-package skill
2chanhaeng Apr 6, 2026
4fbc643
Add lint and format checks step to create-integration-package skill
2chanhaeng Apr 6, 2026
c0ef070
Remove unnecessary .gitignore guidance from create-integration-packag…
2chanhaeng Apr 6, 2026
1e41bbd
Update SKILL.md to include testing prerequisites and linting instruct…
2chanhaeng Apr 9, 2026
2ab0be6
Update SKILL.md to publish package on both JSR and NPM
2chanhaeng Apr 9, 2026
2f31207
Minor fix `/create-integration-package` skill
2chanhaeng Apr 9, 2026
32536fc
Fix delete method in PostStore to correctly filter timeline by URL href
2chanhaeng Apr 9, 2026
57da48a
Fix frameworkDescription property name from 'name' to 'label'
2chanhaeng Apr 9, 2026
9fe141b
Fix module/exports fields in package template
2chanhaeng Apr 9, 2026
97f58f4
Remove NestJS peer from package template
2chanhaeng Apr 9, 2026
98eb652
Add imports section to deno.jsonc template
2chanhaeng Apr 9, 2026
c8092e0
Fix hardcoded @fedify/nuxt in README template
2chanhaeng Apr 9, 2026
bc82931
Fix optional param with default value syntax
2chanhaeng Apr 9, 2026
645230f
Update testing instructions in SKILL.md to include unit tests and usa…
2chanhaeng Apr 9, 2026
b0d3dce
Fix typos in create-integration-package skill templates
2chanhaeng Apr 9, 2026
e7d4160
Fix textarea resize to vertical per design spec
2chanhaeng Apr 9, 2026
2f8ff99
Lint
2chanhaeng Apr 9, 2026
64d9ffe
Separate `create-integration-package` skill
2chanhaeng Apr 10, 2026
87e1fb6
Fix awkward wording in example README
2chanhaeng Apr 10, 2026
36e1f9f
Simplify redundant null check in example
2chanhaeng Apr 10, 2026
0fba445
Use type-only import for WebFrameworkDescription
2chanhaeng Apr 10, 2026
0316e49
Fix `mise test:example` to `mise test:examples`
2chanhaeng Apr 10, 2026
c42ca81
Export `fedifyMiddleware` as default and rename in README.md in skill…
2chanhaeng Apr 10, 2026
7322cb4
Import stores from store.ts to federation.ts
2chanhaeng Apr 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions .agents/skills/add-to-fedify-init/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
---
name: add-to-fedify-init
description: >-
This skill is used to add an integration package to the @fedify/init
package so that users can select the new framework via the `fedify init`
command, and to test it with `mise test:init`.
argument-hint: "Provide the name of the web framework to register in @fedify/init."
---

<!-- deno-fmt-ignore-file -->

Adding an integration package to `@fedify/init`
===============================================

Follow these steps in order to register the integration package in
`@fedify/init` and verify it works.

1. Add to `@fedify/init`
2. Test with `mise test:init`
3. Lint, format, and final checks


Add to `@fedify/init`
---------------------

Add the new package to the `@fedify/init` package so users can select the
new framework via the `fedify init` command. Follow these steps.

Steps may require code modifications not explicitly listed. For example,
if the new package needs specific configuration, utility functions in
`packages/init/src/webframeworks/utils.ts` may need updating. Make
modifications consistent with the existing code style and context.

### Write the `WebFrameworkDescription` object

Create a `packages/init/src/webframeworks/framework.ts` file and write the
`WebFrameworkDescription` object, referring to <init/framework.ts>. Check
the specifications in the comments in `packages/init/src/types.ts` for
details.

### Add to the `WEB_FRAMEWORK` array

Add the new framework name to the end of the `WEB_FRAMEWORK` array in
`packages/init/src/const.ts`.

~~~~ typescript
export const WEB_FRAMEWORK = [
// ... other frameworks
"framework", // Fill with the framework name
];
~~~~

### Add to the `webFrameworks` object

Add the new `WebFrameworkDescription` object in alphabetical order to the
`webFrameworks` object in `packages/init/src/webframeworks/mod.ts`.

~~~~ typescript
// packages/init/src/webframeworks/mod.ts

// ... other imports
import framework from "./framework.ts"; // Fill with the framework name

const webFrameworks: Record<string, WebFrameworkDescription> = {
// ... other frameworks
framework, // Fill with the framework name
};
~~~~

### Add templates in `packages/init/src/templates/framework/`

If additional files need to be generated, add template files under the
`packages/init/src/templates/framework/` directory. Template files must
end with the `.tpl` extension appended to their base name. Then, in
`packages/init/src/webframeworks/framework.ts`, load the templates using
the `readTemplate` function defined in `packages/init/src/lib.ts` and add
them to the `WebFrameworkDescription.init().files` object.


Test with `mise test:init`
--------------------------

Run `mise test:init` to verify that the new package is generated and runs
correctly. If a test fails, the output and error file paths are printed;
read them to diagnose the issue.

Running `mise test:init` without arguments tests all option combinations
and can take a very long time. Use appropriate options to narrow the test
scope.

Immediately remove test paths after completing the tests and analyzing any
resulting errors.

At a minimum, test the following three combinations.

- `mise test:init -w framework -m in-process -k in-memory --no-dry-run`:
Tests the new framework with the in-memory KV store and in-process message
queue, which are the most basic options. This combination verify that the
newly created package can be used without issues by minimizing dependencies
on other environments.
- `mise test:init -w framework`: Tests all package manager, KV store,
and message queue combinations with the framework selected. If a
required database is not installed or running, this combinations are
useless. Therefore, if the test output indicates that the databases are
not running, don't use this combination ever again for the session.
Instead, use the previous one or the next one.
- `mise test:init -m in-process -k in-memory --no-dry-run`: Fixes the
KV store and message queue and tests all web framework and package
manager combinations. This test is mandatory if you modified logic
beyond just writing the `WebFrameworkDescription` object.

For details on options, run `mise test:init --help`.

Some frameworks or combinations may be untestable. Analyze the test
results; if there are impossible combinations, identify the reason and add
the combination and reason as a key-value pair to the
`BANNED_LOOKUP_REASONS` object in
`packages/init/src/test/lookup.ts`.


Lint, format, and final checks
------------------------------

Add keywords related to the framework in `.hongdown.toml` and `cspell.json` in
root path.

After implementation, run `mise run fmt && mise check`.
If there are lint or format errors, fix them and run the command again until
there are no errors.
58 changes: 58 additions & 0 deletions .agents/skills/add-to-fedify-init/init/framework.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// packages/init/src/webframeworks/프레임워크.ts
// The import paths are written based on the files in
// `packages/init/src/webframeworks/` where the actual files must exist,
// so do not modify them unless necessary.

import deps from "../json/deps.json" with { type: "json" };
import type { WebFrameworkDescription } from "../types.ts";
import { defaultDenoDependencies, defaultDevDependencies } from "./const.ts";
import { getInstruction } from "./utils.ts";
Comment thread
coderabbitai[bot] marked this conversation as resolved.

const frameworkDescription: WebFrameworkDescription = {
label: "프레임워크", // Fill 프레임워크 with the official framework name
packageManagers: [
// List the package managers that support this framework,
// the list should be a subset of `PACKAGE_MANAGER` from `../const.ts`.
// If the framework is compatible with all package managers,
// you can just use `packageManagers: PACKAGE_MANAGER`.
],
defaultPort: 0, // Fill in the default port of the framework
init: ({
// Destructure necessary parameters from the argument
packageManager: pm,
}) => ({
command: [
// Optional shell command to run before scaffolding e.g., `create-next-app`.
// Split the command into an array of command and arguments,
// e.g., `["npx", "create-next-app@latest"]`.
],
dependencies: pm === "deno"
? {
// Use `deps.json` for version numbers,
// e.g., `"@fedify/프레임워크": deps["@fedify/프레임워크"]`.
...defaultDenoDependencies,
}
: {
// Use `deps.json` for version numbers,
// e.g., `"@fedify/프레임워크": deps["@fedify/프레임워크"]`.
},
devDependencies: {
// Use `deps.json` for version numbers,
// e.g., `"@fedify/프레임워크": deps["@fedify/프레임워크"]`.
...defaultDevDependencies,
},
federationFile: "**/federation.ts",
loggingFile: "**/logging.ts",
tasks: {
// If `command` create a project with `tasks` in `deno.json` (or `script`s in
// `package.json`) to run application, this could be unnecessary.
// In the tasks of the finally generated application, at least include
// a `dev` task to run the development server. `dev` task is used by
// `mise test:init` to run tests against the generated project.
// For Node.js/Bun, `lint: "eslint ."` is needed.
},
instruction: getInstruction(pm, 0 /* Replace with default port */),
}),
};

export default frameworkDescription;
191 changes: 191 additions & 0 deletions .agents/skills/create-example-app-with-integration/ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
<!-- deno-fmt-ignore-file -->

Fedify example architecture
===========================

This document defines the shared architecture for Fedify example applications.
Every example should follow these conventions regardless of the web framework
used, so that learners can compare examples and transfer knowledge between them.


Middleware integration
----------------------

Every Fedify framework adapter exposes a middleware or hook function that
intercepts incoming requests. Register this middleware at the top level of
the server so that it runs before any application routes.

The middleware inspects the `Accept` and `Content-Type` headers. Requests
carrying ActivityPub media types (`application/activity+json`,
`application/ld+json`, etc.) or targeting well-known federation endpoints
are forwarded to the `Federation` instance. All other requests fall through
to the application's own routes.

The specific API differs, but the role is identical: delegate federation
traffic to Fedify, let everything else pass through.


Reverse proxy support
---------------------

If needed, wrap the middleware (or the request handler it receives) with
`getXForwardedRequest` from the `x-forwarded-fetch` package. This rewrites
the request URL to respect `X-Forwarded-Host` and related headers, which is
required when the server runs behind a tunneling tool or reverse proxy during
local development. Apply this wrapping at the same level as the Fedify
middleware registration, before any routing logic executes.


Routing
-------

### `GET /`

The main page. Contains the following sections:

**Search**

A text input for searching fediverse accounts by handle. The client
debounces input with a 300ms delay, then sends a `GET` request with the
handle as a URL query parameter (e.g. `/?q=@user@example.com`). The server
resolves the handle using Fedify's `lookupObject` and returns the result.
The result shows: profile image, display name, handle, and a follow button.
If the local actor already follows the target, show an unfollow button
instead.

**User info**

Displays the local actor's profile. Because this is a demo there is exactly
one actor, `@demo`.

- Profile image: `/demo-profile.png`
- Name: `"Fedify Demo"`
- Handle: `@demo`
- Summary: `"This is a Fedify Demo account."`

**Following**

Lists accounts the local actor follows. Shows the total count and, for
each account: profile image, display name, handle, and an unfollow button.

**Followers**

Lists accounts that follow the local actor. Shows the total count and, for
each account: profile image, display name, and handle.

The following and followers sections update in real time via SSE (see below).

**Compose**

A text area and a submit button for writing a new post. On submission the
server creates a `Note`, stores it in `postStore`, wraps it in a `Create`
activity, and sends it to followers. If sending fails, the post is removed
from the store.

**Posts**

Lists all posts by the local actor in reverse chronological order. Each
entry shows the post content, published timestamp, and a link to the
single post detail page (`/users/{identifier}/posts/{id}`).

### `GET /users/{identifier}`

Actor profile page. Shares its path with the Fedify actor dispatcher.
When a federation peer requests this URL with an ActivityPub media type, the
middleware handles it. Otherwise the request falls through to this route,
which renders an HTML page showing:

- Profile image
- Name
- Handle
- Summary
- Following count
- Followers count

### `GET /users/{identifier}/posts/{id}`

Single post detail page. Shares its path with the Fedify `Note` object
dispatcher. Same content-negotiation fallback as the actor profile: the
middleware serves ActivityPub JSON to federation peers, and this route
renders HTML for browsers. Shows:

- Author profile (same layout as the actor profile page)
- Post content
- Published timestamp

### `POST /post`

Accepts post content from the compose form, creates a `Note`, stores it in
`postStore`, wraps it in a `Create` activity, and sends it to followers.
If sending fails, the post is removed from the store. Redirects back to
`/` on completion.

### `POST /follow`

Accepts a target actor URI, sends a `Follow` activity from the local actor,
and stores the relationship locally.

### `POST /unfollow`

Accepts a target actor URI, sends an `Undo(Follow)` activity, and removes
the relationship locally.

### `GET /events`

SSE endpoint. See the SSE section below.


Server-sent events
------------------

The `/events` endpoint keeps an open SSE connection to the client.
When the following or followers list changes (a follow is accepted, a
remote follow arrives, an unfollow occurs, etc.), the server pushes an
event so the page can update without a full reload.

The server maintains a set of active SSE connections. Whenever the
follower or following store is mutated — inside inbox listeners or after a
local follow/unfollow request — it broadcasts an event to every open
connection.

The client listens on an `EventSource` and replaces the relevant DOM
section with the received data.


Server-side data access
-----------------------

Use Fedify's `RequestContext` to bridge between the framework routing layer
and the federation layer. Obtain a context by calling
`federation.createContext(request, contextData)` inside a route handler.
Through this context, routes can look up actors, resolve object URIs, and
invoke `sendActivity` without coupling to Fedify internals.

Avoid accessing the data stores directly from route handlers when a
`RequestContext` method exists for the same purpose. This keeps the
routing layer thin and ensures that Fedify's internal bookkeeping (key
resolution, URI canonicalization, etc.) is applied consistently.


Federation
----------

Use `src/federation.ts`.


Storing
-------

Use `src/store.ts` and the provided in-memory stores.


View rendering
--------------

See `DESIGN.md`.


Logging
-------

Use `@logtape/logtape` and `src/logging.ts`.
Loading
Loading