feat(leanix-bridge): Phase 2 & 3, CLI, tooling docs (Draw.io + CLI)#2768
Conversation
…nsibility, unit test - Add cli/bridge/shared.ts: asBridgeModel, buildBridgeArtifacts, writeBridgeArtifacts, ERR_*, BRIDGE_ARTIFACT_NAMES (remove G5 duplication, G25 magic strings) - Refactor leanix-dry-run handler to use shared; refactor sync leanix to use shared + createLeanixClientFromEnv - Fix leanix-dry-run.spec: unit test with inline fixture (no fromWorkspace), 4 tests pass; matches repo pattern Made-with: Cursor
- Phase 2: leanix-inventory-snapshot, reconcile (inbound) - Phase 3: drift-report, impact-report, adr-generation, governance-checks - CLI: gen leanix-inventory-snapshot, gen leanix-reconcile; sync leanix; bridge leanix-client - Docs: Draw.io Export profiles (default/leanix), CLI --profile option - Refactors (Uncle Bob): sync-to-leanix, to-bridge-manifest, to-leanix-inventory-dry-run, report, leanix-api-client, drawio-leanix-roundtrip - E2E: likec4-cli-bridge.spec.ts Made-with: Cursor
Made-with: Cursor
…idge index Made-with: Cursor
…reference) Made-with: Cursor
…t tests; review responses doc; parse-drawio TODO Made-with: Cursor
….gitignore docs - README: Phase 2 command descriptions, --profile leanix note, Phase 2/3 example - CLI: timer in try/finally (inventory-snapshot, dry-run, reconcile) - CLI: SyncCmdArgs + isSyncCmdArgs type guard; runSyncLeanix JSDoc - leanix-reconcile: type guards from @likec4/leanix-bridge/validate - leanix-bridge: validate.ts (isBridgeManifest, isLeanixInventorySnapshot) - leanix-bridge: reconcile destructure candidate; adr formatIsoDateString defensive - leanix-bridge: fetchAllRelations parallel (concurrency); fetchAllFactSheets retry; fetchPage(cursor) - Specs: stronger assertions (dry-run, inventory-snapshot mock + relations + likec4IdAttribute) - e2e + root: @likec4/leanix-bridge override/tarball for install; pretest:e2e packs leanix-bridge - .gitignore: documentação de apoio em docs/ Made-with: Cursor
…rrors, timer finally, validate guards, relation retry Made-with: Cursor
- README: Phase 2 example use reconcileInventoryWithManifest(snapshot, manifest) - leanix-inventory-snapshot: validate maxFactSheets (non-negative int); cap result in page loop; single withGraphQLRetry for fetchPage and relations - validate: isRecord + explicit type checks (no 'as'); isBridgeManifest/isLeanixInventorySnapshot strict - sync leanix: guard dryRun&&apply throw; plan.errors fail command (like apply path) - leanix-client: trim env vars, blank token/baseUrl treated as unset - leanix-reconcile: log on workspace load fallback - JSDoc: leanixInventorySnapshotHandler, leanixDryRunHandler, createLeanixClientFromEnv, asBridgeModel, buildBridgeArtifacts, writeBridgeArtifacts, toReport, mergeWithDefault Made-with: Cursor
…op tracking docs de apoio - .changeset: @likec4/generators and likec4 as minor (semver for new CLI/API) - adr-generation: add Decision section to generateAdrFromDriftReport - reconcile: use NAME_TYPE_SEP instead of hardcoded \\0 - docs/PR-REVIEW-AND-BOT-RESPONSES.md: remove from repo (keep in .gitignore) Made-with: Cursor
…ndex signature) Made-with: Cursor
🦋 Changeset detectedLatest commit: 533ede7 The changes in this PR will be included in the next version bump. This PR includes changesets to release 20 packages
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 |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a new Changes
Sequence Diagram(s)sequenceDiagram
rect rgba(220,230,241,0.5)
actor User
participant CLI as likec4 CLI
participant Workspace as LikeC4 Workspace
participant Bridge as Bridge utils
participant LeanIX as LeanIX API
participant Reconcile as Reconcile module
participant Reporter as Drift/Impact/ADR
end
User->>CLI: run sync leanix [--dry-run|--apply]
CLI->>Workspace: open workspace & layout model
Workspace-->>CLI: layouted model
CLI->>Bridge: buildBridgeArtifacts(manifest, dryRun)
Bridge-->>CLI: write artifacts (manifest, dryRun, report)
CLI->>LeanIX: fetchLeanixInventorySnapshot (optional)
LeanIX-->>CLI: snapshot
CLI->>Reconcile: reconcileInventoryWithManifest(manifest, snapshot)
Reconcile-->>CLI: reconciliation result
CLI->>Reporter: buildDriftReport / impactReport / generateAdr
Reporter-->>CLI: reports
CLI->>User: write outputs & log summary
sequenceDiagram
rect rgba(235,247,233,0.5)
participant Generator as Draw.io generator
participant Parser as parse-drawio
participant Mapping as leanix mapping
participant Output as Bridge manifest
end
Generator->>Parser: parse diagram (vertices, edges)
Parser->>Parser: compute vertexDepth & validate bridgeId usability
Parser->>Mapping: assign FQNs or bridge IDs
Mapping-->>Output: produce elements for manifest
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
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. Comment |
|
@coderabbitai can you please review this? |
|
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (5)
.gitignore (1)
138-139: Optional: Remove redundant pattern.The pattern
docs/PR-*-REVIEW.mdon line 138 is redundant sincedocs/PR-*.mdon line 139 already matches files likePR-123-REVIEW.md. You can safely remove line 138 to simplify the ignore rules.♻️ Simplify by removing redundant pattern
-docs/PR-*-REVIEW.md docs/PR-*.md🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.gitignore around lines 138 - 139, The .gitignore contains a redundant pattern: remove the specific pattern "docs/PR-*-REVIEW.md" because the broader pattern "docs/PR-*.md" already matches those files; delete the "docs/PR-*-REVIEW.md" entry so only "docs/PR-*.md" remains.packages/likec4/src/cli/codegen/leanix-reconcile.ts (1)
79-85: Promote dry-run enrichment fallback fromdebugtowarn.If workspace enrichment fails, reconciliation still runs but with reduced matching quality;
warnmakes that degradation visible to normal users.🔎 Suggested tweak
- logger.debug( + logger.warn( `Could not load workspace for dryRun enrichment; proceeding without it: ${ err instanceof Error ? err.message : String(err) }`, )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/likec4/src/cli/codegen/leanix-reconcile.ts` around lines 79 - 85, In the catch block that currently logs "Could not load workspace for dryRun enrichment; proceeding without it..." using logger.debug and then sets dryRun = undefined, change the log call to logger.warn so the failure is visible to normal users; keep the existing error formatting (err instanceof Error ? err.message : String(err)) and preserve setting dryRun = undefined and surrounding behavior in the same catch block to maintain functionality.e2e/src/likec4-cli-bridge.spec.ts (1)
12-63: Consider extracting shared e2e artifact assertions.Both tests repeat the same cleanup, directory listing, and JSON validation flow; a small helper would reduce duplication and future drift.
♻️ Refactor sketch
+function resetOutDir() { + rmSync(outDirAbs, { recursive: true, force: true }) + mkdirSync(outDirAbs, { recursive: true }) +} + +function expectBridgeArtifacts(expect: any) { + const entries = readdirSync(outDirAbs, { withFileTypes: true }).map(e => e.name).sort() + expect(entries).toContain('manifest.json') + expect(entries).toContain('leanix-dry-run.json') + expect(entries).toContain('report.json') +}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/src/likec4-cli-bridge.spec.ts` around lines 12 - 63, The two tests 'LikeC4 CLI - gen leanix-dry-run produces manifest, leanix-dry-run, report' and 'LikeC4 CLI - sync leanix --dry-run produces same artifacts and does not require token' duplicate cleanup, dir creation, listing and JSON assertions; extract that logic into a small helper (e.g., createCleanOutDir(outDirAbs) for rmSync/mkdirSync and assertE2EArtifacts(outDirAbs) for checking entries and parsing manifest/leanix-dry-run/report) and call those helpers from both test bodies (replace the repeated rmSync/mkdirSync, readdirSync checks and JSON parse/assert blocks). Ensure helper names are unique (createCleanOutDir, assertE2EArtifacts) so you can locate and replace the repeated code in the tests.packages/leanix-bridge/src/governance-checks.ts (1)
43-49: Consider simplifying the nested ternary for readability.The nested ternary on line 47 works correctly but is flagged for complexity. A minor refactor would improve clarity.
♻️ Simplified buildCheck return
function buildCheck( id: string, name: string, passed: boolean, failedMessage?: string, ): GovernanceCheckResult { - return { - id, - name, - passed, - ...(passed ? {} : failedMessage !== undefined ? { message: failedMessage } : {}), - } + const result: GovernanceCheckResult = { id, name, passed } + if (!passed && failedMessage !== undefined) { + result.message = failedMessage + } + return result }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/leanix-bridge/src/governance-checks.ts` around lines 43 - 49, The return object in the function that produces the governance check (the block returning id, name, passed, ...(passed ? {} : failedMessage !== undefined ? { message: failedMessage } : {})) is using a nested ternary that hurts readability; replace it with a simple conditional before the return (e.g., compute a messageProps variable if !passed and failedMessage exists, or set it to an empty object) and then spread messageProps into the returned object, referencing the same identifiers id, name, passed, and failedMessage to locate the code.packages/leanix-bridge/src/reconcile.ts (1)
73-160: Consider extracting helper functions to reduce cognitive complexity.SonarCloud flags this function with cognitive complexity of 34 (allowed: 30). The matching logic is sound, but the function could be more maintainable if split into smaller helpers.
♻️ Suggested refactoring approach
Extract the three matching strategies into separate functions:
+function tryMatchByManifestFactSheetId( + entity: ManifestEntity, + canonicalId: CanonicalId, + snapshotById: Map<string, LeanixFactSheetSnapshotItem>, + usedFactSheetIds: Set<string>, +): MatchedPair | null { + const leanixExternal = entity.external?.[LEANIX_PROVIDER] + const manifestFactSheetId = leanixExternal?.factSheetId ?? leanixExternal?.externalId + if (manifestFactSheetId != null) { + const fs = snapshotById.get(manifestFactSheetId) + if (fs) { + usedFactSheetIds.add(fs.id) + return { canonicalId, factSheetId: fs.id, name: fs.name, type: fs.type } + } + } + return null +} + +function tryMatchByLikec4Id( + canonicalId: CanonicalId, + snapshotByLikec4Id: Map<string, LeanixFactSheetSnapshotItem>, + usedFactSheetIds: Set<string>, +): MatchedPair | null { + const byLikec4 = snapshotByLikec4Id.get(canonicalId) + if (byLikec4 && !usedFactSheetIds.has(byLikec4.id)) { + usedFactSheetIds.add(byLikec4.id) + return { canonicalId, factSheetId: byLikec4.id, name: byLikec4.name, type: byLikec4.type } + } + return null +}Then call these helpers in sequence within the main loop.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/leanix-bridge/src/reconcile.ts` around lines 73 - 160, The function reconcileInventoryWithManifest is too complex; extract the three matching strategies into small helpers to reduce cognitive complexity: create matchByManifestFactSheetId(manifestFactSheetId, snapshotById, matched, usedFactSheetIds) to handle the manifest.external LEANIX_PROVIDER lookup and immediate match, matchByLikec4Id(canonicalId, snapshotByLikec4Id, matched, usedFactSheetIds) to handle the snapshotByLikec4Id lookup and match, and matchByNameAndType(canonicalId, dryRunByCanonicalId, snapshotByNameAndType, usedFactSheetIds, unmatchedInLikec4, ambiguous, matched) to compute entityName/entityType, build nameTypeKey, filter unused candidates and push either unmatchedInLikec4, a single matched entry, or ambiguous; then simplify the main loop in reconcileInventoryWithManifest to call these three helpers in order (short-circuiting when a match is made) and pass in the maps/sets (snapshotById, snapshotByLikec4Id, snapshotByNameAndType, dryRunByCanonicalId, usedFactSheetIds) and arrays (matched, unmatchedInLikec4, ambiguous) so the top-level function becomes a thin orchestrator and the complex matching logic lives in the new functions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.changeset/leanix-phase2-phase3-cli-docs.md:
- Around line 11-13: Update the release notes to use the canonical exported API
names instead of shorthand: replace occurrences of leanixInventorySnapshot,
reconcile, buildImpactReport, and generateAdrReport with the exact exported
function/command names as defined in this PR's public API (use the precise
export identifiers from the changed files), and ensure the Phase 2 / Phase 3
entries and the Refactors list match the concrete exported names used in the
codebase.
In `@packages/generators/src/drawio/parse-drawio.spec.ts`:
- Around line 118-122: The current test in parse-drawio.spec.ts relaxed the
assertion by allowing either actor output or a container fallback (variables
hasActor and hasContainer); revert to the strict contract by asserting only the
actor mapping: require hasActor to be true (expect(hasActor).toBe(true)) and
remove the OR acceptance. Then add a new, separate test that exercises the
fallback path (e.g., a dedicated fixture or manipulated input that lacks raw
style) and asserts hasContainer is true there so regressions in actor handling
remain visible; reference the existing hasActor and hasContainer checks to
implement both the strict actor test and the isolated fallback test.
In `@packages/generators/src/drawio/parse-drawio.ts`:
- Around line 730-735: vertexDepth currently recurses without cycle detection
and can overflow on cyclic parent links; replace the recursive implementation in
function vertexDepth(v: DrawioCell, idToVertex: Map<string, DrawioCell>) with a
cycle-safe, cached algorithm: use a memo Map<string, number> for computed depths
and a temporary Set<string> (or stack) to detect cycles while walking parents
(follow v.parent via idToVertex), return 0 when a cycle or missing parent is
encountered, cache each computed depth to avoid repeated work, and ensure any
other place using the same logic (the related depth computation referenced
nearby) uses the same memoized, cycle-guarded routine.
In `@packages/leanix-bridge/README.md`:
- Line 127: Update the README entry for buildDriftReport to match the actual
implementation: change the documented signature from buildDriftReport(manifest,
snapshot) to buildDriftReport(reconciliation: ReconciliationResult) and describe
that it accepts a single ReconciliationResult (from drift-report.ts) and returns
a DriftReport; ensure you reference the ReconciliationResult parameter name and
return type to keep docs and the buildDriftReport function signature consistent.
- Line 130: The README lists runGovernanceChecks(manifest, options?) but the
actual exported function signature is runGovernanceChecks(reconciliation:
ReconciliationResult, options: GovernanceCheckOptions = {}): GovernanceReport;
update the README entry for runGovernanceChecks to show the correct parameter
name and types (ReconciliationResult and optional GovernanceCheckOptions) and
briefly describe that it accepts a ReconciliationResult rather than a manifest
and returns a GovernanceReport with pass/fail and violations.
In `@packages/leanix-bridge/src/validate.ts`:
- Around line 16-31: The current type guard isBridgeManifest performs only
shallow checks and can return false positives; update it (and the similar guard
around lines 37-44) to validate nested shapes: ensure obj['entities'] and
obj['views'] are non-null objects whose values are Records (not arrays) with
each entry matching the expected entity/view schema (check required string
fields and types), and ensure obj['relations'] is an array whose items are
objects with the required relation properties (e.g., source, target, type as
strings or expected types). Locate and harden the predicates in isBridgeManifest
(and the other guard) to iterate and validate nested entries’ keys/types so the
runtime narrowing is safe.
In `@packages/likec4/src/cli/codegen/leanix-inventory-snapshot.ts`:
- Around line 35-41: The code forwards an empty-string likec4IdAttribute to
fetchLeanixInventorySnapshot because the check uses != null only; update the
construction of the options object passed to fetchLeanixInventorySnapshot
(around variables likec4IdAttribute, requireLeanixClient,
fetchLeanixInventorySnapshot) to ignore blank values by verifying
likec4IdAttribute is a non-empty string (e.g., check typeof likec4IdAttribute
=== 'string' && likec4IdAttribute.trim() !== '') before spreading {
likec4IdAttribute } into the options so empty or whitespace-only strings are not
sent to the API.
In `@packages/likec4/src/cli/sync/index.ts`:
- Around line 49-57: The CLI currently defines options 'dry-run' (default true)
and 'apply' and then forces dryRun=false when apply=true, which silently
overrides an explicit --dry-run; update the yargs setup to declare these options
as mutually exclusive using yargs.conflicts('apply', 'dry-run') (or the reverse)
so the parser rejects passing both flags, and then compute the effective mode
after parsing only when neither flag was explicitly provided (i.e., derive
dryRun from parsed flags with a sensible default), replacing the existing forced
assignment logic around apply/dryRun in the sync command setup.
---
Nitpick comments:
In @.gitignore:
- Around line 138-139: The .gitignore contains a redundant pattern: remove the
specific pattern "docs/PR-*-REVIEW.md" because the broader pattern
"docs/PR-*.md" already matches those files; delete the "docs/PR-*-REVIEW.md"
entry so only "docs/PR-*.md" remains.
In `@e2e/src/likec4-cli-bridge.spec.ts`:
- Around line 12-63: The two tests 'LikeC4 CLI - gen leanix-dry-run produces
manifest, leanix-dry-run, report' and 'LikeC4 CLI - sync leanix --dry-run
produces same artifacts and does not require token' duplicate cleanup, dir
creation, listing and JSON assertions; extract that logic into a small helper
(e.g., createCleanOutDir(outDirAbs) for rmSync/mkdirSync and
assertE2EArtifacts(outDirAbs) for checking entries and parsing
manifest/leanix-dry-run/report) and call those helpers from both test bodies
(replace the repeated rmSync/mkdirSync, readdirSync checks and JSON parse/assert
blocks). Ensure helper names are unique (createCleanOutDir, assertE2EArtifacts)
so you can locate and replace the repeated code in the tests.
In `@packages/leanix-bridge/src/governance-checks.ts`:
- Around line 43-49: The return object in the function that produces the
governance check (the block returning id, name, passed, ...(passed ? {} :
failedMessage !== undefined ? { message: failedMessage } : {})) is using a
nested ternary that hurts readability; replace it with a simple conditional
before the return (e.g., compute a messageProps variable if !passed and
failedMessage exists, or set it to an empty object) and then spread messageProps
into the returned object, referencing the same identifiers id, name, passed, and
failedMessage to locate the code.
In `@packages/leanix-bridge/src/reconcile.ts`:
- Around line 73-160: The function reconcileInventoryWithManifest is too
complex; extract the three matching strategies into small helpers to reduce
cognitive complexity: create matchByManifestFactSheetId(manifestFactSheetId,
snapshotById, matched, usedFactSheetIds) to handle the manifest.external
LEANIX_PROVIDER lookup and immediate match, matchByLikec4Id(canonicalId,
snapshotByLikec4Id, matched, usedFactSheetIds) to handle the snapshotByLikec4Id
lookup and match, and matchByNameAndType(canonicalId, dryRunByCanonicalId,
snapshotByNameAndType, usedFactSheetIds, unmatchedInLikec4, ambiguous, matched)
to compute entityName/entityType, build nameTypeKey, filter unused candidates
and push either unmatchedInLikec4, a single matched entry, or ambiguous; then
simplify the main loop in reconcileInventoryWithManifest to call these three
helpers in order (short-circuiting when a match is made) and pass in the
maps/sets (snapshotById, snapshotByLikec4Id, snapshotByNameAndType,
dryRunByCanonicalId, usedFactSheetIds) and arrays (matched, unmatchedInLikec4,
ambiguous) so the top-level function becomes a thin orchestrator and the complex
matching logic lives in the new functions.
In `@packages/likec4/src/cli/codegen/leanix-reconcile.ts`:
- Around line 79-85: In the catch block that currently logs "Could not load
workspace for dryRun enrichment; proceeding without it..." using logger.debug
and then sets dryRun = undefined, change the log call to logger.warn so the
failure is visible to normal users; keep the existing error formatting (err
instanceof Error ? err.message : String(err)) and preserve setting dryRun =
undefined and surrounding behavior in the same catch block to maintain
functionality.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 87f6ee29-117c-4488-91b3-622dec4ccd2c
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (42)
.changeset/leanix-phase2-phase3-cli-docs.md.github/workflows/checks.yaml.github/workflows/e2e-update-screenshots.yaml.gitignoreapps/docs/src/content/docs/tooling/cli.mdxapps/docs/src/content/docs/tooling/drawio.mdxe2e/package.jsone2e/src/likec4-cli-bridge.spec.tspackage.jsonpackages/generators/src/drawio/generate-drawio.spec.tspackages/generators/src/drawio/parse-drawio.spec.tspackages/generators/src/drawio/parse-drawio.tspackages/generators/src/index.tspackages/leanix-bridge/README.mdpackages/leanix-bridge/src/adr-generation.spec.tspackages/leanix-bridge/src/adr-generation.tspackages/leanix-bridge/src/drift-report.spec.tspackages/leanix-bridge/src/drift-report.tspackages/leanix-bridge/src/governance-checks.spec.tspackages/leanix-bridge/src/governance-checks.tspackages/leanix-bridge/src/impact-report.spec.tspackages/leanix-bridge/src/impact-report.tspackages/leanix-bridge/src/index.tspackages/leanix-bridge/src/leanix-inventory-snapshot.spec.tspackages/leanix-bridge/src/leanix-inventory-snapshot.tspackages/leanix-bridge/src/mapping.tspackages/leanix-bridge/src/reconcile.spec.tspackages/leanix-bridge/src/reconcile.tspackages/leanix-bridge/src/report.tspackages/leanix-bridge/src/validate.tspackages/likec4/package.jsonpackages/likec4/src/cli/bridge/leanix-client.tspackages/likec4/src/cli/bridge/shared.tspackages/likec4/src/cli/codegen/index.tspackages/likec4/src/cli/codegen/leanix-dry-run.spec.tspackages/likec4/src/cli/codegen/leanix-dry-run.tspackages/likec4/src/cli/codegen/leanix-inventory-snapshot.tspackages/likec4/src/cli/codegen/leanix-reconcile.tspackages/likec4/src/cli/index.tspackages/likec4/src/cli/sync/index.tspackages/likec4/src/cli/sync/leanix.tspackages/likec4/tsconfig.cli.json
…hangeset - .gitignore: remove redundant docs/PR-*-REVIEW.md - leanix-reconcile: log dryRun enrichment failure as warn - governance-checks: simplify buildCheck (no nested ternary) - leanix-bridge README: fix buildDriftReport/runGovernanceChecks signatures - leanix-inventory-snapshot: do not pass blank likec4IdAttribute to API - e2e: extract createCleanOutDir, assertE2EArtifacts; add JSDoc - reconcile: extract tryMatchByManifestFactSheetId, tryMatchByLikec4Id, resolveByNameAndType; add JSDoc - changeset: use canonical API names in release notes Made-with: Cursor
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/leanix-bridge/README.md (1)
116-131:⚠️ Potential issue | 🟡 MinorUpdate parameter names in API documentation to match implementations.
The API section has two parameter naming inconsistencies:
- Line 128:
impactReportFromSyncPlan(syncPlan)should beimpactReportFromSyncPlan(plan)— the implementation usesplan: SyncPlanas the parameter name.- Line 129:
generateAdrFromDriftReport(driftReport, options?)should begenerateAdrFromDriftReport(drift, options?)— the implementation usesdrift: DriftReportas the parameter name.All other documented signatures (
reconcileInventoryWithManifest,buildDriftReport,generateAdrFromReconciliation,runGovernanceChecks) match their implementations.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/leanix-bridge/README.md` around lines 116 - 131, The README API docs use parameter names that don't match the implementations; update the signatures for impactReportFromSyncPlan and generateAdrFromDriftReport so they use the actual parameter names used in code: change impactReportFromSyncPlan(syncPlan) to impactReportFromSyncPlan(plan) and change generateAdrFromDriftReport(driftReport, options?) to generateAdrFromDriftReport(drift, options?), ensuring the rest of the signatures remain unchanged and references to these functions (impactReportFromSyncPlan, generateAdrFromDriftReport) are consistent.
🧹 Nitpick comments (2)
packages/leanix-bridge/src/reconcile.ts (1)
109-119: Redundant null check after length validation.After confirming
candidates.length === 1, the element at index 0 is guaranteed to exist. Theif (candidate)check on line 111 is defensive but unnecessary.♻️ Simplified version
} else if (candidates.length === 1) { - const candidate = candidates[0] - if (candidate) { - matched.push({ - canonicalId, - factSheetId: candidate.id, - name: candidate.name, - type: candidate.type, - }) - usedFactSheetIds.add(candidate.id) - } + const candidate = candidates[0]! + matched.push({ + canonicalId, + factSheetId: candidate.id, + name: candidate.name, + type: candidate.type, + }) + usedFactSheetIds.add(candidate.id) } else {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/leanix-bridge/src/reconcile.ts` around lines 109 - 119, The check `if (candidate)` is redundant because you already confirmed `candidates.length === 1`; remove the unnecessary `if (candidate)` guard and directly use `const candidate = candidates[0]` to push into `matched` and add to `usedFactSheetIds` (update the block around the `candidates.length === 1` branch that defines `candidate`, the `matched.push({...})` call, and the `usedFactSheetIds.add(candidate.id)` call).packages/leanix-bridge/src/governance-checks.ts (1)
95-100: Consider using a fresh timestamp for the governance report.The
generatedAtfield is copied fromreconciliation.generatedAt, which reflects when the reconciliation was performed, not when the governance checks ran. If these can happen at different times (e.g., reconciliation is cached and governance checks run later), consider using a fresh timestamp for traceability.♻️ Suggested change
+export function runGovernanceChecks( + reconciliation: ReconciliationResult, + options: GovernanceCheckOptions = {}, +): GovernanceReport { + const opts = { ...DEFAULT_OPTIONS, ...options } + const checks: GovernanceCheckResult[] = [] + const { summary } = reconciliation // ... checks logic ... const passed = checks.every(c => c.passed) return { passed, - generatedAt: reconciliation.generatedAt, + generatedAt: new Date().toISOString(), checks, } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/leanix-bridge/src/governance-checks.ts` around lines 95 - 100, The governance report currently returns generatedAt: reconciliation.generatedAt which uses the reconciliation timestamp; update the return in the function that builds the report (the object containing passed, generatedAt, checks) to set generatedAt to a fresh timestamp (e.g., Date.now() or new Date().toISOString()) so the governance-checks.ts report reflects when checks were run rather than when reconciliation occurred; keep the rest of the structure (passed, checks) unchanged.
🤖 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/likec4/src/cli/codegen/leanix-reconcile.ts`:
- Around line 79-86: The catch currently swallows all errors when loading the
workspace for dryRun enrichment (logger.warn ... dryRun = undefined); change it
to distinguish optional vs user-supplied failures: detect whether the
workspace/project was explicitly provided by the user (the flag/variable that
triggers loading the workspace) and if so surface the error (rethrow or
logger.error + exit) instead of silently degrading, otherwise keep the warning
and set dryRun = undefined; update the catch in leanix-reconcile.ts around the
dryRun/workspace load to inspect the input flags/variables and branch
accordingly.
---
Outside diff comments:
In `@packages/leanix-bridge/README.md`:
- Around line 116-131: The README API docs use parameter names that don't match
the implementations; update the signatures for impactReportFromSyncPlan and
generateAdrFromDriftReport so they use the actual parameter names used in code:
change impactReportFromSyncPlan(syncPlan) to impactReportFromSyncPlan(plan) and
change generateAdrFromDriftReport(driftReport, options?) to
generateAdrFromDriftReport(drift, options?), ensuring the rest of the signatures
remain unchanged and references to these functions (impactReportFromSyncPlan,
generateAdrFromDriftReport) are consistent.
---
Nitpick comments:
In `@packages/leanix-bridge/src/governance-checks.ts`:
- Around line 95-100: The governance report currently returns generatedAt:
reconciliation.generatedAt which uses the reconciliation timestamp; update the
return in the function that builds the report (the object containing passed,
generatedAt, checks) to set generatedAt to a fresh timestamp (e.g., Date.now()
or new Date().toISOString()) so the governance-checks.ts report reflects when
checks were run rather than when reconciliation occurred; keep the rest of the
structure (passed, checks) unchanged.
In `@packages/leanix-bridge/src/reconcile.ts`:
- Around line 109-119: The check `if (candidate)` is redundant because you
already confirmed `candidates.length === 1`; remove the unnecessary `if
(candidate)` guard and directly use `const candidate = candidates[0]` to push
into `matched` and add to `usedFactSheetIds` (update the block around the
`candidates.length === 1` branch that defines `candidate`, the
`matched.push({...})` call, and the `usedFactSheetIds.add(candidate.id)` call).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 9453bea6-d81b-4991-8307-5a4b9c4c8923
📒 Files selected for processing (8)
.changeset/leanix-phase2-phase3-cli-docs.md.gitignoree2e/src/likec4-cli-bridge.spec.tspackages/leanix-bridge/README.mdpackages/leanix-bridge/src/governance-checks.tspackages/leanix-bridge/src/reconcile.tspackages/likec4/src/cli/codegen/leanix-inventory-snapshot.tspackages/likec4/src/cli/codegen/leanix-reconcile.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- .gitignore
- e2e/src/likec4-cli-bridge.spec.ts
- .changeset/leanix-phase2-phase3-cli-docs.md
- leanix-bridge: contracts, validate (type guards), report (buildBridgeReport), leanix-graphql-operations, sync (resolveExistingFactSheetId), adr-generation, inventory (mapNodeToFactSheetItem), CLI runSyncPlan/runSyncApply - config: landingPage single source (LandingPageSchema), validate once and merge; smoke test for landingPage; skip block for Vitest quirk - core guards: hasProp curried + two-arg; _hasProp Record<string,unknown>; spec describe aligned to two-argument form Made-with: Cursor
…typecheck Made-with: Cursor
There was a problem hiding this comment.
Actionable comments posted: 7
♻️ Duplicate comments (1)
packages/likec4/src/cli/codegen/leanix-reconcile.ts (1)
97-107:⚠️ Potential issue | 🟠 MajorDon't silently degrade when explicit workspace/project loading fails.
This catch block turns every workspace/project load error into a warning. If the user asked for a specific project and typoed it, the command still exits successfully with a lower-fidelity reconciliation report, which hides the real configuration problem.
🛠️ Proposed fix
let dryRun: LeanixInventoryDryRun | undefined try { dryRun = await loadDryRunFromWorkspace(workspacePath, project, useDotBin) } catch (err) { - logger.warn( - `Could not load workspace for dryRun enrichment; proceeding without it: ${ - err instanceof Error ? err.message : String(err) - }`, - ) - dryRun = undefined + const msg = err instanceof Error ? err.message : String(err) + if (project) { + logger.error(`Failed to load workspace for dryRun enrichment: ${msg}`) + throw err + } + logger.warn(`Could not load workspace for dryRun enrichment; proceeding without it: ${msg}`) + dryRun = undefined }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/likec4/src/cli/codegen/leanix-reconcile.ts` around lines 97 - 107, The catch currently swallows all errors from loadDryRunFromWorkspace which hides real config issues; update the catch in the block around loadDryRunFromWorkspace(workspacePath, project, useDotBin) to only degrade to a warning when the project argument was not explicitly provided, but if project (or any explicit workspace selector) was passed, rethrow the error (or return a non-zero failure) instead of assigning dryRun = undefined; use the existing logger.warn for the non-fatal case and preserve the original error when rethrowing so callers can fail fast and surface typos or bad configuration.
🧹 Nitpick comments (4)
packages/config/src/schema.ts (1)
300-329: Consider omittinglandingPagefrom the main parse to avoid double validation.The current approach validates
landingPagetwice: once explicitly viaLandingPageSchema.safeParse(line 304) and again as part ofLikeC4ProjectJsonConfigSchema.safeParse(line 310). While the workaround for Zod v4 stripping optional union keys is documented, you could use.omit({ landingPage: true })on the main schema parse to avoid redundant validation.Also, the cast on line 314 (
as unknown as LikeC4ProjectConfig) bypasses type safety. This is necessary becausegeneratorsisn't part ofLikeC4ProjectJsonConfigSchema, but it's worth noting the coupling.♻️ Optional: Avoid double validation
- const parsed = LikeC4ProjectJsonConfigSchema.safeParse(config) + const parsed = LikeC4ProjectJsonConfigSchema.omit({ landingPage: true }).safeParse(config)This ensures
landingPageis validated only once via the explicitLandingPageSchema.safeParsecall.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/config/src/schema.ts` around lines 300 - 329, validateProjectConfig currently validates landingPage twice—once with LandingPageSchema.safeParse and again when parsing LikeC4ProjectJsonConfigSchema—so change the main parse to omit landingPage before safeParse (use LikeC4ProjectJsonConfigSchema.omit({ landingPage: true }).safeParse(config)) and then merge the previously validatedLandingPage into the resulting data; keep the existing GeneratorsSchema branch but remove the unsafe broad cast (`as unknown as LikeC4ProjectConfig`) by tightening the inferred type from the omitted-schema parse (use the z.infer result of the omitted schema) so types line up when you spread in landingPage and generators.packages/core/src/types/guards.ts (1)
15-15: Replace theascast with a record type guard.Line 15 still depends on a cast, which weakens narrowing and conflicts with the repo’s TS guidance.
♻️ Proposed refactor
+function isRecord(value: unknown): value is Record<string, unknown> { + return value != null && typeof value === 'object' +} + function _hasProp(value: unknown, path: string): boolean { invariant(typeof path === 'string', 'Path must be string') - return value != null && typeof value === 'object' && (value as Record<string, unknown>)[path] != null + return isRecord(value) && value[path] != null }As per coding guidelines: “TypeScript-first repo; use explicit types. Avoid using
any, casts withas.”🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/core/src/types/guards.ts` at line 15, The expression in guards.ts uses a cast "(value as Record<string, unknown>)[path]" which violates the TS-first rule; instead add and use a proper type guard (e.g., export function isRecord(v: unknown): v is Record<string, unknown>) and replace the cast with a runtime check like "isRecord(value) && value[path] != null" inside the existing predicate (the function that currently returns value != null && typeof value === 'object' && (value as Record<string, unknown>)[path] != null). Ensure the new isRecord guard is exported from the same module and used in place of the as-cast to allow correct narrowing.packages/leanix-bridge/src/sync-to-leanix.spec.ts (1)
35-83: Expose theupdateFactSheetbranch in this mock.The new
likec4IdAttributepath can fall back to name/type and then backfill the attribute, but this mock currently discardsupdateFactSheet, so the suite can't assert that branch. Recording those calls and adding one focused test would protect the new idempotency path from regressing.Based on learnings, "Aim to cover new features with relevant tests; keep test names descriptive."
🤖 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 35 - 83, The mock in createSyncMockClient discards updateFactSheet calls so tests can't assert backfill behavior; modify the graphql handler inside createSyncMockClient to capture updateFactSheet variables (id and patches) into a local map or array (e.g., updatedPatchesById) and return the existing updateFactSheet response (updateFactSheet: { factSheet: { id: variables?.['id'] } }); then expose an accessor on the returned object (e.g., getUpdatedPatches(id) or getAllUpdates()) so tests can assert that updateFactSheet was called with the expected id and likec4IdAttribute patches; keep the existing createFactSheet/createRelation flows intact and update types for the new accessor.packages/leanix-bridge/src/adr-generation.ts (1)
77-80: Reuse the exported ADR options shape.This public API redefines the same
title/statuspair thatAdrGenerationOptionsalready owns, so the two ADR generators can drift apart. ReusePick<AdrGenerationOptions, 'title' | 'status'>(or an exported sibling type) here.As per coding guidelines, 'Agent types and interfaces should be defined separately and reused across the codebase.'♻️ Proposed refactor
+type DriftAdrGenerationOptions = Pick<AdrGenerationOptions, 'title' | 'status'> + export function generateAdrFromDriftReport( drift: DriftReport, - options: { title?: string; status?: string } = {}, + options: DriftAdrGenerationOptions = {}, ): string {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/leanix-bridge/src/adr-generation.ts` around lines 77 - 80, The function generateAdrFromDriftReport currently redefines the { title?: string; status?: string } shape in its options parameter; update its signature to reuse the exported AdrGenerationOptions type (e.g., use Pick<AdrGenerationOptions, 'title' | 'status'> or an exported sibling type) so both ADR generators share the same option shape and cannot drift apart—locate generateAdrFromDriftReport and replace the inline options type with the reused AdrGenerationOptions-derived type.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@e2e/src/likec4-cli-bridge.spec.ts`:
- Around line 59-63: The dry-run test "LikeC4 CLI - sync leanix --dry-run
produces same artifacts and does not require token" currently spawns the CLI via
the tagged `$` call and inherits the parent environment, so any exported
LEANIX_API_TOKEN will leak into the subprocess; update the invocation that calls
`$({ cwd: projectRoot })\`likec4 sync leanix ...\`` to explicitly clear
LEANIX_API_TOKEN in the env (e.g., pass an env option to the `$` call that
spreads the current env but sets LEANIX_API_TOKEN to an empty string or
undefined) so the subprocess cannot use a token during this dry-run; locate the
call near the test and modify that `$` invocation (and related setup such as
createCleanOutDir/outDirAbs) to include the env override.
In `@packages/config/src/schema.spec.ts`:
- Around line 270-275: Update the smoke test "does not throw when config has
valid landingPage (smoke)" to also assert that the validated result preserves
the landingPage field: call validateConfig(config) and add an expectation that
result.landingPage is equal to the original landingPage object (or at least
defined and matches { redirect: true }), referencing the existing test and the
validateConfig function so the assertion covers the Zod v4 behavior that
optional union keys are not stripped.
In `@packages/core/src/types/guards.spec.ts`:
- Around line 27-45: The test block duplicates the two-arg hasProp tests and
uses unsafe "as any" casts; replace it to exercise the curried overload
implemented in guards.ts (the hasProp(path)(value) form) by calling
hasProp('some') and applying the returned predicate to properly typed objects
(e.g., { some?: string | undefined | null } or declared variables) instead of
using "as any"; update the four cases (present non-nullish, undefined value,
null value, missing property) to assert the predicate's boolean results for the
curried form so the curried overload is covered and no any-casts remain.
In `@packages/leanix-bridge/src/leanix-graphql-operations.ts`:
- Around line 29-32: The current logic in leanix-graphql-operations.ts blindly
picks the first match from data.allFactSheets.edges (the variables edges and
first produced from client.graphql<AllFactSheetsResult>) which can incorrectly
proceed when multiple fact sheets match; change both occurrences (the blocks
that set edges/first and return first?.id) to explicitly check for edges.length
> 1 and reject early (throw a clear error or return a distinct failure)
indicating a non-unique lookup, only returning the id when edges.length === 1
(and handling 0 as null/no-match). Ensure the error message identifies the query
and the provided identity (e.g., name/type) so duplicate hits are surfaced
instead of silently using the first edge.
In `@packages/leanix-bridge/src/leanix-inventory-snapshot.ts`:
- Around line 208-223: The current GraphQL query in const query only fetches
factSheet.relations without pagination, which truncates results; update the
query to request relations(first: $first, after: $after) and include pageInfo {
hasNextPage endCursor } and nodes (or edges.node) so you can iterate; then
implement cursor-based pagination in the code that calls this query (where fact
sheets are processed) to repeatedly fetch pages using variables { id, first,
after } and loop using pageInfo.hasNextPage / pageInfo.endCursor until all
relations are accumulated for each fact sheet (ensure the same change is applied
to the related block noted at lines 225-247).
In `@packages/leanix-bridge/src/reconcile.ts`:
- Around line 68-74: The matching logic adds fs.id to usedFactSheetIds but
doesn't prevent a duplicate match; change the code that resolves
manifestFactSheetId (using entity.external[LEANIX_PROVIDER],
snapshotById.get(manifestFactSheetId)) to first check if fs.id is already in
usedFactSheetIds and return null if it is, only calling
usedFactSheetIds.add(fs.id) and returning the match when the id was not
previously claimed; update the matching block around manifestFactSheetId,
snapshotById, and usedFactSheetIds accordingly and add a regression test in
reconcile.spec.ts that creates two entities pointing at the same LeanIX id to
assert only one match occurs.
In `@packages/leanix-bridge/src/sync-to-leanix.ts`:
- Around line 152-159: The helper currently aborts and drops the fact sheet if
patchFactSheetAttribute throws; change it so reuse of the resolved byNameType is
best-effort: after finding byNameType (via findFactSheetByNameAndType) attempt
patchFactSheetAttribute(fs.likec4Id) inside a try/catch, and on error log a
warning (including error details and context like byNameType and fs.likec4Id)
but still return byNameType; keep the early return for byAttr as-is (from
findFactSheetByLikec4IdAttribute).
---
Duplicate comments:
In `@packages/likec4/src/cli/codegen/leanix-reconcile.ts`:
- Around line 97-107: The catch currently swallows all errors from
loadDryRunFromWorkspace which hides real config issues; update the catch in the
block around loadDryRunFromWorkspace(workspacePath, project, useDotBin) to only
degrade to a warning when the project argument was not explicitly provided, but
if project (or any explicit workspace selector) was passed, rethrow the error
(or return a non-zero failure) instead of assigning dryRun = undefined; use the
existing logger.warn for the non-fatal case and preserve the original error when
rethrowing so callers can fail fast and surface typos or bad configuration.
---
Nitpick comments:
In `@packages/config/src/schema.ts`:
- Around line 300-329: validateProjectConfig currently validates landingPage
twice—once with LandingPageSchema.safeParse and again when parsing
LikeC4ProjectJsonConfigSchema—so change the main parse to omit landingPage
before safeParse (use LikeC4ProjectJsonConfigSchema.omit({ landingPage: true
}).safeParse(config)) and then merge the previously validatedLandingPage into
the resulting data; keep the existing GeneratorsSchema branch but remove the
unsafe broad cast (`as unknown as LikeC4ProjectConfig`) by tightening the
inferred type from the omitted-schema parse (use the z.infer result of the
omitted schema) so types line up when you spread in landingPage and generators.
In `@packages/core/src/types/guards.ts`:
- Line 15: The expression in guards.ts uses a cast "(value as Record<string,
unknown>)[path]" which violates the TS-first rule; instead add and use a proper
type guard (e.g., export function isRecord(v: unknown): v is Record<string,
unknown>) and replace the cast with a runtime check like "isRecord(value) &&
value[path] != null" inside the existing predicate (the function that currently
returns value != null && typeof value === 'object' && (value as Record<string,
unknown>)[path] != null). Ensure the new isRecord guard is exported from the
same module and used in place of the as-cast to allow correct narrowing.
In `@packages/leanix-bridge/src/adr-generation.ts`:
- Around line 77-80: The function generateAdrFromDriftReport currently redefines
the { title?: string; status?: string } shape in its options parameter; update
its signature to reuse the exported AdrGenerationOptions type (e.g., use
Pick<AdrGenerationOptions, 'title' | 'status'> or an exported sibling type) so
both ADR generators share the same option shape and cannot drift apart—locate
generateAdrFromDriftReport and replace the inline options type with the reused
AdrGenerationOptions-derived type.
In `@packages/leanix-bridge/src/sync-to-leanix.spec.ts`:
- Around line 35-83: The mock in createSyncMockClient discards updateFactSheet
calls so tests can't assert backfill behavior; modify the graphql handler inside
createSyncMockClient to capture updateFactSheet variables (id and patches) into
a local map or array (e.g., updatedPatchesById) and return the existing
updateFactSheet response (updateFactSheet: { factSheet: { id: variables?.['id']
} }); then expose an accessor on the returned object (e.g.,
getUpdatedPatches(id) or getAllUpdates()) so tests can assert that
updateFactSheet was called with the expected id and likec4IdAttribute patches;
keep the existing createFactSheet/createRelation flows intact and update types
for the new accessor.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: afd8e991-1701-4986-8301-23a7e4e6a4a0
📒 Files selected for processing (25)
e2e/src/likec4-cli-bridge.spec.tspackages/config/src/schema.spec.tspackages/config/src/schema.tspackages/core/src/types/guards.spec.tspackages/core/src/types/guards.tspackages/leanix-bridge/README.mdpackages/leanix-bridge/src/adr-generation.tspackages/leanix-bridge/src/bridge-artifacts.spec.tspackages/leanix-bridge/src/contracts.tspackages/leanix-bridge/src/drawio-leanix-roundtrip.tspackages/leanix-bridge/src/index.tspackages/leanix-bridge/src/leanix-graphql-operations.tspackages/leanix-bridge/src/leanix-inventory-snapshot.spec.tspackages/leanix-bridge/src/leanix-inventory-snapshot.tspackages/leanix-bridge/src/reconcile.spec.tspackages/leanix-bridge/src/reconcile.tspackages/leanix-bridge/src/report.tspackages/leanix-bridge/src/sync-to-leanix.spec.tspackages/leanix-bridge/src/sync-to-leanix.tspackages/leanix-bridge/src/validate.spec.tspackages/leanix-bridge/src/validate.tspackages/likec4/src/cli/bridge/shared.tspackages/likec4/src/cli/codegen/leanix-dry-run.spec.tspackages/likec4/src/cli/codegen/leanix-reconcile.tspackages/likec4/src/cli/sync/leanix.ts
✅ Files skipped from review due to trivial changes (1)
- packages/leanix-bridge/src/drawio-leanix-roundtrip.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- packages/likec4/src/cli/codegen/leanix-dry-run.spec.ts
- packages/leanix-bridge/src/leanix-inventory-snapshot.spec.ts
- packages/leanix-bridge/src/validate.ts
- packages/likec4/src/cli/bridge/shared.ts
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/config/src/schema.ts (1)
302-326: Remove unsafe casts and type the parsed values directly.At Line 314 and Line 326,
ascasts bypass type safety. Prefer typed schema outputs (or exported aliases) sodataandgeneratorsare inferred without coercion.Refactor sketch
+export type LandingPageConfig = z.infer<typeof LandingPageSchema> + - let validatedLandingPage: z.infer<typeof LandingPageSchema> | null = null + let validatedLandingPage: LandingPageConfig | null = null - let data = parsed.data as unknown as LikeC4ProjectConfig + let data: LikeC4ProjectConfig = parsed.data// outside this hunk, to avoid cast at Line 326: const GeneratorFnSchema = z.custom<GeneratorFn>((v): v is GeneratorFn => typeof v === 'function') export const GeneratorsSchema = z.record(z.string(), GeneratorFnSchema)As per coding guidelines: TypeScript-first repo; use explicit types and avoid casts with
as.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/config/src/schema.ts` around lines 302 - 326, The code uses unsafe "as" casts for parsed results (data and generators); update the Zod schemas and parsing usage so TypeScript infers correct types instead of coercing: export a GeneratorFnSchema (e.g., z.custom<GeneratorFn>(...)) and make GeneratorsSchema a z.record(z.string(), GeneratorFnSchema), ensure LikeC4ProjectJsonConfigSchema is typed to return LikeC4ProjectConfig via z.infer, then replace the casts around parsed.data and genParsed.data by using those inferred types (assign parsed.data directly to a typed variable and return generators as genParsed.data) so no "as" coerce remains in the logic that builds `data` and the returned object.
🤖 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/config/src/schema.ts`:
- Around line 318-329: The current logic silently ignores invalid generators
because GeneratorsSchema.safeParse failures just fall through and return data
without generators; update the branch handling around generatorsInput /
GeneratorsSchema.safeParse so that when genParsed.success is false you throw or
surface a clear error (include genParsed.error) instead of returning the
original data—mirror the behavior in defineConfig (which raises on invalid
input) so callers get a validation failure; locate the code using
generatorsInput, GeneratorsSchema and GeneratorsSchema.safeParse and replace the
silent-fallthrough with an explicit throw or error propagation including the
parse error details.
---
Nitpick comments:
In `@packages/config/src/schema.ts`:
- Around line 302-326: The code uses unsafe "as" casts for parsed results (data
and generators); update the Zod schemas and parsing usage so TypeScript infers
correct types instead of coercing: export a GeneratorFnSchema (e.g.,
z.custom<GeneratorFn>(...)) and make GeneratorsSchema a z.record(z.string(),
GeneratorFnSchema), ensure LikeC4ProjectJsonConfigSchema is typed to return
LikeC4ProjectConfig via z.infer, then replace the casts around parsed.data and
genParsed.data by using those inferred types (assign parsed.data directly to a
typed variable and return generators as genParsed.data) so no "as" coerce
remains in the logic that builds `data` and the returned object.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 5d57c0cf-e809-497e-b5e3-35787ea340d3
📒 Files selected for processing (1)
packages/config/src/schema.ts
…d schema; likec4 test exclude lib Made-with: Cursor
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
packages/config/build.config.ts (1)
10-12: Avoidascast; prefer a type-safe approach.The cast
as string[]assumesconfig.externalis always a string array, but rolldown supports multiple types for this field. Consider a safer pattern:🔧 Suggested improvement
rolldownConfig: (config) => { - config.external = [...(config.external as string[]), '@likec4/config'] + const existing = Array.isArray(config.external) ? config.external : [] + config.external = [...existing, '@likec4/config'] },As per coding guidelines: "Avoid using
any, casts withas".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/config/build.config.ts` around lines 10 - 12, The current rolldownConfig uses an unsafe cast on config.external; update rolldownConfig to normalize config.external in a type-safe way instead of using "as string[]": inspect config.external in rolldownConfig (the config.external field may be undefined, a string, or an array), then produce a new external value that preserves non-string/array types but safely appends '@likec4/config' when external is a string or array; use Array.isArray and typeof checks to branch and assign back to config.external (refer to rolldownConfig and config.external).devops/vitest.ts (1)
24-26: Consider usingdistDirhelper for consistency.Line 25 manually constructs the path while line 26 uses the new
distEntryhelper. For consistency, consider refactoring line 25 to usedistDir:♻️ Suggested refactor
// More specific first so subpath resolves - '@likec4/config/node': resolve(packages('config', 'dist', 'node')), + '@likec4/config/node': resolve(distDir('config'), 'node'), '@likec4/config': distEntry('config'),This alias resolves to a directory with an entry defined in
package.jsonexports (./dist/node/index.mjs), which the resolver will find automatically. Line 26 explicitly specifies the entry file, making this intentional asymmetry that works as designed.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@devops/vitest.ts` around lines 24 - 26, Refactor the alias for '@likec4/config/node' to use the distDir helper instead of manually resolving packages('config', 'dist', 'node'); specifically replace the resolve(packages('config', 'dist', 'node')) usage with distDir('config', 'node') so it consistently mirrors the distEntry('config') pattern and lets the resolver pick the package export (keep the '@likec4/config' entry using distEntry('config') unchanged).packages/generators/src/likec4/operators/base.ts (1)
819-824: Replace cast-based narrowing with a proper type guard.Lines 824 and 832 use
ascasts which violates the repo guideline to avoid type assertions. Line 824 can be replaced with a type guard function instead.♻️ Proposed refactor
+function isZodSchemaCondition(value: unknown): value is z.ZodSchema { + return value !== null && typeof value === 'object' && 'safeParse' in value +} + export function guard( condition: Function | z.ZodSchema, ...ops: Ops<any> ) { return operation('guard', ({ ctx, out }) => { - if ( - condition !== null && - typeof condition === 'object' && - 'safeParse' in condition - ) { - const parsed = (condition as z.ZodSchema).safeParse(ctx) + if (isZodSchemaCondition(condition)) { + const parsed = condition.safeParse(ctx) if (parsed.success) { executeOnCtx({ ctx: parsed.data, out }, ops) } else { throw new Error(`Guard failed: ${z.prettifyError(parsed.error)}`) } return }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/generators/src/likec4/operators/base.ts` around lines 819 - 824, Replace the cast-based narrowing around the "condition" checks with a proper type guard: implement a predicate like isZodSchema(value): value is z.ZodSchema and use it where currently checking 'safeParse' in condition and when calling safeParse, replacing both occurrences of (condition as z.ZodSchema) so the compiler understands the narrowed type without using "as" casts; update the logic in the block that uses safeParse and any subsequent branches that relied on the cast (e.g., the parsed handling) to use the new isZodSchema guard before invoking condition.safeParse.
🤖 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/likec4/operators/base.ts`:
- Around line 832-833: The guard currently silently returns when `condition` is
`false` or `null`, hiding invalid inputs; replace that silent return with an
explicit thrown error so callers fail fast: in the block that checks `if
((condition as unknown) === false || condition == null)` throw a descriptive
Error (e.g. `throw new Error(\`Invalid guard condition: expected function or Zod
schema, got ${typeof condition}\`)`) instead of returning, or alternatively
remove that check entirely and rely on the subsequent `invariant(typeof
condition === 'function')` in the same function to surface invalid inputs—update
the code around the `condition` check and the `invariant` call accordingly.
---
Nitpick comments:
In `@devops/vitest.ts`:
- Around line 24-26: Refactor the alias for '@likec4/config/node' to use the
distDir helper instead of manually resolving packages('config', 'dist', 'node');
specifically replace the resolve(packages('config', 'dist', 'node')) usage with
distDir('config', 'node') so it consistently mirrors the distEntry('config')
pattern and lets the resolver pick the package export (keep the '@likec4/config'
entry using distEntry('config') unchanged).
In `@packages/config/build.config.ts`:
- Around line 10-12: The current rolldownConfig uses an unsafe cast on
config.external; update rolldownConfig to normalize config.external in a
type-safe way instead of using "as string[]": inspect config.external in
rolldownConfig (the config.external field may be undefined, a string, or an
array), then produce a new external value that preserves non-string/array types
but safely appends '@likec4/config' when external is a string or array; use
Array.isArray and typeof checks to branch and assign back to config.external
(refer to rolldownConfig and config.external).
In `@packages/generators/src/likec4/operators/base.ts`:
- Around line 819-824: Replace the cast-based narrowing around the "condition"
checks with a proper type guard: implement a predicate like isZodSchema(value):
value is z.ZodSchema and use it where currently checking 'safeParse' in
condition and when calling safeParse, replacing both occurrences of (condition
as z.ZodSchema) so the compiler understands the narrowed type without using "as"
casts; update the logic in the block that uses safeParse and any subsequent
branches that relied on the cast (e.g., the parsed handling) to use the new
isZodSchema guard before invoking condition.safeParse.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 8a7b7f3b-d876-405f-8bc8-d20668fb7748
⛔ Files ignored due to path filters (1)
packages/generators/src/likec4/operators/__snapshots__/likec4.generate.snapis excluded by!**/*.snap
📒 Files selected for processing (8)
devops/vitest.tspackages/config/build.config.tspackages/config/src/index.tspackages/config/src/node/index.tspackages/config/src/node/load-config.tspackages/generators/src/likec4/operators/base.tspackages/generators/src/likec4/operators/views.spec.tspackages/likec4/vitest.config.ts
There was a problem hiding this comment.
Can you revert changes in packages/generators/src/likec4/?
I believe it is out of scope for this PR
Made-with: Cursor
…of scope for PR Made-with: Cursor
…2e, reconcile, governance, README Made-with: Cursor
Made-with: Cursor
…); Uncle Bob refactors in package-meta Made-with: Cursor
…ng static require Made-with: Cursor
…ler emitting require('../package.json') in CLI
Made-with: Cursor
…h type), remove package-meta Made-with: Cursor
… (e2e) Made-with: Cursor
Made-with: Cursor
|
Hi @davydkov, Thank you for the review and the clear suggestions. I've applied all of them (config cycle, vitest aliases, leanix-bridge as devDep, CLI subcommands, e2e/workflows, CodeRabbit follow-ups). In short: we had introduced a config cycle, treated leanix-bridge as a runtime dep, and hadn't aligned vitest/e2e with the package layout—so things "worked" in the monorepo but were brittle and wrong for publish/CI. Your suggestions fix that: single-direction config, devDep for the bridge so the CLI stays lean, and aliases/workflows that match how the package is consumed. I got the rationale and am happy to iterate if anything still doesn't sit right. Thanks again. |
- config: assert landingPage preserved in schema smoke test; throw on invalid generators - core: guards.spec curried tests, remove as any - leanix-bridge: reject non-unique lookup hits; relations pagination; duplicate manifest id check; backfill warning not block reuse Made-with: Cursor
- No defaults on dry-run/apply so explicit user input is preserved
- conflicts('dry-run','apply') rejects both flags; derive dryRun after parse
Made-with: Cursor
- likec4 CLI: ensureProject, handlers (serve, preview, codegen), ensure-libs, check-update, package-meta, bridge shared - leanix-bridge: interfaces and options (reconcile, contracts, model-input, mapping, sync, inventory, governance, drift, impact, adr, api-client) Made-with: Cursor
There was a problem hiding this comment.
Have you tried this?
likec4/packages/likec4/src/cli/index.ts
Line 18 in 4f140bb
Made-with: Cursor
Summary
This PR extends the LeanIX bridge (from PR #2746) with Phase 2 (inbound: inventory snapshot, reconciliation), Phase 3 (impact analysis, drift detection, ADR generation, governance checks), first-class CLI commands, and documentation updates for Draw.io and CLI as requested.
Changes
1. LeanIX bridge package (
@likec4/leanix-bridge)Phase 2 – Inbound
fetchLeanixInventorySnapshot(client, options?)— fetches a read-only snapshot of LeanIX fact sheets and relations (paginated); returnsLeanixInventorySnapshot.reconcileInventoryWithManifest(snapshot, manifest, options?)— compares manifest to LeanIX snapshot; returnsReconciliationResult(matched, unmatchedInLikec4, unmatchedInLeanix, ambiguous). OptionaldryRunimproves matching.Phase 3 – Reports & governance
buildDriftReport(manifest, snapshot)— produces aDriftReportlisting mismatches (title, description, relations) between manifest and snapshot.impactReportFromSyncPlan(syncPlan)— computes impact analysis from a sync plan; returnsImpactReportwith affected entities and severity.generateAdrFromReconciliation(reconciliation, options?)andgenerateAdrFromDriftReport(driftReport, options?)— generate ADR-style markdown from reconciliation or drift.runGovernanceChecks(manifest, options?)— runs configurable governance rules; returnsGovernanceReportwith pass/fail and violation messages.Exports & quality
packages/leanix-bridge/src/index.ts.isBridgeManifest(obj),isLeanixInventorySnapshot(obj)for parsed JSON.2. CLI (
likec4)likec4 gen leanix-dry-run,likec4 gen leanix-inventory-snapshot -o <dir>,likec4 gen leanix-reconcile -o <dir>.likec4 sync leanix [--dry-run | --apply] -o <dir>— dry-run (plan only) or apply (live sync); uses manifest/dry-run artifacts;--applyrequiresLEANIX_API_TOKEN.likec4 export drawio --profile leanix -o <dir>— already present; documented in tooling docs.packages/likec4/src/cli/bridge/leanix-client.ts(env-based client),packages/likec4/src/cli/bridge/shared.ts(model adapter, artifact builders).3. Generators
packages/generators/src/drawio/parse-drawio.tsfor compatibility with bridge-managed metadata (no breaking changes).packages/generators/src/index.ts— re-exports as needed for consumers.4. Documentation (tooling)
apps/docs/src/content/docs/tooling/drawio.mdx): Export profiles (default,leanix), bridge-managed attributes, CLI example, link to bridge package.apps/docs/src/content/docs/tooling/cli.mdx):--profileoption for Export to Draw.io, example with--profile leanix.5. E2E
e2e/src/likec4-cli-bridge.spec.ts— tests forgen leanix-dry-runandsync leanix --dry-run(artifact layout and structure).6. Other
@likec4/docs-astropatch,@likec4/generatorsandlikec4minor,@likec4/leanix-bridgeminor..gitignore: documentação de apoio emdocs/remains ignored; no support docs committed.Testing
pnpm testinpackages/leanix-bridge: all unit tests pass.pnpm ci:typecheck: passes (including@likec4/leanix-bridge).e2e/with a project that has a LikeC4 model; optionalLEANIX_API_TOKENfor snapshot/sync tests.Checklist
--profile).Related
Summary by CodeRabbit
New Features
defaultorleanixvia--profile(CLI & generator)gen leanix-dry-run,gen leanix-inventory-snapshot,gen leanix-reconcilesync leanixwith dry-run / apply modes; artifact writer for manifest/dry‑run/reportDocumentation
--profileand LeanIX usage notesTests