Skip to content

Commit bb7736f

Browse files
authored
Merge pull request #8 from TomerAberbach/master
Support multiple package.json paths
2 parents 821ea4f + 426a04b commit bb7736f

File tree

12 files changed

+300
-70
lines changed

12 files changed

+300
-70
lines changed

package-lock.json

Lines changed: 70 additions & 31 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,15 @@
4545
"scripts": {
4646
"build": "rollup -c",
4747
"watch": "rollup -cw",
48-
"types": "tsc src/index.ts --declaration --emitDeclarationOnly --outDir dist",
48+
"types": "tsc src/index.ts --downlevelIteration --declaration --emitDeclarationOnly --outDir dist",
4949
"test": "ava",
5050
"watch-tests": "ava -w",
5151
"clean": "trash dist",
5252
"prepublishOnly": "npm run clean && npm run build && npm run types"
5353
},
54+
"dependencies": {
55+
"find-up": "^4.1.0"
56+
},
5457
"peerDependencies": {
5558
"builtin-modules": "^3.1.0"
5659
},

rollup.config.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ import builtins from 'builtin-modules'
55
import pkg from './package.json'
66
const input = 'src/index.ts'
77
const sourcemap = true
8-
const external = builtins.concat(Object.keys(pkg.devDependencies), Object.keys(pkg.peerDependencies))
8+
const external = builtins.concat(
9+
Object.keys(pkg.devDependencies),
10+
Object.keys(pkg.dependencies),
11+
Object.keys(pkg.peerDependencies)
12+
)
913
const tsOptions = {
1014
tsconfig: cfg => ({ ...cfg, removeComments: true })
1115
}

src/dependencies.test.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import test from 'ava'
2+
import { join } from 'path'
3+
import { findPackagePaths, findDependencies } from './dependencies'
4+
5+
const path = (...paths: string[]): string => join(__dirname, ...paths)
6+
7+
test.serial('finds package paths in git monorepo', async t => {
8+
const cwd = path('fixtures/monorepo/packages/package')
9+
process.chdir(cwd)
10+
11+
const packagePaths = [
12+
path('fixtures/monorepo/packages/package/package.json'),
13+
path('fixtures/monorepo/package.json')
14+
]
15+
16+
// Should have two assertions
17+
t.plan(2)
18+
19+
for await (const packagePath of findPackagePaths()) {
20+
t.is(packagePath, packagePaths.shift())
21+
}
22+
})
23+
24+
test.serial('finds package paths in non-git monorepo', async t => {
25+
const cwd = path('fixtures/monorepo-no-git/packages/package')
26+
process.chdir(cwd)
27+
28+
const packagePaths = [
29+
path('fixtures/monorepo-no-git/packages/package/package.json'),
30+
path('fixtures/monorepo-no-git/package.json'),
31+
path('../package.json')
32+
]
33+
34+
// Should have three assertions
35+
t.plan(3)
36+
37+
for await (const packagePath of findPackagePaths()) {
38+
t.is(packagePath, packagePaths.shift())
39+
}
40+
})
41+
42+
test.serial('finds dependencies in monorepo', async t => {
43+
const cwd = path('fixtures/monorepo/packages/package')
44+
process.chdir(cwd)
45+
46+
const dependencies = await findDependencies({
47+
packagePaths: findPackagePaths(),
48+
keys: ['dependencies'],
49+
warnings: []
50+
})
51+
52+
t.deepEqual(new Set(dependencies), new Set(['lodash', 'express', 'chalk']))
53+
})
54+
55+
test.serial('finds dev dependencies in monorepo', async t => {
56+
const cwd = path('fixtures/monorepo/packages/package')
57+
process.chdir(cwd)
58+
59+
const devDependencies = await findDependencies({
60+
packagePaths: findPackagePaths(),
61+
keys: ['devDependencies'],
62+
warnings: []
63+
})
64+
65+
t.deepEqual(new Set(devDependencies), new Set(['typescript', 'rollup']))
66+
})

src/dependencies.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { dirname, relative, isAbsolute } from 'path'
2+
import { promises as fs } from 'fs'
3+
import findUp from 'find-up'
4+
5+
/**
6+
* Determines if the `child` path is under the `parent` path.
7+
*/
8+
function isInDirectory(parent: string, child: string): boolean {
9+
const relativePath = relative(parent, child)
10+
return !relativePath.startsWith('..') && !isAbsolute(relativePath)
11+
}
12+
13+
/**
14+
* Iterates over package.json file paths recursively found in parent directories, starting from the
15+
* current working directory. If the current working directory is in a git repository, then package.json
16+
* files outside of the git repository will not be yielded.
17+
*/
18+
export async function* findPackagePaths(): AsyncGenerator<string> {
19+
// Find git root if in git repository
20+
const gitDirectoryPath: string | undefined = await findUp('.git', { type: 'directory' })
21+
const gitRootPath: string | undefined = gitDirectoryPath === undefined
22+
? undefined
23+
: dirname(gitDirectoryPath)
24+
25+
function isInGitDirectory(path: string): boolean {
26+
return gitRootPath === undefined || isInDirectory(gitRootPath, path)
27+
}
28+
29+
let cwd: string = process.cwd()
30+
let packagePath: string | undefined
31+
32+
while (
33+
(packagePath = await findUp('package.json', { type: 'file', cwd })) &&
34+
isInGitDirectory(packagePath)
35+
) {
36+
yield packagePath
37+
cwd = dirname(dirname(packagePath))
38+
}
39+
}
40+
41+
export async function findDependencies(
42+
{ packagePaths, keys, warnings }: {
43+
packagePaths: AsyncIterable<string> | Iterable<string>,
44+
keys: string[],
45+
warnings: string[]
46+
}
47+
): Promise<string[]> {
48+
const dependencies: Set<string> = new Set()
49+
50+
for await (const packagePath of packagePaths) {
51+
try {
52+
const pkg: { [key in PropertyKey]: any } = JSON.parse((await fs.readFile(packagePath)).toString()) ?? {}
53+
54+
for (const key of keys) {
55+
const dependenciesToVersions: { [key in PropertyKey]: any } = pkg[key] ?? {}
56+
57+
for (const dependency of Object.keys(dependenciesToVersions)) {
58+
dependencies.add(dependency)
59+
}
60+
}
61+
} catch {
62+
warnings.push(`Couldn't process '${packagePath}'. Make sure it is a valid JSON or use the 'packagePath' option`)
63+
}
64+
}
65+
66+
return Array.from(dependencies)
67+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
3+
}

src/fixtures/monorepo/package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"dependencies": {
3+
"lodash": "^4.17.15",
4+
"chalk": "^4.0.0"
5+
},
6+
"devDependencies": {
7+
"typescript": "^3.9.2",
8+
"rollup": "^2.10.2"
9+
}
10+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"dependencies": {
3+
"lodash": "^4.17.15",
4+
"express": "4.17.1"
5+
},
6+
"peerDependencies": {
7+
"@babel/core": "^7.0.0"
8+
}
9+
}

src/index.test.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import test from 'ava'
22
import { testProp, fc } from 'ava-fast-check'
33
import { Arbitrary } from 'fast-check'
4-
import { PluginContext, Plugin } from 'rollup'
4+
import { PluginContext, Plugin, InputOptions } from 'rollup'
55
import externals, { ExternalsOptions } from './index'
6+
import { join } from "path"
67

78
// Returns an arbitrary for generating externals options objects
89
const externalsOptionsArbitrary = (): Arbitrary<ExternalsOptions> =>
@@ -31,10 +32,30 @@ testProp(
3132
}
3233
)
3334

34-
test('does not mark "dependencies" dependency as external by default', t => {
35+
test('does not mark "dependencies" dependency as external by default', async t => {
3536
const source = 'example'
3637
const importer = 'me'
37-
const plugin = externals({ packagePath: './fixtures/test.json' }) as Plugin & PluginContext
38+
const plugin = externals({ packagePath: './fixtures/test.json' }) as Required<Plugin> & PluginContext
3839

40+
await plugin.buildStart({} as InputOptions)
3941
t.is(plugin.resolveId(source, importer), null)
4042
})
43+
44+
const path = (...paths: string[]): string => join(__dirname, ...paths)
45+
46+
test.serial('monorepo usage', async t => {
47+
const cwd = path('fixtures/monorepo/packages/package')
48+
process.chdir(cwd)
49+
50+
const importer = 'me'
51+
const plugin = externals() as Required<Plugin> & PluginContext
52+
await plugin.buildStart({} as InputOptions)
53+
54+
for (const source of ['@babel/core', 'typescript', 'rollup']) {
55+
t.false(plugin.resolveId(source, importer))
56+
}
57+
58+
for (const source of ['lodash', 'express', 'chalk']) {
59+
t.is(plugin.resolveId(source, importer), null)
60+
}
61+
})

0 commit comments

Comments
 (0)