@lntvow/sort-package-json 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +456 -0
- package/cli.js +138 -0
- package/index.cjs +704 -0
- package/index.d.ts +34 -0
- package/index.js +636 -0
- package/package.json +108 -0
- package/reporter.js +139 -0
package/index.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
|
|
3
|
+
type ComparatorFunction = (left: string, right: string) => number
|
|
4
|
+
|
|
5
|
+
interface Options {
|
|
6
|
+
readonly sortOrder?: readonly string[] | ComparatorFunction
|
|
7
|
+
readonly sortScripts?: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface SortPackageJson {
|
|
11
|
+
/**
|
|
12
|
+
* Sort packageJson object.
|
|
13
|
+
*
|
|
14
|
+
* @param packageJson - A packageJson.
|
|
15
|
+
* @param options - An options object.
|
|
16
|
+
* @returns Sorted packageJson object.
|
|
17
|
+
*/
|
|
18
|
+
<T extends Record<any, any>>(packageJson: T, options?: Options): T
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Sort packageJson string.
|
|
22
|
+
*
|
|
23
|
+
* @param packageJson - A packageJson string.
|
|
24
|
+
* @param options - An options object.
|
|
25
|
+
* @returns Sorted packageJson string.
|
|
26
|
+
*/
|
|
27
|
+
(packageJson: string, options?: Options): string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
declare const sortPackageJsonDefault: SortPackageJson
|
|
31
|
+
export default sortPackageJsonDefault
|
|
32
|
+
|
|
33
|
+
export const sortPackageJson: SortPackageJson
|
|
34
|
+
export const sortOrder: string[]
|
package/index.js
ADDED
|
@@ -0,0 +1,636 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import sortObjectKeys from 'sort-object-keys'
|
|
3
|
+
import detectIndent from 'detect-indent'
|
|
4
|
+
import { detectNewlineGraceful as detectNewline } from 'detect-newline'
|
|
5
|
+
import gitHooks from 'git-hooks-list'
|
|
6
|
+
import isPlainObject from 'is-plain-obj'
|
|
7
|
+
import semverCompare from 'semver/functions/compare.js'
|
|
8
|
+
import semverMinVersion from 'semver/ranges/min-version.js'
|
|
9
|
+
|
|
10
|
+
const pipe =
|
|
11
|
+
(fns) =>
|
|
12
|
+
(x, ...args) =>
|
|
13
|
+
fns.reduce((result, fn) => fn(result, ...args), x)
|
|
14
|
+
const onArray = (fn) => (x) => (Array.isArray(x) ? fn(x) : x)
|
|
15
|
+
const onStringArray = (fn) => (x) =>
|
|
16
|
+
Array.isArray(x) && x.every((item) => typeof item === 'string') ? fn(x) : x
|
|
17
|
+
const uniq = onStringArray((xs) => [...new Set(xs)])
|
|
18
|
+
const sortArray = onStringArray((array) => array.toSorted())
|
|
19
|
+
const uniqAndSortArray = pipe([uniq, sortArray])
|
|
20
|
+
const onObject =
|
|
21
|
+
(fn) =>
|
|
22
|
+
(x, ...args) =>
|
|
23
|
+
isPlainObject(x) ? fn(x, ...args) : x
|
|
24
|
+
const sortObjectBy = (comparator, deep) => {
|
|
25
|
+
const over = onObject((object) => {
|
|
26
|
+
if (deep) {
|
|
27
|
+
object = Object.fromEntries(
|
|
28
|
+
Object.entries(object).map(([key, value]) => [key, over(value)]),
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return sortObjectKeys(object, comparator)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
return over
|
|
36
|
+
}
|
|
37
|
+
const objectGroupBy =
|
|
38
|
+
// eslint-disable-next-line n/no-unsupported-features/es-builtins, n/no-unsupported-features/es-syntax -- Safe
|
|
39
|
+
Object.groupBy ||
|
|
40
|
+
// Remove this when we drop support for Node.js 20
|
|
41
|
+
((array, callback) => {
|
|
42
|
+
const result = Object.create(null)
|
|
43
|
+
for (const value of array) {
|
|
44
|
+
const key = callback(value)
|
|
45
|
+
if (result[key]) {
|
|
46
|
+
result[key].push(value)
|
|
47
|
+
} else {
|
|
48
|
+
result[key] = [value]
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return result
|
|
52
|
+
})
|
|
53
|
+
const sortObject = sortObjectBy()
|
|
54
|
+
const sortURLObject = sortObjectBy(['type', 'url'])
|
|
55
|
+
const sortPeopleObject = sortObjectBy(['name', 'email', 'url'])
|
|
56
|
+
const sortDirectories = sortObjectBy([
|
|
57
|
+
'lib',
|
|
58
|
+
'bin',
|
|
59
|
+
'man',
|
|
60
|
+
'doc',
|
|
61
|
+
'example',
|
|
62
|
+
'test',
|
|
63
|
+
])
|
|
64
|
+
const overProperty = (property, over) =>
|
|
65
|
+
onObject((object, ...args) =>
|
|
66
|
+
Object.hasOwn(object, property)
|
|
67
|
+
? { ...object, [property]: over(object[property], ...args) }
|
|
68
|
+
: object,
|
|
69
|
+
)
|
|
70
|
+
const sortGitHooks = sortObjectBy(gitHooks)
|
|
71
|
+
|
|
72
|
+
const parseNameAndVersionRange = (specifier) => {
|
|
73
|
+
// Ignore anything after > & rely on fallback alphanumeric sorting for that
|
|
74
|
+
const [nameAndVersion] = specifier.split('>')
|
|
75
|
+
const atMatches = [...nameAndVersion.matchAll('@')]
|
|
76
|
+
if (
|
|
77
|
+
!atMatches.length ||
|
|
78
|
+
(atMatches.length === 1 && atMatches[0].index === 0)
|
|
79
|
+
) {
|
|
80
|
+
return { name: specifier }
|
|
81
|
+
}
|
|
82
|
+
const splitIndex = atMatches.pop().index
|
|
83
|
+
return {
|
|
84
|
+
name: nameAndVersion.substring(0, splitIndex),
|
|
85
|
+
range: nameAndVersion.substring(splitIndex + 1),
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const sortObjectBySemver = sortObjectBy((a, b) => {
|
|
90
|
+
const { name: aName, range: aRange } = parseNameAndVersionRange(a)
|
|
91
|
+
const { name: bName, range: bRange } = parseNameAndVersionRange(b)
|
|
92
|
+
|
|
93
|
+
if (aName !== bName) {
|
|
94
|
+
return aName.localeCompare(bName, 'en')
|
|
95
|
+
}
|
|
96
|
+
if (!aRange) {
|
|
97
|
+
return -1
|
|
98
|
+
}
|
|
99
|
+
if (!bRange) {
|
|
100
|
+
return 1
|
|
101
|
+
}
|
|
102
|
+
return semverCompare(semverMinVersion(aRange), semverMinVersion(bRange))
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
const getPackageName = (ident) => {
|
|
106
|
+
const index = ident.indexOf('@', ident.startsWith('@') ? 1 : 0)
|
|
107
|
+
// This should not happen, unless user manually edit the package.json file
|
|
108
|
+
return index === -1 ? ident : ident.slice(0, index)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const sortObjectByIdent = (a, b) => {
|
|
112
|
+
const packageNameA = getPackageName(a)
|
|
113
|
+
const packageNameB = getPackageName(b)
|
|
114
|
+
|
|
115
|
+
if (packageNameA < packageNameB) return -1
|
|
116
|
+
if (packageNameA > packageNameB) return 1
|
|
117
|
+
return 0
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Cache by `process.cwd()` instead of a variable to allow user call `process.chdir()`
|
|
121
|
+
const cache = new Map()
|
|
122
|
+
const hasYarnOrPnpmFiles = () => {
|
|
123
|
+
const cwd = process.cwd()
|
|
124
|
+
if (!cache.has(cwd)) {
|
|
125
|
+
cache.set(
|
|
126
|
+
cwd,
|
|
127
|
+
fs.existsSync('yarn.lock') ||
|
|
128
|
+
fs.existsSync('.yarn/') ||
|
|
129
|
+
fs.existsSync('.yarnrc.yml') ||
|
|
130
|
+
fs.existsSync('pnpm-lock.yaml') ||
|
|
131
|
+
fs.existsSync('pnpm-workspace.yaml'),
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
return cache.get(cwd)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Detects the package manager from package.json and lock files
|
|
139
|
+
* @param {object} packageJson - The parsed package.json object
|
|
140
|
+
* @returns {boolean} - The detected package manager. Default to npm if not detected.
|
|
141
|
+
*/
|
|
142
|
+
function shouldSortDependenciesLikeNpm(packageJson) {
|
|
143
|
+
// https://github.com/nodejs/corepack
|
|
144
|
+
if (typeof packageJson.packageManager === 'string') {
|
|
145
|
+
return packageJson.packageManager.startsWith('npm@')
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (packageJson.devEngines?.packageManager?.name) {
|
|
149
|
+
return packageJson.devEngines.packageManager.name === 'npm'
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (packageJson.pnpm) {
|
|
153
|
+
return false
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Optimisation: Check if npm is explicit before reading FS.
|
|
157
|
+
if (packageJson.engines?.npm) {
|
|
158
|
+
return true
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (hasYarnOrPnpmFiles()) {
|
|
162
|
+
return false
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return true
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Sort dependencies alphabetically, detecting package manager to use the
|
|
170
|
+
* appropriate comparison. npm uses locale-aware comparison, yarn and pnpm use
|
|
171
|
+
* simple string comparison
|
|
172
|
+
*/
|
|
173
|
+
const sortDependencies = onObject((dependencies, packageJson) => {
|
|
174
|
+
// Avoid file access
|
|
175
|
+
if (Object.keys(dependencies).length < 2) {
|
|
176
|
+
return dependencies
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// sort deps like the npm CLI does (via the package @npmcli/package-json)
|
|
180
|
+
// https://github.com/npm/package-json/blob/b6465f44c727d6513db6898c7cbe41dd355cebe8/lib/update-dependencies.js#L8-L21
|
|
181
|
+
if (shouldSortDependenciesLikeNpm(packageJson)) {
|
|
182
|
+
return sortObjectKeys(dependencies, (a, b) => a.localeCompare(b, 'en'))
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return sortObjectKeys(dependencies)
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* "workspaces" can be an array (npm or yarn classic) or an object (pnpm/bun).
|
|
190
|
+
* In the case of an array, we do not want to alphabetically sort it in case
|
|
191
|
+
* scripts need to run in a specific order.
|
|
192
|
+
*
|
|
193
|
+
* @see https://docs.npmjs.com/cli/v7/using-npm/workspaces?v=true#running-commands-in-the-context-of-workspaces
|
|
194
|
+
*/
|
|
195
|
+
const sortWorkspaces = pipe([
|
|
196
|
+
sortObjectBy(['packages', 'catalog']),
|
|
197
|
+
overProperty('packages', uniqAndSortArray),
|
|
198
|
+
overProperty('catalog', sortDependencies),
|
|
199
|
+
])
|
|
200
|
+
|
|
201
|
+
// https://github.com/eslint/eslint/blob/acc0e47572a9390292b4e313b4a4bf360d236358/conf/config-schema.js
|
|
202
|
+
const eslintBaseConfigProperties = [
|
|
203
|
+
// `files` and `excludedFiles` are only on `overrides[]`
|
|
204
|
+
// for easier sort `overrides[]`,
|
|
205
|
+
// add them to here, so we don't need sort `overrides[]` twice
|
|
206
|
+
'files',
|
|
207
|
+
'excludedFiles',
|
|
208
|
+
// baseConfig
|
|
209
|
+
'env',
|
|
210
|
+
'parser',
|
|
211
|
+
'parserOptions',
|
|
212
|
+
'settings',
|
|
213
|
+
'plugins',
|
|
214
|
+
'extends',
|
|
215
|
+
'rules',
|
|
216
|
+
'overrides',
|
|
217
|
+
'globals',
|
|
218
|
+
'processor',
|
|
219
|
+
'noInlineConfig',
|
|
220
|
+
'reportUnusedDisableDirectives',
|
|
221
|
+
]
|
|
222
|
+
const sortEslintConfig = pipe([
|
|
223
|
+
sortObjectBy(eslintBaseConfigProperties),
|
|
224
|
+
overProperty('env', sortObject),
|
|
225
|
+
overProperty('globals', sortObject),
|
|
226
|
+
overProperty(
|
|
227
|
+
'overrides',
|
|
228
|
+
onArray((overrides) => overrides.map(sortEslintConfig)),
|
|
229
|
+
),
|
|
230
|
+
overProperty('parserOptions', sortObject),
|
|
231
|
+
overProperty(
|
|
232
|
+
'rules',
|
|
233
|
+
sortObjectBy(
|
|
234
|
+
(rule1, rule2) =>
|
|
235
|
+
rule1.split('/').length - rule2.split('/').length ||
|
|
236
|
+
rule1.localeCompare(rule2),
|
|
237
|
+
),
|
|
238
|
+
),
|
|
239
|
+
overProperty('settings', sortObject),
|
|
240
|
+
])
|
|
241
|
+
const sortVSCodeBadgeObject = sortObjectBy(['description', 'url', 'href'])
|
|
242
|
+
|
|
243
|
+
const sortPrettierConfig = pipe([
|
|
244
|
+
// sort keys alphabetically, but put `overrides` at bottom
|
|
245
|
+
onObject((config) =>
|
|
246
|
+
sortObjectKeys(config, [
|
|
247
|
+
...Object.keys(config)
|
|
248
|
+
.filter((key) => key !== 'overrides')
|
|
249
|
+
.sort(),
|
|
250
|
+
'overrides',
|
|
251
|
+
]),
|
|
252
|
+
),
|
|
253
|
+
// if `config.overrides` exists
|
|
254
|
+
overProperty(
|
|
255
|
+
'overrides',
|
|
256
|
+
// and `config.overrides` is an array
|
|
257
|
+
onArray((overrides) =>
|
|
258
|
+
overrides.map(
|
|
259
|
+
pipe([
|
|
260
|
+
// sort `config.overrides[]` alphabetically
|
|
261
|
+
sortObject,
|
|
262
|
+
// sort `config.overrides[].options` alphabetically
|
|
263
|
+
overProperty('options', sortObject),
|
|
264
|
+
]),
|
|
265
|
+
),
|
|
266
|
+
),
|
|
267
|
+
),
|
|
268
|
+
])
|
|
269
|
+
|
|
270
|
+
const sortVolta = sortObjectBy(['node', 'npm', 'yarn'])
|
|
271
|
+
const sortDevEngines = overProperty(
|
|
272
|
+
'packageManager',
|
|
273
|
+
sortObjectBy(['name', 'version', 'onFail']),
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
const pnpmBaseConfigProperties = [
|
|
277
|
+
'peerDependencyRules',
|
|
278
|
+
'neverBuiltDependencies',
|
|
279
|
+
'onlyBuiltDependencies',
|
|
280
|
+
'onlyBuiltDependenciesFile',
|
|
281
|
+
'allowedDeprecatedVersions',
|
|
282
|
+
'allowNonAppliedPatches',
|
|
283
|
+
'updateConfig',
|
|
284
|
+
'auditConfig',
|
|
285
|
+
'requiredScripts',
|
|
286
|
+
'supportedArchitectures',
|
|
287
|
+
'overrides',
|
|
288
|
+
'patchedDependencies',
|
|
289
|
+
'packageExtensions',
|
|
290
|
+
]
|
|
291
|
+
|
|
292
|
+
const sortPnpmConfig = pipe([
|
|
293
|
+
sortObjectBy(pnpmBaseConfigProperties, true),
|
|
294
|
+
overProperty('overrides', sortObjectBySemver),
|
|
295
|
+
])
|
|
296
|
+
|
|
297
|
+
// See https://docs.npmjs.com/misc/scripts
|
|
298
|
+
const defaultNpmScripts = new Set([
|
|
299
|
+
'install',
|
|
300
|
+
'pack',
|
|
301
|
+
'prepare',
|
|
302
|
+
'publish',
|
|
303
|
+
'restart',
|
|
304
|
+
'shrinkwrap',
|
|
305
|
+
'start',
|
|
306
|
+
'stop',
|
|
307
|
+
'test',
|
|
308
|
+
'uninstall',
|
|
309
|
+
'version',
|
|
310
|
+
])
|
|
311
|
+
|
|
312
|
+
const hasDevDependency = (dependency, packageJson) => {
|
|
313
|
+
return (
|
|
314
|
+
Object.hasOwn(packageJson, 'devDependencies') &&
|
|
315
|
+
Object.hasOwn(packageJson.devDependencies, dependency)
|
|
316
|
+
)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const runSRegExp =
|
|
320
|
+
/(?<=^|[\s&;<>|(])(?:run-s|npm-run-all2? .*(?:--sequential|--serial|-s))(?=$|[\s&;<>|)])/
|
|
321
|
+
|
|
322
|
+
const isSequentialScript = (command) =>
|
|
323
|
+
command.includes('*') && runSRegExp.test(command)
|
|
324
|
+
|
|
325
|
+
const hasSequentialScript = (packageJson) => {
|
|
326
|
+
if (
|
|
327
|
+
!hasDevDependency('npm-run-all', packageJson) &&
|
|
328
|
+
!hasDevDependency('npm-run-all2', packageJson)
|
|
329
|
+
) {
|
|
330
|
+
return false
|
|
331
|
+
}
|
|
332
|
+
const scripts = ['scripts', 'betterScripts'].flatMap((field) =>
|
|
333
|
+
packageJson[field] ? Object.values(packageJson[field]) : [],
|
|
334
|
+
)
|
|
335
|
+
return scripts.some((script) => isSequentialScript(script))
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function sortScriptNames(keys, prefix = '') {
|
|
339
|
+
const groupMap = new Map()
|
|
340
|
+
for (const key of keys) {
|
|
341
|
+
const rest = prefix ? key.slice(prefix.length + 1) : key
|
|
342
|
+
const idx = rest.indexOf(':')
|
|
343
|
+
if (idx > 0) {
|
|
344
|
+
const base = key.slice(0, (prefix ? prefix.length + 1 : 0) + idx)
|
|
345
|
+
if (!groupMap.has(base)) groupMap.set(base, [])
|
|
346
|
+
groupMap.get(base).push(key)
|
|
347
|
+
} else {
|
|
348
|
+
if (!groupMap.has(key)) groupMap.set(key, [])
|
|
349
|
+
groupMap.get(key).push(key)
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return Array.from(groupMap.keys())
|
|
353
|
+
.sort()
|
|
354
|
+
.flatMap((groupKey) => {
|
|
355
|
+
const children = groupMap.get(groupKey)
|
|
356
|
+
if (
|
|
357
|
+
children.length > 1 &&
|
|
358
|
+
children.some((k) => k !== groupKey && k.startsWith(groupKey + ':'))
|
|
359
|
+
) {
|
|
360
|
+
const direct = children
|
|
361
|
+
.filter((k) => k === groupKey || !k.startsWith(groupKey + ':'))
|
|
362
|
+
.sort()
|
|
363
|
+
const nested = children.filter((k) => k.startsWith(groupKey + ':'))
|
|
364
|
+
return [...direct, ...sortScriptNames(nested, groupKey)]
|
|
365
|
+
}
|
|
366
|
+
return children.sort()
|
|
367
|
+
})
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const sortScripts = onObject((scripts, packageJson) => {
|
|
371
|
+
let names = Object.keys(scripts)
|
|
372
|
+
const prefixable = new Set()
|
|
373
|
+
|
|
374
|
+
names = names.map((name) => {
|
|
375
|
+
const omitted = name.replace(/^(?:pre|post)/, '')
|
|
376
|
+
if (defaultNpmScripts.has(omitted) || names.includes(omitted)) {
|
|
377
|
+
prefixable.add(omitted)
|
|
378
|
+
return omitted
|
|
379
|
+
}
|
|
380
|
+
return name
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
if (!hasSequentialScript(packageJson)) {
|
|
384
|
+
names = sortScriptNames(names)
|
|
385
|
+
}
|
|
386
|
+
names = names.flatMap((key) =>
|
|
387
|
+
prefixable.has(key) ? [`pre${key}`, key, `post${key}`] : [key],
|
|
388
|
+
)
|
|
389
|
+
return sortObjectKeys(scripts, names)
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
/*
|
|
393
|
+
- Move `default` condition to bottom
|
|
394
|
+
*/
|
|
395
|
+
const sortConditions = (conditions) => {
|
|
396
|
+
const { defaultConditions = [], restConditions = [] } = objectGroupBy(
|
|
397
|
+
conditions,
|
|
398
|
+
(condition) => {
|
|
399
|
+
if (condition === 'default') {
|
|
400
|
+
return 'defaultConditions'
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return 'restConditions'
|
|
404
|
+
},
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
return [...restConditions, ...defaultConditions]
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const sortExports = onObject((exports) => {
|
|
411
|
+
const { paths = [], conditions = [] } = objectGroupBy(
|
|
412
|
+
Object.keys(exports),
|
|
413
|
+
(key) => (key.startsWith('.') ? 'paths' : 'conditions'),
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
return Object.fromEntries(
|
|
417
|
+
[...paths, ...sortConditions(conditions)].map((key) => [
|
|
418
|
+
key,
|
|
419
|
+
sortExports(exports[key]),
|
|
420
|
+
]),
|
|
421
|
+
)
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
// fields marked `vscode` are for `Visual Studio Code extension manifest` only
|
|
425
|
+
// https://code.visualstudio.com/api/references/extension-manifest
|
|
426
|
+
// Supported fields:
|
|
427
|
+
// publisher, displayName, categories, galleryBanner, preview, contributes,
|
|
428
|
+
// activationEvents, badges, markdown, qna, extensionPack,
|
|
429
|
+
// extensionDependencies, icon
|
|
430
|
+
|
|
431
|
+
// field.key{string}: field name
|
|
432
|
+
// field.over{function}: sort field subKey
|
|
433
|
+
const fields = [
|
|
434
|
+
{ key: '$schema' },
|
|
435
|
+
{ key: 'name' },
|
|
436
|
+
/* vscode */ { key: 'displayName' },
|
|
437
|
+
{ key: 'version' },
|
|
438
|
+
/* yarn */ { key: 'stableVersion' },
|
|
439
|
+
{ key: 'private' },
|
|
440
|
+
{ key: 'description' },
|
|
441
|
+
/* vscode */ { key: 'categories', over: uniq },
|
|
442
|
+
{ key: 'keywords', over: uniq },
|
|
443
|
+
{ key: 'homepage' },
|
|
444
|
+
{ key: 'bugs', over: sortObjectBy(['url', 'email']) },
|
|
445
|
+
{ key: 'repository', over: sortURLObject },
|
|
446
|
+
{ key: 'funding', over: sortURLObject },
|
|
447
|
+
{ key: 'license', over: sortURLObject },
|
|
448
|
+
/* vscode */ { key: 'qna' },
|
|
449
|
+
{ key: 'author', over: sortPeopleObject },
|
|
450
|
+
{
|
|
451
|
+
key: 'maintainers',
|
|
452
|
+
over: onArray((maintainers) => maintainers.map(sortPeopleObject)),
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
key: 'contributors',
|
|
456
|
+
over: onArray((contributors) => contributors.map(sortPeopleObject)),
|
|
457
|
+
},
|
|
458
|
+
/* vscode */ { key: 'publisher' },
|
|
459
|
+
{ key: 'sideEffects' },
|
|
460
|
+
{ key: 'type' },
|
|
461
|
+
{ key: 'imports' },
|
|
462
|
+
{ key: 'exports', over: sortExports },
|
|
463
|
+
{ key: 'main' },
|
|
464
|
+
{ key: 'svelte' },
|
|
465
|
+
{ key: 'umd:main' },
|
|
466
|
+
{ key: 'jsdelivr' },
|
|
467
|
+
{ key: 'unpkg' },
|
|
468
|
+
{ key: 'module' },
|
|
469
|
+
{ key: 'source' },
|
|
470
|
+
{ key: 'jsnext:main' },
|
|
471
|
+
{ key: 'browser' },
|
|
472
|
+
{ key: 'react-native' },
|
|
473
|
+
{ key: 'types' },
|
|
474
|
+
{ key: 'typesVersions' },
|
|
475
|
+
{ key: 'typings' },
|
|
476
|
+
{ key: 'style' },
|
|
477
|
+
{ key: 'example' },
|
|
478
|
+
{ key: 'examplestyle' },
|
|
479
|
+
{ key: 'assets' },
|
|
480
|
+
{ key: 'bin', over: sortObject },
|
|
481
|
+
{ key: 'man' },
|
|
482
|
+
{ key: 'directories', over: sortDirectories },
|
|
483
|
+
{ key: 'files', over: uniq },
|
|
484
|
+
{ key: 'workspaces', over: sortWorkspaces },
|
|
485
|
+
// node-pre-gyp https://www.npmjs.com/package/node-pre-gyp#1-add-new-entries-to-your-packagejson
|
|
486
|
+
{
|
|
487
|
+
key: 'binary',
|
|
488
|
+
over: sortObjectBy([
|
|
489
|
+
'module_name',
|
|
490
|
+
'module_path',
|
|
491
|
+
'remote_path',
|
|
492
|
+
'package_name',
|
|
493
|
+
'host',
|
|
494
|
+
]),
|
|
495
|
+
},
|
|
496
|
+
{ key: 'scripts', over: sortScripts },
|
|
497
|
+
{ key: 'betterScripts', over: sortScripts },
|
|
498
|
+
/* vscode */ { key: 'l10n' },
|
|
499
|
+
/* vscode */ { key: 'contributes', over: sortObject },
|
|
500
|
+
/* vscode */ { key: 'activationEvents', over: uniq },
|
|
501
|
+
{ key: 'husky', over: overProperty('hooks', sortGitHooks) },
|
|
502
|
+
{ key: 'simple-git-hooks', over: sortGitHooks },
|
|
503
|
+
{ key: 'pre-commit' },
|
|
504
|
+
{ key: 'commitlint', over: sortObject },
|
|
505
|
+
{ key: 'lint-staged' },
|
|
506
|
+
{ key: 'nano-staged' },
|
|
507
|
+
{ key: 'config', over: sortObject },
|
|
508
|
+
{ key: 'nodemonConfig', over: sortObject },
|
|
509
|
+
{ key: 'browserify', over: sortObject },
|
|
510
|
+
{ key: 'babel', over: sortObject },
|
|
511
|
+
{ key: 'browserslist' },
|
|
512
|
+
{ key: 'xo', over: sortObject },
|
|
513
|
+
{ key: 'prettier', over: sortPrettierConfig },
|
|
514
|
+
{ key: 'eslintConfig', over: sortEslintConfig },
|
|
515
|
+
{ key: 'eslintIgnore' },
|
|
516
|
+
{ key: 'npmpkgjsonlint', over: sortObject },
|
|
517
|
+
{ key: 'npmPackageJsonLintConfig', over: sortObject },
|
|
518
|
+
{ key: 'npmpackagejsonlint', over: sortObject },
|
|
519
|
+
{ key: 'release', over: sortObject },
|
|
520
|
+
{ key: 'remarkConfig', over: sortObject },
|
|
521
|
+
{ key: 'stylelint' },
|
|
522
|
+
{ key: 'ava', over: sortObject },
|
|
523
|
+
{ key: 'jest', over: sortObject },
|
|
524
|
+
{ key: 'jest-junit', over: sortObject },
|
|
525
|
+
{ key: 'jest-stare', over: sortObject },
|
|
526
|
+
{ key: 'mocha', over: sortObject },
|
|
527
|
+
{ key: 'nyc', over: sortObject },
|
|
528
|
+
{ key: 'c8', over: sortObject },
|
|
529
|
+
{ key: 'tap', over: sortObject },
|
|
530
|
+
{ key: 'oclif', over: sortObjectBy(undefined, true) },
|
|
531
|
+
{ key: 'resolutions', over: sortObject },
|
|
532
|
+
{ key: 'overrides', over: sortDependencies },
|
|
533
|
+
{ key: 'dependencies', over: sortDependencies },
|
|
534
|
+
{ key: 'devDependencies', over: sortDependencies },
|
|
535
|
+
{ key: 'dependenciesMeta', over: sortObjectBy(sortObjectByIdent, true) },
|
|
536
|
+
{ key: 'peerDependencies', over: sortDependencies },
|
|
537
|
+
// TODO: only sort depth = 2
|
|
538
|
+
{ key: 'peerDependenciesMeta', over: sortObjectBy(undefined, true) },
|
|
539
|
+
{ key: 'optionalDependencies', over: sortDependencies },
|
|
540
|
+
{ key: 'bundledDependencies', over: uniqAndSortArray },
|
|
541
|
+
{ key: 'bundleDependencies', over: uniqAndSortArray },
|
|
542
|
+
/* vscode */ { key: 'extensionPack', over: uniqAndSortArray },
|
|
543
|
+
/* vscode */ { key: 'extensionDependencies', over: uniqAndSortArray },
|
|
544
|
+
{ key: 'flat' },
|
|
545
|
+
{ key: 'packageManager' },
|
|
546
|
+
{ key: 'engines', over: sortObject },
|
|
547
|
+
{ key: 'engineStrict', over: sortObject },
|
|
548
|
+
{ key: 'devEngines', over: sortDevEngines },
|
|
549
|
+
{ key: 'volta', over: sortVolta },
|
|
550
|
+
{ key: 'languageName' },
|
|
551
|
+
{ key: 'os' },
|
|
552
|
+
{ key: 'cpu' },
|
|
553
|
+
{ key: 'preferGlobal', over: sortObject },
|
|
554
|
+
{ key: 'publishConfig', over: sortObject },
|
|
555
|
+
/* vscode */ { key: 'icon' },
|
|
556
|
+
/* vscode */ {
|
|
557
|
+
key: 'badges',
|
|
558
|
+
over: onArray((badge) => badge.map(sortVSCodeBadgeObject)),
|
|
559
|
+
},
|
|
560
|
+
/* vscode */ { key: 'galleryBanner', over: sortObject },
|
|
561
|
+
/* vscode */ { key: 'preview' },
|
|
562
|
+
/* vscode */ { key: 'markdown' },
|
|
563
|
+
{ key: 'pnpm', over: sortPnpmConfig },
|
|
564
|
+
]
|
|
565
|
+
|
|
566
|
+
const defaultSortOrder = fields.map(({ key }) => key)
|
|
567
|
+
const overFields = pipe(
|
|
568
|
+
fields
|
|
569
|
+
.map(({ key, over }) => (over ? overProperty(key, over) : undefined))
|
|
570
|
+
.filter(Boolean),
|
|
571
|
+
)
|
|
572
|
+
const overFieldsWithoutScripts = pipe(
|
|
573
|
+
fields
|
|
574
|
+
.map(({ key, over }) => {
|
|
575
|
+
if (!over) {
|
|
576
|
+
return undefined
|
|
577
|
+
}
|
|
578
|
+
if (key === 'scripts' || key === 'betterScripts') {
|
|
579
|
+
return undefined
|
|
580
|
+
}
|
|
581
|
+
return overProperty(key, over)
|
|
582
|
+
})
|
|
583
|
+
.filter(Boolean),
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
function editStringJSON(json, over) {
|
|
587
|
+
if (typeof json === 'string') {
|
|
588
|
+
const { indent, type } = detectIndent(json)
|
|
589
|
+
const endCharacters = json.slice(-1) === '\n' ? '\n' : ''
|
|
590
|
+
const newline = detectNewline(json)
|
|
591
|
+
json = JSON.parse(json)
|
|
592
|
+
|
|
593
|
+
let result =
|
|
594
|
+
JSON.stringify(over(json), null, type === 'tab' ? '\t' : indent) +
|
|
595
|
+
endCharacters
|
|
596
|
+
if (newline === '\r\n') {
|
|
597
|
+
result = result.replace(/\n/g, newline)
|
|
598
|
+
}
|
|
599
|
+
return result
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
return over(json)
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function sortPackageJson(jsonIsh, options = {}) {
|
|
606
|
+
const shouldSortScripts = options.sortScripts ?? false
|
|
607
|
+
const overFieldsForOptions = shouldSortScripts
|
|
608
|
+
? overFields
|
|
609
|
+
: overFieldsWithoutScripts
|
|
610
|
+
|
|
611
|
+
return editStringJSON(
|
|
612
|
+
jsonIsh,
|
|
613
|
+
onObject((json) => {
|
|
614
|
+
let sortOrder = options.sortOrder || defaultSortOrder
|
|
615
|
+
|
|
616
|
+
if (Array.isArray(sortOrder)) {
|
|
617
|
+
const keys = Object.keys(json)
|
|
618
|
+
const { privateKeys = [], publicKeys = [] } = objectGroupBy(
|
|
619
|
+
keys,
|
|
620
|
+
(key) => (key[0] === '_' ? 'privateKeys' : 'publicKeys'),
|
|
621
|
+
)
|
|
622
|
+
sortOrder = [
|
|
623
|
+
...sortOrder,
|
|
624
|
+
...defaultSortOrder,
|
|
625
|
+
...publicKeys.sort(),
|
|
626
|
+
...privateKeys.sort(),
|
|
627
|
+
]
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
return overFieldsForOptions(sortObjectKeys(json, sortOrder), json)
|
|
631
|
+
}),
|
|
632
|
+
)
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
export default sortPackageJson
|
|
636
|
+
export { sortPackageJson, defaultSortOrder as sortOrder }
|