@jterrats/smart-deployment 1.0.3 → 1.0.5

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.
Files changed (258) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +6 -0
  3. package/lib/ai/wave-validation-service.d.ts +21 -0
  4. package/lib/ai/wave-validation-service.js +189 -114
  5. package/lib/ai/wave-validation-service.js.map +1 -1
  6. package/lib/analysis/analysis-reporter.d.ts +12 -0
  7. package/lib/analysis/analysis-reporter.js +61 -0
  8. package/lib/analysis/analysis-reporter.js.map +1 -1
  9. package/lib/analysis/analyze-artifact-service.d.ts +25 -0
  10. package/lib/analysis/analyze-artifact-service.js +50 -0
  11. package/lib/analysis/analyze-artifact-service.js.map +1 -0
  12. package/lib/analysis/analyze-context-service.d.ts +25 -0
  13. package/lib/analysis/analyze-context-service.js +28 -0
  14. package/lib/analysis/analyze-context-service.js.map +1 -0
  15. package/lib/analysis/project-analysis-service.d.ts +48 -0
  16. package/lib/analysis/project-analysis-service.js +134 -0
  17. package/lib/analysis/project-analysis-service.js.map +1 -0
  18. package/lib/commands/analyze.d.ts +0 -2
  19. package/lib/commands/analyze.js +31 -162
  20. package/lib/commands/analyze.js.map +1 -1
  21. package/lib/commands/config.d.ts +0 -4
  22. package/lib/commands/config.js +25 -95
  23. package/lib/commands/config.js.map +1 -1
  24. package/lib/commands/resume.js +6 -16
  25. package/lib/commands/resume.js.map +1 -1
  26. package/lib/commands/start.d.ts +0 -11
  27. package/lib/commands/start.js +38 -550
  28. package/lib/commands/start.js.map +1 -1
  29. package/lib/commands/status.js +36 -12
  30. package/lib/commands/status.js.map +1 -1
  31. package/lib/commands/validate.d.ts +3 -0
  32. package/lib/commands/validate.js +5 -7
  33. package/lib/commands/validate.js.map +1 -1
  34. package/lib/config/config-mutation-service.d.ts +21 -0
  35. package/lib/config/config-mutation-service.js +61 -0
  36. package/lib/config/config-mutation-service.js.map +1 -0
  37. package/lib/dependencies/circular-dependency-detector.d.ts +5 -0
  38. package/lib/dependencies/circular-dependency-detector.js +67 -72
  39. package/lib/dependencies/circular-dependency-detector.js.map +1 -1
  40. package/lib/dependencies/dependency-depth-calculator.d.ts +28 -0
  41. package/lib/dependencies/dependency-depth-calculator.js +86 -39
  42. package/lib/dependencies/dependency-depth-calculator.js.map +1 -1
  43. package/lib/dependencies/dependency-graph-builder.d.ts +45 -13
  44. package/lib/dependencies/dependency-graph-builder.js +228 -123
  45. package/lib/dependencies/dependency-graph-builder.js.map +1 -1
  46. package/lib/dependencies/dependency-impact-analyzer.d.ts +13 -0
  47. package/lib/dependencies/dependency-impact-analyzer.js +146 -102
  48. package/lib/dependencies/dependency-impact-analyzer.js.map +1 -1
  49. package/lib/dependencies/dependency-merger.d.ts +10 -0
  50. package/lib/dependencies/dependency-merger.js +123 -61
  51. package/lib/dependencies/dependency-merger.js.map +1 -1
  52. package/lib/dependencies/dependency-resolver.d.ts +34 -9
  53. package/lib/dependencies/dependency-resolver.js +181 -112
  54. package/lib/dependencies/dependency-resolver.js.map +1 -1
  55. package/lib/dependencies/dependency-semantics.d.ts +20 -0
  56. package/lib/dependencies/dependency-semantics.js +55 -0
  57. package/lib/dependencies/dependency-semantics.js.map +1 -0
  58. package/lib/dependencies/graph-visualizer.d.ts +6 -1
  59. package/lib/dependencies/graph-visualizer.js +38 -4
  60. package/lib/dependencies/graph-visualizer.js.map +1 -1
  61. package/lib/dependencies/heuristic-inference.d.ts +2 -53
  62. package/lib/dependencies/heuristic-inference.js +8 -317
  63. package/lib/dependencies/heuristic-inference.js.map +1 -1
  64. package/lib/dependencies/heuristic-patterns.d.ts +14 -0
  65. package/lib/dependencies/heuristic-patterns.js +247 -0
  66. package/lib/dependencies/heuristic-patterns.js.map +1 -0
  67. package/lib/deployment/cycle-remediation-runner.d.ts +36 -0
  68. package/lib/deployment/cycle-remediation-runner.js +262 -0
  69. package/lib/deployment/cycle-remediation-runner.js.map +1 -0
  70. package/lib/deployment/deployment-context-service.d.ts +24 -0
  71. package/lib/deployment/deployment-context-service.js +17 -0
  72. package/lib/deployment/deployment-context-service.js.map +1 -0
  73. package/lib/deployment/deployment-runner.d.ts +37 -0
  74. package/lib/deployment/deployment-runner.js +94 -0
  75. package/lib/deployment/deployment-runner.js.map +1 -0
  76. package/lib/deployment/deployment-status-service.d.ts +5 -0
  77. package/lib/deployment/deployment-status-service.js +39 -18
  78. package/lib/deployment/deployment-status-service.js.map +1 -1
  79. package/lib/deployment/deployment-validation-service.d.ts +5 -2
  80. package/lib/deployment/deployment-validation-service.js +48 -45
  81. package/lib/deployment/deployment-validation-service.js.map +1 -1
  82. package/lib/deployment/resume-deployment-service.d.ts +14 -0
  83. package/lib/deployment/resume-deployment-service.js +25 -0
  84. package/lib/deployment/resume-deployment-service.js.map +1 -0
  85. package/lib/deployment/start-execution-service.d.ts +44 -0
  86. package/lib/deployment/start-execution-service.js +94 -0
  87. package/lib/deployment/start-execution-service.js.map +1 -0
  88. package/lib/deployment/test-executor.d.ts +60 -1
  89. package/lib/deployment/test-executor.js +180 -12
  90. package/lib/deployment/test-executor.js.map +1 -1
  91. package/lib/deployment/test-plan-service.d.ts +8 -0
  92. package/lib/deployment/test-plan-service.js +26 -0
  93. package/lib/deployment/test-plan-service.js.map +1 -0
  94. package/lib/deployment/wave-manifest-service.d.ts +11 -0
  95. package/lib/deployment/wave-manifest-service.js +41 -0
  96. package/lib/deployment/wave-manifest-service.js.map +1 -0
  97. package/lib/parsers/apex-class-parser.js +180 -98
  98. package/lib/parsers/apex-class-parser.js.map +1 -1
  99. package/lib/parsers/apex-trigger-parser.js +56 -7
  100. package/lib/parsers/apex-trigger-parser.js.map +1 -1
  101. package/lib/parsers/aura-parser.js +2 -2
  102. package/lib/parsers/aura-parser.js.map +1 -1
  103. package/lib/parsers/bot-parser.js +31 -4
  104. package/lib/parsers/bot-parser.js.map +1 -1
  105. package/lib/parsers/custom-metadata-parser.js +37 -3
  106. package/lib/parsers/custom-metadata-parser.js.map +1 -1
  107. package/lib/parsers/custom-object-parser.js +284 -150
  108. package/lib/parsers/custom-object-parser.js.map +1 -1
  109. package/lib/parsers/email-template-parser.js +115 -58
  110. package/lib/parsers/email-template-parser.js.map +1 -1
  111. package/lib/parsers/flexipage-parser.js +41 -16
  112. package/lib/parsers/flexipage-parser.js.map +1 -1
  113. package/lib/parsers/flow-parser.js +73 -63
  114. package/lib/parsers/flow-parser.js.map +1 -1
  115. package/lib/parsers/genai-prompt-parser.js +16 -4
  116. package/lib/parsers/genai-prompt-parser.js.map +1 -1
  117. package/lib/parsers/layout-parser.d.ts +11 -0
  118. package/lib/parsers/layout-parser.js +143 -68
  119. package/lib/parsers/layout-parser.js.map +1 -1
  120. package/lib/parsers/lwc-parser.d.ts +0 -9
  121. package/lib/parsers/lwc-parser.js +245 -100
  122. package/lib/parsers/lwc-parser.js.map +1 -1
  123. package/lib/parsers/parser-utils.d.ts +3 -0
  124. package/lib/parsers/parser-utils.js +16 -0
  125. package/lib/parsers/parser-utils.js.map +1 -0
  126. package/lib/parsers/permission-set-parser.d.ts +6 -0
  127. package/lib/parsers/permission-set-parser.js +31 -40
  128. package/lib/parsers/permission-set-parser.js.map +1 -1
  129. package/lib/parsers/profile-parser.d.ts +13 -0
  130. package/lib/parsers/profile-parser.js +39 -34
  131. package/lib/parsers/profile-parser.js.map +1 -1
  132. package/lib/parsers/visualforce-parser.js +2 -1
  133. package/lib/parsers/visualforce-parser.js.map +1 -1
  134. package/lib/presentation/analyze-command-presenter.d.ts +10 -0
  135. package/lib/presentation/analyze-command-presenter.js +30 -0
  136. package/lib/presentation/analyze-command-presenter.js.map +1 -0
  137. package/lib/presentation/config-command-presenter.d.ts +15 -0
  138. package/lib/presentation/config-command-presenter.js +50 -0
  139. package/lib/presentation/config-command-presenter.js.map +1 -0
  140. package/lib/presentation/project-analysis-presenter.d.ts +9 -0
  141. package/lib/presentation/project-analysis-presenter.js +9 -0
  142. package/lib/presentation/project-analysis-presenter.js.map +1 -0
  143. package/lib/presentation/resume-command-presenter.d.ts +7 -0
  144. package/lib/presentation/resume-command-presenter.js +12 -0
  145. package/lib/presentation/resume-command-presenter.js.map +1 -0
  146. package/lib/presentation/start-command-presenter.d.ts +15 -0
  147. package/lib/presentation/start-command-presenter.js +29 -0
  148. package/lib/presentation/start-command-presenter.js.map +1 -0
  149. package/lib/presentation/status-command-presenter.d.ts +7 -0
  150. package/lib/presentation/status-command-presenter.js +16 -0
  151. package/lib/presentation/status-command-presenter.js.map +1 -0
  152. package/lib/presentation/validate-command-presenter.d.ts +9 -0
  153. package/lib/presentation/validate-command-presenter.js +50 -0
  154. package/lib/presentation/validate-command-presenter.js.map +1 -0
  155. package/lib/services/metadata-scanner-service.d.ts +8 -12
  156. package/lib/services/metadata-scanner-service.js +55 -533
  157. package/lib/services/metadata-scanner-service.js.map +1 -1
  158. package/lib/services/scanners/automation-ai-metadata-scanner.d.ts +4 -0
  159. package/lib/services/scanners/automation-ai-metadata-scanner.js +61 -0
  160. package/lib/services/scanners/automation-ai-metadata-scanner.js.map +1 -0
  161. package/lib/services/scanners/code-metadata-scanner.d.ts +23 -0
  162. package/lib/services/scanners/code-metadata-scanner.js +149 -0
  163. package/lib/services/scanners/code-metadata-scanner.js.map +1 -0
  164. package/lib/services/scanners/data-metadata-scanner.d.ts +3 -0
  165. package/lib/services/scanners/data-metadata-scanner.js +82 -0
  166. package/lib/services/scanners/data-metadata-scanner.js.map +1 -0
  167. package/lib/services/scanners/experience-metadata-scanner.d.ts +5 -0
  168. package/lib/services/scanners/experience-metadata-scanner.js +123 -0
  169. package/lib/services/scanners/experience-metadata-scanner.js.map +1 -0
  170. package/lib/services/scanners/scanner-runtime.d.ts +11 -0
  171. package/lib/services/scanners/scanner-runtime.js +115 -0
  172. package/lib/services/scanners/scanner-runtime.js.map +1 -0
  173. package/lib/services/scanners/security-metadata-scanner.d.ts +3 -0
  174. package/lib/services/scanners/security-metadata-scanner.js +71 -0
  175. package/lib/services/scanners/security-metadata-scanner.js.map +1 -0
  176. package/lib/types/dependency.d.ts +11 -1
  177. package/lib/types/metadata.d.ts +17 -0
  178. package/lib/types/salesforce/flow/actions.d.ts +108 -0
  179. package/lib/types/salesforce/flow/actions.js +2 -0
  180. package/lib/types/salesforce/flow/actions.js.map +1 -0
  181. package/lib/types/salesforce/flow/common.d.ts +36 -0
  182. package/lib/types/salesforce/flow/common.js +5 -0
  183. package/lib/types/salesforce/flow/common.js.map +1 -0
  184. package/lib/types/salesforce/flow/metadata.d.ts +37 -0
  185. package/lib/types/salesforce/flow/metadata.js +2 -0
  186. package/lib/types/salesforce/flow/metadata.js.map +1 -0
  187. package/lib/types/salesforce/flow/records.d.ts +38 -0
  188. package/lib/types/salesforce/flow/records.js +2 -0
  189. package/lib/types/salesforce/flow/records.js.map +1 -0
  190. package/lib/types/salesforce/flow/resources.d.ts +44 -0
  191. package/lib/types/salesforce/flow/resources.js +2 -0
  192. package/lib/types/salesforce/flow/resources.js.map +1 -0
  193. package/lib/types/salesforce/flow/screens.d.ts +56 -0
  194. package/lib/types/salesforce/flow/screens.js +2 -0
  195. package/lib/types/salesforce/flow/screens.js.map +1 -0
  196. package/lib/types/salesforce/flow/start.d.ts +24 -0
  197. package/lib/types/salesforce/flow/start.js +2 -0
  198. package/lib/types/salesforce/flow/start.js.map +1 -0
  199. package/lib/types/salesforce/flow.d.ts +9 -514
  200. package/lib/types/salesforce/flow.js +1 -1
  201. package/lib/types/salesforce/object/core.d.ts +68 -0
  202. package/lib/types/salesforce/object/core.js +5 -0
  203. package/lib/types/salesforce/object/core.js.map +1 -0
  204. package/lib/types/salesforce/object/field.d.ts +155 -0
  205. package/lib/types/salesforce/object/field.js +5 -0
  206. package/lib/types/salesforce/object/field.js.map +1 -0
  207. package/lib/types/salesforce/object/filter.d.ts +16 -0
  208. package/lib/types/salesforce/object/filter.js +5 -0
  209. package/lib/types/salesforce/object/filter.js.map +1 -0
  210. package/lib/types/salesforce/object/policy.d.ts +27 -0
  211. package/lib/types/salesforce/object/policy.js +5 -0
  212. package/lib/types/salesforce/object/policy.js.map +1 -0
  213. package/lib/types/salesforce/object/record.d.ts +44 -0
  214. package/lib/types/salesforce/object/record.js +5 -0
  215. package/lib/types/salesforce/object/record.js.map +1 -0
  216. package/lib/types/salesforce/object/view.d.ts +71 -0
  217. package/lib/types/salesforce/object/view.js +5 -0
  218. package/lib/types/salesforce/object/view.js.map +1 -0
  219. package/lib/types/salesforce/object/weblink.d.ts +70 -0
  220. package/lib/types/salesforce/object/weblink.js +5 -0
  221. package/lib/types/salesforce/object/weblink.js.map +1 -0
  222. package/lib/types/salesforce/object.d.ts +7 -423
  223. package/lib/types/salesforce/object.js +7 -1
  224. package/lib/types/salesforce/object.js.map +1 -1
  225. package/lib/utils/cache-manager.d.ts +16 -15
  226. package/lib/utils/cache-manager.js +290 -188
  227. package/lib/utils/cache-manager.js.map +1 -1
  228. package/lib/utils/logger.d.ts +8 -0
  229. package/lib/utils/logger.js +82 -69
  230. package/lib/utils/logger.js.map +1 -1
  231. package/lib/validators/xml/xml-reference-rules.d.ts +3 -0
  232. package/lib/validators/xml/xml-reference-rules.js +173 -0
  233. package/lib/validators/xml/xml-reference-rules.js.map +1 -0
  234. package/lib/validators/xml/xml-report-formatter.d.ts +2 -0
  235. package/lib/validators/xml/xml-report-formatter.js +47 -0
  236. package/lib/validators/xml/xml-report-formatter.js.map +1 -0
  237. package/lib/validators/xml/xml-schema-rules.d.ts +3 -0
  238. package/lib/validators/xml/xml-schema-rules.js +75 -0
  239. package/lib/validators/xml/xml-schema-rules.js.map +1 -0
  240. package/lib/validators/xml/xml-validation-types.d.ts +25 -0
  241. package/lib/validators/xml/xml-validation-types.js +2 -0
  242. package/lib/validators/xml/xml-validation-types.js.map +1 -0
  243. package/lib/validators/xml-metadata-validator.d.ts +2 -63
  244. package/lib/validators/xml-metadata-validator.js +7 -338
  245. package/lib/validators/xml-metadata-validator.js.map +1 -1
  246. package/lib/waves/test-optimizer.d.ts +13 -0
  247. package/lib/waves/test-optimizer.js +124 -63
  248. package/lib/waves/test-optimizer.js.map +1 -1
  249. package/lib/waves/wave-builder.d.ts +17 -21
  250. package/lib/waves/wave-builder.js +199 -135
  251. package/lib/waves/wave-builder.js.map +1 -1
  252. package/lib/waves/wave-splitter.d.ts +12 -8
  253. package/lib/waves/wave-splitter.js +156 -115
  254. package/lib/waves/wave-splitter.js.map +1 -1
  255. package/npm-shrinkwrap.json +959 -422
  256. package/oclif.lock +294 -57
  257. package/oclif.manifest.json +1 -1
  258. package/package.json +21 -9
@@ -15,26 +15,20 @@
15
15
  *
16
16
  * @issue #46
17
17
  */
18
- import { mkdir, readFile, writeFile } from 'node:fs/promises';
19
- import * as path from 'node:path';
20
18
  import { Messages } from '@salesforce/core';
21
19
  import { Flags, SfCommand, optionalOrgFlagWithDeprecations } from '@salesforce/sf-plugins-core';
22
20
  import { getLogger } from '../utils/logger.js';
23
- import { CycleRemediationPlanner } from '../dependencies/cycle-remediation-planner.js';
24
- import { CycleSourceEditor, } from '../deployment/cycle-source-editor.js';
25
- import { SfCliIntegration } from '../deployment/sf-cli-integration.js';
26
- import { MetadataScannerService } from '../services/metadata-scanner-service.js';
27
- import { WaveBuilder } from '../waves/wave-builder.js';
28
- import { AIEnhancedPriorityWaveGenerator } from '../waves/priority-wave-generator-ai.js';
29
- import { getWavesInExecutionOrder } from '../waves/wave-executor.js';
30
- import { StateManager } from '../deployment/state-manager.js';
31
- import { DeploymentTracker } from '../deployment/deployment-tracker.js';
32
- import { AgentforcePriorityService } from '../ai/agentforce-priority-service.js';
33
- import { DependencyInferenceService } from '../ai/dependency-inference-service.js';
34
- import { DependencyGraphBuilder } from '../dependencies/dependency-graph-builder.js';
21
+ import { StartExecutionService } from '../deployment/start-execution-service.js';
22
+ import { DeploymentContextService } from '../deployment/deployment-context-service.js';
23
+ import { ProjectAnalysisPresenter } from '../presentation/project-analysis-presenter.js';
24
+ import { StartCommandPresenter } from '../presentation/start-command-presenter.js';
35
25
  Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
36
26
  const messages = Messages.loadMessages('@jterrats/smart-deployment', 'start');
37
27
  const logger = getLogger('StartCommand');
28
+ const deploymentContextService = new DeploymentContextService();
29
+ const startExecutionService = new StartExecutionService();
30
+ const projectAnalysisPresenter = new ProjectAnalysisPresenter();
31
+ const presenter = new StartCommandPresenter();
38
32
  export default class Start extends SfCommand {
39
33
  static summary = messages.getMessage('summary');
40
34
  static description = messages.getMessage('description');
@@ -97,517 +91,47 @@ export default class Start extends SfCommand {
97
91
  const sourcePath = typeof flags['source-path'] === 'string' ? flags['source-path'] : undefined;
98
92
  try {
99
93
  logger.info('Starting smart deployment', { flags });
100
- // AC-1: Analyze metadata
101
94
  this.log('📊 Analyzing metadata...');
102
- const metadataCount = await this.analyzeMetadata(sourcePath);
103
- this.log(`✅ Found ${metadataCount} metadata components`);
104
- // AC-2: Generate waves
105
- this.log('🌊 Generating deployment waves...');
106
- const deploymentContext = await this.buildDeploymentContext(flags, sourcePath);
107
- const waves = deploymentContext.orderedWaves.length;
108
- this.log(`✅ Generated ${waves} waves`);
109
- // AC US-057-AC-6: Report AI decisions
110
- if (flags['use-ai']) {
111
- this.log('🤖 AI-enhanced prioritization enabled');
112
- }
113
- // AC-3: Execute deployment
114
- if (!flags['dry-run']) {
115
- this.log('🚀 Executing deployment...');
116
- await this.executeDeployment(flags, sourcePath);
117
- }
118
- else {
119
- this.log('🔍 Dry-run mode: skipping actual deployment');
120
- }
121
- // AC-9: Generate report
122
- this.log('📄 Generating deployment report...');
123
- this.generateReport(waves);
124
- return { success: true, waves, ai: deploymentContext.aiContext };
125
- }
126
- catch (error) {
127
- // AC-10: Handle failures gracefully
128
- logger.error('Deployment failed', { error });
129
- this.error(`Deployment failed: ${error instanceof Error ? error.message : String(error)}`);
130
- }
131
- }
132
- async analyzeMetadata(sourcePath) {
133
- const scanner = new MetadataScannerService();
134
- const result = await scanner.scan({ sourcePath });
135
- if (result.errors.length > 0) {
136
- logger.error('Metadata scanning completed with errors', { errors: result.errors });
137
- result.errors.forEach((err) => this.warn(err));
138
- }
139
- if (result.warnings.length > 0) {
140
- logger.warn('Metadata scanning completed with warnings', { warnings: result.warnings });
141
- result.warnings.forEach((warn) => this.warn(warn));
142
- }
143
- return result.components.length;
144
- }
145
- async executeDeployment(flags, sourcePath) {
146
- const dryRun = flags['dry-run'];
147
- const validateOnly = flags['validate-only'];
148
- const allowCycleRemediation = flags['allow-cycle-remediation'];
149
- const skipTests = flags['skip-tests'];
150
- const targetOrg = this.getTargetOrgIdentifier(flags['target-org']);
151
- if (dryRun || validateOnly) {
152
- this.log('🔍 Dry-run/Validate mode: skipping actual deployment');
153
- return;
154
- }
155
- const deploymentContext = await this.buildDeploymentContext(flags, sourcePath);
156
- const { scanResult, orderedWaves } = deploymentContext;
157
- const planner = new CycleRemediationPlanner(scanResult.dependencyResult.graph, {
158
- components: scanResult.dependencyResult.components,
159
- });
160
- const remediationPlan = planner.createPlan();
161
- if (remediationPlan.cycles.length > 0) {
162
- this.log(`♻️ Detected ${remediationPlan.cycles.length} circular dependency cycle(s).`);
163
- if (!allowCycleRemediation) {
164
- this.error('Circular dependencies detected. Re-run with --allow-cycle-remediation for supported ApexClass cycles or resolve them manually.');
165
- }
166
- if (!remediationPlan.supported) {
167
- this.error([
168
- 'Cycle remediation was requested, but one or more cycles are not safely supported.',
169
- ...remediationPlan.warnings,
170
- ].join('\n'));
171
- }
172
- }
173
- // Initialize deployment services
174
- const sfCli = new SfCliIntegration();
175
- const stateManager = new StateManager({ baseDir: sourcePath ?? process.cwd() });
176
- const tracker = new DeploymentTracker();
177
- const deploymentId = `deployment-${Date.now()}`;
178
- if (remediationPlan.cycles.length > 0) {
179
- if (!targetOrg) {
180
- this.error('The --target-org flag is required for cycle remediation deployments.');
181
- }
182
- await this.executeCycleRemediationDeployment({
183
- deploymentId,
184
- targetOrg,
95
+ const deploymentContext = await deploymentContextService.buildContext({
185
96
  sourcePath,
186
- stateManager,
187
- tracker,
188
- plan: remediationPlan,
189
- sfCli,
190
- skipTests,
191
- componentMap: scanResult.dependencyResult.components,
192
- });
193
- return;
194
- }
195
- if (!targetOrg) {
196
- this.error('The --target-org flag is required for real deployments.');
197
- }
198
- // Execute waves sequentially
199
- await this.forEachSequentially(orderedWaves, async (wave) => {
200
- this.log(`\n🌊 Deploying Wave ${wave.number}/${orderedWaves.length} (${wave.components.length} components)...`);
201
- try {
202
- // Generate manifest for this wave
203
- const manifestPath = await this.generateWaveManifest({
204
- baseDir: sourcePath ?? process.cwd(),
205
- waveNumber: wave.number,
206
- components: wave.components,
207
- componentMap: scanResult.dependencyResult.components,
208
- });
209
- // Execute deployment
210
- tracker.startTracking(deploymentId, wave.number, orderedWaves.length);
211
- const result = await sfCli.deploy({
212
- manifestPath,
213
- targetOrg,
214
- testLevel: this.resolveTestLevel(skipTests),
215
- });
216
- tracker.updateProgress(deploymentId, result);
217
- if (!result.success) {
218
- const aiMetadata = deploymentContext.aiContext === undefined
219
- ? {}
220
- : {
221
- aiProvider: deploymentContext.aiContext.provider,
222
- aiModel: deploymentContext.aiContext.model,
223
- aiFallback: deploymentContext.aiContext.fallback,
224
- aiAdjustments: deploymentContext.aiContext.aiAdjustments,
225
- aiUnknownTypes: deploymentContext.aiContext.unknownTypes,
226
- aiInferenceFallback: deploymentContext.aiContext.inferenceFallback,
227
- aiInferredDependencies: deploymentContext.aiContext.inferredDependencies,
228
- };
229
- await stateManager.saveState({
230
- deploymentId,
231
- targetOrg,
232
- timestamp: new Date().toISOString(),
233
- totalWaves: orderedWaves.length,
234
- completedWaves: Array.from({ length: Math.max(0, wave.number - 1) }, (_, i) => i + 1),
235
- currentWave: wave.number,
236
- failedWave: {
237
- waveNumber: wave.number,
238
- error: result.output,
239
- timestamp: new Date().toISOString(),
240
- },
241
- metadata: {
242
- lastKnownStatus: result.status,
243
- testsRun: result.testsRun,
244
- testFailures: result.testFailures,
245
- testLevel: this.resolveTestLevel(skipTests),
246
- ...aiMetadata,
247
- },
248
- });
249
- this.error(`Wave ${wave.number} failed: ${result.output}`);
250
- }
251
- // Save state after each wave
252
- const aiMetadata = deploymentContext.aiContext === undefined
253
- ? {}
254
- : {
255
- aiProvider: deploymentContext.aiContext.provider,
256
- aiModel: deploymentContext.aiContext.model,
257
- aiFallback: deploymentContext.aiContext.fallback,
258
- aiAdjustments: deploymentContext.aiContext.aiAdjustments,
259
- aiUnknownTypes: deploymentContext.aiContext.unknownTypes,
260
- aiInferenceFallback: deploymentContext.aiContext.inferenceFallback,
261
- aiInferredDependencies: deploymentContext.aiContext.inferredDependencies,
262
- };
263
- await stateManager.saveState({
264
- deploymentId,
265
- targetOrg,
266
- timestamp: new Date().toISOString(),
267
- totalWaves: orderedWaves.length,
268
- completedWaves: Array.from({ length: wave.number }, (_, i) => i + 1),
269
- currentWave: wave.number,
270
- metadata: {
271
- lastKnownStatus: result.status,
272
- testsRun: result.testsRun,
273
- testFailures: result.testFailures,
274
- testLevel: this.resolveTestLevel(skipTests),
275
- ...aiMetadata,
276
- },
277
- });
278
- this.log(`✅ Wave ${wave.number} deployed successfully`);
279
- }
280
- catch (error) {
281
- logger.error('Wave deployment failed', { wave: wave.number, error });
282
- this.error(`Wave ${wave.number} failed: ${error instanceof Error ? error.message : String(error)}`);
283
- }
284
- });
285
- // Clear state on success
286
- await stateManager.clearState();
287
- this.log('\n✅ All waves deployed successfully!');
288
- }
289
- async buildDeploymentContext(flags, sourcePath) {
290
- const scanner = new MetadataScannerService();
291
- const scanResult = await scanner.scan({ sourcePath });
292
- if (scanResult.errors.length > 0) {
293
- logger.error('Metadata scanning completed with errors', { errors: scanResult.errors });
294
- scanResult.errors.forEach((err) => this.warn(err));
295
- }
296
- if (scanResult.warnings.length > 0) {
297
- logger.warn('Metadata scanning completed with warnings', { warnings: scanResult.warnings });
298
- scanResult.warnings.forEach((warn) => this.warn(warn));
299
- }
300
- let dependencyResult = scanResult.dependencyResult;
301
- let inferredDependencies = 0;
302
- let inferenceFallback = false;
303
- if (flags['use-ai']) {
304
- const inferenceService = new DependencyInferenceService({
305
- baseDir: sourcePath ?? process.cwd(),
306
- });
307
- const inferenceResult = await inferenceService.inferDependencies(scanResult.components, scanResult.dependencyResult.graph);
308
- inferredDependencies = inferenceResult.highConfidenceCount;
309
- inferenceFallback = inferenceResult.fallbackToStatic;
310
- if (inferenceResult.dependencies.length > 0) {
311
- const enrichedComponents = this.applyInferredDependencies(scanResult.components, inferenceResult.dependencies);
312
- const graphBuilder = new DependencyGraphBuilder();
313
- graphBuilder.addComponents(enrichedComponents);
314
- scanResult.components = enrichedComponents;
315
- dependencyResult = graphBuilder.build();
316
- scanResult.dependencyResult = dependencyResult;
317
- }
318
- }
319
- const waveBuilder = new WaveBuilder();
320
- const waveResult = waveBuilder.generateWaves(dependencyResult.graph);
321
- let orderedWaves = getWavesInExecutionOrder(waveResult);
322
- let aiContext;
323
- if (flags['use-ai']) {
324
- this.log(' 🤖 Using AI priority weighting for wave ordering...');
325
- const priorityService = new AgentforcePriorityService({
326
- baseDir: sourcePath ?? process.cwd(),
327
- });
328
- const aiWaveGenerator = new AIEnhancedPriorityWaveGenerator({
329
- agentforceService: priorityService,
97
+ useAI: Boolean(flags['use-ai']),
330
98
  orgType: typeof flags['org-type'] === 'string' ? flags['org-type'] : undefined,
331
99
  industry: typeof flags.industry === 'string' ? flags.industry : undefined,
332
100
  });
333
- orderedWaves = await aiWaveGenerator.applyPriorityWavesAsync(orderedWaves, scanResult.dependencyResult.components);
334
- const unknownTypesWarning = aiWaveGenerator.getUnknownTypesWarning();
335
- if (unknownTypesWarning) {
336
- this.warn(unknownTypesWarning);
337
- }
338
- const aiReport = aiWaveGenerator.getAIReport();
339
- if (aiReport) {
340
- this.log(aiReport);
341
- }
342
- const aiStats = aiWaveGenerator.getAIStats();
343
- const providerConfig = priorityService.getProviderConfig();
344
- aiContext = {
345
- enabled: true,
346
- provider: providerConfig.provider,
347
- model: providerConfig.model,
348
- aiAdjustments: aiStats?.aiAdjustments ?? 0,
349
- unknownTypes: aiStats?.unknownTypes ?? [],
350
- fallback: aiStats?.usedFallback ?? false,
351
- inferredDependencies,
352
- inferenceFallback,
353
- };
354
- }
355
- return { scanResult, orderedWaves, aiContext };
356
- }
357
- generateReport(waves) {
358
- this.log('\n📊 Deployment Report:');
359
- this.log(` - Waves: ${waves}`);
360
- this.log(' - Status: Success');
361
- }
362
- async forEachSequentially(items, callback) {
363
- let chain = Promise.resolve();
364
- items.forEach((item, index) => {
365
- chain = chain.then(async () => callback(item, index));
366
- });
367
- await chain;
368
- }
369
- async executeCycleRemediationDeployment(params) {
370
- const { deploymentId, targetOrg, sourcePath, stateManager, tracker, plan, sfCli, skipTests, componentMap } = params;
371
- const editor = new CycleSourceEditor();
372
- const startedAt = new Date().toISOString();
373
- const editRecords = [];
374
- const cycleId = plan.cycles.map((cycle) => cycle.id).join('||');
375
- const phaseOneComponents = [
376
- ...new Set(plan.cycles.flatMap((cycle) => cycle.deployPhases.find((phase) => phase.phase === 1)?.components ?? [])),
377
- ];
378
- const phaseTwoComponents = [
379
- ...new Set(plan.cycles.flatMap((cycle) => cycle.deployPhases.find((phase) => phase.phase === 2)?.components ?? [])),
380
- ];
381
- let editsRestored = false;
382
- let failureStateSaved = false;
383
- try {
384
- this.log('🩹 Applying conservative cycle remediation edits...');
385
- await this.forEachSequentially(plan.cycles, async (cycle) => {
386
- await this.forEachSequentially(cycle.edits, async (edit) => {
387
- const request = await this.createCycleEditRequest(edit);
388
- editRecords.push(await editor.applyEdit(request));
389
- });
390
- });
391
- await stateManager.saveState({
392
- deploymentId,
393
- targetOrg,
394
- timestamp: startedAt,
395
- totalWaves: 2,
396
- completedWaves: [],
397
- currentWave: 1,
398
- cycleRemediation: {
399
- cycleId,
400
- strategy: 'comment-reference',
401
- activePhase: 1,
402
- startedAt,
403
- completedPhases: [],
404
- editRecords,
405
- },
406
- });
407
- tracker.startTracking(deploymentId, 1, 2);
408
- this.log('♻️ Phase 1/2: deploying temporarily cycle-broken metadata...');
409
- const phaseOneManifestPath = await this.generateWaveManifest({
410
- baseDir: sourcePath ?? process.cwd(),
411
- waveNumber: 1,
412
- components: phaseOneComponents,
413
- componentMap,
414
- });
415
- const phaseOneResult = await sfCli.deploy({
416
- manifestPath: phaseOneManifestPath,
417
- targetOrg,
418
- testLevel: this.resolveTestLevel(skipTests),
101
+ projectAnalysisPresenter.reportDiagnostics(this, deploymentContext.scanResult, deploymentContext.messages);
102
+ const metadataCount = deploymentContext.scanResult.components.length;
103
+ const waves = deploymentContext.orderedWaves.length;
104
+ presenter.reportAnalysisSummary(this, {
105
+ metadataCount,
106
+ waves,
107
+ aiEnabled: Boolean(flags['use-ai']),
419
108
  });
420
- if (!phaseOneResult.success) {
421
- await stateManager.saveState({
422
- deploymentId,
423
- targetOrg,
424
- timestamp: new Date().toISOString(),
425
- totalWaves: 2,
426
- completedWaves: [],
427
- currentWave: 1,
428
- failedWave: {
429
- waveNumber: 1,
430
- error: phaseOneResult.output,
431
- timestamp: new Date().toISOString(),
432
- },
433
- cycleRemediation: {
434
- cycleId,
435
- strategy: 'comment-reference',
436
- activePhase: 1,
437
- startedAt,
438
- completedPhases: [],
439
- editRecords,
440
- },
441
- metadata: {
442
- lastKnownStatus: phaseOneResult.status,
443
- testsRun: phaseOneResult.testsRun,
444
- testFailures: phaseOneResult.testFailures,
445
- testLevel: this.resolveTestLevel(skipTests),
446
- },
447
- });
448
- failureStateSaved = true;
449
- throw new Error(`Cycle remediation phase 1 failed: ${phaseOneResult.output}`);
109
+ const executionOptions = {
110
+ dryRun: flags['dry-run'] === true,
111
+ validateOnly: flags['validate-only'] === true,
112
+ allowCycleRemediation: flags['allow-cycle-remediation'] === true,
113
+ skipTests: flags['skip-tests'] === true,
114
+ targetOrg: this.getTargetOrgIdentifier(flags['target-org']),
115
+ sourcePath,
116
+ deploymentContext,
117
+ log: this.log.bind(this),
118
+ };
119
+ if (!executionOptions.dryRun && !executionOptions.validateOnly) {
120
+ presenter.reportExecutionStart(this);
450
121
  }
451
- await stateManager.saveState({
452
- deploymentId,
453
- targetOrg,
454
- timestamp: new Date().toISOString(),
455
- totalWaves: 2,
456
- completedWaves: [1],
457
- currentWave: 2,
458
- cycleRemediation: {
459
- cycleId,
460
- strategy: 'comment-reference',
461
- activePhase: 2,
462
- startedAt,
463
- completedPhases: [1],
464
- editRecords,
465
- },
466
- metadata: {
467
- lastKnownStatus: phaseOneResult.status,
468
- testsRun: phaseOneResult.testsRun,
469
- testFailures: phaseOneResult.testFailures,
470
- testLevel: this.resolveTestLevel(skipTests),
471
- },
472
- });
473
- this.log('♻️ Restoring original references before phase 2...');
474
- await this.restoreCycleEdits(editor, editRecords);
475
- editsRestored = true;
476
- tracker.startTracking(deploymentId, 2, 2);
477
- this.log('♻️ Phase 2/2: redeploying restored metadata...');
478
- const phaseTwoManifestPath = await this.generateWaveManifest({
479
- baseDir: sourcePath ?? process.cwd(),
480
- waveNumber: 2,
481
- components: phaseTwoComponents,
482
- componentMap,
483
- });
484
- const phaseTwoResult = await sfCli.deploy({
485
- manifestPath: phaseTwoManifestPath,
486
- targetOrg,
487
- testLevel: this.resolveTestLevel(skipTests),
488
- });
489
- if (!phaseTwoResult.success) {
490
- await stateManager.saveState({
491
- deploymentId,
492
- targetOrg,
493
- timestamp: new Date().toISOString(),
494
- totalWaves: 2,
495
- completedWaves: [1],
496
- currentWave: 2,
497
- failedWave: {
498
- waveNumber: 2,
499
- error: phaseTwoResult.output,
500
- timestamp: new Date().toISOString(),
501
- },
502
- cycleRemediation: {
503
- cycleId,
504
- strategy: 'comment-reference',
505
- activePhase: 2,
506
- startedAt,
507
- completedPhases: [1],
508
- editRecords,
509
- },
510
- metadata: {
511
- lastKnownStatus: phaseTwoResult.status,
512
- testsRun: phaseTwoResult.testsRun,
513
- testFailures: phaseTwoResult.testFailures,
514
- testLevel: this.resolveTestLevel(skipTests),
515
- },
516
- });
517
- failureStateSaved = true;
518
- throw new Error(`Cycle remediation phase 2 failed: ${phaseTwoResult.output}`);
122
+ const executionResult = await startExecutionService.execute(executionOptions);
123
+ if (executionResult.kind === 'skipped') {
124
+ presenter.reportExecutionSkipped(this, executionResult.reason);
519
125
  }
520
- await stateManager.clearState();
521
- this.log('\n✅ Cycle remediation deployment completed successfully!');
126
+ presenter.reportReportGenerationStart(this);
127
+ presenter.reportDeploymentReport(this, waves);
128
+ return { success: true, waves, ai: deploymentContext.aiContext };
522
129
  }
523
130
  catch (error) {
524
- if (!editsRestored) {
525
- await this.restoreCycleEdits(editor, editRecords, true);
526
- }
527
- if (!failureStateSaved) {
528
- await stateManager.saveState({
529
- deploymentId,
530
- targetOrg,
531
- timestamp: new Date().toISOString(),
532
- totalWaves: 2,
533
- completedWaves: [],
534
- currentWave: 1,
535
- failedWave: {
536
- waveNumber: 1,
537
- error: error instanceof Error ? error.message : String(error),
538
- timestamp: new Date().toISOString(),
539
- },
540
- cycleRemediation: {
541
- cycleId,
542
- strategy: 'comment-reference',
543
- activePhase: 1,
544
- startedAt,
545
- completedPhases: [],
546
- editRecords,
547
- },
548
- });
549
- }
550
- throw error;
551
- }
552
- }
553
- async createCycleEditRequest(edit) {
554
- if (edit.filePath === undefined) {
555
- throw new Error(`Cycle remediation edit for ${edit.nodeId} is missing a file path.`);
556
- }
557
- const content = await readFile(edit.filePath, 'utf8');
558
- const dependencyName = edit.targetDependency.includes(':')
559
- ? edit.targetDependency.split(':').pop() ?? edit.targetDependency
560
- : edit.targetDependency;
561
- const candidateLines = content.split('\n').filter((line) => {
562
- const trimmed = line.trim();
563
- return trimmed.length > 0 && !trimmed.startsWith('//') && line.includes(dependencyName);
564
- });
565
- if (candidateLines.length !== 1) {
566
- throw new Error(`Cycle remediation for ${edit.nodeId} requires exactly one candidate source line containing ${dependencyName}; found ${candidateLines.length}.`);
567
- }
568
- return {
569
- filePath: edit.filePath,
570
- targetDescription: edit.targetDescription,
571
- targetDependency: edit.targetDependency,
572
- sourceSnippet: candidateLines[0],
573
- };
574
- }
575
- applyInferredDependencies(components, inferredDependencies) {
576
- const clonedComponents = components.map((component) => ({
577
- ...component,
578
- dependencies: new Set(component.dependencies),
579
- dependents: new Set(component.dependents),
580
- }));
581
- const nodeIdsByName = new Map();
582
- for (const component of clonedComponents) {
583
- nodeIdsByName.set(component.name, `${component.type}:${component.name}`);
584
- }
585
- const componentsByNodeId = new Map();
586
- for (const component of clonedComponents) {
587
- const nodeId = `${component.type}:${component.name}`;
588
- componentsByNodeId.set(nodeId, component);
589
- }
590
- for (const inferred of inferredDependencies) {
591
- const fromNodeId = nodeIdsByName.get(inferred.from);
592
- const toNodeId = nodeIdsByName.get(inferred.to);
593
- if (!fromNodeId || !toNodeId) {
594
- continue;
595
- }
596
- componentsByNodeId.get(fromNodeId)?.dependencies.add(toNodeId);
597
- componentsByNodeId.get(toNodeId)?.dependents.add(fromNodeId);
131
+ // AC-10: Handle failures gracefully
132
+ logger.error('Deployment failed', { error });
133
+ this.error(`Deployment failed: ${error instanceof Error ? error.message : String(error)}`);
598
134
  }
599
- return clonedComponents;
600
- }
601
- async restoreCycleEdits(editor, editRecords, bestEffort = false) {
602
- await this.forEachSequentially([...editRecords].reverse(), async (record) => {
603
- const result = await editor.restoreEdit(record);
604
- if (!result.restored && result.reason !== 'backup-missing' && !bestEffort) {
605
- throw new Error(`Failed to restore cycle remediation edit for ${record.filePath}: ${result.reason ?? 'unknown'}.`);
606
- }
607
- });
608
- }
609
- resolveTestLevel(skipTests) {
610
- return skipTests ? 'NoTestRun' : 'RunLocalTests';
611
135
  }
612
136
  getTargetOrgIdentifier(value) {
613
137
  if (typeof value === 'string' && value.length > 0) {
@@ -619,41 +143,5 @@ export default class Start extends SfCommand {
619
143
  }
620
144
  return undefined;
621
145
  }
622
- async generateWaveManifest(params) {
623
- const manifestDir = path.join(params.baseDir, '.smart-deployment', 'manifests');
624
- await mkdir(manifestDir, { recursive: true });
625
- const grouped = new Map();
626
- for (const nodeId of params.components) {
627
- const component = params.componentMap.get(nodeId);
628
- if (!component) {
629
- continue;
630
- }
631
- if (!grouped.has(component.type)) {
632
- grouped.set(component.type, new Set());
633
- }
634
- grouped.get(component.type).add(component.name);
635
- }
636
- const typeBlocks = [...grouped.entries()]
637
- .sort((a, b) => a[0].localeCompare(b[0]))
638
- .map(([type, members]) => {
639
- const memberLines = [...members]
640
- .sort((left, right) => left.localeCompare(right))
641
- .map((member) => ` <members>${member}</members>`)
642
- .join('\n');
643
- return [' <types>', memberLines, ` <name>${type}</name>`, ' </types>'].join('\n');
644
- })
645
- .join('\n');
646
- const content = [
647
- '<?xml version="1.0" encoding="UTF-8"?>',
648
- '<Package xmlns="http://soap.sforce.com/2006/04/metadata">',
649
- typeBlocks,
650
- ' <version>61.0</version>',
651
- '</Package>',
652
- '',
653
- ].join('\n');
654
- const manifestPath = path.join(manifestDir, `wave-${String(params.waveNumber).padStart(3, '0')}.xml`);
655
- await writeFile(manifestPath, content, 'utf8');
656
- return manifestPath;
657
- }
658
146
  }
659
147
  //# sourceMappingURL=start.js.map