Skip to content

Commit eba203f

Browse files
Improve startup: ensure package.json is only parsed once (#688)
1 parent 42a5095 commit eba203f

11 files changed

+70
-89
lines changed

Diff for: source/cli-implementation.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ import updateNotifier from 'update-notifier';
77
import hasYarn from 'has-yarn';
88
import {gracefulExit} from 'exit-hook';
99
import config from './config.js';
10+
import * as util from './util.js';
1011
import * as git from './git-util.js';
11-
import {isPackageNameAvailable} from './npm/util.js';
12+
import * as npm from './npm/util.js';
1213
import Version from './version.js';
13-
import * as util from './util.js';
1414
import ui from './ui.js';
1515
import np from './index.js';
1616

@@ -99,7 +99,7 @@ const cli = meow(`
9999
updateNotifier({pkg: cli.pkg}).notify();
100100

101101
try {
102-
const {pkg, pkgPath} = await util.readPkg();
102+
const {pkg, rootDir} = await util.readPkg(cli.flags.contents);
103103

104104
const defaultFlags = {
105105
cleanup: true,
@@ -110,7 +110,7 @@ try {
110110
'2fa': true,
111111
};
112112

113-
const localConfig = await config();
113+
const localConfig = await config(rootDir);
114114

115115
const flags = {
116116
...defaultFlags,
@@ -125,7 +125,7 @@ try {
125125

126126
const runPublish = !flags.releaseDraftOnly && flags.publish && !pkg.private;
127127

128-
const availability = flags.publish ? await isPackageNameAvailable(pkg) : {
128+
const availability = flags.publish ? await npm.isPackageNameAvailable(pkg) : {
129129
isAvailable: false,
130130
isUnknown: false,
131131
};
@@ -140,14 +140,14 @@ try {
140140
version,
141141
runPublish,
142142
branch,
143-
}, {pkg, pkgPath});
143+
}, {pkg, rootDir});
144144

145145
if (!options.confirm) {
146146
gracefulExit();
147147
}
148148

149149
console.log(); // Prints a newline for readability
150-
const newPkg = await np(options.version, options);
150+
const newPkg = await np(options.version, options, {pkg, rootDir});
151151

152152
if (options.preview || options.releaseDraftOnly) {
153153
gracefulExit();

Diff for: source/config.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import os from 'node:os';
22
import isInstalledGlobally from 'is-installed-globally';
3-
import {packageDirectory} from 'pkg-dir';
43
import {cosmiconfig} from 'cosmiconfig';
54

65
// TODO: remove when cosmiconfig/cosmiconfig#283 lands
@@ -9,8 +8,8 @@ const loadESM = async filepath => {
98
return module.default ?? module;
109
};
1110

12-
const getConfig = async () => {
13-
const searchDir = isInstalledGlobally ? os.homedir() : await packageDirectory();
11+
const getConfig = async rootDir => {
12+
const searchDir = isInstalledGlobally ? os.homedir() : rootDir;
1413
const searchPlaces = ['.np-config.json', '.np-config.js', '.np-config.cjs', '.np-config.mjs'];
1514
if (!isInstalledGlobally) {
1615
searchPlaces.push('package.json');

Diff for: source/git-util.js

+4-12
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ 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 {packageDirectory} from 'pkg-dir';
65
import Version from './version.js';
76

87
export const latestTag = async () => {
@@ -15,7 +14,7 @@ export const root = async () => {
1514
return stdout;
1615
};
1716

18-
export const newFilesSinceLastRelease = async () => {
17+
export const newFilesSinceLastRelease = async rootDir => {
1918
try {
2019
const {stdout} = await execa('git', ['diff', '--name-only', '--diff-filter=A', await latestTag(), 'HEAD']);
2120
if (stdout.trim().length === 0) {
@@ -27,7 +26,7 @@ export const newFilesSinceLastRelease = async () => {
2726
} catch {
2827
// Get all files under version control
2928
return ignoreWalker({
30-
path: await packageDirectory(),
29+
path: rootDir,
3130
ignoreFiles: ['.gitignore'],
3231
});
3332
}
@@ -205,12 +204,7 @@ export const tagExistsOnRemote = async tagName => {
205204

206205
async function hasLocalBranch(branch) {
207206
try {
208-
await execa('git', [
209-
'show-ref',
210-
'--verify',
211-
'--quiet',
212-
`refs/heads/${branch}`,
213-
]);
207+
await execa('git', ['show-ref', '--verify', '--quiet', `refs/heads/${branch}`]);
214208
return true;
215209
} catch {
216210
return false;
@@ -225,9 +219,7 @@ export const defaultBranch = async () => {
225219
}
226220
}
227221

228-
throw new Error(
229-
'Could not infer the default Git branch. Please specify one with the --branch flag or with a np config.',
230-
);
222+
throw new Error('Could not infer the default Git branch. Please specify one with the --branch flag or with a np config.');
231223
};
232224

233225
export const verifyTagDoesNotExistOnRemote = async tagName => {

Diff for: source/index.js

+2-5
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import Listr from 'listr';
66
import {merge, throwError, catchError, filter, finalize} from 'rxjs';
77
import {readPackageUp} from 'read-pkg-up';
88
import hasYarn from 'has-yarn';
9-
import {packageDirectorySync} from 'pkg-dir';
109
import hostedGitInfo from 'hosted-git-info';
1110
import onetime from 'onetime';
1211
import {asyncExitHook} from 'exit-hook';
@@ -15,10 +14,10 @@ import prerequisiteTasks from './prerequisite-tasks.js';
1514
import gitTasks from './git-tasks.js';
1615
import publish from './npm/publish.js';
1716
import enable2fa from './npm/enable-2fa.js';
18-
import * as npm from './npm/util.js';
1917
import releaseTaskHelper from './release-task-helper.js';
2018
import * as util from './util.js';
2119
import * as git from './git-util.js';
20+
import * as npm from './npm/util.js';
2221

2322
const exec = (cmd, args) => {
2423
// Use `Observable` support if merged https://github.com/sindresorhus/execa/pull/26
@@ -28,7 +27,7 @@ const exec = (cmd, args) => {
2827
};
2928

3029
// eslint-disable-next-line complexity
31-
const np = async (input = 'patch', options) => {
30+
const np = async (input = 'patch', options, {pkg, rootDir}) => {
3231
if (!hasYarn() && options.yarn) {
3332
throw new Error('Could not use Yarn without yarn.lock file');
3433
}
@@ -38,12 +37,10 @@ const np = async (input = 'patch', options) => {
3837
options.cleanup = false;
3938
}
4039

41-
const {pkg} = await util.readPkg(options.contents);
4240
const runTests = options.tests && !options.yolo;
4341
const runCleanup = options.cleanup && !options.yolo;
4442
const pkgManager = options.yarn === true ? 'yarn' : 'npm';
4543
const pkgManagerName = options.yarn === true ? 'Yarn' : 'npm';
46-
const rootDir = packageDirectorySync();
4744
const hasLockFile = fs.existsSync(path.resolve(rootDir, options.yarn ? 'yarn.lock' : 'package-lock.json')) || fs.existsSync(path.resolve(rootDir, 'npm-shrinkwrap.json'));
4845
const isOnGitHub = options.repoUrl && hostedGitInfo.fromUrl(options.repoUrl)?.type === 'github';
4946
const testScript = options.testScript || 'test';

Diff for: source/npm/util.js

+5-9
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import pTimeout from 'p-timeout';
55
import ow from 'ow';
66
import npmName from 'npm-name';
77
import chalk from 'chalk';
8-
import {packageDirectory} from 'pkg-dir';
98
import semver from 'semver';
109
import Version from '../version.js';
1110

@@ -129,21 +128,18 @@ export const verifyRecentNpmVersion = async () => {
129128
Version.verifyRequirementSatisfied('npm', npmVersion);
130129
};
131130

132-
const npmignoreExistsInPackageRootDir = async () => {
133-
const rootDir = await packageDirectory();
134-
return pathExists(path.resolve(rootDir, '.npmignore'));
135-
};
131+
export const checkIgnoreStrategy = ({files}, rootDir) => {
132+
const npmignoreExistsInPackageRootDir = pathExists(path.resolve(rootDir, '.npmignore'));
136133

137-
export const checkIgnoreStrategy = async ({files}) => {
138-
if (!files && !(await npmignoreExistsInPackageRootDir())) {
134+
if (!files && !npmignoreExistsInPackageRootDir) {
139135
console.log(`
140136
\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.
141137
`);
142138
}
143139
};
144140

145-
export const getFilesToBePacked = async () => {
146-
const {stdout} = await execa('npm', ['pack', '--dry-run', '--json'], {cwd: await packageDirectory()});
141+
export const getFilesToBePacked = async rootDir => {
142+
const {stdout} = await execa('npm', ['pack', '--dry-run', '--json'], {cwd: rootDir});
147143

148144
const {files} = JSON.parse(stdout).at(0);
149145
return files.map(file => file.path);

Diff for: source/ui.js

+10-10
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 * as npmUtil from './npm/util.js';
9+
import * as npm from './npm/util.js';
1010
import Version from './version.js';
1111
import prettyVersionDiff from './pretty-version-diff.js';
1212

@@ -78,9 +78,9 @@ const printCommitLog = async (repoUrl, registryUrl, fromLatestTag, releaseBranch
7878
};
7979
};
8080

81-
const checkNewFilesAndDependencies = async (pkg, pkgPath) => {
82-
const newFiles = await util.getNewFiles(pkg);
83-
const newDependencies = await util.getNewDependencies(pkg, pkgPath);
81+
const checkNewFilesAndDependencies = async (pkg, rootDir) => {
82+
const newFiles = await util.getNewFiles(rootDir);
83+
const newDependencies = await util.getNewDependencies(pkg, rootDir);
8484

8585
const noNewUnpublishedFiles = !newFiles.unpublished || newFiles.unpublished.length === 0;
8686
const noNewFirstTimeFiles = !newFiles.firstTime || newFiles.firstTime.length === 0;
@@ -121,18 +121,18 @@ const checkNewFilesAndDependencies = async (pkg, pkgPath) => {
121121
};
122122

123123
// eslint-disable-next-line complexity
124-
const ui = async (options, {pkg, pkgPath}) => {
124+
const ui = async (options, {pkg, rootDir}) => {
125125
const oldVersion = pkg.version;
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 npmUtil.getRegistryUrl(pkgManager, pkg);
129+
const registryUrl = await npm.getRegistryUrl(pkgManager, pkg);
130130
const releaseBranch = options.branch;
131131

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

135-
const answerIgnoredFiles = await checkNewFilesAndDependencies(pkg, pkgPath);
135+
const answerIgnoredFiles = await checkNewFilesAndDependencies(pkg, rootDir);
136136
if (!answerIgnoredFiles) {
137137
return {
138138
...options,
@@ -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 npmUtil.prereleaseTags(pkg.name);
256+
const existingPrereleaseTags = await npm.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') && !npmUtil.isExternalRegistry(pkg),
286+
when: isScoped(pkg.name) && options.availability.isAvailable && !options.availability.isUnknown && options.runPublish && (pkg.publishConfig && pkg.publishConfig.access !== 'restricted') && !npm.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
},

Diff for: source/util.js

+10-9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import path from 'node:path';
12
import {readPackageUp} from 'read-pkg-up';
23
import issueRegex from 'issue-regex';
34
import terminalLink from 'terminal-link';
@@ -6,20 +7,20 @@ import pMemoize from 'p-memoize';
67
import ow from 'ow';
78
import chalk from 'chalk';
89
import {packageDirectory} from 'pkg-dir';
9-
import * as gitUtil from './git-util.js';
10-
import * as npmUtil from './npm/util.js';
10+
import * as git from './git-util.js';
11+
import * as npm from './npm/util.js';
1112

1213
export const readPkg = async packagePath => {
1314
packagePath = packagePath ? await packageDirectory(packagePath) : await packageDirectory();
1415
if (!packagePath) {
1516
throw new Error('No `package.json` found. Make sure the current directory is a valid package.');
1617
}
1718

18-
const {packageJson, path} = await readPackageUp({
19+
const {packageJson, path: pkgPath} = await readPackageUp({
1920
cwd: packagePath,
2021
});
2122

22-
return {pkg: packageJson, pkgPath: path};
23+
return {pkg: packageJson, rootDir: path.dirname(pkgPath)};
2324
};
2425

2526
export const linkifyIssues = (url, message) => {
@@ -72,18 +73,18 @@ export const getTagVersionPrefix = pMemoize(async options => {
7273

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

75-
export const getNewFiles = async () => {
76-
const listNewFiles = await gitUtil.newFilesSinceLastRelease();
77-
const listPkgFiles = await npmUtil.getFilesToBePacked();
76+
export const getNewFiles = async rootDir => {
77+
const listNewFiles = await git.newFilesSinceLastRelease(rootDir);
78+
const listPkgFiles = await npm.getFilesToBePacked(rootDir);
7879

7980
return {
8081
unpublished: listNewFiles.filter(file => !listPkgFiles.includes(file) && !file.startsWith('.git')),
8182
firstTime: listNewFiles.filter(file => listPkgFiles.includes(file)),
8283
};
8384
};
8485

85-
export const getNewDependencies = async (newPkg, pkgPath) => {
86-
let oldPkg = await gitUtil.readFileFromLastRelease(pkgPath);
86+
export const getNewDependencies = async (newPkg, rootDir) => {
87+
let oldPkg = await git.readFileFromLastRelease(path.resolve(rootDir, 'package.json'));
8788
oldPkg = JSON.parse(oldPkg);
8889

8990
const newDependencies = [];

Diff for: test/config.js

+2-4
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,9 @@ const getConfigsWhenGlobalBinaryIsUsed = async homedirStub => {
1414
const promises = pathsPkgDir.map(async pathPkgDir => {
1515
const getConfig = await esmock(testedModulePath, {
1616
'is-installed-globally': true,
17-
'pkg-dir': {packageDirectory: async () => pathPkgDir},
1817
'node:os': {homedir: homedirStub},
1918
});
20-
return getConfig();
19+
return getConfig(pathPkgDir);
2120
});
2221

2322
return Promise.all(promises);
@@ -29,10 +28,9 @@ const getConfigsWhenLocalBinaryIsUsed = async pathPkgDir => {
2928
const promises = homedirs.map(async homedir => {
3029
const getConfig = await esmock(testedModulePath, {
3130
'is-installed-globally': false,
32-
'pkg-dir': {packageDirectory: async () => pathPkgDir},
3331
'node:os': {homedir: () => homedir},
3432
});
35-
return getConfig();
33+
return getConfig(pathPkgDir);
3634
});
3735

3836
return Promise.all(promises);

Diff for: test/index.js

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import test from 'ava';
22
import sinon from 'sinon';
33
import esmock from 'esmock';
4+
import * as util from '../source/util.js';
45
import np from '../source/index.js';
56

67
const defaultOptions = {
@@ -15,9 +16,11 @@ const defaultOptions = {
1516
renderer: 'silent',
1617
};
1718

19+
const npPkg = await util.readPkg();
20+
1821
const npFails = test.macro(async (t, inputs, message) => {
1922
await t.throwsAsync(
20-
Promise.all(inputs.map(input => np(input, defaultOptions))),
23+
Promise.all(inputs.map(input => np(input, defaultOptions, npPkg))),
2124
{message},
2225
);
2326
});
@@ -51,15 +54,15 @@ test('skip enabling 2FA if the package exists', async t => {
5154
},
5255
'../source/npm/enable-2fa.js': enable2faStub,
5356
'../source/npm/publish.js': sinon.stub().returns({pipe: sinon.stub()}),
54-
}, {});
57+
});
5558

5659
await t.notThrowsAsync(npMock('1.0.0', {
5760
...defaultOptions,
5861
availability: {
5962
isAvailable: false,
6063
isUnknown: false,
6164
},
62-
}));
65+
}, npPkg));
6366

6467
t.true(enable2faStub.notCalled);
6568
});
@@ -87,7 +90,7 @@ test('skip enabling 2FA if the `2fa` option is false', async t => {
8790
isUnknown: false,
8891
},
8992
'2fa': false,
90-
}));
93+
}, npPkg));
9194

9295
t.true(enable2faStub.notCalled);
9396
});

0 commit comments

Comments
 (0)