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.
- package/.github/workflows/ci.yml +23 -0
- package/LICENSE +21 -0
- package/README.md +71 -0
- package/index.js +92 -0
- package/package.json +35 -0
- package/test.js +168 -0
|
@@ -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 [](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
|
+
});
|