├── examples
├── fixtures
│ ├── user-dirs.locale
│ ├── user-dirs.dirs
│ ├── env.js
│ ├── user-dirs.defaults
│ └── user-dirs.conf
├── basedir.js
├── basedir-subdir.js
├── userdirs-conf.js
├── userdirs-dirs.js
├── userdirs-create.js
├── default-platform.js
├── userdirs-defaults.js
├── userdirs-methods.js
├── config.js
├── userdirs.js
├── load.js
├── userdirs-custom-env.js
├── basedir-expanded.js
├── basedir-subdir-platform.js
└── userdirs-onProperty.js
├── .gitattributes
├── .editorconfig
├── test
├── fixtures
│ ├── darwin
│ │ ├── etc
│ │ │ └── user-dirs.defaults
│ │ └── Users
│ │ │ └── user
│ │ │ ├── .config
│ │ │ └── user-dirs.dirs
│ │ │ └── Library
│ │ │ └── Application Support
│ │ │ └── user-dirs.dirs
│ └── linux
│ │ ├── etc
│ │ └── user-dirs.defaults
│ │ └── Users
│ │ └── user
│ │ └── .config
│ │ └── user-dirs.dirs
├── xdg-base-directory.js
├── xdg.js
└── xdg-user-dirs.js
├── .gitignore
├── package.json
├── lib
├── utils.js
├── expand.js
└── user-dirs.js
├── index.js
├── .eslintrc.js
├── .verb.md
└── README.md
/examples/fixtures/user-dirs.locale:
--------------------------------------------------------------------------------
1 | # Used to set the language according to the locale in use
2 |
3 |
--------------------------------------------------------------------------------
/examples/basedir.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const xdg = require('..');
4 | const dirs = xdg();
5 |
6 | console.log(dirs);
7 |
8 |
--------------------------------------------------------------------------------
/examples/basedir-subdir.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const xdg = require('..');
4 |
5 | console.log(xdg({ subdir: 'FooBar' }));
6 |
7 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Enforce Unix newlines
2 | * text eol=lf
3 |
4 | # binaries
5 | *.ai binary
6 | *.psd binary
7 | *.jpg binary
8 | *.gif binary
9 | *.png binary
10 | *.jpeg binary
11 |
--------------------------------------------------------------------------------
/examples/userdirs-conf.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('./fixtures/env');
4 | const path = require('path');
5 | const { userdirs } = require('..');
6 |
7 | console.log(userdirs.conf());
8 |
--------------------------------------------------------------------------------
/examples/userdirs-dirs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('./fixtures/env');
4 | const path = require('path');
5 | const { userdirs } = require('..');
6 |
7 | console.log(userdirs.dirs());
8 |
--------------------------------------------------------------------------------
/examples/fixtures/user-dirs.dirs:
--------------------------------------------------------------------------------
1 | # Custom settings for user directories
2 | MUSIC=Documents/Music
3 | PICTURES=Documents/Pictures
4 | TEMPLATES=Documents/Templates
5 | VIDEOS=Documents/Videos
6 |
--------------------------------------------------------------------------------
/examples/userdirs-create.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('./fixtures/env');
4 | const path = require('path');
5 | const { userdirs } = require('..');
6 |
7 | console.log(userdirs.create());
8 |
--------------------------------------------------------------------------------
/examples/default-platform.js:
--------------------------------------------------------------------------------
1 | const xdg = require('..');
2 |
3 | const dirs = xdg();
4 |
5 | console.log(dirs.cache);
6 | // console.log(process.env);
7 | // console.log(xdg({ expanded: true }));
8 |
--------------------------------------------------------------------------------
/examples/userdirs-defaults.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('./fixtures/env');
4 | const path = require('path');
5 | const { userdirs } = require('..');
6 |
7 | console.log(userdirs.defaults());
8 |
--------------------------------------------------------------------------------
/examples/userdirs-methods.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('./fixtures/env');
4 | const { userdirs } = require('..');
5 |
6 | console.log(userdirs.conf());
7 | console.log(userdirs.dirs());
8 | console.log(userdirs.defaults());
9 |
--------------------------------------------------------------------------------
/examples/config.js:
--------------------------------------------------------------------------------
1 | const xdg = require('..');
2 |
3 | const dirs = xdg({ expanded: true });
4 | const configs = [
5 | dirs.config.parse('.env'),
6 | dirs.local.parse('.env')
7 | ];
8 |
9 | console.log(Object.assign({}, ...configs));
10 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | end_of_line = lf
6 | charset = utf-8
7 | indent_size = 2
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/examples/fixtures/env.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | process.env.XDG_USER_DIRS = path.join(__dirname, 'user-dirs.dirs');
3 | process.env.XDG_USER_DIRS_DEFAULTS = path.join(__dirname, 'user-dirs.defaults');
4 | process.env.XDG_USER_DIRS_CONF = path.join(__dirname, 'user-dirs.conf');
5 |
--------------------------------------------------------------------------------
/examples/userdirs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('./fixtures/env');
4 | const path = require('path');
5 | const { userdirs } = require('..');
6 |
7 | console.log(userdirs.conf());
8 | console.log(userdirs.dirs());
9 | console.log(userdirs.defaults());
10 | console.log(userdirs());
11 |
--------------------------------------------------------------------------------
/examples/load.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('./fixtures/env');
4 | const path = require('path');
5 | const { load, userdirs } = require('..');
6 |
7 | const paths = userdirs();
8 |
9 | console.log(paths);
10 | console.log(load(paths.conf));
11 | console.log(load(paths.defaults));
12 | console.log(load(paths.dirs));
13 |
--------------------------------------------------------------------------------
/test/fixtures/darwin/etc/user-dirs.defaults:
--------------------------------------------------------------------------------
1 | XDG_DESKTOP_DIR="$HOME/Desktop"
2 | XDG_DOCUMENTS_DIR="$HOME/Documents"
3 | XDG_DOWNLOAD_DIR="$HOME/Downloads"
4 | XDG_MUSIC_DIR="$HOME/Music"
5 | XDG_PICTURES_DIR="$HOME/Pictures"
6 | XDG_PUBLICSHARE_DIR="$HOME/Public"
7 | XDG_TEMPLATES_DIR="$HOME/Templates"
8 | XDG_VIDEOS_DIR="$HOME/Videos"
9 |
--------------------------------------------------------------------------------
/test/fixtures/linux/etc/user-dirs.defaults:
--------------------------------------------------------------------------------
1 | XDG_DESKTOP_DIR="$HOME/Desktop"
2 | XDG_DOCUMENTS_DIR="$HOME/Documents"
3 | XDG_DOWNLOAD_DIR="$HOME/Downloads"
4 | XDG_MUSIC_DIR="$HOME/Music"
5 | XDG_PICTURES_DIR="$HOME/Pictures"
6 | XDG_PUBLICSHARE_DIR="$HOME/Public"
7 | XDG_TEMPLATES_DIR="$HOME/Templates"
8 | XDG_VIDEOS_DIR="$HOME/Videos"
9 |
--------------------------------------------------------------------------------
/examples/userdirs-custom-env.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('./fixtures/env');
4 | const path = require('path');
5 | const { userdirs } = require('..');
6 |
7 | const { paths, config, defaults, dirs } = userdirs.expand();
8 | console.log(config());
9 |
10 | const merged = { ...defaults(), ...dirs() };
11 | console.log(merged);
12 |
--------------------------------------------------------------------------------
/test/fixtures/darwin/Users/user/.config/user-dirs.dirs:
--------------------------------------------------------------------------------
1 | XDG_DESKTOP_DIR="$HOME/Desktop"
2 | XDG_DOCUMENTS_DIR="$HOME/Documents"
3 | XDG_DOWNLOAD_DIR="$HOME/Downloads"
4 | XDG_MUSIC_DIR="$HOME/Music"
5 | XDG_PICTURES_DIR="$HOME/Pictures"
6 | XDG_PUBLICSHARE_DIR="$HOME/Public"
7 | XDG_TEMPLATES_DIR="$HOME/Templates"
8 | XDG_VIDEOS_DIR="$HOME/Videos"
9 |
--------------------------------------------------------------------------------
/test/fixtures/linux/Users/user/.config/user-dirs.dirs:
--------------------------------------------------------------------------------
1 | XDG_DESKTOP_DIR="$HOME/Desktop"
2 | XDG_DOCUMENTS_DIR="$HOME/Documents"
3 | XDG_DOWNLOAD_DIR="$HOME/Downloads"
4 | XDG_MUSIC_DIR="$HOME/Music"
5 | XDG_PICTURES_DIR="$HOME/Pictures"
6 | XDG_PUBLICSHARE_DIR="$HOME/Public"
7 | XDG_TEMPLATES_DIR="$HOME/Templates"
8 | XDG_VIDEOS_DIR="$HOME/Videos"
9 |
--------------------------------------------------------------------------------
/test/fixtures/darwin/Users/user/Library/Application Support/user-dirs.dirs:
--------------------------------------------------------------------------------
1 | XDG_DESKTOP_DIR="$HOME/Desktop"
2 | XDG_DOCUMENTS_DIR="$HOME/Documents"
3 | XDG_DOWNLOAD_DIR="$HOME/Downloads"
4 | XDG_MUSIC_DIR="$HOME/Music"
5 | XDG_PICTURES_DIR="$HOME/Pictures"
6 | XDG_PUBLICSHARE_DIR="$HOME/Public"
7 | XDG_TEMPLATES_DIR="$HOME/Templates"
8 | XDG_VIDEOS_DIR="$HOME/Videos"
9 |
--------------------------------------------------------------------------------
/test/xdg-base-directory.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('node:fs');
4 | const os = require('node:os');
5 | const path = require('node:path');
6 | const assert = require('assert/strict');
7 | const xdg = require('..');
8 |
9 | describe('xdg-base-directory', () => {
10 | it('should', () => {
11 | // console.log(xdg());
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # always ignore files
2 | *.sublime-*
3 | *.code-*
4 | *.log
5 | .DS_Store
6 | .env
7 |
8 | # always ignore dirs
9 | temp
10 | tmp
11 | vendor
12 |
13 | # test related, or directories generated by tests
14 | .nyc*
15 | coverage
16 |
17 | # package managers
18 | node_modules
19 | package-lock.json
20 | yarn.lock
21 |
22 | # misc
23 | _draft
24 | TODO.md
25 |
--------------------------------------------------------------------------------
/examples/basedir-expanded.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const start = Date.now();
4 |
5 | require('./fixtures/env');
6 | const xdg = require('..');
7 | const paths = xdg({ subdir: 'toolkit', expanded: true });
8 |
9 | console.log(paths.config.find('config.json', true));
10 | console.log(paths.config.read('config.json'));
11 | console.log(`${Date.now() - start}ms`);
12 |
--------------------------------------------------------------------------------
/examples/fixtures/user-dirs.defaults:
--------------------------------------------------------------------------------
1 | # Default settings for user directories
2 | #
3 | # The values are relative pathnames from the home directory and
4 | # will be translated on a per-path-element basis into the users locale
5 |
6 | DESKTOP=Desktop
7 | DOWNLOAD=Downloads
8 | TEMPLATES=Templates
9 | PUBLICSHARE=Public
10 | DOCUMENTS=Documents
11 | MUSIC=Music
12 | PICTURES=Pictures
13 | VIDEOS=Videos
14 |
--------------------------------------------------------------------------------
/examples/basedir-subdir-platform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const xdg = require('..');
4 |
5 | console.log('=== darwin ===');
6 | console.log(xdg({ subdir: 'FooBar', platform: 'darwin' }));
7 | console.log();
8 | console.log('=== linux ===');
9 | console.log(xdg({ subdir: 'FooBar', platform: 'linux' }));
10 | console.log();
11 | console.log('=== win32 ===');
12 | console.log(xdg({ subdir: 'FooBar', platform: 'win32' }));
13 | console.log();
14 |
--------------------------------------------------------------------------------
/examples/userdirs-onProperty.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const start = Date.now();
4 | process.on('exit', () => console.log(`${Date.now() - start}ms`));
5 |
6 | require('./fixtures/env');
7 | const { userdirs } = require('..');
8 |
9 | const onProperty = (key, value, { name, resolve }) => {
10 | console.log(name);
11 | return { key: key.toLowerCase(), value: resolve(value) };
12 | };
13 |
14 | const res = userdirs.expand({ onProperty });
15 |
16 | console.log(res.create());
17 |
--------------------------------------------------------------------------------
/examples/fixtures/user-dirs.conf:
--------------------------------------------------------------------------------
1 | #
2 | # The /etc/xdg/user-dirs.conf file is a text file that controls the behavior of the
3 | # xdg-user-dirs-update command. Users can have their own ~/.config/user-dirs.conf file,
4 | # which overrides the system-wide configuration.
5 | #
6 | # The following keys are recognised:
7 | # - enabled (boolean)
8 | # - filename_encoding (encoding name)
9 | #
10 |
11 | # When set to False, xdg-user-dirs-update will not change the XDG user dirs
12 | # configuration.
13 | enabled=true
14 |
15 | # This sets the filename encoding to use. encoding may be an explicit encoding name,
16 | # such as UTF-8, or "locale", which means the encoding of the users locale will be used.
17 | filename_encoding='UTF-8'
18 |
19 | # Lines beginning with a # character are ignored.
20 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@folder/xdg",
3 | "description": "Get cross-platform XDG Base Directories or their equivalents. Works with Linux, Windows (win32), or MacOS (darwin).",
4 | "version": "4.0.1",
5 | "repository": "folder/xdg",
6 | "homepage": "https://github.com/folder/xdg",
7 | "author": "Jon Schlinkert (https://github.com/jonschlinkert)",
8 | "bugs": {
9 | "url": "https://github.com/folder/xdg/issues"
10 | },
11 | "scripts": {
12 | "test": "mocha"
13 | },
14 | "files": [
15 | "index.js",
16 | "lib"
17 | ],
18 | "main": "index.js",
19 | "license": "MIT",
20 | "dependencies": {
21 | "ini": "^4.1.1",
22 | "picomatch": "^2.3.1",
23 | "toml": "^3.0.0",
24 | "write": "^2.0.0",
25 | "yaml": "^2.3.1"
26 | },
27 | "devDependencies": {
28 | "gulp-format-md": "^2.0.0",
29 | "mocha": "^10.2.0"
30 | },
31 | "keywords": [
32 | "base",
33 | "basedir",
34 | "cache",
35 | "config",
36 | "data",
37 | "dir",
38 | "directories",
39 | "directory",
40 | "dirname",
41 | "dot",
42 | "file path",
43 | "file paths",
44 | "file",
45 | "filepath",
46 | "filepaths",
47 | "files",
48 | "find",
49 | "folder",
50 | "global",
51 | "home",
52 | "linux",
53 | "path",
54 | "paths",
55 | "rc",
56 | "runtime",
57 | "spec",
58 | "unix",
59 | "user",
60 | "win32",
61 | "windows",
62 | "xd",
63 | "xdg-basedir-spec",
64 | "xdg-basedir",
65 | "xdg-basedirs",
66 | "xdg"
67 | ],
68 | "verb": {
69 | "toc": false,
70 | "layout": "default",
71 | "tasks": [
72 | "readme"
73 | ],
74 | "plugins": [
75 | "gulp-format-md"
76 | ],
77 | "lint": {
78 | "reflinks": true
79 | },
80 | "related": {
81 | "list": [
82 | "expand-tilde",
83 | "global-modules",
84 | "global-paths",
85 | "normalize-path"
86 | ]
87 | },
88 | "reflinks": [
89 | "verb",
90 | "verb-generate-readme"
91 | ]
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/test/xdg.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const os = require('node:os');
4 | const assert = require('node:assert/strict');
5 | const xdg = require('..');
6 |
7 | const { username } = os.userInfo();
8 |
9 | describe('xdg', () => {
10 | it('should throw an error for unsupported platforms', () => {
11 | assert.throws(() => xdg({ platform: 'unsupported' }), /Platform "unsupported" is not supported/);
12 | });
13 |
14 | it('should return paths for linux platforms', () => {
15 | const paths = xdg({ platform: 'linux' });
16 |
17 | assert.ok(paths.cache.endsWith('/.cache'));
18 | assert.ok(paths.config.endsWith('/.config'));
19 |
20 | if (username) {
21 | assert.equal(paths.cache, `/Users/${username}/.cache`);
22 | assert.equal(paths.logs, `/Users/${username}/.cache/logs`);
23 | assert.equal(paths.config, `/Users/${username}/.config`);
24 | assert.equal(paths.state, `/Users/${username}/.local/state`);
25 | assert.equal(paths.data, `/Users/${username}/.local/share`);
26 | }
27 | });
28 |
29 | it('should return paths for linux platforms', () => {
30 | const paths = xdg({ platform: 'darwin' });
31 |
32 | delete paths.create;
33 | delete paths.runtime;
34 |
35 | if (username) {
36 | assert.deepStrictEqual(paths, {
37 | cache: `/Users/${username}/Library/Caches`,
38 | state: `/Users/${username}/Library/Application Support`,
39 | config: `/Users/${username}/Library/Application Support`,
40 | config_dirs: [`/Users/${username}/Library/Application Support`, '/etc/xdg'],
41 | data: `/Users/${username}/Library/Application Support`,
42 | data_dirs: [
43 | `/Users/${username}/Library/Application Support`,
44 | '/usr/local/share/',
45 | '/usr/share/'
46 | ],
47 | logs: `/Users/${username}/Library/Caches/logs`
48 | });
49 | }
50 |
51 | assert.ok(paths.cache.endsWith('/Library/Caches'));
52 | assert.ok(paths.config.endsWith('/Library/Application Support'));
53 | });
54 |
55 | it('should return paths for windows platforms', () => {
56 | const paths = xdg({ platform: 'win32' });
57 |
58 | delete paths.create;
59 | delete paths.runtime;
60 |
61 | if (username) {
62 | assert.deepStrictEqual(paths, {
63 | cache: `/Users/${username}/AppData/Local/Cache`,
64 | state: `/Users/${username}/AppData/Local/xdg`,
65 | config: `/Users/${username}/AppData/Roaming/Config`,
66 | config_dirs: [`/Users/${username}/AppData/Roaming/Config`],
67 | data: `/Users/${username}/AppData/Local/Data`,
68 | data_dirs: [`/Users/${username}/AppData/Local/Data`],
69 | logs: `/Users/${username}/AppData/Local/Cache/logs`
70 | });
71 | }
72 |
73 | assert.ok(paths.cache.endsWith('/AppData/Local/Cache'));
74 | assert.ok(paths.config.endsWith('/AppData/Roaming/Config'));
75 | });
76 | });
77 |
--------------------------------------------------------------------------------
/lib/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const os = require('os');
4 | const fs = require('fs');
5 | const path = require('path');
6 | const ini = require('ini');
7 |
8 | const BOM_REGEX = /^\ufeff/;
9 | const NEWLINE_REGEX = /[\r\n]+/;
10 | const XDG_DIR_REGEX = /^(?:XDG_)?(.+)(?:_DIR)?$/i;
11 | const VARIABLE_REGEX = /(?:\${(?:([^}]+)|{([^}]+)})}|\$([A-Z_]+))/g;
12 |
13 | const split = str => str ? str.split(path.delimiter) : [];
14 | const title = str => str ? str[0].toUpperCase() + str.slice(1) : '';
15 |
16 | const isObject = v => v !== null && typeof v === 'object' && !Array.isArray(v);
17 |
18 | const read = filepath => {
19 | if (filepath) {
20 | return fs.readFileSync(filepath, 'utf8').replace(BOM_REGEX, '');
21 | }
22 | return null;
23 | };
24 |
25 | const homedir = (platform = process.platform) => {
26 | return os.homedir() || (platform === 'win32' ? os.tmpdir() : '/usr/local/share');
27 | };
28 |
29 | const dir = (key, options = {}) => {
30 | const prop = options.envPrefix ? `${options.envPrefix}_${key}_dir` : null;
31 | const name = `${key}dir`;
32 |
33 | if (prop) {
34 | return process.env[prop.toUpperCase()] || process.env[prop.toLowerCase()] || options[name];
35 | }
36 |
37 | return options[name];
38 | };
39 |
40 | const resolve = (parentdir, ...args) => {
41 | if (args.length && /^[A-Z]/.test(path.basename(parentdir))) {
42 | return path.join(parentdir, ...args.map(v => title(v)));
43 | }
44 | if (args.length) {
45 | return path.join(parentdir, ...args.map(v => v.toLowerCase()));
46 | }
47 | return path.join(parentdir, 'xdg');
48 | };
49 |
50 | const castValue = input => {
51 | if (String(input).toLowerCase() === 'true') {
52 | return true;
53 | }
54 |
55 | if (String(input).toLowerCase() === 'false') {
56 | return false;
57 | }
58 |
59 | if (/^[0-9.]+$/.test(input)) {
60 | return Number(input);
61 | }
62 |
63 | if (String(input).toLowerCase() === 'null') {
64 | return null;
65 | }
66 |
67 | if (input.startsWith('"') && input.endsWith('"')) {
68 | return input.slice(1, -1);
69 | }
70 |
71 | if (input.startsWith('\'') && input.endsWith('\'')) {
72 | return input.slice(1, -1);
73 | }
74 |
75 | return input;
76 | };
77 |
78 | const unformatKey = input => {
79 | const match = XDG_DIR_REGEX.exec(input);
80 | return match[1].toLowerCase();
81 | };
82 |
83 | const formatKey = input => {
84 | return `XDG_${unformatKey(input).toUpperCase()}_DIR`;
85 | };
86 |
87 | const cast = (value = '') => {
88 | try {
89 | // Attempt to use JSON.parse to parse the value
90 | return JSON.parse(value);
91 | } catch (err) {
92 | // Fall back to casting the value manually, if JSON.parse didn't work
93 | return castValue(value);
94 | }
95 | };
96 |
97 | const parseIni = (input = '') => {
98 | if (!input) return {};
99 |
100 | const lines = input.split(NEWLINE_REGEX);
101 | const config = {};
102 |
103 | for (const line of lines) {
104 | if (line.trim() === '') {
105 | continue;
106 | }
107 |
108 | if (!line.startsWith('#')) {
109 | continue;
110 | }
111 |
112 | try {
113 | // Split on the `=` character. We're not using PROP_REGEx,
114 | // since that might remove need spaces around the `=` after the first one.
115 | const [key, ...value] = line.split('=');
116 |
117 | // Since we only wanted the first occurrence of "=",
118 | // we join the rest back together. Then cast the value
119 | // to it's proper type.
120 | config[key.trim()] = cast(value.join('='));
121 | } catch (err) {
122 | console.error(err);
123 | }
124 | }
125 |
126 | return config;
127 | };
128 |
129 | const interpolate = (input, data, options) => {
130 | const matches = { matched: [], unmatched: [] };
131 | const temp = {};
132 |
133 | const output = input.replace(VARIABLE_REGEX, (match, $1, $2 = $1, key = $2) => {
134 | const value = data[key] || temp[key];
135 |
136 | if (key === 'HOME' && !value) {
137 | const home = options?.homedir || homedir('darwin');
138 |
139 | if (home) {
140 | temp[key] = home;
141 | matches.matched.push({ key, value: home });
142 | return home;
143 | }
144 | }
145 |
146 | if (!value) {
147 | matches.unmatched.push({ key, value: match });
148 | return match;
149 | }
150 |
151 | matches.matched.push({ key, value });
152 | return value;
153 | });
154 |
155 | return { output, matches };
156 | };
157 |
158 | const load = (filepath, options = {}, resolve = fp => fp) => {
159 | if (Array.isArray(filepath)) {
160 | const results = filepath.map(fp => load(fp, options, resolve));
161 | return Object.assign({}, ...results);
162 | }
163 |
164 | if (fs.existsSync(filepath)) {
165 | const result = interpolate(read(filepath), process.env);
166 | const data = ini.parse(result.output);
167 |
168 | for (const [key, value] of Object.entries(data)) {
169 | data[key] = cast(value);
170 | }
171 |
172 | return { ...data };
173 | }
174 |
175 | return {};
176 | };
177 |
178 | module.exports = {
179 | dir,
180 | formatKey,
181 | castValue,
182 | homedir,
183 | load,
184 | read,
185 | resolve,
186 | split,
187 | title,
188 | unformatKey,
189 | parseIni,
190 | cast,
191 | isObject
192 | };
193 |
--------------------------------------------------------------------------------
/lib/expand.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-multi-assign */
2 | 'use strict';
3 |
4 | const os = require('os');
5 | const fs = require('fs');
6 | const path = require('path');
7 | const write = require('write');
8 | const pico = require('picomatch');
9 | const ini = require('ini');
10 | const toml = require('toml');
11 | const yaml = require('yaml');
12 | const userdirs = require('./user-dirs');
13 | const { dir, homedir, isObject, read, resolve } = require('./utils');
14 |
15 | const readfile = config => (pathname, find = false) => {
16 | try {
17 | const fullpath = config.find && find
18 | ? config.find(pathname)
19 | : path.resolve(config.home, pathname);
20 |
21 | return read(fullpath);
22 | } catch (err) {
23 | if (err.code !== 'ENOENT') {
24 | console.error(err);
25 | }
26 | return null;
27 | }
28 | };
29 |
30 | const parseNpmrc = (input = '') => {
31 | const data = ini.parse(input);
32 | const init = {};
33 |
34 | for (const [key, value] of Object.entries(data)) {
35 | if (key.startsWith('init-')) {
36 | const segs = key.split('-');
37 | let k = segs[0];
38 | let o = init;
39 |
40 | while (segs.length) {
41 | k = segs.shift();
42 | o = o[k] = segs.length ? o[k] || {} : value;
43 | }
44 |
45 | delete data[key];
46 | }
47 | }
48 |
49 | if (init?.init) {
50 | data.init ||= init?.init;
51 | }
52 |
53 | return data;
54 | };
55 |
56 | const loadFile = config => (pathname, { find = true, ext } = {}) => {
57 | const basename = path.basename(pathname);
58 | const contents = config.read(pathname, find !== false);
59 | const extname = ext || (basename.startsWith('.env') ? '.env' : path.extname(basename));
60 |
61 | if (!contents) {
62 | return {};
63 | }
64 |
65 | const load = () => {
66 | switch (extname) {
67 | case '.env':
68 | case '.ini':
69 | return ini.parse(contents);
70 | case '.json':
71 | return JSON.parse(contents);
72 | case '.toml':
73 | return toml.parse(contents);
74 | case '.yaml':
75 | case '.yml':
76 | return yaml.parse(contents);
77 | case '.js':
78 | return require(pathname);
79 | default: {
80 | return contents;
81 | }
82 | }
83 | };
84 |
85 | const data = load();
86 |
87 | if (basename === '.npmrc') {
88 | return parseNpmrc(data);
89 | }
90 |
91 | return data;
92 | };
93 |
94 | const search = (dirs = []) => (patterns, folder) => {
95 | const isMatch = pico(patterns, { dot: true });
96 | const matches = [];
97 |
98 | for (const dir of dirs) {
99 | const dirname = folder ? path.join(dir, folder) : dir;
100 | const dirents = fs.existsSync(dirname) ? fs.readdirSync(dirname, { withFileTypes: true }) : [];
101 |
102 | for (const dirent of dirents) {
103 | dirent.path = path.resolve(dir, dirent.name);
104 | dirent.relative = path.relative(dir, dirent.path);
105 |
106 | if (isMatch(dirent.relative)) {
107 | matches.push(dirent);
108 | }
109 | }
110 | }
111 |
112 | return matches;
113 | };
114 |
115 | const writefile = dir => (pathname, ...args) => {
116 | return write(path.resolve(dir, pathname), ...args);
117 | };
118 |
119 | const find = config => pathname => {
120 | for (const dir of config.dirs) {
121 | const filepath = path.resolve(dir, pathname);
122 |
123 | if (fs.existsSync(filepath)) {
124 | return filepath;
125 | }
126 | }
127 | return null;
128 | };
129 |
130 | const create = config => {
131 | if (Array.isArray(config.dirs)) {
132 | config.find = find(config);
133 | config.search = search(config);
134 | }
135 |
136 | config.read = readfile(config);
137 | config.load = loadFile(config);
138 | config.parse = loadFile(config);
139 | config.write = writefile(config.home);
140 | return config;
141 | };
142 |
143 | const createDirs = dirs => {
144 | for (const key of Object.keys(dirs)) {
145 | const config = dirs[key];
146 |
147 | if (isObject(config)) {
148 | Reflect.defineProperty(config, 'parent', { value: dirs, enumerable: false });
149 | create(config);
150 | }
151 | }
152 |
153 | return dirs;
154 | };
155 |
156 | // eslint-disable-next-line complexity
157 | const expand = (paths, options = {}) => {
158 | const cachedir = dir('cache', options);
159 | const configdir = dir('config', options);
160 | const datadir = dir('data', options);
161 | const runtimedir = dir('runtime', options);
162 | const cwd = options.cwd ? path.resolve(options.cwd) : process.cwd();
163 |
164 | const dirs = {
165 | cwd,
166 | home: dir('home', options) || homedir(options.platform || process.platform),
167 | temp: dir('temp', options) || os.tmpdir(),
168 | cache: {
169 | home: cachedir || paths.cache,
170 | dirs: [cachedir || paths.cache],
171 | logs: resolve(cachedir || paths.cache, 'logs')
172 | },
173 | config: {
174 | home: configdir || paths.config,
175 | dirs: [...new Set([configdir || paths.config, ...paths.config_dirs])]
176 | },
177 | data: {
178 | home: datadir || paths.data,
179 | dirs: [...new Set([datadir || paths.data, ...paths.data_dirs])]
180 | },
181 | runtime: {
182 | home: runtimedir || paths.runtime,
183 | dirs: [runtimedir || paths.runtime]
184 | },
185 | userdirs: userdirs.expand(options),
186 | local: {
187 | name: path.basename(cwd),
188 | home: cwd,
189 | dirs: [cwd]
190 | }
191 | };
192 |
193 | createDirs(dirs);
194 | return dirs;
195 | };
196 |
197 | module.exports = {
198 | expand,
199 | create,
200 | createDirs,
201 | readfile,
202 | loadFile,
203 | search,
204 | writefile,
205 | find
206 | };
207 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const os = require('os');
4 | const path = require('path');
5 | const join = path.join;
6 | const { create, expand, loadFile } = require('./lib/expand');
7 | const { homedir, load, resolve, split } = require('./lib/utils');
8 |
9 | /**
10 | * Get the XDG Base Directory paths for Linux, or equivalent paths for Windows or MaxOS.
11 | * @name xdg
12 | * @param {Object} `options`
13 | * @return {Object} Returns an object of paths for the current platform.
14 | * @api public
15 | */
16 |
17 | const xdg = (options = {}) => {
18 | const platform = options.platform || process.platform;
19 | const fn = xdg[platform];
20 |
21 | if (typeof fn !== 'function') {
22 | throw new Error(`Platform "${platform}" is not supported`);
23 | }
24 |
25 | return fn(options);
26 | };
27 |
28 | /**
29 | * Get XDG equivalent paths for MacOS. Used by the main export when `process.platform`
30 | * is `darwin`. Aliased as `xdg.macos()`.
31 | *
32 | * ```js
33 | * const dirs = xdg.darwin();
34 | * // or
35 | * const dirs = xdg.macos();
36 | * ```
37 | * @param {Object} `options`
38 | * @return {Object} Returns an object of paths.
39 | * @api public
40 | */
41 |
42 | xdg.darwin = (options = {}) => {
43 | const env = options.env || process.env;
44 | const subdir = options.subdir || '';
45 | const resolve = options.resolve || xdg.resolve;
46 |
47 | const lib = () => join(home, 'Library');
48 | const app = () => join(lib(), 'Application Support');
49 | const caches = () => join(lib(), 'Caches');
50 |
51 | const temp = options.tempdir || os.tmpdir();
52 | const home = options.homedir || homedir('darwin');
53 | const data = resolve(env.XDG_DATA_HOME || app(), subdir);
54 | const config = resolve(env.XDG_CONFIG_HOME || app(), subdir);
55 | const state = resolve(env.XDG_STATE_HOME || app(), subdir);
56 | const cch = resolve(env.XDG_CACHE_HOME || caches(), subdir);
57 | const etc = '/etc/xdg';
58 |
59 | const dirs = {
60 | cache: cch,
61 | state,
62 | config,
63 | config_dirs: [config, ...split(env.XDG_CONFIG_DIRS || etc)],
64 | data,
65 | data_dirs: [data, ...split(env.XDG_DATA_DIRS || '/usr/local/share/:/usr/share/')],
66 | runtime: resolve(env.XDG_RUNTIME_DIR || temp, subdir),
67 | logs: join(cch, 'logs')
68 | };
69 |
70 | if (options.expanded === true) {
71 | const expanded = expand(dirs, options);
72 | expanded.create = create;
73 | expanded.load = (pathname, config, options) => loadFile(config)(pathname, options);
74 | return expanded;
75 | }
76 |
77 | dirs.create = create;
78 | return dirs;
79 | };
80 |
81 | /**
82 | * Get XDG equivalent paths for Linux. Used by the main export when `process.platform`
83 | * is `linux`.
84 | *
85 | * ```js
86 | * const dirs = xdg.linux();
87 | * ```
88 | * @return {Object} Returns an object of paths.
89 | * @return {Object} Returns an object of paths.
90 | * @api public
91 | */
92 |
93 | xdg.linux = (options = {}) => {
94 | const env = options.env || process.env;
95 | const subdir = options.subdir || '';
96 | const resolve = options.resolve || xdg.resolve;
97 |
98 | const cache = () => join(home, '.cache');
99 | const config = () => join(home, '.config');
100 | const share = () => join(home, '.local', 'share');
101 | const state = () => join(home, '.local', 'state');
102 |
103 | const temp = options.tempdir || os.tmpdir();
104 | const home = options.homedir || homedir('linux');
105 | const data = resolve(env.XDG_DATA_HOME || share(), subdir);
106 | const stte = resolve(env.XDG_STATE_HOME || state(), subdir);
107 | const cfg = resolve(env.XDG_CONFIG_HOME || config(), subdir);
108 | const cch = resolve(env.XDG_CACHE_HOME || cache(), subdir);
109 | const etc = '/etc/xdg';
110 |
111 | const dirs = {
112 | cache: cch,
113 | state: stte,
114 | config: cfg,
115 | config_dirs: [cfg, ...split(env.XDG_CONFIG_DIRS || etc)],
116 | data,
117 | data_dirs: [data, ...split(env.XDG_DATA_DIRS || '/usr/local/share/:/usr/share/')],
118 | runtime: resolve(env.XDG_RUNTIME_DIR || temp, subdir),
119 | logs: join(cch, 'logs')
120 | };
121 |
122 | if (options.expanded === true) {
123 | const expanded = expand(dirs, options);
124 | expanded.create = create;
125 | return expanded;
126 | }
127 |
128 | return dirs;
129 | };
130 |
131 | /**
132 | * Get XDG equivalent paths for MacOS. Used by the main export when `process.platform`
133 | * is `win32`. Aliased as `xdg.windows()`.
134 | *
135 | * ```js
136 | * const dirs = xdg.win32();
137 | * // or
138 | * const dirs = xdg.windows();
139 | * ```
140 | * @param {Object} `options`
141 | * @return {Object} Returns an object of paths.
142 | * @api public
143 | */
144 |
145 | xdg.win32 = (options = {}) => {
146 | const env = options.env || process.env;
147 | const temp = options.tempdir || os.tmpdir();
148 | const home = options.homedir || homedir('win32');
149 | const subdir = options.subdir || '';
150 | const resolve = options.resolve || xdg.resolve;
151 |
152 | const {
153 | APPDATA = join(home, 'AppData', 'Roaming'),
154 | LOCALAPPDATA = join(home, 'AppData', 'Local'),
155 |
156 | // XDG Base Dir variables
157 | XDG_CACHE_HOME,
158 | XDG_CONFIG_DIRS,
159 | XDG_DATA_DIRS,
160 | XDG_RUNTIME_DIR
161 | } = env;
162 |
163 | const local = options.roaming === true ? APPDATA : LOCALAPPDATA;
164 | const data = resolve(env.XDG_DATA_HOME || local, subdir, 'Data');
165 | const appdata = env.XDG_CONFIG_HOME || APPDATA;
166 | const cache = resolve(XDG_CACHE_HOME || local, subdir, 'Cache');
167 | const config = resolve(appdata, subdir, 'Config');
168 | const state = resolve(env.XDG_STATE_HOME || local);
169 |
170 | const dirs = {
171 | cache,
172 | state,
173 | config,
174 | config_dirs: [config, ...split(XDG_CONFIG_DIRS)],
175 | data,
176 | data_dirs: [data, ...split(XDG_DATA_DIRS)],
177 | runtime: resolve(XDG_RUNTIME_DIR || temp, subdir),
178 | logs: join(cache, 'logs')
179 | };
180 |
181 | if (options.expanded === true) {
182 | const expanded = expand(dirs, options);
183 | expanded.create = create;
184 | return expanded;
185 | }
186 |
187 | return dirs;
188 | };
189 |
190 | /**
191 | * Convenience methods
192 | */
193 |
194 | xdg.load = load;
195 | xdg.resolve = resolve;
196 | xdg.windows = xdg.win32;
197 | xdg.macos = xdg.darwin;
198 |
199 | /**
200 | * Expose "user dirs"
201 | */
202 |
203 | xdg.userdirs = require('./lib/user-dirs');
204 |
205 | /**
206 | * Expose "xdg"
207 | */
208 |
209 | module.exports = xdg;
210 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | root: true,
5 | extends: 'eslint:recommended',
6 |
7 | env: {
8 | commonjs: true,
9 | es2023: true,
10 | mocha: true,
11 | node: true
12 | },
13 |
14 | parserOptions: {
15 | ecmaVersion: 'latest',
16 | sourceType: 'script',
17 | requireConfigFile: false
18 | },
19 |
20 | rules: {
21 | 'accessor-pairs': 2,
22 | 'array-bracket-newline': [1, 'consistent'],
23 | 'array-bracket-spacing': [1, 'never'],
24 | 'array-callback-return': 1,
25 | 'array-element-newline': [1, 'consistent'],
26 | 'arrow-body-style': 0,
27 | 'arrow-parens': [1, 'as-needed'],
28 | 'arrow-spacing': [1, { before: true, after: true }],
29 | 'block-scoped-var': 1,
30 | 'block-spacing': [1, 'always'],
31 | 'brace-style': [1, '1tbs', { allowSingleLine: true }],
32 | 'callback-return': 0,
33 | 'camelcase': [0, { allow: [] }],
34 | 'capitalized-comments': 0,
35 | 'class-methods-use-this': 1,
36 | 'comma-dangle': [2, 'never'],
37 | 'comma-spacing': [1, { before: false, after: true }],
38 | 'comma-style': [1, 'last'],
39 | 'complexity': 1,
40 | 'computed-property-spacing': 1,
41 | 'consistent-return': 0,
42 | 'consistent-this': 1,
43 | 'constructor-super': 2,
44 | 'curly': [1, 'multi-line', 'consistent'],
45 | 'default-case': 1,
46 | 'dot-location': [1, 'property'],
47 | 'dot-notation': 1,
48 | 'eol-last': 1,
49 | 'eqeqeq': [1, 'allow-null'],
50 | 'for-direction': 1,
51 | 'func-call-spacing': 2,
52 | 'generator-star-spacing': [1, { before: true, after: true }],
53 | 'handle-callback-err': [2, '^(err|error)$'],
54 | 'indent': [1, 2, { SwitchCase: 1 }],
55 | 'key-spacing': [1, { beforeColon: false, afterColon: true }],
56 | 'keyword-spacing': [1, { before: true, after: true }],
57 | 'linebreak-style': [1, 'unix'],
58 | 'new-cap': [1, { newIsCap: true, capIsNew: false }],
59 | 'new-parens': 2,
60 | 'no-alert': 1,
61 | 'no-array-constructor': 1,
62 | 'no-async-promise-executor': 1,
63 | 'no-caller': 2,
64 | 'no-case-declarations': 1,
65 | 'no-class-assign': 2,
66 | 'no-cond-assign': 2,
67 | 'no-console': 0,
68 | 'no-const-assign': 2,
69 | 'no-constant-condition': [1, { checkLoops: false }],
70 | 'no-control-regex': 2,
71 | 'no-debugger': 2,
72 | 'no-delete-var': 2,
73 | 'no-dupe-args': 2,
74 | 'no-dupe-class-members': 2,
75 | 'no-dupe-keys': 2,
76 | 'no-duplicate-case': 2,
77 | 'no-duplicate-imports': 1,
78 | 'no-else-return': 0,
79 | 'no-empty-character-class': 2,
80 | 'no-empty-function': 0,
81 | 'no-empty-pattern': 0,
82 | 'no-empty': [1, { allowEmptyCatch: true }],
83 | 'no-eval': 0,
84 | 'no-ex-assign': 2,
85 | 'no-extend-native': 2,
86 | 'no-extra-bind': 1,
87 | 'no-extra-boolean-cast': 1,
88 | 'no-extra-label': 1,
89 | 'no-extra-parens': [1, 'all', { conditionalAssign: false, returnAssign: false, nestedBinaryExpressions: false, ignoreJSX: 'multi-line', enforceForArrowConditionals: false }],
90 | 'no-extra-semi': 1,
91 | 'no-fallthrough': 2,
92 | 'no-floating-decimal': 2,
93 | 'no-func-assign': 2,
94 | 'no-global-assign': 2,
95 | 'no-implicit-coercion': 2,
96 | 'no-implicit-globals': 1,
97 | 'no-implied-eval': 2,
98 | 'no-inner-declarations': [1, 'functions'],
99 | 'no-invalid-regexp': 2,
100 | 'no-invalid-this': 1,
101 | 'no-irregular-whitespace': 2,
102 | 'no-iterator': 2,
103 | 'no-label-var': 2,
104 | 'no-labels': 2,
105 | 'no-lone-blocks': 2,
106 | 'no-lonely-if': 2,
107 | 'no-loop-func': 1,
108 | 'no-mixed-requires': 1,
109 | 'no-mixed-spaces-and-tabs': 2,
110 | 'no-multi-assign': 1,
111 | 'no-multi-spaces': 1,
112 | 'no-multi-str': 2,
113 | 'no-multiple-empty-lines': [1, { max: 1 }],
114 | 'no-native-reassign': 2,
115 | 'no-negated-condition': 0,
116 | 'no-negated-in-lhs': 2,
117 | 'no-new-func': 2,
118 | 'no-new-object': 2,
119 | 'no-new-require': 2,
120 | 'no-new-symbol': 1,
121 | 'no-new-wrappers': 2,
122 | 'no-new': 1,
123 | 'no-obj-calls': 2,
124 | 'no-octal-escape': 2,
125 | 'no-octal': 2,
126 | 'no-path-concat': 1,
127 | 'no-proto': 2,
128 | 'no-prototype-builtins': 0,
129 | 'no-redeclare': 2,
130 | 'no-regex-spaces': 2,
131 | 'no-restricted-globals': 2,
132 | 'no-return-assign': 1,
133 | 'no-return-await': 2,
134 | 'no-script-url': 1,
135 | 'no-self-assign': 1,
136 | 'no-self-compare': 1,
137 | 'no-sequences': 2,
138 | 'no-shadow-restricted-names': 2,
139 | 'no-shadow': 0,
140 | 'no-spaced-func': 2,
141 | 'no-sparse-arrays': 2,
142 | 'no-template-curly-in-string': 0,
143 | 'no-this-before-super': 2,
144 | 'no-throw-literal': 2,
145 | 'no-trailing-spaces': 1,
146 | 'no-undef-init': 2,
147 | 'no-undef': 2,
148 | 'no-unexpected-multiline': 2,
149 | 'no-unneeded-ternary': [1, { defaultAssignment: false }],
150 | 'no-unreachable-loop': 1,
151 | 'no-unreachable': 2,
152 | 'no-unsafe-assignment': 0,
153 | 'no-unsafe-call': 0,
154 | 'no-unsafe-finally': 2,
155 | 'no-unsafe-member-access': 0,
156 | 'no-unsafe-negation': 2,
157 | 'no-unsafe-optional-chaining': 0,
158 | 'no-unsafe-return': 0,
159 | 'no-unused-expressions': 2,
160 | 'no-unused-vars': [1, { vars: 'all', args: 'after-used' }],
161 | 'no-use-before-define': 0,
162 | 'no-useless-call': 2,
163 | 'no-useless-catch': 0,
164 | 'no-useless-escape': 0,
165 | 'no-useless-rename': 1,
166 | 'no-useless-return': 1,
167 | 'no-var': 1,
168 | 'no-void': 1,
169 | 'no-warning-comments': 1,
170 | 'no-with': 2,
171 | 'object-curly-spacing': [2, 'always', { objectsInObjects: true }],
172 | 'object-shorthand': 1,
173 | 'one-var': [1, { initialized: 'never' }],
174 | 'operator-linebreak': [0, 'after', { overrides: { '?': 'before', ':': 'before' } }],
175 | 'padded-blocks': [1, { switches: 'never' }],
176 | 'prefer-const': [1, { destructuring: 'all', ignoreReadBeforeAssign: false }],
177 | 'prefer-promise-reject-errors': 1,
178 | 'quotes': [1, 'single'],
179 | 'radix': 2,
180 | 'rest-spread-spacing': 1,
181 | 'semi-spacing': [1, { before: false, after: true }],
182 | 'semi-style': 1,
183 | 'semi': [1, 'always'],
184 | 'space-before-blocks': [1, 'always'],
185 | 'space-before-function-paren': [1, { anonymous: 'never', named: 'never', asyncArrow: 'always' }],
186 | 'space-in-parens': [1, 'never'],
187 | 'space-infix-ops': 1,
188 | 'space-unary-ops': [1, { words: true, nonwords: false }],
189 | 'spaced-comment': [0, 'always', { markers: ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ','] }],
190 | 'strict': 2,
191 | 'switch-colon-spacing': 1,
192 | 'symbol-description': 1,
193 | 'template-curly-spacing': [2, 'never'],
194 | 'template-tag-spacing': [2, 'never'],
195 | 'unicode-bom': 1,
196 | 'use-isnan': 2,
197 | 'valid-jsdoc': 1,
198 | 'valid-typeof': 2,
199 | 'wrap-iife': [1, 'any'],
200 | 'yoda': [1, 'never']
201 | },
202 |
203 | ignorePatterns: [
204 | 'node_modules',
205 | 'dist',
206 | 'tmp',
207 | 'temp',
208 | 'lib/templates/*',
209 | 'support/*',
210 | 'test/fixtures/*',
211 | 'test/support/*'
212 | ]
213 | };
214 |
--------------------------------------------------------------------------------
/test/xdg-user-dirs.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-invalid-this */
2 |
3 | 'use strict';
4 |
5 | const os = require('node:os');
6 | const path = require('node:path');
7 | const assert = require('node:assert/strict');
8 | const userdirs = require('../lib/user-dirs');
9 |
10 | const fixtures = path.join(__dirname, 'fixtures');
11 | const { username } = os.userInfo();
12 |
13 | describe('userdirs', () => {
14 | describe('.defaults()', () => {
15 | it('should return an object of default paths on darwin', () => {
16 | const paths = {
17 | conf: path.join(fixtures, 'darwin', 'Users/user'),
18 | etc: path.join(fixtures, 'darwin', 'etc')
19 | };
20 |
21 | const expected = {
22 | XDG_DESKTOP_DIR: `/Users/${username}/Desktop`,
23 | XDG_DOCUMENTS_DIR: `/Users/${username}/Documents`,
24 | XDG_DOWNLOAD_DIR: `/Users/${username}/Downloads`,
25 | XDG_MUSIC_DIR: `/Users/${username}/Music`,
26 | XDG_PICTURES_DIR: `/Users/${username}/Pictures`,
27 | XDG_PUBLICSHARE_DIR: `/Users/${username}/Public`,
28 | XDG_TEMPLATES_DIR: `/Users/${username}/Templates`,
29 | XDG_VIDEOS_DIR: `/Users/${username}/Videos`
30 | };
31 |
32 | const actual = userdirs.defaults({
33 | platform: 'darwin',
34 | homedir: paths.conf,
35 | etc: paths.etc
36 | });
37 |
38 | assert.deepStrictEqual(actual, expected);
39 | });
40 |
41 | it('should return an object of default paths on linux', () => {
42 | const paths = {
43 | conf: path.join(fixtures, 'linux', 'Users/user'),
44 | etc: path.join(fixtures, 'linux', 'etc')
45 | };
46 |
47 | const expected = {
48 | XDG_DESKTOP_DIR: `/Users/${username}/Desktop`,
49 | XDG_DOCUMENTS_DIR: `/Users/${username}/Documents`,
50 | XDG_DOWNLOAD_DIR: `/Users/${username}/Downloads`,
51 | XDG_MUSIC_DIR: `/Users/${username}/Music`,
52 | XDG_PICTURES_DIR: `/Users/${username}/Pictures`,
53 | XDG_PUBLICSHARE_DIR: `/Users/${username}/Public`,
54 | XDG_TEMPLATES_DIR: `/Users/${username}/Templates`,
55 | XDG_VIDEOS_DIR: `/Users/${username}/Videos`
56 | };
57 |
58 | const actual = userdirs.defaults({
59 | platform: 'linux',
60 | homedir: paths.conf,
61 | etc: paths.etc
62 | });
63 |
64 | assert.deepStrictEqual(actual, expected);
65 | });
66 | });
67 |
68 | describe('.dirs()', () => {
69 | it('should return an object of paths from user-dirs.dirs on darwin', () => {
70 | const platform = 'darwin';
71 |
72 | const paths = {
73 | conf: path.join(fixtures, platform, 'Users/user'),
74 | etc: path.join(fixtures, platform, 'etc')
75 | };
76 |
77 | const expected = {
78 | XDG_DESKTOP_DIR: `/Users/${username}/Desktop`,
79 | XDG_DOCUMENTS_DIR: `/Users/${username}/Documents`,
80 | XDG_DOWNLOAD_DIR: `/Users/${username}/Downloads`,
81 | XDG_MUSIC_DIR: `/Users/${username}/Music`,
82 | XDG_PICTURES_DIR: `/Users/${username}/Pictures`,
83 | XDG_PUBLICSHARE_DIR: `/Users/${username}/Public`,
84 | XDG_TEMPLATES_DIR: `/Users/${username}/Templates`,
85 | XDG_VIDEOS_DIR: `/Users/${username}/Videos`
86 | };
87 |
88 | const actual = userdirs.dirs({
89 | platform,
90 | homedir: paths.conf,
91 | etc: paths.etc
92 | });
93 |
94 | assert.deepStrictEqual(actual, expected);
95 | });
96 |
97 | it('should return an object of paths from user-dirs.dirs on linux', () => {
98 | const platform = 'linux';
99 |
100 | const paths = {
101 | conf: path.join(fixtures, platform, 'Users/user'),
102 | etc: path.join(fixtures, platform, 'etc')
103 | };
104 |
105 | const expected = {
106 | XDG_DESKTOP_DIR: `/Users/${username}/Desktop`,
107 | XDG_DOCUMENTS_DIR: `/Users/${username}/Documents`,
108 | XDG_DOWNLOAD_DIR: `/Users/${username}/Downloads`,
109 | XDG_MUSIC_DIR: `/Users/${username}/Music`,
110 | XDG_PICTURES_DIR: `/Users/${username}/Pictures`,
111 | XDG_PUBLICSHARE_DIR: `/Users/${username}/Public`,
112 | XDG_TEMPLATES_DIR: `/Users/${username}/Templates`,
113 | XDG_VIDEOS_DIR: `/Users/${username}/Videos`
114 | };
115 |
116 | const actual = userdirs.dirs({
117 | platform,
118 | homedir: paths.conf,
119 | etc: paths.etc
120 | });
121 |
122 | assert.deepStrictEqual(actual, expected);
123 | });
124 | });
125 |
126 | describe('.create()', () => {
127 | it('should return an object of paths for MacOS (darwin)', () => {
128 | const platform = 'darwin';
129 |
130 | const paths = {
131 | conf: path.join(fixtures, platform, 'Users/user'),
132 | etc: path.join(fixtures, platform, 'etc')
133 | };
134 |
135 | const expected = {
136 | XDG_DESKTOP_DIR: `/Users/${username}/Desktop`,
137 | XDG_DOCUMENTS_DIR: `/Users/${username}/Documents`,
138 | XDG_DOWNLOAD_DIR: `/Users/${username}/Downloads`,
139 | XDG_MUSIC_DIR: `/Users/${username}/Music`,
140 | XDG_PICTURES_DIR: `/Users/${username}/Pictures`,
141 | XDG_PUBLICSHARE_DIR: `/Users/${username}/Public`,
142 | XDG_TEMPLATES_DIR: `/Users/${username}/Templates`,
143 | XDG_VIDEOS_DIR: `/Users/${username}/Videos`
144 | };
145 |
146 | const actual = userdirs.create({
147 | platform,
148 | homedir: paths.conf,
149 | etc: paths.etc
150 | });
151 |
152 | assert.deepStrictEqual(actual, expected);
153 | });
154 |
155 | it('should return an object of paths for linux', () => {
156 | const platform = 'linux';
157 |
158 | const paths = {
159 | conf: path.join(fixtures, platform, 'Users/user'),
160 | etc: path.join(fixtures, platform, 'etc')
161 | };
162 |
163 | const expected = {
164 | XDG_DESKTOP_DIR: `/Users/${username}/Desktop`,
165 | XDG_DOCUMENTS_DIR: `/Users/${username}/Documents`,
166 | XDG_DOWNLOAD_DIR: `/Users/${username}/Downloads`,
167 | XDG_MUSIC_DIR: `/Users/${username}/Music`,
168 | XDG_PICTURES_DIR: `/Users/${username}/Pictures`,
169 | XDG_PUBLICSHARE_DIR: `/Users/${username}/Public`,
170 | XDG_TEMPLATES_DIR: `/Users/${username}/Templates`,
171 | XDG_VIDEOS_DIR: `/Users/${username}/Videos`
172 | };
173 |
174 | const actual = userdirs.create({
175 | platform,
176 | homedir: paths.conf,
177 | etc: paths.etc
178 | });
179 |
180 | assert.deepStrictEqual(actual, expected);
181 | });
182 | });
183 |
184 | describe('.darwin()', () => {
185 | it('should return an object of paths for MacOS', () => {
186 | const expected = {
187 | conf: '/etc/xdg/user-dirs.conf',
188 | defaults: '/etc/xdg/user-dirs.defaults',
189 | dirs: `/Users/${username}/Library/Application Support/user-dirs.dirs`
190 | };
191 |
192 | const actual = userdirs.darwin();
193 | assert.deepStrictEqual(actual, expected);
194 | });
195 | });
196 |
197 | describe('.linux()', () => {
198 | it('should return an object of paths for Linux', () => {
199 | const expected = {
200 | conf: '/etc/xdg/user-dirs.conf',
201 | defaults: '/etc/xdg/user-dirs.defaults',
202 | dirs: `/Users/${username}/.config/user-dirs.dirs`
203 | };
204 | const actual = userdirs.linux();
205 | assert.deepStrictEqual(actual, expected);
206 | });
207 | });
208 |
209 | describe('.win32()', () => {
210 | it('should return an object of paths for Windows', () => {
211 | const expected = {
212 | conf: `/Users/${username}/user-dirs.conf`,
213 | defaults: `/Users/${username}/user-dirs.defaults`,
214 | dirs: `/Users/${username}/AppData/Roaming/user-dirs.dirs`
215 | };
216 | const actual = userdirs.win32();
217 | assert.deepStrictEqual(actual, expected);
218 | });
219 | });
220 | });
221 |
--------------------------------------------------------------------------------
/lib/user-dirs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const utils = require('./utils');
5 | const { homedir, load } = utils;
6 |
7 | /**
8 | * Get the XDG User Directories for Linux, or equivalents for Windows or MaxOS.
9 | *
10 | * @name .userdirs()
11 | * @param {Object} `options`
12 | * @return {Object} Returns an object of directory paths.
13 | * @api public
14 | */
15 |
16 | const userdirs = (options = {}) => {
17 | const platform = options.platform || process.platform;
18 | const fn = userdirs[platform];
19 |
20 | if (typeof fn !== 'function') {
21 | throw new Error(`Platform "${platform}" is not supported`);
22 | }
23 |
24 | return fn(options);
25 | };
26 |
27 | /**
28 | * Convenience methods
29 | */
30 |
31 | userdirs.home = (options = {}) => {
32 | return options.homedir || homedir(options.platform || process.platform);
33 | };
34 |
35 | /**
36 | * Returns an object to with paths to `user-dirs.*` files, as well as functions to
37 | * load each file.
38 | *
39 | * @name .userdirs.expand()
40 | * @param {Object} `options`
41 | * @param {Object} `paths` Optionally pass the paths from the `userdirs()` function to avoid creating them again.
42 | * @return {Object} Returns an object with a `paths` object, and `config`, `defaults`, `dirs`, and `create` functions for actually loading the user-dir files.
43 | * @api public
44 | */
45 |
46 | userdirs.expand = (options = {}, paths = userdirs(options)) => {
47 | const home = userdirs.home(options);
48 | const resolve = filepath => path.join(home, filepath);
49 |
50 | const data = {
51 | paths,
52 | config: opts => userdirs.load(paths.conf, { ...options, ...opts }),
53 | defaults: opts => userdirs.load(paths.defaults, { ...options, ...opts }, resolve),
54 | dirs: opts => userdirs.load(paths.dirs, { ...options, ...opts }, resolve),
55 | create: opts => {
56 | const config = data.config(opts);
57 |
58 | if (config.enabled !== false) {
59 | return Object.assign(data.defaults(opts), data.dirs(opts));
60 | }
61 |
62 | return {};
63 | }
64 | };
65 |
66 | return data;
67 | };
68 |
69 | /**
70 | * Loads and parses the contents of `user-dirs.conf`, if one exists in user home.
71 | *
72 | * ```js
73 | * const config = userdirs.conf();
74 | * console.log(config);
75 | * //=> { enabled: true, filename_encoding: 'UTF-8' }
76 | * ```
77 | * @name .userdirs.conf()
78 | * @param {Object} `options`
79 | * @return {Object} Returns configur
80 | * @api public
81 | */
82 |
83 | userdirs.conf = options => {
84 | const dirs = userdirs({ ...options, load: false });
85 | return userdirs.load(dirs.conf, options);
86 | };
87 |
88 | /**
89 | * Loads and parses the contents of `user-dirs.defaults`, if one exists in user home.
90 | *
91 | * ```js
92 | * const defaults = userdirs.defaults();
93 | * console.log(defaults);
94 | * ```
95 | * // Example results:
96 | * // {
97 | * // XDG_DESKTOP_DIR: '/Users/jonschlinkert/Desktop',
98 | * // XDG_DOWNLOAD_DIR: '/Users/jonschlinkert/Downloads',
99 | * // XDG_TEMPLATES_DIR: '/Users/jonschlinkert/Templates',
100 | * // XDG_PUBLICSHARE_DIR: '/Users/jonschlinkert/Public',
101 | * // XDG_DOCUMENTS_DIR: '/Users/jonschlinkert/Documents',
102 | * // XDG_MUSIC_DIR: '/Users/jonschlinkert/Music',
103 | * // XDG_PICTURES_DIR: '/Users/jonschlinkert/Pictures',
104 | * // XDG_VIDEOS_DIR: '/Users/jonschlinkert/Videos'
105 | * // }
106 | * @name .userdirs.defaults()
107 | * @param {Object} `options`
108 | * @return {Object} Returns an object of paths.
109 | * @api public
110 | */
111 |
112 | userdirs.defaults = (options = {}) => {
113 | const home = userdirs.home(options);
114 | const dirs = userdirs({ ...options, load: false });
115 | const resolve = filepath => path.join(home, filepath);
116 | return userdirs.load(dirs.defaults, options, resolve);
117 | };
118 |
119 | /**
120 | * Loads and parses the contents of `user-dirs.dirs`, if one exists in user home.
121 | *
122 | * ```js
123 | * const dirs = userdirs.dirs();
124 | * console.log(dirs);
125 | * // Example results:
126 | * // {
127 | * // XDG_MUSIC_DIR: '/Users/jonschlinkert/Documents/Music',
128 | * // XDG_PICTURES_DIR: '/Users/jonschlinkert/Documents/Pictures',
129 | * // XDG_TEMPLATES_DIR: '/Users/jonschlinkert/Documents/Templates',
130 | * // XDG_VIDEOS_DIR: '/Users/jonschlinkert/Documents/Videos'
131 | * // }
132 | * ```
133 | * @name .userdirs.dirs()
134 | * @param {Object} `options`
135 | * @return {Object} Returns an object of paths.
136 | * @api public
137 | */
138 |
139 | userdirs.dirs = (options = {}) => {
140 | const home = userdirs.home(options);
141 | const dirs = userdirs({ ...options, load: false });
142 | const resolve = filepath => path.join(home, filepath);
143 | return userdirs.load(dirs.dirs, options, resolve);
144 | };
145 |
146 | /**
147 | * Get the actual XDG User Directories to use for MacOS. Gets the `user-dirs.conf`,
148 | * `user-dirs.defaults`, and the `user-dirs.dirs` files and, if not disabled in
149 | * `user-dirs.conf`, merges the values in defaults and dirs to create the paths to use.
150 | *
151 | * ```js
152 | * const dirs = userdirs.create();
153 | * console.log(dirs);
154 | * // Example results:
155 | * // {
156 | * // XDG_DESKTOP_DIR: '/Users/jonschlinkert/Desktop',
157 | * // XDG_DOWNLOAD_DIR: '/Users/jonschlinkert/Downloads',
158 | * // XDG_TEMPLATES_DIR: '/Users/jonschlinkert/Documents/Templates',
159 | * // XDG_PUBLICSHARE_DIR: '/Users/jonschlinkert/Public',
160 | * // XDG_DOCUMENTS_DIR: '/Users/jonschlinkert/Documents',
161 | * // XDG_MUSIC_DIR: '/Users/jonschlinkert/Documents/Music',
162 | * // XDG_PICTURES_DIR: '/Users/jonschlinkert/Documents/Pictures',
163 | * // XDG_VIDEOS_DIR: '/Users/jonschlinkert/Documents/Videos'
164 | * // }
165 | * ```
166 | * @name .userdirs.create()
167 | * @param {Object} `options`
168 | * @return {Object} Returns an object of paths.
169 | * @api public
170 | */
171 |
172 | userdirs.create = (...args) => userdirs.expand(...args).create();
173 |
174 | /**
175 | * Get the XDG User Directories for MacOS. This method is used by the main function
176 | * when `process.platform` is `darwin`. Exposed as a method so you can call it directly
177 | * if necessary. Also aliased as `userdirs.macos()`.
178 | *
179 | * ```js
180 | * const { dirs, conf, defaults } = userdirs.darwin(); // or userdirs.macos();
181 | * ```
182 | * @name .userdirs.darwin()
183 | * @param {Object} `options`
184 | * @return {Object} Returns an object of paths.
185 | * @api public
186 | */
187 |
188 | userdirs.darwin = (options = {}) => {
189 | const env = options.env || process.env;
190 |
191 | // It seems that there is still considerable debate about whether to use
192 | // `Application Support` or `Preferences` for the home folder on MacOS.
193 | // This spec is for linux, so we'll continue to have these debates until
194 | // it's formalized for other OSes. For now, we'll use `Application Support`,
195 | // since that seems to be the most commonly agreed upon. If you disagree,
196 | // You can pass `homeFolder: 'Preferences'` on the options.
197 | const homeFolder = options.homeFolder || 'Application Support';
198 |
199 | // Non-standard, but this gives us an escape hatch
200 | const etc = env.XDG_ETC || options.etc || '/etc/xdg';
201 |
202 | const home = options.homedir || homedir('darwin');
203 | const lib = path.join(home, 'Library');
204 | const app = env.XDG_CONFIG_HOME || path.join(lib, homeFolder);
205 |
206 | return {
207 | conf: env.XDG_USER_DIRS_CONF || path.join(etc, 'user-dirs.conf'),
208 | defaults: env.XDG_USER_DIRS_DEFAULTS || path.join(etc, 'user-dirs.defaults'),
209 | dirs: env.XDG_USER_DIRS || path.join(app, 'user-dirs.dirs')
210 | };
211 | };
212 |
213 | /**
214 | * Gets the XDG User Directories for Linux. Used by the main export when
215 | * `process.platform` is `linux`.
216 | *
217 | * ```js
218 | * const { dirs, conf, defaults } = userdirs.linux();
219 | * ```
220 | * @name .userdirs.linux()
221 | * @return {Object} Returns an object of paths.
222 | * @return {Object} Returns an object of paths.
223 | * @api public
224 | */
225 |
226 | userdirs.linux = (options = {}) => {
227 | const env = options.env || process.env;
228 |
229 | // Non-standard, but this gives us an escape hatch
230 | const etc = env.XDG_ETC || options.etc || '/etc/xdg';
231 | const home = options.homedir || homedir('linux');
232 | const config = env.XDG_CONFIG_HOME || path.join(home, '.config');
233 |
234 | return {
235 | conf: env.XDG_USER_DIRS_CONF || path.join(etc, 'user-dirs.conf'),
236 | defaults: env.XDG_USER_DIRS_DEFAULTS || path.join(etc, 'user-dirs.defaults'),
237 | dirs: env.XDG_USER_DIRS || path.join(config, 'user-dirs.dirs')
238 | };
239 | };
240 |
241 | /**
242 | * Gets the XDG User Directories for MacOS. Used by the `userdirs()` function when
243 | * `process.platform` is `win32`. Also aliased as `userdirs.windows()`.
244 | *
245 | * ```js
246 | * const { dirs, conf, defaults } = userdirs.win32(); // or userdirs.windows();
247 | * ```
248 | * @name .userdirs.win32()
249 | * @param {Object} `options`
250 | * @return {Object} Returns an object of paths.
251 | * @api public
252 | */
253 |
254 | userdirs.win32 = (options = {}) => {
255 | const env = options.env || process.env;
256 | const home = options.homedir || homedir('win32');
257 |
258 | const { APPDATA = path.join(home, 'AppData', 'Roaming') } = env;
259 | const appdata = env.XDG_CONFIG_HOME || APPDATA;
260 |
261 | return {
262 | conf: env.XDG_USER_DIRS_CONF || path.join(home, 'user-dirs.conf'),
263 | defaults: env.XDG_USER_DIRS_DEFAULTS || path.join(home, 'user-dirs.defaults'),
264 | dirs: env.XDG_USER_DIRS || path.join(appdata, 'user-dirs.dirs')
265 | };
266 | };
267 |
268 | /**
269 | * Expose "userdirs" API
270 | */
271 |
272 | userdirs.load = load;
273 | userdirs.windows = userdirs.win32;
274 | userdirs.macos = userdirs.darwin;
275 | module.exports = userdirs;
276 |
--------------------------------------------------------------------------------
/.verb.md:
--------------------------------------------------------------------------------
1 | ## Usage
2 |
3 | Add the following line of code to your [Node.js](https://nodejs.org/) application:
4 |
5 | ```js
6 | const xdg = require('{%= name %}');
7 | ```
8 |
9 | The main export is a function that may be called with an options object:
10 |
11 | ```js
12 | const dirs = xdg(/* options */);
13 | console.log(dirs);
14 |
15 | /* This output is on MacOS, see examples for Linux and Windows below */
16 | console.log(dirs.config); // => '/Users/jonschlinkert/Library/Preferences'
17 | console.log(dirs.cache); // => '/Users/jonschlinkert/Library/Caches'
18 | console.log(dirs.data); // => '/Users/jonschlinkert/Library/Application Support'
19 | ```
20 |
21 | See [example output](#examples) by platform.
22 |
23 | ## Options
24 |
25 |
26 | Click to see all available options
27 |
28 | The following options are available for customizing behavior and/or testing behavior.
29 |
30 | | Option | Type | Description | Default Value |
31 | | --- | --- | --- | --- |
32 | | `cachedir` | `string` | Override the default `cachedir` | Platform specific, see [below](#examples) |
33 | | `configdir` | `string` | Override the default `configdir` | |
34 | | `datadir` | `string` | Override the default `datadir` | |
35 | | `env` | `object` | The `env` object to use for getting paths. | `process.env` |
36 | | `expanded` | `boolean` | Expand paths into an object. See the [Expanded Paths](#expanded-paths) example for more details. | undefined |
37 | | `homedir` | `string` | The user's home directory. | `os.homedir()` |
38 | | `platform` | `string` | The platform to use: `darwin`, `linux`, `win32` | `process.platform` |
39 | | `resolve` | `function` | Custom function for resolving paths to each directory. The default function attempts to respect casing in the user's existing directories. | undefined |
40 | | `runtimedir` | `string` | Override the default `runtimedir` | |
41 | | `subdir` | `string` | A sub-directory to join to the path, typically the name of your application. This path is joined differently on each platform. See [examples](#examples). | `xdg` |
42 | | `tempdir` | `string` | The temp directory to use. | `os.tmpdir()` |
43 |
44 | See [examples](#xamples) below.
45 |
46 |
47 |
48 | ## Examples
49 |
50 |
51 | Click to see example for platform-specific paths
52 |
53 | Get paths for a specific platform.
54 |
55 |
56 | ```js
57 | console.log(xdg.darwin());
58 | console.log(xdg.linux());
59 | console.log(xdg.win32());
60 | // or, if you want "expanded" paths (see the "expanded paths" example)
61 | console.log(xdg({ expanded: true, platform: 'darwin' }));
62 | console.log(xdg({ expanded: true, platform: 'linux' }));
63 | console.log(xdg({ expanded: true, platform: 'win32' }));
64 | ```
65 |
66 |
67 |
68 |
69 | Click to see example with no options
70 |
71 | The following examples show what the paths look like when no options are passed.
72 |
73 |
74 | ```js
75 | console.log(xdg());
76 | ```
77 |
78 | ### MacOS (darwin)
79 |
80 | ```js
81 | {
82 | cache: '/Users/jonschlinkert/Library/Caches/xdg',
83 | config: '/Users/jonschlinkert/Library/Preferences/xdg',
84 | configdirs: [ '/Users/jonschlinkert/Library/Preferences/xdg', '/etc/xdg' ],
85 | data: '/Users/jonschlinkert/Library/Application Support/xdg',
86 | datadirs: [
87 | '/Users/jonschlinkert/Library/Application Support/xdg',
88 | '/usr/local/share/',
89 | '/usr/share/'
90 | ],
91 | runtime: '/var/folders/vd/53h736bj0_sg9gk04c89k0pr0000gq/T/xdg'
92 | }
93 | ```
94 |
95 | ### Linux
96 |
97 | ```js
98 | {
99 | cache: '/Users/jonschlinkert/.cache/xdg',
100 | config: '/Users/jonschlinkert/.config/xdg',
101 | configdirs: [ '/Users/jonschlinkert/.config/xdg', '/etc/xdg' ],
102 | data: '/Users/jonschlinkert/.local/share/xdg',
103 | datadirs: [
104 | '/Users/jonschlinkert/.local/share/xdg',
105 | '/usr/local/share/',
106 | '/usr/share/'
107 | ],
108 | runtime: '/var/folders/vd/53h736bj0_sg9gk04c89k0pr0000gq/T/xdg'
109 | }
110 | ```
111 |
112 | ### Windows (win32)
113 |
114 | ```js
115 | {
116 | cache: '/Users/jonschlinkert/AppData/Local/xdg/Cache',
117 | config: '/Users/jonschlinkert/AppData/Roaming/xdg/Config',
118 | configdirs: [ '/Users/jonschlinkert/AppData/Roaming/xdg/Config' ],
119 | data: '/Users/jonschlinkert/AppData/Local/xdg/Data',
120 | datadirs: [ '/Users/jonschlinkert/AppData/Local/xdg/Data' ],
121 | runtime: '/var/folders/vd/53h736bj0_sg9gk04c89k0pr0000gq/T/xdg'
122 | }
123 | ```
124 |
125 |
126 |
127 |
128 | Click to see "expanded" paths example
129 |
130 | Running the following example returns an object with "expanded" paths, where `config` and `configdirs` are converted to `config.home` and `config.dirs`, etc. Additionally, `cwd`, `home` and `temp` paths are added for convenience.
131 |
132 |
133 | ```js
134 | console.log(xdg({ expanded: true, subdir: 'FooBar' }));
135 | ```
136 |
137 | **Extra directories**
138 |
139 | Note that the `expanded` object includes four additional path properties for convenience:
140 |
141 | - `cwd` - set via `options.cwd` or `process.cwd()`
142 | - `home` - set via `options.homedir` or `os.homedir()`
143 | - `temp` - set via `options.tempdir` or `os.tmpdir()`
144 | - `cache.logs` - set at `path.join(cachedir, 'logs')`
145 |
146 | ### MacOS (darwin)
147 |
148 | ```js
149 | {
150 | cwd: '/Users/jonschlinkert/dev/@folder/xdg',
151 | home: '/Users/jonschlinkert',
152 | temp: '/var/folders/vd/53h736bj0_sg9gk04c89k0pr0000gq/T',
153 | cache: {
154 | home: '/Users/jonschlinkert/Library/Caches/FooBar',
155 | logs: '/Users/jonschlinkert/Library/Caches/FooBar/Logs'
156 | },
157 | config: {
158 | home: '/Users/jonschlinkert/Library/Preferences/FooBar',
159 | dirs: [ '/Users/jonschlinkert/Library/Preferences/FooBar', '/etc/FooBar' ]
160 | },
161 | data: {
162 | home: '/Users/jonschlinkert/Library/Application Support/FooBar',
163 | dirs: [
164 | '/Users/jonschlinkert/Library/Application Support/FooBar',
165 | '/usr/local/share/',
166 | '/usr/share/'
167 | ]
168 | },
169 | runtime: { home: '/var/folders/vd/53h736bj0_sg9gk04c89k0pr0000gq/T/FooBar' }
170 | }
171 | ```
172 |
173 | ### Linux
174 |
175 | ```js
176 | {
177 | cwd: '/Users/jonschlinkert/dev/@folder/xdg',
178 | home: '/Users/jonschlinkert',
179 | temp: '/var/folders/vd/53h736bj0_sg9gk04c89k0pr0000gq/T',
180 | cache: {
181 | home: '/Users/jonschlinkert/.cache/FooBar',
182 | logs: '/Users/jonschlinkert/.cache/FooBar/Logs'
183 | },
184 | config: {
185 | home: '/Users/jonschlinkert/.config/FooBar',
186 | dirs: [ '/Users/jonschlinkert/.config/FooBar', '/etc/FooBar' ]
187 | },
188 | data: {
189 | home: '/Users/jonschlinkert/.local/share/FooBar',
190 | dirs: [
191 | '/Users/jonschlinkert/.local/share/FooBar',
192 | '/usr/local/share/',
193 | '/usr/share/'
194 | ]
195 | },
196 | runtime: { home: '/var/folders/vd/53h736bj0_sg9gk04c89k0pr0000gq/T/FooBar' }
197 | }
198 | ```
199 |
200 | ### Windows (win32)
201 |
202 | ```js
203 | {
204 | cwd: '/Users/jonschlinkert/dev/@folder/xdg',
205 | home: '/Users/jonschlinkert',
206 | temp: '/var/folders/vd/53h736bj0_sg9gk04c89k0pr0000gq/T',
207 | cache: {
208 | home: '/Users/jonschlinkert/AppData/Local/FooBar/Cache',
209 | logs: '/Users/jonschlinkert/AppData/Local/FooBar/Cache/Logs'
210 | },
211 | config: {
212 | home: '/Users/jonschlinkert/AppData/Roaming/FooBar/Config',
213 | dirs: [ '/Users/jonschlinkert/AppData/Roaming/FooBar/Config' ]
214 | },
215 | data: {
216 | home: '/Users/jonschlinkert/AppData/Local/FooBar/Data',
217 | dirs: [ '/Users/jonschlinkert/AppData/Local/FooBar/Data' ]
218 | },
219 | runtime: { home: '/var/folders/vd/53h736bj0_sg9gk04c89k0pr0000gq/T/FooBar' }
220 | }
221 | ```
222 |
223 |
224 |
225 | # API
226 |
227 | Aside from the main export, `xdg()`, several platform-specific methods are exposed to simplify getting paths for specific platforms. Note that you can accomplish the same thing by passing the platform on the options to the main export, e.g. `{ platform: 'linux' }`.
228 |
229 | The main export, and each method, returns an object with the following properties (see the [XDG Base Directories](#xdg-base-directory) docs below for more details on how each property is set):
230 |
231 | - `cache`
232 | - `config`
233 | - `configdirs`
234 | - `data`
235 | - `datadirs`
236 | - `runtime`
237 |
238 | ## Main Export
239 |
240 | {%= apidocs('index.js') %}
241 | {%= apidocs('lib/userdirs.js') %}
242 |
243 | ## XDG Base Directories
244 |
245 | This documentation is from the Arch Linux [XDG Base Directory](https://wiki.archlinux.org/index.php/XDG_Base_Directory) docs.
246 |
247 | ### User directories
248 |
249 | - `XDG_CONFIG_HOME`
250 | * Where user-specific configurations should be written (analogous to `/etc`).
251 | * Should default to `$HOME/.config`.
252 |
253 | - `XDG_CACHE_HOME`
254 | * Where user-specific non-essential (cached) data should be written (analogous to `/var/cache`).
255 | * Should default to `$HOME/.cache`.
256 |
257 | - `XDG_DATA_HOME`
258 | * Where user-specific data files should be written (analogous to `/usr/share`).
259 | * Should default to `$HOME/.local/share`.
260 |
261 | - `XDG_RUNTIME_DIR`
262 | * Used for non-essential, user-specific data files such as sockets, named pipes, etc.
263 | * Not required to have a default value; warnings should be issued if not set or equivalents provided.
264 | * Must be owned by the user with an access mode of `0700`.
265 | * Filesystem fully featured by standards of OS.
266 | * Must be on the local filesystem.
267 | * May be subject to periodic cleanup.
268 | * Modified every 6 hours or set sticky bit if persistence is desired.
269 | * Can only exist for the duration of the user's login.
270 | * Should not store large files as it may be mounted as a tmpfs.
271 |
272 | ### System directories
273 |
274 | - `XDG_DATA_DIRS`
275 | * List of directories seperated by `:` (analogous to `PATH`).
276 | * Should default to `/usr/local/share:/usr/share`.
277 |
278 | - `XDG_CONFIG_DIRS`
279 | * List of directories seperated by `:` (analogous to `PATH`).
280 | * Should default to `/etc/xdg`.
281 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # @folder/xdg [](https://www.npmjs.com/package/@folder/xdg) [](https://npmjs.org/package/@folder/xdg) [](https://npmjs.org/package/@folder/xdg)
2 |
3 | > Get cross-platform XDG Base Directories or their equivalents. Works with Linux, Windows (win32), or MacOS (darwin).
4 |
5 | Please consider following this project's author, [Jon Schlinkert](https://github.com/jonschlinkert), and consider starring the project to show your :heart: and support.
6 |
7 | ## Install
8 |
9 | Install with [npm](https://www.npmjs.com/):
10 |
11 | ```sh
12 | $ npm install --save @folder/xdg
13 | ```
14 |
15 | ## Usage
16 |
17 | Add the following line of code to your [Node.js](https://nodejs.org/) application:
18 |
19 | ```js
20 | const xdg = require('@folder/xdg');
21 | ```
22 |
23 | The main export is a function that may be called with an options object:
24 |
25 | ```js
26 | const dirs = xdg(/* options */);
27 | console.log(dirs);
28 |
29 | /* This output is on MacOS, see examples for Linux and Windows below */
30 | console.log(dirs.config); // => '/Users/jonschlinkert/Library/Preferences'
31 | console.log(dirs.cache); // => '/Users/jonschlinkert/Library/Caches'
32 | console.log(dirs.data); // => '/Users/jonschlinkert/Library/Application Support'
33 | ```
34 |
35 | See [example output](#examples) by platform.
36 |
37 | ## Options
38 |
39 |
40 | Click to see all available options
41 |
42 | The following options are available for customizing behavior and/or testing behavior.
43 |
44 | | Option | Type | Description | Default Value |
45 | | --- | --- | --- | --- |
46 | | `cachedir` | `string` | Override the default `cachedir` | Platform specific, see [below](#examples) |
47 | | `configdir` | `string` | Override the default `configdir` | |
48 | | `datadir` | `string` | Override the default `datadir` | |
49 | | `env` | `object` | The `env` object to use for getting paths. | `process.env` |
50 | | `expanded` | `boolean` | Expand paths into an object. See the [Expanded Paths](#expanded-paths) example for more details. | undefined |
51 | | `homedir` | `string` | The user's home directory. | `os.homedir()` |
52 | | `platform` | `string` | The platform to use: `darwin`, `linux`, `win32` | `process.platform` |
53 | | `resolve` | `function` | Custom function for resolving paths to each directory. The default function attempts to respect casing in the user's existing directories. | undefined |
54 | | `runtimedir` | `string` | Override the default `runtimedir` | |
55 | | `subdir` | `string` | A sub-directory to join to the path, typically the name of your application. This path is joined differently on each platform. See [examples](#examples). | `xdg` |
56 | | `tempdir` | `string` | The temp directory to use. | `os.tmpdir()` |
57 |
58 | See [examples](#xamples) below.
59 |
60 |
61 |
62 | ## Examples
63 |
64 |
65 | Click to see example for platform-specific paths
66 |
67 | Get paths for a specific platform.
68 |
69 |
70 | ```js
71 | console.log(xdg.darwin());
72 | console.log(xdg.linux());
73 | console.log(xdg.win32());
74 | // or, if you want "expanded" paths (see the "expanded paths" example)
75 | console.log(xdg({ expanded: true, platform: 'darwin' }));
76 | console.log(xdg({ expanded: true, platform: 'linux' }));
77 | console.log(xdg({ expanded: true, platform: 'win32' }));
78 | ```
79 |
80 |
81 |
82 |
83 | Click to see example with no options
84 |
85 | The following examples show what the paths look like when no options are passed.
86 |
87 |
88 | ```js
89 | console.log(xdg());
90 | ```
91 |
92 | ### MacOS (darwin)
93 |
94 | ```js
95 | {
96 | cache: '/Users/jonschlinkert/Library/Caches/xdg',
97 | config: '/Users/jonschlinkert/Library/Preferences/xdg',
98 | configdirs: [ '/Users/jonschlinkert/Library/Preferences/xdg', '/etc/xdg' ],
99 | data: '/Users/jonschlinkert/Library/Application Support/xdg',
100 | datadirs: [
101 | '/Users/jonschlinkert/Library/Application Support/xdg',
102 | '/usr/local/share/',
103 | '/usr/share/'
104 | ],
105 | runtime: '/var/folders/vd/53h736bj0_sg9gk04c89k0pr0000gq/T/xdg'
106 | }
107 | ```
108 |
109 | ### Linux
110 |
111 | ```js
112 | {
113 | cache: '/Users/jonschlinkert/.cache/xdg',
114 | config: '/Users/jonschlinkert/.config/xdg',
115 | configdirs: [ '/Users/jonschlinkert/.config/xdg', '/etc/xdg' ],
116 | data: '/Users/jonschlinkert/.local/share/xdg',
117 | datadirs: [
118 | '/Users/jonschlinkert/.local/share/xdg',
119 | '/usr/local/share/',
120 | '/usr/share/'
121 | ],
122 | runtime: '/var/folders/vd/53h736bj0_sg9gk04c89k0pr0000gq/T/xdg'
123 | }
124 | ```
125 |
126 | ### Windows (win32)
127 |
128 | ```js
129 | {
130 | cache: '/Users/jonschlinkert/AppData/Local/xdg/Cache',
131 | config: '/Users/jonschlinkert/AppData/Roaming/xdg/Config',
132 | configdirs: [ '/Users/jonschlinkert/AppData/Roaming/xdg/Config' ],
133 | data: '/Users/jonschlinkert/AppData/Local/xdg/Data',
134 | datadirs: [ '/Users/jonschlinkert/AppData/Local/xdg/Data' ],
135 | runtime: '/var/folders/vd/53h736bj0_sg9gk04c89k0pr0000gq/T/xdg'
136 | }
137 | ```
138 |
139 |
140 |
141 |
142 | Click to see "expanded" paths example
143 |
144 | Running the following example returns an object with "expanded" paths, where `config` and `configdirs` are converted to `config.home` and `config.dirs`, etc. Additionally, `cwd`, `home` and `temp` paths are added for convenience.
145 |
146 |
147 | ```js
148 | console.log(xdg({ expanded: true, subdir: 'FooBar' }));
149 | ```
150 |
151 | **Extra directories**
152 |
153 | Note that the `expanded` object includes four additional path properties for convenience:
154 |
155 | * `cwd` - set via `options.cwd` or `process.cwd()`
156 | * `home` - set via `options.homedir` or `os.homedir()`
157 | * `temp` - set via `options.tempdir` or `os.tmpdir()`
158 | * `cache.logs` - set at `path.join(cachedir, 'logs')`
159 |
160 | ### MacOS (darwin)
161 |
162 | ```js
163 | {
164 | cwd: '/Users/jonschlinkert/dev/@folder/xdg',
165 | home: '/Users/jonschlinkert',
166 | temp: '/var/folders/vd/53h736bj0_sg9gk04c89k0pr0000gq/T',
167 | cache: {
168 | home: '/Users/jonschlinkert/Library/Caches/FooBar',
169 | logs: '/Users/jonschlinkert/Library/Caches/FooBar/Logs'
170 | },
171 | config: {
172 | home: '/Users/jonschlinkert/Library/Preferences/FooBar',
173 | dirs: [ '/Users/jonschlinkert/Library/Preferences/FooBar', '/etc/FooBar' ]
174 | },
175 | data: {
176 | home: '/Users/jonschlinkert/Library/Application Support/FooBar',
177 | dirs: [
178 | '/Users/jonschlinkert/Library/Application Support/FooBar',
179 | '/usr/local/share/',
180 | '/usr/share/'
181 | ]
182 | },
183 | runtime: { home: '/var/folders/vd/53h736bj0_sg9gk04c89k0pr0000gq/T/FooBar' }
184 | }
185 | ```
186 |
187 | ### Linux
188 |
189 | ```js
190 | {
191 | cwd: '/Users/jonschlinkert/dev/@folder/xdg',
192 | home: '/Users/jonschlinkert',
193 | temp: '/var/folders/vd/53h736bj0_sg9gk04c89k0pr0000gq/T',
194 | cache: {
195 | home: '/Users/jonschlinkert/.cache/FooBar',
196 | logs: '/Users/jonschlinkert/.cache/FooBar/Logs'
197 | },
198 | config: {
199 | home: '/Users/jonschlinkert/.config/FooBar',
200 | dirs: [ '/Users/jonschlinkert/.config/FooBar', '/etc/FooBar' ]
201 | },
202 | data: {
203 | home: '/Users/jonschlinkert/.local/share/FooBar',
204 | dirs: [
205 | '/Users/jonschlinkert/.local/share/FooBar',
206 | '/usr/local/share/',
207 | '/usr/share/'
208 | ]
209 | },
210 | runtime: { home: '/var/folders/vd/53h736bj0_sg9gk04c89k0pr0000gq/T/FooBar' }
211 | }
212 | ```
213 |
214 | ### Windows (win32)
215 |
216 | ```js
217 | {
218 | cwd: '/Users/jonschlinkert/dev/@folder/xdg',
219 | home: '/Users/jonschlinkert',
220 | temp: '/var/folders/vd/53h736bj0_sg9gk04c89k0pr0000gq/T',
221 | cache: {
222 | home: '/Users/jonschlinkert/AppData/Local/FooBar/Cache',
223 | logs: '/Users/jonschlinkert/AppData/Local/FooBar/Cache/Logs'
224 | },
225 | config: {
226 | home: '/Users/jonschlinkert/AppData/Roaming/FooBar/Config',
227 | dirs: [ '/Users/jonschlinkert/AppData/Roaming/FooBar/Config' ]
228 | },
229 | data: {
230 | home: '/Users/jonschlinkert/AppData/Local/FooBar/Data',
231 | dirs: [ '/Users/jonschlinkert/AppData/Local/FooBar/Data' ]
232 | },
233 | runtime: { home: '/var/folders/vd/53h736bj0_sg9gk04c89k0pr0000gq/T/FooBar' }
234 | }
235 | ```
236 |
237 |
238 |
239 | # API
240 |
241 | Aside from the main export, `xdg()`, several platform-specific methods are exposed to simplify getting paths for specific platforms. Note that you can accomplish the same thing by passing the platform on the options to the main export, e.g. `{ platform: 'linux' }`.
242 |
243 | The main export, and each method, returns an object with the following properties (see the [XDG Base Directories](#xdg-base-directory) docs below for more details on how each property is set):
244 |
245 | * `cache`
246 | * `config`
247 | * `configdirs`
248 | * `data`
249 | * `datadirs`
250 | * `runtime`
251 |
252 | ## Main Export
253 |
254 | ### [xdg](index.js#L17)
255 |
256 | Get the XDG Base Directory paths for Linux, or equivalent paths for Windows or MaxOS.
257 |
258 | **Params**
259 |
260 | * `options` **{Object}**
261 | * `returns` **{Object}**: Returns an object of paths for the current platform.
262 |
263 | ### [.darwin](index.js#L42)
264 |
265 | Get XDG equivalent paths for MacOS. Used by the main export when `process.platform` is `darwin`. Aliased as `xdg.macos()`.
266 |
267 | **Params**
268 |
269 | * `options` **{Object}**
270 | * `returns` **{Object}**: Returns an object of paths.
271 |
272 | **Example**
273 |
274 | ```js
275 | const dirs = xdg.darwin();
276 | // or
277 | const dirs = xdg.macos();
278 | ```
279 |
280 | ### [.linux](index.js#L93)
281 |
282 | Get XDG equivalent paths for Linux. Used by the main export when `process.platform` is `linux`.
283 |
284 | * `returns` **{Object}**: Returns an object of paths.
285 | * `returns` **{Object}**: Returns an object of paths.
286 |
287 | **Example**
288 |
289 | ```js
290 | const dirs = xdg.linux();
291 | ```
292 |
293 | ### [.win32](index.js#L145)
294 |
295 | Get XDG equivalent paths for MacOS. Used by the main export when `process.platform` is `win32`. Aliased as `xdg.windows()`.
296 |
297 | **Params**
298 |
299 | * `options` **{Object}**
300 | * `returns` **{Object}**: Returns an object of paths.
301 |
302 | **Example**
303 |
304 | ```js
305 | const dirs = xdg.win32();
306 | // or
307 | const dirs = xdg.windows();
308 | ```
309 |
310 | ## XDG Base Directories
311 |
312 | This documentation is from the Arch Linux [XDG Base Directory](https://wiki.archlinux.org/index.php/XDG_Base_Directory) docs.
313 |
314 | ### User directories
315 |
316 | * `XDG_CONFIG_HOME`
317 |
318 | - Where user-specific configurations should be written (analogous to `/etc`).
319 | - Should default to `$HOME/.config`.
320 | * `XDG_CACHE_HOME`
321 |
322 | - Where user-specific non-essential (cached) data should be written (analogous to `/var/cache`).
323 | - Should default to `$HOME/.cache`.
324 | * `XDG_DATA_HOME`
325 |
326 | - Where user-specific data files should be written (analogous to `/usr/share`).
327 | - Should default to `$HOME/.local/share`.
328 | * `XDG_RUNTIME_DIR`
329 |
330 | - Used for non-essential, user-specific data files such as sockets, named pipes, etc.
331 | - Not required to have a default value; warnings should be issued if not set or equivalents provided.
332 | - Must be owned by the user with an access mode of `0700`.
333 | - Filesystem fully featured by standards of OS.
334 | - Must be on the local filesystem.
335 | - May be subject to periodic cleanup.
336 | - Modified every 6 hours or set sticky bit if persistence is desired.
337 | - Can only exist for the duration of the user's login.
338 | - Should not store large files as it may be mounted as a tmpfs.
339 |
340 | ### System directories
341 |
342 | * `XDG_DATA_DIRS`
343 |
344 | - List of directories seperated by `:` (analogous to `PATH`).
345 | - Should default to `/usr/local/share:/usr/share`.
346 | * `XDG_CONFIG_DIRS`
347 |
348 | - List of directories seperated by `:` (analogous to `PATH`).
349 | - Should default to `/etc/xdg`.
350 |
351 | ## About
352 |
353 |
354 | Contributing
355 |
356 | Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new).
357 |
358 |
359 |
360 |
361 | Running Tests
362 |
363 | Running and reviewing unit tests is a great way to get familiarized with a library and its API. You can install dependencies and run tests with the following command:
364 |
365 | ```sh
366 | $ npm install && npm test
367 | ```
368 |
369 |
370 |
371 |
372 | Building docs
373 |
374 | _(This project's readme.md is generated by [verb](https://github.com/verbose/verb-generate-readme), please don't edit the readme directly. Any changes to the readme must be made in the [.verb.md](.verb.md) readme template.)_
375 |
376 | To generate the readme, run the following command:
377 |
378 | ```sh
379 | $ npm install -g verbose/verb#dev verb-generate-readme && verb
380 | ```
381 |
382 |
383 |
384 | ### Related projects
385 |
386 | You might also be interested in these projects:
387 |
388 | * [expand-tilde](https://www.npmjs.com/package/expand-tilde): Bash-like tilde expansion for node.js. Expands a leading tilde in a file path to the… [more](https://github.com/jonschlinkert/expand-tilde) | [homepage](https://github.com/jonschlinkert/expand-tilde "Bash-like tilde expansion for node.js. Expands a leading tilde in a file path to the user home directory, or `~+` to the cwd.")
389 | * [global-modules](https://www.npmjs.com/package/global-modules): The directory used by npm for globally installed npm modules. | [homepage](https://github.com/jonschlinkert/global-modules "The directory used by npm for globally installed npm modules.")
390 | * [global-paths](https://www.npmjs.com/package/global-paths): Returns an array of unique "global" directories based on the user's platform and environment. The… [more](https://github.com/jonschlinkert/global-paths) | [homepage](https://github.com/jonschlinkert/global-paths "Returns an array of unique "global" directories based on the user's platform and environment. The resulting paths can be used for doing lookups for generators or other globally installed npm packages. Node.js / JavaScript.")
391 | * [normalize-path](https://www.npmjs.com/package/normalize-path): Normalize slashes in a file path to be posix/unix-like forward slashes. Also condenses repeat slashes… [more](https://github.com/jonschlinkert/normalize-path) | [homepage](https://github.com/jonschlinkert/normalize-path "Normalize slashes in a file path to be posix/unix-like forward slashes. Also condenses repeat slashes to a single slash and removes and trailing slashes, unless disabled.")
392 |
393 | ### Author
394 |
395 | **Jon Schlinkert**
396 |
397 | * [GitHub Profile](https://github.com/jonschlinkert)
398 | * [Twitter Profile](https://twitter.com/jonschlinkert)
399 | * [LinkedIn Profile](https://linkedin.com/in/jonschlinkert)
400 |
401 | ### License
402 |
403 | Copyright © 2023, [Jon Schlinkert](https://github.com/jonschlinkert).
404 | Released under the MIT License.
405 |
406 | ***
407 |
408 | _This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.8.0, on July 24, 2023._
--------------------------------------------------------------------------------