WIP! A BB-style forum, on the ATmosphere! We're still working... we'll be back soon when we have something to show off!
node typescript hono htmx atproto

test: add vitest infrastructure and initial test suite (97 tests)

Set up vitest as the monorepo test framework with workspace support
and turbo task integration. Added tests across all three packages:

- appview: route handler tests (health, forum, categories, topics,
posts, routing), config loading, and database schema assertions
- lexicon: contract tests validating YAML structure, lexicon IDs,
record key conventions, knownValues usage, and strongRef fields
- web: fetchApi client tests (URL construction, error handling)
and config loading tests

Tests also document two config gaps where ?? doesn't catch empty
strings, causing NaN ports and empty URLs.

https://claude.ai/code/session_01MffppURah8kTTYS3SUZu5e

authored by

Claude and committed by malpercio.dev e5647dd4 94dcc879

+1280 -3
+1
apps/appview/package.json
··· 8 8 "dev": "tsx watch --env-file=../../.env src/index.ts", 9 9 "start": "node dist/index.js", 10 10 "lint": "tsc --noEmit", 11 + "test": "vitest run", 11 12 "clean": "rm -rf dist", 12 13 "db:generate": "drizzle-kit generate", 13 14 "db:migrate": "drizzle-kit migrate"
+68
apps/appview/src/lib/__tests__/config.test.ts
··· 1 + import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; 2 + 3 + describe("loadConfig", () => { 4 + const originalEnv = { ...process.env }; 5 + 6 + beforeEach(() => { 7 + vi.resetModules(); 8 + }); 9 + 10 + afterEach(() => { 11 + process.env = { ...originalEnv }; 12 + }); 13 + 14 + async function loadConfig() { 15 + const mod = await import("../config.js"); 16 + return mod.loadConfig(); 17 + } 18 + 19 + it("returns default port 3000 when PORT is undefined", async () => { 20 + delete process.env.PORT; 21 + const config = await loadConfig(); 22 + expect(config.port).toBe(3000); 23 + }); 24 + 25 + it("parses PORT as an integer", async () => { 26 + process.env.PORT = "4000"; 27 + const config = await loadConfig(); 28 + expect(config.port).toBe(4000); 29 + expect(typeof config.port).toBe("number"); 30 + }); 31 + 32 + it("returns default PDS URL when PDS_URL is undefined", async () => { 33 + delete process.env.PDS_URL; 34 + const config = await loadConfig(); 35 + expect(config.pdsUrl).toBe("https://bsky.social"); 36 + }); 37 + 38 + it("uses provided environment variables", async () => { 39 + process.env.PORT = "5000"; 40 + process.env.FORUM_DID = "did:plc:test123"; 41 + process.env.PDS_URL = "https://my-pds.example.com"; 42 + process.env.DATABASE_URL = "postgres://localhost/testdb"; 43 + const config = await loadConfig(); 44 + expect(config.port).toBe(5000); 45 + expect(config.forumDid).toBe("did:plc:test123"); 46 + expect(config.pdsUrl).toBe("https://my-pds.example.com"); 47 + expect(config.databaseUrl).toBe("postgres://localhost/testdb"); 48 + }); 49 + 50 + it("returns empty string for forumDid when FORUM_DID is undefined", async () => { 51 + delete process.env.FORUM_DID; 52 + const config = await loadConfig(); 53 + expect(config.forumDid).toBe(""); 54 + }); 55 + 56 + it("returns empty string for databaseUrl when DATABASE_URL is undefined", async () => { 57 + delete process.env.DATABASE_URL; 58 + const config = await loadConfig(); 59 + expect(config.databaseUrl).toBe(""); 60 + }); 61 + 62 + it("returns NaN for port when PORT is empty string (?? does not catch empty strings)", async () => { 63 + process.env.PORT = ""; 64 + const config = await loadConfig(); 65 + // Documents a gap: ?? only catches null/undefined, not "" 66 + expect(config.port).toBeNaN(); 67 + }); 68 + });
+34
apps/appview/src/routes/__tests__/categories.test.ts
··· 1 + import { describe, it, expect } from "vitest"; 2 + import { Hono } from "hono"; 3 + import { apiRoutes } from "../index.js"; 4 + 5 + const app = new Hono().route("/api", apiRoutes); 6 + 7 + describe("GET /api/categories", () => { 8 + it("returns 200", async () => { 9 + const res = await app.request("/api/categories"); 10 + expect(res.status).toBe(200); 11 + }); 12 + 13 + it("returns an object with a categories array", async () => { 14 + const res = await app.request("/api/categories"); 15 + const body = await res.json(); 16 + expect(body).toHaveProperty("categories"); 17 + expect(Array.isArray(body.categories)).toBe(true); 18 + }); 19 + }); 20 + 21 + describe("GET /api/categories/:id/topics", () => { 22 + it("returns 200", async () => { 23 + const res = await app.request("/api/categories/123/topics"); 24 + expect(res.status).toBe(200); 25 + }); 26 + 27 + it("echoes the category id and returns a topics array", async () => { 28 + const res = await app.request("/api/categories/42/topics"); 29 + const body = await res.json(); 30 + expect(body).toHaveProperty("categoryId", "42"); 31 + expect(body).toHaveProperty("topics"); 32 + expect(Array.isArray(body.topics)).toBe(true); 33 + }); 34 + });
+23
apps/appview/src/routes/__tests__/forum.test.ts
··· 1 + import { describe, it, expect } from "vitest"; 2 + import { Hono } from "hono"; 3 + import { apiRoutes } from "../index.js"; 4 + 5 + const app = new Hono().route("/api", apiRoutes); 6 + 7 + describe("GET /api/forum", () => { 8 + it("returns 200", async () => { 9 + const res = await app.request("/api/forum"); 10 + expect(res.status).toBe(200); 11 + }); 12 + 13 + it("returns forum metadata with expected shape", async () => { 14 + const res = await app.request("/api/forum"); 15 + const body = await res.json(); 16 + expect(body).toHaveProperty("name"); 17 + expect(body).toHaveProperty("description"); 18 + expect(body).toHaveProperty("did"); 19 + expect(typeof body.name).toBe("string"); 20 + expect(typeof body.description).toBe("string"); 21 + expect(typeof body.did).toBe("string"); 22 + }); 23 + });
+28
apps/appview/src/routes/__tests__/health.test.ts
··· 1 + import { describe, it, expect } from "vitest"; 2 + import { Hono } from "hono"; 3 + import { apiRoutes } from "../index.js"; 4 + 5 + const app = new Hono().route("/api", apiRoutes); 6 + 7 + describe("GET /api/healthz", () => { 8 + it("returns 200 with ok status", async () => { 9 + const res = await app.request("/api/healthz"); 10 + expect(res.status).toBe(200); 11 + const body = await res.json(); 12 + expect(body).toEqual({ status: "ok", version: "0.1.0" }); 13 + }); 14 + 15 + it("returns application/json content type", async () => { 16 + const res = await app.request("/api/healthz"); 17 + expect(res.headers.get("content-type")).toContain("application/json"); 18 + }); 19 + }); 20 + 21 + describe("GET /api/healthz/ready", () => { 22 + it("returns 200 with ready status", async () => { 23 + const res = await app.request("/api/healthz/ready"); 24 + expect(res.status).toBe(200); 25 + const body = await res.json(); 26 + expect(body).toEqual({ status: "ready" }); 27 + }); 28 + });
+18
apps/appview/src/routes/__tests__/posts.test.ts
··· 1 + import { describe, it, expect } from "vitest"; 2 + import { Hono } from "hono"; 3 + import { apiRoutes } from "../index.js"; 4 + 5 + const app = new Hono().route("/api", apiRoutes); 6 + 7 + describe("POST /api/posts", () => { 8 + it("returns 501 not implemented", async () => { 9 + const res = await app.request("/api/posts", { method: "POST" }); 10 + expect(res.status).toBe(501); 11 + }); 12 + 13 + it("returns an error message", async () => { 14 + const res = await app.request("/api/posts", { method: "POST" }); 15 + const body = await res.json(); 16 + expect(body).toHaveProperty("error", "not implemented"); 17 + }); 18 + });
+25
apps/appview/src/routes/__tests__/routing.test.ts
··· 1 + import { describe, it, expect } from "vitest"; 2 + import { Hono } from "hono"; 3 + import { apiRoutes } from "../index.js"; 4 + 5 + const app = new Hono().route("/api", apiRoutes); 6 + 7 + describe("API routing", () => { 8 + it("returns 404 for unknown routes", async () => { 9 + const res = await app.request("/api/nonexistent"); 10 + expect(res.status).toBe(404); 11 + }); 12 + 13 + it("mounts all expected route prefixes", async () => { 14 + const routes = [ 15 + "/api/healthz", 16 + "/api/forum", 17 + "/api/categories", 18 + ]; 19 + 20 + for (const route of routes) { 21 + const res = await app.request(route); 22 + expect(res.status, `${route} should be reachable`).not.toBe(404); 23 + } 24 + }); 25 + });
+34
apps/appview/src/routes/__tests__/topics.test.ts
··· 1 + import { describe, it, expect } from "vitest"; 2 + import { Hono } from "hono"; 3 + import { apiRoutes } from "../index.js"; 4 + 5 + const app = new Hono().route("/api", apiRoutes); 6 + 7 + describe("GET /api/topics/:id", () => { 8 + it("returns 200", async () => { 9 + const res = await app.request("/api/topics/abc123"); 10 + expect(res.status).toBe(200); 11 + }); 12 + 13 + it("echoes the topic id and returns expected shape", async () => { 14 + const res = await app.request("/api/topics/abc123"); 15 + const body = await res.json(); 16 + expect(body).toHaveProperty("topicId", "abc123"); 17 + expect(body).toHaveProperty("post"); 18 + expect(body).toHaveProperty("replies"); 19 + expect(Array.isArray(body.replies)).toBe(true); 20 + }); 21 + }); 22 + 23 + describe("POST /api/topics", () => { 24 + it("returns 501 not implemented", async () => { 25 + const res = await app.request("/api/topics", { method: "POST" }); 26 + expect(res.status).toBe(501); 27 + }); 28 + 29 + it("returns an error message", async () => { 30 + const res = await app.request("/api/topics", { method: "POST" }); 31 + const body = await res.json(); 32 + expect(body).toHaveProperty("error", "not implemented"); 33 + }); 34 + });
+7
apps/appview/vitest.config.ts
··· 1 + import { defineConfig } from "vitest/config"; 2 + 3 + export default defineConfig({ 4 + test: { 5 + environment: "node", 6 + }, 7 + });
+1
apps/web/package.json
··· 8 8 "dev": "tsx watch --env-file=../../.env src/index.ts", 9 9 "start": "node dist/index.js", 10 10 "lint": "tsc --noEmit", 11 + "test": "vitest run", 11 12 "clean": "rm -rf dist" 12 13 }, 13 14 "dependencies": {
+74
apps/web/src/lib/__tests__/api.test.ts
··· 1 + import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; 2 + 3 + const mockFetch = vi.fn(); 4 + 5 + describe("fetchApi", () => { 6 + beforeEach(() => { 7 + vi.stubGlobal("fetch", mockFetch); 8 + vi.stubEnv("APPVIEW_URL", "http://localhost:3000"); 9 + vi.resetModules(); 10 + }); 11 + 12 + afterEach(() => { 13 + vi.unstubAllGlobals(); 14 + vi.unstubAllEnvs(); 15 + mockFetch.mockReset(); 16 + }); 17 + 18 + async function loadFetchApi() { 19 + const mod = await import("../api.js"); 20 + return mod.fetchApi; 21 + } 22 + 23 + it("calls the correct URL", async () => { 24 + mockFetch.mockResolvedValueOnce({ 25 + ok: true, 26 + json: () => Promise.resolve({ data: "test" }), 27 + }); 28 + 29 + const fetchApi = await loadFetchApi(); 30 + await fetchApi("/categories"); 31 + 32 + expect(mockFetch).toHaveBeenCalledOnce(); 33 + const calledUrl = mockFetch.mock.calls[0][0]; 34 + expect(calledUrl).toBe("http://localhost:3000/api/categories"); 35 + }); 36 + 37 + it("returns parsed JSON on success", async () => { 38 + const expected = { categories: [{ id: 1, name: "General" }] }; 39 + mockFetch.mockResolvedValueOnce({ 40 + ok: true, 41 + json: () => Promise.resolve(expected), 42 + }); 43 + 44 + const fetchApi = await loadFetchApi(); 45 + const result = await fetchApi("/categories"); 46 + expect(result).toEqual(expected); 47 + }); 48 + 49 + it("throws on non-ok response", async () => { 50 + mockFetch.mockResolvedValueOnce({ 51 + ok: false, 52 + status: 500, 53 + statusText: "Internal Server Error", 54 + }); 55 + 56 + const fetchApi = await loadFetchApi(); 57 + await expect(fetchApi("/fail")).rejects.toThrow( 58 + "AppView API error: 500 Internal Server Error" 59 + ); 60 + }); 61 + 62 + it("throws on 404 response", async () => { 63 + mockFetch.mockResolvedValueOnce({ 64 + ok: false, 65 + status: 404, 66 + statusText: "Not Found", 67 + }); 68 + 69 + const fetchApi = await loadFetchApi(); 70 + await expect(fetchApi("/missing")).rejects.toThrow( 71 + "AppView API error: 404 Not Found" 72 + ); 73 + }); 74 + });
+59
apps/web/src/lib/__tests__/config.test.ts
··· 1 + import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; 2 + 3 + describe("loadConfig", () => { 4 + const originalEnv = { ...process.env }; 5 + 6 + beforeEach(() => { 7 + vi.resetModules(); 8 + }); 9 + 10 + afterEach(() => { 11 + process.env = { ...originalEnv }; 12 + }); 13 + 14 + async function loadConfig() { 15 + const mod = await import("../config.js"); 16 + return mod.loadConfig(); 17 + } 18 + 19 + it("returns default port 3001 when PORT is undefined", async () => { 20 + delete process.env.PORT; 21 + const config = await loadConfig(); 22 + expect(config.port).toBe(3001); 23 + }); 24 + 25 + it("parses PORT as an integer", async () => { 26 + process.env.PORT = "8080"; 27 + const config = await loadConfig(); 28 + expect(config.port).toBe(8080); 29 + expect(typeof config.port).toBe("number"); 30 + }); 31 + 32 + it("returns default appview URL when APPVIEW_URL is undefined", async () => { 33 + delete process.env.APPVIEW_URL; 34 + const config = await loadConfig(); 35 + expect(config.appviewUrl).toBe("http://localhost:3000"); 36 + }); 37 + 38 + it("uses provided environment variables", async () => { 39 + process.env.PORT = "9000"; 40 + process.env.APPVIEW_URL = "https://api.atbb.space"; 41 + const config = await loadConfig(); 42 + expect(config.port).toBe(9000); 43 + expect(config.appviewUrl).toBe("https://api.atbb.space"); 44 + }); 45 + 46 + it("returns NaN for port when PORT is empty string (?? does not catch empty strings)", async () => { 47 + process.env.PORT = ""; 48 + const config = await loadConfig(); 49 + // Documents a gap: ?? only catches null/undefined, not "" 50 + expect(config.port).toBeNaN(); 51 + }); 52 + 53 + it("returns empty string for appviewUrl when APPVIEW_URL is empty string", async () => { 54 + process.env.APPVIEW_URL = ""; 55 + const config = await loadConfig(); 56 + // Documents a gap: ?? only catches null/undefined, not "" 57 + expect(config.appviewUrl).toBe(""); 58 + }); 59 + });
+7
apps/web/vitest.config.ts
··· 1 + import { defineConfig } from "vitest/config"; 2 + 3 + export default defineConfig({ 4 + test: { 5 + environment: "node", 6 + }, 7 + });
+4 -2
package.json
··· 6 6 "build": "turbo run build", 7 7 "dev": "turbo run dev", 8 8 "lint": "turbo run lint", 9 - "clean": "turbo run clean" 9 + "clean": "turbo run clean", 10 + "test": "turbo run test" 10 11 }, 11 12 "devDependencies": { 12 13 "turbo": "^2.4.0", 13 - "typescript": "^5.7.0" 14 + "typescript": "^5.7.0", 15 + "vitest": "^4.0.18" 14 16 } 15 17 }
+2 -1
packages/db/package.json
··· 18 18 "scripts": { 19 19 "build": "tsc", 20 20 "lint": "tsc --noEmit", 21 - "clean": "rm -rf dist" 21 + "clean": "rm -rf dist", 22 + "test": "vitest run" 22 23 }, 23 24 "dependencies": { 24 25 "drizzle-orm": "^0.45.1",
+166
packages/db/src/__tests__/schema.test.ts
··· 1 + import { describe, it, expect } from "vitest"; 2 + import { getTableName, getTableColumns } from "drizzle-orm"; 3 + import { 4 + forums, 5 + categories, 6 + users, 7 + memberships, 8 + posts, 9 + modActions, 10 + } from "../schema.js"; 11 + 12 + describe("database schema", () => { 13 + describe("forums table", () => { 14 + it("has the correct table name", () => { 15 + expect(getTableName(forums)).toBe("forums"); 16 + }); 17 + 18 + it("has expected columns", () => { 19 + const cols = getTableColumns(forums); 20 + expect(cols).toHaveProperty("id"); 21 + expect(cols).toHaveProperty("did"); 22 + expect(cols).toHaveProperty("rkey"); 23 + expect(cols).toHaveProperty("cid"); 24 + expect(cols).toHaveProperty("name"); 25 + expect(cols).toHaveProperty("description"); 26 + expect(cols).toHaveProperty("indexedAt"); 27 + }); 28 + 29 + it("has did and rkey as not-null", () => { 30 + const cols = getTableColumns(forums); 31 + expect(cols.did.notNull).toBe(true); 32 + expect(cols.rkey.notNull).toBe(true); 33 + }); 34 + }); 35 + 36 + describe("categories table", () => { 37 + it("has the correct table name", () => { 38 + expect(getTableName(categories)).toBe("categories"); 39 + }); 40 + 41 + it("has expected columns", () => { 42 + const cols = getTableColumns(categories); 43 + expect(cols).toHaveProperty("id"); 44 + expect(cols).toHaveProperty("did"); 45 + expect(cols).toHaveProperty("rkey"); 46 + expect(cols).toHaveProperty("name"); 47 + expect(cols).toHaveProperty("slug"); 48 + expect(cols).toHaveProperty("sortOrder"); 49 + expect(cols).toHaveProperty("forumId"); 50 + expect(cols).toHaveProperty("createdAt"); 51 + expect(cols).toHaveProperty("indexedAt"); 52 + }); 53 + }); 54 + 55 + describe("users table", () => { 56 + it("has the correct table name", () => { 57 + expect(getTableName(users)).toBe("users"); 58 + }); 59 + 60 + it("uses did as primary key", () => { 61 + const cols = getTableColumns(users); 62 + expect(cols.did.primary).toBe(true); 63 + }); 64 + 65 + it("has handle as optional", () => { 66 + const cols = getTableColumns(users); 67 + expect(cols.handle.notNull).toBe(false); 68 + }); 69 + }); 70 + 71 + describe("memberships table", () => { 72 + it("has the correct table name", () => { 73 + expect(getTableName(memberships)).toBe("memberships"); 74 + }); 75 + 76 + it("has expected columns", () => { 77 + const cols = getTableColumns(memberships); 78 + expect(cols).toHaveProperty("id"); 79 + expect(cols).toHaveProperty("did"); 80 + expect(cols).toHaveProperty("rkey"); 81 + expect(cols).toHaveProperty("forumId"); 82 + expect(cols).toHaveProperty("forumUri"); 83 + expect(cols).toHaveProperty("role"); 84 + expect(cols).toHaveProperty("roleUri"); 85 + expect(cols).toHaveProperty("joinedAt"); 86 + expect(cols).toHaveProperty("createdAt"); 87 + }); 88 + 89 + it("has did and forumUri as not-null", () => { 90 + const cols = getTableColumns(memberships); 91 + expect(cols.did.notNull).toBe(true); 92 + expect(cols.forumUri.notNull).toBe(true); 93 + }); 94 + }); 95 + 96 + describe("posts table", () => { 97 + it("has the correct table name", () => { 98 + expect(getTableName(posts)).toBe("posts"); 99 + }); 100 + 101 + it("has expected columns for the unified post model", () => { 102 + const cols = getTableColumns(posts); 103 + expect(cols).toHaveProperty("id"); 104 + expect(cols).toHaveProperty("did"); 105 + expect(cols).toHaveProperty("rkey"); 106 + expect(cols).toHaveProperty("text"); 107 + expect(cols).toHaveProperty("rootPostId"); 108 + expect(cols).toHaveProperty("parentPostId"); 109 + expect(cols).toHaveProperty("rootUri"); 110 + expect(cols).toHaveProperty("parentUri"); 111 + expect(cols).toHaveProperty("deleted"); 112 + expect(cols).toHaveProperty("createdAt"); 113 + }); 114 + 115 + it("has text as not-null", () => { 116 + const cols = getTableColumns(posts); 117 + expect(cols.text.notNull).toBe(true); 118 + }); 119 + 120 + it("has deleted defaulting to false", () => { 121 + const cols = getTableColumns(posts); 122 + expect(cols.deleted.notNull).toBe(true); 123 + expect(cols.deleted.hasDefault).toBe(true); 124 + }); 125 + 126 + it("has rootPostId and parentPostId as nullable (topics have no parent)", () => { 127 + const cols = getTableColumns(posts); 128 + expect(cols.rootPostId.notNull).toBe(false); 129 + expect(cols.parentPostId.notNull).toBe(false); 130 + }); 131 + }); 132 + 133 + describe("modActions table", () => { 134 + it("has the correct table name", () => { 135 + expect(getTableName(modActions)).toBe("mod_actions"); 136 + }); 137 + 138 + it("has expected columns", () => { 139 + const cols = getTableColumns(modActions); 140 + expect(cols).toHaveProperty("id"); 141 + expect(cols).toHaveProperty("did"); 142 + expect(cols).toHaveProperty("action"); 143 + expect(cols).toHaveProperty("subjectDid"); 144 + expect(cols).toHaveProperty("subjectPostUri"); 145 + expect(cols).toHaveProperty("reason"); 146 + expect(cols).toHaveProperty("createdBy"); 147 + expect(cols).toHaveProperty("expiresAt"); 148 + }); 149 + 150 + it("has action and createdBy as not-null", () => { 151 + const cols = getTableColumns(modActions); 152 + expect(cols.action.notNull).toBe(true); 153 + expect(cols.createdBy.notNull).toBe(true); 154 + }); 155 + }); 156 + 157 + describe("all tables export correctly", () => { 158 + it("exports six tables", () => { 159 + const tables = [forums, categories, users, memberships, posts, modActions]; 160 + expect(tables).toHaveLength(6); 161 + tables.forEach((table) => { 162 + expect(table).toBeDefined(); 163 + }); 164 + }); 165 + }); 166 + });
+7
packages/db/vitest.config.ts
··· 1 + import { defineConfig } from "vitest/config"; 2 + 3 + export default defineConfig({ 4 + test: { 5 + environment: "node", 6 + }, 7 + });
+98
packages/lexicon/__tests__/lexicons.test.ts
··· 1 + import { describe, it, expect } from "vitest"; 2 + import { readFileSync } from "node:fs"; 3 + import { join } from "node:path"; 4 + import { parse as parseYaml } from "yaml"; 5 + import { globSync } from "glob"; 6 + 7 + const LEXICONS_DIR = join(import.meta.dirname, "..", "lexicons"); 8 + const yamlFiles = globSync("**/*.yaml", { cwd: LEXICONS_DIR }); 9 + 10 + describe("lexicon definitions", () => { 11 + it("finds at least one lexicon file", () => { 12 + expect(yamlFiles.length).toBeGreaterThan(0); 13 + }); 14 + 15 + describe.each(yamlFiles)("%s", (file) => { 16 + const content = readFileSync(join(LEXICONS_DIR, file), "utf-8"); 17 + const parsed = parseYaml(content); 18 + 19 + it("has lexicon version 1", () => { 20 + expect(parsed).toHaveProperty("lexicon", 1); 21 + }); 22 + 23 + it("has an id field", () => { 24 + expect(parsed).toHaveProperty("id"); 25 + expect(typeof parsed.id).toBe("string"); 26 + }); 27 + 28 + it("has a defs object", () => { 29 + expect(parsed).toHaveProperty("defs"); 30 + expect(typeof parsed.defs).toBe("object"); 31 + }); 32 + 33 + it("has an id that matches the file path", () => { 34 + // space/atbb/post.yaml -> space.atbb.post 35 + const expectedId = file.replace(/\.yaml$/, "").replace(/\//g, "."); 36 + expect(parsed.id).toBe(expectedId); 37 + }); 38 + }); 39 + }); 40 + 41 + describe("lexicon uniqueness", () => { 42 + it("has no duplicate lexicon ids", () => { 43 + const ids = yamlFiles.map((file) => { 44 + const content = readFileSync(join(LEXICONS_DIR, file), "utf-8"); 45 + return parseYaml(content).id; 46 + }); 47 + const unique = new Set(ids); 48 + expect(unique.size).toBe(ids.length); 49 + }); 50 + }); 51 + 52 + describe("record key conventions", () => { 53 + const recordLexicons = yamlFiles 54 + .map((file) => { 55 + const content = readFileSync(join(LEXICONS_DIR, file), "utf-8"); 56 + return { file, parsed: parseYaml(content) }; 57 + }) 58 + .filter(({ parsed }) => parsed.defs?.main?.type === "record"); 59 + 60 + it.each(recordLexicons.map(({ file, parsed }) => [file, parsed]))( 61 + "%s has a valid record key type", 62 + (_file, parsed) => { 63 + const key = parsed.defs.main.key; 64 + expect(["tid", "literal:self"]).toContain(key); 65 + } 66 + ); 67 + }); 68 + 69 + describe("extensible fields use knownValues", () => { 70 + it("modAction.action uses knownValues, not enum", () => { 71 + const content = readFileSync( 72 + join(LEXICONS_DIR, "space/atbb/modAction.yaml"), 73 + "utf-8" 74 + ); 75 + const parsed = parseYaml(content); 76 + const actionField = 77 + parsed.defs.main.record?.properties?.action; 78 + if (actionField) { 79 + expect(actionField).not.toHaveProperty("enum"); 80 + expect(actionField).toHaveProperty("knownValues"); 81 + } 82 + }); 83 + }); 84 + 85 + describe("strongRef definitions", () => { 86 + it("strongRef has uri and cid fields", () => { 87 + const content = readFileSync( 88 + join(LEXICONS_DIR, "com/atproto/repo/strongRef.yaml"), 89 + "utf-8" 90 + ); 91 + const parsed = parseYaml(content); 92 + const mainDef = parsed.defs.main; 93 + expect(mainDef.properties).toHaveProperty("uri"); 94 + expect(mainDef.properties).toHaveProperty("cid"); 95 + expect(mainDef.required).toContain("uri"); 96 + expect(mainDef.required).toContain("cid"); 97 + }); 98 + });
+1
packages/lexicon/package.json
··· 13 13 "build": "pnpm run build:json && pnpm run build:types", 14 14 "build:json": "tsx scripts/build.ts", 15 15 "build:types": "bash -c 'shopt -s globstar && lex gen-api --yes ./dist/types ./dist/json/**/*.json'", 16 + "test": "vitest run", 16 17 "clean": "rm -rf dist" 17 18 }, 18 19 "devDependencies": {
+7
packages/lexicon/vitest.config.ts
··· 1 + import { defineConfig } from "vitest/config"; 2 + 3 + export default defineConfig({ 4 + test: { 5 + environment: "node", 6 + }, 7 + });
+605
pnpm-lock.yaml
··· 14 14 typescript: 15 15 specifier: ^5.7.0 16 16 version: 5.9.3 17 + vitest: 18 + specifier: ^4.0.18 19 + version: 4.0.18(@types/node@22.19.9)(tsx@4.21.0)(yaml@2.8.2) 17 20 18 21 apps/appview: 19 22 dependencies: ··· 631 634 resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==} 632 635 engines: {node: '>=18'} 633 636 637 + '@jridgewell/sourcemap-codec@1.5.5': 638 + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} 639 + 634 640 '@nodelib/fs.scandir@2.1.5': 635 641 resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} 636 642 engines: {node: '>= 8'} ··· 643 649 resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 644 650 engines: {node: '>= 8'} 645 651 652 + '@rollup/rollup-android-arm-eabi@4.57.1': 653 + resolution: {integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==} 654 + cpu: [arm] 655 + os: [android] 656 + 657 + '@rollup/rollup-android-arm64@4.57.1': 658 + resolution: {integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==} 659 + cpu: [arm64] 660 + os: [android] 661 + 662 + '@rollup/rollup-darwin-arm64@4.57.1': 663 + resolution: {integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==} 664 + cpu: [arm64] 665 + os: [darwin] 666 + 667 + '@rollup/rollup-darwin-x64@4.57.1': 668 + resolution: {integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==} 669 + cpu: [x64] 670 + os: [darwin] 671 + 672 + '@rollup/rollup-freebsd-arm64@4.57.1': 673 + resolution: {integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==} 674 + cpu: [arm64] 675 + os: [freebsd] 676 + 677 + '@rollup/rollup-freebsd-x64@4.57.1': 678 + resolution: {integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==} 679 + cpu: [x64] 680 + os: [freebsd] 681 + 682 + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': 683 + resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==} 684 + cpu: [arm] 685 + os: [linux] 686 + 687 + '@rollup/rollup-linux-arm-musleabihf@4.57.1': 688 + resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==} 689 + cpu: [arm] 690 + os: [linux] 691 + 692 + '@rollup/rollup-linux-arm64-gnu@4.57.1': 693 + resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==} 694 + cpu: [arm64] 695 + os: [linux] 696 + 697 + '@rollup/rollup-linux-arm64-musl@4.57.1': 698 + resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==} 699 + cpu: [arm64] 700 + os: [linux] 701 + 702 + '@rollup/rollup-linux-loong64-gnu@4.57.1': 703 + resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==} 704 + cpu: [loong64] 705 + os: [linux] 706 + 707 + '@rollup/rollup-linux-loong64-musl@4.57.1': 708 + resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} 709 + cpu: [loong64] 710 + os: [linux] 711 + 712 + '@rollup/rollup-linux-ppc64-gnu@4.57.1': 713 + resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} 714 + cpu: [ppc64] 715 + os: [linux] 716 + 717 + '@rollup/rollup-linux-ppc64-musl@4.57.1': 718 + resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==} 719 + cpu: [ppc64] 720 + os: [linux] 721 + 722 + '@rollup/rollup-linux-riscv64-gnu@4.57.1': 723 + resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==} 724 + cpu: [riscv64] 725 + os: [linux] 726 + 727 + '@rollup/rollup-linux-riscv64-musl@4.57.1': 728 + resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==} 729 + cpu: [riscv64] 730 + os: [linux] 731 + 732 + '@rollup/rollup-linux-s390x-gnu@4.57.1': 733 + resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==} 734 + cpu: [s390x] 735 + os: [linux] 736 + 737 + '@rollup/rollup-linux-x64-gnu@4.57.1': 738 + resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==} 739 + cpu: [x64] 740 + os: [linux] 741 + 742 + '@rollup/rollup-linux-x64-musl@4.57.1': 743 + resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==} 744 + cpu: [x64] 745 + os: [linux] 746 + 747 + '@rollup/rollup-openbsd-x64@4.57.1': 748 + resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==} 749 + cpu: [x64] 750 + os: [openbsd] 751 + 752 + '@rollup/rollup-openharmony-arm64@4.57.1': 753 + resolution: {integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==} 754 + cpu: [arm64] 755 + os: [openharmony] 756 + 757 + '@rollup/rollup-win32-arm64-msvc@4.57.1': 758 + resolution: {integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==} 759 + cpu: [arm64] 760 + os: [win32] 761 + 762 + '@rollup/rollup-win32-ia32-msvc@4.57.1': 763 + resolution: {integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==} 764 + cpu: [ia32] 765 + os: [win32] 766 + 767 + '@rollup/rollup-win32-x64-gnu@4.57.1': 768 + resolution: {integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==} 769 + cpu: [x64] 770 + os: [win32] 771 + 772 + '@rollup/rollup-win32-x64-msvc@4.57.1': 773 + resolution: {integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==} 774 + cpu: [x64] 775 + os: [win32] 776 + 777 + '@standard-schema/spec@1.1.0': 778 + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} 779 + 646 780 '@ts-morph/common@0.17.0': 647 781 resolution: {integrity: sha512-RMSSvSfs9kb0VzkvQ2NWobwnj7TxCA9vI/IjR9bDHqgAyVbu2T0DN4wiKVqomyDWqO7dPr/tErSfq7urQ1Q37g==} 648 782 783 + '@types/chai@5.2.3': 784 + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} 785 + 786 + '@types/deep-eql@4.0.2': 787 + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} 788 + 789 + '@types/estree@1.0.8': 790 + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} 791 + 649 792 '@types/node@22.19.9': 650 793 resolution: {integrity: sha512-PD03/U8g1F9T9MI+1OBisaIARhSzeidsUjQaf51fOxrfjeiKN9bLVO06lHuHYjxdnqLWJijJHfqXPSJri2EM2A==} 651 794 795 + '@vitest/expect@4.0.18': 796 + resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==} 797 + 798 + '@vitest/mocker@4.0.18': 799 + resolution: {integrity: sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==} 800 + peerDependencies: 801 + msw: ^2.4.9 802 + vite: ^6.0.0 || ^7.0.0-0 803 + peerDependenciesMeta: 804 + msw: 805 + optional: true 806 + vite: 807 + optional: true 808 + 809 + '@vitest/pretty-format@4.0.18': 810 + resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==} 811 + 812 + '@vitest/runner@4.0.18': 813 + resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==} 814 + 815 + '@vitest/snapshot@4.0.18': 816 + resolution: {integrity: sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==} 817 + 818 + '@vitest/spy@4.0.18': 819 + resolution: {integrity: sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==} 820 + 821 + '@vitest/utils@4.0.18': 822 + resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==} 823 + 652 824 ansi-styles@4.3.0: 653 825 resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 654 826 engines: {node: '>=8'} 655 827 828 + assertion-error@2.0.1: 829 + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} 830 + engines: {node: '>=12'} 831 + 656 832 await-lock@2.2.2: 657 833 resolution: {integrity: sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==} 658 834 ··· 668 844 669 845 buffer-from@1.1.2: 670 846 resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} 847 + 848 + chai@6.2.2: 849 + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} 850 + engines: {node: '>=18'} 671 851 672 852 chalk@4.1.2: 673 853 resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} ··· 796 976 sqlite3: 797 977 optional: true 798 978 979 + es-module-lexer@1.7.0: 980 + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} 981 + 799 982 esbuild-register@3.6.0: 800 983 resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==} 801 984 peerDependencies: ··· 815 998 resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} 816 999 engines: {node: '>=18'} 817 1000 hasBin: true 1001 + 1002 + estree-walker@3.0.3: 1003 + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} 1004 + 1005 + expect-type@1.3.0: 1006 + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} 1007 + engines: {node: '>=12.0.0'} 818 1008 819 1009 fast-glob@3.3.3: 820 1010 resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} ··· 823 1013 fastq@1.20.1: 824 1014 resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} 825 1015 1016 + fdir@6.5.0: 1017 + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} 1018 + engines: {node: '>=12.0.0'} 1019 + peerDependencies: 1020 + picomatch: ^3 || ^4 1021 + peerDependenciesMeta: 1022 + picomatch: 1023 + optional: true 1024 + 826 1025 fill-range@7.1.1: 827 1026 resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} 828 1027 engines: {node: '>=8'} ··· 883 1082 resolution: {integrity: sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==} 884 1083 engines: {node: 20 || >=22} 885 1084 1085 + magic-string@0.30.21: 1086 + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} 1087 + 886 1088 merge2@1.4.1: 887 1089 resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} 888 1090 engines: {node: '>= 8'} ··· 914 1116 multiformats@9.9.0: 915 1117 resolution: {integrity: sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==} 916 1118 1119 + nanoid@3.3.11: 1120 + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} 1121 + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 1122 + hasBin: true 1123 + 1124 + obug@2.1.1: 1125 + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} 1126 + 917 1127 package-json-from-dist@1.0.1: 918 1128 resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} 919 1129 ··· 928 1138 resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} 929 1139 engines: {node: 20 || >=22} 930 1140 1141 + pathe@2.0.3: 1142 + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} 1143 + 1144 + picocolors@1.1.1: 1145 + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} 1146 + 931 1147 picomatch@2.3.1: 932 1148 resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 933 1149 engines: {node: '>=8.6'} 934 1150 1151 + picomatch@4.0.3: 1152 + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} 1153 + engines: {node: '>=12'} 1154 + 1155 + postcss@8.5.6: 1156 + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} 1157 + engines: {node: ^10 || ^12 || >=14} 1158 + 935 1159 postgres@3.4.8: 936 1160 resolution: {integrity: sha512-d+JFcLM17njZaOLkv6SCev7uoLaBtfK86vMUXhW1Z4glPWh4jozno9APvW/XKFJ3CCxVoC7OL38BqRydtu5nGg==} 937 1161 engines: {node: '>=12'} ··· 951 1175 resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} 952 1176 engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 953 1177 1178 + rollup@4.57.1: 1179 + resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==} 1180 + engines: {node: '>=18.0.0', npm: '>=8.0.0'} 1181 + hasBin: true 1182 + 954 1183 run-parallel@1.2.0: 955 1184 resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 956 1185 ··· 962 1191 resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 963 1192 engines: {node: '>=8'} 964 1193 1194 + siginfo@2.0.0: 1195 + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} 1196 + 965 1197 signal-exit@4.1.0: 966 1198 resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} 967 1199 engines: {node: '>=14'} 1200 + 1201 + source-map-js@1.2.1: 1202 + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} 1203 + engines: {node: '>=0.10.0'} 968 1204 969 1205 source-map-support@0.5.21: 970 1206 resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} ··· 973 1209 resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} 974 1210 engines: {node: '>=0.10.0'} 975 1211 1212 + stackback@0.0.2: 1213 + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} 1214 + 1215 + std-env@3.10.0: 1216 + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} 1217 + 976 1218 supports-color@7.2.0: 977 1219 resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 978 1220 engines: {node: '>=8'} 1221 + 1222 + tinybench@2.9.0: 1223 + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} 1224 + 1225 + tinyexec@1.0.2: 1226 + resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} 1227 + engines: {node: '>=18'} 1228 + 1229 + tinyglobby@0.2.15: 1230 + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} 1231 + engines: {node: '>=12.0.0'} 1232 + 1233 + tinyrainbow@3.0.3: 1234 + resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} 1235 + engines: {node: '>=14.0.0'} 979 1236 980 1237 tlds@1.261.0: 981 1238 resolution: {integrity: sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA==} ··· 1052 1309 unicode-segmenter@0.14.5: 1053 1310 resolution: {integrity: sha512-jHGmj2LUuqDcX3hqY12Ql+uhUTn8huuxNZGq7GvtF6bSybzH3aFgedYu/KTzQStEgt1Ra2F3HxadNXsNjb3m3g==} 1054 1311 1312 + vite@7.3.1: 1313 + resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} 1314 + engines: {node: ^20.19.0 || >=22.12.0} 1315 + hasBin: true 1316 + peerDependencies: 1317 + '@types/node': ^20.19.0 || >=22.12.0 1318 + jiti: '>=1.21.0' 1319 + less: ^4.0.0 1320 + lightningcss: ^1.21.0 1321 + sass: ^1.70.0 1322 + sass-embedded: ^1.70.0 1323 + stylus: '>=0.54.8' 1324 + sugarss: ^5.0.0 1325 + terser: ^5.16.0 1326 + tsx: ^4.8.1 1327 + yaml: ^2.4.2 1328 + peerDependenciesMeta: 1329 + '@types/node': 1330 + optional: true 1331 + jiti: 1332 + optional: true 1333 + less: 1334 + optional: true 1335 + lightningcss: 1336 + optional: true 1337 + sass: 1338 + optional: true 1339 + sass-embedded: 1340 + optional: true 1341 + stylus: 1342 + optional: true 1343 + sugarss: 1344 + optional: true 1345 + terser: 1346 + optional: true 1347 + tsx: 1348 + optional: true 1349 + yaml: 1350 + optional: true 1351 + 1352 + vitest@4.0.18: 1353 + resolution: {integrity: sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==} 1354 + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} 1355 + hasBin: true 1356 + peerDependencies: 1357 + '@edge-runtime/vm': '*' 1358 + '@opentelemetry/api': ^1.9.0 1359 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 1360 + '@vitest/browser-playwright': 4.0.18 1361 + '@vitest/browser-preview': 4.0.18 1362 + '@vitest/browser-webdriverio': 4.0.18 1363 + '@vitest/ui': 4.0.18 1364 + happy-dom: '*' 1365 + jsdom: '*' 1366 + peerDependenciesMeta: 1367 + '@edge-runtime/vm': 1368 + optional: true 1369 + '@opentelemetry/api': 1370 + optional: true 1371 + '@types/node': 1372 + optional: true 1373 + '@vitest/browser-playwright': 1374 + optional: true 1375 + '@vitest/browser-preview': 1376 + optional: true 1377 + '@vitest/browser-webdriverio': 1378 + optional: true 1379 + '@vitest/ui': 1380 + optional: true 1381 + happy-dom: 1382 + optional: true 1383 + jsdom: 1384 + optional: true 1385 + 1055 1386 which@2.0.2: 1056 1387 resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 1057 1388 engines: {node: '>= 8'} 1389 + hasBin: true 1390 + 1391 + why-is-node-running@2.3.0: 1392 + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} 1393 + engines: {node: '>=8'} 1058 1394 hasBin: true 1059 1395 1060 1396 yaml@2.8.2: ··· 1384 1720 1385 1721 '@isaacs/cliui@9.0.0': {} 1386 1722 1723 + '@jridgewell/sourcemap-codec@1.5.5': {} 1724 + 1387 1725 '@nodelib/fs.scandir@2.1.5': 1388 1726 dependencies: 1389 1727 '@nodelib/fs.stat': 2.0.5 ··· 1396 1734 '@nodelib/fs.scandir': 2.1.5 1397 1735 fastq: 1.20.1 1398 1736 1737 + '@rollup/rollup-android-arm-eabi@4.57.1': 1738 + optional: true 1739 + 1740 + '@rollup/rollup-android-arm64@4.57.1': 1741 + optional: true 1742 + 1743 + '@rollup/rollup-darwin-arm64@4.57.1': 1744 + optional: true 1745 + 1746 + '@rollup/rollup-darwin-x64@4.57.1': 1747 + optional: true 1748 + 1749 + '@rollup/rollup-freebsd-arm64@4.57.1': 1750 + optional: true 1751 + 1752 + '@rollup/rollup-freebsd-x64@4.57.1': 1753 + optional: true 1754 + 1755 + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': 1756 + optional: true 1757 + 1758 + '@rollup/rollup-linux-arm-musleabihf@4.57.1': 1759 + optional: true 1760 + 1761 + '@rollup/rollup-linux-arm64-gnu@4.57.1': 1762 + optional: true 1763 + 1764 + '@rollup/rollup-linux-arm64-musl@4.57.1': 1765 + optional: true 1766 + 1767 + '@rollup/rollup-linux-loong64-gnu@4.57.1': 1768 + optional: true 1769 + 1770 + '@rollup/rollup-linux-loong64-musl@4.57.1': 1771 + optional: true 1772 + 1773 + '@rollup/rollup-linux-ppc64-gnu@4.57.1': 1774 + optional: true 1775 + 1776 + '@rollup/rollup-linux-ppc64-musl@4.57.1': 1777 + optional: true 1778 + 1779 + '@rollup/rollup-linux-riscv64-gnu@4.57.1': 1780 + optional: true 1781 + 1782 + '@rollup/rollup-linux-riscv64-musl@4.57.1': 1783 + optional: true 1784 + 1785 + '@rollup/rollup-linux-s390x-gnu@4.57.1': 1786 + optional: true 1787 + 1788 + '@rollup/rollup-linux-x64-gnu@4.57.1': 1789 + optional: true 1790 + 1791 + '@rollup/rollup-linux-x64-musl@4.57.1': 1792 + optional: true 1793 + 1794 + '@rollup/rollup-openbsd-x64@4.57.1': 1795 + optional: true 1796 + 1797 + '@rollup/rollup-openharmony-arm64@4.57.1': 1798 + optional: true 1799 + 1800 + '@rollup/rollup-win32-arm64-msvc@4.57.1': 1801 + optional: true 1802 + 1803 + '@rollup/rollup-win32-ia32-msvc@4.57.1': 1804 + optional: true 1805 + 1806 + '@rollup/rollup-win32-x64-gnu@4.57.1': 1807 + optional: true 1808 + 1809 + '@rollup/rollup-win32-x64-msvc@4.57.1': 1810 + optional: true 1811 + 1812 + '@standard-schema/spec@1.1.0': {} 1813 + 1399 1814 '@ts-morph/common@0.17.0': 1400 1815 dependencies: 1401 1816 fast-glob: 3.3.3 ··· 1403 1818 mkdirp: 1.0.4 1404 1819 path-browserify: 1.0.1 1405 1820 1821 + '@types/chai@5.2.3': 1822 + dependencies: 1823 + '@types/deep-eql': 4.0.2 1824 + assertion-error: 2.0.1 1825 + 1826 + '@types/deep-eql@4.0.2': {} 1827 + 1828 + '@types/estree@1.0.8': {} 1829 + 1406 1830 '@types/node@22.19.9': 1407 1831 dependencies: 1408 1832 undici-types: 6.21.0 1409 1833 1834 + '@vitest/expect@4.0.18': 1835 + dependencies: 1836 + '@standard-schema/spec': 1.1.0 1837 + '@types/chai': 5.2.3 1838 + '@vitest/spy': 4.0.18 1839 + '@vitest/utils': 4.0.18 1840 + chai: 6.2.2 1841 + tinyrainbow: 3.0.3 1842 + 1843 + '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@22.19.9)(tsx@4.21.0)(yaml@2.8.2))': 1844 + dependencies: 1845 + '@vitest/spy': 4.0.18 1846 + estree-walker: 3.0.3 1847 + magic-string: 0.30.21 1848 + optionalDependencies: 1849 + vite: 7.3.1(@types/node@22.19.9)(tsx@4.21.0)(yaml@2.8.2) 1850 + 1851 + '@vitest/pretty-format@4.0.18': 1852 + dependencies: 1853 + tinyrainbow: 3.0.3 1854 + 1855 + '@vitest/runner@4.0.18': 1856 + dependencies: 1857 + '@vitest/utils': 4.0.18 1858 + pathe: 2.0.3 1859 + 1860 + '@vitest/snapshot@4.0.18': 1861 + dependencies: 1862 + '@vitest/pretty-format': 4.0.18 1863 + magic-string: 0.30.21 1864 + pathe: 2.0.3 1865 + 1866 + '@vitest/spy@4.0.18': {} 1867 + 1868 + '@vitest/utils@4.0.18': 1869 + dependencies: 1870 + '@vitest/pretty-format': 4.0.18 1871 + tinyrainbow: 3.0.3 1872 + 1410 1873 ansi-styles@4.3.0: 1411 1874 dependencies: 1412 1875 color-convert: 2.0.1 1876 + 1877 + assertion-error@2.0.1: {} 1413 1878 1414 1879 await-lock@2.2.2: {} 1415 1880 ··· 1424 1889 fill-range: 7.1.1 1425 1890 1426 1891 buffer-from@1.1.2: {} 1892 + 1893 + chai@6.2.2: {} 1427 1894 1428 1895 chalk@4.1.2: 1429 1896 dependencies: ··· 1462 1929 drizzle-orm@0.45.1(postgres@3.4.8): 1463 1930 optionalDependencies: 1464 1931 postgres: 3.4.8 1932 + 1933 + es-module-lexer@1.7.0: {} 1465 1934 1466 1935 esbuild-register@3.6.0(esbuild@0.25.12): 1467 1936 dependencies: ··· 1553 2022 '@esbuild/win32-ia32': 0.27.3 1554 2023 '@esbuild/win32-x64': 0.27.3 1555 2024 2025 + estree-walker@3.0.3: 2026 + dependencies: 2027 + '@types/estree': 1.0.8 2028 + 2029 + expect-type@1.3.0: {} 2030 + 1556 2031 fast-glob@3.3.3: 1557 2032 dependencies: 1558 2033 '@nodelib/fs.stat': 2.0.5 ··· 1564 2039 fastq@1.20.1: 1565 2040 dependencies: 1566 2041 reusify: 1.1.0 2042 + 2043 + fdir@6.5.0(picomatch@4.0.3): 2044 + optionalDependencies: 2045 + picomatch: 4.0.3 1567 2046 1568 2047 fill-range@7.1.1: 1569 2048 dependencies: ··· 1615 2094 '@isaacs/cliui': 9.0.0 1616 2095 1617 2096 lru-cache@11.2.5: {} 2097 + 2098 + magic-string@0.30.21: 2099 + dependencies: 2100 + '@jridgewell/sourcemap-codec': 1.5.5 1618 2101 1619 2102 merge2@1.4.1: {} 1620 2103 ··· 1639 2122 1640 2123 multiformats@9.9.0: {} 1641 2124 2125 + nanoid@3.3.11: {} 2126 + 2127 + obug@2.1.1: {} 2128 + 1642 2129 package-json-from-dist@1.0.1: {} 1643 2130 1644 2131 path-browserify@1.0.1: {} ··· 1650 2137 lru-cache: 11.2.5 1651 2138 minipass: 7.1.2 1652 2139 2140 + pathe@2.0.3: {} 2141 + 2142 + picocolors@1.1.1: {} 2143 + 1653 2144 picomatch@2.3.1: {} 1654 2145 2146 + picomatch@4.0.3: {} 2147 + 2148 + postcss@8.5.6: 2149 + dependencies: 2150 + nanoid: 3.3.11 2151 + picocolors: 1.1.1 2152 + source-map-js: 1.2.1 2153 + 1655 2154 postgres@3.4.8: {} 1656 2155 1657 2156 prettier@3.8.1: {} ··· 1661 2160 resolve-pkg-maps@1.0.0: {} 1662 2161 1663 2162 reusify@1.1.0: {} 2163 + 2164 + rollup@4.57.1: 2165 + dependencies: 2166 + '@types/estree': 1.0.8 2167 + optionalDependencies: 2168 + '@rollup/rollup-android-arm-eabi': 4.57.1 2169 + '@rollup/rollup-android-arm64': 4.57.1 2170 + '@rollup/rollup-darwin-arm64': 4.57.1 2171 + '@rollup/rollup-darwin-x64': 4.57.1 2172 + '@rollup/rollup-freebsd-arm64': 4.57.1 2173 + '@rollup/rollup-freebsd-x64': 4.57.1 2174 + '@rollup/rollup-linux-arm-gnueabihf': 4.57.1 2175 + '@rollup/rollup-linux-arm-musleabihf': 4.57.1 2176 + '@rollup/rollup-linux-arm64-gnu': 4.57.1 2177 + '@rollup/rollup-linux-arm64-musl': 4.57.1 2178 + '@rollup/rollup-linux-loong64-gnu': 4.57.1 2179 + '@rollup/rollup-linux-loong64-musl': 4.57.1 2180 + '@rollup/rollup-linux-ppc64-gnu': 4.57.1 2181 + '@rollup/rollup-linux-ppc64-musl': 4.57.1 2182 + '@rollup/rollup-linux-riscv64-gnu': 4.57.1 2183 + '@rollup/rollup-linux-riscv64-musl': 4.57.1 2184 + '@rollup/rollup-linux-s390x-gnu': 4.57.1 2185 + '@rollup/rollup-linux-x64-gnu': 4.57.1 2186 + '@rollup/rollup-linux-x64-musl': 4.57.1 2187 + '@rollup/rollup-openbsd-x64': 4.57.1 2188 + '@rollup/rollup-openharmony-arm64': 4.57.1 2189 + '@rollup/rollup-win32-arm64-msvc': 4.57.1 2190 + '@rollup/rollup-win32-ia32-msvc': 4.57.1 2191 + '@rollup/rollup-win32-x64-gnu': 4.57.1 2192 + '@rollup/rollup-win32-x64-msvc': 4.57.1 2193 + fsevents: 2.3.3 1664 2194 1665 2195 run-parallel@1.2.0: 1666 2196 dependencies: ··· 1672 2202 1673 2203 shebang-regex@3.0.0: {} 1674 2204 2205 + siginfo@2.0.0: {} 2206 + 1675 2207 signal-exit@4.1.0: {} 2208 + 2209 + source-map-js@1.2.1: {} 1676 2210 1677 2211 source-map-support@0.5.21: 1678 2212 dependencies: ··· 1681 2215 1682 2216 source-map@0.6.1: {} 1683 2217 2218 + stackback@0.0.2: {} 2219 + 2220 + std-env@3.10.0: {} 2221 + 1684 2222 supports-color@7.2.0: 1685 2223 dependencies: 1686 2224 has-flag: 4.0.0 2225 + 2226 + tinybench@2.9.0: {} 2227 + 2228 + tinyexec@1.0.2: {} 2229 + 2230 + tinyglobby@0.2.15: 2231 + dependencies: 2232 + fdir: 6.5.0(picomatch@4.0.3) 2233 + picomatch: 4.0.3 2234 + 2235 + tinyrainbow@3.0.3: {} 1687 2236 1688 2237 tlds@1.261.0: {} 1689 2238 ··· 1748 2297 1749 2298 unicode-segmenter@0.14.5: {} 1750 2299 2300 + vite@7.3.1(@types/node@22.19.9)(tsx@4.21.0)(yaml@2.8.2): 2301 + dependencies: 2302 + esbuild: 0.27.3 2303 + fdir: 6.5.0(picomatch@4.0.3) 2304 + picomatch: 4.0.3 2305 + postcss: 8.5.6 2306 + rollup: 4.57.1 2307 + tinyglobby: 0.2.15 2308 + optionalDependencies: 2309 + '@types/node': 22.19.9 2310 + fsevents: 2.3.3 2311 + tsx: 4.21.0 2312 + yaml: 2.8.2 2313 + 2314 + vitest@4.0.18(@types/node@22.19.9)(tsx@4.21.0)(yaml@2.8.2): 2315 + dependencies: 2316 + '@vitest/expect': 4.0.18 2317 + '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@22.19.9)(tsx@4.21.0)(yaml@2.8.2)) 2318 + '@vitest/pretty-format': 4.0.18 2319 + '@vitest/runner': 4.0.18 2320 + '@vitest/snapshot': 4.0.18 2321 + '@vitest/spy': 4.0.18 2322 + '@vitest/utils': 4.0.18 2323 + es-module-lexer: 1.7.0 2324 + expect-type: 1.3.0 2325 + magic-string: 0.30.21 2326 + obug: 2.1.1 2327 + pathe: 2.0.3 2328 + picomatch: 4.0.3 2329 + std-env: 3.10.0 2330 + tinybench: 2.9.0 2331 + tinyexec: 1.0.2 2332 + tinyglobby: 0.2.15 2333 + tinyrainbow: 3.0.3 2334 + vite: 7.3.1(@types/node@22.19.9)(tsx@4.21.0)(yaml@2.8.2) 2335 + why-is-node-running: 2.3.0 2336 + optionalDependencies: 2337 + '@types/node': 22.19.9 2338 + transitivePeerDependencies: 2339 + - jiti 2340 + - less 2341 + - lightningcss 2342 + - msw 2343 + - sass 2344 + - sass-embedded 2345 + - stylus 2346 + - sugarss 2347 + - terser 2348 + - tsx 2349 + - yaml 2350 + 1751 2351 which@2.0.2: 1752 2352 dependencies: 1753 2353 isexe: 2.0.0 2354 + 2355 + why-is-node-running@2.3.0: 2356 + dependencies: 2357 + siginfo: 2.0.0 2358 + stackback: 0.0.2 1754 2359 1755 2360 yaml@2.8.2: {} 1756 2361
+3
turbo.json
··· 14 14 "lint": { 15 15 "dependsOn": ["^build"] 16 16 }, 17 + "test": { 18 + "dependsOn": ["^build"] 19 + }, 17 20 "clean": { 18 21 "cache": false 19 22 }
+8
vitest.workspace.ts
··· 1 + import { defineWorkspace } from "vitest/config"; 2 + 3 + export default defineWorkspace([ 4 + "apps/appview", 5 + "apps/web", 6 + "packages/db", 7 + "packages/lexicon", 8 + ]);