From 386e4f60dcb2eb057ad1435f1ad5aee71b89714e Mon Sep 17 00:00:00 2001 From: ElasticBottle Date: Thu, 18 Sep 2025 19:01:30 +0800 Subject: [PATCH 01/38] chore: add request headers --- apps/mentions/src/routes/api/$.ts | 10 ++++++---- apps/mentions/src/routes/api/rpc.$.ts | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/mentions/src/routes/api/$.ts b/apps/mentions/src/routes/api/$.ts index 357880c48..af10c9388 100644 --- a/apps/mentions/src/routes/api/$.ts +++ b/apps/mentions/src/routes/api/$.ts @@ -8,6 +8,9 @@ import { createServerFileRoute } from "@tanstack/react-start/server"; import { authServerHandler } from "~/lib/auth/server"; import { serverEnv } from "~/lib/env"; +const docsSearch = createDocsSearchServer(); +const blogSearch = createBlogSearchServer(); + async function handle({ request }: { request: Request }) { if (new URL(request.url).pathname.startsWith("/api/auth/")) { return await authServerHandler.handler(request); @@ -17,20 +20,19 @@ async function handle({ request }: { request: Request }) { if (request.method !== "GET") { return new Response("Method not allowed", { status: 405 }); } - const search = createDocsSearchServer(); - return await search.GET(request); + return await docsSearch.GET(request); } if (new URL(request.url).pathname.startsWith("/api/blog/search")) { if (request.method !== "GET") { return new Response("Method not allowed", { status: 405 }); } - const search = createBlogSearchServer(); - return await search.GET(request); + return await blogSearch.GET(request); } const env = serverEnv(); const context = createApiContext({ url: new URL(request.url), + reqHeaders: request.headers, }); const { response } = await openAPIHandler( diff --git a/apps/mentions/src/routes/api/rpc.$.ts b/apps/mentions/src/routes/api/rpc.$.ts index c0d2fa003..0911daf48 100644 --- a/apps/mentions/src/routes/api/rpc.$.ts +++ b/apps/mentions/src/routes/api/rpc.$.ts @@ -5,6 +5,7 @@ import { createServerFileRoute } from "@tanstack/react-start/server"; async function handle({ request }: { request: Request }) { const context = createApiContext({ url: new URL(request.url), + reqHeaders: request.headers, }); const { response } = await RpcHandler.handle(request, { From 95f311663734fd597f7342f9c5624ba50f60ce1a Mon Sep 17 00:00:00 2001 From: ElasticBottle Date: Thu, 18 Sep 2025 19:02:17 +0800 Subject: [PATCH 02/38] chore(crawler): remove apify reliance --- packages/crawler/package.json | 9 ++------ packages/crawler/src/site-crawl.ts | 12 +++++----- packages/crawler/sst.config.ts | 35 ------------------------------ 3 files changed, 7 insertions(+), 49 deletions(-) delete mode 100644 packages/crawler/sst.config.ts diff --git a/packages/crawler/package.json b/packages/crawler/package.json index b5031a594..ea0644446 100644 --- a/packages/crawler/package.json +++ b/packages/crawler/package.json @@ -4,7 +4,7 @@ "version": "0.0.1", "type": "module", "exports": { - ".": { + "./site-crawl": { "default": "./src/site-crawl.ts", "types": "./dist/site-crawl.d.ts" }, @@ -15,13 +15,11 @@ }, "scripts": { "build": "tsc", - "dev": "sst dev", "clean": "git clean -xdf .turbo node_modules dist .cache storage", "format": "bun x @biomejs/biome format . --write", "lint": "bun x @biomejs/biome lint . --write", "push": "APIFY_CLI_DISABLE_TELEMETRY=1 apify push", "start": "pnpm run start:dev", - "start:apify": "APIFY_CLI_DISABLE_TELEMETRY=1 apify run", "start:dev": "tsx src/site-crawl.ts", "start:prod": "node dist/site-crawl.js", "typecheck": "tsc --noEmit --emitDeclarationOnly false" @@ -30,17 +28,14 @@ "@rectangular-labs/typescript": "workspace:*", "@types/aws-lambda": "8.10.152", "@types/node": "^24.5.1", - "apify-cli": "^1.1.1", "typescript": "^5.9.2" }, "dependencies": { "@t3-oss/env-core": "^0.13.8", - "apify": "^3.4.5", "arktype": "^2.1.22", "crawlee": "^3.14.1", "glob": "^11.0.3", "gpt-tokenizer": "^3.0.1", - "playwright": "1.55.0", - "sst": "3.17.13" + "playwright": "1.55.0" } } diff --git a/packages/crawler/src/site-crawl.ts b/packages/crawler/src/site-crawl.ts index 34b2c902b..726133899 100644 --- a/packages/crawler/src/site-crawl.ts +++ b/packages/crawler/src/site-crawl.ts @@ -2,9 +2,8 @@ import { Buffer } from "node:buffer"; import type { PathLike } from "node:fs"; import { readFile, writeFile } from "node:fs/promises"; -import { Actor } from "apify"; import { type } from "arktype"; -import { log } from "crawlee"; +import { KeyValueStore, log, ProxyConfiguration } from "crawlee"; import { glob } from "glob"; import { isWithinTokenLimit } from "gpt-tokenizer"; import type { Page } from "playwright"; @@ -114,8 +113,7 @@ export async function write(config: Config) { return nextFileNameString; } -await Actor.init(); -const rawInput = await Actor.getInput(); +const rawInput = await KeyValueStore.getInput(); const userInput = SiteCrawlInputSchema.or(type.null)(rawInput); if (userInput instanceof type.errors) { @@ -149,7 +147,9 @@ const router = createCrawlSiteRouter({ match, exclude, }); -const proxyConfiguration = await Actor.createProxyConfiguration(); +const proxyConfiguration = new ProxyConfiguration({ + tieredProxyUrls: [[null]], +}); const crawler = createPlaywrightCrawler( maxRequestsPerCrawl, router, @@ -162,5 +162,3 @@ const startUrls = await parseStartingUrl({ }); await crawler.run(startUrls); console.timeEnd("Crawl"); - -await Actor.exit(); diff --git a/packages/crawler/sst.config.ts b/packages/crawler/sst.config.ts deleted file mode 100644 index 810542071..000000000 --- a/packages/crawler/sst.config.ts +++ /dev/null @@ -1,35 +0,0 @@ -/// - -export default $config({ - app(input) { - return { - name: "rl-site-crawler", - removal: input?.stage === "production" ? "remove" : "remove", - protect: ["production"].includes(input?.stage), - home: "aws", - providers: { - aws: { - profile: - input.stage === "production" - ? "rectangular-production" - : "rectangular-dev", - }, - }, - }; - }, - async run() { - const vpc = new sst.aws.Vpc("MyVpc"); - const cluster = new sst.aws.Cluster("MyCluster", { vpc }); - const task = new sst.aws.Task("MyTask", { - cluster, - image: { - context: "../../", - dockerfile: "../../Dockerfile", - }, - }); - - return await Promise.resolve({ - task, - }); - }, -}); From 23f21a581478c6abdfcff75af19550bde8b7fa3f Mon Sep 17 00:00:00 2001 From: ElasticBottle Date: Thu, 18 Sep 2025 19:02:48 +0800 Subject: [PATCH 03/38] chore: add new command to copy workspace package --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 557cdcd71..49c04faf7 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "env:encrypt": "bun x dotenvx encrypt", "ui:add": "cd ./packages/ui && bun run ui-add && cd ../..", "new:package": "turbo gen package", + "copy:package": "turbo gen workspace --copy", "format": "turbo run format --continue", "lint": "turbo run lint --continue", "typecheck": "turbo run typecheck --continue", From d8bc46a44aaed5588143ac40f360f3ebe01df784 Mon Sep 17 00:00:00 2001 From: ElasticBottle Date: Thu, 18 Sep 2025 20:09:04 +0800 Subject: [PATCH 04/38] chore(tasks): initial commit --- Dockerfile => apps/tasks/Dockerfile | 0 apps/tasks/package.json | 62 ++ apps/tasks/src/client.ts | 6 + apps/tasks/src/context.ts | 26 + apps/tasks/src/env.ts | 15 + apps/tasks/src/routes/index.ts | 6 + apps/tasks/src/routes/tasks.ts | 55 ++ apps/tasks/src/schema/passkey.ts | 108 +++ apps/tasks/src/server.ts | 26 + apps/tasks/src/subscriber.ts | 10 + apps/tasks/src/types.ts | 23 + apps/tasks/sst.config.ts | 47 + apps/tasks/tsconfig.json | 9 + pnpm-lock.yaml | 1297 +++++++++++---------------- 14 files changed, 904 insertions(+), 786 deletions(-) rename Dockerfile => apps/tasks/Dockerfile (100%) create mode 100644 apps/tasks/package.json create mode 100644 apps/tasks/src/client.ts create mode 100644 apps/tasks/src/context.ts create mode 100644 apps/tasks/src/env.ts create mode 100644 apps/tasks/src/routes/index.ts create mode 100644 apps/tasks/src/routes/tasks.ts create mode 100644 apps/tasks/src/schema/passkey.ts create mode 100644 apps/tasks/src/server.ts create mode 100644 apps/tasks/src/subscriber.ts create mode 100644 apps/tasks/src/types.ts create mode 100644 apps/tasks/sst.config.ts create mode 100644 apps/tasks/tsconfig.json diff --git a/Dockerfile b/apps/tasks/Dockerfile similarity index 100% rename from Dockerfile rename to apps/tasks/Dockerfile diff --git a/apps/tasks/package.json b/apps/tasks/package.json new file mode 100644 index 000000000..22342a131 --- /dev/null +++ b/apps/tasks/package.json @@ -0,0 +1,62 @@ +{ + "name": "tasks", + "private": true, + "version": "0.0.1", + "type": "module", + "exports": { + "./client": { + "default": "./src/client.ts" + }, + "./context": { + "default": "./src/context.ts" + }, + "./env": { + "default": "./src/env.ts" + }, + "./schemas/*": { + "default": "./src/schemas/*.ts" + }, + "./server": { + "default": "./src/server.ts" + }, + "./types": { + "default": "./src/types.ts" + } + }, + "scripts": { + "build": "tsc", + "dev": "sst dev", + "clean": "git clean -xdf .turbo node_modules dist .cache", + "dev:items": "tsx watch ./src/_open-api/open-api-resolver.ts", + "format": "pnpx @biomejs/biome format . --write", + "lint": "pnpx @biomejs/biome lint . --write", + "typecheck": "tsc --noEmit --emitDeclarationOnly false" + }, + "devDependencies": { + "@rectangular-labs/typescript": "workspace:*", + "@types/aws-lambda": "8.10.152", + "@types/node": "^24.5.1", + "typescript": "^5.9.2" + }, + "dependencies": { + "@ai-sdk/google": "^2.0.14", + "@aws-sdk/client-sqs": "^3.891.0", + "@orpc/client": "^1.8.9", + "@orpc/contract": "^1.8.9", + "@orpc/server": "^1.8.9", + "@orpc/tanstack-query": "^1.8.9", + "@rectangular-labs/api-core": "workspace:*", + "@rectangular-labs/auth": "workspace:*", + "@rectangular-labs/crawler": "workspace:*", + "@rectangular-labs/db": "workspace:*", + "@rectangular-labs/result": "workspace:*", + "@t3-oss/env-core": "^0.13.8", + "ai": "^5.0.45", + "arktype": "^2.1.22", + "hono": "^4.9.8", + "sst": "3.17.13" + }, + "overrides": { + "@tanstack/react-query": "5.89.0" + } +} diff --git a/apps/tasks/src/client.ts b/apps/tasks/src/client.ts new file mode 100644 index 000000000..1092fa5f6 --- /dev/null +++ b/apps/tasks/src/client.ts @@ -0,0 +1,6 @@ +import { createORPCClient } from "@orpc/client"; +import { createRpcLink } from "@rectangular-labs/api-core/lib/links"; +import type { RouterClient } from "./types"; + +export const rpcClient = (baseUrl: string): RouterClient => + createORPCClient(createRpcLink({ baseUrl, path: "/rpc" })); diff --git a/apps/tasks/src/context.ts b/apps/tasks/src/context.ts new file mode 100644 index 000000000..aa4835987 --- /dev/null +++ b/apps/tasks/src/context.ts @@ -0,0 +1,26 @@ +import { os } from "@orpc/server"; +import { + asyncStorageMiddleware, + getContext as getBaseContext, +} from "@rectangular-labs/api-core/lib/context-storage"; +import { loggerMiddleware } from "@rectangular-labs/api-core/lib/logger"; +import { createDb } from "@rectangular-labs/db"; +import type { InitialContext } from "./types"; + +export const createApiContext = (args: Omit) => { + const db = createDb(); + return { + db, + ...args, + }; +}; +export const getContext = getBaseContext; + +/** + * Base oRPC instance with typed initial context + * Use this instead of the raw `os` import for type-safe dependency injection + */ +export const base = os + .$context() + .use(loggerMiddleware) + .use(asyncStorageMiddleware()); diff --git a/apps/tasks/src/env.ts b/apps/tasks/src/env.ts new file mode 100644 index 000000000..9dbd942f9 --- /dev/null +++ b/apps/tasks/src/env.ts @@ -0,0 +1,15 @@ +import { authEnv } from "@rectangular-labs/auth/env"; +import { dbEnv } from "@rectangular-labs/db/env"; +import { createEnv } from "@t3-oss/env-core"; +import { type } from "arktype"; + +export const apiEnv = () => + createEnv({ + extends: [dbEnv(), authEnv()], + server: { + GOOGLE_GENERATIVE_AI_API_KEY: type("string|undefined"), + REDDIT_USER_AGENT: type("string|undefined"), + }, + runtimeEnv: process.env, + emptyStringAsUndefined: true, + }); diff --git a/apps/tasks/src/routes/index.ts b/apps/tasks/src/routes/index.ts new file mode 100644 index 000000000..f17e02013 --- /dev/null +++ b/apps/tasks/src/routes/index.ts @@ -0,0 +1,6 @@ +import { lazy } from "@orpc/server"; +import { base } from "../context"; + +export const router = base.router({ + tasks: lazy(() => import("./tasks")), +}); diff --git a/apps/tasks/src/routes/tasks.ts b/apps/tasks/src/routes/tasks.ts new file mode 100644 index 000000000..e173c7135 --- /dev/null +++ b/apps/tasks/src/routes/tasks.ts @@ -0,0 +1,55 @@ +import { ORPCError } from "@orpc/client"; +import { schema } from "@rectangular-labs/db"; +import { type } from "arktype"; +import { base } from "../context"; + +const create = base + .route({ method: "POST", path: "/" }) + .input(schema.keywordInsertSchema.merge(type({ projectId: "string" }))) + .output(ProjectKeywordSelectSchema) + .handler(async ({ context, input }) => { + const { session } = context.session; + if (!session.activeOrganizationId) { + throw new ORPCError("BAD_REQUEST", { message: "Organization not found" }); + } + + const project = await getProjectById( + input.projectId, + session.activeOrganizationId, + ); + if (!project.ok) { + throw new ORPCError("BAD_REQUEST", { message: project.error.message }); + } + + const keyword = await context.db.transaction(async (tx) => { + const keywordResult = await upsertKeyword(input.phrase, tx); + if (!keywordResult.ok) { + throw new ORPCError("BAD_REQUEST", { + message: keywordResult.error.message, + }); + } + const keyword = keywordResult.value; + + const [projectKeyword] = await tx + .insert(schema.smProjectKeyword) + .values({ + projectId: input.projectId, + keywordId: keyword.id, + isPaused: false, + pollingIntervalSec: 900, + nextRunAt: new Date(), + lastRunAt: null, + }) + .returning(); + if (!projectKeyword) { + throw new ORPCError("INTERNAL_SERVER_ERROR", { + message: "No project keyword created.", + }); + } + return { phrase: keyword.phrase, ...projectKeyword }; + }); + + return keyword; + }); + +export default base.prefix("/task").router({ create }); diff --git a/apps/tasks/src/schema/passkey.ts b/apps/tasks/src/schema/passkey.ts new file mode 100644 index 000000000..de134df1a --- /dev/null +++ b/apps/tasks/src/schema/passkey.ts @@ -0,0 +1,108 @@ +import { type } from "arktype"; + +// Common WebAuthn types +const authenticatorAttachmentType = type("'cross-platform' | 'platform'"); +const transportType = type( + "('ble' | 'cable' | 'hybrid' | 'internal' | 'nfc' | 'smart-card' | 'usb')[]", +); +const publicKeyCredentialType = type("'public-key'"); +const userVerificationType = type("('required' | 'preferred' | 'discouraged')"); +const residentKeyType = type("('required' | 'preferred' | 'discouraged')"); +const attestationType = type("('direct' | 'enterprise' | 'indirect' | 'none')"); +const hintsType = type("('hybrid' | 'security-key' | 'client-device')[]"); +const attestationFormatsType = type( + "('fido-u2f' | 'packed' | 'android-safetynet' | 'android-key' | 'tpm' | 'apple' | 'none')[]", +); + +// Common credential structure +const credentialDescriptorType = type({ + id: "string", + type: publicKeyCredentialType, + "transports?": transportType, +}); + +// Common extension types +const extensionsType = type({ + "appid?": "string", + "credProps?": "boolean", + "hmacCreateSecret?": "boolean", + "minPinLength?": "boolean", +}); + +const clientExtensionResultsType = type({ + "appid?": "boolean", + "credProps?": type({ + "rk?": "boolean", + }), + "hmacCreateSecret?": "boolean", +}); + +// Registration related schemas; +export const registrationOptionsSchema = type({ + rp: { + name: "string", + "id?": "string", + }, + user: { + id: "string", + name: "string", + displayName: "string", + }, + challenge: "string", + pubKeyCredParams: type({ + alg: "number", + type: publicKeyCredentialType, + }).array(), + "timeout?": "number", + "excludeCredentials?": credentialDescriptorType.array(), + "authenticatorSelection?": type({ + "authenticatorAttachment?": authenticatorAttachmentType, + "requireResidentKey?": "boolean", + "residentKey?": residentKeyType, + "userVerification?": userVerificationType, + }), + "hints?": hintsType, + "attestation?": attestationType, + "attestationFormats?": attestationFormatsType, + "extensions?": extensionsType, +}); + +export const finishRegistrationInputSchema = type({ + username: "string", + registration: type({ + id: "string", + rawId: "string", + response: type({ + clientDataJSON: "string", + attestationObject: "string", + "authenticatorData?": "string", + "transports?": transportType, + "publicKeyAlgorithm?": "number", + "publicKey?": "string", + }), + "authenticatorAttachment?": authenticatorAttachmentType, + clientExtensionResults: clientExtensionResultsType, + type: publicKeyCredentialType, + }), +}); + +// Login related schemas +export const authenticationOptionsSchema = type({ + challenge: "string", + "timeout?": "number", + "rpId?": "string", + allowCredentials: credentialDescriptorType.array().optional(), +}); + +export const finishLoginInputSchema = type({ + id: "string", + rawId: "string", + response: type({ + clientDataJSON: "string", + authenticatorData: "string", + signature: "string", + "userHandle?": "string", + }), + clientExtensionResults: clientExtensionResultsType, + type: publicKeyCredentialType, +}); diff --git a/apps/tasks/src/server.ts b/apps/tasks/src/server.ts new file mode 100644 index 000000000..9b4e0cd86 --- /dev/null +++ b/apps/tasks/src/server.ts @@ -0,0 +1,26 @@ +import { createRpcHandler } from "@rectangular-labs/api-core/lib/handlers"; +import { Hono } from "hono"; +import { handle } from "hono/aws-lambda"; +import { createApiContext } from "./context"; +import { router } from "./routes"; + +const rpcHandler = createRpcHandler(router); + +const app = new Hono(); +app.use("/*", async (c, next) => { + const { matched, response } = await rpcHandler.handle(c.req.raw, { + prefix: "/rpc", + context: createApiContext({ + url: new URL(c.req.raw.url), + reqHeaders: c.req.raw.headers, + }), + }); + + if (matched) { + return c.newResponse(response.body, response); + } + + return await next(); +}); + +export const handler = handle(app); diff --git a/apps/tasks/src/subscriber.ts b/apps/tasks/src/subscriber.ts new file mode 100644 index 000000000..dea57316f --- /dev/null +++ b/apps/tasks/src/subscriber.ts @@ -0,0 +1,10 @@ +import type { SQSEvent } from "aws-lambda"; + +export const handler = async (event: SQSEvent) => { + console.log(JSON.stringify(event, null, 2)); + for (const record of event.Records) { + console.log(); + } + await new Promise((resolve) => setTimeout(resolve, 1000)); + return "ok"; +}; diff --git a/apps/tasks/src/types.ts b/apps/tasks/src/types.ts new file mode 100644 index 000000000..c920ddb3a --- /dev/null +++ b/apps/tasks/src/types.ts @@ -0,0 +1,23 @@ +import type { + InferRouterInputs, + InferRouterOutputs, + RouterClient as ORPCRouterClient, + UnlaziedRouter, +} from "@orpc/server"; +import type { BaseContext } from "@rectangular-labs/api-core/lib/types"; +import type { DB } from "@rectangular-labs/db"; +import type { router } from "./routes"; + +export type Router = UnlaziedRouter; +export type RouterClient = ORPCRouterClient; +export type RouterInputs = InferRouterInputs; +export type RouterOutputs = InferRouterOutputs; + +/** + * Initial context type definition for oRPC procedures + * This defines the required dependencies that must be passed when calling procedures + */ +export interface InitialContext extends BaseContext { + db: DB; + url: URL; +} diff --git a/apps/tasks/sst.config.ts b/apps/tasks/sst.config.ts new file mode 100644 index 000000000..1e37b838e --- /dev/null +++ b/apps/tasks/sst.config.ts @@ -0,0 +1,47 @@ +/// + +export default $config({ + app(input) { + return { + name: "rl-tasks", + removal: input?.stage === "production" ? "remove" : "remove", + protect: ["production"].includes(input?.stage), + home: "aws", + providers: { + aws: { + profile: + input.stage === "production" + ? "rectangular-production" + : "rectangular-dev", + }, + }, + }; + }, + async run() { + const vpc = new sst.aws.Vpc("TasksVpc"); + const cluster = new sst.aws.Cluster("TasksCluster", { vpc }); + const task = new sst.aws.Task("SiteCrawler", { + cluster, + image: { + context: "../../", + dockerfile: "./Dockerfile", + }, + }); + + const queue = new sst.aws.Queue("TasksQueue"); + queue.subscribe({ + handler: "./src/subscriber.handler", + link: [task], + }); + + const app = new sst.aws.Function("TasksAPI", { + handler: "./src/server.handler", + link: [queue], + url: true, + }); + + return await Promise.resolve({ + enqueueUrl: app.url, + }); + }, +}); diff --git a/apps/tasks/tsconfig.json b/apps/tasks/tsconfig.json new file mode 100644 index 000000000..e0db0b5e4 --- /dev/null +++ b/apps/tasks/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "@rectangular-labs/typescript/tsconfig.internal-package.json", + "compilerOptions": { + "rootDir": "src", + "lib": ["esnext"] + }, + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a5ded9f66..0848c77cc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -145,6 +145,70 @@ importers: specifier: ^4.37.1 version: 4.37.1(@cloudflare/workers-types@4.20250414.0) + apps/tasks: + dependencies: + '@ai-sdk/google': + specifier: ^2.0.14 + version: 2.0.14(zod@4.1.9) + '@aws-sdk/client-sqs': + specifier: ^3.891.0 + version: 3.891.0 + '@orpc/client': + specifier: ^1.8.9 + version: 1.8.9(@opentelemetry/api@1.9.0) + '@orpc/contract': + specifier: ^1.8.9 + version: 1.8.9(@opentelemetry/api@1.9.0) + '@orpc/server': + specifier: ^1.8.9 + version: 1.8.9(@opentelemetry/api@1.9.0)(crossws@0.3.5)(ws@8.18.3) + '@orpc/tanstack-query': + specifier: ^1.8.9 + version: 1.8.9(@opentelemetry/api@1.9.0)(@orpc/client@1.8.9(@opentelemetry/api@1.9.0))(@tanstack/query-core@5.89.0) + '@rectangular-labs/api-core': + specifier: workspace:* + version: link:../../packages/api-core + '@rectangular-labs/auth': + specifier: workspace:* + version: link:../../packages/auth + '@rectangular-labs/crawler': + specifier: workspace:* + version: link:../../packages/crawler + '@rectangular-labs/db': + specifier: workspace:* + version: link:../../packages/db + '@rectangular-labs/result': + specifier: workspace:* + version: link:../../packages/result + '@t3-oss/env-core': + specifier: ^0.13.8 + version: 0.13.8(arktype@2.1.22)(typescript@5.9.2)(zod@4.1.9) + ai: + specifier: ^5.0.45 + version: 5.0.45(zod@4.1.9) + arktype: + specifier: ^2.1.22 + version: 2.1.22 + hono: + specifier: ^4.9.8 + version: 4.9.8 + sst: + specifier: 3.17.13 + version: 3.17.13 + devDependencies: + '@rectangular-labs/typescript': + specifier: workspace:* + version: link:../../tooling/typescript + '@types/aws-lambda': + specifier: 8.10.152 + version: 8.10.152 + '@types/node': + specifier: ^24.5.1 + version: 24.5.1 + typescript: + specifier: ^5.9.2 + version: 5.9.2 + apps/www: dependencies: '@react-three/drei': @@ -408,10 +472,7 @@ importers: dependencies: '@t3-oss/env-core': specifier: ^0.13.8 - version: 0.13.8(arktype@2.1.22)(typescript@5.9.2)(zod@3.24.2) - apify: - specifier: ^3.4.5 - version: 3.4.5 + version: 0.13.8(arktype@2.1.22)(typescript@5.9.2)(zod@4.1.9) arktype: specifier: ^2.1.22 version: 2.1.22 @@ -427,9 +488,6 @@ importers: playwright: specifier: 1.55.0 version: 1.55.0 - sst: - specifier: 3.17.13 - version: 3.17.13 devDependencies: '@rectangular-labs/typescript': specifier: workspace:* @@ -440,9 +498,6 @@ importers: '@types/node': specifier: ^24.5.1 version: 24.5.1 - apify-cli: - specifier: ^1.1.1 - version: 1.1.1(@types/node@24.5.1) typescript: specifier: ^5.9.2 version: 5.9.2 @@ -821,23 +876,12 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - '@apify/actor-templates@0.1.5': - resolution: {integrity: sha512-Ztgubaqke9PUpm4urzLcZl+avptbMd0haH3w7qu57ZKGNbH1fL4SGg8PXPHTnxFKRQLe0PVYZ41DT2OA4ihRig==} - '@apify/consts@2.44.1': resolution: {integrity: sha512-d3/IGuJRLtqDizBA0ZWKrH+U9/gt9k7A9bJE64KKsTi58WfkL7MTsmWf/XBsr1wrju+eAFiZPMwFicoyLlLDug==} '@apify/datastructures@2.0.3': resolution: {integrity: sha512-E6yQyc/XZDqJopbaGmhzZXMJqwGf96ELtDANZa0t68jcOAJZS+pF7YUfQOLszXq6JQAdnRvTH2caotL6urX7HA==} - '@apify/input_schema@3.19.1': - resolution: {integrity: sha512-5enq4+SIgru2GbAtlFnZg1RajVLGm8QILpefO/kuaw/QSTN8qZyNXisx0vSDGFqluLzvPwbRydDYkqoMGttqSA==} - peerDependencies: - ajv: ^8.0.0 - - '@apify/input_secrets@1.2.6': - resolution: {integrity: sha512-3vnepGjZVAo1ZCT9uz+K4MO4KAcQFIWY15po/W6EdBjCFh0xPx/8DKl9ZUYdospLoDM2o1T+zBJbrYe5Fiq5Pg==} - '@apify/log@2.5.22': resolution: {integrity: sha512-2b5bYTgHHnl36LE6JjtEc1QoieiwzKzDVajdTyuQTlgJP06SRGL9lDHqMn/unpdYE0ViZgLfWvLMQ3uBen+dog==} @@ -855,9 +899,6 @@ packages: '@apify/utilities@2.18.2': resolution: {integrity: sha512-sLB1qEum2fs+odgTJ+x11R/QCYp/zVX/CYLhst9oub7mfo1geDvioOtZjljptS7aQLyqtco3hgn+Co3rtrIC1w==} - '@apify/utilities@2.19.0': - resolution: {integrity: sha512-DKZT4XNt40eeo+FOgvuknX/XiYK+H7IYoZKKUQQFx/iG0Chs+vPgz4Bj4ciak0IS+xTacTC245cixulLzJ7yjA==} - '@ark/schema@0.49.0': resolution: {integrity: sha512-GphZBLpW72iS0v4YkeUtV3YIno35Gimd7+ezbPO9GwEi9kzdUrPVjvf6aXSBAfHikaFc/9pqZOpv3pOXnC71tw==} @@ -884,10 +925,18 @@ packages: resolution: {integrity: sha512-AM9Lt4QkNet8xKgCwJxfkyqlorwG9S+tvtpSfHYCVq0j2Z6PbkDaUBnvwjGOMBV7Um5IzZ7yhvQXrBg7omZciQ==} engines: {node: '>=18.0.0'} + '@aws-sdk/client-sqs@3.891.0': + resolution: {integrity: sha512-+c/A8Rqa2pTMR4ZljJ8GRWJc9kI0oBQCRelPDLkDLamGEH8EKJksIaDTw15TvJr+Mg6FZrnIRyDWhSRX57RyYQ==} + engines: {node: '>=18.0.0'} + '@aws-sdk/client-sso@3.890.0': resolution: {integrity: sha512-vefYNwh/K5V5YiJpFJfoMPNqsoiRTqD7ZnkvR0cjJdwhOIwFnSKN1vz0OMjySTQmVMcG4JKGVul82ou7ErtOhQ==} engines: {node: '>=18.0.0'} + '@aws-sdk/client-sso@3.891.0': + resolution: {integrity: sha512-QMDaD9GhJe7l0KQp3Tt7dzqFCz/H2XuyNjQgvi10nM1MfI1RagmLtmEhZveQxMPhZ/AtohLSK0Tisp/I5tR8RQ==} + engines: {node: '>=18.0.0'} + '@aws-sdk/core@3.890.0': resolution: {integrity: sha512-CT+yjhytHdyKvV3Nh/fqBjnZ8+UiQZVz4NMm4LrPATgVSOdfygXHqrWxrPTVgiBtuJWkotg06DF7+pTd5ekLBw==} engines: {node: '>=18.0.0'} @@ -904,10 +953,18 @@ packages: resolution: {integrity: sha512-Mxv7ByftHKH7dE6YXu9gQ6ODXwO1iSO32t8tBrZLS3g8K1knWADIqDFv3yErQtJ8hp27IDxbAbVH/1RQdSkmhA==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-ini@3.891.0': + resolution: {integrity: sha512-9LOfm97oy2d2frwCQjl53XLkoEYG6/rsNM3Y6n8UtRU3bzGAEjixdIuv3b6Z/Mk/QLeikcQEJ9FMC02DuQh2Yw==} + engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-node@3.890.0': resolution: {integrity: sha512-zbPz3mUtaBdch0KoH8/LouRDcYSzyT2ecyCOo5OAFVil7AxT1jvsn4vX78FlnSVpZ4mLuHY8pHTVGi235XiyBA==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-node@3.891.0': + resolution: {integrity: sha512-IjGvQJhpCN512xlT1DFGaPeE1q0YEm/X62w7wHsRpBindW//M+heSulJzP4KPkoJvmJNVu1NxN26/p4uH+M8TQ==} + engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-process@3.890.0': resolution: {integrity: sha512-dWZ54TI1Q+UerF5YOqGiCzY+x2YfHsSQvkyM3T4QDNTJpb/zjiVv327VbSOULOlI7gHKWY/G3tMz0D9nWI7YbA==} engines: {node: '>=18.0.0'} @@ -916,34 +973,66 @@ packages: resolution: {integrity: sha512-ajYCZ6f2+98w8zG/IXcQ+NhWYoI5qPUDovw+gMqMWX/jL1cmZ9PFAwj2Vyq9cbjum5RNWwPLArWytTCgJex4AQ==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-sso@3.891.0': + resolution: {integrity: sha512-RtF9BwUIZqc/7sFbK6n6qhe0tNaWJQwin89nSeZ1HOsA0Z7TfTOelX8Otd0L5wfeVBMVcgiN3ofqrcZgjFjQjA==} + engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-web-identity@3.890.0': resolution: {integrity: sha512-qZ2Mx7BeYR1s0F/H6wePI0MAmkFswmBgrpgMCOt2S4b2IpQPnUa2JbxY3GwW2WqX3nV0KjPW08ctSLMmlq/tKA==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-web-identity@3.891.0': + resolution: {integrity: sha512-yq7kzm1sHZ0GZrtS+qpjMUp4ES66UoT1+H2xxrOuAZkvUnkpQq1iSjOgBgJJ9FW1EsDUEmlgn94i4hJTNvm7fg==} + engines: {node: '>=18.0.0'} + '@aws-sdk/middleware-host-header@3.887.0': resolution: {integrity: sha512-ulzqXv6NNqdu/kr0sgBYupWmahISHY+azpJidtK6ZwQIC+vBUk9NdZeqQpy7KVhIk2xd4+5Oq9rxapPwPI21CA==} engines: {node: '>=18.0.0'} + '@aws-sdk/middleware-host-header@3.891.0': + resolution: {integrity: sha512-OYaxbqNDeo/noE7MfYWWQDu86cF/R/bMXdZ2QZwpWpX2yjy8xMwxSg7c/4tEK/OtiDZTKRXXrvPxRxG2+1bnJw==} + engines: {node: '>=18.0.0'} + '@aws-sdk/middleware-logger@3.887.0': resolution: {integrity: sha512-YbbgLI6jKp2qSoAcHnXrQ5jcuc5EYAmGLVFgMVdk8dfCfJLfGGSaOLxF4CXC7QYhO50s+mPPkhBYejCik02Kug==} engines: {node: '>=18.0.0'} + '@aws-sdk/middleware-logger@3.891.0': + resolution: {integrity: sha512-azL4mg1H1FLpOAECiFtU+r+9VDhpeF6Vh9pzD4m51BWPJ60CVnyHayeI/0gqPsL60+5l90/b9VWonoA8DvAvpg==} + engines: {node: '>=18.0.0'} + '@aws-sdk/middleware-recursion-detection@3.887.0': resolution: {integrity: sha512-tjrUXFtQnFLo+qwMveq5faxP5MQakoLArXtqieHphSqZTXm21wDJM73hgT4/PQQGTwgYjDKqnqsE1hvk0hcfDw==} engines: {node: '>=18.0.0'} + '@aws-sdk/middleware-recursion-detection@3.891.0': + resolution: {integrity: sha512-n++KwAEnNlvx5NZdIQZnvl2GjSH/YE3xGSqW2GmPB5780tFY5lOYSb1uA+EUzJSVX4oAKAkSPdR2AOW09kzoew==} + engines: {node: '>=18.0.0'} + '@aws-sdk/middleware-sdk-s3@3.890.0': resolution: {integrity: sha512-58P1lrE606zpp29xH9Keh3j2BWfa2ciGBtygJTpulRMlqPL3U1gFfU2g5nDYJbjKgRtCgNIBqfmtkL4eikCb9w==} engines: {node: '>=18.0.0'} + '@aws-sdk/middleware-sdk-sqs@3.891.0': + resolution: {integrity: sha512-+FkFslhvJqIzw+nVtToGuOcbdzclkTiOj6Z61mnq1ZyH8r5OOW9EkLHNgIacn6ehdoIeCvOCJiLpQbttNn51Lw==} + engines: {node: '>=18.0.0'} + '@aws-sdk/middleware-user-agent@3.890.0': resolution: {integrity: sha512-x4+gLrOFGN7PnfxCaQbs3QEF8bMQE4CVxcOp066UEJqr2Pn4yB12Q3O+YntOtESK5NcTxIh7JlhGss95EHzNng==} engines: {node: '>=18.0.0'} + '@aws-sdk/middleware-user-agent@3.891.0': + resolution: {integrity: sha512-xyxIZtR7FunCWymPAxEm61VUq9lruXxWIYU5AIh5rt0av7nXa2ayAAlscQ7ch9jUlw+lbC2PVbw0K/OYrMovuA==} + engines: {node: '>=18.0.0'} + '@aws-sdk/nested-clients@3.890.0': resolution: {integrity: sha512-D5qVNd+qlqdL8duJShzffAqPllGRA4tG7n/GEpL13eNfHChPvGkkUFBMrxSgCAETaTna13G6kq+dMO+SAdbm1A==} engines: {node: '>=18.0.0'} + '@aws-sdk/nested-clients@3.891.0': + resolution: {integrity: sha512-cpol+Yk4T3GXPXbRfUyN2u6tpMEHUxAiesZgrfMm11QGHV+pmzyejJV/QZ0pdJKj5sXKaCr4DCntoJ5iBx++Cw==} + engines: {node: '>=18.0.0'} + '@aws-sdk/region-config-resolver@3.890.0': resolution: {integrity: sha512-VfdT+tkF9groRYNzKvQCsCGDbOQdeBdzyB1d6hWiq22u13UafMIoskJ1ec0i0H1X29oT6mjTitfnvPq1UiKwzQ==} engines: {node: '>=18.0.0'} @@ -956,6 +1045,10 @@ packages: resolution: {integrity: sha512-+pK/0iQEpPmnztbAw0NNmb+B5pPy8VLu+Ab4SJLgVp41RE9NO13VQtrzUbh61TTAVMrzqWlLQ2qmAl2Fk4VNgw==} engines: {node: '>=18.0.0'} + '@aws-sdk/token-providers@3.891.0': + resolution: {integrity: sha512-n31JDMWhj/53QX33C97+1W63JGtgO8pg1/Tfmv4f9TR2VSGf1rFwYH7cPZ7dVIMmcUBeI2VCVhwUIabGNHw86Q==} + engines: {node: '>=18.0.0'} + '@aws-sdk/types@3.887.0': resolution: {integrity: sha512-fmTEJpUhsPsovQ12vZSpVTEP/IaRoJAMBGQXlQNjtCpkBp6Iq3KQDa/HDaPINE+3xxo6XvTdtibsNOd5zJLV9A==} engines: {node: '>=18.0.0'} @@ -968,6 +1061,10 @@ packages: resolution: {integrity: sha512-nJ8v1x9ZQKzMRK4dS4oefOMIHqb6cguctTcx1RB9iTaFOR5pP7bvq+D4mvNZ6vBxiHg1dQGBUUgl5XJmdR7atQ==} engines: {node: '>=18.0.0'} + '@aws-sdk/util-endpoints@3.891.0': + resolution: {integrity: sha512-MgxvmHIQJbUK+YquX4bdjDw1MjdBqTRJGHs6iU2KM8nN1ut0bPwvavkq7NrY/wB3ZKKECqmv6J/nw+hYKKUIHA==} + engines: {node: '>=18.0.0'} + '@aws-sdk/util-locate-window@3.873.0': resolution: {integrity: sha512-xcVhZF6svjM5Rj89T1WzkjQmrTF6dpR2UvIHPMTnSZoNe6CixejPZ6f0JJ2kAhO8H+dUHwNBlsUgOTIKiK/Syg==} engines: {node: '>=18.0.0'} @@ -984,6 +1081,15 @@ packages: aws-crt: optional: true + '@aws-sdk/util-user-agent-node@3.891.0': + resolution: {integrity: sha512-/mmvVL2PJE2NMTWj9JSY98OISx7yov0mi72eOViWCHQMRYJCN12DY54i1rc4Q/oPwJwTwIrx69MLjVhQ1OZsgw==} + engines: {node: '>=18.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + '@aws-sdk/xml-builder@3.887.0': resolution: {integrity: sha512-lMwgWK1kNgUhHGfBvO/5uLe7TKhycwOn3eRCqsKPT9aPCx/HWuTlpcQp8oW2pCRGLS7qzcxqpQulcD+bbUL7XQ==} engines: {node: '>=18.0.0'} @@ -1728,10 +1834,6 @@ packages: '@cloudflare/workers-types@4.20250414.0': resolution: {integrity: sha512-ZHl8LiyUMWiIxYqpasen8Lc75Ef+0afqL26TEd95eRIi5kgkEbjDJ7uIUnpxMoZTRI0J8Hy5YEPtt4nFXt+TpA==} - '@colors/colors@1.5.0': - resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} - engines: {node: '>=0.1.90'} - '@colors/colors@1.6.0': resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} engines: {node: '>=0.1.90'} @@ -3220,19 +3322,6 @@ packages: cpu: [x64] os: [win32] - '@inquirer/ansi@1.0.0': - resolution: {integrity: sha512-JWaTfCxI1eTmJ1BIv86vUfjVatOdxwD0DAVKYevY8SazeUUZtW+tNbsdejVO1GYE0GXJW1N1ahmiC3TFd+7wZA==} - engines: {node: '>=18'} - - '@inquirer/core@10.2.2': - resolution: {integrity: sha512-yXq/4QUnk4sHMtmbd7irwiepjB8jXU0kkFRL4nr/aDBA2mDz13cMakEWdDwX3eSCTkk03kwcndD1zfRAIlELxA==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - '@inquirer/external-editor@1.0.1': resolution: {integrity: sha512-Oau4yL24d2B5IL4ma4UpbQigkVhzPDXLoqy1ggK4gnHg/stmkffJE4oOXHXF3uz0UEpywG68KcyXsyYpA1Re/Q==} engines: {node: '>=18'} @@ -3246,42 +3335,6 @@ packages: resolution: {integrity: sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==} engines: {node: '>=18'} - '@inquirer/input@4.2.4': - resolution: {integrity: sha512-cwSGpLBMwpwcZZsc6s1gThm0J+it/KIJ+1qFL2euLmSKUMGumJ5TcbMgxEjMjNHRGadouIYbiIgruKoDZk7klw==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/password@4.0.20': - resolution: {integrity: sha512-nxSaPV2cPvvoOmRygQR+h0B+Av73B01cqYLcr7NXcGXhbmsYfUb8fDdw2Us1bI2YsX+VvY7I7upgFYsyf8+Nug==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/select@4.3.4': - resolution: {integrity: sha512-Qp20nySRmfbuJBBsgPU7E/cL62Hf250vMZRzYDcBHty2zdD1kKCnoDFWRr0WO2ZzaXp3R7a4esaVGJUx0E6zvA==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/type@3.0.8': - resolution: {integrity: sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - '@ioredis/commands@1.3.0': resolution: {integrity: sha512-M/T6Zewn7sDaBQEqIZ8Rb+i9y8qfGmq+5SDFSf9sA2lUZTmdDLVdOiQaeDp+Q4wElZ9HG1GAX5KhDaidp6LQsQ==} @@ -5153,29 +5206,14 @@ packages: cpu: [x64] os: [win32] - '@root/walk@1.1.0': - resolution: {integrity: sha512-FfXPAta9u2dBuaXhPRawBcijNC9rmKVApmbi6lIZyg36VR/7L02ytxoY5K/14PJlHqiBUoYII73cTlekdKTUOw==} - '@sapphire/async-queue@1.5.5': resolution: {integrity: sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} - '@sapphire/duration@1.2.0': - resolution: {integrity: sha512-LxjOAFXz81WmrI8XX9YaVcAZDjQj/1p78lZCvkAWZB1nphOwz/D0dU3CBejmhOWx5dO5CszTkLJMNR0xuCK+Zg==} - engines: {node: '>=v14.0.0', npm: '>=7.0.0'} - - '@sapphire/result@2.7.2': - resolution: {integrity: sha512-DJbCGmvi8UZAu/hh85auQL8bODFlpcS3cWjRJZ5/cXTLekmGvs/CrRxrIzwbA6+poyYojo5rK4qu8trmjfneog==} - engines: {node: '>=v14.0.0', npm: '>=7.0.0'} - '@sapphire/shapeshift@3.9.7': resolution: {integrity: sha512-4It2mxPSr4OGn4HSQWGmhFMsNFGfFVhWeRPCRwbH972Ek2pzfGRZtb0pJ4Ze6oIzcyh2jw7nUDa6qGlWofgd9g==} engines: {node: '>=v16'} - '@sapphire/timestamp@1.0.5': - resolution: {integrity: sha512-oNwWyNdbt5wm4aYZvlHl1+64U3g0xrFmRIHsnER7RgMxNnp/wmAE4yTK2oUHeadg3t4V9iYctPAQCF+aINke4g==} - engines: {node: '>=v14.0.0', npm: '>=7.0.0'} - '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} @@ -5265,20 +5303,12 @@ packages: resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} engines: {node: '>=18'} - '@sindresorhus/merge-streams@4.0.0': - resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} - engines: {node: '>=18'} - '@sinonjs/commons@3.0.1': resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} '@sinonjs/fake-timers@10.3.0': resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} - '@skyra/jaro-winkler@1.1.1': - resolution: {integrity: sha512-jT2OWwpajtXTb6opnaIwmBTMpQtKUwl2Ro1zApxIIrpZJon71kZIv6GZSc08LzKO2lpTqUjvD+i7Z2hGuG42KQ==} - engines: {node: '>=v18'} - '@smithy/abort-controller@4.1.1': resolution: {integrity: sha512-vkzula+IwRvPR6oKQhMYioM3A/oX/lFCZiwuxkQbRhqJS2S4YRY2k7k/SyR2jMf3607HLtbEwlRxi0ndXHMjRg==} engines: {node: '>=18.0.0'} @@ -5291,6 +5321,10 @@ packages: resolution: {integrity: sha512-Abs5rdP1o8/OINtE49wwNeWuynCu0kme1r4RI3VXVrHr4odVDG7h7mTnw1WXXfN5Il+c25QOnrdL2y56USfxkA==} engines: {node: '>=18.0.0'} + '@smithy/core@3.11.1': + resolution: {integrity: sha512-REH7crwORgdjSpYs15JBiIWOYjj0hJNC3aCecpJvAlMMaaqL5i2CLb1i6Hc4yevToTKSqslLMI9FKjhugEwALA==} + engines: {node: '>=18.0.0'} + '@smithy/credential-provider-imds@4.1.2': resolution: {integrity: sha512-JlYNq8TShnqCLg0h+afqe2wLAwZpuoSgOyzhYvTgbiKBWRov+uUve+vrZEQO6lkdLOWPh7gK5dtb9dS+KGendg==} engines: {node: '>=18.0.0'} @@ -5315,6 +5349,10 @@ packages: resolution: {integrity: sha512-ePTYUOV54wMogio+he4pBybe8fwg4sDvEVDBU8ZlHOZXbXK3/C0XfJgUCu6qAZcawv05ZhZzODGUerFBPsPUDQ==} engines: {node: '>=18.0.0'} + '@smithy/md5-js@4.1.1': + resolution: {integrity: sha512-MvWXKK743BuHjr/hnWuT6uStdKEaoqxHAQUvbKJPPZM5ZojTNFI5D+47BoQfBE5RgGlRRty05EbWA+NXDv+hIA==} + engines: {node: '>=18.0.0'} + '@smithy/middleware-content-length@4.1.1': resolution: {integrity: sha512-9wlfBBgTsRvC2JxLJxv4xDGNBrZuio3AgSl0lSFX7fneW2cGskXTYpFxCdRYD2+5yzmsiTuaAJD1Wp7gWt9y9w==} engines: {node: '>=18.0.0'} @@ -5323,10 +5361,18 @@ packages: resolution: {integrity: sha512-M51KcwD+UeSOFtpALGf5OijWt915aQT5eJhqnMKJt7ZTfDfNcvg2UZgIgTZUoiORawb6o5lk4n3rv7vnzQXgsA==} engines: {node: '>=18.0.0'} + '@smithy/middleware-endpoint@4.2.3': + resolution: {integrity: sha512-+1H5A28DeffRVrqmVmtqtRraEjoaC6JVap3xEQdVoBh2EagCVY7noPmcBcG4y7mnr9AJitR1ZAse2l+tEtK5vg==} + engines: {node: '>=18.0.0'} + '@smithy/middleware-retry@4.2.2': resolution: {integrity: sha512-KZJueEOO+PWqflv2oGx9jICpHdBYXwCI19j7e2V3IMwKgFcXc9D9q/dsTf4B+uCnYxjNoS1jpyv6pGNGRsKOXA==} engines: {node: '>=18.0.0'} + '@smithy/middleware-retry@4.2.4': + resolution: {integrity: sha512-amyqYQFewnAviX3yy/rI/n1HqAgfvUdkEhc04kDjxsngAUREKuOI24iwqQUirrj6GtodWmR4iO5Zeyl3/3BwWg==} + engines: {node: '>=18.0.0'} + '@smithy/middleware-serde@4.1.1': resolution: {integrity: sha512-lh48uQdbCoj619kRouev5XbWhCwRKLmphAif16c4J6JgJ4uXjub1PI6RL38d3BLliUvSso6klyB/LTNpWSNIyg==} engines: {node: '>=18.0.0'} @@ -5363,6 +5409,10 @@ packages: resolution: {integrity: sha512-Iam75b/JNXyDE41UvrlM6n8DNOa/r1ylFyvgruTUx7h2Uk7vDNV9AAwP1vfL1fOL8ls0xArwEGVcGZVd7IO/Cw==} engines: {node: '>=18.0.0'} + '@smithy/service-error-classification@4.1.2': + resolution: {integrity: sha512-Kqd8wyfmBWHZNppZSMfrQFpc3M9Y/kjyN8n8P4DqJJtuwgK1H914R471HTw7+RL+T7+kI1f1gOnL7Vb5z9+NgQ==} + engines: {node: '>=18.0.0'} + '@smithy/shared-ini-file-loader@4.2.0': resolution: {integrity: sha512-OQTfmIEp2LLuWdxa8nEEPhZmiOREO6bcB6pjs0AySf4yiZhl6kMOfqmcwcY8BaBPX+0Tb+tG7/Ia/6mwpoZ7Pw==} engines: {node: '>=18.0.0'} @@ -5375,6 +5425,10 @@ packages: resolution: {integrity: sha512-u82cjh/x7MlMat76Z38TRmEcG6JtrrxN4N2CSNG5o2v2S3hfLAxRgSgFqf0FKM3dglH41Evknt/HOX+7nfzZ3g==} engines: {node: '>=18.0.0'} + '@smithy/smithy-client@4.6.3': + resolution: {integrity: sha512-K27LqywsaqKz4jusdUQYJh/YP2VbnbdskZ42zG8xfV+eovbTtMc2/ZatLWCfSkW0PDsTUXlpvlaMyu8925HsOw==} + engines: {node: '>=18.0.0'} + '@smithy/types@4.5.0': resolution: {integrity: sha512-RkUpIOsVlAwUIZXO1dsz8Zm+N72LClFfsNqf173catVlvRZiwPy0x2u0JLEA4byreOPKDZPGjmPDylMoP8ZJRg==} engines: {node: '>=18.0.0'} @@ -5431,10 +5485,18 @@ packages: resolution: {integrity: sha512-jGeybqEZ/LIordPLMh5bnmnoIgsqnp4IEimmUp5c5voZ8yx+5kAlN5+juyr7p+f7AtZTgvhmInQk4Q0UVbrZ0Q==} engines: {node: '>=18.0.0'} + '@smithy/util-retry@4.1.2': + resolution: {integrity: sha512-NCgr1d0/EdeP6U5PSZ9Uv5SMR5XRRYoVr1kRVtKZxWL3tixEL3UatrPIMFZSKwHlCcp2zPLDvMubVDULRqeunA==} + engines: {node: '>=18.0.0'} + '@smithy/util-stream@4.3.1': resolution: {integrity: sha512-khKkW/Jqkgh6caxMWbMuox9+YfGlsk9OnHOYCGVEdYQb/XVzcORXHLYUubHmmda0pubEDncofUrPNniS9d+uAA==} engines: {node: '>=18.0.0'} + '@smithy/util-stream@4.3.2': + resolution: {integrity: sha512-Ka+FA2UCC/Q1dEqUanCdpqwxOFdf5Dg2VXtPtB1qxLcSGh5C1HdzklIt18xL504Wiy9nNUKwDMRTVCbKGoK69g==} + engines: {node: '>=18.0.0'} + '@smithy/util-uri-escape@4.1.0': resolution: {integrity: sha512-b0EFQkq35K5NHUYxU72JuoheM6+pytEVUGlTwiFxWFpmddA+Bpz3LgsPRIpBk8lnPE47yT7AF2Egc3jVnKLuPg==} engines: {node: '>=18.0.0'} @@ -6268,10 +6330,6 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn-loose@8.5.2: - resolution: {integrity: sha512-PPvV6g8UGMGgjrMu+n/f9E/tCSkNQ2Y97eFvuVdJfG11+xdIeDcLyNdC8SHcrHbRqkfwLASdplyR6B6sKM1U4A==} - engines: {node: '>=0.4.0'} - acorn-walk@8.3.2: resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} engines: {node: '>=0.4.0'} @@ -6299,18 +6357,10 @@ packages: resolution: {integrity: sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==} engines: {node: '>=12.0'} - agent-base@6.0.2: - resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} - engines: {node: '>= 6.0.0'} - agent-base@7.1.3: resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} engines: {node: '>= 14'} - agentkeepalive@4.6.0: - resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} - engines: {node: '>= 8.0.0'} - aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} @@ -6395,18 +6445,6 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} - apify-cli@1.1.1: - resolution: {integrity: sha512-sRqidhG0pa45VL7KrKQl50D/uAvvys1rOY2cYm8QeZDVwbNYi/H7bWbXYzrhXoeZDFocolbFhkmcTJBM6DavPw==} - engines: {node: '>=20'} - hasBin: true - - apify-client@2.17.0: - resolution: {integrity: sha512-fxYQD/aV75AFRo49Pzwcjz2V5o23H9LQ38DnFjjDtY6+anEN59sLva8oBRXgrTWCgDnQh6JlG9l0s769/1MiuQ==} - - apify@3.4.5: - resolution: {integrity: sha512-9eoRHgPjw0FPfjXIqe8y3mzixlssbnYdTyehyatxR+cps2qDX3ZutAllG1UQKEKp+MV9l2SkHwVMjOgulh6grg==} - engines: {node: '>=16.0.0'} - archiver-utils@5.0.2: resolution: {integrity: sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==} engines: {node: '>= 14'} @@ -6471,9 +6509,6 @@ packages: async-limiter@1.0.1: resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==} - async-retry@1.3.3: - resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==} - async-sema@3.1.1: resolution: {integrity: sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==} @@ -6508,9 +6543,6 @@ packages: aws4fetch@1.0.20: resolution: {integrity: sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g==} - axios@1.12.2: - resolution: {integrity: sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==} - axios@1.8.3: resolution: {integrity: sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A==} @@ -6664,10 +6696,6 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} - binaryextensions@6.11.0: - resolution: {integrity: sha512-sXnYK/Ij80TO3lcqZVV2YgfKN5QjUWIRk/XSm2J/4bd/lPko3lvk0O4ZppH6m+6hB2/GTu+ptNwVFe1xh+QLQw==} - engines: {node: '>=4'} - bindings@1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} @@ -6744,10 +6772,6 @@ packages: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} - bundle-name@4.1.0: - resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} - engines: {node: '>=18'} - bundle-require@5.1.0: resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -6867,10 +6891,6 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - chalk@5.6.2: - resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - change-case@3.1.0: resolution: {integrity: sha512-2AZp7uJZbYEzRPsFoa+ijKdvp9zsrnnt6+yFokfwEpeJm0xuJDVoxiRCAaTzyJND8GJkofo2IcKWaUZ/OECVzw==} @@ -6918,10 +6938,6 @@ packages: chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} - chownr@2.0.0: - resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} - engines: {node: '>=10'} - chownr@3.0.0: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} @@ -6945,10 +6961,6 @@ packages: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} - ci-info@4.3.0: - resolution: {integrity: sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==} - engines: {node: '>=8'} - citty@0.1.6: resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} @@ -6978,10 +6990,6 @@ packages: resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} engines: {node: '>=6'} - cli-table3@0.6.5: - resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} - engines: {node: 10.* || >= 12.*} - cli-width@3.0.0: resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} engines: {node: '>= 10'} @@ -7045,9 +7053,6 @@ packages: resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} engines: {node: '>=12.5.0'} - colorette@1.2.1: - resolution: {integrity: sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==} - colorspace@1.1.4: resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==} @@ -7109,9 +7114,6 @@ packages: compute-scroll-into-view@3.1.1: resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==} - computer-name@0.1.0: - resolution: {integrity: sha512-lD6QWjbspZlqWkk5g4gcSvs+TS4LrSiMQfd4LSbAWmmdvLOJzSgTas3LM4p+sGnAXNP0KVNe+/CmkFkAAHwUbA==} - concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -7133,9 +7135,6 @@ packages: config-chain@1.1.13: resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} - configparser@0.3.10: - resolution: {integrity: sha512-wTMIO5ZMA83xLataWsILTX4JV0IinnHXoWK0ib+DeqQvurUJf8ew+iwS9b4Ol5DfMUiY1UvfGkvrA60DJIIR6A==} - connect@3.7.0: resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==} engines: {node: '>= 0.10.0'} @@ -7197,9 +7196,6 @@ packages: resolution: {integrity: sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==} engines: {node: '>=4'} - countries-list@3.1.1: - resolution: {integrity: sha512-nPklKJ5qtmY5MdBKw1NiBAoyx5Sa7p2yPpljZyQ7gyCN1m+eMFs9I6CT37Mxt8zvR5L3VzD3DJBE4WQzX3WF4A==} - country-flag-icons@1.5.19: resolution: {integrity: sha512-D/ZkRyj+ywJC6b2IrAN3/tpbReMUqmuRLlcKFoY/o0+EPQN9Ev/e8tV+D3+9scvu/tarxwLErNwS73C3yzxs/g==} @@ -7421,14 +7417,6 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} - default-browser-id@5.0.0: - resolution: {integrity: sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==} - engines: {node: '>=18'} - - default-browser@5.2.1: - resolution: {integrity: sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==} - engines: {node: '>=18'} - defaults@1.0.4: resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} @@ -7444,10 +7432,6 @@ packages: resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} engines: {node: '>=8'} - define-lazy-prop@3.0.0: - resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} - engines: {node: '>=12'} - defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} @@ -7489,10 +7473,6 @@ packages: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} engines: {node: '>=8'} - detect-indent@7.0.1: - resolution: {integrity: sha512-Mc7QhQ8s+cLrnUfU/Ji94vG/r8M26m8f++vyres4ZoojaRDpZ1eSIh/EpzLNwlWuvzSZ3UbDFspjFvTDXe6e/g==} - engines: {node: '>=12.20'} - detect-libc@1.0.3: resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} engines: {node: '>=0.10'} @@ -7749,10 +7729,6 @@ packages: resolution: {integrity: sha512-eJAgf9pdv214Hn98FlUzclRMYWF7WfoLlkS9nWMTm1qcCwn6Ad4EGD9lr9HXMBfSrZhYQujRE+p0adPRkctC6A==} engines: {bun: '>=1', deno: '>=2', node: '>=16'} - editions@6.22.0: - resolution: {integrity: sha512-UgGlf8IW75je7HZjNDpJdCv4cGJWIi6yumFdZ0R7A8/CIhQiWUjyGLCxdHpd8bmyD1gnkfUNK0oeOXqUS2cpfQ==} - engines: {ecmascript: '>= es5', node: '>=4'} - editorconfig@1.0.4: resolution: {integrity: sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==} engines: {node: '>=14'} @@ -7813,10 +7789,6 @@ packages: resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} engines: {node: '>=10.13.0'} - enquirer@2.3.6: - resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==} - engines: {node: '>=8.6'} - enquirer@2.4.1: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} @@ -8036,10 +8008,6 @@ packages: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} - execa@9.6.0: - resolution: {integrity: sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==} - engines: {node: ^18.19.0 || >=20.5.0} - exit-hook@2.2.1: resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} engines: {node: '>=6'} @@ -8242,10 +8210,6 @@ packages: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} - figures@6.1.0: - resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} - engines: {node: '>=18'} - file-selector@2.1.2: resolution: {integrity: sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==} engines: {node: '>= 12'} @@ -8342,10 +8306,6 @@ packages: resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==} engines: {node: '>= 6'} - form-data@4.0.4: - resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} - engines: {node: '>= 6'} - format@0.2.2: resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} engines: {node: '>=0.4.x'} @@ -8428,10 +8388,6 @@ packages: resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} engines: {node: '>=6 <7 || >=8'} - fs-minipass@2.1.0: - resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} - engines: {node: '>= 8'} - fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -8512,9 +8468,6 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - fuzzysearch@1.0.3: - resolution: {integrity: sha512-s+kNWQuI3mo9OALw0HJ6YGmMbLqEufCh2nX/zzV5CrICQ/y4AwPxM+6TIiF9ItFCHXFCyM/BfCCmN57NTIJuPg==} - gaxios@6.7.1: resolution: {integrity: sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==} engines: {node: '>=14'} @@ -8539,10 +8492,6 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-east-asian-width@1.4.0: - resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} - engines: {node: '>=18'} - get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -8627,9 +8576,6 @@ packages: resolution: {integrity: sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==} engines: {node: '>=4'} - globalyzer@0.1.0: - resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==} - globby@10.0.2: resolution: {integrity: sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==} engines: {node: '>=8'} @@ -8813,6 +8759,10 @@ packages: resolution: {integrity: sha512-Pst8FuGqz3L7tFF+u9Pu70eI0xa5S3LPUmrNd5Jm8nTHze9FxLTK9Kaj5g/k4UcwuJSXTP65SyHOPLrffpcAJg==} engines: {node: '>=16.9.0'} + hono@4.9.8: + resolution: {integrity: sha512-JW8Bb4RFWD9iOKxg5PbUarBYGM99IcxFl2FPBo2gSJO11jjUDqlP1Bmfyqt8Z/dGhIQ63PMA9LdcLefXyIasyg==} + engines: {node: '>=16.9.0'} + hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} @@ -8868,10 +8818,6 @@ packages: resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==} engines: {node: '>=10.19.0'} - https-proxy-agent@5.0.0: - resolution: {integrity: sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==} - engines: {node: '>= 6'} - https-proxy-agent@7.0.6: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} @@ -8891,13 +8837,6 @@ packages: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} - human-signals@8.0.1: - resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} - engines: {node: '>=18.18.0'} - - humanize-ms@1.2.1: - resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} - iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -8957,10 +8896,6 @@ packages: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} - indent-string@5.0.0: - resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} - engines: {node: '>=12'} - index-to-position@1.1.0: resolution: {integrity: sha512-XPdx9Dq4t9Qk1mTMbWONJqU7boCoumEH7fRET37HX5+khDUl3J2W6PdALxhILYlIYx2amlwYcRPp28p0tSiojg==} engines: {node: '>=18'} @@ -9067,10 +9002,6 @@ packages: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} - is-ci@4.1.0: - resolution: {integrity: sha512-Ab9bQDQ11lWootZUI5qxgN2ZXwxNI5hTwnsvOc1wyxQ7zQ8OkEDw79mI0+9jI3x432NfwbVRru+3noJfXF6lSQ==} - hasBin: true - is-core-module@2.16.1: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} @@ -9201,10 +9132,6 @@ packages: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} - is-unicode-supported@2.1.0: - resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} - engines: {node: '>=18'} - is-upper-case@1.1.2: resolution: {integrity: sha512-GQYSJMgfeAmVwh9ixyk888l7OIhNAGKtY6QA+IrWlu9MDTCaXmeozOZ2S9Knj7bQwBO/H6J2kb+pbyTUiMNbsw==} @@ -9264,10 +9191,6 @@ packages: resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} engines: {node: '>=8'} - istextorbinary@9.5.0: - resolution: {integrity: sha512-5mbUj3SiZXCuRf9fT3ibzbSSEWiy63gFfksmGfdOzujPjW3k+z8WvIBxcJHBoQNlaZaiyB25deviif2+osLmLw==} - engines: {node: '>=4'} - its-fine@2.0.0: resolution: {integrity: sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==} peerDependencies: @@ -9331,9 +9254,6 @@ packages: resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==} hasBin: true - jju@1.4.0: - resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} - jmespath@0.16.0: resolution: {integrity: sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==} engines: {node: '>= 0.6.0'} @@ -9363,10 +9283,6 @@ packages: resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} engines: {node: '>=14'} - js-levenshtein@1.1.6: - resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} - engines: {node: '>=0.10.0'} - js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -9639,9 +9555,6 @@ packages: lodash-es@4.17.21: resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} - lodash.clonedeep@4.5.0: - resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} - lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} @@ -10145,22 +10058,10 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - minipass@3.3.6: - resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} - engines: {node: '>=8'} - - minipass@5.0.0: - resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} - engines: {node: '>=8'} - minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - minizlib@2.1.2: - resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} - engines: {node: '>= 8'} - minizlib@3.0.2: resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==} engines: {node: '>= 18'} @@ -10225,10 +10126,6 @@ packages: react-dom: optional: true - mri@1.1.6: - resolution: {integrity: sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ==} - engines: {node: '>=4'} - mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -10250,10 +10147,6 @@ packages: resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - mute-stream@2.0.0: - resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} - engines: {node: ^18.17.0 || >=20.5.0} - mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} @@ -10421,10 +10314,6 @@ packages: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - npm-run-path@6.0.0: - resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} - engines: {node: '>=18'} - npm-to-yarn@3.0.1: resolution: {integrity: sha512-tt6PvKu4WyzPwWUzy/hvPFqn+uwXO0K1ZHka8az3NnrhWJDmSqI8ncWq0fkL0k/lmmi5tAC11FXwXuh0rFbt1A==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -10519,10 +10408,6 @@ packages: oniguruma-to-es@4.3.3: resolution: {integrity: sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==} - open@10.2.0: - resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} - engines: {node: '>=18'} - open@7.4.2: resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==} engines: {node: '>=8'} @@ -10675,10 +10560,6 @@ packages: resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} engines: {node: '>=18'} - parse-ms@4.0.0: - resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} - engines: {node: '>=18'} - parse-png@2.1.0: resolution: {integrity: sha512-Nt/a5SfCLiTnQAjx3fHlqp8hRgTL3z7kTQZzvIMS9uCAepnCyjpdEc6M/sz69WqMBdaDBw9sF1F1UaHROYzGkQ==} engines: {node: '>=10'} @@ -11034,10 +10915,6 @@ packages: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - pretty-ms@9.2.0: - resolution: {integrity: sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==} - engines: {node: '>=18'} - pretty@2.0.0: resolution: {integrity: sha512-G9xUchgTEiNpormdYBl+Pha50gOUovT18IvAe7EYMZ1/f9W/WWMPRn+xI68yXNMUk3QXHDwo/1wV/4NejVNe1w==} engines: {node: '>=0.10.0'} @@ -11645,10 +11522,6 @@ packages: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} - retry@0.13.1: - resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} - engines: {node: '>= 4'} - reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -11658,11 +11531,6 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true - rimraf@6.0.1: - resolution: {integrity: sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==} - engines: {node: 20 || >=22} - hasBin: true - robots-parser@3.0.1: resolution: {integrity: sha512-s+pyvQeIKIZ0dx5iJiQk1tPLJAWln39+MI5jtM8wnyws+G5azk+dMnMX0qfbqNetKKNgcWWOdi0sfm+FbQbgdQ==} engines: {node: '>=10.0.0'} @@ -11703,10 +11571,6 @@ packages: rrweb-cssom@0.8.0: resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} - run-applescript@7.1.0: - resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} - engines: {node: '>=18'} - run-async@2.4.1: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} engines: {node: '>=0.12.0'} @@ -12127,10 +11991,6 @@ packages: resolution: {integrity: sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==} engines: {node: '>=16'} - string-width@7.2.0: - resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} - engines: {node: '>=18'} - string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} @@ -12172,10 +12032,6 @@ packages: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} - strip-final-newline@4.0.0: - resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} - engines: {node: '>=18'} - strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} @@ -12281,10 +12137,6 @@ packages: tar-stream@3.1.7: resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} - tar@6.2.1: - resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} - engines: {node: '>=10'} - tar@7.4.3: resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} engines: {node: '>=18'} @@ -12335,10 +12187,6 @@ packages: text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - textextensions@6.11.0: - resolution: {integrity: sha512-tXJwSr9355kFJI3lbCkPpUH5cP8/M0GGy2xLO34aZCjMXBaK3SoPnZwr/oWmo1FdCnELcs4npdCIOFtq9W3ruQ==} - engines: {node: '>=4'} - thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} engines: {node: '>=0.8'} @@ -12365,14 +12213,6 @@ packages: through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - tiged@2.12.7: - resolution: {integrity: sha512-6TwlABgdshi1h9atXFRx86IhuDANtNqfD1OuWmZKKdqqwWNEJXHLa2hrRiyve9kLwHPb2ADc8RU3mSc4MVBE5A==} - engines: {node: '>=8.0.0'} - hasBin: true - - tiny-glob@0.2.8: - resolution: {integrity: sha512-vkQP7qOslq63XRX9kMswlby99kyO5OvKptw7AMwBVMjXEI7Tb61eoI5DydyEMOseyGS5anDN1VPoVxEvH01q8w==} - tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} @@ -12981,10 +12821,6 @@ packages: react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc - version-range@4.15.0: - resolution: {integrity: sha512-Ck0EJbAGxHwprkzFO966t4/5QkRuzh+/I1RxhLgUKKwEn+Cd8NwM60mE3AqBZg5gYODoXW0EFsQvbZjRlvdqbg==} - engines: {node: '>=4'} - vfile-location@5.0.3: resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} @@ -13230,20 +13066,11 @@ packages: engines: {node: ^16.13.0 || >=18.0.0} hasBin: true - which@5.0.0: - resolution: {integrity: sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==} - engines: {node: ^18.17.0 || >=20.5.0} - hasBin: true - why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} hasBin: true - widest-line@5.0.0: - resolution: {integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==} - engines: {node: '>=18'} - winston-transport@4.9.0: resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==} engines: {node: '>= 12.0.0'} @@ -13285,10 +13112,6 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} - wrap-ansi@9.0.2: - resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} - engines: {node: '>=18'} - wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -13359,10 +13182,6 @@ packages: utf-8-validate: optional: true - wsl-utils@0.1.0: - resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} - engines: {node: '>=18'} - xcode@3.0.1: resolution: {integrity: sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA==} engines: {node: '>=10.0.0'} @@ -13455,10 +13274,6 @@ packages: resolution: {integrity: sha512-qJNAmSF77lWjfRVwCZK3PcKYWrr+55RUQTiXDxXHGbxzf8WuuRgftIB3hqZ5fykjOF/MC62cazsG/2ZDBedOnQ==} engines: {node: '>=14.16'} - yoctocolors@2.1.2: - resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} - engines: {node: '>=18'} - youch-core@0.3.3: resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==} @@ -13579,26 +13394,10 @@ snapshots: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.30 - '@apify/actor-templates@0.1.5': {} - '@apify/consts@2.44.1': {} '@apify/datastructures@2.0.3': {} - '@apify/input_schema@3.19.1(ajv@8.17.1)': - dependencies: - '@apify/consts': 2.44.1 - '@apify/input_secrets': 1.2.6 - acorn-loose: 8.5.2 - ajv: 8.17.1 - countries-list: 3.1.1 - - '@apify/input_secrets@1.2.6': - dependencies: - '@apify/log': 2.5.22 - '@apify/utilities': 2.19.0 - ow: 0.28.2 - '@apify/log@2.5.22': dependencies: '@apify/consts': 2.44.1 @@ -13619,11 +13418,6 @@ snapshots: '@apify/consts': 2.44.1 '@apify/log': 2.5.22 - '@apify/utilities@2.19.0': - dependencies: - '@apify/consts': 2.44.1 - '@apify/log': 2.5.22 - '@ark/schema@0.49.0': dependencies: '@ark/util': 0.49.0 @@ -13709,6 +13503,52 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/client-sqs@3.891.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.890.0 + '@aws-sdk/credential-provider-node': 3.891.0 + '@aws-sdk/middleware-host-header': 3.891.0 + '@aws-sdk/middleware-logger': 3.891.0 + '@aws-sdk/middleware-recursion-detection': 3.891.0 + '@aws-sdk/middleware-sdk-sqs': 3.891.0 + '@aws-sdk/middleware-user-agent': 3.891.0 + '@aws-sdk/region-config-resolver': 3.890.0 + '@aws-sdk/types': 3.887.0 + '@aws-sdk/util-endpoints': 3.891.0 + '@aws-sdk/util-user-agent-browser': 3.887.0 + '@aws-sdk/util-user-agent-node': 3.891.0 + '@smithy/config-resolver': 4.2.2 + '@smithy/core': 3.11.0 + '@smithy/fetch-http-handler': 5.2.1 + '@smithy/hash-node': 4.1.1 + '@smithy/invalid-dependency': 4.1.1 + '@smithy/md5-js': 4.1.1 + '@smithy/middleware-content-length': 4.1.1 + '@smithy/middleware-endpoint': 4.2.2 + '@smithy/middleware-retry': 4.2.4 + '@smithy/middleware-serde': 4.1.1 + '@smithy/middleware-stack': 4.1.1 + '@smithy/node-config-provider': 4.2.2 + '@smithy/node-http-handler': 4.2.1 + '@smithy/protocol-http': 5.2.1 + '@smithy/smithy-client': 4.6.2 + '@smithy/types': 4.5.0 + '@smithy/url-parser': 4.1.1 + '@smithy/util-base64': 4.1.0 + '@smithy/util-body-length-browser': 4.1.0 + '@smithy/util-body-length-node': 4.1.0 + '@smithy/util-defaults-mode-browser': 4.1.2 + '@smithy/util-defaults-mode-node': 4.1.2 + '@smithy/util-endpoints': 3.1.2 + '@smithy/util-middleware': 4.1.1 + '@smithy/util-retry': 4.1.2 + '@smithy/util-utf8': 4.1.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/client-sso@3.890.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 @@ -13752,6 +13592,49 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/client-sso@3.891.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.890.0 + '@aws-sdk/middleware-host-header': 3.891.0 + '@aws-sdk/middleware-logger': 3.891.0 + '@aws-sdk/middleware-recursion-detection': 3.891.0 + '@aws-sdk/middleware-user-agent': 3.891.0 + '@aws-sdk/region-config-resolver': 3.890.0 + '@aws-sdk/types': 3.887.0 + '@aws-sdk/util-endpoints': 3.891.0 + '@aws-sdk/util-user-agent-browser': 3.887.0 + '@aws-sdk/util-user-agent-node': 3.891.0 + '@smithy/config-resolver': 4.2.2 + '@smithy/core': 3.11.0 + '@smithy/fetch-http-handler': 5.2.1 + '@smithy/hash-node': 4.1.1 + '@smithy/invalid-dependency': 4.1.1 + '@smithy/middleware-content-length': 4.1.1 + '@smithy/middleware-endpoint': 4.2.2 + '@smithy/middleware-retry': 4.2.4 + '@smithy/middleware-serde': 4.1.1 + '@smithy/middleware-stack': 4.1.1 + '@smithy/node-config-provider': 4.2.2 + '@smithy/node-http-handler': 4.2.1 + '@smithy/protocol-http': 5.2.1 + '@smithy/smithy-client': 4.6.2 + '@smithy/types': 4.5.0 + '@smithy/url-parser': 4.1.1 + '@smithy/util-base64': 4.1.0 + '@smithy/util-body-length-browser': 4.1.0 + '@smithy/util-body-length-node': 4.1.0 + '@smithy/util-defaults-mode-browser': 4.1.2 + '@smithy/util-defaults-mode-node': 4.1.2 + '@smithy/util-endpoints': 3.1.2 + '@smithy/util-middleware': 4.1.1 + '@smithy/util-retry': 4.1.2 + '@smithy/util-utf8': 4.1.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/core@3.890.0': dependencies: '@aws-sdk/types': 3.887.0 @@ -13809,6 +13692,24 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/credential-provider-ini@3.891.0': + dependencies: + '@aws-sdk/core': 3.890.0 + '@aws-sdk/credential-provider-env': 3.890.0 + '@aws-sdk/credential-provider-http': 3.890.0 + '@aws-sdk/credential-provider-process': 3.890.0 + '@aws-sdk/credential-provider-sso': 3.891.0 + '@aws-sdk/credential-provider-web-identity': 3.891.0 + '@aws-sdk/nested-clients': 3.891.0 + '@aws-sdk/types': 3.887.0 + '@smithy/credential-provider-imds': 4.1.2 + '@smithy/property-provider': 4.1.1 + '@smithy/shared-ini-file-loader': 4.2.0 + '@smithy/types': 4.5.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/credential-provider-node@3.890.0': dependencies: '@aws-sdk/credential-provider-env': 3.890.0 @@ -13826,6 +13727,23 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/credential-provider-node@3.891.0': + dependencies: + '@aws-sdk/credential-provider-env': 3.890.0 + '@aws-sdk/credential-provider-http': 3.890.0 + '@aws-sdk/credential-provider-ini': 3.891.0 + '@aws-sdk/credential-provider-process': 3.890.0 + '@aws-sdk/credential-provider-sso': 3.891.0 + '@aws-sdk/credential-provider-web-identity': 3.891.0 + '@aws-sdk/types': 3.887.0 + '@smithy/credential-provider-imds': 4.1.2 + '@smithy/property-provider': 4.1.1 + '@smithy/shared-ini-file-loader': 4.2.0 + '@smithy/types': 4.5.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/credential-provider-process@3.890.0': dependencies: '@aws-sdk/core': 3.890.0 @@ -13848,6 +13766,19 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/credential-provider-sso@3.891.0': + dependencies: + '@aws-sdk/client-sso': 3.891.0 + '@aws-sdk/core': 3.890.0 + '@aws-sdk/token-providers': 3.891.0 + '@aws-sdk/types': 3.887.0 + '@smithy/property-provider': 4.1.1 + '@smithy/shared-ini-file-loader': 4.2.0 + '@smithy/types': 4.5.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/credential-provider-web-identity@3.890.0': dependencies: '@aws-sdk/core': 3.890.0 @@ -13860,6 +13791,18 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/credential-provider-web-identity@3.891.0': + dependencies: + '@aws-sdk/core': 3.890.0 + '@aws-sdk/nested-clients': 3.891.0 + '@aws-sdk/types': 3.887.0 + '@smithy/property-provider': 4.1.1 + '@smithy/shared-ini-file-loader': 4.2.0 + '@smithy/types': 4.5.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/middleware-host-header@3.887.0': dependencies: '@aws-sdk/types': 3.887.0 @@ -13867,12 +13810,25 @@ snapshots: '@smithy/types': 4.5.0 tslib: 2.8.1 + '@aws-sdk/middleware-host-header@3.891.0': + dependencies: + '@aws-sdk/types': 3.887.0 + '@smithy/protocol-http': 5.2.1 + '@smithy/types': 4.5.0 + tslib: 2.8.1 + '@aws-sdk/middleware-logger@3.887.0': dependencies: '@aws-sdk/types': 3.887.0 '@smithy/types': 4.5.0 tslib: 2.8.1 + '@aws-sdk/middleware-logger@3.891.0': + dependencies: + '@aws-sdk/types': 3.887.0 + '@smithy/types': 4.5.0 + tslib: 2.8.1 + '@aws-sdk/middleware-recursion-detection@3.887.0': dependencies: '@aws-sdk/types': 3.887.0 @@ -13881,6 +13837,14 @@ snapshots: '@smithy/types': 4.5.0 tslib: 2.8.1 + '@aws-sdk/middleware-recursion-detection@3.891.0': + dependencies: + '@aws-sdk/types': 3.887.0 + '@aws/lambda-invoke-store': 0.0.1 + '@smithy/protocol-http': 5.2.1 + '@smithy/types': 4.5.0 + tslib: 2.8.1 + '@aws-sdk/middleware-sdk-s3@3.890.0': dependencies: '@aws-sdk/core': 3.890.0 @@ -13898,6 +13862,15 @@ snapshots: '@smithy/util-utf8': 4.1.0 tslib: 2.8.1 + '@aws-sdk/middleware-sdk-sqs@3.891.0': + dependencies: + '@aws-sdk/types': 3.887.0 + '@smithy/smithy-client': 4.6.2 + '@smithy/types': 4.5.0 + '@smithy/util-hex-encoding': 4.1.0 + '@smithy/util-utf8': 4.1.0 + tslib: 2.8.1 + '@aws-sdk/middleware-user-agent@3.890.0': dependencies: '@aws-sdk/core': 3.890.0 @@ -13908,6 +13881,16 @@ snapshots: '@smithy/types': 4.5.0 tslib: 2.8.1 + '@aws-sdk/middleware-user-agent@3.891.0': + dependencies: + '@aws-sdk/core': 3.890.0 + '@aws-sdk/types': 3.887.0 + '@aws-sdk/util-endpoints': 3.891.0 + '@smithy/core': 3.11.0 + '@smithy/protocol-http': 5.2.1 + '@smithy/types': 4.5.0 + tslib: 2.8.1 + '@aws-sdk/nested-clients@3.890.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 @@ -13951,6 +13934,49 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/nested-clients@3.891.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.890.0 + '@aws-sdk/middleware-host-header': 3.891.0 + '@aws-sdk/middleware-logger': 3.891.0 + '@aws-sdk/middleware-recursion-detection': 3.891.0 + '@aws-sdk/middleware-user-agent': 3.891.0 + '@aws-sdk/region-config-resolver': 3.890.0 + '@aws-sdk/types': 3.887.0 + '@aws-sdk/util-endpoints': 3.891.0 + '@aws-sdk/util-user-agent-browser': 3.887.0 + '@aws-sdk/util-user-agent-node': 3.891.0 + '@smithy/config-resolver': 4.2.2 + '@smithy/core': 3.11.0 + '@smithy/fetch-http-handler': 5.2.1 + '@smithy/hash-node': 4.1.1 + '@smithy/invalid-dependency': 4.1.1 + '@smithy/middleware-content-length': 4.1.1 + '@smithy/middleware-endpoint': 4.2.2 + '@smithy/middleware-retry': 4.2.4 + '@smithy/middleware-serde': 4.1.1 + '@smithy/middleware-stack': 4.1.1 + '@smithy/node-config-provider': 4.2.2 + '@smithy/node-http-handler': 4.2.1 + '@smithy/protocol-http': 5.2.1 + '@smithy/smithy-client': 4.6.2 + '@smithy/types': 4.5.0 + '@smithy/url-parser': 4.1.1 + '@smithy/util-base64': 4.1.0 + '@smithy/util-body-length-browser': 4.1.0 + '@smithy/util-body-length-node': 4.1.0 + '@smithy/util-defaults-mode-browser': 4.1.2 + '@smithy/util-defaults-mode-node': 4.1.2 + '@smithy/util-endpoints': 3.1.2 + '@smithy/util-middleware': 4.1.1 + '@smithy/util-retry': 4.1.2 + '@smithy/util-utf8': 4.1.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/region-config-resolver@3.890.0': dependencies: '@aws-sdk/types': 3.887.0 @@ -13981,6 +14007,18 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/token-providers@3.891.0': + dependencies: + '@aws-sdk/core': 3.890.0 + '@aws-sdk/nested-clients': 3.891.0 + '@aws-sdk/types': 3.887.0 + '@smithy/property-provider': 4.1.1 + '@smithy/shared-ini-file-loader': 4.2.0 + '@smithy/types': 4.5.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/types@3.887.0': dependencies: '@smithy/types': 4.5.0 @@ -13998,6 +14036,14 @@ snapshots: '@smithy/util-endpoints': 3.1.2 tslib: 2.8.1 + '@aws-sdk/util-endpoints@3.891.0': + dependencies: + '@aws-sdk/types': 3.887.0 + '@smithy/types': 4.5.0 + '@smithy/url-parser': 4.1.1 + '@smithy/util-endpoints': 3.1.2 + tslib: 2.8.1 + '@aws-sdk/util-locate-window@3.873.0': dependencies: tslib: 2.8.1 @@ -14017,6 +14063,14 @@ snapshots: '@smithy/types': 4.5.0 tslib: 2.8.1 + '@aws-sdk/util-user-agent-node@3.891.0': + dependencies: + '@aws-sdk/middleware-user-agent': 3.891.0 + '@aws-sdk/types': 3.887.0 + '@smithy/node-config-provider': 4.2.2 + '@smithy/types': 4.5.0 + tslib: 2.8.1 + '@aws-sdk/xml-builder@3.887.0': dependencies: '@smithy/types': 4.5.0 @@ -15044,9 +15098,6 @@ snapshots: '@cloudflare/workers-types@4.20250414.0': optional: true - '@colors/colors@1.5.0': - optional: true - '@colors/colors@1.6.0': {} '@content-collections/cli@0.1.7(@content-collections/core@0.11.1(typescript@5.9.2))': @@ -16358,21 +16409,6 @@ snapshots: '@img/sharp-win32-x64@0.33.5': optional: true - '@inquirer/ansi@1.0.0': {} - - '@inquirer/core@10.2.2(@types/node@24.5.1)': - dependencies: - '@inquirer/ansi': 1.0.0 - '@inquirer/figures': 1.0.13 - '@inquirer/type': 3.0.8(@types/node@24.5.1) - cli-width: 4.1.0 - mute-stream: 2.0.0 - signal-exit: 4.1.0 - wrap-ansi: 6.2.0 - yoctocolors-cjs: 2.1.3 - optionalDependencies: - '@types/node': 24.5.1 - '@inquirer/external-editor@1.0.1(@types/node@24.5.1)': dependencies: chardet: 2.1.0 @@ -16382,35 +16418,6 @@ snapshots: '@inquirer/figures@1.0.13': {} - '@inquirer/input@4.2.4(@types/node@24.5.1)': - dependencies: - '@inquirer/core': 10.2.2(@types/node@24.5.1) - '@inquirer/type': 3.0.8(@types/node@24.5.1) - optionalDependencies: - '@types/node': 24.5.1 - - '@inquirer/password@4.0.20(@types/node@24.5.1)': - dependencies: - '@inquirer/ansi': 1.0.0 - '@inquirer/core': 10.2.2(@types/node@24.5.1) - '@inquirer/type': 3.0.8(@types/node@24.5.1) - optionalDependencies: - '@types/node': 24.5.1 - - '@inquirer/select@4.3.4(@types/node@24.5.1)': - dependencies: - '@inquirer/ansi': 1.0.0 - '@inquirer/core': 10.2.2(@types/node@24.5.1) - '@inquirer/figures': 1.0.13 - '@inquirer/type': 3.0.8(@types/node@24.5.1) - yoctocolors-cjs: 2.1.3 - optionalDependencies: - '@types/node': 24.5.1 - - '@inquirer/type@3.0.8(@types/node@24.5.1)': - optionalDependencies: - '@types/node': 24.5.1 - '@ioredis/commands@1.3.0': {} '@isaacs/balanced-match@4.0.1': {} @@ -16671,7 +16678,7 @@ snapshots: pkce-challenge: 4.1.0 raw-body: 3.0.1 zod: 3.25.76 - zod-to-json-schema: 3.24.3(zod@3.25.76) + zod-to-json-schema: 3.24.6(zod@3.25.76) transitivePeerDependencies: - supports-color @@ -18483,21 +18490,13 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.49.0': optional: true - '@root/walk@1.1.0': {} - '@sapphire/async-queue@1.5.5': {} - '@sapphire/duration@1.2.0': {} - - '@sapphire/result@2.7.2': {} - '@sapphire/shapeshift@3.9.7': dependencies: fast-deep-equal: 3.1.3 lodash: 4.17.21 - '@sapphire/timestamp@1.0.5': {} - '@sec-ant/readable-stream@0.4.1': {} '@selderee/plugin-htmlparser2@0.11.0': @@ -18625,8 +18624,6 @@ snapshots: '@sindresorhus/merge-streams@2.3.0': {} - '@sindresorhus/merge-streams@4.0.0': {} - '@sinonjs/commons@3.0.1': dependencies: type-detect: 4.0.8 @@ -18635,8 +18632,6 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 - '@skyra/jaro-winkler@1.1.1': {} - '@smithy/abort-controller@4.1.1': dependencies: '@smithy/types': 4.5.0 @@ -18650,7 +18645,21 @@ snapshots: '@smithy/util-middleware': 4.1.1 tslib: 2.8.1 - '@smithy/core@3.11.0': + '@smithy/core@3.11.0': + dependencies: + '@smithy/middleware-serde': 4.1.1 + '@smithy/protocol-http': 5.2.1 + '@smithy/types': 4.5.0 + '@smithy/util-base64': 4.1.0 + '@smithy/util-body-length-browser': 4.1.0 + '@smithy/util-middleware': 4.1.1 + '@smithy/util-stream': 4.3.1 + '@smithy/util-utf8': 4.1.0 + '@types/uuid': 9.0.8 + tslib: 2.8.1 + uuid: 9.0.1 + + '@smithy/core@3.11.1': dependencies: '@smithy/middleware-serde': 4.1.1 '@smithy/protocol-http': 5.2.1 @@ -18658,7 +18667,7 @@ snapshots: '@smithy/util-base64': 4.1.0 '@smithy/util-body-length-browser': 4.1.0 '@smithy/util-middleware': 4.1.1 - '@smithy/util-stream': 4.3.1 + '@smithy/util-stream': 4.3.2 '@smithy/util-utf8': 4.1.0 '@types/uuid': 9.0.8 tslib: 2.8.1 @@ -18700,6 +18709,12 @@ snapshots: dependencies: tslib: 2.8.1 + '@smithy/md5-js@4.1.1': + dependencies: + '@smithy/types': 4.5.0 + '@smithy/util-utf8': 4.1.0 + tslib: 2.8.1 + '@smithy/middleware-content-length@4.1.1': dependencies: '@smithy/protocol-http': 5.2.1 @@ -18717,6 +18732,17 @@ snapshots: '@smithy/util-middleware': 4.1.1 tslib: 2.8.1 + '@smithy/middleware-endpoint@4.2.3': + dependencies: + '@smithy/core': 3.11.1 + '@smithy/middleware-serde': 4.1.1 + '@smithy/node-config-provider': 4.2.2 + '@smithy/shared-ini-file-loader': 4.2.0 + '@smithy/types': 4.5.0 + '@smithy/url-parser': 4.1.1 + '@smithy/util-middleware': 4.1.1 + tslib: 2.8.1 + '@smithy/middleware-retry@4.2.2': dependencies: '@smithy/node-config-provider': 4.2.2 @@ -18730,6 +18756,19 @@ snapshots: tslib: 2.8.1 uuid: 9.0.1 + '@smithy/middleware-retry@4.2.4': + dependencies: + '@smithy/node-config-provider': 4.2.2 + '@smithy/protocol-http': 5.2.1 + '@smithy/service-error-classification': 4.1.2 + '@smithy/smithy-client': 4.6.3 + '@smithy/types': 4.5.0 + '@smithy/util-middleware': 4.1.1 + '@smithy/util-retry': 4.1.2 + '@types/uuid': 9.0.8 + tslib: 2.8.1 + uuid: 9.0.1 + '@smithy/middleware-serde@4.1.1': dependencies: '@smithy/protocol-http': 5.2.1 @@ -18781,6 +18820,10 @@ snapshots: dependencies: '@smithy/types': 4.5.0 + '@smithy/service-error-classification@4.1.2': + dependencies: + '@smithy/types': 4.5.0 + '@smithy/shared-ini-file-loader@4.2.0': dependencies: '@smithy/types': 4.5.0 @@ -18807,6 +18850,16 @@ snapshots: '@smithy/util-stream': 4.3.1 tslib: 2.8.1 + '@smithy/smithy-client@4.6.3': + dependencies: + '@smithy/core': 3.11.1 + '@smithy/middleware-endpoint': 4.2.3 + '@smithy/middleware-stack': 4.1.1 + '@smithy/protocol-http': 5.2.1 + '@smithy/types': 4.5.0 + '@smithy/util-stream': 4.3.2 + tslib: 2.8.1 + '@smithy/types@4.5.0': dependencies: tslib: 2.8.1 @@ -18884,6 +18937,12 @@ snapshots: '@smithy/types': 4.5.0 tslib: 2.8.1 + '@smithy/util-retry@4.1.2': + dependencies: + '@smithy/service-error-classification': 4.1.2 + '@smithy/types': 4.5.0 + tslib: 2.8.1 + '@smithy/util-stream@4.3.1': dependencies: '@smithy/fetch-http-handler': 5.2.1 @@ -18895,6 +18954,17 @@ snapshots: '@smithy/util-utf8': 4.1.0 tslib: 2.8.1 + '@smithy/util-stream@4.3.2': + dependencies: + '@smithy/fetch-http-handler': 5.2.1 + '@smithy/node-http-handler': 4.2.1 + '@smithy/types': 4.5.0 + '@smithy/util-base64': 4.1.0 + '@smithy/util-buffer-from': 4.1.0 + '@smithy/util-hex-encoding': 4.1.0 + '@smithy/util-utf8': 4.1.0 + tslib: 2.8.1 + '@smithy/util-uri-escape@4.1.0': dependencies: tslib: 2.8.1 @@ -18930,12 +19000,6 @@ snapshots: dependencies: defer-to-connect: 2.0.1 - '@t3-oss/env-core@0.13.8(arktype@2.1.22)(typescript@5.9.2)(zod@3.24.2)': - optionalDependencies: - arktype: 2.1.22 - typescript: 5.9.2 - zod: 3.24.2 - '@t3-oss/env-core@0.13.8(arktype@2.1.22)(typescript@5.9.2)(zod@3.25.76)': optionalDependencies: arktype: 2.1.22 @@ -20074,10 +20138,6 @@ snapshots: dependencies: acorn: 8.15.0 - acorn-loose@8.5.2: - dependencies: - acorn: 8.15.0 - acorn-walk@8.3.2: {} acorn-walk@8.3.4: @@ -20092,18 +20152,8 @@ snapshots: adm-zip@0.5.16: {} - agent-base@6.0.2: - dependencies: - debug: 4.4.1 - transitivePeerDependencies: - - supports-color - agent-base@7.1.3: {} - agentkeepalive@4.6.0: - dependencies: - humanize-ms: 1.2.1 - aggregate-error@3.1.0: dependencies: clean-stack: 2.2.0 @@ -20134,6 +20184,7 @@ snapshots: fast-uri: 3.0.6 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + optional: true anser@1.4.10: {} @@ -20174,97 +20225,6 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 - apify-cli@1.1.1(@types/node@24.5.1): - dependencies: - '@apify/actor-templates': 0.1.5 - '@apify/consts': 2.44.1 - '@apify/input_schema': 3.19.1(ajv@8.17.1) - '@apify/utilities': 2.19.0 - '@crawlee/memory-storage': 3.14.1 - '@inquirer/core': 10.2.2(@types/node@24.5.1) - '@inquirer/input': 4.2.4(@types/node@24.5.1) - '@inquirer/password': 4.0.20(@types/node@24.5.1) - '@inquirer/select': 4.3.4(@types/node@24.5.1) - '@root/walk': 1.1.0 - '@sapphire/duration': 1.2.0 - '@sapphire/result': 2.7.2 - '@sapphire/timestamp': 1.0.5 - '@skyra/jaro-winkler': 1.1.1 - adm-zip: 0.5.16 - ajv: 8.17.1 - apify-client: 2.17.0 - archiver: 7.0.1 - axios: 1.12.2 - chalk: 5.6.2 - cli-table3: 0.6.5 - computer-name: 0.1.0 - configparser: 0.3.10 - cors: 2.8.5 - detect-indent: 7.0.1 - escape-string-regexp: 5.0.0 - execa: 9.6.0 - express: 5.1.0 - globby: 14.1.0 - handlebars: 4.7.8 - indent-string: 5.0.0 - is-ci: 4.1.0 - istextorbinary: 9.5.0 - jju: 1.4.0 - js-levenshtein: 1.1.6 - lodash.clonedeep: 4.5.0 - mime: 4.0.7 - open: 10.2.0 - rimraf: 6.0.1 - semver: 7.7.2 - string-width: 7.2.0 - strip-ansi: 7.1.0 - tiged: 2.12.7 - which: 5.0.0 - widest-line: 5.0.0 - wrap-ansi: 9.0.2 - transitivePeerDependencies: - - '@types/node' - - debug - - supports-color - - apify-client@2.17.0: - dependencies: - '@apify/consts': 2.44.1 - '@apify/log': 2.5.22 - '@apify/utilities': 2.19.0 - '@crawlee/types': 3.14.1 - agentkeepalive: 4.6.0 - async-retry: 1.3.3 - axios: 1.12.2 - content-type: 1.0.5 - ow: 0.28.2 - tslib: 2.8.1 - type-fest: 4.41.0 - transitivePeerDependencies: - - debug - - apify@3.4.5: - dependencies: - '@apify/consts': 2.44.1 - '@apify/input_secrets': 1.2.6 - '@apify/log': 2.5.22 - '@apify/timeout': 0.3.2 - '@apify/utilities': 2.19.0 - '@crawlee/core': 3.14.1 - '@crawlee/types': 3.14.1 - '@crawlee/utils': 3.14.1 - apify-client: 2.17.0 - fs-extra: 11.3.1 - ow: 0.28.2 - semver: 7.7.2 - tslib: 2.8.1 - ws: 8.18.1 - transitivePeerDependencies: - - bufferutil - - debug - - supports-color - - utf-8-validate - archiver-utils@5.0.2: dependencies: glob: 10.4.5 @@ -20334,10 +20294,6 @@ snapshots: async-limiter@1.0.1: {} - async-retry@1.3.3: - dependencies: - retry: 0.13.1 - async-sema@3.1.1: {} async@3.2.6: {} @@ -20378,14 +20334,6 @@ snapshots: aws4fetch@1.0.20: optional: true - axios@1.12.2: - dependencies: - follow-redirects: 1.15.9(debug@4.4.0) - form-data: 4.0.4 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - axios@1.8.3(debug@4.4.0): dependencies: follow-redirects: 1.15.9(debug@4.4.0) @@ -20598,10 +20546,6 @@ snapshots: binary-extensions@2.3.0: {} - binaryextensions@6.11.0: - dependencies: - editions: 6.22.0 - bindings@1.5.0: dependencies: file-uri-to-path: 1.0.0 @@ -20700,10 +20644,6 @@ snapshots: builtin-modules@3.3.0: {} - bundle-name@4.1.0: - dependencies: - run-applescript: 7.1.0 - bundle-require@5.1.0(esbuild@0.25.3): dependencies: esbuild: 0.25.3 @@ -20830,8 +20770,6 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 - chalk@5.6.2: {} - change-case@3.1.0: dependencies: camel-case: 3.0.0 @@ -20919,8 +20857,6 @@ snapshots: chownr@1.1.4: optional: true - chownr@2.0.0: {} - chownr@3.0.0: {} chrome-launcher@0.15.2: @@ -20950,8 +20886,6 @@ snapshots: ci-info@3.9.0: {} - ci-info@4.3.0: {} - citty@0.1.6: dependencies: consola: 3.4.2 @@ -20978,12 +20912,6 @@ snapshots: cli-spinners@2.9.2: {} - cli-table3@0.6.5: - dependencies: - string-width: 4.2.3 - optionalDependencies: - '@colors/colors': 1.5.0 - cli-width@3.0.0: {} cli-width@4.1.0: {} @@ -21049,8 +20977,6 @@ snapshots: color-convert: 2.0.1 color-string: 1.9.1 - colorette@1.2.1: {} - colorspace@1.1.4: dependencies: color: 3.2.1 @@ -21108,8 +21034,6 @@ snapshots: compute-scroll-into-view@3.1.1: {} - computer-name@0.1.0: {} - concat-map@0.0.1: {} concurrently@9.2.1: @@ -21136,10 +21060,6 @@ snapshots: ini: 1.3.8 proto-list: 1.2.4 - configparser@0.3.10: - dependencies: - mkdirp: 0.5.6 - connect@3.7.0: dependencies: debug: 2.6.9 @@ -21199,8 +21119,6 @@ snapshots: js-yaml: 3.14.1 parse-json: 4.0.0 - countries-list@3.1.1: {} - country-flag-icons@1.5.19: {} crawlee@3.14.1(playwright@1.55.0): @@ -21373,13 +21291,6 @@ snapshots: deepmerge@4.3.1: {} - default-browser-id@5.0.0: {} - - default-browser@5.2.1: - dependencies: - bundle-name: 4.1.0 - default-browser-id: 5.0.0 - defaults@1.0.4: dependencies: clone: 1.0.4 @@ -21394,8 +21305,6 @@ snapshots: define-lazy-prop@2.0.0: {} - define-lazy-prop@3.0.0: {} - defu@6.1.4: {} degenerator@5.0.1: @@ -21433,8 +21342,6 @@ snapshots: detect-indent@6.1.0: {} - detect-indent@7.0.1: {} - detect-libc@1.0.3: {} detect-libc@2.0.4: {} @@ -21618,10 +21525,6 @@ snapshots: '@noble/curves': 1.8.1 '@noble/hashes': 1.7.1 - editions@6.22.0: - dependencies: - version-range: 4.15.0 - editorconfig@1.0.4: dependencies: '@one-ini/wasm': 0.1.1 @@ -21675,10 +21578,6 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.2.1 - enquirer@2.3.6: - dependencies: - ansi-colors: 4.1.3 - enquirer@2.4.1: dependencies: ansi-colors: 4.1.3 @@ -22065,21 +21964,6 @@ snapshots: signal-exit: 4.1.0 strip-final-newline: 3.0.0 - execa@9.6.0: - dependencies: - '@sindresorhus/merge-streams': 4.0.0 - cross-spawn: 7.0.6 - figures: 6.1.0 - get-stream: 9.0.1 - human-signals: 8.0.1 - is-plain-obj: 4.1.0 - is-stream: 4.0.1 - npm-run-path: 6.0.0 - pretty-ms: 9.2.0 - signal-exit: 4.1.0 - strip-final-newline: 4.0.0 - yoctocolors: 2.1.2 - exit-hook@2.2.1: {} expand-template@2.0.3: @@ -22276,7 +22160,8 @@ snapshots: fast-json-stable-stringify@2.1.0: {} - fast-uri@3.0.6: {} + fast-uri@3.0.6: + optional: true fast-xml-parser@5.2.5: dependencies: @@ -22329,10 +22214,6 @@ snapshots: dependencies: escape-string-regexp: 1.0.5 - figures@6.1.0: - dependencies: - is-unicode-supported: 2.1.0 - file-selector@2.1.2: dependencies: tslib: 2.8.1 @@ -22442,14 +22323,6 @@ snapshots: es-set-tostringtag: 2.1.0 mime-types: 2.1.35 - form-data@4.0.4: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - es-set-tostringtag: 2.1.0 - hasown: 2.0.2 - mime-types: 2.1.35 - format@0.2.2: {} formatly@0.3.0: @@ -22515,10 +22388,6 @@ snapshots: jsonfile: 4.0.0 universalify: 0.1.2 - fs-minipass@2.1.0: - dependencies: - minipass: 3.3.6 - fs.realpath@1.0.0: {} fsevents@2.3.2: @@ -22610,8 +22479,6 @@ snapshots: function-bind@1.1.2: {} - fuzzysearch@1.0.3: {} - gaxios@6.7.1: dependencies: extend: 3.0.2 @@ -22649,8 +22516,6 @@ snapshots: get-caller-file@2.0.5: {} - get-east-asian-width@1.4.0: {} - get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -22757,8 +22622,6 @@ snapshots: dependencies: ini: 1.3.8 - globalyzer@0.1.0: {} - globby@10.0.2: dependencies: '@types/glob': 7.2.0 @@ -23094,6 +22957,8 @@ snapshots: hono@4.7.4: {} + hono@4.9.8: {} + hookable@5.5.3: {} hosted-git-info@7.0.2: @@ -23165,13 +23030,6 @@ snapshots: quick-lru: 5.1.1 resolve-alpn: 1.2.1 - https-proxy-agent@5.0.0: - dependencies: - agent-base: 6.0.2 - debug: 4.4.1 - transitivePeerDependencies: - - supports-color - https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.3 @@ -23187,12 +23045,6 @@ snapshots: human-signals@5.0.0: {} - human-signals@8.0.1: {} - - humanize-ms@1.2.1: - dependencies: - ms: 2.1.3 - iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 @@ -23237,8 +23089,6 @@ snapshots: indent-string@4.0.0: {} - indent-string@5.0.0: {} - index-to-position@1.1.0: {} inflight@1.0.6: @@ -23374,10 +23224,6 @@ snapshots: is-callable@1.2.7: {} - is-ci@4.1.0: - dependencies: - ci-info: 4.3.0 - is-core-module@2.16.1: dependencies: hasown: 2.0.2 @@ -23470,8 +23316,6 @@ snapshots: is-unicode-supported@0.1.0: {} - is-unicode-supported@2.1.0: {} - is-upper-case@1.1.2: dependencies: upper-case: 1.1.3 @@ -23523,12 +23367,6 @@ snapshots: transitivePeerDependencies: - supports-color - istextorbinary@9.5.0: - dependencies: - binaryextensions: 6.11.0 - editions: 6.22.0 - textextensions: 6.11.0 - its-fine@2.0.0(@types/react@19.1.13)(react@19.1.1): dependencies: '@types/react-reconciler': 0.28.9(@types/react@19.1.13) @@ -23631,8 +23469,6 @@ snapshots: jiti@2.5.1: {} - jju@1.4.0: {} - jmespath@0.16.0: {} jose@4.15.9: {} @@ -23655,8 +23491,6 @@ snapshots: js-cookie@3.0.5: {} - js-levenshtein@1.1.6: {} - js-tokens@4.0.0: {} js-tokens@9.0.1: {} @@ -23712,7 +23546,8 @@ snapshots: json-parse-even-better-errors@2.3.1: optional: true - json-schema-traverse@1.0.0: {} + json-schema-traverse@1.0.0: + optional: true json-schema@0.4.0: {} @@ -23980,8 +23815,6 @@ snapshots: lodash-es@4.17.21: {} - lodash.clonedeep@4.5.0: {} - lodash.debounce@4.0.8: {} lodash.defaults@4.2.0: {} @@ -24851,19 +24684,8 @@ snapshots: minimist@1.2.8: {} - minipass@3.3.6: - dependencies: - yallist: 4.0.0 - - minipass@5.0.0: {} - minipass@7.1.2: {} - minizlib@2.1.2: - dependencies: - minipass: 3.3.6 - yallist: 4.0.0 - minizlib@3.0.2: dependencies: minipass: 7.1.2 @@ -24928,8 +24750,6 @@ snapshots: react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - mri@1.1.6: {} - mri@1.2.0: {} ms@2.0.0: {} @@ -24942,8 +24762,6 @@ snapshots: mute-stream@1.0.0: {} - mute-stream@2.0.0: {} - mz@2.7.0: dependencies: any-promise: 1.3.0 @@ -25184,11 +25002,6 @@ snapshots: dependencies: path-key: 4.0.0 - npm-run-path@6.0.0: - dependencies: - path-key: 4.0.0 - unicorn-magic: 0.3.0 - npm-to-yarn@3.0.1: {} nth-check@2.1.1: @@ -25277,13 +25090,6 @@ snapshots: regex: 6.0.1 regex-recursion: 6.0.2 - open@10.2.0: - dependencies: - default-browser: 5.2.1 - define-lazy-prop: 3.0.0 - is-inside-container: 1.0.0 - wsl-utils: 0.1.0 - open@7.4.2: dependencies: is-docker: 2.2.1 @@ -25499,8 +25305,6 @@ snapshots: index-to-position: 1.1.0 type-fest: 4.41.0 - parse-ms@4.0.0: {} - parse-png@2.1.0: dependencies: pngjs: 3.4.0 @@ -25833,10 +25637,6 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.1 - pretty-ms@9.2.0: - dependencies: - parse-ms: 4.0.0 - pretty@2.0.0: dependencies: condense-newlines: 0.2.1 @@ -26655,19 +26455,12 @@ snapshots: retry@0.12.0: {} - retry@0.13.1: {} - reusify@1.1.0: {} rimraf@3.0.2: dependencies: glob: 7.2.3 - rimraf@6.0.1: - dependencies: - glob: 11.0.3 - package-json-from-dist: 1.0.1 - robots-parser@3.0.1: {} rollup-plugin-visualizer@6.0.3(rollup@4.49.0): @@ -26746,8 +26539,6 @@ snapshots: rrweb-cssom@0.8.0: {} - run-applescript@7.1.0: {} - run-async@2.4.1: {} run-async@3.0.0: {} @@ -27239,12 +27030,6 @@ snapshots: emoji-regex: 10.5.0 strip-ansi: 7.1.0 - string-width@7.2.0: - dependencies: - emoji-regex: 10.5.0 - get-east-asian-width: 1.4.0 - strip-ansi: 7.1.0 - string_decoder@1.1.1: dependencies: safe-buffer: 5.1.2 @@ -27282,8 +27067,6 @@ snapshots: strip-final-newline@3.0.0: {} - strip-final-newline@4.0.0: {} - strip-json-comments@2.0.1: {} strip-json-comments@5.0.2: {} @@ -27414,15 +27197,6 @@ snapshots: fast-fifo: 1.3.2 streamx: 2.22.1 - tar@6.2.1: - dependencies: - chownr: 2.0.0 - fs-minipass: 2.1.0 - minipass: 5.0.0 - minizlib: 2.1.2 - mkdirp: 1.0.4 - yallist: 4.0.0 - tar@7.4.3: dependencies: '@isaacs/fs-minipass': 4.0.1 @@ -27472,10 +27246,6 @@ snapshots: text-table@0.2.0: {} - textextensions@6.11.0: - dependencies: - editions: 6.22.0 - thenify-all@1.6.0: dependencies: thenify: 3.3.1 @@ -27504,25 +27274,6 @@ snapshots: through@2.3.8: {} - tiged@2.12.7: - dependencies: - colorette: 1.2.1 - enquirer: 2.3.6 - fs-extra: 10.1.0 - fuzzysearch: 1.0.3 - https-proxy-agent: 5.0.0 - mri: 1.1.6 - rimraf: 3.0.2 - tar: 6.2.1 - tiny-glob: 0.2.8 - transitivePeerDependencies: - - supports-color - - tiny-glob@0.2.8: - dependencies: - globalyzer: 0.1.0 - globrex: 0.1.2 - tiny-invariant@1.3.3: {} tiny-typed-emitter@2.1.0: {} @@ -28076,8 +27827,6 @@ snapshots: - '@types/react' - '@types/react-dom' - version-range@4.15.0: {} - vfile-location@5.0.3: dependencies: '@types/unist': 3.0.3 @@ -28350,19 +28099,11 @@ snapshots: dependencies: isexe: 3.1.1 - which@5.0.0: - dependencies: - isexe: 3.1.1 - why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 stackback: 0.0.2 - widest-line@5.0.0: - dependencies: - string-width: 7.2.0 - winston-transport@4.9.0: dependencies: logform: 2.7.0 @@ -28430,12 +28171,6 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.0 - wrap-ansi@9.0.2: - dependencies: - ansi-styles: 6.2.1 - string-width: 7.2.0 - strip-ansi: 7.1.0 - wrappy@1.0.2: {} write-file-atomic@4.0.2: @@ -28460,10 +28195,6 @@ snapshots: ws@8.18.3: {} - wsl-utils@0.1.0: - dependencies: - is-wsl: 3.1.0 - xcode@3.0.1: dependencies: simple-plist: 1.3.1 @@ -28543,8 +28274,6 @@ snapshots: yoctocolors@1.0.0: {} - yoctocolors@2.1.2: {} - youch-core@0.3.3: dependencies: '@poppinss/exception': 1.2.2 @@ -28576,10 +28305,6 @@ snapshots: dependencies: zod: 3.24.2 - zod-to-json-schema@3.24.3(zod@3.25.76): - dependencies: - zod: 3.25.76 - zod-to-json-schema@3.24.6(zod@3.25.76): dependencies: zod: 3.25.76 From dc8616196ba07f80f923f63b1f7bbbcf99e5b64e Mon Sep 17 00:00:00 2001 From: ElasticBottle Date: Fri, 19 Sep 2025 03:06:30 +0800 Subject: [PATCH 05/38] feat(task): happy path with site crawler --- packages/task/Dockerfile | 64 +++ packages/task/package.json | 47 +++ packages/task/src/crawlers/site.ts | 91 +++++ packages/task/src/env.ts | 10 + packages/task/src/lib/extract-url-locale.ts | 372 ++++++++++++++++++ .../site-crawler-config/parse-starting-url.ts | 37 ++ .../src/lib/site-crawler-config/router.ts | 144 +++++++ packages/task/src/schema/site.ts | 30 ++ packages/task/src/trigger/site-crawl.ts | 15 + packages/task/trigger.config.ts | 24 ++ packages/task/tsconfig.json | 18 + tooling/typescript/tsconfig.base.json | 4 +- 12 files changed, 854 insertions(+), 2 deletions(-) create mode 100644 packages/task/Dockerfile create mode 100644 packages/task/package.json create mode 100644 packages/task/src/crawlers/site.ts create mode 100644 packages/task/src/env.ts create mode 100644 packages/task/src/lib/extract-url-locale.ts create mode 100644 packages/task/src/lib/site-crawler-config/parse-starting-url.ts create mode 100644 packages/task/src/lib/site-crawler-config/router.ts create mode 100644 packages/task/src/schema/site.ts create mode 100644 packages/task/src/trigger/site-crawl.ts create mode 100644 packages/task/trigger.config.ts create mode 100644 packages/task/tsconfig.json diff --git a/packages/task/Dockerfile b/packages/task/Dockerfile new file mode 100644 index 000000000..1fe69eed9 --- /dev/null +++ b/packages/task/Dockerfile @@ -0,0 +1,64 @@ +# Multi-stage Dockerfile to build and run the crawler actor +# - Uses Apify Playwright image (Chrome 1.55.0) +# - Builds using the monorepo provided as Docker build context +# - Uses pnpm +FROM apify/actor-node-playwright-chrome:22-1.55.0 AS base + +############################### +# Builder - generates the appropriate Dockerfile for the crawler package +############################### +FROM base AS pruner + +# Install pnpm without using npm -g +ENV PNPM_HOME=/home/myuser/.local/share/pnpm +ENV PATH=$PNPM_HOME:$PATH +RUN wget -qO- https://get.pnpm.io/install.sh | SHELL="$(which bash)" bash - + +RUN pnpm i -g turbo@2.5.6 + +WORKDIR /app +USER root +RUN chown -R myuser:myuser /app +USER myuser + +COPY --chown=myuser . . +RUN pnpm turbo prune @rectangular-labs/crawler --docker + +############################### +# Installer - installs the dependencies for the crawler package +############################### +FROM base AS builder + +# Install pnpm without using npm -g +ENV PNPM_HOME=/home/myuser/.local/share/pnpm +ENV PATH=$PNPM_HOME:$PATH +RUN wget -qO- https://get.pnpm.io/install.sh | SHELL="$(which bash)" bash - + +# These are mostly from the `apify create` example to check things +# Check preinstalled packages +RUN npm ls crawlee apify puppeteer playwright +# Check Playwright version is the same as the one from base image. +RUN node check-playwright-version.mjs + +WORKDIR /app +USER root +RUN chown -R myuser:myuser /app +USER myuser +# Avoid re-downloading Playwright browsers on install (already in base image) +ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 + + +# Copy only files needed for dependency resolution first (better layer caching) +COPY --chown=myuser --from=pruner /app/out/json/ . + +# Install workspace dependencies +RUN pnpm i --frozen-lockfile + +# Copy the full output +COPY --chown=myuser --from=pruner /app/out/full/ . + +# Build just the crawler package +RUN pnpm run build + +# If your "start:prod" script exists in the package.json (recommended) +CMD ["cd", "packages/crawler", "&&", "pnpm", "run", "start:prod"] diff --git a/packages/task/package.json b/packages/task/package.json new file mode 100644 index 000000000..f5e3db883 --- /dev/null +++ b/packages/task/package.json @@ -0,0 +1,47 @@ +{ + "name": "@rectangular-labs/task", + "private": true, + "version": "0.0.1", + "type": "module", + "exports": { + "./site-crawl": { + "default": "./src/site-crawl.ts", + "types": "./dist/site-crawl.d.ts" + }, + "./env": { + "default": "./src/env.ts", + "types": "./dist/src/env.d.ts" + }, + "./schema": { + "default": "./src/schema/site-crawl.ts", + "types": "./dist/schema/site-crawl.d.ts" + } + }, + "scripts": { + "build": "tsc", + "dev:trigger": "pnpm dotenvx run -f ../../.env.local -f ../../.env -- pnpx trigger.dev@latest dev", + "clean": "git clean -xdf .turbo node_modules dist .cache storage", + "format": "bun x @biomejs/biome format . --write", + "lint": "bun x @biomejs/biome lint . --write", + "typecheck": "tsc --noEmit --emitDeclarationOnly false" + }, + "devDependencies": { + "@rectangular-labs/typescript": "workspace:*", + "@trigger.dev/build": "4.0.2", + "@types/node": "^24.5.1", + "typescript": "^5.9.2" + }, + "dependencies": { + "@orama/orama": "^3.1.14", + "@t3-oss/env-core": "^0.13.8", + "@trigger.dev/sdk": "4.0.2", + "arktype": "^2.1.22", + "aws4fetch": "^1.0.20", + "crawlee": "^3.14.1", + "defuddle": "^0.6.6", + "glob": "^11.0.3", + "gpt-tokenizer": "^3.0.1", + "playwright": "1.55.0", + "unstorage": "^1.17.1" + } +} diff --git a/packages/task/src/crawlers/site.ts b/packages/task/src/crawlers/site.ts new file mode 100644 index 000000000..104c98314 --- /dev/null +++ b/packages/task/src/crawlers/site.ts @@ -0,0 +1,91 @@ +// For more information, see https://crawlee.dev/ +import { + PlaywrightCrawler, + type PlaywrightHook, + ProxyConfiguration, +} from "crawlee"; +import { parseStartingUrl } from "../lib/site-crawler-config/parse-starting-url.js"; +import { createCrawlSiteRouter } from "../lib/site-crawler-config/router.js"; +import type { SiteCrawlInputSchema } from "../schema/site.js"; + +export async function crawlSite(input: typeof SiteCrawlInputSchema.infer) { + const { + startUrl, + maxRequestsPerCrawl, + match = [], + exclude = [], + selector = "body", + waitForSelectorTimeoutMs, + cookie = [], + resourceFileTypeExclusions = [], + } = input; + + console.time("Crawl"); + const preNavigationHooks: PlaywrightHook[] = [ + // Abort requests for certain resource types and add cookies + async (crawlingContext, _gotoOptions) => { + const { request, page, log } = crawlingContext; + // Add cookies to the page + // Because the crawler has not yet navigated to the page, so the loadedUrl is always undefined. Use the request url instead. + if (cookie) { + const cookies = cookie.map((cookie) => { + return { + name: cookie.name, + value: cookie.value, + url: request.url, + }; + }); + await page.context().addCookies(cookies); + } + // If there are no resource exclusions, return + if (resourceFileTypeExclusions.length === 0) { + return; + } + await page.route(`**/*.{${resourceFileTypeExclusions.join()}}`, (route) => + route.abort("aborted"), + ); + log.info(`Aborting requests for as this is a resource excluded route`); + }, + ]; + const router = createCrawlSiteRouter({ + selector, + waitForSelectorTimeoutMs, + match, + exclude, + }); + + const proxyConfiguration = new ProxyConfiguration({ + proxyUrls: [], + }); + const crawler = new PlaywrightCrawler({ + proxyConfiguration, + maxRequestsPerCrawl, + requestHandler: router, + preNavigationHooks, + launchContext: { + launchOptions: { + args: [ + "--disable-gpu", // Mitigates the "crashing GPU process" issue in Docker containers + ], + }, + }, + statusMessageLoggingInterval: 5, + statusMessageCallback: async ({ state, crawler }) => { + const inFlight = crawler.autoscaledPool?.currentConcurrency ?? 0; + await crawler.setStatusMessage( + `succeeded=${state.requestsFinished} failed=${state.requestsFailed} inFlight=${inFlight}`, + { + level: "INFO", + }, + ); + }, + }); + + const startUrls = await parseStartingUrl({ + url: startUrl, + proxyUrl: await proxyConfiguration.newUrl(), + }); + await crawler.run(startUrls); + console.timeEnd("Crawl"); + return crawler; +} diff --git a/packages/task/src/env.ts b/packages/task/src/env.ts new file mode 100644 index 000000000..fa3ca8cf6 --- /dev/null +++ b/packages/task/src/env.ts @@ -0,0 +1,10 @@ +import { createEnv } from "@t3-oss/env-core"; + +export const dbEnv = () => + createEnv({ + server: { + // DATABASE_URL: type("string"), + }, + runtimeEnv: process.env, + emptyStringAsUndefined: true, + }); diff --git a/packages/task/src/lib/extract-url-locale.ts b/packages/task/src/lib/extract-url-locale.ts new file mode 100644 index 000000000..60881b4c9 --- /dev/null +++ b/packages/task/src/lib/extract-url-locale.ts @@ -0,0 +1,372 @@ +// Curated list of allowed locales (language_Region) from https://help.sap.com/docs/SAP_BUSINESSOBJECTS_BUSINESS_INTELLIGENCE_PLATFORM/09382741061c40a989fae01e61d54202/46758c5e6e041014910aba7db0e91070.html. Non exhaustive but should be enough for most cases. +const ALLOWED_LOCALES = [ + "af_za", + "sq_al", + "ar_dz", + "ar_bh", + "ar_eg", + "ar_iq", + "ar_jo", + "ar_kw", + "ar_lb", + "ar_ly", + "ar_ma", + "ar_om", + "ar_qa", + "ar_sa", + "ar_sy", + "ar_tn", + "ar_ae", + "ar_ye", + "hy_am", + "az_az", + "eu_es", + "be_by", + "bn_in", + "bs_ba", + "bg_bg", + "ca_es", + "zh_cn", + "zh_hk", + "zh_mo", + "zh_sg", + "zh_tw", + "hr_hr", + "cs_cz", + "da_dk", + "nl_be", + "nl_nl", + "en_au", + "en_bz", + "en_ca", + "en_ie", + "en_jm", + "en_nz", + "en_ph", + "en_za", + "en_tt", + "en_vi", + "en_gb", + "en_us", + "en_zw", + "et_ee", + "fo_fo", + "fi_fi", + "fr_be", + "fr_ca", + "fr_fr", + "fr_lu", + "fr_mc", + "fr_ch", + "gl_es", + "ka_ge", + "de_at", + "de_de", + "de_li", + "de_lu", + "de_ch", + "el_gr", + "gu_in", + "he_il", + "hi_in", + "hu_hu", + "is_is", + "id_id", + "it_it", + "it_ch", + "ja_jp", + "kn_in", + "kk_kz", + "kok_in", + "ko_kr", + "lv_lv", + "lt_lt", + "mk_mk", + "ms_bn", + "ms_my", + "ml_in", + "mt_mt", + "mr_in", + "mn_mn", + "se_no", + "nb_no", + "nn_no", + "fa_ir", + "pl_pl", + "pt_br", + "pt_pt", + "pa_in", + "ro_ro", + "ru_ru", + "sr_ba", + "sr_cs", + "sk_sk", + "sl_si", + "es_ar", + "es_bo", + "es_cl", + "es_co", + "es_cr", + "es_do", + "es_ec", + "es_sv", + "es_gt", + "es_hn", + "es_mx", + "es_ni", + "es_pa", + "es_py", + "es_pe", + "es_pr", + "es_es", + "es_uy", + "es_ve", + "sw_ke", + "sv_fi", + "sv_se", + "syr_sy", + "ta_in", + "te_in", + "th_th", + "tn_za", + "tr_tr", + "uk_ua", + "uz_uz", + "vi_vn", + "cy_gb", + "xh_za", + "zu_za", + "af-za", + "sq-al", + "ar-dz", + "ar-bh", + "ar-eg", + "ar-iq", + "ar-jo", + "ar-kw", + "ar-lb", + "ar-ly", + "ar-ma", + "ar-om", + "ar-qa", + "ar-sa", + "ar-sy", + "ar-tn", + "ar-ae", + "ar-ye", + "hy-am", + "az-az", + "eu-es", + "be-by", + "bn-in", + "bs-ba", + "bg-bg", + "ca-es", + "zh-cn", + "zh-hk", + "zh-mo", + "zh-sg", + "zh-tw", + "hr-hr", + "cs-cz", + "da-dk", + "nl-be", + "nl-nl", + "en-au", + "en-bz", + "en-ca", + "en-ie", + "en-jm", + "en-nz", + "en-ph", + "en-za", + "en-tt", + "en-vi", + "en-gb", + "en-us", + "en-zw", + "et-ee", + "fo-fo", + "fi-fi", + "fr-be", + "fr-ca", + "fr-fr", + "fr-lu", + "fr-mc", + "fr-ch", + "gl-es", + "ka-ge", + "de-at", + "de-de", + "de-li", + "de-lu", + "de-ch", + "el-gr", + "gu-in", + "he-il", + "hi-in", + "hu-hu", + "is-is", + "id-id", + "it-it", + "it-ch", + "ja-jp", + "kn-in", + "kk-kz", + "kok-in", + "ko-kr", + "lv-lv", + "lt-lt", + "mk-mk", + "ms-bn", + "ms-my", + "ml-in", + "mt-mt", + "mr-in", + "mn-mn", + "se-no", + "nb-no", + "nn-no", + "fa-ir", + "pl-pl", + "pt-br", + "pt-pt", + "pa-in", + "ro-ro", + "ru-ru", + "sr-ba", + "sr-cs", + "sk-sk", + "sl-si", + "es-ar", + "es-bo", + "es-cl", + "es-co", + "es-cr", + "es-do", + "es-ec", + "es-sv", + "es-gt", + "es-hn", + "es-mx", + "es-ni", + "es-pa", + "es-py", + "es-pe", + "es-pr", + "es-es", + "es-uy", + "es-ve", + "sw-ke", + "sv-fi", + "sv-se", + "syr-sy", + "ta-in", + "te-in", + "th-th", + "tn-za", + "tr-tr", + "uk-ua", + "uz-uz", + "vi-vn", + "cy-gb", + "xh-za", + "zu-za", + "af", + "sq", + "ar", + "hy", + "az", + "eu", + "be", + "bn", + "bs", + "bg", + "ca", + "zh", + "hr", + "cs", + "da", + "nl", + "en", + "et", + "fo", + "fi", + "fr", + "gl", + "ka", + "de", + "el", + "gu", + "he", + "hi", + "hu", + "is", + "id", + "it", + "ja", + "kn", + "kk", + "ko", + "lv", + "lt", + "mk", + "ms", + "ml", + "mt", + "mr", + "mn", + "se", + "nb", + "nn", + "fa", + "pl", + "pt", + "pa", + "ro", + "ru", + "sr", + "sk", + "sl", + "es", + "sw", + "sv", + "sy", + "ta", + "te", + "th", + "tn", + "tr", + "uk", + "uz", + "vi", + "cy", + "xh", + "zu", +] as const; +const ALLOWED_NORMALIZED = new Set(ALLOWED_LOCALES); +export type Locales = (typeof ALLOWED_LOCALES)[number]; + +export function extractUrlLocale(url: string) { + try { + const parsed = new URL(url); + const hostLabels = parsed.hostname.split("."); + const firstLabel = hostLabels[0] ?? null; + const firstPathSegment = + parsed.pathname.split("/").filter((item) => item !== "")[0] ?? null; + + const candidates = [firstLabel, firstPathSegment].filter( + (item): item is string => !!item, + ); + for (const candidate of candidates) { + const lower = candidate.toLowerCase(); + if (ALLOWED_NORMALIZED.has(lower)) { + // Return a normalized hyphenated form, e.g., en-us + return lower; + } + } + } catch { + // noop since url is invalid,we simply return null + } + return null; +} diff --git a/packages/task/src/lib/site-crawler-config/parse-starting-url.ts b/packages/task/src/lib/site-crawler-config/parse-starting-url.ts new file mode 100644 index 000000000..4afdda8a7 --- /dev/null +++ b/packages/task/src/lib/site-crawler-config/parse-starting-url.ts @@ -0,0 +1,37 @@ +import { log, RobotsTxtFile } from "crawlee"; +import { extractUrlLocale } from "../extract-url-locale.js"; + +export async function parseStartingUrl({ + url, + proxyUrl, + defaultLocale = "en", + onlyParseDefaultLocale = true, +}: { + url: string; + proxyUrl?: string | undefined; + defaultLocale?: string | undefined; + onlyParseDefaultLocale?: boolean | undefined; +}) { + const robots = await RobotsTxtFile.find(url, proxyUrl); + const { urls } = await robots.parseSitemaps(); + log.info( + `${urls.length} urls found from sitemap. Filtering internationalized urls.`, + ); + + const filteredUrls = onlyParseDefaultLocale + ? urls.filter((url) => { + const locale = extractUrlLocale(url); + if (!locale || locale.includes(defaultLocale)) { + return true; + } + return false; + }) + : urls; + log.info(`${filteredUrls.length} urls after filtering.`); + + // TODO: This is a temporary solution to avoid crawling too many urls. We'll likely inject high ranking urls as needed too. + // If there's too much urls, we'll just crawl the start url and discover the most relevant urls from the perspective of the user. + return filteredUrls.length > 0 && filteredUrls.length < 1000 + ? filteredUrls + : [url]; +} diff --git a/packages/task/src/lib/site-crawler-config/router.ts b/packages/task/src/lib/site-crawler-config/router.ts new file mode 100644 index 000000000..dcf7d2d8f --- /dev/null +++ b/packages/task/src/lib/site-crawler-config/router.ts @@ -0,0 +1,144 @@ +import { createPlaywrightRouter } from "crawlee"; +import { Defuddle as parseWithDefuddle } from "defuddle/node"; +import type { Page } from "playwright"; +import { extractUrlLocale, type Locales } from "../extract-url-locale.js"; + +function getPageContent(page: Page, selector: string) { + return page.evaluate((selector) => { + // Check if the selector is an XPath + if (selector.startsWith("/")) { + const elements = document.evaluate( + selector, + document, + null, + XPathResult.ANY_TYPE, + null, + ); + const result = elements.iterateNext(); + return result ? result.textContent || "" : ""; + } else { + // Handle as a CSS selector + const el = document.querySelector(selector) as HTMLElement | null; + return el?.innerText || ""; + } + }, selector); +} + +async function waitForXPath(page: Page, xpath: string, timeoutMs: number) { + await page.waitForFunction( + (xpath) => { + const elements = document.evaluate( + xpath, + document, + null, + XPathResult.ANY_TYPE, + null, + ); + return elements.iterateNext() !== null; + }, + xpath, + { timeout: timeoutMs }, + ); +} + +/** + * @param config - The config for the crawler + * @param config.selector - The selector to use for the crawler + * @param config.waitForSelectorTimeoutMs - The timeout for the selector + * @param config.match - The match for the crawler + * @param config.exclude - The exclude for the crawler + * @param config.onlyEnqueueDefaultLocale - When true, only enqueue links that match the default locale (or have no locale) + * @param config.defaultLocale - The default locale to keep when filtering (e.g., "en" or "en-us") + * @returns + */ +export const createCrawlSiteRouter = ({ + selector, + waitForSelectorTimeoutMs, + match, + exclude, + onlyEnqueueDefaultLocale = true, + defaultLocale = "en", +}: { + selector: string; + waitForSelectorTimeoutMs?: number | undefined; + match: string[]; + exclude: string[]; + onlyEnqueueDefaultLocale?: boolean | undefined; + defaultLocale?: Locales | (string & {}) | undefined; +}) => { + const siteCrawlRouter = createPlaywrightRouter(); + + siteCrawlRouter.addDefaultHandler( + async ({ enqueueLinks, pushData, request, page }) => { + const title = await page.title(); + + console.info(`Crawling: ${request.loadedUrl}...`); + + // Use custom handling for XPath selector + const timeout = waitForSelectorTimeoutMs ?? 10_000; + if (selector) { + if (selector.startsWith("/")) { + await waitForXPath(page, selector, timeout); + } else { + await page.waitForSelector(selector, { + timeout, + }); + } + } + + // Prefer Defuddle extraction, fallback to selector-based text + let defuddleContentHtml: string | undefined; + let defuddleContentMarkdown: string | undefined; + let defuddleDescription: string | undefined; + try { + const html = await page.content(); + const parsed = await parseWithDefuddle(html, request.loadedUrl, { + separateMarkdown: true, + }); + defuddleContentHtml = parsed.content; + defuddleContentMarkdown = parsed.contentMarkdown; + defuddleDescription = parsed.description; + } catch (_error) { + // Ignore extraction errors and fall back to selector text + console.info(`Defuddle extraction failed for ${request.loadedUrl}`); + } + + const text = await getPageContent(page, selector); + + // Save results as JSON to ./storage/datasets/default + await pushData({ + title, + url: request.loadedUrl, + text, + description: defuddleDescription, + contentHtml: defuddleContentHtml, + contentMarkdown: defuddleContentMarkdown, + extractor: defuddleContentHtml ? "defuddle" : "selector", + }); + + // Extract links from the current page + // and add them to the crawling queue. + await enqueueLinks({ + ...(match.length > 0 ? { globs: match } : {}), + strategy: "same-domain", + exclude: exclude, + // Filter non-default locales when enabled + transformRequestFunction: (request) => { + if (!onlyEnqueueDefaultLocale) { + return request; + } + const locale = extractUrlLocale(request.url); + if (!locale) { + return request; + } + if (locale.includes(defaultLocale)) { + return request; + } + return null; + }, + }); + }, + ); + + return siteCrawlRouter; +}; diff --git a/packages/task/src/schema/site.ts b/packages/task/src/schema/site.ts new file mode 100644 index 000000000..2eef71caa --- /dev/null +++ b/packages/task/src/schema/site.ts @@ -0,0 +1,30 @@ +import { type } from "arktype"; + +export const cookieSchema = type({ + name: "string", + value: "string", +}); +export type Cookie = typeof cookieSchema.infer; + +export const SiteCrawlInputSchema = type({ + startUrl: "string", + maxRequestsPerCrawl: "number", + "selector?": "string", + "waitForSelectorTimeoutMs?": "number", + "match?": "string[]", + "exclude?": "string[]", + "cookie?": [cookieSchema, "[]"], + "resourceFileTypeExclusions?": "string[]", +}); +export type SiteCrawlInput = typeof SiteCrawlInputSchema.infer; + +export const SiteCrawlOutputSchema = type({ + title: "string", + url: "string", + text: "string", + "description?": "string", + "contentHtml?": "string", + "contentMarkdown?": "string", + extractor: "string", +}); +export type SiteCrawlOutput = typeof SiteCrawlOutputSchema.infer; diff --git a/packages/task/src/trigger/site-crawl.ts b/packages/task/src/trigger/site-crawl.ts new file mode 100644 index 000000000..a6ef660ca --- /dev/null +++ b/packages/task/src/trigger/site-crawl.ts @@ -0,0 +1,15 @@ +import { task } from "@trigger.dev/sdk/v3"; +import { crawlSite } from "../crawlers/site.js"; +import type { SiteCrawlInputSchema } from "../schema/site.js"; + +export const helloWorldTask = task({ + id: "site-crawl", + maxDuration: 300, + run: async (payload: typeof SiteCrawlInputSchema.infer) => { + await crawlSite(payload); + + return { + message: "Hello, world!", + }; + }, +}); diff --git a/packages/task/trigger.config.ts b/packages/task/trigger.config.ts new file mode 100644 index 000000000..3abc054cd --- /dev/null +++ b/packages/task/trigger.config.ts @@ -0,0 +1,24 @@ +import { playwright } from "@trigger.dev/build/extensions/playwright"; +import { defineConfig } from "@trigger.dev/sdk"; + +export default defineConfig({ + project: "proj_ybfrijjvtusiailrndnt", + runtime: "node-22", + logLevel: "log", + maxDuration: 1800, + retries: { + enabledInDev: true, + default: { + maxAttempts: 3, + minTimeoutInMs: 1000, + maxTimeoutInMs: 10000, + factor: 2, + randomize: true, + }, + }, + build: { + external: ["crawlee", "jsdom", "defuddle"], + extensions: [playwright()], + }, + dirs: ["src/trigger"], +}); diff --git a/packages/task/tsconfig.json b/packages/task/tsconfig.json new file mode 100644 index 000000000..6056a273e --- /dev/null +++ b/packages/task/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "@rectangular-labs/typescript/tsconfig.internal-package.json", + "compilerOptions": { + "rootDir": "src", + "baseUrl": ".", + "paths": { + "@rectangular-labs/crawler/*": ["./src/*"] + }, + "module": "NodeNext", + "moduleResolution": "nodenext", + "emitDeclarationOnly": false, + "sourceMap": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true + }, + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/tooling/typescript/tsconfig.base.json b/tooling/typescript/tsconfig.base.json index e27be04be..40f147f44 100644 --- a/tooling/typescript/tsconfig.base.json +++ b/tooling/typescript/tsconfig.base.json @@ -2,8 +2,8 @@ "$schema": "https://json.schemastore.org/tsconfig", "compilerOptions": { /** Base Options */ - "target": "ES2024", - "lib": ["ES2024"], + "target": "esnext", + "lib": ["ESNext"], "moduleDetection": "force", "esModuleInterop": true, "skipLibCheck": true, From 214fe6f99db6cd05222d24554d9b7ff746422056 Mon Sep 17 00:00:00 2001 From: ElasticBottle Date: Fri, 19 Sep 2025 03:11:53 +0800 Subject: [PATCH 06/38] chore(crawler): remove package --- packages/crawler/.gitignore | 3 - packages/crawler/package.json | 41 -- packages/crawler/src/env.ts | 10 - .../crawler/src/lib/extract-url-locale.ts | 372 ------------------ .../crawler/src/lib/playwright-crawler.ts | 37 -- .../site-crawler-config/parse-starting-url.ts | 37 -- .../src/lib/site-crawler-config/pre-nav.ts | 37 -- .../src/lib/site-crawler-config/router.ts | 117 ------ packages/crawler/src/schema/site-crawl.ts | 21 - packages/crawler/src/site-crawl.ts | 164 -------- packages/crawler/tsconfig.json | 18 - 11 files changed, 857 deletions(-) delete mode 100644 packages/crawler/.gitignore delete mode 100644 packages/crawler/package.json delete mode 100644 packages/crawler/src/env.ts delete mode 100644 packages/crawler/src/lib/extract-url-locale.ts delete mode 100644 packages/crawler/src/lib/playwright-crawler.ts delete mode 100644 packages/crawler/src/lib/site-crawler-config/parse-starting-url.ts delete mode 100644 packages/crawler/src/lib/site-crawler-config/pre-nav.ts delete mode 100644 packages/crawler/src/lib/site-crawler-config/router.ts delete mode 100644 packages/crawler/src/schema/site-crawl.ts delete mode 100644 packages/crawler/src/site-crawl.ts delete mode 100644 packages/crawler/tsconfig.json diff --git a/packages/crawler/.gitignore b/packages/crawler/.gitignore deleted file mode 100644 index cc54d25a1..000000000 --- a/packages/crawler/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ - -# sst -.sst diff --git a/packages/crawler/package.json b/packages/crawler/package.json deleted file mode 100644 index ea0644446..000000000 --- a/packages/crawler/package.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "@rectangular-labs/crawler", - "private": true, - "version": "0.0.1", - "type": "module", - "exports": { - "./site-crawl": { - "default": "./src/site-crawl.ts", - "types": "./dist/site-crawl.d.ts" - }, - "./env": { - "default": "./src/env.ts", - "types": "./dist/src/env.d.ts" - } - }, - "scripts": { - "build": "tsc", - "clean": "git clean -xdf .turbo node_modules dist .cache storage", - "format": "bun x @biomejs/biome format . --write", - "lint": "bun x @biomejs/biome lint . --write", - "push": "APIFY_CLI_DISABLE_TELEMETRY=1 apify push", - "start": "pnpm run start:dev", - "start:dev": "tsx src/site-crawl.ts", - "start:prod": "node dist/site-crawl.js", - "typecheck": "tsc --noEmit --emitDeclarationOnly false" - }, - "devDependencies": { - "@rectangular-labs/typescript": "workspace:*", - "@types/aws-lambda": "8.10.152", - "@types/node": "^24.5.1", - "typescript": "^5.9.2" - }, - "dependencies": { - "@t3-oss/env-core": "^0.13.8", - "arktype": "^2.1.22", - "crawlee": "^3.14.1", - "glob": "^11.0.3", - "gpt-tokenizer": "^3.0.1", - "playwright": "1.55.0" - } -} diff --git a/packages/crawler/src/env.ts b/packages/crawler/src/env.ts deleted file mode 100644 index fa3ca8cf6..000000000 --- a/packages/crawler/src/env.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { createEnv } from "@t3-oss/env-core"; - -export const dbEnv = () => - createEnv({ - server: { - // DATABASE_URL: type("string"), - }, - runtimeEnv: process.env, - emptyStringAsUndefined: true, - }); diff --git a/packages/crawler/src/lib/extract-url-locale.ts b/packages/crawler/src/lib/extract-url-locale.ts deleted file mode 100644 index 60881b4c9..000000000 --- a/packages/crawler/src/lib/extract-url-locale.ts +++ /dev/null @@ -1,372 +0,0 @@ -// Curated list of allowed locales (language_Region) from https://help.sap.com/docs/SAP_BUSINESSOBJECTS_BUSINESS_INTELLIGENCE_PLATFORM/09382741061c40a989fae01e61d54202/46758c5e6e041014910aba7db0e91070.html. Non exhaustive but should be enough for most cases. -const ALLOWED_LOCALES = [ - "af_za", - "sq_al", - "ar_dz", - "ar_bh", - "ar_eg", - "ar_iq", - "ar_jo", - "ar_kw", - "ar_lb", - "ar_ly", - "ar_ma", - "ar_om", - "ar_qa", - "ar_sa", - "ar_sy", - "ar_tn", - "ar_ae", - "ar_ye", - "hy_am", - "az_az", - "eu_es", - "be_by", - "bn_in", - "bs_ba", - "bg_bg", - "ca_es", - "zh_cn", - "zh_hk", - "zh_mo", - "zh_sg", - "zh_tw", - "hr_hr", - "cs_cz", - "da_dk", - "nl_be", - "nl_nl", - "en_au", - "en_bz", - "en_ca", - "en_ie", - "en_jm", - "en_nz", - "en_ph", - "en_za", - "en_tt", - "en_vi", - "en_gb", - "en_us", - "en_zw", - "et_ee", - "fo_fo", - "fi_fi", - "fr_be", - "fr_ca", - "fr_fr", - "fr_lu", - "fr_mc", - "fr_ch", - "gl_es", - "ka_ge", - "de_at", - "de_de", - "de_li", - "de_lu", - "de_ch", - "el_gr", - "gu_in", - "he_il", - "hi_in", - "hu_hu", - "is_is", - "id_id", - "it_it", - "it_ch", - "ja_jp", - "kn_in", - "kk_kz", - "kok_in", - "ko_kr", - "lv_lv", - "lt_lt", - "mk_mk", - "ms_bn", - "ms_my", - "ml_in", - "mt_mt", - "mr_in", - "mn_mn", - "se_no", - "nb_no", - "nn_no", - "fa_ir", - "pl_pl", - "pt_br", - "pt_pt", - "pa_in", - "ro_ro", - "ru_ru", - "sr_ba", - "sr_cs", - "sk_sk", - "sl_si", - "es_ar", - "es_bo", - "es_cl", - "es_co", - "es_cr", - "es_do", - "es_ec", - "es_sv", - "es_gt", - "es_hn", - "es_mx", - "es_ni", - "es_pa", - "es_py", - "es_pe", - "es_pr", - "es_es", - "es_uy", - "es_ve", - "sw_ke", - "sv_fi", - "sv_se", - "syr_sy", - "ta_in", - "te_in", - "th_th", - "tn_za", - "tr_tr", - "uk_ua", - "uz_uz", - "vi_vn", - "cy_gb", - "xh_za", - "zu_za", - "af-za", - "sq-al", - "ar-dz", - "ar-bh", - "ar-eg", - "ar-iq", - "ar-jo", - "ar-kw", - "ar-lb", - "ar-ly", - "ar-ma", - "ar-om", - "ar-qa", - "ar-sa", - "ar-sy", - "ar-tn", - "ar-ae", - "ar-ye", - "hy-am", - "az-az", - "eu-es", - "be-by", - "bn-in", - "bs-ba", - "bg-bg", - "ca-es", - "zh-cn", - "zh-hk", - "zh-mo", - "zh-sg", - "zh-tw", - "hr-hr", - "cs-cz", - "da-dk", - "nl-be", - "nl-nl", - "en-au", - "en-bz", - "en-ca", - "en-ie", - "en-jm", - "en-nz", - "en-ph", - "en-za", - "en-tt", - "en-vi", - "en-gb", - "en-us", - "en-zw", - "et-ee", - "fo-fo", - "fi-fi", - "fr-be", - "fr-ca", - "fr-fr", - "fr-lu", - "fr-mc", - "fr-ch", - "gl-es", - "ka-ge", - "de-at", - "de-de", - "de-li", - "de-lu", - "de-ch", - "el-gr", - "gu-in", - "he-il", - "hi-in", - "hu-hu", - "is-is", - "id-id", - "it-it", - "it-ch", - "ja-jp", - "kn-in", - "kk-kz", - "kok-in", - "ko-kr", - "lv-lv", - "lt-lt", - "mk-mk", - "ms-bn", - "ms-my", - "ml-in", - "mt-mt", - "mr-in", - "mn-mn", - "se-no", - "nb-no", - "nn-no", - "fa-ir", - "pl-pl", - "pt-br", - "pt-pt", - "pa-in", - "ro-ro", - "ru-ru", - "sr-ba", - "sr-cs", - "sk-sk", - "sl-si", - "es-ar", - "es-bo", - "es-cl", - "es-co", - "es-cr", - "es-do", - "es-ec", - "es-sv", - "es-gt", - "es-hn", - "es-mx", - "es-ni", - "es-pa", - "es-py", - "es-pe", - "es-pr", - "es-es", - "es-uy", - "es-ve", - "sw-ke", - "sv-fi", - "sv-se", - "syr-sy", - "ta-in", - "te-in", - "th-th", - "tn-za", - "tr-tr", - "uk-ua", - "uz-uz", - "vi-vn", - "cy-gb", - "xh-za", - "zu-za", - "af", - "sq", - "ar", - "hy", - "az", - "eu", - "be", - "bn", - "bs", - "bg", - "ca", - "zh", - "hr", - "cs", - "da", - "nl", - "en", - "et", - "fo", - "fi", - "fr", - "gl", - "ka", - "de", - "el", - "gu", - "he", - "hi", - "hu", - "is", - "id", - "it", - "ja", - "kn", - "kk", - "ko", - "lv", - "lt", - "mk", - "ms", - "ml", - "mt", - "mr", - "mn", - "se", - "nb", - "nn", - "fa", - "pl", - "pt", - "pa", - "ro", - "ru", - "sr", - "sk", - "sl", - "es", - "sw", - "sv", - "sy", - "ta", - "te", - "th", - "tn", - "tr", - "uk", - "uz", - "vi", - "cy", - "xh", - "zu", -] as const; -const ALLOWED_NORMALIZED = new Set(ALLOWED_LOCALES); -export type Locales = (typeof ALLOWED_LOCALES)[number]; - -export function extractUrlLocale(url: string) { - try { - const parsed = new URL(url); - const hostLabels = parsed.hostname.split("."); - const firstLabel = hostLabels[0] ?? null; - const firstPathSegment = - parsed.pathname.split("/").filter((item) => item !== "")[0] ?? null; - - const candidates = [firstLabel, firstPathSegment].filter( - (item): item is string => !!item, - ); - for (const candidate of candidates) { - const lower = candidate.toLowerCase(); - if (ALLOWED_NORMALIZED.has(lower)) { - // Return a normalized hyphenated form, e.g., en-us - return lower; - } - } - } catch { - // noop since url is invalid,we simply return null - } - return null; -} diff --git a/packages/crawler/src/lib/playwright-crawler.ts b/packages/crawler/src/lib/playwright-crawler.ts deleted file mode 100644 index a31c40675..000000000 --- a/packages/crawler/src/lib/playwright-crawler.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { - PlaywrightCrawler, - type PlaywrightHook, - type PlaywrightRequestHandler, - type ProxyConfiguration, -} from "crawlee"; - -export function createPlaywrightCrawler( - maxRequestsPerCrawl: number, - requestHandler: PlaywrightRequestHandler, - preNavigationHooks: PlaywrightHook[], - proxyConfiguration?: ProxyConfiguration | undefined, -) { - return new PlaywrightCrawler({ - ...(proxyConfiguration ? { proxyConfiguration } : {}), - maxRequestsPerCrawl, - requestHandler, - preNavigationHooks, - launchContext: { - launchOptions: { - args: [ - "--disable-gpu", // Mitigates the "crashing GPU process" issue in Docker containers - ], - }, - }, - statusMessageLoggingInterval: 5, - statusMessageCallback: async ({ state, crawler }) => { - const inFlight = crawler.autoscaledPool?.currentConcurrency ?? 0; - await crawler.setStatusMessage( - `succeeded=${state.requestsFinished} failed=${state.requestsFailed} inFlight=${inFlight}`, - { - level: "INFO", - }, - ); - }, - }); -} diff --git a/packages/crawler/src/lib/site-crawler-config/parse-starting-url.ts b/packages/crawler/src/lib/site-crawler-config/parse-starting-url.ts deleted file mode 100644 index 4afdda8a7..000000000 --- a/packages/crawler/src/lib/site-crawler-config/parse-starting-url.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { log, RobotsTxtFile } from "crawlee"; -import { extractUrlLocale } from "../extract-url-locale.js"; - -export async function parseStartingUrl({ - url, - proxyUrl, - defaultLocale = "en", - onlyParseDefaultLocale = true, -}: { - url: string; - proxyUrl?: string | undefined; - defaultLocale?: string | undefined; - onlyParseDefaultLocale?: boolean | undefined; -}) { - const robots = await RobotsTxtFile.find(url, proxyUrl); - const { urls } = await robots.parseSitemaps(); - log.info( - `${urls.length} urls found from sitemap. Filtering internationalized urls.`, - ); - - const filteredUrls = onlyParseDefaultLocale - ? urls.filter((url) => { - const locale = extractUrlLocale(url); - if (!locale || locale.includes(defaultLocale)) { - return true; - } - return false; - }) - : urls; - log.info(`${filteredUrls.length} urls after filtering.`); - - // TODO: This is a temporary solution to avoid crawling too many urls. We'll likely inject high ranking urls as needed too. - // If there's too much urls, we'll just crawl the start url and discover the most relevant urls from the perspective of the user. - return filteredUrls.length > 0 && filteredUrls.length < 1000 - ? filteredUrls - : [url]; -} diff --git a/packages/crawler/src/lib/site-crawler-config/pre-nav.ts b/packages/crawler/src/lib/site-crawler-config/pre-nav.ts deleted file mode 100644 index 6d3e12978..000000000 --- a/packages/crawler/src/lib/site-crawler-config/pre-nav.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { PlaywrightHook } from "crawlee"; -import type { Cookie } from "../../schema/site-crawl.js"; - -export const createCrawlSitePreNavHook = ({ - cookie, - resourceFileTypeExclusions, -}: { - cookie: Cookie[]; - resourceFileTypeExclusions: string[]; -}): PlaywrightHook[] => { - return [ - // Abort requests for certain resource types and add cookies - async (crawlingContext, _gotoOptions) => { - const { request, page, log } = crawlingContext; - // Add cookies to the page - // Because the crawler has not yet navigated to the page, so the loadedUrl is always undefined. Use the request url instead. - if (cookie) { - const cookies = cookie.map((cookie) => { - return { - name: cookie.name, - value: cookie.value, - url: request.url, - }; - }); - await page.context().addCookies(cookies); - } - // If there are no resource exclusions, return - if (resourceFileTypeExclusions.length === 0) { - return; - } - await page.route(`**/*.{${resourceFileTypeExclusions.join()}}`, (route) => - route.abort("aborted"), - ); - log.info(`Aborting requests for as this is a resource excluded route`); - }, - ]; -}; diff --git a/packages/crawler/src/lib/site-crawler-config/router.ts b/packages/crawler/src/lib/site-crawler-config/router.ts deleted file mode 100644 index e23131c66..000000000 --- a/packages/crawler/src/lib/site-crawler-config/router.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { createPlaywrightRouter } from "crawlee"; -import type { Page } from "playwright"; -import { extractUrlLocale, type Locales } from "../extract-url-locale.js"; - -function getPageHtml(page: Page, selector: string) { - return page.evaluate((selector) => { - // Check if the selector is an XPath - if (selector.startsWith("/")) { - const elements = document.evaluate( - selector, - document, - null, - XPathResult.ANY_TYPE, - null, - ); - const result = elements.iterateNext(); - return result ? result.textContent || "" : ""; - } else { - // Handle as a CSS selector - const el = document.querySelector(selector) as HTMLElement | null; - return el?.innerText || ""; - } - }, selector); -} - -async function waitForXPath(page: Page, xpath: string, timeoutMs: number) { - await page.waitForFunction( - (xpath) => { - const elements = document.evaluate( - xpath, - document, - null, - XPathResult.ANY_TYPE, - null, - ); - return elements.iterateNext() !== null; - }, - xpath, - { timeout: timeoutMs }, - ); -} - -/** - * @param config - The config for the crawler - * @param config.selector - The selector to use for the crawler - * @param config.waitForSelectorTimeoutMs - The timeout for the selector - * @param config.match - The match for the crawler - * @param config.exclude - The exclude for the crawler - * @param config.onlyEnqueueDefaultLocale - When true, only enqueue links that match the default locale (or have no locale) - * @param config.defaultLocale - The default locale to keep when filtering (e.g., "en" or "en-us") - * @returns - */ -export const createCrawlSiteRouter = ({ - selector, - waitForSelectorTimeoutMs, - match, - exclude, - onlyEnqueueDefaultLocale = true, - defaultLocale = "en", -}: { - selector: string; - waitForSelectorTimeoutMs?: number | undefined; - match: string[]; - exclude: string[]; - onlyEnqueueDefaultLocale?: boolean | undefined; - defaultLocale?: Locales | (string & {}) | undefined; -}) => { - const siteCrawlRouter = createPlaywrightRouter(); - - siteCrawlRouter.addDefaultHandler( - async ({ enqueueLinks, log, pushData, request, page }) => { - const title = await page.title(); - - log.info(`Crawling: ${request.loadedUrl}...`); - - // Use custom handling for XPath selector - if (selector) { - if (selector.startsWith("/")) { - await waitForXPath(page, selector, waitForSelectorTimeoutMs ?? 1000); - } else { - await page.waitForSelector(selector, { - timeout: waitForSelectorTimeoutMs ?? 1000, - }); - } - } - - const html = await getPageHtml(page, selector); - - // Save results as JSON to ./storage/datasets/default - await pushData({ title, url: request.loadedUrl, html }); - - // Extract links from the current page - // and add them to the crawling queue. - await enqueueLinks({ - ...(match.length > 0 ? { globs: match } : {}), - strategy: "same-domain", - exclude: exclude, - // Filter non-default locales when enabled - transformRequestFunction: (request) => { - if (!onlyEnqueueDefaultLocale) { - return request; - } - const locale = extractUrlLocale(request.url); - if (!locale) { - return request; - } - if (locale.includes(defaultLocale)) { - return request; - } - return null; - }, - }); - }, - ); - - return siteCrawlRouter; -}; diff --git a/packages/crawler/src/schema/site-crawl.ts b/packages/crawler/src/schema/site-crawl.ts deleted file mode 100644 index f0021a129..000000000 --- a/packages/crawler/src/schema/site-crawl.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { type ArkErrors as ArkErrorsType, type } from "arktype"; - -export type ArkErrors = ArkErrorsType; - -export const cookieSchema = type({ - name: "string", - value: "string", -}); -export type Cookie = typeof cookieSchema.infer; - -export const SiteCrawlInputSchema = type({ - startUrl: "string", - maxRequestsPerCrawl: "number", - "selector?": "string", - "waitForSelectorTimeoutMs?": "number", - "match?": "string[]", - "exclude?": "string[]", - "cookie?": [cookieSchema, "[]"], - "resourceFileTypeExclusions?": "string[]", -}); -export type SiteCrawlInput = typeof SiteCrawlInputSchema.infer; diff --git a/packages/crawler/src/site-crawl.ts b/packages/crawler/src/site-crawl.ts deleted file mode 100644 index 726133899..000000000 --- a/packages/crawler/src/site-crawl.ts +++ /dev/null @@ -1,164 +0,0 @@ -// For more information, see https://crawlee.dev/ -import { Buffer } from "node:buffer"; -import type { PathLike } from "node:fs"; -import { readFile, writeFile } from "node:fs/promises"; -import { type } from "arktype"; -import { KeyValueStore, log, ProxyConfiguration } from "crawlee"; -import { glob } from "glob"; -import { isWithinTokenLimit } from "gpt-tokenizer"; -import type { Page } from "playwright"; -import { createPlaywrightCrawler } from "./lib/playwright-crawler.js"; -import { parseStartingUrl } from "./lib/site-crawler-config/parse-starting-url.js"; -import { createCrawlSitePreNavHook } from "./lib/site-crawler-config/pre-nav.js"; -import { createCrawlSiteRouter } from "./lib/site-crawler-config/router.js"; -import { SiteCrawlInputSchema } from "./schema/site-crawl.js"; - -type Config = { - maxPagesToCrawl: number; - selector: string; - waitForSelectorTimeout: number; - cookie: string; - resourceExclusions: string[]; - onVisitPage: (args: { - page: Page; - pushData: (data: Record) => Promise; - }) => Promise; - match: string | string[]; - exclude: string | string[]; - outputFileName: string; - maxFileSize: number; - maxTokens: number; - url: string; -}; - -export async function write(config: Config) { - let nextFileNameString: PathLike = ""; - const jsonFiles = await glob("storage/datasets/default/*.json", { - absolute: true, - }); - - console.log(`Found ${jsonFiles.length} files to combine...`); - - let currentResults: Record[] = []; - let currentSize: number = 0; - let fileCounter: number = 1; - const maxBytes: number = config.maxFileSize - ? config.maxFileSize * 1024 * 1024 - : Infinity; - - const getStringByteSize = (str: string): number => - Buffer.byteLength(str, "utf-8"); - - const nextFileName = (): string => - `${config.outputFileName.replace(/\.json$/, "")}-${fileCounter}.json`; - - const writeBatchToFile = async (): Promise => { - nextFileNameString = nextFileName(); - await writeFile( - nextFileNameString, - JSON.stringify(currentResults, null, 2), - ); - console.log( - `Wrote ${currentResults.length} items to ${nextFileNameString}`, - ); - currentResults = []; - currentSize = 0; - fileCounter++; - }; - - let estimatedTokens: number = 0; - - const addContentOrSplit = async ( - data: Record, - ): Promise => { - const contentString: string = JSON.stringify(data); - const tokenCount: number | false = isWithinTokenLimit( - contentString, - config.maxTokens || Infinity, - ); - - if (typeof tokenCount === "number") { - if (estimatedTokens + tokenCount > config.maxTokens) { - // Only write the batch if it's not empty (something to write) - if (currentResults.length > 0) { - await writeBatchToFile(); - } - // Since the addition of a single item exceeded the token limit, halve it. - estimatedTokens = Math.floor(tokenCount / 2); - currentResults.push(data); - } else { - currentResults.push(data); - estimatedTokens += tokenCount; - } - } - - currentSize += getStringByteSize(contentString); - if (currentSize > maxBytes) { - await writeBatchToFile(); - } - }; - - // Iterate over each JSON file and process its contents. - for (const file of jsonFiles) { - const fileContent = await readFile(file, "utf-8"); - const data: Record = JSON.parse(fileContent); - await addContentOrSplit(data); - } - - // Check if any remaining data needs to be written to a file. - if (currentResults.length > 0) { - await writeBatchToFile(); - } - - return nextFileNameString; -} - -const rawInput = await KeyValueStore.getInput(); -const userInput = SiteCrawlInputSchema.or(type.null)(rawInput); - -if (userInput instanceof type.errors) { - log.error(`Invalid input: ${userInput.summary}`); - throw new Error("Invalid input"); -} -if (!userInput) { - throw new Error("No input provided"); -} - -log.info("User Input:", { userInput }); -const { - startUrl, - maxRequestsPerCrawl, - match = [], - exclude = [], - selector = "body", - waitForSelectorTimeoutMs, - cookie = [], - resourceFileTypeExclusions = [], -} = userInput; - -console.time("Crawl"); -const preNavigationHooks = createCrawlSitePreNavHook({ - cookie, - resourceFileTypeExclusions, -}); -const router = createCrawlSiteRouter({ - selector, - waitForSelectorTimeoutMs, - match, - exclude, -}); -const proxyConfiguration = new ProxyConfiguration({ - tieredProxyUrls: [[null]], -}); -const crawler = createPlaywrightCrawler( - maxRequestsPerCrawl, - router, - preNavigationHooks, - proxyConfiguration, -); -const startUrls = await parseStartingUrl({ - url: startUrl, - proxyUrl: await proxyConfiguration?.newUrl(), -}); -await crawler.run(startUrls); -console.timeEnd("Crawl"); diff --git a/packages/crawler/tsconfig.json b/packages/crawler/tsconfig.json deleted file mode 100644 index 6056a273e..000000000 --- a/packages/crawler/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": "@rectangular-labs/typescript/tsconfig.internal-package.json", - "compilerOptions": { - "rootDir": "src", - "baseUrl": ".", - "paths": { - "@rectangular-labs/crawler/*": ["./src/*"] - }, - "module": "NodeNext", - "moduleResolution": "nodenext", - "emitDeclarationOnly": false, - "sourceMap": true, - "experimentalDecorators": true, - "emitDecoratorMetadata": true - }, - "include": ["src"], - "exclude": ["node_modules"] -} From f419a4864e3b1df1d7e50794cbac1652ac6bdc91 Mon Sep 17 00:00:00 2001 From: ElasticBottle Date: Fri, 19 Sep 2025 05:47:12 +0800 Subject: [PATCH 07/38] feat(seo): new seo site --- apps/seo/package.json | 62 +++ apps/seo/public/android-chrome-192x192.png | Bin 0 -> 1495 bytes apps/seo/public/android-chrome-512x512.png | Bin 0 -> 9343 bytes apps/seo/public/apple-touch-icon.png | Bin 0 -> 1245 bytes apps/seo/public/favicon-96x96.png | Bin 0 -> 549 bytes apps/seo/public/favicon.ico | Bin 0 -> 15086 bytes apps/seo/public/favicon.svg | 6 + apps/seo/public/site.webmanifest | 21 ++ apps/seo/src/components/error-boundary.tsx | 56 +++ apps/seo/src/components/logout-button.tsx | 33 ++ apps/seo/src/components/not-found.tsx | 34 ++ apps/seo/src/lib/api.ts | 27 ++ apps/seo/src/lib/auth/client.ts | 39 ++ apps/seo/src/lib/auth/server.ts | 8 + apps/seo/src/lib/env.ts | 26 ++ apps/seo/src/lib/seo.ts | 35 ++ apps/seo/src/reportWebVitals.ts | 13 + apps/seo/src/routeTree.gen.ts | 354 ++++++++++++++++++ apps/seo/src/router.tsx | 33 ++ apps/seo/src/routes/__root.tsx | 96 +++++ apps/seo/src/routes/_authed/dashboard.tsx | 32 ++ .../onboarding/-components/0-welcome.tsx | 37 ++ .../-components/1-user-background.tsx | 257 +++++++++++++ .../-components/2-company-background.tsx | 122 ++++++ .../-components/3-understanding-company.tsx | 117 ++++++ .../-components/4-review-organization.tsx | 145 +++++++ .../-components/5-review-project.tsx | 146 ++++++++ .../onboarding/-components/6-all-set.tsx | 40 ++ .../onboarding/-components/content.tsx | 63 ++++ .../-components/onboarding-progress.tsx | 38 ++ .../routes/_authed/onboarding/-lib/steps.ts | 43 +++ .../src/routes/_authed/onboarding/index.tsx | 39 ++ apps/seo/src/routes/_authed/organizations.tsx | 34 ++ apps/seo/src/routes/_authed/route.tsx | 21 ++ .../src/routes/_marketing/-components/cta.tsx | 26 ++ .../src/routes/_marketing/-components/faq.tsx | 83 ++++ .../_marketing/-components/features.tsx | 84 +++++ .../routes/_marketing/-components/footer.tsx | 31 ++ .../_marketing/-components/forecast.tsx | 54 +++ .../routes/_marketing/-components/header.tsx | 113 ++++++ .../routes/_marketing/-components/hero.tsx | 36 ++ .../_marketing/-components/logo-cloud.tsx | 95 +++++ .../routes/_marketing/-components/pricing.tsx | 306 +++++++++++++++ .../routes/_marketing/-components/stats.tsx | 88 +++++ apps/seo/src/routes/_marketing/blog/$.tsx | 30 ++ apps/seo/src/routes/_marketing/blog/index.tsx | 31 ++ apps/seo/src/routes/_marketing/blog/route.tsx | 14 + .../src/routes/_marketing/blog/rss[.]xml.ts | 15 + apps/seo/src/routes/_marketing/index.tsx | 26 ++ apps/seo/src/routes/_marketing/route.tsx | 19 + apps/seo/src/routes/api/$.ts | 55 +++ apps/seo/src/routes/api/rpc.$.ts | 26 ++ apps/seo/src/routes/login.tsx | 82 ++++ apps/seo/src/styles.css | 7 + apps/seo/tsconfig.json | 20 + apps/seo/vite.config.ts | 38 ++ apps/seo/wrangler.jsonc | 37 ++ 57 files changed, 3293 insertions(+) create mode 100644 apps/seo/package.json create mode 100644 apps/seo/public/android-chrome-192x192.png create mode 100644 apps/seo/public/android-chrome-512x512.png create mode 100644 apps/seo/public/apple-touch-icon.png create mode 100644 apps/seo/public/favicon-96x96.png create mode 100644 apps/seo/public/favicon.ico create mode 100644 apps/seo/public/favicon.svg create mode 100644 apps/seo/public/site.webmanifest create mode 100644 apps/seo/src/components/error-boundary.tsx create mode 100644 apps/seo/src/components/logout-button.tsx create mode 100644 apps/seo/src/components/not-found.tsx create mode 100644 apps/seo/src/lib/api.ts create mode 100644 apps/seo/src/lib/auth/client.ts create mode 100644 apps/seo/src/lib/auth/server.ts create mode 100644 apps/seo/src/lib/env.ts create mode 100644 apps/seo/src/lib/seo.ts create mode 100644 apps/seo/src/reportWebVitals.ts create mode 100644 apps/seo/src/routeTree.gen.ts create mode 100644 apps/seo/src/router.tsx create mode 100644 apps/seo/src/routes/__root.tsx create mode 100644 apps/seo/src/routes/_authed/dashboard.tsx create mode 100644 apps/seo/src/routes/_authed/onboarding/-components/0-welcome.tsx create mode 100644 apps/seo/src/routes/_authed/onboarding/-components/1-user-background.tsx create mode 100644 apps/seo/src/routes/_authed/onboarding/-components/2-company-background.tsx create mode 100644 apps/seo/src/routes/_authed/onboarding/-components/3-understanding-company.tsx create mode 100644 apps/seo/src/routes/_authed/onboarding/-components/4-review-organization.tsx create mode 100644 apps/seo/src/routes/_authed/onboarding/-components/5-review-project.tsx create mode 100644 apps/seo/src/routes/_authed/onboarding/-components/6-all-set.tsx create mode 100644 apps/seo/src/routes/_authed/onboarding/-components/content.tsx create mode 100644 apps/seo/src/routes/_authed/onboarding/-components/onboarding-progress.tsx create mode 100644 apps/seo/src/routes/_authed/onboarding/-lib/steps.ts create mode 100644 apps/seo/src/routes/_authed/onboarding/index.tsx create mode 100644 apps/seo/src/routes/_authed/organizations.tsx create mode 100644 apps/seo/src/routes/_authed/route.tsx create mode 100644 apps/seo/src/routes/_marketing/-components/cta.tsx create mode 100644 apps/seo/src/routes/_marketing/-components/faq.tsx create mode 100644 apps/seo/src/routes/_marketing/-components/features.tsx create mode 100644 apps/seo/src/routes/_marketing/-components/footer.tsx create mode 100644 apps/seo/src/routes/_marketing/-components/forecast.tsx create mode 100644 apps/seo/src/routes/_marketing/-components/header.tsx create mode 100644 apps/seo/src/routes/_marketing/-components/hero.tsx create mode 100644 apps/seo/src/routes/_marketing/-components/logo-cloud.tsx create mode 100644 apps/seo/src/routes/_marketing/-components/pricing.tsx create mode 100644 apps/seo/src/routes/_marketing/-components/stats.tsx create mode 100644 apps/seo/src/routes/_marketing/blog/$.tsx create mode 100644 apps/seo/src/routes/_marketing/blog/index.tsx create mode 100644 apps/seo/src/routes/_marketing/blog/route.tsx create mode 100644 apps/seo/src/routes/_marketing/blog/rss[.]xml.ts create mode 100644 apps/seo/src/routes/_marketing/index.tsx create mode 100644 apps/seo/src/routes/_marketing/route.tsx create mode 100644 apps/seo/src/routes/api/$.ts create mode 100644 apps/seo/src/routes/api/rpc.$.ts create mode 100644 apps/seo/src/routes/login.tsx create mode 100644 apps/seo/src/styles.css create mode 100644 apps/seo/tsconfig.json create mode 100644 apps/seo/vite.config.ts create mode 100644 apps/seo/wrangler.jsonc diff --git a/apps/seo/package.json b/apps/seo/package.json new file mode 100644 index 000000000..5339ae05c --- /dev/null +++ b/apps/seo/package.json @@ -0,0 +1,62 @@ +{ + "name": "seo", + "private": true, + "sideEffects": false, + "type": "module", + "scripts": { + "dev": "pnpm cf-typegen && pnpm with-env-local vite", + "build:local": "pnpm with-env-local vite build", + "build:preview": "pnpm with-env-preview vite build ", + "build:production": "pnpm with-env-production vite build", + "serve:local": "pnpm with-env-local vite preview", + "test": "vitest run", + "clean": "git clean -xdf .turbo node_modules dist .cache", + "format": "pnpx @biomejs/biome format . --write", + "lint": "pnpx @biomejs/biome lint . --write", + "typecheck": "pnpm cf-typegen && pnpm tsc --noEmit --emitDeclarationOnly false", + "with-env-local": "dotenvx run -f ../../.env.local -f ../../.env -- ", + "with-env-preview": "dotenvx run -f ../../.env -- ", + "with-env-production": "dotenvx run -f ../../.env.production -- ", + "wrangler": "wrangler", + "cf-typegen": "wrangler types --env-interface Env" + }, + "dependencies": { + "@rectangular-labs/api-seo": "workspace:*", + "@rectangular-labs/auth": "workspace:*", + "@rectangular-labs/content": "workspace:*", + "@rectangular-labs/db": "workspace:*", + "@rectangular-labs/result": "workspace:*", + "@rectangular-labs/ui": "workspace:*", + "@stepperize/react": "^5.1.7", + "@t3-oss/env-core": "^0.13.8", + "@tanstack/react-query": "^5.89.0", + "@tanstack/react-query-devtools": "^5.89.0", + "@tanstack/react-router": "^1.131.44", + "@tanstack/react-router-devtools": "^1.131.44", + "@tanstack/react-router-with-query": "^1.130.17", + "@tanstack/react-start": "^1.131.44", + "arktype": "^2.1.22", + "motion": "^12.23.13", + "react": "^19.1.1", + "react-dom": "^19.1.1" + }, + "devDependencies": { + "@rectangular-labs/typescript": "workspace:*", + "@tailwindcss/vite": "^4.1.13", + "@testing-library/dom": "^10.4.1", + "@testing-library/react": "^16.3.0", + "@types/node": "^24.5.1", + "@types/react": "^19.1.13", + "@types/react-dom": "^19.1.9", + "@vitejs/plugin-react": "^5.0.3", + "jiti": "^2.5.1", + "jsdom": "^26.1.0", + "typescript": "^5.9.2", + "vite": "^7.1.5", + "vite-plugin-mkcert": "^1.17.8", + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^3.2.4", + "web-vitals": "^5.1.0", + "wrangler": "^4.37.1" + } +} diff --git a/apps/seo/public/android-chrome-192x192.png b/apps/seo/public/android-chrome-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..893a732247d04c5a87817bf99861d41048eda564 GIT binary patch literal 1495 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zf+;V4dgb;uum9_x7%B(W3wfmy7C0 zMV_ck5EOpE+EIF#NkKuaxkaXHhGf7^DFHeC1GO(cVv?YHCOKAwN7E7pC<#JBuvsqEGM9qI=*GlZ=Uy_sVc$oyXX_s5Tc(@v{0F3DkJ z+~YTk;m|%IPX?9`cNo_@#5L|=TDs?-&8sggAAs=35(l3GO&bRO311j$R!?Q%XkbXV zFh}+QV+Dg;1N)1z1KJb97tA?+-+ZbN1ET;?*=A;-;seYd7;Fxtv&*y=h#U&+m?N&> zt|-%9_qIVD0v&?bXC$c2;7n_p`Jl8=>Plt+(69%LlkPRJCc?Bc@PD|$*#1za&AW>E zi+tin$5{WKgg6%JW+=t6 z$-%GLiuc!h>x2K)Z~SMr;CiUhvRJ@#J%fP;4_EV{1*nv#+=}?s@vR?L3!MD@K0WZ= z@An2V8(3@@ITR}XvL-eeIL=Vec`2sBA>6`why}v;5l{nj6E3Lmt8i4mW#DRITIk?e z?jXX!>eLvZfI=-$;Rng4o&|**GyoygX<#H)gCdd7bAw8R&>WRYFx>-Gtsww55=^O9 zDuBiRY+W@H6$<;^-5qE`Zg zZ`Pjw&_Hzj?toz)$TkITmga9(4}?AKCO#4N#2qwO)?ZDuXn#2A`+X24(^f2@_d<>X zBa(o&Nu@M`q~AUB{^;)w@~nzI1Jt()FXS{(eGdtYqT+_He?v2N-(|C0@I6Sp+ck66 zvfZzC^}TYxV}I0s#rwSNuh;#plQ;cPw{i2$HD{iGez$6s)++glOcrwePYW!zTz^^8 z1@88JwP9z4}I!`ojFgtXJ^jt z+3$S2`+fi4o=)1DFxP*PKLB8^aO3)I0KjAw2J{!mVm$A6F90kcT)%Ed-oDXpbJ@<5 zK^ywMGxi0s=N13z6;Z@yq-{+v32xF$DlUQQOOqJCj0RHXt?2$qk3H#bGT7#P#+&_ ziy}M>SvAKRUK;_J+xBeAjm6vWpFdfctg|2l-l(rqgjukcWwi+ni@R*`*zM}I$+|~P zxrThH{aU{v$bEtz1;KZO2A4V_lde%BxYy|!Z8YFcmsuI62nGqu&uhYg>*=GW9z;r1 zn-q#*S=*KaBLWap@JjWVTTCG6SbMk0q>$ozmv#>H(%$`ORw+2{9l9)5=;C}MA_H@| z)S)-76hg6pE_|!GUY_bGdPSHUvoQ9UM!M8gq8nDScrwTeg6mhX!c+wQ{!n zmDn2vMTVoOvIJ+{5z4@^yDpWI9tvB>hwims%h2`a$Hp~zRzsO9%KgYzhh8MA;pS&K z3a}Ic8;pj&>`DLS2#s~o)VYKpCsW5nISMn=OQ2`Knfe&X^(r_MZ9pXTwZ_2=!e<~J z=Rk&aZ&V`l*FfDacdB|!yU!c3VJxJE+*j>+;ffrLl4oC69!{`P8Wj?ou z&X6tLa^O!E2#J+%6#{`i?K%v>NwWRlR|DvvQUlX8b3Y8{gCAC$|Lg=vALp)#B!8HE zz@I!>po-2MnD&(lp3MN9hLZq#a5r5L0DszPfAR$_^;m30G=l}6-!16&W5}HTZ;=e(TtK8 zDIxZml&saqJ!4}tBz#X5M=Z#=n(}sW)s&21uZT*lNl_slZ(Eciz4deT-%2`(IHvJ} zVBm_G03~uJPt7eDe=_VS3Y;$bi&O>Ko~KQgWdaI&RPIc+f@GU_aic*2sp9X$)jkm|0CEphihARC1Zqdr zyaQ-}to;0DqdQEN$iv1*?SEt_Ftac16xe(uCXF-21EjdeG@utri+rfFTP*eY9Qrby zpXSNy{grEJ#$HunrO#zS?5N#NLakLb3xSFR3~Z?ZW5cDOInVZZ*n_zYgtLHzP_a;E z%mg&aG~KSQGm)Ul(u{aQrW;Sr9M%&4ZjsK`UP6+HbGG&{5|?BdY=|4vAH6k${GdrZ ze5uCP1T2NIY5@rCNQX&$)~^_mo7`g5QWy!!t8P6wOR|sVh*~Bd{Xq)zW}wqCU+K3I zbnRF|pF-*;(mM7lnApo>B6L$fiRl466PxW_*1T!ml`jL4-01SD{;rTX=06yi8jLdIzj`L+ fApZvkA!f1m`Ux+Gq$H9Ti-2&$*7f!8q?Y^zNfK*{ literal 0 HcmV?d00001 diff --git a/apps/seo/public/apple-touch-icon.png b/apps/seo/public/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a3a00cf19cee5dc19b51933c39e1f2a5b80f21ed GIT binary patch literal 1245 zcmeAS@N?(olHy`uVBq!ia0vp^TR@nD4M^IaWiw)6U|Hhn;uum9_xAe6!lY;c*Ndwe zG&mR{6Ow{87&X&083S+3NN{8bU@@qdko>IP`uE!AdAIMb&YS!B)9df%HD&@g4{%?Y z=q>g5JX-^s;#S55!Ou;2F7PZdV<>qwr;Tw|;}y0BzqywU9E>O2Vz{ufSc)x!El8Td zs?_f=!&!$cW{2hHG9?yBdgL;^xMIo6Y{I;fmtn5e^2CNT1@a7=;n`4UBloy$ckl1N zcJuxR|4Q3@bH@4S$r~ejp4b1K&GO>(Q=@awKU>W8dt9~mTK=!|HK(5zt-1bsW7OJX zuU}`^ee-Ovlj~33f4~3mLj#b0v)N~F%Mc4WC1^?5UKsTh1u^wc@@0ih-`_fWzV^fO|7+IHKAV=a{r1M&Z;yT6|8MV~Uw^N* z?}^cSTw~XN`K8Ia>)Ew;>YqjHO;6q%*MBw3G%h~=^JaghFONS;Y`>j*=l%DOf978@ zpMCaNjh%e=(IlJyukWikT<_1=w_loJRvW#uF0OK*@jruOMqcdkkJbBu#TJ97tDnm{ Hr-UW|d!ZB$ literal 0 HcmV?d00001 diff --git a/apps/seo/public/favicon-96x96.png b/apps/seo/public/favicon-96x96.png new file mode 100644 index 0000000000000000000000000000000000000000..49664e9f08e4203775f2c78fe60394ed8c08b419 GIT binary patch literal 549 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7Ro>V0`20;uum9_x8^2-a`ojEDx44 zdMPv>~Y&j|`A$Q-wtf4+40T_y2ax7p{GJhI*Vg2Kc3Svi^A|_~R7%|#8_AdO^aAtAmGKF9PZXwqj%qEG6F)#iy z_(lj{YcWlPQrlIE1Lqi=^Zj`xd(}6F>IT~batZt!*l#euVFYp=8W@vT7+#Uc;xTQL za_(JtEFd`}f#Y}cmz)*p&bHF8RQE1mhuA5=J!>9g;v54%3H2V~W1Np$3J+N%-uYsD zL;9jxj_@|l+bzv3PTYT+z3G_N1LmC_GMx;9x9%@sQE-^iXt0Ni2#SOI1Fu@_e!g4w S+7ZC$XYh3Ob6Mw<&;$T_TG^5S literal 0 HcmV?d00001 diff --git a/apps/seo/public/favicon.ico b/apps/seo/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..d7baa0ead7fe71bd201930be27f61942bf9621ad GIT binary patch literal 15086 zcmeI3J&wXa3`V_z18{^iNR(7t?=4b>E_)KLkTPXBMWiT7jF^kjBc0?22c} ziW%(ue7rc(Xb}03jU11n+d?jTkxvmRiu>oC$d8WgbQ{0_-in+x;h;HMA}94Y{>%b~ z0w{n2D1ZV^1;%j + + Rectangular Lab Logo + + + \ No newline at end of file diff --git a/apps/seo/public/site.webmanifest b/apps/seo/public/site.webmanifest new file mode 100644 index 000000000..a1f16f020 --- /dev/null +++ b/apps/seo/public/site.webmanifest @@ -0,0 +1,21 @@ +{ + "name": "Rectangular Labs", + "short_name": "Rectangular Labs", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/apps/seo/src/components/error-boundary.tsx b/apps/seo/src/components/error-boundary.tsx new file mode 100644 index 000000000..2403ae399 --- /dev/null +++ b/apps/seo/src/components/error-boundary.tsx @@ -0,0 +1,56 @@ +import { Terminal } from "@rectangular-labs/ui/components/icon"; +import { + Alert, + AlertDescription, + AlertTitle, +} from "@rectangular-labs/ui/components/ui/alert"; +import { Button } from "@rectangular-labs/ui/components/ui/button"; +import type { ErrorComponentProps } from "@tanstack/react-router"; +import { Link, rootRouteId, useMatch, useRouter } from "@tanstack/react-router"; + +export function DefaultCatchBoundary({ error }: ErrorComponentProps) { + const router = useRouter(); + const isRoot = useMatch({ + strict: false, + select: (state) => state.id === rootRouteId, + }); + + console.error(error); + const message = + error instanceof Error ? error.message : "An unexpected error occurred."; + + return ( +
+ + + Something went wrong + {message} + +
+ + {isRoot ? ( + + ) : ( + + )} +
+
+ ); +} diff --git a/apps/seo/src/components/logout-button.tsx b/apps/seo/src/components/logout-button.tsx new file mode 100644 index 000000000..6c71d5a69 --- /dev/null +++ b/apps/seo/src/components/logout-button.tsx @@ -0,0 +1,33 @@ +import { Button } from "@rectangular-labs/ui/components/ui/button"; +import { useRouter } from "@tanstack/react-router"; +import { useState } from "react"; +import { authClient } from "~/lib/auth/client"; + +export function LogoutButton({ className }: { className?: string }) { + const router = useRouter(); + const [isPending, setIsPending] = useState(false); + + return ( + + ); +} diff --git a/apps/seo/src/components/not-found.tsx b/apps/seo/src/components/not-found.tsx new file mode 100644 index 000000000..69c1363ff --- /dev/null +++ b/apps/seo/src/components/not-found.tsx @@ -0,0 +1,34 @@ +import { Button } from "@rectangular-labs/ui/components/ui/button"; +import { Section } from "@rectangular-labs/ui/components/ui/section"; +import { Link } from "@tanstack/react-router"; + +export function NotFound({ children }: { children?: React.ReactNode }) { + return ( +
+
+

+ 404 +

+

+ Page not found +

+

+ {children || + "The page you are looking for does not exist or has been moved."} +

+
+ + +
+
+
+ ); +} diff --git a/apps/seo/src/lib/api.ts b/apps/seo/src/lib/api.ts new file mode 100644 index 000000000..64e97de91 --- /dev/null +++ b/apps/seo/src/lib/api.ts @@ -0,0 +1,27 @@ +import { rpcClient, rqApiClient } from "@rectangular-labs/api-seo/client"; +import { serverClient } from "@rectangular-labs/api-seo/server"; +import { createIsomorphicFn } from "@tanstack/react-start"; +import { getWebRequest } from "@tanstack/react-start/server"; +import { clientEnv } from "./env"; + +export const getApiClient = createIsomorphicFn() + .server(() => { + const request = getWebRequest(); + const client = serverClient({ + url: new URL(request.url), + // The request isn't populated in the server context, so we need to pass it in manually + reqHeaders: request.headers, + resHeaders: new Headers(), + }); + return client; + }) + .client(() => { + const client = rpcClient(window.location.origin); + return client; + }); + +export const apiClientRq = rqApiClient( + typeof window !== "undefined" + ? window.location.origin + : clientEnv().VITE_MENTIONS_URL, +); diff --git a/apps/seo/src/lib/auth/client.ts b/apps/seo/src/lib/auth/client.ts new file mode 100644 index 000000000..aa5922353 --- /dev/null +++ b/apps/seo/src/lib/auth/client.ts @@ -0,0 +1,39 @@ +import { createAuthClient } from "@rectangular-labs/auth/client"; +import { createIsomorphicFn } from "@tanstack/react-start"; +import { getWebRequest } from "@tanstack/react-start/server"; +import { clientEnv } from "../env"; +import { authServerHandler } from "./server"; + +export const authClient = createAuthClient(clientEnv().VITE_MENTIONS_URL); + +export const getCurrentSession = createIsomorphicFn() + .server(async () => { + const request = getWebRequest(); + const session = await authServerHandler.api.getSession({ + headers: request.headers, + }); + return session; + }) + .client(async () => { + const session = await authClient.getSession(); + return session.data; + }); + +export const getUserOrganizations = createIsomorphicFn() + .server(async () => { + const request = getWebRequest(); + const organizations = await authServerHandler.api.listOrganizations({ + headers: request.headers, + }); + return organizations; + }) + .client(async () => { + const organizations = await authClient.organization.list(); + if (organizations.error) { + throw new Error( + organizations.error.message ?? + "Something went wrong loading organizations. Please try again", + ); + } + return organizations.data; + }); diff --git a/apps/seo/src/lib/auth/server.ts b/apps/seo/src/lib/auth/server.ts new file mode 100644 index 000000000..11fc18595 --- /dev/null +++ b/apps/seo/src/lib/auth/server.ts @@ -0,0 +1,8 @@ +import { initAuthHandler } from "@rectangular-labs/auth"; +import { createDb } from "@rectangular-labs/db"; +import { serverEnv } from "../env"; + +export const authServerHandler = initAuthHandler( + serverEnv().VITE_MENTIONS_URL, + createDb(), +); diff --git a/apps/seo/src/lib/env.ts b/apps/seo/src/lib/env.ts new file mode 100644 index 000000000..9e965d46d --- /dev/null +++ b/apps/seo/src/lib/env.ts @@ -0,0 +1,26 @@ +import { apiEnv } from "@rectangular-labs/api-seo/env"; +import { createEnv } from "@t3-oss/env-core"; +import { type } from "arktype"; + +export const clientEnv = () => + createEnv({ + extends: [], + clientPrefix: "VITE_", + client: { + VITE_MENTIONS_URL: type("string"), + }, + runtimeEnv: import.meta.env, + emptyStringAsUndefined: true, + skipValidation: + !!process.env.CI || process.env.npm_lifecycle_event === "lint", + }); + +export const serverEnv = () => + createEnv({ + extends: [clientEnv(), apiEnv()], + server: {}, + runtimeEnv: process.env, + emptyStringAsUndefined: true, + skipValidation: + !!process.env.CI || process.env.npm_lifecycle_event === "lint", + }); diff --git a/apps/seo/src/lib/seo.ts b/apps/seo/src/lib/seo.ts new file mode 100644 index 000000000..cc899546b --- /dev/null +++ b/apps/seo/src/lib/seo.ts @@ -0,0 +1,35 @@ +export const seo = ({ + title, + description, + keywords, + image, +}: { + title: string; + description?: string; + image?: string; + keywords?: string; +}) => { + const tags = [ + { title }, + { name: "description", content: description }, + { name: "keywords", content: keywords }, + { name: "robots", content: "index,follow" }, + { name: "twitter:title", content: title }, + { name: "twitter:description", content: description }, + { name: "twitter:creator", content: "@winston_yeo" }, + { name: "twitter:site", content: "@winston_yeo" }, + { name: "og:type", content: "website" }, + { name: "og:site_name", content: "SEO" }, + { name: "og:title", content: title }, + { name: "og:description", content: description }, + ...(image + ? [ + { name: "twitter:image", content: image }, + { name: "twitter:card", content: "summary_large_image" }, + { name: "og:image", content: image }, + ] + : []), + ]; + + return tags; +}; diff --git a/apps/seo/src/reportWebVitals.ts b/apps/seo/src/reportWebVitals.ts new file mode 100644 index 000000000..d3c25119e --- /dev/null +++ b/apps/seo/src/reportWebVitals.ts @@ -0,0 +1,13 @@ +const reportWebVitals = (onPerfEntry?: () => void) => { + if (onPerfEntry && onPerfEntry instanceof Function) { + void import("web-vitals").then(({ onCLS, onINP, onFCP, onLCP, onTTFB }) => { + onCLS(onPerfEntry); + onINP(onPerfEntry); + onFCP(onPerfEntry); + onLCP(onPerfEntry); + onTTFB(onPerfEntry); + }); + } +}; + +export default reportWebVitals; diff --git a/apps/seo/src/routeTree.gen.ts b/apps/seo/src/routeTree.gen.ts new file mode 100644 index 000000000..2f2d7872f --- /dev/null +++ b/apps/seo/src/routeTree.gen.ts @@ -0,0 +1,354 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { createServerRootRoute } from '@tanstack/react-start/server' + +import { Route as rootRouteImport } from './routes/__root' +import { Route as LoginRouteImport } from './routes/login' +import { Route as MarketingRouteRouteImport } from './routes/_marketing/route' +import { Route as AuthedRouteRouteImport } from './routes/_authed/route' +import { Route as MarketingIndexRouteImport } from './routes/_marketing/index' +import { Route as AuthedOrganizationsRouteImport } from './routes/_authed/organizations' +import { Route as AuthedDashboardRouteImport } from './routes/_authed/dashboard' +import { Route as MarketingBlogRouteRouteImport } from './routes/_marketing/blog/route' +import { Route as MarketingBlogIndexRouteImport } from './routes/_marketing/blog/index' +import { Route as AuthedOnboardingIndexRouteImport } from './routes/_authed/onboarding/index' +import { Route as MarketingBlogSplatRouteImport } from './routes/_marketing/blog/$' +import { ServerRoute as ApiSplatServerRouteImport } from './routes/api/$' +import { ServerRoute as ApiRpcSplatServerRouteImport } from './routes/api/rpc.$' +import { ServerRoute as MarketingBlogRssDotxmlServerRouteImport } from './routes/_marketing/blog/rss[.]xml' + +const rootServerRouteImport = createServerRootRoute() + +const LoginRoute = LoginRouteImport.update({ + id: '/login', + path: '/login', + getParentRoute: () => rootRouteImport, +} as any) +const MarketingRouteRoute = MarketingRouteRouteImport.update({ + id: '/_marketing', + getParentRoute: () => rootRouteImport, +} as any) +const AuthedRouteRoute = AuthedRouteRouteImport.update({ + id: '/_authed', + getParentRoute: () => rootRouteImport, +} as any) +const MarketingIndexRoute = MarketingIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => MarketingRouteRoute, +} as any) +const AuthedOrganizationsRoute = AuthedOrganizationsRouteImport.update({ + id: '/organizations', + path: '/organizations', + getParentRoute: () => AuthedRouteRoute, +} as any) +const AuthedDashboardRoute = AuthedDashboardRouteImport.update({ + id: '/dashboard', + path: '/dashboard', + getParentRoute: () => AuthedRouteRoute, +} as any) +const MarketingBlogRouteRoute = MarketingBlogRouteRouteImport.update({ + id: '/blog', + path: '/blog', + getParentRoute: () => MarketingRouteRoute, +} as any) +const MarketingBlogIndexRoute = MarketingBlogIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => MarketingBlogRouteRoute, +} as any) +const AuthedOnboardingIndexRoute = AuthedOnboardingIndexRouteImport.update({ + id: '/onboarding/', + path: '/onboarding/', + getParentRoute: () => AuthedRouteRoute, +} as any) +const MarketingBlogSplatRoute = MarketingBlogSplatRouteImport.update({ + id: '/$', + path: '/$', + getParentRoute: () => MarketingBlogRouteRoute, +} as any) +const ApiSplatServerRoute = ApiSplatServerRouteImport.update({ + id: '/api/$', + path: '/api/$', + getParentRoute: () => rootServerRouteImport, +} as any) +const ApiRpcSplatServerRoute = ApiRpcSplatServerRouteImport.update({ + id: '/api/rpc/$', + path: '/api/rpc/$', + getParentRoute: () => rootServerRouteImport, +} as any) +const MarketingBlogRssDotxmlServerRoute = + MarketingBlogRssDotxmlServerRouteImport.update({ + id: '/_marketing/blog/rss.xml', + path: '/blog/rss.xml', + getParentRoute: () => rootServerRouteImport, + } as any) + +export interface FileRoutesByFullPath { + '/login': typeof LoginRoute + '/blog': typeof MarketingBlogRouteRouteWithChildren + '/dashboard': typeof AuthedDashboardRoute + '/organizations': typeof AuthedOrganizationsRoute + '/': typeof MarketingIndexRoute + '/blog/$': typeof MarketingBlogSplatRoute + '/onboarding': typeof AuthedOnboardingIndexRoute + '/blog/': typeof MarketingBlogIndexRoute +} +export interface FileRoutesByTo { + '/login': typeof LoginRoute + '/dashboard': typeof AuthedDashboardRoute + '/organizations': typeof AuthedOrganizationsRoute + '/': typeof MarketingIndexRoute + '/blog/$': typeof MarketingBlogSplatRoute + '/onboarding': typeof AuthedOnboardingIndexRoute + '/blog': typeof MarketingBlogIndexRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/_authed': typeof AuthedRouteRouteWithChildren + '/_marketing': typeof MarketingRouteRouteWithChildren + '/login': typeof LoginRoute + '/_marketing/blog': typeof MarketingBlogRouteRouteWithChildren + '/_authed/dashboard': typeof AuthedDashboardRoute + '/_authed/organizations': typeof AuthedOrganizationsRoute + '/_marketing/': typeof MarketingIndexRoute + '/_marketing/blog/$': typeof MarketingBlogSplatRoute + '/_authed/onboarding/': typeof AuthedOnboardingIndexRoute + '/_marketing/blog/': typeof MarketingBlogIndexRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: + | '/login' + | '/blog' + | '/dashboard' + | '/organizations' + | '/' + | '/blog/$' + | '/onboarding' + | '/blog/' + fileRoutesByTo: FileRoutesByTo + to: + | '/login' + | '/dashboard' + | '/organizations' + | '/' + | '/blog/$' + | '/onboarding' + | '/blog' + id: + | '__root__' + | '/_authed' + | '/_marketing' + | '/login' + | '/_marketing/blog' + | '/_authed/dashboard' + | '/_authed/organizations' + | '/_marketing/' + | '/_marketing/blog/$' + | '/_authed/onboarding/' + | '/_marketing/blog/' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + AuthedRouteRoute: typeof AuthedRouteRouteWithChildren + MarketingRouteRoute: typeof MarketingRouteRouteWithChildren + LoginRoute: typeof LoginRoute +} +export interface FileServerRoutesByFullPath { + '/api/$': typeof ApiSplatServerRoute + '/blog/rss.xml': typeof MarketingBlogRssDotxmlServerRoute + '/api/rpc/$': typeof ApiRpcSplatServerRoute +} +export interface FileServerRoutesByTo { + '/api/$': typeof ApiSplatServerRoute + '/blog/rss.xml': typeof MarketingBlogRssDotxmlServerRoute + '/api/rpc/$': typeof ApiRpcSplatServerRoute +} +export interface FileServerRoutesById { + __root__: typeof rootServerRouteImport + '/api/$': typeof ApiSplatServerRoute + '/_marketing/blog/rss.xml': typeof MarketingBlogRssDotxmlServerRoute + '/api/rpc/$': typeof ApiRpcSplatServerRoute +} +export interface FileServerRouteTypes { + fileServerRoutesByFullPath: FileServerRoutesByFullPath + fullPaths: '/api/$' | '/blog/rss.xml' | '/api/rpc/$' + fileServerRoutesByTo: FileServerRoutesByTo + to: '/api/$' | '/blog/rss.xml' | '/api/rpc/$' + id: '__root__' | '/api/$' | '/_marketing/blog/rss.xml' | '/api/rpc/$' + fileServerRoutesById: FileServerRoutesById +} +export interface RootServerRouteChildren { + ApiSplatServerRoute: typeof ApiSplatServerRoute + MarketingBlogRssDotxmlServerRoute: typeof MarketingBlogRssDotxmlServerRoute + ApiRpcSplatServerRoute: typeof ApiRpcSplatServerRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/login': { + id: '/login' + path: '/login' + fullPath: '/login' + preLoaderRoute: typeof LoginRouteImport + parentRoute: typeof rootRouteImport + } + '/_marketing': { + id: '/_marketing' + path: '' + fullPath: '' + preLoaderRoute: typeof MarketingRouteRouteImport + parentRoute: typeof rootRouteImport + } + '/_authed': { + id: '/_authed' + path: '' + fullPath: '' + preLoaderRoute: typeof AuthedRouteRouteImport + parentRoute: typeof rootRouteImport + } + '/_marketing/': { + id: '/_marketing/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof MarketingIndexRouteImport + parentRoute: typeof MarketingRouteRoute + } + '/_authed/organizations': { + id: '/_authed/organizations' + path: '/organizations' + fullPath: '/organizations' + preLoaderRoute: typeof AuthedOrganizationsRouteImport + parentRoute: typeof AuthedRouteRoute + } + '/_authed/dashboard': { + id: '/_authed/dashboard' + path: '/dashboard' + fullPath: '/dashboard' + preLoaderRoute: typeof AuthedDashboardRouteImport + parentRoute: typeof AuthedRouteRoute + } + '/_marketing/blog': { + id: '/_marketing/blog' + path: '/blog' + fullPath: '/blog' + preLoaderRoute: typeof MarketingBlogRouteRouteImport + parentRoute: typeof MarketingRouteRoute + } + '/_marketing/blog/': { + id: '/_marketing/blog/' + path: '/' + fullPath: '/blog/' + preLoaderRoute: typeof MarketingBlogIndexRouteImport + parentRoute: typeof MarketingBlogRouteRoute + } + '/_authed/onboarding/': { + id: '/_authed/onboarding/' + path: '/onboarding' + fullPath: '/onboarding' + preLoaderRoute: typeof AuthedOnboardingIndexRouteImport + parentRoute: typeof AuthedRouteRoute + } + '/_marketing/blog/$': { + id: '/_marketing/blog/$' + path: '/$' + fullPath: '/blog/$' + preLoaderRoute: typeof MarketingBlogSplatRouteImport + parentRoute: typeof MarketingBlogRouteRoute + } + } +} +declare module '@tanstack/react-start/server' { + interface ServerFileRoutesByPath { + '/api/$': { + id: '/api/$' + path: '/api/$' + fullPath: '/api/$' + preLoaderRoute: typeof ApiSplatServerRouteImport + parentRoute: typeof rootServerRouteImport + } + '/api/rpc/$': { + id: '/api/rpc/$' + path: '/api/rpc/$' + fullPath: '/api/rpc/$' + preLoaderRoute: typeof ApiRpcSplatServerRouteImport + parentRoute: typeof rootServerRouteImport + } + '/_marketing/blog/rss.xml': { + id: '/_marketing/blog/rss.xml' + path: '/blog/rss.xml' + fullPath: '/blog/rss.xml' + preLoaderRoute: typeof MarketingBlogRssDotxmlServerRouteImport + parentRoute: typeof rootServerRouteImport + } + } +} + +interface AuthedRouteRouteChildren { + AuthedDashboardRoute: typeof AuthedDashboardRoute + AuthedOrganizationsRoute: typeof AuthedOrganizationsRoute + AuthedOnboardingIndexRoute: typeof AuthedOnboardingIndexRoute +} + +const AuthedRouteRouteChildren: AuthedRouteRouteChildren = { + AuthedDashboardRoute: AuthedDashboardRoute, + AuthedOrganizationsRoute: AuthedOrganizationsRoute, + AuthedOnboardingIndexRoute: AuthedOnboardingIndexRoute, +} + +const AuthedRouteRouteWithChildren = AuthedRouteRoute._addFileChildren( + AuthedRouteRouteChildren, +) + +interface MarketingBlogRouteRouteChildren { + MarketingBlogSplatRoute: typeof MarketingBlogSplatRoute + MarketingBlogIndexRoute: typeof MarketingBlogIndexRoute +} + +const MarketingBlogRouteRouteChildren: MarketingBlogRouteRouteChildren = { + MarketingBlogSplatRoute: MarketingBlogSplatRoute, + MarketingBlogIndexRoute: MarketingBlogIndexRoute, +} + +const MarketingBlogRouteRouteWithChildren = + MarketingBlogRouteRoute._addFileChildren(MarketingBlogRouteRouteChildren) + +interface MarketingRouteRouteChildren { + MarketingBlogRouteRoute: typeof MarketingBlogRouteRouteWithChildren + MarketingIndexRoute: typeof MarketingIndexRoute +} + +const MarketingRouteRouteChildren: MarketingRouteRouteChildren = { + MarketingBlogRouteRoute: MarketingBlogRouteRouteWithChildren, + MarketingIndexRoute: MarketingIndexRoute, +} + +const MarketingRouteRouteWithChildren = MarketingRouteRoute._addFileChildren( + MarketingRouteRouteChildren, +) + +const rootRouteChildren: RootRouteChildren = { + AuthedRouteRoute: AuthedRouteRouteWithChildren, + MarketingRouteRoute: MarketingRouteRouteWithChildren, + LoginRoute: LoginRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() +const rootServerRouteChildren: RootServerRouteChildren = { + ApiSplatServerRoute: ApiSplatServerRoute, + MarketingBlogRssDotxmlServerRoute: MarketingBlogRssDotxmlServerRoute, + ApiRpcSplatServerRoute: ApiRpcSplatServerRoute, +} +export const serverRouteTree = rootServerRouteImport + ._addFileChildren(rootServerRouteChildren) + ._addFileTypes() diff --git a/apps/seo/src/router.tsx b/apps/seo/src/router.tsx new file mode 100644 index 000000000..ab605195f --- /dev/null +++ b/apps/seo/src/router.tsx @@ -0,0 +1,33 @@ +import { QueryClient } from "@tanstack/react-query"; +import { createRouter as createTanStackRouter } from "@tanstack/react-router"; +import { routerWithQueryClient } from "@tanstack/react-router-with-query"; +import { DefaultCatchBoundary } from "./components/error-boundary"; +import { NotFound } from "./components/not-found"; +import { routeTree } from "./routeTree.gen"; + +// NOTE: Most of the integration code found here is experimental and will +// definitely end up in a more streamlined API in the future. This is just +// to show what's possible with the current APIs. + +export function createRouter() { + const queryClient = new QueryClient(); + + return routerWithQueryClient( + createTanStackRouter({ + routeTree, + context: { queryClient }, + defaultPreload: "intent", + defaultErrorComponent: DefaultCatchBoundary, + defaultNotFoundComponent: () => , + scrollRestoration: true, + defaultStructuralSharing: true, + }), + queryClient, + ); +} + +declare module "@tanstack/react-router" { + interface Register { + router: ReturnType; + } +} diff --git a/apps/seo/src/routes/__root.tsx b/apps/seo/src/routes/__root.tsx new file mode 100644 index 000000000..5ac2fcd3e --- /dev/null +++ b/apps/seo/src/routes/__root.tsx @@ -0,0 +1,96 @@ +import { ThemeProvider } from "@rectangular-labs/ui/components/theme-provider"; +import { Toaster } from "@rectangular-labs/ui/components/ui/sonner"; +import type { QueryClient } from "@tanstack/react-query"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import { + createRootRouteWithContext, + HeadContent, + Outlet, + Scripts, +} from "@tanstack/react-router"; +import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"; +import { seo } from "~/lib/seo"; +import appCss from "../styles.css?url"; + +export const Route = createRootRouteWithContext<{ + queryClient: QueryClient; +}>()({ + head: () => ({ + meta: [ + { charSet: "utf-8" }, + { + name: "viewport", + content: "width=device-width, initial-scale=1", + }, + { + name: "apple-mobile-web-app-title", + content: "Mentions", + }, + { + name: "application-name", + content: "Mentions", + }, + ...seo({ + title: "The AI SEO employee — Understand. Plan. Forecast. Ship.", + description: + "Hire the first AI SEO employee. Understand your site, plan campaigns by intent, forecast ranking ranges, and schedule content that ships.", + keywords: + "AI SEO employee, SEO automation, SEO forecasting, content calendar, keyword clusters", + }), + ], + links: [ + { + rel: "alternate", + type: "application/rss+xml", + href: "/blog/rss.xml", + title: "Blog RSS", + }, + { rel: "stylesheet", href: appCss }, + { + rel: "apple-touch-icon", + sizes: "180x180", + href: "/apple-touch-icon.png", + }, + { + rel: "icon", + type: "image/png", + sizes: "96x96", + href: "/favicon-96x96.png", + }, + { + rel: "icon", + type: "image/svg+xml", + sizes: "any", + href: "/favicon.svg", + }, + { + rel: "icon", + type: "image/x-icon", + href: "/favicon.ico", + }, + { rel: "manifest", href: "/site.webmanifest", color: "#fffff" }, + ], + }), + component: RootLayout, +}); + +function RootLayout() { + return ( + + + + + + + + + + + + + + + ); +} + +// reportWebVitals(typeof window!== 'undefined' ?console.log : undefined); diff --git a/apps/seo/src/routes/_authed/dashboard.tsx b/apps/seo/src/routes/_authed/dashboard.tsx new file mode 100644 index 000000000..2bb26a95d --- /dev/null +++ b/apps/seo/src/routes/_authed/dashboard.tsx @@ -0,0 +1,32 @@ +import { createFileRoute, redirect } from "@tanstack/react-router"; +import { apiClientRq } from "~/lib/api"; +import { getUserOrganizations } from "~/lib/auth/client"; + +export const Route = createFileRoute("/_authed/dashboard")({ + component: ProjectPicker, + beforeLoad: async ({ context }) => { + const organizations = await getUserOrganizations(); + if (organizations.length === 0) { + throw redirect({ + to: "/onboarding", + }); + } + // we need to check org first otherwise the following call will throw a 404 + const projects = await context.queryClient.fetchQuery( + apiClientRq.projects.list.queryOptions({ + input: { + limit: 1, + }, + }), + ); + if (projects.data.length === 0) { + throw redirect({ + to: "/onboarding", + }); + } + }, +}); + +function ProjectPicker() { + return null; +} diff --git a/apps/seo/src/routes/_authed/onboarding/-components/0-welcome.tsx b/apps/seo/src/routes/_authed/onboarding/-components/0-welcome.tsx new file mode 100644 index 000000000..53f3d50ed --- /dev/null +++ b/apps/seo/src/routes/_authed/onboarding/-components/0-welcome.tsx @@ -0,0 +1,37 @@ +import { Sparkles } from "@rectangular-labs/ui/components/icon"; +import { Button } from "@rectangular-labs/ui/components/ui/button"; +import { + Card, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@rectangular-labs/ui/components/ui/card"; +import { OnboardingSteps } from "../-lib/steps"; + +export function OnboardingWelcome({ + description, + title, +}: { + title: string; + description: string; +}) { + const matcher = OnboardingSteps.useStepper(); + + return ( + + + + + {title} + + {description} + + + + + + ); +} diff --git a/apps/seo/src/routes/_authed/onboarding/-components/1-user-background.tsx b/apps/seo/src/routes/_authed/onboarding/-components/1-user-background.tsx new file mode 100644 index 000000000..0a8066bfc --- /dev/null +++ b/apps/seo/src/routes/_authed/onboarding/-components/1-user-background.tsx @@ -0,0 +1,257 @@ +import { Button } from "@rectangular-labs/ui/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@rectangular-labs/ui/components/ui/card"; +import { + arktypeResolver, + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, + useForm, +} from "@rectangular-labs/ui/components/ui/form"; +import { Input } from "@rectangular-labs/ui/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@rectangular-labs/ui/components/ui/select"; +import { useMutation } from "@tanstack/react-query"; +import { type } from "arktype"; +import { authClient } from "~/lib/auth/client"; +import { OnboardingSteps } from "../-lib/steps"; + +const sourceOptions = [ + { value: "x", label: "X" }, + { value: "reddit", label: "Reddit" }, + { value: "hacker-news", label: "Hacker News" }, + { value: "other", label: "Other" }, +]; +const goalOptions = [ + { value: "sentiment", label: "Figuring out customer sentiment" }, + { value: "monitor", label: "Monitor conversation around a topic" }, + { value: "problems", label: "Find problems" }, + { value: "other", label: "Other" }, +]; + +const backgroundSchema = type({ + source: type.string + .atLeastLength(1) + .configure({ message: () => "Source must not be blank" }), + "otherSource?": "string", + goal: type.string + .atLeastLength(1) + .configure({ message: () => "Goal must not be blank" }), + "otherGoal?": "string", +}).narrow((data, ctx) => { + if (data.source === "other" && !data.otherSource) { + return ctx.reject({ + path: ["otherSource"], + message: "Source must not be blank", + }); + } + if (data.goal === "other" && !data.otherGoal) { + return ctx.reject({ + path: ["otherGoal"], + message: "Goal must not be blank", + }); + } + return true; +}); + +export function OnboardingUserBackground({ + description, + title, +}: { + description: string; + title: string; +}) { + const matcher = OnboardingSteps.useStepper(); + const defaultValues = + matcher.getMetadata>( + "user-background", + ) ?? {}; + + const form = useForm({ + resolver: arktypeResolver(backgroundSchema), + defaultValues: { + source: defaultValues.source ?? "", + goal: defaultValues.goal ?? "", + otherGoal: defaultValues.otherGoal ?? "", + otherSource: defaultValues.otherSource ?? "", + }, + }); + const isOtherSource = form.watch("source") === "other"; + const isOtherGoal = form.watch("goal") === "other"; + + const { mutate: updateUser, isPending } = useMutation({ + mutationFn: async (values: typeof backgroundSchema.infer) => { + const source = + values.source === "other" ? values.otherSource : values.source; + const goal = values.goal === "other" ? values.otherGoal : values.goal; + const updatedUser = await authClient.updateUser({ + source, + goal, + }); + if (updatedUser.error) { + throw new Error(updatedUser.error.message); + } + return values; + }, + onSuccess: (data) => { + matcher.setMetadata("user-background", data); + matcher.next(); + }, + }); + + const handleSubmit = (values: typeof backgroundSchema.infer) => { + updateUser(values); + }; + + return ( +
+ + + {title} + {description} + +
+ + + ( + + Where did you first hear about us? + + + + + + + )} + /> + + {isOtherSource && ( + ( + + Other Source + + + + + + + )} + /> + )} + + ( + + + Where would you want Mentions to help with the most? + + + + + + + )} + /> + + {isOtherGoal && ( + ( + + Other Goal + + + + + + )} + /> + )} + + {form.formState.errors.root && ( + {form.formState.errors.root.message} + )} + + +
+ + +
+
+
+ +
+
+ ); +} diff --git a/apps/seo/src/routes/_authed/onboarding/-components/2-company-background.tsx b/apps/seo/src/routes/_authed/onboarding/-components/2-company-background.tsx new file mode 100644 index 000000000..06d367cb3 --- /dev/null +++ b/apps/seo/src/routes/_authed/onboarding/-components/2-company-background.tsx @@ -0,0 +1,122 @@ +import { Button } from "@rectangular-labs/ui/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@rectangular-labs/ui/components/ui/card"; +import { + arktypeResolver, + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, + useForm, +} from "@rectangular-labs/ui/components/ui/form"; +import { Input } from "@rectangular-labs/ui/components/ui/input"; +import { useMutation } from "@tanstack/react-query"; +import { type } from "arktype"; +import { apiClientRq } from "~/lib/api"; +import { OnboardingSteps } from "../-lib/steps"; + +const backgroundSchema = type({ + url: type("string.url").configure({ message: () => "Must be a valid URL" }), +}); + +export function OnboardingCompanyBackground({ + description, + title, +}: { + description: string; + title: string; +}) { + const matcher = OnboardingSteps.useStepper(); + + const form = useForm({ + resolver: arktypeResolver(backgroundSchema), + }); + + const { mutate: crawlInfo, isPending } = useMutation( + apiClientRq.companyBackground.crawlInfo.mutationOptions({ + onSuccess: (data, { websiteUrl }) => { + matcher.setMetadata("user-company", { + websiteUrl, + crawlId: data.id, + }); + matcher.next(); + }, + onError: (error) => { + form.setError("root", { + message: error.message, + }); + }, + }), + ); + + const handleSubmit = (values: typeof backgroundSchema.infer) => { + crawlInfo({ + websiteUrl: values.url, + }); + }; + + return ( +
+ + + {title} + {description} + +
+ + + ( + + Website + + + + + + + )} + /> + + {form.formState.errors.root && ( + {form.formState.errors.root.message} + )} + + +
+ + +
+
+
+ +
+
+ ); +} diff --git a/apps/seo/src/routes/_authed/onboarding/-components/3-understanding-company.tsx b/apps/seo/src/routes/_authed/onboarding/-components/3-understanding-company.tsx new file mode 100644 index 000000000..fd5e3eb90 --- /dev/null +++ b/apps/seo/src/routes/_authed/onboarding/-components/3-understanding-company.tsx @@ -0,0 +1,117 @@ +import { Check, Spinner } from "@rectangular-labs/ui/components/icon"; +import { Button } from "@rectangular-labs/ui/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@rectangular-labs/ui/components/ui/card"; +import { useQuery } from "@tanstack/react-query"; +import { apiClientRq } from "~/lib/api"; +import { OnboardingSteps } from "../-lib/steps"; + +const steps = [ + { + id: "background", + label: "Understanding site background", + }, + { id: "customer", label: "Constructing ideal customer profile" }, + { id: "tone", label: "Figuring out the best tone and response" }, +] as const; +type StepIds = (typeof steps)[number]["id"]; + +export function OnboardingUnderstandingCompany({ + description, + title, +}: { + title: string; + description: string; +}) { + const matcher = OnboardingSteps.useStepper(); + const { crawlId } = matcher.getMetadata<{ + crawlId: string; + }>("user-company"); + + const { data: companyBackground, error: getStatusError } = useQuery( + apiClientRq.companyBackground.getCrawlStatus.queryOptions({ + enabled: !!crawlId, + input: { + id: crawlId ?? "", + }, + refetchInterval: 3_000, // every 3 seconds + }), + ); + + if (getStatusError) { + return ( +
Something went wrong getting status. Trying again in 5 seconds.
+ ); + } + + const status: Record = { + background: companyBackground?.data?.description ? "done" : "doing", + customer: companyBackground?.data?.idealCustomer + ? "done" + : companyBackground?.data?.description + ? "doing" + : "pending", + tone: companyBackground?.data?.responseTone + ? "done" + : companyBackground?.data?.idealCustomer + ? "doing" + : "pending", + }; + + return ( +
+ + + {title} + {description} + + + {steps.map((step) => { + return ( +
+ {status[step.id] === "pending" && ( +
+ )} + {status[step.id] === "doing" && ( + + )} + {status[step.id] === "done" && ( + + )} +
{step.label}
+
+ ); + })} + + +
+ + +
+
+ +
+ ); +} diff --git a/apps/seo/src/routes/_authed/onboarding/-components/4-review-organization.tsx b/apps/seo/src/routes/_authed/onboarding/-components/4-review-organization.tsx new file mode 100644 index 000000000..6c8fd63ca --- /dev/null +++ b/apps/seo/src/routes/_authed/onboarding/-components/4-review-organization.tsx @@ -0,0 +1,145 @@ +import { Button } from "@rectangular-labs/ui/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@rectangular-labs/ui/components/ui/card"; +import { + arktypeResolver, + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, + useForm, +} from "@rectangular-labs/ui/components/ui/form"; +import { Input } from "@rectangular-labs/ui/components/ui/input"; +import { type } from "arktype"; +import { authClient } from "~/lib/auth/client"; +import { OnboardingSteps } from "../-lib/steps"; + +const backgroundSchema = type({ + name: "string.alphanumeric", + "description?": "string", +}); +export function OnboardingReviewOrganization() { + const matcher = OnboardingSteps.useStepper(); + const form = useForm({ + resolver: arktypeResolver(backgroundSchema), + }); + + const handleSubmit = async (values: typeof backgroundSchema.infer) => { + const valid = await authClient.organization.checkSlug({ + slug: values.name, + }); + if (valid.error) { + form.setError("root", { + message: + valid.error.message ?? + "Something went wrong creating the organization. Please try again later", + }); + return; + } + if (!valid.data?.status) { + form.setError("name", { + message: "Organization name already taken, please choose another one!", + }); + return; + } + + const organizationResult = await authClient.organization.create({ + name: values.name, + slug: values.name, + metadata: { description: values.description }, + }); + if (organizationResult.error) { + form.setError("root", { + message: + organizationResult.error.message || + organizationResult.error.statusText, + }); + return; + } + matcher.next(); + }; + + return ( +
+ + + Review Organization + + Your organization will let you manage team members and projects. + + +
+ + + ( + + Organization Name + + + + + + You will be able to change this at anytime later on + + + + )} + /> + + ( + + + Organization Description{" "} + (optional) + + + + + + + )} + /> + + {form.formState.errors.root && ( + {form.formState.errors.root.message} + )} + + +
+ + +
+
+
+ +
+
+ ); +} diff --git a/apps/seo/src/routes/_authed/onboarding/-components/5-review-project.tsx b/apps/seo/src/routes/_authed/onboarding/-components/5-review-project.tsx new file mode 100644 index 000000000..5a63de6a3 --- /dev/null +++ b/apps/seo/src/routes/_authed/onboarding/-components/5-review-project.tsx @@ -0,0 +1,146 @@ +import { Button } from "@rectangular-labs/ui/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@rectangular-labs/ui/components/ui/card"; +import { + arktypeResolver, + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, + useForm, +} from "@rectangular-labs/ui/components/ui/form"; +import { Input } from "@rectangular-labs/ui/components/ui/input"; +import { type } from "arktype"; +import { authClient } from "~/lib/auth/client"; +import { OnboardingSteps } from "../-lib/steps"; + +const schema = type({ + name: "string.alphanumeric", + description: "string", + targetAudience: "string", + suggestedKeywords: "string[]", + responseTone: "string", +}); +export function OnboardingReviewProject() { + const matcher = OnboardingSteps.useStepper(); + const form = useForm({ + resolver: arktypeResolver(schema), + }); + + const handleSubmit = async (values: typeof schema.infer) => { + const valid = await authClient.organization.checkSlug({ + slug: values.name, + }); + if (valid.error) { + form.setError("root", { + message: + valid.error.message ?? + "Something went wrong creating the organization. Please try again later", + }); + return; + } + if (!valid.data?.status) { + form.setError("name", { + message: "Organization name already taken, please choose another one!", + }); + return; + } + + const organizationResult = await authClient.organization.create({ + name: values.name, + slug: values.name, + metadata: { description: values.description }, + }); + if (organizationResult.error) { + form.setError("root", { + message: + organizationResult.error.message || + organizationResult.error.statusText, + }); + return; + } + matcher.next(); + }; + + return ( +
+ + + {matcher.current.title} + {matcher.current.description} + +
+ + + ( + + Organization Name + + + + + + You will be able to change this at anytime later on + + + + )} + /> + + ( + + + Organization Description{" "} + (optional) + + + + + + + )} + /> + + {form.formState.errors.root && ( + {form.formState.errors.root.message} + )} + + +
+ + +
+
+
+ +
+
+ ); +} diff --git a/apps/seo/src/routes/_authed/onboarding/-components/6-all-set.tsx b/apps/seo/src/routes/_authed/onboarding/-components/6-all-set.tsx new file mode 100644 index 000000000..1c251c8ce --- /dev/null +++ b/apps/seo/src/routes/_authed/onboarding/-components/6-all-set.tsx @@ -0,0 +1,40 @@ +import { PartyPopper } from "@rectangular-labs/ui/components/icon"; +import { Button } from "@rectangular-labs/ui/components/ui/button"; +import { + Card, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@rectangular-labs/ui/components/ui/card"; +import { useNavigate } from "@tanstack/react-router"; + +export function OnboardingAllSet({ + description, + title, +}: { + description: string; + title: string; +}) { + const navigate = useNavigate(); + return ( + + + + + {title} + + {description} + + + + + + ); +} diff --git a/apps/seo/src/routes/_authed/onboarding/-components/content.tsx b/apps/seo/src/routes/_authed/onboarding/-components/content.tsx new file mode 100644 index 000000000..b6bc6ffb9 --- /dev/null +++ b/apps/seo/src/routes/_authed/onboarding/-components/content.tsx @@ -0,0 +1,63 @@ +import { AutoHeight } from "@rectangular-labs/ui/animation/auto-height"; +import { OnboardingSteps } from "../-lib/steps"; +import { OnboardingWelcome } from "./0-welcome"; +import { OnboardingUserBackground } from "./1-user-background"; +import { OnboardingCompanyBackground } from "./2-company-background"; +import { OnboardingUnderstandingCompany } from "./3-understanding-company"; +import { OnboardingReviewOrganization } from "./4-review-organization"; +import { OnboardingReviewProject } from "./5-review-project"; +import { OnboardingAllSet } from "./6-all-set"; +import { OnboardingProgress } from "./onboarding-progress"; + +export function OnboardingContent() { + const matcher = OnboardingSteps.useStepper(); + + return ( +
+ + + {matcher.switch({ + welcome: (step) => ( + + ), + "user-background": (step) => ( + + ), + "user-company": (step) => ( + + ), + understanding: (step) => ( + + ), + "review-organization": () => , + "review-project": () => , + "all-set": (step) => ( + + ), + })} + +
+ ); +} diff --git a/apps/seo/src/routes/_authed/onboarding/-components/onboarding-progress.tsx b/apps/seo/src/routes/_authed/onboarding/-components/onboarding-progress.tsx new file mode 100644 index 000000000..e83ba35d7 --- /dev/null +++ b/apps/seo/src/routes/_authed/onboarding/-components/onboarding-progress.tsx @@ -0,0 +1,38 @@ +import { OnboardingSteps } from "../-lib/steps"; + +export function OnboardingProgress() { + const matcher = OnboardingSteps.useStepper(); + + if (matcher.current.id === "welcome" || matcher.current.id === "all-set") { + return null; + } + const relevantSteps = matcher.all.filter( + (step) => step.id !== "welcome" && step.id !== "all-set", + ); + const totalRelevantSteps = relevantSteps.length; + const currentRelevantStepIndex = relevantSteps.findIndex( + (step) => step.id === matcher.current.id, + ); + + return ( +
+

+ Step {currentRelevantStepIndex + 1} of {totalRelevantSteps} +

+
+ {relevantSteps.map((step, index) => { + const isCompleted = currentRelevantStepIndex > index; + const isCurrent = currentRelevantStepIndex === index; + return ( +
+ ); + })} +
+
+ ); +} diff --git a/apps/seo/src/routes/_authed/onboarding/-lib/steps.ts b/apps/seo/src/routes/_authed/onboarding/-lib/steps.ts new file mode 100644 index 000000000..7eb58c1a5 --- /dev/null +++ b/apps/seo/src/routes/_authed/onboarding/-lib/steps.ts @@ -0,0 +1,43 @@ +import { defineStepper } from "@stepperize/react"; + +export const OnboardingSteps = defineStepper( + { + id: "welcome", + title: "Welcome", + description: "We'll set up your workspace in 4 quick steps (~3 minutes)", + }, + { + id: "user-background", + title: "Background", + description: + "Help us better understand your background to personalize your onboarding experience.", + }, + { + id: "user-company", + title: "Company", + description: + "We will visit your company's website to match your goals with what your company does.", + }, + { + id: "understanding", + title: "Formulating Plan", + description: + "Hang tight while we synthesize everything! Note that this may take a few minutes.", + }, + { + id: "review-organization", + title: "Review Organization", + description: + "Your organization will let you manage team members and projects.", + }, + { + id: "review-project", + title: "Review Project", + description: "Configure your first project.", + }, + { + id: "all-set", + title: "You're all set!", + description: "Let's get you to your dashboard.", + }, +); diff --git a/apps/seo/src/routes/_authed/onboarding/index.tsx b/apps/seo/src/routes/_authed/onboarding/index.tsx new file mode 100644 index 000000000..c6cf1736b --- /dev/null +++ b/apps/seo/src/routes/_authed/onboarding/index.tsx @@ -0,0 +1,39 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { getApiClient } from "~/lib/api"; +import { getUserOrganizations } from "~/lib/auth/client"; +import { OnboardingContent } from "./-components/content"; +import { OnboardingSteps } from "./-lib/steps"; + +export const Route = createFileRoute("/_authed/onboarding/")({ + loader: async () => { + const organizations = await getUserOrganizations(); + if (organizations.length === 0) { + return { organizations: organizations, existingProjects: [] }; + } + + const existingProjects = await getApiClient().projects.list({ limit: 1 }); + return { + organizations: organizations, + existingProjects: existingProjects.data, + }; + }, + component: OnboardingPage, +}); + +function OnboardingPage() { + const { existingProjects, organizations } = Route.useLoaderData(); + + return ( + + + + ); +} diff --git a/apps/seo/src/routes/_authed/organizations.tsx b/apps/seo/src/routes/_authed/organizations.tsx new file mode 100644 index 000000000..58ee614e3 --- /dev/null +++ b/apps/seo/src/routes/_authed/organizations.tsx @@ -0,0 +1,34 @@ +import { OrganizationSettingCard } from "@rectangular-labs/auth/components/organization/organization-setting-card"; +import { OrganizationSwitcher } from "@rectangular-labs/auth/components/organization/organization-switcher"; +import { + Card, + CardContent, + CardHeader, + CardTitle, +} from "@rectangular-labs/ui/components/ui/card"; +import { Separator } from "@rectangular-labs/ui/components/ui/separator"; +import { createFileRoute } from "@tanstack/react-router"; + +export const Route = createFileRoute("/_authed/organizations")({ + component: OrganizationsPage, +}); + +function OrganizationsPage() { + return ( +
+
+

Organizations

+ window.location.reload()} /> +
+ + + + Create a new organization + + + window.location.reload()} /> + + +
+ ); +} diff --git a/apps/seo/src/routes/_authed/route.tsx b/apps/seo/src/routes/_authed/route.tsx new file mode 100644 index 000000000..2613da8d6 --- /dev/null +++ b/apps/seo/src/routes/_authed/route.tsx @@ -0,0 +1,21 @@ +import { createFileRoute, Outlet, redirect } from "@tanstack/react-router"; +import { getCurrentSession } from "~/lib/auth/client"; + +export const Route = createFileRoute("/_authed")({ + beforeLoad: async ({ location }) => { + const session = await getCurrentSession(); + if (!session) { + throw redirect({ + to: "/login", + search: { + next: `${location.pathname}${location.searchStr}`, + }, + }); + } + }, + component: AuthedLayout, +}); + +function AuthedLayout() { + return ; +} diff --git a/apps/seo/src/routes/_marketing/-components/cta.tsx b/apps/seo/src/routes/_marketing/-components/cta.tsx new file mode 100644 index 000000000..41a796a4a --- /dev/null +++ b/apps/seo/src/routes/_marketing/-components/cta.tsx @@ -0,0 +1,26 @@ +import { Button } from "@rectangular-labs/ui/components/ui/button"; +import { Section } from "@rectangular-labs/ui/components/ui/section"; +import { Link } from "@tanstack/react-router"; + +export function CTA() { + return ( +
+
+

+ Hire the AI SEO employee +

+

+ Understand. Plan. Forecast. Ship. All in one place. +

+
+ + +
+
+
+ ); +} diff --git a/apps/seo/src/routes/_marketing/-components/faq.tsx b/apps/seo/src/routes/_marketing/-components/faq.tsx new file mode 100644 index 000000000..5c204c65c --- /dev/null +++ b/apps/seo/src/routes/_marketing/-components/faq.tsx @@ -0,0 +1,83 @@ +import { PhoneCall } from "@rectangular-labs/ui/components/icon"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@rectangular-labs/ui/components/ui/accordion"; +import { Badge } from "@rectangular-labs/ui/components/ui/badge"; +import { Button } from "@rectangular-labs/ui/components/ui/button"; +import { Section } from "@rectangular-labs/ui/components/ui/section"; + +const faqs = [ + { + id: "site-understanding", + q: "How do you understand my site?", + a: "We crawl your site to map information architecture, parse metadata and internal links, and (optionally) connect Search Console and Analytics for richer context.", + }, + { + id: "content-writing", + q: "Do you write the content?", + a: "We deliver ready‑to‑write briefs with outlines, headings, keywords, and internal links. You can write in‑house or add our writing add‑on.", + }, + { + id: "forecast-accuracy", + q: "How accurate are the forecasts?", + a: "Forecasts are expressed as ranges with confidence bands and are recalibrated weekly as data accrues. They’re designed for planning, not guarantees.", + }, + { + id: "time-to-results", + q: "How long to see results?", + a: "Most teams see early lift within 2–6 weeks depending on domain authority, competition, and publishing cadence.", + }, + { + id: "integrations", + q: "Which integrations and CMSs are supported?", + a: "We support popular CMSs and native connections to Search Console and Analytics, with more integrations added regularly.", + }, + { + id: "google-updates", + q: "What about Google updates?", + a: "We monitor core updates and adjust campaigns and forecasts accordingly to keep you on track.", + }, + { id: "billing", q: "Is there a free trial?", a: "Yes, start free." }, +]; + +export const FAQ = () => ( +
+
+
+
+
+
+ FAQ +
+
+

+ Everything about campaigns, forecasts, and scheduling +

+

+ From site understanding to scheduled content, plan SEO with + clarity and confidence—not guesswork. +

+
+
+ +
+
+
+ + {faqs.map((item) => ( + + {item.q} + {item.a} + + ))} + +
+
+
+); diff --git a/apps/seo/src/routes/_marketing/-components/features.tsx b/apps/seo/src/routes/_marketing/-components/features.tsx new file mode 100644 index 000000000..14b0fe715 --- /dev/null +++ b/apps/seo/src/routes/_marketing/-components/features.tsx @@ -0,0 +1,84 @@ +import { User } from "@rectangular-labs/ui/components/icon"; +import { Badge } from "@rectangular-labs/ui/components/ui/badge"; +import { Section } from "@rectangular-labs/ui/components/ui/section"; + +export const Feature = () => ( +
+
+
+
+ What it does +
+
+

+ End-to-end SEO that ships itself +

+

+ Onboard once. Your AI SEO employee understands your site, plans by + intent, forecasts ranges, and schedules to ship—automatically. +

+
+
+
+
+ +
+

Campaign briefs

+

+ Ready-to-write briefs with outlines, keywords, internal links, and + intent notes. +

+
+
+
+ +
+

Keyword clusters

+

+ Opportunity mapping by user intent and difficulty across the + funnel. +

+
+
+ +
+ +
+

Forecasts

+

+ Time-to-rank and traffic lift ranges with confidence bands. +

+
+
+
+ +
+

Content calendar

+

+ Auto-scheduling with reminders, so campaigns actually ship on + time. +

+
+
+
+ +
+

Reporting

+

+ Campaign-level metrics tied to intent and keyword targets. +

+
+
+
+ +
+

Integrations

+

+ CMS, Search Console, and Analytics to keep everything in sync. +

+
+
+
+
+
+); diff --git a/apps/seo/src/routes/_marketing/-components/footer.tsx b/apps/seo/src/routes/_marketing/-components/footer.tsx new file mode 100644 index 000000000..deadbf951 --- /dev/null +++ b/apps/seo/src/routes/_marketing/-components/footer.tsx @@ -0,0 +1,31 @@ +import { Section } from "@rectangular-labs/ui/components/ui/section"; + +export function Footer() { + const links = [ + { title: "How it works", href: "#how-it-works" }, + { title: "Pricing", href: "#pricing" }, + { title: "FAQ", href: "#faq" }, + { title: "Open Source", href: "https://github.com/rectangular-labs/" }, + ]; + return ( +
+
+
+ {links.map((link) => ( + + {link.title} + + ))} +
+

+ © {new Date().getFullYear()} Rectangular Labs — SEO performance + analytics, keyword monitoring, and AI content suggestions. +

+
+
+ ); +} diff --git a/apps/seo/src/routes/_marketing/-components/forecast.tsx b/apps/seo/src/routes/_marketing/-components/forecast.tsx new file mode 100644 index 000000000..4d3407c13 --- /dev/null +++ b/apps/seo/src/routes/_marketing/-components/forecast.tsx @@ -0,0 +1,54 @@ +import { + ArrowUp, + MoveUpRight, + Terminal, +} from "@rectangular-labs/ui/components/icon"; +import { Badge } from "@rectangular-labs/ui/components/ui/badge"; +import { Section } from "@rectangular-labs/ui/components/ui/section"; + +export function Forecast() { + return ( +
+
+
+
+ Forecasts +
+
+

+ Plan with ranges, not guesses +

+

+ Every campaign ships with time‑to‑rank and traffic‑lift ranges + plus confidence bands. We recalibrate weekly as signals change—so + plans stay realistic. +

+
+
+
+
+ +
2–6 weeks
+
+ Time to first lift +
+
+
+ +
+5–15%
+
+ Projected traffic lift +
+
+
+ +
Weekly
+
+ Forecast recalibration +
+
+
+
+
+ ); +} diff --git a/apps/seo/src/routes/_marketing/-components/header.tsx b/apps/seo/src/routes/_marketing/-components/header.tsx new file mode 100644 index 000000000..35a4d924d --- /dev/null +++ b/apps/seo/src/routes/_marketing/-components/header.tsx @@ -0,0 +1,113 @@ +import { + GitHubIcon, + Logo, + Menu, + X, +} from "@rectangular-labs/ui/components/icon"; +import { ThemeToggle } from "@rectangular-labs/ui/components/theme-provider"; +import { Button } from "@rectangular-labs/ui/components/ui/button"; +import { useIsMobile } from "@rectangular-labs/ui/hooks/use-mobile"; +import { Link } from "@tanstack/react-router"; +import { AnimatePresence, motion } from "motion/react"; +import { useEffect, useState } from "react"; + +const menuItems = [ + { name: "How it works", href: "#how-it-works" }, + { name: "Pricing", href: "#pricing" }, + { name: "FAQ", href: "#faq" }, + { name: "Login", href: "/login" }, +]; +export function Header() { + const isMobile = useIsMobile(); + const [menuState, setMenuState] = useState(false); + useEffect(() => { + setMenuState(!isMobile); + }, [isMobile]); + + return ( +
+ +
+ ); +} diff --git a/apps/seo/src/routes/_marketing/-components/hero.tsx b/apps/seo/src/routes/_marketing/-components/hero.tsx new file mode 100644 index 000000000..794f46ba8 --- /dev/null +++ b/apps/seo/src/routes/_marketing/-components/hero.tsx @@ -0,0 +1,36 @@ +import { MoveRight, PhoneCall } from "@rectangular-labs/ui/components/icon"; +import { Badge } from "@rectangular-labs/ui/components/ui/badge"; +import { Button } from "@rectangular-labs/ui/components/ui/button"; +import { Section } from "@rectangular-labs/ui/components/ui/section"; + +export const Hero = () => ( +
+
+
+
+
+ Private beta +
+
+

+ The AI SEO employee +

+

+ Onboard once. Your SEO is understood, planned by intent, + forecasted with ranges, and scheduled to ship—automatically. +

+
+
+ + +
+
+
+
+
+
+); diff --git a/apps/seo/src/routes/_marketing/-components/logo-cloud.tsx b/apps/seo/src/routes/_marketing/-components/logo-cloud.tsx new file mode 100644 index 000000000..b887f5670 --- /dev/null +++ b/apps/seo/src/routes/_marketing/-components/logo-cloud.tsx @@ -0,0 +1,95 @@ +import { ChevronRight } from "@rectangular-labs/ui/components/icon"; +import { Section } from "@rectangular-labs/ui/components/ui/section"; +import { Link } from "@tanstack/react-router"; + +export function LogoCloud() { + return ( +
+
+
+ + Trusted by teams shipping SEO at scale + + + +
+
+
+ Nvidia Logo +
+ +
+ Column Logo +
+
+ GitHub Logo +
+
+ Nike Logo +
+
+ Lemon Squeezy Logo +
+
+ Laravel Logo +
+
+ Lilly Logo +
+ +
+ OpenAI Logo +
+
+
+
+ ); +} diff --git a/apps/seo/src/routes/_marketing/-components/pricing.tsx b/apps/seo/src/routes/_marketing/-components/pricing.tsx new file mode 100644 index 000000000..99b362301 --- /dev/null +++ b/apps/seo/src/routes/_marketing/-components/pricing.tsx @@ -0,0 +1,306 @@ +"use client"; +import { + ArrowRight, + Check, + Shield, + Sparkles, + Star, + Zap, +} from "@rectangular-labs/ui/components/icon"; +import { Badge } from "@rectangular-labs/ui/components/ui/badge"; +import { Button } from "@rectangular-labs/ui/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@rectangular-labs/ui/components/ui/card"; +import { Section } from "@rectangular-labs/ui/components/ui/section"; +import { + Tabs, + TabsList, + TabsTrigger, +} from "@rectangular-labs/ui/components/ui/tabs"; +import { cn } from "@rectangular-labs/ui/utils/cn"; +import { motion } from "motion/react"; +import { useState } from "react"; + +const plans = [ + { + id: "starter", + name: "Starter", + icon: Star, + price: { + monthly: "Free forever", + yearly: "Free forever", + }, + description: "Hire your first AI SEO employee and get moving fast.", + features: [ + "Site understanding crawl", + "3 campaigns / month", + "Intent scoring & keyword clusters", + "Basic forecasting", + "Calendar scheduling (1 calendar)", + "Email support", + ], + cta: "Get started for free", + }, + { + id: "growth", + name: "Growth", + icon: Zap, + price: { + monthly: 90, + yearly: 75, + }, + description: "End‑to‑end campaigns with forecasts you can plan around.", + features: [ + "Everything in Starter", + "Unlimited campaigns", + "Advanced forecasting & reporting", + "Content calendar & reminders", + "Integrations (CMS, Search Console, Analytics)", + "Priority support", + ], + cta: "Subscribe to Growth", + popular: true, + }, + { + id: "enterprise", + name: "Enterprise", + icon: Shield, + price: { + monthly: "Get in touch for pricing", + yearly: "Get in touch for pricing", + }, + description: "Security, compliance, SLAs, and bespoke workflows.", + features: [ + "Custom onboarding & SSO", + "Role‑based access control", + "SOC2/ISO requests supported", + "Custom data retention", + "Dedicated success manager", + ], + cta: "Contact us", + }, +]; + +export default function Pricing() { + const [frequency, setFrequency] = useState("monthly"); + const currencyFormatter = new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + maximumFractionDigits: 0, + }); + + return ( +
+
+
+ + + Simple, transparent pricing + + + Pick a plan that fits today. Upgrade when you grow. + + + Pricing that scales with your team. No hidden fees. Cancel anytime. + +
+ +
+ + + + Monthly + + + Yearly + + 20% off + + + + +
+ +
+ {plans.map((plan, index) => ( + + + {plan.popular && ( +
+ + + Popular + +
+ )} + +
+
+ +
+ + {plan.name} + +
+ +

{plan.description}

+
+ {typeof plan.price[ + frequency as keyof typeof plan.price + ] === "number" ? ( +
+ + {currencyFormatter.format( + plan.price[ + frequency as keyof typeof plan.price + ] as number, + )} + + + /month, billed {frequency} + +
+ ) : ( + + {plan.price[frequency as keyof typeof plan.price]} + + )} +
+
+
+ + {plan.features.map((feature) => ( + +
+ +
+ + {feature} + +
+ ))} +
+ + + + + {/* Subtle gradient effects */} + {plan.popular ? ( + <> +
+
+ + ) : ( +
+ )} + + + ))} +
+
+
+ ); +} diff --git a/apps/seo/src/routes/_marketing/-components/stats.tsx b/apps/seo/src/routes/_marketing/-components/stats.tsx new file mode 100644 index 000000000..f3eb67b9e --- /dev/null +++ b/apps/seo/src/routes/_marketing/-components/stats.tsx @@ -0,0 +1,88 @@ +import { ArrowRight, MoveUpRight } from "@rectangular-labs/ui/components/icon"; +import { Badge } from "@rectangular-labs/ui/components/ui/badge"; +import { Section } from "@rectangular-labs/ui/components/ui/section"; + +export const Stats = () => ( +
+
+
+
+
+ How it works +
+
+

+ Onboard in minutes + + Campaigns by intent + + + Forecasts you can plan around + + + Ship on schedule + +

+

+ Connect once. We crawl your IA, score opportunities by intent, + propose briefs with keyword clusters, forecast time‑to‑rank and + traffic‑lift ranges, and schedule it all on your calendar. +

+
+
+
+
+
+ +

+ 2-6 + + weeks + +

+

+ Typical time to first lift +

+
+
+ +

+ 24/7 + + coverage + +

+

+ We watch so you don't have to +

+
+
+ +

+ 0 + + spam + +

+

+ Helpful, brand-safe content only +

+
+
+ +

+ 5 + + minutes + +

+

+ From keyword to ready-to-write brief +

+
+
+
+
+
+
+); diff --git a/apps/seo/src/routes/_marketing/blog/$.tsx b/apps/seo/src/routes/_marketing/blog/$.tsx new file mode 100644 index 000000000..dc8889f11 --- /dev/null +++ b/apps/seo/src/routes/_marketing/blog/$.tsx @@ -0,0 +1,30 @@ +import { blogSource } from "@rectangular-labs/content"; +import { getPostsOverview } from "@rectangular-labs/content/get-posts-overview"; +import { BlogPost } from "@rectangular-labs/content/ui/blog-post"; +import { createFileRoute, notFound } from "@tanstack/react-router"; +import { createServerFn } from "@tanstack/react-start"; + +const getBlogData = createServerFn({ method: "GET" }) + .validator((slugs: string[]) => slugs) + .handler(async ({ data: slugs }) => { + const page = blogSource.getPage(slugs); + if (!page) throw notFound(); + return { + tree: blogSource.pageTree as object, + data: page.data, + postsOverview: await getPostsOverview(), + }; + }); + +export const Route = createFileRoute("/_marketing/blog/$")({ + component: Page, + loader: async ({ params }) => { + const data = await getBlogData({ data: params._splat?.split("/") ?? [] }); + return data; + }, +}); + +function Page() { + const { data, postsOverview, tree } = Route.useLoaderData(); + return ; +} diff --git a/apps/seo/src/routes/_marketing/blog/index.tsx b/apps/seo/src/routes/_marketing/blog/index.tsx new file mode 100644 index 000000000..b46743636 --- /dev/null +++ b/apps/seo/src/routes/_marketing/blog/index.tsx @@ -0,0 +1,31 @@ +import { getPostsOverview } from "@rectangular-labs/content/get-posts-overview"; +import { BlogSection } from "@rectangular-labs/content/ui/blog-section"; +import { Section } from "@rectangular-labs/ui/components/ui/section"; +import { createFileRoute } from "@tanstack/react-router"; +import { createServerFn } from "@tanstack/react-start"; + +const getBlogTree = createServerFn({ method: "GET" }).handler(() => + getPostsOverview(), +); + +export const Route = createFileRoute("/_marketing/blog/")({ + component: Page, + loader: async () => { + const postOverview = await getBlogTree(); + return { postOverview }; + }, +}); + +function Page() { + const { postOverview } = Route.useLoaderData(); + + if (postOverview.length === 0) { + return ( +
+

No posts found.

+
+ ); + } + + return ; +} diff --git a/apps/seo/src/routes/_marketing/blog/route.tsx b/apps/seo/src/routes/_marketing/blog/route.tsx new file mode 100644 index 000000000..eefec07fd --- /dev/null +++ b/apps/seo/src/routes/_marketing/blog/route.tsx @@ -0,0 +1,14 @@ +import { ContentProvider } from "@rectangular-labs/content/ui/content-provider"; +import { createFileRoute, Outlet } from "@tanstack/react-router"; + +export const Route = createFileRoute("/_marketing/blog")({ + component: Layout, +}); + +function Layout() { + return ( + + + + ); +} diff --git a/apps/seo/src/routes/_marketing/blog/rss[.]xml.ts b/apps/seo/src/routes/_marketing/blog/rss[.]xml.ts new file mode 100644 index 000000000..ece6d0f50 --- /dev/null +++ b/apps/seo/src/routes/_marketing/blog/rss[.]xml.ts @@ -0,0 +1,15 @@ +import { getBlogRSS } from "@rectangular-labs/content/rss"; +import { createServerFileRoute } from "@tanstack/react-start/server"; +import { serverEnv } from "~/lib/env"; + +function handle() { + const baseUrl = serverEnv().VITE_MENTIONS_URL; + const xml = getBlogRSS(baseUrl); + return new Response(xml); +} + +export const ServerRoute = createServerFileRoute( + "/_marketing/blog/rss.xml", +).methods({ + GET: handle, +}); diff --git a/apps/seo/src/routes/_marketing/index.tsx b/apps/seo/src/routes/_marketing/index.tsx new file mode 100644 index 000000000..596d6e8a9 --- /dev/null +++ b/apps/seo/src/routes/_marketing/index.tsx @@ -0,0 +1,26 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { CTA } from "./-components/cta"; +import { FAQ } from "./-components/faq"; +import { Feature } from "./-components/features"; +import { Forecast } from "./-components/forecast"; +import { Hero } from "./-components/hero"; +import { LogoCloud } from "./-components/logo-cloud"; +import Pricing from "./-components/pricing"; +import { Stats } from "./-components/stats"; + +export const Route = createFileRoute("/_marketing/")({ component: App }); + +function App() { + return ( +
+ + + + + + + + +
+ ); +} diff --git a/apps/seo/src/routes/_marketing/route.tsx b/apps/seo/src/routes/_marketing/route.tsx new file mode 100644 index 000000000..f80e5e298 --- /dev/null +++ b/apps/seo/src/routes/_marketing/route.tsx @@ -0,0 +1,19 @@ +import { createFileRoute, Outlet } from "@tanstack/react-router"; +import { Footer } from "./-components/footer"; +import { Header } from "./-components/header"; + +export const Route = createFileRoute("/_marketing")({ + component: Layout, +}); + +function Layout() { + return ( +
+
+
+ +
+
+
+ ); +} diff --git a/apps/seo/src/routes/api/$.ts b/apps/seo/src/routes/api/$.ts new file mode 100644 index 000000000..eea310ebb --- /dev/null +++ b/apps/seo/src/routes/api/$.ts @@ -0,0 +1,55 @@ +import { createApiContext } from "@rectangular-labs/api-seo/context"; +import { openAPIHandler } from "@rectangular-labs/api-seo/server"; +import { + createBlogSearchServer, + createDocsSearchServer, +} from "@rectangular-labs/content/search"; +import { createServerFileRoute } from "@tanstack/react-start/server"; +import { authServerHandler } from "~/lib/auth/server"; +import { serverEnv } from "~/lib/env"; + +const docsSearch = createDocsSearchServer(); +const blogSearch = createBlogSearchServer(); + +async function handle({ request }: { request: Request }) { + if (new URL(request.url).pathname.startsWith("/api/auth/")) { + return await authServerHandler.handler(request); + } + + if (new URL(request.url).pathname.startsWith("/api/docs/search")) { + if (request.method !== "GET") { + return new Response("Method not allowed", { status: 405 }); + } + return await docsSearch.GET(request); + } + if (new URL(request.url).pathname.startsWith("/api/blog/search")) { + if (request.method !== "GET") { + return new Response("Method not allowed", { status: 405 }); + } + return await blogSearch.GET(request); + } + + const env = serverEnv(); + const context = createApiContext({ + url: new URL(request.url), + reqHeaders: request.headers, + }); + + const { response } = await openAPIHandler( + `${env.VITE_MENTIONS_URL}/api`, + ).handle(request, { + prefix: "/api", + context, + }); + + return response ?? new Response("Not Found", { status: 404 }); +} + +export const ServerRoute = createServerFileRoute("/api/$").methods({ + HEAD: handle, + GET: handle, + POST: handle, + PUT: handle, + PATCH: handle, + DELETE: handle, +}); diff --git a/apps/seo/src/routes/api/rpc.$.ts b/apps/seo/src/routes/api/rpc.$.ts new file mode 100644 index 000000000..c9cfe6672 --- /dev/null +++ b/apps/seo/src/routes/api/rpc.$.ts @@ -0,0 +1,26 @@ +import { createApiContext } from "@rectangular-labs/api-seo/context"; +import { RpcHandler } from "@rectangular-labs/api-seo/server"; +import { createServerFileRoute } from "@tanstack/react-start/server"; + +async function handle({ request }: { request: Request }) { + const context = createApiContext({ + url: new URL(request.url), + reqHeaders: request.headers, + }); + + const { response } = await RpcHandler.handle(request, { + prefix: "/api/rpc", + context, + }); + + return response ?? new Response("Not Found", { status: 404 }); +} + +export const ServerRoute = createServerFileRoute("/api/rpc/$").methods({ + HEAD: handle, + GET: handle, + POST: handle, + PUT: handle, + PATCH: handle, + DELETE: handle, +}); diff --git a/apps/seo/src/routes/login.tsx b/apps/seo/src/routes/login.tsx new file mode 100644 index 000000000..0a4362771 --- /dev/null +++ b/apps/seo/src/routes/login.tsx @@ -0,0 +1,82 @@ +import { AuthCard } from "@rectangular-labs/auth/components/auth/auth-card"; +import { AuthProvider } from "@rectangular-labs/auth/components/auth/auth-provider"; +import { + DiscordIcon, + GitHubIcon, + GoogleIcon, +} from "@rectangular-labs/ui/components/icon"; +import { ThemeToggle } from "@rectangular-labs/ui/components/theme-provider"; +import { createFileRoute, redirect, useNavigate } from "@tanstack/react-router"; +import { type } from "arktype"; +import { authClient, getCurrentSession } from "~/lib/auth/client"; + +export const Route = createFileRoute("/login")({ + validateSearch: type({ + "next?": "string", + }), + loaderDeps: ({ search }) => { + return { + next: search.next, + }; + }, + loader: async ({ deps }) => { + const session = await getCurrentSession(); + if (session && deps.next) { + return redirect({ to: deps.next }); + } + return; + }, + component: Login, +}); + +function Login() { + const search = Route.useSearch(); + const navigate = useNavigate(); + const normalizedSuccessCallbackURL = search.next ?? "/dashboard"; + const newUserCallbackURL = `/onboarding?next=${normalizedSuccessCallbackURL}`; + + return ( +
+ + { + void navigate({ to: normalizedSuccessCallbackURL }); + }, + newUserCallbackURL, + }} + socialProviders={[ + { + provider: "google", + name: "Google", + icon: GoogleIcon, + method: "social", + }, + { + provider: "github", + name: "GitHub", + icon: GitHubIcon, + method: "social", + }, + { + provider: "discord", + name: "Discord", + icon: DiscordIcon, + method: "social", + }, + ]} + > + + +
+ ); +} diff --git a/apps/seo/src/styles.css b/apps/seo/src/styles.css new file mode 100644 index 000000000..aefb0f5e6 --- /dev/null +++ b/apps/seo/src/styles.css @@ -0,0 +1,7 @@ +@import "@rectangular-labs/ui/styles.css"; +@import "@rectangular-labs/content/styles.css"; +@source "./**/*.{ts,tsx}"; + +:root { + --radius: 0.8rem; +} diff --git a/apps/seo/tsconfig.json b/apps/seo/tsconfig.json new file mode 100644 index 000000000..2ca49a86e --- /dev/null +++ b/apps/seo/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "@rectangular-labs/typescript/tsconfig.base.json", + "compilerOptions": { + "jsx": "react-jsx", + "target": "ESNext", + "module": "ESNext", + "lib": ["ES2024", "DOM", "DOM.Iterable"], + "types": ["vite/client", "./worker-configuration.d.ts"], + + /* Linting */ + "noUncheckedSideEffectImports": true, + "baseUrl": ".", + "paths": { + "~/*": ["./src/*"], + "@rectangular-labs/ui/*": ["../../packages/ui/src/*"] + } + }, + "include": ["**/*.ts", "**/*.tsx"], + "exclude": ["node_modules", "dist", ".turbo", ".cache", ".expo", ".next"] +} diff --git a/apps/seo/vite.config.ts b/apps/seo/vite.config.ts new file mode 100644 index 000000000..6be45ea6e --- /dev/null +++ b/apps/seo/vite.config.ts @@ -0,0 +1,38 @@ +import tailwindcss from "@tailwindcss/vite"; +import { tanstackStart } from "@tanstack/react-start/plugin/vite"; +import viteReact from "@vitejs/plugin-react"; +import { createJiti } from "jiti"; +import mkcert from "vite-plugin-mkcert"; +import viteTsConfigPaths from "vite-tsconfig-paths"; +import { defineConfig } from "vitest/config"; +import type { serverEnv } from "~/lib/env"; + +const jiti = createJiti(import.meta.url); +const env = await jiti.import("./src/lib/env"); +// parses all the required env vars (server env is a super set of client env) +(env as { serverEnv: () => ReturnType }).serverEnv(); + +const config = defineConfig({ + plugins: [ + viteTsConfigPaths({ + projects: ["./tsconfig.json"], + }), + tailwindcss(), + mkcert(), + tanstackStart({ + customViteReactPlugin: true, + target: "cloudflare-module", + }), + viteReact(), + ], + test: { + globals: true, + environment: "jsdom", + }, + server: { + proxy: {}, + port: 7070, + }, +}); + +export default config; diff --git a/apps/seo/wrangler.jsonc b/apps/seo/wrangler.jsonc new file mode 100644 index 000000000..2da64815c --- /dev/null +++ b/apps/seo/wrangler.jsonc @@ -0,0 +1,37 @@ +{ + "$schema": "node_modules/wrangler/config-schema.json", + "name": "rectangular-labs-mentions", + "main": ".output/server/index.mjs", + "preview_urls": false, + "compatibility_date": "2025-09-07", + "compatibility_flags": ["nodejs_compat"], + "assets": { + "directory": ".output/public" + }, + "observability": { + "enabled": true + }, + "placement": { + "mode": "smart" + }, + // mostly a workaround to avoid issues with the build and next themes + "keep_names": false, + "env": { + "production": { + "routes": [ + { + "pattern": "mentions.rectangularlabs.com", + "custom_domain": true + } + ] + }, + "{env.PULL_REQUEST}": { + "routes": [ + { + "pattern": "{env.PULL_REQUEST}.mentions.rectangularlabs.com", + "custom_domain": true + } + ] + } + } +} From 15fda09e8d9c2e78d6d86eebb92a380eff090fdc Mon Sep 17 00:00:00 2001 From: ElasticBottle Date: Fri, 19 Sep 2025 05:47:55 +0800 Subject: [PATCH 08/38] chore(task): update task stuff --- apps/tasks/Dockerfile | 65 ------ apps/tasks/package.json | 62 ----- apps/tasks/src/client.ts | 6 - apps/tasks/src/context.ts | 26 --- apps/tasks/src/env.ts | 15 -- apps/tasks/src/routes/index.ts | 6 - apps/tasks/src/routes/tasks.ts | 55 ----- apps/tasks/src/schema/passkey.ts | 108 --------- apps/tasks/src/server.ts | 26 --- apps/tasks/src/subscriber.ts | 10 - apps/tasks/src/types.ts | 23 -- apps/tasks/sst.config.ts | 47 ---- apps/tasks/tsconfig.json | 9 - packages/result/package.json | 2 +- packages/task/package.json | 5 +- packages/task/src/lib/compute-search-index.ts | 219 ++++++++++++++++++ packages/task/src/trigger/site-crawl.ts | 2 +- packages/task/trigger.config.ts | 2 +- packages/task/tsconfig.json | 12 +- 19 files changed, 228 insertions(+), 472 deletions(-) delete mode 100644 apps/tasks/Dockerfile delete mode 100644 apps/tasks/package.json delete mode 100644 apps/tasks/src/client.ts delete mode 100644 apps/tasks/src/context.ts delete mode 100644 apps/tasks/src/env.ts delete mode 100644 apps/tasks/src/routes/index.ts delete mode 100644 apps/tasks/src/routes/tasks.ts delete mode 100644 apps/tasks/src/schema/passkey.ts delete mode 100644 apps/tasks/src/server.ts delete mode 100644 apps/tasks/src/subscriber.ts delete mode 100644 apps/tasks/src/types.ts delete mode 100644 apps/tasks/sst.config.ts delete mode 100644 apps/tasks/tsconfig.json create mode 100644 packages/task/src/lib/compute-search-index.ts diff --git a/apps/tasks/Dockerfile b/apps/tasks/Dockerfile deleted file mode 100644 index 4904f67e6..000000000 --- a/apps/tasks/Dockerfile +++ /dev/null @@ -1,65 +0,0 @@ -# Multi-stage Dockerfile to build and run the crawler actor -# - Uses Apify Playwright image (Chrome 1.55.0) -# - Builds using the monorepo provided as Docker build context -# - Uses pnpm -FROM apify/actor-node-playwright-chrome:22-1.55.0 AS base - -############################### -# Builder - generates the appropriate Dockerfile for the crawler package -############################### -FROM base AS pruner - -# Install pnpm without using npm -g -ENV PNPM_HOME=/home/myuser/.local/share/pnpm -ENV PATH=$PNPM_HOME:$PATH -RUN wget -qO- https://get.pnpm.io/install.sh | SHELL="$(which bash)" bash - - -RUN pnpm i -g turbo@2.5.6 - -WORKDIR /app -USER root -RUN chown -R myuser:myuser /app -USER myuser - -COPY --chown=myuser . . -RUN pnpm turbo prune @rectangular-labs/crawler --docker - -############################### -# Installer - installs the dependencies for the crawler package -############################### -FROM base AS builder - -# Install pnpm without using npm -g -ENV PNPM_HOME=/home/myuser/.local/share/pnpm -ENV PATH=$PNPM_HOME:$PATH -RUN wget -qO- https://get.pnpm.io/install.sh | SHELL="$(which bash)" bash - - -# These are mostly from the `apify create` example to check things -# Check preinstalled packages -RUN npm ls crawlee apify puppeteer playwright -# Check Playwright version is the same as the one from base image. -RUN node check-playwright-version.mjs - -WORKDIR /app -USER root -RUN chown -R myuser:myuser /app -USER myuser -# Avoid re-downloading Playwright browsers on install (already in base image) -ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 - - -# Copy only files needed for dependency resolution first (better layer caching) -COPY --chown=myuser --from=pruner /app/out/json/ . - -# Install workspace dependencies -RUN pnpm i --frozen-lockfile - -# Copy the full output -COPY --chown=myuser --from=pruner /app/out/full/ . - -# Build just the crawler package -RUN pnpm run build - -CMD cd packages/crawler && pnpm run start:prod - - diff --git a/apps/tasks/package.json b/apps/tasks/package.json deleted file mode 100644 index 22342a131..000000000 --- a/apps/tasks/package.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "name": "tasks", - "private": true, - "version": "0.0.1", - "type": "module", - "exports": { - "./client": { - "default": "./src/client.ts" - }, - "./context": { - "default": "./src/context.ts" - }, - "./env": { - "default": "./src/env.ts" - }, - "./schemas/*": { - "default": "./src/schemas/*.ts" - }, - "./server": { - "default": "./src/server.ts" - }, - "./types": { - "default": "./src/types.ts" - } - }, - "scripts": { - "build": "tsc", - "dev": "sst dev", - "clean": "git clean -xdf .turbo node_modules dist .cache", - "dev:items": "tsx watch ./src/_open-api/open-api-resolver.ts", - "format": "pnpx @biomejs/biome format . --write", - "lint": "pnpx @biomejs/biome lint . --write", - "typecheck": "tsc --noEmit --emitDeclarationOnly false" - }, - "devDependencies": { - "@rectangular-labs/typescript": "workspace:*", - "@types/aws-lambda": "8.10.152", - "@types/node": "^24.5.1", - "typescript": "^5.9.2" - }, - "dependencies": { - "@ai-sdk/google": "^2.0.14", - "@aws-sdk/client-sqs": "^3.891.0", - "@orpc/client": "^1.8.9", - "@orpc/contract": "^1.8.9", - "@orpc/server": "^1.8.9", - "@orpc/tanstack-query": "^1.8.9", - "@rectangular-labs/api-core": "workspace:*", - "@rectangular-labs/auth": "workspace:*", - "@rectangular-labs/crawler": "workspace:*", - "@rectangular-labs/db": "workspace:*", - "@rectangular-labs/result": "workspace:*", - "@t3-oss/env-core": "^0.13.8", - "ai": "^5.0.45", - "arktype": "^2.1.22", - "hono": "^4.9.8", - "sst": "3.17.13" - }, - "overrides": { - "@tanstack/react-query": "5.89.0" - } -} diff --git a/apps/tasks/src/client.ts b/apps/tasks/src/client.ts deleted file mode 100644 index 1092fa5f6..000000000 --- a/apps/tasks/src/client.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { createORPCClient } from "@orpc/client"; -import { createRpcLink } from "@rectangular-labs/api-core/lib/links"; -import type { RouterClient } from "./types"; - -export const rpcClient = (baseUrl: string): RouterClient => - createORPCClient(createRpcLink({ baseUrl, path: "/rpc" })); diff --git a/apps/tasks/src/context.ts b/apps/tasks/src/context.ts deleted file mode 100644 index aa4835987..000000000 --- a/apps/tasks/src/context.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { os } from "@orpc/server"; -import { - asyncStorageMiddleware, - getContext as getBaseContext, -} from "@rectangular-labs/api-core/lib/context-storage"; -import { loggerMiddleware } from "@rectangular-labs/api-core/lib/logger"; -import { createDb } from "@rectangular-labs/db"; -import type { InitialContext } from "./types"; - -export const createApiContext = (args: Omit) => { - const db = createDb(); - return { - db, - ...args, - }; -}; -export const getContext = getBaseContext; - -/** - * Base oRPC instance with typed initial context - * Use this instead of the raw `os` import for type-safe dependency injection - */ -export const base = os - .$context() - .use(loggerMiddleware) - .use(asyncStorageMiddleware()); diff --git a/apps/tasks/src/env.ts b/apps/tasks/src/env.ts deleted file mode 100644 index 9dbd942f9..000000000 --- a/apps/tasks/src/env.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { authEnv } from "@rectangular-labs/auth/env"; -import { dbEnv } from "@rectangular-labs/db/env"; -import { createEnv } from "@t3-oss/env-core"; -import { type } from "arktype"; - -export const apiEnv = () => - createEnv({ - extends: [dbEnv(), authEnv()], - server: { - GOOGLE_GENERATIVE_AI_API_KEY: type("string|undefined"), - REDDIT_USER_AGENT: type("string|undefined"), - }, - runtimeEnv: process.env, - emptyStringAsUndefined: true, - }); diff --git a/apps/tasks/src/routes/index.ts b/apps/tasks/src/routes/index.ts deleted file mode 100644 index f17e02013..000000000 --- a/apps/tasks/src/routes/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { lazy } from "@orpc/server"; -import { base } from "../context"; - -export const router = base.router({ - tasks: lazy(() => import("./tasks")), -}); diff --git a/apps/tasks/src/routes/tasks.ts b/apps/tasks/src/routes/tasks.ts deleted file mode 100644 index e173c7135..000000000 --- a/apps/tasks/src/routes/tasks.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { ORPCError } from "@orpc/client"; -import { schema } from "@rectangular-labs/db"; -import { type } from "arktype"; -import { base } from "../context"; - -const create = base - .route({ method: "POST", path: "/" }) - .input(schema.keywordInsertSchema.merge(type({ projectId: "string" }))) - .output(ProjectKeywordSelectSchema) - .handler(async ({ context, input }) => { - const { session } = context.session; - if (!session.activeOrganizationId) { - throw new ORPCError("BAD_REQUEST", { message: "Organization not found" }); - } - - const project = await getProjectById( - input.projectId, - session.activeOrganizationId, - ); - if (!project.ok) { - throw new ORPCError("BAD_REQUEST", { message: project.error.message }); - } - - const keyword = await context.db.transaction(async (tx) => { - const keywordResult = await upsertKeyword(input.phrase, tx); - if (!keywordResult.ok) { - throw new ORPCError("BAD_REQUEST", { - message: keywordResult.error.message, - }); - } - const keyword = keywordResult.value; - - const [projectKeyword] = await tx - .insert(schema.smProjectKeyword) - .values({ - projectId: input.projectId, - keywordId: keyword.id, - isPaused: false, - pollingIntervalSec: 900, - nextRunAt: new Date(), - lastRunAt: null, - }) - .returning(); - if (!projectKeyword) { - throw new ORPCError("INTERNAL_SERVER_ERROR", { - message: "No project keyword created.", - }); - } - return { phrase: keyword.phrase, ...projectKeyword }; - }); - - return keyword; - }); - -export default base.prefix("/task").router({ create }); diff --git a/apps/tasks/src/schema/passkey.ts b/apps/tasks/src/schema/passkey.ts deleted file mode 100644 index de134df1a..000000000 --- a/apps/tasks/src/schema/passkey.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { type } from "arktype"; - -// Common WebAuthn types -const authenticatorAttachmentType = type("'cross-platform' | 'platform'"); -const transportType = type( - "('ble' | 'cable' | 'hybrid' | 'internal' | 'nfc' | 'smart-card' | 'usb')[]", -); -const publicKeyCredentialType = type("'public-key'"); -const userVerificationType = type("('required' | 'preferred' | 'discouraged')"); -const residentKeyType = type("('required' | 'preferred' | 'discouraged')"); -const attestationType = type("('direct' | 'enterprise' | 'indirect' | 'none')"); -const hintsType = type("('hybrid' | 'security-key' | 'client-device')[]"); -const attestationFormatsType = type( - "('fido-u2f' | 'packed' | 'android-safetynet' | 'android-key' | 'tpm' | 'apple' | 'none')[]", -); - -// Common credential structure -const credentialDescriptorType = type({ - id: "string", - type: publicKeyCredentialType, - "transports?": transportType, -}); - -// Common extension types -const extensionsType = type({ - "appid?": "string", - "credProps?": "boolean", - "hmacCreateSecret?": "boolean", - "minPinLength?": "boolean", -}); - -const clientExtensionResultsType = type({ - "appid?": "boolean", - "credProps?": type({ - "rk?": "boolean", - }), - "hmacCreateSecret?": "boolean", -}); - -// Registration related schemas; -export const registrationOptionsSchema = type({ - rp: { - name: "string", - "id?": "string", - }, - user: { - id: "string", - name: "string", - displayName: "string", - }, - challenge: "string", - pubKeyCredParams: type({ - alg: "number", - type: publicKeyCredentialType, - }).array(), - "timeout?": "number", - "excludeCredentials?": credentialDescriptorType.array(), - "authenticatorSelection?": type({ - "authenticatorAttachment?": authenticatorAttachmentType, - "requireResidentKey?": "boolean", - "residentKey?": residentKeyType, - "userVerification?": userVerificationType, - }), - "hints?": hintsType, - "attestation?": attestationType, - "attestationFormats?": attestationFormatsType, - "extensions?": extensionsType, -}); - -export const finishRegistrationInputSchema = type({ - username: "string", - registration: type({ - id: "string", - rawId: "string", - response: type({ - clientDataJSON: "string", - attestationObject: "string", - "authenticatorData?": "string", - "transports?": transportType, - "publicKeyAlgorithm?": "number", - "publicKey?": "string", - }), - "authenticatorAttachment?": authenticatorAttachmentType, - clientExtensionResults: clientExtensionResultsType, - type: publicKeyCredentialType, - }), -}); - -// Login related schemas -export const authenticationOptionsSchema = type({ - challenge: "string", - "timeout?": "number", - "rpId?": "string", - allowCredentials: credentialDescriptorType.array().optional(), -}); - -export const finishLoginInputSchema = type({ - id: "string", - rawId: "string", - response: type({ - clientDataJSON: "string", - authenticatorData: "string", - signature: "string", - "userHandle?": "string", - }), - clientExtensionResults: clientExtensionResultsType, - type: publicKeyCredentialType, -}); diff --git a/apps/tasks/src/server.ts b/apps/tasks/src/server.ts deleted file mode 100644 index 9b4e0cd86..000000000 --- a/apps/tasks/src/server.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { createRpcHandler } from "@rectangular-labs/api-core/lib/handlers"; -import { Hono } from "hono"; -import { handle } from "hono/aws-lambda"; -import { createApiContext } from "./context"; -import { router } from "./routes"; - -const rpcHandler = createRpcHandler(router); - -const app = new Hono(); -app.use("/*", async (c, next) => { - const { matched, response } = await rpcHandler.handle(c.req.raw, { - prefix: "/rpc", - context: createApiContext({ - url: new URL(c.req.raw.url), - reqHeaders: c.req.raw.headers, - }), - }); - - if (matched) { - return c.newResponse(response.body, response); - } - - return await next(); -}); - -export const handler = handle(app); diff --git a/apps/tasks/src/subscriber.ts b/apps/tasks/src/subscriber.ts deleted file mode 100644 index dea57316f..000000000 --- a/apps/tasks/src/subscriber.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { SQSEvent } from "aws-lambda"; - -export const handler = async (event: SQSEvent) => { - console.log(JSON.stringify(event, null, 2)); - for (const record of event.Records) { - console.log(); - } - await new Promise((resolve) => setTimeout(resolve, 1000)); - return "ok"; -}; diff --git a/apps/tasks/src/types.ts b/apps/tasks/src/types.ts deleted file mode 100644 index c920ddb3a..000000000 --- a/apps/tasks/src/types.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { - InferRouterInputs, - InferRouterOutputs, - RouterClient as ORPCRouterClient, - UnlaziedRouter, -} from "@orpc/server"; -import type { BaseContext } from "@rectangular-labs/api-core/lib/types"; -import type { DB } from "@rectangular-labs/db"; -import type { router } from "./routes"; - -export type Router = UnlaziedRouter; -export type RouterClient = ORPCRouterClient; -export type RouterInputs = InferRouterInputs; -export type RouterOutputs = InferRouterOutputs; - -/** - * Initial context type definition for oRPC procedures - * This defines the required dependencies that must be passed when calling procedures - */ -export interface InitialContext extends BaseContext { - db: DB; - url: URL; -} diff --git a/apps/tasks/sst.config.ts b/apps/tasks/sst.config.ts deleted file mode 100644 index 1e37b838e..000000000 --- a/apps/tasks/sst.config.ts +++ /dev/null @@ -1,47 +0,0 @@ -/// - -export default $config({ - app(input) { - return { - name: "rl-tasks", - removal: input?.stage === "production" ? "remove" : "remove", - protect: ["production"].includes(input?.stage), - home: "aws", - providers: { - aws: { - profile: - input.stage === "production" - ? "rectangular-production" - : "rectangular-dev", - }, - }, - }; - }, - async run() { - const vpc = new sst.aws.Vpc("TasksVpc"); - const cluster = new sst.aws.Cluster("TasksCluster", { vpc }); - const task = new sst.aws.Task("SiteCrawler", { - cluster, - image: { - context: "../../", - dockerfile: "./Dockerfile", - }, - }); - - const queue = new sst.aws.Queue("TasksQueue"); - queue.subscribe({ - handler: "./src/subscriber.handler", - link: [task], - }); - - const app = new sst.aws.Function("TasksAPI", { - handler: "./src/server.handler", - link: [queue], - url: true, - }); - - return await Promise.resolve({ - enqueueUrl: app.url, - }); - }, -}); diff --git a/apps/tasks/tsconfig.json b/apps/tasks/tsconfig.json deleted file mode 100644 index e0db0b5e4..000000000 --- a/apps/tasks/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "@rectangular-labs/typescript/tsconfig.internal-package.json", - "compilerOptions": { - "rootDir": "src", - "lib": ["esnext"] - }, - "include": ["src"], - "exclude": ["node_modules"] -} diff --git a/packages/result/package.json b/packages/result/package.json index d014281bf..57394e422 100644 --- a/packages/result/package.json +++ b/packages/result/package.json @@ -35,7 +35,7 @@ ], "scripts": { "build": "tsup", - "dev": "tsup --watch", + "dev": "tsup", "clean": "git clean -xdf .turbo node_modules dist .cache", "format": "bun x @biomejs/biome format . --write", "lint": "bun x @biomejs/biome lint . --write", diff --git a/packages/task/package.json b/packages/task/package.json index f5e3db883..6ae00ede8 100644 --- a/packages/task/package.json +++ b/packages/task/package.json @@ -19,8 +19,8 @@ }, "scripts": { "build": "tsc", - "dev:trigger": "pnpm dotenvx run -f ../../.env.local -f ../../.env -- pnpx trigger.dev@latest dev", - "clean": "git clean -xdf .turbo node_modules dist .cache storage", + "dev:trigger": "pnpm dotenvx run -f ../../.env.local -f ../../.env -- pnpx trigger.dev@4.0.2 dev", + "clean": "git clean -xdf .turbo node_modules dist .cache .trigger storage", "format": "bun x @biomejs/biome format . --write", "lint": "bun x @biomejs/biome lint . --write", "typecheck": "tsc --noEmit --emitDeclarationOnly false" @@ -41,6 +41,7 @@ "defuddle": "^0.6.6", "glob": "^11.0.3", "gpt-tokenizer": "^3.0.1", + "jsdom": "^26.1.0", "playwright": "1.55.0", "unstorage": "^1.17.1" } diff --git a/packages/task/src/lib/compute-search-index.ts b/packages/task/src/lib/compute-search-index.ts new file mode 100644 index 000000000..cba3e86a6 --- /dev/null +++ b/packages/task/src/lib/compute-search-index.ts @@ -0,0 +1,219 @@ +import { createRequire } from "node:module"; +import type { AnySchema } from "@orama/orama"; +import { create, insertMultiple } from "@orama/orama"; +import { Dataset, type PlaywrightCrawler } from "crawlee"; +import { createStorage } from "unstorage"; + +type OramaPrimitive = "string" | "number" | "boolean"; +type OramaArray = "string[]" | "number[]" | "boolean[]"; +type OramaVector = `vector[${number}]`; + +type ArkTypeLike = { + toJsonSchema: (options?: unknown) => unknown; +}; + +interface JsonSchemaObject { + type?: string | string[]; + properties?: Record; + items?: JsonSchemaObject; + enum?: unknown[]; +} + +interface S3Config { + accessKeyId: string; + secretAccessKey: string; + endpoint: string; + bucket: string; + region: string; +} + +interface ComputeSearchIndexOptions { + vectorProperties?: Record; + keyPrefix?: string; + s3?: Partial; +} + +function resolveS3Config(input?: Partial): S3Config { + const resolved: S3Config = { + accessKeyId: input?.accessKeyId ?? process.env.R2_ACCESS_KEY_ID ?? "", + secretAccessKey: + input?.secretAccessKey ?? process.env.R2_SECRET_ACCESS_KEY ?? "", + endpoint: input?.endpoint ?? process.env.R2_ENDPOINT ?? "", + bucket: input?.bucket ?? process.env.R2_BUCKET ?? "", + region: input?.region ?? process.env.R2_REGION ?? "auto", + }; + return resolved; +} + +function isArkTypeLike(value: unknown): value is ArkTypeLike { + return ( + typeof value === "function" || + (typeof value === "object" && value !== null && "toJsonSchema" in value) + ); +} + +function toJsonSchema(input: ArkTypeLike | JsonSchemaObject): JsonSchemaObject { + if (isArkTypeLike(input)) { + const out = input.toJsonSchema?.(); + return (out as JsonSchemaObject) ?? { type: "object", properties: {} }; + } + return input; +} + +function mapJsonSchemaToOramaSchema( + schema: JsonSchemaObject, + vectorProps?: Record, +): AnySchema { + if (!schema || schema.type !== "object" || !schema.properties) { + return {}; + } + + const result: AnySchema = {} as AnySchema; + for (const [key, propSchema] of Object.entries(schema.properties)) { + if ( + vectorProps && + Number.isFinite((vectorProps as Record)[key]) + ) { + const candidate = (vectorProps as Record)[key]; + const size = + typeof candidate === "number" && Number.isFinite(candidate) + ? candidate + : 0; + result[key] = `vector[${size}]` as unknown as AnySchema; + continue; + } + + const mapped = mapJsonSchemaProperty(propSchema, vectorProps); + if (mapped) { + result[key] = mapped as AnySchema; + } + } + return result; +} + +function mapJsonSchemaProperty( + prop: JsonSchemaObject, + vectorProps?: Record, +): OramaPrimitive | OramaArray | OramaVector | AnySchema | undefined { + const t = Array.isArray(prop.type) ? prop.type[0] : prop.type; + if (t === "string") return "string"; + if (t === "number" || t === "integer") return "number"; + if (t === "boolean") return "boolean"; + if (t === "array") { + const itemType = prop.items?.type; + const it = Array.isArray(itemType) ? itemType?.[0] : itemType; + if (it === "string") return "string[]"; + if (it === "number" || it === "integer") return "number[]"; + if (it === "boolean") return "boolean[]"; + // Fallback for unsupported arrays + return "string[]"; + } + if (t === "object" && prop.properties) { + return mapJsonSchemaToOramaSchema(prop, vectorProps) as AnySchema; + } + // Fallback for enums or unknowns + if (prop.enum) return "string"; + return undefined; +} + +export async function computeSearchIndex( + arkOrJsonSchema: ArkTypeLike | JsonSchemaObject, + _crawler: PlaywrightCrawler, + options?: ComputeSearchIndexOptions, +) { + const jsonSchema = toJsonSchema(arkOrJsonSchema); + const oramaSchema: AnySchema = mapJsonSchemaToOramaSchema( + jsonSchema, + options?.vectorProperties, + ) as unknown as AnySchema; + + const db = create({ schema: oramaSchema }); + + const dataset = await Dataset.open(); + const limit = 1000; + let offset = 0; + let chunkIndex = 0; + let totalInserted = 0; + + const s3 = resolveS3Config(options?.s3); + const shouldPersist = Boolean( + s3.accessKeyId && s3.secretAccessKey && s3.endpoint && s3.bucket, + ); + let storage: ReturnType | null = null; + if (shouldPersist) { + // Use require to load CJS default export shape for s3 driver + const require = createRequire(import.meta.url); + const s3Driver = require("unstorage/drivers/s3"); + const driver = ( + "default" in s3Driver ? s3Driver.default : s3Driver + ) as (opts: { + accessKeyId: string; + secretAccessKey: string; + endpoint: string; + bucket: string; + region: string; + }) => unknown; + const drv = driver({ + accessKeyId: s3.accessKeyId, + secretAccessKey: s3.secretAccessKey, + endpoint: s3.endpoint, + bucket: s3.bucket, + region: s3.region, + }); + storage = createStorage({ driver: drv as never }); + } + + const prefix = options?.keyPrefix ?? "search-index"; + + if (storage) { + await storage.setItem( + `${prefix}/orama-schema.json`, + JSON.stringify(oramaSchema), + ); + await storage.setItem( + `${prefix}/source-schema.json`, + JSON.stringify(jsonSchema), + ); + } + + for (;;) { + const { items, count } = await dataset.getData({ + offset, + limit, + clean: true, + }); + if (!items || items.length === 0) break; + + type InsertParams = Parameters; + const docs = items as unknown as InsertParams[1]; + const target = db as InsertParams[0]; + await insertMultiple(target, docs); + + if (storage) { + const key = `${prefix}/chunks/chunk-${String(chunkIndex).padStart(6, "0")}.json`; + await storage.setItem(key, JSON.stringify(items)); + } + + totalInserted += items.length; + offset += count; + chunkIndex += 1; + if (count < limit) break; + } + + if (storage) { + const manifest = { + totalInserted, + chunkCount: chunkIndex, + createdAt: new Date().toISOString(), + keyPrefix: prefix, + }; + await storage.setItem(`${prefix}/manifest.json`, JSON.stringify(manifest)); + } + + return { + totalInserted, + chunkCount: chunkIndex, + schema: oramaSchema, + keyPrefix: prefix, + }; +} diff --git a/packages/task/src/trigger/site-crawl.ts b/packages/task/src/trigger/site-crawl.ts index a6ef660ca..455213ed1 100644 --- a/packages/task/src/trigger/site-crawl.ts +++ b/packages/task/src/trigger/site-crawl.ts @@ -2,7 +2,7 @@ import { task } from "@trigger.dev/sdk/v3"; import { crawlSite } from "../crawlers/site.js"; import type { SiteCrawlInputSchema } from "../schema/site.js"; -export const helloWorldTask = task({ +export const siteCrawlTask = task({ id: "site-crawl", maxDuration: 300, run: async (payload: typeof SiteCrawlInputSchema.infer) => { diff --git a/packages/task/trigger.config.ts b/packages/task/trigger.config.ts index 3abc054cd..bbd2f88ea 100644 --- a/packages/task/trigger.config.ts +++ b/packages/task/trigger.config.ts @@ -17,7 +17,7 @@ export default defineConfig({ }, }, build: { - external: ["crawlee", "jsdom", "defuddle"], + external: ["crawlee", "jsdom"], extensions: [playwright()], }, dirs: ["src/trigger"], diff --git a/packages/task/tsconfig.json b/packages/task/tsconfig.json index 6056a273e..7f8dafdce 100644 --- a/packages/task/tsconfig.json +++ b/packages/task/tsconfig.json @@ -1,18 +1,12 @@ { "extends": "@rectangular-labs/typescript/tsconfig.internal-package.json", "compilerOptions": { - "rootDir": "src", + "rootDir": ".", "baseUrl": ".", "paths": { "@rectangular-labs/crawler/*": ["./src/*"] - }, - "module": "NodeNext", - "moduleResolution": "nodenext", - "emitDeclarationOnly": false, - "sourceMap": true, - "experimentalDecorators": true, - "emitDecoratorMetadata": true + } }, - "include": ["src"], + "include": ["src", "trigger.config.ts"], "exclude": ["node_modules"] } From a182c8a2c62f9b327933b9c72755a144bf721136 Mon Sep 17 00:00:00 2001 From: ElasticBottle Date: Fri, 19 Sep 2025 05:50:54 +0800 Subject: [PATCH 09/38] chore: misc stuff --- .cursor/rules/trigger.advanced-tasks.mdc | 456 +++++ .cursor/rules/trigger.config.mdc | 351 ++++ .dockerignore | 1 + .github/workflows/cloudflare.yml | 30 +- .gitignore | 5 +- .vscode/settings.json | 1 + pnpm-lock.yaml | 2139 ++++++++++------------ 7 files changed, 1841 insertions(+), 1142 deletions(-) create mode 100644 .cursor/rules/trigger.advanced-tasks.mdc create mode 100644 .cursor/rules/trigger.config.mdc diff --git a/.cursor/rules/trigger.advanced-tasks.mdc b/.cursor/rules/trigger.advanced-tasks.mdc new file mode 100644 index 000000000..78551c1ba --- /dev/null +++ b/.cursor/rules/trigger.advanced-tasks.mdc @@ -0,0 +1,456 @@ +--- +description: Comprehensive rules to help you write advanced Trigger.dev tasks +globs: **/trigger/**/*.ts +alwaysApply: false +--- +# Trigger.dev Advanced Tasks (v4) + +**Advanced patterns and features for writing tasks** + +## Tags & Organization + +```ts +import { task, tags } from "@trigger.dev/sdk"; + +export const processUser = task({ + id: "process-user", + run: async (payload: { userId: string; orgId: string }, { ctx }) => { + // Add tags during execution + await tags.add(`user_${payload.userId}`); + await tags.add(`org_${payload.orgId}`); + + return { processed: true }; + }, +}); + +// Trigger with tags +await processUser.trigger( + { userId: "123", orgId: "abc" }, + { tags: ["priority", "user_123", "org_abc"] } // Max 10 tags per run +); + +// Subscribe to tagged runs +for await (const run of runs.subscribeToRunsWithTag("user_123")) { + console.log(`User task ${run.id}: ${run.status}`); +} +``` + +**Tag Best Practices:** + +- Use prefixes: `user_123`, `org_abc`, `video:456` +- Max 10 tags per run, 1-64 characters each +- Tags don't propagate to child tasks automatically + +## Concurrency & Queues + +```ts +import { task, queue } from "@trigger.dev/sdk"; + +// Shared queue for related tasks +const emailQueue = queue({ + name: "email-processing", + concurrencyLimit: 5, // Max 5 emails processing simultaneously +}); + +// Task-level concurrency +export const oneAtATime = task({ + id: "sequential-task", + queue: { concurrencyLimit: 1 }, // Process one at a time + run: async (payload) => { + // Critical section - only one instance runs + }, +}); + +// Per-user concurrency +export const processUserData = task({ + id: "process-user-data", + run: async (payload: { userId: string }) => { + // Override queue with user-specific concurrency + await childTask.trigger(payload, { + queue: { + name: `user-${payload.userId}`, + concurrencyLimit: 2, + }, + }); + }, +}); + +export const emailTask = task({ + id: "send-email", + queue: emailQueue, // Use shared queue + run: async (payload: { to: string }) => { + // Send email logic + }, +}); +``` + +## Error Handling & Retries + +```ts +import { task, retry, AbortTaskRunError } from "@trigger.dev/sdk"; + +export const resilientTask = task({ + id: "resilient-task", + retry: { + maxAttempts: 10, + factor: 1.8, // Exponential backoff multiplier + minTimeoutInMs: 500, + maxTimeoutInMs: 30_000, + randomize: false, + }, + catchError: async ({ error, ctx }) => { + // Custom error handling + if (error.code === "FATAL_ERROR") { + throw new AbortTaskRunError("Cannot retry this error"); + } + + // Log error details + console.error(`Task ${ctx.task.id} failed:`, error); + + // Allow retry by returning nothing + return { retryAt: new Date(Date.now() + 60000) }; // Retry in 1 minute + }, + run: async (payload) => { + // Retry specific operations + const result = await retry.onThrow( + async () => { + return await unstableApiCall(payload); + }, + { maxAttempts: 3 } + ); + + // Conditional HTTP retries + const response = await retry.fetch("https://api.example.com", { + retry: { + maxAttempts: 5, + condition: (response, error) => { + return response?.status === 429 || response?.status >= 500; + }, + }, + }); + + return result; + }, +}); +``` + +## Machines & Performance + +```ts +export const heavyTask = task({ + id: "heavy-computation", + machine: { preset: "large-2x" }, // 8 vCPU, 16 GB RAM + maxDuration: 1800, // 30 minutes timeout + run: async (payload, { ctx }) => { + // Resource-intensive computation + if (ctx.machine.preset === "large-2x") { + // Use all available cores + return await parallelProcessing(payload); + } + + return await standardProcessing(payload); + }, +}); + +// Override machine when triggering +await heavyTask.trigger(payload, { + machine: { preset: "medium-1x" }, // Override for this run +}); +``` + +**Machine Presets:** + +- `micro`: 0.25 vCPU, 0.25 GB RAM +- `small-1x`: 0.5 vCPU, 0.5 GB RAM (default) +- `small-2x`: 1 vCPU, 1 GB RAM +- `medium-1x`: 1 vCPU, 2 GB RAM +- `medium-2x`: 2 vCPU, 4 GB RAM +- `large-1x`: 4 vCPU, 8 GB RAM +- `large-2x`: 8 vCPU, 16 GB RAM + +## Idempotency + +```ts +import { task, idempotencyKeys } from "@trigger.dev/sdk"; + +export const paymentTask = task({ + id: "process-payment", + retry: { + maxAttempts: 3, + }, + run: async (payload: { orderId: string; amount: number }) => { + // Automatically scoped to this task run, so if the task is retried, the idempotency key will be the same + const idempotencyKey = await idempotencyKeys.create(`payment-${payload.orderId}`); + + // Ensure payment is processed only once + await chargeCustomer.trigger(payload, { + idempotencyKey, + idempotencyKeyTTL: "24h", // Key expires in 24 hours + }); + }, +}); + +// Payload-based idempotency +import { createHash } from "node:crypto"; + +function createPayloadHash(payload: any): string { + const hash = createHash("sha256"); + hash.update(JSON.stringify(payload)); + return hash.digest("hex"); +} + +export const deduplicatedTask = task({ + id: "deduplicated-task", + run: async (payload) => { + const payloadHash = createPayloadHash(payload); + const idempotencyKey = await idempotencyKeys.create(payloadHash); + + await processData.trigger(payload, { idempotencyKey }); + }, +}); +``` + +## Metadata & Progress Tracking + +```ts +import { task, metadata } from "@trigger.dev/sdk"; + +export const batchProcessor = task({ + id: "batch-processor", + run: async (payload: { items: any[] }, { ctx }) => { + const totalItems = payload.items.length; + + // Initialize progress metadata + metadata + .set("progress", 0) + .set("totalItems", totalItems) + .set("processedItems", 0) + .set("status", "starting"); + + const results = []; + + for (let i = 0; i < payload.items.length; i++) { + const item = payload.items[i]; + + // Process item + const result = await processItem(item); + results.push(result); + + // Update progress + const progress = ((i + 1) / totalItems) * 100; + metadata + .set("progress", progress) + .increment("processedItems", 1) + .append("logs", `Processed item ${i + 1}/${totalItems}`) + .set("currentItem", item.id); + } + + // Final status + metadata.set("status", "completed"); + + return { results, totalProcessed: results.length }; + }, +}); + +// Update parent metadata from child task +export const childTask = task({ + id: "child-task", + run: async (payload, { ctx }) => { + // Update parent task metadata + metadata.parent.set("childStatus", "processing"); + metadata.root.increment("childrenCompleted", 1); + + return { processed: true }; + }, +}); +``` + +## Advanced Triggering + +### Frontend Triggering (React) + +```tsx +"use client"; +import { useTaskTrigger } from "@trigger.dev/react-hooks"; +import type { myTask } from "../trigger/tasks"; + +function TriggerButton({ accessToken }: { accessToken: string }) { + const { submit, handle, isLoading } = useTaskTrigger("my-task", { accessToken }); + + return ( + + ); +} +``` + +### Large Payloads + +```ts +// For payloads > 512KB (max 10MB) +export const largeDataTask = task({ + id: "large-data-task", + run: async (payload: { dataUrl: string }) => { + // Trigger.dev automatically handles large payloads + // For > 10MB, use external storage + const response = await fetch(payload.dataUrl); + const largeData = await response.json(); + + return { processed: largeData.length }; + }, +}); + +// Best practice: Use presigned URLs for very large files +await largeDataTask.trigger({ + dataUrl: "https://s3.amazonaws.com/bucket/large-file.json?presigned=true", +}); +``` + +### Advanced Options + +```ts +await myTask.trigger(payload, { + delay: "2h30m", // Delay execution + ttl: "24h", // Expire if not started within 24 hours + priority: 100, // Higher priority (time offset in seconds) + tags: ["urgent", "user_123"], + metadata: { source: "api", version: "v2" }, + queue: { + name: "priority-queue", + concurrencyLimit: 10, + }, + idempotencyKey: "unique-operation-id", + idempotencyKeyTTL: "1h", + machine: { preset: "large-1x" }, + maxAttempts: 5, +}); +``` + +## Hidden Tasks + +```ts +// Hidden task - not exported, only used internally +const internalProcessor = task({ + id: "internal-processor", + run: async (payload: { data: string }) => { + return { processed: payload.data.toUpperCase() }; + }, +}); + +// Public task that uses hidden task +export const publicWorkflow = task({ + id: "public-workflow", + run: async (payload: { input: string }) => { + // Use hidden task internally + const result = await internalProcessor.triggerAndWait({ + data: payload.input, + }); + + if (result.ok) { + return { output: result.output.processed }; + } + + throw new Error("Internal processing failed"); + }, +}); +``` + +## Logging & Tracing + +```ts +import { task, logger } from "@trigger.dev/sdk"; + +export const tracedTask = task({ + id: "traced-task", + run: async (payload, { ctx }) => { + logger.info("Task started", { userId: payload.userId }); + + // Custom trace with attributes + const user = await logger.trace( + "fetch-user", + async (span) => { + span.setAttribute("user.id", payload.userId); + span.setAttribute("operation", "database-fetch"); + + const userData = await database.findUser(payload.userId); + span.setAttribute("user.found", !!userData); + + return userData; + }, + { userId: payload.userId } + ); + + logger.debug("User fetched", { user: user.id }); + + try { + const result = await processUser(user); + logger.info("Processing completed", { result }); + return result; + } catch (error) { + logger.error("Processing failed", { + error: error.message, + userId: payload.userId, + }); + throw error; + } + }, +}); +``` + +## Usage Monitoring + +```ts +import { task, usage } from "@trigger.dev/sdk"; + +export const monitoredTask = task({ + id: "monitored-task", + run: async (payload) => { + // Get current run cost + const currentUsage = await usage.getCurrent(); + logger.info("Current cost", { + costInCents: currentUsage.costInCents, + durationMs: currentUsage.durationMs, + }); + + // Measure specific operation + const { result, compute } = await usage.measure(async () => { + return await expensiveOperation(payload); + }); + + logger.info("Operation cost", { + costInCents: compute.costInCents, + durationMs: compute.durationMs, + }); + + return result; + }, +}); +``` + +## Run Management + +```ts +// Cancel runs +await runs.cancel("run_123"); + +// Replay runs with same payload +await runs.replay("run_123"); + +// Retrieve run with cost details +const run = await runs.retrieve("run_123"); +console.log(`Cost: ${run.costInCents} cents, Duration: ${run.durationMs}ms`); +``` + +## Best Practices + +- **Concurrency**: Use queues to prevent overwhelming external services +- **Retries**: Configure exponential backoff for transient failures +- **Idempotency**: Always use for payment/critical operations +- **Metadata**: Track progress for long-running tasks +- **Machines**: Match machine size to computational requirements +- **Tags**: Use consistent naming patterns for filtering +- **Large Payloads**: Use external storage for files > 10MB +- **Error Handling**: Distinguish between retryable and fatal errors + +Design tasks to be stateless, idempotent, and resilient to failures. Use metadata for state tracking and queues for resource management. diff --git a/.cursor/rules/trigger.config.mdc b/.cursor/rules/trigger.config.mdc new file mode 100644 index 000000000..54e400d73 --- /dev/null +++ b/.cursor/rules/trigger.config.mdc @@ -0,0 +1,351 @@ +--- +description: Configure your Trigger.dev project with a trigger.config.ts file +globs: **/trigger.config.ts +alwaysApply: false +--- +# Trigger.dev Configuration (v4) + +**Complete guide to configuring `trigger.config.ts` with build extensions** + +## Basic Configuration + +```ts +import { defineConfig } from "@trigger.dev/sdk"; + +export default defineConfig({ + project: "", // Required: Your project reference + dirs: ["./trigger"], // Task directories + runtime: "node", // "node", "node-22", or "bun" + logLevel: "info", // "debug", "info", "warn", "error" + + // Default retry settings + retries: { + enabledInDev: false, + default: { + maxAttempts: 3, + minTimeoutInMs: 1000, + maxTimeoutInMs: 10000, + factor: 2, + randomize: true, + }, + }, + + // Build configuration + build: { + autoDetectExternal: true, + keepNames: true, + minify: false, + extensions: [], // Build extensions go here + }, + + // Global lifecycle hooks + onStart: async ({ payload, ctx }) => { + console.log("Global task start"); + }, + onSuccess: async ({ payload, output, ctx }) => { + console.log("Global task success"); + }, + onFailure: async ({ payload, error, ctx }) => { + console.log("Global task failure"); + }, +}); +``` + +## Build Extensions + +### Database & ORM + +#### Prisma + +```ts +import { prismaExtension } from "@trigger.dev/build/extensions/prisma"; + +extensions: [ + prismaExtension({ + schema: "prisma/schema.prisma", + version: "5.19.0", // Optional: specify version + migrate: true, // Run migrations during build + directUrlEnvVarName: "DIRECT_DATABASE_URL", + typedSql: true, // Enable TypedSQL support + }), +]; +``` + +#### TypeScript Decorators (for TypeORM) + +```ts +import { emitDecoratorMetadata } from "@trigger.dev/build/extensions/typescript"; + +extensions: [ + emitDecoratorMetadata(), // Enables decorator metadata +]; +``` + +### Scripting Languages + +#### Python + +```ts +import { pythonExtension } from "@trigger.dev/build/extensions/python"; + +extensions: [ + pythonExtension({ + scripts: ["./python/**/*.py"], // Copy Python files + requirementsFile: "./requirements.txt", // Install packages + devPythonBinaryPath: ".venv/bin/python", // Dev mode binary + }), +]; + +// Usage in tasks +const result = await python.runInline(`print("Hello, world!")`); +const output = await python.runScript("./python/script.py", ["arg1"]); +``` + +### Browser Automation + +#### Playwright + +```ts +import { playwright } from "@trigger.dev/build/extensions/playwright"; + +extensions: [ + playwright({ + browsers: ["chromium", "firefox", "webkit"], // Default: ["chromium"] + headless: true, // Default: true + }), +]; +``` + +#### Puppeteer + +```ts +import { puppeteer } from "@trigger.dev/build/extensions/puppeteer"; + +extensions: [puppeteer()]; + +// Environment variable needed: +// PUPPETEER_EXECUTABLE_PATH: "/usr/bin/google-chrome-stable" +``` + +#### Lightpanda + +```ts +import { lightpanda } from "@trigger.dev/build/extensions/lightpanda"; + +extensions: [ + lightpanda({ + version: "latest", // or "nightly" + disableTelemetry: false, + }), +]; +``` + +### Media Processing + +#### FFmpeg + +```ts +import { ffmpeg } from "@trigger.dev/build/extensions/core"; + +extensions: [ + ffmpeg({ version: "7" }), // Static build, or omit for Debian version +]; + +// Automatically sets FFMPEG_PATH and FFPROBE_PATH +// Add fluent-ffmpeg to external packages if using +``` + +#### Audio Waveform + +```ts +import { audioWaveform } from "@trigger.dev/build/extensions/audioWaveform"; + +extensions: [ + audioWaveform(), // Installs Audio Waveform 1.1.0 +]; +``` + +### System & Package Management + +#### System Packages (apt-get) + +```ts +import { aptGet } from "@trigger.dev/build/extensions/core"; + +extensions: [ + aptGet({ + packages: ["ffmpeg", "imagemagick", "curl=7.68.0-1"], // Can specify versions + }), +]; +``` + +#### Additional NPM Packages + +Only use this for installing CLI tools, NOT packages you import in your code. + +```ts +import { additionalPackages } from "@trigger.dev/build/extensions/core"; + +extensions: [ + additionalPackages({ + packages: ["wrangler"], // CLI tools and specific versions + }), +]; +``` + +#### Additional Files + +```ts +import { additionalFiles } from "@trigger.dev/build/extensions/core"; + +extensions: [ + additionalFiles({ + files: ["wrangler.toml", "./assets/**", "./fonts/**"], // Glob patterns supported + }), +]; +``` + +### Environment & Build Tools + +#### Environment Variable Sync + +```ts +import { syncEnvVars } from "@trigger.dev/build/extensions/core"; + +extensions: [ + syncEnvVars(async (ctx) => { + // ctx contains: environment, projectRef, env + return [ + { name: "SECRET_KEY", value: await getSecret(ctx.environment) }, + { name: "API_URL", value: ctx.environment === "prod" ? "api.prod.com" : "api.dev.com" }, + ]; + }), +]; +``` + +#### ESBuild Plugins + +```ts +import { esbuildPlugin } from "@trigger.dev/build/extensions"; +import { sentryEsbuildPlugin } from "@sentry/esbuild-plugin"; + +extensions: [ + esbuildPlugin( + sentryEsbuildPlugin({ + org: process.env.SENTRY_ORG, + project: process.env.SENTRY_PROJECT, + authToken: process.env.SENTRY_AUTH_TOKEN, + }), + { placement: "last", target: "deploy" } // Optional config + ), +]; +``` + +## Custom Build Extensions + +```ts +import { defineConfig } from "@trigger.dev/sdk"; + +const customExtension = { + name: "my-custom-extension", + + externalsForTarget: (target) => { + return ["some-native-module"]; // Add external dependencies + }, + + onBuildStart: async (context) => { + console.log(`Build starting for ${context.target}`); + // Register esbuild plugins, modify build context + }, + + onBuildComplete: async (context, manifest) => { + console.log("Build complete, adding layers"); + // Add build layers, modify deployment + context.addLayer({ + id: "my-layer", + files: [{ source: "./custom-file", destination: "/app/custom" }], + commands: ["chmod +x /app/custom"], + }); + }, +}; + +export default defineConfig({ + project: "my-project", + build: { + extensions: [customExtension], + }, +}); +``` + +## Advanced Configuration + +### Telemetry + +```ts +import { PrismaInstrumentation } from "@prisma/instrumentation"; +import { OpenAIInstrumentation } from "@langfuse/openai"; + +export default defineConfig({ + // ... other config + telemetry: { + instrumentations: [new PrismaInstrumentation(), new OpenAIInstrumentation()], + exporters: [customExporter], // Optional custom exporters + }, +}); +``` + +### Machine & Performance + +```ts +export default defineConfig({ + // ... other config + defaultMachine: "large-1x", // Default machine for all tasks + maxDuration: 300, // Default max duration (seconds) + enableConsoleLogging: true, // Console logging in development +}); +``` + +## Common Extension Combinations + +### Full-Stack Web App + +```ts +extensions: [ + prismaExtension({ schema: "prisma/schema.prisma", migrate: true }), + additionalFiles({ files: ["./public/**", "./assets/**"] }), + syncEnvVars(async (ctx) => [...envVars]), +]; +``` + +### AI/ML Processing + +```ts +extensions: [ + pythonExtension({ + scripts: ["./ai/**/*.py"], + requirementsFile: "./requirements.txt", + }), + ffmpeg({ version: "7" }), + additionalPackages({ packages: ["wrangler"] }), +]; +``` + +### Web Scraping + +```ts +extensions: [ + playwright({ browsers: ["chromium"] }), + puppeteer(), + additionalFiles({ files: ["./selectors.json", "./proxies.txt"] }), +]; +``` + +## Best Practices + +- **Use specific versions**: Pin extension versions for reproducible builds +- **External packages**: Add modules with native addons to the `build.external` array +- **Environment sync**: Use `syncEnvVars` for dynamic secrets +- **File paths**: Use glob patterns for flexible file inclusion +- **Debug builds**: Use `--log-level debug --dry-run` for troubleshooting + +Extensions only affect deployment, not local development. Use `external` array for packages that shouldn't be bundled. diff --git a/.dockerignore b/.dockerignore index 993944f0e..1574bc26d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -13,6 +13,7 @@ storage # installed files node_modules +dist out .cache .claude diff --git a/.github/workflows/cloudflare.yml b/.github/workflows/cloudflare.yml index 32dda66a8..5522ca6ab 100644 --- a/.github/workflows/cloudflare.yml +++ b/.github/workflows/cloudflare.yml @@ -101,9 +101,11 @@ jobs: run: | FILE_WWW="apps/www/wrangler.jsonc" FILE_MENTIONS="apps/mentions/wrangler.jsonc" + FILE_SEO="apps/seo/wrangler.jsonc" # Replace placeholder with computed slug sed -i "s|{env.PULL_REQUEST}|$ENVIRONMENT|g" "$FILE_WWW" sed -i "s|{env.PULL_REQUEST}|$ENVIRONMENT|g" "$FILE_MENTIONS" + sed -i "s|{env.PULL_REQUEST}|$ENVIRONMENT|g" "$FILE_SEO" sed -i "s|{env.PULL_REQUEST}|$ENVIRONMENT|g" "$ENV_PATH" - name: Prepare secrets @@ -141,6 +143,18 @@ jobs: command: ${{ steps.resolve.outputs.deploy_command }} environment: ${{ steps.resolve.outputs.environment }} secrets: ${{ steps.prepare_secrets.outputs.secret_names }} + + - name: Deploy SEO + id: deploy_seo + uses: cloudflare/wrangler-action@v3 + with: + packageManager: pnpm + workingDirectory: apps/seo + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: ${{ steps.resolve.outputs.deploy_command }} + environment: ${{ steps.resolve.outputs.environment }} + secrets: ${{ steps.prepare_secrets.outputs.secret_names }} - name: Comment for deployment details if: ${{ github.event_name == 'pull_request' }} @@ -149,6 +163,7 @@ jobs: message: | Cloudflare Preview URL for WWW :balloon: : https://${{ steps.deploy_www.outputs.deployment-url }} Cloudflare Preview URL for Mentions :balloon: : https://${{ steps.deploy_mentions.outputs.deployment-url }} + Cloudflare Preview URL for SEO :balloon: : https://${{ steps.deploy_seo.outputs.deployment-url }} comment-tag: execution cleanup: @@ -167,10 +182,11 @@ jobs: run: | FILE_WWW="apps/www/wrangler.jsonc" FILE_MENTIONS="apps/mentions/wrangler.jsonc" + FILE_SEO="apps/seo/wrangler.jsonc" # Replace placeholder with computed slug sed -i "s|{env.PULL_REQUEST}|$STAGE|g" "$FILE_WWW" sed -i "s|{env.PULL_REQUEST}|$STAGE|g" "$FILE_MENTIONS" - + sed -i "s|{env.PULL_REQUEST}|$STAGE|g" "$FILE_SEO" - name: Destroy Preview Environment for WWW uses: cloudflare/wrangler-action@v3 with: @@ -187,4 +203,14 @@ jobs: workingDirectory: apps/mentions apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - command: delete --env ${{ env.STAGE }} \ No newline at end of file + command: delete --env ${{ env.STAGE }} + + - name: Destroy Preview Environment for SEO + uses: cloudflare/wrangler-action@v3 + with: + packageManager: pnpm + workingDirectory: apps/seo + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: delete --env ${{ env.STAGE }} + \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2fe156a20..b5c8a67f6 100644 --- a/.gitignore +++ b/.gitignore @@ -108,4 +108,7 @@ sst-env.d.ts .cursorrules # content-collections -.content-collections \ No newline at end of file +.content-collections + +# trigger dev +.trigger \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 68be19f0c..59ec9aecf 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -49,6 +49,7 @@ "arktype", "crawlee", "Creds", + "defuddle", "dotenvx", "fumadocs", "metas", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0848c77cc..51c08f703 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -145,69 +145,114 @@ importers: specifier: ^4.37.1 version: 4.37.1(@cloudflare/workers-types@4.20250414.0) - apps/tasks: + apps/seo: dependencies: - '@ai-sdk/google': - specifier: ^2.0.14 - version: 2.0.14(zod@4.1.9) - '@aws-sdk/client-sqs': - specifier: ^3.891.0 - version: 3.891.0 - '@orpc/client': - specifier: ^1.8.9 - version: 1.8.9(@opentelemetry/api@1.9.0) - '@orpc/contract': - specifier: ^1.8.9 - version: 1.8.9(@opentelemetry/api@1.9.0) - '@orpc/server': - specifier: ^1.8.9 - version: 1.8.9(@opentelemetry/api@1.9.0)(crossws@0.3.5)(ws@8.18.3) - '@orpc/tanstack-query': - specifier: ^1.8.9 - version: 1.8.9(@opentelemetry/api@1.9.0)(@orpc/client@1.8.9(@opentelemetry/api@1.9.0))(@tanstack/query-core@5.89.0) - '@rectangular-labs/api-core': + '@rectangular-labs/api-seo': specifier: workspace:* - version: link:../../packages/api-core + version: link:../../packages/api-seo '@rectangular-labs/auth': specifier: workspace:* version: link:../../packages/auth - '@rectangular-labs/crawler': + '@rectangular-labs/content': specifier: workspace:* - version: link:../../packages/crawler + version: link:../../packages/content '@rectangular-labs/db': specifier: workspace:* version: link:../../packages/db '@rectangular-labs/result': specifier: workspace:* version: link:../../packages/result + '@rectangular-labs/ui': + specifier: workspace:* + version: link:../../packages/ui + '@stepperize/react': + specifier: ^5.1.7 + version: 5.1.7(react@19.1.1)(typescript@5.9.2) '@t3-oss/env-core': specifier: ^0.13.8 version: 0.13.8(arktype@2.1.22)(typescript@5.9.2)(zod@4.1.9) - ai: - specifier: ^5.0.45 - version: 5.0.45(zod@4.1.9) + '@tanstack/react-query': + specifier: ^5.89.0 + version: 5.89.0(react@19.1.1) + '@tanstack/react-query-devtools': + specifier: ^5.89.0 + version: 5.89.0(@tanstack/react-query@5.89.0(react@19.1.1))(react@19.1.1) + '@tanstack/react-router': + specifier: ^1.131.44 + version: 1.131.44(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@tanstack/react-router-devtools': + specifier: ^1.131.44 + version: 1.131.44(@tanstack/react-router@1.131.44(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@tanstack/router-core@1.131.44)(csstype@3.1.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(solid-js@1.9.5)(tiny-invariant@1.3.3) + '@tanstack/react-router-with-query': + specifier: ^1.130.17 + version: 1.130.17(@tanstack/react-query@5.89.0(react@19.1.1))(@tanstack/react-router@1.131.44(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@tanstack/router-core@1.131.44)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@tanstack/react-start': + specifier: ^1.131.44 + version: 1.131.44(@netlify/blobs@9.1.2)(@tanstack/react-router@1.131.44(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@vitejs/plugin-react@5.0.3(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1)))(aws4fetch@1.0.20)(better-sqlite3@11.9.1)(drizzle-orm@0.44.5(@cloudflare/workers-types@4.20250414.0)(@opentelemetry/api@1.9.0)(@prisma/client@6.6.0(prisma@6.6.0(typescript@5.9.2))(typescript@5.9.2))(@types/pg@8.15.5)(better-sqlite3@11.9.1)(gel@2.0.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7)(prisma@6.6.0(typescript@5.9.2)))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1))(webpack@5.99.5)(xml2js@0.6.2) arktype: specifier: ^2.1.22 version: 2.1.22 - hono: - specifier: ^4.9.8 - version: 4.9.8 - sst: - specifier: 3.17.13 - version: 3.17.13 + motion: + specifier: ^12.23.13 + version: 12.23.13(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: + specifier: ^19.1.1 + version: 19.1.1 + react-dom: + specifier: ^19.1.1 + version: 19.1.1(react@19.1.1) devDependencies: '@rectangular-labs/typescript': specifier: workspace:* version: link:../../tooling/typescript - '@types/aws-lambda': - specifier: 8.10.152 - version: 8.10.152 + '@tailwindcss/vite': + specifier: ^4.1.13 + version: 4.1.13(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1)) + '@testing-library/dom': + specifier: ^10.4.1 + version: 10.4.1 + '@testing-library/react': + specifier: ^16.3.0 + version: 16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@types/node': specifier: ^24.5.1 version: 24.5.1 + '@types/react': + specifier: ^19.1.13 + version: 19.1.13 + '@types/react-dom': + specifier: ^19.1.9 + version: 19.1.9(@types/react@19.1.13) + '@vitejs/plugin-react': + specifier: ^5.0.3 + version: 5.0.3(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1)) + jiti: + specifier: ^2.5.1 + version: 2.5.1 + jsdom: + specifier: ^26.1.0 + version: 26.1.0 typescript: specifier: ^5.9.2 version: 5.9.2 + vite: + specifier: ^7.1.5 + version: 7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1) + vite-plugin-mkcert: + specifier: ^1.17.8 + version: 1.17.8(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1)) + vite-tsconfig-paths: + specifier: ^5.1.4 + version: 5.1.4(typescript@5.9.2)(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1)) + vitest: + specifier: ^3.2.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@24.5.1)(jiti@2.5.1)(jsdom@26.1.0)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.5)(yaml@2.8.1) + web-vitals: + specifier: ^5.1.0 + version: 5.1.0 + wrangler: + specifier: ^4.37.1 + version: 4.37.1(@cloudflare/workers-types@4.20250414.0) apps/www: dependencies: @@ -349,6 +394,58 @@ importers: specifier: ^5.9.2 version: 5.9.2 + packages/api-seo: + dependencies: + '@ai-sdk/google': + specifier: ^2.0.14 + version: 2.0.14(zod@4.1.9) + '@orpc/client': + specifier: ^1.8.9 + version: 1.8.9(@opentelemetry/api@1.9.0) + '@orpc/contract': + specifier: ^1.8.9 + version: 1.8.9(@opentelemetry/api@1.9.0) + '@orpc/server': + specifier: ^1.8.9 + version: 1.8.9(@opentelemetry/api@1.9.0)(crossws@0.3.5)(ws@8.18.3) + '@orpc/tanstack-query': + specifier: ^1.8.9 + version: 1.8.9(@opentelemetry/api@1.9.0)(@orpc/client@1.8.9(@opentelemetry/api@1.9.0))(@tanstack/query-core@5.89.0) + '@rectangular-labs/api-core': + specifier: workspace:* + version: link:../api-core + '@rectangular-labs/auth': + specifier: workspace:* + version: link:../auth + '@rectangular-labs/db': + specifier: workspace:* + version: link:../db + '@rectangular-labs/result': + specifier: workspace:* + version: link:../result + '@rectangular-labs/task': + specifier: workspace:* + version: link:../task + '@t3-oss/env-core': + specifier: ^0.13.8 + version: 0.13.8(arktype@2.1.22)(typescript@5.9.2)(zod@4.1.9) + ai: + specifier: ^5.0.45 + version: 5.0.45(zod@4.1.9) + arktype: + specifier: ^2.1.22 + version: 2.1.22 + devDependencies: + '@rectangular-labs/typescript': + specifier: workspace:* + version: link:../../tooling/typescript + '@types/node': + specifier: ^24.5.1 + version: 24.5.1 + typescript: + specifier: ^5.9.2 + version: 5.9.2 + packages/auth: dependencies: '@better-auth/expo': @@ -468,40 +565,6 @@ importers: specifier: ^5.9.2 version: 5.9.2 - packages/crawler: - dependencies: - '@t3-oss/env-core': - specifier: ^0.13.8 - version: 0.13.8(arktype@2.1.22)(typescript@5.9.2)(zod@4.1.9) - arktype: - specifier: ^2.1.22 - version: 2.1.22 - crawlee: - specifier: ^3.14.1 - version: 3.14.1(playwright@1.55.0) - glob: - specifier: ^11.0.3 - version: 11.0.3 - gpt-tokenizer: - specifier: ^3.0.1 - version: 3.0.1 - playwright: - specifier: 1.55.0 - version: 1.55.0 - devDependencies: - '@rectangular-labs/typescript': - specifier: workspace:* - version: link:../../tooling/typescript - '@types/aws-lambda': - specifier: 8.10.152 - version: 8.10.152 - '@types/node': - specifier: ^24.5.1 - version: 24.5.1 - typescript: - specifier: ^5.9.2 - version: 5.9.2 - packages/db: dependencies: '@t3-oss/env-core': @@ -654,6 +717,58 @@ importers: specifier: ^5.9.2 version: 5.9.2 + packages/task: + dependencies: + '@orama/orama': + specifier: ^3.1.14 + version: 3.1.14 + '@t3-oss/env-core': + specifier: ^0.13.8 + version: 0.13.8(arktype@2.1.22)(typescript@5.9.2)(zod@4.1.9) + '@trigger.dev/sdk': + specifier: 4.0.2 + version: 4.0.2(ai@5.0.45(zod@4.1.9))(zod@4.1.9) + arktype: + specifier: ^2.1.22 + version: 2.1.22 + aws4fetch: + specifier: ^1.0.20 + version: 1.0.20 + crawlee: + specifier: ^3.14.1 + version: 3.14.1(playwright@1.55.0) + defuddle: + specifier: ^0.6.6 + version: 0.6.6(jsdom@26.1.0) + glob: + specifier: ^11.0.3 + version: 11.0.3 + gpt-tokenizer: + specifier: ^3.0.1 + version: 3.0.1 + jsdom: + specifier: ^26.1.0 + version: 26.1.0 + playwright: + specifier: 1.55.0 + version: 1.55.0 + unstorage: + specifier: ^1.17.1 + version: 1.17.1(@netlify/blobs@9.1.2)(aws4fetch@1.0.20)(db0@0.3.2(better-sqlite3@11.9.1)(drizzle-orm@0.44.5(@cloudflare/workers-types@4.20250414.0)(@opentelemetry/api@1.9.0)(@prisma/client@6.6.0(prisma@6.6.0(typescript@5.9.2))(typescript@5.9.2))(@types/pg@8.15.5)(better-sqlite3@11.9.1)(gel@2.0.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7)(prisma@6.6.0(typescript@5.9.2))))(ioredis@5.7.0) + devDependencies: + '@rectangular-labs/typescript': + specifier: workspace:* + version: link:../../tooling/typescript + '@trigger.dev/build': + specifier: 4.0.2 + version: 4.0.2(typescript@5.9.2) + '@types/node': + specifier: ^24.5.1 + version: 24.5.1 + typescript: + specifier: ^5.9.2 + version: 5.9.2 + packages/ui: dependencies: '@fontsource-variable/atkinson-hyperlegible-mono': @@ -925,18 +1040,10 @@ packages: resolution: {integrity: sha512-AM9Lt4QkNet8xKgCwJxfkyqlorwG9S+tvtpSfHYCVq0j2Z6PbkDaUBnvwjGOMBV7Um5IzZ7yhvQXrBg7omZciQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/client-sqs@3.891.0': - resolution: {integrity: sha512-+c/A8Rqa2pTMR4ZljJ8GRWJc9kI0oBQCRelPDLkDLamGEH8EKJksIaDTw15TvJr+Mg6FZrnIRyDWhSRX57RyYQ==} - engines: {node: '>=18.0.0'} - '@aws-sdk/client-sso@3.890.0': resolution: {integrity: sha512-vefYNwh/K5V5YiJpFJfoMPNqsoiRTqD7ZnkvR0cjJdwhOIwFnSKN1vz0OMjySTQmVMcG4JKGVul82ou7ErtOhQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/client-sso@3.891.0': - resolution: {integrity: sha512-QMDaD9GhJe7l0KQp3Tt7dzqFCz/H2XuyNjQgvi10nM1MfI1RagmLtmEhZveQxMPhZ/AtohLSK0Tisp/I5tR8RQ==} - engines: {node: '>=18.0.0'} - '@aws-sdk/core@3.890.0': resolution: {integrity: sha512-CT+yjhytHdyKvV3Nh/fqBjnZ8+UiQZVz4NMm4LrPATgVSOdfygXHqrWxrPTVgiBtuJWkotg06DF7+pTd5ekLBw==} engines: {node: '>=18.0.0'} @@ -953,18 +1060,10 @@ packages: resolution: {integrity: sha512-Mxv7ByftHKH7dE6YXu9gQ6ODXwO1iSO32t8tBrZLS3g8K1knWADIqDFv3yErQtJ8hp27IDxbAbVH/1RQdSkmhA==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-ini@3.891.0': - resolution: {integrity: sha512-9LOfm97oy2d2frwCQjl53XLkoEYG6/rsNM3Y6n8UtRU3bzGAEjixdIuv3b6Z/Mk/QLeikcQEJ9FMC02DuQh2Yw==} - engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-node@3.890.0': resolution: {integrity: sha512-zbPz3mUtaBdch0KoH8/LouRDcYSzyT2ecyCOo5OAFVil7AxT1jvsn4vX78FlnSVpZ4mLuHY8pHTVGi235XiyBA==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-node@3.891.0': - resolution: {integrity: sha512-IjGvQJhpCN512xlT1DFGaPeE1q0YEm/X62w7wHsRpBindW//M+heSulJzP4KPkoJvmJNVu1NxN26/p4uH+M8TQ==} - engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-process@3.890.0': resolution: {integrity: sha512-dWZ54TI1Q+UerF5YOqGiCzY+x2YfHsSQvkyM3T4QDNTJpb/zjiVv327VbSOULOlI7gHKWY/G3tMz0D9nWI7YbA==} engines: {node: '>=18.0.0'} @@ -973,66 +1072,34 @@ packages: resolution: {integrity: sha512-ajYCZ6f2+98w8zG/IXcQ+NhWYoI5qPUDovw+gMqMWX/jL1cmZ9PFAwj2Vyq9cbjum5RNWwPLArWytTCgJex4AQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-sso@3.891.0': - resolution: {integrity: sha512-RtF9BwUIZqc/7sFbK6n6qhe0tNaWJQwin89nSeZ1HOsA0Z7TfTOelX8Otd0L5wfeVBMVcgiN3ofqrcZgjFjQjA==} - engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-web-identity@3.890.0': resolution: {integrity: sha512-qZ2Mx7BeYR1s0F/H6wePI0MAmkFswmBgrpgMCOt2S4b2IpQPnUa2JbxY3GwW2WqX3nV0KjPW08ctSLMmlq/tKA==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-web-identity@3.891.0': - resolution: {integrity: sha512-yq7kzm1sHZ0GZrtS+qpjMUp4ES66UoT1+H2xxrOuAZkvUnkpQq1iSjOgBgJJ9FW1EsDUEmlgn94i4hJTNvm7fg==} - engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-host-header@3.887.0': resolution: {integrity: sha512-ulzqXv6NNqdu/kr0sgBYupWmahISHY+azpJidtK6ZwQIC+vBUk9NdZeqQpy7KVhIk2xd4+5Oq9rxapPwPI21CA==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-host-header@3.891.0': - resolution: {integrity: sha512-OYaxbqNDeo/noE7MfYWWQDu86cF/R/bMXdZ2QZwpWpX2yjy8xMwxSg7c/4tEK/OtiDZTKRXXrvPxRxG2+1bnJw==} - engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-logger@3.887.0': resolution: {integrity: sha512-YbbgLI6jKp2qSoAcHnXrQ5jcuc5EYAmGLVFgMVdk8dfCfJLfGGSaOLxF4CXC7QYhO50s+mPPkhBYejCik02Kug==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-logger@3.891.0': - resolution: {integrity: sha512-azL4mg1H1FLpOAECiFtU+r+9VDhpeF6Vh9pzD4m51BWPJ60CVnyHayeI/0gqPsL60+5l90/b9VWonoA8DvAvpg==} - engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-recursion-detection@3.887.0': resolution: {integrity: sha512-tjrUXFtQnFLo+qwMveq5faxP5MQakoLArXtqieHphSqZTXm21wDJM73hgT4/PQQGTwgYjDKqnqsE1hvk0hcfDw==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-recursion-detection@3.891.0': - resolution: {integrity: sha512-n++KwAEnNlvx5NZdIQZnvl2GjSH/YE3xGSqW2GmPB5780tFY5lOYSb1uA+EUzJSVX4oAKAkSPdR2AOW09kzoew==} - engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-sdk-s3@3.890.0': resolution: {integrity: sha512-58P1lrE606zpp29xH9Keh3j2BWfa2ciGBtygJTpulRMlqPL3U1gFfU2g5nDYJbjKgRtCgNIBqfmtkL4eikCb9w==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-sdk-sqs@3.891.0': - resolution: {integrity: sha512-+FkFslhvJqIzw+nVtToGuOcbdzclkTiOj6Z61mnq1ZyH8r5OOW9EkLHNgIacn6ehdoIeCvOCJiLpQbttNn51Lw==} - engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-user-agent@3.890.0': resolution: {integrity: sha512-x4+gLrOFGN7PnfxCaQbs3QEF8bMQE4CVxcOp066UEJqr2Pn4yB12Q3O+YntOtESK5NcTxIh7JlhGss95EHzNng==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-user-agent@3.891.0': - resolution: {integrity: sha512-xyxIZtR7FunCWymPAxEm61VUq9lruXxWIYU5AIh5rt0av7nXa2ayAAlscQ7ch9jUlw+lbC2PVbw0K/OYrMovuA==} - engines: {node: '>=18.0.0'} - '@aws-sdk/nested-clients@3.890.0': resolution: {integrity: sha512-D5qVNd+qlqdL8duJShzffAqPllGRA4tG7n/GEpL13eNfHChPvGkkUFBMrxSgCAETaTna13G6kq+dMO+SAdbm1A==} engines: {node: '>=18.0.0'} - '@aws-sdk/nested-clients@3.891.0': - resolution: {integrity: sha512-cpol+Yk4T3GXPXbRfUyN2u6tpMEHUxAiesZgrfMm11QGHV+pmzyejJV/QZ0pdJKj5sXKaCr4DCntoJ5iBx++Cw==} - engines: {node: '>=18.0.0'} - '@aws-sdk/region-config-resolver@3.890.0': resolution: {integrity: sha512-VfdT+tkF9groRYNzKvQCsCGDbOQdeBdzyB1d6hWiq22u13UafMIoskJ1ec0i0H1X29oT6mjTitfnvPq1UiKwzQ==} engines: {node: '>=18.0.0'} @@ -1045,10 +1112,6 @@ packages: resolution: {integrity: sha512-+pK/0iQEpPmnztbAw0NNmb+B5pPy8VLu+Ab4SJLgVp41RE9NO13VQtrzUbh61TTAVMrzqWlLQ2qmAl2Fk4VNgw==} engines: {node: '>=18.0.0'} - '@aws-sdk/token-providers@3.891.0': - resolution: {integrity: sha512-n31JDMWhj/53QX33C97+1W63JGtgO8pg1/Tfmv4f9TR2VSGf1rFwYH7cPZ7dVIMmcUBeI2VCVhwUIabGNHw86Q==} - engines: {node: '>=18.0.0'} - '@aws-sdk/types@3.887.0': resolution: {integrity: sha512-fmTEJpUhsPsovQ12vZSpVTEP/IaRoJAMBGQXlQNjtCpkBp6Iq3KQDa/HDaPINE+3xxo6XvTdtibsNOd5zJLV9A==} engines: {node: '>=18.0.0'} @@ -1061,10 +1124,6 @@ packages: resolution: {integrity: sha512-nJ8v1x9ZQKzMRK4dS4oefOMIHqb6cguctTcx1RB9iTaFOR5pP7bvq+D4mvNZ6vBxiHg1dQGBUUgl5XJmdR7atQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/util-endpoints@3.891.0': - resolution: {integrity: sha512-MgxvmHIQJbUK+YquX4bdjDw1MjdBqTRJGHs6iU2KM8nN1ut0bPwvavkq7NrY/wB3ZKKECqmv6J/nw+hYKKUIHA==} - engines: {node: '>=18.0.0'} - '@aws-sdk/util-locate-window@3.873.0': resolution: {integrity: sha512-xcVhZF6svjM5Rj89T1WzkjQmrTF6dpR2UvIHPMTnSZoNe6CixejPZ6f0JJ2kAhO8H+dUHwNBlsUgOTIKiK/Syg==} engines: {node: '>=18.0.0'} @@ -1081,15 +1140,6 @@ packages: aws-crt: optional: true - '@aws-sdk/util-user-agent-node@3.891.0': - resolution: {integrity: sha512-/mmvVL2PJE2NMTWj9JSY98OISx7yov0mi72eOViWCHQMRYJCN12DY54i1rc4Q/oPwJwTwIrx69MLjVhQ1OZsgw==} - engines: {node: '>=18.0.0'} - peerDependencies: - aws-crt: '>=1.0.0' - peerDependenciesMeta: - aws-crt: - optional: true - '@aws-sdk/xml-builder@3.887.0': resolution: {integrity: sha512-lMwgWK1kNgUhHGfBvO/5uLe7TKhycwOn3eRCqsKPT9aPCx/HWuTlpcQp8oW2pCRGLS7qzcxqpQulcD+bbUL7XQ==} engines: {node: '>=18.0.0'} @@ -1710,6 +1760,9 @@ packages: '@borewit/text-codec@0.1.1': resolution: {integrity: sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==} + '@bugsnag/cuid@3.2.1': + resolution: {integrity: sha512-zpvN8xQ5rdRWakMd/BcVkdn2F8HKlDSbM3l7duueK590WmI1T0ObTLc1V/1e55r14WNjPd5AJTYX4yPEAFVi+Q==} + '@changesets/apply-release-plan@7.0.13': resolution: {integrity: sha512-BIW7bofD2yAWoE8H4V40FikC+1nNFEKBisMECccS16W1rt6qqhNTBDmIw5HaqmMgtLNz9e7oiALiEUuKrQ4oHg==} @@ -2007,6 +2060,9 @@ packages: peerDependencies: '@noble/ciphers': ^1.0.0 + '@electric-sql/client@1.0.0-beta.1': + resolution: {integrity: sha512-Ei9jN3pDoGzc+a/bGqnB5ajb52IvSv7/n2btuyzUlcOHIR2kM9fqtYTJXPwZYKLkGZlHWlpHgWyRtrinkP2nHg==} + '@emnapi/core@1.5.0': resolution: {integrity: sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==} @@ -3209,6 +3265,10 @@ packages: '@content-collections/mdx': 0.x.x fumadocs-core: ^14.0.0 || ^15.0.0 + '@google-cloud/precise-date@4.0.0': + resolution: {integrity: sha512-1TUx3KdaU3cN7nfCdNf+UVqA/PSX29Cjcox3fZZBtINlRrXVTmUkQnCKv2MbBUbCopbK4olAT1IHl76uZyCiVA==} + engines: {node: '>=14.0.0'} + '@hexagon/base64@1.1.28': resolution: {integrity: sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==} @@ -3426,6 +3486,9 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@jsonhero/path@1.0.21': + resolution: {integrity: sha512-gVUDj/92acpVoJwsVJ/RuWOaHyG4oFzn898WNGQItLCTQ+hOaVlEaImhwE1WqOTf+l3dGOUkbSiVKlb3q1hd1Q==} + '@jsx-email/doiuse-email@1.0.4': resolution: {integrity: sha512-HfLjuQsAAyAkIZWR0wHR6+P6u40RIX0jBZu/1rgsw18+jc36agZD5j84zG4CDzitRxgXJXrAohPfDFPxcrtjAA==} engines: {node: '>=18.0.0'} @@ -3473,9 +3536,8 @@ packages: '@mediapipe/tasks-vision@0.10.17': resolution: {integrity: sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==} - '@modelcontextprotocol/sdk@1.6.1': - resolution: {integrity: sha512-oxzMzYCkZHMntzuyerehK3fV6A2Kwh5BD6CGEJSVDU2QNEhfLOptf2X7esQgaHZXHZY0oHmMsOtIDLP71UJXgA==} - engines: {node: '>=18'} + '@mixmark-io/domino@2.2.0': + resolution: {integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==} '@monogrid/gainmap-js@3.1.0': resolution: {integrity: sha512-Obb0/gEd/HReTlg8ttaYk+0m62gQJmCblMOjHSMHRrBP2zdfKMHLCRbh/6ex9fSUJMKdjjIEiohwkbGD3wj2Nw==} @@ -3572,13 +3634,97 @@ packages: resolution: {integrity: sha512-T8TbSnGsxo6TDBJx/Sgv/BlVJL3tshxZP7Aq5R1mSnM5OcHY2dQaxLMu2+E8u3gN0MLOzdjurqN4ZRVuzQycOQ==} engines: {node: '>=8.0'} + '@opentelemetry/api-logs@0.203.0': + resolution: {integrity: sha512-9B9RU0H7Ya1Dx/Rkyc4stuBZSGVQF27WigitInx2QQoj6KUpEFYPKoWjdFTunJYxmXmh17HeBvbMa1EhGyPmqQ==} + engines: {node: '>=8.0.0'} + '@opentelemetry/api@1.9.0': resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} - '@orama/orama@3.1.13': - resolution: {integrity: sha512-O0hdKt4K31i8fpq8Bw5RfdPVAqm0EdduBUcluPo2MRcfCOwUEf5JlnvRhf/J0ezOYOD8jQ/LumYZxOVi/XK/BA==} - engines: {node: '>= 20.0.0'} + '@opentelemetry/context-async-hooks@2.0.1': + resolution: {integrity: sha512-XuY23lSI3d4PEqKA+7SLtAgwqIfc6E/E9eAQWLN1vlpC53ybO3o6jW4BsXo1xvz9lYyyWItfQDDLzezER01mCw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@2.0.1': + resolution: {integrity: sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/exporter-logs-otlp-http@0.203.0': + resolution: {integrity: sha512-s0hys1ljqlMTbXx2XiplmMJg9wG570Z5lH7wMvrZX6lcODI56sG4HL03jklF63tBeyNwK2RV1/ntXGo3HgG4Qw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-trace-otlp-http@0.203.0': + resolution: {integrity: sha512-ZDiaswNYo0yq/cy1bBLJFe691izEJ6IgNmkjm4C6kE9ub/OMQqDXORx2D2j8fzTBTxONyzusbaZlqtfmyqURPw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation@0.203.0': + resolution: {integrity: sha512-ke1qyM+3AK2zPuBPb6Hk/GCsc5ewbLvPNkEuELx/JmANeEp6ZjnZ+wypPAJSucTw0wvCGrUaibDSdcrGFoWxKQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-exporter-base@0.203.0': + resolution: {integrity: sha512-Wbxf7k+87KyvxFr5D7uOiSq/vHXWommvdnNE7vECO3tAhsA2GfOlpWINCMWUEPdHZ7tCXxw6Epp3vgx3jU7llQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-transformer@0.203.0': + resolution: {integrity: sha512-Y8I6GgoCna0qDQ2W6GCRtaF24SnvqvA8OfeTi7fqigD23u8Jpb4R5KFv/pRvrlGagcCLICMIyh9wiejp4TXu/A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/resources@2.0.1': + resolution: {integrity: sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-logs@0.203.0': + resolution: {integrity: sha512-vM2+rPq0Vi3nYA5akQD2f3QwossDnTDLvKbea6u/A2NZ3XDkPxMfo/PNrDoXhDUD/0pPo2CdH5ce/thn9K0kLw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.10.0' + + '@opentelemetry/sdk-metrics@2.0.1': + resolution: {integrity: sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.9.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@2.0.1': + resolution: {integrity: sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-node@2.0.1': + resolution: {integrity: sha512-UhdbPF19pMpBtCWYP5lHbTogLWx9N0EBxtdagvkn5YtsAnCBZzL7SjktG+ZmupRgifsHMjwUaCCaVmqGfSADmA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/semantic-conventions@1.36.0': + resolution: {integrity: sha512-TtxJSRD8Ohxp6bKkhrm27JRHAxPczQA7idtcTOMYI+wQRRrfgqxHv1cFbCApcSnNjtXkmzFozn6jQtFrOmbjPQ==} + engines: {node: '>=14'} + + '@orama/orama@3.1.13': + resolution: {integrity: sha512-O0hdKt4K31i8fpq8Bw5RfdPVAqm0EdduBUcluPo2MRcfCOwUEf5JlnvRhf/J0ezOYOD8jQ/LumYZxOVi/XK/BA==} + engines: {node: '>= 20.0.0'} + + '@orama/orama@3.1.14': + resolution: {integrity: sha512-Iq4RxYC7y0pA/hLgcUGpYYs5Vze4qNmJk0Qi1uIrg2bHGpm6A06nbjWcH9h4HQsddkDFFlanLj/zYBH3Sxdb4w==} + engines: {node: '>= 20.0.0'} '@orpc/arktype@1.8.9': resolution: {integrity: sha512-dblZBIJ3siK5zXP1m3Sp3NJTSggk5F853ozmQOe8CUUr3Y999IZmUCAUhrgrkkLji5s0X3yHWqysRlv145H0cA==} @@ -3892,6 +4038,36 @@ packages: '@prisma/get-platform@6.6.0': resolution: {integrity: sha512-3qCwmnT4Jh5WCGUrkWcc6VZaw0JY7eWN175/pcb5Z6FiLZZ3ygY93UX0WuV41bG51a6JN/oBH0uywJ90Y+V5eA==} + '@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==} + '@radix-ui/colors@3.0.0': resolution: {integrity: sha512-FUOsGBkHrYJwCSEtWRCIfQbZG7q1e6DgxCIOe1SUQzDe/7rXXeA47s8yCn6fuTNQAj1Zq4oTFi9Yjp3wzElcxg==} @@ -5321,10 +5497,6 @@ packages: resolution: {integrity: sha512-Abs5rdP1o8/OINtE49wwNeWuynCu0kme1r4RI3VXVrHr4odVDG7h7mTnw1WXXfN5Il+c25QOnrdL2y56USfxkA==} engines: {node: '>=18.0.0'} - '@smithy/core@3.11.1': - resolution: {integrity: sha512-REH7crwORgdjSpYs15JBiIWOYjj0hJNC3aCecpJvAlMMaaqL5i2CLb1i6Hc4yevToTKSqslLMI9FKjhugEwALA==} - engines: {node: '>=18.0.0'} - '@smithy/credential-provider-imds@4.1.2': resolution: {integrity: sha512-JlYNq8TShnqCLg0h+afqe2wLAwZpuoSgOyzhYvTgbiKBWRov+uUve+vrZEQO6lkdLOWPh7gK5dtb9dS+KGendg==} engines: {node: '>=18.0.0'} @@ -5349,10 +5521,6 @@ packages: resolution: {integrity: sha512-ePTYUOV54wMogio+he4pBybe8fwg4sDvEVDBU8ZlHOZXbXK3/C0XfJgUCu6qAZcawv05ZhZzODGUerFBPsPUDQ==} engines: {node: '>=18.0.0'} - '@smithy/md5-js@4.1.1': - resolution: {integrity: sha512-MvWXKK743BuHjr/hnWuT6uStdKEaoqxHAQUvbKJPPZM5ZojTNFI5D+47BoQfBE5RgGlRRty05EbWA+NXDv+hIA==} - engines: {node: '>=18.0.0'} - '@smithy/middleware-content-length@4.1.1': resolution: {integrity: sha512-9wlfBBgTsRvC2JxLJxv4xDGNBrZuio3AgSl0lSFX7fneW2cGskXTYpFxCdRYD2+5yzmsiTuaAJD1Wp7gWt9y9w==} engines: {node: '>=18.0.0'} @@ -5361,18 +5529,10 @@ packages: resolution: {integrity: sha512-M51KcwD+UeSOFtpALGf5OijWt915aQT5eJhqnMKJt7ZTfDfNcvg2UZgIgTZUoiORawb6o5lk4n3rv7vnzQXgsA==} engines: {node: '>=18.0.0'} - '@smithy/middleware-endpoint@4.2.3': - resolution: {integrity: sha512-+1H5A28DeffRVrqmVmtqtRraEjoaC6JVap3xEQdVoBh2EagCVY7noPmcBcG4y7mnr9AJitR1ZAse2l+tEtK5vg==} - engines: {node: '>=18.0.0'} - '@smithy/middleware-retry@4.2.2': resolution: {integrity: sha512-KZJueEOO+PWqflv2oGx9jICpHdBYXwCI19j7e2V3IMwKgFcXc9D9q/dsTf4B+uCnYxjNoS1jpyv6pGNGRsKOXA==} engines: {node: '>=18.0.0'} - '@smithy/middleware-retry@4.2.4': - resolution: {integrity: sha512-amyqYQFewnAviX3yy/rI/n1HqAgfvUdkEhc04kDjxsngAUREKuOI24iwqQUirrj6GtodWmR4iO5Zeyl3/3BwWg==} - engines: {node: '>=18.0.0'} - '@smithy/middleware-serde@4.1.1': resolution: {integrity: sha512-lh48uQdbCoj619kRouev5XbWhCwRKLmphAif16c4J6JgJ4uXjub1PI6RL38d3BLliUvSso6klyB/LTNpWSNIyg==} engines: {node: '>=18.0.0'} @@ -5409,10 +5569,6 @@ packages: resolution: {integrity: sha512-Iam75b/JNXyDE41UvrlM6n8DNOa/r1ylFyvgruTUx7h2Uk7vDNV9AAwP1vfL1fOL8ls0xArwEGVcGZVd7IO/Cw==} engines: {node: '>=18.0.0'} - '@smithy/service-error-classification@4.1.2': - resolution: {integrity: sha512-Kqd8wyfmBWHZNppZSMfrQFpc3M9Y/kjyN8n8P4DqJJtuwgK1H914R471HTw7+RL+T7+kI1f1gOnL7Vb5z9+NgQ==} - engines: {node: '>=18.0.0'} - '@smithy/shared-ini-file-loader@4.2.0': resolution: {integrity: sha512-OQTfmIEp2LLuWdxa8nEEPhZmiOREO6bcB6pjs0AySf4yiZhl6kMOfqmcwcY8BaBPX+0Tb+tG7/Ia/6mwpoZ7Pw==} engines: {node: '>=18.0.0'} @@ -5425,10 +5581,6 @@ packages: resolution: {integrity: sha512-u82cjh/x7MlMat76Z38TRmEcG6JtrrxN4N2CSNG5o2v2S3hfLAxRgSgFqf0FKM3dglH41Evknt/HOX+7nfzZ3g==} engines: {node: '>=18.0.0'} - '@smithy/smithy-client@4.6.3': - resolution: {integrity: sha512-K27LqywsaqKz4jusdUQYJh/YP2VbnbdskZ42zG8xfV+eovbTtMc2/ZatLWCfSkW0PDsTUXlpvlaMyu8925HsOw==} - engines: {node: '>=18.0.0'} - '@smithy/types@4.5.0': resolution: {integrity: sha512-RkUpIOsVlAwUIZXO1dsz8Zm+N72LClFfsNqf173catVlvRZiwPy0x2u0JLEA4byreOPKDZPGjmPDylMoP8ZJRg==} engines: {node: '>=18.0.0'} @@ -5485,18 +5637,10 @@ packages: resolution: {integrity: sha512-jGeybqEZ/LIordPLMh5bnmnoIgsqnp4IEimmUp5c5voZ8yx+5kAlN5+juyr7p+f7AtZTgvhmInQk4Q0UVbrZ0Q==} engines: {node: '>=18.0.0'} - '@smithy/util-retry@4.1.2': - resolution: {integrity: sha512-NCgr1d0/EdeP6U5PSZ9Uv5SMR5XRRYoVr1kRVtKZxWL3tixEL3UatrPIMFZSKwHlCcp2zPLDvMubVDULRqeunA==} - engines: {node: '>=18.0.0'} - '@smithy/util-stream@4.3.1': resolution: {integrity: sha512-khKkW/Jqkgh6caxMWbMuox9+YfGlsk9OnHOYCGVEdYQb/XVzcORXHLYUubHmmda0pubEDncofUrPNniS9d+uAA==} engines: {node: '>=18.0.0'} - '@smithy/util-stream@4.3.2': - resolution: {integrity: sha512-Ka+FA2UCC/Q1dEqUanCdpqwxOFdf5Dg2VXtPtB1qxLcSGh5C1HdzklIt18xL504Wiy9nNUKwDMRTVCbKGoK69g==} - engines: {node: '>=18.0.0'} - '@smithy/util-uri-escape@4.1.0': resolution: {integrity: sha512-b0EFQkq35K5NHUYxU72JuoheM6+pytEVUGlTwiFxWFpmddA+Bpz3LgsPRIpBk8lnPE47yT7AF2Egc3jVnKLuPg==} engines: {node: '>=18.0.0'} @@ -5509,6 +5653,9 @@ packages: resolution: {integrity: sha512-mEu1/UIXAdNYuBcyEPbjScKi/+MQVXNIuY/7Cm5XLIWe319kDrT5SizBE95jqtmEXoDbGoZxKLCMttdZdqTZKQ==} engines: {node: '>=18.0.0'} + '@socket.io/component-emitter@3.1.2': + resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} + '@speed-highlight/core@1.2.7': resolution: {integrity: sha512-0dxmVj4gxg3Jg879kvFS/msl4s9F3T9UXC1InxgOf7t5NvcPD97u/WTA5vL/IxWHMn7qSxBozqrnnE2wvl1m8g==} @@ -5842,12 +5989,27 @@ packages: '@tootallnate/quickjs-emscripten@0.23.0': resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} + '@trigger.dev/build@4.0.2': + resolution: {integrity: sha512-GOqjGIUXWEEIfWqY2o+xr//4KTHYoRQIML8cCoP/L8x1wPb45qFJWTwNaECgYp9i+9vPMI4/G3Jm/jvWp9xznQ==} + engines: {node: '>=18.20.0'} + + '@trigger.dev/core@4.0.2': + resolution: {integrity: sha512-hc/alfT7iVdJNZ5YSMbGR9FirLjURqdZ7tCBX4btKas0GDg6M5onwcQsJ3oom5TDp/Nrt+dHaviNMhFxhKCu3g==} + engines: {node: '>=18.20.0'} + + '@trigger.dev/sdk@4.0.2': + resolution: {integrity: sha512-ulhWJRSHPXOHz0bMvkhAKThkW63x7lnjAb87LPi6dUps1YwwoOL8Nkr15xLXa73UrldPFT+9Y/GvQ9qpzU478w==} + engines: {node: '>=18.20.0'} + peerDependencies: + ai: ^4.2.0 || ^5.0.0 + zod: ^3.0.0 || ^4.0.0 + peerDependenciesMeta: + ai: + optional: true + '@ts-morph/common@0.27.0': resolution: {integrity: sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==} - '@tsconfig/bun@1.0.7': - resolution: {integrity: sha512-udGrGJBNQdXGVulehc1aWT73wkR9wdaGBtB6yL70RJsqwW/yJhIg6ZbRlPOfIUiFNrnBuYLBi9CSmMKfDC7dvA==} - '@tsconfig/node10@1.0.11': resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} @@ -5877,9 +6039,6 @@ packages: '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} - '@types/aws-lambda@8.10.152': - resolution: {integrity: sha512-soT/c2gYBnT5ygwiHPmd9a1bftj462NWVk2tKCc1PYHSIacB2UwbTS2zYG4jzag1mRDuzg/OjtxQjQ2NKRB6Rw==} - '@types/babel__code-frame@7.0.6': resolution: {integrity: sha512-Anitqkl3+KrzcW2k77lRlg/GfLZLWXBuNgbEcIOU6M92yw42vsd3xV/Z/yAHEj8m+KUjL6bWOVOFqX8PFPJ4LA==} @@ -5901,6 +6060,12 @@ packages: '@types/content-type@1.1.9': resolution: {integrity: sha512-Hq9IMnfekuOCsEmYl4QX2HBrT+XsfXiupfrLLY8Dcf3Puf4BkBOxSbWYTITSOQAhJoYPBez+b4MJRpIYL65z8A==} + '@types/cookie@0.4.1': + resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==} + + '@types/cors@2.8.19': + resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} + '@types/d3-array@3.2.1': resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==} @@ -6316,10 +6481,6 @@ packages: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} - accepts@2.0.0: - resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} - engines: {node: '>= 0.6'} - acorn-import-attributes@1.9.5: resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} peerDependencies: @@ -6529,17 +6690,6 @@ packages: peerDependencies: postcss: ^8.1.0 - available-typed-arrays@1.0.7: - resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} - engines: {node: '>= 0.4'} - - aws-sdk@2.1692.0: - resolution: {integrity: sha512-x511uiJ/57FIsbgUe5csJ13k3uzu25uWQE+XqfBis/sB0SFoiElJWXRkgEAUh0U6n40eT3ay5Ue4oPkRMu1LYw==} - engines: {node: '>= 10.0.0'} - - aws4fetch@1.0.18: - resolution: {integrity: sha512-3Cf+YaUl07p24MoQ46rFwulAmiyCwH2+1zw1ZyPAX5OtJ34Hh185DwB8y/qRLb6cYYYtSFJ9pthyLc0MD4e8sQ==} - aws4fetch@1.0.20: resolution: {integrity: sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g==} @@ -6631,6 +6781,10 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + base64id@2.0.0: + resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} + engines: {node: ^4.5.0 || >= 5.9} + baseline-browser-mapping@2.8.4: resolution: {integrity: sha512-L+YvJwGAgwJBV1p6ffpSTa2KRc69EeeYGYjRVWKs0GKrK+LON0GC0gV+rKSNtALEDvMDqkvCFq9r1r94/Gjwxw==} hasBin: true @@ -6699,16 +6853,15 @@ packages: bindings@1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + bintrees@1.0.2: + resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==} + bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} blake3-wasm@2.1.5: resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} - body-parser@2.2.0: - resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==} - engines: {node: '>=18'} - boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -6759,9 +6912,6 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - buffer@4.9.2: - resolution: {integrity: sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==} - buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} @@ -6806,10 +6956,6 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} - call-bind@1.0.8: - resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} - engines: {node: '>= 0.4'} - call-bound@1.0.4: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} @@ -6891,6 +7037,10 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + change-case@3.1.0: resolution: {integrity: sha512-2AZp7uJZbYEzRPsFoa+ijKdvp9zsrnnt6+yFokfwEpeJm0xuJDVoxiRCAaTzyJND8GJkofo2IcKWaUZ/OECVzw==} @@ -6964,6 +7114,9 @@ packages: citty@0.1.6: resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + cjs-module-lexer@1.4.3: + resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} + class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} @@ -7146,10 +7299,6 @@ packages: constant-case@2.0.0: resolution: {integrity: sha512-eS0N9WwmjTqrOmR3o83F5vW8Z+9R1HnVz3xmzT2PMFug9ly+Au/fxRWlEBSb6LcZwspSsEn9Xs1uw9YgzAg1EQ==} - content-disposition@1.0.0: - resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==} - engines: {node: '>= 0.6'} - content-type@1.0.5: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} @@ -7163,18 +7312,18 @@ packages: cookie-es@2.0.0: resolution: {integrity: sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==} - cookie-signature@1.2.2: - resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} - engines: {node: '>=6.6.0'} - - cookie@0.7.2: - resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + cookie@0.4.2: + resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} engines: {node: '>= 0.6'} cookie@1.0.2: resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} engines: {node: '>=18'} + copy-anything@3.0.5: + resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} + engines: {node: '>=12.13'} + copy-file@11.1.0: resolution: {integrity: sha512-X8XDzyvYaA6msMyAM575CUoygY5b44QzLcGRKsK3MFmXcOvQa518dNPLsKYwkYsn72g3EiW+LE0ytd/FlqWmyw==} engines: {node: '>=18'} @@ -7232,6 +7381,10 @@ packages: resolution: {integrity: sha512-p9nwwR4qyT5W996vBZhdvBCnMhicY5ytZkR4D1Xj0wuTDEiMnjwR57Q3RXYY/s0EpX6Ay3vgIcfaR+ewGHsi+g==} engines: {node: '>=18.0'} + cronstrue@2.59.0: + resolution: {integrity: sha512-YKGmAy84hKH+hHIIER07VCAHf9u0Ldelx1uU6EBxsRPDXIA1m5fsKmJfyC3xBhw6cVC/1i83VdbL4PvepTrt8A==} + hasBin: true + cross-env@7.0.3: resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} @@ -7371,6 +7524,15 @@ packages: supports-color: optional: true + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@4.4.0: resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} engines: {node: '>=6.0'} @@ -7424,10 +7586,6 @@ packages: resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} engines: {node: '>=10'} - define-data-property@1.1.4: - resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} - engines: {node: '>= 0.4'} - define-lazy-prop@2.0.0: resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} engines: {node: '>=8'} @@ -7435,6 +7593,11 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + defuddle@0.6.6: + resolution: {integrity: sha512-cexePkdZCwg8g1DHCV3xfE6DGTBeldtJct4/fOumYE/kx+sgoDg8yxjCxlC/Pss0v11G5CUFSUmf7fGJg249AA==} + peerDependencies: + jsdom: ^24.0.0 + degenerator@5.0.1: resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} engines: {node: '>= 14'} @@ -7785,6 +7948,17 @@ packages: end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + engine.io-client@6.5.4: + resolution: {integrity: sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==} + + engine.io-parser@5.2.3: + resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} + engines: {node: '>=10.0.0'} + + engine.io@6.5.5: + resolution: {integrity: sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==} + engines: {node: '>=10.2.0'} + enhanced-resolve@5.18.3: resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} engines: {node: '>=10.13.0'} @@ -7981,10 +8155,6 @@ packages: eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} - events@1.1.1: - resolution: {integrity: sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==} - engines: {node: '>=0.4.x'} - events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} @@ -7997,6 +8167,9 @@ packages: resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} engines: {node: '>=18.0.0'} + evt@2.5.9: + resolution: {integrity: sha512-GpjX476FSlttEGWHT8BdVMoI8wGXQGbEOtKcP4E+kggg+yJzXBZN2n4x7TS/zPBJ1DZqWI+rguZZApjjzQ0HpA==} + exec-async@2.2.0: resolution: {integrity: sha512-87OpwcEiMia/DeiKFzaQNBNFeN3XkkpYIh9FyOqq5mS2oKv3CBE67PXoEKcr6nodWdXNogTiQ0jE2NGuoffXPw==} @@ -8104,16 +8277,6 @@ packages: exponential-backoff@3.1.2: resolution: {integrity: sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==} - express-rate-limit@7.5.1: - resolution: {integrity: sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==} - engines: {node: '>= 16'} - peerDependencies: - express: '>= 4.11' - - express@5.1.0: - resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==} - engines: {node: '>= 18'} - exsolve@1.0.7: resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} @@ -8233,10 +8396,6 @@ packages: resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} engines: {node: '>= 0.8'} - finalhandler@2.1.0: - resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==} - engines: {node: '>= 0.8'} - find-up-simple@1.0.1: resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} engines: {node: '>=18'} @@ -8290,10 +8449,6 @@ packages: fontfaceobserver@2.3.0: resolution: {integrity: sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg==} - for-each@0.3.5: - resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} - engines: {node: '>= 0.4'} - foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} @@ -8319,10 +8474,6 @@ packages: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} - forwarded@0.2.0: - resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} - engines: {node: '>= 0.6'} - fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} @@ -8661,9 +8812,6 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - has-property-descriptors@1.0.2: - resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} - has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} @@ -8755,14 +8903,6 @@ packages: hls.js@1.6.11: resolution: {integrity: sha512-tdDwOAgPGXohSiNE4oxGr3CI9Hx9lsGLFe6TULUvRk2TfHS+w1tSAJntrvxsHaxvjtr6BXsDZM7NOqJFhU4mmg==} - hono@4.7.4: - resolution: {integrity: sha512-Pst8FuGqz3L7tFF+u9Pu70eI0xa5S3LPUmrNd5Jm8nTHze9FxLTK9Kaj5g/k4UcwuJSXTP65SyHOPLrffpcAJg==} - engines: {node: '>=16.9.0'} - - hono@4.9.8: - resolution: {integrity: sha512-JW8Bb4RFWD9iOKxg5PbUarBYGM99IcxFl2FPBo2gSJO11jjUDqlP1Bmfyqt8Z/dGhIQ63PMA9LdcLefXyIasyg==} - engines: {node: '>=16.9.0'} - hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} @@ -8837,6 +8977,9 @@ packages: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} + humanize-duration@3.33.1: + resolution: {integrity: sha512-hwzSCymnRdFx9YdRkQQ0OYequXiVAV6ZGQA2uzocwB0F4309Ke6pO8dg0P8LHhRQJyVjGteRTAA/zNfEcpXn8A==} + iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -8845,16 +8988,9 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} - iconv-lite@0.7.0: - resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} - engines: {node: '>=0.10.0'} - idcac-playwright@0.1.3: resolution: {integrity: sha512-VVYQ4sv6OrUJKVzYaIP1hq0qAHd1O22HW5LnL1Wf6zkrLStQ/QEg4iJ0rllIOEpd+Rmm+635AJD59A+Vw+2PgQ==} - ieee754@1.1.13: - resolution: {integrity: sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==} - ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -8883,6 +9019,9 @@ packages: resolution: {integrity: sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==} engines: {node: '>=4'} + import-in-the-middle@1.14.2: + resolution: {integrity: sha512-5tCuY9BV8ujfOpwtAGgsTx9CGUapcFMEEyByLv1B+v2+6DhAcw+Zr0nhQT7uwaZ7DiourxFEscghOR8e1aPLQw==} + import-local@3.2.0: resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} engines: {node: '>=8'} @@ -8957,10 +9096,6 @@ packages: resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} engines: {node: '>= 12'} - ipaddr.js@1.9.1: - resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} - engines: {node: '>= 0.10'} - iron-webcrypto@1.2.1: resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} @@ -8977,10 +9112,6 @@ packages: is-any-array@2.0.1: resolution: {integrity: sha512-UtilS7hLRu++wb/WBAw9bNuP1Eg04Ivn1vERJck8zJthEvXCBEBpGR/33u/xLKWEQf95803oalHrVDptcAvFdQ==} - is-arguments@1.2.0: - resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} - engines: {node: '>= 0.4'} - is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} @@ -8998,10 +9129,6 @@ packages: resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==} engines: {node: '>=6'} - is-callable@1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} - is-core-module@2.16.1: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} @@ -9035,10 +9162,6 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - is-generator-function@1.1.0: - resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} - engines: {node: '>= 0.4'} - is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -9098,16 +9221,9 @@ packages: is-promise@2.2.2: resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} - is-promise@4.0.0: - resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} - is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} - is-regex@1.2.1: - resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} - engines: {node: '>= 0.4'} - is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} @@ -9124,10 +9240,6 @@ packages: resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} engines: {node: '>=4'} - is-typed-array@1.1.15: - resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} - engines: {node: '>= 0.4'} - is-unicode-supported@0.1.0: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} @@ -9142,6 +9254,10 @@ packages: is-url@1.2.4: resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==} + is-what@4.1.16: + resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} + engines: {node: '>=12.13'} + is-whitespace@0.3.0: resolution: {integrity: sha512-RydPhl4S6JwAyj0JJjshWJEFG6hNye3pZFBRZaTUfZFwGHxzppNaNOVgQuS/E/SlhrApuMXrpnK1EEIXfdo3Dg==} engines: {node: '>=0.10.0'} @@ -9254,15 +9370,8 @@ packages: resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==} hasBin: true - jmespath@0.16.0: - resolution: {integrity: sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==} - engines: {node: '>= 0.6.0'} - - jose@4.15.9: - resolution: {integrity: sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==} - - jose@5.2.3: - resolution: {integrity: sha512-KUXdbctm1uHVL8BYhnyHkgp3zDX5KW8ZhAKVFEfUbU2P8Alpzjb+48hHvjOdQIyPshoblhzsuqOwEEAbtHVirA==} + jose@5.10.0: + resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==} jose@6.1.0: resolution: {integrity: sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA==} @@ -9607,6 +9716,9 @@ packages: resolution: {integrity: sha512-FDl1AI2sJGjHHG3XKJd6sG3/6ncgiGCQ0YkW46nxe7SfqQq6hujd9CvFXIXtkGBUN83KPZ2KSOJK8q5P0bSSRQ==} engines: {node: '>= 18'} + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -9640,10 +9752,6 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} - lru-cache@7.18.3: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} @@ -9715,6 +9823,9 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mathml-to-latex@1.5.0: + resolution: {integrity: sha512-rrWn0eEvcEcdMM4xfHcSGIy+i01DX9byOdXTLWg+w1iJ6O6ohP5UXY1dVzNUZLhzfl3EGcRekWLhY7JT5Omaew==} + md-to-react-email@5.0.4: resolution: {integrity: sha512-+XXP7QWNh2g2EGKeCSP8UIrOG45hX4mxUaxSyn1qSO+BFH2uTZ7LE5tt2TAdGAeIUuGuvzZ/o3cByulQ68OxJg==} peerDependencies: @@ -9777,17 +9888,9 @@ packages: peerDependencies: esbuild: 0.* - media-typer@1.1.0: - resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} - engines: {node: '>= 0.8'} - memoize-one@5.2.1: resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} - merge-descriptors@2.0.0: - resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} - engines: {node: '>=18'} - merge-options@3.0.4: resolution: {integrity: sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==} engines: {node: '>=10'} @@ -10036,6 +10139,9 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + minimal-polyfills@2.2.3: + resolution: {integrity: sha512-oxdmJ9cL+xV72h0xYxp4tP2d5/fTBpP45H8DIOn9pASuF8a3IYTf+25fMGDYGiWW+MFsuog6KD6nfmhZJQ+uUw==} + minimatch@10.0.3: resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==} engines: {node: 20 || >=22} @@ -10106,6 +10212,9 @@ packages: engines: {node: '>=18'} hasBin: true + module-details-from-path@1.0.4: + resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} + motion-dom@12.23.12: resolution: {integrity: sha512-RcR4fvMCTESQBD/uKQe49D5RUeDOokkGRmz4ceaJKDBgHYtZtntC/s2vLvY38gqGaytinij/yi3hMcWVcEF5Kw==} @@ -10155,6 +10264,11 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + nanostores@1.0.1: resolution: {integrity: sha512-kNZ9xnoJYKg/AfxjrVL4SS0fKX++4awQReGqWnwTRHxeHGZ1FJFVgTqr/eMrNQdp0Tz7M7tG/TDaX8QfHDwVCw==} engines: {node: ^20.0.0 || >=22.0.0} @@ -10340,10 +10454,6 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - object-hash@2.2.0: - resolution: {integrity: sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==} - engines: {node: '>= 6'} - object-hash@3.0.0: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} @@ -10365,10 +10475,6 @@ packages: ohash@2.0.11: resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} - oidc-token-hash@5.1.1: - resolution: {integrity: sha512-D7EmwxJV6DsEB6vOFLrBM2OzsVgQzgPWyHlV2OOAVj772n+WTXpudC9e9u5BVKQnYwaD30Ivhi9b+4UeBcGu9g==} - engines: {node: ^10.13.0 || >=12.0.0} - on-finished@2.3.0: resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} engines: {node: '>= 0.8'} @@ -10419,13 +10525,6 @@ packages: openapi-types@12.1.3: resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} - opencontrol@0.0.6: - resolution: {integrity: sha512-QeCrpOK5D15QV8kjnGVeD/BHFLwcVr+sn4T6KKmP0WAMs2pww56e4h+eOGHb5iPOufUQXbdbBKi6WV2kk7tefQ==} - hasBin: true - - openid-client@5.6.4: - resolution: {integrity: sha512-T1h3B10BRPKfcObdBklX639tVz+xh34O7GjofqrqiAQdm7eHsQ00ih18x6wuJ/E6FxdtS2u3FmUGPDeEcMwzNA==} - ora@3.4.0: resolution: {integrity: sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==} engines: {node: '>=6'} @@ -10570,9 +10669,6 @@ packages: parse5-parser-stream@7.1.2: resolution: {integrity: sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==} - parse5@7.2.1: - resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} - parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} @@ -10626,9 +10722,6 @@ packages: path-to-regexp@6.3.0: resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} - path-to-regexp@8.3.0: - resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} - path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -10734,10 +10827,6 @@ packages: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} - pkce-challenge@4.1.0: - resolution: {integrity: sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ==} - engines: {node: '>=16.20.0'} - pkg-dir@4.2.0: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} @@ -10770,10 +10859,6 @@ packages: resolution: {integrity: sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==} engines: {node: '>=4.0.0'} - possible-typed-array-names@1.1.0: - resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} - engines: {node: '>= 0.4'} - postcss-import@15.1.0: resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} engines: {node: '>=14.0.0'} @@ -10944,6 +11029,10 @@ packages: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} engines: {node: '>=0.4.0'} + prom-client@15.1.3: + resolution: {integrity: sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==} + engines: {node: ^16 || ^18 || >=20} + promise-worker-transferable@1.0.4: resolution: {integrity: sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw==} @@ -10966,9 +11055,9 @@ packages: proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} - proxy-addr@2.0.7: - resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} - engines: {node: '>= 0.10'} + protobufjs@7.5.4: + resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} + engines: {node: '>=12.0.0'} proxy-agent@6.5.0: resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==} @@ -10984,9 +11073,6 @@ packages: pump@3.0.2: resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} - punycode@1.3.2: - resolution: {integrity: sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==} - punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -11009,11 +11095,6 @@ packages: quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} - querystring@0.2.0: - resolution: {integrity: sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==} - engines: {node: '>=0.4.x'} - deprecated: The querystring API is considered Legacy. new code should use the URLSearchParams API instead. - queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -11045,10 +11126,6 @@ packages: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} - raw-body@3.0.1: - resolution: {integrity: sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==} - engines: {node: '>= 0.10'} - rc9@2.1.2: resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} @@ -11449,6 +11526,10 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + require-in-the-middle@7.5.2: + resolution: {integrity: sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==} + engines: {node: '>=8.6.0'} + require-package-name@2.0.1: resolution: {integrity: sha512-uuoJ1hU/k6M0779t3VMVIYpb2VMJk05cehCaABFhXaibcbvfgR8wKiozLjVFSzJPmQMRqIcO0HMyTFqfV09V6Q==} @@ -11564,10 +11645,6 @@ packages: rou3@0.7.3: resolution: {integrity: sha512-KKenF/hB2iIhS1ohj226LT+/8uKCBpSMqeS4V1UPN9vad99uLoyIhrULRRB1skaB40LQHcBlSsAi3sT8MaoDDQ==} - router@2.2.0: - resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} - engines: {node: '>= 18'} - rrweb-cssom@0.8.0: resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} @@ -11579,6 +11656,9 @@ packages: resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} engines: {node: '>=0.12.0'} + run-exclusive@2.2.19: + resolution: {integrity: sha512-K3mdoAi7tjJ/qT7Flj90L7QyPozwUaAG+CVhkdDje4HLKXUYC3N/Jzkau3flHVDLQVhiHBtcimVodMjN9egYbA==} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -11595,10 +11675,6 @@ packages: safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - safe-regex-test@1.1.0: - resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} - engines: {node: '>= 0.4'} - safe-stable-stringify@2.5.0: resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} engines: {node: '>=10'} @@ -11606,9 +11682,6 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sax@1.2.1: - resolution: {integrity: sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==} - sax@1.4.1: resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} @@ -11699,10 +11772,6 @@ packages: set-cookie-parser@2.7.1: resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} - set-function-length@1.2.2: - resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} - engines: {node: '>= 0.4'} - setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -11777,6 +11846,9 @@ packages: resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} engines: {node: '>=14.16'} + slug@6.1.0: + resolution: {integrity: sha512-x6vLHCMasg4DR2LPiyFGI0gJJhywY6DTiGhCrOMzb3SOk/0JVLIaL4UhyFSHu04SD3uAavrKY/K3zZ3i6iRcgA==} + slugify@1.6.6: resolution: {integrity: sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==} engines: {node: '>=8.0.0'} @@ -11795,6 +11867,21 @@ packages: snake-case@2.1.0: resolution: {integrity: sha512-FMR5YoPFwOLuh4rRz92dywJjyKYZNLpMn1R5ujVpIYkbA9p01fq8RMg0FkO4M+Yobt4MjHeLTJVm5xFFBHSV2Q==} + socket.io-adapter@2.5.5: + resolution: {integrity: sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==} + + socket.io-client@4.7.5: + resolution: {integrity: sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==} + engines: {node: '>=10.0.0'} + + socket.io-parser@4.2.4: + resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==} + engines: {node: '>=10.0.0'} + + socket.io@4.7.4: + resolution: {integrity: sha512-DcotgfP1Zg9iP/dH9zvAQcWrE0TtbMVwXmlV4T4mqsvY+gw+LqUGPfx2AoVyRk0FLME+GQhufDMyacFmw7ksqw==} + engines: {node: '>=10.2.0'} + socks-proxy-agent@8.0.5: resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} engines: {node: '>= 14'} @@ -11867,50 +11954,6 @@ packages: sprintf-js@1.1.3: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} - sst-darwin-arm64@3.17.13: - resolution: {integrity: sha512-HZaDReT/c+2CcEnFkYjMty63II2ckQrUniiSdoEH6eAWyU1Iy7UwKK4I2GYm+5dy9xeSBaOKga6FMdLqFxIiUg==} - cpu: [arm64] - os: [darwin] - - sst-darwin-x64@3.17.13: - resolution: {integrity: sha512-1DlYMrmrI5RY3/Ob039JatgvDKZ5QNtyRkVu0WcnsOvcxFE4dzrCiYKYHg2A+FMDl+H1qcwy2gGA3BTwC9in1w==} - cpu: [x64] - os: [darwin] - - sst-linux-arm64@3.17.13: - resolution: {integrity: sha512-A4+ZamchUdaX0pqvYWZ+r7OP1bruwEj9qgWT5kU7Q5pqaotIsEitYQi0q9nZFKH+5mXYesUwSy5FA+Q8T3X/Rg==} - cpu: [arm64] - os: [linux] - - sst-linux-x64@3.17.13: - resolution: {integrity: sha512-yhKZc5CojqjB2DnyeVka5jeRb4oc3lBx8Qf6or0w4cs47SBIqyvO0iR/3IeKvRRL1hiEUeUF8r/q83rQo9jZMw==} - cpu: [x64] - os: [linux] - - sst-linux-x86@3.17.13: - resolution: {integrity: sha512-G1FIUmpUaECB/3CV5EO/y1QmV5mQ8RUkFeZq64oyILEEaMzSWWKz0glHzQ3+p316VE74MzbktiWRqsCKQy8GeA==} - cpu: [x86] - os: [linux] - - sst-win32-arm64@3.17.13: - resolution: {integrity: sha512-9uCiIXmsGoxGeNWgM81x/d6V/vecjoiHXhBUPDGlQ1Dct1SbtHAgaf/g2ec5XwSQb9B/tne4qk81eMlTl83Z1A==} - cpu: [arm64] - os: [win32] - - sst-win32-x64@3.17.13: - resolution: {integrity: sha512-hTuj4rFSCI/9tX4NMUpNJ69Q9td/giekELDRNv03ys8TpJGoGvPT8D6VD1eX7j1CQnOZIgeEphfW9WmCXkLaIA==} - cpu: [x64] - os: [win32] - - sst-win32-x86@3.17.13: - resolution: {integrity: sha512-AuMDGux+H1kPckKJ7Szgi04EpBoOKh/v5zFNAPjvWSkcWcGZ+hsBUx3h/FO/AkGK3RnlLMRj4CQQLoa10RSSIg==} - cpu: [x86] - os: [win32] - - sst@3.17.13: - resolution: {integrity: sha512-NaNTZL7uk2AsXzPBySQy7aqXlStXorR+bA785NxvCbskwkc44nVSQcEsvX5tdsD6/jrWpw9tDy4sStv2ycLAng==} - hasBin: true - stack-trace@0.0.10: resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} @@ -12064,6 +12107,10 @@ packages: engines: {node: '>=16 || 14 >=14.17'} hasBin: true + superjson@2.2.2: + resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==} + engines: {node: '>=16'} + supports-color@10.2.0: resolution: {integrity: sha512-5eG9FQjEjDbAlI5+kdpdyPIBMRH4GfTVDGREVupaZHmVoppknhM29b/S9BkQz7cathp85BVgRi/As3Siln7e0Q==} engines: {node: '>=18'} @@ -12141,6 +12188,13 @@ packages: resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} engines: {node: '>=18'} + tdigest@0.1.2: + resolution: {integrity: sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==} + + temml@0.11.11: + resolution: {integrity: sha512-Z/Ihgwad+ges0ez6+KmKWZ3o4BYbP6aZ/cU94cVtN+DwxwqxjHgcF4Z6cb9jLkKN+aU7uni165HsIxLHs5/TqA==} + engines: {node: '>=18.13.0'} + temp-dir@2.0.0: resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} engines: {node: '>=8'} @@ -12378,6 +12432,19 @@ packages: '@swc/wasm': optional: true + tsafe@1.8.10: + resolution: {integrity: sha512-2bBiNHk6Ts4LZQ4+6OxF/BtkJ8YWqo1VMbMo6qrRIZoqAwM8xuwWUx9g3C/p6cCdUmNWeOWIaiJzgO5zWy1Cdg==} + + tsconfck@3.1.3: + resolution: {integrity: sha512-ulNZP1SVpRDesxeMLON/LtWM8HIgAJEIVpVVhBM6gsmvQ8+Rh+ZG7FWGvHh7Ah3pRABwVJWklWCr/BTZSv0xnQ==} + engines: {node: ^18 || >=20} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + tsconfck@3.1.6: resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} engines: {node: ^18 || >=20} @@ -12466,6 +12533,9 @@ packages: resolution: {integrity: sha512-gxToHmi9oTBNB05UjUsrWf0OyN5ZXtD0apOarC1KIx232Vp3WimRNy3810QzeNSgyD5rsaIDXlxlbnOzlouo+w==} hasBin: true + turndown@7.2.1: + resolution: {integrity: sha512-7YiPJw6rLClQL3oUKN3KgMaXeJJ2lAyZItclgKDurqnH61so4k4IH/qwmMva0zpuJc/FhRExBBnk7EbeFANlgQ==} + type-detect@4.0.8: resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} engines: {node: '>=4'} @@ -12493,10 +12563,6 @@ packages: type-flag@3.0.0: resolution: {integrity: sha512-3YaYwMseXCAhBB14RXW5cRQfJQlEknS6i4C8fCfeUdS3ihG9EdccdR9kt3vP73ZdeTGmPb4bZtkDn5XMIn1DLA==} - type-is@2.0.1: - resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} - engines: {node: '>= 0.6'} - typescript@5.9.2: resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} engines: {node: '>=14.17'} @@ -12517,6 +12583,10 @@ packages: resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} engines: {node: '>=18'} + ulid@2.4.0: + resolution: {integrity: sha512-fIRiVTJNcSRmXKPZtGzFQv9WRrZ3M9eoptl/teFJvjOzmpU+/K/JH6HZ8deBfb5vMEpicJcLn7JmvdknlMq7Zg==} + hasBin: true + ultrahtml@1.6.0: resolution: {integrity: sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==} @@ -12692,6 +12762,68 @@ packages: uploadthing: optional: true + unstorage@1.17.1: + resolution: {integrity: sha512-KKGwRTT0iVBCErKemkJCLs7JdxNVfqTPc/85ae1XES0+bsHbc/sFBfVi5kJp156cc51BHinIH2l3k0EZ24vOBQ==} + peerDependencies: + '@azure/app-configuration': ^1.8.0 + '@azure/cosmos': ^4.2.0 + '@azure/data-tables': ^13.3.0 + '@azure/identity': ^4.6.0 + '@azure/keyvault-secrets': ^4.9.0 + '@azure/storage-blob': ^12.26.0 + '@capacitor/preferences': ^6.0.3 || ^7.0.0 + '@deno/kv': '>=0.9.0' + '@netlify/blobs': ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0 + '@planetscale/database': ^1.19.0 + '@upstash/redis': ^1.34.3 + '@vercel/blob': '>=0.27.1' + '@vercel/functions': ^2.2.12 || ^3.0.0 + '@vercel/kv': ^1.0.1 + aws4fetch: ^1.0.20 + db0: '>=0.2.1' + idb-keyval: ^6.2.1 + ioredis: ^5.4.2 + uploadthing: ^7.4.4 + peerDependenciesMeta: + '@azure/app-configuration': + optional: true + '@azure/cosmos': + optional: true + '@azure/data-tables': + optional: true + '@azure/identity': + optional: true + '@azure/keyvault-secrets': + optional: true + '@azure/storage-blob': + optional: true + '@capacitor/preferences': + optional: true + '@deno/kv': + optional: true + '@netlify/blobs': + optional: true + '@planetscale/database': + optional: true + '@upstash/redis': + optional: true + '@vercel/blob': + optional: true + '@vercel/functions': + optional: true + '@vercel/kv': + optional: true + aws4fetch: + optional: true + db0: + optional: true + idb-keyval: + optional: true + ioredis: + optional: true + uploadthing: + optional: true + untun@0.1.3: resolution: {integrity: sha512-4luGP9LMYszMRZwsvyUd9MrxgEGZdZuZgpVQHEEX0lCYFESasVRvZd0EYpCkOIbJKHMuv0LskpXc/8Un+MJzEQ==} hasBin: true @@ -12721,9 +12853,6 @@ packages: uqr@0.1.2: resolution: {integrity: sha512-MJu7ypHq6QasgF5YRTjqscSzQp/W11zoUk6kvmlH+fmWEs63Y0Eib13hYFwAzagRJcVY8WVnlV+eBDUGMJ5IbA==} - url@0.10.3: - resolution: {integrity: sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==} - urlpattern-polyfill@10.1.0: resolution: {integrity: sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==} @@ -12758,9 +12887,6 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - util@0.12.5: - resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} - utility-types@3.11.0: resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==} engines: {node: '>= 4'} @@ -12781,10 +12907,6 @@ packages: resolution: {integrity: sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==} hasBin: true - uuid@8.0.0: - resolution: {integrity: sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==} - hasBin: true - uuid@9.0.1: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true @@ -13052,10 +13174,6 @@ packages: whatwg-url@7.1.0: resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} - which-typed-array@1.1.19: - resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} - engines: {node: '>= 0.4'} - which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -13146,8 +13264,8 @@ packages: utf-8-validate: optional: true - ws@8.18.0: - resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + ws@8.17.1: + resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -13158,8 +13276,8 @@ packages: utf-8-validate: optional: true - ws@8.18.1: - resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==} + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -13217,6 +13335,10 @@ packages: xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + xmlhttprequest-ssl@2.0.0: + resolution: {integrity: sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==} + engines: {node: '>=0.4.0'} + xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} @@ -13228,9 +13350,6 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - yallist@5.0.0: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} @@ -13288,16 +13407,20 @@ packages: resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} engines: {node: '>= 14'} - zod-to-json-schema@3.24.3: - resolution: {integrity: sha512-HIAfWdYIt1sssHfYZFCXp4rU1w2r8hVVXYIlmoa0r0gABLs5di3RCqPU5DDROogVz1pAdYBaz7HK5n9pSUNs3A==} - peerDependencies: - zod: ^3.24.1 + zod-error@1.5.0: + resolution: {integrity: sha512-zzopKZ/skI9iXpqCEPj+iLCKl9b88E43ehcU+sbRoHuwGd9F1IDVGQ70TyO6kmfiRL1g4IXkjsXK+g1gLYl4WQ==} zod-to-json-schema@3.24.6: resolution: {integrity: sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==} peerDependencies: zod: ^3.24.1 + zod-validation-error@1.5.0: + resolution: {integrity: sha512-/7eFkAI4qV0tcxMBB/3+d2c1P6jzzZYdYSlBuAklzMuCrJu5bzJfHS0yVAS87dRHVlhftd6RFJDIvv03JgkSbw==} + engines: {node: '>=16.0.0'} + peerDependencies: + zod: ^3.18.0 + zod-validation-error@3.5.3: resolution: {integrity: sha512-OT5Y8lbUadqVZCsnyFaTQ4/O2mys4tj7PqhdbBCp7McPwvIEKfPtdA6QfPeFQK2/Rz5LgwmAXRJTugBNBi0btw==} engines: {node: '>=18.0.0'} @@ -13307,9 +13430,6 @@ packages: zod@3.22.3: resolution: {integrity: sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==} - zod@3.24.2: - resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==} - zod@3.25.67: resolution: {integrity: sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==} @@ -13503,52 +13623,6 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sqs@3.891.0': - dependencies: - '@aws-crypto/sha256-browser': 5.2.0 - '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.890.0 - '@aws-sdk/credential-provider-node': 3.891.0 - '@aws-sdk/middleware-host-header': 3.891.0 - '@aws-sdk/middleware-logger': 3.891.0 - '@aws-sdk/middleware-recursion-detection': 3.891.0 - '@aws-sdk/middleware-sdk-sqs': 3.891.0 - '@aws-sdk/middleware-user-agent': 3.891.0 - '@aws-sdk/region-config-resolver': 3.890.0 - '@aws-sdk/types': 3.887.0 - '@aws-sdk/util-endpoints': 3.891.0 - '@aws-sdk/util-user-agent-browser': 3.887.0 - '@aws-sdk/util-user-agent-node': 3.891.0 - '@smithy/config-resolver': 4.2.2 - '@smithy/core': 3.11.0 - '@smithy/fetch-http-handler': 5.2.1 - '@smithy/hash-node': 4.1.1 - '@smithy/invalid-dependency': 4.1.1 - '@smithy/md5-js': 4.1.1 - '@smithy/middleware-content-length': 4.1.1 - '@smithy/middleware-endpoint': 4.2.2 - '@smithy/middleware-retry': 4.2.4 - '@smithy/middleware-serde': 4.1.1 - '@smithy/middleware-stack': 4.1.1 - '@smithy/node-config-provider': 4.2.2 - '@smithy/node-http-handler': 4.2.1 - '@smithy/protocol-http': 5.2.1 - '@smithy/smithy-client': 4.6.2 - '@smithy/types': 4.5.0 - '@smithy/url-parser': 4.1.1 - '@smithy/util-base64': 4.1.0 - '@smithy/util-body-length-browser': 4.1.0 - '@smithy/util-body-length-node': 4.1.0 - '@smithy/util-defaults-mode-browser': 4.1.2 - '@smithy/util-defaults-mode-node': 4.1.2 - '@smithy/util-endpoints': 3.1.2 - '@smithy/util-middleware': 4.1.1 - '@smithy/util-retry': 4.1.2 - '@smithy/util-utf8': 4.1.0 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - '@aws-sdk/client-sso@3.890.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 @@ -13592,49 +13666,6 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sso@3.891.0': - dependencies: - '@aws-crypto/sha256-browser': 5.2.0 - '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.890.0 - '@aws-sdk/middleware-host-header': 3.891.0 - '@aws-sdk/middleware-logger': 3.891.0 - '@aws-sdk/middleware-recursion-detection': 3.891.0 - '@aws-sdk/middleware-user-agent': 3.891.0 - '@aws-sdk/region-config-resolver': 3.890.0 - '@aws-sdk/types': 3.887.0 - '@aws-sdk/util-endpoints': 3.891.0 - '@aws-sdk/util-user-agent-browser': 3.887.0 - '@aws-sdk/util-user-agent-node': 3.891.0 - '@smithy/config-resolver': 4.2.2 - '@smithy/core': 3.11.0 - '@smithy/fetch-http-handler': 5.2.1 - '@smithy/hash-node': 4.1.1 - '@smithy/invalid-dependency': 4.1.1 - '@smithy/middleware-content-length': 4.1.1 - '@smithy/middleware-endpoint': 4.2.2 - '@smithy/middleware-retry': 4.2.4 - '@smithy/middleware-serde': 4.1.1 - '@smithy/middleware-stack': 4.1.1 - '@smithy/node-config-provider': 4.2.2 - '@smithy/node-http-handler': 4.2.1 - '@smithy/protocol-http': 5.2.1 - '@smithy/smithy-client': 4.6.2 - '@smithy/types': 4.5.0 - '@smithy/url-parser': 4.1.1 - '@smithy/util-base64': 4.1.0 - '@smithy/util-body-length-browser': 4.1.0 - '@smithy/util-body-length-node': 4.1.0 - '@smithy/util-defaults-mode-browser': 4.1.2 - '@smithy/util-defaults-mode-node': 4.1.2 - '@smithy/util-endpoints': 3.1.2 - '@smithy/util-middleware': 4.1.1 - '@smithy/util-retry': 4.1.2 - '@smithy/util-utf8': 4.1.0 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - '@aws-sdk/core@3.890.0': dependencies: '@aws-sdk/types': 3.887.0 @@ -13692,24 +13723,6 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-ini@3.891.0': - dependencies: - '@aws-sdk/core': 3.890.0 - '@aws-sdk/credential-provider-env': 3.890.0 - '@aws-sdk/credential-provider-http': 3.890.0 - '@aws-sdk/credential-provider-process': 3.890.0 - '@aws-sdk/credential-provider-sso': 3.891.0 - '@aws-sdk/credential-provider-web-identity': 3.891.0 - '@aws-sdk/nested-clients': 3.891.0 - '@aws-sdk/types': 3.887.0 - '@smithy/credential-provider-imds': 4.1.2 - '@smithy/property-provider': 4.1.1 - '@smithy/shared-ini-file-loader': 4.2.0 - '@smithy/types': 4.5.0 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - '@aws-sdk/credential-provider-node@3.890.0': dependencies: '@aws-sdk/credential-provider-env': 3.890.0 @@ -13727,23 +13740,6 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-node@3.891.0': - dependencies: - '@aws-sdk/credential-provider-env': 3.890.0 - '@aws-sdk/credential-provider-http': 3.890.0 - '@aws-sdk/credential-provider-ini': 3.891.0 - '@aws-sdk/credential-provider-process': 3.890.0 - '@aws-sdk/credential-provider-sso': 3.891.0 - '@aws-sdk/credential-provider-web-identity': 3.891.0 - '@aws-sdk/types': 3.887.0 - '@smithy/credential-provider-imds': 4.1.2 - '@smithy/property-provider': 4.1.1 - '@smithy/shared-ini-file-loader': 4.2.0 - '@smithy/types': 4.5.0 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - '@aws-sdk/credential-provider-process@3.890.0': dependencies: '@aws-sdk/core': 3.890.0 @@ -13766,19 +13762,6 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-sso@3.891.0': - dependencies: - '@aws-sdk/client-sso': 3.891.0 - '@aws-sdk/core': 3.890.0 - '@aws-sdk/token-providers': 3.891.0 - '@aws-sdk/types': 3.887.0 - '@smithy/property-provider': 4.1.1 - '@smithy/shared-ini-file-loader': 4.2.0 - '@smithy/types': 4.5.0 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - '@aws-sdk/credential-provider-web-identity@3.890.0': dependencies: '@aws-sdk/core': 3.890.0 @@ -13791,18 +13774,6 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-web-identity@3.891.0': - dependencies: - '@aws-sdk/core': 3.890.0 - '@aws-sdk/nested-clients': 3.891.0 - '@aws-sdk/types': 3.887.0 - '@smithy/property-provider': 4.1.1 - '@smithy/shared-ini-file-loader': 4.2.0 - '@smithy/types': 4.5.0 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - '@aws-sdk/middleware-host-header@3.887.0': dependencies: '@aws-sdk/types': 3.887.0 @@ -13810,25 +13781,12 @@ snapshots: '@smithy/types': 4.5.0 tslib: 2.8.1 - '@aws-sdk/middleware-host-header@3.891.0': - dependencies: - '@aws-sdk/types': 3.887.0 - '@smithy/protocol-http': 5.2.1 - '@smithy/types': 4.5.0 - tslib: 2.8.1 - '@aws-sdk/middleware-logger@3.887.0': dependencies: '@aws-sdk/types': 3.887.0 '@smithy/types': 4.5.0 tslib: 2.8.1 - '@aws-sdk/middleware-logger@3.891.0': - dependencies: - '@aws-sdk/types': 3.887.0 - '@smithy/types': 4.5.0 - tslib: 2.8.1 - '@aws-sdk/middleware-recursion-detection@3.887.0': dependencies: '@aws-sdk/types': 3.887.0 @@ -13837,14 +13795,6 @@ snapshots: '@smithy/types': 4.5.0 tslib: 2.8.1 - '@aws-sdk/middleware-recursion-detection@3.891.0': - dependencies: - '@aws-sdk/types': 3.887.0 - '@aws/lambda-invoke-store': 0.0.1 - '@smithy/protocol-http': 5.2.1 - '@smithy/types': 4.5.0 - tslib: 2.8.1 - '@aws-sdk/middleware-sdk-s3@3.890.0': dependencies: '@aws-sdk/core': 3.890.0 @@ -13862,15 +13812,6 @@ snapshots: '@smithy/util-utf8': 4.1.0 tslib: 2.8.1 - '@aws-sdk/middleware-sdk-sqs@3.891.0': - dependencies: - '@aws-sdk/types': 3.887.0 - '@smithy/smithy-client': 4.6.2 - '@smithy/types': 4.5.0 - '@smithy/util-hex-encoding': 4.1.0 - '@smithy/util-utf8': 4.1.0 - tslib: 2.8.1 - '@aws-sdk/middleware-user-agent@3.890.0': dependencies: '@aws-sdk/core': 3.890.0 @@ -13881,16 +13822,6 @@ snapshots: '@smithy/types': 4.5.0 tslib: 2.8.1 - '@aws-sdk/middleware-user-agent@3.891.0': - dependencies: - '@aws-sdk/core': 3.890.0 - '@aws-sdk/types': 3.887.0 - '@aws-sdk/util-endpoints': 3.891.0 - '@smithy/core': 3.11.0 - '@smithy/protocol-http': 5.2.1 - '@smithy/types': 4.5.0 - tslib: 2.8.1 - '@aws-sdk/nested-clients@3.890.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 @@ -13934,49 +13865,6 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/nested-clients@3.891.0': - dependencies: - '@aws-crypto/sha256-browser': 5.2.0 - '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.890.0 - '@aws-sdk/middleware-host-header': 3.891.0 - '@aws-sdk/middleware-logger': 3.891.0 - '@aws-sdk/middleware-recursion-detection': 3.891.0 - '@aws-sdk/middleware-user-agent': 3.891.0 - '@aws-sdk/region-config-resolver': 3.890.0 - '@aws-sdk/types': 3.887.0 - '@aws-sdk/util-endpoints': 3.891.0 - '@aws-sdk/util-user-agent-browser': 3.887.0 - '@aws-sdk/util-user-agent-node': 3.891.0 - '@smithy/config-resolver': 4.2.2 - '@smithy/core': 3.11.0 - '@smithy/fetch-http-handler': 5.2.1 - '@smithy/hash-node': 4.1.1 - '@smithy/invalid-dependency': 4.1.1 - '@smithy/middleware-content-length': 4.1.1 - '@smithy/middleware-endpoint': 4.2.2 - '@smithy/middleware-retry': 4.2.4 - '@smithy/middleware-serde': 4.1.1 - '@smithy/middleware-stack': 4.1.1 - '@smithy/node-config-provider': 4.2.2 - '@smithy/node-http-handler': 4.2.1 - '@smithy/protocol-http': 5.2.1 - '@smithy/smithy-client': 4.6.2 - '@smithy/types': 4.5.0 - '@smithy/url-parser': 4.1.1 - '@smithy/util-base64': 4.1.0 - '@smithy/util-body-length-browser': 4.1.0 - '@smithy/util-body-length-node': 4.1.0 - '@smithy/util-defaults-mode-browser': 4.1.2 - '@smithy/util-defaults-mode-node': 4.1.2 - '@smithy/util-endpoints': 3.1.2 - '@smithy/util-middleware': 4.1.1 - '@smithy/util-retry': 4.1.2 - '@smithy/util-utf8': 4.1.0 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - '@aws-sdk/region-config-resolver@3.890.0': dependencies: '@aws-sdk/types': 3.887.0 @@ -13989,28 +13877,16 @@ snapshots: '@aws-sdk/signature-v4-multi-region@3.890.0': dependencies: '@aws-sdk/middleware-sdk-s3': 3.890.0 - '@aws-sdk/types': 3.887.0 - '@smithy/protocol-http': 5.2.1 - '@smithy/signature-v4': 5.2.1 - '@smithy/types': 4.5.0 - tslib: 2.8.1 - - '@aws-sdk/token-providers@3.890.0': - dependencies: - '@aws-sdk/core': 3.890.0 - '@aws-sdk/nested-clients': 3.890.0 - '@aws-sdk/types': 3.887.0 - '@smithy/property-provider': 4.1.1 - '@smithy/shared-ini-file-loader': 4.2.0 + '@aws-sdk/types': 3.887.0 + '@smithy/protocol-http': 5.2.1 + '@smithy/signature-v4': 5.2.1 '@smithy/types': 4.5.0 tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - '@aws-sdk/token-providers@3.891.0': + '@aws-sdk/token-providers@3.890.0': dependencies: '@aws-sdk/core': 3.890.0 - '@aws-sdk/nested-clients': 3.891.0 + '@aws-sdk/nested-clients': 3.890.0 '@aws-sdk/types': 3.887.0 '@smithy/property-provider': 4.1.1 '@smithy/shared-ini-file-loader': 4.2.0 @@ -14036,14 +13912,6 @@ snapshots: '@smithy/util-endpoints': 3.1.2 tslib: 2.8.1 - '@aws-sdk/util-endpoints@3.891.0': - dependencies: - '@aws-sdk/types': 3.887.0 - '@smithy/types': 4.5.0 - '@smithy/url-parser': 4.1.1 - '@smithy/util-endpoints': 3.1.2 - tslib: 2.8.1 - '@aws-sdk/util-locate-window@3.873.0': dependencies: tslib: 2.8.1 @@ -14063,14 +13931,6 @@ snapshots: '@smithy/types': 4.5.0 tslib: 2.8.1 - '@aws-sdk/util-user-agent-node@3.891.0': - dependencies: - '@aws-sdk/middleware-user-agent': 3.891.0 - '@aws-sdk/types': 3.887.0 - '@smithy/node-config-provider': 4.2.2 - '@smithy/types': 4.5.0 - tslib: 2.8.1 - '@aws-sdk/xml-builder@3.887.0': dependencies: '@smithy/types': 4.5.0 @@ -14894,6 +14754,8 @@ snapshots: '@borewit/text-codec@0.1.1': {} + '@bugsnag/cuid@3.2.1': {} + '@changesets/apply-release-plan@7.0.13': dependencies: '@changesets/config': 3.1.1 @@ -15438,6 +15300,10 @@ snapshots: dependencies: '@noble/ciphers': 1.2.1 + '@electric-sql/client@1.0.0-beta.1': + optionalDependencies: + '@rollup/rollup-darwin-arm64': 4.49.0 + '@emnapi/core@1.5.0': dependencies: '@emnapi/wasi-threads': 1.1.0 @@ -16327,6 +16193,8 @@ snapshots: '@content-collections/mdx': 0.2.2(@content-collections/core@0.11.1(typescript@5.9.2))(react-dom@19.1.1(react@19.1.1))(react@19.1.1) fumadocs-core: 15.7.12(@tanstack/react-router@1.131.44(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react-router@7.5.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1) + '@google-cloud/precise-date@4.0.0': {} + '@hexagon/base64@1.1.28': {} '@hookform/resolvers@5.2.2(react-hook-form@7.62.0(react@19.1.1))': @@ -16548,6 +16416,8 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@jsonhero/path@1.0.21': {} + '@jsx-email/doiuse-email@1.0.4': dependencies: '@adobe/css-tools': 4.4.4 @@ -16668,19 +16538,8 @@ snapshots: '@mediapipe/tasks-vision@0.10.17': {} - '@modelcontextprotocol/sdk@1.6.1': - dependencies: - content-type: 1.0.5 - cors: 2.8.5 - eventsource: 3.0.7 - express: 5.1.0 - express-rate-limit: 7.5.1(express@5.1.0) - pkce-challenge: 4.1.0 - raw-body: 3.0.1 - zod: 3.25.76 - zod-to-json-schema: 3.24.6(zod@3.25.76) - transitivePeerDependencies: - - supports-color + '@mixmark-io/domino@2.2.0': + optional: true '@monogrid/gainmap-js@3.1.0(three@0.180.0)': dependencies: @@ -16825,10 +16684,104 @@ snapshots: '@oozcitak/util@8.3.8': {} + '@opentelemetry/api-logs@0.203.0': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api@1.9.0': {} + '@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.36.0 + + '@opentelemetry/exporter-logs-otlp-http@0.203.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.203.0 + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.203.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-trace-otlp-http@0.203.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.0.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/instrumentation@0.203.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.203.0 + import-in-the-middle: 1.14.2 + require-in-the-middle: 7.5.2 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/otlp-exporter-base@0.203.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.203.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-transformer@0.203.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.203.0 + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.0.1(@opentelemetry/api@1.9.0) + protobufjs: 7.5.4 + + '@opentelemetry/resources@2.0.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.36.0 + + '@opentelemetry/sdk-logs@0.203.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.203.0 + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-metrics@2.0.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.36.0 + + '@opentelemetry/sdk-trace-node@2.0.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.0.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/semantic-conventions@1.36.0': {} + '@orama/orama@3.1.13': {} + '@orama/orama@3.1.14': {} + '@orpc/arktype@1.8.9(@ark/schema@0.49.0)(@opentelemetry/api@1.9.0)(@orpc/contract@1.8.9(@opentelemetry/api@1.9.0))(arktype@2.1.22)(crossws@0.3.5)(ws@8.18.3)': dependencies: '@ark/schema': 0.49.0 @@ -17185,6 +17138,29 @@ snapshots: '@prisma/debug': 6.6.0 optional: true + '@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': {} + '@radix-ui/colors@3.0.0': {} '@radix-ui/number@1.1.1': {} @@ -18659,20 +18635,6 @@ snapshots: tslib: 2.8.1 uuid: 9.0.1 - '@smithy/core@3.11.1': - dependencies: - '@smithy/middleware-serde': 4.1.1 - '@smithy/protocol-http': 5.2.1 - '@smithy/types': 4.5.0 - '@smithy/util-base64': 4.1.0 - '@smithy/util-body-length-browser': 4.1.0 - '@smithy/util-middleware': 4.1.1 - '@smithy/util-stream': 4.3.2 - '@smithy/util-utf8': 4.1.0 - '@types/uuid': 9.0.8 - tslib: 2.8.1 - uuid: 9.0.1 - '@smithy/credential-provider-imds@4.1.2': dependencies: '@smithy/node-config-provider': 4.2.2 @@ -18709,12 +18671,6 @@ snapshots: dependencies: tslib: 2.8.1 - '@smithy/md5-js@4.1.1': - dependencies: - '@smithy/types': 4.5.0 - '@smithy/util-utf8': 4.1.0 - tslib: 2.8.1 - '@smithy/middleware-content-length@4.1.1': dependencies: '@smithy/protocol-http': 5.2.1 @@ -18732,17 +18688,6 @@ snapshots: '@smithy/util-middleware': 4.1.1 tslib: 2.8.1 - '@smithy/middleware-endpoint@4.2.3': - dependencies: - '@smithy/core': 3.11.1 - '@smithy/middleware-serde': 4.1.1 - '@smithy/node-config-provider': 4.2.2 - '@smithy/shared-ini-file-loader': 4.2.0 - '@smithy/types': 4.5.0 - '@smithy/url-parser': 4.1.1 - '@smithy/util-middleware': 4.1.1 - tslib: 2.8.1 - '@smithy/middleware-retry@4.2.2': dependencies: '@smithy/node-config-provider': 4.2.2 @@ -18756,19 +18701,6 @@ snapshots: tslib: 2.8.1 uuid: 9.0.1 - '@smithy/middleware-retry@4.2.4': - dependencies: - '@smithy/node-config-provider': 4.2.2 - '@smithy/protocol-http': 5.2.1 - '@smithy/service-error-classification': 4.1.2 - '@smithy/smithy-client': 4.6.3 - '@smithy/types': 4.5.0 - '@smithy/util-middleware': 4.1.1 - '@smithy/util-retry': 4.1.2 - '@types/uuid': 9.0.8 - tslib: 2.8.1 - uuid: 9.0.1 - '@smithy/middleware-serde@4.1.1': dependencies: '@smithy/protocol-http': 5.2.1 @@ -18820,10 +18752,6 @@ snapshots: dependencies: '@smithy/types': 4.5.0 - '@smithy/service-error-classification@4.1.2': - dependencies: - '@smithy/types': 4.5.0 - '@smithy/shared-ini-file-loader@4.2.0': dependencies: '@smithy/types': 4.5.0 @@ -18850,16 +18778,6 @@ snapshots: '@smithy/util-stream': 4.3.1 tslib: 2.8.1 - '@smithy/smithy-client@4.6.3': - dependencies: - '@smithy/core': 3.11.1 - '@smithy/middleware-endpoint': 4.2.3 - '@smithy/middleware-stack': 4.1.1 - '@smithy/protocol-http': 5.2.1 - '@smithy/types': 4.5.0 - '@smithy/util-stream': 4.3.2 - tslib: 2.8.1 - '@smithy/types@4.5.0': dependencies: tslib: 2.8.1 @@ -18937,12 +18855,6 @@ snapshots: '@smithy/types': 4.5.0 tslib: 2.8.1 - '@smithy/util-retry@4.1.2': - dependencies: - '@smithy/service-error-classification': 4.1.2 - '@smithy/types': 4.5.0 - tslib: 2.8.1 - '@smithy/util-stream@4.3.1': dependencies: '@smithy/fetch-http-handler': 5.2.1 @@ -18954,17 +18866,6 @@ snapshots: '@smithy/util-utf8': 4.1.0 tslib: 2.8.1 - '@smithy/util-stream@4.3.2': - dependencies: - '@smithy/fetch-http-handler': 5.2.1 - '@smithy/node-http-handler': 4.2.1 - '@smithy/types': 4.5.0 - '@smithy/util-base64': 4.1.0 - '@smithy/util-buffer-from': 4.1.0 - '@smithy/util-hex-encoding': 4.1.0 - '@smithy/util-utf8': 4.1.0 - tslib: 2.8.1 - '@smithy/util-uri-escape@4.1.0': dependencies: tslib: 2.8.1 @@ -18979,6 +18880,8 @@ snapshots: '@smithy/util-buffer-from': 4.1.0 tslib: 2.8.1 + '@socket.io/component-emitter@3.1.2': {} + '@speed-highlight/core@1.2.7': {} '@standard-schema/spec@1.0.0': {} @@ -19479,14 +19382,85 @@ snapshots: '@tootallnate/quickjs-emscripten@0.23.0': {} + '@trigger.dev/build@4.0.2(typescript@5.9.2)': + dependencies: + '@trigger.dev/core': 4.0.2 + pkg-types: 1.3.1 + tinyglobby: 0.2.15 + tsconfck: 3.1.3(typescript@5.9.2) + transitivePeerDependencies: + - bufferutil + - supports-color + - typescript + - utf-8-validate + + '@trigger.dev/core@4.0.2': + dependencies: + '@bugsnag/cuid': 3.2.1 + '@electric-sql/client': 1.0.0-beta.1 + '@google-cloud/precise-date': 4.0.0 + '@jsonhero/path': 1.0.21 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.203.0 + '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-http': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-http': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-node': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.36.0 + dequal: 2.0.3 + eventsource: 3.0.7 + eventsource-parser: 3.0.6 + execa: 8.0.1 + humanize-duration: 3.33.1 + jose: 5.10.0 + nanoid: 3.3.8 + prom-client: 15.1.3 + socket.io: 4.7.4 + socket.io-client: 4.7.5 + std-env: 3.9.0 + superjson: 2.2.2 + tinyexec: 0.3.2 + uncrypto: 0.1.3 + zod: 3.25.76 + zod-error: 1.5.0 + zod-validation-error: 1.5.0(zod@3.25.76) + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@trigger.dev/sdk@4.0.2(ai@5.0.45(zod@4.1.9))(zod@4.1.9)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.36.0 + '@trigger.dev/core': 4.0.2 + chalk: 5.6.2 + cronstrue: 2.59.0 + debug: 4.4.1 + evt: 2.5.9 + slug: 6.1.0 + ulid: 2.4.0 + uncrypto: 0.1.3 + uuid: 9.0.1 + ws: 8.18.3 + zod: 4.1.9 + optionalDependencies: + ai: 5.0.45(zod@4.1.9) + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + '@ts-morph/common@0.27.0': dependencies: fast-glob: 3.3.3 minimatch: 10.0.3 path-browserify: 1.0.1 - '@tsconfig/bun@1.0.7': {} - '@tsconfig/node10@1.0.11': {} '@tsconfig/node12@1.0.11': {} @@ -19538,8 +19512,6 @@ snapshots: '@types/aria-query@5.0.4': {} - '@types/aws-lambda@8.10.152': {} - '@types/babel__code-frame@7.0.6': {} '@types/babel__core@7.20.5': @@ -19569,6 +19541,12 @@ snapshots: '@types/content-type@1.1.9': {} + '@types/cookie@0.4.1': {} + + '@types/cors@2.8.19': + dependencies: + '@types/node': 24.5.1 + '@types/d3-array@3.2.1': {} '@types/d3-color@3.1.3': {} @@ -20125,11 +20103,6 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 - accepts@2.0.0: - dependencies: - mime-types: 3.0.1 - negotiator: 1.0.0 - acorn-import-attributes@1.9.5(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -20312,27 +20285,7 @@ snapshots: postcss: 8.5.6 postcss-value-parser: 4.2.0 - available-typed-arrays@1.0.7: - dependencies: - possible-typed-array-names: 1.1.0 - - aws-sdk@2.1692.0: - dependencies: - buffer: 4.9.2 - events: 1.1.1 - ieee754: 1.1.13 - jmespath: 0.16.0 - querystring: 0.2.0 - sax: 1.2.1 - url: 0.10.3 - util: 0.12.5 - uuid: 8.0.0 - xml2js: 0.6.2 - - aws4fetch@1.0.18: {} - - aws4fetch@1.0.20: - optional: true + aws4fetch@1.0.20: {} axios@1.8.3(debug@4.4.0): dependencies: @@ -20491,6 +20444,8 @@ snapshots: base64-js@1.5.1: {} + base64id@2.0.0: {} + baseline-browser-mapping@2.8.4: {} basic-ftp@5.0.5: {} @@ -20550,6 +20505,8 @@ snapshots: dependencies: file-uri-to-path: 1.0.0 + bintrees@1.0.2: {} + bl@4.1.0: dependencies: buffer: 5.7.1 @@ -20558,20 +20515,6 @@ snapshots: blake3-wasm@2.1.5: {} - body-parser@2.2.0: - dependencies: - bytes: 3.1.2 - content-type: 1.0.5 - debug: 4.4.1 - http-errors: 2.0.0 - iconv-lite: 0.6.3 - on-finished: 2.4.1 - qs: 6.14.0 - raw-body: 3.0.1 - type-is: 2.0.1 - transitivePeerDependencies: - - supports-color - boolbase@1.0.0: {} bowser@2.12.1: {} @@ -20626,12 +20569,6 @@ snapshots: buffer-from@1.1.2: {} - buffer@4.9.2: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - isarray: 1.0.0 - buffer@5.7.1: dependencies: base64-js: 1.5.1 @@ -20687,13 +20624,6 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 - call-bind@1.0.8: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - get-intrinsic: 1.3.0 - set-function-length: 1.2.2 - call-bound@1.0.4: dependencies: call-bind-apply-helpers: 1.0.2 @@ -20770,6 +20700,8 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + chalk@5.6.2: {} + change-case@3.1.0: dependencies: camel-case: 3.0.0 @@ -20890,6 +20822,8 @@ snapshots: dependencies: consola: 3.4.2 + cjs-module-lexer@1.4.3: {} + class-variance-authority@0.7.1: dependencies: clsx: 2.1.1 @@ -21076,10 +21010,6 @@ snapshots: snake-case: 2.1.0 upper-case: 1.1.3 - content-disposition@1.0.0: - dependencies: - safe-buffer: 5.2.1 - content-type@1.0.5: {} convert-source-map@2.0.0: {} @@ -21088,12 +21018,14 @@ snapshots: cookie-es@2.0.0: {} - cookie-signature@1.2.2: {} - - cookie@0.7.2: {} + cookie@0.4.2: {} cookie@1.0.2: {} + copy-anything@3.0.5: + dependencies: + is-what: 4.1.16 + copy-file@11.1.0: dependencies: graceful-fs: 4.2.11 @@ -21160,6 +21092,8 @@ snapshots: croner@9.1.0: {} + cronstrue@2.59.0: {} + cross-env@7.0.3: dependencies: cross-spawn: 7.0.6 @@ -21261,6 +21195,10 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.3.7: + dependencies: + ms: 2.1.3 + debug@4.4.0: dependencies: ms: 2.1.3 @@ -21297,16 +21235,18 @@ snapshots: defer-to-connect@2.0.1: {} - define-data-property@1.1.4: - dependencies: - es-define-property: 1.0.1 - es-errors: 1.3.0 - gopd: 1.2.0 - define-lazy-prop@2.0.0: {} defu@6.1.4: {} + defuddle@0.6.6(jsdom@26.1.0): + dependencies: + jsdom: 26.1.0 + optionalDependencies: + mathml-to-latex: 1.5.0 + temml: 0.11.11 + turndown: 7.2.1 + degenerator@5.0.1: dependencies: ast-types: 0.13.4 @@ -21569,9 +21509,40 @@ snapshots: iconv-lite: 0.6.3 whatwg-encoding: 3.1.1 - end-of-stream@1.4.4: + end-of-stream@1.4.4: + dependencies: + once: 1.4.0 + + engine.io-client@6.5.4: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.7 + engine.io-parser: 5.2.3 + ws: 8.17.1 + xmlhttprequest-ssl: 2.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + engine.io-parser@5.2.3: {} + + engine.io@6.5.5: dependencies: - once: 1.4.0 + '@types/cookie': 0.4.1 + '@types/cors': 2.8.19 + '@types/node': 24.5.1 + accepts: 1.3.8 + base64id: 2.0.0 + cookie: 0.4.2 + cors: 2.8.5 + debug: 4.3.7 + engine.io-parser: 5.2.3 + ws: 8.17.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate enhanced-resolve@5.18.3: dependencies: @@ -21928,8 +21899,6 @@ snapshots: eventemitter3@4.0.7: {} - events@1.1.1: {} - events@3.3.0: {} eventsource-parser@3.0.6: {} @@ -21938,6 +21907,12 @@ snapshots: dependencies: eventsource-parser: 3.0.6 + evt@2.5.9: + dependencies: + minimal-polyfills: 2.2.3 + run-exclusive: 2.2.19 + tsafe: 1.8.10 + exec-async@2.2.0: {} execa@5.1.1: @@ -22082,42 +22057,6 @@ snapshots: exponential-backoff@3.1.2: {} - express-rate-limit@7.5.1(express@5.1.0): - dependencies: - express: 5.1.0 - - express@5.1.0: - dependencies: - accepts: 2.0.0 - body-parser: 2.2.0 - content-disposition: 1.0.0 - content-type: 1.0.5 - cookie: 0.7.2 - cookie-signature: 1.2.2 - debug: 4.4.1 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 2.1.0 - fresh: 2.0.0 - http-errors: 2.0.0 - merge-descriptors: 2.0.0 - mime-types: 3.0.1 - on-finished: 2.4.1 - once: 1.4.0 - parseurl: 1.3.3 - proxy-addr: 2.0.7 - qs: 6.14.0 - range-parser: 1.2.1 - router: 2.2.0 - send: 1.2.0 - serve-static: 2.2.0 - statuses: 2.0.2 - type-is: 2.0.1 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - exsolve@1.0.7: {} extend-shallow@2.0.1: @@ -22247,17 +22186,6 @@ snapshots: transitivePeerDependencies: - supports-color - finalhandler@2.1.0: - dependencies: - debug: 4.4.1 - encodeurl: 2.0.0 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.2 - transitivePeerDependencies: - - supports-color - find-up-simple@1.0.1: {} find-up@4.1.0: @@ -22305,10 +22233,6 @@ snapshots: fontfaceobserver@2.3.0: {} - for-each@0.3.5: - dependencies: - is-callable: 1.2.7 - foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 @@ -22333,8 +22257,6 @@ snapshots: dependencies: fetch-blob: 3.2.0 - forwarded@0.2.0: {} - fraction.js@4.3.7: {} framer-motion@11.12.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1): @@ -22760,10 +22682,6 @@ snapshots: has-flag@4.0.0: {} - has-property-descriptors@1.0.2: - dependencies: - es-define-property: 1.0.1 - has-symbols@1.1.0: {} has-tostringtag@1.0.2: @@ -22942,7 +22860,7 @@ snapshots: header-generator@2.1.72: dependencies: - browserslist: 4.24.4 + browserslist: 4.26.2 generative-bayesian-network: 2.1.72 ow: 0.28.2 tslib: 2.8.1 @@ -22955,10 +22873,6 @@ snapshots: hls.js@1.6.11: {} - hono@4.7.4: {} - - hono@4.9.8: {} - hookable@5.5.3: {} hosted-git-info@7.0.2: @@ -23045,6 +22959,8 @@ snapshots: human-signals@5.0.0: {} + humanize-duration@3.33.1: {} + iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 @@ -23053,14 +22969,8 @@ snapshots: dependencies: safer-buffer: 2.1.2 - iconv-lite@0.7.0: - dependencies: - safer-buffer: 2.1.2 - idcac-playwright@0.1.3: {} - ieee754@1.1.13: {} - ieee754@1.2.1: {} ignore@5.3.2: {} @@ -23080,6 +22990,13 @@ snapshots: caller-path: 2.0.0 resolve-from: 3.0.0 + import-in-the-middle@1.14.2: + dependencies: + acorn: 8.15.0 + acorn-import-attributes: 1.9.5(acorn@8.15.0) + cjs-module-lexer: 1.4.3 + module-details-from-path: 1.0.4 + import-local@3.2.0: dependencies: pkg-dir: 4.2.0 @@ -23188,8 +23105,6 @@ snapshots: jsbn: 1.1.0 sprintf-js: 1.1.3 - ipaddr.js@1.9.1: {} - iron-webcrypto@1.2.1: {} is-absolute-url@4.0.1: {} @@ -23203,11 +23118,6 @@ snapshots: is-any-array@2.0.1: {} - is-arguments@1.2.0: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - is-arrayish@0.2.1: {} is-arrayish@0.3.2: {} @@ -23222,8 +23132,6 @@ snapshots: dependencies: builtin-modules: 3.3.0 - is-callable@1.2.7: {} - is-core-module@2.16.1: dependencies: hasown: 2.0.2 @@ -23242,13 +23150,6 @@ snapshots: is-fullwidth-code-point@3.0.0: {} - is-generator-function@1.1.0: - dependencies: - call-bound: 1.0.4 - get-proto: 1.0.1 - has-tostringtag: 1.0.2 - safe-regex-test: 1.1.0 - is-glob@4.0.3: dependencies: is-extglob: 2.1.1 @@ -23287,19 +23188,10 @@ snapshots: is-promise@2.2.2: {} - is-promise@4.0.0: {} - is-reference@1.2.1: dependencies: '@types/estree': 1.0.8 - is-regex@1.2.1: - dependencies: - call-bound: 1.0.4 - gopd: 1.2.0 - has-tostringtag: 1.0.2 - hasown: 2.0.2 - is-stream@2.0.1: {} is-stream@3.0.0: {} @@ -23310,10 +23202,6 @@ snapshots: dependencies: better-path-resolve: 1.0.0 - is-typed-array@1.1.15: - dependencies: - which-typed-array: 1.1.19 - is-unicode-supported@0.1.0: {} is-upper-case@1.1.2: @@ -23324,6 +23212,8 @@ snapshots: is-url@1.2.4: {} + is-what@4.1.16: {} + is-whitespace@0.3.0: {} is-windows@1.0.2: {} @@ -23469,11 +23359,7 @@ snapshots: jiti@2.5.1: {} - jmespath@0.16.0: {} - - jose@4.15.9: {} - - jose@5.2.3: {} + jose@5.10.0: {} jose@6.1.0: {} @@ -23518,7 +23404,7 @@ snapshots: https-proxy-agent: 7.0.6 is-potential-custom-element-name: 1.0.1 nwsapi: 2.2.18 - parse5: 7.2.1 + parse5: 7.3.0 rrweb-cssom: 0.8.0 saxes: 6.0.0 symbol-tree: 3.2.4 @@ -23528,7 +23414,7 @@ snapshots: whatwg-encoding: 3.1.1 whatwg-mimetype: 4.0.0 whatwg-url: 14.2.0 - ws: 8.18.1 + ws: 8.18.3 xml-name-validator: 5.0.0 transitivePeerDependencies: - bufferutil @@ -23859,6 +23745,8 @@ snapshots: loglevelnext@6.0.0: {} + long@5.3.2: {} + longest-streak@3.1.0: {} loose-envify@1.4.0: @@ -23885,10 +23773,6 @@ snapshots: dependencies: yallist: 3.1.1 - lru-cache@6.0.0: - dependencies: - yallist: 4.0.0 - lru-cache@7.18.3: {} lucide-react@0.544.0(react@19.1.1): @@ -23949,6 +23833,11 @@ snapshots: math-intrinsics@1.1.0: {} + mathml-to-latex@1.5.0: + dependencies: + '@xmldom/xmldom': 0.8.11 + optional: true + md-to-react-email@5.0.4(react@19.1.1): dependencies: marked: 7.0.4 @@ -24143,12 +24032,8 @@ snapshots: transitivePeerDependencies: - supports-color - media-typer@1.1.0: {} - memoize-one@5.2.1: {} - merge-descriptors@2.0.0: {} - merge-options@3.0.4: dependencies: is-plain-obj: 2.1.0 @@ -24662,6 +24547,8 @@ snapshots: - bufferutil - utf-8-validate + minimal-polyfills@2.2.3: {} + minimatch@10.0.3: dependencies: '@isaacs/brace-expansion': 5.0.0 @@ -24736,6 +24623,8 @@ snapshots: ast-module-types: 6.0.1 node-source-walk: 7.0.1 + module-details-from-path@1.0.4: {} + motion-dom@12.23.12: dependencies: motion-utils: 12.23.6 @@ -24770,6 +24659,8 @@ snapshots: nanoid@3.3.11: {} + nanoid@3.3.8: {} + nanostores@1.0.1: {} napi-build-utils@2.0.0: @@ -25026,8 +24917,6 @@ snapshots: object-assign@4.1.1: {} - object-hash@2.2.0: {} - object-hash@3.0.0: {} object-inspect@1.13.4: {} @@ -25044,8 +24933,6 @@ snapshots: ohash@2.0.11: {} - oidc-token-hash@5.1.1: {} - on-finished@2.3.0: dependencies: ee-first: 1.1.1 @@ -25103,23 +24990,6 @@ snapshots: openapi-types@12.1.3: {} - opencontrol@0.0.6: - dependencies: - '@modelcontextprotocol/sdk': 1.6.1 - '@tsconfig/bun': 1.0.7 - hono: 4.7.4 - zod: 3.24.2 - zod-to-json-schema: 3.24.3(zod@3.24.2) - transitivePeerDependencies: - - supports-color - - openid-client@5.6.4: - dependencies: - jose: 4.15.9 - lru-cache: 6.0.0 - object-hash: 2.2.0 - oidc-token-hash: 5.1.1 - ora@3.4.0: dependencies: chalk: 2.4.2 @@ -25318,10 +25188,6 @@ snapshots: dependencies: parse5: 7.3.0 - parse5@7.2.1: - dependencies: - entities: 4.5.0 - parse5@7.3.0: dependencies: entities: 6.0.1 @@ -25368,8 +25234,6 @@ snapshots: path-to-regexp@6.3.0: {} - path-to-regexp@8.3.0: {} - path-type@4.0.0: {} path-type@5.0.0: {} @@ -25452,8 +25316,6 @@ snapshots: pirates@4.0.7: {} - pkce-challenge@4.1.0: {} - pkg-dir@4.2.0: dependencies: find-up: 4.1.0 @@ -25488,8 +25350,6 @@ snapshots: pngjs@3.4.0: {} - possible-typed-array-names@1.1.0: {} - postcss-import@15.1.0(postcss@8.5.6): dependencies: postcss: 8.5.6 @@ -25662,6 +25522,11 @@ snapshots: progress@2.0.3: {} + prom-client@15.1.3: + dependencies: + '@opentelemetry/api': 1.9.0 + tdigest: 0.1.2 + promise-worker-transferable@1.0.4: dependencies: is-promise: 2.2.2 @@ -25692,10 +25557,20 @@ snapshots: proto-list@1.2.4: {} - proxy-addr@2.0.7: - dependencies: - forwarded: 0.2.0 - ipaddr.js: 1.9.1 + 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': 24.5.1 + long: 5.3.2 proxy-agent@6.5.0: dependencies: @@ -25725,8 +25600,6 @@ snapshots: end-of-stream: 1.4.4 once: 1.4.0 - punycode@1.3.2: {} - punycode@2.3.1: {} pvtsutils@1.3.6: @@ -25743,8 +25616,6 @@ snapshots: quansync@0.2.11: {} - querystring@0.2.0: {} - queue-microtask@1.2.3: {} queue@6.0.2: @@ -25767,13 +25638,6 @@ snapshots: range-parser@1.2.1: {} - raw-body@3.0.1: - dependencies: - bytes: 3.1.2 - http-errors: 2.0.0 - iconv-lite: 0.7.0 - unpipe: 1.0.0 - rc9@2.1.2: dependencies: defu: 6.1.4 @@ -26393,6 +26257,14 @@ snapshots: require-from-string@2.0.2: {} + require-in-the-middle@7.5.2: + dependencies: + debug: 4.4.1 + module-details-from-path: 1.0.4 + resolve: 1.22.10 + transitivePeerDependencies: + - supports-color + require-package-name@2.0.1: {} requireg@0.2.2: @@ -26527,22 +26399,16 @@ snapshots: rou3@0.7.3: {} - router@2.2.0: - dependencies: - debug: 4.4.1 - depd: 2.0.0 - is-promise: 4.0.0 - parseurl: 1.3.3 - path-to-regexp: 8.3.0 - transitivePeerDependencies: - - supports-color - rrweb-cssom@0.8.0: {} run-async@2.4.1: {} run-async@3.0.0: {} + run-exclusive@2.2.19: + dependencies: + minimal-polyfills: 2.2.3 + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -26559,18 +26425,10 @@ snapshots: safe-buffer@5.2.1: {} - safe-regex-test@1.1.0: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-regex: 1.2.1 - safe-stable-stringify@2.5.0: {} safer-buffer@2.1.2: {} - sax@1.2.1: {} - sax@1.4.1: {} saxes@6.0.0: @@ -26703,15 +26561,6 @@ snapshots: set-cookie-parser@2.7.1: {} - set-function-length@1.2.2: - dependencies: - define-data-property: 1.1.4 - es-errors: 1.3.0 - function-bind: 1.1.2 - get-intrinsic: 1.3.0 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 - setprototypeof@1.2.0: {} sharp@0.33.5: @@ -26830,6 +26679,8 @@ snapshots: slash@5.1.0: {} + slug@6.1.0: {} + slugify@1.6.6: {} smart-buffer@4.2.0: {} @@ -26842,6 +26693,47 @@ snapshots: dependencies: no-case: 2.3.2 + socket.io-adapter@2.5.5: + dependencies: + debug: 4.3.7 + ws: 8.17.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + socket.io-client@4.7.5: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.7 + engine.io-client: 6.5.4 + socket.io-parser: 4.2.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + socket.io-parser@4.2.4: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.7 + transitivePeerDependencies: + - supports-color + + socket.io@4.7.4: + dependencies: + accepts: 1.3.8 + base64id: 2.0.0 + cors: 2.8.5 + debug: 4.3.7 + engine.io: 6.5.5 + socket.io-adapter: 2.5.5 + socket.io-parser: 4.2.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + socks-proxy-agent@8.0.5: dependencies: agent-base: 7.1.3 @@ -26915,49 +26807,6 @@ snapshots: sprintf-js@1.1.3: {} - sst-darwin-arm64@3.17.13: - optional: true - - sst-darwin-x64@3.17.13: - optional: true - - sst-linux-arm64@3.17.13: - optional: true - - sst-linux-x64@3.17.13: - optional: true - - sst-linux-x86@3.17.13: - optional: true - - sst-win32-arm64@3.17.13: - optional: true - - sst-win32-x64@3.17.13: - optional: true - - sst-win32-x86@3.17.13: - optional: true - - sst@3.17.13: - dependencies: - aws-sdk: 2.1692.0 - aws4fetch: 1.0.18 - jose: 5.2.3 - opencontrol: 0.0.6 - openid-client: 5.6.4 - optionalDependencies: - sst-darwin-arm64: 3.17.13 - sst-darwin-x64: 3.17.13 - sst-linux-arm64: 3.17.13 - sst-linux-x64: 3.17.13 - sst-linux-x86: 3.17.13 - sst-win32-arm64: 3.17.13 - sst-win32-x64: 3.17.13 - sst-win32-x86: 3.17.13 - transitivePeerDependencies: - - supports-color - stack-trace@0.0.10: {} stack-utils@2.0.6: @@ -27101,6 +26950,10 @@ snapshots: pirates: 4.0.7 ts-interface-checker: 0.1.13 + superjson@2.2.2: + dependencies: + copy-anything: 3.0.5 + supports-color@10.2.0: {} supports-color@2.0.0: {} @@ -27206,6 +27059,13 @@ snapshots: mkdirp: 3.0.1 yallist: 5.0.0 + tdigest@0.1.2: + dependencies: + bintrees: 1.0.2 + + temml@0.11.11: + optional: true + temp-dir@2.0.0: {} term-size@2.2.1: {} @@ -27424,6 +27284,12 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 + tsafe@1.8.10: {} + + tsconfck@3.1.3(typescript@5.9.2): + optionalDependencies: + typescript: 5.9.2 + tsconfck@3.1.6(typescript@5.9.2): optionalDependencies: typescript: 5.9.2 @@ -27516,6 +27382,11 @@ snapshots: turbo-windows-64: 2.5.6 turbo-windows-arm64: 2.5.6 + turndown@7.2.1: + dependencies: + '@mixmark-io/domino': 2.2.0 + optional: true + type-detect@4.0.8: {} type-fest@0.21.3: {} @@ -27530,12 +27401,6 @@ snapshots: type-flag@3.0.0: {} - type-is@2.0.1: - dependencies: - content-type: 1.0.5 - media-typer: 1.1.0 - mime-types: 3.0.1 - typescript@5.9.2: {} ufo@1.6.1: {} @@ -27547,6 +27412,8 @@ snapshots: uint8array-extras@1.5.0: {} + ulid@2.4.0: {} + ultrahtml@1.6.0: {} uncrypto@0.1.3: {} @@ -27701,6 +27568,22 @@ snapshots: db0: 0.3.2(better-sqlite3@11.9.1)(drizzle-orm@0.44.5(@cloudflare/workers-types@4.20250414.0)(@opentelemetry/api@1.9.0)(@prisma/client@6.6.0(prisma@6.6.0(typescript@5.9.2))(typescript@5.9.2))(@types/pg@8.15.5)(better-sqlite3@11.9.1)(gel@2.0.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7)(prisma@6.6.0(typescript@5.9.2))) ioredis: 5.7.0 + unstorage@1.17.1(@netlify/blobs@9.1.2)(aws4fetch@1.0.20)(db0@0.3.2(better-sqlite3@11.9.1)(drizzle-orm@0.44.5(@cloudflare/workers-types@4.20250414.0)(@opentelemetry/api@1.9.0)(@prisma/client@6.6.0(prisma@6.6.0(typescript@5.9.2))(typescript@5.9.2))(@types/pg@8.15.5)(better-sqlite3@11.9.1)(gel@2.0.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7)(prisma@6.6.0(typescript@5.9.2))))(ioredis@5.7.0): + dependencies: + anymatch: 3.1.3 + chokidar: 4.0.3 + destr: 2.0.5 + h3: 1.15.4 + lru-cache: 10.4.3 + node-fetch-native: 1.6.7 + ofetch: 1.4.1 + ufo: 1.6.1 + optionalDependencies: + '@netlify/blobs': 9.1.2 + aws4fetch: 1.0.20 + db0: 0.3.2(better-sqlite3@11.9.1)(drizzle-orm@0.44.5(@cloudflare/workers-types@4.20250414.0)(@opentelemetry/api@1.9.0)(@prisma/client@6.6.0(prisma@6.6.0(typescript@5.9.2))(typescript@5.9.2))(@types/pg@8.15.5)(better-sqlite3@11.9.1)(gel@2.0.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7)(prisma@6.6.0(typescript@5.9.2))) + ioredis: 5.7.0 + untun@0.1.3: dependencies: citty: 0.1.6 @@ -27749,11 +27632,6 @@ snapshots: uqr@0.1.2: {} - url@0.10.3: - dependencies: - punycode: 1.3.2 - querystring: 0.2.0 - urlpattern-polyfill@10.1.0: {} urlpattern-polyfill@8.0.2: {} @@ -27779,14 +27657,6 @@ snapshots: util-deprecate@1.0.2: {} - util@0.12.5: - dependencies: - inherits: 2.0.4 - is-arguments: 1.2.0 - is-generator-function: 1.1.0 - is-typed-array: 1.1.15 - which-typed-array: 1.1.19 - utility-types@3.11.0: {} utils-merge@1.0.1: {} @@ -27797,8 +27667,6 @@ snapshots: uuid@7.0.3: {} - uuid@8.0.0: {} - uuid@9.0.1: {} v8-compile-cache-lib@3.0.1: {} @@ -28081,16 +27949,6 @@ snapshots: tr46: 1.0.1 webidl-conversions: 4.0.2 - which-typed-array@1.1.19: - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.8 - call-bound: 1.0.4 - for-each: 0.3.5 - get-proto: 1.0.1 - gopd: 1.2.0 - has-tostringtag: 1.0.2 - which@2.0.2: dependencies: isexe: 2.0.0 @@ -28189,9 +28047,9 @@ snapshots: ws@7.5.10: {} - ws@8.18.0: {} + ws@8.17.1: {} - ws@8.18.1: {} + ws@8.18.0: {} ws@8.18.3: {} @@ -28215,6 +28073,7 @@ snapshots: dependencies: sax: 1.4.1 xmlbuilder: 11.0.1 + optional: true xmlbuilder2@3.1.1: dependencies: @@ -28229,14 +28088,14 @@ snapshots: xmlchars@2.2.0: {} + xmlhttprequest-ssl@2.0.0: {} + xtend@4.0.2: {} y18n@5.0.8: {} yallist@3.1.1: {} - yallist@4.0.0: {} - yallist@5.0.0: {} yaml@2.8.1: {} @@ -28301,22 +28160,24 @@ snapshots: compress-commons: 6.0.2 readable-stream: 4.7.0 - zod-to-json-schema@3.24.3(zod@3.24.2): + zod-error@1.5.0: dependencies: - zod: 3.24.2 + zod: 3.25.76 zod-to-json-schema@3.24.6(zod@3.25.76): dependencies: zod: 3.25.76 + zod-validation-error@1.5.0(zod@3.25.76): + dependencies: + zod: 3.25.76 + zod-validation-error@3.5.3(zod@3.25.76): dependencies: zod: 3.25.76 zod@3.22.3: {} - zod@3.24.2: {} - zod@3.25.67: {} zod@3.25.76: {} From 4d57fb8a119b35042c7b09af429a810d98696536 Mon Sep 17 00:00:00 2001 From: ElasticBottle Date: Fri, 19 Sep 2025 06:44:56 +0800 Subject: [PATCH 10/38] feat(task): expose api --- packages/task/package.json | 17 +++++------ packages/task/src/client.ts | 16 +++++++++++ packages/task/src/crawlers/site.ts | 5 ++-- packages/task/src/env.ts | 3 +- packages/task/src/trigger/site-crawl.ts | 15 ---------- packages/task/src/trigger/understand-site.ts | 30 ++++++++++++++++++++ 6 files changed, 58 insertions(+), 28 deletions(-) create mode 100644 packages/task/src/client.ts delete mode 100644 packages/task/src/trigger/site-crawl.ts create mode 100644 packages/task/src/trigger/understand-site.ts diff --git a/packages/task/package.json b/packages/task/package.json index 6ae00ede8..24d2400b5 100644 --- a/packages/task/package.json +++ b/packages/task/package.json @@ -4,17 +4,17 @@ "version": "0.0.1", "type": "module", "exports": { - "./site-crawl": { - "default": "./src/site-crawl.ts", - "types": "./dist/site-crawl.d.ts" - }, "./env": { "default": "./src/env.ts", "types": "./dist/src/env.d.ts" }, - "./schema": { - "default": "./src/schema/site-crawl.ts", - "types": "./dist/schema/site-crawl.d.ts" + "./client": { + "default": "./src/client.ts", + "types": "./dist/client.d.ts" + }, + "./schema/*": { + "default": "./src/schema/*.ts", + "types": "./dist/schema/*.d.ts" } }, "scripts": { @@ -36,11 +36,8 @@ "@t3-oss/env-core": "^0.13.8", "@trigger.dev/sdk": "4.0.2", "arktype": "^2.1.22", - "aws4fetch": "^1.0.20", "crawlee": "^3.14.1", "defuddle": "^0.6.6", - "glob": "^11.0.3", - "gpt-tokenizer": "^3.0.1", "jsdom": "^26.1.0", "playwright": "1.55.0", "unstorage": "^1.17.1" diff --git a/packages/task/src/client.ts b/packages/task/src/client.ts new file mode 100644 index 000000000..075e66488 --- /dev/null +++ b/packages/task/src/client.ts @@ -0,0 +1,16 @@ +import { type RetrieveRunResult, runs, tasks } from "@trigger.dev/sdk"; +import type { understandSiteTask } from "./trigger/understand-site"; + +export * from "@trigger.dev/sdk"; + +export const triggerUnderstandSiteTask = ( + siteUrl: string, +): Promise<{ id: string }> => + tasks.trigger("understand-site", { + startUrl: siteUrl, + }); + +export const getUnderstandSiteTask = ( + taskId: string, +): Promise> => + runs.retrieve(taskId); diff --git a/packages/task/src/crawlers/site.ts b/packages/task/src/crawlers/site.ts index 104c98314..f3c66d424 100644 --- a/packages/task/src/crawlers/site.ts +++ b/packages/task/src/crawlers/site.ts @@ -55,7 +55,7 @@ export async function crawlSite(input: typeof SiteCrawlInputSchema.infer) { }); const proxyConfiguration = new ProxyConfiguration({ - proxyUrls: [], + tieredProxyUrls: [[null]], }); const crawler = new PlaywrightCrawler({ proxyConfiguration, @@ -81,9 +81,10 @@ export async function crawlSite(input: typeof SiteCrawlInputSchema.infer) { }, }); + const sitemapProxyUrl = await proxyConfiguration.newUrl(); const startUrls = await parseStartingUrl({ url: startUrl, - proxyUrl: await proxyConfiguration.newUrl(), + proxyUrl: sitemapProxyUrl, }); await crawler.run(startUrls); console.timeEnd("Crawl"); diff --git a/packages/task/src/env.ts b/packages/task/src/env.ts index fa3ca8cf6..450f8538e 100644 --- a/packages/task/src/env.ts +++ b/packages/task/src/env.ts @@ -1,9 +1,10 @@ import { createEnv } from "@t3-oss/env-core"; +import { type } from "arktype"; export const dbEnv = () => createEnv({ server: { - // DATABASE_URL: type("string"), + TRIGGER_SECRET_KEY: type("string"), }, runtimeEnv: process.env, emptyStringAsUndefined: true, diff --git a/packages/task/src/trigger/site-crawl.ts b/packages/task/src/trigger/site-crawl.ts deleted file mode 100644 index 455213ed1..000000000 --- a/packages/task/src/trigger/site-crawl.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { task } from "@trigger.dev/sdk/v3"; -import { crawlSite } from "../crawlers/site.js"; -import type { SiteCrawlInputSchema } from "../schema/site.js"; - -export const siteCrawlTask = task({ - id: "site-crawl", - maxDuration: 300, - run: async (payload: typeof SiteCrawlInputSchema.infer) => { - await crawlSite(payload); - - return { - message: "Hello, world!", - }; - }, -}); diff --git a/packages/task/src/trigger/understand-site.ts b/packages/task/src/trigger/understand-site.ts new file mode 100644 index 000000000..8677714be --- /dev/null +++ b/packages/task/src/trigger/understand-site.ts @@ -0,0 +1,30 @@ +import { schemaTask } from "@trigger.dev/sdk"; +import { type } from "arktype"; +import { crawlSite } from "../crawlers/site.js"; + +const inputSchema = type({ + startUrl: "string", + maxRequestsPerCrawl: "number=20", +}); +export const understandSiteTask: ReturnType< + typeof schemaTask<"understand-site", typeof inputSchema> +> = schemaTask({ + id: "understand-site", + maxDuration: 300, + schema: inputSchema, + run: async (payload) => { + const result = await crawlSite({ + startUrl: payload.startUrl, + maxRequestsPerCrawl: payload.maxRequestsPerCrawl, + }); + + const data = await result.getData(); + for (const item of data.items) { + console.log(item); + } + // TODO: use ai and extract relevant information from the result + return { + message: "Site crawled successfully", + }; + }, +}); From 622f4c81ad6317a69ec7cb72e927d443383989a1 Mon Sep 17 00:00:00 2001 From: ElasticBottle Date: Fri, 19 Sep 2025 16:03:20 +0800 Subject: [PATCH 11/38] chore: update seo wrangler --- apps/seo/wrangler.jsonc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/seo/wrangler.jsonc b/apps/seo/wrangler.jsonc index 2da64815c..3d29e9e64 100644 --- a/apps/seo/wrangler.jsonc +++ b/apps/seo/wrangler.jsonc @@ -1,6 +1,6 @@ { "$schema": "node_modules/wrangler/config-schema.json", - "name": "rectangular-labs-mentions", + "name": "rectangular-labs-seo", "main": ".output/server/index.mjs", "preview_urls": false, "compatibility_date": "2025-09-07", @@ -20,7 +20,7 @@ "production": { "routes": [ { - "pattern": "mentions.rectangularlabs.com", + "pattern": "seo.rectangularlabs.com", "custom_domain": true } ] @@ -28,7 +28,7 @@ "{env.PULL_REQUEST}": { "routes": [ { - "pattern": "{env.PULL_REQUEST}.mentions.rectangularlabs.com", + "pattern": "{env.PULL_REQUEST}.seo.rectangularlabs.com", "custom_domain": true } ] From 760698d94cd46b596cc3a3a7ed1792f945e9dcf5 Mon Sep 17 00:00:00 2001 From: ElasticBottle Date: Fri, 19 Sep 2025 16:44:56 +0800 Subject: [PATCH 12/38] chore: reduce cpu usage from constant watching --- packages/api-core/package.json | 2 +- packages/auth/package.json | 2 +- packages/content/package.json | 2 +- packages/emails/package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/api-core/package.json b/packages/api-core/package.json index 902ffffe3..0d6293a06 100644 --- a/packages/api-core/package.json +++ b/packages/api-core/package.json @@ -11,7 +11,7 @@ }, "scripts": { "build": "tsc", - "dev": "tsc --watch", + "dev": "tsc", "clean": "git clean -xdf .turbo node_modules dist .cache", "format": "bun x @biomejs/biome format . --write", "lint": "bun x @biomejs/biome lint . --write", diff --git a/packages/auth/package.json b/packages/auth/package.json index 2b25a4c0c..5cf8449cf 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -22,7 +22,7 @@ }, "scripts": { "build": "tsc", - "dev": "tsc --watch", + "dev": "tsc", "generate": "pnpm with-env pnpx @better-auth/cli generate --config ./src/generate.ts --output ../db/src/schema/auth-schema.ts", "clean": "git clean -xdf .turbo node_modules dist .cache", "format": "pnpx @biomejs/biome format . --write", diff --git a/packages/content/package.json b/packages/content/package.json index 27e8e55cc..bc428371e 100644 --- a/packages/content/package.json +++ b/packages/content/package.json @@ -29,7 +29,7 @@ }, "scripts": { "build": "content-collections build && tsc", - "dev": "concurrently \"content-collections watch\" \"tsc --watch\"", + "dev": "concurrently \"content-collections build\" \"tsc\"", "clean": "git clean -xdf .turbo node_modules dist .cache .next .content-collections", "format": "bun x @biomejs/biome format . --write", "lint": "bun x @biomejs/biome lint . --write", diff --git a/packages/emails/package.json b/packages/emails/package.json index dd5243477..b8b7259ec 100644 --- a/packages/emails/package.json +++ b/packages/emails/package.json @@ -47,7 +47,7 @@ }, "scripts": { "build": "tsup", - "dev": "tsup --watch", + "dev": "tsup", "create": "email create --out=./src/templates", "preview": "email preview ./src/templates ", "check": "email check ./src/templates", From ceafc885829464f64e91480935c1f577d9a8b603 Mon Sep 17 00:00:00 2001 From: ElasticBottle Date: Sat, 20 Sep 2025 01:08:05 +0800 Subject: [PATCH 13/38] feat(task): add metadata support to track progress --- packages/task/package.json | 5 +-- .../router.ts => crawlers/site.router.ts} | 26 ++++++++--- .../site.ts => crawlers/site.schema.ts} | 10 ++++- packages/task/src/crawlers/site.ts | 21 +++++---- ...tarting-url.ts => extract-sitemap-urls.ts} | 21 +++++---- .../src/trigger/understand-site.metadata.ts | 28 ++++++++++++ packages/task/src/trigger/understand-site.ts | 45 ++++++++++++++++++- 7 files changed, 129 insertions(+), 27 deletions(-) rename packages/task/src/{lib/site-crawler-config/router.ts => crawlers/site.router.ts} (84%) rename packages/task/src/{schema/site.ts => crawlers/site.schema.ts} (74%) rename packages/task/src/lib/{site-crawler-config/parse-starting-url.ts => extract-sitemap-urls.ts} (56%) create mode 100644 packages/task/src/trigger/understand-site.metadata.ts diff --git a/packages/task/package.json b/packages/task/package.json index 24d2400b5..1df1dc593 100644 --- a/packages/task/package.json +++ b/packages/task/package.json @@ -11,10 +11,6 @@ "./client": { "default": "./src/client.ts", "types": "./dist/client.d.ts" - }, - "./schema/*": { - "default": "./src/schema/*.ts", - "types": "./dist/schema/*.d.ts" } }, "scripts": { @@ -33,6 +29,7 @@ }, "dependencies": { "@orama/orama": "^3.1.14", + "@rectangular-labs/result": "workspace:*", "@t3-oss/env-core": "^0.13.8", "@trigger.dev/sdk": "4.0.2", "arktype": "^2.1.22", diff --git a/packages/task/src/lib/site-crawler-config/router.ts b/packages/task/src/crawlers/site.router.ts similarity index 84% rename from packages/task/src/lib/site-crawler-config/router.ts rename to packages/task/src/crawlers/site.router.ts index dcf7d2d8f..ab4f9cb73 100644 --- a/packages/task/src/lib/site-crawler-config/router.ts +++ b/packages/task/src/crawlers/site.router.ts @@ -1,7 +1,8 @@ import { createPlaywrightRouter } from "crawlee"; import { Defuddle as parseWithDefuddle } from "defuddle/node"; import type { Page } from "playwright"; -import { extractUrlLocale, type Locales } from "../extract-url-locale.js"; +import { extractUrlLocale, type Locales } from "../lib/extract-url-locale.js"; +import type { SiteCrawlInput } from "./site.schema.js"; function getPageContent(page: Page, selector: string) { return page.evaluate((selector) => { @@ -58,6 +59,7 @@ export const createCrawlSiteRouter = ({ exclude, onlyEnqueueDefaultLocale = true, defaultLocale = "en", + onProgress, }: { selector: string; waitForSelectorTimeoutMs?: number | undefined; @@ -65,15 +67,28 @@ export const createCrawlSiteRouter = ({ exclude: string[]; onlyEnqueueDefaultLocale?: boolean | undefined; defaultLocale?: Locales | (string & {}) | undefined; + onProgress: SiteCrawlInput["onProgress"]; }) => { const siteCrawlRouter = createPlaywrightRouter(); siteCrawlRouter.addDefaultHandler( - async ({ enqueueLinks, pushData, request, page }) => { + async ({ enqueueLinks, pushData, request, page, crawler }) => { const title = await page.title(); console.info(`Crawling: ${request.loadedUrl}...`); + const inFlight = crawler.autoscaledPool?.currentConcurrency ?? 0; + const succeeded = crawler.stats.state.requestsFinished; + const failed = crawler.stats.state.requestsFailed; + const currentUrl = request.loadedUrl; + + await onProgress?.({ + currentUrl, + inFlight, + succeeded, + failed, + }); + // Use custom handling for XPath selector const timeout = waitForSelectorTimeoutMs ?? 10_000; if (selector) { @@ -92,15 +107,16 @@ export const createCrawlSiteRouter = ({ let defuddleDescription: string | undefined; try { const html = await page.content(); - const parsed = await parseWithDefuddle(html, request.loadedUrl, { + const parsed = await parseWithDefuddle(html, currentUrl, { separateMarkdown: true, }); + defuddleContentHtml = parsed.content; defuddleContentMarkdown = parsed.contentMarkdown; defuddleDescription = parsed.description; } catch (_error) { // Ignore extraction errors and fall back to selector text - console.info(`Defuddle extraction failed for ${request.loadedUrl}`); + console.info(`Defuddle extraction failed for ${currentUrl}`); } const text = await getPageContent(page, selector); @@ -108,7 +124,7 @@ export const createCrawlSiteRouter = ({ // Save results as JSON to ./storage/datasets/default await pushData({ title, - url: request.loadedUrl, + url: currentUrl, text, description: defuddleDescription, contentHtml: defuddleContentHtml, diff --git a/packages/task/src/schema/site.ts b/packages/task/src/crawlers/site.schema.ts similarity index 74% rename from packages/task/src/schema/site.ts rename to packages/task/src/crawlers/site.schema.ts index 2eef71caa..534490725 100644 --- a/packages/task/src/schema/site.ts +++ b/packages/task/src/crawlers/site.schema.ts @@ -9,6 +9,7 @@ export type Cookie = typeof cookieSchema.infer; export const SiteCrawlInputSchema = type({ startUrl: "string", maxRequestsPerCrawl: "number", + crawlSitemap: "boolean=false", "selector?": "string", "waitForSelectorTimeoutMs?": "number", "match?": "string[]", @@ -16,7 +17,14 @@ export const SiteCrawlInputSchema = type({ "cookie?": [cookieSchema, "[]"], "resourceFileTypeExclusions?": "string[]", }); -export type SiteCrawlInput = typeof SiteCrawlInputSchema.infer; +export type SiteCrawlInput = typeof SiteCrawlInputSchema.infer & { + onProgress?: (args: { + currentUrl?: string; + inFlight: number; + succeeded: number; + failed: number; + }) => void | Promise; +}; export const SiteCrawlOutputSchema = type({ title: "string", diff --git a/packages/task/src/crawlers/site.ts b/packages/task/src/crawlers/site.ts index f3c66d424..0717a465b 100644 --- a/packages/task/src/crawlers/site.ts +++ b/packages/task/src/crawlers/site.ts @@ -4,14 +4,16 @@ import { type PlaywrightHook, ProxyConfiguration, } from "crawlee"; -import { parseStartingUrl } from "../lib/site-crawler-config/parse-starting-url.js"; -import { createCrawlSiteRouter } from "../lib/site-crawler-config/router.js"; -import type { SiteCrawlInputSchema } from "../schema/site.js"; +import { extractSitemapUrls } from "../lib/extract-sitemap-urls.js"; +import { createCrawlSiteRouter } from "./site.router.js"; +import type { SiteCrawlInput } from "./site.schema.js"; -export async function crawlSite(input: typeof SiteCrawlInputSchema.infer) { +export async function crawlSite(input: SiteCrawlInput) { const { + onProgress, startUrl, maxRequestsPerCrawl, + crawlSitemap, match = [], exclude = [], selector = "body", @@ -52,6 +54,7 @@ export async function crawlSite(input: typeof SiteCrawlInputSchema.infer) { waitForSelectorTimeoutMs, match, exclude, + onProgress, }); const proxyConfiguration = new ProxyConfiguration({ @@ -82,10 +85,12 @@ export async function crawlSite(input: typeof SiteCrawlInputSchema.infer) { }); const sitemapProxyUrl = await proxyConfiguration.newUrl(); - const startUrls = await parseStartingUrl({ - url: startUrl, - proxyUrl: sitemapProxyUrl, - }); + const startUrls = crawlSitemap + ? await extractSitemapUrls({ + url: startUrl, + proxyUrl: sitemapProxyUrl, + }) + : [startUrl]; await crawler.run(startUrls); console.timeEnd("Crawl"); return crawler; diff --git a/packages/task/src/lib/site-crawler-config/parse-starting-url.ts b/packages/task/src/lib/extract-sitemap-urls.ts similarity index 56% rename from packages/task/src/lib/site-crawler-config/parse-starting-url.ts rename to packages/task/src/lib/extract-sitemap-urls.ts index 4afdda8a7..89ed5e5b7 100644 --- a/packages/task/src/lib/site-crawler-config/parse-starting-url.ts +++ b/packages/task/src/lib/extract-sitemap-urls.ts @@ -1,7 +1,16 @@ import { log, RobotsTxtFile } from "crawlee"; -import { extractUrlLocale } from "../extract-url-locale.js"; +import { extractUrlLocale, type Locales } from "./extract-url-locale.js"; -export async function parseStartingUrl({ +/** + * + * @param {object} param - The parameters for the function + * @param {string} param.url - The url of the site from which we will try to find sitemap urls from + * @param {string} param.proxyUrl - The proxy url to use + * @param {string} param.defaultLocale - The default locale to use + * @param {boolean} param.onlyParseDefaultLocale - Whether to only parse default locale + * @returns + */ +export async function extractSitemapUrls({ url, proxyUrl, defaultLocale = "en", @@ -9,7 +18,7 @@ export async function parseStartingUrl({ }: { url: string; proxyUrl?: string | undefined; - defaultLocale?: string | undefined; + defaultLocale?: Locales | undefined; onlyParseDefaultLocale?: boolean | undefined; }) { const robots = await RobotsTxtFile.find(url, proxyUrl); @@ -29,9 +38,5 @@ export async function parseStartingUrl({ : urls; log.info(`${filteredUrls.length} urls after filtering.`); - // TODO: This is a temporary solution to avoid crawling too many urls. We'll likely inject high ranking urls as needed too. - // If there's too much urls, we'll just crawl the start url and discover the most relevant urls from the perspective of the user. - return filteredUrls.length > 0 && filteredUrls.length < 1000 - ? filteredUrls - : [url]; + return filteredUrls; } diff --git a/packages/task/src/trigger/understand-site.metadata.ts b/packages/task/src/trigger/understand-site.metadata.ts new file mode 100644 index 000000000..18f64c753 --- /dev/null +++ b/packages/task/src/trigger/understand-site.metadata.ts @@ -0,0 +1,28 @@ +import { err, ok } from "@rectangular-labs/result"; +import { logger, metadata } from "@trigger.dev/sdk"; +import { type } from "arktype"; + +const UnderstandSiteSchema = type({ + progress: "number", + statusMessage: "string", +}); + +export function setUnderstandSiteMetadata( + values: Partial, +) { + if (typeof values.progress === "number") { + metadata.set("progress", values.progress); + } + if (values.statusMessage) { + metadata.set("statusMessage", values.statusMessage); + } +} + +export function getUnderstandSiteMetadata() { + const parsedMetadata = UnderstandSiteSchema(metadata.current()); + if (parsedMetadata instanceof type.errors) { + logger.error("Invalid metadata", { error: parsedMetadata.summary }); + return err(parsedMetadata); + } + return ok(parsedMetadata); +} diff --git a/packages/task/src/trigger/understand-site.ts b/packages/task/src/trigger/understand-site.ts index 8677714be..799874095 100644 --- a/packages/task/src/trigger/understand-site.ts +++ b/packages/task/src/trigger/understand-site.ts @@ -1,11 +1,13 @@ import { schemaTask } from "@trigger.dev/sdk"; import { type } from "arktype"; import { crawlSite } from "../crawlers/site.js"; +import { setUnderstandSiteMetadata } from "./understand-site.metadata.js"; const inputSchema = type({ startUrl: "string", - maxRequestsPerCrawl: "number=20", + maxRequestsPerCrawl: "number=25", }); + export const understandSiteTask: ReturnType< typeof schemaTask<"understand-site", typeof inputSchema> > = schemaTask({ @@ -13,16 +15,57 @@ export const understandSiteTask: ReturnType< maxDuration: 300, schema: inputSchema, run: async (payload) => { + setUnderstandSiteMetadata({ + progress: 0, + statusMessage: `Loading up ${payload.startUrl}...`, + }); const result = await crawlSite({ startUrl: payload.startUrl, maxRequestsPerCrawl: payload.maxRequestsPerCrawl, + crawlSitemap: false, + onProgress: (args) => { + const progress = Math.round( + ((args.succeeded + args.failed + args.inFlight) / + (payload.maxRequestsPerCrawl * 2)) * + 100, + ); + setUnderstandSiteMetadata({ + progress, + statusMessage: args.currentUrl + ? `Currently understanding ${args.currentUrl}...` + : `${progress}% done, hang tight...`, + }); + }, + }); + + setUnderstandSiteMetadata({ + progress: 50, + statusMessage: + "Finished reading through the site. Extracting relevant information...", }); const data = await result.getData(); for (const item of data.items) { console.log(item); } + // TODO: construct search index + setUnderstandSiteMetadata({ + progress: 60, + statusMessage: "Found relevant information, synthesizing the results...", + }); + + // TODO: upload search index + setUnderstandSiteMetadata({ + progress: 75, + statusMessage: "Almost done!", + }); + // TODO: use ai and extract relevant information from the result + setUnderstandSiteMetadata({ + progress: 100, + statusMessage: "All done, loading the results...", + }); + return { message: "Site crawled successfully", }; From df456a27f660ee7997e8aea9b10c3f7d03143d8c Mon Sep 17 00:00:00 2001 From: ElasticBottle Date: Sat, 20 Sep 2025 02:04:15 +0800 Subject: [PATCH 14/38] chore(db): clean up schema to support seo project --- .../0000_motionless_tomorrow_man.sql | 504 +++++ .../db/migrations/0000_smart_moonstone.sql | 249 --- .../db/migrations/meta/0000_snapshot.json | 1978 ++++++++++++++++- packages/db/migrations/meta/_journal.json | 4 +- packages/db/src/client.ts | 4 + packages/db/src/schema/_table.ts | 1 + .../src/schema/{mention => }/auth-schema.ts | 0 .../mention/company-background-schema.ts | 13 +- packages/db/src/schema/mention/index.ts | 1 - .../db/src/schema/mention/project-schema.ts | 2 +- packages/db/src/schema/seo/index.ts | 4 + packages/db/src/schema/seo/project-schema.ts | 51 + packages/db/src/schema/seo/task-run-schema.ts | 73 + 13 files changed, 2623 insertions(+), 261 deletions(-) create mode 100644 packages/db/migrations/0000_motionless_tomorrow_man.sql delete mode 100644 packages/db/migrations/0000_smart_moonstone.sql rename packages/db/src/schema/{mention => }/auth-schema.ts (100%) create mode 100644 packages/db/src/schema/seo/index.ts create mode 100644 packages/db/src/schema/seo/project-schema.ts create mode 100644 packages/db/src/schema/seo/task-run-schema.ts diff --git a/packages/db/migrations/0000_motionless_tomorrow_man.sql b/packages/db/migrations/0000_motionless_tomorrow_man.sql new file mode 100644 index 000000000..1c3fd74d5 --- /dev/null +++ b/packages/db/migrations/0000_motionless_tomorrow_man.sql @@ -0,0 +1,504 @@ +CREATE TABLE "account" ( + "id" text PRIMARY KEY NOT NULL, + "account_id" text NOT NULL, + "provider_id" text NOT NULL, + "user_id" text NOT NULL, + "access_token" text, + "refresh_token" text, + "id_token" text, + "access_token_expires_at" timestamp, + "refresh_token_expires_at" timestamp, + "scope" text, + "password" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "invitation" ( + "id" text PRIMARY KEY NOT NULL, + "organization_id" text NOT NULL, + "email" text NOT NULL, + "role" text, + "status" text DEFAULT 'pending' NOT NULL, + "expires_at" timestamp NOT NULL, + "inviter_id" text NOT NULL +); +--> statement-breakpoint +CREATE TABLE "member" ( + "id" text PRIMARY KEY NOT NULL, + "organization_id" text NOT NULL, + "user_id" text NOT NULL, + "role" text DEFAULT 'member' NOT NULL, + "created_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "organization" ( + "id" text PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "slug" text, + "logo" text, + "created_at" timestamp with time zone NOT NULL, + "metadata" text, + CONSTRAINT "organization_slug_unique" UNIQUE("slug") +); +--> statement-breakpoint +CREATE TABLE "passkey" ( + "id" text PRIMARY KEY NOT NULL, + "name" text, + "public_key" text NOT NULL, + "user_id" text NOT NULL, + "credential_id" text NOT NULL, + "counter" integer NOT NULL, + "device_type" text NOT NULL, + "backed_up" boolean NOT NULL, + "transports" text, + "created_at" timestamp, + "aaguid" text +); +--> statement-breakpoint +CREATE TABLE "session" ( + "id" text PRIMARY KEY NOT NULL, + "expires_at" timestamp NOT NULL, + "token" text NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone NOT NULL, + "ip_address" text, + "user_agent" text, + "user_id" text NOT NULL, + "active_organization_id" text, + CONSTRAINT "session_token_unique" UNIQUE("token") +); +--> statement-breakpoint +CREATE TABLE "two_factor" ( + "id" text PRIMARY KEY NOT NULL, + "secret" text NOT NULL, + "backup_codes" text NOT NULL, + "user_id" text NOT NULL +); +--> statement-breakpoint +CREATE TABLE "user" ( + "id" text PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "email" text NOT NULL, + "email_verified" boolean DEFAULT false NOT NULL, + "image" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + "two_factor_enabled" boolean DEFAULT false, + "source" text, + "goal" text, + CONSTRAINT "user_email_unique" UNIQUE("email") +); +--> statement-breakpoint +CREATE TABLE "verification" ( + "id" text PRIMARY KEY NOT NULL, + "identifier" text NOT NULL, + "value" text NOT NULL, + "expires_at" timestamp NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "sm_company_background" ( + "id" uuid PRIMARY KEY NOT NULL, + "website_url" text NOT NULL, + "status" text DEFAULT 'queued' NOT NULL, + "last_indexed_at" timestamp with time zone, + "data" jsonb, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "sm_keyword" ( + "id" uuid PRIMARY KEY NOT NULL, + "phrase" text NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "sm_keyword_source_cursor" ( + "id" uuid PRIMARY KEY NOT NULL, + "keyword_id" uuid NOT NULL, + "source" text NOT NULL, + "current_cursor" text, + "latest_item_at" timestamp with time zone, + "empty_streak" integer DEFAULT 0 NOT NULL, + "next_earliest_run_at" timestamp with time zone, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "sm_mention" ( + "id" uuid PRIMARY KEY NOT NULL, + "provider" text DEFAULT 'reddit' NOT NULL, + "provider_id" text NOT NULL, + "provider_url" text, + "provider_created_at" timestamp with time zone, + "author" text, + "title" text, + "content" text, + "metadata" jsonb, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "sm_project_keyword_mention" ( + "project_id" uuid NOT NULL, + "keyword_id" uuid NOT NULL, + "mention_id" uuid NOT NULL, + "matched_at" timestamp with time zone NOT NULL, + "status" text DEFAULT 'new' NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone NOT NULL, + CONSTRAINT "sm_project_keyword_mention_project_id_keyword_id_mention_id_pk" PRIMARY KEY("project_id","keyword_id","mention_id") +); +--> statement-breakpoint +CREATE TABLE "sm_project_keyword" ( + "project_id" uuid NOT NULL, + "keyword_id" uuid NOT NULL, + "polling_interval_sec" integer, + "next_run_at" timestamp with time zone, + "last_run_at" timestamp with time zone, + "is_paused" boolean DEFAULT false NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone NOT NULL, + CONSTRAINT "sm_project_keyword_project_id_keyword_id_pk" PRIMARY KEY("project_id","keyword_id") +); +--> statement-breakpoint +CREATE TABLE "sm_project_mention_reply" ( + "id" uuid PRIMARY KEY NOT NULL, + "project_id" uuid NOT NULL, + "mention_id" uuid NOT NULL, + "attributed_keyword_id" uuid NOT NULL, + "model" text, + "prompt_override_id" uuid, + "reply_text" text, + "is_auto_generated" boolean DEFAULT false NOT NULL, + "status" text DEFAULT 'draft' NOT NULL, + "error" text, + "provider_published_id" text, + "provider_published_url" text, + "published_at" timestamp with time zone, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "sm_project" ( + "sm_project_id" uuid PRIMARY KEY NOT NULL, + "organization_id" text, + "current_reply_prompt_id" uuid, + "polling_interval_sec" integer DEFAULT 900 NOT NULL, + "auto_generate_replies" boolean DEFAULT false NOT NULL, + "is_paused" boolean DEFAULT false NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "sm_prompt" ( + "sm_prompt_id" uuid PRIMARY KEY NOT NULL, + "prompt" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "seo_audit_snapshot" ( + "seo_audit_snapshot_id" uuid PRIMARY KEY NOT NULL, + "project_id" uuid NOT NULL, + "page_id" uuid, + "keyword_id" uuid, + "captured_at" timestamp with time zone NOT NULL, + "overall_score" numeric(5, 2), + "status" text DEFAULT 'ok' NOT NULL, + "metrics" jsonb NOT NULL, + "explanation_technical" text, + "explanation_layman" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "seo_calendar_event" ( + "seo_calendar_event_id" uuid PRIMARY KEY NOT NULL, + "project_id" uuid NOT NULL, + "campaign_id" uuid, + "task_id" uuid, + "title" text NOT NULL, + "start_at" timestamp with time zone NOT NULL, + "end_at" timestamp with time zone, + "status" text DEFAULT 'scheduled' NOT NULL, + "metadata" jsonb, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "seo_campaign" ( + "seo_campaign_id" uuid PRIMARY KEY NOT NULL, + "project_id" uuid NOT NULL, + "proposal_id" uuid, + "title" text NOT NULL, + "thesis" text, + "status" text DEFAULT 'planned' NOT NULL, + "start_date" timestamp with time zone, + "end_date" timestamp with time zone, + "goal_metrics" jsonb, + "schedule" jsonb, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "seo_campaign_proposal" ( + "seo_campaign_proposal_id" uuid PRIMARY KEY NOT NULL, + "project_id" uuid NOT NULL, + "title" text NOT NULL, + "thesis" text, + "why_five_ws" jsonb, + "explanation_technical" text, + "explanation_layman" text, + "baseline_metrics" jsonb, + "status" text DEFAULT 'pending' NOT NULL, + "requested_by" text, + "approved_at" timestamp with time zone, + "rejected_at" timestamp with time zone, + "rejection_reason" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "seo_campaign_task" ( + "seo_campaign_task_id" uuid PRIMARY KEY NOT NULL, + "campaign_id" uuid NOT NULL, + "type" text NOT NULL, + "status" text DEFAULT 'todo' NOT NULL, + "title" text, + "target_page_id" uuid, + "target_keyword_id" uuid, + "due_at" timestamp with time zone, + "payload" jsonb, + "result" jsonb, + "assignee_id" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "seo_cms_connection" ( + "seo_cms_connection_id" uuid PRIMARY KEY NOT NULL, + "project_id" uuid NOT NULL, + "provider" text NOT NULL, + "external_site_id" text, + "status" text DEFAULT 'disconnected' NOT NULL, + "config" jsonb, + "last_synced_at" timestamp with time zone, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "seo_keyword_cluster" ( + "seo_keyword_cluster_id" uuid PRIMARY KEY NOT NULL, + "project_id" uuid NOT NULL, + "name" text NOT NULL, + "method" text, + "centroid_keyword_id" uuid, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "seo_keyword_cluster_member" ( + "seo_keyword_cluster_member_id" uuid PRIMARY KEY NOT NULL, + "cluster_id" uuid NOT NULL, + "keyword_id" uuid NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "seo_metric_threshold" ( + "seo_metric_threshold_id" uuid PRIMARY KEY NOT NULL, + "metric_key" text NOT NULL, + "direction" text NOT NULL, + "good_threshold" numeric(10, 3), + "bad_threshold" numeric(10, 3), + "notes" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "seo_page" ( + "seo_page_id" uuid PRIMARY KEY NOT NULL, + "project_id" uuid NOT NULL, + "url" text NOT NULL, + "canonical_url" text, + "title" text, + "description" text, + "content_text" text, + "content_html" text, + "content_markdown" text, + "extractor" text, + "http_status" integer, + "robots_noindex" boolean, + "robots_nofollow" boolean, + "sitemap_priority" numeric(2, 1), + "status" text DEFAULT 'discovered' NOT NULL, + "last_crawled_at" timestamp with time zone, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "seo_page_keyword" ( + "seo_page_keyword_id" uuid PRIMARY KEY NOT NULL, + "page_id" uuid NOT NULL, + "keyword_id" uuid NOT NULL, + "is_primary" boolean DEFAULT false NOT NULL, + "match_score" numeric(4, 3), + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "seo_publishing_job" ( + "seo_publishing_job_id" uuid PRIMARY KEY NOT NULL, + "project_id" uuid NOT NULL, + "cms_connection_id" uuid, + "task_id" uuid, + "target_page_id" uuid, + "status" text DEFAULT 'enqueued' NOT NULL, + "payload" jsonb, + "external_job_id" text, + "error_message" text, + "started_at" timestamp with time zone, + "finished_at" timestamp with time zone, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "seo_sitemap_source" ( + "seo_sitemap_source_id" uuid PRIMARY KEY NOT NULL, + "project_id" uuid NOT NULL, + "url" text NOT NULL, + "status" text DEFAULT 'active' NOT NULL, + "last_fetched_at" timestamp with time zone, + "last_fetch_error" text, + "discovered_count" integer DEFAULT 0 NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "seo_project" ( + "id" uuid PRIMARY KEY NOT NULL, + "organization_id" text NOT NULL, + "website_url" text NOT NULL, + "website_info" jsonb, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "seo_task_run" ( + "id" uuid PRIMARY KEY NOT NULL, + "project_id" uuid NOT NULL, + "requested_by" text NOT NULL, + "task_id" text NOT NULL, + "provider" text DEFAULT 'trigger.dev' NOT NULL, + "input_data" jsonb NOT NULL, + "cost_in_cents" numeric(10, 5) DEFAULT '0.00000' NOT NULL, + "duration_ms" integer DEFAULT 0 NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "invitation" ADD CONSTRAINT "invitation_organization_id_organization_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "invitation" ADD CONSTRAINT "invitation_inviter_id_user_id_fk" FOREIGN KEY ("inviter_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "member" ADD CONSTRAINT "member_organization_id_organization_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "member" ADD CONSTRAINT "member_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "passkey" ADD CONSTRAINT "passkey_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "two_factor" ADD CONSTRAINT "two_factor_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "sm_keyword_source_cursor" ADD CONSTRAINT "sm_keyword_source_cursor_keyword_id_sm_keyword_id_fk" FOREIGN KEY ("keyword_id") REFERENCES "public"."sm_keyword"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "sm_project_keyword_mention" ADD CONSTRAINT "sm_project_keyword_mention_project_id_sm_project_sm_project_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."sm_project"("sm_project_id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "sm_project_keyword_mention" ADD CONSTRAINT "sm_project_keyword_mention_keyword_id_sm_keyword_id_fk" FOREIGN KEY ("keyword_id") REFERENCES "public"."sm_keyword"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "sm_project_keyword_mention" ADD CONSTRAINT "sm_project_keyword_mention_mention_id_sm_mention_id_fk" FOREIGN KEY ("mention_id") REFERENCES "public"."sm_mention"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "sm_project_keyword_mention" ADD CONSTRAINT "sm_pkm_project_keyword_fk" FOREIGN KEY ("project_id","keyword_id") REFERENCES "public"."sm_project_keyword"("project_id","keyword_id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "sm_project_keyword" ADD CONSTRAINT "sm_project_keyword_project_id_sm_project_sm_project_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."sm_project"("sm_project_id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "sm_project_keyword" ADD CONSTRAINT "sm_project_keyword_keyword_id_sm_keyword_id_fk" FOREIGN KEY ("keyword_id") REFERENCES "public"."sm_keyword"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "sm_project_mention_reply" ADD CONSTRAINT "sm_project_mention_reply_project_id_sm_project_sm_project_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."sm_project"("sm_project_id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "sm_project_mention_reply" ADD CONSTRAINT "sm_project_mention_reply_mention_id_sm_mention_id_fk" FOREIGN KEY ("mention_id") REFERENCES "public"."sm_mention"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "sm_project_mention_reply" ADD CONSTRAINT "sm_project_mention_reply_attributed_keyword_id_sm_keyword_id_fk" FOREIGN KEY ("attributed_keyword_id") REFERENCES "public"."sm_keyword"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "sm_project_mention_reply" ADD CONSTRAINT "sm_project_mention_reply_prompt_override_id_sm_prompt_sm_prompt_id_fk" FOREIGN KEY ("prompt_override_id") REFERENCES "public"."sm_prompt"("sm_prompt_id") ON DELETE set null ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "sm_project_mention_reply" ADD CONSTRAINT "sm_pmr_project_keyword_mention_fk" FOREIGN KEY ("project_id","mention_id","attributed_keyword_id") REFERENCES "public"."sm_project_keyword_mention"("project_id","mention_id","keyword_id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "sm_project" ADD CONSTRAINT "sm_project_organization_id_organization_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "sm_project" ADD CONSTRAINT "sm_project_current_reply_prompt_id_sm_prompt_sm_prompt_id_fk" FOREIGN KEY ("current_reply_prompt_id") REFERENCES "public"."sm_prompt"("sm_prompt_id") ON DELETE set null ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "seo_audit_snapshot" ADD CONSTRAINT "seo_audit_snapshot_project_id_sm_project_sm_project_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."sm_project"("sm_project_id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "seo_audit_snapshot" ADD CONSTRAINT "seo_audit_snapshot_page_id_seo_page_seo_page_id_fk" FOREIGN KEY ("page_id") REFERENCES "public"."seo_page"("seo_page_id") ON DELETE set null ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "seo_audit_snapshot" ADD CONSTRAINT "seo_audit_snapshot_keyword_id_sm_keyword_id_fk" FOREIGN KEY ("keyword_id") REFERENCES "public"."sm_keyword"("id") ON DELETE set null ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "seo_calendar_event" ADD CONSTRAINT "seo_calendar_event_project_id_sm_project_sm_project_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."sm_project"("sm_project_id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "seo_calendar_event" ADD CONSTRAINT "seo_calendar_event_campaign_id_seo_campaign_seo_campaign_id_fk" FOREIGN KEY ("campaign_id") REFERENCES "public"."seo_campaign"("seo_campaign_id") ON DELETE set null ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "seo_calendar_event" ADD CONSTRAINT "seo_calendar_event_task_id_seo_campaign_task_seo_campaign_task_id_fk" FOREIGN KEY ("task_id") REFERENCES "public"."seo_campaign_task"("seo_campaign_task_id") ON DELETE set null ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "seo_campaign" ADD CONSTRAINT "seo_campaign_project_id_sm_project_sm_project_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."sm_project"("sm_project_id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "seo_campaign" ADD CONSTRAINT "seo_campaign_proposal_id_seo_campaign_proposal_seo_campaign_proposal_id_fk" FOREIGN KEY ("proposal_id") REFERENCES "public"."seo_campaign_proposal"("seo_campaign_proposal_id") ON DELETE set null ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "seo_campaign_proposal" ADD CONSTRAINT "seo_campaign_proposal_project_id_sm_project_sm_project_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."sm_project"("sm_project_id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "seo_campaign_task" ADD CONSTRAINT "seo_campaign_task_campaign_id_seo_campaign_seo_campaign_id_fk" FOREIGN KEY ("campaign_id") REFERENCES "public"."seo_campaign"("seo_campaign_id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "seo_campaign_task" ADD CONSTRAINT "seo_campaign_task_target_page_id_seo_page_seo_page_id_fk" FOREIGN KEY ("target_page_id") REFERENCES "public"."seo_page"("seo_page_id") ON DELETE set null ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "seo_campaign_task" ADD CONSTRAINT "seo_campaign_task_target_keyword_id_sm_keyword_id_fk" FOREIGN KEY ("target_keyword_id") REFERENCES "public"."sm_keyword"("id") ON DELETE set null ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "seo_cms_connection" ADD CONSTRAINT "seo_cms_connection_project_id_sm_project_sm_project_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."sm_project"("sm_project_id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "seo_keyword_cluster" ADD CONSTRAINT "seo_keyword_cluster_project_id_sm_project_sm_project_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."sm_project"("sm_project_id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "seo_keyword_cluster" ADD CONSTRAINT "seo_keyword_cluster_centroid_keyword_id_sm_keyword_id_fk" FOREIGN KEY ("centroid_keyword_id") REFERENCES "public"."sm_keyword"("id") ON DELETE set null ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "seo_keyword_cluster_member" ADD CONSTRAINT "seo_keyword_cluster_member_cluster_id_seo_keyword_cluster_seo_keyword_cluster_id_fk" FOREIGN KEY ("cluster_id") REFERENCES "public"."seo_keyword_cluster"("seo_keyword_cluster_id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "seo_keyword_cluster_member" ADD CONSTRAINT "seo_keyword_cluster_member_keyword_id_sm_keyword_id_fk" FOREIGN KEY ("keyword_id") REFERENCES "public"."sm_keyword"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "seo_page" ADD CONSTRAINT "seo_page_project_id_sm_project_sm_project_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."sm_project"("sm_project_id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "seo_page_keyword" ADD CONSTRAINT "seo_page_keyword_page_id_seo_page_seo_page_id_fk" FOREIGN KEY ("page_id") REFERENCES "public"."seo_page"("seo_page_id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "seo_page_keyword" ADD CONSTRAINT "seo_page_keyword_keyword_id_sm_keyword_id_fk" FOREIGN KEY ("keyword_id") REFERENCES "public"."sm_keyword"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "seo_publishing_job" ADD CONSTRAINT "seo_publishing_job_project_id_sm_project_sm_project_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."sm_project"("sm_project_id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "seo_publishing_job" ADD CONSTRAINT "seo_publishing_job_cms_connection_id_seo_cms_connection_seo_cms_connection_id_fk" FOREIGN KEY ("cms_connection_id") REFERENCES "public"."seo_cms_connection"("seo_cms_connection_id") ON DELETE set null ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "seo_publishing_job" ADD CONSTRAINT "seo_publishing_job_task_id_seo_campaign_task_seo_campaign_task_id_fk" FOREIGN KEY ("task_id") REFERENCES "public"."seo_campaign_task"("seo_campaign_task_id") ON DELETE set null ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "seo_publishing_job" ADD CONSTRAINT "seo_publishing_job_target_page_id_seo_page_seo_page_id_fk" FOREIGN KEY ("target_page_id") REFERENCES "public"."seo_page"("seo_page_id") ON DELETE set null ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "seo_sitemap_source" ADD CONSTRAINT "seo_sitemap_source_project_id_sm_project_sm_project_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."sm_project"("sm_project_id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "seo_project" ADD CONSTRAINT "seo_project_organization_id_organization_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "seo_task_run" ADD CONSTRAINT "seo_task_run_project_id_seo_project_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."seo_project"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "seo_task_run" ADD CONSTRAINT "seo_task_run_requested_by_user_id_fk" FOREIGN KEY ("requested_by") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +CREATE UNIQUE INDEX "sm_company_background_website_url_unique" ON "sm_company_background" USING btree ("website_url");--> statement-breakpoint +CREATE UNIQUE INDEX "sm_keyword_phrase_unique" ON "sm_keyword" USING btree ("phrase");--> statement-breakpoint +CREATE UNIQUE INDEX "sm_keyword_source_cursor_source_keyword_unique" ON "sm_keyword_source_cursor" USING btree ("source","keyword_id");--> statement-breakpoint +CREATE INDEX "sm_keyword_source_cursor_src_idx" ON "sm_keyword_source_cursor" USING btree ("source");--> statement-breakpoint +CREATE INDEX "sm_keyword_source_cursor_kw_idx" ON "sm_keyword_source_cursor" USING btree ("keyword_id");--> statement-breakpoint +CREATE UNIQUE INDEX "sm_mention_provider_unique" ON "sm_mention" USING btree ("provider","provider_id");--> statement-breakpoint +CREATE INDEX "sm_mention_provider_created_at_idx" ON "sm_mention" USING btree ("provider_created_at");--> statement-breakpoint +CREATE INDEX "sm_pkm_project_idx" ON "sm_project_keyword_mention" USING btree ("project_id");--> statement-breakpoint +CREATE INDEX "sm_pkm_keyword_idx" ON "sm_project_keyword_mention" USING btree ("keyword_id");--> statement-breakpoint +CREATE INDEX "sm_pkm_mention_idx" ON "sm_project_keyword_mention" USING btree ("mention_id");--> statement-breakpoint +CREATE INDEX "sm_pkm_status_idx" ON "sm_project_keyword_mention" USING btree ("status");--> statement-breakpoint +CREATE INDEX "sm_pkm_matched_at_idx" ON "sm_project_keyword_mention" USING btree ("matched_at");--> statement-breakpoint +CREATE INDEX "sm_project_keyword_project_idx" ON "sm_project_keyword" USING btree ("project_id");--> statement-breakpoint +CREATE INDEX "sm_project_keyword_keyword_idx" ON "sm_project_keyword" USING btree ("keyword_id");--> statement-breakpoint +CREATE INDEX "sm_project_keyword_next_run_at_idx" ON "sm_project_keyword" USING btree ("next_run_at");--> statement-breakpoint +CREATE INDEX "sm_project_keyword_proj_created_id_idx" ON "sm_project_keyword" USING btree ("project_id","created_at","keyword_id");--> statement-breakpoint +CREATE UNIQUE INDEX "sm_pmr_unique" ON "sm_project_mention_reply" USING btree ("project_id","mention_id");--> statement-breakpoint +CREATE INDEX "sm_pmr_project_keyword_mention_idx" ON "sm_project_mention_reply" USING btree ("project_id","mention_id","attributed_keyword_id");--> statement-breakpoint +CREATE INDEX "sm_pmr_project_idx" ON "sm_project_mention_reply" USING btree ("project_id");--> statement-breakpoint +CREATE INDEX "sm_pmr_mention_idx" ON "sm_project_mention_reply" USING btree ("mention_id");--> statement-breakpoint +CREATE INDEX "sm_pmr_status_idx" ON "sm_project_mention_reply" USING btree ("status");--> statement-breakpoint +CREATE INDEX "sm_project_org_idx" ON "sm_project" USING btree ("organization_id");--> statement-breakpoint +CREATE INDEX "sm_project_current_reply_prompt_idx" ON "sm_project" USING btree ("current_reply_prompt_id");--> statement-breakpoint +CREATE INDEX "sm_prompt_created_at_idx" ON "sm_prompt" USING btree ("created_at");--> statement-breakpoint +CREATE INDEX "seo_audit_snapshot_project_idx" ON "seo_audit_snapshot" USING btree ("project_id");--> statement-breakpoint +CREATE INDEX "seo_audit_snapshot_captured_idx" ON "seo_audit_snapshot" USING btree ("captured_at");--> statement-breakpoint +CREATE INDEX "seo_calendar_event_project_idx" ON "seo_calendar_event" USING btree ("project_id");--> statement-breakpoint +CREATE INDEX "seo_campaign_project_idx" ON "seo_campaign" USING btree ("project_id");--> statement-breakpoint +CREATE INDEX "seo_campaign_status_idx" ON "seo_campaign" USING btree ("status");--> statement-breakpoint +CREATE INDEX "seo_campaign_proposal_project_idx" ON "seo_campaign_proposal" USING btree ("project_id");--> statement-breakpoint +CREATE INDEX "seo_campaign_task_campaign_idx" ON "seo_campaign_task" USING btree ("campaign_id");--> statement-breakpoint +CREATE INDEX "seo_campaign_task_status_idx" ON "seo_campaign_task" USING btree ("status");--> statement-breakpoint +CREATE INDEX "seo_cms_connection_project_idx" ON "seo_cms_connection" USING btree ("project_id");--> statement-breakpoint +CREATE INDEX "seo_keyword_cluster_project_idx" ON "seo_keyword_cluster" USING btree ("project_id");--> statement-breakpoint +CREATE UNIQUE INDEX "seo_keyword_cluster_member_unique" ON "seo_keyword_cluster_member" USING btree ("cluster_id","keyword_id");--> statement-breakpoint +CREATE INDEX "seo_keyword_cluster_member_cluster_idx" ON "seo_keyword_cluster_member" USING btree ("cluster_id");--> statement-breakpoint +CREATE UNIQUE INDEX "seo_metric_threshold_metric_unique" ON "seo_metric_threshold" USING btree ("metric_key");--> statement-breakpoint +CREATE UNIQUE INDEX "seo_page_project_url_unique" ON "seo_page" USING btree ("project_id","url");--> statement-breakpoint +CREATE INDEX "seo_page_project_idx" ON "seo_page" USING btree ("project_id");--> statement-breakpoint +CREATE INDEX "seo_page_status_idx" ON "seo_page" USING btree ("status");--> statement-breakpoint +CREATE UNIQUE INDEX "seo_page_keyword_unique" ON "seo_page_keyword" USING btree ("page_id","keyword_id");--> statement-breakpoint +CREATE INDEX "seo_page_keyword_page_idx" ON "seo_page_keyword" USING btree ("page_id");--> statement-breakpoint +CREATE INDEX "seo_page_keyword_keyword_idx" ON "seo_page_keyword" USING btree ("keyword_id");--> statement-breakpoint +CREATE INDEX "seo_publishing_job_project_idx" ON "seo_publishing_job" USING btree ("project_id");--> statement-breakpoint +CREATE UNIQUE INDEX "seo_sitemap_source_project_url_unique" ON "seo_sitemap_source" USING btree ("project_id","url");--> statement-breakpoint +CREATE INDEX "seo_project_org_idx" ON "seo_project" USING btree ("organization_id");--> statement-breakpoint +CREATE INDEX "seo_project_website_url_idx" ON "seo_project" USING btree ("website_url");--> statement-breakpoint +CREATE INDEX "seo_task_run_project_idx" ON "seo_task_run" USING btree ("project_id");--> statement-breakpoint +CREATE INDEX "seo_task_run_task_id_idx" ON "seo_task_run" USING btree ("task_id");--> statement-breakpoint +CREATE INDEX "seo_task_run_provider_idx" ON "seo_task_run" USING btree ("provider");--> statement-breakpoint +CREATE INDEX "seo_task_run_requested_by_idx" ON "seo_task_run" USING btree ("requested_by"); \ No newline at end of file diff --git a/packages/db/migrations/0000_smart_moonstone.sql b/packages/db/migrations/0000_smart_moonstone.sql deleted file mode 100644 index f24089419..000000000 --- a/packages/db/migrations/0000_smart_moonstone.sql +++ /dev/null @@ -1,249 +0,0 @@ -CREATE TABLE "account" ( - "id" text PRIMARY KEY NOT NULL, - "account_id" text NOT NULL, - "provider_id" text NOT NULL, - "user_id" text NOT NULL, - "access_token" text, - "refresh_token" text, - "id_token" text, - "access_token_expires_at" timestamp, - "refresh_token_expires_at" timestamp, - "scope" text, - "password" text, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone NOT NULL -); ---> statement-breakpoint -CREATE TABLE "invitation" ( - "id" text PRIMARY KEY NOT NULL, - "organization_id" text NOT NULL, - "email" text NOT NULL, - "role" text, - "status" text DEFAULT 'pending' NOT NULL, - "expires_at" timestamp NOT NULL, - "inviter_id" text NOT NULL -); ---> statement-breakpoint -CREATE TABLE "member" ( - "id" text PRIMARY KEY NOT NULL, - "organization_id" text NOT NULL, - "user_id" text NOT NULL, - "role" text DEFAULT 'member' NOT NULL, - "created_at" timestamp with time zone NOT NULL -); ---> statement-breakpoint -CREATE TABLE "organization" ( - "id" text PRIMARY KEY NOT NULL, - "name" text NOT NULL, - "slug" text, - "logo" text, - "created_at" timestamp with time zone NOT NULL, - "metadata" text, - CONSTRAINT "organization_slug_unique" UNIQUE("slug") -); ---> statement-breakpoint -CREATE TABLE "passkey" ( - "id" text PRIMARY KEY NOT NULL, - "name" text, - "public_key" text NOT NULL, - "user_id" text NOT NULL, - "credential_id" text NOT NULL, - "counter" integer NOT NULL, - "device_type" text NOT NULL, - "backed_up" boolean NOT NULL, - "transports" text, - "created_at" timestamp, - "aaguid" text -); ---> statement-breakpoint -CREATE TABLE "session" ( - "id" text PRIMARY KEY NOT NULL, - "expires_at" timestamp NOT NULL, - "token" text NOT NULL, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone NOT NULL, - "ip_address" text, - "user_agent" text, - "user_id" text NOT NULL, - "active_organization_id" text, - CONSTRAINT "session_token_unique" UNIQUE("token") -); ---> statement-breakpoint -CREATE TABLE "two_factor" ( - "id" text PRIMARY KEY NOT NULL, - "secret" text NOT NULL, - "backup_codes" text NOT NULL, - "user_id" text NOT NULL -); ---> statement-breakpoint -CREATE TABLE "user" ( - "id" text PRIMARY KEY NOT NULL, - "name" text NOT NULL, - "email" text NOT NULL, - "email_verified" boolean DEFAULT false NOT NULL, - "image" text, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone DEFAULT now() NOT NULL, - "two_factor_enabled" boolean DEFAULT false, - "source" text, - "goal" text, - CONSTRAINT "user_email_unique" UNIQUE("email") -); ---> statement-breakpoint -CREATE TABLE "verification" ( - "id" text PRIMARY KEY NOT NULL, - "identifier" text NOT NULL, - "value" text NOT NULL, - "expires_at" timestamp NOT NULL, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone DEFAULT now() NOT NULL -); ---> statement-breakpoint -CREATE TABLE "sm_company_background" ( - "id" uuid PRIMARY KEY NOT NULL, - "website_url" text NOT NULL, - "status" text DEFAULT 'queued' NOT NULL, - "last_indexed_at" timestamp with time zone, - "data" jsonb, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone NOT NULL -); ---> statement-breakpoint -CREATE TABLE "sm_keyword" ( - "id" uuid PRIMARY KEY NOT NULL, - "phrase" text NOT NULL, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone NOT NULL -); ---> statement-breakpoint -CREATE TABLE "sm_keyword_source_cursor" ( - "id" uuid PRIMARY KEY NOT NULL, - "keyword_id" uuid NOT NULL, - "source" text NOT NULL, - "current_cursor" text, - "latest_item_at" timestamp with time zone, - "empty_streak" integer DEFAULT 0 NOT NULL, - "next_earliest_run_at" timestamp with time zone, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone NOT NULL -); ---> statement-breakpoint -CREATE TABLE "sm_mention" ( - "id" uuid PRIMARY KEY NOT NULL, - "provider" text DEFAULT 'reddit' NOT NULL, - "provider_id" text NOT NULL, - "provider_url" text, - "provider_created_at" timestamp with time zone, - "author" text, - "title" text, - "content" text, - "metadata" jsonb, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone NOT NULL -); ---> statement-breakpoint -CREATE TABLE "sm_project_keyword_mention" ( - "project_id" uuid NOT NULL, - "keyword_id" uuid NOT NULL, - "mention_id" uuid NOT NULL, - "matched_at" timestamp with time zone NOT NULL, - "status" text DEFAULT 'new' NOT NULL, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone NOT NULL, - CONSTRAINT "sm_project_keyword_mention_project_id_keyword_id_mention_id_pk" PRIMARY KEY("project_id","keyword_id","mention_id") -); ---> statement-breakpoint -CREATE TABLE "sm_project_keyword" ( - "project_id" uuid NOT NULL, - "keyword_id" uuid NOT NULL, - "polling_interval_sec" integer, - "next_run_at" timestamp with time zone, - "last_run_at" timestamp with time zone, - "is_paused" boolean DEFAULT false NOT NULL, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone NOT NULL, - CONSTRAINT "sm_project_keyword_project_id_keyword_id_pk" PRIMARY KEY("project_id","keyword_id") -); ---> statement-breakpoint -CREATE TABLE "sm_project_mention_reply" ( - "id" uuid PRIMARY KEY NOT NULL, - "project_id" uuid NOT NULL, - "mention_id" uuid NOT NULL, - "attributed_keyword_id" uuid NOT NULL, - "model" text, - "prompt_override_id" uuid, - "reply_text" text, - "is_auto_generated" boolean DEFAULT false NOT NULL, - "status" text DEFAULT 'draft' NOT NULL, - "error" text, - "provider_published_id" text, - "provider_published_url" text, - "published_at" timestamp with time zone, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone NOT NULL -); ---> statement-breakpoint -CREATE TABLE "sm_project" ( - "sm_project_id" uuid PRIMARY KEY NOT NULL, - "organization_id" text, - "current_reply_prompt_id" uuid, - "polling_interval_sec" integer DEFAULT 900 NOT NULL, - "auto_generate_replies" boolean DEFAULT false NOT NULL, - "is_paused" boolean DEFAULT false NOT NULL, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone NOT NULL -); ---> statement-breakpoint -CREATE TABLE "sm_prompt" ( - "sm_prompt_id" uuid PRIMARY KEY NOT NULL, - "prompt" text, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone NOT NULL -); ---> statement-breakpoint -ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "invitation" ADD CONSTRAINT "invitation_organization_id_organization_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "invitation" ADD CONSTRAINT "invitation_inviter_id_user_id_fk" FOREIGN KEY ("inviter_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "member" ADD CONSTRAINT "member_organization_id_organization_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "member" ADD CONSTRAINT "member_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "passkey" ADD CONSTRAINT "passkey_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "two_factor" ADD CONSTRAINT "two_factor_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "sm_keyword_source_cursor" ADD CONSTRAINT "sm_keyword_source_cursor_keyword_id_sm_keyword_id_fk" FOREIGN KEY ("keyword_id") REFERENCES "public"."sm_keyword"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint -ALTER TABLE "sm_project_keyword_mention" ADD CONSTRAINT "sm_project_keyword_mention_project_id_sm_project_sm_project_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."sm_project"("sm_project_id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint -ALTER TABLE "sm_project_keyword_mention" ADD CONSTRAINT "sm_project_keyword_mention_keyword_id_sm_keyword_id_fk" FOREIGN KEY ("keyword_id") REFERENCES "public"."sm_keyword"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint -ALTER TABLE "sm_project_keyword_mention" ADD CONSTRAINT "sm_project_keyword_mention_mention_id_sm_mention_id_fk" FOREIGN KEY ("mention_id") REFERENCES "public"."sm_mention"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint -ALTER TABLE "sm_project_keyword_mention" ADD CONSTRAINT "sm_pkm_project_keyword_fk" FOREIGN KEY ("project_id","keyword_id") REFERENCES "public"."sm_project_keyword"("project_id","keyword_id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "sm_project_keyword" ADD CONSTRAINT "sm_project_keyword_project_id_sm_project_sm_project_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."sm_project"("sm_project_id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint -ALTER TABLE "sm_project_keyword" ADD CONSTRAINT "sm_project_keyword_keyword_id_sm_keyword_id_fk" FOREIGN KEY ("keyword_id") REFERENCES "public"."sm_keyword"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint -ALTER TABLE "sm_project_mention_reply" ADD CONSTRAINT "sm_project_mention_reply_project_id_sm_project_sm_project_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."sm_project"("sm_project_id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint -ALTER TABLE "sm_project_mention_reply" ADD CONSTRAINT "sm_project_mention_reply_mention_id_sm_mention_id_fk" FOREIGN KEY ("mention_id") REFERENCES "public"."sm_mention"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint -ALTER TABLE "sm_project_mention_reply" ADD CONSTRAINT "sm_project_mention_reply_attributed_keyword_id_sm_keyword_id_fk" FOREIGN KEY ("attributed_keyword_id") REFERENCES "public"."sm_keyword"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint -ALTER TABLE "sm_project_mention_reply" ADD CONSTRAINT "sm_project_mention_reply_prompt_override_id_sm_prompt_sm_prompt_id_fk" FOREIGN KEY ("prompt_override_id") REFERENCES "public"."sm_prompt"("sm_prompt_id") ON DELETE set null ON UPDATE cascade;--> statement-breakpoint -ALTER TABLE "sm_project_mention_reply" ADD CONSTRAINT "sm_pmr_project_keyword_mention_fk" FOREIGN KEY ("project_id","mention_id","attributed_keyword_id") REFERENCES "public"."sm_project_keyword_mention"("project_id","mention_id","keyword_id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "sm_project" ADD CONSTRAINT "sm_project_organization_id_organization_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint -ALTER TABLE "sm_project" ADD CONSTRAINT "sm_project_current_reply_prompt_id_sm_prompt_sm_prompt_id_fk" FOREIGN KEY ("current_reply_prompt_id") REFERENCES "public"."sm_prompt"("sm_prompt_id") ON DELETE set null ON UPDATE cascade;--> statement-breakpoint -CREATE UNIQUE INDEX "sm_company_background_website_url_unique" ON "sm_company_background" USING btree ("website_url");--> statement-breakpoint -CREATE UNIQUE INDEX "sm_keyword_phrase_unique" ON "sm_keyword" USING btree ("phrase");--> statement-breakpoint -CREATE UNIQUE INDEX "sm_keyword_source_cursor_source_keyword_unique" ON "sm_keyword_source_cursor" USING btree ("source","keyword_id");--> statement-breakpoint -CREATE INDEX "sm_keyword_source_cursor_src_idx" ON "sm_keyword_source_cursor" USING btree ("source");--> statement-breakpoint -CREATE INDEX "sm_keyword_source_cursor_kw_idx" ON "sm_keyword_source_cursor" USING btree ("keyword_id");--> statement-breakpoint -CREATE UNIQUE INDEX "sm_mention_provider_unique" ON "sm_mention" USING btree ("provider","provider_id");--> statement-breakpoint -CREATE INDEX "sm_mention_provider_created_at_idx" ON "sm_mention" USING btree ("provider_created_at");--> statement-breakpoint -CREATE INDEX "sm_pkm_project_idx" ON "sm_project_keyword_mention" USING btree ("project_id");--> statement-breakpoint -CREATE INDEX "sm_pkm_keyword_idx" ON "sm_project_keyword_mention" USING btree ("keyword_id");--> statement-breakpoint -CREATE INDEX "sm_pkm_mention_idx" ON "sm_project_keyword_mention" USING btree ("mention_id");--> statement-breakpoint -CREATE INDEX "sm_pkm_status_idx" ON "sm_project_keyword_mention" USING btree ("status");--> statement-breakpoint -CREATE INDEX "sm_pkm_matched_at_idx" ON "sm_project_keyword_mention" USING btree ("matched_at");--> statement-breakpoint -CREATE INDEX "sm_project_keyword_project_idx" ON "sm_project_keyword" USING btree ("project_id");--> statement-breakpoint -CREATE INDEX "sm_project_keyword_keyword_idx" ON "sm_project_keyword" USING btree ("keyword_id");--> statement-breakpoint -CREATE INDEX "sm_project_keyword_next_run_at_idx" ON "sm_project_keyword" USING btree ("next_run_at");--> statement-breakpoint -CREATE INDEX "sm_project_keyword_proj_created_id_idx" ON "sm_project_keyword" USING btree ("project_id","created_at","keyword_id");--> statement-breakpoint -CREATE UNIQUE INDEX "sm_pmr_unique" ON "sm_project_mention_reply" USING btree ("project_id","mention_id");--> statement-breakpoint -CREATE INDEX "sm_pmr_project_keyword_mention_idx" ON "sm_project_mention_reply" USING btree ("project_id","mention_id","attributed_keyword_id");--> statement-breakpoint -CREATE INDEX "sm_pmr_project_idx" ON "sm_project_mention_reply" USING btree ("project_id");--> statement-breakpoint -CREATE INDEX "sm_pmr_mention_idx" ON "sm_project_mention_reply" USING btree ("mention_id");--> statement-breakpoint -CREATE INDEX "sm_pmr_status_idx" ON "sm_project_mention_reply" USING btree ("status");--> statement-breakpoint -CREATE INDEX "sm_project_org_idx" ON "sm_project" USING btree ("organization_id");--> statement-breakpoint -CREATE INDEX "sm_project_current_reply_prompt_idx" ON "sm_project" USING btree ("current_reply_prompt_id");--> statement-breakpoint -CREATE INDEX "sm_prompt_created_at_idx" ON "sm_prompt" USING btree ("created_at"); \ No newline at end of file diff --git a/packages/db/migrations/meta/0000_snapshot.json b/packages/db/migrations/meta/0000_snapshot.json index d1ca71af6..259f1aa4c 100644 --- a/packages/db/migrations/meta/0000_snapshot.json +++ b/packages/db/migrations/meta/0000_snapshot.json @@ -1,5 +1,5 @@ { - "id": "94de5e4e-9b2b-4d0a-8601-d38aa952af96", + "id": "1e20e375-537c-4b71-928d-b8657c494b5b", "prevId": "00000000-0000-0000-0000-000000000000", "version": "7", "dialect": "postgresql", @@ -1885,6 +1885,1982 @@ "policies": {}, "checkConstraints": {}, "isRLSEnabled": false + }, + "public.seo_audit_snapshot": { + "name": "seo_audit_snapshot", + "schema": "", + "columns": { + "seo_audit_snapshot_id": { + "name": "seo_audit_snapshot_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "page_id": { + "name": "page_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "keyword_id": { + "name": "keyword_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "captured_at": { + "name": "captured_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "overall_score": { + "name": "overall_score", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'ok'" + }, + "metrics": { + "name": "metrics", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "explanation_technical": { + "name": "explanation_technical", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "explanation_layman": { + "name": "explanation_layman", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "seo_audit_snapshot_project_idx": { + "name": "seo_audit_snapshot_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "seo_audit_snapshot_captured_idx": { + "name": "seo_audit_snapshot_captured_idx", + "columns": [ + { + "expression": "captured_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "seo_audit_snapshot_project_id_sm_project_sm_project_id_fk": { + "name": "seo_audit_snapshot_project_id_sm_project_sm_project_id_fk", + "tableFrom": "seo_audit_snapshot", + "tableTo": "sm_project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "sm_project_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "seo_audit_snapshot_page_id_seo_page_seo_page_id_fk": { + "name": "seo_audit_snapshot_page_id_seo_page_seo_page_id_fk", + "tableFrom": "seo_audit_snapshot", + "tableTo": "seo_page", + "columnsFrom": [ + "page_id" + ], + "columnsTo": [ + "seo_page_id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "seo_audit_snapshot_keyword_id_sm_keyword_id_fk": { + "name": "seo_audit_snapshot_keyword_id_sm_keyword_id_fk", + "tableFrom": "seo_audit_snapshot", + "tableTo": "sm_keyword", + "columnsFrom": [ + "keyword_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.seo_calendar_event": { + "name": "seo_calendar_event", + "schema": "", + "columns": { + "seo_calendar_event_id": { + "name": "seo_calendar_event_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "campaign_id": { + "name": "campaign_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "start_at": { + "name": "start_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "end_at": { + "name": "end_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'scheduled'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "seo_calendar_event_project_idx": { + "name": "seo_calendar_event_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "seo_calendar_event_project_id_sm_project_sm_project_id_fk": { + "name": "seo_calendar_event_project_id_sm_project_sm_project_id_fk", + "tableFrom": "seo_calendar_event", + "tableTo": "sm_project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "sm_project_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "seo_calendar_event_campaign_id_seo_campaign_seo_campaign_id_fk": { + "name": "seo_calendar_event_campaign_id_seo_campaign_seo_campaign_id_fk", + "tableFrom": "seo_calendar_event", + "tableTo": "seo_campaign", + "columnsFrom": [ + "campaign_id" + ], + "columnsTo": [ + "seo_campaign_id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "seo_calendar_event_task_id_seo_campaign_task_seo_campaign_task_id_fk": { + "name": "seo_calendar_event_task_id_seo_campaign_task_seo_campaign_task_id_fk", + "tableFrom": "seo_calendar_event", + "tableTo": "seo_campaign_task", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "seo_campaign_task_id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.seo_campaign": { + "name": "seo_campaign", + "schema": "", + "columns": { + "seo_campaign_id": { + "name": "seo_campaign_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "proposal_id": { + "name": "proposal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thesis": { + "name": "thesis", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'planned'" + }, + "start_date": { + "name": "start_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "end_date": { + "name": "end_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "goal_metrics": { + "name": "goal_metrics", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "schedule": { + "name": "schedule", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "seo_campaign_project_idx": { + "name": "seo_campaign_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "seo_campaign_status_idx": { + "name": "seo_campaign_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "seo_campaign_project_id_sm_project_sm_project_id_fk": { + "name": "seo_campaign_project_id_sm_project_sm_project_id_fk", + "tableFrom": "seo_campaign", + "tableTo": "sm_project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "sm_project_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "seo_campaign_proposal_id_seo_campaign_proposal_seo_campaign_proposal_id_fk": { + "name": "seo_campaign_proposal_id_seo_campaign_proposal_seo_campaign_proposal_id_fk", + "tableFrom": "seo_campaign", + "tableTo": "seo_campaign_proposal", + "columnsFrom": [ + "proposal_id" + ], + "columnsTo": [ + "seo_campaign_proposal_id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.seo_campaign_proposal": { + "name": "seo_campaign_proposal", + "schema": "", + "columns": { + "seo_campaign_proposal_id": { + "name": "seo_campaign_proposal_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thesis": { + "name": "thesis", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "why_five_ws": { + "name": "why_five_ws", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "explanation_technical": { + "name": "explanation_technical", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "explanation_layman": { + "name": "explanation_layman", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "baseline_metrics": { + "name": "baseline_metrics", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "requested_by": { + "name": "requested_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "rejected_at": { + "name": "rejected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "rejection_reason": { + "name": "rejection_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "seo_campaign_proposal_project_idx": { + "name": "seo_campaign_proposal_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "seo_campaign_proposal_project_id_sm_project_sm_project_id_fk": { + "name": "seo_campaign_proposal_project_id_sm_project_sm_project_id_fk", + "tableFrom": "seo_campaign_proposal", + "tableTo": "sm_project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "sm_project_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.seo_campaign_task": { + "name": "seo_campaign_task", + "schema": "", + "columns": { + "seo_campaign_task_id": { + "name": "seo_campaign_task_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "campaign_id": { + "name": "campaign_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'todo'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_page_id": { + "name": "target_page_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_keyword_id": { + "name": "target_keyword_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "due_at": { + "name": "due_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "assignee_id": { + "name": "assignee_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "seo_campaign_task_campaign_idx": { + "name": "seo_campaign_task_campaign_idx", + "columns": [ + { + "expression": "campaign_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "seo_campaign_task_status_idx": { + "name": "seo_campaign_task_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "seo_campaign_task_campaign_id_seo_campaign_seo_campaign_id_fk": { + "name": "seo_campaign_task_campaign_id_seo_campaign_seo_campaign_id_fk", + "tableFrom": "seo_campaign_task", + "tableTo": "seo_campaign", + "columnsFrom": [ + "campaign_id" + ], + "columnsTo": [ + "seo_campaign_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "seo_campaign_task_target_page_id_seo_page_seo_page_id_fk": { + "name": "seo_campaign_task_target_page_id_seo_page_seo_page_id_fk", + "tableFrom": "seo_campaign_task", + "tableTo": "seo_page", + "columnsFrom": [ + "target_page_id" + ], + "columnsTo": [ + "seo_page_id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "seo_campaign_task_target_keyword_id_sm_keyword_id_fk": { + "name": "seo_campaign_task_target_keyword_id_sm_keyword_id_fk", + "tableFrom": "seo_campaign_task", + "tableTo": "sm_keyword", + "columnsFrom": [ + "target_keyword_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.seo_cms_connection": { + "name": "seo_cms_connection", + "schema": "", + "columns": { + "seo_cms_connection_id": { + "name": "seo_cms_connection_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_site_id": { + "name": "external_site_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'disconnected'" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "seo_cms_connection_project_idx": { + "name": "seo_cms_connection_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "seo_cms_connection_project_id_sm_project_sm_project_id_fk": { + "name": "seo_cms_connection_project_id_sm_project_sm_project_id_fk", + "tableFrom": "seo_cms_connection", + "tableTo": "sm_project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "sm_project_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.seo_keyword_cluster": { + "name": "seo_keyword_cluster", + "schema": "", + "columns": { + "seo_keyword_cluster_id": { + "name": "seo_keyword_cluster_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "centroid_keyword_id": { + "name": "centroid_keyword_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "seo_keyword_cluster_project_idx": { + "name": "seo_keyword_cluster_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "seo_keyword_cluster_project_id_sm_project_sm_project_id_fk": { + "name": "seo_keyword_cluster_project_id_sm_project_sm_project_id_fk", + "tableFrom": "seo_keyword_cluster", + "tableTo": "sm_project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "sm_project_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "seo_keyword_cluster_centroid_keyword_id_sm_keyword_id_fk": { + "name": "seo_keyword_cluster_centroid_keyword_id_sm_keyword_id_fk", + "tableFrom": "seo_keyword_cluster", + "tableTo": "sm_keyword", + "columnsFrom": [ + "centroid_keyword_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.seo_keyword_cluster_member": { + "name": "seo_keyword_cluster_member", + "schema": "", + "columns": { + "seo_keyword_cluster_member_id": { + "name": "seo_keyword_cluster_member_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "cluster_id": { + "name": "cluster_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "keyword_id": { + "name": "keyword_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "seo_keyword_cluster_member_unique": { + "name": "seo_keyword_cluster_member_unique", + "columns": [ + { + "expression": "cluster_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "keyword_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "seo_keyword_cluster_member_cluster_idx": { + "name": "seo_keyword_cluster_member_cluster_idx", + "columns": [ + { + "expression": "cluster_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "seo_keyword_cluster_member_cluster_id_seo_keyword_cluster_seo_keyword_cluster_id_fk": { + "name": "seo_keyword_cluster_member_cluster_id_seo_keyword_cluster_seo_keyword_cluster_id_fk", + "tableFrom": "seo_keyword_cluster_member", + "tableTo": "seo_keyword_cluster", + "columnsFrom": [ + "cluster_id" + ], + "columnsTo": [ + "seo_keyword_cluster_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "seo_keyword_cluster_member_keyword_id_sm_keyword_id_fk": { + "name": "seo_keyword_cluster_member_keyword_id_sm_keyword_id_fk", + "tableFrom": "seo_keyword_cluster_member", + "tableTo": "sm_keyword", + "columnsFrom": [ + "keyword_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.seo_metric_threshold": { + "name": "seo_metric_threshold", + "schema": "", + "columns": { + "seo_metric_threshold_id": { + "name": "seo_metric_threshold_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "metric_key": { + "name": "metric_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "good_threshold": { + "name": "good_threshold", + "type": "numeric(10, 3)", + "primaryKey": false, + "notNull": false + }, + "bad_threshold": { + "name": "bad_threshold", + "type": "numeric(10, 3)", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "seo_metric_threshold_metric_unique": { + "name": "seo_metric_threshold_metric_unique", + "columns": [ + { + "expression": "metric_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.seo_page": { + "name": "seo_page", + "schema": "", + "columns": { + "seo_page_id": { + "name": "seo_page_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "canonical_url": { + "name": "canonical_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_text": { + "name": "content_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_html": { + "name": "content_html", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_markdown": { + "name": "content_markdown", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "extractor": { + "name": "extractor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_status": { + "name": "http_status", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "robots_noindex": { + "name": "robots_noindex", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "robots_nofollow": { + "name": "robots_nofollow", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "sitemap_priority": { + "name": "sitemap_priority", + "type": "numeric(2, 1)", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'discovered'" + }, + "last_crawled_at": { + "name": "last_crawled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "seo_page_project_url_unique": { + "name": "seo_page_project_url_unique", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "seo_page_project_idx": { + "name": "seo_page_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "seo_page_status_idx": { + "name": "seo_page_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "seo_page_project_id_sm_project_sm_project_id_fk": { + "name": "seo_page_project_id_sm_project_sm_project_id_fk", + "tableFrom": "seo_page", + "tableTo": "sm_project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "sm_project_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.seo_page_keyword": { + "name": "seo_page_keyword", + "schema": "", + "columns": { + "seo_page_keyword_id": { + "name": "seo_page_keyword_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "page_id": { + "name": "page_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "keyword_id": { + "name": "keyword_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "match_score": { + "name": "match_score", + "type": "numeric(4, 3)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "seo_page_keyword_unique": { + "name": "seo_page_keyword_unique", + "columns": [ + { + "expression": "page_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "keyword_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "seo_page_keyword_page_idx": { + "name": "seo_page_keyword_page_idx", + "columns": [ + { + "expression": "page_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "seo_page_keyword_keyword_idx": { + "name": "seo_page_keyword_keyword_idx", + "columns": [ + { + "expression": "keyword_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "seo_page_keyword_page_id_seo_page_seo_page_id_fk": { + "name": "seo_page_keyword_page_id_seo_page_seo_page_id_fk", + "tableFrom": "seo_page_keyword", + "tableTo": "seo_page", + "columnsFrom": [ + "page_id" + ], + "columnsTo": [ + "seo_page_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "seo_page_keyword_keyword_id_sm_keyword_id_fk": { + "name": "seo_page_keyword_keyword_id_sm_keyword_id_fk", + "tableFrom": "seo_page_keyword", + "tableTo": "sm_keyword", + "columnsFrom": [ + "keyword_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.seo_publishing_job": { + "name": "seo_publishing_job", + "schema": "", + "columns": { + "seo_publishing_job_id": { + "name": "seo_publishing_job_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cms_connection_id": { + "name": "cms_connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_page_id": { + "name": "target_page_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'enqueued'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "external_job_id": { + "name": "external_job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "seo_publishing_job_project_idx": { + "name": "seo_publishing_job_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "seo_publishing_job_project_id_sm_project_sm_project_id_fk": { + "name": "seo_publishing_job_project_id_sm_project_sm_project_id_fk", + "tableFrom": "seo_publishing_job", + "tableTo": "sm_project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "sm_project_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "seo_publishing_job_cms_connection_id_seo_cms_connection_seo_cms_connection_id_fk": { + "name": "seo_publishing_job_cms_connection_id_seo_cms_connection_seo_cms_connection_id_fk", + "tableFrom": "seo_publishing_job", + "tableTo": "seo_cms_connection", + "columnsFrom": [ + "cms_connection_id" + ], + "columnsTo": [ + "seo_cms_connection_id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "seo_publishing_job_task_id_seo_campaign_task_seo_campaign_task_id_fk": { + "name": "seo_publishing_job_task_id_seo_campaign_task_seo_campaign_task_id_fk", + "tableFrom": "seo_publishing_job", + "tableTo": "seo_campaign_task", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "seo_campaign_task_id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "seo_publishing_job_target_page_id_seo_page_seo_page_id_fk": { + "name": "seo_publishing_job_target_page_id_seo_page_seo_page_id_fk", + "tableFrom": "seo_publishing_job", + "tableTo": "seo_page", + "columnsFrom": [ + "target_page_id" + ], + "columnsTo": [ + "seo_page_id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.seo_sitemap_source": { + "name": "seo_sitemap_source", + "schema": "", + "columns": { + "seo_sitemap_source_id": { + "name": "seo_sitemap_source_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_fetched_at": { + "name": "last_fetched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_fetch_error": { + "name": "last_fetch_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discovered_count": { + "name": "discovered_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "seo_sitemap_source_project_url_unique": { + "name": "seo_sitemap_source_project_url_unique", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "seo_sitemap_source_project_id_sm_project_sm_project_id_fk": { + "name": "seo_sitemap_source_project_id_sm_project_sm_project_id_fk", + "tableFrom": "seo_sitemap_source", + "tableTo": "sm_project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "sm_project_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.seo_project": { + "name": "seo_project", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "website_url": { + "name": "website_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "website_info": { + "name": "website_info", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "seo_project_org_idx": { + "name": "seo_project_org_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "seo_project_website_url_idx": { + "name": "seo_project_website_url_idx", + "columns": [ + { + "expression": "website_url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "seo_project_organization_id_organization_id_fk": { + "name": "seo_project_organization_id_organization_id_fk", + "tableFrom": "seo_project", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.seo_task_run": { + "name": "seo_task_run", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "requested_by": { + "name": "requested_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'trigger.dev'" + }, + "input_data": { + "name": "input_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "cost_in_cents": { + "name": "cost_in_cents", + "type": "numeric(10, 5)", + "primaryKey": false, + "notNull": true, + "default": "'0.00000'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "seo_task_run_project_idx": { + "name": "seo_task_run_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "seo_task_run_task_id_idx": { + "name": "seo_task_run_task_id_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "seo_task_run_provider_idx": { + "name": "seo_task_run_provider_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "seo_task_run_requested_by_idx": { + "name": "seo_task_run_requested_by_idx", + "columns": [ + { + "expression": "requested_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "seo_task_run_project_id_seo_project_id_fk": { + "name": "seo_task_run_project_id_seo_project_id_fk", + "tableFrom": "seo_task_run", + "tableTo": "seo_project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "seo_task_run_requested_by_user_id_fk": { + "name": "seo_task_run_requested_by_user_id_fk", + "tableFrom": "seo_task_run", + "tableTo": "user", + "columnsFrom": [ + "requested_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false } }, "enums": {}, diff --git a/packages/db/migrations/meta/_journal.json b/packages/db/migrations/meta/_journal.json index 638ab5901..8a1e674d6 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -5,8 +5,8 @@ { "idx": 0, "version": "7", - "when": 1757885592087, - "tag": "0000_smart_moonstone", + "when": 1758287612370, + "tag": "0000_motionless_tomorrow_man", "breakpoints": true } ] diff --git a/packages/db/src/client.ts b/packages/db/src/client.ts index 907ff8229..66e666751 100644 --- a/packages/db/src/client.ts +++ b/packages/db/src/client.ts @@ -1,12 +1,16 @@ import { drizzle } from "drizzle-orm/postgres-js"; import postgres from "postgres"; import { dbEnv } from "./env"; +import * as authSchema from "./schema/auth-schema"; import * as mentionScheme from "./schema/mention"; +import * as taskScheme from "./schema/seo"; export * from "drizzle-orm"; export const schema = { + ...authSchema, ...mentionScheme, + ...taskScheme, }; export const createDb = () => { diff --git a/packages/db/src/schema/_table.ts b/packages/db/src/schema/_table.ts index 5b56f641e..334a51111 100644 --- a/packages/db/src/schema/_table.ts +++ b/packages/db/src/schema/_table.ts @@ -2,3 +2,4 @@ import { pgTableCreator } from "drizzle-orm/pg-core"; export const pgAppTable = pgTableCreator((name) => `ra_${name}`); export const pgMentionTable = pgTableCreator((name) => `sm_${name}`); +export const pgSeoTable = pgTableCreator((name) => `seo_${name}`); diff --git a/packages/db/src/schema/mention/auth-schema.ts b/packages/db/src/schema/auth-schema.ts similarity index 100% rename from packages/db/src/schema/mention/auth-schema.ts rename to packages/db/src/schema/auth-schema.ts diff --git a/packages/db/src/schema/mention/company-background-schema.ts b/packages/db/src/schema/mention/company-background-schema.ts index adc7a28d8..5566fda99 100644 --- a/packages/db/src/schema/mention/company-background-schema.ts +++ b/packages/db/src/schema/mention/company-background-schema.ts @@ -36,13 +36,12 @@ export const smCompanyBackgroundRelations = relations( smCompanyBackground, () => ({}), ); -export const companyInsertSchema = createInsertSchema(smCompanyBackground).omit( - "createdAt", - "updatedAt", - "id", -); -export const companyUpdateSchema = createUpdateSchema( +export const smcCompanyBackgroundInsertSchema = createInsertSchema( + smCompanyBackground, +).omit("createdAt", "updatedAt", "id"); +export const smCompanyBackgroundUpdateSchema = createUpdateSchema( smCompanyBackground, {}, ).omit("createdAt", "updatedAt", "id"); -export const companySelectSchema = createSelectSchema(smCompanyBackground); +export const smCompanyBackgroundSelectSchema = + createSelectSchema(smCompanyBackground); diff --git a/packages/db/src/schema/mention/index.ts b/packages/db/src/schema/mention/index.ts index 1f8a37721..66e0e56d1 100644 --- a/packages/db/src/schema/mention/index.ts +++ b/packages/db/src/schema/mention/index.ts @@ -1,4 +1,3 @@ -export * from "./auth-schema"; export * from "./company-background-schema"; export * from "./keyword-schema"; export * from "./keyword-source-cursor-schema"; diff --git a/packages/db/src/schema/mention/project-schema.ts b/packages/db/src/schema/mention/project-schema.ts index 178f2ba09..c81c309e3 100644 --- a/packages/db/src/schema/mention/project-schema.ts +++ b/packages/db/src/schema/mention/project-schema.ts @@ -7,7 +7,7 @@ import { relations } from "drizzle-orm"; import { boolean, index, integer, text, uuid } from "drizzle-orm/pg-core"; import { timestamps, uuidv7 } from "../_helper"; import { pgMentionTable } from "../_table"; -import { organization } from "./auth-schema"; +import { organization } from "../auth-schema"; import { smProjectKeyword } from "./project-keyword-schema"; import { smPrompt } from "./prompt-schema"; diff --git a/packages/db/src/schema/seo/index.ts b/packages/db/src/schema/seo/index.ts new file mode 100644 index 000000000..6692ee99b --- /dev/null +++ b/packages/db/src/schema/seo/index.ts @@ -0,0 +1,4 @@ +export * from "./core-schema"; +export * from "./project-schema"; +export * from "./task-run-schema"; +export * from "./task-run-schema"; diff --git a/packages/db/src/schema/seo/project-schema.ts b/packages/db/src/schema/seo/project-schema.ts new file mode 100644 index 000000000..4012710be --- /dev/null +++ b/packages/db/src/schema/seo/project-schema.ts @@ -0,0 +1,51 @@ +import { type } from "arktype"; +import { + createInsertSchema, + createSelectSchema, + createUpdateSchema, +} from "drizzle-arktype"; +import { relations } from "drizzle-orm"; +import { index, jsonb, text, uuid } from "drizzle-orm/pg-core"; +import { timestamps, uuidv7 } from "../_helper"; +import { pgSeoTable } from "../_table"; +import { organization } from "../auth-schema"; +import { seoTaskRun } from "./task-run-schema"; + +export const seoWebsiteInfoSchema = type({ + version: "'v1'", + businessOverview: "string", + idealCustomer: "string", + serviceRegion: "string", + industry: "string", +}); + +export const seoProject = pgSeoTable( + "project", + { + id: uuid().primaryKey().$defaultFn(uuidv7), + organizationId: text() + .notNull() + .references(() => organization.id, { + onDelete: "cascade", + onUpdate: "cascade", + }), + websiteUrl: text().notNull(), + websiteInfo: jsonb().$type(), + ...timestamps, + }, + (table) => [ + index("seo_project_org_idx").on(table.organizationId), + index("seo_project_website_url_idx").on(table.websiteUrl), + ], +); + +export const seoProjectRelations = relations(seoProject, ({ one, many }) => ({ + organization: one(organization, { + fields: [seoProject.organizationId], + references: [organization.id], + }), + tasks: many(seoTaskRun), +})); +export const seoProjectInsertSchema = createInsertSchema(seoProject); +export const seoProjectSelectSchema = createSelectSchema(seoProject); +export const seoProjectUpdateSchema = createUpdateSchema(seoProject); diff --git a/packages/db/src/schema/seo/task-run-schema.ts b/packages/db/src/schema/seo/task-run-schema.ts new file mode 100644 index 000000000..cffc1223a --- /dev/null +++ b/packages/db/src/schema/seo/task-run-schema.ts @@ -0,0 +1,73 @@ +import { + createInsertSchema, + createSelectSchema, + createUpdateSchema, +} from "drizzle-arktype"; +import { relations } from "drizzle-orm"; +import { + index, + integer, + jsonb, + numeric, + text, + uuid, +} from "drizzle-orm/pg-core"; +import { timestamps, uuidv7 } from "../_helper"; +import { pgSeoTable } from "../_table"; +import { user } from "../auth-schema"; +import { seoProject } from "./project-schema"; + +export const seoTaskRun = pgSeoTable( + "task_run", + { + id: uuid().primaryKey().$defaultFn(uuidv7), + projectId: uuid() + .notNull() + .references(() => seoProject.id, { + onDelete: "cascade", + onUpdate: "cascade", + }), + requestedBy: text() + .notNull() + .references(() => user.id, { + onDelete: "cascade", + onUpdate: "cascade", + }), + taskId: text().notNull(), + provider: text().notNull().default("trigger.dev"), + inputData: jsonb().notNull().$type<{ + type: "site-understanding"; + siteUrl: string; + }>(), + costInCents: numeric({ + mode: "string", + precision: 10, + scale: 5, + }) + .notNull() + .default("0.00000"), + durationMs: integer().notNull().default(0), + ...timestamps, + }, + (table) => [ + index("seo_task_run_project_idx").on(table.projectId), + index("seo_task_run_task_id_idx").on(table.taskId), + index("seo_task_run_provider_idx").on(table.provider), + index("seo_task_run_requested_by_idx").on(table.requestedBy), + ], +); + +export const seoTaskRunRelations = relations(seoTaskRun, ({ one }) => ({ + user: one(user, { + fields: [seoTaskRun.requestedBy], + references: [user.id], + }), + project: one(seoProject, { + fields: [seoTaskRun.projectId], + references: [seoProject.id], + }), +})); + +export const seoTaskRunInsertSchema = createInsertSchema(seoTaskRun); +export const seoTaskRunUpdateSchema = createUpdateSchema(seoTaskRun); +export const seoTaskRunSelectSchema = createSelectSchema(seoTaskRun); From d2913e02565cdcbb60032f4dd2cd2b8369129c74 Mon Sep 17 00:00:00 2001 From: ElasticBottle Date: Sat, 20 Sep 2025 06:16:12 +0800 Subject: [PATCH 15/38] feat(task): add llm parsing for site data --- packages/task/package.json | 9 +- packages/task/src/env.ts | 3 +- .../task/src/lib/ai-tools/get-site-data.ts | 31 +++ .../task/src/lib/ai-tools/llm-parse-json.ts | 86 +++++++ packages/task/src/lib/ai-tools/search-site.ts | 43 ++++ packages/task/src/lib/compute-search-index.ts | 219 ------------------ packages/task/src/lib/orama/site-schema.ts | 10 + packages/task/src/trigger/understand-site.ts | 112 ++++++++- 8 files changed, 279 insertions(+), 234 deletions(-) create mode 100644 packages/task/src/lib/ai-tools/get-site-data.ts create mode 100644 packages/task/src/lib/ai-tools/llm-parse-json.ts create mode 100644 packages/task/src/lib/ai-tools/search-site.ts delete mode 100644 packages/task/src/lib/compute-search-index.ts create mode 100644 packages/task/src/lib/orama/site-schema.ts diff --git a/packages/task/package.json b/packages/task/package.json index 1df1dc593..1e5043a01 100644 --- a/packages/task/package.json +++ b/packages/task/package.json @@ -15,7 +15,7 @@ }, "scripts": { "build": "tsc", - "dev:trigger": "pnpm dotenvx run -f ../../.env.local -f ../../.env -- pnpx trigger.dev@4.0.2 dev", + "dev:trigger": "pnpm dotenvx run -f ../../.env.local -f ../../.env -- pnpx trigger.dev@latest dev", "clean": "git clean -xdf .turbo node_modules dist .cache .trigger storage", "format": "bun x @biomejs/biome format . --write", "lint": "bun x @biomejs/biome lint . --write", @@ -23,15 +23,18 @@ }, "devDependencies": { "@rectangular-labs/typescript": "workspace:*", - "@trigger.dev/build": "4.0.2", + "@trigger.dev/build": "4.0.4", "@types/node": "^24.5.1", "typescript": "^5.9.2" }, "dependencies": { + "@ai-sdk/google": "^2.0.14", "@orama/orama": "^3.1.14", + "@rectangular-labs/db": "workspace:*", "@rectangular-labs/result": "workspace:*", "@t3-oss/env-core": "^0.13.8", - "@trigger.dev/sdk": "4.0.2", + "@trigger.dev/sdk": "4.0.4", + "ai": "^5.0.47", "arktype": "^2.1.22", "crawlee": "^3.14.1", "defuddle": "^0.6.6", diff --git a/packages/task/src/env.ts b/packages/task/src/env.ts index 450f8538e..7a2a0f93d 100644 --- a/packages/task/src/env.ts +++ b/packages/task/src/env.ts @@ -1,10 +1,11 @@ import { createEnv } from "@t3-oss/env-core"; import { type } from "arktype"; -export const dbEnv = () => +export const taskEnv = () => createEnv({ server: { TRIGGER_SECRET_KEY: type("string"), + GOOGLE_GENERATIVE_AI_API_KEY: type("string"), }, runtimeEnv: process.env, emptyStringAsUndefined: true, diff --git a/packages/task/src/lib/ai-tools/get-site-data.ts b/packages/task/src/lib/ai-tools/get-site-data.ts new file mode 100644 index 000000000..a7ea5d064 --- /dev/null +++ b/packages/task/src/lib/ai-tools/get-site-data.ts @@ -0,0 +1,31 @@ +import { type AnyOrama, getByID } from "@orama/orama"; +import { type JSONSchema7, jsonSchema, tool } from "ai"; +import { type } from "arktype"; +import type { SiteSchema } from "../orama/site-schema"; + +const toolInputSchema = type({ + ids: type("string[]").describe( + "Document id from search-sites hits[number].id", + ), +}); +export function createGetSitesDataTool(db: AnyOrama) { + return tool({ + description: + "Retrieve the full, original site data by its hit id returned from the `search-sites` tool.", + inputSchema: jsonSchema( + toolInputSchema.toJsonSchema() as JSONSchema7, + ), + execute: async ({ ids }) => { + const documents = []; + for (const id of ids) { + const document = await getByID(db, id); + if (!document) { + continue; + } + const { contentHtml: _, text: __, extractor: ___, ...rest } = document; + documents.push(rest); + } + return { found: documents.length > 0, documents }; + }, + }); +} diff --git a/packages/task/src/lib/ai-tools/llm-parse-json.ts b/packages/task/src/lib/ai-tools/llm-parse-json.ts new file mode 100644 index 000000000..6a1fadb9c --- /dev/null +++ b/packages/task/src/lib/ai-tools/llm-parse-json.ts @@ -0,0 +1,86 @@ +import { google } from "@ai-sdk/google"; +import { generateObject, type JSONSchema7, jsonSchema } from "ai"; +import type { type } from "arktype"; + +export async function llmParseJson( + outputData: string, + schema: T, +): Promise> { + const functionStartTime = Date.now(); // Record start time for the whole function + const llmParseJsonLabel = `llmParseJson for schema ${schema.description ?? "unknown_schema"}`; + console.time(llmParseJsonLabel); + + const extractionPrompt = `Convert the following text into a valid JSON in javascript: +${outputData}`; + + const generateObjectLabel = `generateObject in llmParseJson for schema ${schema.description ?? "unknown_schema"}`; + console.time(generateObjectLabel); + try { + const { object } = await generateObject({ + model: google("gemini-2.5-flash-lite"), + messages: [{ role: "user", content: extractionPrompt }], + temperature: 0, + schema: jsonSchema>(schema.toJsonSchema() as JSONSchema7), + experimental_repairText: ({ text }) => { + let repairedText = text; + + console.log( + "in experimental_repairText with text starting with ", + repairedText.substring(0, 100), + ); + + const tripleTildeRegex = /```(?:json)?\s*([\s\S]*?)\s*```/; + const tripleTildeMatch = repairedText.match(tripleTildeRegex); + + const singleTildeRegex = /`\s*({[^`]*?}|\[(?:[^`]*?)\])\s*`/; + const singleTildeMatch = repairedText.match(singleTildeRegex); + + if (tripleTildeMatch?.[1]) { + repairedText = tripleTildeMatch[1]; + } else if (singleTildeMatch?.[1]) { + repairedText = singleTildeMatch[1]; + } else { + repairedText = repairedText + .replaceAll("ny\n```json\n", "") + .replaceAll("\n```", ""); + } + + repairedText = repairedText.trim(); + return Promise.resolve(repairedText); + }, + maxRetries: 3, + }); + console.timeEnd(generateObjectLabel); + + const functionEndTime = Date.now(); + const durationMs = functionEndTime - functionStartTime; + + if (durationMs > 20000) { + // 20 seconds = 20000 milliseconds + console.log( + `llmParseJson took ${durationMs / 1000}s (longer than 20s). Outputting generated object for schema '${schema.description ?? "unknown_schema"}':`, + ); + try { + console.log(JSON.stringify(object, null, 2)); + } catch (stringifyError) { + console.error( + "Error stringifying the object for detailed logging:", + stringifyError, + ); + console.log("Object (raw, direct log):", object); + } + } + + console.timeEnd(llmParseJsonLabel); + return object as type.infer; + } catch (error) { + console.error( + `Error in llmParseJson during generateObject for schema ${schema.description ?? "unknown_schema"}:`, + error, + ); + // Ensure timers are ended even if an error occurs before the custom duration check + console.timeEnd(generateObjectLabel); + console.timeEnd(llmParseJsonLabel); + throw error; // Re-throw the error after logging + } +} diff --git a/packages/task/src/lib/ai-tools/search-site.ts b/packages/task/src/lib/ai-tools/search-site.ts new file mode 100644 index 000000000..122a1c8ce --- /dev/null +++ b/packages/task/src/lib/ai-tools/search-site.ts @@ -0,0 +1,43 @@ +import type { AnyOrama } from "@orama/orama"; +import { search } from "@orama/orama"; +import { type JSONSchema7, jsonSchema, tool } from "ai"; +import { type } from "arktype"; +import type { SiteSchema } from "../orama/site-schema"; + +const toolInputSchema = type({ + query: type.string + .atLeastLength(1) + .describe( + "Natural language query to find relevant business, audience, services, and industry info.", + ), +}); +export function createSearchSitesTool(db: AnyOrama) { + return tool({ + description: + "Search the indexed sites to find the most relevant pages. To get the full site data, use the `get-sites-data` tool.", + inputSchema: jsonSchema( + toolInputSchema.toJsonSchema() as JSONSchema7, + ), + execute: async ({ query }) => { + const result = await search(db, { + term: query, + mode: "fulltext", + limit: 50, + }); + + return { + count: result.count, + hits: result.hits.map((hit) => ({ + id: hit.id, + score: hit.score, + // return a compact preview for ranking; the full doc is obtainable via get-document + preview: { + url: hit.document.url, + title: hit.document.title, + description: hit.document.description, + }, + })), + }; + }, + }); +} diff --git a/packages/task/src/lib/compute-search-index.ts b/packages/task/src/lib/compute-search-index.ts deleted file mode 100644 index cba3e86a6..000000000 --- a/packages/task/src/lib/compute-search-index.ts +++ /dev/null @@ -1,219 +0,0 @@ -import { createRequire } from "node:module"; -import type { AnySchema } from "@orama/orama"; -import { create, insertMultiple } from "@orama/orama"; -import { Dataset, type PlaywrightCrawler } from "crawlee"; -import { createStorage } from "unstorage"; - -type OramaPrimitive = "string" | "number" | "boolean"; -type OramaArray = "string[]" | "number[]" | "boolean[]"; -type OramaVector = `vector[${number}]`; - -type ArkTypeLike = { - toJsonSchema: (options?: unknown) => unknown; -}; - -interface JsonSchemaObject { - type?: string | string[]; - properties?: Record; - items?: JsonSchemaObject; - enum?: unknown[]; -} - -interface S3Config { - accessKeyId: string; - secretAccessKey: string; - endpoint: string; - bucket: string; - region: string; -} - -interface ComputeSearchIndexOptions { - vectorProperties?: Record; - keyPrefix?: string; - s3?: Partial; -} - -function resolveS3Config(input?: Partial): S3Config { - const resolved: S3Config = { - accessKeyId: input?.accessKeyId ?? process.env.R2_ACCESS_KEY_ID ?? "", - secretAccessKey: - input?.secretAccessKey ?? process.env.R2_SECRET_ACCESS_KEY ?? "", - endpoint: input?.endpoint ?? process.env.R2_ENDPOINT ?? "", - bucket: input?.bucket ?? process.env.R2_BUCKET ?? "", - region: input?.region ?? process.env.R2_REGION ?? "auto", - }; - return resolved; -} - -function isArkTypeLike(value: unknown): value is ArkTypeLike { - return ( - typeof value === "function" || - (typeof value === "object" && value !== null && "toJsonSchema" in value) - ); -} - -function toJsonSchema(input: ArkTypeLike | JsonSchemaObject): JsonSchemaObject { - if (isArkTypeLike(input)) { - const out = input.toJsonSchema?.(); - return (out as JsonSchemaObject) ?? { type: "object", properties: {} }; - } - return input; -} - -function mapJsonSchemaToOramaSchema( - schema: JsonSchemaObject, - vectorProps?: Record, -): AnySchema { - if (!schema || schema.type !== "object" || !schema.properties) { - return {}; - } - - const result: AnySchema = {} as AnySchema; - for (const [key, propSchema] of Object.entries(schema.properties)) { - if ( - vectorProps && - Number.isFinite((vectorProps as Record)[key]) - ) { - const candidate = (vectorProps as Record)[key]; - const size = - typeof candidate === "number" && Number.isFinite(candidate) - ? candidate - : 0; - result[key] = `vector[${size}]` as unknown as AnySchema; - continue; - } - - const mapped = mapJsonSchemaProperty(propSchema, vectorProps); - if (mapped) { - result[key] = mapped as AnySchema; - } - } - return result; -} - -function mapJsonSchemaProperty( - prop: JsonSchemaObject, - vectorProps?: Record, -): OramaPrimitive | OramaArray | OramaVector | AnySchema | undefined { - const t = Array.isArray(prop.type) ? prop.type[0] : prop.type; - if (t === "string") return "string"; - if (t === "number" || t === "integer") return "number"; - if (t === "boolean") return "boolean"; - if (t === "array") { - const itemType = prop.items?.type; - const it = Array.isArray(itemType) ? itemType?.[0] : itemType; - if (it === "string") return "string[]"; - if (it === "number" || it === "integer") return "number[]"; - if (it === "boolean") return "boolean[]"; - // Fallback for unsupported arrays - return "string[]"; - } - if (t === "object" && prop.properties) { - return mapJsonSchemaToOramaSchema(prop, vectorProps) as AnySchema; - } - // Fallback for enums or unknowns - if (prop.enum) return "string"; - return undefined; -} - -export async function computeSearchIndex( - arkOrJsonSchema: ArkTypeLike | JsonSchemaObject, - _crawler: PlaywrightCrawler, - options?: ComputeSearchIndexOptions, -) { - const jsonSchema = toJsonSchema(arkOrJsonSchema); - const oramaSchema: AnySchema = mapJsonSchemaToOramaSchema( - jsonSchema, - options?.vectorProperties, - ) as unknown as AnySchema; - - const db = create({ schema: oramaSchema }); - - const dataset = await Dataset.open(); - const limit = 1000; - let offset = 0; - let chunkIndex = 0; - let totalInserted = 0; - - const s3 = resolveS3Config(options?.s3); - const shouldPersist = Boolean( - s3.accessKeyId && s3.secretAccessKey && s3.endpoint && s3.bucket, - ); - let storage: ReturnType | null = null; - if (shouldPersist) { - // Use require to load CJS default export shape for s3 driver - const require = createRequire(import.meta.url); - const s3Driver = require("unstorage/drivers/s3"); - const driver = ( - "default" in s3Driver ? s3Driver.default : s3Driver - ) as (opts: { - accessKeyId: string; - secretAccessKey: string; - endpoint: string; - bucket: string; - region: string; - }) => unknown; - const drv = driver({ - accessKeyId: s3.accessKeyId, - secretAccessKey: s3.secretAccessKey, - endpoint: s3.endpoint, - bucket: s3.bucket, - region: s3.region, - }); - storage = createStorage({ driver: drv as never }); - } - - const prefix = options?.keyPrefix ?? "search-index"; - - if (storage) { - await storage.setItem( - `${prefix}/orama-schema.json`, - JSON.stringify(oramaSchema), - ); - await storage.setItem( - `${prefix}/source-schema.json`, - JSON.stringify(jsonSchema), - ); - } - - for (;;) { - const { items, count } = await dataset.getData({ - offset, - limit, - clean: true, - }); - if (!items || items.length === 0) break; - - type InsertParams = Parameters; - const docs = items as unknown as InsertParams[1]; - const target = db as InsertParams[0]; - await insertMultiple(target, docs); - - if (storage) { - const key = `${prefix}/chunks/chunk-${String(chunkIndex).padStart(6, "0")}.json`; - await storage.setItem(key, JSON.stringify(items)); - } - - totalInserted += items.length; - offset += count; - chunkIndex += 1; - if (count < limit) break; - } - - if (storage) { - const manifest = { - totalInserted, - chunkCount: chunkIndex, - createdAt: new Date().toISOString(), - keyPrefix: prefix, - }; - await storage.setItem(`${prefix}/manifest.json`, JSON.stringify(manifest)); - } - - return { - totalInserted, - chunkCount: chunkIndex, - schema: oramaSchema, - keyPrefix: prefix, - }; -} diff --git a/packages/task/src/lib/orama/site-schema.ts b/packages/task/src/lib/orama/site-schema.ts new file mode 100644 index 000000000..aee610013 --- /dev/null +++ b/packages/task/src/lib/orama/site-schema.ts @@ -0,0 +1,10 @@ +export const siteSchema = { + title: "string", + url: "string", + description: "string", + text: "string", + contentHtml: "string", + contentMarkdown: "string", + extractor: "string", +} as const; +export type SiteSchema = typeof siteSchema; diff --git a/packages/task/src/trigger/understand-site.ts b/packages/task/src/trigger/understand-site.ts index 799874095..20dd5d8b8 100644 --- a/packages/task/src/trigger/understand-site.ts +++ b/packages/task/src/trigger/understand-site.ts @@ -1,6 +1,14 @@ +import { google } from "@ai-sdk/google"; +import { create, insertMultiple } from "@orama/orama"; +import { schema } from "@rectangular-labs/db"; import { schemaTask } from "@trigger.dev/sdk"; +import { generateText, stepCountIs } from "ai"; import { type } from "arktype"; import { crawlSite } from "../crawlers/site.js"; +import { createGetSitesDataTool } from "../lib/ai-tools/get-site-data.js"; +import { llmParseJson } from "../lib/ai-tools/llm-parse-json.js"; +import { createSearchSitesTool } from "../lib/ai-tools/search-site.js"; +import { siteSchema } from "../lib/orama/site-schema.js"; import { setUnderstandSiteMetadata } from "./understand-site.metadata.js"; const inputSchema = type({ @@ -8,8 +16,17 @@ const inputSchema = type({ maxRequestsPerCrawl: "number=25", }); +const outputSchema = type({ + message: "string", + websiteInfo: schema.seoWebsiteInfoSchema, +}); + export const understandSiteTask: ReturnType< - typeof schemaTask<"understand-site", typeof inputSchema> + typeof schemaTask< + "understand-site", + typeof inputSchema, + typeof outputSchema.infer + > > = schemaTask({ id: "understand-site", maxDuration: 300, @@ -44,23 +61,92 @@ export const understandSiteTask: ReturnType< "Finished reading through the site. Extracting relevant information...", }); - const data = await result.getData(); - for (const item of data.items) { - console.log(item); + // construct search index and insert in batches of 500 + const db = create({ + schema: siteSchema, + }); + const LIMIT = 500; + let offset = 0; + let data = await result.getData({ + offset, + limit: LIMIT, + }); + const ids: string[] = []; + while (data.count > 0) { + const newIds = await insertMultiple(db, data.items, LIMIT); + + ids.push(...newIds); + offset += LIMIT; + data = await result.getData({ + offset, + limit: LIMIT, + }); } - // TODO: construct search index + setUnderstandSiteMetadata({ - progress: 60, + progress: 75, statusMessage: "Found relevant information, synthesizing the results...", }); - // TODO: upload search index - setUnderstandSiteMetadata({ - progress: 75, - statusMessage: "Almost done!", + // Define structured output schema to match seoWebsiteInfoSchema + const StructuredSeoSchema = type({ + businessOverview: type.string.describe( + "Start with org type + primary offering(s); state ALL the business Unique Value Proposition comprehensively; no fluff.", + ), + idealCustomer: type.string.describe( + "Format: B2B - Roles/Titles; Industries; Company size; Geo. B2C - Personas; Demographics/Age; Needs/Use cases; Geo. If both, include both separated by ' | '. Examples — B2B: 'Ops leaders; SaaS; 50-500 FTE; US/UK' | 'HR Directors; Healthcare; 200-1000 FTE; US/CA'. B2C: 'Parents of toddlers; Age 25-40; Childcare savings; US' | 'College students; Age 18-24; Budget laptops; UK'.", + ), + serviceRegion: type.string.describe( + "Canonical regions. Prefer 'Global', regions like 'EU', 'Asia', 'Africa', or country list separated by ';'. For local, use 'City, ST' or 'Metro, ST'.", + ), + industry: type.string.describe( + "Broad top-level category, e.g. 'Software', 'Healthcare', 'E-commerce'.", + ), + }).describe("Normalized business context for downstream SEO planning."); + + // Guide the model to use tools to ground the output + const { text } = await generateText({ + model: google("gemini-2.5-flash"), + temperature: 0, + system: [ + "You are an SEO strategy expert at extracting concise, high-signal, normalized business context.", + "Optimize for downstream LLM keyword/content planning: concrete nouns, no fluff, no marketing speak.", + "## Research policy:", + "- Prioritize authoritative sources like About, Product/Service, Pricing, Contact, Case Studies, Blog, Testimonials.", + "- Use `search-sites` tool to find these pages; for top hits, call the `get-sites-data` tool to get the full content for relevant pages to ground your statements.", + "- If specifics are missing, make sure you search the site thoroughly. If things are still missing infer conservatively from the various search results; do not hallucinate.", + "- Determine whether the site serves businesses (B2B), consumers (B2C), or both; format idealCustomer accordingly.", + "## Output rules:", + "Final answer must be STRICT JSON matching the schema below exactly (no prose).", + `{ + "businessOverview": "string", // Start with org type + primary offering(s); state ALL the business Unique Value Proposition comprehensively; no fluff + "idealCustomer": "string", // B2B: Roles/Titles; Industries; Company size; Geo. B2C: Personas; Demographics/Age; Needs/Use cases; Geo. If both, separate with ' | '. Examples — B2B: 'Ops leaders; SaaS; 50-500 FTE; US/UK'; B2C: 'Parents of toddlers; 25-40; Childcare savings; US' + "serviceRegion": "string", // Global OR 'US; UK' OR regions like 'EU', 'Asia', 'Africa', OR local 'City, ST; City, ST' + "industry": "string" // Broad top-level category, e.g. 'Software', 'Healthcare', 'E-commerce' +}`, + "", + "DO NOT ASK FOR MORE INFORMATION. Start planning deeply for 30 minutes right away and use the appropriate tools to get the information. A great response will help a lot of people and could save the business from going under. Good work will be thoroughly rewarded.", + ].join(" \n"), + tools: { + "search-sites": createSearchSitesTool(db), + "get-sites-data": createGetSitesDataTool(db), + }, + prompt: `I've extracted information from ${payload.startUrl} to inform SEO keyword and content planning. Use the tools to research and return ONLY the normalized JSON.`, + onStepFinish: (step) => { + console.log("Step content", step.text); + console.log( + "Step tool calls", + step.toolResults.map((tool) => JSON.stringify(tool)), + ); + }, + stopWhen: stepCountIs(15), + }).catch((error) => { + console.error("Error in understandSiteTask", error); + throw error; }); + console.log("text", text); + const websiteInfo = await llmParseJson(text, StructuredSeoSchema); - // TODO: use ai and extract relevant information from the result setUnderstandSiteMetadata({ progress: 100, statusMessage: "All done, loading the results...", @@ -68,6 +154,10 @@ export const understandSiteTask: ReturnType< return { message: "Site crawled successfully", + websiteInfo: { + ...websiteInfo, + version: "v1", + }, }; }, }); From d3a8790020d45ba5bb93f2da3b898cf518fbaa87 Mon Sep 17 00:00:00 2001 From: ElasticBottle Date: Sat, 20 Sep 2025 06:17:00 +0800 Subject: [PATCH 16/38] chore: add env variables --- .env | 2 ++ .env.production | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.env b/.env index 91092ec58..5a4618cf4 100644 --- a/.env +++ b/.env @@ -6,9 +6,11 @@ DOTENV_PUBLIC_KEY="029b5432287e802a315a922dd9d54d39c60a9c2b90352e6e182c7ebd76085 VITE_APP_URL="https://{env.PULL_REQUEST}.rectangularlabs.com" VITE_MENTIONS_URL="https://{env.PULL_REQUEST}.mentions.rectangularlabs.com" +VITE_SEO_URL="https://{env.PULL_REQUEST}.seo.rectangularlabs.com" AUTH_ENCRYPTION_KEY="encrypted:BIE5Z8BAuioQtWpzw17jS8AMiySKliZhtiJJOq6vEF9yFMDgnkE3PVdY7SeapU6xM0PZhuw/hpPzHosna3N2vVLX/wtPW5W7VCDtATypf72Rvjb4aL/AO4PVBxkuHBpMl+RBa6hxaHtgMeley03rQCXzaYEb4r7UhcwOpFucK7RRurhzrt60r0LCmoxW" AUTH_GITHUB_ID="encrypted:BGggZsKdNloQB/Eowp0xHbwH/zi8Nx6+5/q2UyH6AQhbgkkgxCXHKROcx74VNkvkXBkZyy4NVEQHlC3W/8NZenkJlwkxdIzkg3H898qSBCLps/iAg1xbi6JuGCF7mXp5C2Ia4bvqOnR7zy9aIIlkIVFPCLeX" AUTH_GITHUB_SECRET="encrypted:BJ8mhInEz77Cdfj6Kj8MOuj06zmySKJZt+WfnzzlgHU7lNCXNsWbjqwe1gPhe/nRUmpgy/2CjWWpjT2UTU32aCu1v7jqUCNeo4SAdt2g3N7pT87HEudzF+7z+9zpfm7AI1CnsfmAa0/HhySLiFTk9rG/H/WRF7j2mrPHb8915MYj6yw2aWhK9vI=" DATABASE_URL="encrypted:BOmJpqtAa5VZFQLySIYf/OcgshOpNgV+CRsNRp3Xu/zlW0luMe7HZEQQfefrdtGbh8vjlTG+/HvvZVZyNK6+5C5MRVCPaVuqHb6yRIh7fR5BiiApDm+U9LDfwsRbaBDTkH6iacPC3EuOfNk5GLiqnhmPDVapqFuYldYQKtPP2riJVceh1yNCtWYv8shDO4awDms/efsJUBW7j8JUaK7LPtAgc8pbc0ylpAP+fSr0wuY+C4yycupbi0dz3VySPwqXzvWNVNkDKGZRe/CWFnk=" +GOOGLE_GENERATIVE_AI_API_KEY="encrypted:BHwRYKNORCwTf6ctgb8x5U5fbwHrMtA1Ejrrdmut2spPl9esaJgOEC80tO1spinAcG5Fdbx+J5rUFX10MLN2PFiSgxPVdamgi0Jqiil+6r7a7k/APUqEgxGKQKMKCpoPrE5wl7Y/LlLL0i/MYsT8Ot3Nykz1iLt6gnNSs4yacQpAqeQqyD5MzQ==" diff --git a/.env.production b/.env.production index bbb08e71d..577e331b9 100644 --- a/.env.production +++ b/.env.production @@ -7,9 +7,11 @@ DOTENV_PUBLIC_KEY_PRODUCTION="03741dc2723a48fc90ce0be37d246e96d1ee68ff34af5f702d # .env.production VITE_APP_URL="https://rectangularlabs.com" VITE_MENTIONS_URL="https://mentions.rectangularlabs.com" +VITE_SEO_URL="https://seo.rectangularlabs.com" AUTH_ENCRYPTION_KEY="encrypted:BB7GTM5rZHiQxZ8UNUnajRL+QRCxDuSZvOfcrsqrQIFtLTrM/p0Xhc6zDnZnDDnShczhi4m1eaHq5cZWBuzjMUfv2CpnU4yPxZYuOMRsKjY1mQ/DztxYMT5tH42+QCMxA1bU6sJCdDnWwFPqx0DcL30oxCkxuaWzBmeHpmEPAcjV/NHtZ9pbm87cdXtj" AUTH_GITHUB_ID="encrypted:BGhstwS69Y3j8JlzPfLKAXSSB0OYXBmLDzq0BFT0q5ujVp6yzziNzfiV3Miom6sIk6PQCcGYud4cwyQWAp/G/Vu3f3vBvIvTwjB05xR8ZFW5P3Eb1a4L5k2wT2/tr5ANY0P4i9T14j4zy6nNhXafU7f8fiVA" AUTH_GITHUB_SECRET="encrypted:BJ42L6e6x7bgwQW8AiYjl53L+Gu81RklwcApDtP5xkZkOPZNU714g9BP3HQrdjWuQTmyDlEi9WPfS89M93Wqm6AGhUnZFdU48/EXhphZzHCed6cXKzTyWQROfM0hXwtaoFzecPKeJyEwBfT3SBe61fq88nX+oyniGToLi+4PMmQCBUuCfor+/YA=" DATABASE_URL="encrypted:BILowGq6zORIhEi96PxHyCIANFDPnlbXZFc1rKzdC82fUHBWAbF/MZaYD2+Mb8+bDmVRMlAdd6wONZsQc62U2H5gP/1gUjafBV5jwCOkvuu4I2eNR6qPwDOixfidkVsnt7h16nO7whQ9AM9R0faoBUw3f0BMB7uyKfJmr9b/T6bVX94oCVLy3nVCnjkD8lt5rPQHR+Yqper3Onc1u24HDjGXIaQQN9o2nv1zfYXYgtqhhIYNRnUiJUPNri/i2ocEzLGFuCowU/ac5AAEqy0=" +GOOGLE_GENERATIVE_AI_API_KEY="encrypted:BDZa7EK7Vm8lSHNIttoWwrxvx44wvX5lGXIiCodWuD3mVAjLgqzPchI7UWrGoxyJkuYXdbhxw5AYcaIhaZBVKCzVdXKkCx5x+0WNZjzHNAYGI/diPsTDl8MNGx/J43csJ12Yc7rYJGLdOFiD/hSqgiDJCTqMvweNXtniMnD4sSMMU/JFe6Fhqw==" From 41f99d8de93bae08483b6c6d21f055e05becb5d4 Mon Sep 17 00:00:00 2001 From: ElasticBottle Date: Sun, 21 Sep 2025 00:07:57 +0800 Subject: [PATCH 17/38] chore(seo): update env var for site url --- apps/seo/src/lib/api.ts | 2 +- apps/seo/src/lib/auth/server.ts | 6 +++--- apps/seo/src/lib/env.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/seo/src/lib/api.ts b/apps/seo/src/lib/api.ts index 64e97de91..3034f6580 100644 --- a/apps/seo/src/lib/api.ts +++ b/apps/seo/src/lib/api.ts @@ -23,5 +23,5 @@ export const getApiClient = createIsomorphicFn() export const apiClientRq = rqApiClient( typeof window !== "undefined" ? window.location.origin - : clientEnv().VITE_MENTIONS_URL, + : clientEnv().VITE_SEO_URL, ); diff --git a/apps/seo/src/lib/auth/server.ts b/apps/seo/src/lib/auth/server.ts index 11fc18595..5e9d4dc08 100644 --- a/apps/seo/src/lib/auth/server.ts +++ b/apps/seo/src/lib/auth/server.ts @@ -1,8 +1,8 @@ -import { initAuthHandler } from "@rectangular-labs/auth"; +import { type Auth, initAuthHandler } from "@rectangular-labs/auth"; import { createDb } from "@rectangular-labs/db"; import { serverEnv } from "../env"; export const authServerHandler = initAuthHandler( - serverEnv().VITE_MENTIONS_URL, + serverEnv().VITE_SEO_URL, createDb(), -); +) as Auth; diff --git a/apps/seo/src/lib/env.ts b/apps/seo/src/lib/env.ts index 9e965d46d..d50c2a388 100644 --- a/apps/seo/src/lib/env.ts +++ b/apps/seo/src/lib/env.ts @@ -7,7 +7,7 @@ export const clientEnv = () => extends: [], clientPrefix: "VITE_", client: { - VITE_MENTIONS_URL: type("string"), + VITE_SEO_URL: type("string"), }, runtimeEnv: import.meta.env, emptyStringAsUndefined: true, From 29eb44ce5c5ecde91a2caa2fca07719a77881fac Mon Sep 17 00:00:00 2001 From: ElasticBottle Date: Sun, 21 Sep 2025 00:08:18 +0800 Subject: [PATCH 18/38] chore(seo): display better message for arktype parsing failure --- apps/seo/src/components/error-boundary.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/seo/src/components/error-boundary.tsx b/apps/seo/src/components/error-boundary.tsx index 2403ae399..4e309f12a 100644 --- a/apps/seo/src/components/error-boundary.tsx +++ b/apps/seo/src/components/error-boundary.tsx @@ -16,8 +16,17 @@ export function DefaultCatchBoundary({ error }: ErrorComponentProps) { }); console.error(error); - const message = - error instanceof Error ? error.message : "An unexpected error occurred."; + const message = (() => { + if (error instanceof Error) { + try { + const parsed = JSON.parse(error.message); + return parsed[0].problem; + } catch { + return error.message; + } + } + return "An unexpected error occurred."; + })(); return (
From 3d974b8daa145835fad56ec95bfa0190d051391d Mon Sep 17 00:00:00 2001 From: ElasticBottle Date: Sun, 21 Sep 2025 00:08:34 +0800 Subject: [PATCH 19/38] refactor(seo): update onboarding steps name --- .../_authed/onboarding/-components/2-company-background.tsx | 2 +- .../onboarding/-components/3-understanding-company.tsx | 2 +- .../src/routes/_authed/onboarding/-components/content.tsx | 2 +- apps/mentions/src/routes/_authed/onboarding/-lib/steps.ts | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/mentions/src/routes/_authed/onboarding/-components/2-company-background.tsx b/apps/mentions/src/routes/_authed/onboarding/-components/2-company-background.tsx index 06d367cb3..caeb72532 100644 --- a/apps/mentions/src/routes/_authed/onboarding/-components/2-company-background.tsx +++ b/apps/mentions/src/routes/_authed/onboarding/-components/2-company-background.tsx @@ -43,7 +43,7 @@ export function OnboardingCompanyBackground({ const { mutate: crawlInfo, isPending } = useMutation( apiClientRq.companyBackground.crawlInfo.mutationOptions({ onSuccess: (data, { websiteUrl }) => { - matcher.setMetadata("user-company", { + matcher.setMetadata("website-info", { websiteUrl, crawlId: data.id, }); diff --git a/apps/mentions/src/routes/_authed/onboarding/-components/3-understanding-company.tsx b/apps/mentions/src/routes/_authed/onboarding/-components/3-understanding-company.tsx index fd5e3eb90..9e1b5f80c 100644 --- a/apps/mentions/src/routes/_authed/onboarding/-components/3-understanding-company.tsx +++ b/apps/mentions/src/routes/_authed/onboarding/-components/3-understanding-company.tsx @@ -32,7 +32,7 @@ export function OnboardingUnderstandingCompany({ const matcher = OnboardingSteps.useStepper(); const { crawlId } = matcher.getMetadata<{ crawlId: string; - }>("user-company"); + }>("website-info"); const { data: companyBackground, error: getStatusError } = useQuery( apiClientRq.companyBackground.getCrawlStatus.queryOptions({ diff --git a/apps/mentions/src/routes/_authed/onboarding/-components/content.tsx b/apps/mentions/src/routes/_authed/onboarding/-components/content.tsx index b6bc6ffb9..c9a9be100 100644 --- a/apps/mentions/src/routes/_authed/onboarding/-components/content.tsx +++ b/apps/mentions/src/routes/_authed/onboarding/-components/content.tsx @@ -36,7 +36,7 @@ export function OnboardingContent() { title={step.title} /> ), - "user-company": (step) => ( + "website-info": (step) => ( Date: Sun, 21 Sep 2025 00:09:20 +0800 Subject: [PATCH 20/38] refactor(seo): update site layout to support org name in slug --- apps/seo/src/lib/auth/client.ts | 54 +++++-- apps/seo/src/routeTree.gen.ts | 132 +++++++++++++----- .../_authed/$organizationSlug/index.tsx | 9 ++ .../_authed/$organizationSlug/route.tsx | 9 ++ .../setting.tsx} | 6 +- apps/seo/src/routes/_authed/dashboard.tsx | 32 ----- .../src/routes/_authed/onboarding/index.tsx | 53 ++++--- apps/seo/src/routes/_authed/organization.tsx | 38 +++++ apps/seo/src/routes/_authed/route.tsx | 1 + apps/seo/src/routes/login.tsx | 2 +- 10 files changed, 237 insertions(+), 99 deletions(-) create mode 100644 apps/seo/src/routes/_authed/$organizationSlug/index.tsx create mode 100644 apps/seo/src/routes/_authed/$organizationSlug/route.tsx rename apps/seo/src/routes/_authed/{organizations.tsx => $organizationSlug/setting.tsx} (86%) delete mode 100644 apps/seo/src/routes/_authed/dashboard.tsx create mode 100644 apps/seo/src/routes/_authed/organization.tsx diff --git a/apps/seo/src/lib/auth/client.ts b/apps/seo/src/lib/auth/client.ts index aa5922353..40a7a4d01 100644 --- a/apps/seo/src/lib/auth/client.ts +++ b/apps/seo/src/lib/auth/client.ts @@ -1,13 +1,14 @@ import { createAuthClient } from "@rectangular-labs/auth/client"; +import { err, ok, safe } from "@rectangular-labs/result"; import { createIsomorphicFn } from "@tanstack/react-start"; import { getWebRequest } from "@tanstack/react-start/server"; import { clientEnv } from "../env"; -import { authServerHandler } from "./server"; -export const authClient = createAuthClient(clientEnv().VITE_MENTIONS_URL); +export const authClient = createAuthClient(clientEnv().VITE_SEO_URL); export const getCurrentSession = createIsomorphicFn() .server(async () => { + const { authServerHandler } = await import("./server"); const request = getWebRequest(); const session = await authServerHandler.api.getSession({ headers: request.headers, @@ -21,19 +22,54 @@ export const getCurrentSession = createIsomorphicFn() export const getUserOrganizations = createIsomorphicFn() .server(async () => { + const { authServerHandler } = await import("./server"); const request = getWebRequest(); - const organizations = await authServerHandler.api.listOrganizations({ - headers: request.headers, - }); + const organizations = await safe(() => + authServerHandler.api.listOrganizations({ + headers: request.headers, + }), + ); + return organizations; }) .client(async () => { const organizations = await authClient.organization.list(); if (organizations.error) { - throw new Error( - organizations.error.message ?? - "Something went wrong loading organizations. Please try again", + return err( + new Error( + organizations.error.message ?? + "Something went wrong loading organizations. Please try again", + ), ); } - return organizations.data; + return ok(organizations.data); }); + +export const setDefaultOrganization = createIsomorphicFn() + .server( + async (args: { organizationId: string; organizationSlug: string }) => { + const { authServerHandler } = await import("./server"); + const request = getWebRequest(); + const organization = await safe(() => + authServerHandler.api.setActiveOrganization({ + headers: request.headers, + body: args, + }), + ); + return organization; + }, + ) + .client( + async (args: { organizationId: string; organizationSlug: string }) => { + const organization = await authClient.organization.setActive(args); + if (organization.error) { + return err( + new Error( + organization.error.message ?? + "Something went wrong setting the default organization. Please try again", + ), + ); + } + return ok(organization.data); + }, + ); diff --git a/apps/seo/src/routeTree.gen.ts b/apps/seo/src/routeTree.gen.ts index 2f2d7872f..b79cbec2e 100644 --- a/apps/seo/src/routeTree.gen.ts +++ b/apps/seo/src/routeTree.gen.ts @@ -15,12 +15,14 @@ import { Route as LoginRouteImport } from './routes/login' import { Route as MarketingRouteRouteImport } from './routes/_marketing/route' import { Route as AuthedRouteRouteImport } from './routes/_authed/route' import { Route as MarketingIndexRouteImport } from './routes/_marketing/index' -import { Route as AuthedOrganizationsRouteImport } from './routes/_authed/organizations' -import { Route as AuthedDashboardRouteImport } from './routes/_authed/dashboard' +import { Route as AuthedOrganizationRouteImport } from './routes/_authed/organization' import { Route as MarketingBlogRouteRouteImport } from './routes/_marketing/blog/route' +import { Route as AuthedOrganizationSlugRouteRouteImport } from './routes/_authed/$organizationSlug/route' import { Route as MarketingBlogIndexRouteImport } from './routes/_marketing/blog/index' import { Route as AuthedOnboardingIndexRouteImport } from './routes/_authed/onboarding/index' +import { Route as AuthedOrganizationSlugIndexRouteImport } from './routes/_authed/$organizationSlug/index' import { Route as MarketingBlogSplatRouteImport } from './routes/_marketing/blog/$' +import { Route as AuthedOrganizationSlugSettingRouteImport } from './routes/_authed/$organizationSlug/setting' import { ServerRoute as ApiSplatServerRouteImport } from './routes/api/$' import { ServerRoute as ApiRpcSplatServerRouteImport } from './routes/api/rpc.$' import { ServerRoute as MarketingBlogRssDotxmlServerRouteImport } from './routes/_marketing/blog/rss[.]xml' @@ -45,14 +47,9 @@ const MarketingIndexRoute = MarketingIndexRouteImport.update({ path: '/', getParentRoute: () => MarketingRouteRoute, } as any) -const AuthedOrganizationsRoute = AuthedOrganizationsRouteImport.update({ - id: '/organizations', - path: '/organizations', - getParentRoute: () => AuthedRouteRoute, -} as any) -const AuthedDashboardRoute = AuthedDashboardRouteImport.update({ - id: '/dashboard', - path: '/dashboard', +const AuthedOrganizationRoute = AuthedOrganizationRouteImport.update({ + id: '/organization', + path: '/organization', getParentRoute: () => AuthedRouteRoute, } as any) const MarketingBlogRouteRoute = MarketingBlogRouteRouteImport.update({ @@ -60,6 +57,12 @@ const MarketingBlogRouteRoute = MarketingBlogRouteRouteImport.update({ path: '/blog', getParentRoute: () => MarketingRouteRoute, } as any) +const AuthedOrganizationSlugRouteRoute = + AuthedOrganizationSlugRouteRouteImport.update({ + id: '/$organizationSlug', + path: '/$organizationSlug', + getParentRoute: () => AuthedRouteRoute, + } as any) const MarketingBlogIndexRoute = MarketingBlogIndexRouteImport.update({ id: '/', path: '/', @@ -70,11 +73,23 @@ const AuthedOnboardingIndexRoute = AuthedOnboardingIndexRouteImport.update({ path: '/onboarding/', getParentRoute: () => AuthedRouteRoute, } as any) +const AuthedOrganizationSlugIndexRoute = + AuthedOrganizationSlugIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => AuthedOrganizationSlugRouteRoute, + } as any) const MarketingBlogSplatRoute = MarketingBlogSplatRouteImport.update({ id: '/$', path: '/$', getParentRoute: () => MarketingBlogRouteRoute, } as any) +const AuthedOrganizationSlugSettingRoute = + AuthedOrganizationSlugSettingRouteImport.update({ + id: '/setting', + path: '/setting', + getParentRoute: () => AuthedOrganizationSlugRouteRoute, + } as any) const ApiSplatServerRoute = ApiSplatServerRouteImport.update({ id: '/api/$', path: '/api/$', @@ -94,20 +109,23 @@ const MarketingBlogRssDotxmlServerRoute = export interface FileRoutesByFullPath { '/login': typeof LoginRoute + '/$organizationSlug': typeof AuthedOrganizationSlugRouteRouteWithChildren '/blog': typeof MarketingBlogRouteRouteWithChildren - '/dashboard': typeof AuthedDashboardRoute - '/organizations': typeof AuthedOrganizationsRoute + '/organization': typeof AuthedOrganizationRoute '/': typeof MarketingIndexRoute + '/$organizationSlug/setting': typeof AuthedOrganizationSlugSettingRoute '/blog/$': typeof MarketingBlogSplatRoute + '/$organizationSlug/': typeof AuthedOrganizationSlugIndexRoute '/onboarding': typeof AuthedOnboardingIndexRoute '/blog/': typeof MarketingBlogIndexRoute } export interface FileRoutesByTo { '/login': typeof LoginRoute - '/dashboard': typeof AuthedDashboardRoute - '/organizations': typeof AuthedOrganizationsRoute + '/organization': typeof AuthedOrganizationRoute '/': typeof MarketingIndexRoute + '/$organizationSlug/setting': typeof AuthedOrganizationSlugSettingRoute '/blog/$': typeof MarketingBlogSplatRoute + '/$organizationSlug': typeof AuthedOrganizationSlugIndexRoute '/onboarding': typeof AuthedOnboardingIndexRoute '/blog': typeof MarketingBlogIndexRoute } @@ -116,11 +134,13 @@ export interface FileRoutesById { '/_authed': typeof AuthedRouteRouteWithChildren '/_marketing': typeof MarketingRouteRouteWithChildren '/login': typeof LoginRoute + '/_authed/$organizationSlug': typeof AuthedOrganizationSlugRouteRouteWithChildren '/_marketing/blog': typeof MarketingBlogRouteRouteWithChildren - '/_authed/dashboard': typeof AuthedDashboardRoute - '/_authed/organizations': typeof AuthedOrganizationsRoute + '/_authed/organization': typeof AuthedOrganizationRoute '/_marketing/': typeof MarketingIndexRoute + '/_authed/$organizationSlug/setting': typeof AuthedOrganizationSlugSettingRoute '/_marketing/blog/$': typeof MarketingBlogSplatRoute + '/_authed/$organizationSlug/': typeof AuthedOrganizationSlugIndexRoute '/_authed/onboarding/': typeof AuthedOnboardingIndexRoute '/_marketing/blog/': typeof MarketingBlogIndexRoute } @@ -128,20 +148,23 @@ export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath fullPaths: | '/login' + | '/$organizationSlug' | '/blog' - | '/dashboard' - | '/organizations' + | '/organization' | '/' + | '/$organizationSlug/setting' | '/blog/$' + | '/$organizationSlug/' | '/onboarding' | '/blog/' fileRoutesByTo: FileRoutesByTo to: | '/login' - | '/dashboard' - | '/organizations' + | '/organization' | '/' + | '/$organizationSlug/setting' | '/blog/$' + | '/$organizationSlug' | '/onboarding' | '/blog' id: @@ -149,11 +172,13 @@ export interface FileRouteTypes { | '/_authed' | '/_marketing' | '/login' + | '/_authed/$organizationSlug' | '/_marketing/blog' - | '/_authed/dashboard' - | '/_authed/organizations' + | '/_authed/organization' | '/_marketing/' + | '/_authed/$organizationSlug/setting' | '/_marketing/blog/$' + | '/_authed/$organizationSlug/' | '/_authed/onboarding/' | '/_marketing/blog/' fileRoutesById: FileRoutesById @@ -223,18 +248,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof MarketingIndexRouteImport parentRoute: typeof MarketingRouteRoute } - '/_authed/organizations': { - id: '/_authed/organizations' - path: '/organizations' - fullPath: '/organizations' - preLoaderRoute: typeof AuthedOrganizationsRouteImport - parentRoute: typeof AuthedRouteRoute - } - '/_authed/dashboard': { - id: '/_authed/dashboard' - path: '/dashboard' - fullPath: '/dashboard' - preLoaderRoute: typeof AuthedDashboardRouteImport + '/_authed/organization': { + id: '/_authed/organization' + path: '/organization' + fullPath: '/organization' + preLoaderRoute: typeof AuthedOrganizationRouteImport parentRoute: typeof AuthedRouteRoute } '/_marketing/blog': { @@ -244,6 +262,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof MarketingBlogRouteRouteImport parentRoute: typeof MarketingRouteRoute } + '/_authed/$organizationSlug': { + id: '/_authed/$organizationSlug' + path: '/$organizationSlug' + fullPath: '/$organizationSlug' + preLoaderRoute: typeof AuthedOrganizationSlugRouteRouteImport + parentRoute: typeof AuthedRouteRoute + } '/_marketing/blog/': { id: '/_marketing/blog/' path: '/' @@ -258,6 +283,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthedOnboardingIndexRouteImport parentRoute: typeof AuthedRouteRoute } + '/_authed/$organizationSlug/': { + id: '/_authed/$organizationSlug/' + path: '/' + fullPath: '/$organizationSlug/' + preLoaderRoute: typeof AuthedOrganizationSlugIndexRouteImport + parentRoute: typeof AuthedOrganizationSlugRouteRoute + } '/_marketing/blog/$': { id: '/_marketing/blog/$' path: '/$' @@ -265,6 +297,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof MarketingBlogSplatRouteImport parentRoute: typeof MarketingBlogRouteRoute } + '/_authed/$organizationSlug/setting': { + id: '/_authed/$organizationSlug/setting' + path: '/setting' + fullPath: '/$organizationSlug/setting' + preLoaderRoute: typeof AuthedOrganizationSlugSettingRouteImport + parentRoute: typeof AuthedOrganizationSlugRouteRoute + } } } declare module '@tanstack/react-start/server' { @@ -293,15 +332,32 @@ declare module '@tanstack/react-start/server' { } } +interface AuthedOrganizationSlugRouteRouteChildren { + AuthedOrganizationSlugSettingRoute: typeof AuthedOrganizationSlugSettingRoute + AuthedOrganizationSlugIndexRoute: typeof AuthedOrganizationSlugIndexRoute +} + +const AuthedOrganizationSlugRouteRouteChildren: AuthedOrganizationSlugRouteRouteChildren = + { + AuthedOrganizationSlugSettingRoute: AuthedOrganizationSlugSettingRoute, + AuthedOrganizationSlugIndexRoute: AuthedOrganizationSlugIndexRoute, + } + +const AuthedOrganizationSlugRouteRouteWithChildren = + AuthedOrganizationSlugRouteRoute._addFileChildren( + AuthedOrganizationSlugRouteRouteChildren, + ) + interface AuthedRouteRouteChildren { - AuthedDashboardRoute: typeof AuthedDashboardRoute - AuthedOrganizationsRoute: typeof AuthedOrganizationsRoute + AuthedOrganizationSlugRouteRoute: typeof AuthedOrganizationSlugRouteRouteWithChildren + AuthedOrganizationRoute: typeof AuthedOrganizationRoute AuthedOnboardingIndexRoute: typeof AuthedOnboardingIndexRoute } const AuthedRouteRouteChildren: AuthedRouteRouteChildren = { - AuthedDashboardRoute: AuthedDashboardRoute, - AuthedOrganizationsRoute: AuthedOrganizationsRoute, + AuthedOrganizationSlugRouteRoute: + AuthedOrganizationSlugRouteRouteWithChildren, + AuthedOrganizationRoute: AuthedOrganizationRoute, AuthedOnboardingIndexRoute: AuthedOnboardingIndexRoute, } diff --git a/apps/seo/src/routes/_authed/$organizationSlug/index.tsx b/apps/seo/src/routes/_authed/$organizationSlug/index.tsx new file mode 100644 index 000000000..824a90a45 --- /dev/null +++ b/apps/seo/src/routes/_authed/$organizationSlug/index.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from "@tanstack/react-router"; + +export const Route = createFileRoute("/_authed/$organizationSlug/")({ + component: RouteComponent, +}); + +function RouteComponent() { + return
Hello "/_authed/$organizationSlug/"!
; +} diff --git a/apps/seo/src/routes/_authed/$organizationSlug/route.tsx b/apps/seo/src/routes/_authed/$organizationSlug/route.tsx new file mode 100644 index 000000000..f86d261ab --- /dev/null +++ b/apps/seo/src/routes/_authed/$organizationSlug/route.tsx @@ -0,0 +1,9 @@ +import { createFileRoute, Outlet } from "@tanstack/react-router"; + +export const Route = createFileRoute("/_authed/$organizationSlug")({ + component: RouteComponent, +}); + +function RouteComponent() { + return ; +} diff --git a/apps/seo/src/routes/_authed/organizations.tsx b/apps/seo/src/routes/_authed/$organizationSlug/setting.tsx similarity index 86% rename from apps/seo/src/routes/_authed/organizations.tsx rename to apps/seo/src/routes/_authed/$organizationSlug/setting.tsx index 58ee614e3..fd7002ac0 100644 --- a/apps/seo/src/routes/_authed/organizations.tsx +++ b/apps/seo/src/routes/_authed/$organizationSlug/setting.tsx @@ -9,11 +9,11 @@ import { import { Separator } from "@rectangular-labs/ui/components/ui/separator"; import { createFileRoute } from "@tanstack/react-router"; -export const Route = createFileRoute("/_authed/organizations")({ - component: OrganizationsPage, +export const Route = createFileRoute("/_authed/$organizationSlug/setting")({ + component: OrganizationSettingPage, }); -function OrganizationsPage() { +function OrganizationSettingPage() { return (
diff --git a/apps/seo/src/routes/_authed/dashboard.tsx b/apps/seo/src/routes/_authed/dashboard.tsx deleted file mode 100644 index 2bb26a95d..000000000 --- a/apps/seo/src/routes/_authed/dashboard.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { createFileRoute, redirect } from "@tanstack/react-router"; -import { apiClientRq } from "~/lib/api"; -import { getUserOrganizations } from "~/lib/auth/client"; - -export const Route = createFileRoute("/_authed/dashboard")({ - component: ProjectPicker, - beforeLoad: async ({ context }) => { - const organizations = await getUserOrganizations(); - if (organizations.length === 0) { - throw redirect({ - to: "/onboarding", - }); - } - // we need to check org first otherwise the following call will throw a 404 - const projects = await context.queryClient.fetchQuery( - apiClientRq.projects.list.queryOptions({ - input: { - limit: 1, - }, - }), - ); - if (projects.data.length === 0) { - throw redirect({ - to: "/onboarding", - }); - } - }, -}); - -function ProjectPicker() { - return null; -} diff --git a/apps/seo/src/routes/_authed/onboarding/index.tsx b/apps/seo/src/routes/_authed/onboarding/index.tsx index c6cf1736b..8f8fc5be6 100644 --- a/apps/seo/src/routes/_authed/onboarding/index.tsx +++ b/apps/seo/src/routes/_authed/onboarding/index.tsx @@ -1,38 +1,59 @@ +import type { Organization } from "@rectangular-labs/auth"; import { createFileRoute } from "@tanstack/react-router"; -import { getApiClient } from "~/lib/api"; +import { type } from "arktype"; import { getUserOrganizations } from "~/lib/auth/client"; import { OnboardingContent } from "./-components/content"; import { OnboardingSteps } from "./-lib/steps"; export const Route = createFileRoute("/_authed/onboarding/")({ + validateSearch: type({ + type: "'new-user' | 'new-project' = 'new-user'", + }), loader: async () => { const organizations = await getUserOrganizations(); - if (organizations.length === 0) { - return { organizations: organizations, existingProjects: [] }; + if (!organizations.ok) { + throw new Error(organizations.error.message); + } + if (organizations.value.length === 0) { + return { organizations: organizations.value }; } - const existingProjects = await getApiClient().projects.list({ limit: 1 }); return { - organizations: organizations, - existingProjects: existingProjects.data, + organizations: organizations.value, }; }, component: OnboardingPage, }); +function getInitialStep( + type: "new-user" | "new-project", + organizations: Organization[], +) { + switch (type) { + case "new-user": { + if (organizations.length === 0) { + return "welcome"; + } + return "website-info"; + } + case "new-project": { + return "website-info"; + } + default: { + const _never: never = type; + throw new Error("Invalid type"); + } + } +} + function OnboardingPage() { - const { existingProjects, organizations } = Route.useLoaderData(); + const { type } = Route.useSearch(); + const { organizations } = Route.useLoaderData(); + + const initialStep = getInitialStep(type, organizations); return ( - + ); diff --git a/apps/seo/src/routes/_authed/organization.tsx b/apps/seo/src/routes/_authed/organization.tsx new file mode 100644 index 000000000..457a7281b --- /dev/null +++ b/apps/seo/src/routes/_authed/organization.tsx @@ -0,0 +1,38 @@ +import { createFileRoute, redirect } from "@tanstack/react-router"; +import { + getUserOrganizations, + setDefaultOrganization, +} from "~/lib/auth/client"; + +export const Route = createFileRoute("/_authed/organization")({ + component: OrganizationPage, + loader: async () => { + const organizations = await getUserOrganizations(); + if (!organizations.ok) { + throw new Error(organizations.error.message); + } + const [organization] = organizations.value; + if (!organization) { + throw redirect({ + to: "/onboarding", + }); + } + const result = await setDefaultOrganization({ + organizationId: organization.id, + organizationSlug: organization.slug, + }); + if (!result.ok) { + throw new Error(result.error.message); + } + throw redirect({ + to: "/$organizationSlug", + params: { + organizationSlug: organization.slug, + }, + }); + }, +}); + +function OrganizationPage() { + return null; +} diff --git a/apps/seo/src/routes/_authed/route.tsx b/apps/seo/src/routes/_authed/route.tsx index 2613da8d6..d16501be6 100644 --- a/apps/seo/src/routes/_authed/route.tsx +++ b/apps/seo/src/routes/_authed/route.tsx @@ -12,6 +12,7 @@ export const Route = createFileRoute("/_authed")({ }, }); } + return { ...session }; }, component: AuthedLayout, }); diff --git a/apps/seo/src/routes/login.tsx b/apps/seo/src/routes/login.tsx index 0a4362771..bfcb9f06e 100644 --- a/apps/seo/src/routes/login.tsx +++ b/apps/seo/src/routes/login.tsx @@ -32,7 +32,7 @@ export const Route = createFileRoute("/login")({ function Login() { const search = Route.useSearch(); const navigate = useNavigate(); - const normalizedSuccessCallbackURL = search.next ?? "/dashboard"; + const normalizedSuccessCallbackURL = search.next ?? "/organization"; const newUserCallbackURL = `/onboarding?next=${normalizedSuccessCallbackURL}`; return ( From ebd77b4b35b7b2ae83f255fd9c4a022ed76e7a99 Mon Sep 17 00:00:00 2001 From: ElasticBottle Date: Sun, 21 Sep 2025 00:12:35 +0800 Subject: [PATCH 21/38] chore(mentions): remove mentions web app --- .github/workflows/cloudflare.yml | 26 +- .vscode/settings.json | 1 + apps/mentions/package.json | 62 --- .../public/android-chrome-192x192.png | Bin 1495 -> 0 bytes .../public/android-chrome-512x512.png | Bin 9343 -> 0 bytes apps/mentions/public/apple-touch-icon.png | Bin 1245 -> 0 bytes apps/mentions/public/favicon-96x96.png | Bin 549 -> 0 bytes apps/mentions/public/favicon.ico | Bin 15086 -> 0 bytes apps/mentions/public/favicon.svg | 6 - apps/mentions/public/site.webmanifest | 21 - .../src/components/error-boundary.tsx | 56 --- .../mentions/src/components/logout-button.tsx | 33 -- apps/mentions/src/components/not-found.tsx | 34 -- apps/mentions/src/lib/api.ts | 27 -- apps/mentions/src/lib/auth/client.ts | 39 -- apps/mentions/src/lib/auth/server.ts | 8 - apps/mentions/src/lib/env.ts | 26 -- apps/mentions/src/lib/seo.ts | 33 -- apps/mentions/src/reportWebVitals.ts | 13 - apps/mentions/src/routeTree.gen.ts | 417 ------------------ apps/mentions/src/router.tsx | 33 -- apps/mentions/src/routes/__root.tsx | 94 ---- .../mentions/src/routes/_authed/dashboard.tsx | 32 -- .../onboarding/-components/0-welcome.tsx | 37 -- .../-components/1-user-background.tsx | 257 ----------- .../-components/3-understanding-company.tsx | 117 ----- .../-components/5-review-project.tsx | 146 ------ .../onboarding/-components/6-all-set.tsx | 40 -- .../onboarding/-components/content.tsx | 63 --- .../-components/onboarding-progress.tsx | 38 -- .../routes/_authed/onboarding/-lib/steps.ts | 43 -- .../src/routes/_authed/onboarding/index.tsx | 39 -- .../src/routes/_authed/organizations.tsx | 34 -- apps/mentions/src/routes/_authed/route.tsx | 21 - .../src/routes/_marketing/-components/cta.tsx | 27 -- .../src/routes/_marketing/-components/faq.tsx | 79 ---- .../routes/_marketing/-components/footer.tsx | 31 -- .../routes/_marketing/-components/header.tsx | 113 ----- .../routes/_marketing/-components/hero.tsx | 37 -- .../routes/_marketing/-components/pricing.tsx | 306 ------------- .../routes/_marketing/-components/stats.tsx | 89 ---- .../mentions/src/routes/_marketing/blog/$.tsx | 30 -- .../src/routes/_marketing/blog/index.tsx | 31 -- .../src/routes/_marketing/blog/route.tsx | 14 - .../src/routes/_marketing/blog/rss[.]xml.ts | 15 - apps/mentions/src/routes/_marketing/index.tsx | 20 - apps/mentions/src/routes/_marketing/route.tsx | 19 - apps/mentions/src/routes/api/$.ts | 55 --- apps/mentions/src/routes/api/rpc.$.ts | 26 -- apps/mentions/src/routes/login.tsx | 82 ---- apps/mentions/src/styles.css | 31 -- apps/mentions/tsconfig.json | 20 - apps/mentions/vite.config.ts | 38 -- apps/mentions/wrangler.jsonc | 37 -- .../-components/2-company-background.tsx | 122 ----- .../-components/2-create-organization.tsx} | 55 +-- .../-components/3-company-background.tsx} | 6 +- .../-components/3-understanding-company.tsx | 117 ----- .../-components/4-review-organization.tsx | 145 ------ .../-components/4-understanding-company.tsx | 124 ++++++ .../-components/5-review-project.tsx | 157 ++++--- .../onboarding/-components/6-all-set.tsx | 11 +- .../onboarding/-components/content.tsx | 12 +- 63 files changed, 261 insertions(+), 3384 deletions(-) delete mode 100644 apps/mentions/package.json delete mode 100644 apps/mentions/public/android-chrome-192x192.png delete mode 100644 apps/mentions/public/android-chrome-512x512.png delete mode 100644 apps/mentions/public/apple-touch-icon.png delete mode 100644 apps/mentions/public/favicon-96x96.png delete mode 100644 apps/mentions/public/favicon.ico delete mode 100644 apps/mentions/public/favicon.svg delete mode 100644 apps/mentions/public/site.webmanifest delete mode 100644 apps/mentions/src/components/error-boundary.tsx delete mode 100644 apps/mentions/src/components/logout-button.tsx delete mode 100644 apps/mentions/src/components/not-found.tsx delete mode 100644 apps/mentions/src/lib/api.ts delete mode 100644 apps/mentions/src/lib/auth/client.ts delete mode 100644 apps/mentions/src/lib/auth/server.ts delete mode 100644 apps/mentions/src/lib/env.ts delete mode 100644 apps/mentions/src/lib/seo.ts delete mode 100644 apps/mentions/src/reportWebVitals.ts delete mode 100644 apps/mentions/src/routeTree.gen.ts delete mode 100644 apps/mentions/src/router.tsx delete mode 100644 apps/mentions/src/routes/__root.tsx delete mode 100644 apps/mentions/src/routes/_authed/dashboard.tsx delete mode 100644 apps/mentions/src/routes/_authed/onboarding/-components/0-welcome.tsx delete mode 100644 apps/mentions/src/routes/_authed/onboarding/-components/1-user-background.tsx delete mode 100644 apps/mentions/src/routes/_authed/onboarding/-components/3-understanding-company.tsx delete mode 100644 apps/mentions/src/routes/_authed/onboarding/-components/5-review-project.tsx delete mode 100644 apps/mentions/src/routes/_authed/onboarding/-components/6-all-set.tsx delete mode 100644 apps/mentions/src/routes/_authed/onboarding/-components/content.tsx delete mode 100644 apps/mentions/src/routes/_authed/onboarding/-components/onboarding-progress.tsx delete mode 100644 apps/mentions/src/routes/_authed/onboarding/-lib/steps.ts delete mode 100644 apps/mentions/src/routes/_authed/onboarding/index.tsx delete mode 100644 apps/mentions/src/routes/_authed/organizations.tsx delete mode 100644 apps/mentions/src/routes/_authed/route.tsx delete mode 100644 apps/mentions/src/routes/_marketing/-components/cta.tsx delete mode 100644 apps/mentions/src/routes/_marketing/-components/faq.tsx delete mode 100644 apps/mentions/src/routes/_marketing/-components/footer.tsx delete mode 100644 apps/mentions/src/routes/_marketing/-components/header.tsx delete mode 100644 apps/mentions/src/routes/_marketing/-components/hero.tsx delete mode 100644 apps/mentions/src/routes/_marketing/-components/pricing.tsx delete mode 100644 apps/mentions/src/routes/_marketing/-components/stats.tsx delete mode 100644 apps/mentions/src/routes/_marketing/blog/$.tsx delete mode 100644 apps/mentions/src/routes/_marketing/blog/index.tsx delete mode 100644 apps/mentions/src/routes/_marketing/blog/route.tsx delete mode 100644 apps/mentions/src/routes/_marketing/blog/rss[.]xml.ts delete mode 100644 apps/mentions/src/routes/_marketing/index.tsx delete mode 100644 apps/mentions/src/routes/_marketing/route.tsx delete mode 100644 apps/mentions/src/routes/api/$.ts delete mode 100644 apps/mentions/src/routes/api/rpc.$.ts delete mode 100644 apps/mentions/src/routes/login.tsx delete mode 100644 apps/mentions/src/styles.css delete mode 100644 apps/mentions/tsconfig.json delete mode 100644 apps/mentions/vite.config.ts delete mode 100644 apps/mentions/wrangler.jsonc delete mode 100644 apps/seo/src/routes/_authed/onboarding/-components/2-company-background.tsx rename apps/{mentions/src/routes/_authed/onboarding/-components/4-review-organization.tsx => seo/src/routes/_authed/onboarding/-components/2-create-organization.tsx} (74%) rename apps/{mentions/src/routes/_authed/onboarding/-components/2-company-background.tsx => seo/src/routes/_authed/onboarding/-components/3-company-background.tsx} (95%) delete mode 100644 apps/seo/src/routes/_authed/onboarding/-components/3-understanding-company.tsx delete mode 100644 apps/seo/src/routes/_authed/onboarding/-components/4-review-organization.tsx create mode 100644 apps/seo/src/routes/_authed/onboarding/-components/4-understanding-company.tsx diff --git a/.github/workflows/cloudflare.yml b/.github/workflows/cloudflare.yml index 5522ca6ab..48267f6ef 100644 --- a/.github/workflows/cloudflare.yml +++ b/.github/workflows/cloudflare.yml @@ -100,11 +100,9 @@ jobs: ENV_PATH: ${{ steps.resolve.outputs.env_path }} run: | FILE_WWW="apps/www/wrangler.jsonc" - FILE_MENTIONS="apps/mentions/wrangler.jsonc" FILE_SEO="apps/seo/wrangler.jsonc" # Replace placeholder with computed slug sed -i "s|{env.PULL_REQUEST}|$ENVIRONMENT|g" "$FILE_WWW" - sed -i "s|{env.PULL_REQUEST}|$ENVIRONMENT|g" "$FILE_MENTIONS" sed -i "s|{env.PULL_REQUEST}|$ENVIRONMENT|g" "$FILE_SEO" sed -i "s|{env.PULL_REQUEST}|$ENVIRONMENT|g" "$ENV_PATH" @@ -132,17 +130,7 @@ jobs: command: ${{ steps.resolve.outputs.deploy_command }} environment: ${{ steps.resolve.outputs.environment }} secrets: ${{ steps.prepare_secrets.outputs.secret_names }} - - name: Deploy Mentions - id: deploy_mentions - uses: cloudflare/wrangler-action@v3 - with: - packageManager: pnpm - workingDirectory: apps/mentions - apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} - accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - command: ${{ steps.resolve.outputs.deploy_command }} - environment: ${{ steps.resolve.outputs.environment }} - secrets: ${{ steps.prepare_secrets.outputs.secret_names }} + - name: Deploy SEO id: deploy_seo @@ -162,7 +150,6 @@ jobs: with: message: | Cloudflare Preview URL for WWW :balloon: : https://${{ steps.deploy_www.outputs.deployment-url }} - Cloudflare Preview URL for Mentions :balloon: : https://${{ steps.deploy_mentions.outputs.deployment-url }} Cloudflare Preview URL for SEO :balloon: : https://${{ steps.deploy_seo.outputs.deployment-url }} comment-tag: execution @@ -181,11 +168,9 @@ jobs: shell: bash run: | FILE_WWW="apps/www/wrangler.jsonc" - FILE_MENTIONS="apps/mentions/wrangler.jsonc" FILE_SEO="apps/seo/wrangler.jsonc" # Replace placeholder with computed slug sed -i "s|{env.PULL_REQUEST}|$STAGE|g" "$FILE_WWW" - sed -i "s|{env.PULL_REQUEST}|$STAGE|g" "$FILE_MENTIONS" sed -i "s|{env.PULL_REQUEST}|$STAGE|g" "$FILE_SEO" - name: Destroy Preview Environment for WWW uses: cloudflare/wrangler-action@v3 @@ -196,15 +181,6 @@ jobs: accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} command: delete --env ${{ env.STAGE }} - - name: Destroy Preview Environment for Mentions - uses: cloudflare/wrangler-action@v3 - with: - packageManager: pnpm - workingDirectory: apps/mentions - apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} - accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - command: delete --env ${{ env.STAGE }} - - name: Destroy Preview Environment for SEO uses: cloudflare/wrangler-action@v3 with: diff --git a/.vscode/settings.json b/.vscode/settings.json index 59ec9aecf..64b1f50c2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -65,6 +65,7 @@ "totp", "turso", "Unlazied", + "uuidv", "webauthn" ] } diff --git a/apps/mentions/package.json b/apps/mentions/package.json deleted file mode 100644 index a7fdc3e7b..000000000 --- a/apps/mentions/package.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "name": "mentions", - "private": true, - "sideEffects": false, - "type": "module", - "scripts": { - "dev": "pnpm cf-typegen && pnpm with-env-local vite", - "build:local": "pnpm with-env-local vite build", - "build:preview": "pnpm with-env-preview vite build ", - "build:production": "pnpm with-env-production vite build", - "serve:local": "pnpm with-env-local vite preview", - "test": "vitest run", - "clean": "git clean -xdf .turbo node_modules dist .cache", - "format": "pnpx @biomejs/biome format . --write", - "lint": "pnpx @biomejs/biome lint . --write", - "typecheck": "pnpm cf-typegen && pnpm tsc --noEmit --emitDeclarationOnly false", - "with-env-local": "dotenvx run -f ../../.env.local -f ../../.env -- ", - "with-env-preview": "dotenvx run -f ../../.env -- ", - "with-env-production": "dotenvx run -f ../../.env.production -- ", - "wrangler": "wrangler", - "cf-typegen": "wrangler types --env-interface Env" - }, - "dependencies": { - "@rectangular-labs/auth": "workspace:*", - "@rectangular-labs/content": "workspace:*", - "@rectangular-labs/db": "workspace:*", - "@rectangular-labs/mentions-api": "workspace:*", - "@rectangular-labs/result": "workspace:*", - "@rectangular-labs/ui": "workspace:*", - "@stepperize/react": "^5.1.7", - "@t3-oss/env-core": "^0.13.8", - "@tanstack/react-query": "^5.89.0", - "@tanstack/react-query-devtools": "^5.89.0", - "@tanstack/react-router": "^1.131.44", - "@tanstack/react-router-devtools": "^1.131.44", - "@tanstack/react-router-with-query": "^1.130.17", - "@tanstack/react-start": "^1.131.44", - "arktype": "^2.1.22", - "motion": "^12.23.13", - "react": "^19.1.1", - "react-dom": "^19.1.1" - }, - "devDependencies": { - "@rectangular-labs/typescript": "workspace:*", - "@tailwindcss/vite": "^4.1.13", - "@testing-library/dom": "^10.4.1", - "@testing-library/react": "^16.3.0", - "@types/node": "^24.5.1", - "@types/react": "^19.1.13", - "@types/react-dom": "^19.1.9", - "@vitejs/plugin-react": "^5.0.3", - "jiti": "^2.5.1", - "jsdom": "^26.1.0", - "typescript": "^5.9.2", - "vite": "^7.1.5", - "vite-plugin-mkcert": "^1.17.8", - "vite-tsconfig-paths": "^5.1.4", - "vitest": "^3.2.4", - "web-vitals": "^5.1.0", - "wrangler": "^4.37.1" - } -} diff --git a/apps/mentions/public/android-chrome-192x192.png b/apps/mentions/public/android-chrome-192x192.png deleted file mode 100644 index 893a732247d04c5a87817bf99861d41048eda564..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1495 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zf+;V4dgb;uum9_x7%B(W3wfmy7C0 zMV_ck5EOpE+EIF#NkKuaxkaXHhGf7^DFHeC1GO(cVv?YHCOKAwN7E7pC<#JBuvsqEGM9qI=*GlZ=Uy_sVc$oyXX_s5Tc(@v{0F3DkJ z+~YTk;m|%IPX?9`cNo_@#5L|=TDs?-&8sggAAs=35(l3GO&bRO311j$R!?Q%XkbXV zFh}+QV+Dg;1N)1z1KJb97tA?+-+ZbN1ET;?*=A;-;seYd7;Fxtv&*y=h#U&+m?N&> zt|-%9_qIVD0v&?bXC$c2;7n_p`Jl8=>Plt+(69%LlkPRJCc?Bc@PD|$*#1za&AW>E zi+tin$5{WKgg6%JW+=t6 z$-%GLiuc!h>x2K)Z~SMr;CiUhvRJ@#J%fP;4_EV{1*nv#+=}?s@vR?L3!MD@K0WZ= z@An2V8(3@@ITR}XvL-eeIL=Vec`2sBA>6`why}v;5l{nj6E3Lmt8i4mW#DRITIk?e z?jXX!>eLvZfI=-$;Rng4o&|**GyoygX<#H)gCdd7bAw8R&>WRYFx>-Gtsww55=^O9 zDuBiRY+W@H6$<;^-5qE`Zg zZ`Pjw&_Hzj?toz)$TkITmga9(4}?AKCO#4N#2qwO)?ZDuXn#2A`+X24(^f2@_d<>X zBa(o&Nu@M`q~AUB{^;)w@~nzI1Jt()FXS{(eGdtYqT+_He?v2N-(|C0@I6Sp+ck66 zvfZzC^}TYxV}I0s#rwSNuh;#plQ;cPw{i2$HD{iGez$6s)++glOcrwePYW!zTz^^8 z1@88JwP9z4}I!`ojFgtXJ^jt z+3$S2`+fi4o=)1DFxP*PKLB8^aO3)I0KjAw2J{!mVm$A6F90kcT)%Ed-oDXpbJ@<5 zK^ywMGxi0s=N13z6;Z@yq-{+v32xF$DlUQQOOqJCj0RHXt?2$qk3H#bGT7#P#+&_ ziy}M>SvAKRUK;_J+xBeAjm6vWpFdfctg|2l-l(rqgjukcWwi+ni@R*`*zM}I$+|~P zxrThH{aU{v$bEtz1;KZO2A4V_lde%BxYy|!Z8YFcmsuI62nGqu&uhYg>*=GW9z;r1 zn-q#*S=*KaBLWap@JjWVTTCG6SbMk0q>$ozmv#>H(%$`ORw+2{9l9)5=;C}MA_H@| z)S)-76hg6pE_|!GUY_bGdPSHUvoQ9UM!M8gq8nDScrwTeg6mhX!c+wQ{!n zmDn2vMTVoOvIJ+{5z4@^yDpWI9tvB>hwims%h2`a$Hp~zRzsO9%KgYzhh8MA;pS&K z3a}Ic8;pj&>`DLS2#s~o)VYKpCsW5nISMn=OQ2`Knfe&X^(r_MZ9pXTwZ_2=!e<~J z=Rk&aZ&V`l*FfDacdB|!yU!c3VJxJE+*j>+;ffrLl4oC69!{`P8Wj?ou z&X6tLa^O!E2#J+%6#{`i?K%v>NwWRlR|DvvQUlX8b3Y8{gCAC$|Lg=vALp)#B!8HE zz@I!>po-2MnD&(lp3MN9hLZq#a5r5L0DszPfAR$_^;m30G=l}6-!16&W5}HTZ;=e(TtK8 zDIxZml&saqJ!4}tBz#X5M=Z#=n(}sW)s&21uZT*lNl_slZ(Eciz4deT-%2`(IHvJ} zVBm_G03~uJPt7eDe=_VS3Y;$bi&O>Ko~KQgWdaI&RPIc+f@GU_aic*2sp9X$)jkm|0CEphihARC1Zqdr zyaQ-}to;0DqdQEN$iv1*?SEt_Ftac16xe(uCXF-21EjdeG@utri+rfFTP*eY9Qrby zpXSNy{grEJ#$HunrO#zS?5N#NLakLb3xSFR3~Z?ZW5cDOInVZZ*n_zYgtLHzP_a;E z%mg&aG~KSQGm)Ul(u{aQrW;Sr9M%&4ZjsK`UP6+HbGG&{5|?BdY=|4vAH6k${GdrZ ze5uCP1T2NIY5@rCNQX&$)~^_mo7`g5QWy!!t8P6wOR|sVh*~Bd{Xq)zW}wqCU+K3I zbnRF|pF-*;(mM7lnApo>B6L$fiRl466PxW_*1T!ml`jL4-01SD{;rTX=06yi8jLdIzj`L+ fApZvkA!f1m`Ux+Gq$H9Ti-2&$*7f!8q?Y^zNfK*{ diff --git a/apps/mentions/public/apple-touch-icon.png b/apps/mentions/public/apple-touch-icon.png deleted file mode 100644 index a3a00cf19cee5dc19b51933c39e1f2a5b80f21ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1245 zcmeAS@N?(olHy`uVBq!ia0vp^TR@nD4M^IaWiw)6U|Hhn;uum9_xAe6!lY;c*Ndwe zG&mR{6Ow{87&X&083S+3NN{8bU@@qdko>IP`uE!AdAIMb&YS!B)9df%HD&@g4{%?Y z=q>g5JX-^s;#S55!Ou;2F7PZdV<>qwr;Tw|;}y0BzqywU9E>O2Vz{ufSc)x!El8Td zs?_f=!&!$cW{2hHG9?yBdgL;^xMIo6Y{I;fmtn5e^2CNT1@a7=;n`4UBloy$ckl1N zcJuxR|4Q3@bH@4S$r~ejp4b1K&GO>(Q=@awKU>W8dt9~mTK=!|HK(5zt-1bsW7OJX zuU}`^ee-Ovlj~33f4~3mLj#b0v)N~F%Mc4WC1^?5UKsTh1u^wc@@0ih-`_fWzV^fO|7+IHKAV=a{r1M&Z;yT6|8MV~Uw^N* z?}^cSTw~XN`K8Ia>)Ew;>YqjHO;6q%*MBw3G%h~=^JaghFONS;Y`>j*=l%DOf978@ zpMCaNjh%e=(IlJyukWikT<_1=w_loJRvW#uF0OK*@jruOMqcdkkJbBu#TJ97tDnm{ Hr-UW|d!ZB$ diff --git a/apps/mentions/public/favicon-96x96.png b/apps/mentions/public/favicon-96x96.png deleted file mode 100644 index 49664e9f08e4203775f2c78fe60394ed8c08b419..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 549 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7Ro>V0`20;uum9_x8^2-a`ojEDx44 zdMPv>~Y&j|`A$Q-wtf4+40T_y2ax7p{GJhI*Vg2Kc3Svi^A|_~R7%|#8_AdO^aAtAmGKF9PZXwqj%qEG6F)#iy z_(lj{YcWlPQrlIE1Lqi=^Zj`xd(}6F>IT~batZt!*l#euVFYp=8W@vT7+#Uc;xTQL za_(JtEFd`}f#Y}cmz)*p&bHF8RQE1mhuA5=J!>9g;v54%3H2V~W1Np$3J+N%-uYsD zL;9jxj_@|l+bzv3PTYT+z3G_N1LmC_GMx;9x9%@sQE-^iXt0Ni2#SOI1Fu@_e!g4w S+7ZC$XYh3Ob6Mw<&;$T_TG^5S diff --git a/apps/mentions/public/favicon.ico b/apps/mentions/public/favicon.ico deleted file mode 100644 index d7baa0ead7fe71bd201930be27f61942bf9621ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 zcmeI3J&wXa3`V_z18{^iNR(7t?=4b>E_)KLkTPXBMWiT7jF^kjBc0?22c} ziW%(ue7rc(Xb}03jU11n+d?jTkxvmRiu>oC$d8WgbQ{0_-in+x;h;HMA}94Y{>%b~ z0w{n2D1ZV^1;%j - - Rectangular Lab Logo - - - \ No newline at end of file diff --git a/apps/mentions/public/site.webmanifest b/apps/mentions/public/site.webmanifest deleted file mode 100644 index a1f16f020..000000000 --- a/apps/mentions/public/site.webmanifest +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "Rectangular Labs", - "short_name": "Rectangular Labs", - "icons": [ - { - "src": "/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png", - "purpose": "maskable" - }, - { - "src": "/android-chrome-512x512.png", - "sizes": "512x512", - "type": "image/png", - "purpose": "maskable" - } - ], - "theme_color": "#ffffff", - "background_color": "#ffffff", - "display": "standalone" -} diff --git a/apps/mentions/src/components/error-boundary.tsx b/apps/mentions/src/components/error-boundary.tsx deleted file mode 100644 index 2403ae399..000000000 --- a/apps/mentions/src/components/error-boundary.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { Terminal } from "@rectangular-labs/ui/components/icon"; -import { - Alert, - AlertDescription, - AlertTitle, -} from "@rectangular-labs/ui/components/ui/alert"; -import { Button } from "@rectangular-labs/ui/components/ui/button"; -import type { ErrorComponentProps } from "@tanstack/react-router"; -import { Link, rootRouteId, useMatch, useRouter } from "@tanstack/react-router"; - -export function DefaultCatchBoundary({ error }: ErrorComponentProps) { - const router = useRouter(); - const isRoot = useMatch({ - strict: false, - select: (state) => state.id === rootRouteId, - }); - - console.error(error); - const message = - error instanceof Error ? error.message : "An unexpected error occurred."; - - return ( -
- - - Something went wrong - {message} - -
- - {isRoot ? ( - - ) : ( - - )} -
-
- ); -} diff --git a/apps/mentions/src/components/logout-button.tsx b/apps/mentions/src/components/logout-button.tsx deleted file mode 100644 index 6c71d5a69..000000000 --- a/apps/mentions/src/components/logout-button.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { Button } from "@rectangular-labs/ui/components/ui/button"; -import { useRouter } from "@tanstack/react-router"; -import { useState } from "react"; -import { authClient } from "~/lib/auth/client"; - -export function LogoutButton({ className }: { className?: string }) { - const router = useRouter(); - const [isPending, setIsPending] = useState(false); - - return ( - - ); -} diff --git a/apps/mentions/src/components/not-found.tsx b/apps/mentions/src/components/not-found.tsx deleted file mode 100644 index 69c1363ff..000000000 --- a/apps/mentions/src/components/not-found.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { Button } from "@rectangular-labs/ui/components/ui/button"; -import { Section } from "@rectangular-labs/ui/components/ui/section"; -import { Link } from "@tanstack/react-router"; - -export function NotFound({ children }: { children?: React.ReactNode }) { - return ( -
-
-

- 404 -

-

- Page not found -

-

- {children || - "The page you are looking for does not exist or has been moved."} -

-
- - -
-
-
- ); -} diff --git a/apps/mentions/src/lib/api.ts b/apps/mentions/src/lib/api.ts deleted file mode 100644 index afe02125c..000000000 --- a/apps/mentions/src/lib/api.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { rpcClient, rqApiClient } from "@rectangular-labs/mentions-api/client"; -import { serverClient } from "@rectangular-labs/mentions-api/server"; -import { createIsomorphicFn } from "@tanstack/react-start"; -import { getWebRequest } from "@tanstack/react-start/server"; -import { clientEnv } from "./env"; - -export const getApiClient = createIsomorphicFn() - .server(() => { - const request = getWebRequest(); - const client = serverClient({ - url: new URL(request.url), - // The request isn't populated in the server context, so we need to pass it in manually - reqHeaders: request.headers, - resHeaders: new Headers(), - }); - return client; - }) - .client(() => { - const client = rpcClient(window.location.origin); - return client; - }); - -export const apiClientRq = rqApiClient( - typeof window !== "undefined" - ? window.location.origin - : clientEnv().VITE_MENTIONS_URL, -); diff --git a/apps/mentions/src/lib/auth/client.ts b/apps/mentions/src/lib/auth/client.ts deleted file mode 100644 index aa5922353..000000000 --- a/apps/mentions/src/lib/auth/client.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { createAuthClient } from "@rectangular-labs/auth/client"; -import { createIsomorphicFn } from "@tanstack/react-start"; -import { getWebRequest } from "@tanstack/react-start/server"; -import { clientEnv } from "../env"; -import { authServerHandler } from "./server"; - -export const authClient = createAuthClient(clientEnv().VITE_MENTIONS_URL); - -export const getCurrentSession = createIsomorphicFn() - .server(async () => { - const request = getWebRequest(); - const session = await authServerHandler.api.getSession({ - headers: request.headers, - }); - return session; - }) - .client(async () => { - const session = await authClient.getSession(); - return session.data; - }); - -export const getUserOrganizations = createIsomorphicFn() - .server(async () => { - const request = getWebRequest(); - const organizations = await authServerHandler.api.listOrganizations({ - headers: request.headers, - }); - return organizations; - }) - .client(async () => { - const organizations = await authClient.organization.list(); - if (organizations.error) { - throw new Error( - organizations.error.message ?? - "Something went wrong loading organizations. Please try again", - ); - } - return organizations.data; - }); diff --git a/apps/mentions/src/lib/auth/server.ts b/apps/mentions/src/lib/auth/server.ts deleted file mode 100644 index 11fc18595..000000000 --- a/apps/mentions/src/lib/auth/server.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { initAuthHandler } from "@rectangular-labs/auth"; -import { createDb } from "@rectangular-labs/db"; -import { serverEnv } from "../env"; - -export const authServerHandler = initAuthHandler( - serverEnv().VITE_MENTIONS_URL, - createDb(), -); diff --git a/apps/mentions/src/lib/env.ts b/apps/mentions/src/lib/env.ts deleted file mode 100644 index d2ee8429e..000000000 --- a/apps/mentions/src/lib/env.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { apiEnv } from "@rectangular-labs/mentions-api/env"; -import { createEnv } from "@t3-oss/env-core"; -import { type } from "arktype"; - -export const clientEnv = () => - createEnv({ - extends: [], - clientPrefix: "VITE_", - client: { - VITE_MENTIONS_URL: type("string"), - }, - runtimeEnv: import.meta.env, - emptyStringAsUndefined: true, - skipValidation: - !!process.env.CI || process.env.npm_lifecycle_event === "lint", - }); - -export const serverEnv = () => - createEnv({ - extends: [clientEnv(), apiEnv()], - server: {}, - runtimeEnv: process.env, - emptyStringAsUndefined: true, - skipValidation: - !!process.env.CI || process.env.npm_lifecycle_event === "lint", - }); diff --git a/apps/mentions/src/lib/seo.ts b/apps/mentions/src/lib/seo.ts deleted file mode 100644 index d182da511..000000000 --- a/apps/mentions/src/lib/seo.ts +++ /dev/null @@ -1,33 +0,0 @@ -export const seo = ({ - title, - description, - keywords, - image, -}: { - title: string; - description?: string; - image?: string; - keywords?: string; -}) => { - const tags = [ - { title }, - { name: "description", content: description }, - { name: "keywords", content: keywords }, - { name: "twitter:title", content: title }, - { name: "twitter:description", content: description }, - { name: "twitter:creator", content: "@winston_yeo" }, - { name: "twitter:site", content: "@winston_yeo" }, - { name: "og:type", content: "website" }, - { name: "og:title", content: title }, - { name: "og:description", content: description }, - ...(image - ? [ - { name: "twitter:image", content: image }, - { name: "twitter:card", content: "summary_large_image" }, - { name: "og:image", content: image }, - ] - : []), - ]; - - return tags; -}; diff --git a/apps/mentions/src/reportWebVitals.ts b/apps/mentions/src/reportWebVitals.ts deleted file mode 100644 index d3c25119e..000000000 --- a/apps/mentions/src/reportWebVitals.ts +++ /dev/null @@ -1,13 +0,0 @@ -const reportWebVitals = (onPerfEntry?: () => void) => { - if (onPerfEntry && onPerfEntry instanceof Function) { - void import("web-vitals").then(({ onCLS, onINP, onFCP, onLCP, onTTFB }) => { - onCLS(onPerfEntry); - onINP(onPerfEntry); - onFCP(onPerfEntry); - onLCP(onPerfEntry); - onTTFB(onPerfEntry); - }); - } -}; - -export default reportWebVitals; diff --git a/apps/mentions/src/routeTree.gen.ts b/apps/mentions/src/routeTree.gen.ts deleted file mode 100644 index c4e6a1d3e..000000000 --- a/apps/mentions/src/routeTree.gen.ts +++ /dev/null @@ -1,417 +0,0 @@ -/* eslint-disable */ - -// @ts-nocheck - -// noinspection JSUnusedGlobalSymbols - -// This file was automatically generated by TanStack Router. -// You should NOT make any changes in this file as it will be overwritten. -// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. - -import { createServerRootRoute } from '@tanstack/react-start/server' - -import { Route as rootRouteImport } from './routes/__root' -import { Route as LoginRouteImport } from './routes/login' -import { Route as MarketingRouteRouteImport } from './routes/_marketing/route' -import { Route as AuthedRouteRouteImport } from './routes/_authed/route' -import { Route as MarketingIndexRouteImport } from './routes/_marketing/index' -import { Route as AuthedProjectsRouteImport } from './routes/_authed/projects' -import { Route as AuthedOrganizationsRouteImport } from './routes/_authed/organizations' -import { Route as AuthedDashboardRouteImport } from './routes/_authed/dashboard' -import { Route as MarketingBlogRouteRouteImport } from './routes/_marketing/blog/route' -import { Route as MarketingBlogIndexRouteImport } from './routes/_marketing/blog/index' -import { Route as AuthedOnboardingIndexRouteImport } from './routes/_authed/onboarding/index' -import { Route as MarketingBlogSplatRouteImport } from './routes/_marketing/blog/$' -import { Route as AuthedProjectIdKeywordsRouteImport } from './routes/_authed/$projectId/keywords' -import { Route as AuthedProjectIdInboxRouteImport } from './routes/_authed/$projectId/inbox' -import { ServerRoute as ApiSplatServerRouteImport } from './routes/api/$' -import { ServerRoute as ApiRpcSplatServerRouteImport } from './routes/api/rpc.$' -import { ServerRoute as MarketingBlogRssDotxmlServerRouteImport } from './routes/_marketing/blog/rss[.]xml' - -const rootServerRouteImport = createServerRootRoute() - -const LoginRoute = LoginRouteImport.update({ - id: '/login', - path: '/login', - getParentRoute: () => rootRouteImport, -} as any) -const MarketingRouteRoute = MarketingRouteRouteImport.update({ - id: '/_marketing', - getParentRoute: () => rootRouteImport, -} as any) -const AuthedRouteRoute = AuthedRouteRouteImport.update({ - id: '/_authed', - getParentRoute: () => rootRouteImport, -} as any) -const MarketingIndexRoute = MarketingIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => MarketingRouteRoute, -} as any) -const AuthedProjectsRoute = AuthedProjectsRouteImport.update({ - id: '/projects', - path: '/projects', - getParentRoute: () => AuthedRouteRoute, -} as any) -const AuthedOrganizationsRoute = AuthedOrganizationsRouteImport.update({ - id: '/organizations', - path: '/organizations', - getParentRoute: () => AuthedRouteRoute, -} as any) -const AuthedDashboardRoute = AuthedDashboardRouteImport.update({ - id: '/dashboard', - path: '/dashboard', - getParentRoute: () => AuthedRouteRoute, -} as any) -const MarketingBlogRouteRoute = MarketingBlogRouteRouteImport.update({ - id: '/blog', - path: '/blog', - getParentRoute: () => MarketingRouteRoute, -} as any) -const MarketingBlogIndexRoute = MarketingBlogIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => MarketingBlogRouteRoute, -} as any) -const AuthedOnboardingIndexRoute = AuthedOnboardingIndexRouteImport.update({ - id: '/onboarding/', - path: '/onboarding/', - getParentRoute: () => AuthedRouteRoute, -} as any) -const MarketingBlogSplatRoute = MarketingBlogSplatRouteImport.update({ - id: '/$', - path: '/$', - getParentRoute: () => MarketingBlogRouteRoute, -} as any) -const AuthedProjectIdKeywordsRoute = AuthedProjectIdKeywordsRouteImport.update({ - id: '/$projectId/keywords', - path: '/$projectId/keywords', - getParentRoute: () => AuthedRouteRoute, -} as any) -const AuthedProjectIdInboxRoute = AuthedProjectIdInboxRouteImport.update({ - id: '/$projectId/inbox', - path: '/$projectId/inbox', - getParentRoute: () => AuthedRouteRoute, -} as any) -const ApiSplatServerRoute = ApiSplatServerRouteImport.update({ - id: '/api/$', - path: '/api/$', - getParentRoute: () => rootServerRouteImport, -} as any) -const ApiRpcSplatServerRoute = ApiRpcSplatServerRouteImport.update({ - id: '/api/rpc/$', - path: '/api/rpc/$', - getParentRoute: () => rootServerRouteImport, -} as any) -const MarketingBlogRssDotxmlServerRoute = - MarketingBlogRssDotxmlServerRouteImport.update({ - id: '/_marketing/blog/rss.xml', - path: '/blog/rss.xml', - getParentRoute: () => rootServerRouteImport, - } as any) - -export interface FileRoutesByFullPath { - '/login': typeof LoginRoute - '/blog': typeof MarketingBlogRouteRouteWithChildren - '/dashboard': typeof AuthedDashboardRoute - '/organizations': typeof AuthedOrganizationsRoute - '/projects': typeof AuthedProjectsRoute - '/': typeof MarketingIndexRoute - '/$projectId/inbox': typeof AuthedProjectIdInboxRoute - '/$projectId/keywords': typeof AuthedProjectIdKeywordsRoute - '/blog/$': typeof MarketingBlogSplatRoute - '/onboarding': typeof AuthedOnboardingIndexRoute - '/blog/': typeof MarketingBlogIndexRoute -} -export interface FileRoutesByTo { - '/login': typeof LoginRoute - '/dashboard': typeof AuthedDashboardRoute - '/organizations': typeof AuthedOrganizationsRoute - '/projects': typeof AuthedProjectsRoute - '/': typeof MarketingIndexRoute - '/$projectId/inbox': typeof AuthedProjectIdInboxRoute - '/$projectId/keywords': typeof AuthedProjectIdKeywordsRoute - '/blog/$': typeof MarketingBlogSplatRoute - '/onboarding': typeof AuthedOnboardingIndexRoute - '/blog': typeof MarketingBlogIndexRoute -} -export interface FileRoutesById { - __root__: typeof rootRouteImport - '/_authed': typeof AuthedRouteRouteWithChildren - '/_marketing': typeof MarketingRouteRouteWithChildren - '/login': typeof LoginRoute - '/_marketing/blog': typeof MarketingBlogRouteRouteWithChildren - '/_authed/dashboard': typeof AuthedDashboardRoute - '/_authed/organizations': typeof AuthedOrganizationsRoute - '/_authed/projects': typeof AuthedProjectsRoute - '/_marketing/': typeof MarketingIndexRoute - '/_authed/$projectId/inbox': typeof AuthedProjectIdInboxRoute - '/_authed/$projectId/keywords': typeof AuthedProjectIdKeywordsRoute - '/_marketing/blog/$': typeof MarketingBlogSplatRoute - '/_authed/onboarding/': typeof AuthedOnboardingIndexRoute - '/_marketing/blog/': typeof MarketingBlogIndexRoute -} -export interface FileRouteTypes { - fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: - | '/login' - | '/blog' - | '/dashboard' - | '/organizations' - | '/projects' - | '/' - | '/$projectId/inbox' - | '/$projectId/keywords' - | '/blog/$' - | '/onboarding' - | '/blog/' - fileRoutesByTo: FileRoutesByTo - to: - | '/login' - | '/dashboard' - | '/organizations' - | '/projects' - | '/' - | '/$projectId/inbox' - | '/$projectId/keywords' - | '/blog/$' - | '/onboarding' - | '/blog' - id: - | '__root__' - | '/_authed' - | '/_marketing' - | '/login' - | '/_marketing/blog' - | '/_authed/dashboard' - | '/_authed/organizations' - | '/_authed/projects' - | '/_marketing/' - | '/_authed/$projectId/inbox' - | '/_authed/$projectId/keywords' - | '/_marketing/blog/$' - | '/_authed/onboarding/' - | '/_marketing/blog/' - fileRoutesById: FileRoutesById -} -export interface RootRouteChildren { - AuthedRouteRoute: typeof AuthedRouteRouteWithChildren - MarketingRouteRoute: typeof MarketingRouteRouteWithChildren - LoginRoute: typeof LoginRoute -} -export interface FileServerRoutesByFullPath { - '/api/$': typeof ApiSplatServerRoute - '/blog/rss.xml': typeof MarketingBlogRssDotxmlServerRoute - '/api/rpc/$': typeof ApiRpcSplatServerRoute -} -export interface FileServerRoutesByTo { - '/api/$': typeof ApiSplatServerRoute - '/blog/rss.xml': typeof MarketingBlogRssDotxmlServerRoute - '/api/rpc/$': typeof ApiRpcSplatServerRoute -} -export interface FileServerRoutesById { - __root__: typeof rootServerRouteImport - '/api/$': typeof ApiSplatServerRoute - '/_marketing/blog/rss.xml': typeof MarketingBlogRssDotxmlServerRoute - '/api/rpc/$': typeof ApiRpcSplatServerRoute -} -export interface FileServerRouteTypes { - fileServerRoutesByFullPath: FileServerRoutesByFullPath - fullPaths: '/api/$' | '/blog/rss.xml' | '/api/rpc/$' - fileServerRoutesByTo: FileServerRoutesByTo - to: '/api/$' | '/blog/rss.xml' | '/api/rpc/$' - id: '__root__' | '/api/$' | '/_marketing/blog/rss.xml' | '/api/rpc/$' - fileServerRoutesById: FileServerRoutesById -} -export interface RootServerRouteChildren { - ApiSplatServerRoute: typeof ApiSplatServerRoute - MarketingBlogRssDotxmlServerRoute: typeof MarketingBlogRssDotxmlServerRoute - ApiRpcSplatServerRoute: typeof ApiRpcSplatServerRoute -} - -declare module '@tanstack/react-router' { - interface FileRoutesByPath { - '/login': { - id: '/login' - path: '/login' - fullPath: '/login' - preLoaderRoute: typeof LoginRouteImport - parentRoute: typeof rootRouteImport - } - '/_marketing': { - id: '/_marketing' - path: '' - fullPath: '' - preLoaderRoute: typeof MarketingRouteRouteImport - parentRoute: typeof rootRouteImport - } - '/_authed': { - id: '/_authed' - path: '' - fullPath: '' - preLoaderRoute: typeof AuthedRouteRouteImport - parentRoute: typeof rootRouteImport - } - '/_marketing/': { - id: '/_marketing/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof MarketingIndexRouteImport - parentRoute: typeof MarketingRouteRoute - } - '/_authed/projects': { - id: '/_authed/projects' - path: '/projects' - fullPath: '/projects' - preLoaderRoute: typeof AuthedProjectsRouteImport - parentRoute: typeof AuthedRouteRoute - } - '/_authed/organizations': { - id: '/_authed/organizations' - path: '/organizations' - fullPath: '/organizations' - preLoaderRoute: typeof AuthedOrganizationsRouteImport - parentRoute: typeof AuthedRouteRoute - } - '/_authed/dashboard': { - id: '/_authed/dashboard' - path: '/dashboard' - fullPath: '/dashboard' - preLoaderRoute: typeof AuthedDashboardRouteImport - parentRoute: typeof AuthedRouteRoute - } - '/_marketing/blog': { - id: '/_marketing/blog' - path: '/blog' - fullPath: '/blog' - preLoaderRoute: typeof MarketingBlogRouteRouteImport - parentRoute: typeof MarketingRouteRoute - } - '/_marketing/blog/': { - id: '/_marketing/blog/' - path: '/' - fullPath: '/blog/' - preLoaderRoute: typeof MarketingBlogIndexRouteImport - parentRoute: typeof MarketingBlogRouteRoute - } - '/_authed/onboarding/': { - id: '/_authed/onboarding/' - path: '/onboarding' - fullPath: '/onboarding' - preLoaderRoute: typeof AuthedOnboardingIndexRouteImport - parentRoute: typeof AuthedRouteRoute - } - '/_marketing/blog/$': { - id: '/_marketing/blog/$' - path: '/$' - fullPath: '/blog/$' - preLoaderRoute: typeof MarketingBlogSplatRouteImport - parentRoute: typeof MarketingBlogRouteRoute - } - '/_authed/$projectId/keywords': { - id: '/_authed/$projectId/keywords' - path: '/$projectId/keywords' - fullPath: '/$projectId/keywords' - preLoaderRoute: typeof AuthedProjectIdKeywordsRouteImport - parentRoute: typeof AuthedRouteRoute - } - '/_authed/$projectId/inbox': { - id: '/_authed/$projectId/inbox' - path: '/$projectId/inbox' - fullPath: '/$projectId/inbox' - preLoaderRoute: typeof AuthedProjectIdInboxRouteImport - parentRoute: typeof AuthedRouteRoute - } - } -} -declare module '@tanstack/react-start/server' { - interface ServerFileRoutesByPath { - '/api/$': { - id: '/api/$' - path: '/api/$' - fullPath: '/api/$' - preLoaderRoute: typeof ApiSplatServerRouteImport - parentRoute: typeof rootServerRouteImport - } - '/api/rpc/$': { - id: '/api/rpc/$' - path: '/api/rpc/$' - fullPath: '/api/rpc/$' - preLoaderRoute: typeof ApiRpcSplatServerRouteImport - parentRoute: typeof rootServerRouteImport - } - '/_marketing/blog/rss.xml': { - id: '/_marketing/blog/rss.xml' - path: '/blog/rss.xml' - fullPath: '/blog/rss.xml' - preLoaderRoute: typeof MarketingBlogRssDotxmlServerRouteImport - parentRoute: typeof rootServerRouteImport - } - } -} - -interface AuthedRouteRouteChildren { - AuthedDashboardRoute: typeof AuthedDashboardRoute - AuthedOrganizationsRoute: typeof AuthedOrganizationsRoute - AuthedProjectsRoute: typeof AuthedProjectsRoute - AuthedProjectIdInboxRoute: typeof AuthedProjectIdInboxRoute - AuthedProjectIdKeywordsRoute: typeof AuthedProjectIdKeywordsRoute - AuthedOnboardingIndexRoute: typeof AuthedOnboardingIndexRoute -} - -const AuthedRouteRouteChildren: AuthedRouteRouteChildren = { - AuthedDashboardRoute: AuthedDashboardRoute, - AuthedOrganizationsRoute: AuthedOrganizationsRoute, - AuthedProjectsRoute: AuthedProjectsRoute, - AuthedProjectIdInboxRoute: AuthedProjectIdInboxRoute, - AuthedProjectIdKeywordsRoute: AuthedProjectIdKeywordsRoute, - AuthedOnboardingIndexRoute: AuthedOnboardingIndexRoute, -} - -const AuthedRouteRouteWithChildren = AuthedRouteRoute._addFileChildren( - AuthedRouteRouteChildren, -) - -interface MarketingBlogRouteRouteChildren { - MarketingBlogSplatRoute: typeof MarketingBlogSplatRoute - MarketingBlogIndexRoute: typeof MarketingBlogIndexRoute -} - -const MarketingBlogRouteRouteChildren: MarketingBlogRouteRouteChildren = { - MarketingBlogSplatRoute: MarketingBlogSplatRoute, - MarketingBlogIndexRoute: MarketingBlogIndexRoute, -} - -const MarketingBlogRouteRouteWithChildren = - MarketingBlogRouteRoute._addFileChildren(MarketingBlogRouteRouteChildren) - -interface MarketingRouteRouteChildren { - MarketingBlogRouteRoute: typeof MarketingBlogRouteRouteWithChildren - MarketingIndexRoute: typeof MarketingIndexRoute -} - -const MarketingRouteRouteChildren: MarketingRouteRouteChildren = { - MarketingBlogRouteRoute: MarketingBlogRouteRouteWithChildren, - MarketingIndexRoute: MarketingIndexRoute, -} - -const MarketingRouteRouteWithChildren = MarketingRouteRoute._addFileChildren( - MarketingRouteRouteChildren, -) - -const rootRouteChildren: RootRouteChildren = { - AuthedRouteRoute: AuthedRouteRouteWithChildren, - MarketingRouteRoute: MarketingRouteRouteWithChildren, - LoginRoute: LoginRoute, -} -export const routeTree = rootRouteImport - ._addFileChildren(rootRouteChildren) - ._addFileTypes() -const rootServerRouteChildren: RootServerRouteChildren = { - ApiSplatServerRoute: ApiSplatServerRoute, - MarketingBlogRssDotxmlServerRoute: MarketingBlogRssDotxmlServerRoute, - ApiRpcSplatServerRoute: ApiRpcSplatServerRoute, -} -export const serverRouteTree = rootServerRouteImport - ._addFileChildren(rootServerRouteChildren) - ._addFileTypes() diff --git a/apps/mentions/src/router.tsx b/apps/mentions/src/router.tsx deleted file mode 100644 index ab605195f..000000000 --- a/apps/mentions/src/router.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { QueryClient } from "@tanstack/react-query"; -import { createRouter as createTanStackRouter } from "@tanstack/react-router"; -import { routerWithQueryClient } from "@tanstack/react-router-with-query"; -import { DefaultCatchBoundary } from "./components/error-boundary"; -import { NotFound } from "./components/not-found"; -import { routeTree } from "./routeTree.gen"; - -// NOTE: Most of the integration code found here is experimental and will -// definitely end up in a more streamlined API in the future. This is just -// to show what's possible with the current APIs. - -export function createRouter() { - const queryClient = new QueryClient(); - - return routerWithQueryClient( - createTanStackRouter({ - routeTree, - context: { queryClient }, - defaultPreload: "intent", - defaultErrorComponent: DefaultCatchBoundary, - defaultNotFoundComponent: () => , - scrollRestoration: true, - defaultStructuralSharing: true, - }), - queryClient, - ); -} - -declare module "@tanstack/react-router" { - interface Register { - router: ReturnType; - } -} diff --git a/apps/mentions/src/routes/__root.tsx b/apps/mentions/src/routes/__root.tsx deleted file mode 100644 index 0dc646882..000000000 --- a/apps/mentions/src/routes/__root.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { ThemeProvider } from "@rectangular-labs/ui/components/theme-provider"; -import { Toaster } from "@rectangular-labs/ui/components/ui/sonner"; -import type { QueryClient } from "@tanstack/react-query"; -import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; -import { - createRootRouteWithContext, - HeadContent, - Outlet, - Scripts, -} from "@tanstack/react-router"; -import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"; -import { seo } from "~/lib/seo"; -import appCss from "../styles.css?url"; - -export const Route = createRootRouteWithContext<{ - queryClient: QueryClient; -}>()({ - head: () => ({ - meta: [ - { charSet: "utf-8" }, - { - name: "viewport", - content: "width=device-width, initial-scale=1", - }, - { - name: "apple-mobile-web-app-title", - content: "Mentions", - }, - { - name: "application-name", - content: "Mentions", - }, - ...seo({ - title: "Mentions — Monitor keywords, generate replies, publish fast", - description: - "Track brand and product mentions, curate an inbox, generate helpful replies with AI, and publish to Reddit in one flow.", - }), - ], - links: [ - { - rel: "alternate", - type: "application/rss+xml", - href: "/blog/rss.xml", - title: "Blog RSS", - }, - { rel: "stylesheet", href: appCss }, - { - rel: "apple-touch-icon", - sizes: "180x180", - href: "/apple-touch-icon.png", - }, - { - rel: "icon", - type: "image/png", - sizes: "96x96", - href: "/favicon-96x96.png", - }, - { - rel: "icon", - type: "image/svg+xml", - sizes: "any", - href: "/favicon.svg", - }, - { - rel: "icon", - type: "image/x-icon", - href: "/favicon.ico", - }, - { rel: "manifest", href: "/site.webmanifest", color: "#fffff" }, - ], - }), - component: RootLayout, -}); - -function RootLayout() { - return ( - - - - - - - - - - - - - - - ); -} - -// reportWebVitals(typeof window!== 'undefined' ?console.log : undefined); diff --git a/apps/mentions/src/routes/_authed/dashboard.tsx b/apps/mentions/src/routes/_authed/dashboard.tsx deleted file mode 100644 index 2bb26a95d..000000000 --- a/apps/mentions/src/routes/_authed/dashboard.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { createFileRoute, redirect } from "@tanstack/react-router"; -import { apiClientRq } from "~/lib/api"; -import { getUserOrganizations } from "~/lib/auth/client"; - -export const Route = createFileRoute("/_authed/dashboard")({ - component: ProjectPicker, - beforeLoad: async ({ context }) => { - const organizations = await getUserOrganizations(); - if (organizations.length === 0) { - throw redirect({ - to: "/onboarding", - }); - } - // we need to check org first otherwise the following call will throw a 404 - const projects = await context.queryClient.fetchQuery( - apiClientRq.projects.list.queryOptions({ - input: { - limit: 1, - }, - }), - ); - if (projects.data.length === 0) { - throw redirect({ - to: "/onboarding", - }); - } - }, -}); - -function ProjectPicker() { - return null; -} diff --git a/apps/mentions/src/routes/_authed/onboarding/-components/0-welcome.tsx b/apps/mentions/src/routes/_authed/onboarding/-components/0-welcome.tsx deleted file mode 100644 index 53f3d50ed..000000000 --- a/apps/mentions/src/routes/_authed/onboarding/-components/0-welcome.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { Sparkles } from "@rectangular-labs/ui/components/icon"; -import { Button } from "@rectangular-labs/ui/components/ui/button"; -import { - Card, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "@rectangular-labs/ui/components/ui/card"; -import { OnboardingSteps } from "../-lib/steps"; - -export function OnboardingWelcome({ - description, - title, -}: { - title: string; - description: string; -}) { - const matcher = OnboardingSteps.useStepper(); - - return ( - - - - - {title} - - {description} - - - - - - ); -} diff --git a/apps/mentions/src/routes/_authed/onboarding/-components/1-user-background.tsx b/apps/mentions/src/routes/_authed/onboarding/-components/1-user-background.tsx deleted file mode 100644 index 0a8066bfc..000000000 --- a/apps/mentions/src/routes/_authed/onboarding/-components/1-user-background.tsx +++ /dev/null @@ -1,257 +0,0 @@ -import { Button } from "@rectangular-labs/ui/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "@rectangular-labs/ui/components/ui/card"; -import { - arktypeResolver, - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, - useForm, -} from "@rectangular-labs/ui/components/ui/form"; -import { Input } from "@rectangular-labs/ui/components/ui/input"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@rectangular-labs/ui/components/ui/select"; -import { useMutation } from "@tanstack/react-query"; -import { type } from "arktype"; -import { authClient } from "~/lib/auth/client"; -import { OnboardingSteps } from "../-lib/steps"; - -const sourceOptions = [ - { value: "x", label: "X" }, - { value: "reddit", label: "Reddit" }, - { value: "hacker-news", label: "Hacker News" }, - { value: "other", label: "Other" }, -]; -const goalOptions = [ - { value: "sentiment", label: "Figuring out customer sentiment" }, - { value: "monitor", label: "Monitor conversation around a topic" }, - { value: "problems", label: "Find problems" }, - { value: "other", label: "Other" }, -]; - -const backgroundSchema = type({ - source: type.string - .atLeastLength(1) - .configure({ message: () => "Source must not be blank" }), - "otherSource?": "string", - goal: type.string - .atLeastLength(1) - .configure({ message: () => "Goal must not be blank" }), - "otherGoal?": "string", -}).narrow((data, ctx) => { - if (data.source === "other" && !data.otherSource) { - return ctx.reject({ - path: ["otherSource"], - message: "Source must not be blank", - }); - } - if (data.goal === "other" && !data.otherGoal) { - return ctx.reject({ - path: ["otherGoal"], - message: "Goal must not be blank", - }); - } - return true; -}); - -export function OnboardingUserBackground({ - description, - title, -}: { - description: string; - title: string; -}) { - const matcher = OnboardingSteps.useStepper(); - const defaultValues = - matcher.getMetadata>( - "user-background", - ) ?? {}; - - const form = useForm({ - resolver: arktypeResolver(backgroundSchema), - defaultValues: { - source: defaultValues.source ?? "", - goal: defaultValues.goal ?? "", - otherGoal: defaultValues.otherGoal ?? "", - otherSource: defaultValues.otherSource ?? "", - }, - }); - const isOtherSource = form.watch("source") === "other"; - const isOtherGoal = form.watch("goal") === "other"; - - const { mutate: updateUser, isPending } = useMutation({ - mutationFn: async (values: typeof backgroundSchema.infer) => { - const source = - values.source === "other" ? values.otherSource : values.source; - const goal = values.goal === "other" ? values.otherGoal : values.goal; - const updatedUser = await authClient.updateUser({ - source, - goal, - }); - if (updatedUser.error) { - throw new Error(updatedUser.error.message); - } - return values; - }, - onSuccess: (data) => { - matcher.setMetadata("user-background", data); - matcher.next(); - }, - }); - - const handleSubmit = (values: typeof backgroundSchema.infer) => { - updateUser(values); - }; - - return ( -
- - - {title} - {description} - -
- - - ( - - Where did you first hear about us? - - - - - - - )} - /> - - {isOtherSource && ( - ( - - Other Source - - - - - - - )} - /> - )} - - ( - - - Where would you want Mentions to help with the most? - - - - - - - )} - /> - - {isOtherGoal && ( - ( - - Other Goal - - - - - - )} - /> - )} - - {form.formState.errors.root && ( - {form.formState.errors.root.message} - )} - - -
- - -
-
-
- -
-
- ); -} diff --git a/apps/mentions/src/routes/_authed/onboarding/-components/3-understanding-company.tsx b/apps/mentions/src/routes/_authed/onboarding/-components/3-understanding-company.tsx deleted file mode 100644 index 9e1b5f80c..000000000 --- a/apps/mentions/src/routes/_authed/onboarding/-components/3-understanding-company.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import { Check, Spinner } from "@rectangular-labs/ui/components/icon"; -import { Button } from "@rectangular-labs/ui/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "@rectangular-labs/ui/components/ui/card"; -import { useQuery } from "@tanstack/react-query"; -import { apiClientRq } from "~/lib/api"; -import { OnboardingSteps } from "../-lib/steps"; - -const steps = [ - { - id: "background", - label: "Understanding site background", - }, - { id: "customer", label: "Constructing ideal customer profile" }, - { id: "tone", label: "Figuring out the best tone and response" }, -] as const; -type StepIds = (typeof steps)[number]["id"]; - -export function OnboardingUnderstandingCompany({ - description, - title, -}: { - title: string; - description: string; -}) { - const matcher = OnboardingSteps.useStepper(); - const { crawlId } = matcher.getMetadata<{ - crawlId: string; - }>("website-info"); - - const { data: companyBackground, error: getStatusError } = useQuery( - apiClientRq.companyBackground.getCrawlStatus.queryOptions({ - enabled: !!crawlId, - input: { - id: crawlId ?? "", - }, - refetchInterval: 3_000, // every 3 seconds - }), - ); - - if (getStatusError) { - return ( -
Something went wrong getting status. Trying again in 5 seconds.
- ); - } - - const status: Record = { - background: companyBackground?.data?.description ? "done" : "doing", - customer: companyBackground?.data?.idealCustomer - ? "done" - : companyBackground?.data?.description - ? "doing" - : "pending", - tone: companyBackground?.data?.responseTone - ? "done" - : companyBackground?.data?.idealCustomer - ? "doing" - : "pending", - }; - - return ( -
- - - {title} - {description} - - - {steps.map((step) => { - return ( -
- {status[step.id] === "pending" && ( -
- )} - {status[step.id] === "doing" && ( - - )} - {status[step.id] === "done" && ( - - )} -
{step.label}
-
- ); - })} - - -
- - -
-
- -
- ); -} diff --git a/apps/mentions/src/routes/_authed/onboarding/-components/5-review-project.tsx b/apps/mentions/src/routes/_authed/onboarding/-components/5-review-project.tsx deleted file mode 100644 index 5a63de6a3..000000000 --- a/apps/mentions/src/routes/_authed/onboarding/-components/5-review-project.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import { Button } from "@rectangular-labs/ui/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "@rectangular-labs/ui/components/ui/card"; -import { - arktypeResolver, - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, - useForm, -} from "@rectangular-labs/ui/components/ui/form"; -import { Input } from "@rectangular-labs/ui/components/ui/input"; -import { type } from "arktype"; -import { authClient } from "~/lib/auth/client"; -import { OnboardingSteps } from "../-lib/steps"; - -const schema = type({ - name: "string.alphanumeric", - description: "string", - targetAudience: "string", - suggestedKeywords: "string[]", - responseTone: "string", -}); -export function OnboardingReviewProject() { - const matcher = OnboardingSteps.useStepper(); - const form = useForm({ - resolver: arktypeResolver(schema), - }); - - const handleSubmit = async (values: typeof schema.infer) => { - const valid = await authClient.organization.checkSlug({ - slug: values.name, - }); - if (valid.error) { - form.setError("root", { - message: - valid.error.message ?? - "Something went wrong creating the organization. Please try again later", - }); - return; - } - if (!valid.data?.status) { - form.setError("name", { - message: "Organization name already taken, please choose another one!", - }); - return; - } - - const organizationResult = await authClient.organization.create({ - name: values.name, - slug: values.name, - metadata: { description: values.description }, - }); - if (organizationResult.error) { - form.setError("root", { - message: - organizationResult.error.message || - organizationResult.error.statusText, - }); - return; - } - matcher.next(); - }; - - return ( -
- - - {matcher.current.title} - {matcher.current.description} - -
- - - ( - - Organization Name - - - - - - You will be able to change this at anytime later on - - - - )} - /> - - ( - - - Organization Description{" "} - (optional) - - - - - - - )} - /> - - {form.formState.errors.root && ( - {form.formState.errors.root.message} - )} - - -
- - -
-
-
- -
-
- ); -} diff --git a/apps/mentions/src/routes/_authed/onboarding/-components/6-all-set.tsx b/apps/mentions/src/routes/_authed/onboarding/-components/6-all-set.tsx deleted file mode 100644 index 1c251c8ce..000000000 --- a/apps/mentions/src/routes/_authed/onboarding/-components/6-all-set.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { PartyPopper } from "@rectangular-labs/ui/components/icon"; -import { Button } from "@rectangular-labs/ui/components/ui/button"; -import { - Card, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "@rectangular-labs/ui/components/ui/card"; -import { useNavigate } from "@tanstack/react-router"; - -export function OnboardingAllSet({ - description, - title, -}: { - description: string; - title: string; -}) { - const navigate = useNavigate(); - return ( - - - - - {title} - - {description} - - - - - - ); -} diff --git a/apps/mentions/src/routes/_authed/onboarding/-components/content.tsx b/apps/mentions/src/routes/_authed/onboarding/-components/content.tsx deleted file mode 100644 index c9a9be100..000000000 --- a/apps/mentions/src/routes/_authed/onboarding/-components/content.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { AutoHeight } from "@rectangular-labs/ui/animation/auto-height"; -import { OnboardingSteps } from "../-lib/steps"; -import { OnboardingWelcome } from "./0-welcome"; -import { OnboardingUserBackground } from "./1-user-background"; -import { OnboardingCompanyBackground } from "./2-company-background"; -import { OnboardingUnderstandingCompany } from "./3-understanding-company"; -import { OnboardingReviewOrganization } from "./4-review-organization"; -import { OnboardingReviewProject } from "./5-review-project"; -import { OnboardingAllSet } from "./6-all-set"; -import { OnboardingProgress } from "./onboarding-progress"; - -export function OnboardingContent() { - const matcher = OnboardingSteps.useStepper(); - - return ( -
- - - {matcher.switch({ - welcome: (step) => ( - - ), - "user-background": (step) => ( - - ), - "website-info": (step) => ( - - ), - understanding: (step) => ( - - ), - "review-organization": () => , - "review-project": () => , - "all-set": (step) => ( - - ), - })} - -
- ); -} diff --git a/apps/mentions/src/routes/_authed/onboarding/-components/onboarding-progress.tsx b/apps/mentions/src/routes/_authed/onboarding/-components/onboarding-progress.tsx deleted file mode 100644 index e83ba35d7..000000000 --- a/apps/mentions/src/routes/_authed/onboarding/-components/onboarding-progress.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { OnboardingSteps } from "../-lib/steps"; - -export function OnboardingProgress() { - const matcher = OnboardingSteps.useStepper(); - - if (matcher.current.id === "welcome" || matcher.current.id === "all-set") { - return null; - } - const relevantSteps = matcher.all.filter( - (step) => step.id !== "welcome" && step.id !== "all-set", - ); - const totalRelevantSteps = relevantSteps.length; - const currentRelevantStepIndex = relevantSteps.findIndex( - (step) => step.id === matcher.current.id, - ); - - return ( -
-

- Step {currentRelevantStepIndex + 1} of {totalRelevantSteps} -

-
- {relevantSteps.map((step, index) => { - const isCompleted = currentRelevantStepIndex > index; - const isCurrent = currentRelevantStepIndex === index; - return ( -
- ); - })} -
-
- ); -} diff --git a/apps/mentions/src/routes/_authed/onboarding/-lib/steps.ts b/apps/mentions/src/routes/_authed/onboarding/-lib/steps.ts deleted file mode 100644 index 65321f66e..000000000 --- a/apps/mentions/src/routes/_authed/onboarding/-lib/steps.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { defineStepper } from "@stepperize/react"; - -export const OnboardingSteps = defineStepper( - { - id: "welcome", - title: "Welcome", - description: "We'll set up your workspace in 4 quick steps (~3 minutes)", - }, - { - id: "user-background", - title: "Background", - description: - "Help us better understand your background to personalize your onboarding experience.", - }, - { - id: "website-info", - title: "Company", - description: - "We will visit your company's website to match your goals with what your company does.", - }, - { - id: "understanding-site", - title: "Formulating Plan", - description: - "Hang tight while we synthesize everything! Note that this may take a few minutes.", - }, - { - id: "review-organization", - title: "Review Organization", - description: - "Your organization will let you manage team members and projects.", - }, - { - id: "review-project", - title: "Review Project", - description: "Configure your first project.", - }, - { - id: "all-set", - title: "You're all set!", - description: "Let's get you to your dashboard.", - }, -); diff --git a/apps/mentions/src/routes/_authed/onboarding/index.tsx b/apps/mentions/src/routes/_authed/onboarding/index.tsx deleted file mode 100644 index c6cf1736b..000000000 --- a/apps/mentions/src/routes/_authed/onboarding/index.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { createFileRoute } from "@tanstack/react-router"; -import { getApiClient } from "~/lib/api"; -import { getUserOrganizations } from "~/lib/auth/client"; -import { OnboardingContent } from "./-components/content"; -import { OnboardingSteps } from "./-lib/steps"; - -export const Route = createFileRoute("/_authed/onboarding/")({ - loader: async () => { - const organizations = await getUserOrganizations(); - if (organizations.length === 0) { - return { organizations: organizations, existingProjects: [] }; - } - - const existingProjects = await getApiClient().projects.list({ limit: 1 }); - return { - organizations: organizations, - existingProjects: existingProjects.data, - }; - }, - component: OnboardingPage, -}); - -function OnboardingPage() { - const { existingProjects, organizations } = Route.useLoaderData(); - - return ( - - - - ); -} diff --git a/apps/mentions/src/routes/_authed/organizations.tsx b/apps/mentions/src/routes/_authed/organizations.tsx deleted file mode 100644 index 58ee614e3..000000000 --- a/apps/mentions/src/routes/_authed/organizations.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { OrganizationSettingCard } from "@rectangular-labs/auth/components/organization/organization-setting-card"; -import { OrganizationSwitcher } from "@rectangular-labs/auth/components/organization/organization-switcher"; -import { - Card, - CardContent, - CardHeader, - CardTitle, -} from "@rectangular-labs/ui/components/ui/card"; -import { Separator } from "@rectangular-labs/ui/components/ui/separator"; -import { createFileRoute } from "@tanstack/react-router"; - -export const Route = createFileRoute("/_authed/organizations")({ - component: OrganizationsPage, -}); - -function OrganizationsPage() { - return ( -
-
-

Organizations

- window.location.reload()} /> -
- - - - Create a new organization - - - window.location.reload()} /> - - -
- ); -} diff --git a/apps/mentions/src/routes/_authed/route.tsx b/apps/mentions/src/routes/_authed/route.tsx deleted file mode 100644 index 2613da8d6..000000000 --- a/apps/mentions/src/routes/_authed/route.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { createFileRoute, Outlet, redirect } from "@tanstack/react-router"; -import { getCurrentSession } from "~/lib/auth/client"; - -export const Route = createFileRoute("/_authed")({ - beforeLoad: async ({ location }) => { - const session = await getCurrentSession(); - if (!session) { - throw redirect({ - to: "/login", - search: { - next: `${location.pathname}${location.searchStr}`, - }, - }); - } - }, - component: AuthedLayout, -}); - -function AuthedLayout() { - return ; -} diff --git a/apps/mentions/src/routes/_marketing/-components/cta.tsx b/apps/mentions/src/routes/_marketing/-components/cta.tsx deleted file mode 100644 index e31ef3237..000000000 --- a/apps/mentions/src/routes/_marketing/-components/cta.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { Button } from "@rectangular-labs/ui/components/ui/button"; -import { Section } from "@rectangular-labs/ui/components/ui/section"; -import { Link } from "@tanstack/react-router"; - -export function CTA() { - return ( -
-
-

- Get your first leads this week -

-

- Don't waste hours monitoring social feeds. Let our AI find the - best conversations and draft replies for you. -

-
- - -
-
-
- ); -} diff --git a/apps/mentions/src/routes/_marketing/-components/faq.tsx b/apps/mentions/src/routes/_marketing/-components/faq.tsx deleted file mode 100644 index f8a4f9e5a..000000000 --- a/apps/mentions/src/routes/_marketing/-components/faq.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { PhoneCall } from "@rectangular-labs/ui/components/icon"; -import { - Accordion, - AccordionContent, - AccordionItem, - AccordionTrigger, -} from "@rectangular-labs/ui/components/ui/accordion"; -import { Badge } from "@rectangular-labs/ui/components/ui/badge"; -import { Button } from "@rectangular-labs/ui/components/ui/button"; -import { Section } from "@rectangular-labs/ui/components/ui/section"; - -const faqs = [ - { id: "free-trial", q: "Is there a free trial?", a: "Yes, try it free." }, - { - id: "cancel-anytime", - q: "Can I cancel my subscription anytime?", - a: "You can cancel any time from the dashboard.", - }, - { - id: "who-posts", - q: "What accounts send the replies?", - a: "We suggest replies. You post them from your own accounts.", - }, - { - id: "results", - q: "How long to see results?", - a: "Most users see traction within 1-2 weeks.", - }, - { id: "projects", q: "How many projects can I run?", a: "Depends on plan." }, - { - id: "keywords", - q: "How many keywords can I track?", - a: "Depends on plan.", - }, - { - id: "support", - q: "Do you offer support?", - a: "24/7 support on paid plans.", - }, - { id: "billing", q: "Do you bill yearly?", a: "Yes, with discounts." }, -]; - -export const FAQ = () => ( -
-
-
-
-
-
- FAQ -
-
-

- Questions about keyword tracking & replying -

-

- We help you discover high-intent conversations across socials - and forums where your product can add value—without spamming. -

-
-
- -
-
-
- - {faqs.map((item) => ( - - {item.q} - {item.a} - - ))} - -
-
-
-); diff --git a/apps/mentions/src/routes/_marketing/-components/footer.tsx b/apps/mentions/src/routes/_marketing/-components/footer.tsx deleted file mode 100644 index 87b241e2b..000000000 --- a/apps/mentions/src/routes/_marketing/-components/footer.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { Section } from "@rectangular-labs/ui/components/ui/section"; - -export function Footer() { - const links = [ - { title: "How it works", href: "#how-it-works" }, - { title: "Pricing", href: "#pricing" }, - { title: "FAQ", href: "#faq" }, - { title: "Open Source", href: "https://github.com/rectangular-labs/" }, - ]; - return ( -
-
-
- {links.map((link) => ( - - {link.title} - - ))} -
-

- © {new Date().getFullYear()} Rectangular Labs — Keyword mention - tracking & AI reply assistant. -

-
-
- ); -} diff --git a/apps/mentions/src/routes/_marketing/-components/header.tsx b/apps/mentions/src/routes/_marketing/-components/header.tsx deleted file mode 100644 index 35a4d924d..000000000 --- a/apps/mentions/src/routes/_marketing/-components/header.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { - GitHubIcon, - Logo, - Menu, - X, -} from "@rectangular-labs/ui/components/icon"; -import { ThemeToggle } from "@rectangular-labs/ui/components/theme-provider"; -import { Button } from "@rectangular-labs/ui/components/ui/button"; -import { useIsMobile } from "@rectangular-labs/ui/hooks/use-mobile"; -import { Link } from "@tanstack/react-router"; -import { AnimatePresence, motion } from "motion/react"; -import { useEffect, useState } from "react"; - -const menuItems = [ - { name: "How it works", href: "#how-it-works" }, - { name: "Pricing", href: "#pricing" }, - { name: "FAQ", href: "#faq" }, - { name: "Login", href: "/login" }, -]; -export function Header() { - const isMobile = useIsMobile(); - const [menuState, setMenuState] = useState(false); - useEffect(() => { - setMenuState(!isMobile); - }, [isMobile]); - - return ( -
- -
- ); -} diff --git a/apps/mentions/src/routes/_marketing/-components/hero.tsx b/apps/mentions/src/routes/_marketing/-components/hero.tsx deleted file mode 100644 index 33d893d43..000000000 --- a/apps/mentions/src/routes/_marketing/-components/hero.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { MoveRight, PhoneCall } from "@rectangular-labs/ui/components/icon"; -import { Badge } from "@rectangular-labs/ui/components/ui/badge"; -import { Button } from "@rectangular-labs/ui/components/ui/button"; -import { Section } from "@rectangular-labs/ui/components/ui/section"; - -export const Hero = () => ( -
-
-
-
-
- We're live! -
-
-

- AI that finds you the best places to plug your product -

-

- Add your keywords once. We monitor socials and forums 24/7, - surface high-intent conversations, and draft helpful replies that - authentically mention your product. -

-
-
- - -
-
-
-
-
-
-); diff --git a/apps/mentions/src/routes/_marketing/-components/pricing.tsx b/apps/mentions/src/routes/_marketing/-components/pricing.tsx deleted file mode 100644 index f641b5c87..000000000 --- a/apps/mentions/src/routes/_marketing/-components/pricing.tsx +++ /dev/null @@ -1,306 +0,0 @@ -"use client"; -import { - ArrowRight, - Check, - Shield, - Sparkles, - Star, - Zap, -} from "@rectangular-labs/ui/components/icon"; -import { Badge } from "@rectangular-labs/ui/components/ui/badge"; -import { Button } from "@rectangular-labs/ui/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "@rectangular-labs/ui/components/ui/card"; -import { Section } from "@rectangular-labs/ui/components/ui/section"; -import { - Tabs, - TabsList, - TabsTrigger, -} from "@rectangular-labs/ui/components/ui/tabs"; -import { cn } from "@rectangular-labs/ui/utils/cn"; -import { motion } from "motion/react"; -import { useState } from "react"; - -const plans = [ - { - id: "hobby", - name: "Hobby", - icon: Star, - price: { - monthly: "Free forever", - yearly: "Free forever", - }, - description: - "The perfect starting place for your web app or personal project.", - features: [ - "50 API calls / month", - "60 second checks", - "Single-user account", - "5 monitors", - "Basic email support", - ], - cta: "Get started for free", - }, - { - id: "pro", - name: "Pro", - icon: Zap, - price: { - monthly: 90, - yearly: 75, - }, - description: "Everything you need to build and scale your business.", - features: [ - "Unlimited API calls", - "30 second checks", - "Multi-user account", - "10 monitors", - "Priority email support", - ], - cta: "Subscribe to Pro", - popular: true, - }, - { - id: "enterprise", - name: "Enterprise", - icon: Shield, - price: { - monthly: "Get in touch for pricing", - yearly: "Get in touch for pricing", - }, - description: "Critical security, performance, observability and support.", - features: [ - "You can DDOS our API.", - "Nano-second checks.", - "Invite your extended family.", - "Unlimited monitors.", - "We'll sit on your desk.", - ], - cta: "Contact us", - }, -]; - -export default function Pricing() { - const [frequency, setFrequency] = useState("monthly"); - const currencyFormatter = new Intl.NumberFormat("en-US", { - style: "currency", - currency: "USD", - maximumFractionDigits: 0, - }); - - return ( -
-
-
- - - Simple, transparent pricing - - - Pick the perfect plan for your needs - - - Pricing that scales with your business. No hidden fees, no - surprises. - -
- -
- - - - Monthly - - - Yearly - - 20% off - - - - -
- -
- {plans.map((plan, index) => ( - - - {plan.popular && ( -
- - - Popular - -
- )} - -
-
- -
- - {plan.name} - -
- -

{plan.description}

-
- {typeof plan.price[ - frequency as keyof typeof plan.price - ] === "number" ? ( -
- - {currencyFormatter.format( - plan.price[ - frequency as keyof typeof plan.price - ] as number, - )} - - - /month, billed {frequency} - -
- ) : ( - - {plan.price[frequency as keyof typeof plan.price]} - - )} -
-
-
- - {plan.features.map((feature) => ( - -
- -
- - {feature} - -
- ))} -
- - - - - {/* Subtle gradient effects */} - {plan.popular ? ( - <> -
-
- - ) : ( -
- )} - - - ))} -
-
-
- ); -} diff --git a/apps/mentions/src/routes/_marketing/-components/stats.tsx b/apps/mentions/src/routes/_marketing/-components/stats.tsx deleted file mode 100644 index 994ee438d..000000000 --- a/apps/mentions/src/routes/_marketing/-components/stats.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { - ArrowRight, - MoveDownLeft, - MoveUpRight, -} from "@rectangular-labs/ui/components/icon"; -import { Badge } from "@rectangular-labs/ui/components/ui/badge"; -import { Section } from "@rectangular-labs/ui/components/ui/section"; - -export const Stats = () => ( -
-
-
-
-
- How it Works -
-
-

- Choose keywords - - We find perfect posts - - - You review drafts - -

-

- While you do more important things, we monitor the web 24/7, pick - high-quality, relevant posts, and write helpful replies that - mention your product when it genuinely fits. -

-
-
-
-
-
- -

- 30–60 - - hrs saved/mo - -

-

- Per project compared to manual monitoring -

-
-
- -

- 1–2 - - weeks - -

-

- Typical time to first results -

-
-
- -

- 24/7 - - coverage - -

-

- We watch so you don’t have to -

-
-
- -

- 0 - - spam - -

-

- Helpful replies only, tailored to the post -

-
-
-
-
-
-
-); diff --git a/apps/mentions/src/routes/_marketing/blog/$.tsx b/apps/mentions/src/routes/_marketing/blog/$.tsx deleted file mode 100644 index dc8889f11..000000000 --- a/apps/mentions/src/routes/_marketing/blog/$.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { blogSource } from "@rectangular-labs/content"; -import { getPostsOverview } from "@rectangular-labs/content/get-posts-overview"; -import { BlogPost } from "@rectangular-labs/content/ui/blog-post"; -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { createServerFn } from "@tanstack/react-start"; - -const getBlogData = createServerFn({ method: "GET" }) - .validator((slugs: string[]) => slugs) - .handler(async ({ data: slugs }) => { - const page = blogSource.getPage(slugs); - if (!page) throw notFound(); - return { - tree: blogSource.pageTree as object, - data: page.data, - postsOverview: await getPostsOverview(), - }; - }); - -export const Route = createFileRoute("/_marketing/blog/$")({ - component: Page, - loader: async ({ params }) => { - const data = await getBlogData({ data: params._splat?.split("/") ?? [] }); - return data; - }, -}); - -function Page() { - const { data, postsOverview, tree } = Route.useLoaderData(); - return ; -} diff --git a/apps/mentions/src/routes/_marketing/blog/index.tsx b/apps/mentions/src/routes/_marketing/blog/index.tsx deleted file mode 100644 index b46743636..000000000 --- a/apps/mentions/src/routes/_marketing/blog/index.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { getPostsOverview } from "@rectangular-labs/content/get-posts-overview"; -import { BlogSection } from "@rectangular-labs/content/ui/blog-section"; -import { Section } from "@rectangular-labs/ui/components/ui/section"; -import { createFileRoute } from "@tanstack/react-router"; -import { createServerFn } from "@tanstack/react-start"; - -const getBlogTree = createServerFn({ method: "GET" }).handler(() => - getPostsOverview(), -); - -export const Route = createFileRoute("/_marketing/blog/")({ - component: Page, - loader: async () => { - const postOverview = await getBlogTree(); - return { postOverview }; - }, -}); - -function Page() { - const { postOverview } = Route.useLoaderData(); - - if (postOverview.length === 0) { - return ( -
-

No posts found.

-
- ); - } - - return ; -} diff --git a/apps/mentions/src/routes/_marketing/blog/route.tsx b/apps/mentions/src/routes/_marketing/blog/route.tsx deleted file mode 100644 index eefec07fd..000000000 --- a/apps/mentions/src/routes/_marketing/blog/route.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { ContentProvider } from "@rectangular-labs/content/ui/content-provider"; -import { createFileRoute, Outlet } from "@tanstack/react-router"; - -export const Route = createFileRoute("/_marketing/blog")({ - component: Layout, -}); - -function Layout() { - return ( - - - - ); -} diff --git a/apps/mentions/src/routes/_marketing/blog/rss[.]xml.ts b/apps/mentions/src/routes/_marketing/blog/rss[.]xml.ts deleted file mode 100644 index ece6d0f50..000000000 --- a/apps/mentions/src/routes/_marketing/blog/rss[.]xml.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { getBlogRSS } from "@rectangular-labs/content/rss"; -import { createServerFileRoute } from "@tanstack/react-start/server"; -import { serverEnv } from "~/lib/env"; - -function handle() { - const baseUrl = serverEnv().VITE_MENTIONS_URL; - const xml = getBlogRSS(baseUrl); - return new Response(xml); -} - -export const ServerRoute = createServerFileRoute( - "/_marketing/blog/rss.xml", -).methods({ - GET: handle, -}); diff --git a/apps/mentions/src/routes/_marketing/index.tsx b/apps/mentions/src/routes/_marketing/index.tsx deleted file mode 100644 index 00ee15b49..000000000 --- a/apps/mentions/src/routes/_marketing/index.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { createFileRoute } from "@tanstack/react-router"; -import { CTA } from "./-components/cta"; -import { FAQ } from "./-components/faq"; -import { Hero } from "./-components/hero"; -import Pricing from "./-components/pricing"; -import { Stats } from "./-components/stats"; - -export const Route = createFileRoute("/_marketing/")({ component: App }); - -function App() { - return ( -
- - - - - -
- ); -} diff --git a/apps/mentions/src/routes/_marketing/route.tsx b/apps/mentions/src/routes/_marketing/route.tsx deleted file mode 100644 index f80e5e298..000000000 --- a/apps/mentions/src/routes/_marketing/route.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { createFileRoute, Outlet } from "@tanstack/react-router"; -import { Footer } from "./-components/footer"; -import { Header } from "./-components/header"; - -export const Route = createFileRoute("/_marketing")({ - component: Layout, -}); - -function Layout() { - return ( -
-
-
- -
-
-
- ); -} diff --git a/apps/mentions/src/routes/api/$.ts b/apps/mentions/src/routes/api/$.ts deleted file mode 100644 index af10c9388..000000000 --- a/apps/mentions/src/routes/api/$.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { - createBlogSearchServer, - createDocsSearchServer, -} from "@rectangular-labs/content/search"; -import { createApiContext } from "@rectangular-labs/mentions-api/context"; -import { openAPIHandler } from "@rectangular-labs/mentions-api/server"; -import { createServerFileRoute } from "@tanstack/react-start/server"; -import { authServerHandler } from "~/lib/auth/server"; -import { serverEnv } from "~/lib/env"; - -const docsSearch = createDocsSearchServer(); -const blogSearch = createBlogSearchServer(); - -async function handle({ request }: { request: Request }) { - if (new URL(request.url).pathname.startsWith("/api/auth/")) { - return await authServerHandler.handler(request); - } - - if (new URL(request.url).pathname.startsWith("/api/docs/search")) { - if (request.method !== "GET") { - return new Response("Method not allowed", { status: 405 }); - } - return await docsSearch.GET(request); - } - if (new URL(request.url).pathname.startsWith("/api/blog/search")) { - if (request.method !== "GET") { - return new Response("Method not allowed", { status: 405 }); - } - return await blogSearch.GET(request); - } - - const env = serverEnv(); - const context = createApiContext({ - url: new URL(request.url), - reqHeaders: request.headers, - }); - - const { response } = await openAPIHandler( - `${env.VITE_MENTIONS_URL}/api`, - ).handle(request, { - prefix: "/api", - context, - }); - - return response ?? new Response("Not Found", { status: 404 }); -} - -export const ServerRoute = createServerFileRoute("/api/$").methods({ - HEAD: handle, - GET: handle, - POST: handle, - PUT: handle, - PATCH: handle, - DELETE: handle, -}); diff --git a/apps/mentions/src/routes/api/rpc.$.ts b/apps/mentions/src/routes/api/rpc.$.ts deleted file mode 100644 index 0911daf48..000000000 --- a/apps/mentions/src/routes/api/rpc.$.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { createApiContext } from "@rectangular-labs/mentions-api/context"; -import { RpcHandler } from "@rectangular-labs/mentions-api/server"; -import { createServerFileRoute } from "@tanstack/react-start/server"; - -async function handle({ request }: { request: Request }) { - const context = createApiContext({ - url: new URL(request.url), - reqHeaders: request.headers, - }); - - const { response } = await RpcHandler.handle(request, { - prefix: "/api/rpc", - context, - }); - - return response ?? new Response("Not Found", { status: 404 }); -} - -export const ServerRoute = createServerFileRoute("/api/rpc/$").methods({ - HEAD: handle, - GET: handle, - POST: handle, - PUT: handle, - PATCH: handle, - DELETE: handle, -}); diff --git a/apps/mentions/src/routes/login.tsx b/apps/mentions/src/routes/login.tsx deleted file mode 100644 index 0a4362771..000000000 --- a/apps/mentions/src/routes/login.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { AuthCard } from "@rectangular-labs/auth/components/auth/auth-card"; -import { AuthProvider } from "@rectangular-labs/auth/components/auth/auth-provider"; -import { - DiscordIcon, - GitHubIcon, - GoogleIcon, -} from "@rectangular-labs/ui/components/icon"; -import { ThemeToggle } from "@rectangular-labs/ui/components/theme-provider"; -import { createFileRoute, redirect, useNavigate } from "@tanstack/react-router"; -import { type } from "arktype"; -import { authClient, getCurrentSession } from "~/lib/auth/client"; - -export const Route = createFileRoute("/login")({ - validateSearch: type({ - "next?": "string", - }), - loaderDeps: ({ search }) => { - return { - next: search.next, - }; - }, - loader: async ({ deps }) => { - const session = await getCurrentSession(); - if (session && deps.next) { - return redirect({ to: deps.next }); - } - return; - }, - component: Login, -}); - -function Login() { - const search = Route.useSearch(); - const navigate = useNavigate(); - const normalizedSuccessCallbackURL = search.next ?? "/dashboard"; - const newUserCallbackURL = `/onboarding?next=${normalizedSuccessCallbackURL}`; - - return ( -
- - { - void navigate({ to: normalizedSuccessCallbackURL }); - }, - newUserCallbackURL, - }} - socialProviders={[ - { - provider: "google", - name: "Google", - icon: GoogleIcon, - method: "social", - }, - { - provider: "github", - name: "GitHub", - icon: GitHubIcon, - method: "social", - }, - { - provider: "discord", - name: "Discord", - icon: DiscordIcon, - method: "social", - }, - ]} - > - - -
- ); -} diff --git a/apps/mentions/src/styles.css b/apps/mentions/src/styles.css deleted file mode 100644 index 420f960bf..000000000 --- a/apps/mentions/src/styles.css +++ /dev/null @@ -1,31 +0,0 @@ -@import "@rectangular-labs/ui/styles.css"; -@import "@rectangular-labs/content/styles.css"; -@source "./**/*.{ts,tsx}"; - -:root { - --radius: 0.8rem; - --fun-pink: 336 100% 67%; /* hot pink */ - --fun-yellow: 45 100% 51%; /* sunny yellow */ - --fun-blue: 210 100% 56%; /* bright blue */ -} - -.marketing-fun-bg { - background: - radial-gradient(1200px 400px at 10% -10%, hsl(var(--fun-pink)/0.12), transparent 60%), - radial-gradient(900px 300px at 110% 10%, hsl(var(--fun-blue)/0.12), transparent 60%), - radial-gradient(800px 300px at -10% 90%, hsl(var(--fun-yellow)/0.12), transparent 60%); -} - -/* Fun accent gradient on headings */ -.fun-gradient-text { - background-image: linear-gradient(180deg, hsl(var(--fun-pink)), hsl(var(--fun-blue)) 60%, hsl(var(--fun-yellow))); - -webkit-background-clip: text; - background-clip: text; - color: transparent; -} - -/* Card subtle hover float */ -.card:hover { - transform: translateY(-2px); - transition: transform 200ms ease; -} diff --git a/apps/mentions/tsconfig.json b/apps/mentions/tsconfig.json deleted file mode 100644 index 2ca49a86e..000000000 --- a/apps/mentions/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extends": "@rectangular-labs/typescript/tsconfig.base.json", - "compilerOptions": { - "jsx": "react-jsx", - "target": "ESNext", - "module": "ESNext", - "lib": ["ES2024", "DOM", "DOM.Iterable"], - "types": ["vite/client", "./worker-configuration.d.ts"], - - /* Linting */ - "noUncheckedSideEffectImports": true, - "baseUrl": ".", - "paths": { - "~/*": ["./src/*"], - "@rectangular-labs/ui/*": ["../../packages/ui/src/*"] - } - }, - "include": ["**/*.ts", "**/*.tsx"], - "exclude": ["node_modules", "dist", ".turbo", ".cache", ".expo", ".next"] -} diff --git a/apps/mentions/vite.config.ts b/apps/mentions/vite.config.ts deleted file mode 100644 index 6be45ea6e..000000000 --- a/apps/mentions/vite.config.ts +++ /dev/null @@ -1,38 +0,0 @@ -import tailwindcss from "@tailwindcss/vite"; -import { tanstackStart } from "@tanstack/react-start/plugin/vite"; -import viteReact from "@vitejs/plugin-react"; -import { createJiti } from "jiti"; -import mkcert from "vite-plugin-mkcert"; -import viteTsConfigPaths from "vite-tsconfig-paths"; -import { defineConfig } from "vitest/config"; -import type { serverEnv } from "~/lib/env"; - -const jiti = createJiti(import.meta.url); -const env = await jiti.import("./src/lib/env"); -// parses all the required env vars (server env is a super set of client env) -(env as { serverEnv: () => ReturnType }).serverEnv(); - -const config = defineConfig({ - plugins: [ - viteTsConfigPaths({ - projects: ["./tsconfig.json"], - }), - tailwindcss(), - mkcert(), - tanstackStart({ - customViteReactPlugin: true, - target: "cloudflare-module", - }), - viteReact(), - ], - test: { - globals: true, - environment: "jsdom", - }, - server: { - proxy: {}, - port: 7070, - }, -}); - -export default config; diff --git a/apps/mentions/wrangler.jsonc b/apps/mentions/wrangler.jsonc deleted file mode 100644 index 2da64815c..000000000 --- a/apps/mentions/wrangler.jsonc +++ /dev/null @@ -1,37 +0,0 @@ -{ - "$schema": "node_modules/wrangler/config-schema.json", - "name": "rectangular-labs-mentions", - "main": ".output/server/index.mjs", - "preview_urls": false, - "compatibility_date": "2025-09-07", - "compatibility_flags": ["nodejs_compat"], - "assets": { - "directory": ".output/public" - }, - "observability": { - "enabled": true - }, - "placement": { - "mode": "smart" - }, - // mostly a workaround to avoid issues with the build and next themes - "keep_names": false, - "env": { - "production": { - "routes": [ - { - "pattern": "mentions.rectangularlabs.com", - "custom_domain": true - } - ] - }, - "{env.PULL_REQUEST}": { - "routes": [ - { - "pattern": "{env.PULL_REQUEST}.mentions.rectangularlabs.com", - "custom_domain": true - } - ] - } - } -} diff --git a/apps/seo/src/routes/_authed/onboarding/-components/2-company-background.tsx b/apps/seo/src/routes/_authed/onboarding/-components/2-company-background.tsx deleted file mode 100644 index 06d367cb3..000000000 --- a/apps/seo/src/routes/_authed/onboarding/-components/2-company-background.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import { Button } from "@rectangular-labs/ui/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "@rectangular-labs/ui/components/ui/card"; -import { - arktypeResolver, - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, - useForm, -} from "@rectangular-labs/ui/components/ui/form"; -import { Input } from "@rectangular-labs/ui/components/ui/input"; -import { useMutation } from "@tanstack/react-query"; -import { type } from "arktype"; -import { apiClientRq } from "~/lib/api"; -import { OnboardingSteps } from "../-lib/steps"; - -const backgroundSchema = type({ - url: type("string.url").configure({ message: () => "Must be a valid URL" }), -}); - -export function OnboardingCompanyBackground({ - description, - title, -}: { - description: string; - title: string; -}) { - const matcher = OnboardingSteps.useStepper(); - - const form = useForm({ - resolver: arktypeResolver(backgroundSchema), - }); - - const { mutate: crawlInfo, isPending } = useMutation( - apiClientRq.companyBackground.crawlInfo.mutationOptions({ - onSuccess: (data, { websiteUrl }) => { - matcher.setMetadata("user-company", { - websiteUrl, - crawlId: data.id, - }); - matcher.next(); - }, - onError: (error) => { - form.setError("root", { - message: error.message, - }); - }, - }), - ); - - const handleSubmit = (values: typeof backgroundSchema.infer) => { - crawlInfo({ - websiteUrl: values.url, - }); - }; - - return ( -
- - - {title} - {description} - -
- - - ( - - Website - - - - - - - )} - /> - - {form.formState.errors.root && ( - {form.formState.errors.root.message} - )} - - -
- - -
-
-
- -
-
- ); -} diff --git a/apps/mentions/src/routes/_authed/onboarding/-components/4-review-organization.tsx b/apps/seo/src/routes/_authed/onboarding/-components/2-create-organization.tsx similarity index 74% rename from apps/mentions/src/routes/_authed/onboarding/-components/4-review-organization.tsx rename to apps/seo/src/routes/_authed/onboarding/-components/2-create-organization.tsx index 6c8fd63ca..32e24ffdb 100644 --- a/apps/mentions/src/routes/_authed/onboarding/-components/4-review-organization.tsx +++ b/apps/seo/src/routes/_authed/onboarding/-components/2-create-organization.tsx @@ -24,18 +24,30 @@ import { authClient } from "~/lib/auth/client"; import { OnboardingSteps } from "../-lib/steps"; const backgroundSchema = type({ - name: "string.alphanumeric", - "description?": "string", + name: type("string").atLeastLength(1), }); -export function OnboardingReviewOrganization() { + +function toSlug(input: string) { + return ( + input + .toLowerCase() + .trim() + // replace all non-alphanumeric characters with a hyphen + .replace(/[^a-z0-9]+/g, "-") + // replace any names starting with a hyphen or ending with a hyphen + .replace(/^-+|-+$/g, "") + ); +} +export function OnboardingCreateOrganization() { const matcher = OnboardingSteps.useStepper(); const form = useForm({ resolver: arktypeResolver(backgroundSchema), }); const handleSubmit = async (values: typeof backgroundSchema.infer) => { + const slug = toSlug(values.name); const valid = await authClient.organization.checkSlug({ - slug: values.name, + slug, }); if (valid.error) { form.setError("root", { @@ -47,15 +59,14 @@ export function OnboardingReviewOrganization() { } if (!valid.data?.status) { form.setError("name", { - message: "Organization name already taken, please choose another one!", + message: "Organization name already taken, please choose another one.", }); return; } const organizationResult = await authClient.organization.create({ name: values.name, - slug: values.name, - metadata: { description: values.description }, + slug, }); if (organizationResult.error) { form.setError("root", { @@ -72,14 +83,17 @@ export function OnboardingReviewOrganization() {
- Review Organization + Set Up Organization Your organization will let you manage team members and projects.
- - + + - ( - - - Organization Description{" "} - (optional) - - - - - - - )} - /> - {form.formState.errors.root && ( {form.formState.errors.root.message} )} diff --git a/apps/mentions/src/routes/_authed/onboarding/-components/2-company-background.tsx b/apps/seo/src/routes/_authed/onboarding/-components/3-company-background.tsx similarity index 95% rename from apps/mentions/src/routes/_authed/onboarding/-components/2-company-background.tsx rename to apps/seo/src/routes/_authed/onboarding/-components/3-company-background.tsx index caeb72532..2411c3447 100644 --- a/apps/mentions/src/routes/_authed/onboarding/-components/2-company-background.tsx +++ b/apps/seo/src/routes/_authed/onboarding/-components/3-company-background.tsx @@ -40,8 +40,8 @@ export function OnboardingCompanyBackground({ resolver: arktypeResolver(backgroundSchema), }); - const { mutate: crawlInfo, isPending } = useMutation( - apiClientRq.companyBackground.crawlInfo.mutationOptions({ + const { mutate: startUnderstanding, isPending } = useMutation( + apiClientRq.companyBackground.understandSite.mutationOptions({ onSuccess: (data, { websiteUrl }) => { matcher.setMetadata("website-info", { websiteUrl, @@ -58,7 +58,7 @@ export function OnboardingCompanyBackground({ ); const handleSubmit = (values: typeof backgroundSchema.infer) => { - crawlInfo({ + startUnderstanding({ websiteUrl: values.url, }); }; diff --git a/apps/seo/src/routes/_authed/onboarding/-components/3-understanding-company.tsx b/apps/seo/src/routes/_authed/onboarding/-components/3-understanding-company.tsx deleted file mode 100644 index fd5e3eb90..000000000 --- a/apps/seo/src/routes/_authed/onboarding/-components/3-understanding-company.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import { Check, Spinner } from "@rectangular-labs/ui/components/icon"; -import { Button } from "@rectangular-labs/ui/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "@rectangular-labs/ui/components/ui/card"; -import { useQuery } from "@tanstack/react-query"; -import { apiClientRq } from "~/lib/api"; -import { OnboardingSteps } from "../-lib/steps"; - -const steps = [ - { - id: "background", - label: "Understanding site background", - }, - { id: "customer", label: "Constructing ideal customer profile" }, - { id: "tone", label: "Figuring out the best tone and response" }, -] as const; -type StepIds = (typeof steps)[number]["id"]; - -export function OnboardingUnderstandingCompany({ - description, - title, -}: { - title: string; - description: string; -}) { - const matcher = OnboardingSteps.useStepper(); - const { crawlId } = matcher.getMetadata<{ - crawlId: string; - }>("user-company"); - - const { data: companyBackground, error: getStatusError } = useQuery( - apiClientRq.companyBackground.getCrawlStatus.queryOptions({ - enabled: !!crawlId, - input: { - id: crawlId ?? "", - }, - refetchInterval: 3_000, // every 3 seconds - }), - ); - - if (getStatusError) { - return ( -
Something went wrong getting status. Trying again in 5 seconds.
- ); - } - - const status: Record = { - background: companyBackground?.data?.description ? "done" : "doing", - customer: companyBackground?.data?.idealCustomer - ? "done" - : companyBackground?.data?.description - ? "doing" - : "pending", - tone: companyBackground?.data?.responseTone - ? "done" - : companyBackground?.data?.idealCustomer - ? "doing" - : "pending", - }; - - return ( -
- - - {title} - {description} - - - {steps.map((step) => { - return ( -
- {status[step.id] === "pending" && ( -
- )} - {status[step.id] === "doing" && ( - - )} - {status[step.id] === "done" && ( - - )} -
{step.label}
-
- ); - })} - - -
- - -
-
- -
- ); -} diff --git a/apps/seo/src/routes/_authed/onboarding/-components/4-review-organization.tsx b/apps/seo/src/routes/_authed/onboarding/-components/4-review-organization.tsx deleted file mode 100644 index 6c8fd63ca..000000000 --- a/apps/seo/src/routes/_authed/onboarding/-components/4-review-organization.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import { Button } from "@rectangular-labs/ui/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "@rectangular-labs/ui/components/ui/card"; -import { - arktypeResolver, - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, - useForm, -} from "@rectangular-labs/ui/components/ui/form"; -import { Input } from "@rectangular-labs/ui/components/ui/input"; -import { type } from "arktype"; -import { authClient } from "~/lib/auth/client"; -import { OnboardingSteps } from "../-lib/steps"; - -const backgroundSchema = type({ - name: "string.alphanumeric", - "description?": "string", -}); -export function OnboardingReviewOrganization() { - const matcher = OnboardingSteps.useStepper(); - const form = useForm({ - resolver: arktypeResolver(backgroundSchema), - }); - - const handleSubmit = async (values: typeof backgroundSchema.infer) => { - const valid = await authClient.organization.checkSlug({ - slug: values.name, - }); - if (valid.error) { - form.setError("root", { - message: - valid.error.message ?? - "Something went wrong creating the organization. Please try again later", - }); - return; - } - if (!valid.data?.status) { - form.setError("name", { - message: "Organization name already taken, please choose another one!", - }); - return; - } - - const organizationResult = await authClient.organization.create({ - name: values.name, - slug: values.name, - metadata: { description: values.description }, - }); - if (organizationResult.error) { - form.setError("root", { - message: - organizationResult.error.message || - organizationResult.error.statusText, - }); - return; - } - matcher.next(); - }; - - return ( -
- - - Review Organization - - Your organization will let you manage team members and projects. - - - - - - ( - - Organization Name - - - - - - You will be able to change this at anytime later on - - - - )} - /> - - ( - - - Organization Description{" "} - (optional) - - - - - - - )} - /> - - {form.formState.errors.root && ( - {form.formState.errors.root.message} - )} - - -
- - -
-
- - -
-
- ); -} diff --git a/apps/seo/src/routes/_authed/onboarding/-components/4-understanding-company.tsx b/apps/seo/src/routes/_authed/onboarding/-components/4-understanding-company.tsx new file mode 100644 index 000000000..dc4faa986 --- /dev/null +++ b/apps/seo/src/routes/_authed/onboarding/-components/4-understanding-company.tsx @@ -0,0 +1,124 @@ +import { Button } from "@rectangular-labs/ui/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@rectangular-labs/ui/components/ui/card"; +import { Progress } from "@rectangular-labs/ui/components/ui/progress"; +import { toast } from "@rectangular-labs/ui/components/ui/sonner"; +import { useMutation, useQuery } from "@tanstack/react-query"; +import { useState } from "react"; +import { apiClientRq } from "~/lib/api"; +import { OnboardingSteps } from "../-lib/steps"; + +export function OnboardingUnderstandingCompany({ + description, + title, +}: { + title: string; + description: string; +}) { + const matcher = OnboardingSteps.useStepper(); + const { crawlId, websiteUrl } = matcher.getMetadata<{ + crawlId: string; + websiteUrl: string; + }>("website-info"); + const [currentCrawlId, setCurrentCrawlId] = useState(crawlId); + const { data: status, error: getStatusError } = useQuery( + apiClientRq.companyBackground.getUnderstandSiteStatus.queryOptions({ + refetchInterval: 5_000, + input: { + id: currentCrawlId, + }, + }), + ); + const { mutate: retry, isPending } = useMutation( + apiClientRq.companyBackground.understandSite.mutationOptions({ + onSuccess: (data) => { + setCurrentCrawlId(data.id); + toast.success("Retrying understanding site"); + }, + onError: () => { + toast.error("Failed to retry understanding site"); + }, + }), + ); + const goNext = () => { + if (!status?.websiteInfo) { + toast.error("No website info found"); + return; + } + matcher.setMetadata("website-info", { + websiteUrl, + ...status?.websiteInfo, + }); + matcher.next(); + }; + + if (getStatusError) { + return ( +
Something went wrong getting status. Trying again in 5 seconds.
+ ); + } + + const isCompleted = status?.status === "completed"; + const needsRetry = + status?.status === "failed" || status?.status === "cancelled"; + + return ( +
+ + + {title} + {description} + + +
+
+ {status?.statusMessage ?? "We are setting things up..."} +
+ +
+
+ +
+ + {!isCompleted && !needsRetry && ( + + )} + {isCompleted && ( + + )} + {needsRetry && ( + + )} +
+
+
+
+ ); +} diff --git a/apps/seo/src/routes/_authed/onboarding/-components/5-review-project.tsx b/apps/seo/src/routes/_authed/onboarding/-components/5-review-project.tsx index 5a63de6a3..5a57a2ccb 100644 --- a/apps/seo/src/routes/_authed/onboarding/-components/5-review-project.tsx +++ b/apps/seo/src/routes/_authed/onboarding/-components/5-review-project.tsx @@ -11,7 +11,6 @@ import { arktypeResolver, Form, FormControl, - FormDescription, FormField, FormItem, FormLabel, @@ -19,55 +18,49 @@ import { useForm, } from "@rectangular-labs/ui/components/ui/form"; import { Input } from "@rectangular-labs/ui/components/ui/input"; +import { Textarea } from "@rectangular-labs/ui/components/ui/textarea"; import { type } from "arktype"; -import { authClient } from "~/lib/auth/client"; import { OnboardingSteps } from "../-lib/steps"; -const schema = type({ - name: "string.alphanumeric", - description: "string", - targetAudience: "string", - suggestedKeywords: "string[]", - responseTone: "string", -}); +const formSchema = type({ + websiteUrl: type("string.url").configure({ + message: () => "Must be a valid URL", + }), +}).merge( + type({ + businessOverview: type("string").configure({ + message: () => "Business Overview is required", + }), + idealCustomer: type("string").configure({ + message: () => "Ideal Customer is required", + }), + serviceRegion: type("string").configure({ + message: () => "Service Region is required", + }), + industry: type("string").configure({ + message: () => "Industry is required", + }), + }), +); export function OnboardingReviewProject() { const matcher = OnboardingSteps.useStepper(); + + const defaultValues = + matcher.getMetadata>("understanding-site"); + const form = useForm({ - resolver: arktypeResolver(schema), + resolver: arktypeResolver(formSchema), + defaultValues: { + websiteUrl: defaultValues?.websiteUrl || "", + businessOverview: defaultValues?.businessOverview || "", + idealCustomer: defaultValues?.idealCustomer || "", + serviceRegion: defaultValues?.serviceRegion || "", + industry: defaultValues?.industry || "", + }, }); - const handleSubmit = async (values: typeof schema.infer) => { - const valid = await authClient.organization.checkSlug({ - slug: values.name, - }); - if (valid.error) { - form.setError("root", { - message: - valid.error.message ?? - "Something went wrong creating the organization. Please try again later", - }); - return; - } - if (!valid.data?.status) { - form.setError("name", { - message: "Organization name already taken, please choose another one!", - }); - return; - } - - const organizationResult = await authClient.organization.create({ - name: values.name, - slug: values.name, - metadata: { description: values.description }, - }); - if (organizationResult.error) { - form.setError("root", { - message: - organizationResult.error.message || - organizationResult.error.statusText, - }); - return; - } + const handleSubmit = (_values: typeof formSchema.infer) => { + // TODO: Persist to API once endpoint exists. For now, proceed. matcher.next(); }; @@ -79,40 +72,88 @@ export function OnboardingReviewProject() { {matcher.current.description}
- + ( + + Website URL + + + + + + )} + /> + + ( - Organization Name + Service Region - + - - - You will be able to change this at anytime later on - - + )} /> ( - - Organization Description{" "} - (optional) - + Industry + + + + )} + /> + + ( + + Business Overview + +