pathmix 1.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.
Files changed (4) hide show
  1. package/README.md +161 -0
  2. package/index.cjs +121 -0
  3. package/index.js +118 -0
  4. package/package.json +39 -0
package/README.md ADDED
@@ -0,0 +1,161 @@
1
+ # pathmix
2
+
3
+ Unified path utilities for Node.js that work the same way in both ESM and CommonJS.
4
+
5
+ ## The problem
6
+
7
+ Whenever you need the path of the current file or its directory, Node.js requires different boilerplate depending on the module system you are using.
8
+
9
+ In CommonJS, `__dirname` and `__filename` are available as globals with no setup:
10
+
11
+ ```js
12
+ const path = require('node:path');
13
+ const config = path.join(__dirname, 'config.json');
14
+ ```
15
+
16
+ In ESM they do not exist. You have to reconstruct them every time:
17
+
18
+ ```js
19
+ import { fileURLToPath } from 'node:url';
20
+ import { dirname, join } from 'node:path';
21
+
22
+ const __filename = fileURLToPath(import.meta.url);
23
+ const __dirname = dirname(__filename);
24
+
25
+ const config = join(__dirname, 'config.json');
26
+ ```
27
+
28
+ That boilerplate is easy to forget, easy to get slightly wrong, and has to be repeated in every file that needs it. If you are working across a mixed codebase — or publishing a package that must support both module systems — you end up maintaining two separate conventions.
29
+
30
+ pathmix collapses all of that into a single, consistent API.
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ npm install pathmix
36
+ ```
37
+
38
+ ## Usage
39
+
40
+ ### ESM
41
+
42
+ ```js
43
+ import PathMix from 'pathmix';
44
+
45
+ // Directory of the current file (no import.meta.url needed)
46
+ PathMix.dir() // /home/user/project/src
47
+ PathMix.dir('config.json') // /home/user/project/src/config.json
48
+
49
+ // Current file path
50
+ PathMix.file() // /home/user/project/src/index.js
51
+
52
+ // Or pass import.meta.url explicitly when auto-detection is not desired
53
+ PathMix.dir(import.meta.url, 'assets') // /home/user/project/src/assets
54
+ ```
55
+
56
+ ### CommonJS
57
+
58
+ ```js
59
+ const PathMix = require('pathmix');
60
+
61
+ // Identical API — no boilerplate required
62
+ PathMix.dir() // /home/user/project/src
63
+ PathMix.dir('config.json') // /home/user/project/src/config.json
64
+ PathMix.file() // /home/user/project/src/index.js
65
+ ```
66
+
67
+ ## API
68
+
69
+ ### File-relative paths
70
+
71
+ #### `PathMix.dir(...segments)`
72
+
73
+ Returns the directory of the calling file. Optionally joins one or more path segments onto it.
74
+
75
+ ```js
76
+ PathMix.dir() // /project/src
77
+ PathMix.dir('data', 'seed.json') // /project/src/data/seed.json
78
+ PathMix.dir(import.meta.url) // same, explicit (ESM only)
79
+ ```
80
+
81
+ #### `PathMix.file(...args)`
82
+
83
+ Returns the absolute path of the calling file.
84
+
85
+ ```js
86
+ PathMix.file() // /project/src/utils.js
87
+ PathMix.file(import.meta.url) // same, explicit (ESM only)
88
+ ```
89
+
90
+ #### `PathMix.__dirname()` / `PathMix.__filename()`
91
+
92
+ Drop-in equivalents for the CommonJS globals, available in both module systems.
93
+
94
+ ```js
95
+ PathMix.__dirname() // /project/src
96
+ PathMix.__filename() // /project/src/utils.js
97
+ ```
98
+
99
+ ### Well-known base paths
100
+
101
+ #### `PathMix.home(...segments)`
102
+
103
+ ```js
104
+ PathMix.home() // /home/user
105
+ PathMix.home('.ssh') // /home/user/.ssh
106
+ PathMix.home('.config/app') // /home/user/.config/app
107
+ ```
108
+
109
+ #### `PathMix.cwd(...segments)`
110
+
111
+ ```js
112
+ PathMix.cwd() // /project
113
+ PathMix.cwd('src', 'index') // /project/src/index
114
+ ```
115
+
116
+ #### `PathMix.tmp(...segments)`
117
+
118
+ ```js
119
+ PathMix.tmp() // /tmp
120
+ PathMix.tmp('session-1') // /tmp/session-1
121
+ ```
122
+
123
+ ### Token expansion
124
+
125
+ #### `PathMix.resolve(...segments)`
126
+
127
+ Like `path.resolve`, but also expands `~` and `$HOME` to the user home directory.
128
+
129
+ ```js
130
+ PathMix.resolve('~/.ssh/config') // /home/user/.ssh/config
131
+ PathMix.resolve('$HOME/.config/app') // /home/user/.config/app
132
+ ```
133
+
134
+ ### Standard path utilities
135
+
136
+ All core `node:path` methods are re-exported on the same class, so you can import one thing and have everything:
137
+
138
+ ```js
139
+ PathMix.join('a', 'b', 'c') // a/b/c
140
+ PathMix.basename('/a/b.js') // b.js
141
+ PathMix.extname('app.ts') // .ts
142
+ PathMix.dirname('/a/b/c') // /a/b
143
+ PathMix.normalize('a//b/../c') // a/c
144
+ PathMix.relative('/a', '/a/b') // b
145
+ PathMix.isAbsolute('/foo') // true
146
+ PathMix.parse('/a/b.js') // { root, dir, base, ext, name }
147
+ PathMix.format({ dir: '/a', base: 'b.js' }) // /a/b.js
148
+ PathMix.sep // '/' on Unix, '\\' on Windows
149
+ ```
150
+
151
+ ## AI Skill
152
+
153
+ You can add PathMix as a skill for AI agentic development:
154
+
155
+ ```bash
156
+ npx skills add https://github.com/clasen/PathMix --skill pathmix
157
+ ```
158
+
159
+ ## Requirements
160
+
161
+ Node.js >= 16
package/index.cjs ADDED
@@ -0,0 +1,121 @@
1
+ 'use strict';
2
+
3
+ const { join, resolve, dirname: pathDirname, basename, normalize, sep, parse, format, relative, isAbsolute, extname } = require('node:path');
4
+ const { fileURLToPath } = require('node:url');
5
+ const { homedir, tmpdir } = require('node:os');
6
+
7
+ class PathMix {
8
+
9
+ static #getCallerFile(methodRef) {
10
+ const orig = Error.prepareStackTrace;
11
+ Error.prepareStackTrace = (_, stack) => stack;
12
+ const err = new Error();
13
+ Error.captureStackTrace(err, methodRef);
14
+ const stack = err.stack;
15
+ Error.prepareStackTrace = orig;
16
+
17
+ const fileName = stack[0]?.getFileName();
18
+ if (!fileName) return null;
19
+
20
+ if (fileName.startsWith('file://')) return fileURLToPath(fileName);
21
+ return fileName;
22
+ }
23
+
24
+ static #isFileUrl(value) {
25
+ return typeof value === 'string' && value.startsWith('file://');
26
+ }
27
+
28
+ static #expandTokens(segment) {
29
+ if (typeof segment !== 'string') return segment;
30
+ if (segment.startsWith('$HOME')) return segment.replace(/^\$HOME/, homedir());
31
+ if (segment.startsWith('~')) return segment.replace(/^~/, homedir());
32
+ return segment;
33
+ }
34
+
35
+ // ── Base path methods with integrated join ──
36
+
37
+ static home(...segments) {
38
+ return segments.length ? join(homedir(), ...segments) : homedir();
39
+ }
40
+
41
+ static cwd(...segments) {
42
+ return segments.length ? join(process.cwd(), ...segments) : process.cwd();
43
+ }
44
+
45
+ static dir(...args) {
46
+ let base;
47
+ let segments;
48
+
49
+ if (args.length > 0 && PathMix.#isFileUrl(args[0])) {
50
+ base = pathDirname(fileURLToPath(args[0]));
51
+ segments = args.slice(1);
52
+ } else {
53
+ const callerFile = PathMix.#getCallerFile(PathMix.dir);
54
+ if (!callerFile) throw new Error('PathMix.dir(): could not detect caller file. Pass import.meta.url explicitly.');
55
+ base = pathDirname(callerFile);
56
+ segments = args;
57
+ }
58
+
59
+ return segments.length ? join(base, ...segments) : base;
60
+ }
61
+
62
+ static file(...args) {
63
+ if (args.length > 0 && PathMix.#isFileUrl(args[0])) {
64
+ return fileURLToPath(args[0]);
65
+ }
66
+
67
+ const callerFile = PathMix.#getCallerFile(PathMix.file);
68
+ if (!callerFile) throw new Error('PathMix.file(): could not detect caller file. Pass import.meta.url explicitly.');
69
+ return callerFile;
70
+ }
71
+
72
+ static tmp(...segments) {
73
+ return segments.length ? join(tmpdir(), ...segments) : tmpdir();
74
+ }
75
+
76
+ // ── Token expansion ──
77
+
78
+ static resolve(...segments) {
79
+ const expanded = segments.map(s => PathMix.#expandTokens(s));
80
+ return resolve(...expanded);
81
+ }
82
+
83
+ // ── CJS compatibility helpers ──
84
+
85
+ static __dirname(...args) {
86
+ if (args.length > 0 && PathMix.#isFileUrl(args[0])) {
87
+ return pathDirname(fileURLToPath(args[0]));
88
+ }
89
+
90
+ const callerFile = PathMix.#getCallerFile(PathMix.__dirname);
91
+ if (!callerFile) throw new Error('PathMix.__dirname(): could not detect caller file. Pass import.meta.url explicitly.');
92
+ return pathDirname(callerFile);
93
+ }
94
+
95
+ static __filename(...args) {
96
+ if (args.length > 0 && PathMix.#isFileUrl(args[0])) {
97
+ return fileURLToPath(args[0]);
98
+ }
99
+
100
+ const callerFile = PathMix.#getCallerFile(PathMix.__filename);
101
+ if (!callerFile) throw new Error('PathMix.__filename(): could not detect caller file. Pass import.meta.url explicitly.');
102
+ return callerFile;
103
+ }
104
+
105
+ // ── Re-exported path utilities ──
106
+
107
+ static join(...segments) { return join(...segments); }
108
+ static normalize(p) { return normalize(p); }
109
+ static basename(p, ext) { return basename(p, ext); }
110
+ static dirname(p) { return pathDirname(p); }
111
+ static extname(p) { return extname(p); }
112
+ static relative(from, to) { return relative(from, to); }
113
+ static isAbsolute(p) { return isAbsolute(p); }
114
+ static parse(p) { return parse(p); }
115
+ static format(obj) { return format(obj); }
116
+ static get sep() { return sep; }
117
+ }
118
+
119
+ module.exports = PathMix;
120
+ module.exports.PathMix = PathMix;
121
+ module.exports.default = PathMix;
package/index.js ADDED
@@ -0,0 +1,118 @@
1
+ import { join, resolve, dirname as pathDirname, basename, normalize, sep, parse, format, relative, isAbsolute, extname } from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { homedir, tmpdir } from 'node:os';
4
+
5
+ class PathMix {
6
+
7
+ static #getCallerFile(methodRef) {
8
+ const orig = Error.prepareStackTrace;
9
+ Error.prepareStackTrace = (_, stack) => stack;
10
+ const err = new Error();
11
+ Error.captureStackTrace(err, methodRef);
12
+ const stack = err.stack;
13
+ Error.prepareStackTrace = orig;
14
+
15
+ const fileName = stack[0]?.getFileName();
16
+ if (!fileName) return null;
17
+
18
+ if (fileName.startsWith('file://')) return fileURLToPath(fileName);
19
+ return fileName;
20
+ }
21
+
22
+ static #isFileUrl(value) {
23
+ return typeof value === 'string' && value.startsWith('file://');
24
+ }
25
+
26
+ static #expandTokens(segment) {
27
+ if (typeof segment !== 'string') return segment;
28
+ if (segment.startsWith('$HOME')) return segment.replace(/^\$HOME/, homedir());
29
+ if (segment.startsWith('~')) return segment.replace(/^~/, homedir());
30
+ return segment;
31
+ }
32
+
33
+ // ── Base path methods with integrated join ──
34
+
35
+ static home(...segments) {
36
+ return segments.length ? join(homedir(), ...segments) : homedir();
37
+ }
38
+
39
+ static cwd(...segments) {
40
+ return segments.length ? join(process.cwd(), ...segments) : process.cwd();
41
+ }
42
+
43
+ static dir(...args) {
44
+ let base;
45
+ let segments;
46
+
47
+ if (args.length > 0 && PathMix.#isFileUrl(args[0])) {
48
+ base = pathDirname(fileURLToPath(args[0]));
49
+ segments = args.slice(1);
50
+ } else {
51
+ const callerFile = PathMix.#getCallerFile(PathMix.dir);
52
+ if (!callerFile) throw new Error('PathMix.dir(): could not detect caller file. Pass import.meta.url explicitly.');
53
+ base = pathDirname(callerFile);
54
+ segments = args;
55
+ }
56
+
57
+ return segments.length ? join(base, ...segments) : base;
58
+ }
59
+
60
+ static file(...args) {
61
+ if (args.length > 0 && PathMix.#isFileUrl(args[0])) {
62
+ return fileURLToPath(args[0]);
63
+ }
64
+
65
+ const callerFile = PathMix.#getCallerFile(PathMix.file);
66
+ if (!callerFile) throw new Error('PathMix.file(): could not detect caller file. Pass import.meta.url explicitly.');
67
+ return callerFile;
68
+ }
69
+
70
+ static tmp(...segments) {
71
+ return segments.length ? join(tmpdir(), ...segments) : tmpdir();
72
+ }
73
+
74
+ // ── Token expansion ──
75
+
76
+ static resolve(...segments) {
77
+ const expanded = segments.map(s => PathMix.#expandTokens(s));
78
+ return resolve(...expanded);
79
+ }
80
+
81
+ // ── CJS compatibility helpers ──
82
+
83
+ static __dirname(...args) {
84
+ if (args.length > 0 && PathMix.#isFileUrl(args[0])) {
85
+ return pathDirname(fileURLToPath(args[0]));
86
+ }
87
+
88
+ const callerFile = PathMix.#getCallerFile(PathMix.__dirname);
89
+ if (!callerFile) throw new Error('PathMix.__dirname(): could not detect caller file. Pass import.meta.url explicitly.');
90
+ return pathDirname(callerFile);
91
+ }
92
+
93
+ static __filename(...args) {
94
+ if (args.length > 0 && PathMix.#isFileUrl(args[0])) {
95
+ return fileURLToPath(args[0]);
96
+ }
97
+
98
+ const callerFile = PathMix.#getCallerFile(PathMix.__filename);
99
+ if (!callerFile) throw new Error('PathMix.__filename(): could not detect caller file. Pass import.meta.url explicitly.');
100
+ return callerFile;
101
+ }
102
+
103
+ // ── Re-exported path utilities ──
104
+
105
+ static join(...segments) { return join(...segments); }
106
+ static normalize(p) { return normalize(p); }
107
+ static basename(p, ext) { return basename(p, ext); }
108
+ static dirname(p) { return pathDirname(p); }
109
+ static extname(p) { return extname(p); }
110
+ static relative(from, to) { return relative(from, to); }
111
+ static isAbsolute(p) { return isAbsolute(p); }
112
+ static parse(p) { return parse(p); }
113
+ static format(obj) { return format(obj); }
114
+ static get sep() { return sep; }
115
+ }
116
+
117
+ export { PathMix };
118
+ export default PathMix;
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "pathmix",
3
+ "version": "1.0.0",
4
+ "description": "Static class for unified path management across ESM and CommonJS",
5
+ "license": "ISC",
6
+ "author": "",
7
+ "type": "module",
8
+ "main": "index.cjs",
9
+ "module": "index.js",
10
+ "exports": {
11
+ ".": {
12
+ "import": "./index.js",
13
+ "require": "./index.cjs"
14
+ }
15
+ },
16
+ "engines": {
17
+ "node": ">=16.0.0"
18
+ },
19
+ "files": [
20
+ "index.js",
21
+ "index.cjs"
22
+ ],
23
+ "keywords": [
24
+ "path",
25
+ "dirname",
26
+ "filename",
27
+ "import.meta",
28
+ "meta.url",
29
+ "esm",
30
+ "commonjs",
31
+ "cross-platform",
32
+ "SKILL.md",
33
+ "helper",
34
+ "clasen"
35
+ ],
36
+ "scripts": {
37
+ "test": "node demo/example.mjs && node demo/example.cjs"
38
+ }
39
+ }