11package app.morphe.cli.command
22
33import app.morphe.cli.command.model.PatchBundle
4+ import app.morphe.engine.MorpheData
5+ import app.morphe.engine.patches.LoadedBundle
6+ import app.morphe.engine.patches.PatchBundleLoader
47import app.morphe.cli.command.model.findMatchingBundle
58import app.morphe.cli.command.model.mergeWithBundle
69import app.morphe.cli.command.model.withUpdatedBundle
7- import app.morphe.patcher.patch.loadPatchesFromJar
810import kotlinx.serialization.json.Json
911import picocli.CommandLine
1012import picocli.CommandLine.Command
@@ -71,14 +73,19 @@ internal object OptionsCommand : Callable<Int> {
7173 private val json = Json { prettyPrint = true }
7274
7375 override fun call (): Int {
74- val temporaryFilesPath = temporaryFilesPath ? : File ( " " ).absoluteFile.resolve( " morphe-temporary-files " )
76+ val temporaryFilesPath = temporaryFilesPath ? : MorpheData .tmpDir
7577
7678 try {
77- patchesFiles = PatchFileResolver .resolve(
78- patchesFiles,
79- prerelease,
80- temporaryFilesPath
81- )
79+ // Since we could have many URLs, we resolve each of them separately
80+ patchesFiles = patchesFiles.map { file ->
81+ val resolved = PatchFileResolver .resolve(
82+ setOf (file),
83+ prerelease,
84+ temporaryFilesPath,
85+ CliHttpClient .instance
86+ )
87+ resolved.single()
88+ }.toSet()
8289 } catch (e: IllegalArgumentException ) {
8390 throw CommandLine .ParameterException (
8491 spec.commandLine(),
@@ -87,51 +94,64 @@ internal object OptionsCommand : Callable<Int> {
8794 }
8895
8996 return try {
90- logger.info(" Loading patches" )
91-
92- val patches = loadPatchesFromJar(patchesFiles)
97+ logger.info(" Loading patches..." )
9398
94- val filtered = packageName?.let { pkg ->
95- patches.filter { patch ->
96- patch.compatiblePackages?.any { (name, _) -> name == pkg } ? : true
97- }.toSet()
98- } ? : patches
99+ // Load each bundle separately so we produce one JSON entry per .mpp
100+ // matches the shape PatchCommand expects when reading --options-file.
101+ val loadedBundles: List <LoadedBundle > = PatchBundleLoader .loadEach(patchesFiles)
99102
100- // Read existing bundles list if the file already exists
101- val existingBundles: List <PatchBundle >? = if (outputFile.exists()) {
103+ // Read existing bundles list if the file already exists.
104+ val existingBundles: List <PatchBundle > = if (outputFile.exists())
105+ {
102106 try {
103107 Json .decodeFromString<List <PatchBundle >>(outputFile.readText())
104108 } catch (e: Exception ) {
105- logger.warning(" Could not parse existing file, creating fresh: ${e.message} " )
106- null
109+ logger.warning(
110+ " Could not parse existing file, creating fresh: ${e.message} "
111+ )
112+ emptyList()
107113 }
108- } else null
109-
110- // Find the bundle matching the current .mpp file(s), merge with it (or create fresh)
111- val existingBundle = existingBundles?.findMatchingBundle(patchesFiles)
112- val updatedBundle = filtered.mergeWithBundle(
113- existing = existingBundle,
114- sourceFiles = patchesFiles,
115- )
116-
117- // Replace the matching entry in the list (or start a new list)
118- val updatedBundles = existingBundles?.withUpdatedBundle(updatedBundle)
119- ? : listOf (updatedBundle)
114+ } else emptyList()
115+
116+ // For each bundle: apply optional package filter, find its matching JSON
117+ // entry (by source filename), merge, splice updated entry back into the running list.
118+ var updatedBundles = existingBundles
119+ loadedBundles.forEach { lb ->
120+ val filtered = packageName?.let { pkg ->
121+ lb.patches.filter { patch ->
122+ patch.compatiblePackages?.any { (name, _) -> name == pkg } ? : true
123+ }.toSet()
124+ } ? : lb.patches
125+
126+ val existingBundle = updatedBundles.findMatchingBundle(setOf (lb.sourceFile))
127+ val updatedBundle = filtered.mergeWithBundle(
128+ existing = existingBundle,
129+ sourceFiles = setOf (lb.sourceFile),
130+ )
131+ updatedBundles = updatedBundles.withUpdatedBundle(updatedBundle)
132+
133+ // Per-bundle log line so users can see what changed for each .mpp
134+ if (existingBundle != null ) {
135+ val existingNames = existingBundle.patches.keys.map { it.lowercase() }.toSet()
136+ val newNames = updatedBundle.patches.keys.map { it.lowercase() }.toSet()
137+ val added = newNames - existingNames
138+ val removed = existingNames - newNames
139+ val kept = newNames.intersect(existingNames)
140+
141+ logger.info(
142+ " Updated bundle for ${lb.sourceFile.name} : ${kept.size} preserved, ${added.size} added, ${removed.size} removed"
143+ )
144+ } else {
145+ logger.info(
146+ " Created new bundle for ${lb.sourceFile.name} with ${updatedBundle.patches.size} patches"
147+ )
148+ }
149+ }
120150
121151 outputFile.absoluteFile.parentFile?.mkdirs()
122152 outputFile.writeText(json.encodeToString(updatedBundles))
123153
124- if (existingBundle != null ) {
125- val existingNames = existingBundle.patches.keys.map { it.lowercase() }.toSet()
126- val newNames = updatedBundle.patches.keys.map { it.lowercase() }.toSet()
127- val added = newNames - existingNames
128- val removed = existingNames - newNames
129- val kept = newNames.intersect(existingNames)
130- logger.info(" Updated bundle in options file at ${outputFile.path} " )
131- logger.info(" ${kept.size} patches preserved, ${added.size} added, ${removed.size} removed" )
132- } else {
133- logger.info(" Created new bundle in options file at ${outputFile.path} with ${updatedBundle.patches.size} patches" )
134- }
154+ logger.info(" Options file saved to ${outputFile.path} " )
135155
136156 EXIT_CODE_SUCCESS
137157 } catch (e: Exception ) {
0 commit comments