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.
- package/README.md +161 -0
- package/index.cjs +121 -0
- package/index.js +118 -0
- 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
|
+
}
|