1- import { InternalError } from '../common/errors .js' ;
2- import { PluginInitOrchestrator } from '../common/initialize-plugins .js' ;
1+ import { InitializationResult , PluginInitOrchestrator } from '../common/initialize-plugins .js' ;
2+ import { Plan } from '../entities/plan .js' ;
33import { Project } from '../entities/project.js' ;
44import { ResourceConfig } from '../entities/resource-config.js' ;
5+ import { ResourceInfo } from '../entities/resource-info.js' ;
56import { ProcessName , SubProcessName , ctx } from '../events/context.js' ;
67import { DependencyMap , PluginManager } from '../plugins/plugin-manager.js' ;
7- import { Reporter } from '../ui/reporters/reporter.js' ;
8- import { getTypeAndNameFromId } from '../utils/index .js' ;
8+ import { PromptType , Reporter } from '../ui/reporters/reporter.js' ;
9+ import { wildCardMatch } from '../utils/wild-card-match .js' ;
910
1011export interface DestroyArgs {
11- ids : string [ ] ;
12+ typeIds : string [ ] ;
1213 path ?: string ;
1314 secureMode ?: boolean ;
1415}
1516
1617export class DestroyOrchestrator {
1718
1819 static async run ( args : DestroyArgs , reporter : Reporter ) {
19- const { ids } = args ;
20- if ( ids . length === 0 ) {
21- throw new InternalError ( 'getDestroyPlan called with no ids passed in' ) ;
22- }
23-
20+ const typeIds = args . typeIds ?. filter ( Boolean )
2421 ctx . processStarted ( ProcessName . DESTROY )
25-
26- const { typeIdsToDependenciesMap, pluginManager, project } = await PluginInitOrchestrator . run ( {
27- ...args ,
28- allowEmptyProject : true ,
29- transformProject ( project ) {
30- project . filter ( ids ) // We only care about the types being uninstalled
31-
32- const nonProjectConfigs = ids . filter ( ( id ) =>
33- project . resourceConfigs . findIndex ( ( r ) => r . id . includes ( id ) ) === - 1
34- )
35-
36- project . add ( ...nonProjectConfigs . map ( ( id ) => {
37- const { name, type } = getTypeAndNameFromId ( id ) ;
38- return new ResourceConfig ( { name, type } )
39- } ) )
40-
41- return project ;
42- }
43- } , reporter ) ;
4422
45- await DestroyOrchestrator . validate ( project , pluginManager , typeIdsToDependenciesMap )
23+ const initializationResult = await PluginInitOrchestrator . run (
24+ { ...args , allowEmptyProject : true , } ,
25+ reporter
26+ ) ;
27+ const { pluginManager, project } = initializationResult ;
4628
47- const uninstallProject = project . toDestroyProject ( )
48- uninstallProject . resolveDependenciesAndCalculateEvalOrder ( typeIdsToDependenciesMap ) ;
29+ if ( ( ! typeIds || typeIds . length === 0 ) && project . isEmpty ( ) ) {
30+ throw new Error ( 'At least one resource [type] must be specified. Ex: "codify destroy homebrew". Or the destroy command must be run in a directory with a valid codify file' )
31+ }
32+
33+ const { plan, destroyProject } = ( ! typeIds || typeIds . length === 0 )
34+ ? await DestroyOrchestrator . destroyExistingProject ( reporter , initializationResult )
35+ : await DestroyOrchestrator . destroySpecificResources ( typeIds , reporter , initializationResult )
4936
50- const plan = await ctx . subprocess ( ProcessName . PLAN , ( ) =>
51- pluginManager . plan ( uninstallProject )
52- )
5337
5438 plan . sortByEvalOrder ( project . evaluationOrder ) ;
55- uninstallProject . removeNoopFromEvaluationOrder ( plan ) ;
39+ destroyProject . removeNoopFromEvaluationOrder ( plan ) ;
5640
5741 reporter . displayPlan ( plan ) ;
5842
@@ -70,21 +54,132 @@ export class DestroyOrchestrator {
7054 const filteredPlan = plan . filterNoopResources ( )
7155
7256 await ctx . process ( ProcessName . DESTROY , ( ) =>
73- pluginManager . apply ( uninstallProject , filteredPlan )
57+ pluginManager . apply ( destroyProject , filteredPlan )
7458 )
7559
7660 await reporter . displayMessage ( `
7761🎉 Finished applying 🎉
7862Open a new terminal or source '.zshrc' for the new changes to be reflected` ) ;
7963 }
8064
81- private static async validate ( project : Project , pluginManager : PluginManager , dependencyMap : DependencyMap ) : Promise < void > {
82- ctx . subprocessStarted ( SubProcessName . VALIDATE )
65+ /** This method is responsible for generating a plan for specific resources specified by the user */
66+ private static async destroySpecificResources (
67+ typeIds : string [ ] ,
68+ reporter : Reporter ,
69+ initializeResult : InitializationResult
70+ ) : Promise < { plan : Plan , destroyProject : Project } > {
71+ const { project, pluginManager, typeIdsToDependenciesMap } = initializeResult ;
72+
73+ const matchedTypes = this . matchTypeIds ( typeIds , [ ...typeIdsToDependenciesMap . keys ( ) ] )
74+ await DestroyOrchestrator . validateTypeIds ( matchedTypes , project , pluginManager , typeIdsToDependenciesMap ) ;
75+
76+ const resourceInfoList = ( await pluginManager . getMultipleResourceInfo ( matchedTypes ) ) ;
77+ const resourcesToDestroy = await DestroyOrchestrator . getDestroyParameters ( reporter , project , resourceInfoList ) ;
78+
79+ const destroyProject = new Project (
80+ null ,
81+ resourcesToDestroy ,
82+ project . codifyFiles
83+ ) . toDestroyProject ( ) ;
84+
85+ destroyProject . resolveDependenciesAndCalculateEvalOrder ( typeIdsToDependenciesMap ) ;
86+ const plan = await ctx . subprocess ( ProcessName . PLAN , ( ) =>
87+ pluginManager . plan ( destroyProject )
88+ )
89+
90+ return { plan, destroyProject } ;
91+ }
92+
93+ /** This method is responsible for generating the plan when no args are specified (ie: destroy all resources inside a codify.json file) **/
94+ private static async destroyExistingProject (
95+ reporter : Reporter ,
96+ initializeResult : InitializationResult
97+ ) : Promise < { plan : Plan , destroyProject : Project } > {
98+ const { pluginManager, project, typeIdsToDependenciesMap } = initializeResult ;
99+
100+ await ctx . subprocess ( SubProcessName . VALIDATE , async ( ) => {
101+ project . validateTypeIds ( typeIdsToDependenciesMap ) ;
102+ const validationResults = await pluginManager . validate ( project ) ;
103+ project . handlePluginResourceValidationResults ( validationResults ) ;
104+ } )
105+
106+ const destroyProject = project . toDestroyProject ( ) ;
107+ destroyProject . resolveDependenciesAndCalculateEvalOrder ( typeIdsToDependenciesMap ) ;
108+
109+ const plan = await ctx . subprocess ( ProcessName . PLAN , ( ) =>
110+ pluginManager . plan ( destroyProject )
111+ )
112+
113+ return { plan, destroyProject } ;
114+ }
115+
116+ private static matchTypeIds ( typeIds : string [ ] , validTypeIds : string [ ] ) : string [ ] {
117+ const result : string [ ] = [ ] ;
118+ const unsupportedTypeIds : string [ ] = [ ] ;
119+
120+ for ( const typeId of typeIds ) {
121+ if ( ! typeId . includes ( '*' ) && ! typeId . includes ( '?' ) ) {
122+ const matched = validTypeIds . includes ( typeId ) ;
123+ if ( ! matched ) {
124+ unsupportedTypeIds . push ( typeId ) ;
125+ continue ;
126+ }
127+
128+ result . push ( typeId )
129+ continue ;
130+ }
131+
132+ const matched = validTypeIds . filter ( ( valid ) => wildCardMatch ( valid , typeId ) )
133+ if ( matched . length === 0 ) {
134+ unsupportedTypeIds . push ( typeId ) ;
135+ continue ;
136+ }
137+
138+ result . push ( ...matched ) ;
139+ }
140+
141+ if ( unsupportedTypeIds . length > 0 ) {
142+ throw new Error ( `The following resources cannot be imported. No plugins found that support the following types:
143+ ${ JSON . stringify ( unsupportedTypeIds ) } `) ;
144+ }
145+
146+ return result ;
147+ }
83148
149+ private static async validateTypeIds ( typeIds : string [ ] , project : Project , pluginManager : PluginManager , dependencyMap : DependencyMap ) : Promise < void > {
84150 project . validateTypeIds ( dependencyMap ) ;
85- const validationResults = await pluginManager . validate ( project ) ;
86- project . handlePluginResourceValidationResults ( validationResults ) ;
87151
88- ctx . subprocessFinished ( SubProcessName . VALIDATE )
152+ const unsupportedTypeIds = typeIds . filter ( ( type ) => ! dependencyMap . has ( type ) ) ;
153+ if ( unsupportedTypeIds . length > 0 ) {
154+ throw new Error ( `The following resources cannot be destroyed. No plugins found that support the following types:
155+ ${ JSON . stringify ( unsupportedTypeIds ) } `) ;
156+ }
89157 }
158+
159+ private static async getDestroyParameters ( reporter : Reporter , project : Project , resourceInfoList : ResourceInfo [ ] ) : Promise < Array < ResourceConfig > > {
160+ // Figure out which resources we need to prompt the user for additional info (based on the resource info)
161+ const [ noPrompt , askPrompt ] = resourceInfoList . reduce ( ( result , info ) => {
162+ info . getRequiredParameters ( ) . length === 0 ? result [ 0 ] . push ( info ) : result [ 1 ] . push ( info ) ;
163+ return result ;
164+ } , [ < ResourceInfo [ ] > [ ] , < ResourceInfo [ ] > [ ] ] )
165+
166+ askPrompt . forEach ( ( info ) => {
167+ const matchedResources = project . findAll ( info . type ) ;
168+ if ( matchedResources . length > 0 ) {
169+ info . attachDefaultValues ( matchedResources [ 0 ] ) ;
170+ }
171+ } )
172+
173+ if ( askPrompt . length > 0 ) {
174+ await reporter . displayImportWarning ( askPrompt . map ( ( r ) => r . type ) , noPrompt . map ( ( r ) => r . type ) ) ;
175+ }
176+
177+ const userSupplied = await reporter . promptUserForValues ( askPrompt , PromptType . DESTROY ) ;
178+
179+ return [
180+ ...noPrompt . map ( ( info ) => new ResourceConfig ( { type : info . type } ) ) ,
181+ ...userSupplied
182+ ]
183+ }
184+
90185}
0 commit comments