文章目录
- 失败案例
- npm
- yarn
- 成功案例
- cnpm
- 更奇怪的事
- 原因分析
- npm/yarn尝试多次却失败,cnpm第一次尝试就安装成功
- 当 cnpm 安装成功一次后,再次使用 npm/yarn 安装都会成功。
- install.js 脚本内容
昨晚在本地安装 Electron 一直失败,想着早上使用下载服务的人应该少点。
失败案例
安装之前有设置 npm 和 yarn 的镜像为淘宝
npm config set registry https://registry.npm.taobao.org/
yarn config set registry https://registry.npm.taobao.org/npm
等待时间较长,有错误日志(可惜了,我没截图,没日志内容)。
yarn
sudo yarn add electron 卡在这一步: Building fresh packages...

成功案例
cnpm
命令列表
# 全局安装 cnpm
sudo npm i -g cnpm
# 查看 cnpm 的安装路径
where cnpm
# 查看镜像(未修改)
cnpm config get registry
# 使用 cnpm 安装 electron, 失败,对一些目录的访问受限。
cnpm i electron
# sudo 执行,安装成功。。but why?
sudo cnpm i electron
# 查看 package.json
more package.json
wuyujin1997@mac11 electron-demo % sudo npm i -g cnpm
npm WARN deprecated har-validator@5.1.5: this library is no longer supported
npm WARN deprecated uuid@3.4.0: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.
npm WARN deprecated request@2.88.2: request has been deprecated, see https:///request/request/issues/3142
added 372 packages in 8s
3 packages are looking for funding
run `npm fund` for details
wuyujin1997@mac11 electron-demo % where cnpm
/usr/local/bin/cnpm
wuyujin1997@mac11 electron-demo % cnpm config get registry
https://registry.npmmirror.com/
wuyujin1997@mac11 electron-demo % cnpm i electron
✖ Install fail! Error: EACCES: permission denied, unlink '/Users/wuyujin1997/Desktop/Coderepo/quickstart/electron-demo/node_modules/.bin/electron'
Error: EACCES: permission denied, unlink '/Users/wuyujin1997/Desktop/Coderepo/quickstart/electron-demo/node_modules/.bin/electron'
npminstall version: 5.8.1
npminstall args: /usr/local/bin/node /usr/local/lib/node_modules/cnpm/node_modules/npminstall/bin/install.js --fix-bug-versions --china --userconfig=/Users/wuyujin1997/.cnpmrc --disturl=https://npmmirror.com/mirrors/node --registry=https://registry.npmmirror.com electron
wuyujin1997@mac11 electron-demo % sudo cnpm i electron
✔ Installed 1 packages
✔ Linked 1 latest versions
✔ Run 0 scripts
✔ All packages installed (used 21ms(network 20ms), speed 0B/s, json 0(0B), tarball 0B, manifests cache hit 1, etag hit 0 / miss 0)
wuyujin1997@mac11 electron-demo %
wuyujin1997@mac11 electron-demo %
wuyujin1997@mac11 electron-demo %
wuyujin1997@mac11 electron-demo %
wuyujin1997@mac11 electron-demo % more package.json
{
"name": "electron-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"cross-env": "^7.0.3",
"electron": "^18.1.0"
}
}
wuyujin1997@mac11 electron-demo %更奇怪的事
当我使用cnpm i electron安装成功一次之后,
又去新建了项目,使用npm i electron,也安装成功了(而且速度很快):

再去新建项目,sudo yarn install electron,也是一次成功,而且很快:

……

wuyujin1997@mac11 start-electron % npm config get registry
https://registry.npm.taobao.org/
wuyujin1997@mac11 start-electron % yarn config get registry
https://registry.npm.taobao.org/
wuyujin1997@mac11 start-electron % yarn add electron
yarn add v1.22.18
[1/4] 🔍 Resolving packages...
[2/4] 🚚 Fetching packages...
[3/4] 🔗 Linking dependencies...
error Could not write file "/opt/tmpcode/start-electron/yarn-error.log": "EACCES: permission denied, open '/opt/tmpcode/start-electron/yarn-error.log'"
error An unexpected error occurred: "EACCES: permission denied, unlink '/opt/tmpcode/start-electron/node_modules/@sindresorhus/is/license'".
info Visit https://yarnpkg.com/en/docs/cli/add for documentation about this command.
wuyujin1997@mac11 start-electron % sudo yarn add electron
Password:
yarn add v1.22.18
[1/4] 🔍 Resolving packages...
[2/4] 🚚 Fetching packages...
[3/4] 🔗 Linking dependencies...
[4/4] 🔨 Building fresh packages...
success Saved lockfile.
success Saved 82 new dependencies.
info Direct dependencies
└─ electron@18.1.0
info All dependencies
├─ @electron/get@1.14.1
├─ @sindresorhus/is@0.14.0
├─ @szmarczak/http-timer@1.1.2
├─ @types/node@16.11.31
├─ buffer-crc32@0.2.13
├─ buffer-from@1.1.2
├─ cacheable-request@6.1.0
├─ clone-response@1.0.2
├─ concat-stream@1.6.2
├─ config-chain@1.1.13
├─ core-util-is@1.0.3
├─ debug@4.3.4
├─ decompress-response@3.3.0
├─ defer-to-connect@1.1.3
├─ define-properties@1.1.4
├─ detect-node@2.1.0
├─ duplexer3@0.1.4
├─ electron@18.1.0
├─ encodeurl@1.0.2
├─ end-of-stream@1.4.4
├─ env-paths@2.2.1
├─ es6-error@4.1.1
├─ escape-string-regexp@4.0.0
├─ extract-zip@1.7.0
├─ fd-slicer@1.1.0
├─ fs-extra@8.1.0
├─ get-intrinsic@1.1.1
├─ get-stream@4.1.0
├─ global-agent@3.0.0
├─ global-tunnel-ng@2.7.1
├─ globalthis@1.0.2
├─ got@9.6.0
├─ graceful-fs@4.2.10
├─ has-property-descriptors@1.0.0
├─ has-symbols@1.0.3
├─ has@1.0.3
├─ http-cache-semantics@4.1.0
├─ inherits@2.0.4
├─ ini@1.3.8
├─ isarray@1.0.0
├─ json-buffer@3.0.0
├─ json-stringify-safe@5.0.1
├─ jsonfile@4.0.0
├─ keyv@3.1.0
├─ lodash@4.17.21
├─ lowercase-keys@1.0.1
├─ lru-cache@6.0.0
├─ matcher@3.0.0
├─ minimist@1.2.6
├─ mkdirp@0.5.6
├─ ms@2.1.2
├─ normalize-url@4.5.1
├─ npm-conf@1.1.3
├─ object-keys@1.1.1
├─ once@1.4.0
├─ p-cancelable@1.1.0
├─ pend@1.2.0
├─ pify@3.0.0
├─ prepend-http@2.0.0
├─ process-nextick-args@2.0.1
├─ progress@2.0.3
├─ proto-list@1.2.4
├─ readable-stream@2.3.7
├─ responselike@1.0.2
├─ roarr@2.15.4
├─ safe-buffer@5.1.2
├─ semver-compare@1.0.0
├─ semver@6.3.0
├─ serialize-error@7.0.1
├─ sprintf-js@1.1.2
├─ string_decoder@1.1.1
├─ sumchecker@3.0.1
├─ to-readable-stream@1.0.0
├─ tunnel@0.0.6
├─ type-fest@0.13.1
├─ typedarray@0.0.6
├─ universalify@0.1.2
├─ url-parse-lax@3.0.0
├─ util-deprecate@1.0.2
├─ wrappy@1.0.2
├─ yallist@4.0.0
└─ yauzl@2.10.0
✨ Done in 2.79s.
wuyujin1997@mac11 start-electron % pwd
/opt/tmpcode/start-electron
wuyujin1997@mac11 start-electron %原因分析
npm/yarn尝试多次却失败,cnpm第一次尝试就安装成功

/usr/local/bin/node
/usr/local/lib/node_modules/cnpm/node_modules/npminstall/bin/install.js
--fix-bug-versions
--china
--userconfig=/Users/wuyujin1997/.cnpmrc
--disturl=https://npmmirror.com/mirrors/node
--registry=https://registry.npmmirror.com
electron用 node 执行了脚本 install.js ,5个参数,最后指明要安装的包是 electron 。
所以,npm/yarn 安装失败, cnpm 第一次就安装成功的原因,必定就在这个 install.js 和这5个参数中(尤其是 registry 参数)。
(操作时间也算一个,昨晚下载失败,今天早晨下载成功,请求服务器资源的人数不同)。
install.js 脚本内容会贴在文末。
当 cnpm 安装成功一次后,再次使用 npm/yarn 安装都会成功。
node 全局包管理的地方,应该有一些缓存信息。
比如之前我这台机器下载过 electron ,从哪个URL下载的,应该会有记录。
之后再次下载,要么从记录中成功过的URL下载,
要么从本地已经下载过的文件里拷贝。
install.js 脚本内容
快500行代码:
#!/usr/bin/env node
'use strict';
const debug = require('debug')('npminstall:bin:install');
const npa = require('../lib/npa');
const chalk = require('chalk');
const path = require('path');
const util = require('util');
const execSync = require('child_process').execSync;
const fs = require('mz/fs');
const parseArgs = require('minimist');
const utils = require('../lib/utils');
const globalConfig = require('../lib/config');
const installLocal = require('..').installLocal;
const installGlobal = require('..').installGlobal;
const { parsePackageName } = require('../lib/alias');
const {
LOCAL_TYPES,
REMOTE_TYPES,
ALIAS_TYPES,
} = require('../lib/npa_types');
const Context = require('../lib/context');
const originalArgv = process.argv.slice(2);
// since minimist consider --no-xx is xx:false, we handle it manually here
const argv = { 'no-save': originalArgv.includes('--no-save') };
Object.assign(argv, parseArgs(originalArgv, {
string: [
'root',
'registry',
'prefix',
'forbidden-licenses',
'custom-china-mirror-url',
// {"http://":"http://"}
'tarball-url-mapping',
'proxy',
// --high-speed-store=filepath
'high-speed-store',
'dependencies-tree',
],
boolean: [
'version',
'help',
'production',
'client',
'global',
'save',
'save-dev',
'save-optional',
'save-client',
'save-build',
'save-isomorphic',
// Saved dependencies will be configured with an exact version rather than using npm's default semver range operator.
'save-exact',
'china',
'ignore-scripts',
// install ignore optionalDependencies
'optional',
'detail',
'trace',
'engine-strict',
'flatten',
'registry-only',
'cache-strict',
'fix-bug-versions',
'prune',
// disable dedupe mode https://docs.npmjs.com/cli/dedupe, back to npm@2 mode
// please don't use on frontend project
'disable-dedupe',
'save-dependencies-tree',
'force-link-latest',
],
default: {
optional: true,
},
alias: {
// npm install [-S|--save|-D|--save-dev|-O|--save-optional] [-E|--save-exact] [-d|--detail]
S: 'save',
D: 'save-dev',
O: 'save-optional',
E: 'save-exact',
v: 'version',
h: 'help',
g: 'global',
c: 'china',
r: 'registry',
d: 'detail',
},
})
);
if (argv.version) {
console.log(`npminstall v${require('../package.json').version}`);
process.exit(0);
}
if (argv.help) {
console.log(`
Usage:
npminstall
npminstall <pkg>
npminstall <pkg>@<tag>
npminstall <pkg>@<version>
npminstall <pkg>@<version range>
npminstall <folder>
npminstall <tarball file>
npminstall <tarball url>
npminstall <git:// url>
npminstall <github username>/<github project>
npminstall --proxy=http://localhost:8080
Can specify one or more: npminstall ./foo.tgz bar@stable /some/folder
If no argument is supplied, installs dependencies from ./package.json.
Options:
--production: won't install devDependencies
--client: install clientDependencies and buildDependencies
--save, --save-dev, --save-optional, --save-exact, --save-client, --save-build, --save-isomorphic: save installed dependencies into package.json
--no-save: Prevents saving to dependencies
-g, --global: install devDependencies to global directory which specified in '$npm config get prefix'
-r, --registry: specify custom registry
-c, --china: specify in china, will automatically using chinese npm registry and other binary's mirrors
-d, --detail: show detail log of installation
--trace: show memory and cpu usages traces of installation
--ignore-scripts: ignore all preinstall / install and postinstall scripts during the installation
--no-optional: ignore all optionalDependencies during the installation
--forbidden-licenses: forbit install packages which used these licenses
--engine-strict: refuse to install (or even consider installing) any package that claims to not be compatible with the current Node.js version.
--flatten: flatten dependencies by matching ancestors' dependencies
--registry-only: make sure all packages install from registry. Any package is installed from remote(e.g.: git, remote url) cause install fail.
--cache-strict: use disk cache even on production env.
--fix-bug-versions: auto fix bug version of package.
--prune: prune unnecessary files from ./node_modules, such as markdown, typescript source files, and so on.
--high-speed-store: specify high speed store script to cache tgz files, and so on. Should export '* getStream(url)' function.
--dependencies-tree: install with dependencies tree to restore the last install.
--force-link-latest: force link latest version package to module root path.
`
);
process.exit(0);
}
const pkgs = [];
if (process.env.NPMINSTALL_BY_UPDATE) {
// ignore all package names on update
argv._ = [];
}
const context = new Context();
for (const name of argv._) {
context.nested.update([ name ]);
const [
aliasPackageName,
] = parsePackageName(name, context.nested);
const p = npa(name, { where: argv.root, nested: context.nested });
pkgs.push({
name: ,
// `mozilla/nunjucks#0f8b21b8df7e8e852b2e1889388653b7075f0d09` should be rawSpec
version: p.fetchSpec || p.rawSpec,
type: p.type,
alias: aliasPackageName,
});
}
let root = argv.root || process.cwd();
if (Array.isArray(root)) {
// use last one, e.g.: $ npminstall --root=abc --root=def
root = root[root.length - 1];
}
const production = argv.production || process.env.NODE_ENV === 'production';
let cacheDir = argv.cache === false ? '' : null;
if (production) {
cacheDir = '';
}
// support npm_config_cache to change default cache dir
if (cacheDir === null && process.env.npm_config_cache) {
cacheDir = process.env.npm_config_cache;
}
let forbiddenLicenses = argv['forbidden-licenses'];
forbiddenLicenses = forbiddenLicenses ? forbiddenLicenses.split(',') : null;
const flatten = argv.flatten;
const prune = argv.prune;
// if in china, will automatic using chines registry and mirros.
const inChina = argv.china || !!process.env.npm_china;
// if exists, override default china mirror url
const customChinaMirrorUrl = argv['custom-china-mirror-url'];
// example: npminstall --registry xx --registry xxxx
let registry = (Array.isArray(argv.registry) ? argv.registry[0] : argv.registry) || process.env.npm_registry;
if (inChina) {
registry = registry || globalConfig.chineseRegistry;
}
// for env.npm_config_registry
registry = registry || 'https://registry.npmjs.com';
const proxy = argv.proxy || process.env.npm_proxy || process.env.npm_config_proxy;
const env = {
npm_config_registry: registry,
// set npm_config_argv
// see https:///cnpm/npminstall/issues/121#issuecomment-247836741
npm_config_argv: JSON.stringify({
remain: [],
cooked: originalArgv,
original: originalArgv,
}),
// user-agent
npm_config_user_agent: globalConfig.userAgent,
};
// https:///npm/npm/blob/2005f4ce11f6cdf142f8a77f4f7ee4996000fb57/lib/utils/lifecycle.js#L67
env.npm_node_execpath = env.NODE = process.env.NODE || process.execPath;
env.npm_execpath = require.main.filename;
// package's npm script can get root from `env.npm_rootpath`
env.npm_rootpath = process.env.npm_rootpath || root;
// npm cli will auto set options to npm_xx env.
for (const key in argv) {
const value = argv[key];
if (value && typeof value === 'string') {
env['npm_config_' + key] = value;
}
}
debug('argv: %j, env: %j', argv, env);
(async () => {
let binaryMirrors = {};
if (inChina) {
binaryMirrors = await utils.getBinaryMirrors(registry, { proxy });
if (customChinaMirrorUrl) {
for (const key in binaryMirrors) {
const item = binaryMirrors[key];
if (item.host) {
item.host = item.host.replace(globalConfig.chineseMirrorUrl, customChinaMirrorUrl);
}
}
}
// set env
for (const key in binaryMirrors.ENVS) {
env[key] = binaryMirrors.ENVS[key];
if (customChinaMirrorUrl) {
env[key] = env[key].replace(globalConfig.chineseMirrorUrl, customChinaMirrorUrl);
}
}
}
const config = {
root,
registry,
pkgs,
production,
cacheStrict: argv['cache-strict'],
cacheDir,
env,
binaryMirrors,
forbiddenLicenses,
flatten,
proxy,
prune,
disableDedupe: argv['disable-dedupe'],
};
config.strictSSL = getStrictSSL();
config.ignoreScripts = argv['ignore-scripts'] || getIgnoreScripts();
config.ignoreOptionalDependencies = !argv.optional;
config.detail = argv.detail;
config.forceLinkLatest = !!argv['force-link-latest'];
config.trace = argv.trace;
config.engineStrict = argv['engine-strict'];
config.registryOnly = argv['registry-only'];
if (config.production || argv.global) {
// make sure show detail on production install or global install
config.detail = true;
}
config.client = argv.client;
if (argv['tarball-url-mapping']) {
const tarballUrlMapping = JSON.parse(argv['tarball-url-mapping']);
config.formatNpmTarbalUrl = function formatNpmTarbalUrl(url) {
for (const fromUrl in tarballUrlMapping) {
const toUrl = tarballUrlMapping[fromUrl];
url = url.replace(fromUrl, toUrl);
}
return url;
};
}
if (argv['fix-bug-versions']) {
const packageVersionMapping = await utils.getBugVersions(registry, { proxy });
config.autoFixVersion = function autoFixVersion(name, version) {
const fixVersions = packageVersionMapping[name];
return fixVersions && fixVersions[version] || null;
};
}
const dependenciesTree = argv['dependencies-tree'];
if (dependenciesTree) {
try {
const content = fs.readFileSync(dependenciesTree);
config.dependenciesTree = JSON.parse(content);
} catch (err) {
console.warn(chalk.yellow('npminstall WARN load dependencies tree %s error: %s'), dependenciesTree, err.message);
}
}
if (argv['save-dependencies-tree']) {
config.saveDependenciesTree = true;
}
if (argv['high-speed-store']) {
config.highSpeedStore = require(argv['high-speed-store']);
}
// -g install to npm's global prefix
if (argv.global) {
// support custom prefix for global install
const meta = utils.getGlobalInstallMeta(argv.prefix);
config.targetDir = meta.targetDir;
config.binDir = meta.binDir;
await installGlobal(config, context);
} else {
if (pkgs.length === 0) {
if (config.production) {
// warning when `${root}/node_modules` exists
const nodeModulesDir = path.join(root, 'node_modules');
if (await fs.exists(nodeModulesDir)) {
const dirs = await fs.readdir(nodeModulesDir);
// ignore [ '.bin', 'node' ], it will install first by https:///cnpm/nodeinstall
if (!(dirs.length === 2 && dirs.indexOf('.bin') >= 0 && dirs.indexOf('node') >= 0)) {
console.error(chalk.yellow(`npminstall WARN node_modules exists: ${nodeModulesDir}, contains ${dirs.length} dirs`));
}
}
}
const pkgFile = path.join(root, 'package.json');
const exists = await fs.exists(pkgFile);
if (!exists) {
console.warn(chalk.yellow(`npminstall WARN package.json not exists: ${pkgFile}`));
} else {
// try to read npminstall config from package.json
const pkg = await utils.readJSON(pkgFile);
pkg.config = pkg.config || {};
pkg.config.npminstall = pkg.config.npminstall || {};
// {
// "config": {
// "npminstall": {
// "prune": true
// }
// }
// }
if (pkg.config.npminstall.prune === true) {
config.prune = true;
}
if (pkg.config.npminstall.disableDedupe === true) {
config.disableDedupe = true;
}
// env config
// {
// "config": {
// "npminstall": {
// "env:production": {
// "disableDedupe": true
// }
// }
// }
// }
// production
if (config.production && pkg.config.npminstall['env:production']) {
const envConfig = pkg.config.npminstall['env:production'];
if (envConfig.prune === true) {
config.prune = true;
}
if (envConfig.disableDedupe === true) {
config.disableDedupe = true;
}
}
// development
if (!config.production && pkg.config.npminstall['env:development']) {
const envConfig = pkg.config.npminstall['env:development'];
if (envConfig.prune === true) {
config.prune = true;
}
if (envConfig.disableDedupe === true) {
config.disableDedupe = true;
}
}
}
}
await installLocal(config, context);
if (pkgs.length > 0) {
// support --save, --save-dev, --save-optional, --save-client, --save-build and --save-isomorphic
const map = {
save: 'dependencies',
'save-dev': 'devDependencies',
'save-optional': 'optionalDependencies',
'save-client': 'clientDependencies',
'save-build': 'buildDependencies',
'save-isomorphic': 'isomorphicDependencies',
};
// install saves any specified packages into dependencies by default.
if (Object.keys(map).every(key => !argv[key]) && !argv['no-save']) {
await updateDependencies(root, pkgs, map.save, argv['save-exact'], config.remoteNames);
} else {
for (const key in map) {
if (argv[key]) await updateDependencies(root, pkgs, map[key], argv['save-exact'], config.remoteNames);
}
}
}
}
process.on('exit', code => {
if (code !== 0) {
fs.writeFileSync(path.join(root, 'npminstall-debug.log'), util.inspect(config, { depth: 2 }));
}
});
})().catch(err => {
console.error((err.stack));
console.error(chalk.yellow('npminstall version: %s'), require('../package.json').version);
console.error(chalk.yellow('npminstall args: %s'), process.argv.join(' '));
process.exit(1);
});
function getVersionSavePrefix() {
try {
return execSync('npm config get save-prefix').toString().trim();
} catch (err) {
debug(`exec npm config get save-prefix ERROR: ${err.message}`);
return '^';
}
}
function getStrictSSL() {
try {
const strictSSL = execSync('npm config get strict-ssl').toString().trim();
return strictSSL !== 'false';
} catch (err) {
debug(`exec npm config get strict-ssl ERROR: ${err.message}`);
return true;
}
}
function getIgnoreScripts() {
try {
const ignoreScripts = execSync('npm config get ignore-scripts').toString().trim();
return ignoreScripts === 'true';
} catch (err) {
debug(`exec npm config get ignore-scripts ERROR: ${err.message}`);
return false;
}
}
async function updateDependencies(root, pkgs, propName, saveExact, remoteNames) {
const savePrefix = saveExact ? '' : getVersionSavePrefix();
const pkgFile = path.join(root, 'package.json');
const pkg = await utils.readJSON(pkgFile);
const deps = pkg[propName] = pkg[propName] || {};
for (const item of pkgs) {
if (REMOTE_TYPES.includes(item.type)) {
// if install from remote or git and don't specified name
// get package's name from `remoteNames`
item.name
? deps[item.name] = item.version
: deps[remoteNames[item.version]] = item.version;
} else if (item.type === ALIAS_TYPES) {
deps[item.name] = item.version;
} else {
const pkgDir = LOCAL_TYPES.includes(item.type) ? item.version : path.join(root, 'node_modules', item.name);
const itemPkg = await utils.readJSON(path.join(pkgDir, 'package.json'));
deps[itemPkg.name] = `${savePrefix}${itemPkg.version}`;
}
}
// sort pkg[propName]
const newDeps = {};
for (const key of Object.keys(deps).sort()) {
newDeps[key] = deps[key];
}
pkg[propName] = newDeps;
await fs.writeFile(pkgFile, JSON.stringify(pkg, null, 2) + '\n');
}
















