The recipes.blue monorepo recipes.blue
recipes appview atproto
2
fork

Configure Feed

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

feat: script to build a cloudflare-importable lexicon resolution binding

+85 -1
+83
libs/lexicons/scripts/publish.ts
··· 1 + import { readdirSync, statSync } from "fs"; 2 + import { join } from "path"; 3 + 4 + const TSP_OUTPUT_DIR = join(import.meta.dir, "../tsp-output/@typelex/emitter"); 5 + 6 + interface LexiconFile { 7 + path: string; 8 + id: string; 9 + content: unknown; 10 + } 11 + 12 + async function crawlJsonFiles(dir: string): Promise<LexiconFile[]> { 13 + const results: LexiconFile[] = []; 14 + 15 + const entries = readdirSync(dir); 16 + 17 + for (const entry of entries) { 18 + const fullPath = join(dir, entry); 19 + const stat = statSync(fullPath); 20 + 21 + if (stat.isDirectory()) { 22 + results.push(...(await crawlJsonFiles(fullPath))); 23 + } else if (entry.endsWith(".json")) { 24 + const file = Bun.file(fullPath); 25 + const content = await file.json(); 26 + 27 + results.push({ 28 + path: fullPath, 29 + id: content.id || entry.replace(".json", ""), 30 + content, 31 + }); 32 + } 33 + } 34 + 35 + return results; 36 + } 37 + 38 + async function generateDNSRecords(did: string): Promise<string> { 39 + const lexicons = await crawlJsonFiles(TSP_OUTPUT_DIR); 40 + const authorities = new Map<string, string[]>(); 41 + 42 + // group lexicons by authority (everything except the final "name" part) 43 + for (const lex of lexicons) { 44 + // blue.recipes.feed.getRecipe -> blue.recipes.feed (authority) 45 + const parts = lex.id.split("."); 46 + const authority = parts.slice(0, -1).join("."); 47 + 48 + if (!authorities.has(authority)) { 49 + authorities.set(authority, []); 50 + } 51 + authorities.get(authority)!.push(lex.id); 52 + } 53 + 54 + // generate DNS TXT records 55 + const records: string[] = []; 56 + records.push("; Lexicon DNS TXT Records for Cloudflare"); 57 + records.push("; Upload these to your Cloudflare DNS settings\n"); 58 + 59 + for (const [authority, nsids] of authorities) { 60 + // blue.recipes.feed -> _lexicon.feed.recipes.blue 61 + const reversedAuthority = authority.split(".").reverse().join("."); 62 + const txtName = `_lexicon.${reversedAuthority}`; 63 + 64 + records.push(`; Authority: ${authority}`); 65 + records.push(`; NSIDs: ${nsids.join(", ")}`); 66 + records.push(`${txtName}\tTXT\tdid=${did}`); 67 + records.push(""); 68 + } 69 + 70 + return records.join("\n"); 71 + } 72 + 73 + // main 74 + const did = process.argv[2]; 75 + 76 + if (!did || !did.startsWith("did:")) { 77 + console.error("usage: bun run scripts/publish.ts <did>"); 78 + console.error("example: bun run scripts/publish.ts did:plc:xyz123"); 79 + process.exit(1); 80 + } 81 + 82 + const dnsRecords = await generateDNSRecords(did); 83 + console.log(dnsRecords);
+1
libs/lexicons/tsconfig.build.json
··· 1 1 { 2 2 "extends": "./tsconfig.json", 3 + "include": ["lib"], 3 4 "compilerOptions": { 4 5 "noEmit": false, 5 6 "outDir": "./dist"
+1 -1
libs/lexicons/tsconfig.json
··· 1 1 { 2 2 "extends": "@cookware/tsconfig/base.json", 3 - "include": ["lib"], 3 + "include": ["lib", "scripts"], 4 4 "compilerOptions": { 5 5 "noEmit": true 6 6 }