Skip to content

Commit a6ce792

Browse files
Get files to be packed via npm pack --dry-run --json (#682)
1 parent 72879e0 commit a6ce792

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+343
-388
lines changed

package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"yarn": ">=1.7.0"
1515
},
1616
"scripts": {
17-
"test": "xo && ava && ava test/integration.js --no-worker-threads"
17+
"test": "xo && ava"
1818
},
1919
"files": [
2020
"source"
@@ -60,6 +60,7 @@
6060
"ow": "^1.1.1",
6161
"p-memoize": "^7.1.1",
6262
"p-timeout": "^6.1.1",
63+
"path-exists": "^5.0.0",
6364
"pkg-dir": "^7.0.0",
6465
"read-pkg-up": "^9.1.0",
6566
"rxjs": "^7.8.0",
@@ -71,18 +72,17 @@
7172
"devDependencies": {
7273
"ava": "^5.2.0",
7374
"common-tags": "^1.8.2",
74-
"esmock": "^2.2.0",
75+
"esmock": "^2.2.1",
7576
"fs-extra": "^11.1.1",
7677
"sinon": "^15.0.3",
78+
"tempy": "^3.0.0",
79+
"write-pkg": "^5.1.0",
7780
"xo": "^0.53.1"
7881
},
7982
"ava": {
8083
"environmentVariables": {
8184
"FORCE_HYPERLINK": "1"
8285
},
83-
"files": [
84-
"!test/integration.js"
85-
],
8686
"nodeArguments": [
8787
"--loader=esmock"
8888
]

source/git-util.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import path from 'node:path';
22
import {execa} from 'execa';
33
import escapeStringRegexp from 'escape-string-regexp';
44
import ignoreWalker from 'ignore-walk';
5-
import {packageDirectorySync} from 'pkg-dir';
5+
import {packageDirectory} from 'pkg-dir';
66
import Version from './version.js';
77

88
export const latestTag = async () => {
@@ -27,7 +27,7 @@ export const newFilesSinceLastRelease = async () => {
2727
} catch {
2828
// Get all files under version control
2929
return ignoreWalker({
30-
path: packageDirectorySync(),
30+
path: await packageDirectory(),
3131
ignoreFiles: ['.gitignore'],
3232
});
3333
}

source/npm/util.js

Lines changed: 13 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,14 @@
1-
import fs from 'node:fs';
21
import path from 'node:path';
2+
import {pathExists} from 'path-exists';
33
import {execa} from 'execa';
44
import pTimeout from 'p-timeout';
55
import ow from 'ow';
66
import npmName from 'npm-name';
77
import chalk from 'chalk';
8-
import {packageDirectorySync} from 'pkg-dir';
9-
import ignoreWalker from 'ignore-walk';
10-
import minimatch from 'minimatch';
8+
import {packageDirectory} from 'pkg-dir';
119
import semver from 'semver';
1210
import Version from '../version.js';
1311

14-
// According to https://docs.npmjs.com/files/package.json#files
15-
// npm's default behavior is to ignore these files.
16-
const filesIgnoredByDefault = [
17-
'.*.swp',
18-
'.npmignore',
19-
'.gitignore',
20-
'._*',
21-
'.DS_Store',
22-
'.hg',
23-
'.npmrc',
24-
'.lock-wscript',
25-
'.svn',
26-
'.wafpickle-N',
27-
'*.orig',
28-
'config.gypi',
29-
'CVS',
30-
'node_modules/**/*',
31-
'npm-debug.log',
32-
'package-lock.json',
33-
'.git/**/*',
34-
'.git',
35-
];
36-
3712
export const checkConnection = () => pTimeout(
3813
(async () => {
3914
try {
@@ -154,137 +129,24 @@ export const verifyRecentNpmVersion = async () => {
154129
Version.verifyRequirementSatisfied('npm', npmVersion);
155130
};
156131

157-
export const checkIgnoreStrategy = ({files}) => {
158-
if (!files && !npmignoreExistsInPackageRootDir()) {
132+
const npmignoreExistsInPackageRootDir = async () => {
133+
const rootDir = await packageDirectory();
134+
return pathExists(path.resolve(rootDir, '.npmignore'));
135+
};
136+
137+
export const checkIgnoreStrategy = async ({files}) => {
138+
if (!files && !(await npmignoreExistsInPackageRootDir())) {
159139
console.log(`
160140
\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.
161141
`);
162142
}
163143
};
164144

165-
function npmignoreExistsInPackageRootDir() {
166-
const rootDir = packageDirectorySync();
167-
return fs.existsSync(path.resolve(rootDir, '.npmignore'));
168-
}
169-
170-
function excludeGitAndNodeModulesPaths(singlePath) {
171-
return !singlePath.startsWith('.git/') && !singlePath.startsWith('node_modules/');
172-
}
173-
174-
async function getFilesIgnoredByDotnpmignore(pkg, fileList) {
175-
let allowList = await ignoreWalker({
176-
path: packageDirectorySync(),
177-
ignoreFiles: ['.npmignore'],
178-
});
179-
allowList = allowList.filter(singlePath => excludeGitAndNodeModulesPaths(singlePath));
180-
return fileList.filter(minimatch.filter(getIgnoredFilesGlob(allowList, pkg.directories), {matchBase: true, dot: true}));
181-
}
182-
183-
function filterFileList(globArray, fileList) {
184-
if (globArray.length === 0) {
185-
return [];
186-
}
187-
188-
const globString = globArray.length > 1 ? `{${globArray.filter(singlePath => excludeGitAndNodeModulesPaths(singlePath))}}` : globArray[0];
189-
return fileList.filter(minimatch.filter(globString, {matchBase: true, dot: true})); // eslint-disable-line unicorn/no-array-callback-reference, unicorn/no-array-method-this-argument
190-
}
191-
192-
async function getFilesIncludedByDotnpmignore(pkg, fileList) {
193-
const allowList = await ignoreWalker({
194-
path: packageDirectorySync(),
195-
ignoreFiles: ['.npmignore'],
196-
});
197-
return filterFileList(allowList, fileList);
198-
}
199-
200-
function getFilesNotIncludedInFilesProperty(pkg, fileList) {
201-
const globArrayForFilesAndDirectories = [...pkg.files];
202-
const rootDir = packageDirectorySync();
203-
for (const glob of pkg.files) {
204-
try {
205-
if (fs.statSync(path.resolve(rootDir, glob)).isDirectory()) {
206-
globArrayForFilesAndDirectories.push(`${glob}/**/*`);
207-
}
208-
} catch {}
209-
}
210-
211-
const result = fileList.filter(minimatch.filter(getIgnoredFilesGlob(globArrayForFilesAndDirectories, pkg.directories), {matchBase: true, dot: true}));
212-
return result.filter(minimatch.filter(getDefaultIncludedFilesGlob(pkg.main), {nocase: true, matchBase: true}));
213-
}
214-
215-
function getFilesIncludedInFilesProperty(pkg, fileList) {
216-
const globArrayForFilesAndDirectories = [...pkg.files];
217-
const rootDir = packageDirectorySync();
218-
for (const glob of pkg.files) {
219-
try {
220-
if (fs.statSync(path.resolve(rootDir, glob)).isDirectory()) {
221-
globArrayForFilesAndDirectories.push(`${glob}/**/*`);
222-
}
223-
} catch {}
224-
}
225-
226-
return filterFileList(globArrayForFilesAndDirectories, fileList);
227-
}
228-
229-
function getDefaultIncludedFilesGlob(mainFile) {
230-
// According to https://docs.npmjs.com/files/package.json#files
231-
// npm's default behavior is to always include these files.
232-
const filesAlwaysIncluded = [
233-
'package.json',
234-
'README*',
235-
'CHANGES*',
236-
'CHANGELOG*',
237-
'HISTORY*',
238-
'LICENSE*',
239-
'LICENCE*',
240-
'NOTICE*',
241-
];
242-
if (mainFile) {
243-
filesAlwaysIncluded.push(mainFile);
244-
}
245-
246-
return `!{${filesAlwaysIncluded}}`;
247-
}
248-
249-
function getIgnoredFilesGlob(globArrayFromFilesProperty, packageDirectories) {
250-
// Test files are assumed not to be part of the package
251-
let testDirectoriesGlob = '';
252-
if (packageDirectories && Array.isArray(packageDirectories.test)) {
253-
testDirectoriesGlob = packageDirectories.test.join(',');
254-
} else if (packageDirectories && typeof packageDirectories.test === 'string') {
255-
testDirectoriesGlob = packageDirectories.test;
256-
} else {
257-
// Fallback to `test` directory
258-
testDirectoriesGlob = 'test/**/*';
259-
}
260-
261-
return `!{${globArrayFromFilesProperty.join(',')},${filesIgnoredByDefault.join(',')},${testDirectoriesGlob}}`;
262-
}
263-
264-
// Get all files which will be ignored by either `.npmignore` or the `files` property in `package.json` (if defined).
265-
export const getNewAndUnpublishedFiles = async (pkg, newFiles = []) => {
266-
if (pkg.files) {
267-
return getFilesNotIncludedInFilesProperty(pkg, newFiles);
268-
}
269-
270-
if (npmignoreExistsInPackageRootDir()) {
271-
return getFilesIgnoredByDotnpmignore(pkg, newFiles);
272-
}
273-
274-
return [];
275-
};
276-
277-
export const getFirstTimePublishedFiles = async (pkg, newFiles = []) => {
278-
let result;
279-
if (pkg.files) {
280-
result = getFilesIncludedInFilesProperty(pkg, newFiles);
281-
} else if (npmignoreExistsInPackageRootDir()) {
282-
result = await getFilesIncludedByDotnpmignore(pkg, newFiles);
283-
} else {
284-
result = newFiles;
285-
}
145+
export const getFilesToBePacked = async () => {
146+
const {stdout} = await execa('npm', ['pack', '--dry-run', '--json'], {cwd: await packageDirectory()});
286147

287-
return result.filter(minimatch.filter(`!{${filesIgnoredByDefault}}`, {matchBase: true, dot: true})).filter(minimatch.filter(getDefaultIncludedFilesGlob(pkg.main), {nocase: true, matchBase: true}));
148+
const {files} = JSON.parse(stdout).at(0);
149+
return files.map(file => file.path);
288150
};
289151

290152
export const getRegistryUrl = async (pkgManager, pkg) => {

source/ui.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import isScoped from 'is-scoped';
66
import isInteractive from 'is-interactive';
77
import * as util from './util.js';
88
import * as git from './git-util.js';
9-
import {prereleaseTags, checkIgnoreStrategy, getRegistryUrl, isExternalRegistry} from './npm/util.js';
9+
import * as npmUtil from './npm/util.js';
1010
import Version from './version.js';
1111
import prettyVersionDiff from './pretty-version-diff.js';
1212

@@ -126,11 +126,11 @@ const ui = async (options, {pkg, pkgPath}) => {
126126
const extraBaseUrls = ['gitlab.com'];
127127
const repoUrl = pkg.repository && githubUrlFromGit(pkg.repository.url, {extraBaseUrls});
128128
const pkgManager = options.yarn ? 'yarn' : 'npm';
129-
const registryUrl = await getRegistryUrl(pkgManager, pkg);
129+
const registryUrl = await npmUtil.getRegistryUrl(pkgManager, pkg);
130130
const releaseBranch = options.branch;
131131

132132
if (options.runPublish) {
133-
checkIgnoreStrategy(pkg);
133+
await npmUtil.checkIgnoreStrategy(pkg);
134134

135135
const answerIgnoredFiles = await checkNewFilesAndDependencies(pkg, pkgPath);
136136
if (!answerIgnoredFiles) {
@@ -253,7 +253,7 @@ const ui = async (options, {pkg, pkgPath}) => {
253253
message: 'How should this pre-release version be tagged in npm?',
254254
when: answers => options.runPublish && (Version.isPrereleaseOrIncrement(answers.customVersion) || Version.isPrereleaseOrIncrement(answers.version)) && !options.tag,
255255
async choices() {
256-
const existingPrereleaseTags = await prereleaseTags(pkg.name);
256+
const existingPrereleaseTags = await npmUtil.prereleaseTags(pkg.name);
257257

258258
return [
259259
...existingPrereleaseTags,
@@ -283,7 +283,7 @@ const ui = async (options, {pkg, pkgPath}) => {
283283
},
284284
publishScoped: {
285285
type: 'confirm',
286-
when: isScoped(pkg.name) && options.availability.isAvailable && !options.availability.isUnknown && options.runPublish && (pkg.publishConfig && pkg.publishConfig.access !== 'restricted') && !isExternalRegistry(pkg),
286+
when: isScoped(pkg.name) && options.availability.isAvailable && !options.availability.isUnknown && options.runPublish && (pkg.publishConfig && pkg.publishConfig.access !== 'restricted') && !npmUtil.isExternalRegistry(pkg),
287287
message: `This scoped repo ${chalk.bold.magenta(pkg.name)} hasn't been published. Do you want to publish it publicly?`,
288288
default: false,
289289
},

source/util.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,14 @@ export const getTagVersionPrefix = pMemoize(async options => {
7272

7373
export const joinList = list => chalk.reset(list.map(item => `- ${item}`).join('\n'));
7474

75-
export const getNewFiles = async pkg => {
75+
export const getNewFiles = async () => {
7676
const listNewFiles = await gitUtil.newFilesSinceLastRelease();
77-
return {unpublished: await npmUtil.getNewAndUnpublishedFiles(pkg, listNewFiles), firstTime: await npmUtil.getFirstTimePublishedFiles(pkg, listNewFiles)};
77+
const listPkgFiles = await npmUtil.getFilesToBePacked();
78+
79+
return {
80+
unpublished: listNewFiles.filter(file => !listPkgFiles.includes(file) && !file.startsWith('.git')),
81+
firstTime: listNewFiles.filter(file => listPkgFiles.includes(file)),
82+
};
7883
};
7984

8085
export const getNewDependencies = async (newPkg, pkgPath) => {

test/_utils.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,9 @@ export const assertTaskDisabled = (t, taskTitle) => {
4848
export const assertTaskDoesntExist = (t, taskTitle) => {
4949
t.true(SilentRenderer.tasks.every(task => task.title !== taskTitle), `'${taskTitle}' exists!`);
5050
};
51+
52+
export const runIfExists = async (func, ...args) => {
53+
if (typeof func === 'function') {
54+
await func(...args);
55+
}
56+
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<!--
2+
3+
Thanks for submitting a pull request 🙌
4+
5+
**Note:** Please don't create a pull request which has significant changes (i.e. adds new functionality or modifies existing one in a non-trivial way) without creating an issue first.
6+
7+
Try to limit the scope of your pull request and provide a general description of the changes. If this fixes an open issue, link to it in the following way: `Fixes #321`. New features and bug fixes should come with tests.
8+
9+
-->
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log('foo');
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "foo",
3+
"version": "0.0.0",
4+
"files": ["index.js"]
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "foo",
3+
"version": "0.0.0",
4+
"files": ["source"]
5+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Foo
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
index.test-d.ts
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log('bar');
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log('foo');
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export function foo(): string;
2+
export function bar(): string;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import {expectType} from 'tsd';
2+
import {foo, bar} from '.';
3+
4+
expectType<string>(foo());
5+
expectType<string>(bar());
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log('foo');
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "foo",
3+
"version": "0.0.0",
4+
"files": ["/index.js"]
5+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
console.log('foo');
2+
console.log('bar');
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# This file is renamed to `.gitignore` in the test
2+
# This is not named `.gitignore` to allow `dist/` to be committed
3+
dist
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default function foo(): string;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function foo() {
2+
return 'bar';
3+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import {expectType} from 'tsd';
2+
import foo from '.';
3+
4+
expectType<string>(foo());
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "foo",
3+
"version": "0.0.0",
4+
"files": ["dist"]
5+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Foo
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log('foo');
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
MIT

0 commit comments

Comments
 (0)