feat: add an outline command over document-outline.js - #242
Merged
Conversation
…ge gate The outline command below reads document-outline.js's buildOutline over a ConversionResult.package, so the package becomes a real runtime dependency rather than a vendored copy. The minimumReleaseAgeExclude entry is a bare package name, matching every other sibling already listed: these are the org's own packages, published only through their own semantic-release CI with npm OIDC trusted publishing, so a version-agnostic exemption avoids re-pinning the file on every release. minimumReleaseAge itself is unchanged at 60, since that is the only setting `pnpm install --frozen-lockfile` honours on the CI path.
…projection Prints headings, list items, and the synthetic slide/sheet/page groups as indented text, two spaces per nesting depth, with leaves rendering their own text (a paragraph's runs, a table's cell text, an image's alt text) or their kind in brackets when they carry none. Indentation comes from tree depth, never from an OutlineNode's own `level`: that field is the source's level signal, and the heading and list scales legitimately coexist within one tree, so a level-0 list item can sit inside a level-1 slide group. The command reaches a DocumentPackage by running the cheapest conversion that still populates one -- a PDF-bypassing bridge to a sibling format, so no layout engine runs -- and discards the resulting bytes. pdf and odf take the one conversion each actually has. An absent package fails loudly rather than printing nothing, since the local converter populates one for every conversion and its absence would mean a broken port contract, not an empty document. --json emits document-outline.js's own tree verbatim rather than a CLI-private shape, so a consumer can walk it with that package's own isOutlineNode/isOutlineChild guards.
… the family Adds the command's own section, its entry in the flag-registration paragraph (the shared flags minus --out, since it prints to stdout and writes no file), its mention in the two command lists that enumerate what `formats` does not cover, and document-outline.js as a node in the dependency graph.
OUTLINE_CONVERSION_TARGET.odg was 'svg', and buildSvgText refuses to write a document with more than one page (SvgMultiPageNotSpecifiedError) since this command has no --page flag to answer it with. Every multi-page .odg therefore failed outright with an error naming a write-side target format the caller never asked to convert to. odp is a registered odg conversion pair with no per-document page-selection constraint, so it bridges cleanly regardless of page count. The trade-off is that each drawing page becomes a presentation slide group (labelled "Slide N", document-outline.js's own presentation convention) rather than a "Page N" draw-page group, since odp is a cross-variant bridge and svg -- the only same-variant target odg has -- is exactly the format whose write side cannot hold more than one page.
…tline A spreadsheet source outlines to sheet-group labels only -- cells are addressable data, not outline content -- so a csv source's --delimiter had nothing downstream to change: outline semi.csv and outline semi.csv --delimiter ';' printed the identical output regardless of the flag. Flags are registered only on commands whose pair can actually reach the edge in question; advertising one that cannot do anything invites a caller to believe it has an effect it doesn't.
… can't resolve
inferFormatFromExtension('-') always returns undefined by design, so
outline - failed before ever reaching the markdown-image-resolver line
that special-cases stdin -- that branch was genuinely unreachable dead
code, and the failure message told a stdin caller to "rename the file
with a recognised extension", advice that makes no sense for a pipe.
resolveSourceFormat gives an explicit --from priority over extension
inference, mirroring resolveTargetFormat's own --to resolution order
(commands/shared.ts) on the source side. This makes stdin genuinely
usable (outline - --from docx) rather than only rewording the error,
and it makes the stdin arm of the image-resolver ternary reachable
again, since resolveSourceFormat now requires --from for '-' instead
of failing before the conversion ever runs.
…tline content
lines.join('\n') on an empty array is '', and the write appended '\n'
unconditionally, so a document with nothing to outline (no headings, no
list items, no leaves) printed one stray blank line instead of no output
at all. --json already handled this correctly, printing a bare [].
…rces "headings nested by heading level" doesn't hold across the board for docx or odt: readDocxContent/readOdtContent correctly recover headingLevel from w:outlineLvl/text:outline-level when the file on disk actually carries it -- true of anything Word or LibreOffice authors, and always true of a markdown source parsed straight from #/## -- but buildDocxPackage and buildOdtPackage never write that signal back out (no w:outlineLvl, no text:h element), so a docx or odt this ecosystem's own conversions produce loses heading nesting even though its paragraphs still carry a Heading1/Heading2-style reference. List nesting and slide/sheet/page grouping are unaffected either way.
…a real round trip The smoke suite is this repo's convention for proving every command family works through the actual built dist/cli.js, not just the in-process command tree -- outline had neither a --help mention nor a subprocess-level case, so a green smoke run proved nothing about it.
Contributor
|
🎉 This PR is included in version 3.1.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds an
outlinecommand that prints a document's table-of-contents projection — headings, list items, and the synthetic slide/sheet/page groups — as indented text, or as JSON with--json.This makes document-cli the first real consumer of
document-outline.js, which just cut its 1.0.0 (see ExaDev/document-outline.js#2 for the phase-1 work that produced it).Indentation comes from tree depth, deliberately not from an
OutlineNode's ownlevel. That field carries the source's level signal — a heading'sheadingLevel, a list item'slist.level, 1 for the synthetic groups — and those scales legitimately coexist in one tree, so a level-0 list item can sit inside a level-1 slide group. Depth is unambiguous and recoverable from the nesting itself.To get a
DocumentPackageto project, the command runs the cheapest conversion that still populates one and throws the output bytes away: a PDF-bypassing bridge to a sibling format, so no layout engine runs.pdfandodftake the one conversion each of them actually has. The internal conversion's diagnostics still reach stderr — a PDF reconstruction's parse warnings show up exactly as they would onpdf-to-docx. An absentpackagefails loudly rather than printing nothing: the port declares the field optional, but the local converter populates one for every conversion, so its absence is a broken contract, not an empty document.--jsonemits document-outline.js's tree verbatim rather than a CLI-private shape, so a consumer can walk it with that package's ownisOutlineNode/isOutlineChildguards.document-outline.jsgoes intominimumReleaseAgeExcludeas a bare package name, matching the seven siblings already listed — same org, same semantic-release + npm OIDC trusted publishing path, so a version-agnostic exemption avoids re-pinning the file on every release.minimumReleaseAgestays at 60; that is the only one of the two settingspnpm install --frozen-lockfilehonours, so CI is unaffected either way.Tested against a markdown source (heading and list nesting), an ods source (per-sheet groups), a pdf source (the reconstruction path, the one format that cannot be bridged), and a
--jsonrun pinning the tree shape.Typecheck, lint, unit tests, workers tests, and build are all green locally.
Generated by Claude Code