sort_package_json/
lib.rs

1use serde_json::{Map, Value};
2
3/// Options for controlling JSON formatting when sorting
4#[derive(Debug, Clone)]
5pub struct SortOptions {
6    /// Whether to pretty-print the output JSON
7    pub pretty: bool,
8}
9
10impl Default for SortOptions {
11    fn default() -> Self {
12        Self { pretty: true }
13    }
14}
15
16/// Sorts a package.json string with custom options
17pub fn sort_package_json_with_options(
18    input: &str,
19    options: &SortOptions,
20) -> Result<String, serde_json::Error> {
21    let value: Value = serde_json::from_str(input)?;
22
23    let sorted_value =
24        if let Value::Object(obj) = value { Value::Object(sort_object_keys(obj)) } else { value };
25
26    let result = if options.pretty {
27        let mut s = serde_json::to_string_pretty(&sorted_value)?;
28        s.push('\n');
29        s
30    } else {
31        serde_json::to_string(&sorted_value)?
32    };
33
34    Ok(result)
35}
36
37/// Sorts a package.json string with default options (pretty-printed)
38pub fn sort_package_json(input: &str) -> Result<String, serde_json::Error> {
39    sort_package_json_with_options(input, &SortOptions::default())
40}
41
42fn sort_object_alphabetically(obj: &Map<String, Value>) -> Map<String, Value> {
43    let mut keys: Vec<&String> = obj.keys().collect();
44    keys.sort();
45
46    let mut result = Map::new();
47    for key in keys {
48        if let Some(value) = obj.get(key) {
49            result.insert(key.clone(), value.clone());
50        }
51    }
52    result
53}
54
55fn sort_object_recursive(obj: &Map<String, Value>) -> Map<String, Value> {
56    let mut keys: Vec<&String> = obj.keys().collect();
57    keys.sort();
58
59    let mut result = Map::new();
60    for key in keys {
61        if let Some(value) = obj.get(key) {
62            let transformed_value = match value {
63                Value::Object(nested) => Value::Object(sort_object_recursive(nested)),
64                _ => value.clone(),
65            };
66            result.insert(key.clone(), transformed_value);
67        }
68    }
69    result
70}
71
72fn sort_array_unique(arr: &[Value]) -> Vec<Value> {
73    let mut strings: Vec<String> =
74        arr.iter().filter_map(|v| v.as_str().map(String::from)).collect();
75
76    strings.sort();
77    strings.dedup();
78
79    strings.into_iter().map(Value::String).collect()
80}
81
82fn sort_object_by_key_order(obj: &Map<String, Value>, key_order: &[&str]) -> Map<String, Value> {
83    let mut result = Map::new();
84
85    // Add keys in specified order
86    for &key in key_order {
87        if let Some(value) = obj.get(key) {
88            result.insert(key.to_string(), value.clone());
89        }
90    }
91
92    // Add remaining keys alphabetically
93    let mut remaining: Vec<&String> =
94        obj.keys().filter(|k| !key_order.contains(&k.as_str())).collect();
95    remaining.sort();
96
97    for key in remaining {
98        if let Some(value) = obj.get(key) {
99            result.insert(key.clone(), value.clone());
100        }
101    }
102
103    result
104}
105
106fn sort_people_object(obj: &Map<String, Value>) -> Map<String, Value> {
107    sort_object_by_key_order(obj, &["name", "email", "url"])
108}
109
110fn sort_exports(obj: &Map<String, Value>) -> Map<String, Value> {
111    let mut paths = Vec::new();
112    let mut types_conds = Vec::new();
113    let mut other_conds = Vec::new();
114    let mut default_cond = None;
115
116    for (key, value) in obj.iter() {
117        if key.starts_with('.') {
118            paths.push(key);
119        } else if key == "default" {
120            default_cond = Some((key, value));
121        } else if key == "types" || key.starts_with("types@") {
122            types_conds.push(key);
123        } else {
124            other_conds.push(key);
125        }
126    }
127
128    // Sort each category
129    paths.sort();
130    types_conds.sort();
131    other_conds.sort();
132
133    let mut result = Map::new();
134
135    // Add in order: paths, types, others, default
136    for key in paths {
137        if let Some(value) = obj.get(key) {
138            let transformed = match value {
139                Value::Object(nested) => Value::Object(sort_exports(nested)),
140                _ => value.clone(),
141            };
142            result.insert(key.clone(), transformed);
143        }
144    }
145
146    for key in types_conds {
147        if let Some(value) = obj.get(key) {
148            result.insert(key.clone(), value.clone());
149        }
150    }
151
152    for key in other_conds {
153        if let Some(value) = obj.get(key) {
154            result.insert(key.clone(), value.clone());
155        }
156    }
157
158    if let Some((key, value)) = default_cond {
159        result.insert(key.clone(), value.clone());
160    }
161
162    result
163}
164
165fn sort_object_keys(obj: Map<String, Value>) -> Map<String, Value> {
166    // Storage for categorized keys with their values and ordering information
167    let mut known: Vec<(usize, String, Value)> = Vec::new(); // (order_index, key, value)
168    let mut non_private: Vec<(String, Value)> = Vec::new();
169    let mut private: Vec<(String, Value)> = Vec::new();
170
171    // Single pass through all keys using into_iter()
172    for (key, value) in obj {
173        match key.as_str() {
174            "$schema" => known.push((0, key, value)),
175            "name" => known.push((1, key, value)),
176            "displayName" => known.push((2, key, value)),
177            "version" => known.push((3, key, value)),
178            "stableVersion" => known.push((4, key, value)),
179            "private" => known.push((5, key, value)),
180            "description" => known.push((6, key, value)),
181            "categories" => {
182                let transformed = match &value {
183                    Value::Array(arr) => Value::Array(sort_array_unique(arr)),
184                    _ => value,
185                };
186                known.push((7, key, transformed));
187            }
188            "keywords" => {
189                let transformed = match &value {
190                    Value::Array(arr) => Value::Array(sort_array_unique(arr)),
191                    _ => value,
192                };
193                known.push((8, key, transformed));
194            }
195            "homepage" => known.push((9, key, value)),
196            "bugs" => {
197                let transformed = match &value {
198                    Value::Object(o) => {
199                        Value::Object(sort_object_by_key_order(o, &["url", "email"]))
200                    }
201                    _ => value,
202                };
203                known.push((10, key, transformed));
204            }
205            "repository" => {
206                let transformed = match &value {
207                    Value::Object(o) => {
208                        Value::Object(sort_object_by_key_order(o, &["type", "url"]))
209                    }
210                    _ => value,
211                };
212                known.push((11, key, transformed));
213            }
214            "author" => {
215                let transformed = match &value {
216                    Value::Object(o) => Value::Object(sort_people_object(o)),
217                    _ => value,
218                };
219                known.push((12, key, transformed));
220            }
221            "maintainers" => {
222                let transformed = match &value {
223                    Value::Array(arr) => {
224                        let people: Vec<Value> = arr
225                            .iter()
226                            .map(|v| match v {
227                                Value::Object(o) => Value::Object(sort_people_object(o)),
228                                _ => v.clone(),
229                            })
230                            .collect();
231                        Value::Array(people)
232                    }
233                    _ => value,
234                };
235                known.push((13, key, transformed));
236            }
237            "contributors" => {
238                let transformed = match &value {
239                    Value::Array(arr) => {
240                        let people: Vec<Value> = arr
241                            .iter()
242                            .map(|v| match v {
243                                Value::Object(o) => Value::Object(sort_people_object(o)),
244                                _ => v.clone(),
245                            })
246                            .collect();
247                        Value::Array(people)
248                    }
249                    _ => value,
250                };
251                known.push((14, key, transformed));
252            }
253            "donate" => {
254                let transformed = match &value {
255                    Value::Object(o) => {
256                        Value::Object(sort_object_by_key_order(o, &["type", "url"]))
257                    }
258                    _ => value,
259                };
260                known.push((15, key, transformed));
261            }
262            "funding" => {
263                let transformed = match &value {
264                    Value::Object(o) => {
265                        Value::Object(sort_object_by_key_order(o, &["type", "url"]))
266                    }
267                    _ => value,
268                };
269                known.push((16, key, transformed));
270            }
271            "sponsor" => {
272                let transformed = match &value {
273                    Value::Object(o) => {
274                        Value::Object(sort_object_by_key_order(o, &["type", "url"]))
275                    }
276                    _ => value,
277                };
278                known.push((17, key, transformed));
279            }
280            "license" => known.push((18, key, value)),
281            "qna" => known.push((19, key, value)),
282            "publisher" => known.push((20, key, value)),
283            "sideEffects" => known.push((21, key, value)),
284            "type" => known.push((22, key, value)),
285            "imports" => known.push((23, key, value)),
286            "exports" => {
287                let transformed = match &value {
288                    Value::Object(o) => Value::Object(sort_exports(o)),
289                    _ => value,
290                };
291                known.push((24, key, transformed));
292            }
293            "main" => known.push((25, key, value)),
294            "svelte" => known.push((26, key, value)),
295            "umd:main" => known.push((27, key, value)),
296            "jsdelivr" => known.push((28, key, value)),
297            "unpkg" => known.push((29, key, value)),
298            "module" => known.push((30, key, value)),
299            "esnext" => known.push((31, key, value)),
300            "es2020" => known.push((32, key, value)),
301            "esm2020" => known.push((33, key, value)),
302            "fesm2020" => known.push((34, key, value)),
303            "es2015" => known.push((35, key, value)),
304            "esm2015" => known.push((36, key, value)),
305            "fesm2015" => known.push((37, key, value)),
306            "es5" => known.push((38, key, value)),
307            "esm5" => known.push((39, key, value)),
308            "fesm5" => known.push((40, key, value)),
309            "source" => known.push((41, key, value)),
310            "jsnext:main" => known.push((42, key, value)),
311            "browser" => known.push((43, key, value)),
312            "umd" => known.push((44, key, value)),
313            "react-native" => known.push((45, key, value)),
314            "types" => known.push((46, key, value)),
315            "typesVersions" => known.push((47, key, value)),
316            "typings" => known.push((48, key, value)),
317            "style" => known.push((49, key, value)),
318            "example" => known.push((50, key, value)),
319            "examplestyle" => known.push((51, key, value)),
320            "assets" => known.push((52, key, value)),
321            "bin" => {
322                let transformed = match &value {
323                    Value::Object(o) => Value::Object(sort_object_alphabetically(o)),
324                    _ => value,
325                };
326                known.push((53, key, transformed));
327            }
328            "man" => known.push((54, key, value)),
329            "directories" => {
330                let transformed = match &value {
331                    Value::Object(o) => Value::Object(sort_object_by_key_order(
332                        o,
333                        &["lib", "bin", "man", "doc", "example", "test"],
334                    )),
335                    _ => value,
336                };
337                known.push((55, key, transformed));
338            }
339            "files" => {
340                let transformed = match &value {
341                    Value::Array(arr) => Value::Array(sort_array_unique(arr)),
342                    _ => value,
343                };
344                known.push((56, key, transformed));
345            }
346            "workspaces" => known.push((57, key, value)),
347            "binary" => {
348                let transformed = match &value {
349                    Value::Object(o) => Value::Object(sort_object_by_key_order(
350                        o,
351                        &["module_name", "module_path", "remote_path", "package_name", "host"],
352                    )),
353                    _ => value,
354                };
355                known.push((58, key, transformed));
356            }
357            "scripts" => known.push((59, key, value)),
358            "betterScripts" => known.push((60, key, value)),
359            "l10n" => known.push((61, key, value)),
360            "contributes" => known.push((62, key, value)),
361            "activationEvents" => {
362                let transformed = match &value {
363                    Value::Array(arr) => Value::Array(sort_array_unique(arr)),
364                    _ => value,
365                };
366                known.push((63, key, transformed));
367            }
368            "husky" => {
369                let transformed = match &value {
370                    Value::Object(o) => Value::Object(sort_object_recursive(o)),
371                    _ => value,
372                };
373                known.push((64, key, transformed));
374            }
375            "simple-git-hooks" => known.push((65, key, value)),
376            "pre-commit" => known.push((66, key, value)),
377            "commitlint" => {
378                let transformed = match &value {
379                    Value::Object(o) => Value::Object(sort_object_recursive(o)),
380                    _ => value,
381                };
382                known.push((67, key, transformed));
383            }
384            "lint-staged" => known.push((68, key, value)),
385            "nano-staged" => known.push((69, key, value)),
386            "resolutions" => {
387                let transformed = match &value {
388                    Value::Object(o) => Value::Object(sort_object_alphabetically(o)),
389                    _ => value,
390                };
391                known.push((70, key, transformed));
392            }
393            "overrides" => {
394                let transformed = match &value {
395                    Value::Object(o) => Value::Object(sort_object_alphabetically(o)),
396                    _ => value,
397                };
398                known.push((71, key, transformed));
399            }
400            "dependencies" => {
401                let transformed = match &value {
402                    Value::Object(o) => Value::Object(sort_object_alphabetically(o)),
403                    _ => value,
404                };
405                known.push((72, key, transformed));
406            }
407            "devDependencies" => {
408                let transformed = match &value {
409                    Value::Object(o) => Value::Object(sort_object_alphabetically(o)),
410                    _ => value,
411                };
412                known.push((73, key, transformed));
413            }
414            "dependenciesMeta" => known.push((74, key, value)),
415            "peerDependencies" => {
416                let transformed = match &value {
417                    Value::Object(o) => Value::Object(sort_object_alphabetically(o)),
418                    _ => value,
419                };
420                known.push((75, key, transformed));
421            }
422            "peerDependenciesMeta" => known.push((76, key, value)),
423            "optionalDependencies" => {
424                let transformed = match &value {
425                    Value::Object(o) => Value::Object(sort_object_alphabetically(o)),
426                    _ => value,
427                };
428                known.push((77, key, transformed));
429            }
430            "bundledDependencies" => {
431                let transformed = match &value {
432                    Value::Array(arr) => Value::Array(sort_array_unique(arr)),
433                    _ => value,
434                };
435                known.push((78, key, transformed));
436            }
437            "bundleDependencies" => {
438                let transformed = match &value {
439                    Value::Array(arr) => Value::Array(sort_array_unique(arr)),
440                    _ => value,
441                };
442                known.push((79, key, transformed));
443            }
444            "extensionPack" => {
445                let transformed = match &value {
446                    Value::Array(arr) => Value::Array(sort_array_unique(arr)),
447                    _ => value,
448                };
449                known.push((80, key, transformed));
450            }
451            "extensionDependencies" => {
452                let transformed = match &value {
453                    Value::Array(arr) => Value::Array(sort_array_unique(arr)),
454                    _ => value,
455                };
456                known.push((81, key, transformed));
457            }
458            "extensionKind" => {
459                let transformed = match &value {
460                    Value::Array(arr) => Value::Array(sort_array_unique(arr)),
461                    _ => value,
462                };
463                known.push((82, key, transformed));
464            }
465            "flat" => known.push((83, key, value)),
466            "packageManager" => known.push((84, key, value)),
467            "config" => {
468                let transformed = match &value {
469                    Value::Object(o) => Value::Object(sort_object_alphabetically(o)),
470                    _ => value,
471                };
472                known.push((85, key, transformed));
473            }
474            "nodemonConfig" => {
475                let transformed = match &value {
476                    Value::Object(o) => Value::Object(sort_object_recursive(o)),
477                    _ => value,
478                };
479                known.push((86, key, transformed));
480            }
481            "browserify" => {
482                let transformed = match &value {
483                    Value::Object(o) => Value::Object(sort_object_recursive(o)),
484                    _ => value,
485                };
486                known.push((87, key, transformed));
487            }
488            "babel" => {
489                let transformed = match &value {
490                    Value::Object(o) => Value::Object(sort_object_recursive(o)),
491                    _ => value,
492                };
493                known.push((88, key, transformed));
494            }
495            "browserslist" => known.push((89, key, value)),
496            "xo" => {
497                let transformed = match &value {
498                    Value::Object(o) => Value::Object(sort_object_recursive(o)),
499                    _ => value,
500                };
501                known.push((90, key, transformed));
502            }
503            "prettier" => {
504                let transformed = match &value {
505                    Value::Object(o) => Value::Object(sort_object_recursive(o)),
506                    _ => value,
507                };
508                known.push((91, key, transformed));
509            }
510            "eslintConfig" => {
511                let transformed = match &value {
512                    Value::Object(o) => Value::Object(sort_object_recursive(o)),
513                    _ => value,
514                };
515                known.push((92, key, transformed));
516            }
517            "eslintIgnore" => known.push((93, key, value)),
518            "npmpkgjsonlint" => known.push((94, key, value)),
519            "npmPackageJsonLintConfig" => known.push((95, key, value)),
520            "npmpackagejsonlint" => known.push((96, key, value)),
521            "release" => known.push((97, key, value)),
522            "remarkConfig" => {
523                let transformed = match &value {
524                    Value::Object(o) => Value::Object(sort_object_recursive(o)),
525                    _ => value,
526                };
527                known.push((98, key, transformed));
528            }
529            "stylelint" => {
530                let transformed = match &value {
531                    Value::Object(o) => Value::Object(sort_object_recursive(o)),
532                    _ => value,
533                };
534                known.push((99, key, transformed));
535            }
536            "ava" => {
537                let transformed = match &value {
538                    Value::Object(o) => Value::Object(sort_object_recursive(o)),
539                    _ => value,
540                };
541                known.push((100, key, transformed));
542            }
543            "jest" => {
544                let transformed = match &value {
545                    Value::Object(o) => Value::Object(sort_object_recursive(o)),
546                    _ => value,
547                };
548                known.push((101, key, transformed));
549            }
550            "jest-junit" => known.push((102, key, value)),
551            "jest-stare" => known.push((103, key, value)),
552            "mocha" => {
553                let transformed = match &value {
554                    Value::Object(o) => Value::Object(sort_object_recursive(o)),
555                    _ => value,
556                };
557                known.push((104, key, transformed));
558            }
559            "nyc" => {
560                let transformed = match &value {
561                    Value::Object(o) => Value::Object(sort_object_recursive(o)),
562                    _ => value,
563                };
564                known.push((105, key, transformed));
565            }
566            "c8" => {
567                let transformed = match &value {
568                    Value::Object(o) => Value::Object(sort_object_recursive(o)),
569                    _ => value,
570                };
571                known.push((106, key, transformed));
572            }
573            "tap" => known.push((107, key, value)),
574            "oclif" => {
575                let transformed = match &value {
576                    Value::Object(o) => Value::Object(sort_object_recursive(o)),
577                    _ => value,
578                };
579                known.push((108, key, transformed));
580            }
581            "engines" => {
582                let transformed = match &value {
583                    Value::Object(o) => Value::Object(sort_object_alphabetically(o)),
584                    _ => value,
585                };
586                known.push((109, key, transformed));
587            }
588            "engineStrict" => known.push((110, key, value)),
589            "volta" => {
590                let transformed = match &value {
591                    Value::Object(o) => Value::Object(sort_object_recursive(o)),
592                    _ => value,
593                };
594                known.push((111, key, transformed));
595            }
596            "languageName" => known.push((112, key, value)),
597            "os" => known.push((113, key, value)),
598            "cpu" => known.push((114, key, value)),
599            "libc" => {
600                let transformed = match &value {
601                    Value::Array(arr) => Value::Array(sort_array_unique(arr)),
602                    _ => value,
603                };
604                known.push((115, key, transformed));
605            }
606            "devEngines" => {
607                let transformed = match &value {
608                    Value::Object(o) => Value::Object(sort_object_alphabetically(o)),
609                    _ => value,
610                };
611                known.push((116, key, transformed));
612            }
613            "preferGlobal" => known.push((117, key, value)),
614            "publishConfig" => {
615                let transformed = match &value {
616                    Value::Object(o) => Value::Object(sort_object_alphabetically(o)),
617                    _ => value,
618                };
619                known.push((118, key, transformed));
620            }
621            "icon" => known.push((119, key, value)),
622            "badges" => known.push((120, key, value)),
623            "galleryBanner" => known.push((121, key, value)),
624            "preview" => known.push((122, key, value)),
625            "markdown" => known.push((123, key, value)),
626            "pnpm" => known.push((124, key, value)),
627            _ => {
628                // Unknown field - check if private
629                if key.starts_with('_') {
630                    private.push((key, value));
631                } else {
632                    non_private.push((key, value));
633                }
634            }
635        }
636    }
637
638    // Sort each category
639    known.sort_by_key(|(index, _, _)| *index);
640    non_private.sort_by(|(a, _), (b, _)| a.cmp(b));
641    private.sort_by(|(a, _), (b, _)| a.cmp(b));
642
643    // Build result map
644    let mut result = Map::new();
645
646    // Insert known fields (already transformed)
647    for (_index, key, value) in known {
648        result.insert(key, value);
649    }
650
651    // Insert non-private unknown fields
652    for (key, value) in non_private {
653        result.insert(key, value);
654    }
655
656    // Insert private fields
657    for (key, value) in private {
658        result.insert(key, value);
659    }
660
661    result
662}