Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 44c060c

Browse files
committedJul 20, 2022
fix: don't fail immediately if cache dir is not accessible
This also changes all the log messages about not being able to create initial directories and files to `log.verbose` since we know run those commands on init. There are a lot of valid reasons why those might fail, and we don't want to show a warning for them every time. Fixes: #4769 Fixes: #4838 Fixes: #4996
1 parent 51b12a0 commit 44c060c

File tree

4 files changed

+64
-37
lines changed

4 files changed

+64
-37
lines changed
 

‎lib/npm.js

+6-4
Original file line numberDiff line numberDiff line change
@@ -241,16 +241,18 @@ class Npm extends EventEmitter {
241241
await this.time('npm:load:configload', () => this.config.load())
242242

243243
// mkdir this separately since the logs dir can be set to
244-
// a different location. an error here should be surfaced
245-
// right away since it will error in cacache later
244+
// a different location. if this fails, then we don't have
245+
// a cache dir, but we don't want to fail immediately since
246+
// the command might not need a cache dir (like `npm --version`)
246247
await this.time('npm:load:mkdirpcache', () =>
247-
fs.mkdir(this.cache, { recursive: true, owner: 'inherit' }))
248+
fs.mkdir(this.cache, { recursive: true, owner: 'inherit' })
249+
.catch((e) => log.verbose('cache', `could not create cache: ${e}`)))
248250

249251
// its ok if this fails. user might have specified an invalid dir
250252
// which we will tell them about at the end
251253
await this.time('npm:load:mkdirplogs', () =>
252254
fs.mkdir(this.logsDir, { recursive: true, owner: 'inherit' })
253-
.catch((e) => log.warn('logfile', `could not create logs-dir: ${e}`)))
255+
.catch((e) => log.verbose('logfile', `could not create logs-dir: ${e}`)))
254256

255257
// note: this MUST be shorter than the actual argv length, because it
256258
// uses the same memory, so node will truncate it if it's too long.

‎lib/utils/log-file.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,9 @@ class LogFiles {
204204
this.#files.push(logStream.path)
205205
return logStream
206206
} catch (e) {
207-
log.warn('logfile', `could not be created: ${e}`)
207+
// If the user has a readonly logdir then we don't want to
208+
// warn this on every command so it should be verbose
209+
log.verbose('logfile', `could not be created: ${e}`)
208210
}
209211
}
210212

@@ -226,7 +228,7 @@ class LogFiles {
226228
)
227229

228230
// Always ignore the currently written files
229-
const files = await glob(globify(logGlob), { ignore: this.#files.map(globify) })
231+
const files = await glob(globify(logGlob), { ignore: this.#files.map(globify), silent: true })
230232
const toDelete = files.length - this.#logsMax
231233

232234
if (toDelete <= 0) {

‎test/fixtures/mock-npm.js

+18-18
Original file line numberDiff line numberDiff line change
@@ -108,25 +108,29 @@ const LoadMockNpm = async (t, {
108108
cache: cacheDir,
109109
global: globalPrefixDir,
110110
})
111-
const prefix = path.join(dir, 'prefix')
112-
const cache = path.join(dir, 'cache')
113-
const globalPrefix = path.join(dir, 'global')
114-
const home = path.join(dir, 'home')
111+
const dirs = {
112+
testdir: dir,
113+
prefix: path.join(dir, 'prefix'),
114+
cache: path.join(dir, 'cache'),
115+
globalPrefix: path.join(dir, 'global'),
116+
home: path.join(dir, 'home'),
117+
}
115118

116119
// Set cache to testdir via env var so it is available when load is run
117120
// XXX: remove this for a solution where cache argv is passed in
118121
mockGlobals(t, {
119-
'process.env.HOME': home,
120-
'process.env.npm_config_cache': cache,
121-
...(globals ? result(globals, { prefix, cache, home }) : {}),
122+
'process.env.HOME': dirs.home,
123+
'process.env.npm_config_cache': dirs.cache,
124+
...(globals ? result(globals, { ...dirs }) : {}),
122125
// Some configs don't work because they can't be set via npm.config.set until
123126
// config is loaded. But some config items are needed before that. So this is
124127
// an explicit set of configs that must be loaded as env vars.
125128
// XXX(npm9): make this possible by passing in argv directly to npm/config
126129
...Object.entries(config)
127130
.filter(([k]) => envConfigKeys.includes(k))
128131
.reduce((acc, [k, v]) => {
129-
acc[`process.env.npm_config_${k.replace(/-/g, '_')}`] = v.toString()
132+
acc[`process.env.npm_config_${k.replace(/-/g, '_')}`] =
133+
result(v, { ...dirs }).toString()
130134
return acc
131135
}, {}),
132136
})
@@ -138,7 +142,7 @@ const LoadMockNpm = async (t, {
138142

139143
if (load) {
140144
await npm.load()
141-
for (const [k, v] of Object.entries(result(config, { npm, prefix, cache }))) {
145+
for (const [k, v] of Object.entries(result(config, { npm, ...dirs }))) {
142146
if (typeof v === 'object' && v.value && v.where) {
143147
npm.config.set(k, v.value, v.where)
144148
} else {
@@ -148,20 +152,16 @@ const LoadMockNpm = async (t, {
148152
// Set global loglevel *again* since it possibly got reset during load
149153
// XXX: remove with npmlog
150154
setLoglevel(t, config.loglevel, false)
151-
npm.prefix = prefix
152-
npm.cache = cache
153-
npm.globalPrefix = globalPrefix
155+
npm.prefix = dirs.prefix
156+
npm.cache = dirs.cache
157+
npm.globalPrefix = dirs.globalPrefix
154158
}
155159

156160
return {
157161
...rest,
162+
...dirs,
158163
Npm,
159164
npm,
160-
home,
161-
prefix,
162-
globalPrefix,
163-
testdir: dir,
164-
cache,
165165
debugFile: async () => {
166166
const readFiles = npm.logFiles.map(f => fs.readFile(f))
167167
const logFiles = await Promise.all(readFiles)
@@ -171,7 +171,7 @@ const LoadMockNpm = async (t, {
171171
.join('\n')
172172
},
173173
timingFile: async () => {
174-
const data = await fs.readFile(path.resolve(cache, '_timing.json'), 'utf8')
174+
const data = await fs.readFile(path.resolve(dirs.cache, '_timing.json'), 'utf8')
175175
return JSON.parse(data) // XXX: this fails if multiple timings are written
176176
},
177177
}

‎test/lib/npm.js

+36-13
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ const { resolve, dirname, join } = require('path')
33

44
const { load: loadMockNpm } = require('../fixtures/mock-npm.js')
55
const mockGlobals = require('../fixtures/mock-globals')
6+
const fs = require('@npmcli/fs')
7+
8+
const getMode = (f) => (fs.statSync(f).mode & parseInt('777', '8')).toString(8)
69

710
// delete this so that we don't have configs from the fact that it
811
// is being run by 'npm test'
@@ -435,23 +438,43 @@ t.test('debug log', async t => {
435438
t.match(debug, log2.join(' '), 'after load log appears')
436439
})
437440

438-
t.test('with bad dir', async t => {
439-
const { npm } = await loadMockNpm(t, {
441+
t.test('can load with bad dir', async t => {
442+
const { npm, testdir } = await loadMockNpm(t, {
443+
load: false,
440444
config: {
441-
'logs-dir': 'LOGS_DIR',
442-
},
443-
mocks: {
444-
'@npmcli/fs': {
445-
mkdir: async (dir) => {
446-
if (dir.includes('LOGS_DIR')) {
447-
throw new Error('err')
448-
}
449-
},
450-
},
445+
'logs-dir': (c) => join(c.testdir, 'my_logs_dir'),
451446
},
452447
})
448+
const logsDir = join(testdir, 'my_logs_dir')
449+
450+
// make inaccessible logs dir before load
451+
await fs.mkdir(logsDir, { recursive: true, mode: '400' })
452+
await t.resolves(npm.load(), 'loads with 400 logs dir')
453+
454+
t.equal(npm.logFiles.length, 0, 'no log files array')
455+
t.strictSame(fs.readdirSync(logsDir), [], 'no actual log files')
456+
t.equal(getMode(logsDir), '400')
457+
})
458+
})
459+
460+
t.test('cache dir', async t => {
461+
t.test('creates a cache dir', async t => {
462+
const { npm } = await loadMockNpm(t)
463+
464+
t.ok(fs.existsSync(npm.cache), 'cache dir exists')
465+
})
466+
467+
t.test('can load with a bad cache dir', async t => {
468+
const { npm, cache } = await loadMockNpm(t, {
469+
load: false,
470+
// The easiest way to make mkdir(cache) fail is to make it a file.
471+
// This will have the same effect as if its read only or inaccessible.
472+
cacheDir: 'A_TEXT_FILE',
473+
})
474+
475+
await t.resolves(npm.load(), 'loads with 400 cache dir')
453476

454-
t.equal(npm.logFiles.length, 0, 'no log file')
477+
t.equal(fs.readFileSync(cache, 'utf-8'), 'A_TEXT_FILE')
455478
})
456479
})
457480

0 commit comments

Comments
 (0)
Please sign in to comment.