Skip to content

fix(oas:sync): reuse an existing tag folder regardless of hyphen/space spelling - #51

Merged
rossrdme merged 2 commits into
mainfrom
fix/oas-sync-folder-slug
Sep 22, 2026
Merged

rossrdme merged 2 commits into
mainfrom
fix/oas-sync-folder-slug

Conversation

@rossrdme

@rossrdme rossrdme commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Category folders derived from a spec tag were only lowercased (safeSegment(op.tag).toLowerCase()), not slugified. A spec tag containing spaces (e.g. Shipping Labels) produced a folder named literally shipping labels, instead of landing on the platform's shipping-labels folder already on disk — so every sync created a duplicate category folder for the same tag instead of reusing the existing one.
  • Reproduced against a real customer reference tree whose OAS spec uses space-separated tag names: running oas:sync created brand-new space-separated duplicate folders alongside the hyphenated ones the platform's own OAS-upload had already created on disk.
  • Hyphen vs. space isn't a "one is correct" situation — they're just two spellings of the same folder. The fix has two parts:
    1. Added slugifyFolder(), applied in operationGroup() (both the tag-folder and untagged path-folder branches) and in the declaredOrder tag-ordering computation in syncOneOas. It's used both as an equivalence key (to recognize e.g. Shipping Labels / shipping labels / shipping-labels as the same folder) and, when nothing already exists under any spelling, as the spelling used to create a new folder — matching what a fresh platform OAS-upload would produce.
    2. Whatever spelling is already on disk is authoritative. resolveFolder(), memoized per OAS file per sync run, looks for an existing folder whose slugified name matches before falling back to creating the hyphenated default. Used both when placing new operation pages and when backfilling a missing category index.md, so an operation always lands in whichever folder is actually there instead of spawning a second, differently-spelled one next to it.
  • Updated the existing path-traversal-sanitization test to match the now-more-aggressive tag slugification, and added regression tests covering: a spaced tag reusing an existing hyphenated folder, a spaced tag's own pre-existing space-separated folder being reused as-is (not replaced by a hyphenated duplicate), and the category-index backfill pass resolving the same folder regardless of its spelling.

Test plan

  • node --test src/**/*.test.js test/*.test.js — 184/184 passing
  • Reproduced the bug against a real customer docs repo by syncing an updated OAS spec: before the fix, duplicate space-separated folders were created alongside existing hyphenated ones; after the fix, new operations land in the existing hyphenated folders with no duplicates
  • Verified reuse works in both directions with unit tests: a hyphenated folder already on disk is reused, and a space-separated folder already on disk is also reused as-is (no forced re-hyphenation of existing content)

@greptile-apps

greptile-apps Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

The PR appears safe to merge; no actionable regression remains in the changes since the previous review.

Reviews (5) · Last reviewed commit: "refactor(oas:sync): resolve group folder..."

Comment thread src/commands/oas-sync.js
greptile-apps[bot]
greptile-apps Bot previously approved these changes Sep 21, 2026
@greptile-apps
greptile-apps Bot dismissed their stale review September 21, 2026 23:07

Dismissed because a newer commit was pushed; Greptile will re-review the current head.

greptile-apps[bot]
greptile-apps Bot previously approved these changes Sep 21, 2026
…e spelling

Tag-derived category folders were only lowercased, not slugified, so a spec
tag with spaces (e.g. "Shipping Labels") produced a folder named literally
"shipping labels" instead of landing on the platform's own hyphenated
"shipping-labels" folder — creating a duplicate category on every sync
instead of reusing the existing one.

Hyphen vs. space isn't a "one spelling is correct" situation: they're just
two spellings of the same folder. Added `slugifyFolder()`, used both as an
equivalence key (so "Shipping Labels" / "shipping labels" / "shipping-labels"
are recognized as the same folder) and, when nothing exists under any
spelling yet, as the spelling used to create a new folder, matching what a
fresh platform OAS-upload would produce. Added `resolveFolder()`, memoized
per OAS file per sync run, which looks for an existing folder whose
slugified name matches before falling back to the hyphenated default — used
both when placing new operation pages and when backfilling a missing
category index.md, so whatever's already on disk wins.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@rossrdme
rossrdme force-pushed the fix/oas-sync-folder-slug branch from 74074fb to 89dcfd2 Compare September 21, 2026 23:13
@greptile-apps
greptile-apps Bot dismissed their stale review September 21, 2026 23:13

Dismissed because a newer commit was pushed; Greptile will re-review the current head.

@rossrdme rossrdme changed the title fix(oas:sync): slugify tag folders so spaced tags match the platform's hyphenated ones fix(oas:sync): reuse an existing tag folder regardless of hyphen/space spelling Sep 21, 2026
greptile-apps[bot]
greptile-apps Bot previously approved these changes Sep 21, 2026
Comment thread src/commands/oas-sync.js Outdated

let resolved = slug;
try {
for (const entry of fs.readdirSync(apiDir, { withFileTypes: true })) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how many entries could we see here? hundreds? Thousands? 10k+? I worry about the perf of walking each item like this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Response from Claude) Replaced the per-slug directory walk with a single readdirSync call into a slug -> actual name map. Folder resolution is now an O(1) lookup — O(entries) total per API file instead of O(tags × entries).

Comment thread src/commands/oas-sync.js Outdated
// to be created). Memoized per OAS file per run.
const apiDir = path.join(refDir, infoTitle);
const resolvedFolders = new Map();
function resolveFolder(slug) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔧 if we can. lets not declare this inside of a function.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Response from Claude) Moved to a top-level existingFoldersBySlug() helper, matching the other helpers in this file. Also resolves the performance issue noted below.

…a nested closure

Addresses review feedback from flinehan on #51:
- resolveFolder() re-walked apiDir with fs.readdirSync on every distinct
  tag/group not yet memoized, which is O(tags x entries) for an API with
  many tags and a large existing reference tree.
- resolveFolder() was declared as a closure nested inside syncOneOas,
  inconsistent with every other helper in this file being top-level.

Replaces it with existingFoldersBySlug(apiDir), a top-level function that
builds a slug -> actual-name Map with a single readdirSync call; folder
resolution at each call site is now foldersBySlug.get(folder) || folder,
an O(1) lookup. No behavior change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@greptile-apps
greptile-apps Bot dismissed their stale review September 21, 2026 23:52

Dismissed because a newer commit was pushed; Greptile will re-review the current head.

@rossrdme
rossrdme merged commit 95a8e51 into main Sep 22, 2026
4 checks passed
@rossrdme
rossrdme deleted the fix/oas-sync-folder-slug branch September 22, 2026 18:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants