Skip to content

Commit 08a2c06

Browse files
Show files added since the last release and not part of the package (#456)
Co-authored-by: Sindre Sorhus <[email protected]>
1 parent 0cff2b4 commit 08a2c06

File tree

22 files changed

+280
-5
lines changed

22 files changed

+280
-5
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "integration-test"]
2+
path = integration-test
3+
url = https://github.com/bunysae/np_integration_test

integration-test

Submodule integration-test added at ad5e6e3

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"github-url-from-git": "^1.5.0",
4343
"has-yarn": "^2.1.0",
4444
"hosted-git-info": "^3.0.0",
45+
"ignore-walk": "^3.0.3",
4546
"import-local": "^3.0.2",
4647
"inquirer": "^7.0.0",
4748
"is-installed-globally": "^0.3.1",
@@ -51,6 +52,7 @@
5152
"listr-input": "^0.2.1",
5253
"log-symbols": "^3.0.0",
5354
"meow": "^6.0.0",
55+
"minimatch": "^3.0.4",
5456
"new-github-release-url": "^1.0.0",
5557
"npm-name": "^6.0.0",
5658
"onetime": "^5.1.0",
@@ -77,7 +79,8 @@
7779
},
7880
"ava": {
7981
"files": [
80-
"!test/fixtures"
82+
"!test/fixtures",
83+
"!integration-test"
8184
]
8285
}
8386
}

readme.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,10 @@ Host *
280280

281281
If you're running into other issues when using SSH, please consult [GitHub's support article](https://help.github.com/articles/connecting-to-github-with-ssh/).
282282

283+
### Ignore strategy
284+
285+
The [ignore strategy](https://docs.npmjs.com/files/package.json#files), either maintained in the `files`-property in `package.json` or in `.npmignore`, is meant to help reduce the package size. To avoid broken packages caused by essential files being accidentally ignored, `np` prints out all the new and unpublished files added to Git. Test files and other [common files](https://docs.npmjs.com/files/package.json#files) that are never published are not considered. `np` assumes either a standard directory layout or a customized layout represented in the `directories` property in `package.json`.
286+
283287
## FAQ
284288

285289
### I get an error when publishing my package through Yarn

source/git-util.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,29 @@
11
'use strict';
22
const execa = require('execa');
33
const escapeStringRegexp = require('escape-string-regexp');
4+
const ignoreWalker = require('ignore-walk');
5+
const pkgDir = require('pkg-dir');
46
const {verifyRequirementSatisfied} = require('./version');
57

68
exports.latestTag = async () => {
79
const {stdout} = await execa('git', ['describe', '--abbrev=0', '--tags']);
810
return stdout;
911
};
1012

13+
exports.newFilesSinceLastRelease = async () => {
14+
try {
15+
const {stdout} = await execa('git', ['diff', '--stat', '--diff-filter=A', await this.latestTag(), 'HEAD']);
16+
const result = stdout.trim().split('\n').slice(0, -1).map(row => row.slice(0, row.indexOf('|')).trim());
17+
return result;
18+
} catch (_) {
19+
// Get all files under version control
20+
return ignoreWalker({
21+
path: pkgDir.sync(),
22+
ignoreFiles: ['.gitignore']
23+
});
24+
}
25+
};
26+
1127
const firstCommit = async () => {
1228
const {stdout} = await execa('git', ['rev-list', '--max-parents=0', 'HEAD']);
1329
return stdout;

source/npm/util.js

Lines changed: 100 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ const ow = require('ow');
77
const npmName = require('npm-name');
88
const chalk = require('chalk');
99
const pkgDir = require('pkg-dir');
10+
const ignoreWalker = require('ignore-walk');
11+
const minimatch = require('minimatch');
1012
const {verifyRequirementSatisfied} = require('../version');
1113

1214
exports.checkConnection = () => pTimeout(
@@ -117,16 +119,110 @@ exports.verifyRecentNpmVersion = async () => {
117119
};
118120

119121
exports.checkIgnoreStrategy = ({files}) => {
120-
const rootDir = pkgDir.sync();
121-
const npmignoreExists = fs.existsSync(path.resolve(rootDir, '.npmignore'));
122-
123-
if (!files && !npmignoreExists) {
122+
if (!files && !npmignoreExistsInPackageRootDir()) {
124123
console.log(`
125124
\n${chalk.bold.yellow('Warning:')} No ${chalk.bold.cyan('files')} field specified in ${chalk.bold.magenta('package.json')} nor is a ${chalk.bold.magenta('.npmignore')} file present. Having one of those will prevent you from accidentally publishing development-specific files along with your package's source code to npm.
126125
`);
127126
}
128127
};
129128

129+
function npmignoreExistsInPackageRootDir() {
130+
const rootDir = pkgDir.sync();
131+
return fs.existsSync(path.resolve(rootDir, '.npmignore'));
132+
}
133+
134+
async function getFilesIgnoredByDotnpmignore(pkg, fileList) {
135+
const whiteList = await ignoreWalker({
136+
path: pkgDir.sync(),
137+
ignoreFiles: ['.npmignore']
138+
});
139+
return fileList.filter(minimatch.filter(getIgnoredFilesGlob(whiteList, pkg.directories), {matchBase: true, dot: true}));
140+
}
141+
142+
function getFilesNotIncludedInFilesProperty(pkg, fileList) {
143+
const globArrayForFilesAndDirectories = [...pkg.files];
144+
const rootDir = pkgDir.sync();
145+
for (const glob of pkg.files) {
146+
try {
147+
if (fs.statSync(path.resolve(rootDir, glob)).isDirectory()) {
148+
globArrayForFilesAndDirectories.push(`${glob}/**/*`);
149+
}
150+
} catch (_) {}
151+
}
152+
153+
const result = fileList.filter(minimatch.filter(getIgnoredFilesGlob(globArrayForFilesAndDirectories, pkg.directories), {matchBase: true, dot: true}));
154+
return result.filter(minimatch.filter(getDefaultIncludedFilesGlob(pkg.main), {nocase: true, matchBase: true}));
155+
}
156+
157+
function getDefaultIncludedFilesGlob(mainFile) {
158+
// According to https://docs.npmjs.com/files/package.json#files
159+
// npm's default behavior is to always include these files.
160+
const filesAlwaysIncluded = [
161+
'package.json',
162+
'README*',
163+
'CHANGES*',
164+
'CHANGELOG*',
165+
'HISTORY*',
166+
'LICENSE*',
167+
'LICENCE*',
168+
'NOTICE*'
169+
];
170+
if (mainFile) {
171+
filesAlwaysIncluded.push(mainFile);
172+
}
173+
174+
return `!{${filesAlwaysIncluded}}`;
175+
}
176+
177+
function getIgnoredFilesGlob(globArrayFromFilesProperty, packageDirectories) {
178+
// According to https://docs.npmjs.com/files/package.json#files
179+
// npm's default behavior is to ignore these files.
180+
const filesIgnoredByDefault = [
181+
'.*.swp',
182+
'.npmignore',
183+
'.gitignore',
184+
'._*',
185+
'.DS_Store',
186+
'.hg',
187+
'.npmrc',
188+
'.lock-wscript',
189+
'.svn',
190+
'.wafpickle-N',
191+
'*.orig',
192+
'config.gypi',
193+
'CVS',
194+
'node_modules/**/*',
195+
'npm-debug.log',
196+
'package-lock.json',
197+
'.git/**/*',
198+
'.git'
199+
];
200+
201+
// Test files are assumed not to be part of the package
202+
let testDirectoriesGlob = '';
203+
if (packageDirectories && Array.isArray(packageDirectories.test)) {
204+
testDirectoriesGlob = packageDirectories.test.join(',');
205+
} else if (packageDirectories && typeof packageDirectories.test === 'string') {
206+
testDirectoriesGlob = packageDirectories.test;
207+
} else {
208+
// Fallback to `test` directory
209+
testDirectoriesGlob = 'test/**/*';
210+
}
211+
212+
return `!{${globArrayFromFilesProperty.join(',')},${filesIgnoredByDefault.join(',')},${testDirectoriesGlob}}`;
213+
}
214+
215+
// Get all files which will be ignored by either `.npmignore` or the `files` property in `package.json` (if defined).
216+
exports.getNewAndUnpublishedFiles = async (pkg, newFiles = []) => {
217+
if (pkg.files) {
218+
return getFilesNotIncludedInFilesProperty(pkg, newFiles);
219+
}
220+
221+
if (npmignoreExistsInPackageRootDir()) {
222+
return getFilesIgnoredByDotnpmignore(pkg, newFiles);
223+
}
224+
};
225+
130226
exports.getRegistryUrl = async (pkgManager, pkg) => {
131227
const args = ['config', 'get', 'registry'];
132228
if (exports.isExternalRegistry(pkg)) {

source/ui.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,22 @@ const printCommitLog = async (repoUrl, registryUrl) => {
5050
};
5151
};
5252

53+
const checkIgnoredFiles = async pkg => {
54+
const ignoredFiles = await util.getNewAndUnpublishedFiles(pkg);
55+
if (!ignoredFiles || ignoredFiles.length === 0) {
56+
return true;
57+
}
58+
59+
const answers = await inquirer.prompt([{
60+
type: 'confirm',
61+
name: 'confirm',
62+
message: `The following new files are not already part of your published package:\n${chalk.reset(ignoredFiles.map(path => `- ${path}`).join('\n'))}\nContinue?`,
63+
default: false
64+
}]);
65+
66+
return answers.confirm;
67+
};
68+
5369
module.exports = async (options, pkg) => {
5470
const oldVersion = pkg.version;
5571
const extraBaseUrls = ['gitlab.com'];
@@ -59,6 +75,14 @@ module.exports = async (options, pkg) => {
5975

6076
if (options.runPublish) {
6177
checkIgnoreStrategy(pkg);
78+
79+
const answerIgnoredFiles = await checkIgnoredFiles(pkg);
80+
if (!answerIgnoredFiles) {
81+
return {
82+
...options,
83+
confirm: answerIgnoredFiles
84+
};
85+
}
6286
}
6387

6488
console.log(`\nPublish a new version of ${chalk.bold.magenta(pkg.name)} ${chalk.dim(`(current: ${oldVersion})`)}\n`);

source/util.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ const execa = require('execa');
66
const pMemoize = require('p-memoize');
77
const ow = require('ow');
88
const pkgDir = require('pkg-dir');
9+
const gitUtil = require('./git-util');
10+
const npmUtil = require('./npm/util');
911

1012
exports.readPkg = packagePath => {
1113
packagePath = packagePath ? pkgDir.sync(packagePath) : pkgDir.sync();
@@ -69,6 +71,11 @@ exports.getTagVersionPrefix = pMemoize(async options => {
6971
}
7072
});
7173

74+
exports.getNewAndUnpublishedFiles = async pkg => {
75+
const listNewFiles = await gitUtil.newFilesSinceLastRelease();
76+
return npmUtil.getNewAndUnpublishedFiles(pkg, listNewFiles);
77+
};
78+
7279
exports.getPreReleasePrefix = pMemoize(async options => {
7380
ow(options, ow.object.hasKeys('yarn'));
7481

test/fixtures/npmignore/.hg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
should be ignored by default

test/fixtures/npmignore/.npmignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ignore.txt
2+
test

0 commit comments

Comments
 (0)