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 sort_paths_naturally(mut arr: Vec<Value>) -> Vec<Value> {
187 arr.retain(|v| v.is_string());
189 arr.sort_unstable_by(|a, b| a.as_str().unwrap().cmp(b.as_str().unwrap()));
190 arr.dedup_by(|a, b| a.as_str() == b.as_str());
191
192 let mut with_keys: Vec<(usize, String, Value)> = arr
195 .into_iter()
196 .map(|v| {
197 let s = v.as_str().unwrap();
198 let depth = s.matches('/').count();
199 let lowercase = s.to_lowercase();
200 (depth, lowercase, v)
201 })
202 .collect();
203
204 with_keys.sort_unstable_by(|(depth_a, lower_a, _), (depth_b, lower_b, _)| {
206 depth_a.cmp(depth_b).then_with(|| lower_a.cmp(lower_b))
207 });
208
209 with_keys.into_iter().map(|(_, _, v)| v).collect()
211}
212
213fn sort_object_by_key_order(mut obj: Map<String, Value>, key_order: &[&str]) -> Map<String, Value> {
214 let mut result = Map::with_capacity(obj.len());
216
217 for &key in key_order {
219 if let Some(value) = obj.remove(key) {
220 result.insert(key.into(), value);
221 }
222 }
223
224 let mut remaining: Vec<(String, Value)> = obj.into_iter().collect();
226 remaining.sort_unstable_by(|(a, _), (b, _)| a.cmp(b));
227
228 for (key, value) in remaining {
229 result.insert(key, value);
230 }
231
232 result
233}
234
235fn sort_people_object(obj: Map<String, Value>) -> Map<String, Value> {
236 sort_object_by_key_order(obj, &["name", "email", "url"])
237}
238
239fn sort_exports(obj: Map<String, Value>) -> Map<String, Value> {
240 let mut paths = Vec::new();
241 let mut types_conds = Vec::new();
242 let mut other_conds = Vec::new();
243 let mut default_cond = None;
244
245 for (key, value) in obj {
246 if key.starts_with('.') {
247 paths.push((key, value));
248 } else if key == "default" {
249 default_cond = Some((key, value));
250 } else if key == "types" || key.starts_with("types@") {
251 types_conds.push((key, value));
252 } else {
253 other_conds.push((key, value));
254 }
255 }
256
257 let mut result = Map::new();
258
259 for (key, value) in paths {
261 let transformed = match value {
262 Value::Object(nested) => Value::Object(sort_exports(nested)),
263 _ => value,
264 };
265 result.insert(key, transformed);
266 }
267
268 for (key, value) in types_conds {
269 let transformed = match value {
270 Value::Object(nested) => Value::Object(sort_exports(nested)),
271 _ => value,
272 };
273 result.insert(key, transformed);
274 }
275
276 for (key, value) in other_conds {
277 let transformed = match value {
278 Value::Object(nested) => Value::Object(sort_exports(nested)),
279 _ => value,
280 };
281 result.insert(key, transformed);
282 }
283
284 if let Some((key, value)) = default_cond {
285 let transformed = match value {
286 Value::Object(nested) => Value::Object(sort_exports(nested)),
287 _ => value,
288 };
289 result.insert(key, transformed);
290 }
291
292 result
293}
294
295fn sort_object_keys(obj: Map<String, Value>, options: &SortOptions) -> Map<String, Value> {
296 let mut known: Vec<(usize, String, Value)> = Vec::new(); let mut non_private: Vec<(String, Value)> = Vec::new();
299 let mut private: Vec<(String, Value)> = Vec::new();
300
301 for (key, value) in obj {
303 declare_field_order!(key, value, known, non_private, private; [
304 0 => "$schema",
306 1 => "name",
307 2 => "displayName",
308 3 => "version",
309 4 => "stableVersion",
310 5 => "gitHead",
311 6 => "private",
312 7 => "description",
313 8 => "categories" => transform_array(value, sort_array_unique),
314 9 => "keywords" => transform_array(value, sort_array_unique),
315 10 => "homepage",
316 11 => "bugs" => transform_with_key_order(value, &["url", "email"]),
317 12 => "license",
319 13 => "author" => transform_value(value, sort_people_object),
320 14 => "maintainers",
321 15 => "contributors",
322 16 => "repository" => transform_with_key_order(value, &["type", "url"]),
324 17 => "funding" => transform_with_key_order(value, &["type", "url"]),
325 18 => "donate" => transform_with_key_order(value, &["type", "url"]),
326 19 => "sponsor" => transform_with_key_order(value, &["type", "url"]),
327 20 => "qna",
328 21 => "publisher",
329 22 => "man",
331 23 => "style",
332 24 => "example",
333 25 => "examplestyle",
334 26 => "assets",
335 27 => "bin" => transform_value(value, sort_object_alphabetically),
336 28 => "source",
337 29 => "directories" => transform_with_key_order(value, &["lib", "bin", "man", "doc", "example", "test"]),
338 30 => "workspaces",
339 31 => "binary" => transform_with_key_order(value, &["module_name", "module_path", "remote_path", "package_name", "host"]),
340 32 => "files" => transform_array(value, sort_paths_naturally),
341 33 => "os",
342 34 => "cpu",
343 35 => "libc" => transform_array(value, sort_array_unique),
344 36 => "type",
346 37 => "sideEffects",
347 38 => "main",
348 39 => "module",
349 40 => "browser",
350 41 => "types",
351 42 => "typings",
352 43 => "typesVersions",
353 44 => "typeScriptVersion",
354 45 => "typesPublisherContentHash",
355 46 => "react-native",
356 47 => "svelte",
357 48 => "unpkg",
358 49 => "jsdelivr",
359 50 => "jsnext:main",
360 51 => "umd",
361 52 => "umd:main",
362 53 => "es5",
363 54 => "esm5",
364 55 => "fesm5",
365 56 => "es2015",
366 57 => "esm2015",
367 58 => "fesm2015",
368 59 => "es2020",
369 60 => "esm2020",
370 61 => "fesm2020",
371 62 => "esnext",
372 63 => "imports",
373 64 => "exports" => transform_value(value, sort_exports),
374 65 => "publishConfig" => transform_value(value, sort_object_alphabetically),
375 66 => "scripts" => if options.sort_scripts { transform_value(value, sort_object_alphabetically) } else { value },
377 67 => "betterScripts" => if options.sort_scripts { transform_value(value, sort_object_alphabetically) } else { value },
378 68 => "dependencies" => transform_value(value, sort_object_alphabetically),
380 69 => "devDependencies" => transform_value(value, sort_object_alphabetically),
381 70 => "dependenciesMeta",
382 71 => "peerDependencies" => transform_value(value, sort_object_alphabetically),
383 72 => "peerDependenciesMeta",
384 73 => "optionalDependencies" => transform_value(value, sort_object_alphabetically),
385 74 => "bundledDependencies" => transform_array(value, sort_array_unique),
386 75 => "bundleDependencies" => transform_array(value, sort_array_unique),
387 76 => "resolutions" => transform_value(value, sort_object_alphabetically),
388 77 => "overrides" => transform_value(value, sort_object_alphabetically),
389 78 => "husky" => transform_value(value, sort_object_recursive),
391 79 => "simple-git-hooks",
392 80 => "pre-commit",
393 81 => "lint-staged",
394 82 => "nano-staged",
395 83 => "commitlint" => transform_value(value, sort_object_recursive),
396 84 => "l10n",
398 85 => "contributes",
399 86 => "activationEvents" => transform_array(value, sort_array_unique),
400 87 => "extensionPack" => transform_array(value, sort_array_unique),
401 88 => "extensionDependencies" => transform_array(value, sort_array_unique),
402 89 => "extensionKind" => transform_array(value, sort_array_unique),
403 90 => "icon",
404 91 => "badges",
405 92 => "galleryBanner",
406 93 => "preview",
407 94 => "markdown",
408 95 => "napi" => transform_value(value, sort_object_alphabetically),
410 96 => "flat",
411 97 => "config" => transform_value(value, sort_object_alphabetically),
412 98 => "nodemonConfig" => transform_value(value, sort_object_recursive),
413 99 => "browserify" => transform_value(value, sort_object_recursive),
414 100 => "babel" => transform_value(value, sort_object_recursive),
415 101 => "browserslist",
416 102 => "xo" => transform_value(value, sort_object_recursive),
417 103 => "prettier" => transform_value(value, sort_object_recursive),
418 104 => "eslintConfig" => transform_value(value, sort_object_recursive),
419 105 => "eslintIgnore",
420 106 => "standard" => transform_value(value, sort_object_recursive),
421 107 => "npmpkgjsonlint",
422 108 => "npmPackageJsonLintConfig",
423 109 => "npmpackagejsonlint",
424 110 => "release",
425 111 => "auto-changelog" => transform_value(value, sort_object_recursive),
426 112 => "remarkConfig" => transform_value(value, sort_object_recursive),
427 113 => "stylelint" => transform_value(value, sort_object_recursive),
428 114 => "typescript" => transform_value(value, sort_object_recursive),
429 115 => "typedoc" => transform_value(value, sort_object_recursive),
430 116 => "tshy" => transform_value(value, sort_object_recursive),
431 117 => "tsdown" => transform_value(value, sort_object_recursive),
432 118 => "size-limit",
433 119 => "ava" => transform_value(value, sort_object_recursive),
435 120 => "jest" => transform_value(value, sort_object_recursive),
436 121 => "jest-junit",
437 122 => "jest-stare",
438 123 => "mocha" => transform_value(value, sort_object_recursive),
439 124 => "nyc" => transform_value(value, sort_object_recursive),
440 125 => "c8" => transform_value(value, sort_object_recursive),
441 126 => "tap",
442 127 => "tsd" => transform_value(value, sort_object_recursive),
443 128 => "typeCoverage" => transform_value(value, sort_object_recursive),
444 129 => "oclif" => transform_value(value, sort_object_recursive),
445 130 => "languageName",
447 131 => "preferGlobal",
448 132 => "devEngines" => transform_value(value, sort_object_alphabetically),
449 133 => "engines" => transform_value(value, sort_object_alphabetically),
450 134 => "engineStrict",
451 135 => "volta" => transform_value(value, sort_object_recursive),
452 136 => "packageManager",
453 137 => "pnpm",
454 ]);
455 }
456
457 known.sort_unstable_by_key(|(index, _, _)| *index);
459 non_private.sort_unstable_by(|(a, _), (b, _)| a.cmp(b));
460 private.sort_unstable_by(|(a, _), (b, _)| a.cmp(b));
461
462 let mut result = Map::new();
464
465 for (_index, key, value) in known {
467 result.insert(key, value);
468 }
469
470 for (key, value) in non_private {
472 result.insert(key, value);
473 }
474
475 for (key, value) in private {
477 result.insert(key, value);
478 }
479
480 result
481}