@documentation-robotics/cli 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -2
- package/dist/analysis/relationship-classifier.js +2 -9
- package/dist/analysis/relationship-classifier.js.map +2 -2
- package/dist/analyzers/call-graph-utils.js +2 -2
- package/dist/analyzers/call-graph-utils.js.map +2 -2
- package/dist/analyzers/cards/build.js +171 -0
- package/dist/analyzers/cards/build.js.map +7 -0
- package/dist/analyzers/cards/classify.js +42 -0
- package/dist/analyzers/cards/classify.js.map +7 -0
- package/dist/analyzers/cards/edge-index.js +44 -0
- package/dist/analyzers/cards/edge-index.js.map +7 -0
- package/dist/analyzers/cards/facts.js +46 -0
- package/dist/analyzers/cards/facts.js.map +7 -0
- package/dist/analyzers/cards/graph.js +42 -0
- package/dist/analyzers/cards/graph.js.map +7 -0
- package/dist/analyzers/cards/index.js +8 -0
- package/dist/analyzers/cards/index.js.map +7 -0
- package/dist/analyzers/cards/tier0.js +18 -0
- package/dist/analyzers/cards/tier0.js.map +7 -0
- package/dist/analyzers/cards/types.js +1 -0
- package/dist/analyzers/cards/types.js.map +7 -0
- package/dist/analyzers/cbm-analyzer.js +195 -171
- package/dist/analyzers/cbm-analyzer.js.map +2 -2
- package/dist/analyzers/cbm-responses.js +336 -0
- package/dist/analyzers/cbm-responses.js.map +7 -0
- package/dist/analyzers/edge-relationships.js +45 -0
- package/dist/analyzers/edge-relationships.js.map +7 -0
- package/dist/analyzers/mapping-loader.js +9 -0
- package/dist/analyzers/mapping-loader.js.map +2 -2
- package/dist/analyzers/tier0-rules.js +153 -0
- package/dist/analyzers/tier0-rules.js.map +7 -0
- package/dist/analyzers/types.js.map +1 -1
- package/dist/audit/relationships/ai/prompts.js +10 -10
- package/dist/audit/relationships/ai/prompts.js.map +2 -2
- package/dist/audit/relationships/analysis/layer-templates.js +52 -52
- package/dist/audit/relationships/analysis/layer-templates.js.map +1 -1
- package/dist/cli.js +12 -2
- package/dist/cli.js.map +2 -2
- package/dist/coding-agents/default-permissions.js +31 -1
- package/dist/coding-agents/default-permissions.js.map +2 -2
- package/dist/coding-agents/drbot-mcp.js +167 -0
- package/dist/coding-agents/drbot-mcp.js.map +7 -0
- package/dist/commands/add.js +3 -2
- package/dist/commands/add.js.map +2 -2
- package/dist/commands/analyzer.js +117 -14
- package/dist/commands/analyzer.js.map +2 -2
- package/dist/commands/changeset-verify.js +55 -0
- package/dist/commands/changeset-verify.js.map +7 -0
- package/dist/commands/changeset.js +43 -22
- package/dist/commands/changeset.js.map +2 -2
- package/dist/commands/delete.js +10 -4
- package/dist/commands/delete.js.map +2 -2
- package/dist/commands/farm.js +176 -60
- package/dist/commands/farm.js.map +2 -2
- package/dist/commands/mcp.js +19 -4
- package/dist/commands/mcp.js.map +2 -2
- package/dist/commands/relationship.js +207 -26
- package/dist/commands/relationship.js.map +2 -2
- package/dist/commands/update.js +42 -23
- package/dist/commands/update.js.map +2 -2
- package/dist/commands/upgrade.js +99 -11
- package/dist/commands/upgrade.js.map +3 -3
- package/dist/commands/version.js +1 -1
- package/dist/commands/visualize.js +4 -3
- package/dist/commands/visualize.js.map +2 -2
- package/dist/core/catalog-migration.js +33 -0
- package/dist/core/catalog-migration.js.map +7 -0
- package/dist/core/changeset-analysis.js +421 -0
- package/dist/core/changeset-analysis.js.map +7 -0
- package/dist/core/changeset-base-index.js +153 -0
- package/dist/core/changeset-base-index.js.map +7 -0
- package/dist/core/changeset-brief-delta.js +102 -0
- package/dist/core/changeset-brief-delta.js.map +7 -0
- package/dist/core/changeset-graph.js +281 -0
- package/dist/core/changeset-graph.js.map +7 -0
- package/dist/core/changeset-only-guard.js +32 -0
- package/dist/core/changeset-only-guard.js.map +7 -0
- package/dist/core/changeset-summary.js +68 -0
- package/dist/core/changeset-summary.js.map +7 -0
- package/dist/core/changeset-validator.js +66 -20
- package/dist/core/changeset-validator.js.map +2 -2
- package/dist/core/changeset-verify.js +518 -0
- package/dist/core/changeset-verify.js.map +7 -0
- package/dist/core/changeset.js +56 -9
- package/dist/core/changeset.js.map +2 -2
- package/dist/core/element-type-migration.js +145 -0
- package/dist/core/element-type-migration.js.map +7 -0
- package/dist/core/farm-manifest.js +12 -6
- package/dist/core/farm-manifest.js.map +2 -2
- package/dist/core/farm-sync-engine.js +1 -1
- package/dist/core/farm-sync-engine.js.map +2 -2
- package/dist/core/farm-sync-state.js +10 -2
- package/dist/core/farm-sync-state.js.map +2 -2
- package/dist/core/migration-registry.js +75 -46
- package/dist/core/migration-registry.js.map +2 -2
- package/dist/core/model-upgrade.js +102 -0
- package/dist/core/model-upgrade.js.map +7 -0
- package/dist/core/model.js +82 -30
- package/dist/core/model.js.map +2 -2
- package/dist/core/mutation-handler.js +71 -6
- package/dist/core/mutation-handler.js.map +2 -2
- package/dist/core/relationship-migration-table.js +6639 -0
- package/dist/core/relationship-migration-table.js.map +7 -0
- package/dist/core/relationship-migration.js +272 -0
- package/dist/core/relationship-migration.js.map +7 -0
- package/dist/core/relationship-prune.js +49 -0
- package/dist/core/relationship-prune.js.map +7 -0
- package/dist/core/relationship-staging.js +68 -0
- package/dist/core/relationship-staging.js.map +7 -0
- package/dist/core/relationships.js +26 -4
- package/dist/core/relationships.js.map +2 -2
- package/dist/core/report-data-model.js +7 -13
- package/dist/core/report-data-model.js.map +2 -2
- package/dist/core/staged-changeset-storage.js +64 -17
- package/dist/core/staged-changeset-storage.js.map +2 -2
- package/dist/core/staged-source-reference.js +28 -0
- package/dist/core/staged-source-reference.js.map +7 -0
- package/dist/core/staging-area.js +83 -18
- package/dist/core/staging-area.js.map +2 -2
- package/dist/core/virtual-projection.js +13 -5
- package/dist/core/virtual-projection.js.map +2 -2
- package/dist/export/archimate-exporter.js +1 -0
- package/dist/export/archimate-exporter.js.map +2 -2
- package/dist/generated/compiled-validators.js +2 -2
- package/dist/generated/compiled-validators.js.map +2 -2
- package/dist/generated/layer-registry.js +16 -16
- package/dist/generated/layer-registry.js.map +2 -2
- package/dist/generated/node-types.js +186 -168
- package/dist/generated/node-types.js.map +2 -2
- package/dist/generated/relationship-index.js +2236 -7790
- package/dist/generated/relationship-index.js.map +2 -2
- package/dist/integrations/claude_code/agents/dr-architect.md +16 -3
- package/dist/integrations/claude_code/agents/dr-extractor.md +1 -1
- package/dist/integrations/claude_code/commands/dr-analyzer.md +565 -0
- package/dist/integrations/claude_code/commands/dr-map.md +13 -0
- package/dist/integrations/claude_code/commands/dr-plan.md +40 -0
- package/dist/integrations/claude_code/commands/dr-relate.md +106 -96
- package/dist/integrations/claude_code/commands/dr-verify.md +45 -2
- package/dist/integrations/claude_code/docs/END_TO_END_WALKTHROUGH.md +14 -0
- package/dist/integrations/claude_code/docs/MCP_SERVER.md +26 -2
- package/dist/integrations/claude_code/docs/QUICK_START.md +37 -8
- package/dist/integrations/claude_code/docs/README.md +48 -1
- package/dist/integrations/claude_code/skills/dr_01_motivation_layer/SKILL.md +93 -72
- package/dist/integrations/claude_code/skills/dr_02_business_layer/SKILL.md +163 -126
- package/dist/integrations/claude_code/skills/dr_03_product_layer/SKILL.md +161 -126
- package/dist/integrations/claude_code/skills/dr_04_security_layer/SKILL.md +153 -133
- package/dist/integrations/claude_code/skills/dr_05_application_layer/SKILL.md +129 -100
- package/dist/integrations/claude_code/skills/dr_06_technology_layer/SKILL.md +111 -94
- package/dist/integrations/claude_code/skills/dr_07_api_layer/SKILL.md +143 -157
- package/dist/integrations/claude_code/skills/dr_08_data_model_layer/SKILL.md +117 -33
- package/dist/integrations/claude_code/skills/dr_09_dataset_layer/SKILL.md +74 -25
- package/dist/integrations/claude_code/skills/dr_10_ux_layer/SKILL.md +88 -12
- package/dist/integrations/claude_code/skills/dr_11_navigation_layer/SKILL.md +83 -24
- package/dist/integrations/claude_code/skills/dr_12_apm_layer/SKILL.md +57 -12
- package/dist/integrations/claude_code/skills/dr_13_testing_layer/SKILL.md +92 -15
- package/dist/integrations/claude_code/skills/dr_change_planning/SKILL.md +119 -0
- package/dist/integrations/claude_code/skills/dr_changeset_reviewer/SKILL.md +3 -0
- package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +1 -1
- package/dist/integrations/github_copilot/agents/dr-architect.md +1 -1
- package/dist/integrations/github_copilot/skills/dr_01_motivation_layer/SKILL.md +182 -102
- package/dist/integrations/github_copilot/skills/dr_02_business_layer/SKILL.md +266 -148
- package/dist/integrations/github_copilot/skills/dr_03_product_layer/SKILL.md +547 -86
- package/dist/integrations/github_copilot/skills/dr_04_security_layer/SKILL.md +164 -169
- package/dist/integrations/github_copilot/skills/dr_05_application_layer/SKILL.md +420 -150
- package/dist/integrations/github_copilot/skills/dr_06_technology_layer/SKILL.md +331 -138
- package/dist/integrations/github_copilot/skills/dr_07_api_layer/SKILL.md +265 -201
- package/dist/integrations/github_copilot/skills/dr_08_data_model_layer/SKILL.md +181 -58
- package/dist/integrations/github_copilot/skills/dr_09_dataset_layer/SKILL.md +209 -57
- package/dist/integrations/github_copilot/skills/dr_10_ux_layer/SKILL.md +277 -112
- package/dist/integrations/github_copilot/skills/dr_11_navigation_layer/SKILL.md +219 -70
- package/dist/integrations/github_copilot/skills/dr_12_apm_layer/SKILL.md +189 -105
- package/dist/integrations/github_copilot/skills/dr_13_testing_layer/SKILL.md +222 -102
- package/dist/integrations/github_copilot/skills/dr_change_planning/SKILL.md +119 -0
- package/dist/integrations/github_copilot/skills/dr_changeset_reviewer/SKILL.md +3 -0
- package/dist/integrations/hash-utils.js.map +2 -2
- package/dist/mcp/api-key-manager.js +14 -1
- package/dist/mcp/api-key-manager.js.map +2 -2
- package/dist/mcp/changeset-only.js +34 -0
- package/dist/mcp/changeset-only.js.map +7 -0
- package/dist/mcp/tool-registry.js +19 -3
- package/dist/mcp/tool-registry.js.map +2 -2
- package/dist/mcp/tools/changeset-analysis-tools.js +78 -0
- package/dist/mcp/tools/changeset-analysis-tools.js.map +7 -0
- package/dist/mcp/tools/changeset-editing.js +184 -0
- package/dist/mcp/tools/changeset-editing.js.map +7 -0
- package/dist/mcp/tools/changeset-lifecycle.js +83 -0
- package/dist/mcp/tools/changeset-lifecycle.js.map +7 -0
- package/dist/mcp/tools/changeset-shared.js +31 -0
- package/dist/mcp/tools/changeset-shared.js.map +7 -0
- package/dist/mcp/tools/changeset-show.js +17 -1
- package/dist/mcp/tools/changeset-show.js.map +2 -2
- package/dist/mcp/tools/changeset-verify.js +43 -0
- package/dist/mcp/tools/changeset-verify.js.map +7 -0
- package/dist/mcp/tools/index.js +19 -1
- package/dist/mcp/tools/index.js.map +2 -2
- package/dist/mcp/tools/model-add.js +6 -5
- package/dist/mcp/tools/model-add.js.map +2 -2
- package/dist/mcp/tools/model-delete.js +2 -2
- package/dist/mcp/tools/model-delete.js.map +2 -2
- package/dist/mcp/tools/model-search.js +14 -0
- package/dist/mcp/tools/model-search.js.map +2 -2
- package/dist/mcp/tools/model-show.js +29 -2
- package/dist/mcp/tools/model-show.js.map +2 -2
- package/dist/mcp/tools/model-trace.js +93 -30
- package/dist/mcp/tools/model-trace.js.map +2 -2
- package/dist/mcp/tools/model-update.js +35 -19
- package/dist/mcp/tools/model-update.js.map +2 -2
- package/dist/mcp/tools/relationship-tools.js +98 -0
- package/dist/mcp/tools/relationship-tools.js.map +7 -0
- package/dist/mcp/tools/shared.js +25 -2
- package/dist/mcp/tools/shared.js.map +2 -2
- package/dist/mcp/tools/spec-type.js +90 -0
- package/dist/mcp/tools/spec-type.js.map +7 -0
- package/dist/schemas/bundled/analyzers/cbm.json +1186 -68
- package/dist/schemas/bundled/analyzers/manifest.json +2 -2
- package/dist/schemas/bundled/api.json +1029 -1658
- package/dist/schemas/bundled/apm.json +499 -734
- package/dist/schemas/bundled/application.json +735 -371
- package/dist/schemas/bundled/base.json +3 -220
- package/dist/schemas/bundled/business.json +452 -366
- package/dist/schemas/bundled/data-model.json +395 -411
- package/dist/schemas/bundled/data-store.json +334 -558
- package/dist/schemas/bundled/manifest.json +18 -18
- package/dist/schemas/bundled/motivation.json +349 -368
- package/dist/schemas/bundled/navigation.json +415 -731
- package/dist/schemas/bundled/product.json +325 -159
- package/dist/schemas/bundled/security.json +864 -1232
- package/dist/schemas/bundled/technology.json +574 -703
- package/dist/schemas/bundled/testing.json +718 -1643
- package/dist/schemas/bundled/ux.json +739 -1352
- package/dist/server/schemas.js +8 -0
- package/dist/server/schemas.js.map +2 -2
- package/dist/server/server.js +218 -34
- package/dist/server/server.js.map +2 -2
- package/dist/server-entry.js +2 -1
- package/dist/server-entry.js.map +2 -2
- package/dist/telemetry/index.js +1 -1
- package/dist/utils/ansi-suppressor.js +6 -8
- package/dist/utils/ansi-suppressor.js.map +2 -2
- package/dist/utils/attribute-patch.js +41 -0
- package/dist/utils/attribute-patch.js.map +7 -0
- package/dist/utils/error-hints.js +29 -0
- package/dist/utils/error-hints.js.map +7 -0
- package/dist/utils/errors.js +9 -3
- package/dist/utils/errors.js.map +2 -2
- package/dist/utils/project-paths.js +26 -1
- package/dist/utils/project-paths.js.map +2 -2
- package/dist/utils/source-reference.js +14 -12
- package/dist/utils/source-reference.js.map +2 -2
- package/dist/utils/spec-version.js +2 -2
- package/dist/utils/spec-version.js.map +1 -1
- package/dist/validators/composed-reference-validator.js +5 -27
- package/dist/validators/composed-reference-validator.js.map +2 -2
- package/dist/validators/reference-validator.js +0 -7
- package/dist/validators/reference-validator.js.map +2 -2
- package/dist/validators/relationship-schema-validator.js +3 -3
- package/dist/validators/relationship-schema-validator.js.map +2 -2
- package/dist/validators/schema-validator.js +27 -9
- package/dist/validators/schema-validator.js.map +2 -2
- package/dist/validators/types.js.map +1 -1
- package/dist/viewer/README.md +6 -6
- package/dist/viewer/assets/main-CxzcuU_o.js +84 -0
- package/dist/viewer/assets/main-j-7ULYpm.css +1 -0
- package/dist/viewer/index.html +2 -2
- package/dist/viewer/manifest.json +10 -10
- package/dist/viewer/version.json +1 -1
- package/package.json +2 -1
- package/dist/viewer/assets/main-B7AOQXXX.js +0 -81
- package/dist/viewer/assets/main-vdrFrG7q.css +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/commands/delete.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Delete an element\n */\n\nimport { confirm } from \"@clack/prompts\";\nimport ansis from \"ansis\";\nimport { Model } from \"../core/model.js\";\nimport { MutationHandler } from \"../core/mutation-handler.js\";\nimport { ReferenceRegistry } from \"../core/reference-registry.js\";\nimport { DependencyTracker, TraceDirection } from \"../core/dependency-tracker.js\";\nimport { findElementLayer } from \"../utils/element-utils.js\";\nimport { CLIError, handleError, handleSuccess, handleInfo, ErrorCategory, ModelNotFoundError } from \"../utils/errors.js\";\nimport { startSpan, endSpan } from \"../telemetry/index.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\n\ndeclare const TELEMETRY_ENABLED: boolean | undefined;\nconst isTelemetryEnabled = typeof TELEMETRY_ENABLED !== \"undefined\" ? TELEMETRY_ENABLED : false;\n\nexport interface DeleteOptions {\n model?: string;\n force?: boolean;\n verbose?: boolean;\n debug?: boolean;\n cascade?: boolean;\n dryRun?: boolean;\n}\n\nexport async function deleteCommand(id: string, options: DeleteOptions): Promise<void> {\n const span = isTelemetryEnabled\n ? startSpan(\"element.delete\", {\n \"element.id\": id,\n \"element.force\": !!options.force,\n \"element.cascade\": !!options.cascade,\n \"element.dryRun\": !!options.dryRun,\n })\n : null;\n\n try {\n // Load model (with error handling for missing models)\n let model: Model;\n try {\n model = await Model.load(options.model || process.cwd());\n } catch (error) {\n const message = getErrorMessage(error);\n if (message.includes(\"No DR project\") || message.includes(\"Model not found\")) {\n throw new ModelNotFoundError();\n }\n throw error;\n }\n\n // Find element\n const layerName = await findElementLayer(model, id);\n if (!layerName) {\n throw new CLIError(`Element ${id} not found`, ErrorCategory.USER, [\n `Use \"dr search ${id}\" to find similar elements`,\n 'Use \"dr list <layer>\" to list all elements in a layer',\n ]);\n }\n\n const layer = (await model.getLayer(layerName))!;\n const element = layer.getElement(id);\n\n if (!element) {\n throw new CLIError(`Element ${id} not found`, ErrorCategory.USER, [\n `Use \"dr search ${id}\" to find similar elements`,\n 'Use \"dr list <layer>\" to list all elements in a layer',\n ]);\n }\n\n // Build reference registry to track dependencies\n const registry = new ReferenceRegistry();\n for (const layer of model.layers.values()) {\n for (const element of layer.listElements()) {\n registry.registerElement(element);\n }\n }\n\n // Create dependency tracker\n const tracker = new DependencyTracker(registry, model);\n\n // Find all elements that depend on this element (would be orphaned)\n const dependents = tracker.traceDependencies(id, TraceDirection.DOWN, null);\n\n // Display element information\n handleInfo(\"\");\n handleInfo(ansis.bold(`Removing element: ${ansis.yellow(id)}`), {\n Layer: layerName,\n Type: element.type || \"unknown\",\n ...(element.name ? { Name: element.name } : {}),\n });\n\n // Display dependencies if any exist\n if (dependents.length > 0) {\n handleInfo(\"\");\n handleInfo(\n ansis.yellow(`\u26A0 Warning: This element has ${dependents.length} dependent element(s):`)\n );\n\n const directDependents = tracker.traceDependencies(id, TraceDirection.DOWN, 1);\n\n // Show direct dependents\n for (const dep of directDependents.slice(0, 5)) {\n const depElement = model.getElementById(dep);\n const depLayer = depElement?.layer || \"unknown\";\n handleInfo(ansis.dim(` - ${dep} (${depLayer})`));\n }\n\n if (directDependents.length > 5) {\n handleInfo(ansis.dim(` ... and ${directDependents.length - 5} more direct dependents`));\n }\n\n if (dependents.length > directDependents.length) {\n handleInfo(\n ansis.dim(\n ` (${dependents.length - directDependents.length} transitive dependents not shown)`\n )\n );\n }\n\n // Check if cascade or force is enabled\n if (!options.cascade && !options.force) {\n handleInfo(\"\");\n handleInfo(ansis.red(\"\u2717 Cannot remove element with dependencies.\"));\n handleInfo(ansis.dim(\"Use --cascade to remove all dependent elements\"));\n handleInfo(ansis.dim(\"or --force to skip dependency checks\"));\n const suggestions = [\n `Use --dry-run with --cascade to preview what would be deleted`,\n 'Review dependencies before deletion using \"dr show <element>\"',\n \"Use --cascade to automatically remove all dependent elements\",\n \"Or use --force to remove only this element (dependencies will reference a non-existent element)\",\n ];\n throw new CLIError(\"Element has dependencies\", ErrorCategory.USER, suggestions, {\n operation: \"delete\",\n relatedElements: directDependents.slice(0, 10),\n context: `Element ${id} has ${dependents.length} dependent elements`,\n });\n }\n }\n\n // Calculate total elements to remove\n const elementsToRemove = options.cascade ? [id, ...dependents] : [id];\n\n // Display dry-run summary\n if (options.dryRun) {\n handleInfo(\"\");\n handleInfo(ansis.yellow(`Dry run - not removing`));\n handleInfo(ansis.dim(`Would remove ${elementsToRemove.length} element(s)`));\n\n if (options.cascade && dependents.length > 0) {\n handleInfo(\"\");\n handleInfo(ansis.dim(\"Elements that would be removed:\"));\n handleInfo(ansis.dim(` - ${id} (target)`));\n for (const dep of dependents.slice(0, 10)) {\n handleInfo(ansis.dim(` - ${dep} (dependent)`));\n }\n if (dependents.length > 10) {\n handleInfo(ansis.dim(` ... and ${dependents.length - 10} more`));\n }\n }\n\n handleSuccess(\n `Dry run: would remove ${elementsToRemove.length} element(s)`,\n {\n elementId: id,\n layer: layerName!,\n dryRun: true,\n elementsToRemoveCount: 1,\n dependentElementsToRemoveCount: dependents.length,\n },\n { verbose: options.verbose }\n );\n return;\n }\n\n // Confirm deletion unless --force\n if (!options.force) {\n const isInteractive = process.stdin.isTTY && process.stdout.isTTY;\n if (!isInteractive) {\n handleInfo(ansis.dim(\"Non-interactive environment detected - proceeding without confirmation\"));\n } else {\n const message =\n options.cascade && dependents.length > 0\n ? `Delete ${ansis.bold(id)} and ${ansis.bold(String(dependents.length))} dependent element(s)? This cannot be undone.`\n : `Delete element ${ansis.bold(id)}? This cannot be undone.`;\n\n const confirmed = await confirm({\n message,\n initialValue: false,\n });\n\n if (!confirmed) {\n handleSuccess(\n \"Deletion cancelled\",\n {\n status: \"cancelled\",\n elementId: id,\n layer: layerName!,\n },\n { verbose: options.verbose }\n );\n return;\n }\n }\n }\n\n // Execute deletion with error recovery guidance\n let deletedCount = 0;\n try {\n if (options.cascade && dependents.length > 0) {\n // Delete dependents first (reverse dependency order to avoid orphaned references)\n const sortedDependents = [...dependents].reverse();\n\n for (const depId of sortedDependents) {\n try {\n const depLayerName = await findElementLayer(model, depId);\n if (!depLayerName) continue;\n\n const depLayer = (await model.getLayer(depLayerName))!;\n const depElement = depLayer.getElement(depId)!;\n\n const handler = new MutationHandler(model, depId, depLayerName);\n await handler.executeDelete(depElement);\n deletedCount++;\n\n if (options.verbose) {\n handleInfo(ansis.dim(` Deleted dependent: ${depId}`));\n }\n } catch (depError) {\n // Track partial progress for error recovery\n const suggestions = [\n `${deletedCount} dependent element(s) were deleted before the error`,\n 'Check the model state with \"dr validate\" to identify any broken references',\n 'You may need to manually delete remaining elements or use \"dr show\" to inspect',\n ];\n throw new CLIError(\n `Failed to delete dependent element ${depId}`,\n ErrorCategory.SYSTEM,\n suggestions,\n {\n operation: \"delete (cascade)\",\n partialProgress: { completed: deletedCount, total: sortedDependents.length },\n relatedElements: [id, ...sortedDependents.slice(0, 5)],\n }\n );\n }\n }\n }\n } catch (error) {\n // If we're in the middle of cascade deletion, provide recovery info\n if (options.cascade && deletedCount > 0 && !(error instanceof CLIError)) {\n const suggestions = [\n `${deletedCount} dependent element(s) were successfully deleted`,\n 'Use \"dr validate\" to check for broken references',\n \"The target element was not deleted - you can retry the operation\",\n ];\n throw new CLIError(\n \"Cascade deletion was partially completed but encountered an error\",\n ErrorCategory.SYSTEM,\n suggestions,\n {\n operation: \"delete (cascade)\",\n partialProgress: { completed: deletedCount, total: dependents.length },\n }\n );\n }\n throw error;\n }\n\n // Delete the target element\n const handler = new MutationHandler(model, id, layerName);\n await handler.executeDelete(element);\n\n if (isTelemetryEnabled && span) {\n (span as any).setAttribute(\"layer.name\", layerName);\n (span as any).setAttribute(\"element.dependents\", dependents.length);\n (span as any).setAttribute(\"element.totalDeleted\", elementsToRemove.length);\n }\n\n // Check if operation was staged or applied to base model\n const beforeState = handler.getBeforeState();\n if (beforeState) {\n // Check if we went through staging path\n const stagingManager = handler.getStagingManager();\n const activeChangeset = await stagingManager.getActive();\n if (activeChangeset && activeChangeset.status === \"staged\") {\n // Staging path\n handleSuccess(\n `Staged deletion of element ${ansis.bold(id)} to ${ansis.bold(activeChangeset.name)}`,\n {\n elementId: id,\n layer: layerName!,\n changesetStatus: \"staged\",\n changeset: activeChangeset.name,\n totalElementsDeleted: elementsToRemove.length,\n },\n { verbose: options.verbose }\n );\n } else {\n // Base model path: purge any stale relationships MutationHandler may have missed\n const staleBase = elementsToRemove.reduce(\n (n, eid) => n + model.relationships.deleteForElement(eid), 0\n );\n if (staleBase > 0) await model.saveRelationships();\n\n // Report deletion with cascade confirmation\n showDeletionSuccess();\n }\n } else {\n // Fallback success message if before state is not available\n // This should rarely occur, but ensures user always gets feedback\n const staleFallback = elementsToRemove.reduce(\n (n, eid) => n + model.relationships.deleteForElement(eid), 0\n );\n if (staleFallback > 0) await model.saveRelationships();\n\n // Report deletion with cascade confirmation\n showDeletionSuccess();\n }\n\n function showDeletionSuccess(): void {\n const details: Record<string, unknown> = {\n elementId: id,\n layer: layerName!,\n totalElementsDeleted: elementsToRemove.length,\n };\n handleSuccess(`Deleted element ${ansis.bold(id)}`, details, { verbose: options.verbose });\n\n // Show cascade confirmation when cascade is active\n if (options.cascade && dependents.length > 0) {\n handleInfo(ansis.green(`\u2713 Deleted ${dependents.length} dependent element(s)`));\n }\n }\n } catch (error) {\n if (isTelemetryEnabled && span) {\n (span as any).recordException(error as Error);\n (span as any).setStatus({ code: 2, message: (error as Error).message });\n }\n handleError(error);\n } finally {\n if (isTelemetryEnabled) {\n endSpan(span);\n }\n }\n}\n"],
|
|
5
|
-
"mappings": "AAIA,SAAS,eAAe;AACxB,OAAO,WAAW;AAClB,SAAS,aAAa;AACtB,SAAS,uBAAuB;AAChC,SAAS,yBAAyB;AAClC,SAAS,mBAAmB,sBAAsB;AAClD,SAAS,wBAAwB;AACjC,SAAS,UAAU,aAAa,eAAe,YAAY,eAAe,0BAA0B;AACpG,SAAS,WAAW,eAAe;AACnC,SAAS,uBAAuB;AAGhC,MAAM,qBAAgE;AAWtE,eAAsB,cAAc,IAAY,SAAsB;AACpE,QAAM,OAAO,qBACT,UAAU,kBAAkB;IAC1B,cAAc;IACd,iBAAiB,CAAC,CAAC,QAAQ;IAC3B,mBAAmB,CAAC,CAAC,QAAQ;IAC7B,kBAAkB,CAAC,CAAC,QAAQ;GAC7B,IACD;AAEJ,MAAI;
|
|
4
|
+
"sourcesContent": ["/**\n * Delete an element\n */\n\nimport { confirm } from \"@clack/prompts\";\nimport ansis from \"ansis\";\nimport { Model } from \"../core/model.js\";\nimport { MutationHandler } from \"../core/mutation-handler.js\";\nimport { ReferenceRegistry } from \"../core/reference-registry.js\";\nimport { DependencyTracker, TraceDirection } from \"../core/dependency-tracker.js\";\nimport { findElementLayer } from \"../utils/element-utils.js\";\nimport { elementIdentifiers } from \"../core/relationships.js\";\nimport { CLIError, handleError, handleSuccess, handleInfo, ErrorCategory, ModelNotFoundError } from \"../utils/errors.js\";\nimport { startSpan, endSpan } from \"../telemetry/index.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\n\ndeclare const TELEMETRY_ENABLED: boolean | undefined;\nconst isTelemetryEnabled = typeof TELEMETRY_ENABLED !== \"undefined\" ? TELEMETRY_ENABLED : false;\n\nexport interface DeleteOptions {\n model?: string;\n force?: boolean;\n verbose?: boolean;\n debug?: boolean;\n cascade?: boolean;\n dryRun?: boolean;\n}\n\nexport async function deleteCommand(id: string, options: DeleteOptions): Promise<void> {\n const span = isTelemetryEnabled\n ? startSpan(\"element.delete\", {\n \"element.id\": id,\n \"element.force\": !!options.force,\n \"element.cascade\": !!options.cascade,\n \"element.dryRun\": !!options.dryRun,\n })\n : null;\n\n try {\n // Load model (with error handling for missing models)\n let model: Model;\n try {\n model = await Model.load(options.model);\n } catch (error) {\n const message = getErrorMessage(error);\n if (message.includes(\"No DR project\") || message.includes(\"Model not found\")) {\n throw new ModelNotFoundError();\n }\n throw error;\n }\n\n // Find element\n const layerName = await findElementLayer(model, id);\n if (!layerName) {\n throw new CLIError(`Element ${id} not found`, ErrorCategory.USER, [\n `Use \"dr search ${id}\" to find similar elements`,\n 'Use \"dr list <layer>\" to list all elements in a layer',\n ]);\n }\n\n const layer = (await model.getLayer(layerName))!;\n const element = layer.getElement(id);\n\n if (!element) {\n throw new CLIError(`Element ${id} not found`, ErrorCategory.USER, [\n `Use \"dr search ${id}\" to find similar elements`,\n 'Use \"dr list <layer>\" to list all elements in a layer',\n ]);\n }\n\n // Build reference registry to track dependencies\n const registry = new ReferenceRegistry();\n for (const layer of model.layers.values()) {\n for (const element of layer.listElements()) {\n registry.registerElement(element);\n }\n }\n\n // Create dependency tracker\n const tracker = new DependencyTracker(registry, model);\n\n // Find all elements that depend on this element (would be orphaned)\n const dependents = tracker.traceDependencies(id, TraceDirection.DOWN, null);\n\n // Display element information\n handleInfo(\"\");\n handleInfo(ansis.bold(`Removing element: ${ansis.yellow(id)}`), {\n Layer: layerName,\n Type: element.type || \"unknown\",\n ...(element.name ? { Name: element.name } : {}),\n });\n\n // Display dependencies if any exist\n if (dependents.length > 0) {\n handleInfo(\"\");\n handleInfo(\n ansis.yellow(`\u26A0 Warning: This element has ${dependents.length} dependent element(s):`)\n );\n\n const directDependents = tracker.traceDependencies(id, TraceDirection.DOWN, 1);\n\n // Show direct dependents\n for (const dep of directDependents.slice(0, 5)) {\n const depElement = model.getElementById(dep);\n const depLayer = depElement?.layer || \"unknown\";\n handleInfo(ansis.dim(` - ${dep} (${depLayer})`));\n }\n\n if (directDependents.length > 5) {\n handleInfo(ansis.dim(` ... and ${directDependents.length - 5} more direct dependents`));\n }\n\n if (dependents.length > directDependents.length) {\n handleInfo(\n ansis.dim(\n ` (${dependents.length - directDependents.length} transitive dependents not shown)`\n )\n );\n }\n\n // Check if cascade or force is enabled\n if (!options.cascade && !options.force) {\n handleInfo(\"\");\n handleInfo(ansis.red(\"\u2717 Cannot remove element with dependencies.\"));\n handleInfo(ansis.dim(\"Use --cascade to remove all dependent elements\"));\n handleInfo(ansis.dim(\"or --force to skip dependency checks\"));\n const suggestions = [\n `Use --dry-run with --cascade to preview what would be deleted`,\n 'Review dependencies before deletion using \"dr show <element>\"',\n \"Use --cascade to automatically remove all dependent elements\",\n \"Or use --force to remove only this element (dependencies will reference a non-existent element)\",\n ];\n throw new CLIError(\"Element has dependencies\", ErrorCategory.USER, suggestions, {\n operation: \"delete\",\n relatedElements: directDependents.slice(0, 10),\n context: `Element ${id} has ${dependents.length} dependent elements`,\n });\n }\n }\n\n // Calculate total elements to remove\n const elementsToRemove = options.cascade ? [id, ...dependents] : [id];\n\n // Every identifier relationships.yaml may use for the elements being removed\n // (UUID id and dotted path), captured before they are deleted from the model.\n const relationshipIdentifiers = new Set<string>();\n for (const eid of elementsToRemove) {\n const target = eid === id ? element : model.getElementById(eid);\n for (const identifier of elementIdentifiers(target ?? {}, eid)) {\n relationshipIdentifiers.add(identifier);\n }\n }\n\n // Display dry-run summary\n if (options.dryRun) {\n handleInfo(\"\");\n handleInfo(ansis.yellow(`Dry run - not removing`));\n handleInfo(ansis.dim(`Would remove ${elementsToRemove.length} element(s)`));\n\n if (options.cascade && dependents.length > 0) {\n handleInfo(\"\");\n handleInfo(ansis.dim(\"Elements that would be removed:\"));\n handleInfo(ansis.dim(` - ${id} (target)`));\n for (const dep of dependents.slice(0, 10)) {\n handleInfo(ansis.dim(` - ${dep} (dependent)`));\n }\n if (dependents.length > 10) {\n handleInfo(ansis.dim(` ... and ${dependents.length - 10} more`));\n }\n }\n\n handleSuccess(\n `Dry run: would remove ${elementsToRemove.length} element(s)`,\n {\n elementId: id,\n layer: layerName!,\n dryRun: true,\n elementsToRemoveCount: 1,\n dependentElementsToRemoveCount: dependents.length,\n },\n { verbose: options.verbose }\n );\n return;\n }\n\n // Confirm deletion unless --force\n if (!options.force) {\n const isInteractive = process.stdin.isTTY && process.stdout.isTTY;\n if (!isInteractive) {\n handleInfo(ansis.dim(\"Non-interactive environment detected - proceeding without confirmation\"));\n } else {\n const message =\n options.cascade && dependents.length > 0\n ? `Delete ${ansis.bold(id)} and ${ansis.bold(String(dependents.length))} dependent element(s)? This cannot be undone.`\n : `Delete element ${ansis.bold(id)}? This cannot be undone.`;\n\n const confirmed = await confirm({\n message,\n initialValue: false,\n });\n\n if (!confirmed) {\n handleSuccess(\n \"Deletion cancelled\",\n {\n status: \"cancelled\",\n elementId: id,\n layer: layerName!,\n },\n { verbose: options.verbose }\n );\n return;\n }\n }\n }\n\n // Execute deletion with error recovery guidance\n let deletedCount = 0;\n try {\n if (options.cascade && dependents.length > 0) {\n // Delete dependents first (reverse dependency order to avoid orphaned references)\n const sortedDependents = [...dependents].reverse();\n\n for (const depId of sortedDependents) {\n try {\n const depLayerName = await findElementLayer(model, depId);\n if (!depLayerName) continue;\n\n const depLayer = (await model.getLayer(depLayerName))!;\n const depElement = depLayer.getElement(depId)!;\n\n const handler = new MutationHandler(model, depId, depLayerName);\n await handler.executeDelete(depElement);\n deletedCount++;\n\n if (options.verbose) {\n handleInfo(ansis.dim(` Deleted dependent: ${depId}`));\n }\n } catch (depError) {\n // Track partial progress for error recovery\n const suggestions = [\n `${deletedCount} dependent element(s) were deleted before the error`,\n 'Check the model state with \"dr validate\" to identify any broken references',\n 'You may need to manually delete remaining elements or use \"dr show\" to inspect',\n ];\n throw new CLIError(\n `Failed to delete dependent element ${depId}`,\n ErrorCategory.SYSTEM,\n suggestions,\n {\n operation: \"delete (cascade)\",\n partialProgress: { completed: deletedCount, total: sortedDependents.length },\n relatedElements: [id, ...sortedDependents.slice(0, 5)],\n }\n );\n }\n }\n }\n } catch (error) {\n // If we're in the middle of cascade deletion, provide recovery info\n if (options.cascade && deletedCount > 0 && !(error instanceof CLIError)) {\n const suggestions = [\n `${deletedCount} dependent element(s) were successfully deleted`,\n 'Use \"dr validate\" to check for broken references',\n \"The target element was not deleted - you can retry the operation\",\n ];\n throw new CLIError(\n \"Cascade deletion was partially completed but encountered an error\",\n ErrorCategory.SYSTEM,\n suggestions,\n {\n operation: \"delete (cascade)\",\n partialProgress: { completed: deletedCount, total: dependents.length },\n }\n );\n }\n throw error;\n }\n\n // Delete the target element\n const handler = new MutationHandler(model, id, layerName);\n await handler.executeDelete(element);\n\n if (isTelemetryEnabled && span) {\n (span as any).setAttribute(\"layer.name\", layerName);\n (span as any).setAttribute(\"element.dependents\", dependents.length);\n (span as any).setAttribute(\"element.totalDeleted\", elementsToRemove.length);\n }\n\n // Check if operation was staged or applied to base model\n const beforeState = handler.getBeforeState();\n if (beforeState) {\n // Check if we went through staging path\n const stagingManager = handler.getStagingManager();\n const activeChangeset = await stagingManager.getActive();\n if (activeChangeset && activeChangeset.status === \"staged\") {\n // Staging path\n handleSuccess(\n `Staged deletion of element ${ansis.bold(id)} to ${ansis.bold(activeChangeset.name)}`,\n {\n elementId: id,\n layer: layerName!,\n changesetStatus: \"staged\",\n changeset: activeChangeset.name,\n totalElementsDeleted: elementsToRemove.length,\n },\n { verbose: options.verbose }\n );\n } else {\n // Base model path: purge any stale relationships MutationHandler may have missed\n const staleBase = model.relationships.deleteForElement([...relationshipIdentifiers]);\n if (staleBase > 0) await model.saveRelationships();\n\n // Report deletion with cascade confirmation\n showDeletionSuccess();\n }\n } else {\n // Fallback success message if before state is not available\n // This should rarely occur, but ensures user always gets feedback\n const staleFallback = model.relationships.deleteForElement([...relationshipIdentifiers]);\n if (staleFallback > 0) await model.saveRelationships();\n\n // Report deletion with cascade confirmation\n showDeletionSuccess();\n }\n\n function showDeletionSuccess(): void {\n const details: Record<string, unknown> = {\n elementId: id,\n layer: layerName!,\n totalElementsDeleted: elementsToRemove.length,\n };\n handleSuccess(`Deleted element ${ansis.bold(id)}`, details, { verbose: options.verbose });\n\n // Show cascade confirmation when cascade is active\n if (options.cascade && dependents.length > 0) {\n handleInfo(ansis.green(`\u2713 Deleted ${dependents.length} dependent element(s)`));\n }\n }\n } catch (error) {\n if (isTelemetryEnabled && span) {\n (span as any).recordException(error as Error);\n (span as any).setStatus({ code: 2, message: (error as Error).message });\n }\n handleError(error);\n } finally {\n if (isTelemetryEnabled) {\n endSpan(span);\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["showDeletionSuccess", "layer", "element"]
|
|
7
7
|
}
|
package/dist/commands/farm.js
CHANGED
|
@@ -2,25 +2,76 @@ import ansis from "ansis";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { rm } from "fs/promises";
|
|
4
4
|
import { FarmManifest } from "../core/farm-manifest.js";
|
|
5
|
-
import { findFarmRoot } from "../utils/project-paths.js";
|
|
5
|
+
import { findFarmRoot, resolveModelDirectory } from "../utils/project-paths.js";
|
|
6
6
|
import { fileExists } from "../utils/file-io.js";
|
|
7
7
|
import { execSync, execFileSync } from "child_process";
|
|
8
8
|
import * as prompts from "@clack/prompts";
|
|
9
|
-
import { getErrorMessage, handleSuccess, handleInfo } from "../utils/errors.js";
|
|
9
|
+
import { getErrorMessage, handleSuccess, handleInfo, handleWarning } from "../utils/errors.js";
|
|
10
10
|
import { isJson } from "../utils/globals.js";
|
|
11
11
|
import { isTelemetryEnabled, startSpan, endSpan } from "../telemetry/index.js";
|
|
12
12
|
import { Model } from "../core/model.js";
|
|
13
13
|
import { ComposedReferenceValidator } from "../validators/composed-reference-validator.js";
|
|
14
14
|
import { ValidationFormatter } from "../validators/validation-formatter.js";
|
|
15
15
|
import { FarmSyncEngine } from "../core/farm-sync-engine.js";
|
|
16
|
-
|
|
16
|
+
import { FarmSyncState, FarmSyncStateNotFoundError } from "../core/farm-sync-state.js";
|
|
17
|
+
function checkGitRepository(cwd) {
|
|
17
18
|
try {
|
|
18
|
-
return execSync("git rev-parse --git-dir", { cwd, stdio: "pipe" }), !0;
|
|
19
|
+
return execSync("git rev-parse --git-dir", { cwd, stdio: "pipe" }), { isRepo: !0 };
|
|
19
20
|
} catch (error) {
|
|
20
21
|
const errorMsg = getErrorMessage(error);
|
|
21
|
-
return errorMsg.toLowerCase().includes("not a git repository")
|
|
22
|
+
return errorMsg.toLowerCase().includes("not a git repository") ? { isRepo: !1 } : { isRepo: !1, error: errorMsg.trim() };
|
|
22
23
|
}
|
|
23
24
|
}
|
|
25
|
+
function uncommittedChangeWarning(what, farmRoot, gitError) {
|
|
26
|
+
return `${what} was saved but not committed: could not check git repository at ${farmRoot}: ${gitError}`;
|
|
27
|
+
}
|
|
28
|
+
function gitTopLevel(cwd) {
|
|
29
|
+
try {
|
|
30
|
+
return execSync("git rev-parse --show-toplevel", { cwd, stdio: "pipe", encoding: "utf-8" }).trim() || null;
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function isInSameGitRepository(farmRoot, target) {
|
|
36
|
+
const farmTop = gitTopLevel(farmRoot);
|
|
37
|
+
return farmTop !== null && farmTop === gitTopLevel(target);
|
|
38
|
+
}
|
|
39
|
+
function farmRelativePath(farmRoot, target) {
|
|
40
|
+
const rel = path.relative(farmRoot, target);
|
|
41
|
+
return rel === "" ? "." : rel.split(path.sep).join("/");
|
|
42
|
+
}
|
|
43
|
+
function isWithinPath(target, parent) {
|
|
44
|
+
return target === parent || target.startsWith(parent + path.sep);
|
|
45
|
+
}
|
|
46
|
+
async function resolveProjectModel(name, input) {
|
|
47
|
+
const { farmRoot, codebaseFullPath, explicitModel, forceNewModel } = input;
|
|
48
|
+
if (explicitModel) {
|
|
49
|
+
const explicitFullPath = path.resolve(farmRoot, explicitModel), resolved = await resolveModelDirectory(explicitFullPath, { requireManifest: !0 });
|
|
50
|
+
if (!resolved)
|
|
51
|
+
throw new Error(`No model found at '${explicitModel}': expected manifest.yaml in ${explicitFullPath}, ${path.join(explicitFullPath, "model")} or ${path.join(explicitFullPath, "documentation-robotics", "model")}. Omit --model to scaffold a new model at ${name}-model.`);
|
|
52
|
+
return {
|
|
53
|
+
modelFolder: farmRelativePath(farmRoot, resolved.modelDir),
|
|
54
|
+
modelFullPath: resolved.modelDir,
|
|
55
|
+
adopted: !0
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const detected = codebaseFullPath === farmRoot ? null : await resolveModelDirectory(codebaseFullPath, { requireManifest: !0 });
|
|
59
|
+
if (detected && !forceNewModel)
|
|
60
|
+
return {
|
|
61
|
+
modelFolder: farmRelativePath(farmRoot, detected.modelDir),
|
|
62
|
+
modelFullPath: detected.modelDir,
|
|
63
|
+
adopted: !0
|
|
64
|
+
};
|
|
65
|
+
const modelFolder = `${name}-model`, modelFullPath = path.resolve(path.join(farmRoot, modelFolder));
|
|
66
|
+
if (!modelFullPath.startsWith(farmRoot + path.sep))
|
|
67
|
+
throw new Error(`Invalid model path: would escape farm root. Path: ${modelFolder}`);
|
|
68
|
+
return {
|
|
69
|
+
modelFolder,
|
|
70
|
+
modelFullPath,
|
|
71
|
+
adopted: !1,
|
|
72
|
+
...detected ? { ignoredModelPath: farmRelativePath(farmRoot, detected.modelDir) } : {}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
24
75
|
async function farmInitCommand(options) {
|
|
25
76
|
const span = isTelemetryEnabled ? startSpan("farm.init") : null;
|
|
26
77
|
try {
|
|
@@ -73,6 +124,8 @@ async function farmInitCommand(options) {
|
|
|
73
124
|
async function farmAddCommand(name, options) {
|
|
74
125
|
const span = isTelemetryEnabled ? startSpan("farm.add", { "project.name": name }) : null, useJson = options.format === "json" || isJson();
|
|
75
126
|
try {
|
|
127
|
+
if (options.model && options.newModel)
|
|
128
|
+
throw new Error("--model and --new-model cannot be combined: --model registers an existing model, --new-model scaffolds a fresh one");
|
|
76
129
|
const farmRoot = await findFarmRoot();
|
|
77
130
|
if (!farmRoot)
|
|
78
131
|
throw new Error("Not in a farm directory. Run 'dr farm init' first.");
|
|
@@ -80,8 +133,10 @@ async function farmAddCommand(name, options) {
|
|
|
80
133
|
if (manifest.getProject(name))
|
|
81
134
|
throw new Error(`Project '${name}' already exists in this farm`);
|
|
82
135
|
let codebasePath = options.codebase || name;
|
|
136
|
+
const codebaseFullPath = path.resolve(path.join(farmRoot, codebasePath)), farmRootResolved = path.resolve(farmRoot);
|
|
83
137
|
if (options.remote) {
|
|
84
|
-
|
|
138
|
+
if (!codebaseFullPath.startsWith(farmRootResolved + path.sep))
|
|
139
|
+
throw new Error(`Invalid codebase path: would escape farm root. Path: ${codebasePath}`);
|
|
85
140
|
if (await fileExists(codebaseFullPath))
|
|
86
141
|
throw new Error(`Codebase directory '${codebasePath}' already exists`);
|
|
87
142
|
useJson || handleInfo(`Cloning ${options.remote} to ${codebasePath}...`);
|
|
@@ -93,8 +148,17 @@ async function farmAddCommand(name, options) {
|
|
|
93
148
|
throw new Error(`Failed to clone repository: ${getErrorMessage(error)}`);
|
|
94
149
|
}
|
|
95
150
|
}
|
|
96
|
-
const
|
|
97
|
-
|
|
151
|
+
const registration = await resolveProjectModel(name, {
|
|
152
|
+
farmRoot: farmRootResolved,
|
|
153
|
+
codebaseFullPath,
|
|
154
|
+
explicitModel: options.model,
|
|
155
|
+
forceNewModel: options.newModel ?? !1
|
|
156
|
+
}), modelFolder = registration.modelFolder;
|
|
157
|
+
if (registration.ignoredModelPath) {
|
|
158
|
+
const ignoredWarning = `Codebase '${codebasePath}' already has a model at ${registration.ignoredModelPath}; ignoring it and scaffolding a new model at ${modelFolder} (--new-model)`;
|
|
159
|
+
registration.warning = ignoredWarning, useJson || handleWarning(ignoredWarning);
|
|
160
|
+
}
|
|
161
|
+
registration.adopted ? useJson || handleInfo(`Using existing model at ${registration.modelFullPath}; nothing was created`) : await fileExists(registration.modelFullPath) || (await Model.init(registration.modelFullPath, {
|
|
98
162
|
name,
|
|
99
163
|
version: "0.1.0",
|
|
100
164
|
description: void 0,
|
|
@@ -106,25 +170,42 @@ async function farmAddCommand(name, options) {
|
|
|
106
170
|
source: codebasePath,
|
|
107
171
|
model: modelFolder,
|
|
108
172
|
remote: options.remote
|
|
109
|
-
}), await manifest.save(farmYamlPath)
|
|
173
|
+
}), await manifest.save(farmYamlPath);
|
|
174
|
+
const gitCheck = checkGitRepository(farmRoot);
|
|
175
|
+
let gitCommitted = !1, gitWarning;
|
|
176
|
+
if (gitCheck.error)
|
|
177
|
+
gitWarning = uncommittedChangeWarning("farm.yaml", farmRoot, gitCheck.error), useJson || handleWarning(gitWarning);
|
|
178
|
+
else if (gitCheck.isRepo)
|
|
110
179
|
try {
|
|
111
180
|
execSync("git add farm.yaml", { cwd: farmRoot, stdio: "pipe" }), execFileSync("git", ["commit", "-m", `Add project: ${name}`], {
|
|
112
181
|
cwd: farmRoot,
|
|
113
182
|
stdio: useJson ? "pipe" : "inherit"
|
|
114
|
-
});
|
|
183
|
+
}), gitCommitted = !0;
|
|
115
184
|
} catch (commitError) {
|
|
116
185
|
const errorMsg = getErrorMessage(commitError);
|
|
117
186
|
if (!errorMsg.includes("nothing to commit"))
|
|
118
187
|
throw new Error(`Failed to commit farm changes: ${errorMsg}`);
|
|
119
188
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
189
|
+
if (useJson)
|
|
190
|
+
console.log(JSON.stringify({
|
|
191
|
+
status: "ok",
|
|
192
|
+
project: name,
|
|
193
|
+
source: codebasePath,
|
|
194
|
+
model: modelFolder,
|
|
195
|
+
model_path: registration.modelFullPath,
|
|
196
|
+
model_adopted: registration.adopted,
|
|
197
|
+
...registration.ignoredModelPath ? { ignored_model: registration.ignoredModelPath } : {},
|
|
198
|
+
...registration.warning ? { warning: registration.warning } : {},
|
|
199
|
+
git_committed: gitCommitted,
|
|
200
|
+
...gitWarning ? { git_warning: gitWarning } : {}
|
|
201
|
+
}));
|
|
202
|
+
else {
|
|
203
|
+
const modelNote = registration.adopted ? " (existing model, adopted in place)" : "";
|
|
204
|
+
handleSuccess(`Project added to farm: ${ansis.bold(name)}
|
|
126
205
|
Codebase: ${codebasePath}
|
|
127
|
-
Model: ${modelFolder}`)
|
|
206
|
+
Model: ${modelFolder}${modelNote}`);
|
|
207
|
+
}
|
|
208
|
+
span && endSpan(span);
|
|
128
209
|
} catch (error) {
|
|
129
210
|
useJson ? console.log(JSON.stringify({
|
|
130
211
|
status: "error",
|
|
@@ -142,23 +223,29 @@ async function farmRemoveCommand(name, options) {
|
|
|
142
223
|
const farmYamlPath = path.join(farmRoot, "farm.yaml"), manifest = await FarmManifest.load(farmYamlPath), project = manifest.getProject(name);
|
|
143
224
|
if (!project)
|
|
144
225
|
throw new Error(`Project '${name}' not found in farm`);
|
|
226
|
+
let modelDeleted = !1, modelKeptReason;
|
|
145
227
|
if (options.deleteModel) {
|
|
146
|
-
const modelFullPath = path.resolve(path.join(farmRoot, project.model)), farmRootResolved = path.resolve(farmRoot);
|
|
147
|
-
if (
|
|
148
|
-
|
|
149
|
-
if (await fileExists(modelFullPath))
|
|
228
|
+
const modelFullPath = path.resolve(path.join(farmRoot, project.model)), farmRootResolved = path.resolve(farmRoot), codebaseFullPath = path.resolve(path.join(farmRoot, project.source));
|
|
229
|
+
if (modelFullPath.startsWith(farmRootResolved + path.sep) ? isWithinPath(modelFullPath, codebaseFullPath) && (modelKeptReason = `model '${project.model}' is the codebase's own model (inside '${project.source}')`) : modelKeptReason = `model '${project.model}' lies outside the farm root and is not managed by the farm`, modelKeptReason)
|
|
230
|
+
useJson || handleWarning(`Model folder not deleted: ${modelKeptReason}. Remove it by hand if that is what you want.`);
|
|
231
|
+
else if (await fileExists(modelFullPath))
|
|
150
232
|
try {
|
|
151
|
-
await rm(modelFullPath, { recursive: !0, force: !1 }), useJson || handleInfo(`Deleted model folder: ${project.model}`);
|
|
233
|
+
await rm(modelFullPath, { recursive: !0, force: !1 }), modelDeleted = !0, useJson || handleInfo(`Deleted model folder: ${project.model}`);
|
|
152
234
|
} catch (error) {
|
|
153
235
|
throw new Error(`Failed to delete model folder: ${getErrorMessage(error)}`);
|
|
154
236
|
}
|
|
155
237
|
}
|
|
156
|
-
|
|
238
|
+
manifest.removeProject(name), await manifest.save(farmYamlPath);
|
|
239
|
+
const gitCheck = checkGitRepository(farmRoot);
|
|
240
|
+
let gitCommitted = !1, gitWarning;
|
|
241
|
+
if (gitCheck.error)
|
|
242
|
+
gitWarning = uncommittedChangeWarning("farm.yaml", farmRoot, gitCheck.error), useJson || handleWarning(gitWarning);
|
|
243
|
+
else if (gitCheck.isRepo)
|
|
157
244
|
try {
|
|
158
245
|
execSync("git add farm.yaml", { cwd: farmRoot, stdio: "pipe" }), execFileSync("git", ["commit", "-m", `Remove project: ${name}`], {
|
|
159
246
|
cwd: farmRoot,
|
|
160
247
|
stdio: useJson ? "pipe" : "inherit"
|
|
161
|
-
});
|
|
248
|
+
}), gitCommitted = !0;
|
|
162
249
|
} catch (commitError) {
|
|
163
250
|
const errorMsg = getErrorMessage(commitError);
|
|
164
251
|
if (!errorMsg.includes("nothing to commit"))
|
|
@@ -167,7 +254,11 @@ async function farmRemoveCommand(name, options) {
|
|
|
167
254
|
useJson ? console.log(JSON.stringify({
|
|
168
255
|
status: "ok",
|
|
169
256
|
project: name,
|
|
170
|
-
|
|
257
|
+
model: project.model,
|
|
258
|
+
modelDeleted,
|
|
259
|
+
...modelKeptReason ? { model_kept_reason: modelKeptReason } : {},
|
|
260
|
+
git_committed: gitCommitted,
|
|
261
|
+
...gitWarning ? { git_warning: gitWarning } : {}
|
|
171
262
|
})) : handleSuccess(`Project removed from farm: ${ansis.bold(name)}`), span && endSpan(span);
|
|
172
263
|
} catch (error) {
|
|
173
264
|
useJson ? console.log(JSON.stringify({
|
|
@@ -183,33 +274,34 @@ async function farmStatusCommand(options) {
|
|
|
183
274
|
const farmRoot = await findFarmRoot();
|
|
184
275
|
if (!farmRoot)
|
|
185
276
|
throw new Error("Not in a farm directory.");
|
|
186
|
-
const farmYamlPath = path.join(farmRoot, "farm.yaml"), manifest = await FarmManifest.load(farmYamlPath), projects = manifest.getAllProjects(), engine = new FarmSyncEngine(farmRoot), projectsWithStatus = await Promise.all(projects.map(async (p) => {
|
|
277
|
+
const farmYamlPath = path.join(farmRoot, "farm.yaml"), manifest = await FarmManifest.load(farmYamlPath), projects = manifest.getAllProjects(), engine = new FarmSyncEngine(farmRoot), useJson = options.format === "json" || isJson(), projectsWithStatus = await Promise.all(projects.map(async (p) => {
|
|
278
|
+
const result = {
|
|
279
|
+
name: p.name,
|
|
280
|
+
source: p.source,
|
|
281
|
+
model: p.model,
|
|
282
|
+
remote: p.remote,
|
|
283
|
+
last_sync_commit: void 0,
|
|
284
|
+
current_commit: void 0,
|
|
285
|
+
has_pending_changes: void 0
|
|
286
|
+
};
|
|
287
|
+
let lastSyncCommit;
|
|
187
288
|
try {
|
|
188
|
-
const syncStatePath = path.join(farmRoot, p.model, ".farm-sync.yaml")
|
|
189
|
-
|
|
190
|
-
name: p.name,
|
|
191
|
-
source: p.source,
|
|
192
|
-
model: p.model,
|
|
193
|
-
remote: p.remote,
|
|
194
|
-
last_sync_commit: lastSyncCommit,
|
|
195
|
-
current_commit: currentCommit,
|
|
196
|
-
has_pending_changes: hasPendingChanges
|
|
197
|
-
};
|
|
289
|
+
const syncStatePath = path.join(farmRoot, p.model, ".farm-sync.yaml");
|
|
290
|
+
lastSyncCommit = (await FarmSyncState.loadOrCreate(syncStatePath, p.name)).lastSyncCommit, result.last_sync_commit = lastSyncCommit;
|
|
198
291
|
} catch (error) {
|
|
199
|
-
|
|
200
|
-
return !errorMsg.includes("not found") && !errorMsg.includes("does not exist") && !errorMsg.includes("ENOENT") && console.error(`Warning: Error reading sync state for ${p.name}: ${errorMsg}`), {
|
|
201
|
-
name: p.name,
|
|
202
|
-
source: p.source,
|
|
203
|
-
model: p.model,
|
|
204
|
-
remote: p.remote,
|
|
205
|
-
last_sync_commit: void 0,
|
|
206
|
-
current_commit: void 0,
|
|
207
|
-
has_pending_changes: !1
|
|
208
|
-
};
|
|
292
|
+
return error instanceof FarmSyncStateNotFoundError ? (result.has_pending_changes = !1, result) : (result.sync_state_error = getErrorMessage(error), useJson || handleWarning(`${p.name}: could not read sync state: ${result.sync_state_error}`), result);
|
|
209
293
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
294
|
+
try {
|
|
295
|
+
const currentCommit = await engine.getCurrentCommit(p.source);
|
|
296
|
+
result.current_commit = currentCommit, result.has_pending_changes = !!(lastSyncCommit && lastSyncCommit !== currentCommit);
|
|
297
|
+
} catch (error) {
|
|
298
|
+
result.codebase_error = getErrorMessage(error), useJson || handleWarning(`${p.name}: ${result.codebase_error}`);
|
|
299
|
+
}
|
|
300
|
+
return result;
|
|
301
|
+
})), projectsWithErrors = projectsWithStatus.filter((p) => p.sync_state_error !== void 0 || p.codebase_error !== void 0), allHealthy = projectsWithErrors.length === 0;
|
|
302
|
+
useJson ? console.log(JSON.stringify({
|
|
303
|
+
status: allHealthy ? "ok" : "error",
|
|
304
|
+
code: allHealthy ? 0 : 1,
|
|
213
305
|
farm: {
|
|
214
306
|
name: manifest.name,
|
|
215
307
|
path: farmRoot,
|
|
@@ -221,8 +313,9 @@ async function farmStatusCommand(options) {
|
|
|
221
313
|
project_count: projects.length
|
|
222
314
|
})) : (console.log(ansis.bold(`Farm: ${manifest.name}`)), console.log(`Location: ${farmRoot}`), manifest.platform_view && console.log(`Platform-View: ${ansis.green("enabled")}`), console.log(`Projects: ${projects.length}
|
|
223
315
|
`), projects.length === 0 ? console.log(ansis.dim("No projects registered yet.")) : projectsWithStatus.forEach((project) => {
|
|
224
|
-
console.log(` ${ansis.cyan(project.name)}`), console.log(` Codebase: ${project.source}`), console.log(` Model: ${project.model}`), project.remote && console.log(` Remote: ${project.remote}`), project.has_pending_changes ? console.log(` Status: ${ansis.yellow("\u26A0 Pending changes")}`) : project.last_sync_commit ? console.log(` Status: ${ansis.green("\u2713 Up to date")}`) : console.log(` Status: ${ansis.dim("Not synced yet")}`);
|
|
225
|
-
})
|
|
316
|
+
console.log(` ${ansis.cyan(project.name)}`), console.log(` Codebase: ${project.source}`), console.log(` Model: ${project.model}`), project.remote && console.log(` Remote: ${project.remote}`), project.sync_state_error ? (console.log(` Status: ${ansis.red("\u2717 Sync state corrupted")}`), console.log(` Error: ${project.sync_state_error}`)) : project.codebase_error ? (console.log(` Status: ${ansis.red("\u2717 Codebase unavailable")}`), console.log(` Error: ${project.codebase_error}`)) : project.has_pending_changes ? console.log(` Status: ${ansis.yellow("\u26A0 Pending changes")}`) : project.last_sync_commit ? console.log(` Status: ${ansis.green("\u2713 Up to date")}`) : console.log(` Status: ${ansis.dim("Not synced yet")}`);
|
|
317
|
+
}), allHealthy || console.error(ansis.red(`
|
|
318
|
+
${projectsWithErrors.length} project(s) could not be checked (see errors above)`))), span && endSpan(span), allHealthy || process.exit(1);
|
|
226
319
|
} catch (error) {
|
|
227
320
|
options.format === "json" || isJson() ? console.log(JSON.stringify({
|
|
228
321
|
status: "error",
|
|
@@ -465,6 +558,11 @@ async function farmSyncCommand(options) {
|
|
|
465
558
|
throw new Error("Concurrency must be a positive number");
|
|
466
559
|
if (options.autoCommit && concurrency > 1)
|
|
467
560
|
throw new Error("Cannot use --auto-commit with --concurrency > 1: concurrent git operations would cause race conditions. Use --concurrency 1 for auto-commit, or remove --auto-commit for parallel processing.");
|
|
561
|
+
if (options.output) {
|
|
562
|
+
const outputExt = path.extname(options.output).toLowerCase();
|
|
563
|
+
if (outputExt !== ".json")
|
|
564
|
+
throw new Error(`Unsupported output format: ${outputExt || "(no extension)"}. Use .json`);
|
|
565
|
+
}
|
|
468
566
|
const useJson = options.format === "json" || isJson();
|
|
469
567
|
!useJson && !options.verbose && handleInfo(`Syncing ${projectsToSync.length} project(s) (concurrency: ${concurrency})...`);
|
|
470
568
|
const allResults = [], processSyncProject = async (project) => {
|
|
@@ -492,17 +590,26 @@ async function farmSyncCommand(options) {
|
|
|
492
590
|
};
|
|
493
591
|
if (options.autoCommit && result.changesetId && result.changeCount > 0 && !options.dryRun)
|
|
494
592
|
try {
|
|
495
|
-
const
|
|
496
|
-
|
|
593
|
+
const commitResult = await new (await import("../core/staging-area.js")).StagingAreaManager(model.rootPath, model).commit(model, result.changesetId);
|
|
594
|
+
resultEntry.auto_committed = !0, resultEntry.committed_changes = commitResult.committed;
|
|
595
|
+
const gitCheck = checkGitRepository(farmRoot);
|
|
596
|
+
if (gitCheck.error)
|
|
597
|
+
resultEntry.git_committed = !1, resultEntry.git_warning = uncommittedChangeWarning(`Model '${project.model}'`, farmRoot, gitCheck.error), useJson || handleWarning(`${project.name}: ${resultEntry.git_warning}`);
|
|
598
|
+
else if (gitCheck.isRepo && !isInSameGitRepository(farmRoot, modelPath))
|
|
599
|
+
resultEntry.git_committed = !1, resultEntry.git_warning = `Model '${project.model}' is outside the farm git repository at ${farmRoot}: the synced changes were saved but not committed. Commit them in the model's own repository.`, useJson || handleWarning(`${project.name}: ${resultEntry.git_warning}`);
|
|
600
|
+
else if (gitCheck.isRepo)
|
|
497
601
|
try {
|
|
498
|
-
execFileSync("git", ["add", project.model], { cwd: farmRoot, stdio: "pipe" }), execFileSync("git", ["commit", "-m", `Sync: ${result.changesetId} - ${commitResult.committed} change(s)`], { cwd: farmRoot, stdio: "pipe" });
|
|
602
|
+
execFileSync("git", ["add", project.model], { cwd: farmRoot, stdio: "pipe" }), execFileSync("git", ["commit", "-m", `Sync: ${result.changesetId} - ${commitResult.committed} change(s)`], { cwd: farmRoot, stdio: "pipe" }), resultEntry.git_committed = !0;
|
|
499
603
|
} catch (gitCommitError) {
|
|
500
604
|
if (!getErrorMessage(gitCommitError).includes("nothing to commit"))
|
|
501
605
|
throw gitCommitError;
|
|
606
|
+
resultEntry.git_committed = !0;
|
|
502
607
|
}
|
|
608
|
+
else
|
|
609
|
+
resultEntry.git_committed = !1;
|
|
503
610
|
options.verbose && !useJson && handleInfo(` Auto-committed: ${commitResult.committed} change(s)`);
|
|
504
611
|
} catch (commitError) {
|
|
505
|
-
resultEntry.auto_committed = !1, resultEntry.commit_error = getErrorMessage(commitError), resultEntry.status = "error", useJson ||
|
|
612
|
+
resultEntry.auto_committed = !1, resultEntry.commit_error = getErrorMessage(commitError), resultEntry.status = "error", useJson || handleWarning(`${project.name}: auto-commit failed: ${getErrorMessage(commitError)}`);
|
|
506
613
|
}
|
|
507
614
|
if (allResults.push(resultEntry), options.verbose && !useJson) {
|
|
508
615
|
handleInfo(`
|
|
@@ -559,11 +666,11 @@ Project: ${project.name}`);
|
|
|
559
666
|
}
|
|
560
667
|
if (options.output) {
|
|
561
668
|
const { writeFile } = await import("../utils/file-io.js");
|
|
562
|
-
|
|
669
|
+
await writeFile(options.output, JSON.stringify({
|
|
563
670
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
564
671
|
farm_root: farmRoot,
|
|
565
672
|
projects: allResults
|
|
566
|
-
}, null, 2));
|
|
673
|
+
}, null, 2)), useJson || handleInfo(`Report written to ${options.output}`);
|
|
567
674
|
}
|
|
568
675
|
span && endSpan(span), allSucceeded || process.exit(1);
|
|
569
676
|
} catch (error) {
|
|
@@ -588,12 +695,21 @@ Notes:
|
|
|
588
695
|
the aggregation hub. When validating this project with 'dr farm validate --project platform-view',
|
|
589
696
|
the CLI will automatically resolve cross-model references from all farm projects.`).action(async (options) => {
|
|
590
697
|
await farmInitCommand(options);
|
|
591
|
-
}), farmGroup.command("add <name>").description("Add a project to the farm").option("--remote <url>", "Git remote URL to clone").option("--codebase <path>", "Path to codebase folder (defaults to project name)").option("--format <format>", "Output format: text, json", "text").addHelpText("after", `
|
|
698
|
+
}), farmGroup.command("add <name>").description("Add a project to the farm").option("--remote <url>", "Git remote URL to clone").option("--codebase <path>", "Path to codebase folder, relative to the farm root (defaults to project name; may point outside the farm root unless --remote is used)").option("--model <path>", "Register an existing model at this path (relative to the farm root) instead of detecting or scaffolding one").option("--new-model", "Always scaffold a fresh <name>-model, even when the codebase already carries a model").option("--format <format>", "Output format: text, json", "text").addHelpText("after", `
|
|
592
699
|
Examples:
|
|
593
700
|
$ dr farm add my-service
|
|
594
701
|
$ dr farm add my-service --remote https://github.com/org/my-service.git
|
|
595
702
|
$ dr farm add my-service --codebase ./services/my-service
|
|
596
|
-
$ dr farm add my-service --
|
|
703
|
+
$ dr farm add my-service --codebase ../my-service # sibling checkout outside the farm root
|
|
704
|
+
$ dr farm add my-service --codebase ../my-service --new-model
|
|
705
|
+
$ dr farm add my-service --model ../shared-models/my-service
|
|
706
|
+
$ dr farm add my-service --format json
|
|
707
|
+
|
|
708
|
+
Model detection:
|
|
709
|
+
If the codebase already holds a model (documentation-robotics/model/manifest.yaml, model/manifest.yaml
|
|
710
|
+
or manifest.yaml at the codebase path itself), that model is adopted in place and nothing is
|
|
711
|
+
created; farm.yaml records its path (which may lie outside the farm root). Use --new-model to
|
|
712
|
+
scaffold a separate <name>-model regardless, or --model to point at a model explicitly.`).action(async (name, options) => {
|
|
597
713
|
await farmAddCommand(name, options);
|
|
598
714
|
}), farmGroup.command("remove <name>").description("Remove a project from the farm").option("--delete-model", "Also delete the model folder").option("--format <format>", "Output format: text, json", "text").addHelpText("after", `
|
|
599
715
|
Examples:
|
|
@@ -626,7 +742,7 @@ Examples:
|
|
|
626
742
|
$ dr farm pull --verbose
|
|
627
743
|
$ dr farm pull --format json`).action(async (options) => {
|
|
628
744
|
await farmPullCommand(options);
|
|
629
|
-
}), farmGroup.command("sync").description("Sync farm projects with their models (generates staged changesets)").option("--project <name>", "Sync only a specific project (default: all)").option("--verbose", "Show verbose output").option("--dry-run", "Preview without creating changesets").option("--force", "Proceed despite ambiguous file-to-element mappings").option("--output <path>", "Export sync report to file (
|
|
745
|
+
}), farmGroup.command("sync").description("Sync farm projects with their models (generates staged changesets)").option("--project <name>", "Sync only a specific project (default: all)").option("--verbose", "Show verbose output").option("--dry-run", "Preview without creating changesets").option("--force", "Proceed despite ambiguous file-to-element mappings").option("--output <path>", "Export sync report to file (.json only)").option("--format <format>", "Output format: text, json", "text").option("--auto-commit", "Automatically commit changes (bypasses staged review)").option("--concurrency <n>", "Process multiple projects in parallel (default: 1)", "1").addHelpText("after", `
|
|
630
746
|
Examples:
|
|
631
747
|
$ dr farm sync
|
|
632
748
|
$ dr farm sync --project my-service
|