From 9cca8cb6ee59b2bc2879338f3d1fde643492c400 Mon Sep 17 00:00:00 2001 From: Florian <45694132+flo-bit@users.noreply.github.com> Date: Sun, 3 May 2026 19:29:41 +0200 Subject: [PATCH] quick follow feed fix --- .changeset/feed-follow-pull-nsids.md | 5 ++ packages/lexicons/src/generate.ts | 10 ++- packages/lexicons/tests/generate.test.ts | 78 ++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 .changeset/feed-follow-pull-nsids.md diff --git a/.changeset/feed-follow-pull-nsids.md b/.changeset/feed-follow-pull-nsids.md new file mode 100644 index 0000000..f64f452 --- /dev/null +++ b/.changeset/feed-follow-pull-nsids.md @@ -0,0 +1,5 @@ +--- +"@atmo-dev/contrail-lexicons": patch +--- + +fix: resolve `feeds[*].follow` short names to NSIDs when emitting `lex.config.js`. previously the generator pushed the raw short name (e.g. `"follow"`) into `pull.sources[0].nsids`, causing `lex-cli pull` to fail with `ValitaError: must be valid nsid`. now matches the existing `collections` / `profiles` resolution path; feeds pointing at unknown collections are skipped instead of leaking `undefined`. diff --git a/packages/lexicons/src/generate.ts b/packages/lexicons/src/generate.ts index ed8bb63..9ca0dd2 100644 --- a/packages/lexicons/src/generate.ts +++ b/packages/lexicons/src/generate.ts @@ -1031,7 +1031,15 @@ export function generateLexicons(options: GenerateOptions): Record (typeof p === "string" ? p : p.collection) ); - const feedFollowNsids = config.feeds ? Object.values(config.feeds).map((f) => f.follow) : []; + // f.follow is a short name (a key in config.collections), not an NSID — resolve + // it before pushing into the pull list, otherwise lex-cli pull rejects it as + // "must be valid nsid". Filter out any feed pointing at a non-existent + // collection so we never emit an undefined. + const feedFollowNsids = config.feeds + ? Object.values(config.feeds) + .map((f) => config.collections[f.follow]?.collection) + .filter((nsid): nsid is string => typeof nsid === "string") + : []; const pullNsids = new Set([...collectionNsids, ...profileNsids, ...feedFollowNsids]); for (const ref of allRefs) { if (!ref.startsWith("com.atproto.")) pullNsids.add(ref); diff --git a/packages/lexicons/tests/generate.test.ts b/packages/lexicons/tests/generate.test.ts index 44f45c5..6a8e884 100644 --- a/packages/lexicons/tests/generate.test.ts +++ b/packages/lexicons/tests/generate.test.ts @@ -373,3 +373,81 @@ describe("manifest emission (lexicons/generated/index.ts)", () => { } }); }); + +describe("runtime files: lex.config.js pull NSIDs", () => { + let workdir: string; + + beforeAll(() => { + workdir = mkdtempSync(join(tmpdir(), "contrail-feedpull-")); + }); + afterAll(() => { + rmSync(workdir, { recursive: true, force: true }); + }); + + function readPullNsids(): string[] { + const lexConfig = readFileSync(join(workdir, "lex.config.js"), "utf-8"); + const match = lexConfig.match(/nsids:\s*(\[[\s\S]*?\])/); + if (!match) throw new Error("could not locate pull nsids array in lex.config.js"); + return JSON.parse(match[1]); + } + + it("resolves feed.follow short names to NSIDs (regression: lex-cli pull rejects bare short names)", () => { + const config: ContrailConfig = { + namespace: "test.app", + collections: { + follow: { collection: "app.bsky.graph.follow" }, + event: { collection: "community.lexicon.calendar.event" }, + }, + feeds: { + network: { follow: "follow", targets: ["event"] }, + }, + }; + + generateLexicons({ + config, + rootDir: workdir, + lexiconDirs: [], + writeRuntimeFiles: true, + quiet: true, + }); + + const nsids = readPullNsids(); + + // Every entry must be a valid NSID — at least one dot, and never equal a feed short name. + const feedShortNames = Object.keys(config.collections); + for (const nsid of nsids) { + expect(nsid).toMatch(/\./); + expect(feedShortNames).not.toContain(nsid); + } + + // The follow collection's NSID is included via the feeds path. + expect(nsids).toContain("app.bsky.graph.follow"); + }); + + it("skips feeds whose follow short name is missing from collections (no undefineds in pull list)", () => { + const config = { + namespace: "test.app", + collections: { + event: { collection: "community.lexicon.calendar.event" }, + }, + feeds: { + broken: { follow: "doesNotExist", targets: ["event"] }, + }, + } as unknown as ContrailConfig; + + generateLexicons({ + config, + rootDir: workdir, + lexiconDirs: [], + writeRuntimeFiles: true, + quiet: true, + }); + + const nsids = readPullNsids(); + for (const nsid of nsids) { + expect(typeof nsid).toBe("string"); + expect(nsid).toMatch(/\./); + } + expect(nsids).not.toContain("doesNotExist"); + }); +}); -- 2.51.2