cosmiconfig icon indicating copy to clipboard operation
cosmiconfig copied to clipboard

Respect the XDG base directory specification

Open dsifford opened this issue 6 years ago • 15 comments

Hello,

I searched in the issues here and shockingly I haven't found this mentioned.

Currently, cosmiconfig searches up the parent tree from the working directory, stopping at $HOME, which is fine and most, I would assume, are okay with this.

However, for filesystem psychopaths like myself, it pains me to have to move any sort of user configuration file out $XDG_CONFIG_HOME/project-name/ directory (usually, ~/.config/project-name/) and pollute my home directory.

Any thoughts on adding this as a final search path?

You can read up on the specification here and here.

In short, using prettier as an example, I'd imagine the workflow to go like this...

  1. Perform the current lookup traversal as it is done right now.
  2. If nothing found, check the following:
  • $XDG_CONFIG_HOME/prettier/config
  • $XDG_CONFIG_HOME/prettier/[...current standard file name checks]

Thanks for considering.

dsifford avatar Jul 04 '18 18:07 dsifford

this is one of the reasons davidtheclark added searchPlaces. if you find it doesn't suit your needs then let me know.

olsonpm avatar Jul 04 '18 19:07 olsonpm

Thanks @dsifford. @olsonpm is right that this should be possible already by using searchPlaces. So at this point your feature request is for the cosmiconfig consumer, maybe Prettier.

davidtheclark avatar Jul 04 '18 19:07 davidtheclark

It makes sense to look in XDG_CONFIG_HOME by default, doesn't it?

SimenB avatar Jan 15 '19 16:01 SimenB

@SimenB We've still heard very minimal feedback that this is important to enough people and conventional enough that it should be included by default. We have 2 voices in this issue, 3 in https://github.com/prettier/prettier/issues/4810, and the discussion started more than 6 months ago. If Prettier has only received a few thumbs about it and has not addressed it themselves, despite being able to do so with searchPlaces; and no other Cosmiconfig users have asked for it; then I'm not sure there's enough weight behind it for a change to the library.

davidtheclark avatar Jan 16 '19 00:01 davidtheclark

Reopening the issue to encourage more people to chime in.

davidtheclark avatar Jan 16 '19 15:01 davidtheclark

@davidtheclark After looking through closed issues, I think this really is a symptom of a more general problem.

End users don't have much control over where their config files are located

I'm making an assumption here, but I'm assuming that one of cosmiconfig's goals is to actually give end users more control over where their config files are located. Indeed, cosmiconfig does give end users more control "on paper" -- but in practice, not really.

Various tooling is setup to look in specific places for configs. If you specify a custom configuration directory, you have to specify this everywhere that tool is called from. Prettier is a good example of this.

This wouldn't normally be an issue on it's own. It's only an issue in this modern era of JS dev where we've had an explosion of "configurable tooling".

Our repos now have more config in the root dir than anything else on average and it's beginning to (for me at least) become a burden.

It would be great to have a solution like OP pointed out, or as others have suggested: <project_root>/.config

I know that adding searchPaths has made such functionality possible, however, that relies on library authors to implement which practically guarantees there will be no standard.

cosmiconfig is growing rapidly and quickly becoming the de-facto config loading library. (congrats btw. 😃)

That means cosmiconfig has a golden opportunity to be a leader in standardizing / consolidating schemes.

A number of us have a preferred solution to the problem, but perhaps this could all be solved by simply letting the end user customize searchPaths somehow. (maybe using package.json?)

Alternatively, comsmiconfig could just go the way of prettier and be opinionated and offer no configuration and set the standard that way.

The core problem for me is that my projects are cluttered with config files at the top level and I'd like to have the ability to personally wrangle that.

pthrasher avatar Apr 02 '19 17:04 pthrasher

I'm making an assumption here, but I'm assuming that one of cosmiconfig's goals is to actually give end users more control over where their config files are located

below is my understanding of cosmiconfig. davidtheclark might think differently

One difference in our understandings is that cosmiconfig offers library authors more control over where their config files are located, which changes your premise a bit. Granted, like you say, we do set the defaults so end-users are ultimately affected.

Another difference is that cosmiconfig is a lower level tool that isn't meant to be opinionated. Since cosmiconfig aims to allow library authors the flexibility they need, we also don't want to set standards without the library authors' appeal - hence why this issue remains open.

Hopefully that helps.

olsonpm avatar Apr 02 '19 18:04 olsonpm

just commenting to offer up https://github.com/sindresorhus/env-paths#pathsconfig which respects the xdg spec.

swyxio avatar May 25 '19 20:05 swyxio

this is one of the reasons davidtheclark added searchPlaces . if you find it doesn't suit your needs then let me know.

searchPlaces are searched for each one of the folders while moving up, checking on each one of the folders. If we add a new "place" pointing inside $HOME, it will sucess on the first try, ignoring the other parent folders. I think we need a new fallbackSearchPlaces to search when no config has been found when searching in parent folders, so users can add there $HOME or /etc or whatever other default common config places.

piranna avatar Jan 31 '20 22:01 piranna

@olsonpm I'd consider myself a library author (or at least most of the stuff I work on isn't applications), but I really don't want to add duplicated config files to every single repository I ever work on. I'd much rather have my config in a centralized repository that I can check out to a well specified path on my system (and share with someone who wants to collaborate with me). This is why I believe obeying the XDG config directory specification would be a good idea.

This way if I need configuration changes they happen in a centralized place and don't need to be copy pasted into every single repository I work on.

masaeedu avatar Feb 04 '20 15:02 masaeedu

Strong agree that searchPlaces should default to XDG compliance. Library authors (by and large) will not change the defaults. So cosmiconfig's defaults will perpetuate to all users of all libs which use cosmicconfig. In one swift PR, cosmiconfig could give control to all the users of all the cosmic-dependent libs by just making searchPlaces default to XDG's directories.

jasonkarns avatar Mar 24 '20 19:03 jasonkarns

Open to a PR implementing this idea 🚀

davidtheclark avatar Apr 04 '20 15:04 davidtheclark

I am working on putting together a PR for this. I think I have the impl, but now thinking about the best way to test. Gotta keep up that 💯 coverage!

TillaTheHun0 avatar Dec 11 '20 20:12 TillaTheHun0

Just posted this note at the top of the README:

MAINTAINERS WANTED! If you're interested in taking over and maintaining Cosmiconfig, please let @davidtheclark know (with an issue or email). I'd like to hand over the keys completely, so I'm looking for owners, not people who just want to merge a PR or two! You can make the decisions about what happens in v8 and subsequent versions, how the package balances stability and opinionated features, and so on. Take a look at open issues and PRs to learn about possibilities that have been on people's minds over the years.

If you're interested in taking ownership of the package and moving this feature forward, let me know!

davidtheclark avatar Jun 04 '22 17:06 davidtheclark

Just posted this note at the top of the README:

MAINTAINERS WANTED! If you're interested in taking over and maintaining Cosmiconfig, please let @davidtheclark know (with an issue or email). I'd like to hand over the keys completely, so I'm looking for owners, not people who just want to merge a PR or two! You can make the decisions about what happens in v8 and subsequent versions, how the package balances stability and opinionated features, and so on. Take a look at open issues and PRs to learn about possibilities that have been on people's minds over the years.

If you're interested in taking ownership of the package and moving this feature forward, let me know!

I've thrown my hat into the ring to help maintain this amazing module.

4lch4 avatar Aug 10 '22 15:08 4lch4

Just to understand the call for XDG-by-default:

I do know that end user software generally has configuration files saved somewhere in the user's immediate scope. However, in most cases, it doesn't make sense to use, for example, a global Prettier config file.

There could be problems caused by configuration files outside of the repository.

Suppose you're forking a library in order to send a pull request to it. There's a few different scenarios:

  • The library doesn't use prettier, so nothing should ever reformat the files in the repository. Nothing changes from the status quo as long as nothing still tries to run prettier, which would be an odd case.
  • The library already uses prettier with a custom config file, so according to the premise, that config file in its repository takes priority over the one in the home folder. Nothing changes from the status quo.
  • The library uses prettier with defaults. This is the dangerous one. The config file is not found in the repo, so it's being found in the XDG config directory, overriding the defaults. Then your IDE automatically picks up that the repo uses prettier (from package.json, node_modules, whatever), runs a format using the settings from your global config and then the PR you're sending is riddled with unnecessary reformats that the original maintainer does not want.

This is only one use case, but I'd argue that in most cases, accepting a global config by default would work similarly against the original maintainer of repositories using cosmiconfig if they accept PRs from strangers.

Therefore, I'd say that XDG should not be enabled by default. I do still see the benefit of offering it as an option.

I have some other possibilities in mind that could give some control back to the end user (e.g. the user of prettier), but XDG-by-default breaks expectations too hard to be viable IMO.

I'd like some opinions on this, especially from @SimenB and @jasonkarns who advocated for enabling this by default in this issue.

@TillaTheHun0 I'd definitely take your PR if someone figures out a good way to test this. (Might just override the XDG_CONFIG_DIR env var before loading in xdg-basedir, maybe?)

d-fischer avatar Nov 06 '22 22:11 d-fischer

Currently, cosmiconfig searches up the parent tree from the working directory, stopping at $HOME, which is fine and most, I would assume, are okay with this.

Keep doing this but at a last step look for the config file at a XDG dir.

The library uses prettier with defaults. This is the dangerous one. The config file is not found in the repo, so it's being found in the XDG config directory, overriding the defaults. Then your IDE automatically picks up that the repo uses prettier (from package.sjon, node_modules, whatever), runs a format using the settings from your global config and then the PR you're sending is riddled with unnecessary reformats that the original maintainer does not want.

This is also an issue with a config file at $HOME so looking for config at a XDG dir wouldn't make this worse.

danisztls avatar Nov 07 '22 11:11 danisztls

This is also an issue with a config file at $HOME so looking for config at a XDG dir wouldn't make this worse.

Maybe a better default would then be to not traverse at all (i.e. stopDir = cwd) or maybe stop whenever a package.json is found in the currently traversed directory?

Thinking about the future major version here, there could be something like a global: true mode that would both enable XDG compliance and set stopDir = $HOME.

d-fischer avatar Nov 07 '22 18:11 d-fischer

Maybe a better default would then be to not traverse at all (i.e. stopDir = cwd) or maybe stop whenever a package.json is found in the currently traversed directory?

This approach would be challenging when working with npm/yarn/pnpm workspaces

The way config lookup works for the vast majority of native applications that support XDG is to look up a config file in the xdg directory ie $XDG_CONFIG_HOME/[app]/config.ext and if not present fallback to home directory. This approach differs from how cosmiconfig currently works.

I can see two main ways cosmiconfig could add support for XDG

  • Once reaching home dir cosmiconfig could first check XDG directory and only then check home dir to respect XDG spec.
  • Distinguish between project local config lookup and a global config lookup with a search strategy option.

antonk52 avatar Dec 30 '22 06:12 antonk52

Maybe a better default would then be to not traverse at all (i.e. stopDir = cwd) or maybe stop whenever a package.json is found in the currently traversed directory?

This approach would be challenging when working with npm/yarn/pnpm workspaces

Why though? Stuff like a linter or other directly project related things should generally always run in one of two contexts anyway:

  • in the whole repo context, i.e. cwd = repo root and config file in the repo root as well
  • in the context of a single package, i.e. cwd = package root and config file in the package root as well

Both cases should be well supported by the proposed approach.

Distinguish between project local config lookup and a global config lookup with a search strategy option.

That's basically a slightly more flexible version of my global: true flag proposal. I can see us adopting this. But: if you say you might have problems with workspaces when limiting the traversal, how do you imagine "project local config lookup" could work?

d-fischer avatar Dec 30 '22 13:12 d-fischer

Why though? Stuff like a linter or other directly project related things should generally always run in one of two contexts anyway:

  • in the whole repo context, i.e. cwd = repo root and config file in the repo root as well
  • in the context of a single package, i.e. cwd = package root and config file in the package root as well

Totally agree! My point was agains the idea of "stopping when encountering a directory containing package.json"

antonk52 avatar Jan 04 '23 18:01 antonk52

My point was agains the idea of "stopping when encountering a directory containing package.json"

So why is that a problem then? I legitimately don't see an issue with it, if the baseline is "don't traverse at all", then why not extend it to traverse until a package.json exists? (Could also make it an option/separate search strategy, maybe.)

d-fischer avatar Jan 04 '23 19:01 d-fischer

@d-fischer You are correct. I was thinking about a case where for example a configuration would not be found in a workspaces project root due to the cwd being a workspace and the search stopping in a workspace package.

project-root
├── package.json
├── packages
│   └── foo
│       └── package.json
└── some.config.js

So if we run the script from project-root/packages/foo the file project-root/some.config.js won't be discovered with a strategy if the search stops at a directory containing package.json

Does this make sense?

antonk52 avatar Feb 21 '23 11:02 antonk52

Yes, this does make sense, and to me it seems absolutely fine to do it that way. This is exactly what I outlined above. If (for example) a linter is run within packages/foo, I'd expect the linter being a direct dev dependency of that package (not of the root) and therefore the configuration file should live there too.

As a compromise, I could imagine a special configuration option being introduced with the same version as this. where you could explicitly declare other files to read and merge. For example:

.prettier.base.yml

printWidth: 80
semi: true

packages/foo/.prettier.yml

$import:
  - ../../.prettier.base.yml
tabWidth: 4
useTabs: true
printWidth: 120

Resulting configuration object

{
  semi: true,
  tabWidth: 4,
  useTabs: true,
  printWidth: 120
}

This would be available to end users of all cosmiconfig consumers, using all file formats (it would just evaluate the resulting JS object) and would make it possible to access workspace configuration in subpackages without much duplication.

It could also automatically bring shared configuration outside of JS files to all cosmiconfig consumers (i.e. people could just include configuration baselines from npm even from non-JS configuration files).

(I'll probably open a separate issue for this relatively shortly.)

Would this address your concern?

d-fischer avatar Feb 21 '23 23:02 d-fischer

All of the above sounds totally reasonable. Thanks for expanding on it 👍

antonk52 avatar Feb 22 '23 14:02 antonk52

Now added to the v9 branch and will be documented and released soon.

d-fischer avatar Oct 10 '23 17:10 d-fischer