From 626aed670687c23cb6bb036a2e727cfd31821d84 Mon Sep 17 00:00:00 2001 From: Bunyanuch Saengnet <53788417+bunysae@users.noreply.github.com> Date: Sun, 25 Apr 2021 06:33:36 +0000 Subject: [PATCH] Fix showing files added since the last release (#595) --- integration-test | 2 +- source/npm/util.js | 86 ++++++++++++++++++------- source/ui.js | 22 +++++-- source/util.js | 4 +- test/fixtures/npmignore/source/.dotfile | 0 test/npmignore.js | 66 ++++++++++++++++++- 6 files changed, 144 insertions(+), 36 deletions(-) create mode 100644 test/fixtures/npmignore/source/.dotfile diff --git a/integration-test b/integration-test index 1771d17d..ae82745e 160000 --- a/integration-test +++ b/integration-test @@ -1 +1 @@ -Subproject commit 1771d17d524bb33a4cfc99df36000a4e460fcde5 +Subproject commit ae82745e85ff662a569d64c896cb53b80091e654 diff --git a/source/npm/util.js b/source/npm/util.js index f4393cb3..d29f28f4 100644 --- a/source/npm/util.js +++ b/source/npm/util.js @@ -11,6 +11,29 @@ const ignoreWalker = require('ignore-walk'); const minimatch = require('minimatch'); const {verifyRequirementSatisfied} = require('../version'); +// According to https://docs.npmjs.com/files/package.json#files +// npm's default behavior is to ignore these files. +const filesIgnoredByDefault = [ + '.*.swp', + '.npmignore', + '.gitignore', + '._*', + '.DS_Store', + '.hg', + '.npmrc', + '.lock-wscript', + '.svn', + '.wafpickle-N', + '*.orig', + 'config.gypi', + 'CVS', + 'node_modules/**/*', + 'npm-debug.log', + 'package-lock.json', + '.git/**/*', + '.git' +]; + exports.checkConnection = () => pTimeout( (async () => { try { @@ -139,6 +162,19 @@ async function getFilesIgnoredByDotnpmignore(pkg, fileList) { return fileList.filter(minimatch.filter(getIgnoredFilesGlob(allowList, pkg.directories), {matchBase: true, dot: true})); } +function filterFileList(globArray, fileList) { + const globString = globArray.length > 1 ? `{${globArray}}` : globArray[0]; + return fileList.filter(minimatch.filter(globString, {matchBase: true, dot: true})); // eslint-disable-line unicorn/no-fn-reference-in-iterator +} + +async function getFilesIncludedByDotnpmignore(pkg, fileList) { + const allowList = await ignoreWalker({ + path: pkgDir.sync(), + ignoreFiles: ['.npmignore'] + }); + return filterFileList(allowList, fileList); +} + function getFilesNotIncludedInFilesProperty(pkg, fileList) { const globArrayForFilesAndDirectories = [...pkg.files]; const rootDir = pkgDir.sync(); @@ -154,6 +190,20 @@ function getFilesNotIncludedInFilesProperty(pkg, fileList) { return result.filter(minimatch.filter(getDefaultIncludedFilesGlob(pkg.main), {nocase: true, matchBase: true})); } +function getFilesIncludedInFilesProperty(pkg, fileList) { + const globArrayForFilesAndDirectories = [...pkg.files]; + const rootDir = pkgDir.sync(); + for (const glob of pkg.files) { + try { + if (fs.statSync(path.resolve(rootDir, glob)).isDirectory()) { + globArrayForFilesAndDirectories.push(`${glob}/**/*`); + } + } catch {} + } + + return filterFileList(globArrayForFilesAndDirectories, fileList); +} + function getDefaultIncludedFilesGlob(mainFile) { // According to https://docs.npmjs.com/files/package.json#files // npm's default behavior is to always include these files. @@ -175,29 +225,6 @@ function getDefaultIncludedFilesGlob(mainFile) { } function getIgnoredFilesGlob(globArrayFromFilesProperty, packageDirectories) { - // According to https://docs.npmjs.com/files/package.json#files - // npm's default behavior is to ignore these files. - const filesIgnoredByDefault = [ - '.*.swp', - '.npmignore', - '.gitignore', - '._*', - '.DS_Store', - '.hg', - '.npmrc', - '.lock-wscript', - '.svn', - '.wafpickle-N', - '*.orig', - 'config.gypi', - 'CVS', - 'node_modules/**/*', - 'npm-debug.log', - 'package-lock.json', - '.git/**/*', - '.git' - ]; - // Test files are assumed not to be part of the package let testDirectoriesGlob = ''; if (packageDirectories && Array.isArray(packageDirectories.test)) { @@ -223,6 +250,19 @@ exports.getNewAndUnpublishedFiles = async (pkg, newFiles = []) => { } }; +exports.getFirstTimePublishedFiles = async (pkg, newFiles = []) => { + let result; + if (pkg.files) { + result = getFilesIncludedInFilesProperty(pkg, newFiles); + } else if (npmignoreExistsInPackageRootDir()) { + result = await getFilesIncludedByDotnpmignore(pkg, newFiles); + } else { + result = newFiles; + } + + return result.filter(minimatch.filter(`!{${filesIgnoredByDefault}}`, {matchBase: true, dot: true})).filter(minimatch.filter(getDefaultIncludedFilesGlob(pkg.main), {nocase: true, matchBase: true})); +}; + exports.getRegistryUrl = async (pkgManager, pkg) => { const args = ['config', 'get', 'registry']; if (exports.isExternalRegistry(pkg)) { diff --git a/source/ui.js b/source/ui.js index 39d0941a..5114986f 100644 --- a/source/ui.js +++ b/source/ui.js @@ -79,22 +79,30 @@ const printCommitLog = async (repoUrl, registryUrl, fromLatestTag, releaseBranch }; }; -const checkIgnoredFiles = async pkg => { - const ignoredFiles = await util.getNewAndUnpublishedFiles(pkg); - if (!ignoredFiles || ignoredFiles.length === 0) { +const checkNewFiles = async pkg => { + const newFiles = await util.getNewFiles(pkg); + if ((!newFiles.unpublished || newFiles.unpublished.length === 0) && (!newFiles.firstTime || newFiles.firstTime.length === 0)) { return true; } - const message = `The following new files are not already part of your published package:\n${chalk.reset(ignoredFiles.map(path => `- ${path}`).join('\n'))}`; + const messages = []; + if (newFiles.unpublished.length > 0) { + messages.push(`The following new files will not be part of your published package:\n${chalk.reset(newFiles.unpublished.map(path => `- ${path}`).join('\n'))}`); + } + + if (newFiles.firstTime.length > 0) { + messages.push(`The following new files will be published the first time:\n${chalk.reset(newFiles.firstTime.map(path => `- ${path}`).join('\n'))}`); + } + if (!isInteractive()) { - console.log(message); + console.log(messages.join('\n')); return true; } const answers = await inquirer.prompt([{ type: 'confirm', name: 'confirm', - message: `${message}\nContinue?`, + message: `${messages.join('\n')}\nContinue?`, default: false }]); @@ -112,7 +120,7 @@ module.exports = async (options, pkg) => { if (options.runPublish) { checkIgnoreStrategy(pkg); - const answerIgnoredFiles = await checkIgnoredFiles(pkg); + const answerIgnoredFiles = await checkNewFiles(pkg); if (!answerIgnoredFiles) { return { ...options, diff --git a/source/util.js b/source/util.js index 87105c69..79d329c9 100644 --- a/source/util.js +++ b/source/util.js @@ -71,9 +71,9 @@ exports.getTagVersionPrefix = pMemoize(async options => { } }); -exports.getNewAndUnpublishedFiles = async pkg => { +exports.getNewFiles = async pkg => { const listNewFiles = await gitUtil.newFilesSinceLastRelease(); - return npmUtil.getNewAndUnpublishedFiles(pkg, listNewFiles); + return {unpublished: await npmUtil.getNewAndUnpublishedFiles(pkg, listNewFiles), firstTime: await npmUtil.getFirstTimePublishedFiles(pkg, listNewFiles)}; }; exports.getPreReleasePrefix = pMemoize(async options => { diff --git a/test/fixtures/npmignore/source/.dotfile b/test/fixtures/npmignore/source/.dotfile new file mode 100644 index 00000000..e69de29b diff --git a/test/npmignore.js b/test/npmignore.js index 2769916a..c9e678b2 100644 --- a/test/npmignore.js +++ b/test/npmignore.js @@ -63,7 +63,7 @@ test('ignored test files using files attribute and .npmignore', async t => { t.deepEqual(await testedModule.getNewAndUnpublishedFiles({directories: {test: ['test-tap']}}, newFiles), ['source/ignore.txt', 'test/file.txt']); }); -test('dot files using files attribute', async t => { +test('ignored files - dot files using files attribute', async t => { const testedModule = proxyquire('../source/npm/util', { 'pkg-dir': { @@ -73,7 +73,7 @@ test('dot files using files attribute', async t => { t.deepEqual(await testedModule.getNewAndUnpublishedFiles({files: ['source']}, ['test/.dotfile']), []); }); -test('dot files using .npmignore', async t => { +test('ignored files - dot files using .npmignore', async t => { const testedModule = proxyquire('../source/npm/util', { 'pkg-dir': { @@ -83,7 +83,7 @@ test('dot files using .npmignore', async t => { t.deepEqual(await testedModule.getNewAndUnpublishedFiles({}, ['test/.dot']), []); }); -test('ignore strategy is not used', async t => { +test('ignored files - ignore strategy is not used', async t => { const testedModule = proxyquire('../source/npm/util', { 'pkg-dir': { @@ -92,3 +92,63 @@ test('ignore strategy is not used', async t => { }); t.is(await testedModule.getNewAndUnpublishedFiles({name: 'no ignore strategy'}, newFiles), undefined); }); + +test('first time published files using file-attribute in package.json with one file', async t => { + const testedModule = proxyquire('../source/npm/util', { + 'pkg-dir': + { + sync: () => path.resolve('test', 'fixtures', 'package') + } + }); + t.deepEqual(await testedModule.getFirstTimePublishedFiles({files: ['pay_attention.txt']}, newFiles), ['source/pay_attention.txt']); +}); + +test('first time published files using file-attribute in package.json with directory', async t => { + const testedModule = proxyquire('../source/npm/util', { + 'pkg-dir': + { + sync: () => path.resolve('test', 'fixtures', 'package') + } + }); + t.deepEqual(await testedModule.getFirstTimePublishedFiles({files: ['source']}, newFiles), ['source/ignore.txt', 'source/pay_attention.txt']); +}); + +test('first time published files using .npmignore', async t => { + const testedModule = proxyquire('../source/npm/util', { + 'pkg-dir': + { + sync: () => path.resolve('test', 'fixtures', 'npmignore') + } + }); + t.deepEqual(await testedModule.getFirstTimePublishedFiles({name: 'npmignore'}, newFiles), ['source/pay_attention.txt']); +}); + +test('first time published dot files using files attribute', async t => { + const testedModule = proxyquire('../source/npm/util', { + 'pkg-dir': + { + sync: () => path.resolve('test', 'fixtures', 'package') + } + }); + t.deepEqual(await testedModule.getFirstTimePublishedFiles({files: ['source']}, ['source/.dotfile']), ['source/.dotfile']); +}); + +test('first time published dot files using .npmignore', async t => { + const testedModule = proxyquire('../source/npm/util', { + 'pkg-dir': + { + sync: () => path.resolve('test', 'fixtures', 'npmignore') + } + }); + t.deepEqual(await testedModule.getFirstTimePublishedFiles({}, ['source/.dotfile']), ['source/.dotfile']); +}); + +test('first time published files - ignore strategy is not used', async t => { + const testedModule = proxyquire('../source/npm/util', { + 'pkg-dir': + { + sync: () => path.resolve('test', 'fixtures') + } + }); + t.deepEqual(await testedModule.getFirstTimePublishedFiles({name: 'no ignore strategy'}, newFiles), ['source/ignore.txt', 'source/pay_attention.txt', 'test/file.txt']); +});