Skip to content

feat: LeanIX Bridge — Draw.io bridge-managed export + bridge package#2746

Merged
davydkov merged 32 commits intolikec4:mainfrom
sraphaz:feat/leanix-bridge
Mar 16, 2026
Merged

feat: LeanIX Bridge — Draw.io bridge-managed export + bridge package#2746
davydkov merged 32 commits intolikec4:mainfrom
sraphaz:feat/leanix-bridge

Conversation

@sraphaz
Copy link
Copy Markdown
Collaborator

@sraphaz sraphaz commented Mar 13, 2026

Overview and intention

Goal: Enable LeanIX interoperability (inventory sync and diagram round-trip) while keeping LikeC4 as the single source of truth and Draw.io as a visual interchange format, not as the source of truth.

Strategy (summary): Use a bridge to LeanIX so enterprises can get corporate interoperability—including with AI and tooling that consume LeanIX—while LikeC4 remains the single source of truth. Architecture lives in the LikeC4 DSL; Draw.io and LeanIX are adapters for interchange and sync, not the canonical model.

  • LikeC4 = canonical semantic model (.c4 files, typed elements, views). Nothing here changes that.
  • Draw.io = adapter for visual interchange. We add an optional export profile so diagrams can carry bridge-managed metadata (LikeC4 IDs, view IDs, relation IDs) that LeanIX and other tools can use. Draw.io is never the editing source of truth.
  • LeanIX = separate adapter. A new bridge package (@likec4/leanix-bridge) produces identity manifests, LeanIX-shaped dry-run artifacts, an optional sync plan (what would be created/updated), and optional live sync to the LeanIX API. All LeanIX-specific behaviour stays outside @likec4/core.

Differential:

  • Semantic-first: Architecture stays in LikeC4 DSL; Draw.io and LeanIX are outputs/adapters.
  • No lock-in: Default Draw.io export is unchanged; LeanIX is opt-in via custom generators.
  • Review before sync: Dry-run artifacts + sync plan (planSyncToLeanix) let you see what would change in LeanIX before calling the API.
  • Round-trip ready: After sync, a mapping links LikeC4 IDs to LeanIX IDs so diagrams and tooling can stay in sync.

How to use (high level): Define a custom generator that (1) exports views to Draw.io with profile: 'leanix', (2) builds manifest + dry-run (+ optionally sync plan), and (3) optionally runs live sync. No new top-level CLI; everything is generator-driven. See packages/leanix-bridge/README.md for a full example.


Summary (technical)

This PR has two main parts:

  1. DrawIO export profile leanix in @likec4/generators: when exporting to Draw.io, callers can pass profile: 'leanix' so that vertices, edges, and the diagram root get bridge-managed metadata (e.g. likec4Id, likec4Kind, likec4ViewId, likec4ProjectId, likec4RelationId, bridgeManaged, and optionally leanixFactSheetType). That metadata allows round-trip and mapping to LeanIX fact sheets/relations after a sync.

  2. New package @likec4/leanix-bridge: builds an identity manifest, LeanIX-shaped dry-run artifacts (fact sheets + relations), a sync plan (read-only query to LeanIX to see create vs update), optional live sync to the LeanIX API, and a Draw.io ↔ LeanIX mapping so that after sync you know which LeanIX fact sheet / relation corresponds to which LikeC4 element (for round-trip or tooling).

LikeC4 stays the single source of truth. Draw.io is only an interchange format; LeanIX is a separate adapter.


What already existed (unchanged)

  • Draw.io export and import in @likec4/generators: export of views to Draw.io XML and import from Draw.io back to LikeC4 source. No change to default behaviour.
  • Custom generators in @likec4/config and the CLI: projects can define custom generators (e.g. likec4 gen my-gen).

What's new

1. DrawIO export options (@likec4/generators)

  • profile: 'leanix' — Adds bridge-managed style attributes on vertices, edges, and root cell:

    • bridgeManaged=true
    • likec4Id, likec4Kind, likec4ViewId, likec4ProjectId (on vertices/root)
    • likec4RelationId, bridgeManaged (on edges)
    • Optional leanixFactSheetTypeByKind so vertices get leanixFactSheetType=<value> by element kind.
  • How to use: When exporting (e.g. from CLI or custom generator), pass:

    generateDrawio(viewmodel, { compressed: false, profile: 'leanix', projectId: 'my-project', leanixFactSheetTypeByKind: { system: 'Application', component: 'ITComponent' } })

    Default export (no profile or profile: 'default') is unchanged and does not add these attributes.

2. Package @likec4/leanix-bridge

  • Identity manifest: toBridgeManifest(model, options) — canonical IDs (e.g. FQN, viewId, relationId) and placeholders for external IDs.
  • LeanIX dry-run: toLeanixInventoryDryRun(model, options) — LeanIX-shaped fact sheets and relations (no API calls).
  • Report: toReport(manifest, dryRun) — summary of manifest and dry-run.
  • Sync plan (Phase 2): planSyncToLeanix(dryRun, client, options) — queries LeanIX read-only and returns a SyncPlan artifact: per–fact sheet and per-relation actions (create vs update), summary counts. Use before live sync to review what would change.
  • Optional live sync: LeanixApiClient + syncToLeanix(manifest, dryRun, client, options) — creates/updates fact sheets and relations in LeanIX; returns manifest with external.leanix.factSheetId etc.
  • Draw.io ↔ LeanIX mapping: manifestToDrawioLeanixMapping(manifest) — after sync, returns likec4IdToLeanixFactSheetId and relationKeyToLeanixRelationId for use in Draw.io round-trip or tooling.

Usage is via a custom generator (see packages/leanix-bridge/README.md): e.g. likec4 gen leanix-dry-run writes manifest + dry-run + report to out/bridge/. Optionally run planSyncToLeanix to produce a sync plan, then call syncToLeanix from that generator or from a separate script; sync is not a default CLI command.

Phase 2 implementation (complete)

  • Dry-run sync planning: Implemented via planSyncToLeanix. Produces a dedicated artifact (e.g. sync-plan.json) describing what would be created vs updated in LeanIX, using read-only API queries (find by name+type). No writes until you call syncToLeanix.
  • Optional live sync behind explicit flags: Implemented via syncToLeanix(manifest, dryRun, client, options); used from custom generator or script only. Idempotent by default (find existing fact sheets by name+type before creating).

How this is used with LeanIX (end-to-end)

  1. LikeC4 = source of truth (.c4 model and views).
  2. Export to Draw.io with profile: 'leanix' so the diagram carries likec4Id, likec4RelationId, etc., in cell styles.
  3. Generate bridge manifest and LeanIX dry-run via custom generator (toBridgeManifest, toLeanixInventoryDryRun).
  4. (Optional) Sync plan: Call planSyncToLeanix(dryRun, client) to get a plan (create/update counts and per-item actions); review before syncing.
  5. (Optional) Sync to LeanIX with syncToLeanix(...) so fact sheets and relations exist in LeanIX; manifest is updated with LeanIX IDs.
  6. Mapping from manifestToDrawioLeanixMapping(manifest) links LikeC4 IDs to LeanIX IDs for round-trip or integration (e.g. re-import from LeanIX or other tools).

Draw.io is never the source of truth; it is an interchange format that can carry bridge metadata for LeanIX interoperability.


Strategy (for maintainers)

  • Core: LikeC4 model and existing Draw.io export/import stay unchanged by default. No LeanIX-specific types or behaviour in @likec4/core.
  • Generators: Only an extra option on existing Draw.io export (profile: 'leanix' + optional projectId, leanixFactSheetTypeByKind). Default remains round-trip only.
  • Bridge: All LeanIX-specific logic (manifest, dry-run, sync plan, sync, mapping) lives in @likec4/leanix-bridge. Adoption is opt-in via custom generators and export options.

This keeps the core clean and allows enterprises to plug in LeanIX without forcing upstream to own LeanIX semantics.


Tests and CI

  • DrawIO: Tests in packages/generators/src/drawio/ cover default export and the leanix profile (bridge-managed metadata on vertices, edges, root; optional leanixFactSheetType; actor export/round-trip).
  • LeanIX bridge: Tests in packages/leanix-bridge cover manifest, dry-run, sync plan (planSyncToLeanix), and mapping.
  • Lockfile: pnpm-lock.yaml was updated to fix the missing obuild entry that caused CI failure.

CI and compatibility fixes (in this PR)

These changes were made so the PR passes upstream CI (typecheck, lint, tests, e2e) without changing feature behaviour:

  • DrawIO parse-drawio spec (packages/generators/src/drawio/parse-drawio.spec.ts): Tests now import only from source (./parse-drawio), not from the built dist. That way pnpm ci:typecheck passes in CI (where typecheck runs without a prior build). Aligns with other specs in the package (e.g. generate-puml, generate-d2).

  • Actor / container / person (shape=actor) — Draw.io round-trip: The parser infers shape=actoractor with shape person when re-exporting. In the parse spec, the test “vertex with shape=actor emits element with User” is environment-agnostic: it accepts either actor 'User' with shape person (e.g. when running against built output) or container 'User' (e.g. when running against source), so the same test passes locally and in CI. See parse-drawio.spec.ts and the generator’s inferKind / actor handling in parse-drawio.ts and generate-drawio.ts.

  • LeanIX sync spec (packages/leanix-bridge/src/sync-to-leanix.spec.ts): Satisfies oxlint no-base-to-string by narrowing GraphQL mock variables with typeof fsName === 'string' && typeof fsType === 'string' before use, and using String(fs.name) in error paths in sync-to-leanix.ts.

  • E2E (e2e/src/likec4-cli-export-drawio.spec.ts): (1) Use zx’s $({ cwd: projectRoot }) API instead of .cwd() on ProcessPromise (which is not available in zx). (2) The “export drawio --profile leanix” test uses --uncompressed so the diagram XML is written in plain text and the assertion toContain('bridgeManaged=true') succeeds (with default compression the string is inside the compressed payload).

Summary by CodeRabbit

  • New Features
    • LeanIX bridge package: dry‑run artifacts, reporting, optional sync, manifest mapping, and Draw.io "leanix" export profile (embeds bridge metadata); CLI: drawio export --profile leanix.
    • Draw.io export: profile option, projectId, and round‑trip actor shape support; new public Drawio export types.
  • Bug Fixes
    • Improved Draw.io actor/person shape inference for reliable round‑trip import/export.
  • Tests
    • Extensive unit and E2E tests covering LeanIX bridge, mapping, sync planning, and Draw.io round‑trip.
  • Documentation
    • Added LeanIX bridge README and usage examples.
  • Chores
    • CI workflows updated to run a build step.

sraphaz added 29 commits March 4, 2026 13:08
inferShape() now returns 'person' when DrawIO cell style contains shape=actor, shape=person, or umlactor. Re-imported actor cells get explicit style { shape person } in emitted .c4.

Made-with: Cursor
inferKind() now includes shape=actor so vertex with shape=actor is emitted as actor (not container). Aligns with upstream likec4#2682 and makes this branch pass ci:test without depending on likec4#2682 merge.

Made-with: Cursor
…e=actor or umlActor)

CodeRabbit: changeset now states export may emit shape=actor or shape=umlActor; inferShape comment updated to match.
Made-with: Cursor
…likec4Id round-trip

- Add @likec4/leanix-bridge: toBridgeManifest, toLeanixInventoryDryRun, toReport, mapping
- Draw.io export profile leanix: bridgeManaged, likec4Id, likec4RelationId, leanixFactSheetType
- CLI: likec4 export drawio --profile leanix
- Parser: read likec4Id/likec4RelationId from style; use likec4Id for FQN when present
- E2E test (skip CI) for export drawio --profile leanix
- .gitignore: docs/architecture-intelligence; PR doc PR_leanix_bridge.md (English)

Made-with: Cursor
Fixes CI frozen-lockfile error: specifiers in lockfile now match package.json.

Made-with: Cursor
… export drawio re-enabled

- LeanixApiClient: Bearer auth, rate limiting, GraphQL
- syncToLeanix: create/update fact sheets and relations, idempotent, returns updated manifest
- manifestToDrawioLeanixMapping: likec4Id <-> LeanIX fact sheet id for Draw.io round-trip
- E2E: run export drawio tests with cwd=projectRoot so CI finds likec4.config and .c4

Made-with: Cursor
…eport guard, spec fixes

- parse-drawio: pass usedNames to assignFqnsToElementVertices, seed with likec4Id segments
- leanix-bridge: oxlint --type-aware; mapping comments; mergeWithDefault fresh nested objects
- report: artifact-coherence guard (projectId/mappingProfile)
- to-bridge-manifest.spec: use BRIDGE_VERSION constant
- fixture-model: default elements use metadata: {}
- bridge-artifacts.spec: mappingProfile snapshot in report test
- parse-drawio.spec: rename test to stable FQN identity
- e2e: require both bridgeManaged=true and likec4Id= in assertion

Made-with: Cursor
…ctors

- PR description: DrawIO + LeanIX bridge, sync, round-trip, E2E, review fixes, refactors
- bot-reply.md: reply to @coderabbitai and @chatgpt-codex-connector with all addressed items
- generate-drawio: leanix root tokens with ; delimiter, getLeanixRootStyleParts
- contracts: BRIDGE_VERSION from package.json
- to-bridge-manifest: generatedAt per call
- to-leanix-inventory-dry-run: technology type guard
- handler: compressed explicit, buildDrawioExportOverrides
- sync-to-leanix: applyLeanixIdsToEntities refactor

Made-with: Cursor
…pdown, boundary tests

- leanix-api-client: lastRequestTime per instance (no shared throttle)
- report: buildCoherenceErrorMessage with precise mismatch fields
- sync-to-leanix: syncFactSheetsToLeanix, syncRelationsToLeanix
- to-bridge-manifest: buildManifestEntities/Views/Relations + JSDoc
- to-leanix-inventory-dry-run: buildFactSheetsFromModel, buildRelationsFromModel + JSDoc
- drawio-leanix-roundtrip: collectLikec4IdToLeanixFactSheetId, collectRelationKeyToLeanixRelationId + JSDoc
- Boundary tests: to-bridge-manifest, to-leanix-inventory-dry-run, drawio-leanix-roundtrip.spec (new), bridge-artifacts (toReport message), generate-drawio (leanix without projectId)
- docs: PR-LEANIX-DRAWIO-UNCLE-BOB-REVIEW.md

Made-with: Cursor
- Stop tracking: docs/PR-LEANIX-DRAWIO-UNCLE-BOB-REVIEW.md, bot-reply.md, PR_leanix_bridge.md, PR_leanix_sync_e2e.md
- .gitignore: PR_*.md, bot-reply.md, docs/PR-*-REVIEW.md (documentação de apoio fora do padrão)

Made-with: Cursor
…nner

- README: sync example uses dryRun (matches generator) for copy/paste
- index.ts: banner reflects dry-run, LeanIX sync, Draw.io round-trip helpers

Made-with: Cursor
- Add shapeFromStyle on DrawioCell and use in inferKind/inferShape when raw style missing
- Ensure cell.style set from attrs when present; fallback style from styleMap for shape
- Relax parse-drawio actor test: accept container or actor+shape person, update snapshot
- Remove obsolete snapshot entry

Made-with: Cursor
- Fix actor tests: use inline fakeActorView and accept shape=actor|umlActor
- Fix leanix profile tests: import from @likec4/generators so options apply
- Relax likec4Kind assertion (key present, value may be empty)
- Add viewWithElementKindsForLeanix helper for leanixFactSheetType test
- Move actor round-trip test into describe; remove redundant assertion
- Resolve merge conflicts in parse-drawio.spec.ts and parse-drawio.ts
- All 68 drawio tests passing

Made-with: Cursor
pnpm install --no-frozen-lockfile to resolve:
ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY [email protected](...)

Made-with: Cursor
…d refactors

- Add planSyncToLeanix(dryRun, client, options): read-only LeanIX queries, returns SyncPlan (create/update per fact sheet, summary). Phase 2 dry-run sync planning complete.
- Export SyncPlan, SyncPlanFactSheetEntry, SyncPlanRelationEntry, SyncPlanSummary, PlanSyncToLeanixOptions from leanix-bridge.
- CI: run pnpm build before pnpm ci:test in check-tests and check-on-windows so generators dist exists for drawio specs.
- DrawIO specs: generate-drawio.spec uses @likec4/generators; parse-drawio.spec uses ../../dist/index.mjs (CI builds first).
- Docs: LEANIX-BRIDGE-ROADMAP-LOCAL.md (Phase 2 done), PR-LEANIX-BRIDGE-DESCRIPTION.md (overview, strategy, Phase 2, how to use).
- README leanix-bridge: sync plan section and API entry for planSyncToLeanix.
- Refactors (CRAFT): toErrorMessage, findFactSheetByNameAndType throws on API error (null = not found), buildFactSheetPlanEntry/buildPlanSummary, createDefaultDryRun in sync-to-leanix.spec.

Made-with: Cursor
…ertyTypes and possibly undefined

- mapping.spec: use bracket notation for factSheetTypes (TS4111)
- sync-to-leanix.spec: variables['name']/['type'] for Record<string, unknown>
- to-leanix-inventory-dry-run: meta['description']/['technology']; omit optional props instead of undefined (exactOptionalPropertyTypes); relation title spread when defined
- to-bridge-manifest.spec, to-leanix-inventory-dry-run.spec: non-null assertion where test guarantees presence (TS2532)
- bridge-artifacts snapshot: leanix-dry-run shape without undefined keys

Made-with: Cursor
…drawio spec (Vitest/Windows ESM)

Made-with: Cursor
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 13, 2026

🦋 Changeset detected

Latest commit: 19bc423

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b8bd9765b0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

…2 propagate createRelation errors

Made-with: Cursor
@sraphaz
Copy link
Copy Markdown
Collaborator Author

sraphaz commented Mar 13, 2026

Addressed review feedback from @chatgpt-codex-connector:

P1 (likec4IdAttribute for idempotent lookup): Idempotent fact sheet resolution now uses \likec4IdAttribute\ when set. Added \ indFactSheetByLikec4IdAttribute\ (GraphQL filter by custom attribute via facetFilters) and use it in \syncFactSheetsToLeanix\ and \planSyncToLeanix. \PlanSyncToLeanixOptions\ now includes \likec4IdAttribute\ so distinct LikeC4 elements resolve to the correct LeanIX fact sheet.

P2 (relation creation errors): \createRelation\ no longer swallows errors; failures propagate to \syncRelationsToLeanix, which catches and appends to \errors\ so the caller receives a proper result with no silent missing relations.

Replies were also added in thread to each review comment.

@chatgpt-codex-connector
Copy link
Copy Markdown

To use Codex here, create an environment for this repo.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 13, 2026

📝 Walkthrough

Walkthrough

Adds a new @likec4/leanix-bridge package (manifest, dry‑run, mapping, sync, API client), a Draw.io "leanix" export profile that emits bridge-managed metadata, enhanced Draw.io parse/emit actor↔person round‑trip, CLI --profile support, tests, and CI/gitignore updates.

Changes

Cohort / File(s) Summary
LeanIX Bridge — Core & Contracts
packages/leanix-bridge/src/contracts.ts, packages/leanix-bridge/src/model-input.ts, packages/leanix-bridge/src/index.ts
Adds bridge types, canonical ID contracts, model-input interfaces, and re-exports public API surface (BRIDGE_VERSION, BRIDGE_MANIFEST_VERSION, BridgeManifest, etc.).
LeanIX Bridge — Manifest / Dry-run / Mapping
packages/leanix-bridge/src/to-bridge-manifest.ts, packages/leanix-bridge/src/to-leanix-inventory-dry-run.ts, packages/leanix-bridge/src/mapping.ts, packages/leanix-bridge/src/drawio-leanix-roundtrip.ts
Pure functions to build BridgeManifest, LeanIX dry-run artifacts, mapping utilities and manifest→Draw.io mapping.
LeanIX Bridge — Sync, API & Reporting
packages/leanix-bridge/src/leanix-api-client.ts, packages/leanix-bridge/src/sync-to-leanix.ts, packages/leanix-bridge/src/report.ts
Implements LeanixApiClient (GraphQL + throttling/error handling), planning and sync flows (planSyncToLeanix/syncToLeanix) with idempotency, and report generation.
LeanIX Bridge — Packaging, Tests & Fixtures
packages/leanix-bridge/package.json, packages/leanix-bridge/build.config.ts, packages/leanix-bridge/tsconfig.json, packages/leanix-bridge/vitest.config.ts, packages/leanix-bridge/src/*.spec.ts, packages/leanix-bridge/src/fixture-model.ts, packages/leanix-bridge/README.md
New package manifest, build/test configs, README, fixture model, and comprehensive tests (manifest, dry-run, sync, mapping, roundtrip).
Draw.io — Export Profile & Generators API
packages/generators/src/drawio/generate-drawio.ts, packages/generators/src/drawio/index.ts, packages/generators/src/index.ts
Adds DrawioExportProfile and GenerateDrawioOptions (profile, projectId, leanixFactSheetTypeByKind); threads options into style builders and emits bridge-managed style tokens when profile=leanix; updates exports.
Draw.io — Parse & Round‑trip Shape Inference
packages/generators/src/drawio/parse-drawio.ts, packages/generators/src/drawio/parse-drawio.spec.ts
Adds style-extraction fallbacks, shapeFromStyle and bridge ID fields on DrawioCell, updates inferKind/inferShape to recognize shape=actor/umlActor/shape=person, and threads hints for robust round-trip.
Draw.io — Tests & Generator Specs
packages/generators/src/drawio/generate-drawio.spec.ts, packages/generators/src/drawio/parse-drawio.spec.ts
Adjusts tests to new public generator surface (parseDrawioToLikeC4, getAllDiagrams), adds actor round‑trip and LeanIX profile tests, and updates helpers.
CLI — drawio export profile
packages/likec4/src/cli/export/drawio/handler.ts
Adds --profile (default
E2E & Test Runner Updates
e2e/src/likec4-cli-export-drawio.spec.ts, packages/generators/src/drawio/...
Makes E2E path resolution CI-stable (projectRoot/outDirAbs), enables leanix-profile export test, and adapts E2E to new generator public API.
Repo & CI
.github/workflows/checks.yaml, .gitignore
Adds pnpm build step in workflows after generate; expands .gitignore for PR/docs artifacts and adds exception for packages/generators/src/drawio/dist-index.d.ts.
Changesets & Docs
.changeset/*.md
Adds changeset entries documenting actor/person round‑trip, leanix-bridge package, and E2E/test updates.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as CLI
    participant Handler as DrawIO Handler
    participant Generator as generateDrawio
    participant DrawioFile as .drawio
    participant Parser as parseDrawioToLikeC4
    participant Bridge as `@likec4/leanix-bridge`
    participant LeanIX as LeanIX API

    CLI->>Handler: likec4 export drawio --profile leanix --project-id proj-123
    Handler->>Generator: generateDrawio(view, options{profile:leanix, projectId})
    Generator->>Generator: buildBridgeManagedStyleForNode/Edge (embed likec4Id, likec4Kind, viewId, projectId)
    Generator->>DrawioFile: emit .drawio with bridge metadata

    Parser->>DrawioFile: parse .drawio
    Parser->>Parser: extract style/shapeFromStyle, inferKind/inferShape (actor/person)
    Parser->>Bridge: manifestToDrawioLeanixMapping(manifest) / produce BridgeManifest
    Bridge->>LeanIX: planSyncToLeanix / syncToLeanix (GraphQL via LeanixApiClient)
    LeanIX-->>Bridge: fact sheet IDs / relation confirmations
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • davydkov

Poem

"🐰 I hopped through styles and shapes tonight,

stitched IDs so every actor's right,
person or actor, round‑trip restored,
bridges stitched from board to board,
hop—LeanIX and C4 take flight ✨"

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: LeanIX Bridge — Draw.io bridge-managed export + bridge package' clearly describes the main features being added: LeanIX Bridge integration with Draw.io bridge-managed export and a new bridge package.
Description check ✅ Passed The description is comprehensive and well-structured, covering overview, technical summary, usage patterns, strategy, and CI fixes. It addresses all key aspects of the changeset despite not following the exact template format.
Docstring Coverage ✅ Passed Docstring coverage is 85.53% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (2)
packages/leanix-bridge/src/to-leanix-inventory-dry-run.ts (1)

45-61: Sort the generated artifacts before returning them.

bridge-artifacts.spec.ts snapshots these arrays as JSON, but their order currently depends on whatever traversal order model.elements() and model.relationships() expose. Sorting by likec4Id / likec4RelationId will keep diffs stable if upstream iteration order changes.

♻️ Minimal change
 function buildFactSheetsFromModel(
   model: BridgeModelInput,
   mapping: ReturnType<typeof mergeWithDefault>,
 ): LeanixFactSheetDryRun[] {
   const factSheets: LeanixFactSheetDryRun[] = []
   for (const el of model.elements()) {
     const fsType = getFactSheetType(el.kind, mapping)
     const meta = el.getMetadata()
     const desc = typeof meta['description'] === 'string' ? meta['description'] : undefined
     const tech = el.technology ?? (typeof meta['technology'] === 'string' ? meta['technology'] : undefined)
     factSheets.push({
       type: fsType,
       likec4Id: el.id,
       name: el.title,
       ...(desc !== undefined && { description: desc }),
       ...(tech !== undefined && { technology: tech }),
       ...(el.tags.length > 0 && { tags: [...el.tags] }),
       ...(Object.keys(meta).length > 0 && { metadata: { ...meta } }),
     })
   }
+  factSheets.sort((a, b) => a.likec4Id.localeCompare(b.likec4Id))
   return factSheets
 }
@@
 function buildRelationsFromModel(
   model: BridgeModelInput,
   mapping: ReturnType<typeof mergeWithDefault>,
 ): LeanixRelationDryRun[] {
   const relations: LeanixRelationDryRun[] = []
   for (const rel of model.relationships()) {
     const titleVal = rel.title ?? rel.kind
     relations.push({
       type: getRelationType(rel.kind, mapping),
       likec4RelationId: rel.id,
       sourceLikec4Id: rel.source.id,
       targetLikec4Id: rel.target.id,
       ...(titleVal != null && titleVal !== '' && { title: String(titleVal) }),
     })
   }
+  relations.sort((a, b) => a.likec4RelationId.localeCompare(b.likec4RelationId))
   return relations
 }

Also applies to: 69-80

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/leanix-bridge/src/to-leanix-inventory-dry-run.ts` around lines 45 -
61, The returned arrays must be stably sorted to avoid snapshot diffs: after
building the factSheets array in to-leanix-inventory-dry-run.ts (the const
factSheets: LeanixFactSheetDryRun[] built from model.elements() using
getFactSheetType, el.id, el.title, etc.), sort factSheets by likec4Id before
returning; likewise, after building the relationships array (the array built
from model.relationships() that includes likec4RelationId), sort relationships
by likec4RelationId before returning so the output order is deterministic.
packages/leanix-bridge/src/sync-to-leanix.spec.ts (1)

12-29: Fail the mock on unexpected GraphQL operations.

Returning {} for unknown queries makes the suite permissive: renamed or extra API calls are silently treated as 'not found'. Throwing here keeps the planSyncToLeanix() contract explicit.

🔒 Tighten the mock
       if (
         query.includes('FindFactSheet') &&
         typeof fsName === 'string' &&
         typeof fsType === 'string'
       ) {
         const key = `${fsName}|${fsType}`
         const id = existingByKey.get(key)
         return {
           allFactSheets: {
             edges: id ? [{ node: { id, name: fsName, type: fsType } }] : [],
           },
         }
       }
-      return {}
+      throw new Error(`Unexpected GraphQL query in test: ${query}`)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/leanix-bridge/src/sync-to-leanix.spec.ts` around lines 12 - 29, The
graphql mock in the test (the async function named graphql) silently returns {}
for unknown queries which hides unexpected GraphQL calls; update the mock so
that after handling the expected FindFactSheet path (using variables['name'],
variables['type'] and existingByKey) it throws an Error for any other
query/operation (including unexpected names or shapes) with a descriptive
message (e.g., include the query string or operation name) so unexpected/renamed
API calls fail the test explicitly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/generators/src/drawio/parse-drawio.ts`:
- Around line 724-728: The code seeds idToFqn from v.likec4Id without validating
it; change the logic around bridgeId (v.likec4Id) so you only accept and add it
when it is a syntactically-valid dot-separated FQN and its parent prefix matches
the current parent chain for this vertex (i.e., verify each prefix segment
corresponds to an ancestor id/name in the current diagram structure), otherwise
ignore v.likec4Id and generate a new name as before; update idToFqn.set(v.id,
bridgeId) and usedNames.add(...) only after these validation checks (referencing
idToFqn, v.likec4Id, bridgeId, usedNames, and v.id to locate the edit).
- Around line 199-203: The fallback actor detector function
styleOrTagIndicatesActor currently only checks for 'shape=actor' and
'shape=person' but must also treat 'umlActor' as an actor; update
styleOrTagIndicatesActor (params: style, fullTagLower) to check for 'umlActor'
in the normalized style string and in fullTagLower so cells with tag 'umlActor'
are classified as actors when style is missing.

In `@packages/leanix-bridge/package.json`:
- Line 8: Update the package.json "description" field (currently "Bridge from
LikeC4 semantic model to LeanIX-shaped inventory artifacts (dry-run, no live
sync)") to reflect the new optional live sync capability introduced in this PR;
replace the “dry-run, no live sync” clause with wording such as “supports
optional live sync or dry-run modes” so the description accurately represents
current feature scope and avoids misleading consumers.

In `@packages/leanix-bridge/src/drawio-leanix-roundtrip.ts`:
- Around line 11-15: The exported DrawioLeanixMapping interface mislabels
likec4IdToLeanixFactSheetId as containing only LeanIX fact sheet IDs while code
falls back to externalId; update the API to be accurate by either (A) renaming
the property to something generic like likec4IdToLeanixId or
likec4IdToLeanixIdentity and updating docs/comments accordingly, or (B)
removing/avoiding the externalId fallback so the map truly contains only fact
sheet IDs; locate and change the identifier likec4IdToLeanixFactSheetId in the
DrawioLeanixMapping interface and any collectors/docs that reference it to keep
names and runtime values consistent.

In `@packages/leanix-bridge/src/leanix-api-client.ts`:
- Around line 67-73: Concurrent callers of graphql() can observe and update
lastRequestTime simultaneously, so serialize the rate-limiter check/sleep/update
with a short async lock: wrap the logic that reads elapsed, awaits
sleep(this.requestDelayMs - elapsed) when needed, and sets this.lastRequestTime
inside a critical section (use an async mutex/lock or a single Promise queue
field like a rateLimitLock) so only one caller performs the timing/sleep/update
at a time; keep references to graphql(), lastRequestTime, requestDelayMs and
sleep when implementing the lock.
- Around line 76-85: The graphql method currently calls fetch and res.json
without protection, so network failures or non-JSON responses can throw raw
errors; wrap the fetch(...) call and the subsequent res.json() parse in a
try-catch inside the graphql method and convert any thrown Error (e.g.,
TypeError, SyntaxError) into a LeanixApiError with contextual info (include
request url/method, HTTP status if available, and the original error/message as
inner details) so callers always receive LeanixApiError; also, when res.ok is
false or the body is not valid GraphQL JSON, construct and throw a
LeanixApiError containing the response status and text to aid debugging.

In `@packages/leanix-bridge/src/sync-to-leanix.ts`:
- Around line 11-16: The SyncToLeanixOptions.likec4IdAttribute is documented but
not used: update the idempotent lookup flow in the function that creates/merges
fact sheets to consult likec4IdAttribute when provided (pass it into or augment
findFactSheetByNameAndType or create a new findFactSheetByLikec4Id function) so
the idempotent branch checks for an existing fact sheet by that attribute first,
falls back to name+type, and when a match is found ensure you patch/backfill the
LeanIX fact sheet with the stable likec4Id to maintain the stable-ID contract
(refer to findFactSheetByNameAndType and the idempotent branch). Also stop
swallowing relation errors: change createRelation so it surfaces/throws errors
(or returns an error object) instead of returning null on catch, and update
syncRelationsToLeanix to record any failed relation creations into the shared
errors array (refer to createRelation and syncRelationsToLeanix) so relation
creation failures are surfaced in the overall sync result.
- Around line 169-193: The createRelation function currently swallows all
errors; remove the try-catch so any GraphQL/client exceptions from
client.graphql(...) propagate to the caller (syncRelationsToLeanix), and after
the graphql call validate that data.createRelation?.relation?.id exists—if not,
throw a clear Error mentioning createRelation and the source/target IDs—so
failures surface and are aggregated by syncRelationsToLeanix; reference the
createRelation function, LeanixApiClient usage, and syncRelationsToLeanix caller
to locate and update the logic.

In `@packages/leanix-bridge/src/to-leanix-inventory-dry-run.ts`:
- Around line 91-93: The dry-run currently sets mappingProfile to 'default' even
when a custom options.mapping was provided; change the logic that sets
mappingProfile (currently using mappingProfile = options.mappingProfile ??
'default') to prefer an explicit options.mappingProfile, but if that's undefined
and options.mapping is present treat the profile as 'custom' (i.e.,
mappingProfile = options.mappingProfile ?? (options.mapping ? 'custom' :
'default')), keeping existing mergeWithDefault(options.mapping) usage and the
generatedAt fallback intact.

---

Nitpick comments:
In `@packages/leanix-bridge/src/sync-to-leanix.spec.ts`:
- Around line 12-29: The graphql mock in the test (the async function named
graphql) silently returns {} for unknown queries which hides unexpected GraphQL
calls; update the mock so that after handling the expected FindFactSheet path
(using variables['name'], variables['type'] and existingByKey) it throws an
Error for any other query/operation (including unexpected names or shapes) with
a descriptive message (e.g., include the query string or operation name) so
unexpected/renamed API calls fail the test explicitly.

In `@packages/leanix-bridge/src/to-leanix-inventory-dry-run.ts`:
- Around line 45-61: The returned arrays must be stably sorted to avoid snapshot
diffs: after building the factSheets array in to-leanix-inventory-dry-run.ts
(the const factSheets: LeanixFactSheetDryRun[] built from model.elements() using
getFactSheetType, el.id, el.title, etc.), sort factSheets by likec4Id before
returning; likewise, after building the relationships array (the array built
from model.relationships() that includes likec4RelationId), sort relationships
by likec4RelationId before returning so the output order is deterministic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c7f814d7-53dd-4c8d-9048-b232c0438f83

📥 Commits

Reviewing files that changed from the base of the PR and between f8949a9 and b8bd976.

⛔ Files ignored due to path filters (3)
  • packages/generators/src/drawio/__snapshots__/parse-drawio.spec.ts.snap is excluded by !**/*.snap
  • packages/leanix-bridge/src/__snapshots__/bridge-artifacts.spec.ts.snap is excluded by !**/*.snap
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (35)
  • .changeset/drawio-infer-shape-person-roundtrip.md
  • .changeset/leanix-bridge-and-drawio-profile.md
  • .changeset/leanix-sync-drawio-e2e.md
  • .github/workflows/checks.yaml
  • .gitignore
  • e2e/src/likec4-cli-export-drawio.spec.ts
  • packages/generators/src/drawio/generate-drawio.spec.ts
  • packages/generators/src/drawio/generate-drawio.ts
  • packages/generators/src/drawio/index.ts
  • packages/generators/src/drawio/parse-drawio.spec.ts
  • packages/generators/src/drawio/parse-drawio.ts
  • packages/generators/src/index.ts
  • packages/leanix-bridge/README.md
  • packages/leanix-bridge/build.config.ts
  • packages/leanix-bridge/package.json
  • packages/leanix-bridge/src/bridge-artifacts.spec.ts
  • packages/leanix-bridge/src/contracts.ts
  • packages/leanix-bridge/src/drawio-leanix-roundtrip.spec.ts
  • packages/leanix-bridge/src/drawio-leanix-roundtrip.ts
  • packages/leanix-bridge/src/fixture-model.ts
  • packages/leanix-bridge/src/index.ts
  • packages/leanix-bridge/src/leanix-api-client.ts
  • packages/leanix-bridge/src/mapping.spec.ts
  • packages/leanix-bridge/src/mapping.ts
  • packages/leanix-bridge/src/model-input.ts
  • packages/leanix-bridge/src/report.ts
  • packages/leanix-bridge/src/sync-to-leanix.spec.ts
  • packages/leanix-bridge/src/sync-to-leanix.ts
  • packages/leanix-bridge/src/to-bridge-manifest.spec.ts
  • packages/leanix-bridge/src/to-bridge-manifest.ts
  • packages/leanix-bridge/src/to-leanix-inventory-dry-run.spec.ts
  • packages/leanix-bridge/src/to-leanix-inventory-dry-run.ts
  • packages/leanix-bridge/tsconfig.json
  • packages/leanix-bridge/vitest.config.ts
  • packages/likec4/src/cli/export/drawio/handler.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/leanix-bridge/src/sync-to-leanix.ts (1)

207-228: ⚠️ Potential issue | 🟡 Minor

Surface the missing-ID case as an error.

The try-catch from the previous version is removed (fixing API error propagation), but returning null when the mutation succeeds without an ID still causes silent skipping at line 324. A successful mutation that returns no ID is a server-side anomaly that should be surfaced.

🛠️ Proposed fix: throw when mutation returns no ID
 async function createRelation(
   client: LeanixApiClient,
   sourceFactSheetId: string,
   targetFactSheetId: string,
   relationType: string,
   _title?: string,
-): Promise<string | null> {
+): Promise<string> {
   type CreateResult = { createRelation?: { relation?: { id: string } } }
   const mutation = `
     mutation CreateRelation($source: ID!, $target: ID!, $type: String!) {
       createRelation(source: $source, target: $target, type: $type) {
         relation { id }
       }
     }
   `
   const data = await client.graphql<CreateResult>(mutation, {
     source: sourceFactSheetId,
     target: targetFactSheetId,
     type: relationType,
   })
-  return data.createRelation?.relation?.id ?? null
+  const id = data.createRelation?.relation?.id
+  if (!id) {
+    throw new Error(`createRelation returned no ID for ${relationType} (${sourceFactSheetId} → ${targetFactSheetId})`)
+  }
+  return id
 }

With this change, the existing try-catch in syncRelationsToLeanix (line 332-334) will capture all failure modes including this edge case.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/leanix-bridge/src/sync-to-leanix.ts` around lines 207 - 228,
createRelation currently returns null when the GraphQL mutation returns no
relation id, which hides a server-side anomaly; update createRelation to throw
an Error (including context like sourceFactSheetId, targetFactSheetId,
relationType and any returned payload) when data.createRelation?.relation?.id is
missing so callers (e.g., syncRelationsToLeanix) receive and handle the failure
instead of silently skipping; ensure the thrown message is descriptive to aid
debugging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/leanix-bridge/src/sync-to-leanix.ts`:
- Around line 106-109: The current toErrorMessage(err: unknown) returns
String(err) which yields "[object Object]" for plain objects; update
toErrorMessage to produce useful text by: if err is an Error return err.message
(and include err.stack optionally); else if err is a plain object attempt
JSON.stringify(err, null, 2) inside a try/catch to handle circular structures
and fall back to a safe inspect (or toString) when JSON fails; ensure the
function still handles primitives and null/undefined gracefully so callers of
toErrorMessage get a readable string for logging.

---

Duplicate comments:
In `@packages/leanix-bridge/src/sync-to-leanix.ts`:
- Around line 207-228: createRelation currently returns null when the GraphQL
mutation returns no relation id, which hides a server-side anomaly; update
createRelation to throw an Error (including context like sourceFactSheetId,
targetFactSheetId, relationType and any returned payload) when
data.createRelation?.relation?.id is missing so callers (e.g.,
syncRelationsToLeanix) receive and handle the failure instead of silently
skipping; ensure the thrown message is descriptive to aid debugging.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2c91d58d-60bd-4a72-90a0-30ee79c62432

📥 Commits

Reviewing files that changed from the base of the PR and between b8bd976 and 7868bf9.

📒 Files selected for processing (1)
  • packages/leanix-bridge/src/sync-to-leanix.ts

- to-leanix-inventory-dry-run: sort factSheets/relations by id, mappingProfile custom when options.mapping set
- sync-to-leanix.spec: mock throws on unexpected GraphQL query
- parse-drawio: validate likec4Id FQN and parent chain before use; add umlActor to actor detection
- package.json description: optional live sync or dry-run modes
- drawio-leanix-roundtrip: rename likec4IdToLeanixFactSheetId to likec4IdToLeanixId
- leanix-api-client: rate-limit lock and LeanixApiError on fetch/json errors

Made-with: Cursor
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/generators/src/drawio/parse-drawio.ts (2)

186-197: Simplify nested ternary for readability.

The static analysis flags the nested ternary at line 192 and negated conditions. Consider extracting to clearer logic:

♻️ Suggested refactor
 function extractStyleFromTagContent(fullTag: string, maxScan = STYLE_VALUE_SCAN_CHARS): string | undefined {
   const scan = fullTag.slice(0, maxScan)
   const styleDq = scan.toLowerCase().indexOf('style="')
   const styleSq = scan.toLowerCase().indexOf("style='")
-  const useDq = styleDq !== -1 && (styleSq === -1 || styleDq <= styleSq)
-  const styleIdx = useDq ? styleDq : styleSq
-  const quote = styleIdx !== -1 ? (useDq ? '"' : "'") : ''
-  if (styleIdx === -1 || !quote) return undefined
+  const hasDq = styleDq !== -1
+  const hasSq = styleSq !== -1
+  if (!hasDq && !hasSq) return undefined
+  const useDq = hasDq && (!hasSq || styleDq <= styleSq)
+  const styleIdx = useDq ? styleDq : styleSq
+  const quote = useDq ? '"' : "'"
   const valueStart = styleIdx + 7
   const valueEnd = scan.indexOf(quote, valueStart)
   return valueEnd !== -1 ? scan.slice(valueStart, valueEnd) : undefined
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/generators/src/drawio/parse-drawio.ts` around lines 186 - 197, In
extractStyleFromTagContent, replace the nested ternary/negated logic that sets
useDq, styleIdx and quote with clearer stepwise logic: compute scanLower once,
get styleDq and styleSq, if both are -1 return undefined, otherwise choose the
smaller non-negative index (styleIdx) and set quote to '"' if styleIdx ===
styleDq else "'" (or vice versa), then use valueStart = styleIdx + 7 and find
valueEnd as before; update the function to use those explicit branches to
improve readability and eliminate the nested ternary.

728-774: Cognitive complexity exceeds threshold (38 vs. 30 allowed).

The function correctly validates bridge IDs and parent chains (addressing the previous review feedback), but its cognitive complexity is flagged by static analysis. Consider extracting the bridge ID validation into a separate helper:

♻️ Suggested extraction for clarity
+/** Try to assign bridge-managed FQN if valid and parent chain matches; returns true if assigned. */
+function tryAssignBridgeFqn(
+  v: DrawioCell,
+  idToFqn: Map<string, string>,
+  usedNames: Set<string>,
+  isRootParent: (parent: string | undefined) => boolean,
+): boolean {
+  const bridgeId = v.likec4Id?.trim()
+  if (!bridgeId || !isValidFqn(bridgeId)) return false
+  const parentFqn = v.parent ? idToFqn.get(v.parent) : undefined
+  const useBridgeId = parentFqn === undefined
+    ? isRootParent(v.parent)
+    : bridgeId.startsWith(parentFqn + '.') && bridgeId.length > parentFqn.length + 1
+  if (!useBridgeId) return false
+  idToFqn.set(v.id, bridgeId)
+  for (const segment of bridgeId.split('.')) usedNames.add(segment)
+  return true
+}
+
 function assignFqnsToElementVertices(
   idToFqn: Map<string, string>,
   elementVertices: DrawioCell[],
   containerIdToTitle: Map<string, string>,
   isRootParent: (parent: string | undefined) => boolean,
   uniqueName: (base: string) => string,
   usedNames: Set<string>,
 ): void {
   const baseName = (v: DrawioCell) => v.value ?? containerIdToTitle.get(v.id) ?? v.id
   const idToVertex = new Map(elementVertices.map(v => [v.id, v]))
   const depth = (v: DrawioCell): number =>
     v.parent == null || !idToVertex.has(v.parent) ? 0 : 1 + depth(idToVertex.get(v.parent)!)
   const byDepth = [...elementVertices].sort((a, b) => depth(a) - depth(b))
-  for (const v of byDepth) {
-    const bridgeId = v.likec4Id?.trim()
-    if (bridgeId && isValidFqn(bridgeId)) {
-      const parentFqn = v.parent ? idToFqn.get(v.parent) : undefined
-      const useBridgeId =
-        parentFqn === undefined
-          ? isRootParent(v.parent)
-          : bridgeId.startsWith(parentFqn + '.') && bridgeId.length > parentFqn.length + 1
-      if (useBridgeId) {
-        idToFqn.set(v.id, bridgeId)
-        for (const segment of bridgeId.split('.')) usedNames.add(segment)
-      }
-    }
-  }
+  for (const v of byDepth) tryAssignBridgeFqn(v, idToFqn, usedNames, isRootParent)
   // ... rest unchanged
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/generators/src/drawio/parse-drawio.ts` around lines 728 - 774, The
assignFqnsToElementVertices function is too complex; extract the bridge-ID and
parent-chain logic into a small helper and also pull out the depth calculation
to reduce nesting and conditionals: create a helper like
isUsableBridgeId(bridgeId: string, v: DrawioCell, idToFqn: Map<string,string>,
idToVertex: Map<string,DrawioCell>, isRootParent: (p?:string)=>boolean) that
encapsulates the checks currently inside the first for-loop (including
isValidFqn, computing parentFqn, and the useBridgeId logic), and another small
helper depth(v: DrawioCell, idToVertex: Map<string,DrawioCell>) to replace the
inline recursive depth; then call these helpers from assignFqnsToElementVertices
(leaving the loop structure intact) to reduce cognitive complexity and improve
readability while preserving behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/generators/src/drawio/parse-drawio.ts`:
- Around line 186-197: In extractStyleFromTagContent, replace the nested
ternary/negated logic that sets useDq, styleIdx and quote with clearer stepwise
logic: compute scanLower once, get styleDq and styleSq, if both are -1 return
undefined, otherwise choose the smaller non-negative index (styleIdx) and set
quote to '"' if styleIdx === styleDq else "'" (or vice versa), then use
valueStart = styleIdx + 7 and find valueEnd as before; update the function to
use those explicit branches to improve readability and eliminate the nested
ternary.
- Around line 728-774: The assignFqnsToElementVertices function is too complex;
extract the bridge-ID and parent-chain logic into a small helper and also pull
out the depth calculation to reduce nesting and conditionals: create a helper
like isUsableBridgeId(bridgeId: string, v: DrawioCell, idToFqn:
Map<string,string>, idToVertex: Map<string,DrawioCell>, isRootParent:
(p?:string)=>boolean) that encapsulates the checks currently inside the first
for-loop (including isValidFqn, computing parentFqn, and the useBridgeId logic),
and another small helper depth(v: DrawioCell, idToVertex:
Map<string,DrawioCell>) to replace the inline recursive depth; then call these
helpers from assignFqnsToElementVertices (leaving the loop structure intact) to
reduce cognitive complexity and improve readability while preserving behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6a4b4e74-4782-40d0-8e1a-db65febd2ee0

📥 Commits

Reviewing files that changed from the base of the PR and between 7868bf9 and 853840b.

📒 Files selected for processing (8)
  • packages/generators/src/drawio/parse-drawio.ts
  • packages/leanix-bridge/README.md
  • packages/leanix-bridge/package.json
  • packages/leanix-bridge/src/drawio-leanix-roundtrip.spec.ts
  • packages/leanix-bridge/src/drawio-leanix-roundtrip.ts
  • packages/leanix-bridge/src/leanix-api-client.ts
  • packages/leanix-bridge/src/sync-to-leanix.spec.ts
  • packages/leanix-bridge/src/to-leanix-inventory-dry-run.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/leanix-bridge/src/sync-to-leanix.spec.ts
  • packages/leanix-bridge/src/leanix-api-client.ts
  • packages/leanix-bridge/src/to-leanix-inventory-dry-run.ts
  • packages/leanix-bridge/README.md
  • packages/leanix-bridge/src/drawio-leanix-roundtrip.spec.ts
  • packages/leanix-bridge/package.json
  • packages/leanix-bridge/src/drawio-leanix-roundtrip.ts

…likec4IdAttribute fallback and backfill

- toErrorMessage: Error with stack, objects via JSON.stringify(err,null,2), safe fallback
- createRelation: throw when no relation id, syncRelationsToLeanix already records to errors
- likec4IdAttribute: fallback to name+type when attribute lookup misses; backfill fact sheet with likec4Id via patchFactSheetAttribute when found by name+type; same fallback in planSyncToLeanix

Made-with: Cursor
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/leanix-bridge/src/sync-to-leanix.ts (1)

422-428: Plan always shows relations as 'create' regardless of manifest state.

The planSyncToLeanix function marks all relations with action: 'create', but syncRelationsToLeanix at line 362 skips creation when rel.external?.[LEANIX_PROVIDER]?.relationId already exists in the manifest. This means the plan may overstate the number of relations to create if the manifest already contains LeanIX relation IDs from a previous sync.

Consider either:

  • Accepting a manifest parameter in planSyncToLeanix to filter out already-synced relations, or
  • Documenting this behavior in the SyncPlan.relationPlans JSDoc
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/leanix-bridge/src/sync-to-leanix.ts` around lines 422 - 428,
planSyncToLeanix currently sets action: 'create' for every
leanixDryRun.relations entry which overstates creates; change planSyncToLeanix
signature to accept an optional manifest parameter and filter
leanixDryRun.relations before building SyncPlan.relationPlans so any relation
with rel.external?.[LEANIX_PROVIDER]?.relationId in the manifest is skipped (or
marked action: 'none' if you prefer), i.e. update the mapping of
leanixDryRun.relations in planSyncToLeanix to check the provided manifest for
existing relation IDs and only include new relations in the produced
SyncPlan.relationPlans; keep references to planSyncToLeanix,
SyncPlan.relationPlans, leanixDryRun.relations, syncRelationsToLeanix and
LEANIX_PROVIDER to locate and align behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/leanix-bridge/src/sync-to-leanix.ts`:
- Around line 422-428: planSyncToLeanix currently sets action: 'create' for
every leanixDryRun.relations entry which overstates creates; change
planSyncToLeanix signature to accept an optional manifest parameter and filter
leanixDryRun.relations before building SyncPlan.relationPlans so any relation
with rel.external?.[LEANIX_PROVIDER]?.relationId in the manifest is skipped (or
marked action: 'none' if you prefer), i.e. update the mapping of
leanixDryRun.relations in planSyncToLeanix to check the provided manifest for
existing relation IDs and only include new relations in the produced
SyncPlan.relationPlans; keep references to planSyncToLeanix,
SyncPlan.relationPlans, leanixDryRun.relations, syncRelationsToLeanix and
LEANIX_PROVIDER to locate and align behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e069cd8f-1eda-42b0-9ee4-c0b65936b0c2

📥 Commits

Reviewing files that changed from the base of the PR and between 853840b and 19bc423.

📒 Files selected for processing (1)
  • packages/leanix-bridge/src/sync-to-leanix.ts

Copy link
Copy Markdown
Member

@davydkov davydkov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

2 participants