Skip to content

Commit b2f9a18

Browse files
committed
feat(utils): readPackageJson
Signed-off-by: Lexus Drumgold <unicornware@flexdevelopment.llc>
1 parent bda813d commit b2f9a18

File tree

10 files changed

+165
-3
lines changed

10 files changed

+165
-3
lines changed

.dictionary.txt

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ iife
2222
keyid
2323
larsgw
2424
lcov
25+
lintstagedrc
2526
micnncim
2627
mkbuild
2728
nocheck

.eslintrc.base.cjs

+1-2
Original file line numberDiff line numberDiff line change
@@ -619,7 +619,7 @@ const config = {
619619
'unicorn/prefer-default-parameters': 2,
620620
'unicorn/prefer-export-from': [2, { ignoreUsedVariables: true }],
621621
'unicorn/prefer-includes': 2,
622-
'unicorn/prefer-json-parse-buffer': 2,
622+
'unicorn/prefer-json-parse-buffer': 0,
623623
'unicorn/prefer-math-trunc': 2,
624624
'unicorn/prefer-module': 2,
625625
'unicorn/prefer-negative-index': 2,
@@ -1058,7 +1058,6 @@ const config = {
10581058
'unicorn/no-useless-undefined': 0,
10591059
'unicorn/prefer-at': 0,
10601060
'unicorn/prefer-dom-node-append': 0,
1061-
'unicorn/prefer-json-parse-buffer': 0,
10621061
'unicorn/string-content': 0
10631062
}
10641063
},

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ codecov
4848
coverage/
4949
tsconfig.temp.json
5050
vitest.config.*.timestamp-*.mjs
51+
!__fixtures__/node_modules/
5152

5253
# ESLint
5354
# ------------------------------------------------------------------------------

.husky/pre-commit

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@
99

1010
yarn typecheck --changed HEAD^
1111
yarn check:types:build
12-
lint-staged
12+
lint-staged --config=.lintstagedrc.json

__fixtures__/node_modules/package-invalid-json/package.json

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
"@flex-development/import-regex": "2.0.1",
8080
"@flex-development/is-builtin": "1.0.1",
8181
"@flex-development/pathe": "1.0.3",
82+
"@flex-development/pkg-types": "2.0.0",
8283
"@flex-development/tsconfig-types": "2.0.1",
8384
"@flex-development/tutils": "6.0.0-alpha.7",
8485
"import-meta-resolve": "2.2.0",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* @file Unit Tests - readPackageJson
3+
* @module mlly/utils/tests/unit/readPackageJson
4+
*/
5+
6+
import { ErrorCode, NodeError } from '@flex-development/errnode'
7+
import pathe from '@flex-development/pathe'
8+
import type { PackageJson } from '@flex-development/pkg-types'
9+
import fs from 'node:fs'
10+
import { pathToFileURL } from 'node:url'
11+
import testSubject from '../read-package-json'
12+
13+
describe('unit:utils/readPackageJson', () => {
14+
it('should return PackageJson object', () => {
15+
// Arrange
16+
const pkg: PackageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'))
17+
const cases: Parameters<typeof testSubject>[0][] = [
18+
pathToFileURL(process.cwd()),
19+
pathToFileURL(process.cwd()).href,
20+
process.cwd(),
21+
undefined
22+
]
23+
24+
// Act + Expect
25+
cases.forEach(dir => expect(testSubject(dir)).deep.equal(pkg))
26+
})
27+
28+
it('should return null if package.json file does not exist', () => {
29+
expect(testSubject(pathe.resolve('dist'))).to.be.null
30+
})
31+
32+
it('should throw if package.json file is not valid json', () => {
33+
// Arrange
34+
const code: ErrorCode = ErrorCode.ERR_INVALID_PACKAGE_CONFIG
35+
const dir: string = '__fixtures__/node_modules/package-invalid-json'
36+
const err: string = `Invalid package config ${dir}/package.json`
37+
const parent: string = import.meta.url
38+
const specifier: string = 'package-invalid-json'
39+
const cases: [...Parameters<typeof testSubject>, string][] = [
40+
[dir, undefined, undefined, err],
41+
[dir, specifier, undefined, `${err} while importing '${specifier}'`],
42+
[
43+
dir,
44+
specifier,
45+
parent,
46+
`${err} while importing '${specifier}' from ${parent}`
47+
]
48+
]
49+
50+
// Act + Expect
51+
cases.forEach(([dir, specifier, parent, expected]) => {
52+
let error: NodeError
53+
54+
try {
55+
testSubject(dir, specifier, parent)
56+
} catch (e: unknown) {
57+
error = e as typeof error
58+
}
59+
60+
expect(error!).to.not.be.undefined
61+
expect(error!).to.have.property('code').equal(code)
62+
expect(error!).to.have.property('message').startWith(expected)
63+
})
64+
})
65+
})

src/utils/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export { default as findRequires } from './find-requires'
1111
export { default as findStaticImports } from './find-static-imports'
1212
export { default as hasCJSSyntax } from './has-cjs-syntax'
1313
export { default as hasESMSyntax } from './has-esm-syntax'
14+
export { default as readPackageJson } from './read-package-json'
1415
export { default as resolveAlias } from './resolve-alias'
1516
export { default as resolveAliases } from './resolve-aliases'
1617
export { default as resolveModule } from './resolve-module'

src/utils/read-package-json.ts

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* @file readPackageJson
3+
* @module mlly/utils/readPackageJson
4+
*/
5+
6+
import {
7+
ERR_INVALID_PACKAGE_CONFIG,
8+
type NodeError
9+
} from '@flex-development/errnode'
10+
import pathe from '@flex-development/pathe'
11+
import type { PackageJson } from '@flex-development/pkg-types'
12+
import type { Nullable } from '@flex-development/tutils'
13+
import fs from 'node:fs'
14+
import { URL, fileURLToPath } from 'node:url'
15+
16+
/**
17+
* Reads a `package.json` file from the given directory.
18+
*
19+
* Returns `null` if a file is not found.
20+
*
21+
* Throws [`ERR_INVALID_PACKAGE_CONFIG`][1] if the file found is not valid JSON.
22+
*
23+
* [1]: https://nodejs.org/api/errors.html#err_invalid_package_config
24+
*
25+
* @param {URL | string} [dir='.'] - Id of directory containing `package.json`
26+
* @param {string?} [specifier] - Module specifier passed by user to initiate
27+
* reading of `package.json` file
28+
* @param {string?} [parent] - Id of module `specifier` is relative to
29+
* @return {?PackageJson} `package.json` object or `null` if file is not found
30+
* @throws {NodeError} If `package.json` file does not contain valid JSON
31+
*/
32+
const readPackageJson = (
33+
dir: URL | string = '.',
34+
specifier?: string,
35+
parent?: string
36+
): Nullable<PackageJson> => {
37+
// ensure dir is a path
38+
if (dir instanceof URL) dir = dir.pathname
39+
else if (dir.startsWith('file:')) dir = fileURLToPath(dir)
40+
41+
/**
42+
* Full path to `package.json` file.
43+
*
44+
* @const {string} path
45+
*/
46+
const path: string = pathe.toNamespacedPath(pathe.join(dir, 'package.json'))
47+
48+
// return null if package.json file does not exist
49+
if (!fs.existsSync(path)) return null
50+
51+
/**
52+
* Possible `package.json` object.
53+
*
54+
* @var {PackageJson} pkg
55+
*/
56+
let pkg: PackageJson
57+
58+
// try parsing package.json file
59+
try {
60+
pkg = JSON.parse(fs.readFileSync(path, 'utf8'))
61+
} catch (e: unknown) {
62+
/**
63+
* String containing module specifier passed by user to initiate reading of
64+
* `package.json` file and the location the module specifier was imported
65+
* from.
66+
*
67+
* @var {string | undefined} base
68+
*/
69+
let base: string | undefined = specifier ? `'${specifier}'` : undefined
70+
71+
// add specifier import location
72+
if (base && parent) base += ` from ${parent}`
73+
74+
throw new ERR_INVALID_PACKAGE_CONFIG(path, base, (e as SyntaxError).message)
75+
}
76+
77+
return pkg
78+
}
79+
80+
export default readPackageJson

yarn.lock

+10
Original file line numberDiff line numberDiff line change
@@ -1352,6 +1352,7 @@ __metadata:
13521352
"@flex-development/is-builtin": "npm:1.0.1"
13531353
"@flex-development/mkbuild": "npm:1.0.0-alpha.9"
13541354
"@flex-development/pathe": "npm:1.0.3"
1355+
"@flex-development/pkg-types": "npm:2.0.0"
13551356
"@flex-development/tsconfig-types": "npm:2.0.1"
13561357
"@flex-development/tutils": "npm:6.0.0-alpha.7"
13571358
"@graphql-eslint/eslint-plugin": "npm:3.14.3"
@@ -1454,6 +1455,15 @@ __metadata:
14541455
languageName: node
14551456
linkType: hard
14561457

1458+
"@flex-development/pkg-types@npm:2.0.0":
1459+
version: 2.0.0
1460+
resolution: "@flex-development/pkg-types@npm:2.0.0::__archiveUrl=https%3A%2F%2Fnpm.pkg.github.com%2Fdownload%2F%40flex-development%2Fpkg-types%2F2.0.0%2F58267203fea93d31234e31d65e2058ed566b209a"
1461+
peerDependencies:
1462+
"@flex-development/tutils": ">=6.0.0-alpha.7"
1463+
checksum: 7a7ce8a3289f30e0d6aa0b5e5028c308a36956f779b9baf6f2d209842b01acc2bc645f85a541125f83f461d41b15262b3af65ff6cd3b3f727679216acf0520cb
1464+
languageName: node
1465+
linkType: hard
1466+
14571467
"@flex-development/tsconfig-types@npm:2.0.1":
14581468
version: 2.0.1
14591469
resolution: "@flex-development/tsconfig-types@npm:2.0.1::__archiveUrl=https%3A%2F%2Fnpm.pkg.github.com%2Fdownload%2F%40flex-development%2Ftsconfig-types%2F2.0.1%2F8e8d4ca7bdb91b10b634d4d02044f15f72c46ede"

0 commit comments

Comments
 (0)