2626import  me .itzg .helpers .http .Fetch ;
2727import  me .itzg .helpers .http .SharedFetchArgs ;
2828import  me .itzg .helpers .json .ObjectMappers ;
29- import  me .itzg .helpers .modrinth .model .Constants ;
3029import  me .itzg .helpers .modrinth .model .DependencyType ;
3130import  me .itzg .helpers .modrinth .model .Project ;
3231import  me .itzg .helpers .modrinth .model .ProjectType ;
4443public  class  ModrinthCommand  implements  Callable <Integer > {
4544
4645    public  static  final  String  DATAPACKS_SUBDIR  = "datapacks" ;
47-     @ Option (names  = "--projects" , description  = "Project ID or Slug" ,
48-         split  = SPLIT_COMMA_NL , splitSynopsisLabel  = SPLIT_SYNOPSIS_COMMA_NL ,
49-         paramLabel  = "id|slug" 
46+ 
47+     @ Option (
48+         names  = "--projects" ,
49+         description  = "Project ID or Slug. Prefix with loader: e.g. fabric:project-id" ,
50+         split  = SPLIT_COMMA_NL ,
51+         splitSynopsisLabel  = SPLIT_SYNOPSIS_COMMA_NL ,
52+         paramLabel  = "[loader:]id|slug" 
5053    )
5154    List <String > projects ;
5255
@@ -75,7 +78,7 @@ public enum DownloadDependencies {
7578        /** 
7679         * Implies {@link #REQUIRED} 
7780         */ 
78-         OPTIONAL 
81+         OPTIONAL , 
7982    }
8083
8184    @ Option (names  = "--allowed-version-type" , defaultValue  = "release" , description  = "Valid values: ${COMPLETION-CANDIDATES}" )
@@ -128,9 +131,13 @@ private List<Path> processProjects(List<String> projects) {
128131                .defaultIfEmpty (Collections .emptyList ())
129132                .block ()
130133                .stream ()
131-                 .flatMap (resolvedProject  -> processProject (
132-                     modrinthApiClient , resolvedProject .getProjectRef (), resolvedProject .getProject ()
133-                 ))
134+                 .flatMap (resolvedProject  ->
135+                     processProject (
136+                         modrinthApiClient ,
137+                         resolvedProject .getProjectRef (),
138+                         resolvedProject .getProject ()
139+                     )
140+                 )
134141                .collect (Collectors .toList ());
135142        }
136143    }
@@ -142,9 +149,9 @@ private ModrinthManifest loadManifest() throws IOException {
142149            final  ObjectMapper  objectMapper  = ObjectMappers .defaultMapper ();
143150
144151            final  LegacyModrinthManifest  legacyManifest  = objectMapper .readValue (
145-                 legacyManifestPath .toFile (),
146-                 LegacyModrinthManifest .class 
147-             );
152+                      legacyManifestPath .toFile (),
153+                      LegacyModrinthManifest .class 
154+                  );
148155
149156            Files .delete (legacyManifestPath );
150157
@@ -157,7 +164,13 @@ private ModrinthManifest loadManifest() throws IOException {
157164        return  Manifests .load (outputDirectory , ModrinthManifest .ID , ModrinthManifest .class );
158165    }
159166
160-     private  Stream <Version > expandDependencies (ModrinthApiClient  modrinthApiClient , Project  project , Version  version ) {
167+     private  Stream <Version > expandDependencies (
168+         ModrinthApiClient  modrinthApiClient ,
169+         Loader  loader ,
170+         String  gameVersion ,
171+         Project  project ,
172+         Version  version 
173+     ) {
161174        log .debug ("Expanding dependencies of version={}" , version );
162175        return  version .getDependencies ().stream ()
163176            .filter (this ::filterDependency )
@@ -170,7 +183,7 @@ private Stream<Version> expandDependencies(ModrinthApiClient modrinthApiClient,
170183                    if  (dep .getVersionId () == null ) {
171184                        log .debug ("Fetching versions of dep={} and picking" , dep );
172185                        depVersion  = pickVersion (
173-                             getVersionsForProject (modrinthApiClient , dep .getProjectId ())
186+                             getVersionsForProject (modrinthApiClient , dep .getProjectId (),  loader ,  gameVersion )
174187                        );
175188                    }
176189                    else  {
@@ -192,8 +205,8 @@ private Stream<Version> expandDependencies(ModrinthApiClient modrinthApiClient,
192205                if  (depVersion  != null ) {
193206                    log .debug ("Resolved version={} for dep={}" , depVersion .getVersionNumber (), dep );
194207                    return  Stream .concat (
195-                              Stream .of (depVersion ),
196-                             expandDependencies (modrinthApiClient , project , depVersion )
208+                         Stream .of (depVersion ),
209+                             expandDependencies (modrinthApiClient , loader ,  gameVersion ,  project , depVersion )
197210                        )
198211                        .peek (expandedVer  -> log .debug ("Expanded dependency={} into version={}" , dep , expandedVer ));
199212                }
@@ -229,16 +242,14 @@ private Version pickVersion(List<Version> versions, VersionType versionType) {
229242        return  null ;
230243    }
231244
232-     private  Path  download (boolean   isDatapack , VersionFile  versionFile ) {
245+     private  Path  download (Loader   loader , VersionFile  versionFile ) {
233246        final  Path  outPath ;
234247        try  {
235-             if  (!isDatapack ) {
236-                 outPath  = Files .createDirectories (outputDirectory 
237-                         .resolve (loader .getType ())
238-                     )
239-                     .resolve (versionFile .getFilename ());
240-             }
241-             else  {
248+             final  Loader  effectiveLoader  = loader  != null  ? loader  : this .loader ;
249+             final  String  outputType  = effectiveLoader .getType ();
250+ 
251+             if  (outputType  == null ) {
252+                 // Datapack case 
242253                if  (worldDirectory .isAbsolute ()) {
243254                    outPath  = Files .createDirectories (worldDirectory 
244255                            .resolve (DATAPACKS_SUBDIR )
@@ -253,9 +264,15 @@ private Path download(boolean isDatapack, VersionFile versionFile) {
253264                        .resolve (versionFile .getFilename ());
254265                }
255266            }
267+             else  {
268+                 outPath  = Files .createDirectories (outputDirectory 
269+                         .resolve (outputType )
270+                     )
271+                     .resolve (versionFile .getFilename ());
272+             }
256273
257274        } catch  (IOException  e ) {
258-             throw  new  RuntimeException ("Creating mods  directory" , e );
275+             throw  new  RuntimeException ("Creating output  directory" , e );
259276        }
260277
261278        try  {
@@ -267,11 +284,11 @@ private Path download(boolean isDatapack, VersionFile versionFile) {
267284                .handleStatus (Fetch .loggingDownloadStatusHandler (log ))
268285                .execute ();
269286        } catch  (IOException  e ) {
270-             throw  new  RuntimeException ("Downloading mod  file" , e );
287+             throw  new  RuntimeException ("Downloading file" , e );
271288        }
272289    }
273290
274-     private  List <Version > getVersionsForProject (ModrinthApiClient  modrinthApiClient , String  project ) {
291+     private  List <Version > getVersionsForProject (ModrinthApiClient  modrinthApiClient , String  project ,  Loader   loader ,  String   gameVersion ) {
275292        final  List <Version > versions  = modrinthApiClient .getVersionsForProject (
276293                project , loader , gameVersion 
277294            )
@@ -294,10 +311,19 @@ private Stream<Path> processProject(ModrinthApiClient modrinthApiClient, Project
294311        log .debug ("Starting with project='{}' slug={}" , project .getTitle (), project .getSlug ());
295312
296313        if  (projectsProcessed .add (project .getId ())) {
314+             final  Loader  effectiveLoader  = projectRef .getLoader () != null 
315+                 ? projectRef .getLoader ()
316+                 : this .loader ;
317+ 
297318            final  Version  version ;
298319            try  {
299-                 version  = modrinthApiClient .resolveProjectVersion (
300-                         project , projectRef , loader , gameVersion , defaultVersionType 
320+                 version  = modrinthApiClient 
321+                     .resolveProjectVersion (
322+                         project ,
323+                         projectRef ,
324+                         effectiveLoader ,
325+                         gameVersion ,
326+                         defaultVersionType 
301327                    )
302328                    .block ();
303329            } catch  (NoApplicableVersionsException  | NoFilesAvailableException  e ) {
@@ -306,36 +332,46 @@ private Stream<Path> processProject(ModrinthApiClient modrinthApiClient, Project
306332
307333            if  (version  != null ) {
308334                if  (version .getFiles ().isEmpty ()) {
309-                     throw  new  GenericException (String .format ("Project %s has no files declared" , project .getSlug ()));
335+                     throw  new  GenericException (
336+                         String .format (
337+                             "Project %s has no files declared" ,
338+                             project .getSlug ()
339+                         )
340+                     );
310341                }
311342
312-                 final  boolean  isDatapack  = isDatapack (version );
313- 
314343                return  Stream .concat (
315-                         Stream .of (version ),
316-                         expandDependencies (modrinthApiClient , project , version )
344+                     Stream .of (version ),
345+                     expandDependencies (
346+                         modrinthApiClient ,
347+                         effectiveLoader ,
348+                         gameVersion ,
349+                         project ,
350+                         version 
317351                    )
352+                 )
318353                    .map (ModrinthApiClient ::pickVersionFile )
319-                     .map (versionFile  -> download (isDatapack , versionFile ))
320-                     .flatMap (downloadedFile  -> !isDatapack  ? expandIfZip (downloadedFile ) : Stream .empty ());
321-             }
322-             else  {
354+                     .map (versionFile  -> download (effectiveLoader , versionFile ))
355+                     .flatMap (downloadedFile  -> {
356+                         // Only expand ZIPs for non-datapack loaders 
357+                         return  effectiveLoader  == Loader .datapack 
358+                             ? Stream .of (downloadedFile )
359+                             : expandIfZip (downloadedFile );
360+                     });
361+             } else  {
323362                throw  new  InvalidParameterException (
324-                     String .format ("Project %s does not have any matching versions for loader %s, game version %s" ,
325-                         projectRef , loader , gameVersion 
326-                     ));
363+                     String .format (
364+                         "Project %s does not have any matching versions for loader %s, game version %s" ,
365+                         projectRef ,
366+                         effectiveLoader ,
367+                         gameVersion 
368+                     )
369+                 );
327370            }
328371        }
329372        return  Stream .empty ();
330373    }
331374
332-     private  boolean  isDatapack (Version  version ) {
333-         return 
334-             version .getLoaders () != null 
335-             && version .getLoaders ().size () == 1 
336-             && version .getLoaders ().get (0 ).equals (Constants .LOADER_DATAPACK );
337-     }
338- 
339375    /** 
340376     * If downloadedFile ends in .zip, then expand it, return its files and given file. 
341377     * 
0 commit comments