Skip to content

Commit 6781565

Browse files
committed
fix(paths/packages): treat only the literal basename as a manifest
`resolvePackageJsonDirname` and `resolvePackageJsonPath` used `filepath.endsWith('package.json')`, which matched any filename whose suffix happened to be `package.json` — e.g. `/foo/my-package.json`. The first would strip it to `/foo`; the second would keep it verbatim and skip the append. Now both gate on `path === 'package.json'` or a literal `/package.json`/`\package.json` suffix, so only real manifests are classified as manifests regardless of the host separator. Adds regression tests for `my-package.json` under both APIs.
1 parent 0361beb commit 6781565

2 files changed

Lines changed: 35 additions & 2 deletions

File tree

src/paths/packages.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,26 @@ function getPath() {
1818
return _path as typeof import('node:path')
1919
}
2020

21+
/**
22+
* Whether `filepath`'s final segment is exactly `package.json`. Accepts both
23+
* POSIX and Windows-style separators so paths captured on either platform
24+
* classify the same regardless of the host we're running on.
25+
*/
26+
/*@__NO_SIDE_EFFECTS__*/
27+
function isPackageJsonFile(filepath: string): boolean {
28+
return (
29+
filepath === 'package.json' ||
30+
filepath.endsWith('/package.json') ||
31+
filepath.endsWith('\\package.json')
32+
)
33+
}
34+
2135
/**
2236
* Resolve directory path from a package.json file path.
2337
*/
2438
/*@__NO_SIDE_EFFECTS__*/
2539
export function resolvePackageJsonDirname(filepath: string): string {
26-
if (filepath.endsWith('package.json')) {
40+
if (isPackageJsonFile(filepath)) {
2741
const path = getPath()
2842
return normalizePath(path.dirname(filepath))
2943
}
@@ -35,7 +49,7 @@ export function resolvePackageJsonDirname(filepath: string): string {
3549
*/
3650
/*@__NO_SIDE_EFFECTS__*/
3751
export function resolvePackageJsonPath(filepath: string): string {
38-
if (filepath.endsWith('package.json')) {
52+
if (isPackageJsonFile(filepath)) {
3953
return normalizePath(filepath)
4054
}
4155
const path = getPath()

test/unit/paths/packages.test.mts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,17 @@ describe('paths/packages', () => {
5353
const result = resolvePackageJsonDirname('/foo/bar/baz/qux/package.json')
5454
expect(result).toBe('/foo/bar/baz/qux')
5555
})
56+
57+
it('should not strip to parent for files merely ending in package.json', () => {
58+
// Regression: `endsWith('package.json')` used to misclassify
59+
// `my-package.json` as a manifest and return the parent dir.
60+
expect(resolvePackageJsonDirname('/foo/my-package.json')).toBe(
61+
'/foo/my-package.json',
62+
)
63+
expect(resolvePackageJsonDirname('/foo/bar-package.json')).toBe(
64+
'/foo/bar-package.json',
65+
)
66+
})
5667
})
5768

5869
describe('resolvePackageJsonPath', () => {
@@ -93,6 +104,14 @@ describe('paths/packages', () => {
93104
expect(result).toBe('/foo/bar/package.json')
94105
expect(result).not.toBe('/foo/bar/package.json/package.json')
95106
})
107+
108+
it('should append package.json to files that merely end in package.json', () => {
109+
// Regression: `endsWith('package.json')` used to skip the append
110+
// for paths like `/foo/my-package.json`, treating them as manifests.
111+
expect(resolvePackageJsonPath('/foo/my-package.json')).toBe(
112+
'/foo/my-package.json/package.json',
113+
)
114+
})
96115
})
97116

98117
describe('integration', () => {

0 commit comments

Comments
 (0)