diff --git a/README.md b/README.md index a3c3a3c..1c0e17e 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,11 @@ arborist.loadActual().then((tree) => { This uses the following rules: 1. If a `package.json` file is found, and it has a `files` list, - then ignore everything that isn't in `files`. Always include the + then ignore everything that isn't in `files`. Always include the root readme, license, licence and copying files, if they exist, as well - as the package.json file itself. + as the package.json file itself. Non-root readme, license, licence and + copying files are included by default, but can be excluded using the + `files` list e.g. `"!readme"`. 2. If there's no `package.json` file (or it has no `files` list), and there is a `.npmignore` file, then ignore all the files in the `.npmignore` file. diff --git a/lib/index.js b/lib/index.js index 887018b..7577cba 100644 --- a/lib/index.js +++ b/lib/index.js @@ -38,13 +38,22 @@ const defaults = [ ] const strictDefaults = [ - // these are forcibly included at all levels + // these are forcibly excluded + '/.git', +] + +const allLevels = [ + // these are included by default but can be excluded by package.json files array '!/readme{,.*[^~$]}', '!/copying{,.*[^~$]}', '!/license{,.*[^~$]}', '!/licence{,.*[^~$]}', - // these are forcibly excluded - '/.git', +] + +const rootOnly = [ + /^!.*readme/i, + /^!.*copying/i, + /^!.*licen[sc]e/i, ] const normalizePath = (path) => path.split('\\').join('/') @@ -132,6 +141,7 @@ class PackWalker extends IgnoreWalker { // known required files for this directory this.injectRules(strictRules, [ ...strictDefaults, + ...allLevels, ...this.requiredFiles.map((file) => `!${file}`), ]) } @@ -284,6 +294,7 @@ class PackWalker extends IgnoreWalker { const ignores = [] const strict = [ ...strictDefaults, + ...allLevels, '!/package.json', '/.git', '/node_modules', @@ -304,6 +315,9 @@ class PackWalker extends IgnoreWalker { file = file.slice(0, -2) } const inverse = `!${file}` + + this.excludeNonRoot(file) + try { // if an entry in the files array is a specific file, then we need to include it as a // strict requirement for this package. if it's a directory or a pattern, it's a default @@ -352,6 +366,20 @@ class PackWalker extends IgnoreWalker { this.injectRules(strictRules, strict, callback) } + // excludes non root files by checking if elements from the files array in + // package.json contain an ! and readme/license/licence/copying, and then + // removing readme/license/licence/copying accordingly from strict defaults + excludeNonRoot (file) { + // Find the pattern + const matchingPattern = rootOnly.find(regex => regex.test(file)) + + if (matchingPattern) { + // Find which index matches the pattern and remove it from allLevels + const indexToRemove = allLevels.findIndex(element => matchingPattern.test(element)) + allLevels.splice(indexToRemove, 1) + } + } + // custom method: after we've finished gathering the files for the root package, we call this // before emitting the 'done' event in order to gather all of the files for bundled deps async gatherBundles () { diff --git a/test/package-json-negate-non-root.js b/test/package-json-negate-non-root.js new file mode 100644 index 0000000..02c3c85 --- /dev/null +++ b/test/package-json-negate-non-root.js @@ -0,0 +1,69 @@ +// exclude readme, license, and licnce files if package.json +// files array includes !readme, !license, or !licence +'use strict' + +const Arborist = require('@npmcli/arborist') +const t = require('tap') +const packlist = require('../') + +const pkg = t.testdir({ + 'package.json': JSON.stringify({ + files: [ + '**/*.js', + '!readme.md', + '!licence', + '!license', + '!copying', + ], + + }), + 'readme.md': 'one', + licence: 'two', + license: 'tre', + copying: 'for', + lib: { + 'readme.md': 'one', + licence: 'two', + license: 'tre', + copying: 'for', + a: { + 'readme.md': 'one', + licence: 'two', + license: 'tre', + copying: 'for', + b: { + 'readme.md': 'one', + licence: 'two', + license: 'tre', + copying: 'for', + c: { + 'readme.md': 'one', + licence: 'two', + license: 'tre', + copying: 'for', + 'file.txt': 'one', + 'c.js': 'two', + }, + 'file.txt': 'one', + 'b.js': 'two', + }, + 'file.txt': 'one', + 'a.js': 'two', + }, + } }) + +t.test('package with negated readme, licence and license files', async (t) => { + const arborist = new Arborist({ path: pkg }) + const tree = await arborist.loadActual() + const files = await packlist(tree) + t.same(files, [ + 'copying', + 'licence', + 'license', + 'lib/a/a.js', + 'lib/a/b/b.js', + 'lib/a/b/c/c.js', + 'package.json', + 'readme.md', + ]) +})