feat(cli,playground,docs,generators): Export LikeC4 views to Draw.io#2622
feat(cli,playground,docs,generators): Export LikeC4 views to Draw.io#2622sraphaz merged 131 commits intolikec4:mainfrom
Conversation
- Docs: Export DrawIO option --output,-o -> --outdir,-o - generate-drawio: pageScale="1" for well-formed XML, mxUserObject after mxGeometry - generate-drawio: flattenMarkdownOrString and isEmptyish for description/technology - parse-drawio: infer system only for swimlane not shape=rectangle+rounded - parse-drawio: toId remove dot from allowed chars to avoid invalid FQN - Playground: DrawioContextMenu refactor with hook and presentational component Co-authored-by: Cursor <[email protected]>
…PR drafts Co-authored-by: Cursor <[email protected]>
…d export/import - Export: view title/description (diagram name + likec4ViewTitle/Description), summary, links, border, opacity, relationship kind/notation, likec4ColorName - Import: diagram name as view id, view title/description from root cell, edge strokeColor to style color, opacity, shape (cylinder/document), summary/links/border/relationshipKind/notation - Add CONVERSION-MAPPING.md documenting mapped and unmapped items - Add pako.d.ts for typecheck; export DiagramInfo from parse-drawio Co-authored-by: Cursor <[email protected]>
…pport - IMPLEMENTATION-PLAN.md: phased plan for full conversion coverage - parseDrawioToLikeC4Multi(xml): merge multiple diagrams into one model, one view per tab with include list - getAllDiagrams(xml): extract all diagram name/id/content from mxfile - First diagram without name attribute still yields view 'index' for backward compatibility - Test: two-tab drawio produces view overview (include A, B) and view detail (include A, C) Co-authored-by: Cursor <[email protected]>
…and single English doc
- Import: emit layout as comment block (likec4.layout.drawio) for round-trip
- Import: emit vertex strokeColor as comment block (DSL has no element strokeColor)
- Import: parse likec4Metadata on edges, emit metadata { } block
- Import: parse Draw.io native style 'link', emit element link when no likec4Links
- Export: add likec4StrokeColor on vertex, likec4Metadata on edge
- Docs: single Draw.io integration page (tooling/drawio.mdx) in English; link from CLI
- Remove CONVERSION-MAPPING.md and IMPLEMENTATION-PLAN.md; add changeset
Co-authored-by: Cursor <[email protected]>
…on, size/padding/textSize/iconPosition - 2.1: Import emit vertex strokeWidth as comment block (likec4.strokeWidth.vertices) - 2.4: Export likec4ViewNotation on root cell when view has notation - 2.5: Export/import element size, padding, textSize, iconPosition (likec4Size etc.) - Export likec4StrokeWidth on vertex for round-trip - Docs: drawio.mdx updated with new mappings Co-authored-by: Cursor <[email protected]>
…ustomData comment Co-authored-by: Cursor <[email protected]>
- Export: GenerateDrawioOptions with layoutOverride, strokeColorByNodeId, strokeWidthByNodeId, edgeWaypoints; view notation string; customData and waypoints in XML. - Import: view notation and edge waypoints comment blocks (single + multi); parseDrawioRoundtripComments() to read comment blocks for re-export. - Playground: getSourceContent from workspace files; export applies round-trip options when comment blocks present. - Playground generate: use PowerShell on Windows (usePowerShell + quotePowerShell) so generate runs without bash. - Generators: export getAllDiagrams, DrawioRoundtripData, parseDrawioRoundtripComments; @types/pako; fix view.notation type and layoutByView possibly undefined. - Docs: drawio.mdx updated with options, waypoints, re-export note. - Tests: parseDrawioRoundtripComments; generate snapshots updated. - Changeset: drawio-roundtrip-options.md. Co-authored-by: Cursor <[email protected]>
- CLI: likec4 export drawio --roundtrip reads .c4 source, parses comment blocks (layout, stroke, waypoints), applies options per view. - Generators: export generateDrawioMulti, parseDrawioRoundtripComments, parseDrawioToLikeC4Multi from main index for CLI/consumers. - Playground: DrawioContextMenu accepts optional getSourceContent for round-trip export when used with explicit props. - Docs: cli.mdx documents --roundtrip and --all-in-one for Export to DrawIO. - E2E: playwright.playground.config.ts and drawio-playground.spec.ts; test:playground script; e2e/.gitignore allows drawio-playground.spec.ts. - Changeset: drawio-cli-roundtrip-e2e.md (likec4, e2e patch). Co-authored-by: Cursor <[email protected]>
Co-authored-by: Cursor <[email protected]>
… correctly Co-authored-by: Cursor <[email protected]>
- Use BBox, ThemeColorValues, RelationshipColorValues from @likec4/core - Add getEffectiveStyles(viewmodel) and resolveThemeColor for DRY - JSDoc for public and key internal helpers - Docs: drawio.mdx export behavior (theme colors, containers behind, edge anchors, layoutOverride BBox) Co-authored-by: Cursor <[email protected]>
…ut fallbacks - useDrawioContextMenuActions: build viewModels from likec4model.views() then fill via getLayoutedModel, viewStates, layoutViews so all tabs export - DrawioContextMenuDropdown: minor copy/tooltip tweaks - README and schema updates Co-authored-by: Cursor <[email protected]>
Export: - Container: keep rounded=0 (no rounded corners) - Elements: rounded=1 + arcSize=0.12 (subtle curve) - UserObject with link for navigateTo; style link=data:page/id,likec4-<viewId> - Docs: navigability and link format Import: - Parse UserObject wrapping mxCell: use id and link from UserObject - navigateTo from link attribute (data:page/id,likec4-<viewId>) for round-trip - Refactor: buildCellFromMxCell for reuse; skip duplicate id from UserObject Playground: - DrawIO context menu font size 10px Co-authored-by: Cursor <[email protected]>
Co-authored-by: Cursor <[email protected]>
- Parse: container=1 -> system, child of container -> component; fillOpacity/likec4Opacity; view title/description/notation from root cell - Merge container title cells (text shape) into container element; exclude from vertex list - inferKind(style, parentCell); single and multi-diagram use containerIdToTitle and byId - generate-drawio.spec: ProcessedView type for viewWithNav; drawio-demo: expected nodes include container title count - CLI: likec4 import drawio described as experimental (import not yet fully validated) Co-authored-by: Cursor <[email protected]>
…r oxlint Co-authored-by: Cursor <[email protected]>
- Add devops/commands/postpack.ts: copy packed tgz to package.tgz (Node fs) - Replace cp ... || true in all 14 packages and styled-system with likec4ops postpack - Enables pnpm lint:package and validate on Windows Co-authored-by: Cursor <[email protected]>
- CI: single check-validate job runs pnpm validate (same as local); e2e/docs/playground depend on it - Root: pnpm validate script (generate -> typecheck -> core type tests -> lint -> build -> lint:package -> test) - Husky: pre-push runs validate when pushing to main - AGENTS.md: document pnpm validate - Changeset: drawio import alignment, CLI experimental, postpack, validate pipeline Co-authored-by: Cursor <[email protected]>
…hangeset" This reverts commit bdc881e.
…changes) Co-authored-by: Cursor <[email protected]>
Co-authored-by: Cursor <[email protected]>
- Export: preserve node parent links (parentId from view hierarchy); container title cell parent = container id - Docs: mention --all-in-one early in Export section - Playground: use layouted diagram for single-view export when available; forEach -> for...of - generate-drawio: hex regex 3/4/6/8 only; remove sourcePoint/targetPoint from Array points; container margins: horizontal = xl, vertical = xl+md (vertical slightly larger) - Remove pako.d.ts, use @types/pako - parse-drawio: likec4LineType only 1 1 / 2 2 = dotted; getAllDiagrams guard empty/invalid content; DRY: stripHtml, stripHtmlForTitle, emitElementToLines, emitEdgesToLines, emitRoundtripComments(Single|Multi) - worker: errToString guard JSON.stringify circular refs - drawio-demo spec: assert reimportViews.length > 0; fix NodeId type in containerCount - Snapshots updated for parent/container changes Co-authored-by: Cursor <[email protected]>
Co-authored-by: Cursor <[email protected]>
- generate-drawio: use view.bounds for page, zero offset when content fits - drawio-demo spec: increase timeout to 15s for vice versa test - handler: use loggable(err) instead of String(err) for error logging - worker: avoid String(err) on object in errToString (return literal) Co-authored-by: Cursor <[email protected]>
…iew LSP request, MonacoEditor props Co-authored-by: Cursor <[email protected]>
…etry Co-authored-by: Cursor <[email protected]>
- Wait for .react-flow.initialized (20s) like export snapshot tests - Increase test timeout to 35s for CI layout delay - Remove arbitrary sleep and fragile canvas wait Co-authored-by: Cursor <[email protected]>
- Remove CLI import drawio command and handlers - Playground: DrawIO menu export only (no Import item, no Monaco import action) - Docs: export-only sections in drawio.mdx and cli.mdx - Tests: skip import/round-trip specs (to re-enable in import PR) - E2E: assert Export to DrawIO and Export all only - Add DRAWIO-PR-SPLIT.md and .pr-description-export.md / .pr-description-import.md Co-authored-by: Cursor <[email protected]>
…, playground) Co-authored-by: Cursor <[email protected]>
Co-authored-by: Cursor <[email protected]>
…fillFromModelView dev warn Co-authored-by: Cursor <[email protected]>
…w modes Co-authored-by: Cursor <[email protected]>
Co-authored-by: Cursor <[email protected]>
feat(cli,playground,docs,generators): Export LikeC4 views to Draw.io
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@packages/generators/src/drawio/parse-drawio.ts`:
- Around line 160-163: getDecodedStyle and decodeRootStyleField call
decodeURIComponent which can throw on malformed percent-encodings and will abort
parsing (affects buildCellOptionalFields calling getDecodedStyle ~20x per cell);
wrap the decodeURIComponent usage in a try/catch and on error return the raw
un-decoded string (or undefined if original was empty) instead of allowing the
exception to propagate, and do the same in decodeRootStyleField so malformed
style values are ignored/returned safely and parsing continues; reference
getDecodedStyle, decodeRootStyleField and buildCellOptionalFields when making
this change.
🧹 Nitpick comments (10)
packages/log/src/index.ts (1)
70-73: Nit:...configspreadssinksandloggersthat are immediately overwritten.On line 72,
...configmay includesinksandloggersfrom the caller, but both are unconditionally overridden on lines 73–74. This is harmless but slightly misleading to readers. Consider destructuring to exclude them:Proposed refactor
const sinks = config?.sinks ?? {} + const { sinks: _sinks, loggers: _loggers, ...restConfig } = config ?? {} const sinksWithConsole: Record<TSinkId | 'console', Sink> = { console: getConsoleSink(), ...sinks, } as Record<TSinkId | 'console', Sink> configureLogtape<TSinkId | 'console', TFilterId>({ reset: true, - ...config, + ...restConfig, sinks: sinksWithConsole,apps/playground/src/components/drawio/DrawioContextMenuProvider.tsx (2)
88-93: Ref assignment during render — consider moving touseEffectfor concurrent-safety.Writing
filesRef.current = filesduring render can be problematic if React discards a render (Suspense, transitions in concurrent mode). Since the ref is only read from event handlers (not render), the practical risk is low, but moving the assignment into auseEffectis the React-recommended pattern.Proposed fix
const filesRef = useRef(files) - filesRef.current = files + useEffect(() => { + filesRef.current = files + }, [files]) const getSourceContent = useCallback(() => { const contents = Object.values(filesRef.current).filter(Boolean) return contents.length > 0 ? contents.join('\n\n') : undefined }, [])
95-105: Conditional spread is fine but slightly obscure — consider explicit assignment for readability.The
...(condition && { ... })pattern works but can be non-obvious at a glance. This is purely a readability nit; no functional issue.packages/likec4/src/cli/export/drawio/handler.ts (1)
258-265: Add an explicit type annotation toexportParamsfor compile-time safety.If a field is later added to
ExportDrawioParams, this untyped object literal won't produce a compile error. Addingsatisfies ExportDrawioParams(or a: ExportDrawioParamsannotation) catches drift early.Suggested change
- const exportParams = { + const exportParams: ExportDrawioParams = { viewmodels, outdir: args.outdir, workspacePath: args.path, roundtrip: args.roundtrip, uncompressed: args.uncompressed, logger, }packages/log/src/formatters.ts (1)
107-107: Remove commented-out code.Line 107 (
// const format = options?.format) is a leftover artifact that adds noise.Proposed fix
- // const format = options?.formatpackages/likec4/src/cli/export/png/handler.ts (1)
236-239: Minor:pngHandlercreates a logger with prefix'export'while JSON uses'c4:export'.The logger prefix inconsistency (
'export'here vs'c4:export'in the JSON handler at line 122 of json/handler.ts) produces different log output formatting. Consider aligning them if a consistent prefix is desired.packages/generators/src/drawio/parse-drawio.ts (3)
983-989: Empty elements still emit{ }block — is this intentional?When
hasBodyis false, the code emits an element header followed by an empty{ }block. In LikeC4, a simple declaration without braces (myElem = container 'Title') is valid and more concise. If the empty block is required by the parser, this is fine — otherwise it adds visual noise to generated output.
1019-1025: Operator precedence intitlePartternary chain is hard to read.const titlePart = title ? ` '${title}'` : desc || tech ? ` ''` : ''The
||binds tighter than?:, so this works correctly astitle ? ... : (desc || tech) ? ... : ''. However, adding parentheses would improve clarity.♻️ Suggested clarification
- const titlePart = title ? ` '${title}'` : desc || tech ? ` ''` : '' + const titlePart = title ? ` '${title}'` : (desc || tech) ? ` ''` : ''
1438-1456:DiagramStateis declared after its first usage.
CommonDiagramStateFromCellsat Line 1314 referencesDiagramState, which is defined 120+ lines later at Line 1438. TypeScript hoists type declarations so this compiles, but it hurts readability. Consider moving theDiagramStateandSingleDiagramStatetype declarations before their first use (beforebuildCommonDiagramStateFromCells).packages/generators/src/drawio/generate-drawio.ts (1)
1293-1314:buildDrawioExportOptionsFromSourceparses round-trip once and distributes — clean design.One minor observation:
buildDrawioExportOptionsForViews(Line 1324) callsbuildDrawioExportOptionsFromSourceper view, which re-parses the entire.c4source each time. For projects with many views, parsing once and distributing would be more efficient.♻️ Suggested optimization
export function buildDrawioExportOptionsForViews( viewIds: string[], sourceContent: string | undefined, overrides?: Partial<GenerateDrawioOptions>, ): Record<string, GenerateDrawioOptions> { + if (!sourceContent) { + return Object.fromEntries(viewIds.map(id => [id, { compressed: false, ...overrides }])) + } + const roundtrip = parseDrawioRoundtripComments(sourceContent) return Object.fromEntries( viewIds.map(viewId => [ viewId, - buildDrawioExportOptionsFromSource(viewId, sourceContent, overrides), + buildOptionsFromRoundtrip(viewId, roundtrip, overrides), ]), ) }Extract a helper that takes the already-parsed
DrawioRoundtripData | nullsoparseDrawioRoundtripCommentsruns once.
…filesRef useEffect, exportParams type, formatters typo, titlePart parens, png logger, buildOptionsFromRoundtrip parse-once) Co-authored-by: Cursor <[email protected]>
feat(cli,playground,docs,generators): Export LikeC4 views to Draw.io
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@packages/generators/src/drawio/parse-drawio.ts`:
- Around line 107-123: The getAttr function can match attribute name suffixes
(e.g., matching "userid=" when searching for "id="); fix getAttr by ensuring the
found needle has a word-boundary before it: after finding start via indexOf,
verify that start === 0 or the character at start - 1 is whitespace (space, tab,
newline) before accepting the match, and only then parse the quoted value as
currently done; update the loop in getAttr to continue searching if the
preceding character is not a valid boundary so it returns the correct attribute
value.
In `@PR-DESCRIPTION.md`:
- Around line 47-49: Edit the three consecutive bullets in PR-DESCRIPTION.md so
they don't all start with "No": keep the first as-is and reword the next two for
variety and clarity (for example, change the second to "Missing `likec4 import
drawio` command (no `packages/likec4/src/cli/import/`)" and the third to
"Playground lacks an 'Import from DrawIO' menu item, file input, or Monaco
Import action", or combine related points into a single sentence) — update the
three bullet lines shown in the diff accordingly to improve readability.
🧹 Nitpick comments (7)
apps/playground/src/components/drawio/DrawioContextMenuProvider.tsx (1)
75-75:usePlayground()is called but its return value is never used.
playgroundis assigned on Line 75 but never referenced in the component body. This is dead code that also triggers hook execution unnecessarily.Proposed fix
- const playground = usePlayground()And remove
usePlaygroundfrom the import on Line 1:-import { usePlayground, usePlaygroundSnapshot } from '$/hooks/usePlayground' +import { usePlaygroundSnapshot } from '$/hooks/usePlayground'#!/bin/bash # Verify that `playground` is not referenced elsewhere in this file rg -n '\bplayground\b' -- "apps/playground/src/components/drawio/DrawioContextMenuProvider.tsx"packages/likec4/src/cli/export/drawio/handler.ts (1)
86-107: Symlinked source directories are silently skipped during round-trip reads.
e.isDirectory()returnsfalsefor symlinks pointing to directories, sowalknever descends into them. If a workspace uses symlinked source folders (e.g., monorepo setups), their.c4/.likec4files won't be included in the round-trip content.If this is intentional for safety, a brief comment explaining the choice would help. Otherwise, consider adding an
e.isSymbolicLink()check that resolves the target and recurses if it's a directory (thevisitedDirs/realpathcycle guard already handles loops).packages/likec4/src/cli/export/png/handler.ts (2)
334-356: Minor: inconsistent access style formax-attempts/maxAttempts.Line 336 accesses the yargs arg as
args['max-attempts'], while line 346 uses the camelCase formargs.maxAttempts. Both work due to yargs' automatic camelCase aliasing, but mixing styles in the same handler is a readability nit.Suggested consistency fix
- invariant(args['max-attempts'] >= 1, 'max-attempts must be >= 1') + invariant(args.maxAttempts >= 1, 'max-attempts must be >= 1')
18-40: Consider documenting all public fields onPngExportArgs.Several fields (
project,theme,useDotBin,timeoutMs,maxAttempts,filter) lack JSDoc while others have it. For a publicly exported type this is a minor inconsistency. As per coding guidelines, "Use JSDoc to document public classes and methods".packages/generators/src/drawio/parse-drawio.ts (2)
1447-1466:DiagramStatetype is defined after its first usage viaCommonDiagramStateFromCells.
CommonDiagramStateFromCellsat line 1324 isDiagramState & { byId: ... }, butDiagramStateis declared at line 1448. TypeScript hoists type declarations so this compiles fine, but it hurts readability — the reader encounters the alias before the definition.
646-689:computeContainerTitlesuses.find()— only the first matching text shape near a container is considered.If multiple text-shape cells overlap the title area of a container, only the first one found (in array order) becomes the title cell. Remaining text shapes near the container's top would not be filtered as title cells and could become spurious elements. This is fine for LikeC4-generated drawio files but may produce unexpected results for hand-crafted diagrams with multiple overlapping labels.
packages/generators/src/drawio/generate-drawio.ts (1)
86-96: Accessing optional DSL fields viaascasts — pragmatic but fragile.The
ExtendedNodetype andnodeOptionalFields/edgeOptionalFieldsaccessors castNodeto a wider type to access fields (notes,summary,tags,navigateTo, etc.) that may or may not exist on the runtime object. This works today but is invisible to the type system — if upstream types change, these casts won't produce compile errors.Consider adding a runtime guard (e.g.,
'notes' in node) or using a shared type from@likec4/coreif one becomes available.Also applies to: 104-133
| function getAttr(attrs: string, name: string): string | undefined { | ||
| const re = new RegExp(`${name}="([^"]*)"`, 'i') | ||
| const m = attrs.match(re) | ||
| return m ? m[1] : undefined | ||
| const needle = `${name}=` | ||
| const lower = attrs.toLowerCase() | ||
| const needleLower = needle.toLowerCase() | ||
| let i = 0 | ||
| while (i < attrs.length) { | ||
| const start = lower.indexOf(needleLower, i) | ||
| if (start === -1) return undefined | ||
| const quoteStart = start + needle.length | ||
| if (quoteStart < attrs.length && attrs[quoteStart] === '"') { | ||
| const valueEnd = attrs.indexOf('"', quoteStart + 1) | ||
| if (valueEnd !== -1) return attrs.slice(quoteStart + 1, valueEnd) | ||
| } | ||
| i = start + 1 | ||
| } | ||
| return undefined | ||
| } |
There was a problem hiding this comment.
getAttr can match attribute names that are suffixes of longer attributes.
The indexOf-based search for name= doesn't enforce a word boundary before the match. For example, searching for "id" in userid="foo" id="bar" would match userid= first and return "foo" instead of "bar".
In practice, draw.io's standard attribute names (id, vertex, edge, style, etc.) don't collide with each other as suffixes, so this is safe for current usage. However, if the file ever needs to parse custom attributes or third-party draw.io extensions, this could silently produce wrong values.
🛡️ Proposed fix — check for word boundary (space or start-of-string) before the needle
let i = 0
while (i < attrs.length) {
const start = lower.indexOf(needleLower, i)
if (start === -1) return undefined
+ // Ensure we matched a full attribute name, not a suffix of a longer name
+ if (start > 0 && attrs[start - 1] !== ' ' && attrs[start - 1] !== '\t' && attrs[start - 1] !== '\n') {
+ i = start + 1
+ continue
+ }
const quoteStart = start + needle.length
if (quoteStart < attrs.length && attrs[quoteStart] === '"') {🤖 Prompt for AI Agents
In `@packages/generators/src/drawio/parse-drawio.ts` around lines 107 - 123, The
getAttr function can match attribute name suffixes (e.g., matching "userid="
when searching for "id="); fix getAttr by ensuring the found needle has a
word-boundary before it: after finding start via indexOf, verify that start ===
0 or the character at start - 1 is whitespace (space, tab, newline) before
accepting the match, and only then parse the quoted value as currently done;
update the loop in getAttr to continue searching if the preceding character is
not a valid boundary so it returns the correct attribute value.
…d (DrawioContextMenuProvider); remove ad-hoc PR docs Co-authored-by: Cursor <[email protected]>
…heck) Co-authored-by: Cursor <[email protected]>
…Provider Co-authored-by: Cursor <[email protected]>
feat(cli,playground,docs,generators): Export LikeC4 views to Draw.io
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
feat(cli,playground,docs,generators): Export LikeC4 views to Draw.io
Summary
This PR adds export of LikeC4 views to Draw.io (
.drawio) format from the CLI (likec4 export drawio) and the Playground (right-click on diagram → DrawIO → Export view / Export all). It does not include import; import will be proposed in a separate PR.Context for maintainers: This work was first submitted in PR #2614, which received substantial review feedback focused on clean code, structure, and maintainability. We took that feedback seriously and ran a deliberate refactor pass (Uncle Bob / Clean Code) over the DrawIO-related code. This PR re-submits the same feature set with a cleaner, refactored codebase that we believe is easier to review and maintain. We’re grateful for the earlier review and have aimed to address those concerns in this iteration.
What’s in this PR
1. Generators (
@likec4/generators)computeDiagramLayoutsplit into smaller helpers;getViewDescriptionStringextracted;buildNodeCellXml/buildEdgeCellXmland constants (SOLID/DRY/KISS); exported typeDrawioViewModelLike.parseDrawioToLikeC4Multisplit intomergeDiagramStatesIntoMaps,buildRootsFromFqnToCell,emitMultiDiagramModel(orchestrator ~50–60 lines);buildViewBlockLines/escapeLikec4Quotes; O(n²) deduplication replaced withparsedIdsSet; UserObjectfullTagusesinnerXmlso sibling<data>is preserved.generate-drawio.spec.ts,parse-drawio.spec.ts; snapshots in__snapshots__/. Decompress error assertion accepts (base64 decode|inflate|URI decode) for Node/env behavior; snapshots updated for CI.2. CLI (
@likec4/likec4)likec4 export drawiowith--outdir/-o,--all-in-one,--roundtrip,--uncompressed,--project,--use-dot. UsesDEFAULT_DRAWIO_ALL_FILENAMEfrom@likec4/generators(DRY). Phase comments and thin handler pattern aligned with other export commands.PngExportArgs,runExportPng(args, logger); PNG export supports--outdir/-o(docs updated).likec4 import drawioin this PR (nopackages/likec4/src/cli/import/).3. Playground
generateDrawio/generateDrawioMultiandparseDrawioRoundtripComments. No Import menu item or file input.4. Documentation
--outdir; no Import section.5. E2E & tests
playwright.playground.config.ts.drawio-demo-export-import.spec.ts,drawio-tutorial-export-import.spec.ts— export tests; import/round-trip tests skipped in this PR.What’s not in this PR
likec4 import drawiocommand.Refactor summary (response to PR #2614 feedback)
After the initial submission (PR #2614), we applied a structured clean-code pass:
DrawioViewModelLike); constants; phase comments; DRY (e.g.DEFAULT_DRAWIO_ALL_FILENAME, shared helpers).runExport*(args, logger)pattern for drawio/PNG/JSON; consistent error handling and JSDoc.e2e/helpers/.We believe this version is in better shape for review and long-term maintenance.
Checklist
main(merge/rebase as appropriate).feat:,refactor:,test:).pnpm ci:test(Vitest) passes.Verification
pnpm build(filter!./apps/*),pnpm typecheck,pnpm test(orpnpm ci:test) — pass.Notes for reviewers
DrawioViewModelLikeis the public type for view models passed togenerateDrawio/generateDrawioMulti.runExport*+ thin handler).Reference
Summary by CodeRabbit
New Features
Documentation
Tests
Improvements