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