Skip to content

Commit

Permalink
feat: adds ability to sort package.json on save (#133)
Browse files Browse the repository at this point in the history
Adds the ability to sort on save, for use with init (and possibly
addition of `npm pkg fix --sort`)

---------

Co-authored-by: Gar <gar+gh@danger.computer>
  • Loading branch information
reggi and wraithgar authored Nov 27, 2024
1 parent 914737e commit 4c22738
Show file tree
Hide file tree
Showing 8 changed files with 423 additions and 19 deletions.
9 changes: 7 additions & 2 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const updateScripts = require('./update-scripts.js')
const updateWorkspaces = require('./update-workspaces.js')
const normalize = require('./normalize.js')
const { read, parse } = require('./read-package.js')
const { packageSort } = require('./sort.js')

// a list of handy specialized helper functions that take
// care of special cases that are handled by the npm cli
Expand Down Expand Up @@ -230,19 +231,23 @@ class PackageJson {
return this
}

async save () {
async save ({ sort } = {}) {
if (!this.#canSave) {
throw new Error('No package.json to save to')
}
const {
[Symbol.for('indent')]: indent,
[Symbol.for('newline')]: newline,
...rest
} = this.content

const format = indent === undefined ? ' ' : indent
const eol = newline === undefined ? '\n' : newline

const content = sort ? packageSort(rest) : rest

const fileContent = `${
JSON.stringify(this.content, null, format)
JSON.stringify(content, null, format)
}\n`
.replace(/\n/g, eol)

Expand Down
101 changes: 101 additions & 0 deletions lib/sort.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* arbitrary sort order for package.json largely pulled from:
* https://github.com/keithamus/sort-package-json/blob/main/defaultRules.md
*
* cross checked with:
* https://github.com/npm/types/blob/main/types/index.d.ts#L104
* https://docs.npmjs.com/cli/configuring-npm/package-json
*/
function packageSort (json) {
const {
name,
version,
private: isPrivate,
description,
keywords,
homepage,
bugs,
repository,
funding,
license,
author,
maintainers,
contributors,
type,
imports,
exports,
main,
browser,
types,
bin,
man,
directories,
files,
workspaces,
scripts,
config,
dependencies,
devDependencies,
peerDependencies,
peerDependenciesMeta,
optionalDependencies,
bundledDependencies,
bundleDependencies,
engines,
os,
cpu,
publishConfig,
devEngines,
licenses,
overrides,
...rest
} = json

return {
...(typeof name !== 'undefined' ? { name } : {}),
...(typeof version !== 'undefined' ? { version } : {}),
...(typeof isPrivate !== 'undefined' ? { private: isPrivate } : {}),
...(typeof description !== 'undefined' ? { description } : {}),
...(typeof keywords !== 'undefined' ? { keywords } : {}),
...(typeof homepage !== 'undefined' ? { homepage } : {}),
...(typeof bugs !== 'undefined' ? { bugs } : {}),
...(typeof repository !== 'undefined' ? { repository } : {}),
...(typeof funding !== 'undefined' ? { funding } : {}),
...(typeof license !== 'undefined' ? { license } : {}),
...(typeof author !== 'undefined' ? { author } : {}),
...(typeof maintainers !== 'undefined' ? { maintainers } : {}),
...(typeof contributors !== 'undefined' ? { contributors } : {}),
...(typeof type !== 'undefined' ? { type } : {}),
...(typeof imports !== 'undefined' ? { imports } : {}),
...(typeof exports !== 'undefined' ? { exports } : {}),
...(typeof main !== 'undefined' ? { main } : {}),
...(typeof browser !== 'undefined' ? { browser } : {}),
...(typeof types !== 'undefined' ? { types } : {}),
...(typeof bin !== 'undefined' ? { bin } : {}),
...(typeof man !== 'undefined' ? { man } : {}),
...(typeof directories !== 'undefined' ? { directories } : {}),
...(typeof files !== 'undefined' ? { files } : {}),
...(typeof workspaces !== 'undefined' ? { workspaces } : {}),
...(typeof scripts !== 'undefined' ? { scripts } : {}),
...(typeof config !== 'undefined' ? { config } : {}),
...(typeof dependencies !== 'undefined' ? { dependencies } : {}),
...(typeof devDependencies !== 'undefined' ? { devDependencies } : {}),
...(typeof peerDependencies !== 'undefined' ? { peerDependencies } : {}),
...(typeof peerDependenciesMeta !== 'undefined' ? { peerDependenciesMeta } : {}),
...(typeof optionalDependencies !== 'undefined' ? { optionalDependencies } : {}),
...(typeof bundledDependencies !== 'undefined' ? { bundledDependencies } : {}),
...(typeof bundleDependencies !== 'undefined' ? { bundleDependencies } : {}),
...(typeof engines !== 'undefined' ? { engines } : {}),
...(typeof os !== 'undefined' ? { os } : {}),
...(typeof cpu !== 'undefined' ? { cpu } : {}),
...(typeof publishConfig !== 'undefined' ? { publishConfig } : {}),
...(typeof devEngines !== 'undefined' ? { devEngines } : {}),
...(typeof licenses !== 'undefined' ? { licenses } : {}),
...(typeof overrides !== 'undefined' ? { overrides } : {}),
...rest,
}
}

module.exports = {
packageSort,
}
32 changes: 16 additions & 16 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@
"name": "@npmcli/package-json",
"version": "6.0.1",
"description": "Programmatic API to update package.json",
"keywords": [
"npm",
"oss"
],
"repository": {
"type": "git",
"url": "git+https://github.com/npm/package-json.git"
},
"license": "ISC",
"author": "GitHub Inc.",
"main": "lib/index.js",
"files": [
"bin/",
Expand All @@ -18,19 +28,6 @@
"template-oss-apply": "template-oss-apply --force",
"eslint": "eslint \"**/*.{js,cjs,ts,mjs,jsx,tsx}\""
},
"keywords": [
"npm",
"oss"
],
"author": "GitHub Inc.",
"license": "ISC",
"devDependencies": {
"@npmcli/eslint-config": "^5.0.0",
"@npmcli/template-oss": "4.23.3",
"read-package-json": "^7.0.0",
"read-package-json-fast": "^4.0.0",
"tap": "^16.0.1"
},
"dependencies": {
"@npmcli/git": "^6.0.0",
"glob": "^10.2.2",
Expand All @@ -40,9 +37,12 @@
"proc-log": "^5.0.0",
"semver": "^7.5.3"
},
"repository": {
"type": "git",
"url": "git+https://github.com/npm/package-json.git"
"devDependencies": {
"@npmcli/eslint-config": "^5.0.0",
"@npmcli/template-oss": "4.23.3",
"read-package-json": "^7.0.0",
"read-package-json-fast": "^4.0.0",
"tap": "^16.0.1"
},
"engines": {
"node": "^18.17.0 || >=20.5.0"
Expand Down
119 changes: 119 additions & 0 deletions tap-snapshots/test/index.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,125 @@ exports[`test/index.js TAP load read, update content and write > should properly
`

exports[`test/index.js TAP load sorts on save > should properly save content to a package.json 1`] = `
{
"name": "foo",
"version": "1.0.0",
"description": "A sample package",
"keywords": [
"sample",
"package"
],
"homepage": "https://example.com",
"bugs": {
"url": "https://example.com/bugs",
"email": "bugs@example.com"
},
"repository": {
"type": "git",
"url": "https://example.com/repo.git"
},
"funding": "https://example.com/funding",
"license": "MIT",
"author": "Author Name <author@example.com>",
"maintainers": [
"Maintainer One <maintainer1@example.com>",
"Maintainer Two <maintainer2@example.com>"
],
"contributors": [
"Contributor One <contributor1@example.com>",
"Contributor Two <contributor2@example.com>"
],
"type": "module",
"imports": {
"#dep": "./src/dep.js"
},
"exports": {
".": "./src/index.js"
},
"main": "index.js",
"browser": "browser.js",
"types": "index.d.ts",
"bin": {
"my-cli": "./bin/cli.js"
},
"man": [
"./man/doc.1"
],
"directories": {
"lib": "lib",
"bin": "bin",
"man": "man"
},
"files": [
"lib/**/*.js",
"bin/**/*.js"
],
"workspaces": [
"packages/*"
],
"scripts": {
"start": "node index.js",
"test": "tap test/*.js"
},
"config": {
"port": "8080"
},
"dependencies": {
"some-dependency": "^1.0.0"
},
"devDependencies": {
"some-dev-dependency": "^1.0.0"
},
"peerDependencies": {
"some-peer-dependency": "^1.0.0"
},
"peerDependenciesMeta": {
"some-peer-dependency": {
"optional": true
}
},
"optionalDependencies": {
"some-optional-dependency": "^1.0.0"
},
"bundledDependencies": [
"some-bundled-dependency"
],
"bundleDependencies": [
"some-bundled-dependency"
],
"engines": {
"node": ">=14.0.0"
},
"os": [
"darwin",
"linux"
],
"cpu": [
"x64",
"arm64"
],
"publishConfig": {
"registry": "https://registry.example.com"
},
"devEngines": {
"node": ">=14.0.0"
},
"licenses": [
{
"type": "MIT",
"url": "https://opensource.org/licenses/MIT"
}
],
"overrides": {
"some-dependency": {
"some-sub-dependency": "1.0.0"
}
}
}
`

exports[`test/index.js TAP load update long package.json > should only update the defined property 1`] = `
{
"version": "7.18.1",
Expand Down
Loading

0 comments on commit 4c22738

Please sign in to comment.