refactor: delegate markdown + changelog to @doist/cli-core#210
Merged
Conversation
doistbot
reviewed
May 9, 2026
Member
doistbot
left a comment
There was a problem hiding this comment.
This is a great refactor that successfully streamlines the CLI by delegating markdown rendering and the changelog command to @doist/cli-core. Relying on shared core utilities will significantly reduce maintenance overhead and improve long-term consistency. A few edge cases require attention, including safely handling parallel markdown preloading to prevent unhandled rejections, preventing unsupported core flags from leaking into the global arguments shape, improving the robustness of lazy markdown initialization and NDJSON parsing, and refining tests to properly cover Twist-specific changelog options and preload bypass behavior.
scottlovegrove
added a commit
that referenced
this pull request
May 9, 2026
- src/lib/markdown.ts: renderMarkdown now self-inits via preloadMarkdown() rather than silently returning raw content when not preloaded. The bootstrap preload in src/index.ts becomes a pure perf optimization (parallelize with module load); correctness no longer depends on it. - src/lib/global-args.ts: narrow exported TwGlobalArgs to drop quiet/ verbose, which twist does not register with Commander. The internal store keeps the full cli-core shape so the shared gate helpers (createSpinnerGate, createAccessibleGate) still satisfy `T extends GlobalArgs`. Add isNdjsonMode() helper matching isJsonMode(). - src/index.ts: use isNdjsonMode() instead of an argv string-search; await loader() and preloadMarkdown() via Promise.all so a preload rejection co-rejects with the loader rather than leaking as an unhandledRejection. - src/commands/changelog.test.ts: extend the wrapper-test fixture with flexible heading levels, a deps-only release, and a wrapped-bullet continuation line. Add assertions that lock down the three twist- specific options (headingLevel: 'flexible', filterEmptyVersions: true, continuationIndent: true) — regressions in any one would now fail the suite rather than slip through. - src/lib/markdown.test.ts: drop the beforeAll(preloadMarkdown) hook now that renderMarkdown self-inits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- src/lib/markdown.ts now delegates preloadMarkdown / renderMarkdown to
@doist/cli-core/markdown, keeping the local twist-mention:// → @mention
preprocessor on top.
- src/commands/changelog.ts is a thin wrapper over registerChangelogCommand
from @doist/cli-core/commands, passing path, repoUrl, version, plus
twist-specific options (headingLevel: 'flexible', continuationIndent,
filterEmptyVersions) so the rendered output is unchanged.
- src/index.ts kicks off preloadMarkdown() in parallel with the dynamic
command-module load when the output mode renders markdown, so the import
cost overlaps with module load and adds no perceptible latency. Skipped
for json/ndjson/raw and non-rendering commands (changelog/update/etc.).
- src/index.ts top-level error handler now matches BaseCliError so errors
thrown from cli-core helpers (e.g. CliError('INVALID_TYPE') from the
shared changelog command) route through the existing formatError path.
src/lib/errors.ts re-exports BaseCliError; src/lib/output.ts widens the
formatError / formatErrorJson parameter types accordingly.
- Drops the local marked-terminal-renderer ambient .d.ts shim now that
cli-core owns the typing surface.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- src/lib/markdown.ts: renderMarkdown now self-inits via preloadMarkdown() rather than silently returning raw content when not preloaded. The bootstrap preload in src/index.ts becomes a pure perf optimization (parallelize with module load); correctness no longer depends on it. - src/lib/global-args.ts: narrow exported TwGlobalArgs to drop quiet/ verbose, which twist does not register with Commander. The internal store keeps the full cli-core shape so the shared gate helpers (createSpinnerGate, createAccessibleGate) still satisfy `T extends GlobalArgs`. Add isNdjsonMode() helper matching isJsonMode(). - src/index.ts: use isNdjsonMode() instead of an argv string-search; await loader() and preloadMarkdown() via Promise.all so a preload rejection co-rejects with the loader rather than leaking as an unhandledRejection. - src/commands/changelog.test.ts: extend the wrapper-test fixture with flexible heading levels, a deps-only release, and a wrapped-bullet continuation line. Add assertions that lock down the three twist- specific options (headingLevel: 'flexible', filterEmptyVersions: true, continuationIndent: true) — regressions in any one would now fail the suite rather than slip through. - src/lib/markdown.test.ts: drop the beforeAll(preloadMarkdown) hook now that renderMarkdown self-inits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
407352e to
f642b99
Compare
|
✅ |
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.
Summary
Ports todoist-cli#319 to twist-cli — delegates markdown rendering and the
changelogcommand to@doist/cli-core@0.9.0(already pinned), and widens the top-level error handler so cli-core-thrown errors route through twist's formatter.src/lib/markdown.tsnow delegatespreloadMarkdown/renderMarkdownto@doist/cli-core/markdown, keeping the localtwist-mention://→@mentionpreprocessor on top. Drops the localmarked-terminal-rendererambient.d.tsshim now that cli-core owns the typing.src/index.tskicks offpreloadMarkdown()in parallel with the dynamic command-module load when the output mode renders markdown (skipped for--json/--ndjson/--rawand non-rendering commands likechangelog/update/completion/doctor/auth/config/skill). The import cost overlaps with module load — zero added latency.src/commands/changelog.tsis now a thin wrapper overregisterChangelogCommandfrom@doist/cli-core/commands, passing twist-specific options (headingLevel: 'flexible',continuationIndent: true,filterEmptyVersions: true) so rendered output is unchanged. Local behavior tests deleted — cli-core covers them.src/index.tsnow matchesinstanceof BaseCliError(cli-core class) so errors thrown from cli-core commands route through the existingformatErrorpath. LocalCliErrorextends base, so subclass throws still match.formatError/formatErrorJsonparameter types widened toBaseCliError<string>.Net diff: −301 / +114 lines.
Test plan
npm run lint:check(oxlint + oxfmt)npm run type-check(tsc --noEmit)npm test— 583 tests passnpm run buildtw changelog -n 1— output unchanged (heading green-bold, dimmed bullets, footer link with current version)tw changelog -n abc— cli-core throwsCliError('INVALID_TYPE'); twist's widened handler rendersError: INVALID_TYPE\nCount must be a positive integer, exit 1tw thread view <id>— markdown body renders with ANSI styling (not exercised in CI)tw msg view <id> --json— markdown rendering skipped, output unchanged🤖 Generated with Claude Code