xdg-app-paths
Determine (XDG-compatible) paths for storing application files (cache, config, data, etc)
Installation (CJS/ESM/TypeScript)
npm install xdg-app-paths
# or... `npm install "git:github.com/rivy/js.xdg-app-paths"`
# or... `npm install "git:github.com/rivy/js.xdg-app-paths#v7.0.0"`
# or... `npm install "https://cdn.jsdelivr.net/gh/rivy/js.xdg-app-paths/dist/xdg-app-paths.tgz"`
Usage
CommonJS (CJS)
// MyApp.js
const xdgAppPaths = require('xdg-app-paths/cjs');
const cache = xdgAppPaths.cache();
//(nix)=> '/home/rivy/.cache/MyApp.js'
//(win)=> 'C:\\Users\\rivy\\AppData\\Local\\MyApp\\Cache'
const config = xdgAppPaths.config();
//(nix)=> '/home/rivy/.config/MyApp.js'
//(win)=> 'C:\\Users\\rivy\\AppData\\Roaming\\MyApp\\Config'
const data = xdgAppPaths.data();
//(nix)=> '/home/rivy/.local/share/MyApp.js'
//(win)=> 'C:\\Users\\rivy\\AppData\\Roaming\\MyApp\\Data'
ECMAScript (ESM)/TypeScript
import xdgAppPaths from 'xdg-app-paths';
const configDirs = xdgAppPaths.configDirs();
//...
Deno
import xdgAppPaths from 'https://deno.land/x/xdg_app_paths/src/mod.deno.ts';
//or...
//import xdgAppPaths from 'https://deno.land/x/xdg_app_paths@v8.0.0/src/mod.deno.ts';
//or (via CDN, with optional version/version-range/latest/commit support)...
//import xdgAppPaths from 'https://cdn.jsdelivr.net/gh/rivy/js.xdg-app-paths@8.0.0/src/mod.deno.ts'; // v8.0.0
//import xdgAppPaths from 'https://cdn.jsdelivr.net/gh/rivy/js.xdg-app-paths@8/src/mod.deno.ts'; // v8.x.y
//import xdgAppPaths from 'https://cdn.jsdelivr.net/gh/rivy/js.xdg-app-paths/src/mod.deno.ts'; // latest
//import xdgAppPaths from 'https://cdn.jsdelivr.net/gh/rivy/js.xdg-app-paths@latest/src/mod.deno.ts'; // latest
//import xdgAppPaths from 'https://cdn.jsdelivr.net/gh/rivy/js.xdg-app-paths@COMMIT/src/mod.deno.ts'; // commit
const configDirs = xdgAppPaths.configDirs();
//...
API
Construction/Initialization
XDGAppPaths( Options? )
// CJS
const xdgAppPaths = require('xdg-app-paths/cjs');
// or ...
const xdgAppPaths = require('xdg-app-paths/cjs')(options);
// ESM/TypeScript
import xdgAppPaths from 'xdg-app-paths';
// or ...
import XDGAppPaths from 'xdg-app-paths';
const xdgAppPaths = XDGAppPaths(options);
// Deno
import xdgAppPaths from 'https://deno.land/x/xdg_app_paths/src/mod.deno.ts';
// or ...
import XDGAppPaths from 'https://deno.land/x/xdg_app_paths/src/mod.deno.ts';
const xdgAppPaths = XDGAppPaths(options);
When importing this module, the object returned is a function object, XDGAppPaths
, augmented with attached methods. Additional XDGAppPaths
objects may be constructed by direct call of the imported XDGAppPaths
object (eg, const x = xdgAppPaths(...)
) or by using new
(eg, const x = new xdgAppPaths(...)
).
Upon construction, if not supplied with a specified name (via Options.name
), XDGAppPaths
will generate an application name which is used to further generate isolated application directories, where needed. “$eval” is used as the fallback value when automatically generating names (ie, for immediate mode scripts such as node -e "..."
). The generated or supplied name is stored during XDGAppPaths
construction and subsequently accessible via the $name()
method.
Interfaces/Types
import type { DirOptions, Options, XDGAppPaths } from 'xdg-app-paths'; // TypeScript
//or...
//import type { DirOptions, Options, XDGAppPaths } from 'https://deno.land/x/xdg_app_paths/src/mod.deno.ts'; // Deno
XDGAppPaths
XDGAppPaths
API; also, the interface/type of the default function object export
DirOptions
Configuration options supplied to XDGAppPaths
methods
DirOptions: boolean
=>{ isolated: boolean }
As a shortcut, whenDirOptions
is supplied as aboolean
, it is directly interpreted as theisolated
property (ie,dirOptions = { isolated: dirOptions }
).
DirOptions: object
• default ={ isolated: true }
DirOptions.isolated: boolean
• default =true
Isolation flag; used to override the default isolation mode, when needed
Options
Configuration options supplied when constructing XDGAppPaths
Options: string
=>{ name: string }
As a shortcut, whenOptions
is supplied as astring
, is interpreted directly as thename
property (ie,options = { name: options }
).
Options: object
• default ={ name: '', suffix: '', isolated: true }
Options.name: string
• default =''
Name of the application; used to generate isolated application paths
When missing (undefined
),null
, or empty (''
), it is generated automatically from the process main file name, where determinable. “$eval” is used as a final fallback value when the application name cannot otherwise be determined. Note, Deno reports “$deno$eval” as the main file name when executingdeno eval ...
.
Options.suffix: string
• default =''
Suffix which is appended to the application name when generating the application paths
Options.isolated: boolean
• default =true
Default isolation flag (used when no isolation flag is supplied forDirOptions
)
All interfaces/types listed are exported individually by name (eg, as “XDGAppPaths”).
Methods
All returned path strings are simple, platform-compatible, strings and are not guaranteed to exist. The application is responsible for construction of the directories. If needed, make-dir
or mkdirp
can be used to create the directories.
xdgAppPaths.cache( DirOptions? ): string
Returns the directory for non-essential data files
Deletion of the data contained here might cause an application to slow down.
xdgAppPaths.config( DirOptions? ): string
Returns the directory for config files
Deletion of the data contained here might require the user to reconfigure an application.
xdgAppPaths.data( DirOptions? ): string
Returns the directory for data files
Deletion of the data contained here might force the user to restore from backups.
xdgAppPaths.runtime( DirOptions? ): string?
Returns the directory for runtime files; may return undefined
Deletion of the data contained here might interfere with a currently executing application but should have no effect on future executions.
xdgAppPaths.state( DirOptions? ): string
Returns the directory for state files
Deletion of the data contained here should not materially interfere with execution of an application.
xdgAppPaths.configDirs( DirOptions? ): readonly string[]
Returns a priority-sorted list of possible directories for configuration file storage (includes paths.config()
as the first entry)
xdgAppPaths.dataDirs( DirOptions? ): readonly string[]
Returns a priority-sorted list of possible directories for data file storage (includes paths.data()
as the first entry)
xdgAppPaths.$name(): string
Application name used for path construction (from supplied configuration or auto-generated)
xdgAppPaths.$isolated(): boolean
Default isolation mode used by the particular XDGAppPaths
instance
Example
// MyApp.js
const locatePath = require('locate-path');
const mkdirp = require('mkdirp');
const path = require('path');
const xdgAppPaths = require('xdg-app-paths/cjs');
// Extend appPaths with a "log" location function
xdgAppPaths.log = function (dirOptions) {
const self = xdgAppPaths; // * bind `self` to `xdgAppPaths` => avoids `this` variability due to caller context
function typeOf(x) {
// use avoids circumvention of eslint variable tracking for `x`
return typeof x;
}
if (typeOf(dirOptions) === 'boolean') {
dirOptions = { isolated: dirOptions };
}
if (
typeOf(dirOptions) !== 'object' ||
dirOptions === null ||
typeOf(dirOptions.isolated) !== 'boolean'
) {
dirOptions = { isolated: self.$isolated() };
}
return path.join(self.state(dirOptions), (dirOptions.isolated ? '' : self.$name() + '-') + 'log');
};
// log file
const logPath = path.join(xdgAppPaths.log(), 'debug.txt');
mkdirp.sync(path.dirname(logPath), 0o700);
// config file
// * search for config file within user preferred directories; otherwise, use preferred directory
const possibleConfigPaths = xdgAppPaths
.configDirs()
.concat(xdgAppPaths.configDirs({ isolated: !xdgAppPaths.$isolated() }))
.map((v) => path.join(v, xdgAppPaths.$name() + '.json'));
const configPath = locatePath.sync(possibleConfigPaths) || possibleConfigPaths[0];
// debug(logPath, 'configPath="%s"', configPath);
mkdirp.sync(path.dirname(configPath), 0o700);
// cache file
const cacheDir = path.join(xdgAppPaths.cache());
// debug(logPath, 'cacheDir="%s"', cacheDir);
mkdirp.sync(cacheDir, 0o700);
const cachePath = {};
cachePath.orders = path.join(cacheDir, 'orders.json');
cachePath.customers = path.join(cacheDir, 'customers.json');
//...
Supported Platforms
NodeJS
Requirements
NodeJS >= 4.0[^*]
[^*]: With the conversion to a TypeScript-based project, due to tooling constraints, building and testing are more difficult and more limited on Node platforms earlier than NodeJS-v10. However, the generated CommonJS/UMD project code is fully tested (for NodeJS-v10+) and continues to be compatible with NodeJS-v4+.
*.js
and *.cjs
)
CommonJS modules (CJS; CJS is the basic supported output (with support for NodeJS versions as early as NodeJS-v4).
const xdgAppPaths = require('xdg-app-paths/cjs');
console.log(xdgAppPaths.config());
Note: for CJS,
require('xdg-app-paths')
is supported for backward-compatibility and will execute correctly at run-time. However,require('xdg-app-paths')
links to the default package type declarations which, though correct for ESM or TypeScript, are incorrect for CJS. This, then, leads to incorrect analysis of CJS files by static analysis tools such as TypeScript and Intellisense.Using
require('xdg-app-paths/cjs')
is preferred as it associates the proper CJS type declarations and provides correct information to static analysis tools.
*.mjs
)
ECMAScript modules (ESM; - Requires
XDGAppPaths
v6.0
+.
XDGAppPaths
fully supports ESM imports.
import xdgAppPaths from 'xdg-app-paths';
console.log(xdgAppPaths.config());
*.ts
)
TypeScript (- Requires
XDGAppPaths
v6.0
+.
As of v6.0
+, XDGAppPaths
has been converted to a TypeScript-based module.
As a consequence, TypeScript type definitions are automatically generated, bundled, and exported by the module.
Deno
Requirements
Deno >= v1.8.0[^deno-version-req]
[^deno-version-req]: The Deno.permissions
API (stabilized in Deno v1.8.0) is required to avoid needless panics or prompts by Deno during static imports of this module/package. Note: Deno v1.3.0+ may be used if the run flag --unstable
is also used.
Required Permissions
--allow-env
· allow access to the process environment variables
This is a transitive requirement from the ‘xdg’/’xdg-portable’ module;XDG
requires access to various environment variable to determine platform and user configuration (eg, XDG configuration variables, location of temp and user directories, …).--allow-read
· allow read(-only) access to the file system
This permission is required to useDeno.mainModule
, which is, in turn, required to auto-generate the application name used for data isolation.
- Requires
XDGAppPaths
v7.0
+.
XDGAppPaths
also fully supports use by Deno.
import xdgAppPaths from 'https://deno.land/x/xdg_app_paths/src/mod.deno.ts';
console.log(xdgAppPaths.config());
Discussion
The XDG Base Directory Specification@
defines categories of user information (ie, “cache”, “config”, “data”, …), defines their standard storage locations, and defines the standard process for user configuration of those locations (using XDG_CACHE_HOME
, etc).
Applications supporting the XDG convention are expected to store user-specific files within these locations, either within the common/shared directory (eg, `${xdg.cache()}/filename`
) or within a more isolated application-defined subdirectory (eg, `${xdg.config()/dir/filename`
; dir
usually being the application name).
Windows (“win32”) specific notes
Windows has an alternate convention, offering just two standard locations for applications to persist data, either %APPDATA%
(for files which may “roam” with the user between hosts) and %LOCALAPPDATA%
(for local-machine-only files). All application files are expected to be stored within an application-unique subdirectory in one of those two locations, usually under a directory matching the application name. There is no further popular convention used to segregate the file types (ie, into “cache”, “config”, …) in any way similar to the XDG specification.
So, to support basic XDG-like behavior (that is, segregating the information types into type-specific directories), this module supports a new convention for Windows hosts (taken from xdg-portable
), placing the specific types of files into subdirectories under either %APPDATA%
or %LOCALAPPDATA%
, as appropriate for the file type. The default directories used for the windows platform are listed by xdg-portable
.
By default, this module returns paths which are isolated, application-specific sub-directories under the respective common/shared base directories. These sub-directories are purely dedicated to use by the application. If, however, the application requires access to the common/shared areas, the isolated: false
option may be used during initialization (or as an optional override for specific function calls) to generate and return the common/shared paths. Note, that when using the command/shared directories, take care to use file names which do not collide with those used by other applications.
Origins
This module was forked from sindresorhus/env-paths in order to add cross-platform portability and support simpler cross-platform applications.
Building and Contributing
Build requirements
optional
git-changelog
(v1.1+) … enables changelog automationperl
… enables automated version updates topackage.json
during packaging
Quick build/test
npm install-test
Contributions/development
Reproducible setup (for CI or local development)
git clone "https://github.com/rivy/js.xdg-portable"
cd js.os-paths
# * note: for WinOS, replace `cp` with `copy`
# npm
cp .deps-lock/package-lock.json .
npm clean-install
# yarn
cp .deps-lock/yarn.lock .
yarn --immutable --immutable-cache --check-cache
Project development scripts
> npm run help
...
usage: `npm run TARGET` or `npx run-s TARGET [TARGET..]`
TARGETs:
build build/compile package
clean remove build artifacts
coverage calculate and display (or send) code coverage [alias: 'cov']
fix fix package issues (automated/non-interactive)
fix:lint fix ESLint issues
fix:style fix Prettier formatting issues
help display help
lint check for package code 'lint'
lint:commits check for commit flaws (using `commitlint` and `cspell`)
lint:editorconfig check for EditorConfig format flaws (using `editorconfig-checker`)
lint:lint check for code 'lint' (using `eslint`)
lint:markdown check for markdown errors (using `remark`)
lint:spell check for spelling errors (using `cspell`)
lint:style check for format imperfections (using `prettier`)
prerelease clean, rebuild, and fully test (useful prior to publish/release)
realclean remove all generated files
rebuild clean and (re-)build project
rebuild:all clean and fully reconstruct project distribution
retest clean and (re-)test project
reset:hard remove *all* generated files and reinstall dependencies
show:deps show package dependencies
test test package
test:code test package code
test:types test for type declaration errors (using `tsd`)
update update/prepare for distribution [alias: 'dist']
update:changelog update CHANGELOG (using `git changelog ...`)
update:dist update distribution content
verify fully (and verbosely) test package
Packaging & Publishing
Package
#=== * POSIX
# next VERSION in M.m.r (semver) format
VERSION=...
# update to VERSION in package.json
perl -i -E 'use open IO => q/:raw:utf8/} while(<>) { s/^(\s*\x22version\x22\s*:\s*)\x22(?:\d+(?:[.]\d+)*)?\x22/$1\x22$ENV{VERSION}\x22/ims; print }' package.json
git-changelog --next-tag "v${VERSION}" > CHANGELOG.mkd
# create/commit updates and distribution
npm run retest # optional; note: `[lint] WARN Missing commit tag ...` is ok at this point
git add package.json CHANGELOG.mkd
git commit -m "${VERSION}"
npm run clean && npm run update:dist
git add dist
git commit --amend --no-edit
# tag VERSION commit
git tag -f "v${VERSION}"
# (optional) save dependency locks
mkdir .deps-lock 2> /dev/null
cp package-lock.json yarn.lock .deps-lock/
#=== * WinOS
@rem ::# next VERSION in M.m.r (semver) format
set VERSION=...
@rem ::# update to VERSION in package.json
perl -i -E "use open IO => q/:raw:utf8/; while(<>) { s/^(\s*\x22version\x22\s*:\s*)\x22(?:\d+(?:[.]\d+)*)?\x22/$1\x22$ENV{VERSION}\x22/ims; print }" package.json
git-changelog --next-tag "v%VERSION%" > CHANGELOG.mkd
@rem ::# create/commit updates and distribution
npm run retest &@rem ::# optional; note: `[lint] WARN Missing commit tag ...` is ok at this point
git add package.json CHANGELOG.mkd
git commit -m "%VERSION%"
npm run clean && npm run update:dist
git add dist
git commit --amend --no-edit
@rem ::# tag VERSION commit
git tag -f "v%VERSION%"
@rem ::# (optional) save dependency locks
mkdir .deps-lock 2>NUL
copy package-lock.json .deps-lock/
copy yarn.lock .deps-lock/
Publish
# publish
# * optional (will be done in 'prePublishOnly' by `npm publish`)
npm run clean && npm run test && npm run dist && git-changelog > CHANGELOG.mkd
npm run _:v_tag:exists || echo "[lint] ERROR Missing version matching commit tag" # expect no output and exit code == 0
git diff-index --quiet HEAD || echo "[lint] ERROR uncommitted changes" # expect no output and exit code == 0
# *
npm publish # `npm publish --dry-run` will perform all prepublication actions and stop just before the actual publish push
# * if no ERRORs *
git push origin --tags
Contributions
Contributions are welcome.
Any pull requests should be based off of the default branch (master
). And, whenever possible, please include tests for any new code, ensuring that local (via npm test
) and remote CI testing passes.
By contributing to the project, you are agreeing to provide your contributions under the same license as the project itself.
Related
xdg-portable
… XDG Base Directory paths (cross-platform)env-paths
… inspiration for this module