Skip to content

fix(cli): preserve example JSON key order in ledger publish#16672

Open
devin-ai-integration[bot] wants to merge 2 commits into
mainfrom
devin/1782283479-fix-ledger-example-json-key-order
Open

fix(cli): preserve example JSON key order in ledger publish#16672
devin-ai-integration[bot] wants to merge 2 commits into
mainfrom
devin/1782283479-fix-ledger-example-json-key-order

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes ledger publish reordering API example JSON fields alphabetically.

Changes Made

The CLI's publishDocsLedger.ts uses stableStringify to serialize the API manifest blob — this recursively sorts ALL object keys at every nesting level for deterministic hashing, including preformatted API example bodies (requestBody/responseBody).

Before (broken): example { "name": "Monstera", "species": "...", "id": "..." }{ "id": "...", "name": "Monstera", "species": "..." }

After (fixed): example key order preserved as authored.

The fix teaches stableStringify to skip sorting for subtrees rooted at known preformatted keys:

const PREFORMATTED_KEYS = new Set(["requestBody", "responseBody", "payload", "body"]);

function stableStringify(value: unknown): string {
    const preformatted = new WeakSet();
    return JSON.stringify(value, function (key, val) {
        if (PREFORMATTED_KEYS.has(key) && val != null && typeof val === "object") {
            preformatted.add(val);
            return val; // skip sorting for this subtree root
        }
        // propagate the mark to child objects via `this` (parent holder)
        if (this != null && typeof this === "object" && preformatted.has(this)) {
            if (val != null && typeof val === "object") preformatted.add(val);
            return val;
        }
        // sort structural keys for determinism
        if (val != null && typeof val === "object" && !Array.isArray(val)) {
            return Object.fromEntries(Object.entries(val).sort(...));
        }
        return val;
    });
}

Uses a WeakSet to track which objects belong to a preformatted subtree so deeply nested keys (e.g. care.water inside a responseBody) are also preserved. CLI-only change — no FDR changes needed.

Preformatted keys cover all z.unknown() example body fields in the fdr-sdk register contract:

  • Endpoint examples: requestBody, responseBody

  • Webhook examples: payload

  • WebSocket message examples: body

  • Added changelog entry

Testing

  • Unit test added — endpoint with responseBody whose keys are intentionally non-alphabetical; verifies both top-level and nested key order preserved
  • Existing determinism test (Map insertion order stability) continues to pass
  • All 207 tests in @fern-api/remote-workspace-runner pass

Link to Devin session: https://app.devin.ai/sessions/b5df68c7e9ef4f629ca01c7a1f0aac15
Requested by: @broady


Open in Devin Review

Replace recursive stableStringify with JSON.stringify in the CLI's
ledger publish path. The old stableStringify sorted ALL object keys
at every nesting level, which reordered preformatted API example
bodies (request/response JSON) alphabetically.

Now only top-level manifest entry keys (apiDefinitionId) are sorted
for deterministic hashing. Nested content — including API examples —
preserves its original field order.

Co-Authored-By: cbro <cbro@buildwithfern.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration devin-ai-integration Bot left a comment

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.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

…bleStringify

Rework the approach: instead of replacing stableStringify entirely,
teach it to skip recursive key-sorting for subtrees rooted at known
preformatted keys (requestBody, responseBody, payload, body).

Uses a WeakSet to track which objects belong to a preformatted subtree
so deeply nested keys are also preserved. This is CLI-only — no FDR
changes needed.

Co-Authored-By: cbro <cbro@buildwithfern.com>
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.

0 participants