feat: discover `.nirc` by walking up from cwd
Describe the feature
Today .nirc is looked up at exactly one location. From src/config.ts:
const customRcPath = process.env.NI_CONFIG_FILE
const home = process.platform === 'win32' ? process.env.USERPROFILE : process.env.HOME
const defaultRcPath = path.join(home || '~/', '.nirc')
const rcPath = customRcPath || defaultRcPathThere is no directory traversal, so a .nirc sitting next to a project's package.json is ignored. The only way to get per-project configuration today is to export NI_CONFIG_FILE yourself, which has to be done per shell session and is easy to forget or to leak into unrelated projects.
This is inconsistent with the rest of ni: detect() already walks up from cwd to find the lockfile, so the agent is resolved relative to the project while the configuration that shapes how that agent is invoked is resolved relative to the user's home directory.
Suggested solution
Give .nirc the same upward discovery that other package manager config files get, and layer the results.
#359 already introduces exactly this traversal for .npmrc / .yarnrc.yml in src/catalog/range-prefix.ts:
// Nearest config first, falling back to the one in the home directory, which is
// how both npm and yarn layer their own configuration files.
function findConfigFiles(cwd: string, fileName: string): string[] {
const found: string[] = []
let dir = path.resolve(cwd)
while (true) {
const filePath = path.join(dir, fileName)
if (fs.existsSync(filePath))
found.push(filePath)
const parent = path.dirname(dir)
if (parent === dir)
break
dir = parent
}
const homeConfig = path.join(os.homedir(), fileName)
if (!found.includes(homeConfig) && fs.existsSync(homeConfig))
found.push(homeConfig)
return found
}The proposal is to reuse that helper (extracted to a shared module) for .nirc, and merge the files it returns key-by-key with the file nearest to cwd winning. ~/.nirc then behaves as a base layer that a project .nirc overrides only for the keys it actually sets, which is how npm and yarn layer their own configuration.
Resolution order would become:
- built-in defaults
~/.nirc- any
.nircfound walking up fromcwd, nearest last NI_*environment variables
Backwards compatibility:
NI_CONFIG_FILE, when set, keeps its current meaning: that single file is used and no traversal happens.- A user with only
~/.nircsees no behaviour change. - Environment variables keep overriding files.
Two details worth settling in the discussion:
getConfig()currently takes no arguments and caches its result in a module-level variable, whilercPathis computed once at import time. Traversal needs acwd, sogetConfig()would readprocess.cwd()(or accept an optionalcwd) and the cache key would have to account for it.getConfig()ends by overwritingdefaultAgentwith the result ofdetect(), so a project-leveldefaultAgentwould still lose to lockfile detection. That may well be the desired precedence, but it is worth stating explicitly rather than leaving it as an accident of ordering.
Alternative
- Nearest
.nircwins outright and~/.nircis ignored when a closer file exists. Simpler, but a project file that sets one key would silently discard every global setting. - Document
NI_CONFIG_FILEwith direnv or a similar tool as the supported per-project story and change nothing inni. This works but pushes the problem onto every user and every project.
Source: antfu-collective/ni