@@ -9,9 +9,9 @@ import { DependencyMap, PluginManager } from '../plugins/plugin-manager.js';
99import { PromptType , Reporter } from '../ui/reporters/reporter.js' ;
1010import { FileUtils } from '../utils/file.js' ;
1111import { FileModificationCalculator , ModificationType } from '../utils/file-modification-calculator.js' ;
12- import { sleep } from '../utils/index.js' ;
12+ import { groupBy , sleep } from '../utils/index.js' ;
1313import { wildCardMatch } from '../utils/wild-card-match.js' ;
14- import { InitializeOrchestrator } from './initialize.js' ;
14+ import { InitializationResult , InitializeOrchestrator } from './initialize.js' ;
1515
1616export type RequiredParameters = Map < string , RequiredParameter [ ] > ;
1717export type UserSuppliedParameters = Map < string , Record < string , unknown > > ;
@@ -46,34 +46,70 @@ export class ImportOrchestrator {
4646 reporter : Reporter
4747 ) {
4848 const { typeIds } = args
49- if ( typeIds . length === 0 ) {
50- throw new Error ( 'At least one resource <type> must be specified. Ex: "codify import homebrew"' )
51- }
52-
5349 ctx . processStarted ( ProcessName . IMPORT )
5450
55- const { typeIdsToDependenciesMap , pluginManager , project } = await InitializeOrchestrator . run (
51+ const initializationResult = await InitializeOrchestrator . run (
5652 { ...args , allowEmptyProject : true } ,
5753 reporter
5854 ) ;
59-
55+ const { project } = initializationResult ;
56+
57+ if ( ( ! typeIds || typeIds . length === 0 ) && project . isEmpty ( ) ) {
58+ throw new Error ( 'At least one resource [type] must be specified. Ex: "codify import homebrew". Or the import command must be run in a directory with a valid codify file' )
59+ }
60+
61+ if ( ! typeIds || typeIds . length === 0 ) {
62+ await ImportOrchestrator . runExistingProject ( reporter , initializationResult )
63+ } else {
64+ await ImportOrchestrator . runNewImport ( typeIds , reporter , initializationResult )
65+ }
66+ }
67+
68+ /** Import new resources. Type ids supplied. This will ask for any required parameters */
69+ static async runNewImport ( typeIds : string [ ] , reporter : Reporter , initializeResult : InitializationResult ) : Promise < ResourceConfig [ ] > {
70+ const { project, pluginManager, typeIdsToDependenciesMap } = initializeResult ;
71+
6072 const matchedTypes = this . matchTypeIds ( typeIds , [ ...typeIdsToDependenciesMap . keys ( ) ] )
6173 await ImportOrchestrator . validate ( matchedTypes , project , pluginManager , typeIdsToDependenciesMap ) ;
6274
6375 const resourceInfoList = await pluginManager . getMultipleResourceInfo ( matchedTypes ) ;
64-
65- const importParameters = await ImportOrchestrator . getImportParameters ( reporter , project , resourceInfoList ) ;
66- const importResult = await ImportOrchestrator . import ( pluginManager , importParameters ) ;
76+ const resourcesToImport = await ImportOrchestrator . getImportParameters ( reporter , project , resourceInfoList ) ;
77+ const importResult = await ImportOrchestrator . import ( pluginManager , resourcesToImport ) ;
6778
6879 ctx . processFinished ( ProcessName . IMPORT )
69- reporter . displayImportResult ( importResult , false ) ;
7080
71- const additionalResourceInfo = await pluginManager . getMultipleResourceInfo ( project . resourceConfigs . map ( ( r ) => r . type ) ) ;
72- resourceInfoList . push ( ...additionalResourceInfo ) ;
81+ reporter . displayImportResult ( importResult , false ) ;
7382
83+ resourceInfoList . push ( ...( await pluginManager . getMultipleResourceInfo (
84+ project . resourceConfigs . map ( ( r ) => r . type )
85+ ) ) ) ;
7486 await ImportOrchestrator . saveResults ( reporter , importResult , project , resourceInfoList )
7587 }
7688
89+ /** Update an existing project. This will use the existing resources as the parameters (no user input required). */
90+ static async runExistingProject ( reporter : Reporter , initializeResult : InitializationResult ) : Promise < ResourceConfig [ ] > {
91+ const { pluginManager, project } = initializeResult ;
92+
93+ await pluginManager . validate ( project ) ;
94+ const importResult = await ImportOrchestrator . import ( pluginManager , project . resourceConfigs ) ;
95+
96+ ctx . processFinished ( ProcessName . IMPORT ) ;
97+
98+ const resourceInfoList = await pluginManager . getMultipleResourceInfo (
99+ project . resourceConfigs . map ( ( r ) => r . type ) ,
100+ ) ;
101+
102+ await ImportOrchestrator . updateExistingFiles (
103+ reporter ,
104+ project ,
105+ importResult ,
106+ resourceInfoList ,
107+ project . codifyFiles [ 0 ] ,
108+ ) ;
109+
110+ return project . resourceConfigs ;
111+ }
112+
77113 static async import (
78114 pluginManager : PluginManager ,
79115 resources : ResourceConfig [ ] ,
@@ -90,7 +126,10 @@ export class ImportOrchestrator {
90126 if ( response . result !== null && response . result . length > 0 ) {
91127 importedConfigs . push ( ...response
92128 ?. result
93- ?. map ( ( r ) => ResourceConfig . fromJson ( r ) ) ?? [ ]
129+ ?. map ( ( r ) =>
130+ // Keep the name on the resource if possible, this makes it easier to identify where the import came from
131+ ResourceConfig . fromJson ( { ...r , core : { ...r . core , name : resource . name } } )
132+ ) ?? [ ]
94133 ) ;
95134 } else {
96135 errors . push ( `Unable to import resource '${ resource . type } ', resource not found` ) ;
@@ -183,18 +222,20 @@ ${JSON.stringify(unsupportedTypeIds)}`);
183222
184223 const promptResult = await reporter . promptOptions (
185224 '\nDo you want to save the results?' ,
186- [ projectExists ? multipleCodifyFiles ? 'Update existing file (multiple found)' : `Update existing file (${ project . codifyFiles } )` : undefined , 'In a new file' , 'No' ] . filter ( Boolean ) as string [ ]
225+ [
226+ projectExists ?
227+ multipleCodifyFiles ? `Update existing files (${ project . codifyFiles } )` : `Update existing file (${ project . codifyFiles } )`
228+ : undefined ,
229+ 'In a new file' ,
230+ 'No'
231+ ] . filter ( Boolean ) as string [ ]
187232 )
188233
189- if ( promptResult === 'Update existing file (multiple found)' ) {
190- const file = await reporter . promptOptions (
191- '\nWhich file would you like to update?' ,
192- project . codifyFiles ,
193- )
194- await ImportOrchestrator . updateExistingFile ( reporter , file , importResult , resourceInfoList ) ;
195-
196- } else if ( promptResult . startsWith ( 'Update existing file' ) ) {
197- await ImportOrchestrator . updateExistingFile ( reporter , project . codifyFiles [ 0 ] , importResult , resourceInfoList ) ;
234+ if ( promptResult . startsWith ( 'Update existing file' ) ) {
235+ const file = multipleCodifyFiles
236+ ? await reporter . promptOptions ( '\nIf new resources are added, where to write them?' , project . codifyFiles )
237+ : project . codifyFiles [ 0 ] ;
238+ await ImportOrchestrator . updateExistingFiles ( reporter , project , importResult , resourceInfoList , file ) ;
198239
199240 } else if ( promptResult === 'In a new file' ) {
200241 const newFileName = await ImportOrchestrator . generateNewImportFileName ( ) ;
@@ -209,42 +250,62 @@ ${JSON.stringify(unsupportedTypeIds)}`);
209250 }
210251 }
211252
212- private static async updateExistingFile (
253+ private static async updateExistingFiles (
213254 reporter : Reporter ,
214- filePath : string ,
255+ existingProject : Project ,
215256 importResult : ImportResult ,
216- resourceInfoList : ResourceInfo [ ]
257+ resourceInfoList : ResourceInfo [ ] ,
258+ preferredFile : string , // File to write any new resources (unknown file path)
217259 ) : Promise < void > {
218- const existing = await CodifyParser . parse ( filePath ) ;
219- ImportOrchestrator . attachResourceInfo ( importResult . result , resourceInfoList ) ;
220- ImportOrchestrator . attachResourceInfo ( existing . resourceConfigs , resourceInfoList ) ;
260+ const groupedResults = groupBy ( importResult . result , ( r ) =>
261+ existingProject . findSpecific ( r . type , r . name ) ?. sourceMapKey ?. split ( '#' ) ?. [ 0 ] ?? 'unknown'
262+ )
221263
222- const modificationCalculator = new FileModificationCalculator ( existing ) ;
223- const result = modificationCalculator . calculate ( importResult . result . map ( ( resource ) => ( {
224- modification : ModificationType . INSERT_OR_UPDATE ,
225- resource
226- } ) ) ) ;
264+ // New resources exists (they don't belong to any existing files)
265+ if ( groupedResults . unknown ) {
266+ groupedResults [ preferredFile ] = [
267+ ...groupedResults . unknown ,
268+ ...groupedResults [ preferredFile ] ,
269+ ]
270+ delete groupedResults . unknown ;
271+ }
272+
273+ const diffs = await Promise . all ( Object . entries ( groupedResults ) . map ( async ( [ filePath , imported ] ) => {
274+ const existing = await CodifyParser . parse ( filePath ! ) ;
275+ ImportOrchestrator . attachResourceInfo ( imported , resourceInfoList ) ;
276+ ImportOrchestrator . attachResourceInfo ( existing . resourceConfigs , resourceInfoList ) ;
277+
278+ const modificationCalculator = new FileModificationCalculator ( existing ) ;
279+ const modification = modificationCalculator . calculate ( imported . map ( ( resource ) => ( {
280+ modification : ModificationType . INSERT_OR_UPDATE ,
281+ resource
282+ } ) ) ) ;
283+
284+ return { file : filePath ! , modification } ;
285+ } ) ) ;
227286
228287 // No changes to be made
229- if ( result . diff === '' ) {
288+ if ( diffs . every ( ( d ) => d . modification . diff === '' ) ) {
230289 reporter . displayMessage ( '\nNo changes are needed! Exiting...' )
231290
232291 // Wait for the message to display before we exit
233292 await sleep ( 100 ) ;
234- process . exit ( 0 ) ;
293+ return ;
235294 }
236295
237- reporter . displayFileModification ( result . diff ) ;
238- const shouldSave = await reporter . promptConfirmation ( ` Save to file ( ${ filePath } )?` ) ;
296+ reporter . displayFileModifications ( diffs ) ;
297+ const shouldSave = await reporter . promptConfirmation ( ' Save the changes?' ) ;
239298 if ( ! shouldSave ) {
240299 reporter . displayMessage ( '\nSkipping save! Exiting...' ) ;
241300
242301 // Wait for the message to display before we exit
243302 await sleep ( 100 ) ;
244- process . exit ( 0 ) ;
303+ return ;
245304 }
246305
247- await FileUtils . writeFile ( filePath , result . newFile ) ;
306+ for ( const diff of diffs ) {
307+ await FileUtils . writeFile ( diff . file , diff . modification . newFile ) ;
308+ }
248309
249310 reporter . displayMessage ( '\n🎉 Imported completed and saved to file 🎉' ) ;
250311
0 commit comments