Skip to content

fix(oas:sync): match the platform's OAS-upload output (tag index pages, root ordering, slug casing)#35

Open
rossrdme wants to merge 1 commit into
mainfrom
fix/oas-sync-upload-parity
Open

fix(oas:sync): match the platform's OAS-upload output (tag index pages, root ordering, slug casing)#35
rossrdme wants to merge 1 commit into
mainfrom
fix/oas-sync-upload-parity

Conversation

@rossrdme

Copy link
Copy Markdown
Contributor

Problem

oas:sync produced a different tree than uploading the same OAS spec to ReadMe, so a repo initialized by upload drifted after a local sync:

  • Tag category pages (<tag>/index.md) were never generated — the tag's description from the spec's top-level tags array was silently dropped.
  • The root reference/_order.yaml was never written, losing top-level ordering.
  • Slugs kept operationId casing (getUserById.md) where the platform lowercases (getuserbyid.md), diverging page URLs.
  • Generated pages omitted hidden: false.

Fix

In syncOneOas:

  • Generate <tag>/index.md (title = tag name, excerpt = tag description) when creating a tag's first page — never overwriting an existing index, mirroring the existing never-clobber rule for op pages.
  • Maintain the root reference/_order.yaml alongside the existing tag/op order files.
  • Lowercase generated slugs; include hidden: false in op-page frontmatter.

Verification

  • Clean-room parity test: wiped a real ReadMe Git Sync repo's reference/ down to the spec, ran the patched sync, and diffed against the platform's actual upload output (committed state): identical file trees, byte-identical _order.yaml files, frontmatter matching on every page. Only remaining delta is a trailing newline (we emit POSIX-standard final newline; the upload does not).
  • Idempotent: immediate re-run reports "already in sync" with zero changes.
  • Behavior matrix (empirical, sandboxed): add endpoint to existing tag ✅; add new tag (dir + index + ordering) ✅; remove endpoint from tag ✅. Known gap: when a tag loses its last endpoint, the generated index.md and the tag's entry in the parent _order.yaml are left behind — deliberately out of scope here (deleting an index risks eating hand-authored category pages; needs a provenance guard).
  • Tests: 4 added (tag index generated with description; root _order.yaml maintained; existing index.md never overwritten; lowercased slugs + hidden: false), existing tests updated for the lowercase-slug behavior change. 89/89 passing.

Independent of #33 and #34.

🤖 Generated with Claude Code

oas:sync generated a different tree than uploading the same spec to
ReadMe, so a repo initialized by upload drifted after a local sync:

- Tag category pages (<tag>/index.md) were never generated, silently
  dropping the tag's description from the spec's top-level tags array.
- The root reference/_order.yaml was never written, losing top-level
  ordering.
- Slugs kept the operationId casing (getUserById.md) where the platform
  lowercases (getuserbyid.md), diverging URLs.
- Generated pages omitted hidden: false.

Generate the tag index.md (title from tag name, excerpt from tag
description, never overwriting an existing one), maintain the root
_order.yaml, lowercase slugs, and include hidden: false.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

OpenAPI synchronization now generates visible operation pages and per-tag index.md pages with optional tag descriptions. Generated operation slugs are lowercased, existing tag indexes are preserved, and root reference ordering includes the specification title. Tests cover frontmatter, tag index generation, ordering behavior, operation collisions, and preservation of existing pages.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/commands/oas-sync.js (1)

253-253: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider generating tag index pages even when operation pages already exist.

Because this continue skips the rest of the loop for already-synced operations, missing tag category pages (index.md) and their corresponding _order.yaml updates won't be backfilled for tags whose operation pages were generated in previous runs.

If the goal is to fully align with the platform and ensure all tags have an index.md regardless of their operations' page status, consider moving the tag index generation logic (lines 268-277) and the relevant _order.yaml updates above this check.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/oas-sync.js` at line 253, Update the operation-processing loop
so tag index generation and its corresponding _order.yaml updates execute before
the pagesByOpId.has(opId) early continue. Preserve the continue for
already-existing operation pages, while still backfilling missing tag index.md
pages and ordering entries.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/commands/oas-sync.js`:
- Line 257: Update the slug derivation in the operation-page generation flow
around safeSegment to reserve the "index" slug: when the normalized operation
slug equals "index", append a distinct suffix before constructing pagePath,
while preserving existing slugs for all other operations.

---

Nitpick comments:
In `@src/commands/oas-sync.js`:
- Line 253: Update the operation-processing loop so tag index generation and its
corresponding _order.yaml updates execute before the pagesByOpId.has(opId) early
continue. Preserve the continue for already-existing operation pages, while
still backfilling missing tag index.md pages and ordering entries.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: dc454943-2f4a-4b94-9bcc-52610b01a54c

📥 Commits

Reviewing files that changed from the base of the PR and between 7c5c920 and 2c738b2.

📒 Files selected for processing (2)
  • src/commands/oas-sync.js
  • test/oas-sync.test.js
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • readmeio/ai (manual)
  • readmeio/gitto (manual)
  • readmeio/markdown (manual)
  • readmeio/readme (manual)

Comment thread src/commands/oas-sync.js
const slug = safeSegment(opId, 'operation');
const rawTag = op.tag || 'Other';
const tag = safeSegment(rawTag, 'Other');
const slug = safeSegment(opId, 'operation').toLowerCase();

@coderabbitai coderabbitai Bot Jul 17, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Prevent the index slug from colliding with the tag category page.

If an operation's ID results in the slug 'index' (e.g., operationId: "index"), pagePath will evaluate to index.md. This causes the operation page to silently overwrite the newly generated tag category page (index.md) at line 279, pushing the exact same path to changes.added twice and incorrectly adding 'index' to the tag's _order.yaml.

Consider appending a suffix to the slug if it resolves to 'index' to prevent this collision.

🐛 Proposed fix to prevent collision
-    const slug = safeSegment(opId, 'operation').toLowerCase();
+    let slug = safeSegment(opId, 'operation').toLowerCase();
+    if (slug === 'index') {
+      slug = 'index-operation';
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const slug = safeSegment(opId, 'operation').toLowerCase();
let slug = safeSegment(opId, 'operation').toLowerCase();
if (slug === 'index') {
slug = 'index-operation';
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/oas-sync.js` at line 257, Update the slug derivation in the
operation-page generation flow around safeSegment to reserve the "index" slug:
when the normalized operation slug equals "index", append a distinct suffix
before constructing pagePath, while preserving existing slugs for all other
operations.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yeah this is a valid issue. We should deduplicate any index operation ID pages so they don't collide with the parent page index.md files we expect.

And I don't think that the bot's suggestion to do index-operation is going to work because you could have an operation called index-operation. We'll need to do some unique slug generation work here to add -<num> suffixes to this instead.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline test/oas-sync.test.js --items all --type function
rg -n -C 5 -P '(operation-name collision|index\.md|operationId:\s*[\"\x27]index|destination already exists|lowercase)' test/oas-sync.test.js
rg -n -C 4 -P '\bpagePath\b|\bindexPath\b|\bslug\b|fs\.existsSync' src/commands/oas-sync.js

Length of output: 7583


@erunion, confirmed. The current test suite covers tag-index creation/preservation and normal operation-path collisions, but not the reserved index operation case.

The change should reserve index.md exclusively for the category page and, only when the normalized operation slug is index, probe index-1.md, index-2.md, etc. until it finds an available destination. Non-index slug collisions should retain the current skip behavior.


✏️ Learnings added
Learnt from: erunion
Repo: readmeio/cli PR: 35
File: src/commands/oas-sync.js:257-257
Timestamp: 2026-07-17T16:16:41.243Z
Learning: In `src/commands/oas-sync.js`, each OpenAPI tag directory reserves `index.md` for its tag category landing page. An operation whose normalized lowercased `operationId` is `index` must instead receive the first available numeric slug (`index-1`, `index-2`, and so on), rather than a fixed suffix, to avoid collisions with both the landing page and other operation pages.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment thread src/commands/oas-sync.js
file: oasFilename,
operationId,
},
hidden: false,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why do we need to add this in here? Can we rely on the defaults in our backend?

Comment thread src/commands/oas-sync.js
const slug = safeSegment(opId, 'operation');
const rawTag = op.tag || 'Other';
const tag = safeSegment(rawTag, 'Other');
const slug = safeSegment(opId, 'operation').toLowerCase();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yeah this is a valid issue. We should deduplicate any index operation ID pages so they don't collide with the parent page index.md files we expect.

And I don't think that the bot's suggestion to do index-operation is going to work because you could have an operation called index-operation. We'll need to do some unique slug generation work here to add -<num> suffixes to this instead.

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