Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .hongdown.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ exclude = [
"AGENTS.md",
"CLAUDE.md",
"node_modules/",
"plans/",
]
1 change: 1 addition & 0 deletions .oxfmtrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"ignorePatterns": [
"*.md",
"**/*.md",
"plans/",
"**/dist/*.{cjs,d.cts,d.mts,d.ts,js,mjs}"
]
}
3 changes: 2 additions & 1 deletion .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
{ "ignoreConsecutiveComments": true }
],
"eslint/eqeqeq": ["off", "smart"],
"eslint/func-style": ["warn", "declaration"],
"eslint/func-style": ["off"],
"eslint/id-length": ["warn", { "exceptionPatterns": ["^_", "^[Tertv]$"] }],
"eslint/init-declarations": "off",
"eslint/max-params": ["warn", { "max": 4 }],
Expand Down Expand Up @@ -50,6 +50,7 @@
],
"import/no-namespace": "off",
"import/no-nodejs-modules": "off",
"import/no-relative-parent-imports": "off",
"import/prefer-default-export": "off",
"jsdoc/require-param-type": "off",
"jsdoc/require-returns-type": "off",
Expand Down
2 changes: 1 addition & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"recommendations": ["oxc.oxc-vscode"]
"recommendations": ["oxc.oxc-vscode", "hverlin.mise-vscode"]
}
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"files.trimFinalNewlines": true,
"oxc.enable.oxfmt": true,
"prettier.enable": false,
"oxc.enable": true,
"[css]": {
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
Expand Down
18 changes: 18 additions & 0 deletions cspell.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"words": [
"drfed",
"fedify",
"HMAC",
"logtape",
"mailpit",
"metavar",
"optique",
"oxlint",
"pglite",
"psql",
"smtps",
"upyo",
"valueparser",
"varchar",
],
}
2 changes: 2 additions & 0 deletions mise.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
min_version = "2026.6.10"

[tools]
"aqua:axllent/mailpit" = "latest"
"aqua:dahlia/hongdown" = "0.4.3"
"github:nushell/nushell" = "latest"
node = "26"
Expand Down Expand Up @@ -108,6 +109,7 @@ let name = (if "usage_name" in $env { ["--name" $env.usage_name] } else { [] })
let custom = (if "usage_custom" in $env { ["--custom"] } else { [] })
pnpm exec drizzle-kit generate ...$name ...$custom
"""
depends_post = ["fmt"]

[tasks.dev]
description = "Run the development server"
Expand Down
1 change: 1 addition & 0 deletions packages/drfed/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"@optique/core": "^1.1.0",
"@optique/logtape": "catalog:",
"@optique/run": "^1.1.0",
"@upyo/smtp": "^0.5.0",
"drizzle-orm": "catalog:",
"pg": "catalog:",
"srvx": "^0.11.16"
Expand Down
9 changes: 8 additions & 1 deletion packages/drfed/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { serve } from "srvx";
import metadata from "../package.json" with { type: "json" };
import type { Options } from "./parser.ts";
import program from "./program.ts";
import seedData from "./seed.ts";

// oxlint-disable-next-line max-lines-per-function
export async function main(): Promise<void> {
Expand Down Expand Up @@ -69,14 +70,20 @@ export async function main(): Promise<void> {
if (options.drizzle.migrate) {
await migrate({ credentials: options.drizzle.credentials });
}
const yogaServer = createYogaServer(options.drizzle.db);
if (options.seed) {
await seedData(options.drizzle.db);
}
const yogaServer = createYogaServer(options.drizzle.db, {
mailer: options.mailer,
});
const server = serve({
fetch: yogaServer.fetch.bind(yogaServer),
hostname: options.address.host,
manual: true,
port: options.address.port,
});
function shutdown() {
options.mailer?.closeAllConnections();
// oxlint-disable-next-line promise/catch-or-return promise/prefer-await-to-then no-magic-numbers
server.close().then(() => process.exit(0));
}
Expand Down
54 changes: 40 additions & 14 deletions packages/drfed/src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,19 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

import { relations, schema } from "@drfed/models";
import { PGlite } from "@electric-sql/pglite";
import { getLogger } from "@logtape/drizzle-orm";
import { merge, object, or } from "@optique/core/constructs";
import { message, optionNames } from "@optique/core/message";
import { map, withDefault } from "@optique/core/modifiers";
import { map, optional, withDefault } from "@optique/core/modifiers";
import type { InferValue } from "@optique/core/parser";
import { option } from "@optique/core/primitives";
import { flag, option } from "@optique/core/primitives";
import { socketAddress, url } from "@optique/core/valueparser";
import { loggingOptions } from "@optique/logtape";
import { path } from "@optique/run/valueparser";
import { SmtpTransport } from "@upyo/smtp";
import { drizzle as drizzlePostgres } from "drizzle-orm/node-postgres";
import { drizzle as drizzlePglite } from "drizzle-orm/pglite";

Expand All @@ -36,18 +39,21 @@ const pgliteParser = map(
description: message`The path to the directory where the PGlite database files will be stored. Mutually exclusive with ${optionNames(["--postgres-url", "--database-url", "-D"])}.`,
},
),
(dbPath) => ({
credentials: {
driver: "pglite" as const,
url: dbPath,
},
db: drizzlePglite({
connection: { dataDir: dbPath },
relations,
schema,
logger: getLogger(),
}),
}),
(dbPath) => {
const client = new PGlite(dbPath);
return {
credentials: {
driver: "pglite" as const,
client,
},
db: drizzlePglite({
client,
relations,
schema,
logger: getLogger(),
}),
};
},
);

const postgresParser = map(
Expand Down Expand Up @@ -75,6 +81,24 @@ const postgresParser = map(
}),
);

const smtpParser = map(
option("--smtp-url", "-s", url({ allowedProtocols: ["smtp:"] }), {
description: message`The URL of the SMTP server to deliver emails through.`,
}),
(smtpUrl) =>
new SmtpTransport({
host: smtpUrl.hostname,
port: smtpUrl.port === "" ? SMTP_DEFAULT_PORT : Number(smtpUrl.port),
// SMTPS for later
secure: false,
}),
);
const SMTP_DEFAULT_PORT = 25;

const seedParser = flag("--seed", {
description: message`Seeding initial data for dev or test.`,
});

export const parser = object({
logging: loggingOptions({ level: "option", short: "-L" }),
address: withDefault(
Expand All @@ -97,6 +121,8 @@ export const parser = object({
),
}),
),
mailer: optional(smtpParser),
seed: seedParser,
});

export type Options = InferValue<typeof parser>;
Expand Down
54 changes: 54 additions & 0 deletions packages/drfed/src/seed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// DrFed: A web-based platform for developing and debugging ActivityPub apps
// Copyright (C) 2026 DrFed team
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

import { type Database, schema } from "@drfed/models";

export default async function seedData(db: Database): Promise<void> {
await db.transaction(async (tx) => {
await seedAccounts(tx);
});
}

const accountId = "00000000-0000-4000-8000-000000000001";
const memberId = "00000000-0000-4000-8000-000000000002";
const pendingMemberId = "00000000-0000-4000-8000-000000000003";
const created = new Date("2026-06-24T00:00:00.000Z");

async function seedAccounts(db: Database): Promise<void> {
await db
.insert(schema.accounts)
.values([
{
id: accountId,
email: "owner@example.com",
name: "Owner",
created,
},
{
id: memberId,
email: "member@example.com",
name: "Member",
created,
},
{
id: pendingMemberId,
email: "pending@example.com",
name: "Pending",
created,
},
])
.onConflictDoNothing();
}
4 changes: 4 additions & 0 deletions packages/graphql/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,16 +88,20 @@
"@electric-sql/pglite": "catalog:",
"@logtape/testing-node": "catalog:",
"@types/node": "catalog:",
"@upyo/mock": "^0.5.0",
"tsdown": "catalog:",
"typescript": "catalog:"
},
"dependencies": {
"@drfed/models": "workspace:*",
"@fedify/uri-template": "^2.3.1",
"@logtape/graphql-yoga": "catalog:",
"@logtape/logtape": "catalog:",
"@pothos/core": "^4.13.0",
"@pothos/plugin-drizzle": "^0.17.4",
"@pothos/plugin-errors": "^4.9.1",
"@pothos/plugin-relay": "^4.7.0",
"@upyo/core": "^0.5.0",
"drizzle-orm": "catalog:",
"graphql": "^16.14.2",
"graphql-scalars": "^1.25.0",
Expand Down
Loading