os-user-dirs 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,23 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+ branches: [master]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ${{ matrix.os }}
12
+ strategy:
13
+ matrix:
14
+ os: [ubuntu-latest, macos-latest, windows-latest]
15
+ node-version: [20, 22]
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - uses: actions/setup-node@v4
19
+ with:
20
+ node-version: ${{ matrix.node-version }}
21
+ cache: npm
22
+ - run: npm install
23
+ - run: npm test
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019 Hiroshi Murakami
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # os-user-dirs [![CI](https://github.com/velocitylabo/os-user-dirs/actions/workflows/ci.yml/badge.svg)](https://github.com/velocitylabo/os-user-dirs/actions/workflows/ci.yml)
2
+
3
+ Get OS-specific user directories (Downloads, Desktop, Documents, Music, Pictures, Videos) with zero dependencies.
4
+
5
+ > **Note:** This package was previously published as [`os-downloads`](https://www.npmjs.com/package/os-downloads). The old package is deprecated — please use `os-user-dirs` instead.
6
+
7
+ ## Supported platforms
8
+
9
+ - Windows
10
+ - macOS
11
+ - Linux (with XDG user-dirs.dirs support)
12
+
13
+ ## Install
14
+
15
+ ```console
16
+ $ npm install os-user-dirs
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ```javascript
22
+ const { downloads, desktop, documents, music, pictures, videos, getPath } = require("os-user-dirs");
23
+
24
+ downloads();
25
+ //=> '/home/user/Downloads'
26
+
27
+ desktop();
28
+ //=> '/home/user/Desktop'
29
+
30
+ documents();
31
+ //=> '/home/user/Documents'
32
+
33
+ getPath("music");
34
+ //=> '/home/user/Music'
35
+ ```
36
+
37
+ ### Default export (backward compatibility)
38
+
39
+ ```javascript
40
+ const downloads = require("os-user-dirs");
41
+
42
+ downloads();
43
+ //=> '/home/user/Downloads'
44
+ ```
45
+
46
+ ## API
47
+
48
+ ### `downloads()`
49
+ Returns the path to the Downloads directory.
50
+
51
+ ### `desktop()`
52
+ Returns the path to the Desktop directory.
53
+
54
+ ### `documents()`
55
+ Returns the path to the Documents directory.
56
+
57
+ ### `music()`
58
+ Returns the path to the Music directory.
59
+
60
+ ### `pictures()`
61
+ Returns the path to the Pictures directory.
62
+
63
+ ### `videos()`
64
+ Returns the path to the Videos directory (Movies on macOS).
65
+
66
+ ### `getPath(name)`
67
+ Returns the path to the specified directory. Valid names: `desktop`, `downloads`, `documents`, `music`, `pictures`, `videos`.
68
+
69
+ ## License
70
+
71
+ MIT
package/index.js ADDED
@@ -0,0 +1,92 @@
1
+ const path = require("path");
2
+ const os = require("os");
3
+ const fs = require("fs");
4
+
5
+ const XDG_KEYS = {
6
+ desktop: "XDG_DESKTOP_DIR",
7
+ downloads: "XDG_DOWNLOAD_DIR",
8
+ documents: "XDG_DOCUMENTS_DIR",
9
+ music: "XDG_MUSIC_DIR",
10
+ pictures: "XDG_PICTURES_DIR",
11
+ videos: "XDG_VIDEOS_DIR",
12
+ };
13
+
14
+ const MACOS_DEFAULTS = {
15
+ desktop: "Desktop",
16
+ downloads: "Downloads",
17
+ documents: "Documents",
18
+ music: "Music",
19
+ pictures: "Pictures",
20
+ videos: "Movies",
21
+ };
22
+
23
+ const DEFAULT_DIRS = {
24
+ desktop: "Desktop",
25
+ downloads: "Downloads",
26
+ documents: "Documents",
27
+ music: "Music",
28
+ pictures: "Pictures",
29
+ videos: "Videos",
30
+ };
31
+
32
+ function getXDGUserDir(key, configPath) {
33
+ configPath = configPath || path.join(os.homedir(), ".config", "user-dirs.dirs");
34
+ try {
35
+ const content = fs.readFileSync(configPath, "utf8");
36
+ const regex = new RegExp('^' + key + '="(.+)"$', "m");
37
+ const match = content.match(regex);
38
+ if (match) {
39
+ return path.normalize(match[1].replace("$HOME", os.homedir()));
40
+ }
41
+ } catch (e) {
42
+ // file not found or unreadable, fall through to default
43
+ }
44
+ return null;
45
+ }
46
+
47
+ function resolve(name) {
48
+ const xdgKey = XDG_KEYS[name];
49
+ if (!xdgKey) {
50
+ throw new Error("Unknown directory: " + name + ". Valid names: " + Object.keys(XDG_KEYS).join(", "));
51
+ }
52
+
53
+ if (process.platform === "linux") {
54
+ const xdgDir = getXDGUserDir(xdgKey);
55
+ if (xdgDir) {
56
+ return xdgDir;
57
+ }
58
+ }
59
+
60
+ const dirName = process.platform === "darwin"
61
+ ? MACOS_DEFAULTS[name]
62
+ : DEFAULT_DIRS[name];
63
+
64
+ return path.join(os.homedir(), dirName);
65
+ }
66
+
67
+ function getPath(name) {
68
+ return resolve(name);
69
+ }
70
+
71
+ function desktop() { return resolve("desktop"); }
72
+ function downloads() { return resolve("downloads"); }
73
+ function documents() { return resolve("documents"); }
74
+ function music() { return resolve("music"); }
75
+ function pictures() { return resolve("pictures"); }
76
+ function videos() { return resolve("videos"); }
77
+
78
+ // Backward compatibility: require("os-user-dirs")() returns Downloads path
79
+ module.exports = downloads;
80
+ module.exports.getPath = getPath;
81
+ module.exports.desktop = desktop;
82
+ module.exports.downloads = downloads;
83
+ module.exports.documents = documents;
84
+ module.exports.music = music;
85
+ module.exports.pictures = pictures;
86
+ module.exports.videos = videos;
87
+ module.exports.getXDGUserDir = getXDGUserDir;
88
+
89
+ // Deprecated: kept for backward compatibility
90
+ module.exports.getXDGDownloadDir = function (configPath) {
91
+ return getXDGUserDir("XDG_DOWNLOAD_DIR", configPath);
92
+ };
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "os-user-dirs",
3
+ "version": "2.0.0",
4
+ "description": "Get OS-specific user directories (Downloads, Desktop, Documents, Music, Pictures, Videos) with zero dependencies.",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "mocha test.js"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/velocitylabo/os-user-dirs.git"
12
+ },
13
+ "keywords": [
14
+ "os",
15
+ "directories",
16
+ "downloads",
17
+ "desktop",
18
+ "documents",
19
+ "music",
20
+ "pictures",
21
+ "videos",
22
+ "xdg",
23
+ "user-dirs",
24
+ "platform-folders"
25
+ ],
26
+ "author": "piroz",
27
+ "license": "MIT",
28
+ "bugs": {
29
+ "url": "https://github.com/velocitylabo/os-user-dirs/issues"
30
+ },
31
+ "homepage": "https://github.com/velocitylabo/os-user-dirs#readme",
32
+ "devDependencies": {
33
+ "mocha": "^11.7.5"
34
+ }
35
+ }
package/test.js ADDED
@@ -0,0 +1,168 @@
1
+ const assert = require("assert");
2
+ const path = require("path");
3
+ const os = require("os");
4
+ const fs = require("fs");
5
+ const downloads = require("./");
6
+ const {
7
+ getXDGDownloadDir,
8
+ getXDGUserDir,
9
+ getPath,
10
+ desktop,
11
+ documents,
12
+ music,
13
+ pictures,
14
+ videos,
15
+ } = require("./");
16
+
17
+ describe("os-user-dirs", () => {
18
+ describe("downloads (default export / backward compatibility)", () => {
19
+ it("returns a path ending with Downloads", () => {
20
+ assert.ok(path.basename(downloads()).match(/downloads/i));
21
+ });
22
+
23
+ it("returns an absolute path", () => {
24
+ assert.ok(path.isAbsolute(downloads()));
25
+ });
26
+
27
+ it("path starts with home directory", () => {
28
+ assert.ok(downloads().startsWith(os.homedir()));
29
+ });
30
+ });
31
+
32
+ describe("named directory functions", () => {
33
+ const cases = [
34
+ { fn: desktop, name: "desktop" },
35
+ { fn: downloads, name: "downloads" },
36
+ { fn: documents, name: "documents" },
37
+ { fn: music, name: "music" },
38
+ { fn: pictures, name: "pictures" },
39
+ { fn: videos, name: "videos" },
40
+ ];
41
+
42
+ cases.forEach(({ fn, name }) => {
43
+ it(`${name}() returns an absolute path`, () => {
44
+ assert.ok(path.isAbsolute(fn()));
45
+ });
46
+
47
+ it(`${name}() starts with home directory`, () => {
48
+ assert.ok(fn().startsWith(os.homedir()));
49
+ });
50
+ });
51
+ });
52
+
53
+ describe("getPath", () => {
54
+ it("returns the same result as named functions", () => {
55
+ assert.strictEqual(getPath("desktop"), desktop());
56
+ assert.strictEqual(getPath("downloads"), downloads());
57
+ assert.strictEqual(getPath("documents"), documents());
58
+ assert.strictEqual(getPath("music"), music());
59
+ assert.strictEqual(getPath("pictures"), pictures());
60
+ assert.strictEqual(getPath("videos"), videos());
61
+ });
62
+
63
+ it("throws for unknown directory names", () => {
64
+ assert.throws(() => getPath("unknown"), /Unknown directory/);
65
+ });
66
+ });
67
+
68
+ describe("getXDGUserDir", () => {
69
+ const tmpDir = path.join(os.tmpdir(), "os-user-dirs-test");
70
+
71
+ beforeEach(() => {
72
+ fs.mkdirSync(tmpDir, { recursive: true });
73
+ });
74
+
75
+ afterEach(() => {
76
+ fs.rmSync(tmpDir, { recursive: true, force: true });
77
+ });
78
+
79
+ const xdgEntries = [
80
+ { key: "XDG_DESKTOP_DIR", value: "Desktop" },
81
+ { key: "XDG_DOWNLOAD_DIR", value: "Downloads" },
82
+ { key: "XDG_DOCUMENTS_DIR", value: "Documents" },
83
+ { key: "XDG_MUSIC_DIR", value: "Music" },
84
+ { key: "XDG_PICTURES_DIR", value: "Pictures" },
85
+ { key: "XDG_VIDEOS_DIR", value: "Videos" },
86
+ ];
87
+
88
+ xdgEntries.forEach(({ key, value }) => {
89
+ it(`parses ${key} with $HOME`, () => {
90
+ const configPath = path.join(tmpDir, "user-dirs.dirs");
91
+ fs.writeFileSync(configPath, `${key}="$HOME/${value}"\n`);
92
+ const result = getXDGUserDir(key, configPath);
93
+ assert.strictEqual(result, path.join(os.homedir(), value));
94
+ });
95
+ });
96
+
97
+ it("parses entry with absolute path", () => {
98
+ const configPath = path.join(tmpDir, "user-dirs.dirs");
99
+ fs.writeFileSync(configPath, 'XDG_DOWNLOAD_DIR="/custom/downloads"\n');
100
+ const result = getXDGUserDir("XDG_DOWNLOAD_DIR", configPath);
101
+ assert.strictEqual(result, path.normalize("/custom/downloads"));
102
+ });
103
+
104
+ it("parses correct entry among multiple entries", () => {
105
+ const configPath = path.join(tmpDir, "user-dirs.dirs");
106
+ const content = [
107
+ 'XDG_DESKTOP_DIR="$HOME/Desktop"',
108
+ 'XDG_DOWNLOAD_DIR="$HOME/MyDownloads"',
109
+ 'XDG_DOCUMENTS_DIR="$HOME/Documents"',
110
+ 'XDG_MUSIC_DIR="$HOME/Musik"',
111
+ 'XDG_PICTURES_DIR="$HOME/Bilder"',
112
+ 'XDG_VIDEOS_DIR="$HOME/Videos"',
113
+ ].join("\n");
114
+ fs.writeFileSync(configPath, content);
115
+
116
+ assert.strictEqual(
117
+ getXDGUserDir("XDG_DOWNLOAD_DIR", configPath),
118
+ path.join(os.homedir(), "MyDownloads")
119
+ );
120
+ assert.strictEqual(
121
+ getXDGUserDir("XDG_MUSIC_DIR", configPath),
122
+ path.join(os.homedir(), "Musik")
123
+ );
124
+ assert.strictEqual(
125
+ getXDGUserDir("XDG_PICTURES_DIR", configPath),
126
+ path.join(os.homedir(), "Bilder")
127
+ );
128
+ });
129
+
130
+ it("returns null when key is not present", () => {
131
+ const configPath = path.join(tmpDir, "user-dirs.dirs");
132
+ fs.writeFileSync(configPath, 'XDG_DESKTOP_DIR="$HOME/Desktop"\n');
133
+ const result = getXDGUserDir("XDG_DOWNLOAD_DIR", configPath);
134
+ assert.strictEqual(result, null);
135
+ });
136
+
137
+ it("returns null when config file does not exist", () => {
138
+ const configPath = path.join(tmpDir, "nonexistent");
139
+ const result = getXDGUserDir("XDG_DOWNLOAD_DIR", configPath);
140
+ assert.strictEqual(result, null);
141
+ });
142
+ });
143
+
144
+ describe("getXDGDownloadDir (backward compatibility)", () => {
145
+ const tmpDir = path.join(os.tmpdir(), "os-user-dirs-test");
146
+
147
+ beforeEach(() => {
148
+ fs.mkdirSync(tmpDir, { recursive: true });
149
+ });
150
+
151
+ afterEach(() => {
152
+ fs.rmSync(tmpDir, { recursive: true, force: true });
153
+ });
154
+
155
+ it("parses XDG_DOWNLOAD_DIR with $HOME", () => {
156
+ const configPath = path.join(tmpDir, "user-dirs.dirs");
157
+ fs.writeFileSync(configPath, 'XDG_DOWNLOAD_DIR="$HOME/Downloads"\n');
158
+ const result = getXDGDownloadDir(configPath);
159
+ assert.strictEqual(result, path.join(os.homedir(), "Downloads"));
160
+ });
161
+
162
+ it("returns null when config file does not exist", () => {
163
+ const configPath = path.join(tmpDir, "nonexistent");
164
+ const result = getXDGDownloadDir(configPath);
165
+ assert.strictEqual(result, null);
166
+ });
167
+ });
168
+ });