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