Skip to content

Commit 124438f

Browse files
committed
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 124438f

File tree

4 files changed

+61
-37
lines changed

4 files changed

+61
-37
lines changed

lib/npm.js

Lines changed: 6 additions & 4 deletions
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

Lines changed: 4 additions & 2 deletions
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

Lines changed: 18 additions & 18 deletions
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

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ 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')
67

78
// delete this so that we don't have configs from the fact that it
89
// is being run by 'npm test'
@@ -435,23 +436,42 @@ t.test('debug log', async t => {
435436
t.match(debug, log2.join(' '), 'after load log appears')
436437
})
437438

438-
t.test('with bad dir', async t => {
439-
const { npm } = await loadMockNpm(t, {
439+
t.test('can load with bad dir', async t => {
440+
const { npm, testdir } = await loadMockNpm(t, {
441+
load: false,
440442
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-
},
443+
'logs-dir': (c) => join(c.testdir, 'my_logs_dir'),
451444
},
452445
})
446+
const logsDir = join(testdir, 'my_logs_dir')
447+
448+
// make logs dir a file before load so it files
449+
await fs.writeFile(logsDir, 'A_TEXT_FILE')
450+
await t.resolves(npm.load(), 'loads with invalid logs dir')
451+
452+
t.equal(npm.logFiles.length, 0, 'no log files array')
453+
t.strictSame(fs.readFileSync(logsDir, 'utf-8'), 'A_TEXT_FILE')
454+
})
455+
})
456+
457+
t.test('cache dir', async t => {
458+
t.test('creates a cache dir', async t => {
459+
const { npm } = await loadMockNpm(t)
460+
461+
t.ok(fs.existsSync(npm.cache), 'cache dir exists')
462+
})
463+
464+
t.test('can load with a bad cache dir', async t => {
465+
const { npm, cache } = await loadMockNpm(t, {
466+
load: false,
467+
// The easiest way to make mkdir(cache) fail is to make it a file.
468+
// This will have the same effect as if its read only or inaccessible.
469+
cacheDir: 'A_TEXT_FILE',
470+
})
471+
472+
await t.resolves(npm.load(), 'loads with cache dir as a file')
453473

454-
t.equal(npm.logFiles.length, 0, 'no log file')
474+
t.equal(fs.readFileSync(cache, 'utf-8'), 'A_TEXT_FILE')
455475
})
456476
})
457477

0 commit comments

Comments
 (0)