Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show files added since the last release and not part of the package #456

Merged
merged 41 commits into from Oct 29, 2020
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
f7473d8
retrieve the files, which were added since the last release
bunysae Sep 14, 2019
5507f54
Checks for new files added since the last release implemented
bunysae Sep 21, 2019
7278c44
test-passes
bunysae Sep 23, 2019
9f3a1c3
integration ignored files in ui
bunysae Sep 23, 2019
d02e183
Merge branch 'master' into issue_103
bunysae Oct 8, 2019
5bb7561
Refactorings for new function show files since
bunysae Dec 11, 2019
c43f900
Resolved merge-conflict in ui.js
bunysae Dec 11, 2019
71d179a
Merge branch 'master' into issue_103
bunysae Dec 11, 2019
12a16f0
Show files added since the last release not part of the release packa…
bunysae Dec 27, 2019
73b86f7
Show files added since the last release not part of the release packa…
bunysae Dec 27, 2019
7cef7cb
Show files added since the last release not part of the release packa…
bunysae Jan 4, 2020
4a75e6d
Merge branch 'master' into issue_103
bunysae Jan 4, 2020
2827f81
Show files added since the last release not part of the release packa…
bunysae Jan 4, 2020
4a8f2e4
Merge branch 'issue_103' of https://github.com/bunysae/np into issue_103
bunysae Jan 4, 2020
da3f187
Show files added since the last release not part of the release packa…
bunysae Jan 25, 2020
5db79e4
Show files added since the last release not part of the release packa…
bunysae Jan 28, 2020
76e6a80
Merge branch 'master' into issue_103
bunysae Feb 8, 2020
786187d
Show files added since last release refactoring testclass
bunysae Feb 27, 2020
32d0d47
Refactoring ui
bunysae Feb 29, 2020
71d410f
Simplify tests
bunysae Jul 8, 2020
3634a96
Merge branch 'issue_103' of https://github.com/bunysae/np into issue_103
bunysae Jul 8, 2020
ddcaebf
Merge branch 'master' into issue_103
bunysae Jul 8, 2020
d86bf7d
Update npmignore.js
sindresorhus Jul 15, 2020
c445ff6
Apply suggestions from code review
bunysae Jul 18, 2020
239c61f
1. Ingore test files
bunysae Jul 18, 2020
fba3ab4
Add submodule integration test
bunysae Jul 18, 2020
2fa3cf5
New files since last release: additional test cases
bunysae Aug 2, 2020
22b242a
exclude linting submodules
bunysae Aug 2, 2020
dd5353b
Updating submodule
bunysae Aug 2, 2020
a082606
Exclude integration-test from direct invocation
bunysae Aug 2, 2020
f8a5507
Adjust submodule integration-test
bunysae Aug 2, 2020
bd12ee8
integration-test: cleanup task
bunysae Aug 2, 2020
fa5c3c2
Tweak integration-test
bunysae Aug 2, 2020
aa73b47
Add published file check to readme
bunysae Aug 11, 2020
3bb2783
fix for dot-files
bunysae Aug 11, 2020
546aebb
Fix early exit
bunysae Aug 12, 2020
c0a7b4c
Rewriting readme
bunysae Aug 30, 2020
7f383bf
Fix grammar and meaning in readme
bunysae Oct 3, 2020
db59d35
Update readme.md
sindresorhus Oct 29, 2020
d75573f
Merge branch 'master' into issue_103
sindresorhus Oct 29, 2020
d784837
Update source/ui.js
sindresorhus Oct 29, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions package.json
Expand Up @@ -41,6 +41,7 @@
"github-url-from-git": "^1.5.0",
"has-yarn": "^2.1.0",
"hosted-git-info": "^3.0.0",
"ignore-walk": "^3.0.2",
"inquirer": "^7.0.0",
"is-installed-globally": "^0.3.1",
"is-scoped": "^2.1.0",
Expand All @@ -49,6 +50,7 @@
"listr-input": "^0.2.0",
"log-symbols": "^3.0.0",
"meow": "^6.0.0",
"minimatch": "^3.0.4",
"new-github-release-url": "^1.0.0",
"npm-name": "^5.4.0",
"onetime": "^5.1.0",
Expand All @@ -68,6 +70,7 @@
"devDependencies": {
"ava": "^2.3.0",
"proxyquire": "^2.1.0",
"mockery": "^2.1.0",
"sinon": "^8.0.1",
"xo": "^0.25.3"
}
Expand Down
6 changes: 6 additions & 0 deletions source/git-util.js
Expand Up @@ -8,6 +8,12 @@ exports.latestTag = async () => {
return stdout;
};

exports.newFilesSinceLastRelease = async () => {
const {stdout} = await execa('git', ['diff', '--stat', '--diff-filter=A', await this.latestTag(), 'HEAD']);
itaisteinherz marked this conversation as resolved.
Show resolved Hide resolved
const result = stdout.trim().split('\n').slice(0, -1).map(row => row.slice(0, row.indexOf('|')).trim());
return result;
};

const firstCommit = async () => {
const {stdout} = await execa('git', ['rev-list', '--max-parents=0', 'HEAD']);
return stdout;
Expand Down
66 changes: 62 additions & 4 deletions source/npm/util.js
Expand Up @@ -7,6 +7,8 @@ const ow = require('ow');
const npmName = require('npm-name');
const chalk = require('chalk');
const pkgDir = require('pkg-dir');
const ignoreWalker = require('ignore-walk');
const minimatch = require('minimatch');
const {verifyRequirementSatisfied} = require('../version');

exports.checkConnection = () => pTimeout(
Expand Down Expand Up @@ -117,16 +119,72 @@ exports.verifyRecentNpmVersion = async () => {
};

exports.checkIgnoreStrategy = ({files}) => {
const rootDir = pkgDir.sync();
const npmignoreExists = fs.existsSync(path.resolve(rootDir, '.npmignore'));

if (!files && !npmignoreExists) {
if (!files && !npmignoreExistsInPackageRootDir()) {
console.log(`
\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.
`);
}
};

function npmignoreExistsInPackageRootDir() {
bunysae marked this conversation as resolved.
Show resolved Hide resolved
const rootDir = pkgDir.sync();
return fs.existsSync(path.resolve(rootDir, '.npmignore'));
}

async function getFilesIgnoredByDotnpmignore(fileList) {
const whiteList = await ignoreWalker({
path: pkgDir.sync(),
ignoreFiles: ['.npmignore']
itaisteinherz marked this conversation as resolved.
Show resolved Hide resolved
});
return fileList.filter(minimatch.filter(getIgnoredFilesGlob(whiteList), {matchBase: true}));
}

function getFilesNotIncludedInFilesProperty(globArrayFromFilesProperty, fileList) {
const globArrayForFilesAndDirs = [...globArrayFromFilesProperty];
bunysae marked this conversation as resolved.
Show resolved Hide resolved
const rootDir = pkgDir.sync();
for (const glob of globArrayFromFilesProperty) {
try {
if (fs.statSync(path.resolve(rootDir, glob)).isDirectory()) {
globArrayForFilesAndDirs.push(`${glob}/**/*`);
}
bunysae marked this conversation as resolved.
Show resolved Hide resolved
} catch (_) {}
}

return fileList.filter(minimatch.filter(getIgnoredFilesGlob(globArrayForFilesAndDirs), {matchBase: true}));
}

function getIgnoredFilesGlob(globArrayFromFilesProperty) {
/* According to https://docs.npmjs.com/files/package.json#files
npm's default behavior is to ignore these files. */
bunysae marked this conversation as resolved.
Show resolved Hide resolved
const filesIgnoredByDefault = ['.*.swp',
itaisteinherz marked this conversation as resolved.
Show resolved Hide resolved
'.npmignore',
'._*',
'.DS_Store',
'.hg',
'.npmrc',
'.lock-wscript',
'.svn',
'.wafpickle-N',
'*.orig',
'config.gypi',
'CVS',
'node_modules/**/*',
'npm-debug.log',
'package-lock.json'];
bunysae marked this conversation as resolved.
Show resolved Hide resolved
return `!{${globArrayFromFilesProperty.join(',')},${filesIgnoredByDefault.join(',')}}`;
}

// Get all files which will be ignored by either `.npmignore` or the `files` property in `package.json` (if defined).
exports.getNewAndUnpublishedFiles = async (globArrayFromFilesProperty, newFiles = []) => {
if (globArrayFromFilesProperty) {
return getFilesNotIncludedInFilesProperty(globArrayFromFilesProperty, newFiles);
}

if (npmignoreExistsInPackageRootDir()) {
return getFilesIgnoredByDotnpmignore(newFiles);
}
};

exports.getRegistryUrl = async (pkgManager, pkg) => {
const args = ['config', 'get', 'registry'];
if (exports.isExternalRegistry(pkg)) {
Expand Down
23 changes: 23 additions & 0 deletions source/ui.js
Expand Up @@ -49,6 +49,22 @@ const printCommitLog = async (repoUrl, registryUrl) => {
};
};

const checkIgnoredFiles = async pkg => {
const ignoredFiles = await util.getNewAndUnpublishedFiles(pkg);
if (ignoredFiles === undefined || ignoredFiles.length === 0) {
bunysae marked this conversation as resolved.
Show resolved Hide resolved
return true;
}

const answers = await inquirer.prompt([{
type: 'confirm',
name: 'confirm',
message: `The following new files are not part of your published package:\n${chalk.reset(ignoredFiles.map(path => `- ${path}`).join('\n'))}\nContinue?`,
bunysae marked this conversation as resolved.
Show resolved Hide resolved
default: false
}]);

return answers;
};

module.exports = async (options, pkg) => {
const oldVersion = pkg.version;
const extraBaseUrls = ['gitlab.com'];
Expand All @@ -59,6 +75,13 @@ module.exports = async (options, pkg) => {

if (runPublish) {
checkIgnoreStrategy(pkg);
sindresorhus marked this conversation as resolved.
Show resolved Hide resolved
const answerIgnoredFiles = await checkIgnoredFiles(pkg);
if (answerIgnoredFiles.confirm === false) {
bunysae marked this conversation as resolved.
Show resolved Hide resolved
return {
...options,
...answerIgnoredFiles
};
}
}

console.log(`\nPublish a new version of ${chalk.bold.magenta(pkg.name)} ${chalk.dim(`(current: ${oldVersion})`)}\n`);
Expand Down
7 changes: 7 additions & 0 deletions source/util.js
Expand Up @@ -6,6 +6,8 @@ const execa = require('execa');
const pMemoize = require('p-memoize');
const ow = require('ow');
const pkgDir = require('pkg-dir');
const gitUtil = require('./git-util');
const npmUtil = require('./npm/util');

exports.readPkg = (packagePath = pkgDir.sync()) => {
const {packageJson} = readPkgUp.sync({
Expand Down Expand Up @@ -66,3 +68,8 @@ exports.getTagVersionPrefix = pMemoize(async options => {
return 'v';
}
});

exports.getNewAndUnpublishedFiles = async pkg => {
const listNewFiles = await gitUtil.newFilesSinceLastRelease();
return npmUtil.getNewAndUnpublishedFiles(pkg.files, listNewFiles);
};
1 change: 1 addition & 0 deletions test/fixtures/npmignore/.hg
@@ -0,0 +1 @@
should be ignored by default
2 changes: 2 additions & 0 deletions test/fixtures/npmignore/.npmignore
@@ -0,0 +1,2 @@
ignore.txt
test
1 change: 1 addition & 0 deletions test/fixtures/npmignore/source/ignore.txt
@@ -0,0 +1 @@
Ignore this file
1 change: 1 addition & 0 deletions test/fixtures/npmignore/source/pay_attention.txt
@@ -0,0 +1 @@
File is excluded from .npmignore
1 change: 1 addition & 0 deletions test/fixtures/npmignore/test/file.txt
@@ -0,0 +1 @@
ignore this file
1 change: 1 addition & 0 deletions test/fixtures/package/.hg
@@ -0,0 +1 @@
should be ignored by default
3 changes: 3 additions & 0 deletions test/fixtures/package/package.json
@@ -0,0 +1,3 @@
{
"files": ["pay_attention.txt"]
}
1 change: 1 addition & 0 deletions test/fixtures/package/source/ignore.txt
@@ -0,0 +1 @@
File is excluded from package.json
1 change: 1 addition & 0 deletions test/fixtures/package/source/pay_attention.txt
@@ -0,0 +1 @@
File in included in package.json
2 changes: 2 additions & 0 deletions test/fixtures/readme.md
@@ -0,0 +1,2 @@
The directory is for the resources
in the script npmignore.js
78 changes: 78 additions & 0 deletions test/npmignore.js
@@ -0,0 +1,78 @@
import path from 'path';
import test from 'ava';
import mockery from 'mockery';
import sinon from 'sinon';

let testedModule;

const gitUtilApi = {
newFilesSinceLastRelease: async () => {}
};

const pkgDirApi = {
sync: () => {}
};

test.before(() => {
const stubGitUtil = sinon.stub(gitUtilApi, 'newFilesSinceLastRelease');

mockery.registerAllowable('../source/util');
mockery.registerAllowable('../source/npm/util');
mockery.registerMock('./git-util', gitUtilApi);
mockery.registerMock('pkg-dir', pkgDirApi);

stubGitUtil.returns(['source/ignore.txt', 'source/pay_attention.txt', '.hg', 'test/file.txt']);

mockery.enable({
useCleanCache: true,
warnOnReplace: false,
warnOnUnregistered: false
});

// Mockery has to setup before module is loaded
testedModule = require('../source/util');
itaisteinherz marked this conversation as resolved.
Show resolved Hide resolved
});

test.after(() => {
mockery.deregisterAll();
mockery.disable();
});

test.serial('ignored files using file-attribute in package.json with one file', async t => {
sindresorhus marked this conversation as resolved.
Show resolved Hide resolved
const stubPkgDir = sinon.stub();
stubPkgDir.returns(path.resolve('test', 'fixtures', 'package'));
pkgDirApi.sync = stubPkgDir;
t.deepEqual(await testedModule.getNewAndUnpublishedFiles({files: ['pay_attention.txt']}),
['source/ignore.txt', 'test/file.txt']);
});

test.serial('ignored files using file-attribute in package.json with multiple file', async t => {
const stubPkgDir = sinon.stub();
stubPkgDir.returns(path.resolve('test', 'fixtures', 'package'));
pkgDirApi.sync = stubPkgDir;
t.deepEqual(await testedModule.getNewAndUnpublishedFiles(
{files: ['pay_attention.txt', 'ignore.txt']}), ['test/file.txt']);
});

test.serial('ignored file using file-attribute in package.json with directory', async t => {
const stubPkgDir = sinon.stub();
stubPkgDir.returns(path.resolve('test', 'fixtures', 'package'));
pkgDirApi.sync = stubPkgDir;
t.deepEqual(await testedModule.getNewAndUnpublishedFiles(
{files: ['source']}), ['test/file.txt']);
});

test.serial('ignored files using .npmignore', async t => {
const stubPkgDir = sinon.stub();
stubPkgDir.returns(path.resolve('test', 'fixtures', 'npmignore'));
pkgDirApi.sync = stubPkgDir;
t.deepEqual(await testedModule.getNewAndUnpublishedFiles({name: 'without file-attribute'}),
['source/ignore.txt', 'test/file.txt']);
});

test.serial('ignore strategy is not used', async t => {
const stubPkgDir = sinon.stub();
stubPkgDir.returns(path.resolve('test', 'fixtures'));
pkgDirApi.sync = stubPkgDir;
t.true(await testedModule.getNewAndUnpublishedFiles({name: 'without file-attribute'}) === undefined);
});