Compare commits

...

2 Commits

Author SHA1 Message Date
Henry
4f6fc66f84 chore: add LOG_LEVEL to docker compose and readme 2026-03-13 22:26:04 +00:00
Henry
3604a49a89 feat: add logger package to improve observability 2026-03-13 22:19:45 +00:00
30 changed files with 440 additions and 123 deletions

View File

@@ -51,6 +51,9 @@ TRELLO_APP_SECRET=
# If not provided, rate limiting will use in-memory storage
REDIS_URL= # e.g. redis://default:your_password@your_host:6379
# Logging (optional - debug, info, warn, error; defaults to debug in dev, info in prod)
LOG_LEVEL=
# OAuth providers (optional)
BETTER_AUTH_TRUSTED_ORIGINS=
# Optional: Restrict OIDC/Social sign-ins to specific email domains (comma-separated)

View File

@@ -185,9 +185,17 @@ Use `assertUserInWorkspace` helper for workspace checks.
- Use TRPCError with appropriate codes (UNAUTHORIZED, NOT_FOUND, etc.)
- Provide user-friendly error messages
- Log errors appropriately
- Log errors appropriately using the `@kan/logger` package
- Show popup notifications for user-facing errors
### Logging
- Import from `@kan/logger`: `import { createLogger } from "@kan/logger"`
- Create a module-scoped logger: `const logger = createLogger("module-name")`
- Log level is controlled by `LOG_LEVEL` env var (debug, info, warn, error)
- Defaults to `debug` in development, `info` in production
- Never use `console.log` — always use the logger
## Common Patterns
### Creating a Card
@@ -219,6 +227,16 @@ Use `assertUserInWorkspace` helper for workspace checks.
5. **Frontend**: Add UI components in `apps/web/src/`
6. **i18n**: Add translations for new strings
## Adding a New Environment Variable
Update all of the following:
1. `.env.example` — add the variable with an empty value and a comment explaining it
2. `turbo.json` — add to `globalEnv` (or `globalPassThroughEnv` for CI/platform vars)
3. `docker-compose.yml` — add to the `web` service `environment` section
4. `cloud/docker-compose.yml` — add to the `web` service `environment` section
5. `README.md` — add a row to the Environment Variables table
## Database Changes
- Always create migrations (never modify existing migrations)

View File

@@ -213,6 +213,7 @@ pnpm dev
| `NEXT_PUBLIC_DISABLE_SIGN_UP` | Disable sign up | For authentication | `false` |
| `NEXT_PUBLIC_WHITE_LABEL_HIDE_POWERED_BY` | Hide “Powered by kan.bn” on public boards (self-host) | For white labelling | `true` |
| `KAN_ADMIN_API_KEY` | Admin API key for stats and admin endpoints | For admin/monitoring | `your-secret-admin-key` |
| `LOG_LEVEL` | Log verbosity level (debug, info, warn, error) | No (defaults to debug in dev, info in prod) | `info` |
See `.env.example` for a complete list of supported environment variables.

View File

@@ -27,6 +27,7 @@
"@kan/api": "workspace:*",
"@kan/auth": "workspace:*",
"@kan/db": "workspace:^",
"@kan/logger": "workspace:^",
"@kan/shared": "workspace:^",
"@lingui/babel-preset-react": "^2.9.2",
"@lingui/conf": "^5.3.2",

View File

@@ -145,7 +145,6 @@ export default function CommandPallette({
value={result}
className="cursor-pointer select-none px-4 py-3 data-[focus]:bg-light-200 hover:bg-light-200 focus:outline-none dark:data-[focus]:bg-dark-200 dark:hover:bg-dark-200"
onClick={() => {
console.log("clicked", url);
void router.push(url);
onClose();
setQuery("");

View File

@@ -75,7 +75,7 @@ export function LabelForm({
reset(newFormState);
}
} catch (e) {
console.log(e);
console.error(e);
}
},
});
@@ -231,9 +231,7 @@ export function LabelForm({
<Button
type="submit"
isLoading={updateLabel.isPending || createLabel.isPending}
disabled={
!watch("name")
}
disabled={!watch("name")}
>
{isEdit ? t`Update label` : t`Create label`}
</Button>

View File

@@ -3,8 +3,11 @@ import type { Readable } from "node:stream";
import { createNextApiContext } from "@kan/api/trpc";
import * as workspaceRepo from "@kan/db/repository/workspace.repo";
import { createLogger } from "@kan/logger";
import { createStripeClient } from "@kan/stripe";
const log = createLogger("stripe-webhook");
async function buffer(readable: Readable) {
const chunks = [];
for await (const chunk of readable) {
@@ -41,6 +44,8 @@ export default async function handler(
const { db } = await createNextApiContext(req);
log.info({ eventType: event.type, eventId: event.id }, "Stripe webhook received");
switch (event.type) {
case "checkout.session.completed": {
const checkoutSession = event.data.object;
@@ -56,12 +61,12 @@ export default async function handler(
break;
}
default:
console.log(`Unhandled event type: ${event.type}`);
log.warn({ eventType: event.type }, "Unhandled Stripe event type");
}
return res.status(200).json({ received: true });
} catch (err) {
console.error("Webhook error:", err);
log.error({ err }, "Stripe webhook handler failed");
return res.status(400).json({ message: "Webhook handler failed" });
}
}

View File

@@ -1,24 +1,25 @@
import { useEffect } from "react";
export default function TrelloAuthorize() {
useEffect(() => {
const hash = window.location.hash;
const token = hash.split("=")[1];
if (token) {
console.log("Posting token to /api/trello/authenticate", token);
fetch("/api/trello/authenticate", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ token }),
}).then(() => {
window.close();
});
}
}, []);
useEffect(() => {
const hash = window.location.hash;
const token = hash.split("=")[1];
if (token) {
fetch("/api/trello/authenticate", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ token }),
}).then(() => {
window.close();
});
}
}, []);
return <div className="flex h-[200px] items-center justify-center">
<p className="text-center">Connecting to Trello...</p>
</div>;
}
return (
<div className="flex h-[200px] items-center justify-center">
<p className="text-center">Connecting to Trello...</p>
</div>
);
}

View File

@@ -234,7 +234,7 @@ const ImportGithub: React.FC = () => {
await refetchBoards();
closeModal();
} catch (e) {
console.log(e);
console.error(e);
}
},
onError: () => {
@@ -386,7 +386,7 @@ const ImportTrello: React.FC = () => {
await refetchBoards();
closeModal();
} catch (e) {
console.log(e);
console.error(e);
}
},
onError: () => {

View File

@@ -37,6 +37,9 @@ services:
- NEXT_PUBLIC_USE_STANDALONE_OUTPUT=${NEXT_PUBLIC_USE_STANDALONE_OUTPUT}
- REDIS_URL=${REDIS_URL}
# Logging (optional - debug, info, warn, error; defaults to debug in dev, info in prod)
- LOG_LEVEL=${LOG_LEVEL}
# Stripe
- STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY}
- STRIPE_WEBHOOK_SECRET=${STRIPE_WEBHOOK_SECRET}

View File

@@ -37,6 +37,9 @@ services:
# Redis (optional - for rate limiting)
- REDIS_URL=${REDIS_URL}
# Logging (optional - debug, info, warn, error; defaults to debug in dev, info in prod)
- LOG_LEVEL=${LOG_LEVEL}
# Admin API key (optional)
- KAN_ADMIN_API_KEY=${KAN_ADMIN_API_KEY}

View File

@@ -48,6 +48,7 @@
"@kan/auth": "workspace:*",
"@kan/db": "workspace:*",
"@kan/email": "workspace:^",
"@kan/logger": "workspace:^",
"@kan/shared": "workspace:^",
"@kan/stripe": "workspace:^",
"@trpc/server": "catalog:",

View File

@@ -9,6 +9,9 @@ import { ZodError } from "zod";
import type { dbClient } from "@kan/db/client";
import { initAuth } from "@kan/auth/server";
import { createDrizzleClient } from "@kan/db/client";
import { createLogger } from "@kan/logger";
const log = createLogger("trpc");
export interface User {
id: string;
@@ -90,7 +93,7 @@ export const createRESTContext = async ({ req }: CreateNextContextOptions) => {
try {
session = await auth.api.getSession();
} catch (error) {
console.error("Error getting session, ", error);
log.error({ err: error }, "Error getting session");
throw error;
}
@@ -118,7 +121,23 @@ export const createTRPCRouter = t.router;
export const createCallerFactory = t.createCallerFactory;
export const publicProcedure = t.procedure.meta({
const loggingMiddleware = t.middleware(async ({ path, type, next, ctx }) => {
const start = Date.now();
const result = await next();
const duration = Date.now() - start;
const meta = { procedure: path, type, duration, userId: (ctx as { user?: { id: string } }).user?.id };
if (result.ok) {
log.info(meta, "tRPC OK");
} else {
log.error({ ...meta, err: result.error }, "tRPC error");
}
return result;
});
export const publicProcedure = t.procedure.use(loggingMiddleware).meta({
openapi: { method: "GET", path: "/public" },
});
@@ -142,14 +161,18 @@ const enforceUserIsAdmin = t.middleware(async ({ ctx, next }) => {
});
});
export const protectedProcedure = t.procedure.use(enforceUserIsAuthed).meta({
openapi: {
method: "GET",
path: "/protected",
},
});
export const protectedProcedure = t.procedure
.use(loggingMiddleware)
.use(enforceUserIsAuthed)
.meta({
openapi: {
method: "GET",
path: "/protected",
},
});
export const adminProtectedProcedure = t.procedure
.use(loggingMiddleware)
.use(enforceUserIsAdmin)
.meta({
openapi: {

View File

@@ -1,6 +1,9 @@
import { env } from "next-runtime-env";
import type { dbClient } from "@kan/db/client";
import { createLogger } from "@kan/logger";
const log = createLogger("notifications");
import * as cardRepo from "@kan/db/repository/card.repo";
import * as memberRepo from "@kan/db/repository/member.repo";
import * as notificationRepo from "@kan/db/repository/notification.repo";
@@ -72,6 +75,7 @@ export async function sendMentionEmails({
const baseUrl = env("NEXT_PUBLIC_BASE_URL");
const cardUrl = `${baseUrl}/cards/${cardPublicId}`;
log.info({ cardPublicId, mentionCount: membersToNotify.length, commenterUserId }, "Sending mention emails");
// Send emails to all mentioned members (only if notification doesn't exist)
await Promise.all(
membersToNotify.map(async (member) => {
@@ -91,6 +95,7 @@ export async function sendMentionEmails({
// If notification already exists, skip sending email
if (notificationExists) {
log.debug({ email, cardPublicId }, "Skipping duplicate mention email");
return;
}
@@ -114,20 +119,14 @@ export async function sendMentionEmails({
cardUrl,
},
);
log.info({ email, cardPublicId }, "Mention email sent");
} catch (error) {
console.error("Failed to send mention email:", {
email,
cardPublicId,
error: error instanceof Error ? error.message : String(error),
});
log.error({ err: error, email, cardPublicId }, "Failed to send mention email");
}
}),
);
} catch (error) {
console.error("Error sending mention emails:", {
cardPublicId,
error: error instanceof Error ? error.message : String(error),
});
log.error({ err: error, cardPublicId }, "Error sending mention emails");
}
}

View File

@@ -5,6 +5,9 @@ import {
} from "rate-limiter-flexible";
import { getRedisClient } from "@kan/db/redis";
import { createLogger } from "@kan/logger";
const log = createLogger("rateLimit");
export interface RateLimitOptions {
points?: number;
@@ -45,7 +48,7 @@ function createRateLimiter(options: RateLimitOptions = {}) {
// Use Redis if available, otherwise fall back to in-memory storage
if (redis) {
console.log("Using Redis for rate limiting");
log.debug("Using Redis for rate limiting");
return new RateLimiterRedis({
storeClient: redis,
points,
@@ -53,7 +56,7 @@ function createRateLimiter(options: RateLimitOptions = {}) {
});
}
console.log("Using in-memory for rate limiting");
log.debug("Redis unavailable, falling back to in-memory rate limiting");
return new RateLimiterMemory({
points,
duration,

View File

@@ -4,6 +4,9 @@ import { z } from "zod";
import type { dbClient } from "@kan/db/client";
import type { WebhookEvent } from "@kan/db/schema";
import * as webhookRepo from "@kan/db/repository/webhook.repo";
import { createLogger } from "@kan/logger";
const log = createLogger("webhook");
export type WebhookEventType = WebhookEvent;
@@ -199,9 +202,9 @@ export async function sendWebhooksForWorkspace(
sendWebhookToUrl(webhook.url, webhook.secret ?? undefined, payload).then(
(result) => {
if (!result.success) {
console.error(
`Webhook delivery failed to ${webhook.url}: ${result.error}`,
);
log.error({ url: webhook.url, event: payload.event, error: result.error, statusCode: result.statusCode }, "Webhook delivery failed");
} else {
log.info({ url: webhook.url, event: payload.event, statusCode: result.statusCode }, "Webhook delivered");
}
},
),
@@ -210,7 +213,7 @@ export async function sendWebhooksForWorkspace(
// Wait for all to complete but don't block on failures
await Promise.allSettled(promises);
} catch (error) {
console.error("Failed to send webhooks for workspace:", error);
log.error({ err: error, workspaceId }, "Failed to send webhooks for workspace");
}
}

View File

@@ -39,6 +39,7 @@
"prettier": "@kan/prettier-config",
"dependencies": {
"@better-auth/stripe": "^1.4.6",
"@kan/logger": "workspace:^",
"better-auth": "^1.4.6"
}
}

View File

@@ -7,8 +7,11 @@ import type { dbClient } from "@kan/db/client";
import * as memberRepo from "@kan/db/repository/member.repo";
import * as userRepo from "@kan/db/repository/user.repo";
import { notificationClient } from "@kan/email";
import { createLogger } from "@kan/logger";
import { createEmailUnsubscribeLink, createS3Client } from "@kan/shared";
const log = createLogger("auth");
import { downloadImage } from "./utils";
type BetterAuthUser = {
@@ -103,6 +106,7 @@ export function createDatabaseHooks(db: dbClient) {
const unsubscribeUrl = await createEmailUnsubscribeLink(user.id);
log.info({ workflowId: "user-signup", userId: user.id, email: user.email }, "Triggering Novu workflow");
await notificationClient.trigger({
to: {
subscriberId: user.id,
@@ -122,6 +126,7 @@ export function createDatabaseHooks(db: dbClient) {
},
workflowId: "user-signup",
});
log.info({ workflowId: "user-signup", userId: user.id }, "Novu workflow triggered");
await notificationClient.subscribers.credentials.update(
{
@@ -134,7 +139,7 @@ export function createDatabaseHooks(db: dbClient) {
user.id,
);
} catch (error) {
console.error("Error adding user to notification client", error);
log.error({ err: error }, "Error adding user to notification client");
}
}
},

View File

@@ -8,7 +8,10 @@ import * as subscriptionRepo from "@kan/db/repository/subscription.repo";
import * as userRepo from "@kan/db/repository/user.repo";
import * as workspaceRepo from "@kan/db/repository/workspace.repo";
import { sendEmail } from "@kan/email";
import { createLogger } from "@kan/logger";
import { generateUID } from "@kan/shared/utils";
const log = createLogger("auth");
import { createStripeClient } from "@kan/stripe";
import { socialProvidersPlugin } from "./providers";
@@ -101,9 +104,7 @@ export function createPlugins(db: dbClient) {
unlimitedSeats: true,
},
);
console.log(
`Pro subscription ${stripeSubscription.id} activated with unlimited seats`,
);
log.info({ subscriptionId: stripeSubscription.id }, "Pro subscription activated with unlimited seats");
const workspace = await workspaceRepo.getByPublicId(
db,
@@ -175,11 +176,7 @@ export function createPlugins(db: dbClient) {
sendMagicLink: async ({ email, url }) => {
try {
const decodedUrl = decodeURIComponent(url);
console.log("Sending magic link to:", email, "URL:", url);
console.log(
"Magic link contains invite:",
decodedUrl.includes("type=invite"),
);
log.info({ email, isInvite: decodedUrl.includes("type=invite") }, "Sending magic link");
if (decodedUrl.includes("type=invite")) {
let inviterName = "";
let workspaceName = "";
@@ -211,7 +208,7 @@ export function createPlugins(db: dbClient) {
}
}
} catch (error) {
console.error("Failed to fetch invite details:", error);
log.error({ err: error }, "Failed to fetch invite details");
}
await sendEmail(
@@ -239,11 +236,7 @@ export function createPlugins(db: dbClient) {
);
}
} catch (error) {
console.error("Error sending magic link:", {
email,
url,
error,
});
log.error({ err: error, email }, "Error sending magic link");
}
},
}),
@@ -273,7 +266,7 @@ export function createPlugins(db: dbClient) {
picture?: string;
avatar?: string;
}) => {
console.log("OIDC profile:", profile);
log.debug({ profile }, "OIDC profile received");
const name =
profile.name ??

View File

@@ -4,8 +4,11 @@ import type Stripe from "stripe";
import type { dbClient } from "@kan/db/client";
import * as userRepo from "@kan/db/repository/user.repo";
import { notificationClient } from "@kan/email";
import { createLogger } from "@kan/logger";
import { createEmailUnsubscribeLink } from "@kan/shared";
const log = createLogger("auth");
export async function downloadImage(url: string): Promise<Buffer> {
const response = await fetch(url);
if (!response.ok) {
@@ -32,6 +35,7 @@ export async function triggerWorkflow(
const unsubscribeUrl = await createEmailUnsubscribeLink(user.id);
log.info({ workflowId, userId: user.id }, "Triggering Novu workflow");
await notificationClient.trigger({
to: {
subscriberId: user.id,
@@ -43,7 +47,8 @@ export async function triggerWorkflow(
},
workflowId,
});
log.info({ workflowId, userId: user.id }, "Novu workflow triggered");
} catch (error) {
console.error("Error triggering workflow", error);
log.error({ err: error, workflowId }, "Error triggering workflow");
}
}

View File

@@ -44,6 +44,7 @@
},
"dependencies": {
"@electric-sql/pglite": "^0.3.7",
"@kan/logger": "workspace:^",
"@kan/shared": "workspace:^",
"drizzle-orm": "^0.42.0",
"drizzle-zod": "^0.5.1",

View File

@@ -6,8 +6,12 @@ import { drizzle as drizzlePgLite } from "drizzle-orm/pglite";
import { migrate } from "drizzle-orm/pglite/migrator";
import { Pool } from "pg";
import { createLogger } from "@kan/logger";
import * as schema from "./schema";
const log = createLogger("db");
export type dbClient = NodePgDatabase<typeof schema> & {
$client: Pool;
};
@@ -16,7 +20,7 @@ export const createDrizzleClient = (): dbClient => {
const connectionString = process.env.POSTGRES_URL;
if (!connectionString) {
console.log("POSTGRES_URL environment variable is not set, using PGLite");
log.warn("POSTGRES_URL not set, falling back to PGLite");
const client = new PGlite({
dataDir: "./pgdata",

View File

@@ -402,8 +402,6 @@ export const softDeleteById = async (
.groupBy(lists.index)
.having(gt(countExpr, 1));
console.log(duplicateIndices);
if (duplicateIndices.length > 0) {
throw new Error(
`Duplicate indices found after reordering in board ${result.boardId}`,

View File

@@ -23,6 +23,7 @@
"typecheck": "tsc --noEmit --emitDeclarationOnly false"
},
"dependencies": {
"@kan/logger": "workspace:^",
"@novu/api": "^3.11.0",
"@react-email/components": "^1.0.1",
"nodemailer": "^7.0.3",

View File

@@ -1,5 +1,8 @@
import { render } from "@react-email/render";
import nodemailer from "nodemailer";
import { createLogger } from "@kan/logger";
const log = createLogger("email");
import JoinWorkspaceTemplate from "./templates/join-workspace";
import MagicLinkTemplate from "./templates/magic-link";
@@ -44,6 +47,7 @@ export const sendEmail = async (
template: Templates,
data: Record<string, string>,
) => {
log.info({ to, subject, template }, "Sending email");
try {
const EmailTemplate = emailTemplates[template];
@@ -62,16 +66,10 @@ export const sendEmail = async (
throw new Error(`Failed to send email: ${response.response}`);
}
log.info({ to, subject, template, messageId: response.messageId }, "Email sent");
return response;
} catch (error) {
console.error("Email sending failed:", {
to,
from: process.env.EMAIL_FROM,
subject,
template,
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
});
log.error({ err: error, to, from: process.env.EMAIL_FROM, subject, template }, "Email sending failed");
throw error;
}
};

View File

@@ -0,0 +1,34 @@
{
"name": "@kan/logger",
"private": true,
"version": "0.1.0",
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./src/index.ts"
}
},
"license": "GPL-3.0",
"scripts": {
"build": "tsc",
"clean": "git clean -xdf .cache .turbo dist node_modules",
"dev": "tsc",
"format": "prettier --check . --ignore-path ../../.gitignore",
"lint": "eslint",
"typecheck": "tsc --noEmit --emitDeclarationOnly false"
},
"dependencies": {
"pino": "^9.14.0",
"pino-pretty": "^13.1.3"
},
"devDependencies": {
"@kan/eslint-config": "workspace:*",
"@kan/prettier-config": "workspace:*",
"@kan/tsconfig": "workspace:*",
"eslint": "catalog:",
"prettier": "catalog:",
"typescript": "catalog:"
},
"prettier": "@kan/prettier-config"
}

View File

@@ -0,0 +1,20 @@
import pino from "pino";
const isDev = process.env.NODE_ENV !== "production";
const level = process.env.LOG_LEVEL ?? (isDev ? "debug" : "info");
export const logger = pino({
level,
...(isDev && {
transport: {
target: "pino-pretty",
options: {
colorize: true,
ignore: "pid,hostname",
translateTime: "HH:MM:ss",
},
},
}),
});
export const createLogger = (module: string) => logger.child({ module });

View File

@@ -0,0 +1,6 @@
{
"extends": "@kan/tsconfig/internal-package.json",
"compilerOptions": {},
"include": ["*.ts", "src"],
"exclude": ["node_modules"]
}

271
pnpm-lock.yaml generated
View File

@@ -103,6 +103,9 @@ importers:
'@kan/db':
specifier: workspace:^
version: link:../../packages/db
'@kan/logger':
specifier: workspace:^
version: link:../../packages/logger
'@kan/shared':
specifier: workspace:^
version: link:../../packages/shared
@@ -311,6 +314,9 @@ importers:
'@kan/email':
specifier: workspace:^
version: link:../email
'@kan/logger':
specifier: workspace:^
version: link:../logger
'@kan/shared':
specifier: workspace:^
version: link:../shared
@@ -353,13 +359,16 @@ importers:
version: 5.9.2
vitest:
specifier: ^3.0.0
version: 3.2.4(@types/debug@4.1.12)(@types/node@25.0.0)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.1)
version: 3.2.4(@types/debug@4.1.12)(@types/node@25.0.0)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.1)
packages/auth:
dependencies:
'@better-auth/stripe':
specifier: ^1.4.6
version: 1.4.6(@better-auth/core@1.4.6(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.1.5(zod@4.1.13))(jose@6.1.3)(kysely@0.28.8)(nanostores@1.1.0))(better-auth@1.4.6(next@16.0.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(stripe@18.5.0(@types/node@25.0.0))
'@kan/logger':
specifier: workspace:^
version: link:../logger
better-auth:
specifier: ^1.4.6
version: 1.4.6(next@16.0.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -400,6 +409,9 @@ importers:
'@electric-sql/pglite':
specifier: ^0.3.7
version: 0.3.7
'@kan/logger':
specifier: workspace:^
version: link:../logger
'@kan/shared':
specifier: workspace:^
version: link:../shared
@@ -452,6 +464,9 @@ importers:
packages/email:
dependencies:
'@kan/logger':
specifier: workspace:^
version: link:../logger
'@novu/api':
specifier: ^3.11.0
version: 3.11.0
@@ -487,6 +502,34 @@ importers:
specifier: 'catalog:'
version: 5.9.2
packages/logger:
dependencies:
pino:
specifier: ^9.14.0
version: 9.14.0
pino-pretty:
specifier: ^13.1.3
version: 13.1.3
devDependencies:
'@kan/eslint-config':
specifier: workspace:*
version: link:../../tooling/eslint
'@kan/prettier-config':
specifier: workspace:*
version: link:../../tooling/prettier
'@kan/tsconfig':
specifier: workspace:*
version: link:../../tooling/typescript
eslint:
specifier: 'catalog:'
version: 9.34.0(jiti@2.6.1)
prettier:
specifier: 'catalog:'
version: 3.6.2
typescript:
specifier: 'catalog:'
version: 5.9.2
packages/shared:
dependencies:
'@aws-sdk/client-s3':
@@ -2976,6 +3019,9 @@ packages:
'@octokit/types@9.3.2':
resolution: {integrity: sha512-D4iHGTdAnEEVsB8fl95m1hiz7D5YiRdQ9b/OEb3BYRVwbLsGHcRVPz+u+BgRLNk0Q0/4iZCBqDN96j2XNxfXrA==}
'@pinojs/redact@0.4.0':
resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==}
'@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
@@ -4468,6 +4514,10 @@ packages:
asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
atomic-sleep@1.0.0:
resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
engines: {node: '>=8.0.0'}
atomically@2.1.0:
resolution: {integrity: sha512-+gDffFXRW6sl/HCwbta7zK4uNqbPjv4YJEAdz7Vu+FLQHe77eZ4bvbJGi4hE0QPeJlMYMA3piXEr1UL3dAwx7Q==}
@@ -4809,6 +4859,9 @@ packages:
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
engines: {node: '>=12.5.0'}
colorette@2.0.20:
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
colors@1.0.3:
resolution: {integrity: sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==}
engines: {node: '>=0.1.90'}
@@ -4964,6 +5017,9 @@ packages:
date-fns@4.1.0:
resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
dateformat@4.6.3:
resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==}
debounce-fn@6.0.0:
resolution: {integrity: sha512-rBMW+F2TXryBwB54Q0d8drNEI+TfoS9JpNTAoVpukbWEhjXQq4rySFYLaqXMFXwdv61Zb2OHtj5bviSoimqxRQ==}
engines: {node: '>=18'}
@@ -4989,15 +5045,6 @@ packages:
supports-color:
optional: true
debug@4.4.1:
resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
debug@4.4.3:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
engines: {node: '>=6.0'}
@@ -5270,6 +5317,9 @@ packages:
emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
end-of-stream@1.4.5:
resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
engine.io-parser@5.2.3:
resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==}
engines: {node: '>=10.0.0'}
@@ -5533,6 +5583,9 @@ packages:
resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==}
engines: {node: '>=4'}
fast-copy@4.0.2:
resolution: {integrity: sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==}
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
@@ -5546,6 +5599,9 @@ packages:
fast-levenshtein@2.0.6:
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
fast-safe-stringify@2.1.1:
resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
fast-uri@3.1.0:
resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}
@@ -5818,6 +5874,9 @@ packages:
header-case@1.0.1:
resolution: {integrity: sha512-i0q9mkOeSuhXw6bGgiQCCBgY/jlZuV/7dZXyZ9c6LcBrqwvT8eT719E9uxE5LiZftdl+z81Ugbg/VvXV4OJOeQ==}
help-me@5.0.0:
resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==}
hoist-non-react-statics@3.3.2:
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
@@ -6181,6 +6240,10 @@ packages:
jose@6.1.3:
resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==}
joycon@3.1.1:
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
engines: {node: '>=10'}
js-sha256@0.10.1:
resolution: {integrity: sha512-5obBtsz9301ULlsgggLg542s/jqtddfOpV5KJc4hajc9JV8GeY2gZHSVpYBn4nWqAUTJ9v+xwtbJ1mIBgIH5Vw==}
@@ -6719,10 +6782,6 @@ packages:
resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==}
hasBin: true
minimatch@10.0.3:
resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==}
engines: {node: 20 || >=22}
minimatch@10.1.1:
resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==}
engines: {node: 20 || >=22}
@@ -6952,6 +7011,10 @@ packages:
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
engines: {node: '>= 0.4'}
on-exit-leak-free@2.1.2:
resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
engines: {node: '>=14.0.0'}
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
@@ -7143,6 +7206,23 @@ packages:
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
engines: {node: '>=0.10.0'}
pino-abstract-transport@2.0.0:
resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==}
pino-abstract-transport@3.0.0:
resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==}
pino-pretty@13.1.3:
resolution: {integrity: sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==}
hasBin: true
pino-std-serializers@7.1.0:
resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==}
pino@9.14.0:
resolution: {integrity: sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==}
hasBin: true
pirates@4.0.7:
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
engines: {node: '>= 6'}
@@ -7314,6 +7394,9 @@ packages:
resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==}
engines: {node: '>=6'}
process-warning@5.0.0:
resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==}
prompts@2.4.2:
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
engines: {node: '>= 6'}
@@ -7394,6 +7477,9 @@ packages:
engines: {node: '>=16.0.0'}
hasBin: true
pump@3.0.4:
resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==}
punycode.js@2.3.1:
resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
engines: {node: '>=6'}
@@ -7409,6 +7495,9 @@ packages:
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
quick-format-unescaped@4.0.4:
resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
radix3@1.1.2:
resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==}
@@ -7523,6 +7612,10 @@ packages:
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
engines: {node: '>= 14.18.0'}
real-require@0.2.0:
resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
engines: {node: '>= 12.13.0'}
rechoir@0.6.2:
resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==}
engines: {node: '>= 0.10'}
@@ -7694,6 +7787,10 @@ packages:
resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
engines: {node: '>= 0.4'}
safe-stable-stringify@2.5.0:
resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
engines: {node: '>=10'}
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
@@ -7711,6 +7808,9 @@ packages:
resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==}
engines: {node: '>=4'}
secure-json-parse@4.1.0:
resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==}
selderee@0.11.0:
resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==}
@@ -7843,6 +7943,9 @@ packages:
resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==}
engines: {node: '>= 10.0.0', npm: '>= 3.0.0'}
sonic-boom@4.2.1:
resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==}
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
@@ -7966,6 +8069,10 @@ packages:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
strip-json-comments@5.0.3:
resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==}
engines: {node: '>=14.16'}
strip-literal@3.1.0:
resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==}
@@ -8098,6 +8205,9 @@ packages:
thenify@3.3.1:
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
thread-stream@3.1.0:
resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==}
through@2.3.8:
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
@@ -10483,7 +10593,7 @@ snapshots:
'@eslint/config-array@0.21.0':
dependencies:
'@eslint/object-schema': 2.1.6
debug: 4.4.1
debug: 4.4.3
minimatch: 3.1.2
transitivePeerDependencies:
- supports-color
@@ -10497,7 +10607,7 @@ snapshots:
'@eslint/eslintrc@3.3.1':
dependencies:
ajv: 6.12.6
debug: 4.4.1
debug: 4.4.3
espree: 10.4.0
globals: 14.0.0
ignore: 5.3.2
@@ -11000,7 +11110,7 @@ snapshots:
dependencies:
'@apidevtools/swagger-parser': 10.1.1(openapi-types@12.1.3)
'@mintlify/prebuild': 1.0.33(@apidevtools/swagger-parser@10.1.1(openapi-types@12.1.3))(@mintlify/models@0.0.29(openapi-types@12.1.3))(@mintlify/validation@0.1.63(@mintlify/models@0.0.29(openapi-types@12.1.3))(openapi-types@12.1.3))(fs-extra@11.3.1)(gray-matter@4.0.3)(openapi-types@12.1.3)(unist-util-visit@4.1.2)
chalk: 5.6.0
chalk: 5.6.2
fs-extra: 11.3.1
is-absolute-url: 4.0.1
openapi-types: 12.1.3
@@ -11037,7 +11147,7 @@ snapshots:
'@mintlify/validation': 0.1.63(@mintlify/models@0.0.29(openapi-types@12.1.3))(openapi-types@12.1.3)
'@octokit/rest': 19.0.13
axios: 1.11.0
chalk: 5.6.0
chalk: 5.6.2
chokidar: 3.6.0
fs-extra: 11.3.1
gray-matter: 4.0.3
@@ -11226,6 +11336,8 @@ snapshots:
dependencies:
'@octokit/openapi-types': 18.1.1
'@pinojs/redact@0.4.0': {}
'@pkgjs/parseargs@0.11.0':
optional: true
@@ -12684,13 +12796,13 @@ snapshots:
optionalDependencies:
vite: 7.3.1(@types/node@20.19.11)(jiti@1.21.7)(terser@5.44.1)(yaml@2.8.1)
'@vitest/mocker@3.2.4(vite@7.3.1(@types/node@25.0.0)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.1))':
'@vitest/mocker@3.2.4(vite@7.3.1(@types/node@25.0.0)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.1))':
dependencies:
'@vitest/spy': 3.2.4
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
vite: 7.3.1(@types/node@25.0.0)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.1)
vite: 7.3.1(@types/node@25.0.0)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.1)
'@vitest/pretty-format@3.2.4':
dependencies:
@@ -12977,6 +13089,8 @@ snapshots:
asynckit@0.4.0: {}
atomic-sleep@1.0.0: {}
atomically@2.1.0:
dependencies:
stubborn-fs: 2.0.0
@@ -13341,6 +13455,8 @@ snapshots:
color-convert: 2.0.1
color-string: 1.9.1
colorette@2.0.20: {}
colors@1.0.3: {}
combined-stream@1.0.8:
@@ -13488,6 +13604,8 @@ snapshots:
date-fns@4.1.0: {}
dateformat@4.6.3: {}
debounce-fn@6.0.0:
dependencies:
mimic-function: 5.0.1
@@ -13502,10 +13620,6 @@ snapshots:
dependencies:
ms: 2.1.3
debug@4.4.1:
dependencies:
ms: 2.1.3
debug@4.4.3:
dependencies:
ms: 2.1.3
@@ -13685,6 +13799,10 @@ snapshots:
emoji-regex@9.2.2: {}
end-of-stream@1.4.5:
dependencies:
once: 1.4.0
engine.io-parser@5.2.3: {}
engine.io@6.6.4:
@@ -14112,7 +14230,7 @@ snapshots:
ajv: 6.12.6
chalk: 4.1.2
cross-spawn: 7.0.6
debug: 4.4.1
debug: 4.4.3
escape-string-regexp: 4.0.0
eslint-scope: 8.4.0
eslint-visitor-keys: 4.2.1
@@ -14154,7 +14272,7 @@ snapshots:
ajv: 6.12.6
chalk: 4.1.2
cross-spawn: 7.0.6
debug: 4.4.1
debug: 4.4.3
escape-string-regexp: 4.0.0
eslint-scope: 8.4.0
eslint-visitor-keys: 4.2.1
@@ -14243,6 +14361,8 @@ snapshots:
iconv-lite: 0.4.24
tmp: 0.0.33
fast-copy@4.0.2: {}
fast-deep-equal@3.1.3: {}
fast-glob@3.3.3:
@@ -14257,6 +14377,8 @@ snapshots:
fast-levenshtein@2.0.6: {}
fast-safe-stringify@2.1.1: {}
fast-uri@3.1.0: {}
fast-xml-parser@5.2.5:
@@ -14448,7 +14570,7 @@ snapshots:
dependencies:
foreground-child: 3.3.1
jackspeak: 4.1.1
minimatch: 10.0.3
minimatch: 10.1.1
minipass: 7.1.2
package-json-from-dist: 1.0.1
path-scurry: 2.0.0
@@ -14581,6 +14703,8 @@ snapshots:
no-case: 2.3.2
upper-case: 1.1.3
help-me@5.0.0: {}
hoist-non-react-statics@3.3.2:
dependencies:
react-is: 16.13.1
@@ -14971,6 +15095,8 @@ snapshots:
jose@6.1.3: {}
joycon@3.1.1: {}
js-sha256@0.10.1: {}
js-tokens@4.0.0: {}
@@ -15099,7 +15225,7 @@ snapshots:
log-symbols@5.1.0:
dependencies:
chalk: 5.6.0
chalk: 5.6.2
is-unicode-supported: 1.3.0
log-symbols@6.0.0:
@@ -15854,10 +15980,6 @@ snapshots:
mini-svg-data-uri@1.4.4: {}
minimatch@10.0.3:
dependencies:
'@isaacs/brace-expansion': 5.0.0
minimatch@10.1.1:
dependencies:
'@isaacs/brace-expansion': 5.0.0
@@ -16084,6 +16206,8 @@ snapshots:
define-properties: 1.2.1
es-object-atoms: 1.1.1
on-exit-leak-free@2.1.2: {}
once@1.4.0:
dependencies:
wrappy: 1.0.2
@@ -16142,7 +16266,7 @@ snapshots:
ora@6.3.1:
dependencies:
chalk: 5.6.0
chalk: 5.6.2
cli-cursor: 4.0.0
cli-spinners: 2.9.2
is-interactive: 2.0.0
@@ -16321,6 +16445,46 @@ snapshots:
pify@2.3.0: {}
pino-abstract-transport@2.0.0:
dependencies:
split2: 4.2.0
pino-abstract-transport@3.0.0:
dependencies:
split2: 4.2.0
pino-pretty@13.1.3:
dependencies:
colorette: 2.0.20
dateformat: 4.6.3
fast-copy: 4.0.2
fast-safe-stringify: 2.1.1
help-me: 5.0.0
joycon: 3.1.1
minimist: 1.2.8
on-exit-leak-free: 2.1.2
pino-abstract-transport: 3.0.0
pump: 3.0.4
secure-json-parse: 4.1.0
sonic-boom: 4.2.1
strip-json-comments: 5.0.3
pino-std-serializers@7.1.0: {}
pino@9.14.0:
dependencies:
'@pinojs/redact': 0.4.0
atomic-sleep: 1.0.0
on-exit-leak-free: 2.1.2
pino-abstract-transport: 2.0.0
pino-std-serializers: 7.1.0
process-warning: 5.0.0
quick-format-unescaped: 4.0.4
real-require: 0.2.0
safe-stable-stringify: 2.5.0
sonic-boom: 4.2.1
thread-stream: 3.1.0
pirates@4.0.7: {}
pkg-types@2.3.0:
@@ -16428,6 +16592,8 @@ snapshots:
prismjs@1.30.0: {}
process-warning@5.0.0: {}
prompts@2.4.2:
dependencies:
kleur: 3.0.3
@@ -16563,6 +16729,11 @@ snapshots:
dependencies:
commander: 10.0.1
pump@3.0.4:
dependencies:
end-of-stream: 1.4.5
once: 1.4.0
punycode.js@2.3.1: {}
punycode@2.3.1: {}
@@ -16573,6 +16744,8 @@ snapshots:
queue-microtask@1.2.3: {}
quick-format-unescaped@4.0.4: {}
radix3@1.1.2: {}
raf-schd@4.0.3: {}
@@ -16726,6 +16899,8 @@ snapshots:
readdirp@4.1.2: {}
real-require@0.2.0: {}
rechoir@0.6.2:
dependencies:
resolve: 1.22.10
@@ -16979,6 +17154,8 @@ snapshots:
es-errors: 1.3.0
is-regex: 1.2.1
safe-stable-stringify@2.5.0: {}
safer-buffer@2.1.2: {}
sax@1.2.1: {}
@@ -16999,6 +17176,8 @@ snapshots:
extend-shallow: 2.0.1
kind-of: 6.0.3
secure-json-parse@4.1.0: {}
selderee@0.11.0:
dependencies:
parseley: 0.12.1
@@ -17212,6 +17391,10 @@ snapshots:
ip-address: 10.0.1
smart-buffer: 4.2.0
sonic-boom@4.2.1:
dependencies:
atomic-sleep: 1.0.0
source-map-js@1.2.1: {}
source-map-support@0.5.21:
@@ -17350,6 +17533,8 @@ snapshots:
strip-json-comments@3.1.1: {}
strip-json-comments@5.0.3: {}
strip-literal@3.1.0:
dependencies:
js-tokens: 9.0.1
@@ -17520,6 +17705,10 @@ snapshots:
dependencies:
any-promise: 1.3.0
thread-stream@3.1.0:
dependencies:
real-require: 0.2.0
through@2.3.8: {}
tiny-invariant@1.3.3: {}
@@ -17955,13 +18144,13 @@ snapshots:
- tsx
- yaml
vite-node@3.2.4(@types/node@25.0.0)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.1):
vite-node@3.2.4(@types/node@25.0.0)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.1):
dependencies:
cac: 6.7.14
debug: 4.4.3
es-module-lexer: 1.7.0
pathe: 2.0.3
vite: 7.3.1(@types/node@25.0.0)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.1)
vite: 7.3.1(@types/node@25.0.0)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.1)
transitivePeerDependencies:
- '@types/node'
- jiti
@@ -17991,7 +18180,7 @@ snapshots:
terser: 5.44.1
yaml: 2.8.1
vite@7.3.1(@types/node@25.0.0)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.1):
vite@7.3.1(@types/node@25.0.0)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.1):
dependencies:
esbuild: 0.27.2
fdir: 6.5.0(picomatch@4.0.3)
@@ -18002,7 +18191,7 @@ snapshots:
optionalDependencies:
'@types/node': 25.0.0
fsevents: 2.3.3
jiti: 2.4.2
jiti: 2.6.1
terser: 5.44.1
yaml: 2.8.1
@@ -18048,11 +18237,11 @@ snapshots:
- tsx
- yaml
vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.0.0)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.1):
vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.0.0)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.1):
dependencies:
'@types/chai': 5.2.3
'@vitest/expect': 3.2.4
'@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@25.0.0)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.1))
'@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@25.0.0)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.1))
'@vitest/pretty-format': 3.2.4
'@vitest/runner': 3.2.4
'@vitest/snapshot': 3.2.4
@@ -18070,8 +18259,8 @@ snapshots:
tinyglobby: 0.2.15
tinypool: 1.1.1
tinyrainbow: 2.0.0
vite: 7.3.1(@types/node@25.0.0)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.1)
vite-node: 3.2.4(@types/node@25.0.0)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.1)
vite: 7.3.1(@types/node@25.0.0)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.1)
vite-node: 3.2.4(@types/node@25.0.0)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.1)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/debug': 4.1.12

View File

@@ -129,7 +129,8 @@
"BETTER_AUTH_TRUSTED_ORIGINS",
"NOVU_API_KEY",
"EMAIL_UNSUBSCRIBE_SECRET",
"REDIS_URL"
"REDIS_URL",
"LOG_LEVEL"
],
"globalPassThroughEnv": [
"NODE_ENV",