1use serde_json::{Map, Value};
2
3#[derive(Debug, Clone)]
5pub struct SortOptions {
6 pub pretty: bool,
8 pub sort_scripts: bool,
10}
11
12impl Default for SortOptions {
13 fn default() -> Self {
14 Self { pretty: true, sort_scripts: false }
15 }
16}
17
18pub fn sort_package_json_with_options(
20 input: &str,
21 options: &SortOptions,
22) -> Result<String, serde_json::Error> {
23 const BOM: char = '\u{FEFF}';
25 let input_without_bom = input.strip_prefix(BOM).unwrap_or(input);
26 let has_bom = input_without_bom.len() != input.len();
27
28 let value: Value = serde_json::from_str(input_without_bom)?;
29
30 let sorted_value = if let Value::Object(obj) = value {
31 Value::Object(sort_object_keys(obj, options))
32 } else {
33 value
34 };
35
36 let result = if options.pretty {
37 let mut s = serde_json::to_string_pretty(&sorted_value)?;
38 s.push('\n');
39 s
40 } else {
41 serde_json::to_string(&sorted_value)?
42 };
43
44 if has_bom {
46 let mut output = String::with_capacity(BOM.len_utf8() + result.len());
47 output.push(BOM);
48 output.push_str(&result);
49 Ok(output)
50 } else {
51 Ok(result)
52 }
53}
54
55pub fn sort_package_json(input: &str) -> Result<String, serde_json::Error> {
57 sort_package_json_with_options(input, &SortOptions::default())
58}
59
60macro_rules! declare_field_order {
87 (
88 $key:ident, $value:ident, $known:ident, $non_private:ident, $private:ident;
89 [
90 $( $idx:literal => $field_name:literal $( => $transform:expr )? ),* $(,)?
91 ]
92 ) => {
93 {
94 $( let _ = $idx; )*
96
97 match $key.as_str() {
99 $(
100 $field_name => {
101 $known.push((
102 $idx,
103 $key,
104 declare_field_order!(@value $value $(, $transform)?)
105 ));
106 },
107 )*
108 _ => {
109 if $key.starts_with('_') {
111 $private.push(($key, $value));
112 } else {
113 $non_private.push(($key, $value));
114 }
115 }
116 }
117 }
118 };
119
120 (@value $value:ident) => { $value };
122
123 (@value $value:ident, $transform:expr) => { $transform };
125}
126
127fn transform_value<F>(value: Value, transform: F) -> Value
128where
129 F: FnOnce(Map<String, Value>) -> Map<String, Value>,
130{
131 match value {
132 Value::Object(o) => Value::Object(transform(o)),
133 _ => value,
134 }
135}
136
137fn transform_array<F>(value: Value, transform: F) -> Value
138where
139 F: FnOnce(Vec<Value>) -> Vec<Value>,
140{
141 match value {
142 Value::Array(arr) => Value::Array(transform(arr)),
143 _ => value,
144 }
145}
146
147fn transform_with_key_order(value: Value, key_order: &[&str]) -> Value {
148 transform_value(value, |o| sort_object_by_key_order(o, key_order))
149}
150
151fn sort_object_alphabetically(obj: Map<String, Value>) -> Map<String, Value> {
152 let mut entries: Vec<(String, Value)> = obj.into_iter().collect();
153 entries.sort_unstable_by(|(a, _), (b, _)| a.cmp(b));
154 entries.into_iter().collect()
155}
156
157fn sort_object_recursive(obj: Map<String, Value>) -> Map<String, Value> {
158 let mut entries: Vec<(String, Value)> = obj.into_iter().collect();
159 entries.sort_unstable_by(|(a, _), (b, _)| a.cmp(b));
160
161 entries
162 .into_iter()
163 .map(|(key, value)| {
164 let transformed_value = match value {
165 Value::Object(nested) => Value::Object(sort_object_recursive(nested)),
166 _ => value,
167 };
168 (key, transformed_value)
169 })
170 .collect()
171}
172
173fn sort_array_unique(mut arr: Vec<Value>) -> Vec<Value> {
174 arr.retain(|v| v.is_string());
176
177 arr.sort_unstable_by(|a, b| a.as_str().unwrap().cmp(b.as_str().unwrap()));
179
180 arr.dedup_by(|a, b| a.as_str() == b.as_str());
182
183 arr
184}
185
186fn dedupe_array(arr: Vec<Value>) -> Vec<Value> {
189 let mut seen: Vec<&str> = Vec::new();
190 let keep: Vec<bool> = arr
191 .iter()
192 .map(|v| {
193 v.as_str().is_some_and(|s| {
194 if seen.contains(&s) {
195 false
196 } else {
197 seen.push(s);
198 true
199 }
200 })
201 })
202 .collect();
203 arr.into_iter().zip(keep).filter_map(|(v, keep)| keep.then_some(v)).collect()
204}
205
206fn sort_object_by_key_order(mut obj: Map<String, Value>, key_order: &[&str]) -> Map<String, Value> {
207 let mut result = Map::with_capacity(obj.len());
209
210 for &key in key_order {
212 if let Some(value) = obj.remove(key) {
213 result.insert(key.into(), value);
214 }
215 }
216
217 let mut remaining: Vec<(String, Value)> = obj.into_iter().collect();
219 remaining.sort_unstable_by(|(a, _), (b, _)| a.cmp(b));
220
221 for (key, value) in remaining {
222 result.insert(key, value);
223 }
224
225 result
226}
227
228fn sort_people_object(obj: Map<String, Value>) -> Map<String, Value> {
229 sort_object_by_key_order(obj, &["name", "email", "url"])
230}
231
232fn sort_exports(obj: Map<String, Value>) -> Map<String, Value> {
233 let mut paths = Vec::new();
234 let mut types_conds = Vec::new();
235 let mut other_conds = Vec::new();
236 let mut default_cond = None;
237
238 for (key, value) in obj {
239 if key.starts_with('.') {
240 paths.push((key, value));
241 } else if key == "default" {
242 default_cond = Some((key, value));
243 } else if key == "types" || key.starts_with("types@") {
244 types_conds.push((key, value));
245 } else {
246 other_conds.push((key, value));
247 }
248 }
249
250 let mut result = Map::new();
251
252 for (key, value) in paths {
254 let transformed = match value {
255 Value::Object(nested) => Value::Object(sort_exports(nested)),
256 _ => value,
257 };
258 result.insert(key, transformed);
259 }
260
261 for (key, value) in types_conds {
262 let transformed = match value {
263 Value::Object(nested) => Value::Object(sort_exports(nested)),
264 _ => value,
265 };
266 result.insert(key, transformed);
267 }
268
269 for (key, value) in other_conds {
270 let transformed = match value {
271 Value::Object(nested) => Value::Object(sort_exports(nested)),
272 _ => value,
273 };
274 result.insert(key, transformed);
275 }
276
277 if let Some((key, value)) = default_cond {
278 let transformed = match value {
279 Value::Object(nested) => Value::Object(sort_exports(nested)),
280 _ => value,
281 };
282 result.insert(key, transformed);
283 }
284
285 result
286}
287
288fn sort_object_keys(obj: Map<String, Value>, options: &SortOptions) -> Map<String, Value> {
289 let mut known: Vec<(usize, String, Value)> = Vec::new(); let mut non_private: Vec<(String, Value)> = Vec::new();
292 let mut private: Vec<(String, Value)> = Vec::new();
293
294 for (key, value) in obj {
296 declare_field_order!(key, value, known, non_private, private; [
297 0 => "$schema",
299 1 => "name",
300 2 => "displayName",
301 3 => "version",
302 4 => "stableVersion",
303 5 => "gitHead",
304 6 => "private",
305 7 => "description",
306 8 => "categories" => transform_array(value, sort_array_unique),
307 9 => "keywords" => transform_array(value, sort_array_unique),
308 10 => "homepage",
309 11 => "bugs" => transform_with_key_order(value, &["url", "email"]),
310 12 => "license",
312 13 => "author" => transform_value(value, sort_people_object),
313 14 => "maintainers",
314 15 => "contributors",
315 16 => "repository" => transform_with_key_order(value, &["type", "url"]),
317 17 => "funding" => transform_with_key_order(value, &["type", "url"]),
318 18 => "donate" => transform_with_key_order(value, &["type", "url"]),
319 19 => "sponsor" => transform_with_key_order(value, &["type", "url"]),
320 20 => "qna",
321 21 => "publisher",
322 22 => "man",
324 23 => "style",
325 24 => "example",
326 25 => "examplestyle",
327 26 => "assets",
328 27 => "bin" => transform_value(value, sort_object_alphabetically),
329 28 => "source",
330 29 => "directories" => transform_with_key_order(value, &["lib", "bin", "man", "doc", "example", "test"]),
331 30 => "workspaces",
332 31 => "binary" => transform_with_key_order(value, &["module_name", "module_path", "remote_path", "package_name", "host"]),
333 32 => "files" => transform_array(value, dedupe_array),
334 33 => "os",
335 34 => "cpu",
336 35 => "libc" => transform_array(value, sort_array_unique),
337 36 => "type",
339 37 => "sideEffects",
340 38 => "main",
341 39 => "module",
342 40 => "browser",
343 41 => "types",
344 42 => "typings",
345 43 => "typesVersions",
346 44 => "typeScriptVersion",
347 45 => "typesPublisherContentHash",
348 46 => "react-native",
349 47 => "svelte",
350 48 => "unpkg",
351 49 => "jsdelivr",
352 50 => "jsnext:main",
353 51 => "umd",
354 52 => "umd:main",
355 53 => "es5",
356 54 => "esm5",
357 55 => "fesm5",
358 56 => "es2015",
359 57 => "esm2015",
360 58 => "fesm2015",
361 59 => "es2020",
362 60 => "esm2020",
363 61 => "fesm2020",
364 62 => "esnext",
365 63 => "imports",
366 64 => "exports" => transform_value(value, sort_exports),
367 65 => "publishConfig" => transform_value(value, sort_object_alphabetically),
368 66 => "scripts" => if options.sort_scripts { transform_value(value, sort_object_alphabetically) } else { value },
370 67 => "betterScripts" => if options.sort_scripts { transform_value(value, sort_object_alphabetically) } else { value },
371 68 => "dependencies" => transform_value(value, sort_object_alphabetically),
373 69 => "devDependencies" => transform_value(value, sort_object_alphabetically),
374 70 => "dependenciesMeta",
375 71 => "peerDependencies" => transform_value(value, sort_object_alphabetically),
376 72 => "peerDependenciesMeta",
377 73 => "optionalDependencies" => transform_value(value, sort_object_alphabetically),
378 74 => "bundledDependencies" => transform_array(value, sort_array_unique),
379 75 => "bundleDependencies" => transform_array(value, sort_array_unique),
380 76 => "resolutions" => transform_value(value, sort_object_alphabetically),
381 77 => "overrides" => transform_value(value, sort_object_alphabetically),
382 78 => "husky" => transform_value(value, sort_object_recursive),
384 79 => "simple-git-hooks",
385 80 => "pre-commit",
386 81 => "lint-staged",
387 82 => "nano-staged",
388 83 => "commitlint" => transform_value(value, sort_object_recursive),
389 84 => "l10n",
391 85 => "contributes",
392 86 => "activationEvents" => transform_array(value, sort_array_unique),
393 87 => "extensionPack" => transform_array(value, sort_array_unique),
394 88 => "extensionDependencies" => transform_array(value, sort_array_unique),
395 89 => "extensionKind" => transform_array(value, sort_array_unique),
396 90 => "icon",
397 91 => "badges",
398 92 => "galleryBanner",
399 93 => "preview",
400 94 => "markdown",
401 95 => "napi" => transform_value(value, sort_object_alphabetically),
403 96 => "flat",
404 97 => "config" => transform_value(value, sort_object_alphabetically),
405 98 => "nodemonConfig" => transform_value(value, sort_object_recursive),
406 99 => "browserify" => transform_value(value, sort_object_recursive),
407 100 => "babel" => transform_value(value, sort_object_recursive),
408 101 => "browserslist",
409 102 => "xo" => transform_value(value, sort_object_recursive),
410 103 => "prettier" => transform_value(value, sort_object_recursive),
411 104 => "eslintConfig" => transform_value(value, sort_object_recursive),
412 105 => "eslintIgnore",
413 106 => "standard" => transform_value(value, sort_object_recursive),
414 107 => "npmpkgjsonlint",
415 108 => "npmPackageJsonLintConfig",
416 109 => "npmpackagejsonlint",
417 110 => "release",
418 111 => "auto-changelog" => transform_value(value, sort_object_recursive),
419 112 => "remarkConfig" => transform_value(value, sort_object_recursive),
420 113 => "stylelint" => transform_value(value, sort_object_recursive),
421 114 => "typescript" => transform_value(value, sort_object_recursive),
422 115 => "typedoc" => transform_value(value, sort_object_recursive),
423 116 => "tshy" => transform_value(value, sort_object_recursive),
424 117 => "tsdown" => transform_value(value, sort_object_recursive),
425 118 => "size-limit",
426 119 => "ava" => transform_value(value, sort_object_recursive),
428 120 => "jest" => transform_value(value, sort_object_recursive),
429 121 => "jest-junit",
430 122 => "jest-stare",
431 123 => "mocha" => transform_value(value, sort_object_recursive),
432 124 => "nyc" => transform_value(value, sort_object_recursive),
433 125 => "c8" => transform_value(value, sort_object_recursive),
434 126 => "tap",
435 127 => "tsd" => transform_value(value, sort_object_recursive),
436 128 => "typeCoverage" => transform_value(value, sort_object_recursive),
437 129 => "oclif" => transform_value(value, sort_object_recursive),
438 130 => "languageName",
440 131 => "preferGlobal",
441 132 => "devEngines" => transform_value(value, sort_object_alphabetically),
442 133 => "engines" => transform_value(value, sort_object_alphabetically),
443 134 => "engineStrict",
444 135 => "volta" => transform_value(value, sort_object_recursive),
445 136 => "packageManager",
446 137 => "pnpm",
447 ]);
448 }
449
450 known.sort_unstable_by_key(|(index, _, _)| *index);
452 non_private.sort_unstable_by(|(a, _), (b, _)| a.cmp(b));
453 private.sort_unstable_by(|(a, _), (b, _)| a.cmp(b));
454
455 let mut result = Map::new();
457
458 for (_index, key, value) in known {
460 result.insert(key, value);
461 }
462
463 for (key, value) in non_private {
465 result.insert(key, value);
466 }
467
468 for (key, value) in private {
470 result.insert(key, value);
471 }
472
473 result
474}