a collection of lightweight TypeScript packages for AT Protocol, the protocol powering Bluesky
atproto bluesky typescript npm
101
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(lex-cli): add package.json-based lexicon import metadata

Mary 9e481048 93b3e0a8

+468 -120
+61
.changeset/stale-mugs-create.md
··· 1 + --- 2 + '@atcute/lex-cli': minor 3 + '@atcute/atproto': patch 4 + '@atcute/bluesky': patch 5 + '@atcute/bluemoji': patch 6 + '@atcute/frontpage': patch 7 + '@atcute/leaflet': patch 8 + '@atcute/lexicon-community': patch 9 + '@atcute/ozone': patch 10 + '@atcute/tangled': patch 11 + '@atcute/whitewind': patch 12 + --- 13 + 14 + add package.json-based lexicon import metadata 15 + 16 + instead of manually configuring mappings: 17 + 18 + ```js 19 + mappings: [ 20 + { 21 + nsid: ['com.atproto.*'], 22 + imports: (nsid) => { 23 + const specifier = nsid.slice('com.atproto.'.length).replaceAll('.', '/'); 24 + return { type: 'namespace', from: `@atcute/atproto/types/${specifier}` }; 25 + }, 26 + }, 27 + ]; 28 + ``` 29 + 30 + you can now simply write: 31 + 32 + ```js 33 + imports: ['@atcute/atproto']; 34 + ``` 35 + 36 + the package metadata is automatically discovered and used for import resolution. 37 + 38 + for lexicon definition packages, add an `atcute:lexicons` field to your package.json with NSID 39 + patterns mapped to import paths: 40 + 41 + ```json 42 + { 43 + "atcute:lexicons": { 44 + "mapping": { 45 + "com.atproto.*": { 46 + "type": "namespace", 47 + "path": "./types/{{nsid_remainder}}" 48 + } 49 + } 50 + } 51 + } 52 + ``` 53 + 54 + the CLI discovers these mappings from packages listed in the `imports` array. available template 55 + expansions: 56 + 57 + - `./` at the start of the path is replaced with the package name (e.g., `./types/foo` becomes 58 + `@atcute/atproto/types/foo`) 59 + - `{{nsid}}` is replaced with the full NSID (e.g., `com/atproto/sync/subscribeRepos`) 60 + - `{{nsid_prefix}}` is replaced with the part before the wildcard (e.g., `com/atproto`) 61 + - `{{nsid_remainder}}` is replaced with the part after the prefix (e.g., `sync/subscribeRepos`)
+8
packages/definitions/atproto/package.json
··· 33 33 "devDependencies": { 34 34 "@atcute/atproto": "file:", 35 35 "@atcute/lex-cli": "workspace:^" 36 + }, 37 + "atcute:lexicons": { 38 + "mapping": { 39 + "com.atproto.*": { 40 + "type": "namespace", 41 + "path": "./types/{{nsid_remainder}}" 42 + } 43 + } 36 44 } 37 45 }
+1 -16
packages/definitions/bluemoji/lex.config.js
··· 3 3 export default defineLexiconConfig({ 4 4 files: ['../../../lexdocs/bluemoji/**/*.json'], 5 5 outdir: 'lib/lexicons/', 6 - mappings: [ 7 - { 8 - nsid: ['com.atproto.*'], 9 - imports: (nsid) => { 10 - const specifier = nsid.slice('com.atproto.'.length).replaceAll('.', '/'); 11 - return { type: 'namespace', from: `@atcute/atproto/types/${specifier}` }; 12 - }, 13 - }, 14 - { 15 - nsid: ['app.bsky.*'], 16 - imports: (nsid) => { 17 - const specifier = nsid.slice('app.bsky.'.length).replaceAll('.', '/'); 18 - return { type: 'namespace', from: `@atcute/bluesky/types/app/${specifier}` }; 19 - }, 20 - }, 21 - ], 6 + imports: ['@atcute/atproto', '@atcute/bluesky'], 22 7 });
+8
packages/definitions/bluemoji/package.json
··· 31 31 "devDependencies": { 32 32 "@atcute/bluemoji": "file:", 33 33 "@atcute/lex-cli": "workspace:^" 34 + }, 35 + "atcute:lexicons": { 36 + "mapping": { 37 + "blue.moji.*": { 38 + "type": "namespace", 39 + "path": "./types/{{nsid_remainder}}" 40 + } 41 + } 34 42 } 35 43 }
+1 -9
packages/definitions/bluesky/lex.config.js
··· 3 3 export default defineLexiconConfig({ 4 4 files: ['../../../lexdocs/bluesky/app/bsky/**/*.json', '../../../lexdocs/bluesky/chat/bsky/**/*.json'], 5 5 outdir: 'lib/lexicons/', 6 - mappings: [ 7 - { 8 - nsid: ['com.atproto.*'], 9 - imports: (nsid) => { 10 - const specifier = nsid.slice('com.atproto.'.length).replaceAll('.', '/'); 11 - return { type: 'namespace', from: `@atcute/atproto/types/${specifier}` }; 12 - }, 13 - }, 14 - ], 6 + imports: ['@atcute/atproto'], 15 7 });
+12
packages/definitions/bluesky/package.json
··· 39 39 "@atcute/lex-cli": "workspace:^", 40 40 "@atproto/api": "^0.15.27", 41 41 "vitest": "^3.2.4" 42 + }, 43 + "atcute:lexicons": { 44 + "mapping": { 45 + "app.bsky.*": { 46 + "type": "namespace", 47 + "path": "./types/app/{{nsid_remainder}}" 48 + }, 49 + "chat.bsky.*": { 50 + "type": "namespace", 51 + "path": "./types/chat/{{nsid_remainder}}" 52 + } 53 + } 42 54 } 43 55 }
+1 -9
packages/definitions/frontpage/lex.config.js
··· 3 3 export default defineLexiconConfig({ 4 4 files: ['../../../lexdocs/frontpage/**/*.json'], 5 5 outdir: 'lib/lexicons/', 6 - mappings: [ 7 - { 8 - nsid: ['com.atproto.*'], 9 - imports: (nsid) => { 10 - const specifier = nsid.slice('com.atproto.'.length).replaceAll('.', '/'); 11 - return { type: 'namespace', from: `@atcute/atproto/types/${specifier}` }; 12 - }, 13 - }, 14 - ], 6 + imports: ['@atcute/atproto'], 15 7 });
+8
packages/definitions/frontpage/package.json
··· 37 37 "@atcute/frontpage": "file:", 38 38 "@atcute/lex-cli": "workspace:^", 39 39 "vitest": "^3.2.4" 40 + }, 41 + "atcute:lexicons": { 42 + "mapping": { 43 + "fyi.unravel.frontpage.*": { 44 + "type": "namespace", 45 + "path": "./types/{{nsid_remainder}}" 46 + } 47 + } 40 48 } 41 49 }
+1 -9
packages/definitions/leaflet/lex.config.js
··· 3 3 export default defineLexiconConfig({ 4 4 files: ['../../../lexdocs/leaflet/**/*.json'], 5 5 outdir: 'lib/lexicons/', 6 - mappings: [ 7 - { 8 - nsid: ['com.atproto.*'], 9 - imports: (nsid) => { 10 - const specifier = nsid.slice('com.atproto.'.length).replaceAll('.', '/'); 11 - return { type: 'namespace', from: `@atcute/atproto/types/${specifier}` }; 12 - }, 13 - }, 14 - ], 6 + imports: ['@atcute/atproto'], 15 7 });
+8
packages/definitions/leaflet/package.json
··· 37 37 "@atcute/leaflet": "file:", 38 38 "@atcute/lex-cli": "workspace:^", 39 39 "vitest": "^3.2.4" 40 + }, 41 + "atcute:lexicons": { 42 + "mapping": { 43 + "pub.leaflet.*": { 44 + "type": "namespace", 45 + "path": "./types/{{nsid_remainder}}" 46 + } 47 + } 40 48 } 41 49 }
+1 -9
packages/definitions/lexicon-community/lex.config.js
··· 3 3 export default defineLexiconConfig({ 4 4 files: ['../../../lexdocs/lexcom/**/*.json'], 5 5 outdir: 'lib/lexicons/', 6 - mappings: [ 7 - { 8 - nsid: ['com.atproto.*'], 9 - imports: (nsid) => { 10 - const specifier = nsid.slice('com.atproto.'.length).replaceAll('.', '/'); 11 - return { type: 'namespace', from: `@atcute/atproto/types/${specifier}` }; 12 - }, 13 - }, 14 - ], 6 + imports: ['@atcute/atproto'], 15 7 });
+8
packages/definitions/lexicon-community/package.json
··· 30 30 "devDependencies": { 31 31 "@atcute/lexicon-community": "file:", 32 32 "@atcute/lex-cli": "workspace:^" 33 + }, 34 + "atcute:lexicons": { 35 + "mapping": { 36 + "community.lexicon.*": { 37 + "type": "namespace", 38 + "path": "./types/{{nsid_remainder}}" 39 + } 40 + } 33 41 } 34 42 }
+1 -23
packages/definitions/ozone/lex.config.js
··· 3 3 export default defineLexiconConfig({ 4 4 files: ['../../../lexdocs/bluesky/tools/ozone/**/*.json'], 5 5 outdir: 'lib/lexicons/', 6 - mappings: [ 7 - { 8 - nsid: ['com.atproto.*'], 9 - imports: (nsid) => { 10 - const specifier = nsid.slice('com.atproto.'.length).replaceAll('.', '/'); 11 - return { type: 'namespace', from: `@atcute/atproto/types/${specifier}` }; 12 - }, 13 - }, 14 - { 15 - nsid: ['app.bsky.*'], 16 - imports: (nsid) => { 17 - const specifier = nsid.slice('app.bsky.'.length).replaceAll('.', '/'); 18 - return { type: 'namespace', from: `@atcute/bluesky/types/app/${specifier}` }; 19 - }, 20 - }, 21 - { 22 - nsid: ['chat.bsky.*'], 23 - imports: (nsid) => { 24 - const specifier = nsid.slice('chat.bsky.'.length).replaceAll('.', '/'); 25 - return { type: 'namespace', from: `@atcute/bluesky/types/chat/${specifier}` }; 26 - }, 27 - }, 28 - ], 6 + imports: ['@atcute/atproto', '@atcute/bluesky'], 29 7 });
+8
packages/definitions/ozone/package.json
··· 37 37 "devDependencies": { 38 38 "@atcute/lex-cli": "workspace:^", 39 39 "@atcute/ozone": "file:" 40 + }, 41 + "atcute:lexicons": { 42 + "mapping": { 43 + "tools.ozone.*": { 44 + "type": "namespace", 45 + "path": "./types/{{nsid_remainder}}" 46 + } 47 + } 40 48 } 41 49 }
+1 -9
packages/definitions/tangled/lex.config.js
··· 3 3 export default defineLexiconConfig({ 4 4 files: ['../../../lexdocs/tangled/**/*.json'], 5 5 outdir: 'lib/lexicons/', 6 - mappings: [ 7 - // { 8 - // nsid: ['com.atproto.*'], 9 - // imports: (nsid) => { 10 - // const specifier = nsid.slice('com.atproto.'.length).replaceAll('.', '/'); 11 - // return { type: 'namespace', from: `@atcute/atproto/types/${specifier}` }; 12 - // }, 13 - // }, 14 - ], 6 + imports: ['@atcute/atproto'], 15 7 });
+8
packages/definitions/tangled/package.json
··· 37 37 "@atcute/lex-cli": "workspace:^", 38 38 "@atcute/tangled": "file:", 39 39 "vitest": "^3.2.4" 40 + }, 41 + "atcute:lexicons": { 42 + "mapping": { 43 + "sh.tangled.*": { 44 + "type": "namespace", 45 + "path": "./types/{{nsid_remainder}}" 46 + } 47 + } 40 48 } 41 49 }
-1
packages/definitions/whitewind/lex.config.js
··· 3 3 export default defineLexiconConfig({ 4 4 files: ['../../../lexdocs/whtwnd/**/*.json'], 5 5 outdir: 'lib/lexicons/', 6 - mappings: [], 7 6 });
+8
packages/definitions/whitewind/package.json
··· 34 34 "devDependencies": { 35 35 "@atcute/lex-cli": "workspace:^", 36 36 "@atcute/whitewind": "file:" 37 + }, 38 + "atcute:lexicons": { 39 + "mapping": { 40 + "com.whtwnd.*": { 41 + "type": "namespace", 42 + "path": "./types/{{nsid_remainder}}" 43 + } 44 + } 37 45 } 38 46 }
+8 -4
packages/lexicons/lex-cli/package.json
··· 13 13 "src/", 14 14 "!src/**/*.bench.ts", 15 15 "!src/**/*.test.ts", 16 - "cli.mjs" 16 + "cli.mjs", 17 + "schema/" 17 18 ], 18 19 "bin": "./cli.mjs", 19 20 "exports": { 20 21 ".": "./dist/index.js" 21 22 }, 22 23 "scripts": { 23 - "build": "tsc", 24 + "build": "pnpm run generate:schema && tsc", 25 + "generate:schema": "node scripts/generate-schema.ts", 24 26 "prepublish": "rm -rf dist; pnpm run build" 25 27 }, 26 28 "dependencies": { ··· 29 31 "@optique/core": "^0.6.1", 30 32 "@optique/run": "^0.6.1", 31 33 "picocolors": "^1.1.1", 32 - "prettier": "^3.6.2" 34 + "prettier": "^3.6.2", 35 + "valibot": "^1.0.0" 33 36 }, 34 37 "devDependencies": { 35 38 "@atcute/lexicons": "workspace:^", 36 - "@types/node": "^22.18.0" 39 + "@types/node": "^22.18.0", 40 + "@valibot/to-json-schema": "^1.3.0" 37 41 } 38 42 }
+37
packages/lexicons/lex-cli/schema/lexicon-package.schema.json
··· 1 + { 2 + "$schema": "http://json-schema.org/draft-07/schema#", 3 + "$id": "https://unpkg.com/@atcute/lex-cli/schema/lexicon-package.schema.json", 4 + "title": "package.json with atcute:lexicons", 5 + "description": "JSON Schema for package.json with atcute:lexicons field for lexicon import mappings", 6 + "type": "object", 7 + "properties": { 8 + "atcute:lexicons": { 9 + "type": "object", 10 + "properties": { 11 + "mapping": { 12 + "type": "object", 13 + "additionalProperties": { 14 + "type": "object", 15 + "properties": { 16 + "type": { 17 + "enum": [ 18 + "namespace", 19 + "named" 20 + ] 21 + }, 22 + "path": { 23 + "type": "string" 24 + } 25 + }, 26 + "required": [ 27 + "type", 28 + "path" 29 + ] 30 + } 31 + } 32 + }, 33 + "required": [] 34 + } 35 + }, 36 + "required": [] 37 + }
+32
packages/lexicons/lex-cli/scripts/generate-schema.ts
··· 1 + import * as fs from 'node:fs/promises'; 2 + import * as path from 'node:path'; 3 + import * as url from 'node:url'; 4 + 5 + import { toJsonSchema } from '@valibot/to-json-schema'; 6 + 7 + import { packageJsonSchema } from '../src/lexicon-metadata.ts'; 8 + 9 + const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); 10 + 11 + // Generate JSON Schema from Valibot schema 12 + // Use errorMode: 'ignore' to skip unsupported validations like startsWith 13 + const jsonSchema = toJsonSchema(packageJsonSchema, { errorMode: 'ignore' }); 14 + 15 + // Add schema metadata 16 + const schemaWithMetadata = { 17 + $schema: 'http://json-schema.org/draft-07/schema#', 18 + $id: 'https://unpkg.com/@atcute/lex-cli/schema/lexicon-package.schema.json', 19 + title: 'package.json with atcute:lexicons', 20 + description: 'JSON Schema for package.json with atcute:lexicons field for lexicon import mappings', 21 + ...jsonSchema, 22 + }; 23 + 24 + // Ensure schema directory exists 25 + const schemaDir = path.join(__dirname, '..', 'schema'); 26 + await fs.mkdir(schemaDir, { recursive: true }); 27 + 28 + // Write to file 29 + const schemaPath = path.join(schemaDir, 'lexicon-package.schema.json'); 30 + await fs.writeFile(schemaPath, JSON.stringify(schemaWithMetadata, null, 2) + '\n'); 31 + 32 + console.log(`generated JSON Schema at ${schemaPath}`);
+103 -2
packages/lexicons/lex-cli/src/cli.ts
··· 10 10 11 11 import { lexiconDoc, type LexiconDoc } from '@atcute/lexicon-doc'; 12 12 13 - import { generateLexiconApi } from './codegen.js'; 13 + import { generateLexiconApi, type ImportMapping } from './codegen.js'; 14 14 import type { LexiconConfig } from './index.js'; 15 + import { validatePackageJson } from './lexicon-metadata.js'; 16 + 17 + /** 18 + * Resolves package imports to ImportMapping[] 19 + */ 20 + const resolveImportsToMappings = async ( 21 + imports: string[], 22 + configDirname: string, 23 + ): Promise<ImportMapping[]> => { 24 + const mappings: ImportMapping[] = []; 25 + 26 + for (const packageName of imports) { 27 + // Walk up from config directory to find package in node_modules 28 + let packageJson: unknown; 29 + let currentDir = configDirname; 30 + let found = false; 31 + 32 + while (currentDir !== path.dirname(currentDir)) { 33 + const candidatePath = path.join(currentDir, 'node_modules', packageName, 'package.json'); 34 + try { 35 + const content = await fs.readFile(candidatePath, 'utf8'); 36 + packageJson = JSON.parse(content); 37 + found = true; 38 + break; 39 + } catch (err: any) { 40 + // Only continue to parent if file not found 41 + if (err.code !== 'ENOENT') { 42 + console.error(pc.bold(pc.red(`failed to read package.json for "${packageName}":`))); 43 + console.error(err); 44 + process.exit(1); 45 + } 46 + 47 + // Not found, try parent directory 48 + currentDir = path.dirname(currentDir); 49 + } 50 + } 51 + 52 + if (!found) { 53 + console.error(pc.bold(pc.red(`failed to resolve package "${packageName}"`))); 54 + console.error(`Could not find package in node_modules starting from ${configDirname}`); 55 + process.exit(1); 56 + } 57 + 58 + // Validate package.json 59 + const result = validatePackageJson(packageJson); 60 + if (!result.success) { 61 + console.error(pc.bold(pc.red(`invalid atcute:lexicons in "${packageName}":`))); 62 + console.error(result.issues); 63 + process.exit(1); 64 + } 65 + 66 + const lexicons = result.output['atcute:lexicons']; 67 + if (!lexicons?.mapping) { 68 + continue; 69 + } 70 + 71 + // Convert mapping to ImportMapping[] 72 + for (const [pattern, entry] of Object.entries(lexicons.mapping)) { 73 + const isWildcard = pattern.endsWith('.*'); 74 + 75 + mappings.push({ 76 + nsid: [pattern], 77 + imports: (nsid: string) => { 78 + // Check if pattern matches 79 + if (isWildcard) { 80 + if (!nsid.startsWith(pattern.slice(0, -1))) { 81 + throw new Error(`NSID ${nsid} does not match pattern ${pattern}`); 82 + } 83 + } else { 84 + if (nsid !== pattern) { 85 + throw new Error(`NSID ${nsid} does not match pattern ${pattern}`); 86 + } 87 + } 88 + 89 + const nsidPrefix = isWildcard ? pattern.slice(0, -2) : pattern; 90 + const nsidRemainder = isWildcard ? nsid.slice(nsidPrefix.length + 1) : ''; 91 + 92 + let expandedPath = entry.path 93 + .replaceAll('{{nsid}}', nsid.replaceAll('.', '/')) 94 + .replaceAll('{{nsid_remainder}}', nsidRemainder.replaceAll('.', '/')) 95 + .replaceAll('{{nsid_prefix}}', nsidPrefix.replaceAll('.', '/')); 96 + 97 + if (expandedPath.startsWith('./')) { 98 + expandedPath = `${packageName}/${expandedPath.slice(2)}`; 99 + } 100 + 101 + return { 102 + type: entry.type, 103 + from: expandedPath, 104 + }; 105 + }, 106 + }); 107 + } 108 + } 109 + 110 + return mappings; 111 + }; 15 112 16 113 const parser = command( 17 114 'generate', ··· 38 135 39 136 process.exit(1); 40 137 } 138 + 139 + // Resolve imports to mappings 140 + const importMappings = config.imports ? await resolveImportsToMappings(config.imports, configDirname) : []; 141 + const allMappings = [...importMappings, ...(config.mappings ?? [])]; 41 142 42 143 const documents: LexiconDoc[] = []; 43 144 ··· 79 180 80 181 const generationResult = await generateLexiconApi({ 81 182 documents: documents, 82 - mappings: config.mappings ?? [], 183 + mappings: allMappings, 83 184 modules: { 84 185 importSuffix: config.modules?.importSuffix ?? '.js', 85 186 },
+1
packages/lexicons/lex-cli/src/index.ts
··· 3 3 export interface LexiconConfig { 4 4 outdir: string; 5 5 files: string[]; 6 + imports?: string[]; 6 7 mappings?: ImportMapping[]; 7 8 modules?: { 8 9 importSuffix?: string;
+59
packages/lexicons/lex-cli/src/lexicon-metadata.ts
··· 1 + import * as v from 'valibot'; 2 + 3 + import { isNsid } from '@atcute/lexicons/syntax'; 4 + 5 + /** 6 + * Validates if a string is a valid NSID pattern (exact or wildcard) 7 + * - Exact: "com.atproto.repo.getRecord" 8 + * - Wildcard: "com.atproto.*" 9 + */ 10 + const isValidLexiconPattern = (pattern: string): boolean => { 11 + if (pattern.endsWith('.*')) { 12 + // For wildcards, remove the .* and validate the prefix as an NSID segment 13 + const prefix = pattern.slice(0, -2); 14 + // Add a dummy segment to make it a valid NSID for validation 15 + return isNsid(prefix + '.x'); 16 + } 17 + return isNsid(pattern); 18 + }; 19 + 20 + /** 21 + * Schema for a single lexicon mapping entry 22 + */ 23 + const lexiconMappingEntry = v.object({ 24 + type: v.picklist(['namespace', 'named']), 25 + path: v.pipe(v.string(), v.startsWith('./')), 26 + }); 27 + 28 + /** 29 + * Schema for the atcute:lexicons field in package.json 30 + */ 31 + const atcuteLexiconsField = v.object({ 32 + mapping: v.optional( 33 + v.record( 34 + v.pipe( 35 + v.string(), 36 + v.check(isValidLexiconPattern, 'Invalid NSID pattern (must be valid NSID or end with .*)'), 37 + ), 38 + lexiconMappingEntry, 39 + ), 40 + ), 41 + }); 42 + 43 + /** 44 + * Schema for package.json with atcute:lexicons field 45 + */ 46 + export const packageJsonSchema = v.looseObject({ 47 + 'atcute:lexicons': v.optional(atcuteLexiconsField), 48 + }); 49 + 50 + export type LexiconMappingEntry = v.InferOutput<typeof lexiconMappingEntry>; 51 + export type AtcuteLexiconsField = v.InferOutput<typeof atcuteLexiconsField>; 52 + export type PackageJsonWithLexicons = v.InferOutput<typeof packageJsonSchema>; 53 + 54 + /** 55 + * Validates a package.json object against the schema 56 + */ 57 + export const validatePackageJson = (data: unknown): v.SafeParseResult<typeof packageJsonSchema> => { 58 + return v.safeParse(packageJsonSchema, data); 59 + };
+84 -29
pnpm-lock.yaml
··· 47 47 version: 1.2.21(@types/react@19.1.8) 48 48 vitest: 49 49 specifier: ^3.2.4 50 - version: 3.2.4(@types/node@24.3.0)(@vitest/browser@3.2.4)(yaml@2.8.0) 50 + version: 3.2.4(@types/node@24.3.0)(@vitest/browser@3.2.4)(tsx@4.20.6)(yaml@2.8.0) 51 51 52 52 packages/bluesky/richtext-builder: 53 53 dependencies: ··· 141 141 version: 3.2.4(@vitest/browser@3.2.4)(vitest@3.2.4) 142 142 vitest: 143 143 specifier: ^3.2.4 144 - version: 3.2.4(@types/node@24.3.0)(@vitest/browser@3.2.4)(yaml@2.8.0) 144 + version: 3.2.4(@types/node@24.3.0)(@vitest/browser@3.2.4)(tsx@4.20.6)(yaml@2.8.0) 145 145 146 146 packages/clients/jetstream: 147 147 dependencies: ··· 172 172 version: 3.2.4(@vitest/browser@3.2.4)(vitest@3.2.4) 173 173 vitest: 174 174 specifier: ^3.2.4 175 - version: 3.2.4(@types/node@24.3.0)(@vitest/browser@3.2.4)(yaml@2.8.0) 175 + version: 3.2.4(@types/node@24.3.0)(@vitest/browser@3.2.4)(tsx@4.20.6)(yaml@2.8.0) 176 176 177 177 packages/definitions/atproto: 178 178 dependencies: ··· 226 226 version: 0.15.27 227 227 vitest: 228 228 specifier: ^3.2.4 229 - version: 3.2.4(@types/node@24.3.0)(@vitest/browser@3.2.4)(yaml@2.8.0) 229 + version: 3.2.4(@types/node@24.3.0)(@vitest/browser@3.2.4)(tsx@4.20.6)(yaml@2.8.0) 230 230 231 231 packages/definitions/frontpage: 232 232 dependencies: ··· 245 245 version: link:../../lexicons/lex-cli 246 246 vitest: 247 247 specifier: ^3.2.4 248 - version: 3.2.4(@types/node@24.3.0)(@vitest/browser@3.2.4)(yaml@2.8.0) 248 + version: 3.2.4(@types/node@24.3.0)(@vitest/browser@3.2.4)(tsx@4.20.6)(yaml@2.8.0) 249 249 250 250 packages/definitions/leaflet: 251 251 dependencies: ··· 264 264 version: link:../../lexicons/lex-cli 265 265 vitest: 266 266 specifier: ^3.2.4 267 - version: 3.2.4(@types/node@24.3.0)(@vitest/browser@3.2.4)(yaml@2.8.0) 267 + version: 3.2.4(@types/node@24.3.0)(@vitest/browser@3.2.4)(tsx@4.20.6)(yaml@2.8.0) 268 268 269 269 packages/definitions/lexicon-community: 270 270 dependencies: ··· 318 318 version: file:packages/definitions/tangled 319 319 vitest: 320 320 specifier: ^3.2.4 321 - version: 3.2.4(@types/node@24.3.0)(@vitest/browser@3.2.4)(yaml@2.8.0) 321 + version: 3.2.4(@types/node@24.3.0)(@vitest/browser@3.2.4)(tsx@4.20.6)(yaml@2.8.0) 322 322 323 323 packages/definitions/whitewind: 324 324 dependencies: ··· 475 475 prettier: 476 476 specifier: ^3.6.2 477 477 version: 3.6.2 478 + valibot: 479 + specifier: ^1.0.0 480 + version: 1.1.0(typescript@5.9.2) 478 481 devDependencies: 479 482 '@atcute/lexicons': 480 483 specifier: workspace:^ ··· 482 485 '@types/node': 483 486 specifier: ^22.18.0 484 487 version: 22.18.0 488 + '@valibot/to-json-schema': 489 + specifier: ^1.3.0 490 + version: 1.3.0(valibot@1.1.0(typescript@5.9.2)) 491 + tsx: 492 + specifier: ^4.19.2 493 + version: 4.20.6 485 494 486 495 packages/lexicons/lexicon-doc: 487 496 dependencies: ··· 494 503 version: 3.2.4(@vitest/browser@3.2.4)(vitest@3.2.4) 495 504 vitest: 496 505 specifier: ^3.2.4 497 - version: 3.2.4(@types/node@24.3.0)(@vitest/browser@3.2.4)(yaml@2.8.0) 506 + version: 3.2.4(@types/node@24.3.0)(@vitest/browser@3.2.4)(tsx@4.20.6)(yaml@2.8.0) 498 507 499 508 packages/lexicons/lexicon-resolver: 500 509 dependencies: ··· 556 565 version: 3.2.4(@vitest/browser@3.2.4)(vitest@3.2.4) 557 566 vitest: 558 567 specifier: ^3.2.4 559 - version: 3.2.4(@types/node@24.3.0)(@vitest/browser@3.2.4)(yaml@2.8.0) 568 + version: 3.2.4(@types/node@24.3.0)(@vitest/browser@3.2.4)(tsx@4.20.6)(yaml@2.8.0) 560 569 561 570 packages/misc/util-fetch: 562 571 dependencies: ··· 634 643 version: 3.2.4(@vitest/browser@3.2.4)(vitest@3.2.4) 635 644 vitest: 636 645 specifier: ^3.2.4 637 - version: 3.2.4(@types/node@24.3.0)(@vitest/browser@3.2.4)(yaml@2.8.0) 646 + version: 3.2.4(@types/node@24.3.0)(@vitest/browser@3.2.4)(tsx@4.20.6)(yaml@2.8.0) 638 647 639 648 packages/utilities/car: 640 649 dependencies: ··· 716 725 version: 1.2.21(@types/react@19.1.8) 717 726 '@vitest/browser': 718 727 specifier: ^3.2.4 719 - version: 3.2.4(playwright@1.55.0)(vite@7.1.4(@types/node@24.3.0)(yaml@2.8.0))(vitest@3.2.4) 728 + version: 3.2.4(playwright@1.55.0)(vite@7.1.4(@types/node@24.3.0)(tsx@4.20.6)(yaml@2.8.0))(vitest@3.2.4) 720 729 '@vitest/coverage-v8': 721 730 specifier: ^3.2.4 722 731 version: 3.2.4(@vitest/browser@3.2.4)(vitest@3.2.4) ··· 725 734 version: 1.55.0 726 735 vitest: 727 736 specifier: ^3.2.4 728 - version: 3.2.4(@types/node@24.3.0)(@vitest/browser@3.2.4)(yaml@2.8.0) 737 + version: 3.2.4(@types/node@24.3.0)(@vitest/browser@3.2.4)(tsx@4.20.6)(yaml@2.8.0) 729 738 730 739 packages/utilities/multibase: 731 740 dependencies: ··· 741 750 devDependencies: 742 751 vitest: 743 752 specifier: ^3.2.4 744 - version: 3.2.4(@types/node@24.3.0)(@vitest/browser@3.2.4)(yaml@2.8.0) 753 + version: 3.2.4(@types/node@24.3.0)(@vitest/browser@3.2.4)(tsx@4.20.6)(yaml@2.8.0) 745 754 746 755 packages/utilities/uint8array: 747 756 devDependencies: ··· 753 762 devDependencies: 754 763 vitest: 755 764 specifier: ^3.2.4 756 - version: 3.2.4(@types/node@24.3.0)(@vitest/browser@3.2.4)(yaml@2.8.0) 765 + version: 3.2.4(@types/node@24.3.0)(@vitest/browser@3.2.4)(tsx@4.20.6)(yaml@2.8.0) 757 766 758 767 packages: 759 768 ··· 1944 1953 '@types/uuid@9.0.8': 1945 1954 resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==} 1946 1955 1956 + '@valibot/to-json-schema@1.3.0': 1957 + resolution: {integrity: sha512-82Vv6x7sOYhv5YmTRgSppSqj1nn2pMCk5BqCMGWYp0V/fq+qirrbGncqZAtZ09/lrO40ne/7z8ejwE728aVreg==} 1958 + peerDependencies: 1959 + valibot: ^1.1.0 1960 + 1947 1961 '@vitest/browser@3.2.4': 1948 1962 resolution: {integrity: sha512-tJxiPrWmzH8a+w9nLKlQMzAKX/7VjFs50MWgcAj7p9XQ7AQ9/35fByFYptgPELyLw+0aixTnC4pUWV+APcZ/kw==} 1949 1963 peerDependencies: ··· 2551 2565 resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} 2552 2566 engines: {node: '>= 0.4'} 2553 2567 2568 + get-tsconfig@4.12.0: 2569 + resolution: {integrity: sha512-LScr2aNr2FbjAjZh2C6X6BxRx1/x+aTDExct/xyq2XKbYOiG5c0aK7pMsSuyc0brz3ibr/lbQiHD9jzt4lccJw==} 2570 + 2554 2571 github-from-package@0.0.0: 2555 2572 resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} 2556 2573 ··· 3208 3225 resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} 3209 3226 engines: {node: '>=8'} 3210 3227 3228 + resolve-pkg-maps@1.0.0: 3229 + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} 3230 + 3211 3231 reusify@1.1.0: 3212 3232 resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} 3213 3233 engines: {iojs: '>=1.0.0', node: '>=0.10.0'} ··· 3449 3469 tslib@2.8.1: 3450 3470 resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} 3451 3471 3472 + tsx@4.20.6: 3473 + resolution: {integrity: sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==} 3474 + engines: {node: '>=18.0.0'} 3475 + hasBin: true 3476 + 3452 3477 tunnel-agent@0.6.0: 3453 3478 resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} 3454 3479 ··· 3512 3537 resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} 3513 3538 hasBin: true 3514 3539 3540 + valibot@1.1.0: 3541 + resolution: {integrity: sha512-Nk8lX30Qhu+9txPYTwM0cFlWLdPFsFr6LblzqIySfbZph9+BFsAHsNvHOymEviUepeIW6KFHzpX8TKhbptBXXw==} 3542 + peerDependencies: 3543 + typescript: '>=5' 3544 + peerDependenciesMeta: 3545 + typescript: 3546 + optional: true 3547 + 3515 3548 varint@6.0.0: 3516 3549 resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==} 3517 3550 ··· 5540 5573 5541 5574 '@types/uuid@9.0.8': {} 5542 5575 5543 - '@vitest/browser@3.2.4(playwright@1.55.0)(vite@7.1.4(@types/node@24.3.0)(yaml@2.8.0))(vitest@3.2.4)': 5576 + '@valibot/to-json-schema@1.3.0(valibot@1.1.0(typescript@5.9.2))': 5577 + dependencies: 5578 + valibot: 1.1.0(typescript@5.9.2) 5579 + 5580 + '@vitest/browser@3.2.4(playwright@1.55.0)(vite@7.1.4(@types/node@24.3.0)(tsx@4.20.6)(yaml@2.8.0))(vitest@3.2.4)': 5544 5581 dependencies: 5545 5582 '@testing-library/dom': 10.4.1 5546 5583 '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) 5547 - '@vitest/mocker': 3.2.4(vite@7.1.4(@types/node@24.3.0)(yaml@2.8.0)) 5584 + '@vitest/mocker': 3.2.4(vite@7.1.4(@types/node@24.3.0)(tsx@4.20.6)(yaml@2.8.0)) 5548 5585 '@vitest/utils': 3.2.4 5549 5586 magic-string: 0.30.18 5550 5587 sirv: 3.0.1 5551 5588 tinyrainbow: 2.0.0 5552 - vitest: 3.2.4(@types/node@24.3.0)(@vitest/browser@3.2.4)(yaml@2.8.0) 5589 + vitest: 3.2.4(@types/node@24.3.0)(@vitest/browser@3.2.4)(tsx@4.20.6)(yaml@2.8.0) 5553 5590 ws: 8.18.3 5554 5591 optionalDependencies: 5555 5592 playwright: 1.55.0 ··· 5574 5611 std-env: 3.9.0 5575 5612 test-exclude: 7.0.1 5576 5613 tinyrainbow: 2.0.0 5577 - vitest: 3.2.4(@types/node@24.3.0)(@vitest/browser@3.2.4)(yaml@2.8.0) 5614 + vitest: 3.2.4(@types/node@24.3.0)(@vitest/browser@3.2.4)(tsx@4.20.6)(yaml@2.8.0) 5578 5615 optionalDependencies: 5579 - '@vitest/browser': 3.2.4(playwright@1.55.0)(vite@7.1.4(@types/node@24.3.0)(yaml@2.8.0))(vitest@3.2.4) 5616 + '@vitest/browser': 3.2.4(playwright@1.55.0)(vite@7.1.4(@types/node@24.3.0)(tsx@4.20.6)(yaml@2.8.0))(vitest@3.2.4) 5580 5617 transitivePeerDependencies: 5581 5618 - supports-color 5582 5619 ··· 5588 5625 chai: 5.3.3 5589 5626 tinyrainbow: 2.0.0 5590 5627 5591 - '@vitest/mocker@3.2.4(vite@7.1.4(@types/node@24.3.0)(yaml@2.8.0))': 5628 + '@vitest/mocker@3.2.4(vite@7.1.4(@types/node@24.3.0)(tsx@4.20.6)(yaml@2.8.0))': 5592 5629 dependencies: 5593 5630 '@vitest/spy': 3.2.4 5594 5631 estree-walker: 3.0.3 5595 5632 magic-string: 0.30.18 5596 5633 optionalDependencies: 5597 - vite: 7.1.4(@types/node@24.3.0)(yaml@2.8.0) 5634 + vite: 7.1.4(@types/node@24.3.0)(tsx@4.20.6)(yaml@2.8.0) 5598 5635 5599 5636 '@vitest/pretty-format@3.2.4': 5600 5637 dependencies: ··· 6208 6245 dunder-proto: 1.0.1 6209 6246 es-object-atoms: 1.1.1 6210 6247 6248 + get-tsconfig@4.12.0: 6249 + dependencies: 6250 + resolve-pkg-maps: 1.0.0 6251 + 6211 6252 github-from-package@0.0.0: {} 6212 6253 6213 6254 glob-parent@5.1.2: ··· 6815 6856 6816 6857 resolve-from@5.0.0: {} 6817 6858 6859 + resolve-pkg-maps@1.0.0: {} 6860 + 6818 6861 reusify@1.1.0: {} 6819 6862 6820 6863 roarr@7.21.1: ··· 7114 7157 7115 7158 tslib@2.8.1: {} 7116 7159 7160 + tsx@4.20.6: 7161 + dependencies: 7162 + esbuild: 0.25.9 7163 + get-tsconfig: 4.12.0 7164 + optionalDependencies: 7165 + fsevents: 2.3.3 7166 + 7117 7167 tunnel-agent@0.6.0: 7118 7168 dependencies: 7119 7169 safe-buffer: 5.2.1 ··· 7161 7211 7162 7212 uuid@9.0.1: {} 7163 7213 7214 + valibot@1.1.0(typescript@5.9.2): 7215 + optionalDependencies: 7216 + typescript: 5.9.2 7217 + 7164 7218 varint@6.0.0: {} 7165 7219 7166 7220 vary@1.1.2: {} 7167 7221 7168 - vite-node@3.2.4(@types/node@24.3.0)(yaml@2.8.0): 7222 + vite-node@3.2.4(@types/node@24.3.0)(tsx@4.20.6)(yaml@2.8.0): 7169 7223 dependencies: 7170 7224 cac: 6.7.14 7171 7225 debug: 4.4.1 7172 7226 es-module-lexer: 1.7.0 7173 7227 pathe: 2.0.3 7174 - vite: 7.1.4(@types/node@24.3.0)(yaml@2.8.0) 7228 + vite: 7.1.4(@types/node@24.3.0)(tsx@4.20.6)(yaml@2.8.0) 7175 7229 transitivePeerDependencies: 7176 7230 - '@types/node' 7177 7231 - jiti ··· 7186 7240 - tsx 7187 7241 - yaml 7188 7242 7189 - vite@7.1.4(@types/node@24.3.0)(yaml@2.8.0): 7243 + vite@7.1.4(@types/node@24.3.0)(tsx@4.20.6)(yaml@2.8.0): 7190 7244 dependencies: 7191 7245 esbuild: 0.25.9 7192 7246 fdir: 6.5.0(picomatch@4.0.3) ··· 7197 7251 optionalDependencies: 7198 7252 '@types/node': 24.3.0 7199 7253 fsevents: 2.3.3 7254 + tsx: 4.20.6 7200 7255 yaml: 2.8.0 7201 7256 7202 - vitest@3.2.4(@types/node@24.3.0)(@vitest/browser@3.2.4)(yaml@2.8.0): 7257 + vitest@3.2.4(@types/node@24.3.0)(@vitest/browser@3.2.4)(tsx@4.20.6)(yaml@2.8.0): 7203 7258 dependencies: 7204 7259 '@types/chai': 5.2.2 7205 7260 '@vitest/expect': 3.2.4 7206 - '@vitest/mocker': 3.2.4(vite@7.1.4(@types/node@24.3.0)(yaml@2.8.0)) 7261 + '@vitest/mocker': 3.2.4(vite@7.1.4(@types/node@24.3.0)(tsx@4.20.6)(yaml@2.8.0)) 7207 7262 '@vitest/pretty-format': 3.2.4 7208 7263 '@vitest/runner': 3.2.4 7209 7264 '@vitest/snapshot': 3.2.4 ··· 7221 7276 tinyglobby: 0.2.14 7222 7277 tinypool: 1.1.1 7223 7278 tinyrainbow: 2.0.0 7224 - vite: 7.1.4(@types/node@24.3.0)(yaml@2.8.0) 7225 - vite-node: 3.2.4(@types/node@24.3.0)(yaml@2.8.0) 7279 + vite: 7.1.4(@types/node@24.3.0)(tsx@4.20.6)(yaml@2.8.0) 7280 + vite-node: 3.2.4(@types/node@24.3.0)(tsx@4.20.6)(yaml@2.8.0) 7226 7281 why-is-node-running: 2.3.0 7227 7282 optionalDependencies: 7228 7283 '@types/node': 24.3.0 7229 - '@vitest/browser': 3.2.4(playwright@1.55.0)(vite@7.1.4(@types/node@24.3.0)(yaml@2.8.0))(vitest@3.2.4) 7284 + '@vitest/browser': 3.2.4(playwright@1.55.0)(vite@7.1.4(@types/node@24.3.0)(tsx@4.20.6)(yaml@2.8.0))(vitest@3.2.4) 7230 7285 transitivePeerDependencies: 7231 7286 - jiti 7232 7287 - less