diff --git a/.changeset/config.json b/.changeset/config.json index 479cbd0..6a096ec 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -14,9 +14,7 @@ "@embedly/builder", "@embedly/config", "@embedly/logging", - "@embedly/parser", - "@embedly/platforms", - "@embedly/types" + "@embedly/platforms" ] ], "linked": [], diff --git a/.changeset/hungry-wolves-drop.md b/.changeset/hungry-wolves-drop.md new file mode 100644 index 0000000..04d0d28 --- /dev/null +++ b/.changeset/hungry-wolves-drop.md @@ -0,0 +1,10 @@ +--- +"@embedly/platforms": minor +"@embedly/builder": minor +"@embedly/logging": minor +"@embedly/config": minor +"@embedly/api": minor +"@embedly/bot": minor +--- + +Harden platform error handling, add messageDelete listener for automatic embed cleanup, and remove unnecessary comments diff --git a/.changeset/nice-pans-know.md b/.changeset/nice-pans-know.md new file mode 100644 index 0000000..75a33f6 --- /dev/null +++ b/.changeset/nice-pans-know.md @@ -0,0 +1,10 @@ +--- +"@embedly/platforms": minor +"@embedly/builder": minor +"@embedly/logging": minor +"@embedly/config": minor +"@embedly/api": minor +"@embedly/bot": minor +--- + +refactored a bunch of the code surrounding types and parsing diff --git a/.changeset/rare-moles-beg.md b/.changeset/rare-moles-beg.md new file mode 100644 index 0000000..b56c41d --- /dev/null +++ b/.changeset/rare-moles-beg.md @@ -0,0 +1,5 @@ +--- +"@embedly/builder": patch +--- + +limit descriptions to 1500 because discord has a global cap of 4k per message diff --git a/.changeset/six-singers-jog.md b/.changeset/six-singers-jog.md new file mode 100644 index 0000000..24dc567 --- /dev/null +++ b/.changeset/six-singers-jog.md @@ -0,0 +1,8 @@ +--- +"@embedly/platforms": minor +"@embedly/logging": minor +"@embedly/api": minor +"@embedly/bot": minor +--- + +Replace BetterStack (Logtail) with OTEL-native logging for LGTM stack. Logs now flow through OpenTelemetry to Loki alongside traces and metrics. Added EmbedlyLogger class, platform and source labels for Grafana indexing, and trace correlation for the API. diff --git a/.changeset/wicked-cobras-like.md b/.changeset/wicked-cobras-like.md new file mode 100644 index 0000000..d2f3ff7 --- /dev/null +++ b/.changeset/wicked-cobras-like.md @@ -0,0 +1,6 @@ +--- +"@embedly/api": patch +"@embedly/bot": patch +--- + +added request tracing via otel diff --git a/.gitignore b/.gitignore index 55e77a0..ecf9e98 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,5 @@ yarn.lock .env AGENTS.md +.claude +.serena diff --git a/apps/api/package.json b/apps/api/package.json index 72689d7..ece0a8a 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -24,18 +24,16 @@ "@discordjs/rest": "^2.6.0", "@embedly/builder": "workspace:*", "@embedly/logging": "workspace:*", - "@embedly/parser": "workspace:*", "@embedly/platforms": "workspace:*", - "@embedly/types": "workspace:*", - "@logtail/edge": "^0.5.7", - "@logtail/node": "^0.5.6", "discord-verify": "^1.2.0", "elysia": "^1.4.19", - "wrangler": "^4.56.0" + "wrangler": "^4.59.1" }, "devDependencies": { "@biomejs/biome": "2.3.10", "@embedly/config": "workspace:*", + "@microlabs/otel-cf-workers": "1.0.0-rc.52", + "@opentelemetry/api": "^1.9.0", "@types/node": "^25.0.3", "discord-api-types": "^0.38.37", "pkgroll": "^2.21.4", diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index 2b3f073..4cb36b6 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -4,16 +4,25 @@ import { EMBEDLY_NO_LINK_IN_MESSAGE, EMBEDLY_NO_VALID_LINK, type EmbedlyPostContext, - formatBetterStack + type FormattedLog, + formatLog } from "@embedly/logging"; -import { +import Platforms, { + EmbedlyPlatformType, GENERIC_LINK_REGEX, getPlatformFromURL, hasLink -} from "@embedly/parser"; -import Platforms from "@embedly/platforms"; -import { EmbedlyPlatformType } from "@embedly/types"; -import { Logtail } from "@logtail/edge"; +} from "@embedly/platforms"; +import { + instrument, + type ResolveConfigFn +} from "@microlabs/otel-cf-workers"; +import { + context, + propagation, + SpanStatusCode, + trace +} from "@opentelemetry/api"; import { Elysia, t } from "elysia"; const app = (env: Env, ctx: ExecutionContext) => @@ -24,13 +33,121 @@ const app = (env: Env, ctx: ExecutionContext) => }) .decorate({ env, ctx }) .derive(({ ctx, env }) => { - const logtail = new Logtail(env.BETTERSTACK_SOURCE_TOKEN, { - endpoint: env.BETTERSTACK_INGESTING_HOST - }); + const pending: { + severityText: string; + severityNumber: number; + log: FormattedLog; + timestamp: number; + traceId: string; + spanId: string; + }[] = []; + + const emit = ( + severityText: string, + severityNumber: number, + log: FormattedLog + ) => { + const method = severityText.toLowerCase() as + | "debug" + | "info" + | "warn" + | "error"; + console[method]?.(log.body, log.attributes); + const span = trace.getActiveSpan(); + const spanCtx = span?.spanContext(); + pending.push({ + severityText, + severityNumber, + log, + timestamp: Date.now(), + traceId: spanCtx?.traceId ?? "", + spanId: spanCtx?.spanId ?? "" + }); + }; + + const flush = () => { + if (pending.length === 0) return; + const body = { + resourceLogs: [ + { + resource: { + attributes: [ + { + key: "service.name", + value: { stringValue: "embedly-api" } + } + ] + }, + scopeLogs: [ + { + scope: { name: "embedly-api" }, + logRecords: pending.map( + ({ + severityText, + severityNumber, + log, + timestamp, + traceId, + spanId + }) => ({ + timeUnixNano: String(timestamp * 1_000_000), + severityNumber, + traceId, + spanId, + severityText, + body: { stringValue: log.body }, + attributes: Object.entries(log.attributes) + .filter(([, v]) => v !== undefined) + .map(([key, value]) => ({ + key, + value: + typeof value === "number" + ? { intValue: value } + : typeof value === "boolean" + ? { boolValue: value } + : { stringValue: String(value) } + })) + }) + ) + } + ] + } + ] + }; + + ctx.waitUntil( + fetch( + `${env.OTEL_ENDPOINT.replace("/v1/traces", "")}/v1/logs`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body) + } + ).catch(() => {}) + ); + }; + return { - logger: logtail.withExecutionContext(ctx) + logger: { + debug(log: FormattedLog) { + emit("DEBUG", 5, log); + }, + info(log: FormattedLog) { + emit("INFO", 9, log); + }, + warn(log: FormattedLog) { + emit("WARN", 13, log); + }, + error(log: FormattedLog) { + emit("ERROR", 17, log); + }, + flush + } }; }) + .onAfterHandle(({ logger }) => { + logger.flush(); + }) .guard({ headers: t.Object( { @@ -55,61 +172,160 @@ const app = (env: Env, ctx: ExecutionContext) => }) .post( "/api/scrape", - async ({ body: { platform, url }, status, logger, set }) => { - const handler = Platforms[platform as keyof typeof Platforms]; - const post_id = await handler.parsePostId(url); - const post_log_ctx: EmbedlyPostContext = { - platform: handler.name, - post_url: url, - post_id - }; - let post_data = await handler.getPostFromCache( - post_id, - env.STORAGE + async ({ + body: { platform, url }, + status, + logger, + set, + headers + }) => { + const carrier: Record = {}; + Object.values(headers).forEach((value, key) => { + carrier[key] = value; + }); + + const parent_ctx = propagation.extract( + context.active(), + carrier ); - if (!post_data) { - logger.debug( - ...formatBetterStack( - handler.log_messages.fetching, - post_log_ctx - ) - ); - - try { - post_data = await handler.fetchPost(post_id, env); - } catch (error: any) { - post_log_ctx.resp_status = error.code; - post_log_ctx.resp_message = error.message; - - const err = handler.log_messages.failed; - err.context = post_log_ctx; - - logger.error(...formatBetterStack(err, err.context)); - - return status(err.status!, err); - } + const tracer = trace.getTracer("embedly-api"); + return await context.with(parent_ctx, async () => { + return tracer.startActiveSpan("scrape", async (root_span) => { + const handler = + Platforms[platform as keyof typeof Platforms]; - if (!post_data) { - const err = handler.log_messages.failed; - return status(err.status!, err); - } + root_span.setAttributes({ + "embedly.platform": platform, + "embedly.url": url + }); - if (handler.name === EmbedlyPlatformType.Instagram) { - post_data!.url = url; - } + let post_id: string; + try { + post_id = await tracer.startActiveSpan( + "parse_post_id", + async (s) => { + const id = await handler.parsePostId(url); + s.setAttribute("embedly.post_id", id); + s.end(); + return id; + } + ); + } catch (error: any) { + root_span.setStatus({ + code: SpanStatusCode.ERROR, + message: error.message + }); + root_span.recordException(error); + root_span.end(); + return status(400, { + type: "EMBEDLY_INVALID_URL", + title: "Invalid URL.", + detail: `Failed to parse post ID from URL: ${url}` + }); + } - handler.addPostToCache(post_id, post_data, env.STORAGE); - logger.debug( - ...formatBetterStack(EMBEDLY_CACHING_POST, post_log_ctx) - ); - } else { - logger.debug( - ...formatBetterStack(EMBEDLY_CACHED_POST, post_log_ctx) - ); - } + root_span.setAttribute("embedly.post_id", post_id); + + const post_log_ctx: EmbedlyPostContext = { + platform: handler.name, + post_url: url, + post_id + }; + let post_data = await tracer.startActiveSpan( + "cache_lookup", + async (s) => { + const res = await handler.getPostFromCache( + post_id, + env.STORAGE + ); + s.setAttribute("embedly.cache_hit", !!res); + s.end(); + return res; + } + ); + + if (!post_data) { + logger.debug( + formatLog(handler.log_messages.fetching, post_log_ctx) + ); + + try { + post_data = await tracer.startActiveSpan( + "fetch_post", + async (s) => { + s.setAttribute("embedly.platform", handler.name); + try { + const data = await handler.fetchPost( + post_id, + env + ); + s.setStatus({ code: SpanStatusCode.OK }); + return data; + } catch (error: any) { + s.setStatus({ + code: SpanStatusCode.ERROR, + message: error.message + }); + s.recordException(error); + throw error; + } finally { + s.end(); + } + } + ); + } catch (error: any) { + post_log_ctx.resp_status = error.code; + post_log_ctx.resp_message = error.message; + + const err = handler.log_messages.failed; + err.context = post_log_ctx; + + logger.error(formatLog(err, err.context)); + + root_span.setStatus({ + code: SpanStatusCode.ERROR, + message: error.message + }); + root_span.end(); + return status(err.status!, err); + } - set.headers["content-type"] = "application/json"; - return post_data as Record; + if (!post_data) { + const err = handler.log_messages.failed; + + logger.error(formatLog(err, post_log_ctx)); + + root_span.setStatus({ + code: SpanStatusCode.ERROR, + message: err.detail + }); + root_span.end(); + return status(err.status!, err); + } + + if (handler.name === EmbedlyPlatformType.Instagram) { + post_data!.url = url; + } + + await tracer.startActiveSpan("cache_store", async (s) => { + handler.addPostToCache(post_id, post_data, env.STORAGE); + logger.debug( + formatLog(EMBEDLY_CACHING_POST, post_log_ctx) + ); + s.end(); + }); + } else { + logger.debug( + formatLog(EMBEDLY_CACHED_POST, post_log_ctx) + ); + } + + set.headers["content-type"] = "application/json"; + root_span.setStatus({ code: SpanStatusCode.OK }); + root_span.end(); + return post_data as Record; + }); + }); }, { body: t.Object({ @@ -179,10 +395,22 @@ const app = (env: Env, ctx: ExecutionContext) => } ); -export default { - fetch(req, env, ctx) { +const handler = { + fetch(req: Request, env: Env, ctx: ExecutionContext) { return app(env, ctx).fetch(req); } } satisfies ExportedHandler; +const config: ResolveConfigFn = (env: Env) => ({ + exporter: { + url: env.OTEL_ENDPOINT, + headers: {} + }, + service: { + name: "embedly-api" + } +}); + +export default instrument(handler, config); + export type App = ReturnType; diff --git a/apps/api/worker-configuration.d.ts b/apps/api/worker-configuration.d.ts index b0cdaf1..76e6576 100644 --- a/apps/api/worker-configuration.d.ts +++ b/apps/api/worker-configuration.d.ts @@ -1,6 +1,6 @@ /* eslint-disable */ -// Generated by Wrangler by running `wrangler types` (hash: 37f49ce2d8d1275f19bca443420efd84) -// Runtime types generated with workerd@1.20251217.0 2025-06-23 nodejs_compat +// Generated by Wrangler by running `wrangler types` (hash: 44f998e2f4bea2fa3bbe96fe8c8510a9) +// Runtime types generated with workerd@1.20260111.0 2025-06-23 nodejs_compat declare namespace Cloudflare { interface GlobalProps { mainModule: typeof import("./src/main"); @@ -14,6 +14,7 @@ declare namespace Cloudflare { EMBED_USER_AGENT: string; BETTERSTACK_SOURCE_TOKEN: string; BETTERSTACK_INGESTING_HOST: string; + OTEL_ENDPOINT: string; } } interface Env extends Cloudflare.Env {} @@ -21,7 +22,7 @@ type StringifyValues> = { [Binding in keyof EnvType]: EnvType[Binding] extends string ? EnvType[Binding] : string; }; declare namespace NodeJS { - interface ProcessEnv extends StringifyValues> {} + interface ProcessEnv extends StringifyValues> {} } // Begin runtime types @@ -507,8 +508,10 @@ interface DurableObjectNamespaceNewUniqueIdOptions { jurisdiction?: DurableObjectJurisdiction; } type DurableObjectLocationHint = "wnam" | "enam" | "sam" | "weur" | "eeur" | "apac" | "oc" | "afr" | "me"; +type DurableObjectRoutingMode = "primary-only"; interface DurableObjectNamespaceGetDurableObjectOptions { locationHint?: DurableObjectLocationHint; + routingMode?: DurableObjectRoutingMode; } interface DurableObjectClass<_T extends Rpc.DurableObjectBranded | undefined = undefined> { } @@ -2092,6 +2095,8 @@ interface Transformer { expectedLength?: number; } interface StreamPipeOptions { + preventAbort?: boolean; + preventCancel?: boolean; /** * Pipes this readable stream to a given writable stream destination. The way in which the piping process behaves under various error conditions can be customized with a number of passed options. It returns a promise that fulfills when the piping process completes successfully, or rejects if any errors were encountered. * @@ -2110,8 +2115,6 @@ interface StreamPipeOptions { * The signal option can be set to an AbortSignal to allow aborting an ongoing pipe operation via the corresponding AbortController. In this case, this source readable stream will be canceled, and destination aborted, unless the respective options preventCancel or preventAbort are set. */ preventClose?: boolean; - preventAbort?: boolean; - preventCancel?: boolean; signal?: AbortSignal; } type ReadableStreamReadResult = { @@ -2386,13 +2389,13 @@ declare abstract class TransformStreamDefaultController { terminate(): void; } interface ReadableWritablePair { + readable: ReadableStream; /** * Provides a convenient, chainable way of piping this readable stream through a transform stream (or any other { writable, readable } pair). It simply pipes the stream into the writable side of the supplied pair, and returns the readable side for further use. * * Piping a stream will lock it for the duration of the pipe, preventing any other consumer from acquiring a reader. */ writable: WritableStream; - readable: ReadableStream; } /** * The **`WritableStream`** interface of the Streams API provides a standard abstraction for writing streaming data to a destination, known as a sink. diff --git a/apps/bot/package.json b/apps/bot/package.json index ba7a728..54559c1 100644 --- a/apps/bot/package.json +++ b/apps/bot/package.json @@ -13,7 +13,7 @@ } }, "scripts": { - "dev": "tsx watch --env-file=.env src/main.ts", + "dev": "tsx watch --env-file=.env --import ./src/otel.ts src/main.ts", "lint": "biome ci ./src/*", "build": "pkgroll --sourcemap=inline && tsc" }, @@ -23,8 +23,20 @@ "@embedly/builder": "workspace:*", "@embedly/config": "workspace:*", "@embedly/logging": "workspace:*", - "@embedly/parser": "workspace:*", "@embedly/platforms": "workspace:*", + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/api-logs": "^0.211.0", + "@opentelemetry/exporter-logs-otlp-http": "^0.211.0", + "@opentelemetry/exporter-metrics-otlp-grpc": "^0.211.0", + "@opentelemetry/exporter-metrics-otlp-http": "^0.211.0", + "@opentelemetry/exporter-trace-otlp-grpc": "^0.211.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.211.0", + "@opentelemetry/instrumentation-http": "^0.211.0", + "@opentelemetry/resources": "^2.5.0", + "@opentelemetry/sdk-logs": "^0.211.0", + "@opentelemetry/sdk-metrics": "^2.5.0", + "@opentelemetry/sdk-node": "^0.211.0", + "@opentelemetry/semantic-conventions": "^1.39.0", "@types/node": "^25.0.3", "discord-api-types": "^0.38.37", "pkgroll": "^2.21.4", @@ -36,7 +48,6 @@ "@discordjs/rest": "^2.6.0", "@discordjs/ws": "^2.0.4", "@elysiajs/eden": "^1.4.5", - "@logtail/node": "^0.5.6", "@sapphire/discord.js-utilities": "^7.3.3", "@sapphire/framework": "^5.4.0", "discord.js": "~14.25.1", diff --git a/apps/bot/src/client.ts b/apps/bot/src/client.ts index f089c34..765a77f 100644 --- a/apps/bot/src/client.ts +++ b/apps/bot/src/client.ts @@ -1,17 +1,48 @@ -import { Logtail } from "@logtail/node"; -import { container, SapphireClient } from "@sapphire/framework"; +import { EmbedlyLogger } from "@embedly/logging"; +import { type Tracer, trace } from "@opentelemetry/api"; +import { logs } from "@opentelemetry/api-logs"; +import { + container, + type ILogger, + LogLevel, + SapphireClient +} from "@sapphire/framework"; import { ActivityType, GatewayIntentBits, + Partials, PresenceUpdateStatus } from "discord.js"; import { PostHog } from "posthog-node"; +class SapphireLogger extends EmbedlyLogger implements ILogger { + has(level: LogLevel): boolean { + return level >= LogLevel.Info; + } + + trace() {} + + debug() {} + + write(level: LogLevel, ...values: readonly unknown[]) { + if (level >= LogLevel.Info) { + const methods = { + [LogLevel.Info]: this.info, + [LogLevel.Warn]: this.warn, + [LogLevel.Error]: this.error, + [LogLevel.Fatal]: this.fatal + } as Record void>; + methods[level]?.call(this, ...values); + } + } +} + declare module "@sapphire/framework" { interface Container { - betterstack: Logtail; embed_authors: Map; + embed_messages: Map; posthog: PostHog; + tracer: Tracer; } } @@ -23,6 +54,7 @@ export class EmbedlyClient extends SapphireClient { GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent ], + partials: [Partials.Message], presence: { activities: [ { @@ -38,21 +70,21 @@ export class EmbedlyClient extends SapphireClient { } public override async login(token?: string) { - container.betterstack = new Logtail( - process.env.BETTERSTACK_SOURCE_TOKEN!, - { - endpoint: process.env.BETTERSTACK_INGESTING_HOST - } - ); + container.logger = new SapphireLogger("embedly-bot"); container.embed_authors = new Map(); + container.embed_messages = new Map(); container.posthog = new PostHog(process.env.POSTHOG_API_KEY!, { host: process.env.POSTHOG_HOST }); + container.tracer = trace.getTracer("embedly-bot"); return super.login(token); } public override async destroy() { - await container.betterstack?.flush(); + const provider = logs.getLoggerProvider() as { + forceFlush?: () => Promise; + }; + await provider.forceFlush?.(); await container.posthog.shutdown(); return super.destroy(); } diff --git a/apps/bot/src/commands/delete.ts b/apps/bot/src/commands/delete.ts index 9282200..76c395c 100644 --- a/apps/bot/src/commands/delete.ts +++ b/apps/bot/src/commands/delete.ts @@ -3,13 +3,15 @@ import { EMBEDLY_DELETE_FAILED_WARN, EMBEDLY_DELETE_SUCCESS, EMBEDLY_DELETE_SUCCESS_INFO, - formatBetterStack, - formatDiscord + formatDiscord, + formatLog } from "@embedly/logging"; import { Command } from "@sapphire/framework"; import { ApplicationCommandType, ApplicationIntegrationType, + type Guild, + type GuildMember, InteractionContextType, MessageFlags, PermissionFlagsBits @@ -52,8 +54,8 @@ export class DeleteCommand extends Command { if (!interaction.inGuild()) return; const msg = interaction.targetMessage; if (msg.author.id !== this.container.client.id) { - this.container.betterstack.warn( - ...formatBetterStack(EMBEDLY_DELETE_FAILED_WARN, { + this.container.logger.warn( + formatLog(EMBEDLY_DELETE_FAILED_WARN, { message_id: msg.id, user_id: interaction.user.id, reason: "not_bot_message" @@ -72,8 +74,8 @@ export class DeleteCommand extends Command { }); if (!msg.deletable) { - this.container.betterstack.warn( - ...formatBetterStack(EMBEDLY_DELETE_FAILED_WARN, { + this.container.logger.warn( + formatLog(EMBEDLY_DELETE_FAILED_WARN, { message_id: msg.id, user_id: interaction.user.id, reason: "not_deletable" @@ -93,8 +95,8 @@ export class DeleteCommand extends Command { const reference = await msg.fetchReference(); original_author_id = reference.author.id; } catch { - this.container.betterstack.warn( - ...formatBetterStack(EMBEDLY_DELETE_FAILED_WARN, { + this.container.logger.warn( + formatLog(EMBEDLY_DELETE_FAILED_WARN, { message_id: msg.id, user_id: interaction.user.id, reason: "no_author_mapping_and_no_reference" @@ -108,10 +110,25 @@ export class DeleteCommand extends Command { } } - const guild = await interaction.guild!.fetch(); - const runner = await guild.members.fetch( - interaction.member.user.id - ); + let guild: Guild; + let runner: GuildMember; + try { + guild = await interaction.guild!.fetch(); + runner = await guild.members.fetch(interaction.member.user.id); + } catch { + this.container.logger.warn( + formatLog(EMBEDLY_DELETE_FAILED_WARN, { + message_id: msg.id, + user_id: interaction.user.id, + reason: "failed_to_fetch_guild_or_member" + }) + ); + return await interaction.editReply({ + content: formatDiscord(EMBEDLY_DELETE_FAILED, { + message_id: msg.id + }) + }); + } const has_manage_permission = runner.permissions.has( PermissionFlagsBits.ManageMessages, @@ -120,8 +137,8 @@ export class DeleteCommand extends Command { const is_original_poster = runner.id === original_author_id; if (!has_manage_permission && !is_original_poster) { - this.container.betterstack.warn( - ...formatBetterStack(EMBEDLY_DELETE_FAILED_WARN, { + this.container.logger.warn( + formatLog(EMBEDLY_DELETE_FAILED_WARN, { message_id: msg.id, user_id: interaction.user.id, original_author_id, @@ -138,8 +155,19 @@ export class DeleteCommand extends Command { await msg.delete(); this.container.embed_authors.delete(msg.id); - this.container.betterstack.info( - ...formatBetterStack(EMBEDLY_DELETE_SUCCESS_INFO, { + for (const [user_msg_id, bot_ids] of this.container + .embed_messages) { + const filtered = bot_ids.filter((id) => id !== msg.id); + if (filtered.length === bot_ids.length) continue; + if (filtered.length === 0) { + this.container.embed_messages.delete(user_msg_id); + } else { + this.container.embed_messages.set(user_msg_id, filtered); + } + break; + } + this.container.logger.info( + formatLog(EMBEDLY_DELETE_SUCCESS_INFO, { message_id: msg.id, user_id: interaction.user.id, original_author_id diff --git a/apps/bot/src/commands/embed.ts b/apps/bot/src/commands/embed.ts index e36f651..9dddf15 100644 --- a/apps/bot/src/commands/embed.ts +++ b/apps/bot/src/commands/embed.ts @@ -12,15 +12,20 @@ import { EMBEDLY_NO_VALID_LINK, EMBEDLY_NO_VALID_LINK_WARN, type EmbedlyInteractionContext, - formatBetterStack, - formatDiscord + type EmbedlySource, + formatDiscord, + formatLog } from "@embedly/logging"; -import { +import Platforms, { GENERIC_LINK_REGEX, getPlatformFromURL, hasLink -} from "@embedly/parser"; -import Platforms from "@embedly/platforms"; +} from "@embedly/platforms"; +import { + context, + propagation, + SpanStatusCode +} from "@opentelemetry/api"; import { Command } from "@sapphire/framework"; import { ApplicationCommandType, @@ -103,15 +108,17 @@ export class EmbedCommand extends Command { | Command.ChatInputCommandInteraction | Command.ContextMenuCommandInteraction, content: string, + source: EmbedlySource, flags?: Partial ) { const log_ctx = { interaction_id: interaction.id, - user_id: interaction.user.id + user_id: interaction.user.id, + source } satisfies EmbedlyInteractionContext; if (!hasLink(content)) { - this.container.betterstack.warn( - ...formatBetterStack(EMBEDLY_NO_LINK_WARN, log_ctx) + this.container.logger.warn( + formatLog(EMBEDLY_NO_LINK_WARN, log_ctx) ); return await interaction.reply({ content: formatDiscord(EMBEDLY_NO_LINK_IN_MESSAGE, log_ctx), @@ -119,10 +126,19 @@ export class EmbedCommand extends Command { }); } const url = GENERIC_LINK_REGEX.exec(content)![0]; - const platform = getPlatformFromURL(url); + const platform = await this.container.tracer.startActiveSpan( + "detect_platform", + async (s) => { + const platform = getPlatformFromURL(url); + s.setAttribute("embedly.platform", platform?.type ?? "unknown"); + s.setAttribute("embedly.url", url); + s.end(); + return platform; + } + ); if (!platform) { - this.container.betterstack.warn( - ...formatBetterStack(EMBEDLY_NO_VALID_LINK_WARN, log_ctx) + this.container.logger.warn( + formatLog(EMBEDLY_NO_VALID_LINK_WARN, log_ctx) ); return await interaction.reply({ content: formatDiscord(EMBEDLY_NO_VALID_LINK, log_ctx), @@ -132,50 +148,98 @@ export class EmbedCommand extends Command { await interaction.deferReply(); - const { data, error } = await app.api.scrape.post( - { - platform: platform.type, - url - }, - { - headers: { - authorization: `Bearer ${process.env.DISCORD_BOT_TOKEN}` + const { data, error } = await this.container.tracer.startActiveSpan( + "fetch_from_api", + async (s) => { + s.setAttribute("embedly.platform", platform.type); + s.setAttribute("embedly.url", url); + + const otelHeaders: Record = {}; + propagation.inject(context.active(), otelHeaders); + + const res = await app.api.scrape.post( + { + platform: platform.type, + url + }, + { + headers: { + authorization: `Bearer ${process.env.DISCORD_BOT_TOKEN}`, + ...otelHeaders + } + } + ); + if (res.error) { + s.setStatus({ + code: SpanStatusCode.ERROR, + message: + "detail" in res.error.value + ? res.error.value.detail + : res.error.value.type + }); + s.recordException( + "detail" in res.error.value + ? res.error.value.detail + : res.error.value.type + ); } + s.end(); + return res; } ); if (error?.status === 400 || error?.status === 500) { const error_context = { ...log_ctx, - ...error.value.context! + platform: platform.type, + ...("context" in error.value ? error.value.context : {}) }; - this.container.betterstack.error( - ...formatBetterStack(error.value, error_context) + this.container.logger.error( + formatLog(error.value, error_context) ); return await interaction.editReply({ content: formatDiscord(error.value, error_context) }); } - const embed = await Platforms[platform.type].createEmbed(data); - const bot_message = await interaction.editReply({ - components: [Embed.getDiscordEmbed(embed, flags)!], - flags: ["IsComponentsV2"], - allowedMentions: { - parse: [], - repliedUser: false + const embed = await this.container.tracer.startActiveSpan( + "create_embed", + async (s) => { + s.setAttribute("embedly.platform", platform.type); + const embed = await Platforms[platform.type].createEmbed(data); + s.end(); + return embed; } - }); + ); + + const bot_message = await this.container.tracer.startActiveSpan( + "send_message", + async (s) => { + const res = await interaction.editReply({ + components: [Embed.getDiscordEmbed(embed, flags)!], + flags: ["IsComponentsV2"], + allowedMentions: { + parse: [], + repliedUser: false + } + }); + s.setAttribute("discord.bot_message_id", res.id); + s.end(); + return res; + } + ); + this.container.embed_authors.set( bot_message.id, interaction.user.id ); - this.container.betterstack.info( - ...formatBetterStack(EMBEDLY_EMBED_CREATED_COMMAND, { + this.container.logger.info( + formatLog(EMBEDLY_EMBED_CREATED_COMMAND, { interaction_id: interaction.id, user_id: interaction.user.id, bot_message_id: bot_message.id, platform: platform.type, + source, url }) ); @@ -187,25 +251,84 @@ export class EmbedCommand extends Command { ) { if (!interaction.isMessageContextMenuCommand()) return; const msg = interaction.targetMessage; - this.fetchEmbed(interaction, msg.content); + this.container.tracer.startActiveSpan( + "context_menu", + async (root_span) => { + root_span.setAttributes({ + "discord.interaction_id": interaction.id, + "discord.command": interaction.commandName, + "discord.guild_id": interaction.guildId ?? "dm", + "discord.user_id": interaction.user.id, + "discord.message_id": msg.id + }); + try { + await this.fetchEmbed( + interaction, + msg.content, + "context_menu" + ); + root_span.setStatus({ code: SpanStatusCode.OK }); + } catch (error: any) { + root_span.setStatus({ + code: SpanStatusCode.ERROR, + message: error.message + }); + root_span.recordException(error); + } finally { + root_span.end(); + } + } + ); } public override async chatInputRun( interaction: Command.ChatInputCommandInteraction ) { const url = interaction.options.getString("url", true); - const link_style = (await this.container.posthog.getFeatureFlag( - "embed-link-styling-test", - interaction.user.id - )) as EmbedFlags[EmbedFlagNames.LinkStyle] | undefined; - this.fetchEmbed(interaction, url, { - [EmbedFlagNames.MediaOnly]: - interaction.options.getBoolean("media_only") ?? false, - [EmbedFlagNames.SourceOnly]: - interaction.options.getBoolean("source_only") ?? false, - [EmbedFlagNames.Spoiler]: - interaction.options.getBoolean("spoiler") ?? false, - [EmbedFlagNames.LinkStyle]: link_style ?? "control" - }); + + this.container.tracer.startActiveSpan( + "/embed", + async (root_span) => { + root_span.setAttributes({ + "discord.interaction_id": interaction.id, + "discord.command": interaction.commandName, + "discord.guild_id": interaction.guildId ?? "dm", + "discord.user_id": interaction.user.id + }); + + let link_style: + | EmbedFlags[EmbedFlagNames.LinkStyle] + | undefined; + try { + link_style = (await this.container.posthog.getFeatureFlag( + "embed-link-styling-test", + interaction.user.id + )) as EmbedFlags[EmbedFlagNames.LinkStyle] | undefined; + } catch { + link_style = undefined; + } + + try { + await this.fetchEmbed(interaction, url, "command", { + [EmbedFlagNames.MediaOnly]: + interaction.options.getBoolean("media_only") ?? false, + [EmbedFlagNames.SourceOnly]: + interaction.options.getBoolean("source_only") ?? false, + [EmbedFlagNames.Spoiler]: + interaction.options.getBoolean("spoiler") ?? false, + [EmbedFlagNames.LinkStyle]: link_style ?? "control" + }); + root_span.setStatus({ code: SpanStatusCode.OK }); + } catch (error: any) { + root_span.setStatus({ + code: SpanStatusCode.ERROR, + message: error.message + }); + root_span.recordException(error); + } finally { + root_span.end(); + } + } + ); } } diff --git a/apps/bot/src/listeners/messageCreate.ts b/apps/bot/src/listeners/messageCreate.ts index 218a28e..b5ba8a9 100644 --- a/apps/bot/src/listeners/messageCreate.ts +++ b/apps/bot/src/listeners/messageCreate.ts @@ -9,16 +9,20 @@ import { EMBEDLY_EMBED_CREATED_MESSAGE, type EmbedlyInteractionContext, type EmbedlyPostContext, - formatBetterStack + formatLog } from "@embedly/logging"; -import { +import Platforms, { GENERIC_LINK_REGEX, getPlatformFromURL, hasLink, isEscaped, isSpoiler -} from "@embedly/parser"; -import Platforms from "@embedly/platforms"; +} from "@embedly/platforms"; +import { + context, + propagation, + SpanStatusCode +} from "@opentelemetry/api"; import { Events, Listener } from "@sapphire/framework"; import { type Message, MessageFlags } from "discord.js"; @@ -43,78 +47,183 @@ export class MessageListener extends Listener< if (message.author.bot) return; if (message.author.id === this.container.client.id) return; if (!hasLink(message.content)) return; - const urls = message.content.match( new RegExp(GENERIC_LINK_REGEX, "g") ); if (!urls) return; - for (const [ind, url] of urls.entries()) { - if (isEscaped(url, message.content)) return; - const platform = getPlatformFromURL(url); - if (!platform) return; - const { data, error } = await app.api.scrape.post( - { - platform: platform.type, - url - }, - { - headers: { - authorization: `Bearer ${process.env.DISCORD_BOT_TOKEN}` + this.container.tracer.startActiveSpan( + "message", + async (root_span) => { + root_span.setAttributes({ + "discord.message_id": message.id, + "discord.guild_id": message.guildId ?? "dm", + "discord.channel_id": message.channelId, + "discord.user_id": message.author.id + }); + + try { + for (const [ind, url] of urls.entries()) { + if (isEscaped(url, message.content)) continue; + const platform = + await this.container.tracer.startActiveSpan( + "detect_platform", + async (s) => { + const platform = getPlatformFromURL(url); + s.setAttribute( + "embedly.platform", + platform?.type ?? "unknown" + ); + s.setAttribute("embedly.url", url); + s.end(); + return platform; + } + ); + if (!platform) continue; + + const { data, error } = + await this.container.tracer.startActiveSpan( + "fetch_from_api", + async (s) => { + s.setAttribute("embedly.platform", platform.type); + s.setAttribute("embedly.url", url); + + const otelHeaders: Record = {}; + propagation.inject(context.active(), otelHeaders); + + const res = await app.api.scrape.post( + { + platform: platform.type, + url + }, + { + headers: { + authorization: `Bearer ${process.env.DISCORD_BOT_TOKEN}`, + ...otelHeaders + } + } + ); + if (res.error) { + s.setStatus({ + code: SpanStatusCode.ERROR, + message: + "detail" in res.error.value + ? res.error.value.detail + : res.error.value.type + }); + s.recordException( + "detail" in res.error.value + ? res.error.value.detail + : res.error.value.type + ); + } + s.end(); + return res; + } + ); + + if (error) { + if ("detail" in error.value) { + const error_context: EmbedlyInteractionContext & + EmbedlyPostContext = { + ...("context" in error.value + ? error.value.context + : {}), + message_id: message.id, + user_id: message.author.id, + source: "message", + platform: platform.type + }; + this.container.logger.error( + formatLog(error.value, error_context) + ); + } + return; + } + + const embed = await this.container.tracer.startActiveSpan( + "create_embed", + async (s) => { + s.setAttribute("embedly.platform", platform.type); + const embed = + await Platforms[platform.type].createEmbed(data); + s.end(); + return embed; + } + ); + + let link_style: + | EmbedFlags[EmbedFlagNames.LinkStyle] + | undefined; + try { + link_style = (await this.container.posthog.getFeatureFlag( + "embed-link-styling-test", + message.author.id + )) as EmbedFlags[EmbedFlagNames.LinkStyle] | undefined; + } catch { + link_style = undefined; + } + + const msg = { + components: [ + Embed.getDiscordEmbed(embed, { + [EmbedFlagNames.Spoiler]: isSpoiler( + url, + message.content + ), + [EmbedFlagNames.LinkStyle]: link_style ?? "control" + })! + ], + flags: MessageFlags.IsComponentsV2, + allowedMentions: { + parse: [], + repliedUser: false + } + } as const; + const bot_message = + await this.container.tracer.startActiveSpan( + "send_message", + async (s) => { + const res = + ind > 0 && message.channel.isSendable() + ? await message.channel.send(msg) + : await message.reply(msg); + s.setAttribute("discord.bot_message_id", res.id); + s.end(); + return res; + } + ); + this.container.embed_authors.set( + bot_message.id, + message.author.id + ); + const existing = + this.container.embed_messages.get(message.id) ?? []; + existing.push(bot_message.id); + this.container.embed_messages.set(message.id, existing); + this.container.logger.info( + formatLog(EMBEDLY_EMBED_CREATED_MESSAGE, { + user_message_id: message.id, + bot_message_id: bot_message.id, + user_id: message.author.id, + platform: platform.type, + source: "message", + url + }) + ); } + await message.edit({ flags: MessageFlags.SuppressEmbeds }); + root_span.setStatus({ code: SpanStatusCode.OK }); + } catch (error: any) { + root_span.setStatus({ + code: SpanStatusCode.ERROR, + message: error.message + }); + root_span.recordException(error); + } finally { + root_span.end(); } - ); - if (error) { - if ("detail" in error.value) { - const error_context: EmbedlyInteractionContext & - EmbedlyPostContext = { - ...error.value.context, - message_id: message.id, - user_id: message.author.id - }; - this.container.betterstack.error( - ...formatBetterStack(error.value, error_context) - ); - } - return; } - - const embed = await Platforms[platform.type].createEmbed(data); - const link_style = (await this.container.posthog.getFeatureFlag( - "embed-link-styling-test", - message.author.id - )) as EmbedFlags[EmbedFlagNames.LinkStyle] | undefined; - const msg = { - components: [ - Embed.getDiscordEmbed(embed, { - [EmbedFlagNames.Spoiler]: isSpoiler(url, message.content), - [EmbedFlagNames.LinkStyle]: link_style ?? "control" - })! - ], - flags: MessageFlags.IsComponentsV2, - allowedMentions: { - parse: [], - repliedUser: false - } - } as const; - const bot_message = - ind > 0 && message.channel.isSendable() - ? await message.channel.send(msg) - : await message.reply(msg); - this.container.embed_authors.set( - bot_message.id, - message.author.id - ); - this.container.betterstack.info( - ...formatBetterStack(EMBEDLY_EMBED_CREATED_MESSAGE, { - user_message_id: message.id, - bot_message_id: bot_message.id, - user_id: message.author.id, - platform: platform.type, - url - }) - ); - } - await message.edit({ flags: MessageFlags.SuppressEmbeds }); + ); } } diff --git a/apps/bot/src/listeners/messageDelete.ts b/apps/bot/src/listeners/messageDelete.ts new file mode 100644 index 0000000..27be50e --- /dev/null +++ b/apps/bot/src/listeners/messageDelete.ts @@ -0,0 +1,43 @@ +import { EMBEDLY_AUTO_DELETE_INFO, formatLog } from "@embedly/logging"; +import { Events, Listener } from "@sapphire/framework"; +import type { Message, PartialMessage } from "discord.js"; + +export class MessageDeleteListener extends Listener< + typeof Events.MessageDelete +> { + public constructor( + context: Listener.LoaderContext, + options: Listener.Options + ) { + super(context, { + ...options, + event: Events.MessageDelete + }); + } + + public async run(message: Message | PartialMessage) { + const bot_message_ids = this.container.embed_messages.get( + message.id + ); + if (!bot_message_ids) return; + + for (const bot_msg_id of bot_message_ids) { + try { + const bot_msg = + await message.channel.messages.fetch(bot_msg_id); + await bot_msg.delete(); + } catch {} + this.container.embed_authors.delete(bot_msg_id); + } + + this.container.embed_messages.delete(message.id); + + this.container.logger.info( + formatLog(EMBEDLY_AUTO_DELETE_INFO, { + message_id: message.id, + user_id: message.author?.id, + original_author_id: message.author?.id + }) + ); + } +} diff --git a/apps/bot/src/otel.ts b/apps/bot/src/otel.ts new file mode 100644 index 0000000..6cea4cc --- /dev/null +++ b/apps/bot/src/otel.ts @@ -0,0 +1,65 @@ +import { + DiagConsoleLogger, + DiagLogLevel, + diag +} from "@opentelemetry/api"; +import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http"; +import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http"; +import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; +import { HttpInstrumentation } from "@opentelemetry/instrumentation-http"; +import { resourceFromAttributes } from "@opentelemetry/resources"; +import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs"; +import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics"; +import { NodeSDK } from "@opentelemetry/sdk-node"; +import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions"; + +const endpoint = + process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://lgtm:4317"; + +const sdk = new NodeSDK({ + resource: resourceFromAttributes({ + [ATTR_SERVICE_NAME]: "embedly-bot" + }), + traceExporter: new OTLPTraceExporter({ + url: `${endpoint}/v1/traces` + }), + logRecordProcessors: [ + new BatchLogRecordProcessor( + new OTLPLogExporter({ + url: `${endpoint}/v1/logs` + }) + ) + ], + metricReader: new PeriodicExportingMetricReader({ + exporter: new OTLPMetricExporter({ + url: `${endpoint}/v1/metrics` + }) + }), + instrumentations: [ + new HttpInstrumentation({ + ignoreIncomingRequestHook: () => true, + ignoreOutgoingRequestHook: (request) => { + const host = + typeof request === "string" + ? request + : (request.hostname ?? request.host ?? ""); + + return ( + host.includes("discord.gg") || host.includes("discord.com") + ); + } + }) + ] +}); + +diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.ERROR); + +sdk.start(); + +const shutdown = async () => { + await sdk.shutdown(); + process.exit(0); +}; + +process.on("SIGTERM", shutdown); +process.on("SIGINT", shutdown); diff --git a/packages/builder/package.json b/packages/builder/package.json index f73dbb3..100d9ba 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -14,7 +14,7 @@ }, "scripts": { "lint": "biome ci ./src/*", - "build": "pkgroll --sourcemap=inline && tsc && tsc" + "build": "pkgroll --sourcemap=inline && tsc" }, "devDependencies": { "@biomejs/biome": "2.3.10", @@ -24,8 +24,6 @@ }, "dependencies": { "@discordjs/builders": "^1.13.1", - "@embedly/parser": "workspace:*", - "@embedly/types": "workspace:*", "discord-api-types": "^0.38.37" } } diff --git a/packages/builder/src/Embed.ts b/packages/builder/src/Embed.ts index 89a0041..33b2e7b 100644 --- a/packages/builder/src/Embed.ts +++ b/packages/builder/src/Embed.ts @@ -6,26 +6,58 @@ import { heading, hyperlink, MediaGalleryBuilder, + type RGBTuple, SectionBuilder, subtext, TimestampStyles, time } from "@discordjs/builders"; -import { - type BaseEmbedData, - type BaseEmbedDataWithoutPlatform, - EmbedlyPlatformColors, - type EmbedlyPlatformType, - type Emojis, - emojis, - type StatsData -} from "@embedly/types"; + import { type APIMediaGalleryItem, - ButtonStyle, SeparatorSpacingSize } from "discord-api-types/v10"; +export interface StatsData { + comments: number; + reposts?: number; + likes: number; + bookmarks?: number; + views?: number; +} + +export const statEmojis = { + comments: "<:comment:1386639521373753374>", + reposts: "<:repost:1386639564143198349>", + likes: "<:like:1386639662772391987>", + bookmarks: "<:bookmark:1386639640433529014>", + views: "<:view:1386639685237084292>", + reply: "<:reply:1386639619768058007>", + quote: "<:quote:1389657738480713838>" +} as const; + +export type StatEmojis = typeof statEmojis; + +export interface BaseEmbedData { + platform: string; + color: RGBTuple; + emoji: string; + name: string; + username?: string; + profile_url?: string; + avatar_url: string; + url: string; + timestamp: number; + stats?: StatsData; + description?: string; + media?: APIMediaGalleryItem[]; +} + +export type BaseEmbedDataWithoutPlatform = Omit< + BaseEmbedData, + "platform" | "color" | "emoji" +>; + export interface EmbedData extends BaseEmbedData { quote?: BaseEmbedDataWithoutPlatform; replying_to?: BaseEmbedDataWithoutPlatform; @@ -46,7 +78,9 @@ export interface EmbedFlags { } export class Embed implements EmbedData { - public platform!: EmbedlyPlatformType; + public platform!: string; + public color!: RGBTuple; + public emoji!: string; public name!: string; public username?: string; public profile_url?: string; @@ -97,24 +131,19 @@ export class Embed implements EmbedData { } const container = new ContainerBuilder(); - container.setAccentColor(EmbedlyPlatformColors[embed.platform]); + container.setAccentColor(embed.color); if (hidden) { container.setSpoiler(true); } - // Add primary content section (reply content if exists, otherwise main content) Embed.addPrimaryContentSection(container, embed, source_only); - - // Add primary media (reply media if exists, otherwise main media) Embed.addPrimaryMedia(container, embed, source_only); - // Add secondary content (reply/quote) - skip if source_only if (!source_only) { Embed.addSecondaryContent(container, embed); } - // Add footer with stats and metadata Embed.addFooterSection(container, embed, link_style); return container.toJSON(); @@ -152,9 +181,9 @@ export class Embed implements EmbedData { author_profile_url = embed.profile_url; author_description = embed.description; author_avatar_url = embed.avatar_url; - prefix_emoji = !source_only && embed.quote ? emojis.quote : ""; + prefix_emoji = + !source_only && embed.quote ? statEmojis.quote : ""; } else { - // Show reply content first (only when not source_only) author_name = embed.replying_to.name; author_username = embed.replying_to.username; author_profile_url = embed.replying_to.profile_url; @@ -181,13 +210,10 @@ export class Embed implements EmbedData { ) { let media: APIMediaGalleryItem[] | undefined; if (source_only) { - // For source_only, use main embed's media media = embed.media; } else if (embed.replying_to) { - // For replies, show parent's media first (derek's media) media = embed.replying_to.media; } else { - // Otherwise use main embed's media media = embed.media; } @@ -206,16 +232,13 @@ export class Embed implements EmbedData { return; } - // Add separator container.addSeparatorComponents((builder) => builder.setDivider(true).setSpacing(SeparatorSpacingSize.Large) ); if (embed.replying_to) { - // Show original content after reply Embed.addOriginalContentAfterReply(container, embed); } else if (embed.quote) { - // Show quote content Embed.addQuoteContent(container, embed.quote); } } @@ -230,7 +253,7 @@ export class Embed implements EmbedData { embed.profile_url, embed.avatar_url, embed.description, - emojis.reply + statEmojis.reply ); container.addSectionComponents(reply_section); @@ -286,7 +309,10 @@ export class Embed implements EmbedData { if (description) { section.addTextDisplayComponents((builder) => - builder.setContent(description) + builder.setContent( + description.slice(0, 1500) + + (description.length > 1500 ? "..." : "") + ) ); } @@ -311,43 +337,20 @@ export class Embed implements EmbedData { private static addFooterSection( container: ContainerBuilder, embed: Embed, - link_style: EmbedFlags[EmbedFlagNames.LinkStyle] + _link_style?: EmbedFlags[EmbedFlagNames.LinkStyle] ) { const stats = Embed.formatStats(embed); - if (link_style === "control") { - container.addSectionComponents((builder) => { - builder.addTextDisplayComponents((builder) => - builder.setContent( - `${stats.length > 0 ? `${subtext(stats.join(" "))}\n` : ""}${emojis[embed.platform]} • ${time( - embed.timestamp, - TimestampStyles.LongDateShortTime - )}` - ) - ); - - builder.setButtonAccessory((builder) => - builder - .setStyle(ButtonStyle.Link) - .setURL(embed.url) - .setLabel(`View on ${embed.platform}`) - ); - - return builder; - }); - return; - } - container.addSeparatorComponents((builder) => builder.setDivider(true).setSpacing(SeparatorSpacingSize.Large) ); container.addTextDisplayComponents((builder) => builder.setContent( - `${stats.length > 0 ? `${subtext(stats.join(" "))}\n` : ""}${emojis[embed.platform]} • ${time( + `${stats.length > 0 ? `${subtext(stats.join(" "))}\n` : ""}${embed.emoji} • ${time( embed.timestamp, TimestampStyles.LongDateShortTime - )} • ${link_style === "inline" ? hyperlink(`View on ${embed.platform}`, embed.url) : ""}` + )} • ${hyperlink(`View on ${embed.platform}`, embed.url)}` ) ); } @@ -363,7 +366,7 @@ export class Embed implements EmbedData { return Object.entries(stats_data).map( ([key, val]) => - `${emojis[key as keyof Emojis]} ${Embed.NumberFormatter.format(val)}` + `${statEmojis[key as keyof StatEmojis]} ${Embed.NumberFormatter.format(val)}` ); } } diff --git a/packages/logging/package.json b/packages/logging/package.json index 8528fd4..8cba988 100644 --- a/packages/logging/package.json +++ b/packages/logging/package.json @@ -19,8 +19,10 @@ "devDependencies": { "@biomejs/biome": "2.3.10", "@embedly/config": "workspace:*", - "@embedly/types": "workspace:*", "pkgroll": "^2.21.4", "typescript": "^5.9.3" + }, + "dependencies": { + "@opentelemetry/api-logs": "^0.211.0" } } diff --git a/packages/logging/src/main.ts b/packages/logging/src/main.ts index 50968fd..2b7b550 100644 --- a/packages/logging/src/main.ts +++ b/packages/logging/src/main.ts @@ -1,4 +1,4 @@ -import type { EmbedlyPlatformType } from "@embedly/types"; +type PlatformName = string; export interface EmbedlyLogBase { type: string; @@ -21,10 +21,14 @@ export const EMBEDLY_INVALID_REQUEST: EmbedlyErrorBase<{ detail: "Failed to validate request from Discord." }; +export type EmbedlySource = "message" | "command" | "context_menu"; + export interface EmbedlyInteractionContext { interaction_id?: string; user_id?: string; message_id?: string; + source?: EmbedlySource; + platform?: PlatformName; } export const EMBEDLY_NO_LINK_IN_MESSAGE: EmbedlyErrorBase = @@ -46,7 +50,7 @@ export const EMBEDLY_NO_VALID_LINK: EmbedlyErrorBase }; export interface EmbedlyPostContext { - platform?: EmbedlyPlatformType; + platform?: PlatformName; post_url?: string; post_id?: string; resp_status?: number; @@ -54,25 +58,24 @@ export interface EmbedlyPostContext { resp_data?: any; } -export const EMBEDLY_FETCH_PLATFORM: ( - platform: EmbedlyPlatformType -) => EmbedlyErrorBase = ( - platform: EmbedlyPlatformType -) => ({ +export const EMBEDLY_FETCH_PLATFORM = ( + platform: PlatformName +): EmbedlyErrorBase => ({ type: `EMBEDLY_FETCH_${platform.toUpperCase()}`, title: `Fetching ${platform}.`, detail: `Fetching ${platform} from the ${platform} API.` }); -export const EMBEDLY_FAILED_PLATFORM: ( - platform: EmbedlyPlatformType -) => EmbedlyErrorBase = - (platform: EmbedlyPlatformType) => ({ - type: `EMBEDLY_FAILED_${platform.toUpperCase()}`, - status: 500, - title: `Failed to fetch ${platform}.`, - detail: `Failed to fetch this ${platform} post from the ${platform} API.` - }); +export const EMBEDLY_FAILED_PLATFORM = ( + platform: PlatformName +): EmbedlyErrorBase< + EmbedlyInteractionContext & EmbedlyPostContext +> => ({ + type: `EMBEDLY_FAILED_${platform.toUpperCase()}`, + status: 500, + title: `Failed to fetch ${platform}.`, + detail: `Failed to fetch this ${platform} post from the ${platform} API.` +}); export const EMBEDLY_CACHED_POST: EmbedlyErrorBase = { @@ -127,7 +130,7 @@ export const EMBEDLY_DELETE_SUCCESS: EmbedlyErrorBase }; export interface EmbedlyEmbedContext extends EmbedlyInteractionContext { - platform?: EmbedlyPlatformType; + platform?: PlatformName; url?: string; bot_message_id?: string; user_message_id?: string; @@ -169,6 +172,14 @@ export const EMBEDLY_DELETE_SUCCESS_INFO: EmbedlyLogBase = detail: "User successfully deleted an embed." }; +export const EMBEDLY_AUTO_DELETE_INFO: EmbedlyLogBase = + { + type: "EMBEDLY_AUTO_DELETE_INFO", + title: "Embed auto-deleted.", + detail: + "Bot embed automatically deleted because the original message was deleted." + }; + export const EMBEDLY_NO_LINK_WARN: EmbedlyLogBase = { type: "EMBEDLY_NO_LINK_WARN", @@ -183,12 +194,107 @@ export const EMBEDLY_NO_VALID_LINK_WARN: EmbedlyLogBase( +export interface FormattedLog { + body: string; + attributes: Record; +} + +export function formatLog( log: T, ctx: T["context"] -) { +): FormattedLog { log.context = ctx; - return [log.detail, log] as const; + const contextAttrs: Record< + string, + string | number | boolean | undefined + > = {}; + if (ctx && typeof ctx === "object") { + for (const [key, value] of Object.entries(ctx)) { + if (value !== undefined && typeof value !== "object") { + contextAttrs[key] = value; + } + } + } + return { + body: log.detail, + attributes: { + "log.type": log.type, + "log.title": log.title, + ...contextAttrs + } + }; +} + +import { + logs, + type Logger as OtelLogger, + SeverityNumber +} from "@opentelemetry/api-logs"; + +export class EmbedlyLogger { + private otel: OtelLogger; + + constructor(name: string) { + this.otel = logs.getLogger(name); + } + + private static readonly consoleMethods: Record< + string, + (...args: unknown[]) => void + > = { + INFO: console.info, + WARN: console.warn, + ERROR: console.error, + FATAL: console.error + }; + + info(...values: readonly unknown[]) { + this.emit(SeverityNumber.INFO, "INFO", values); + } + + warn(...values: readonly unknown[]) { + this.emit(SeverityNumber.WARN, "WARN", values); + } + + error(...values: readonly unknown[]) { + this.emit(SeverityNumber.ERROR, "ERROR", values); + } + + fatal(...values: readonly unknown[]) { + this.emit(SeverityNumber.FATAL, "FATAL", values); + } + + private emit( + severityNumber: SeverityNumber, + severityText: string, + values: readonly unknown[] + ) { + const consoleMethod = + EmbedlyLogger.consoleMethods[severityText] ?? console.log; + const first = values[0]; + if ( + first && + typeof first === "object" && + "body" in first && + "attributes" in first + ) { + const log = first as FormattedLog; + consoleMethod(log.body, log.attributes); + this.otel.emit({ + severityNumber, + severityText, + body: log.body, + attributes: log.attributes + }); + } else { + consoleMethod(...values); + this.otel.emit({ + severityNumber, + severityText, + body: values.map(String).join(" ") + }); + } + } } export function formatDiscord< diff --git a/packages/parser/CHANGELOG.md b/packages/parser/CHANGELOG.md deleted file mode 100644 index 5dc84be..0000000 --- a/packages/parser/CHANGELOG.md +++ /dev/null @@ -1,209 +0,0 @@ -# @embedly/parser - -## 0.11.0 - -### Patch Changes - -- Updated dependencies []: - - @embedly/types@0.11.0 - -## 0.10.0 - -### Patch Changes - -- [#36](https://github.com/embed-team/embedly/pull/36) [`dde5d23`](https://github.com/embed-team/embedly/commit/dde5d23d2d3705dc743fb772d602f64d85069f8c) Thanks [@ItsRauf](https://github.com/ItsRauf)! - feat(ci): split github actions across multiple files - -- Updated dependencies [[`dde5d23`](https://github.com/embed-team/embedly/commit/dde5d23d2d3705dc743fb772d602f64d85069f8c)]: - - @embedly/types@0.10.0 - -## 0.9.1 - -### Patch Changes - -- [#34](https://github.com/embed-team/embedly/pull/34) [`d8495b4`](https://github.com/embed-team/embedly/commit/d8495b40953bce13cb528dfb28f467cdbf19d07c) Thanks [@ItsRauf](https://github.com/ItsRauf)! - feat(ci): split github actions across multiple files - -- Updated dependencies [[`d8495b4`](https://github.com/embed-team/embedly/commit/d8495b40953bce13cb528dfb28f467cdbf19d07c)]: - - @embedly/types@0.9.1 - -## 0.9.0 - -### Patch Changes - -- Updated dependencies []: - - @embedly/types@0.9.0 - -## 0.8.0 - -### Patch Changes - -- [#29](https://github.com/embed-team/embedly/pull/29) [`9c5c80d`](https://github.com/embed-team/embedly/commit/9c5c80d3015bcee08027856d919283f8a7cd916c) Thanks [@ItsRauf](https://github.com/ItsRauf)! - cbc regex was only capturing a subset of links - -- Updated dependencies []: - - @embedly/types@0.8.0 - -## 0.7.1 - -### Patch Changes - -- Updated dependencies []: - - @embedly/types@0.7.1 - -## 0.7.0 - -### Patch Changes - -- Updated dependencies []: - - @embedly/types@0.7.0 - -## 0.6.0 - -### Minor Changes - -- [#22](https://github.com/embed-team/embedly/pull/22) [`327acfe`](https://github.com/embed-team/embedly/commit/327acfe0c405fd57b65643e881bd4978af47e0bb) Thanks [@ItsRauf](https://github.com/ItsRauf)! - refactor embed builder and fix delete command - -### Patch Changes - -- [#22](https://github.com/embed-team/embedly/pull/22) [`327acfe`](https://github.com/embed-team/embedly/commit/327acfe0c405fd57b65643e881bd4978af47e0bb) Thanks [@ItsRauf](https://github.com/ItsRauf)! - replies now show in the correct order - -- Updated dependencies [[`327acfe`](https://github.com/embed-team/embedly/commit/327acfe0c405fd57b65643e881bd4978af47e0bb), [`327acfe`](https://github.com/embed-team/embedly/commit/327acfe0c405fd57b65643e881bd4978af47e0bb)]: - - @embedly/types@0.6.0 - -## 0.5.3 - -### Patch Changes - -- [#20](https://github.com/embed-team/embedly/pull/20) [`455d9b1`](https://github.com/embed-team/embedly/commit/455d9b19b0c8662a85799557fedd8b9bfae2cc33) Thanks [@ItsRauf](https://github.com/ItsRauf)! - sane spoiler and escape detection - -- Updated dependencies [[`455d9b1`](https://github.com/embed-team/embedly/commit/455d9b19b0c8662a85799557fedd8b9bfae2cc33)]: - - @embedly/types@0.5.3 - -## 0.5.2 - -### Patch Changes - -- Updated dependencies []: - - @embedly/types@0.5.2 - -## 0.5.1 - -### Patch Changes - -- Updated dependencies []: - - @embedly/types@0.5.1 - -## 0.5.0 - -### Patch Changes - -- Updated dependencies []: - - @embedly/types@0.5.0 - -## 0.4.1 - -### Patch Changes - -- [#8](https://github.com/embed-team/embedly/pull/8) [`4386504`](https://github.com/embed-team/embedly/commit/438650487368ec722bc75801ccf5f495be62a485) Thanks [@ItsRauf](https://github.com/ItsRauf)! - fix broken spoiler regex - -- Updated dependencies [[`4386504`](https://github.com/embed-team/embedly/commit/438650487368ec722bc75801ccf5f495be62a485)]: - - @embedly/types@0.4.1 - -## 0.4.0 - -### Minor Changes - -- [#6](https://github.com/embed-team/embedly/pull/6) [`14785b7`](https://github.com/embed-team/embedly/commit/14785b70759445b7d402a3d63bca72993239f5b3) Thanks [@ItsRauf](https://github.com/ItsRauf)! - add a spoiler flag and support masked links in messages - -### Patch Changes - -- Updated dependencies [[`14785b7`](https://github.com/embed-team/embedly/commit/14785b70759445b7d402a3d63bca72993239f5b3)]: - - @embedly/types@0.4.0 - -## 0.3.3 - -### Patch Changes - -- [`6d3fccd`](https://github.com/embed-team/embedly/commit/6d3fccd8190a8b697e0ee93edc8c81affb036f01) Thanks [@ItsRauf](https://github.com/ItsRauf)! - added DX and CI tooling - -- Updated dependencies [[`6d3fccd`](https://github.com/embed-team/embedly/commit/6d3fccd8190a8b697e0ee93edc8c81affb036f01)]: - - @embedly/types@0.3.3 - -## 0.3.2 - -### Patch Changes - -- fixed title for cbc embeds where username doesn't exist -- Updated dependencies - - @embedly/types@0.3.2 - -## 0.3.1 - -### Patch Changes - -- escape markdown in embed description -- Updated dependencies - - @embedly/types@0.3.1 - -## 0.3.0 - -### Minor Changes - -- added threads and fixed a few emoji issues - -### Patch Changes - -- Updated dependencies - - @embedly/types@0.3.0 - -## 0.2.0 - -### Minor Changes - -- added cbc.ca, fixed tiktok, added media proxy - -### Patch Changes - -- Updated dependencies - - @embedly/types@0.2.0 - -## 0.1.4 - -### Patch Changes - -- added a fallback error for random failures and added multi-embedding for messages -- Updated dependencies - - @embedly/types@0.1.4 - -## 0.1.3 - -### Patch Changes - -- fixed tiktok embeds and added new instagram share link handling -- Updated dependencies - - @embedly/types@0.1.3 - -## 0.1.2 - -### Patch Changes - -- rewrote api and bot embed handling to make the api only handle scraping -- Updated dependencies - - @embedly/types@0.1.2 - -## 0.1.1 - -### Patch Changes - -- added sidebar color to containers -- Updated dependencies - - @embedly/types@0.1.1 - -## 0.1.0 - -### Minor Changes - -- this is the first oss version of embedly :3 - -### Patch Changes - -- Updated dependencies - - @embedly/types@0.1.0 diff --git a/packages/parser/biome.json b/packages/parser/biome.json deleted file mode 100644 index 167b18f..0000000 --- a/packages/parser/biome.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "root": false, - "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", - "extends": ["@embedly/config/biome.json"] -} diff --git a/packages/parser/package.json b/packages/parser/package.json deleted file mode 100644 index 088b424..0000000 --- a/packages/parser/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "@embedly/parser", - "version": "0.11.0", - "private": true, - "type": "module", - "main": "./dist/main.js", - "module": "./dist/main.js", - "types": "./dist/main.d.ts", - "exports": { - "import": { - "types": "./dist/main.d.ts", - "default": "./dist/main.js" - } - }, - "scripts": { - "lint": "biome ci ./src/*", - "build": "pkgroll --sourcemap=inline && tsc" - }, - "devDependencies": { - "@biomejs/biome": "2.3.10", - "@embedly/config": "workspace:*", - "pkgroll": "^2.21.4", - "typescript": "^5.9.3" - }, - "dependencies": { - "@embedly/types": "workspace:*" - } -} diff --git a/packages/parser/src/main.ts b/packages/parser/src/main.ts deleted file mode 100644 index 1459049..0000000 --- a/packages/parser/src/main.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./parse.ts"; -export * from "./regex.ts"; diff --git a/packages/parser/src/parse.ts b/packages/parser/src/parse.ts deleted file mode 100644 index 7cfe006..0000000 --- a/packages/parser/src/parse.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { EmbedlyPlatformType } from "@embedly/types"; -import { - CBC_REGEX, - GENERIC_LINK_REGEX, - IG_REGEX, - REDDIT_REGEX, - THREADS_REGEX, - TIKTOK_REGEX_MAIN, - TWITTER_REGEX -} from "./regex.ts"; - -export function hasLink(content: string) { - return GENERIC_LINK_REGEX.test(content); -} - -export function isSpoiler(url: string, content: string) { - return content - .split("||") - .some((part, ind) => ind % 2 === 1 && part.includes(url)); -} - -export function isEscaped(url: string, content: string) { - return content.includes(`<${url}>`); -} - -export function getPlatformFromURL( - url: string -): null | { type: EmbedlyPlatformType } { - if (TWITTER_REGEX.test(url)) - return { type: EmbedlyPlatformType.Twitter }; - if (IG_REGEX.test(url)) - return { type: EmbedlyPlatformType.Instagram }; - if (TIKTOK_REGEX_MAIN.test(url)) - return { type: EmbedlyPlatformType.TikTok }; - if (CBC_REGEX.test(url)) { - return { type: EmbedlyPlatformType.CBC }; - } - if (THREADS_REGEX.test(url)) { - return { type: EmbedlyPlatformType.Threads }; - } - if (REDDIT_REGEX.test(url)) { - return { type: EmbedlyPlatformType.Reddit }; - } - return null; -} diff --git a/packages/parser/src/regex.ts b/packages/parser/src/regex.ts deleted file mode 100644 index 7688a73..0000000 --- a/packages/parser/src/regex.ts +++ /dev/null @@ -1,15 +0,0 @@ -export const GENERIC_LINK_REGEX = - /\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()[\]{};:'".,<>?«»“”‘’]))/i; -export const TWITTER_REGEX = - /(?:twitter|x).com\/.*\/status(?:es)?\/(?[^/?]+)/; -export const IG_REGEX = - /instagram.com\/(?:[A-Za-z0-9_.]+\/)?(p|share|reels|reel|stories)\/(?[A-Za-z0-9-_]+)/; -export const TIKTOK_REGEX_MAIN = - /(https?:\/\/)?(?:[\w-]+\.)*tiktok\.com/; -export const TIKTOK_REGEX = - /https:\/\/(?:m|www|vm)?\.?tiktok\.com\/(?@[\w.-]+)\/video\/(?\d+)/; -export const THREADS_REGEX = - /threads\.com\/@.*\/post\/(?[A-Za-z0-9-_]+)/; -export const CBC_REGEX = /cbc.ca\/.*(?\d\.\d+)/; -export const REDDIT_REGEX = - /https?:\/\/(?:www\.|old\.|m\.)?reddit\.com\/r\/(?\w+)\/comments\/(?[a-z0-9]+)/; diff --git a/packages/parser/tsconfig.json b/packages/parser/tsconfig.json deleted file mode 100644 index 3a138a9..0000000 --- a/packages/parser/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "@embedly/config/tsconfig.json", - "compilerOptions": { - "lib": ["WebWorker", "ESNext"], - "outDir": "./dist" - }, - "exclude": ["node_modules"], - "include": ["src"] -} diff --git a/packages/platforms/package.json b/packages/platforms/package.json index 3a7eead..9fa3b67 100644 --- a/packages/platforms/package.json +++ b/packages/platforms/package.json @@ -27,8 +27,6 @@ "dependencies": { "@embedly/builder": "workspace:*", "@embedly/logging": "workspace:*", - "@embedly/parser": "workspace:*", - "@embedly/types": "workspace:*", "@types/he": "^1.2.3", "cheerio": "^1.1.2", "he": "^1.2.0" diff --git a/packages/platforms/src/CBC.ts b/packages/platforms/src/CBC.ts index 5acbb68..8473e1d 100644 --- a/packages/platforms/src/CBC.ts +++ b/packages/platforms/src/CBC.ts @@ -1,29 +1,25 @@ import { createHmac } from "node:crypto"; import { Embed } from "@embedly/builder"; -import { - EMBEDLY_FAILED_PLATFORM, - EMBEDLY_FETCH_PLATFORM -} from "@embedly/logging"; -import { CBC_REGEX } from "@embedly/parser"; -import { - type BaseEmbedData, - EmbedlyPlatformType -} from "@embedly/types"; import * as cheerio from "cheerio"; import he from "he"; -import { EmbedlyPlatform } from "./Platform.ts"; +import { CF_CACHE_OPTIONS } from "./constants.ts"; +import { type BaseEmbedData, EmbedlyPlatform } from "./Platform.ts"; +import { EmbedlyPlatformType } from "./types.ts"; +import { validateRegexMatch } from "./utils.ts"; export class CBC extends EmbedlyPlatform { + readonly color = [215, 36, 42] as const; + readonly emoji = "<:cbc:1409997044495683674>"; + readonly regex = /cbc.ca\/.*(?\d\.\d+)/; + constructor() { - super(EmbedlyPlatformType.CBC, "cbc.ca", { - fetching: EMBEDLY_FETCH_PLATFORM(EmbedlyPlatformType.CBC), - failed: EMBEDLY_FAILED_PLATFORM(EmbedlyPlatformType.CBC) - }); + super(EmbedlyPlatformType.CBC, "cbc.ca"); } async parsePostId(url: string): Promise { - const match = CBC_REGEX.exec(url)!; - const { cbc_id } = match.groups!; + const match = this.regex.exec(url); + validateRegexMatch(match, "Invalid CBC URL: could not extract ID"); + const { cbc_id } = match.groups; return cbc_id; } @@ -31,29 +27,53 @@ export class CBC extends EmbedlyPlatform { const resp = await fetch(`https://cbc.ca/${post_id}`, { method: "GET", redirect: "follow", - cf: { - cacheTtl: 60 * 60 * 24, - cacheEverything: true - } + ...CF_CACHE_OPTIONS }); + if (!resp.ok) { throw { code: resp.status, message: resp.statusText }; } + const html = await resp.text(); const $ = cheerio.load(html); const script = $("script#initialStateDom"); - const data = JSON.parse( - script - .text() - .replace("window.__INITIAL_STATE__ = ", "") - .slice(0, -1) - ); - return data.app.meta.jsonld; + const scriptText = script.text(); + + if (!scriptText) { + throw { + code: 500, + message: "CBC page structure changed: missing data" + }; + } + + let data: any; + try { + data = JSON.parse( + scriptText + .replace("window.__INITIAL_STATE__ = ", "") + .slice(0, -1) + ); + } catch { + throw { code: 500, message: "Failed to parse CBC data" }; + } + + const jsonld = data?.app?.meta?.jsonld; + + if (!jsonld) { + throw { + code: 500, + message: "CBC page structure changed: missing metadata" + }; + } + + return jsonld; } transformRawData(raw_data: any): BaseEmbedData { return { platform: this.name, + color: [...this.color], + emoji: this.emoji, name: he.decode(raw_data.name), avatar_url: this.signProxyURL(raw_data.publisher.logo), timestamp: Math.floor( diff --git a/packages/platforms/src/Instagram.ts b/packages/platforms/src/Instagram.ts index bae7742..63ce935 100644 --- a/packages/platforms/src/Instagram.ts +++ b/packages/platforms/src/Instagram.ts @@ -1,21 +1,21 @@ import { Embed } from "@embedly/builder"; -import { - EMBEDLY_FAILED_PLATFORM, - EMBEDLY_FETCH_PLATFORM -} from "@embedly/logging"; -import { IG_REGEX } from "@embedly/parser"; +import { CF_CACHE_OPTIONS } from "./constants.ts"; import { type BaseEmbedData, - EmbedlyPlatformType -} from "@embedly/types"; -import { EmbedlyPlatform } from "./Platform.ts"; + type CloudflareEnv, + EmbedlyPlatform +} from "./Platform.ts"; +import { EmbedlyPlatformType } from "./types.ts"; +import { validateRegexMatch } from "./utils.ts"; export class Instagram extends EmbedlyPlatform { + readonly color = [225, 48, 108] as const; + readonly emoji = "<:instagram:1386639712013254748>"; + readonly regex = + /instagram.com\/(?:[A-Za-z0-9_.]+\/)?(p|share|reels|reel|stories)\/(?[A-Za-z0-9-_]+)/; + constructor() { - super(EmbedlyPlatformType.Instagram, "insta", { - fetching: EMBEDLY_FETCH_PLATFORM(EmbedlyPlatformType.Instagram), - failed: EMBEDLY_FAILED_PLATFORM(EmbedlyPlatformType.Instagram) - }); + super(EmbedlyPlatformType.Instagram, "insta"); } async parsePostId(url: string): Promise { @@ -28,14 +28,18 @@ export class Instagram extends EmbedlyPlatform { }); url = req.url; } - const match = IG_REGEX.exec(url)!; - const { ig_shortcode } = match.groups!; + const match = this.regex.exec(url); + validateRegexMatch( + match, + "Invalid Instagram URL: could not extract shortcode" + ); + const { ig_shortcode } = match.groups; return ig_shortcode; } async fetchPost( ig_shortcode: string, - env: { EMBED_USER_AGENT: string } + env?: Partial ): Promise { const graphql = new URL(`https://www.instagram.com/api/graphql`); graphql.searchParams.set( @@ -48,23 +52,31 @@ export class Instagram extends EmbedlyPlatform { const resp = await fetch(graphql.toString(), { method: "POST", headers: { - "User-Agent": env.EMBED_USER_AGENT, + "User-Agent": env?.EMBED_USER_AGENT ?? "", "Content-Type": "application/x-www-form-urlencoded", "X-IG-App-ID": "936619743392459", "X-FB-LSD": "AVqbxe3J_YA", "X-ASBD-ID": "129477", "Sec-Fetch-Site": "same-origin" }, - cf: { - cacheTtl: 60 * 60 * 24, - cacheEverything: true - } + ...CF_CACHE_OPTIONS }); + if (!resp.ok) { throw { code: resp.status, message: resp.statusText }; } + const { data } = (await resp.json()) as Record; - return data.xdt_shortcode_media; + const media = data?.xdt_shortcode_media; + + if (!media) { + throw { + code: 500, + message: "Instagram API returned unexpected structure" + }; + } + + return media; } parsePostMedia( @@ -112,6 +124,8 @@ export class Instagram extends EmbedlyPlatform { transformRawData(raw_data: any): BaseEmbedData { return { platform: this.name, + color: [...this.color], + emoji: this.emoji, name: raw_data.owner.full_name, username: raw_data.owner.username, profile_url: `https://instagram.com/${raw_data.owner.username}`, @@ -130,9 +144,6 @@ export class Instagram extends EmbedlyPlatform { createEmbed(post_data: any): Embed { const embed = new Embed(this.transformRawData(post_data)); const media = this.parsePostMedia(post_data); - if (media.length > 10) { - media.length = 10; - } embed.setMedia(media); return embed; diff --git a/packages/platforms/src/Platform.ts b/packages/platforms/src/Platform.ts index 5edae6c..12f87a1 100644 --- a/packages/platforms/src/Platform.ts +++ b/packages/platforms/src/Platform.ts @@ -1,13 +1,14 @@ -import type { Embed } from "@embedly/builder"; -import type { - EmbedlyErrorBase, - EmbedlyInteractionContext, - EmbedlyPostContext +import type { BaseEmbedData } from "@embedly/builder"; +import { + EMBEDLY_FAILED_PLATFORM, + EMBEDLY_FETCH_PLATFORM, + type EmbedlyErrorBase, + type EmbedlyInteractionContext, + type EmbedlyPostContext } from "@embedly/logging"; -import type { - BaseEmbedData, - EmbedlyPlatformType -} from "@embedly/types"; +import type { EmbedlyPlatformType } from "./types.ts"; + +export type { BaseEmbedData } from "@embedly/builder"; export interface EmbedlyPlatformLogMessages { fetching: EmbedlyErrorBase; @@ -15,37 +16,65 @@ export interface EmbedlyPlatformLogMessages { EmbedlyInteractionContext & EmbedlyPostContext >; } + +export interface CloudflareEnv { + EMBED_USER_AGENT: string; + THREADS_CSRF_TOKEN?: string; + STORAGE: KVNamespace; + DISCORD_BOT_TOKEN: string; +} + export abstract class EmbedlyPlatform { + abstract readonly color: readonly [number, number, number]; + abstract readonly emoji: string; + abstract readonly regex: RegExp; + + public log_messages: EmbedlyPlatformLogMessages; + constructor( public name: EmbedlyPlatformType, - private cache_prefix: string, - public log_messages: EmbedlyPlatformLogMessages - ) {} + private cache_prefix: string + ) { + this.log_messages = { + fetching: EMBEDLY_FETCH_PLATFORM(name), + failed: EMBEDLY_FAILED_PLATFORM(name) + }; + } + + public matchesUrl(url: string): boolean { + return this.regex.test(url); + } abstract parsePostId(url: string): Promise; + public async getPostFromCache( post_id: string, cache_store: KVNamespace - ) { + ): Promise | null> { const cache_key = `${this.cache_prefix}:${post_id}`; return await cache_store.get>(cache_key, { cacheTtl: 60 * 60 * 24, type: "json" }); } + public async addPostToCache( post_id: string, post_data: any, cache_store: KVNamespace - ) { + ): Promise { const cache_key = `${this.cache_prefix}:${post_id}`; - cache_store.put(cache_key, JSON.stringify(post_data), { + await cache_store.put(cache_key, JSON.stringify(post_data), { expirationTtl: 60 * 60 * 24 }); } - abstract fetchPost(post_id: string, env?: any): Promise; + + abstract fetchPost( + post_id: string, + env?: Partial + ): Promise; abstract transformRawData( raw_data: any ): Promise | BaseEmbedData; - abstract createEmbed(post_data: T): Promise | Embed; + abstract createEmbed(post_data: T): Promise | any; } diff --git a/packages/platforms/src/Reddit.ts b/packages/platforms/src/Reddit.ts index d4b4d78..0cd1572 100644 --- a/packages/platforms/src/Reddit.ts +++ b/packages/platforms/src/Reddit.ts @@ -1,32 +1,36 @@ import { Embed } from "@embedly/builder"; -import { - EMBEDLY_FAILED_PLATFORM, - EMBEDLY_FETCH_PLATFORM -} from "@embedly/logging"; -import { REDDIT_REGEX } from "@embedly/parser"; +import { CF_CACHE_OPTIONS } from "./constants.ts"; import { type BaseEmbedData, - EmbedlyPlatformType -} from "@embedly/types"; -import { EmbedlyPlatform } from "./Platform.ts"; + type CloudflareEnv, + EmbedlyPlatform +} from "./Platform.ts"; +import { EmbedlyPlatformType } from "./types.ts"; +import { validateRegexMatch } from "./utils.ts"; export class Reddit extends EmbedlyPlatform { + readonly color = [255, 86, 0] as const; + readonly emoji = "<:reddit:1461320093240655922>"; + readonly regex = + /https?:\/\/(?:www\.|old\.|m\.)?reddit\.com\/r\/(?\w+)\/comments\/(?[a-z0-9]+)/; + constructor() { - super(EmbedlyPlatformType.Reddit, "reddit", { - fetching: EMBEDLY_FETCH_PLATFORM(EmbedlyPlatformType.Reddit), - failed: EMBEDLY_FAILED_PLATFORM(EmbedlyPlatformType.Reddit) - }); + super(EmbedlyPlatformType.Reddit, "reddit"); } async parsePostId(url: string): Promise { - const match = REDDIT_REGEX.exec(url)!; - const { post_id, subreddit } = match.groups!; + const match = this.regex.exec(url); + validateRegexMatch( + match, + "Invalid Reddit URL: could not extract post ID or subreddit" + ); + const { post_id, subreddit } = match.groups; return `${subreddit}/${post_id}`; } async fetchPost( post_id: string, - env: { EMBED_USER_AGENT: string } + env?: Partial ): Promise { const [subreddit, reddit_id] = post_id.split("/"); const resp = await fetch( @@ -34,12 +38,9 @@ export class Reddit extends EmbedlyPlatform { { method: "GET", headers: { - "User-Agent": env.EMBED_USER_AGENT + "User-Agent": env?.EMBED_USER_AGENT ?? "" }, - cf: { - cacheTtl: 60 * 60 * 24, - cacheEverything: true - } + ...CF_CACHE_OPTIONS } ); @@ -49,31 +50,53 @@ export class Reddit extends EmbedlyPlatform { const post_data = (await resp.json()) as Record; + const postDataItem = post_data?.[0]?.data?.children?.[0]?.data; + + if (!postDataItem) { + throw { + code: 500, + message: "Reddit API returned unexpected structure" + }; + } + + const authorName = postDataItem.author; + + if (!authorName) { + throw { + code: 500, + message: "Reddit post missing author information" + }; + } + const profile_resp = await fetch( - `https://www.reddit.com/user/${post_data.author}/about.json?raw_json=1`, + `https://www.reddit.com/user/${authorName}/about.json?raw_json=1`, { method: "GET", headers: { - "User-Agent": env.EMBED_USER_AGENT + "User-Agent": env?.EMBED_USER_AGENT ?? "" }, - cf: { - cacheTtl: 60 * 60 * 24, - cacheEverything: true - } + ...CF_CACHE_OPTIONS } ); if (!profile_resp.ok) { - throw { code: resp.status, message: resp.statusText }; + throw { + code: profile_resp.status, + message: profile_resp.statusText + }; } const { data: profile_data } = (await profile_resp.json()) as Record; - return { - post_data: post_data[0].data.children[0].data, - profile_data - }; + if (!profile_data) { + throw { + code: 500, + message: "Reddit profile API returned unexpected structure" + }; + } + + return { post_data: postDataItem, profile_data }; } parsePostMedia( @@ -125,6 +148,8 @@ export class Reddit extends EmbedlyPlatform { }: any): Promise { return { platform: this.name, + color: [...this.color], + emoji: this.emoji, username: post_data.author, name: post_data.subreddit_name_prefixed, profile_url: `https://reddit.com/user/${post_data.author}`, @@ -147,9 +172,6 @@ export class Reddit extends EmbedlyPlatform { const embed = new Embed(data); - if (media.length > 10) { - media.length = 10; - } if (media.length > 0) { embed.setMedia(media); } diff --git a/packages/platforms/src/Threads.ts b/packages/platforms/src/Threads.ts index 7501178..c2f7d8e 100644 --- a/packages/platforms/src/Threads.ts +++ b/packages/platforms/src/Threads.ts @@ -1,29 +1,32 @@ import { Embed } from "@embedly/builder"; -import { - EMBEDLY_FAILED_PLATFORM, - EMBEDLY_FETCH_PLATFORM -} from "@embedly/logging"; -import { THREADS_REGEX } from "@embedly/parser"; import { type BaseEmbedData, - EmbedlyPlatformType -} from "@embedly/types"; -import { EmbedlyPlatform } from "./Platform.ts"; + type CloudflareEnv, + EmbedlyPlatform +} from "./Platform.ts"; +import { EmbedlyPlatformType } from "./types.ts"; +import { validateRegexMatch } from "./utils.ts"; const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; export class Threads extends EmbedlyPlatform { + readonly color = [0, 0, 0] as const; + readonly emoji = "<:threads:1413343483929956446>"; + readonly regex = + /threads\.net\/@.*\/post\/(?[A-Za-z0-9-_]+)/; + constructor() { - super(EmbedlyPlatformType.Threads, "threads", { - fetching: EMBEDLY_FETCH_PLATFORM(EmbedlyPlatformType.Threads), - failed: EMBEDLY_FAILED_PLATFORM(EmbedlyPlatformType.Threads) - }); + super(EmbedlyPlatformType.Threads, "threads"); } async parsePostId(url: string): Promise { - const match = THREADS_REGEX.exec(url)!; - const { thread_shortcode } = match.groups!; + const match = this.regex.exec(url); + validateRegexMatch( + match, + "Invalid Threads URL: could not extract shortcode" + ); + const { thread_shortcode } = match.groups; const thread_id = thread_shortcode .split("") .reduce( @@ -36,12 +39,9 @@ export class Threads extends EmbedlyPlatform { async fetchPost( thread_id: string, - env: { - EMBED_USER_AGENT: string; - THREADS_CSRF_TOKEN: string; - } + env?: Partial ): Promise { - const graphql = new URL(`https://www.threads.com/graphql/query`); + const graphql = new URL(`https://www.threads.net/graphql/query`); graphql.searchParams.set( "variables", JSON.stringify({ @@ -76,20 +76,31 @@ export class Threads extends EmbedlyPlatform { const resp = await fetch(graphql.toString(), { method: "POST", headers: { - "User-Agent": env.EMBED_USER_AGENT, + "User-Agent": env?.EMBED_USER_AGENT ?? "", "Content-Type": "application/x-www-form-urlencoded", "X-IG-App-ID": "238260118697367", - "X-CSRFTOKEN": env.THREADS_CSRF_TOKEN, + "X-CSRFTOKEN": env?.THREADS_CSRF_TOKEN ?? "", "X-FB-LSD": "UpH8MtbTBKi8Wbdbt_uZX3", "X-ASBD-ID": "359341", "Sec-Fetch-Site": "same-origin" } }); + if (!resp.ok) { throw { code: resp.status, message: resp.statusText }; } + const { data } = (await resp.json()) as Record; - return data.data.edges[0].node.thread_items[0].post; + const post = data?.data?.edges?.[0]?.node?.thread_items?.[0]?.post; + + if (!post) { + throw { + code: 500, + message: "Threads API returned unexpected structure" + }; + } + + return post; } parsePostMedia( @@ -128,12 +139,14 @@ export class Threads extends EmbedlyPlatform { transformRawData(raw_data: any): BaseEmbedData { return { platform: this.name, + color: [...this.color], + emoji: this.emoji, name: raw_data.user.full_name, username: raw_data.user.username, - profile_url: `https://threads.com/@${raw_data.user.username}`, + profile_url: `https://threads.net/@${raw_data.user.username}`, avatar_url: raw_data.user.profile_pic_url, timestamp: raw_data.taken_at, - url: `https://threads.com/@${raw_data.user.username}/post/${raw_data.code}`, + url: `https://threads.net/@${raw_data.user.username}/post/${raw_data.code}`, stats: { comments: raw_data.text_post_app_info.direct_reply_count, likes: raw_data.like_count, @@ -147,9 +160,6 @@ export class Threads extends EmbedlyPlatform { const embed = new Embed(this.transformRawData(post_data)); const media = this.parsePostMedia(post_data); if (media.length > 0) { - if (media.length > 10) { - media.length = 10; - } embed.setMedia(media); } diff --git a/packages/platforms/src/TikTok.ts b/packages/platforms/src/TikTok.ts index fa34b13..855e6e4 100644 --- a/packages/platforms/src/TikTok.ts +++ b/packages/platforms/src/TikTok.ts @@ -1,34 +1,42 @@ import { Embed } from "@embedly/builder"; -import { - EMBEDLY_FAILED_PLATFORM, - EMBEDLY_FETCH_PLATFORM -} from "@embedly/logging"; -import { TIKTOK_REGEX } from "@embedly/parser"; +import * as cheerio from "cheerio"; +import { CF_CACHE_OPTIONS } from "./constants.ts"; import { type BaseEmbedData, - EmbedlyPlatformType -} from "@embedly/types"; -import * as cheerio from "cheerio"; -import { EmbedlyPlatform } from "./Platform.ts"; + type CloudflareEnv, + EmbedlyPlatform +} from "./Platform.ts"; +import { EmbedlyPlatformType } from "./types.ts"; +import { validateRegexMatch } from "./utils.ts"; + +const TIKTOK_REGEX_MAIN = /(https?:\/\/)?(?:[\w-]+\.)*tiktok\.com/; + +const TIKTOK_REGEX_DETAIL = + /https:\/\/(?:m|www|vm)?\.?tiktok\.com\/(?@[\w.-]+)\/video\/(?\d+)/; export class TikTok extends EmbedlyPlatform { + readonly color = [57, 118, 132] as const; + readonly emoji = "<:tiktok:1386641825963708446>"; + readonly regex = TIKTOK_REGEX_MAIN; + constructor() { - super(EmbedlyPlatformType.TikTok, "tiktok", { - fetching: EMBEDLY_FETCH_PLATFORM(EmbedlyPlatformType.TikTok), - failed: EMBEDLY_FAILED_PLATFORM(EmbedlyPlatformType.TikTok) - }); + super(EmbedlyPlatformType.TikTok, "tiktok"); } async parsePostId(url: string): Promise { const req = await fetch(url, { redirect: "follow" }); - const match = TIKTOK_REGEX.exec(req.url)!; - const { tiktok_user, tiktok_id } = match.groups!; + const match = TIKTOK_REGEX_DETAIL.exec(req.url); + validateRegexMatch( + match, + "Invalid TikTok URL: could not extract user/id" + ); + const { tiktok_user, tiktok_id } = match.groups; return `${tiktok_user}/${tiktok_id}`; } async fetchPost( post_id: string, - env: { EMBED_USER_AGENT: string } + env?: Partial ): Promise { const [tiktok_user, tiktok_id] = post_id.split("/"); const resp = await fetch( @@ -36,28 +44,53 @@ export class TikTok extends EmbedlyPlatform { { method: "GET", headers: { - "User-Agent": env.EMBED_USER_AGENT + "User-Agent": env?.EMBED_USER_AGENT ?? "" }, - cf: { - cacheTtl: 60 * 60 * 24, - cacheEverything: true - } + ...CF_CACHE_OPTIONS } ); + if (!resp.ok) { throw { code: resp.status, message: resp.statusText }; } + const html = await resp.text(); const $ = cheerio.load(html); const script = $("script#__UNIVERSAL_DATA_FOR_REHYDRATION__"); - const data = JSON.parse(script.text()); - return data.__DEFAULT_SCOPE__["webapp.video-detail"].itemInfo - .itemStruct; + const scriptText = script.text(); + + if (!scriptText) { + throw { + code: 500, + message: "TikTok page structure changed: missing data script" + }; + } + + let data: any; + try { + data = JSON.parse(scriptText); + } catch { + throw { code: 500, message: "Failed to parse TikTok data" }; + } + const itemStruct = + data?.__DEFAULT_SCOPE__?.["webapp.video-detail"]?.itemInfo + ?.itemStruct; + + if (!itemStruct) { + throw { + code: 500, + message: "TikTok page structure changed: missing video data" + }; + } + + return itemStruct; } transformRawData(raw_data: any): BaseEmbedData { return { platform: this.name, + color: [...this.color], + emoji: this.emoji, name: raw_data.author.nickname, username: raw_data.author.uniqueId, profile_url: `https://tiktok.com/@${raw_data.author.uniqueId}`, @@ -76,17 +109,18 @@ export class TikTok extends EmbedlyPlatform { async createEmbed(post_data: any): Promise { const embed = new Embed(this.transformRawData(post_data)); - const video = await fetch( - post_data.video.bitrateInfo[0].PlayAddr.UrlList[2], - { redirect: "follow" } - ); - embed.setMedia([ - { - media: { - url: video.url - } - } - ]); + + const videoUrl = + post_data?.video?.bitrateInfo?.[0]?.PlayAddr?.UrlList?.[2]; + + if (!videoUrl) { + return embed; + } + + try { + const video = await fetch(videoUrl, { redirect: "follow" }); + embed.setMedia([{ media: { url: video.url } }]); + } catch {} return embed; } diff --git a/packages/platforms/src/Twitter.ts b/packages/platforms/src/Twitter.ts index 7cfadb4..4dd43a9 100644 --- a/packages/platforms/src/Twitter.ts +++ b/packages/platforms/src/Twitter.ts @@ -1,44 +1,52 @@ import { Embed } from "@embedly/builder"; -import { - EMBEDLY_FAILED_PLATFORM, - EMBEDLY_FETCH_PLATFORM -} from "@embedly/logging"; -import { TWITTER_REGEX } from "@embedly/parser"; -import { - type BaseEmbedData, - EmbedlyPlatformType -} from "@embedly/types"; import he from "he"; -import { EmbedlyPlatform } from "./Platform.ts"; +import packageJSON from "../package.json" with { type: "json" }; +import { CF_CACHE_OPTIONS } from "./constants.ts"; +import { type BaseEmbedData, EmbedlyPlatform } from "./Platform.ts"; +import { EmbedlyPlatformType } from "./types.ts"; +import { validateRegexMatch } from "./utils.ts"; export class Twitter extends EmbedlyPlatform { + readonly color = [29, 161, 242] as const; + readonly emoji = "<:twitter:1386639732179599481>"; + readonly regex = + /(?:twitter|x).com\/.*\/status(?:es)?\/(?[^/?]+)/; + constructor() { - super(EmbedlyPlatformType.Twitter, "tweet", { - fetching: EMBEDLY_FETCH_PLATFORM(EmbedlyPlatformType.Twitter), - failed: EMBEDLY_FAILED_PLATFORM(EmbedlyPlatformType.Twitter) - }); + super(EmbedlyPlatformType.Twitter, "tweet"); } async parsePostId(url: string): Promise { - const match = TWITTER_REGEX.exec(url)!; - const { tweet_id } = match.groups!; + const match = this.regex.exec(url); + validateRegexMatch( + match, + "Invalid Twitter URL: could not extract tweet ID" + ); + const { tweet_id } = match.groups; return tweet_id; } async fetchPost(tweet_id: string): Promise { - const { tweet, code, message } = await fetch( - `https://api.fxtwitter.com/embedly/status/${tweet_id}`, + const resp = await fetch( + `https://api.fxtwitter.com/embedly/status/${tweet_id}/en`, { method: "GET", headers: { - "User-Agent": "Embedly/0.0.1" + "User-Agent": `Embedly/${packageJSON.version}` }, - cf: { - cacheTtl: 60 * 60 * 24, - cacheEverything: true - } + ...CF_CACHE_OPTIONS } - ).then((r) => r.json() as Record); + ); + + if (!resp.ok) { + throw { code: resp.status, message: resp.statusText }; + } + + const { tweet, code, message } = (await resp.json()) as Record< + string, + any + >; + if (code !== 200) { throw { code, message }; } @@ -48,6 +56,11 @@ export class Twitter extends EmbedlyPlatform { enrichTweetText(text_data: Record) { let text = text_data.text as string; + + if (!text_data.facets || !Array.isArray(text_data.facets)) { + return he.decode(text); + } + for (const facet of text_data.facets) { if (facet.type === "url") { text = text.replace(facet.original, facet.replacement); @@ -69,6 +82,8 @@ export class Twitter extends EmbedlyPlatform { transformRawData(raw_data: any): BaseEmbedData { return { platform: this.name, + color: [...this.color], + emoji: this.emoji, name: raw_data.author.name, username: raw_data.author.screen_name, profile_url: `https://x.com/${raw_data.author.screen_name}`, @@ -89,6 +104,9 @@ export class Twitter extends EmbedlyPlatform { if (tweet_data.text !== "") { embed.setDescription(this.enrichTweetText(tweet_data.raw_text)); } + if (tweet_data.translation?.text) { + embed.setDescription(tweet_data.translation.text); + } if (tweet_data.media) { embed.setMedia( tweet_data.media.all.map((media: any) => ({ diff --git a/packages/platforms/src/constants.ts b/packages/platforms/src/constants.ts new file mode 100644 index 0000000..7ad4ae8 --- /dev/null +++ b/packages/platforms/src/constants.ts @@ -0,0 +1,9 @@ +export const CF_CACHE_OPTIONS = { + cf: { + cacheTtl: 60 * 60 * 24, + cacheEverything: true + } +} as const; + +export const GENERIC_LINK_REGEX = + /\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()[\]{};:'".,<>?«»""'']))/i; diff --git a/packages/platforms/src/main.ts b/packages/platforms/src/main.ts index a3560a2..807a94b 100644 --- a/packages/platforms/src/main.ts +++ b/packages/platforms/src/main.ts @@ -1,16 +1,54 @@ -import { EmbedlyPlatformType } from "@embedly/types"; import { CBC } from "./CBC.ts"; import { Instagram } from "./Instagram.ts"; import { Reddit } from "./Reddit.ts"; import { Threads } from "./Threads.ts"; import { TikTok } from "./TikTok.ts"; import { Twitter } from "./Twitter.ts"; +import { EmbedlyPlatformType } from "./types.ts"; -export default { +export const Platforms = { [EmbedlyPlatformType.Twitter]: new Twitter(), [EmbedlyPlatformType.Instagram]: new Instagram(), [EmbedlyPlatformType.TikTok]: new TikTok(), [EmbedlyPlatformType.CBC]: new CBC(), [EmbedlyPlatformType.Threads]: new Threads(), [EmbedlyPlatformType.Reddit]: new Reddit() -}; +} as const; + +export const EmbedlyPlatformColors = Object.fromEntries( + Object.entries(Platforms).map(([key, platform]) => [ + key, + platform.color + ]) +) as Record; + +export const platformEmojis = Object.fromEntries( + Object.entries(Platforms).map(([key, platform]) => [ + key, + platform.emoji + ]) +) as Record; + +export function getPlatformFromURL( + url: string +): null | { type: EmbedlyPlatformType } { + for (const [name, platform] of Object.entries(Platforms)) { + if (platform.matchesUrl(url)) { + return { type: name as EmbedlyPlatformType }; + } + } + return null; +} + +export { CF_CACHE_OPTIONS, GENERIC_LINK_REGEX } from "./constants.ts"; +export { type CloudflareEnv, EmbedlyPlatform } from "./Platform.ts"; +export { + EmbedlyPlatformType, + type Emojis, + emojis, + type StatEmojis, + statEmojis +} from "./types.ts"; +export { hasLink, isEscaped, isSpoiler } from "./utils.ts"; + +export default Platforms; diff --git a/packages/platforms/src/types.ts b/packages/platforms/src/types.ts new file mode 100644 index 0000000..25762dd --- /dev/null +++ b/packages/platforms/src/types.ts @@ -0,0 +1,43 @@ +export const EmbedlyPlatformType = { + Twitter: "Twitter", + Instagram: "Instagram", + TikTok: "TikTok", + CBC: "cbc.ca", + Threads: "Threads", + Reddit: "Reddit" +} as const; + +export type EmbedlyPlatformType = + (typeof EmbedlyPlatformType)[keyof typeof EmbedlyPlatformType]; + +export interface StatEmojis { + comments: string; + reposts: string; + likes: string; + bookmarks: string; + views: string; + reply: string; + quote: string; +} + +export const statEmojis: StatEmojis = { + comments: "<:comment:1386639521373753374>", + reposts: "<:repost:1386639564143198349>", + likes: "<:like:1386639662772391987>", + bookmarks: "<:bookmark:1386639640433529014>", + views: "<:view:1386639685237084292>", + reply: "<:reply:1386639619768058007>", + quote: "<:quote:1389657738480713838>" +}; + +export type Emojis = StatEmojis & Record; + +export const emojis: Emojis = { + ...statEmojis, + [EmbedlyPlatformType.Twitter]: "<:twitter:1386639732179599481>", + [EmbedlyPlatformType.Instagram]: "<:instagram:1386639712013254748>", + [EmbedlyPlatformType.TikTok]: "<:tiktok:1386641825963708446>", + [EmbedlyPlatformType.CBC]: "<:cbc:1409997044495683674>", + [EmbedlyPlatformType.Threads]: "<:threads:1413343483929956446>", + [EmbedlyPlatformType.Reddit]: "<:reddit:1461320093240655922>" +}; diff --git a/packages/platforms/src/utils.ts b/packages/platforms/src/utils.ts new file mode 100644 index 0000000..ed24efb --- /dev/null +++ b/packages/platforms/src/utils.ts @@ -0,0 +1,26 @@ +import { GENERIC_LINK_REGEX } from "./constants.ts"; + +export function hasLink(content: string): boolean { + return GENERIC_LINK_REGEX.test(content); +} + +export function isSpoiler(url: string, content: string): boolean { + return content + .split("||") + .some((part, ind) => ind % 2 === 1 && part.includes(url)); +} + +export function isEscaped(url: string, content: string): boolean { + return content.includes(`<${url}>`); +} + +export function validateRegexMatch( + match: RegExpExecArray | null, + errorMessage?: string +): asserts match is RegExpExecArray & { + groups: Record; +} { + if (match === null || match.groups === undefined) { + throw new Error(errorMessage ?? "Invalid URL: regex match failed"); + } +} diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md deleted file mode 100644 index f39eb73..0000000 --- a/packages/types/CHANGELOG.md +++ /dev/null @@ -1,117 +0,0 @@ -# @embedly/types - -## 0.11.0 - -## 0.10.0 - -### Patch Changes - -- [#36](https://github.com/embed-team/embedly/pull/36) [`dde5d23`](https://github.com/embed-team/embedly/commit/dde5d23d2d3705dc743fb772d602f64d85069f8c) Thanks [@ItsRauf](https://github.com/ItsRauf)! - feat(ci): split github actions across multiple files - -## 0.9.1 - -### Patch Changes - -- [#34](https://github.com/embed-team/embedly/pull/34) [`d8495b4`](https://github.com/embed-team/embedly/commit/d8495b40953bce13cb528dfb28f467cdbf19d07c) Thanks [@ItsRauf](https://github.com/ItsRauf)! - feat(ci): split github actions across multiple files - -## 0.9.0 - -## 0.8.0 - -## 0.7.1 - -## 0.7.0 - -## 0.6.0 - -### Minor Changes - -- [#22](https://github.com/embed-team/embedly/pull/22) [`327acfe`](https://github.com/embed-team/embedly/commit/327acfe0c405fd57b65643e881bd4978af47e0bb) Thanks [@ItsRauf](https://github.com/ItsRauf)! - refactor embed builder and fix delete command - -### Patch Changes - -- [#22](https://github.com/embed-team/embedly/pull/22) [`327acfe`](https://github.com/embed-team/embedly/commit/327acfe0c405fd57b65643e881bd4978af47e0bb) Thanks [@ItsRauf](https://github.com/ItsRauf)! - replies now show in the correct order - -## 0.5.3 - -### Patch Changes - -- [#20](https://github.com/embed-team/embedly/pull/20) [`455d9b1`](https://github.com/embed-team/embedly/commit/455d9b19b0c8662a85799557fedd8b9bfae2cc33) Thanks [@ItsRauf](https://github.com/ItsRauf)! - sane spoiler and escape detection - -## 0.5.2 - -## 0.5.1 - -## 0.5.0 - -## 0.4.1 - -### Patch Changes - -- [#8](https://github.com/embed-team/embedly/pull/8) [`4386504`](https://github.com/embed-team/embedly/commit/438650487368ec722bc75801ccf5f495be62a485) Thanks [@ItsRauf](https://github.com/ItsRauf)! - fix broken spoiler regex - -## 0.4.0 - -### Minor Changes - -- [#6](https://github.com/embed-team/embedly/pull/6) [`14785b7`](https://github.com/embed-team/embedly/commit/14785b70759445b7d402a3d63bca72993239f5b3) Thanks [@ItsRauf](https://github.com/ItsRauf)! - add a spoiler flag and support masked links in messages - -## 0.3.3 - -### Patch Changes - -- [`6d3fccd`](https://github.com/embed-team/embedly/commit/6d3fccd8190a8b697e0ee93edc8c81affb036f01) Thanks [@ItsRauf](https://github.com/ItsRauf)! - added DX and CI tooling - -## 0.3.2 - -### Patch Changes - -- fixed title for cbc embeds where username doesn't exist - -## 0.3.1 - -### Patch Changes - -- escape markdown in embed description - -## 0.3.0 - -### Minor Changes - -- added threads and fixed a few emoji issues - -## 0.2.0 - -### Minor Changes - -- added cbc.ca, fixed tiktok, added media proxy - -## 0.1.4 - -### Patch Changes - -- added a fallback error for random failures and added multi-embedding for messages - -## 0.1.3 - -### Patch Changes - -- fixed tiktok embeds and added new instagram share link handling - -## 0.1.2 - -### Patch Changes - -- rewrote api and bot embed handling to make the api only handle scraping - -## 0.1.1 - -### Patch Changes - -- added sidebar color to containers - -## 0.1.0 - -### Minor Changes - -- this is the first oss version of embedly :3 diff --git a/packages/types/biome.json b/packages/types/biome.json deleted file mode 100644 index 167b18f..0000000 --- a/packages/types/biome.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "root": false, - "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", - "extends": ["@embedly/config/biome.json"] -} diff --git a/packages/types/package.json b/packages/types/package.json deleted file mode 100644 index 416da01..0000000 --- a/packages/types/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "@embedly/types", - "version": "0.11.0", - "private": true, - "type": "module", - "main": "./dist/main.js", - "module": "./dist/main.js", - "types": "./dist/main.d.ts", - "exports": { - "import": { - "types": "./dist/main.d.ts", - "default": "./dist/main.js" - } - }, - "scripts": { - "lint": "biome ci ./src/*", - "build": "pkgroll --sourcemap=inline && tsc" - }, - "devDependencies": { - "@biomejs/biome": "2.3.10", - "@embedly/config": "workspace:*", - "pkgroll": "^2.21.4", - "typescript": "^5.9.3" - }, - "dependencies": { - "discord-api-types": "^0.38.37" - } -} diff --git a/packages/types/src/main.ts b/packages/types/src/main.ts deleted file mode 100644 index b01958b..0000000 --- a/packages/types/src/main.ts +++ /dev/null @@ -1,70 +0,0 @@ -import type { APIMediaGalleryItem } from "discord-api-types/v10"; - -export enum EmbedlyPlatformType { - Twitter = "Twitter", - Instagram = "Instagram", - TikTok = "TikTok", - CBC = "cbc.ca", - Threads = "Threads", - Reddit = "Reddit" -} - -export const EmbedlyPlatformColors: Record< - EmbedlyPlatformType, - [red: number, blue: number, green: number] -> = { - [EmbedlyPlatformType.Twitter]: [29, 161, 242], - [EmbedlyPlatformType.Instagram]: [225, 48, 108], - [EmbedlyPlatformType.TikTok]: [57, 118, 132], - [EmbedlyPlatformType.CBC]: [215, 36, 42], - [EmbedlyPlatformType.Threads]: [0, 0, 0], - [EmbedlyPlatformType.Reddit]: [255, 86, 0] -}; - -export interface StatsData { - comments: number; - reposts?: number; - likes: number; - bookmarks?: number; - views?: number; -} - -export interface BaseEmbedData { - platform: EmbedlyPlatformType; - name: string; - username?: string; - profile_url?: string; - avatar_url: string; - url: string; - timestamp: number; - stats?: StatsData; - description?: string; - media?: APIMediaGalleryItem[]; -} -export type BaseEmbedDataWithoutPlatform = Omit< - BaseEmbedData, - "platform" ->; - -export type Emojis = { - [K in keyof Required]: string; -} & { [K in EmbedlyPlatformType]: string } & { - reply: string; - quote: string; -}; - -export const emojis: Emojis = { - comments: "<:comment:1386639521373753374>", - reposts: "<:repost:1386639564143198349>", - likes: "<:like:1386639662772391987>", - bookmarks: "<:bookmark:1386639640433529014>", - views: "<:view:1386639685237084292>", - reply: "<:reply:1386639619768058007>", - quote: "<:quote:1389657738480713838>", - [EmbedlyPlatformType.Twitter]: "<:twitter:1386639732179599481>", - [EmbedlyPlatformType.Instagram]: "<:instagram:1386639712013254748>", - [EmbedlyPlatformType.TikTok]: "<:tiktok:1386641825963708446>", - [EmbedlyPlatformType.CBC]: "<:cbc:1409997044495683674>", - [EmbedlyPlatformType.Threads]: "<:threads:1413343483929956446>", - [EmbedlyPlatformType.Reddit]: "<:reddit:1461320093240655922>" -}; diff --git a/packages/types/tsconfig.json b/packages/types/tsconfig.json deleted file mode 100644 index 3a138a9..0000000 --- a/packages/types/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "@embedly/config/tsconfig.json", - "compilerOptions": { - "lib": ["WebWorker", "ESNext"], - "outDir": "./dist" - }, - "exclude": ["node_modules"], - "include": ["src"] -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f795203..092b2e8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,19 +16,19 @@ importers: version: 0.5.2 '@changesets/cli': specifier: 2.29.8 - version: 2.29.8(@types/node@25.0.3) + version: 2.29.8(@types/node@25.0.9) '@changesets/config': specifier: ^3.1.2 version: 3.1.2 '@commitlint/cli': specifier: ^20.2.0 - version: 20.2.0(@types/node@25.0.3)(typescript@5.9.3) + version: 20.2.0(@types/node@25.0.9)(typescript@5.9.3) '@embedly/config': specifier: workspace:* version: link:packages/config commitlint: specifier: ^20.2.0 - version: 20.2.0(@types/node@25.0.3)(typescript@5.9.3) + version: 20.2.0(@types/node@25.0.9)(typescript@5.9.3) commitlint-config-gitmoji: specifier: ^2.3.1 version: 2.3.1 @@ -56,21 +56,9 @@ importers: '@embedly/logging': specifier: workspace:* version: link:../../packages/logging - '@embedly/parser': - specifier: workspace:* - version: link:../../packages/parser '@embedly/platforms': specifier: workspace:* version: link:../../packages/platforms - '@embedly/types': - specifier: workspace:* - version: link:../../packages/types - '@logtail/edge': - specifier: ^0.5.7 - version: 0.5.7(@cloudflare/workers-types@4.20251220.0) - '@logtail/node': - specifier: ^0.5.6 - version: 0.5.6 discord-verify: specifier: ^1.2.0 version: 1.2.0 @@ -78,8 +66,8 @@ importers: specifier: ^1.4.19 version: 1.4.19(@sinclair/typebox@0.34.38)(@types/bun@1.3.5)(exact-mirror@0.1.2(@sinclair/typebox@0.34.38))(file-type@21.0.0)(openapi-types@12.1.3)(typescript@5.9.3) wrangler: - specifier: ^4.56.0 - version: 4.56.0(@cloudflare/workers-types@4.20251220.0) + specifier: ^4.59.1 + version: 4.59.1 devDependencies: '@biomejs/biome': specifier: 2.3.10 @@ -87,6 +75,12 @@ importers: '@embedly/config': specifier: workspace:* version: link:../../packages/config + '@microlabs/otel-cf-workers': + specifier: 1.0.0-rc.52 + version: 1.0.0-rc.52(@opentelemetry/api@1.9.0) + '@opentelemetry/api': + specifier: ^1.9.0 + version: 1.9.0 '@types/node': specifier: ^25.0.3 version: 25.0.3 @@ -117,9 +111,6 @@ importers: '@elysiajs/eden': specifier: ^1.4.5 version: 1.4.5(elysia@1.4.19(@sinclair/typebox@0.34.38)(@types/bun@1.3.5)(exact-mirror@0.1.2(@sinclair/typebox@0.34.38))(file-type@21.0.0)(openapi-types@12.1.3)(typescript@5.9.3)) - '@logtail/node': - specifier: ^0.5.6 - version: 0.5.6 '@sapphire/discord.js-utilities': specifier: ^7.3.3 version: 7.3.3 @@ -148,12 +139,48 @@ importers: '@embedly/logging': specifier: workspace:* version: link:../../packages/logging - '@embedly/parser': - specifier: workspace:* - version: link:../../packages/parser '@embedly/platforms': specifier: workspace:* version: link:../../packages/platforms + '@opentelemetry/api': + specifier: ^1.9.0 + version: 1.9.0 + '@opentelemetry/api-logs': + specifier: ^0.211.0 + version: 0.211.0 + '@opentelemetry/exporter-logs-otlp-http': + specifier: ^0.211.0 + version: 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-grpc': + specifier: ^0.211.0 + version: 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-http': + specifier: ^0.211.0 + version: 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-grpc': + specifier: ^0.211.0 + version: 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-http': + specifier: ^0.211.0 + version: 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-http': + specifier: ^0.211.0 + version: 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': + specifier: ^2.5.0 + version: 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': + specifier: ^0.211.0 + version: 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': + specifier: ^2.5.0 + version: 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-node': + specifier: ^0.211.0 + version: 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': + specifier: ^1.39.0 + version: 1.39.0 '@types/node': specifier: ^25.0.3 version: 25.0.3 @@ -175,12 +202,6 @@ importers: '@discordjs/builders': specifier: ^1.13.1 version: 1.13.1 - '@embedly/parser': - specifier: workspace:* - version: link:../parser - '@embedly/types': - specifier: workspace:* - version: link:../types discord-api-types: specifier: ^0.38.37 version: 0.38.37 @@ -205,28 +226,10 @@ importers: version: 2.3.10 packages/logging: - devDependencies: - '@biomejs/biome': - specifier: 2.3.10 - version: 2.3.10 - '@embedly/config': - specifier: workspace:* - version: link:../config - '@embedly/types': - specifier: workspace:* - version: link:../types - pkgroll: - specifier: ^2.21.4 - version: 2.21.4(typescript@5.9.3) - typescript: - specifier: ^5.9.3 - version: 5.9.3 - - packages/parser: dependencies: - '@embedly/types': - specifier: workspace:* - version: link:../types + '@opentelemetry/api-logs': + specifier: ^0.211.0 + version: 0.211.0 devDependencies: '@biomejs/biome': specifier: 2.3.10 @@ -249,12 +252,6 @@ importers: '@embedly/logging': specifier: workspace:* version: link:../logging - '@embedly/parser': - specifier: workspace:* - version: link:../parser - '@embedly/types': - specifier: workspace:* - version: link:../types '@types/he': specifier: ^1.2.3 version: 1.2.3 @@ -284,25 +281,6 @@ importers: specifier: ^5.9.3 version: 5.9.3 - packages/types: - dependencies: - discord-api-types: - specifier: ^0.38.37 - version: 0.38.37 - devDependencies: - '@biomejs/biome': - specifier: 2.3.10 - version: 2.3.10 - '@embedly/config': - specifier: workspace:* - version: link:../config - pkgroll: - specifier: ^2.21.4 - version: 2.21.4(typescript@5.9.3) - typescript: - specifier: ^5.9.3 - version: 5.9.3 - packages: '@babel/code-frame@7.27.1': @@ -370,8 +348,8 @@ packages: cpu: [x64] os: [win32] - '@borewit/text-codec@0.1.1': - resolution: {integrity: sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==} + '@borewit/text-codec@0.2.1': + resolution: {integrity: sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw==} '@changesets/apply-release-plan@7.0.14': resolution: {integrity: sha512-ddBvf9PHdy2YY0OUiEl3TV78mH9sckndJR14QAt87KLEbIov81XO0q0QAmvooBxXlqRRP8I9B7XOzZwQG7JkWA==} @@ -438,8 +416,8 @@ packages: resolution: {integrity: sha512-Nu8ahitGFFJztxUml9oD/DLb7Z28C8cd8F46IVQ7y5Btz575pvMY8AqZsXkX7Gds29eCKdMgIHjIvzskHgPSFg==} engines: {node: '>=18.0.0'} - '@cloudflare/unenv-preset@2.7.13': - resolution: {integrity: sha512-NulO1H8R/DzsJguLC0ndMuk4Ufv0KSlN+E54ay9rn9ZCQo0kpAPwwh3LhgpZ96a3Dr6L9LqW57M4CqC34iLOvw==} + '@cloudflare/unenv-preset@2.9.0': + resolution: {integrity: sha512-99nEvuOTCGGGRNaIat8UVVXJ27aZK+U09SYDp0kVjQLwC9wyxcrQ28IqLwrQq2DjWLmBI1+UalGJzdPqYgPlRw==} peerDependencies: unenv: 2.0.0-rc.24 workerd: ^1.20251202.0 @@ -447,32 +425,32 @@ packages: workerd: optional: true - '@cloudflare/workerd-darwin-64@1.20251217.0': - resolution: {integrity: sha512-DN6vT+9ho61d/1/YuILW4VS+N1JBLaixWRL1vqNmhgbf8J8VHwWWotrRruEUYigJKx2yZyw6YsasE+yLXgx/Fw==} + '@cloudflare/workerd-darwin-64@1.20260111.0': + resolution: {integrity: sha512-UGAjrGLev2/CMLZy7b+v1NIXA4Hupc/QJBFlJwMqldywMcJ/iEqvuUYYuVI2wZXuXeWkgmgFP87oFDQsg78YTQ==} engines: {node: '>=16'} cpu: [x64] os: [darwin] - '@cloudflare/workerd-darwin-arm64@1.20251217.0': - resolution: {integrity: sha512-5nZOpRTkHmtcTc4Wbr1mj/O3dLb6aHZSiJuVBgtdbVcVmOXueSay3hnw1PXEyR+vpTKGUPkM+omUIslKHWnXDw==} + '@cloudflare/workerd-darwin-arm64@1.20260111.0': + resolution: {integrity: sha512-YFAZwidLCQVa6rKCCaiWrhA+eh87a7MUhyd9lat3KSbLBAGpYM+ORpyTXpi2Gjm3j6Mp1e/wtzcFTSeMIy2UqA==} engines: {node: '>=16'} cpu: [arm64] os: [darwin] - '@cloudflare/workerd-linux-64@1.20251217.0': - resolution: {integrity: sha512-uoPGhMaZVXPpCsU0oG3HQzyVpXCGi5rU+jcHRjUI7DXM4EwctBGvZ380Knkja36qtl+ZvSKVR1pUFSGdK+45Pg==} + '@cloudflare/workerd-linux-64@1.20260111.0': + resolution: {integrity: sha512-zx1GW6FwfOBjCV7QUCRzGRkViUtn3Is/zaaVPmm57xyy9sjtInx6/SdeBr2Y45tx9AnOP1CnaOFFdmH1P7VIEg==} engines: {node: '>=16'} cpu: [x64] os: [linux] - '@cloudflare/workerd-linux-arm64@1.20251217.0': - resolution: {integrity: sha512-ixHnHKsiz1Xko+eDgCJOZ7EEUZKtmnYq3AjW3nkVcLFypSLks4C29E45zVewdaN4wq8sCLeyQCl6r1kS17+DQQ==} + '@cloudflare/workerd-linux-arm64@1.20260111.0': + resolution: {integrity: sha512-wFVKxNvCyjRaAcgiSnJNJAmIos3p3Vv6Uhf4pFUZ9JIxr69GNlLWlm9SdCPvtwNFAjzSoDaKzDwjj5xqpuCS6Q==} engines: {node: '>=16'} cpu: [arm64] os: [linux] - '@cloudflare/workerd-windows-64@1.20251217.0': - resolution: {integrity: sha512-rP6USX+7ctynz3AtmKi+EvlLP3Xdr1ETrSdcnv693/I5QdUwBxq4yE1Lj6CV7GJizX6opXKYg8QMq0Q4eB9zRQ==} + '@cloudflare/workerd-windows-64@1.20260111.0': + resolution: {integrity: sha512-zWgd77L7OI1BxgBbG+2gybDahIMgPX5iNo6e3LqcEz1Xm3KfiqgnDyMBcxeQ7xDrj7fHUGAlc//QnKvDchuUoQ==} engines: {node: '>=16'} cpu: [x64] os: [win32] @@ -594,8 +572,8 @@ packages: peerDependencies: elysia: '>= 1.4.0' - '@emnapi/runtime@1.7.1': - resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} + '@emnapi/runtime@1.8.1': + resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} '@esbuild/aix-ppc64@0.26.0': resolution: {integrity: sha512-hj0sKNCQOOo2fgyII3clmJXP28VhgDfU5iy3GNHlWO76KG6N7x4D9ezH5lJtQTG+1J6MFDAJXC1qsI+W+LvZoA==} @@ -1074,107 +1052,148 @@ packages: '@gitmoji/parser-opts@1.4.0': resolution: {integrity: sha512-zzmx/vtpdB/ijjUm7u9OzHNCXWKpSbzVEgVzOzhilMgoTBlUDyInZFUtiCTV+Wf4oCP9nxGa/kQGQFfN+XLH1g==} - '@img/sharp-darwin-arm64@0.33.5': - resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} + '@grpc/grpc-js@1.14.3': + resolution: {integrity: sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==} + engines: {node: '>=12.10.0'} + + '@grpc/proto-loader@0.8.0': + resolution: {integrity: sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==} + engines: {node: '>=6'} + hasBin: true + + '@img/colour@1.0.0': + resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [darwin] - '@img/sharp-darwin-x64@0.33.5': - resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [darwin] - '@img/sharp-libvips-darwin-arm64@1.0.4': - resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} cpu: [arm64] os: [darwin] - '@img/sharp-libvips-darwin-x64@1.0.4': - resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} cpu: [x64] os: [darwin] - '@img/sharp-libvips-linux-arm64@1.0.4': - resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linux-arm@1.0.5': - resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] - '@img/sharp-libvips-linux-s390x@1.0.4': - resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] - '@img/sharp-libvips-linux-x64@1.0.4': - resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] - '@img/sharp-libvips-linuxmusl-arm64@1.0.4': - resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linuxmusl-x64@1.0.4': - resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] - '@img/sharp-linux-arm64@0.33.5': - resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - '@img/sharp-linux-arm@0.33.5': - resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] - '@img/sharp-linux-s390x@0.33.5': - resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] - '@img/sharp-linux-x64@0.33.5': - resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - '@img/sharp-linuxmusl-arm64@0.33.5': - resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - '@img/sharp-linuxmusl-x64@0.33.5': - resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - '@img/sharp-wasm32@0.33.5': - resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [wasm32] - '@img/sharp-win32-ia32@0.33.5': - resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ia32] os: [win32] - '@img/sharp-win32-x64@0.33.5': - resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [win32] @@ -1202,22 +1221,8 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - '@logtail/core@0.5.6': - resolution: {integrity: sha512-wbExeqFgH8mfEJ0N1X1KmBZVmh1oGyQLtHo329O9TvDqyL3tmyQnXbtEHwB0whfCKrkzHPZvWAamcv/Y8uPOZw==} - - '@logtail/edge@0.5.7': - resolution: {integrity: sha512-joxu8ygUOc6VuQyhSK6dRhqi5VS7lNIFUfy/m14mcmFJZ4hPplAI8qwoQ23UPkDmClyytkgLeNIH/FTatrrV6g==} - peerDependencies: - '@cloudflare/workers-types': ^4.20230904.0 - - '@logtail/node@0.5.6': - resolution: {integrity: sha512-j+Q/LXVrZa6p+7qfNtiT5ANqmrusrSpBXU5Cxe6Wm+tB48NLaNdOiFz1EgqgTW50WzwlTHYl7ZhfPGsYgmyMOA==} - - '@logtail/tools@0.5.6': - resolution: {integrity: sha512-l6VIzOUGZs6eLIWjZD88hNyE61xjRN0xypvFvY7CGl9nH+MlBSoFobA8XPTB+JpU1Zn3UhovOJrRM2SAoVyq4Q==} - - '@logtail/types@0.5.6': - resolution: {integrity: sha512-a7tkl2rBE24SoV1dG3Jme6sfSRtQjSonlVym0PvoBHFbretqm0FB1SQuba27zOC9qH0kcYtyJGkoFi4vpJE/tQ==} + '@js-sdsl/ordered-map@4.4.2': + resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} '@manypkg/find-root@1.1.0': resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} @@ -1225,9 +1230,10 @@ packages: '@manypkg/get-packages@1.1.3': resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} - '@msgpack/msgpack@2.8.0': - resolution: {integrity: sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==} - engines: {node: '>= 10'} + '@microlabs/otel-cf-workers@1.0.0-rc.52': + resolution: {integrity: sha512-7jf4sBNoaZCz2RSayefcvc/CZX0PDVA9aVk44bHpUebLrap+xjzDDw4T03klcYrBl/FdC7NamwvqjUMCtYdBvQ==} + peerDependencies: + '@opentelemetry/api': ~1.9.0 '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -1241,6 +1247,232 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@opentelemetry/api-logs@0.200.0': + resolution: {integrity: sha512-IKJBQxh91qJ+3ssRly5hYEJ8NDHu9oY/B1PXVSCWf7zytmYO9RNLB0Ox9XQ/fJ8m6gY6Q6NtBWlmXfaXt5Uc4Q==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/api-logs@0.211.0': + resolution: {integrity: sha512-swFdZq8MCdmdR22jTVGQDhwqDzcI4M10nhjXkLr1EsIzXgZBqm4ZlmmcWsg3TSNf+3mzgOiqveXmBLZuDi2Lgg==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/api@1.9.0': + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/configuration@0.211.0': + resolution: {integrity: sha512-PNsCkzsYQKyv8wiUIsH+loC4RYyblOaDnVASBtKS22hK55ToWs2UP6IsrcfSWWn54wWTvVe2gnfwz67Pvrxf2Q==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + + '@opentelemetry/context-async-hooks@2.5.0': + resolution: {integrity: sha512-uOXpVX0ZjO7heSVjhheW2XEPrhQAWr2BScDPoZ9UDycl5iuHG+Usyc3AIfG6kZeC1GyLpMInpQ6X5+9n69yOFw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@2.0.0': + resolution: {integrity: sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@2.5.0': + resolution: {integrity: sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/exporter-logs-otlp-grpc@0.211.0': + resolution: {integrity: sha512-UhOoWENNqyaAMP/dL1YXLkXt6ZBtovkDDs1p4rxto9YwJX1+wMjwg+Obfyg2kwpcMoaiIFT3KQIcLNW8nNGNfQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-logs-otlp-http@0.211.0': + resolution: {integrity: sha512-c118Awf1kZirHkqxdcF+rF5qqWwNjJh+BB1CmQvN9AQHC/DUIldy6dIkJn3EKlQnQ3HmuNRKc/nHHt5IusN7mA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-logs-otlp-proto@0.211.0': + resolution: {integrity: sha512-kMvfKMtY5vJDXeLnwhrZMEwhZ2PN8sROXmzacFU/Fnl4Z79CMrOaL7OE+5X3SObRYlDUa7zVqaXp9ZetYCxfDQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-metrics-otlp-grpc@0.211.0': + resolution: {integrity: sha512-D/U3G8L4PzZp8ot5hX9wpgbTymgtLZCiwR7heMe4LsbGV4OdctS1nfyvaQHLT6CiGZ6FjKc1Vk9s6kbo9SWLXQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-metrics-otlp-http@0.211.0': + resolution: {integrity: sha512-lfHXElPAoDSPpPO59DJdN5FLUnwi1wxluLTWQDayqrSPfWRnluzxRhD+g7rF8wbj1qCz0sdqABl//ug1IZyWvA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-metrics-otlp-proto@0.211.0': + resolution: {integrity: sha512-61iNbffEpyZv/abHaz3BQM3zUtA2kVIDBM+0dS9RK68ML0QFLRGYa50xVMn2PYMToyfszEPEgFC3ypGae2z8FA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-prometheus@0.211.0': + resolution: {integrity: sha512-cD0WleEL3TPqJbvxwz5MVdVJ82H8jl8mvMad4bNU24cB5SH2mRW5aMLDTuV4614ll46R//R3RMmci26mc2L99g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-trace-otlp-grpc@0.211.0': + resolution: {integrity: sha512-eFwx4Gvu6LaEiE1rOd4ypgAiWEdZu7Qzm2QNN2nJqPW1XDeAVH1eNwVcVQl+QK9HR/JCDZ78PZgD7xD/DBDqbw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-trace-otlp-http@0.200.0': + resolution: {integrity: sha512-Goi//m/7ZHeUedxTGVmEzH19NgqJY+Bzr6zXo1Rni1+hwqaksEyJ44gdlEMREu6dzX1DlAaH/qSykSVzdrdafA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-trace-otlp-http@0.211.0': + resolution: {integrity: sha512-F1Rv3JeMkgS//xdVjbQMrI3+26e5SXC7vXA6trx8SWEA0OUhw4JHB+qeHtH0fJn46eFItrYbL5m8j4qi9Sfaxw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-trace-otlp-proto@0.211.0': + resolution: {integrity: sha512-DkjXwbPiqpcPlycUojzG2RmR0/SIK8Gi9qWO9znNvSqgzrnAIE9x2n6yPfpZ+kWHZGafvsvA1lVXucTyyQa5Kg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-zipkin@2.5.0': + resolution: {integrity: sha512-bk9VJgFgUAzkZzU8ZyXBSWiUGLOM3mZEgKJ1+jsZclhRnAoDNf+YBdq+G9R3cP0+TKjjWad+vVrY/bE/vRR9lA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/instrumentation-http@0.211.0': + resolution: {integrity: sha512-n0IaQ6oVll9PP84SjbOCwDjaJasWRHi6BLsbMLiT6tNj7QbVOkuA5sk/EfZczwI0j5uTKl1awQPivO/ldVtsqA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation@0.211.0': + resolution: {integrity: sha512-h0nrZEC/zvI994nhg7EgQ8URIHt0uDTwN90r3qQUdZORS455bbx+YebnGeEuFghUT0HlJSrLF4iHw67f+odY+Q==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-exporter-base@0.200.0': + resolution: {integrity: sha512-IxJgA3FD7q4V6gGq4bnmQM5nTIyMDkoGFGrBrrDjB6onEiq1pafma55V+bHvGYLWvcqbBbRfezr1GED88lacEQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-exporter-base@0.211.0': + resolution: {integrity: sha512-bp1+63V8WPV+bRI9EQG6E9YID1LIHYSZVbp7f+44g9tRzCq+rtw/o4fpL5PC31adcUsFiz/oN0MdLISSrZDdrg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-grpc-exporter-base@0.211.0': + resolution: {integrity: sha512-mR5X+N4SuphJeb7/K7y0JNMC8N1mB6gEtjyTLv+TSAhl0ZxNQzpSKP8S5Opk90fhAqVYD4R0SQSAirEBlH1KSA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-transformer@0.200.0': + resolution: {integrity: sha512-+9YDZbYybOnv7sWzebWOeK6gKyt2XE7iarSyBFkwwnP559pEevKOUD8NyDHhRjCSp13ybh9iVXlMfcj/DwF/yw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-transformer@0.211.0': + resolution: {integrity: sha512-julhCJ9dXwkOg9svuuYqqjXLhVaUgyUvO2hWbTxwjvLXX2rG3VtAaB0SzxMnGTuoCZizBT7Xqqm2V7+ggrfCXA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/propagator-b3@2.5.0': + resolution: {integrity: sha512-g10m4KD73RjHrSvUge+sUxUl8m4VlgnGc6OKvo68a4uMfaLjdFU+AULfvMQE/APq38k92oGUxEzBsAZ8RN/YHg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/propagator-jaeger@2.5.0': + resolution: {integrity: sha512-t70ErZCncAR/zz5AcGkL0TF25mJiK1FfDPEQCgreyAHZ+mRJ/bNUiCnImIBDlP3mSDXy6N09DbUEKq0ktW98Hg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/resources@2.0.0': + resolution: {integrity: sha512-rnZr6dML2z4IARI4zPGQV4arDikF/9OXZQzrC01dLmn0CZxU5U5OLd/m1T7YkGRj5UitjeoCtg/zorlgMQcdTg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/resources@2.5.0': + resolution: {integrity: sha512-F8W52ApePshpoSrfsSk1H2yJn9aKjCrbpQF1M9Qii0GHzbfVeFUB+rc3X4aggyZD8x9Gu3Slua+s6krmq6Dt8g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-logs@0.200.0': + resolution: {integrity: sha512-VZG870063NLfObmQQNtCVcdXXLzI3vOjjrRENmU37HYiPFa0ZXpXVDsTD02Nh3AT3xYJzQaWKl2X2lQ2l7TWJA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.10.0' + + '@opentelemetry/sdk-logs@0.211.0': + resolution: {integrity: sha512-O5nPwzgg2JHzo59kpQTPUOTzFi0Nv5LxryG27QoXBciX3zWM3z83g+SNOHhiQVYRWFSxoWn1JM2TGD5iNjOwdA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.10.0' + + '@opentelemetry/sdk-metrics@2.0.0': + resolution: {integrity: sha512-Bvy8QDjO05umd0+j+gDeWcTaVa1/R2lDj/eOvjzpm8VQj1K1vVZJuyjThpV5/lSHyYW2JaHF2IQ7Z8twJFAhjA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.9.0 <1.10.0' + + '@opentelemetry/sdk-metrics@2.5.0': + resolution: {integrity: sha512-BeJLtU+f5Gf905cJX9vXFQorAr6TAfK3SPvTFqP+scfIpDQEJfRaGJWta7sJgP+m4dNtBf9y3yvBKVAZZtJQVA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.9.0 <1.10.0' + + '@opentelemetry/sdk-node@0.211.0': + resolution: {integrity: sha512-+s1eGjoqmPCMptNxcJJD4IxbWJKNLOQFNKhpwkzi2gLkEbCj6LzSHJNhPcLeBrBlBLtlSpibM+FuS7fjZ8SSFQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@2.0.0': + resolution: {integrity: sha512-qQnYdX+ZCkonM7tA5iU4fSRsVxbFGml8jbxOgipRGMFHKaXKHQ30js03rTobYjKjIfnOsZSbHKWF0/0v0OQGfw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@2.5.0': + resolution: {integrity: sha512-VzRf8LzotASEyNDUxTdaJ9IRJ1/h692WyArDBInf5puLCjxbICD6XkHgpuudis56EndyS7LYFmtTMny6UABNdQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-node@2.5.0': + resolution: {integrity: sha512-O6N/ejzburFm2C84aKNrwJVPpt6HSTSq8T0ZUMq3xT2XmqT4cwxUItcL5UWGThYuq8RTcbH8u1sfj6dmRci0Ow==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/semantic-conventions@1.39.0': + resolution: {integrity: sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg==} + engines: {node: '>=14'} + '@pnpm/config.env-replace@1.1.0': resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} engines: {node: '>=12.22.0'} @@ -1265,6 +1497,36 @@ packages: '@posthog/core@1.9.1': resolution: {integrity: sha512-kRb1ch2dhQjsAapZmu6V66551IF2LnCbc1rnrQqnR7ArooVyJN9KOPXre16AJ3ObJz2eTfuP7x25BMyS2Y5Exw==} + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.4': + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + + '@protobufjs/eventemitter@1.1.0': + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + + '@protobufjs/fetch@1.1.0': + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/inquire@1.1.0': + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.0': + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + '@rollup/plugin-alias@6.0.0': resolution: {integrity: sha512-tPCzJOtS7uuVZd+xPhoy5W4vThe6KWXNmsFCNktaAh5RTqcLiSfT4huPQIXkgJ6YCOjJHvecOAzQxLFhPxKr+g==} engines: {node: '>=20.19.0'} @@ -1501,12 +1763,12 @@ packages: '@sinclair/typebox@0.34.38': resolution: {integrity: sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA==} - '@sindresorhus/is@7.1.1': - resolution: {integrity: sha512-rO92VvpgMc3kfiTjGT52LEtJ8Yc5kCWhZjLQ3LwlA4pSgPpQO7bVpYXParOD8Jwf+cVQECJo3yP/4I8aZtUQTQ==} + '@sindresorhus/is@7.2.0': + resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==} engines: {node: '>=18'} - '@speed-highlight/core@1.2.12': - resolution: {integrity: sha512-uilwrK0Ygyri5dToHYdZSjcvpS2ZwX0w5aSt3GCEN9hrjxWCoeV4Z2DTXuxjwbntaLQIEEAlCeNQss5SoHvAEA==} + '@speed-highlight/core@1.2.14': + resolution: {integrity: sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA==} '@tokenizer/inflate@0.2.7': resolution: {integrity: sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==} @@ -1554,6 +1816,9 @@ packages: '@types/node@25.0.3': resolution: {integrity: sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==} + '@types/node@25.0.9': + resolution: {integrity: sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==} + '@types/qs@6.14.0': resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} @@ -1569,9 +1834,6 @@ packages: '@types/serve-static@1.15.8': resolution: {integrity: sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==} - '@types/stack-trace@0.0.33': - resolution: {integrity: sha512-O7in6531Bbvlb2KEsJ0dq0CHZvc3iWSR5ZYMtvGgnHA56VgriAN/AU2LorfmcvAl2xc9N5fbCTRyMRRl8nd74g==} - '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} @@ -1583,6 +1845,11 @@ packages: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} hasBin: true + acorn-import-attributes@1.9.5: + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 + acorn-walk@8.3.2: resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} engines: {node: '>=0.4.0'} @@ -1592,6 +1859,11 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + agent-base@7.1.4: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} @@ -1662,9 +1934,6 @@ packages: atomically@2.0.3: resolution: {integrity: sha512-kU6FmrwZ3Lx7/7y3hPS5QnbJfaohcIul5fGqf7ok+4KklIEk9tJ0C2IQPdacSbVUWv6zVHXEBWoWd6NrVMT7Cw==} - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -1689,9 +1958,6 @@ packages: resolution: {integrity: sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==} engines: {node: '>=18'} - brace-expansion@2.0.2: - resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} - braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -1738,6 +2004,9 @@ packages: cjs-module-lexer@2.1.1: resolution: {integrity: sha512-+CmxIZ/L2vNcEfvNtLdU0ZQ6mbq3FZnwAP2PPTiKP+1QOoKwlKlPgb8UKV0Dds7QVaMnHm+FwSft2VB0s/SLjQ==} + cjs-module-lexer@2.2.0: + resolution: {integrity: sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==} + cli-boxes@3.0.0: resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} engines: {node: '>=10'} @@ -1773,13 +2042,6 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - color-string@1.9.1: - resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} - - color@4.2.3: - resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} - engines: {node: '>=12.5.0'} - commitlint-config-gitmoji@2.3.1: resolution: {integrity: sha512-T15ssbsyNc6szHlnGWo0/xvIA1mObqM70E9TwKNVTpksxhm+OdFht8hvDdKJAVi4nlZX5tcfTeILOi7SHBGH3w==} @@ -2129,6 +2391,9 @@ packages: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} + forwarded-parse@2.1.2: + resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==} + fs-extra@7.0.1: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} engines: {node: '>=6 <7 || >=8'} @@ -2256,6 +2521,9 @@ packages: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} + import-in-the-middle@2.0.6: + resolution: {integrity: sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw==} + import-meta-resolve@4.2.0: resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} @@ -2286,9 +2554,6 @@ packages: is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - is-arrayish@0.3.4: - resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} - is-core-module@2.16.1: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} @@ -2521,6 +2786,9 @@ packages: resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} engines: {node: '>=18'} + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + lru-cache@7.18.3: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} @@ -2570,18 +2838,17 @@ packages: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} - miniflare@4.20251217.0: - resolution: {integrity: sha512-8xsTQbPS6YV+ABZl9qiJIbsum6hbpbhqiyKpOVdzZrhK+1N8EFpT8R6aBZff7kezGmxYZSntjgjqTwJmj3JLgA==} + miniflare@4.20260111.0: + resolution: {integrity: sha512-pUsbDlumPaTzliA+J9HMAM74nLR8wqpCQNOESximab51jAfvL7ZaP5Npzh4PWNV0Jfq28tlqazakuJcw6w5qlA==} engines: {node: '>=18.0.0'} hasBin: true - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} - engines: {node: '>=16 || 14 >=14.17'} - minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + module-details-from-path@1.0.4: + resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} + mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -2778,6 +3045,14 @@ packages: proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + protobufjs@7.5.4: + resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} + engines: {node: '>=12.0.0'} + + protobufjs@8.0.0: + resolution: {integrity: sha512-jx6+sE9h/UryaCZhsJWbJtTEy47yXoGNYI4z8ZaRncM0zBKeRqjO2JEcOUYwrYGb1WLhXM1FfMzW3annvFv0rw==} + engines: {node: '>=12.0.0'} + proxy-agent@6.5.0: resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==} engines: {node: '>= 14'} @@ -2823,6 +3098,10 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + require-in-the-middle@8.0.1: + resolution: {integrity: sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==} + engines: {node: '>=9.3.0 || >=8.10.0 <9.0.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -2889,12 +3168,8 @@ packages: engines: {node: '>=10'} hasBin: true - serialize-error@8.1.0: - resolution: {integrity: sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==} - engines: {node: '>=10'} - - sharp@0.33.5: - resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} shebang-command@2.0.0: @@ -2912,9 +3187,6 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - simple-swizzle@0.2.4: - resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} - slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -2945,9 +3217,6 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - stack-trace@0.0.10: - resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} - stdin-discarder@0.2.2: resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} engines: {node: '>=18'} @@ -3029,8 +3298,8 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - token-types@6.1.1: - resolution: {integrity: sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==} + token-types@6.1.2: + resolution: {integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==} engines: {node: '>=14.16'} tr46@0.0.3: @@ -3081,10 +3350,6 @@ packages: resolution: {integrity: sha512-1dUGwi6cSSVZts1BwJa/Gh7w5dPNNGsNWZEAuRKxXWME44hTKWpQZrgiPnqMc5jJJOovzPK5N6tL+PHYRYL5Wg==} hasBin: true - type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - type-fest@0.21.3: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} @@ -3169,18 +3434,17 @@ packages: resolution: {integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==} engines: {node: '>=18'} - workerd@1.20251217.0: - resolution: {integrity: sha512-s3mHDSWwHTduyY8kpHOsl27ZJ4ziDBJlc18PfBvNMqNnhO7yBeemlxH7bo7yQyU1foJrIZ6IENHDDg0Z9N8zQA==} + workerd@1.20260111.0: + resolution: {integrity: sha512-ov6Pt4k6d/ALfJja/EIHohT9IrY/f6GAa0arWEPat2qekp78xHbVM7jSxNWAMbaE7ZmnQQIFEGD1ZhAWZmQKIg==} engines: {node: '>=16'} hasBin: true - wrangler@4.56.0: - resolution: {integrity: sha512-Nqi8duQeRbA+31QrD6QlWHW3IZVnuuRxMy7DEg46deUzywivmaRV/euBN5KKXDPtA24VyhYsK7I0tkb7P5DM2w==} + wrangler@4.59.1: + resolution: {integrity: sha512-5DddGSNxHd6dOjREWTDQdovQlZ1Lh80NNRXZFQ4/CrK3fNyVIBj9tqCs9pmXMNrKQ/AnKNeYzEs/l1kr8rHhOg==} engines: {node: '>=20.0.0'} - deprecated: Version 4.55.0 and 4.56.0 can incorrectly automatically delegate 'wrangler deploy' to 'opennextjs-cloudflare'. Use an older or newer version. hasBin: true peerDependencies: - '@cloudflare/workers-types': ^4.20251217.0 + '@cloudflare/workers-types': ^4.20260111.0 peerDependenciesMeta: '@cloudflare/workers-types': optional: true @@ -3256,8 +3520,8 @@ packages: youch@4.1.0-beta.10: resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==} - zod@3.22.3: - resolution: {integrity: sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==} + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} snapshots: @@ -3306,7 +3570,7 @@ snapshots: '@biomejs/cli-win32-x64@2.3.10': optional: true - '@borewit/text-codec@0.1.1': {} + '@borewit/text-codec@0.2.1': {} '@changesets/apply-release-plan@7.0.14': dependencies: @@ -3345,7 +3609,7 @@ snapshots: transitivePeerDependencies: - encoding - '@changesets/cli@2.29.8(@types/node@25.0.3)': + '@changesets/cli@2.29.8(@types/node@25.0.9)': dependencies: '@changesets/apply-release-plan': 7.0.14 '@changesets/assemble-release-plan': 6.0.9 @@ -3361,7 +3625,7 @@ snapshots: '@changesets/should-skip-package': 0.1.2 '@changesets/types': 6.1.0 '@changesets/write': 0.4.0 - '@inquirer/external-editor': 1.0.3(@types/node@25.0.3) + '@inquirer/external-editor': 1.0.3(@types/node@25.0.9) '@manypkg/get-packages': 1.1.3 ansi-colors: 4.1.3 ci-info: 3.9.0 @@ -3471,34 +3735,34 @@ snapshots: dependencies: mime: 3.0.0 - '@cloudflare/unenv-preset@2.7.13(unenv@2.0.0-rc.24)(workerd@1.20251217.0)': + '@cloudflare/unenv-preset@2.9.0(unenv@2.0.0-rc.24)(workerd@1.20260111.0)': dependencies: unenv: 2.0.0-rc.24 optionalDependencies: - workerd: 1.20251217.0 + workerd: 1.20260111.0 - '@cloudflare/workerd-darwin-64@1.20251217.0': + '@cloudflare/workerd-darwin-64@1.20260111.0': optional: true - '@cloudflare/workerd-darwin-arm64@1.20251217.0': + '@cloudflare/workerd-darwin-arm64@1.20260111.0': optional: true - '@cloudflare/workerd-linux-64@1.20251217.0': + '@cloudflare/workerd-linux-64@1.20260111.0': optional: true - '@cloudflare/workerd-linux-arm64@1.20251217.0': + '@cloudflare/workerd-linux-arm64@1.20260111.0': optional: true - '@cloudflare/workerd-windows-64@1.20251217.0': + '@cloudflare/workerd-windows-64@1.20260111.0': optional: true '@cloudflare/workers-types@4.20251220.0': {} - '@commitlint/cli@20.2.0(@types/node@25.0.3)(typescript@5.9.3)': + '@commitlint/cli@20.2.0(@types/node@25.0.9)(typescript@5.9.3)': dependencies: '@commitlint/format': 20.2.0 '@commitlint/lint': 20.2.0 - '@commitlint/load': 20.2.0(@types/node@25.0.3)(typescript@5.9.3) + '@commitlint/load': 20.2.0(@types/node@25.0.9)(typescript@5.9.3) '@commitlint/read': 20.2.0 '@commitlint/types': 20.2.0 tinyexec: 1.0.2 @@ -3540,7 +3804,7 @@ snapshots: '@commitlint/rules': 20.2.0 '@commitlint/types': 20.2.0 - '@commitlint/load@20.2.0(@types/node@25.0.3)(typescript@5.9.3)': + '@commitlint/load@20.2.0(@types/node@25.0.9)(typescript@5.9.3)': dependencies: '@commitlint/config-validator': 20.2.0 '@commitlint/execute-rule': 20.0.0 @@ -3548,7 +3812,7 @@ snapshots: '@commitlint/types': 20.2.0 chalk: 5.6.2 cosmiconfig: 9.0.0(typescript@5.9.3) - cosmiconfig-typescript-loader: 6.2.0(@types/node@25.0.3)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3) + cosmiconfig-typescript-loader: 6.2.0(@types/node@25.0.9)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 @@ -3687,7 +3951,7 @@ snapshots: dependencies: elysia: 1.4.19(@sinclair/typebox@0.34.38)(@types/bun@1.3.5)(exact-mirror@0.1.2(@sinclair/typebox@0.34.38))(file-type@21.0.0)(openapi-types@12.1.3)(typescript@5.9.3) - '@emnapi/runtime@1.7.1': + '@emnapi/runtime@1.8.1': dependencies: tslib: 2.8.1 optional: true @@ -3937,87 +4201,120 @@ snapshots: dependencies: '@gitmoji/gitmoji-regex': 1.0.0 - '@img/sharp-darwin-arm64@0.33.5': + '@grpc/grpc-js@1.14.3': + dependencies: + '@grpc/proto-loader': 0.8.0 + '@js-sdsl/ordered-map': 4.4.2 + + '@grpc/proto-loader@0.8.0': + dependencies: + lodash.camelcase: 4.3.0 + long: 5.3.2 + protobufjs: 7.5.4 + yargs: 17.7.2 + + '@img/colour@1.0.0': {} + + '@img/sharp-darwin-arm64@0.34.5': optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.0.4 + '@img/sharp-libvips-darwin-arm64': 1.2.4 optional: true - '@img/sharp-darwin-x64@0.33.5': + '@img/sharp-darwin-x64@0.34.5': optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.0.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': optional: true - '@img/sharp-libvips-darwin-arm64@1.0.4': + '@img/sharp-libvips-linux-arm64@1.2.4': optional: true - '@img/sharp-libvips-darwin-x64@1.0.4': + '@img/sharp-libvips-linux-arm@1.2.4': optional: true - '@img/sharp-libvips-linux-arm64@1.0.4': + '@img/sharp-libvips-linux-ppc64@1.2.4': optional: true - '@img/sharp-libvips-linux-arm@1.0.5': + '@img/sharp-libvips-linux-riscv64@1.2.4': optional: true - '@img/sharp-libvips-linux-s390x@1.0.4': + '@img/sharp-libvips-linux-s390x@1.2.4': optional: true - '@img/sharp-libvips-linux-x64@1.0.4': + '@img/sharp-libvips-linux-x64@1.2.4': optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': optional: true - '@img/sharp-libvips-linuxmusl-x64@1.0.4': + '@img/sharp-libvips-linuxmusl-x64@1.2.4': optional: true - '@img/sharp-linux-arm64@0.33.5': + '@img/sharp-linux-arm64@0.34.5': optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.0.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 optional: true - '@img/sharp-linux-arm@0.33.5': + '@img/sharp-linux-arm@0.34.5': optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.0.5 + '@img/sharp-libvips-linux-arm': 1.2.4 optional: true - '@img/sharp-linux-s390x@0.33.5': + '@img/sharp-linux-ppc64@0.34.5': optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.0.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 optional: true - '@img/sharp-linux-x64@0.33.5': + '@img/sharp-linux-riscv64@0.34.5': optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.0.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 optional: true - '@img/sharp-linuxmusl-arm64@0.33.5': + '@img/sharp-linux-s390x@0.34.5': optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 optional: true - '@img/sharp-linuxmusl-x64@0.33.5': + '@img/sharp-linux-x64@0.34.5': optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + '@img/sharp-libvips-linux-x64': 1.2.4 optional: true - '@img/sharp-wasm32@0.33.5': + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': dependencies: - '@emnapi/runtime': 1.7.1 + '@emnapi/runtime': 1.8.1 optional: true - '@img/sharp-win32-ia32@0.33.5': + '@img/sharp-win32-arm64@0.34.5': optional: true - '@img/sharp-win32-x64@0.33.5': + '@img/sharp-win32-ia32@0.34.5': optional: true - '@inquirer/external-editor@1.0.3(@types/node@25.0.3)': + '@img/sharp-win32-x64@0.34.5': + optional: true + + '@inquirer/external-editor@1.0.3(@types/node@25.0.9)': dependencies: chardet: 2.1.1 iconv-lite: 0.7.1 optionalDependencies: - '@types/node': 25.0.3 + '@types/node': 25.0.9 '@inquirer/figures@1.0.13': {} @@ -4030,36 +4327,7 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@logtail/core@0.5.6': - dependencies: - '@logtail/tools': 0.5.6 - '@logtail/types': 0.5.6 - serialize-error: 8.1.0 - - '@logtail/edge@0.5.7(@cloudflare/workers-types@4.20251220.0)': - dependencies: - '@cloudflare/workers-types': 4.20251220.0 - '@logtail/core': 0.5.6 - '@logtail/types': 0.5.6 - '@msgpack/msgpack': 2.8.0 - '@types/stack-trace': 0.0.33 - minimatch: 9.0.5 - stack-trace: 0.0.10 - - '@logtail/node@0.5.6': - dependencies: - '@logtail/core': 0.5.6 - '@logtail/types': 0.5.6 - '@msgpack/msgpack': 2.8.0 - '@types/stack-trace': 0.0.33 - minimatch: 9.0.5 - stack-trace: 0.0.10 - - '@logtail/tools@0.5.6': - dependencies: - '@logtail/types': 0.5.6 - - '@logtail/types@0.5.6': {} + '@js-sdsl/ordered-map@4.4.2': {} '@manypkg/find-root@1.1.0': dependencies: @@ -4077,7 +4345,16 @@ snapshots: globby: 11.1.0 read-yaml-file: 1.1.0 - '@msgpack/msgpack@2.8.0': {} + '@microlabs/otel-cf-workers@1.0.0-rc.52(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-http': 0.200.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.200.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.200.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 '@nodelib/fs.scandir@2.1.5': dependencies: @@ -4091,6 +4368,312 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 + '@opentelemetry/api-logs@0.200.0': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/api-logs@0.211.0': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/api@1.9.0': {} + + '@opentelemetry/configuration@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + yaml: 2.8.2 + + '@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/core@2.0.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.39.0 + + '@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.39.0 + + '@opentelemetry/exporter-logs-otlp-grpc@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.211.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-logs-otlp-http@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.211.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.211.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-logs-otlp-proto@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.211.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.5.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-metrics-otlp-grpc@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-http': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.5.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-metrics-otlp-http@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.5.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-metrics-otlp-proto@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-http': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.5.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-prometheus@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.5.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-trace-otlp-grpc@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.5.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-trace-otlp-http@0.200.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.200.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.200.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.0.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.0.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-trace-otlp-http@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.5.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-trace-otlp-proto@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.5.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-zipkin@2.5.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + + '@opentelemetry/instrumentation-http@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + forwarded-parse: 2.1.2 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.211.0 + import-in-the-middle: 2.0.6 + require-in-the-middle: 8.0.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/otlp-exporter-base@0.200.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.200.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-exporter-base@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.211.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-grpc-exporter-base@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.211.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-transformer@0.200.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.200.0 + '@opentelemetry/core': 2.0.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.0.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.200.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.0.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.0.0(@opentelemetry/api@1.9.0) + protobufjs: 7.5.4 + + '@opentelemetry/otlp-transformer@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.211.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.5.0(@opentelemetry/api@1.9.0) + protobufjs: 8.0.0 + + '@opentelemetry/propagator-b3@2.5.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/propagator-jaeger@2.5.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/resources@2.0.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + + '@opentelemetry/resources@2.5.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + + '@opentelemetry/sdk-logs@0.200.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.200.0 + '@opentelemetry/core': 2.0.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.0.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-logs@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.211.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-metrics@2.0.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.0.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-metrics@2.5.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-node@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.211.0 + '@opentelemetry/configuration': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/context-async-hooks': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-grpc': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-http': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-proto': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-grpc': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-http': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-proto': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-prometheus': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-grpc': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-http': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-proto': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-zipkin': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-b3': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-jaeger': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-node': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/sdk-trace-base@2.0.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.0.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + + '@opentelemetry/sdk-trace-base@2.5.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + + '@opentelemetry/sdk-trace-node@2.5.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.5.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/semantic-conventions@1.39.0': {} + '@pnpm/config.env-replace@1.1.0': {} '@pnpm/network.ca-file@1.0.2': @@ -4110,7 +4693,7 @@ snapshots: '@poppinss/dumper@0.6.5': dependencies: '@poppinss/colors': 4.1.6 - '@sindresorhus/is': 7.1.1 + '@sindresorhus/is': 7.2.0 supports-color: 10.2.2 '@poppinss/exception@1.2.3': {} @@ -4119,6 +4702,29 @@ snapshots: dependencies: cross-spawn: 7.0.6 + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.4': {} + + '@protobufjs/eventemitter@1.1.0': {} + + '@protobufjs/fetch@1.1.0': + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/inquire@1.1.0': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.0': {} + '@rollup/plugin-alias@6.0.0(rollup@4.53.5)': optionalDependencies: rollup: 4.53.5 @@ -4305,15 +4911,15 @@ snapshots: '@sinclair/typebox@0.34.38': {} - '@sindresorhus/is@7.1.1': {} + '@sindresorhus/is@7.2.0': {} - '@speed-highlight/core@1.2.12': {} + '@speed-highlight/core@1.2.14': {} '@tokenizer/inflate@0.2.7': dependencies: debug: 4.4.3 fflate: 0.8.2 - token-types: 6.1.1 + token-types: 6.1.2 transitivePeerDependencies: - supports-color @@ -4333,7 +4939,7 @@ snapshots: '@types/connect@3.4.38': dependencies: - '@types/node': 25.0.3 + '@types/node': 25.0.9 '@types/conventional-commits-parser@5.0.2': dependencies: @@ -4367,6 +4973,10 @@ snapshots: dependencies: undici-types: 7.16.0 + '@types/node@25.0.9': + dependencies: + undici-types: 7.16.0 + '@types/qs@6.14.0': {} '@types/range-parser@1.2.7': {} @@ -4376,7 +4986,7 @@ snapshots: '@types/send@0.17.5': dependencies: '@types/mime': 1.3.5 - '@types/node': 25.0.3 + '@types/node': 25.0.9 '@types/serve-static@1.15.8': dependencies: @@ -4384,8 +4994,6 @@ snapshots: '@types/node': 25.0.3 '@types/send': 0.17.5 - '@types/stack-trace@0.0.33': {} - '@types/ws@8.18.1': dependencies: '@types/node': 25.0.3 @@ -4397,10 +5005,16 @@ snapshots: jsonparse: 1.3.1 through: 2.3.8 + acorn-import-attributes@1.9.5(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + acorn-walk@8.3.2: {} acorn@8.14.0: {} + acorn@8.15.0: {} + agent-base@7.1.4: {} ajv-formats@3.0.1(ajv@8.17.1): @@ -4457,8 +5071,6 @@ snapshots: stubborn-fs: 1.2.5 when-exit: 2.1.4 - balanced-match@1.0.2: {} - base64-js@1.5.1: {} basic-ftp@5.0.5: {} @@ -4488,10 +5100,6 @@ snapshots: widest-line: 5.0.0 wrap-ansi: 9.0.2 - brace-expansion@2.0.2: - dependencies: - balanced-match: 1.0.2 - braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -4503,7 +5111,7 @@ snapshots: bun-types@1.3.5: dependencies: - '@types/node': 25.0.3 + '@types/node': 25.0.9 optional: true callsites@3.1.0: {} @@ -4548,6 +5156,8 @@ snapshots: cjs-module-lexer@2.1.1: {} + cjs-module-lexer@2.2.0: {} + cli-boxes@3.0.0: {} cli-cursor@3.1.0: @@ -4576,16 +5186,6 @@ snapshots: color-name@1.1.4: {} - color-string@1.9.1: - dependencies: - color-name: 1.1.4 - simple-swizzle: 0.2.4 - - color@4.2.3: - dependencies: - color-convert: 2.0.1 - color-string: 1.9.1 - commitlint-config-gitmoji@2.3.1: dependencies: '@commitlint/types': 17.8.1 @@ -4599,9 +5199,9 @@ snapshots: '@gitmoji/gitmoji-regex': 1.0.0 gitmojis: 3.15.0 - commitlint@20.2.0(@types/node@25.0.3)(typescript@5.9.3): + commitlint@20.2.0(@types/node@25.0.9)(typescript@5.9.3): dependencies: - '@commitlint/cli': 20.2.0(@types/node@25.0.3)(typescript@5.9.3) + '@commitlint/cli': 20.2.0(@types/node@25.0.9)(typescript@5.9.3) '@commitlint/types': 20.2.0 transitivePeerDependencies: - '@types/node' @@ -4651,9 +5251,9 @@ snapshots: cookie@1.1.1: {} - cosmiconfig-typescript-loader@6.2.0(@types/node@25.0.3)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3): + cosmiconfig-typescript-loader@6.2.0(@types/node@25.0.9)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3): dependencies: - '@types/node': 25.0.3 + '@types/node': 25.0.9 cosmiconfig: 9.0.0(typescript@5.9.3) jiti: 2.6.1 typescript: 5.9.3 @@ -4994,7 +5594,7 @@ snapshots: dependencies: '@tokenizer/inflate': 0.2.7 strtok3: 10.3.4 - token-types: 6.1.1 + token-types: 6.1.2 uint8array-extras: 1.5.0 transitivePeerDependencies: - supports-color @@ -5018,6 +5618,8 @@ snapshots: dependencies: fetch-blob: 3.2.0 + forwarded-parse@2.1.2: {} + fs-extra@7.0.1: dependencies: graceful-fs: 4.2.11 @@ -5051,7 +5653,7 @@ snapshots: dependencies: basic-ftp: 5.0.5 data-uri-to-buffer: 6.0.2 - debug: 4.4.1 + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -5158,6 +5760,13 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 + import-in-the-middle@2.0.6: + dependencies: + acorn: 8.15.0 + acorn-import-attributes: 1.9.5(acorn@8.15.0) + cjs-module-lexer: 2.2.0 + module-details-from-path: 1.0.4 + import-meta-resolve@4.2.0: {} inherits@2.0.4: {} @@ -5194,8 +5803,6 @@ snapshots: is-arrayish@0.2.1: {} - is-arrayish@0.3.4: {} - is-core-module@2.16.1: dependencies: hasown: 2.0.2 @@ -5369,6 +5976,8 @@ snapshots: chalk: 5.6.2 is-unicode-supported: 1.3.0 + long@5.3.2: {} + lru-cache@7.18.3: {} magic-bytes.js@1.12.1: {} @@ -5400,30 +6009,28 @@ snapshots: mimic-function@5.0.1: {} - miniflare@4.20251217.0: + miniflare@4.20260111.0: dependencies: '@cspotcode/source-map-support': 0.8.1 acorn: 8.14.0 acorn-walk: 8.3.2 exit-hook: 2.2.1 glob-to-regexp: 0.4.1 - sharp: 0.33.5 + sharp: 0.34.5 stoppable: 1.1.0 undici: 7.14.0 - workerd: 1.20251217.0 + workerd: 1.20260111.0 ws: 8.18.0 youch: 4.1.0-beta.10 - zod: 3.22.3 + zod: 3.25.76 transitivePeerDependencies: - bufferutil - utf-8-validate - minimatch@9.0.5: - dependencies: - brace-expansion: 2.0.2 - minimist@1.2.8: {} + module-details-from-path@1.0.4: {} + mri@1.2.0: {} ms@2.1.3: {} @@ -5541,7 +6148,7 @@ snapshots: ky: 1.10.0 registry-auth-token: 5.1.0 registry-url: 6.0.1 - semver: 7.7.2 + semver: 7.7.3 package-manager-detector@0.2.11: dependencies: @@ -5621,6 +6228,36 @@ snapshots: proto-list@1.2.4: {} + protobufjs@7.5.4: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 25.0.9 + long: 5.3.2 + + protobufjs@8.0.0: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 25.0.9 + long: 5.3.2 + proxy-agent@6.5.0: dependencies: agent-base: 7.1.4 @@ -5676,6 +6313,13 @@ snapshots: require-from-string@2.0.2: {} + require-in-the-middle@8.0.1: + dependencies: + debug: 4.4.3 + module-details-from-path: 1.0.4 + transitivePeerDependencies: + - supports-color + resolve-from@4.0.0: {} resolve-from@5.0.0: {} @@ -5752,35 +6396,36 @@ snapshots: semver@7.7.3: {} - serialize-error@8.1.0: - dependencies: - type-fest: 0.20.2 - - sharp@0.33.5: + sharp@0.34.5: dependencies: - color: 4.2.3 + '@img/colour': 1.0.0 detect-libc: 2.1.2 semver: 7.7.3 optionalDependencies: - '@img/sharp-darwin-arm64': 0.33.5 - '@img/sharp-darwin-x64': 0.33.5 - '@img/sharp-libvips-darwin-arm64': 1.0.4 - '@img/sharp-libvips-darwin-x64': 1.0.4 - '@img/sharp-libvips-linux-arm': 1.0.5 - '@img/sharp-libvips-linux-arm64': 1.0.4 - '@img/sharp-libvips-linux-s390x': 1.0.4 - '@img/sharp-libvips-linux-x64': 1.0.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 - '@img/sharp-libvips-linuxmusl-x64': 1.0.4 - '@img/sharp-linux-arm': 0.33.5 - '@img/sharp-linux-arm64': 0.33.5 - '@img/sharp-linux-s390x': 0.33.5 - '@img/sharp-linux-x64': 0.33.5 - '@img/sharp-linuxmusl-arm64': 0.33.5 - '@img/sharp-linuxmusl-x64': 0.33.5 - '@img/sharp-wasm32': 0.33.5 - '@img/sharp-win32-ia32': 0.33.5 - '@img/sharp-win32-x64': 0.33.5 + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 shebang-command@2.0.0: dependencies: @@ -5792,10 +6437,6 @@ snapshots: signal-exit@4.1.0: {} - simple-swizzle@0.2.4: - dependencies: - is-arrayish: 0.3.4 - slash@3.0.0: {} smart-buffer@4.2.0: {} @@ -5825,8 +6466,6 @@ snapshots: sprintf-js@1.0.3: {} - stack-trace@0.0.10: {} - stdin-discarder@0.2.2: {} stoppable@1.1.0: {} @@ -5891,9 +6530,9 @@ snapshots: dependencies: is-number: 7.0.0 - token-types@6.1.1: + token-types@6.1.2: dependencies: - '@borewit/text-codec': 0.1.1 + '@borewit/text-codec': 0.2.1 '@tokenizer/token': 0.3.0 ieee754: 1.2.1 @@ -5937,8 +6576,6 @@ snapshots: turbo-windows-64: 2.7.0 turbo-windows-arm64: 2.7.0 - type-fest@0.20.2: {} - type-fest@0.21.3: {} type-fest@4.41.0: {} @@ -6007,26 +6644,25 @@ snapshots: dependencies: string-width: 7.2.0 - workerd@1.20251217.0: + workerd@1.20260111.0: optionalDependencies: - '@cloudflare/workerd-darwin-64': 1.20251217.0 - '@cloudflare/workerd-darwin-arm64': 1.20251217.0 - '@cloudflare/workerd-linux-64': 1.20251217.0 - '@cloudflare/workerd-linux-arm64': 1.20251217.0 - '@cloudflare/workerd-windows-64': 1.20251217.0 + '@cloudflare/workerd-darwin-64': 1.20260111.0 + '@cloudflare/workerd-darwin-arm64': 1.20260111.0 + '@cloudflare/workerd-linux-64': 1.20260111.0 + '@cloudflare/workerd-linux-arm64': 1.20260111.0 + '@cloudflare/workerd-windows-64': 1.20260111.0 - wrangler@4.56.0(@cloudflare/workers-types@4.20251220.0): + wrangler@4.59.1: dependencies: '@cloudflare/kv-asset-handler': 0.4.1 - '@cloudflare/unenv-preset': 2.7.13(unenv@2.0.0-rc.24)(workerd@1.20251217.0) + '@cloudflare/unenv-preset': 2.9.0(unenv@2.0.0-rc.24)(workerd@1.20260111.0) blake3-wasm: 2.1.5 esbuild: 0.27.0 - miniflare: 4.20251217.0 + miniflare: 4.20260111.0 path-to-regexp: 6.3.0 unenv: 2.0.0-rc.24 - workerd: 1.20251217.0 + workerd: 1.20260111.0 optionalDependencies: - '@cloudflare/workers-types': 4.20251220.0 fsevents: 2.3.3 transitivePeerDependencies: - bufferutil @@ -6085,8 +6721,8 @@ snapshots: dependencies: '@poppinss/colors': 4.1.6 '@poppinss/dumper': 0.6.5 - '@speed-highlight/core': 1.2.12 + '@speed-highlight/core': 1.2.14 cookie: 1.1.1 youch-core: 0.3.3 - zod@3.22.3: {} + zod@3.25.76: {}