Skip to content

json-simple: stop removing headings from the metadata it shares with other generators #1182

Closed

Description

@ovflowd

json-simple strips heading and stability nodes so its api-docs.json is simpler, but it does that in place, on the metadata entries it receives. Those aren't its own copy: every generator that depends on @doc-kit/core/metadata (legacy-json, legacy-html, json, man-page, llms-txt, jsx-ast, ...) gets the same array of the same objects. So building json-simple together with another target breaks the other one:

  • doc-kit generate -t json-simple legacy-json crashes with TypeError: Cannot read properties of undefined (reading 'name'), because legacy-json expects each entry's content to start with its heading.
  • doc-kit generate -t json-simple legacy-html finishes, but the HTML has no section headings and no stability banners.

The target order and thread count don't matter: json-simple has no worker step, so it changes the shared objects on the main thread before anything else copies them.

A bit of context if you're new here: each generator is one build target, and dependsOn says whose output it reads as input. Results are cached and handed to every dependent as-is, so a generator must treat its input as read-only. section-pages already does this by copying each entry before changing it.

Where to look

  • packages/core/src/generators/json-simple/generate.mjs:19-21: input.forEach(node => remove(node.content, [UNIST.isStabilityNode, UNIST.isHeading])) changes the shared entries.
  • The same file, lines 24 and 27: it writes and returns that same input.
  • packages/node-legacy/src/legacy-json/utils/buildSection.mjs:172 and :85: where legacy-json expects the heading and crashes without it.
  • packages/react/src/section-pages/generate.mjs:85: the existing pattern, structuredClone(original) before changing an entry.

How to reproduce

Save this as foo.md:

# Foo

<!--introduced_in=v1.0.0-->

> Stability: 2 - Stable

The foo module.

## `foo.bar()`

<!-- YAML
added: v1.0.0
-->

* Returns: {string}

Returns bar.

Then, from the repo root:

node packages/cli/bin/cli.mjs generate -i foo.md -t legacy-json -o out-a -v v24.0.0
# works

node packages/cli/bin/cli.mjs generate -i foo.md -t json-simple legacy-json -o out-b -v v24.0.0
# TypeError: Cannot read properties of undefined (reading 'name')

Suggested approach

Have json-simple work on its own copies and leave input alone. Copying content is enough, since that's all remove changes:

const output = input.map(entry => {
  const copy = { ...entry, content: structuredClone(entry.content) };

  remove(copy.content, [UNIST.isStabilityNode, UNIST.isHeading]);

  return copy;
});

Then write and return output instead of input.

How to test

json-simple has no tests yet. Create packages/core/src/generators/json-simple/__tests__/generate.test.mjs, setting up the config like json-all/__tests__/generate.test.mjs does (setConfig({ target: ['json-simple'] }), then set output to undefined so nothing is written). Build an entry where the same heading object is both entry.heading and the first node of entry.content, as the metadata generator makes it, plus a stability blockquote and a paragraph. Then test that:

  1. The input is unchanged: const snapshot = structuredClone(input); await generate(input); assert.deepEqual(input, snapshot);. Same idea as section-pages' "does not mutate the original entries" test. It fails today.
  2. The output still drops them: the returned entry's content.children only has the paragraph.

Run everything with node --run test, and optionally repeat the CLI check above.

Things to watch out for

  • Write and return the new array. If either line still uses input, api-docs.json gets its headings back.
  • Don't swap remove for a shallow children.filter(...): remove is deep and also drops parents that end up empty, so the output would change.
  • Leave the cache alone. Sharing one result between generators is deliberate, to save memory; the rule is that generators don't change their input.
  • The order of entries in api-docs.json can change from build to build, which is a separate issue. Compare contents, not order, when checking by hand.

Getting started

New to doc-kit? CONTRIBUTING.md covers setup, running the tests and adding a changeset (node --run changeset, a patch for @doc-kit/core). Leave a comment here if you'd like to take it on.

Activity

  1. added theissue type on Oct 26, 2026
  2. dxnnystyles commented on Oct 26, 2026

    @dxnnystyles
    Contributor

    i can take this one. i'll make json-simple copy the content it modifies, then add regression tests for input immutability + filtered output.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions