platformdirs 4.3.6 → 4.3.8-rc1

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/src/macos.js ADDED
@@ -0,0 +1,199 @@
1
+ /**
2
+ * @module
3
+ * macOS.
4
+ */
5
+
6
+ import * as os from "node:os";
7
+ import * as path from "node:path";
8
+ import process from "node:process";
9
+ import { PlatformDirsABC } from "./api.js";
10
+
11
+ /**
12
+ * Platform directories for the macOS operating system.
13
+ *
14
+ * Follows the guidance from [Apple documentation](https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/MacOSXDirectories/MacOSXDirectories.html). Makes use of the {@link PlatformDirsABC.appname}, {@link PlatformDirsABC.version}, {@link PlatformDirsABC.ensureExists}.
15
+ */
16
+ export class MacOS extends PlatformDirsABC {
17
+ /**
18
+ * @return {string} data directory tied to the user, e.g. `~/Library/Application Support/$appname/$version`
19
+ * @override
20
+ */
21
+ get userDataDir() {
22
+ return this._appendAppNameAndVersion(
23
+ path.join(os.homedir(), "Library/Application Support"),
24
+ );
25
+ }
26
+
27
+ /**
28
+ * @return {string} data directory shared by users, e.g. `/Library/Application
29
+ * Support/$appname/$version`. If we're using a Node.js, Deno, or Bun binary
30
+ * managed by [Homebrew](https://brew.sh), the directory will be under the
31
+ * Homebrew prefix, e.g. `/opt/homebrew/share/$appname/$version`. If
32
+ * {@link PlatformDirsABC.multipath} is enabled, and we're in Homebrew, the
33
+ * response is a multi-path string separated by ":", e.g.
34
+ * `/opt/homebrew/share/$appname/$version:/Library/Application
35
+ * Support/$appname/$version`.
36
+ * @override
37
+ */
38
+ get siteDataDir() {
39
+ const isHomebrew = process.execPath.startsWith("/opt/homebrew");
40
+ const pathList = isHomebrew
41
+ ? [this._appendAppNameAndVersion("/opt/homebrew/share")]
42
+ : [];
43
+ pathList.push(
44
+ this._appendAppNameAndVersion("/Library/Application Support"),
45
+ );
46
+ if (this.multipath) {
47
+ return pathList.join(path.delimiter);
48
+ }
49
+ return pathList[0];
50
+ }
51
+
52
+ /**
53
+ * @return {string} data path shared by users. Only return the first item, even if
54
+ * `multipath` is enabled is set to `true`.
55
+ * @override
56
+ */
57
+ get siteDataPath() {
58
+ return this._firstItemAsPathIfMultipath(this.siteDataDir);
59
+ }
60
+
61
+ /**
62
+ * @return {string} config directory tied to the user, same as `userDataDir`
63
+ * @override
64
+ */
65
+ get userConfigDir() {
66
+ return this.userDataDir;
67
+ }
68
+
69
+ /**
70
+ * @return {string} config directory shared by users, same as `siteDataDir`
71
+ * @override
72
+ */
73
+ get siteConfigDir() {
74
+ return this.siteDataDir;
75
+ }
76
+
77
+ /**
78
+ * @return {string} cache directory tied to the user, e.g. `~/Library/Caches/$appname/$version`
79
+ * @override
80
+ */
81
+ get userCacheDir() {
82
+ return this._appendAppNameAndVersion(
83
+ path.join(os.homedir(), "Library/Caches"),
84
+ );
85
+ }
86
+
87
+ /**
88
+ * @return {string} cache directory shared by users, e.g. `/Library/Caches/$appname/$version`.
89
+ * If we're using a Node.js, Deno, or Bun binary managed by [Homebrew](https://brew.sh),
90
+ * the directory will be under the Homebrew prefix, e.g. `/opt/homebrew/var/cache/$appname/$version`.
91
+ * If {@link PlatformDirsABC.multipath} is enabled, and we're in Homebrew, the response is a multi-path string separated by ":", e.g.
92
+ * `/opt/homebrew/var/cache/$appname/$version:/Library/Caches/$appname/$version`.
93
+ * @override
94
+ */
95
+ get siteCacheDir() {
96
+ const isHomebrew = process.execPath.startsWith("/opt/homebrew");
97
+ const pathList = isHomebrew
98
+ ? [this._appendAppNameAndVersion("/opt/homebrew/var/cache")]
99
+ : [];
100
+ pathList.push(this._appendAppNameAndVersion("/Library/Caches"));
101
+ if (this.multipath) {
102
+ return pathList.join(path.delimiter);
103
+ }
104
+ return pathList[0];
105
+ }
106
+
107
+ /**
108
+ * @return {string} cache path shared by users. Only return the first item, even if
109
+ * `multipath` is enabled is set to `true`.
110
+ * @override
111
+ */
112
+ get siteCachePath() {
113
+ return this._firstItemAsPathIfMultipath(this.siteCacheDir);
114
+ }
115
+
116
+ /**
117
+ * @return {string} state directory tied to the user, e.g. `~/Library/Application Support/$appname/$version`
118
+ * @override
119
+ */
120
+ get userStateDir() {
121
+ return this.userDataDir;
122
+ }
123
+
124
+ /**
125
+ * @return {string} log directory tied to the user, e.g. `~/Library/Logs/$appname/$version`
126
+ * @override
127
+ */
128
+ get userLogDir() {
129
+ return this._appendAppNameAndVersion(
130
+ path.join(os.homedir(), "Library/Logs"),
131
+ );
132
+ }
133
+
134
+ /**
135
+ * @return {string} documents directory tied to the user, e.g. `~/Documents`
136
+ * @override
137
+ */
138
+ get userDocumentsDir() {
139
+ return path.join(os.homedir(), "Documents");
140
+ }
141
+
142
+ /**
143
+ * @return {string} downloads directory tied to the user, e.g. `~/Downloads`
144
+ * @override
145
+ */
146
+ get userDownloadsDir() {
147
+ return path.join(os.homedir(), "Downloads");
148
+ }
149
+
150
+ /**
151
+ * @return {string} pictures directory tied to the user, e.g. `~/Pictures`
152
+ * @override
153
+ */
154
+ get userPicturesDir() {
155
+ return path.join(os.homedir(), "Pictures");
156
+ }
157
+
158
+ /**
159
+ * @return {string} videos directory tied to the user, e.g. `~/Movies`
160
+ * @override
161
+ */
162
+ get userVideosDir() {
163
+ return path.join(os.homedir(), "Movies");
164
+ }
165
+
166
+ /**
167
+ * @return {string} music directory tied to the user, e.g. `~/Music`
168
+ * @override
169
+ */
170
+ get userMusicDir() {
171
+ return path.join(os.homedir(), "Music");
172
+ }
173
+
174
+ /**
175
+ * @return {string} desktop directory tied to the user, e.g. `~/Desktop`
176
+ * @override
177
+ */
178
+ get userDesktopDir() {
179
+ return path.join(os.homedir(), "Desktop");
180
+ }
181
+
182
+ /**
183
+ * @return {string} runtime directory tied to the user, e.g. `~/Library/Caches/TemporaryItems`
184
+ * @override
185
+ */
186
+ get userRuntimeDir() {
187
+ return this._appendAppNameAndVersion(
188
+ path.join(os.homedir(), "Library/Caches/TemporaryItems"),
189
+ );
190
+ }
191
+
192
+ /**
193
+ * @return {string} runtime directory shared by users, same as `userRuntimeDir`
194
+ * @override
195
+ */
196
+ get siteRuntimeDir() {
197
+ return this.userRuntimeDir;
198
+ }
199
+ }
package/src/main.js ADDED
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+ import { PlatformDirs, version } from "./index.js";
3
+
4
+ /** @type {Record<string, keyof PlatformDirs>} */
5
+ const props = {
6
+ user_data_dir: "userDataDir",
7
+ user_config_dir: "userConfigDir",
8
+ user_cache_dir: "userCacheDir",
9
+ user_state_dir: "userStateDir",
10
+ user_log_dir: "userLogDir",
11
+ user_documents_dir: "userDocumentsDir",
12
+ user_downloads_dir: "userDownloadsDir",
13
+ user_pictures_dir: "userPicturesDir",
14
+ user_videos_dir: "userVideosDir",
15
+ user_music_dir: "userMusicDir",
16
+ user_runtime_dir: "userRuntimeDir",
17
+ site_data_dir: "siteDataDir",
18
+ site_config_dir: "siteConfigDir",
19
+ site_cache_dir: "siteCacheDir",
20
+ site_runtime_dir: "siteRuntimeDir",
21
+ };
22
+
23
+ const appName = "MyApp";
24
+ const appAuthor = "MyCompany";
25
+
26
+ console.log(`-- platformdirs ${version} --`);
27
+
28
+ console.log("-- app dirs (with optional 'version')");
29
+ let dirs = new PlatformDirs(appName, appAuthor, "1.0");
30
+ for (const [label, key] of Object.entries(props)) {
31
+ console.log(`${label}: ${dirs[key]}`);
32
+ }
33
+
34
+ console.log("\n-- app dirs (without optional 'version')");
35
+ dirs = new PlatformDirs(appName, appAuthor);
36
+ for (const [label, key] of Object.entries(props)) {
37
+ console.log(`${label}: ${dirs[key]}`);
38
+ }
39
+
40
+ console.log("\n-- app dirs (without optional 'appauthor')");
41
+ dirs = new PlatformDirs(appName);
42
+ for (const [label, key] of Object.entries(props)) {
43
+ console.log(`${label}: ${dirs[key]}`);
44
+ }
45
+
46
+ console.log("\n-- app dirs (with disabled 'appauthor')");
47
+ dirs = new PlatformDirs(appName, false);
48
+ for (const [label, key] of Object.entries(props)) {
49
+ console.log(`${label}: ${dirs[key]}`);
50
+ }
package/src/unix.js ADDED
@@ -0,0 +1,350 @@
1
+ /**
2
+ * @module
3
+ * Unix.
4
+ */
5
+
6
+ import * as fs from "node:fs";
7
+ import * as os from "node:os";
8
+ import * as path from "node:path";
9
+ import process from "node:process";
10
+ import { PlatformDirsABC } from "./api.js";
11
+ import { ConfigParser } from "./configparser.js";
12
+
13
+ /** @return {number} */
14
+ function getuid() {
15
+ if (!process.getuid) {
16
+ throw new Error("should only be used on Unix");
17
+ }
18
+ return process.getuid();
19
+ }
20
+
21
+ /**
22
+ * On Unix/Linux, we follow the [XDG Basedir
23
+ * Spec](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html).
24
+ *
25
+ * The spec allows overriding directories via environment variables. The
26
+ * examples shown are the default values, alongside the name of the environment
27
+ * variable that overrides them. Makes use of the
28
+ * {@link PlatformDirsABC.appname}, {@link PlatformDirsABC.version},
29
+ * {@link PlatformDirsABC.multipath}, {@link PlatformDirsABC.opinion},
30
+ * {@link PlatformDirsABC.ensureExists}.
31
+ */
32
+ export class Unix extends PlatformDirsABC {
33
+ /**
34
+ * @return {string} data directory tied to the user, e.g. `~/.local/share/$appname/$version` or `$XDG_DATA_HOME/$appname/$version`
35
+ * @override
36
+ */
37
+ get userDataDir() {
38
+ let path2 = process.env.XDG_DATA_HOME ?? "";
39
+ if (!path2.trim()) {
40
+ path2 = path.join(os.homedir(), ".local/share");
41
+ }
42
+ return this._appendAppNameAndVersion(path2);
43
+ }
44
+
45
+ /**
46
+ * @return {string[]}
47
+ * @protected @ignore @internal
48
+ */
49
+ get _siteDataDirs() {
50
+ let path2 = process.env.XDG_DATA_DIRS ?? "";
51
+ if (!path2.trim()) {
52
+ path2 = `/usr/local/share${path.delimiter}/usr/share`;
53
+ }
54
+ return path2
55
+ .split(path.delimiter)
56
+ .map((p) => this._appendAppNameAndVersion(p));
57
+ }
58
+
59
+ /**
60
+ * @return {string} data directories shared by users (if
61
+ * {@link PlatformDirsABC.multipath} is enabled and `XDG_DATA_DIRS` is and a
62
+ * multi path the response is also a multi path separated by the OS path
63
+ * separator), e.g.
64
+ * `/usr/local/share/$appname/$version:/usr/share/$appname/$version`
65
+ * @override
66
+ */
67
+ get siteDataDir() {
68
+ const dirs = this._siteDataDirs;
69
+ if (!this.multipath) {
70
+ return dirs[0];
71
+ }
72
+ return dirs.join(path.delimiter);
73
+ }
74
+
75
+ /**
76
+ * @return {string} config directory tied to the user, e.g. `~/.config/$appname/$version` or `$XDG_CONFIG_HOME/$appname/$version`
77
+ * @override
78
+ */
79
+ get userConfigDir() {
80
+ let path2 = process.env.XDG_CONFIG_HOME ?? "";
81
+ if (!path2.trim()) {
82
+ path2 = path.join(os.homedir(), ".config");
83
+ }
84
+ return this._appendAppNameAndVersion(path2);
85
+ }
86
+
87
+ /**
88
+ * @return {string[]}
89
+ * @ignore @internal
90
+ */
91
+ get _siteConfigDirs() {
92
+ let path2 = process.env.XDG_CONFIG_DIRS ?? "";
93
+ if (!path2.trim()) {
94
+ path2 = "/etc/xdg";
95
+ }
96
+ return path2
97
+ .split(path.delimiter)
98
+ .map((p) => this._appendAppNameAndVersion(p));
99
+ }
100
+
101
+ /**
102
+ * @return {string} config directories shared by users (if
103
+ * {@link PlatformDirsABC.multipath} is enabled and `XDG_CONFIG_DIRS` is and
104
+ * a multi path the response is also a multi path separated by the OS path
105
+ * separator), e.g. `/etc/xdg/$appname/$version`
106
+ * @override
107
+ */
108
+ get siteConfigDir() {
109
+ const dirs = this._siteConfigDirs;
110
+ if (!this.multipath) {
111
+ return dirs[0];
112
+ }
113
+ return dirs.join(path.delimiter);
114
+ }
115
+
116
+ /**
117
+ * @return {string} cache directory tied to the user, e.g. `~/.cache/$appname/$version` or `$XDG_CACHE_HOME/$appname/$version`
118
+ * @override
119
+ */
120
+ get userCacheDir() {
121
+ let path2 = process.env.XDG_CACHE_HOME ?? "";
122
+ if (!path2.trim()) {
123
+ path2 = path.join(os.homedir(), ".cache");
124
+ }
125
+ return this._appendAppNameAndVersion(path2);
126
+ }
127
+
128
+ /**
129
+ * @return {string} cache directory shared by users, e.g. `/var/cache/$appname/$version`
130
+ * @override
131
+ */
132
+ get siteCacheDir() {
133
+ return this._appendAppNameAndVersion("/var/cache");
134
+ }
135
+
136
+ /**
137
+ * @return {string} state directory tied to the user, e.g. `~/.local/state/$appname/$version` or `$XDG_STATE_HOME/$appname/$version`
138
+ * @override
139
+ */
140
+ get userStateDir() {
141
+ let path2 = process.env.XDG_STATE_HOME ?? "";
142
+ if (!path2.trim()) {
143
+ path2 = path.join(os.homedir(), ".local/state");
144
+ }
145
+ return this._appendAppNameAndVersion(path2);
146
+ }
147
+
148
+ /**
149
+ * @return {string} log directory tied to the user, same as `userCacheDir` if not opinionated else `log` in it.
150
+ * @override
151
+ */
152
+ get userLogDir() {
153
+ let path2 = this.userStateDir;
154
+ if (this.opinion) {
155
+ path2 = path.join(path2, "log");
156
+ this._optionallyCreateDirectory(path2);
157
+ }
158
+ return path2;
159
+ }
160
+
161
+ /**
162
+ * @return {string} documents directory tied to the user, e.g. `~/Documents`
163
+ * @override
164
+ */
165
+ get userDocumentsDir() {
166
+ return getUserMediaDir("XDG_DOCUMENTS_DIR", "~/Documents");
167
+ }
168
+
169
+ /**
170
+ * @return {string} downloads directory tied to the user, e.g. `~/Downloads`
171
+ * @override
172
+ */
173
+ get userDownloadsDir() {
174
+ return getUserMediaDir("XDG_DOWNLOAD_DIR", "~/Downloads");
175
+ }
176
+
177
+ /**
178
+ * @return {string} pictures directory tied to the user, e.g. `~/Pictures`
179
+ * @override
180
+ */
181
+ get userPicturesDir() {
182
+ return getUserMediaDir("XDG_PICTURES_DIR", "~/Pictures");
183
+ }
184
+
185
+ /**
186
+ * @return {string} videos directory tied to the user, e.g. `~/Videos`
187
+ * @override
188
+ */
189
+ get userVideosDir() {
190
+ return getUserMediaDir("XDG_VIDEOS_DIR", "~/Videos");
191
+ }
192
+
193
+ /**
194
+ * @return {string} music directory tied to the user, e.g. `~/Music`
195
+ * @override
196
+ */
197
+ get userMusicDir() {
198
+ return getUserMediaDir("XDG_MUSIC_DIR", "~/Music");
199
+ }
200
+
201
+ /**
202
+ * @return {string} desktop directory tied to the user, e.g. `~/Desktop`
203
+ * @override
204
+ */
205
+ get userDesktopDir() {
206
+ return getUserMediaDir("XDG_DESKTOP_DIR", "~/Desktop");
207
+ }
208
+
209
+ /**
210
+ * @return {string} runtime directory tied to the user, e.g. `/run/user/$(id -u)/$appname/$version` or `$XDG_RUNTIME_DIR/$appname/$version`.
211
+ *
212
+ * For FreeBSD/OpenBSD/NetBSD, it would return `/var/run/user/$(id -u)/$appname/$version` if it exists, otherwise `/tmp/runtime-$(id -u)/$appname/$version`, if `XDG_RUNTIME_DIR` is not set.
213
+ * @override
214
+ */
215
+ get userRuntimeDir() {
216
+ let path2 = process.env.XDG_RUNTIME_DIR ?? "";
217
+ if (!path2.trim()) {
218
+ if (
219
+ process.platform === "freebsd" ||
220
+ process.platform === "netbsd" ||
221
+ process.platform === "openbsd"
222
+ ) {
223
+ path2 = `/var/run/user/${getuid()}`;
224
+ if (!fs.existsSync(path2)) {
225
+ path2 = `/tmp/runtime-${getuid()}`;
226
+ }
227
+ } else {
228
+ path2 = `/run/user/${getuid()}`;
229
+ }
230
+ }
231
+ return this._appendAppNameAndVersion(path2);
232
+ }
233
+
234
+ /**
235
+ * @return {string} runtime directory shared by users, e.g. `/run/$appname/$version` or `$XDG_RUNTIME_DIR/$appname/$version`.
236
+ *
237
+ * Note that this behaves almost exactly like `userRuntimeDir` if `XDG_RUNTIME_DIR` is set, but will fall back to paths associated to the root user isntead of a regular logged-in user if it's not set.
238
+ *
239
+ * If you wish to ensure that a logged-in user path is returned e.g. `/run/user/0`, use `userRuntimeDir` instead.
240
+ *
241
+ * For FreeBSD/OpenBSD/NetBSD, it would return `/var/run/$appname/$version` if `XDG_RUNTIME_DIR` is not set.
242
+ * @override
243
+ */
244
+ get siteRuntimeDir() {
245
+ let path2 = process.env.XDG_RUNTIME_DIR ?? "";
246
+ if (!path2.trim()) {
247
+ if (
248
+ process.platform === "freebsd" ||
249
+ process.platform === "netbsd" ||
250
+ process.platform === "openbsd"
251
+ ) {
252
+ path2 = "/var/run";
253
+ } else {
254
+ path2 = "/run";
255
+ }
256
+ }
257
+ return this._appendAppNameAndVersion(path2);
258
+ }
259
+
260
+ /**
261
+ * @return {string} data path shared by users. Only return the first item, even if `multipath` is set to `true`.
262
+ * @override
263
+ */
264
+ get siteDataPath() {
265
+ return this._firstItemAsPathIfMultipath(this.siteDataDir);
266
+ }
267
+
268
+ /**
269
+ * @return {string} config path shared by users. Only return the first item, even if `multipath` is set to `true`.
270
+ * @override
271
+ */
272
+ get siteConfigPath() {
273
+ return this._firstItemAsPathIfMultipath(this.siteConfigDir);
274
+ }
275
+
276
+ /**
277
+ * @return {string} cache path shared by users. Only return the first item, even if `multipath` is set to `true`.
278
+ * @override
279
+ */
280
+ get siteCachePath() {
281
+ return this._firstItemAsPathIfMultipath(this.siteCacheDir);
282
+ }
283
+
284
+ /**
285
+ * @yields all user and site configuation directories
286
+ * @return {Generator<string>}
287
+ * @override
288
+ */
289
+ *iterConfigDirs() {
290
+ yield this.userConfigDir;
291
+ yield* this._siteConfigDirs;
292
+ }
293
+
294
+ /**
295
+ * @yields all user and site data directories
296
+ * @return {Generator<string>}
297
+ * @override
298
+ */
299
+ *iterDataDirs() {
300
+ yield this.userDataDir;
301
+ yield* this._siteDataDirs;
302
+ }
303
+ }
304
+
305
+ /**
306
+ * @param {string} envVar
307
+ * @param {string} fallbackTildePath
308
+ * @returns {string}
309
+ */
310
+ function getUserMediaDir(envVar, fallbackTildePath) {
311
+ let mediaDir = getUserDirsFolder(envVar);
312
+ if (mediaDir === undefined) {
313
+ mediaDir = (process.env[envVar] ?? "").trim();
314
+ if (!mediaDir) {
315
+ mediaDir = fallbackTildePath.replace(/^~/, os.homedir());
316
+ }
317
+ }
318
+ return mediaDir;
319
+ }
320
+
321
+ /**
322
+ * Return directory from user-dirs.dirs config file.
323
+ *
324
+ * See https://freedesktop.org/wiki/Software/xdg-user-dirs/.
325
+ *
326
+ * ⚠️ This function uses **synchronous FS operations**.
327
+ *
328
+ * @param {string} key
329
+ * @returns {string | undefined}
330
+ */
331
+ function getUserDirsFolder(key) {
332
+ const userDirsConfigPath = path.join(new Unix().userConfigDir, "user-dirs.dirs");
333
+ if (fs.existsSync(userDirsConfigPath)) {
334
+ // This works for now.
335
+ const parser = new ConfigParser()
336
+
337
+ const read = fs.readFileSync(userDirsConfigPath, "utf-8")
338
+ parser.readString(`[top]\n${read}`)
339
+
340
+ const top = parser.get("top");
341
+ if (!top || !(Object.hasOwn(top, key))) {
342
+ return undefined
343
+ }
344
+
345
+ const path2 = top[key].replace(/(^"|"$)/g, "")
346
+ return path2.replace("$HOME", os.homedir())
347
+ }
348
+
349
+ return undefined;
350
+ }
package/src/version.js ADDED
@@ -0,0 +1,16 @@
1
+ // https://github.com/nodejs/node/issues/51347
2
+ import packageJSON from "../package.json" with { type: "json" };
3
+
4
+ /**
5
+ * @param {unknown} error
6
+ * @return {never}
7
+ */
8
+ function throwExpression(error) {
9
+ throw error;
10
+ }
11
+
12
+ export const version = packageJSON.version;
13
+ const match =
14
+ packageJSON.version.match(/^(\d+)\.(\d+)\.(\d+)/) ??
15
+ throwExpression(new Error("unreachable"));
16
+ export const versionTuple = [+match[1], +match[2], +match[3]];