@cldmv/wisp 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/LICENSE +21 -0
- package/README.md +153 -0
- package/index.cjs +24 -0
- package/index.mjs +22 -0
- package/package.json +79 -0
- package/src/lib/resolve-from-caller.mjs +440 -0
- package/src/types/lib/resolve-from-caller.d.mts +148 -0
- package/src/types/wisp.d.mts +54 -0
- package/src/wisp.mjs +187 -0
- package/types/lib/resolve-from-caller.d.mts +149 -0
- package/types/lib/resolve-from-caller.d.mts.map +1 -0
- package/types/wisp.d.mts +55 -0
- package/types/wisp.d.mts.map +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 CLDMV Inc.
|
|
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,153 @@
|
|
|
1
|
+
# @cldmv/wisp
|
|
2
|
+
|
|
3
|
+
A Node.js module for version-agnostic JSON importing, providing transparent support for modern and legacy import syntaxes with automatic fallbacks.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`@cldmv/wisp` allows you to load JSON files in Node.js without worrying about version-specific import syntax. It automatically tries the most modern import methods first and falls back to reliable file system operations.
|
|
8
|
+
|
|
9
|
+
## Node.js Version Support
|
|
10
|
+
|
|
11
|
+
| Node Version | `import ... with { type: 'json' }` | `import ... assert { type: 'json' }` | Fallback |
|
|
12
|
+
| ------------ | ---------------------------------- | ------------------------------------ | -------- |
|
|
13
|
+
| ≥ 22.10 | ✅ | ✅ | ✅ |
|
|
14
|
+
| ≥ 20.10 | ✅ | ✅ | ✅ |
|
|
15
|
+
| ≥ 18.20 | ✅ | ✅ | ✅ |
|
|
16
|
+
| ≥ 16.14 | ❌ | ✅ | ✅ |
|
|
17
|
+
| < 16.14 | ❌ | ❌ | ✅ |
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @cldmv/wisp
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
### ESM (Modern)
|
|
28
|
+
|
|
29
|
+
```javascript
|
|
30
|
+
import { wisp, wispSync } from "@cldmv/wisp";
|
|
31
|
+
|
|
32
|
+
// Asynchronous loading
|
|
33
|
+
const config = await wisp("./config.json");
|
|
34
|
+
|
|
35
|
+
// Synchronous loading
|
|
36
|
+
const data = wispSync("./data.json");
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### CJS (CommonJS)
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
const { wisp, wispSync } = require("@cldmv/wisp");
|
|
43
|
+
|
|
44
|
+
// Asynchronous loading
|
|
45
|
+
wisp("./config.json").then((config) => {
|
|
46
|
+
console.log(config);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Synchronous loading
|
|
50
|
+
const data = wispSync("./data.json");
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## API Reference
|
|
54
|
+
|
|
55
|
+
### `wisp(input, options?)`
|
|
56
|
+
|
|
57
|
+
Asynchronously loads JSON from a file.
|
|
58
|
+
|
|
59
|
+
#### Parameters
|
|
60
|
+
|
|
61
|
+
- `input` (string | URL): Path or URL to the JSON file
|
|
62
|
+
- `options` (object, optional):
|
|
63
|
+
- `base` (string | URL, optional): Base URL for resolving relative paths. Defaults to the caller's file URL.
|
|
64
|
+
- `validate` (function, optional): Validation function called with the parsed JSON. Throws if validation fails.
|
|
65
|
+
- `reviver` (function, optional): Reviver function passed to `JSON.parse`.
|
|
66
|
+
|
|
67
|
+
#### Returns
|
|
68
|
+
|
|
69
|
+
`Promise<*>`: The parsed JSON value.
|
|
70
|
+
|
|
71
|
+
#### Example
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
import { wisp } from "@cldmv/wisp";
|
|
75
|
+
|
|
76
|
+
const data = await wisp("./config.json", {
|
|
77
|
+
validate: (json) => {
|
|
78
|
+
if (!json.requiredField) throw new Error("Missing required field");
|
|
79
|
+
},
|
|
80
|
+
reviver: (key, value) => (key === "date" ? new Date(value) : value)
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### `wispSync(input, options?)`
|
|
85
|
+
|
|
86
|
+
Synchronously loads JSON from a file.
|
|
87
|
+
|
|
88
|
+
#### Parameters
|
|
89
|
+
|
|
90
|
+
- `input` (string | URL): Path or URL to the JSON file
|
|
91
|
+
- `options` (object, optional): Same as `wisp` options.
|
|
92
|
+
|
|
93
|
+
#### Returns
|
|
94
|
+
|
|
95
|
+
`Promise<*>`: The parsed JSON value.
|
|
96
|
+
|
|
97
|
+
#### Example
|
|
98
|
+
|
|
99
|
+
```javascript
|
|
100
|
+
import { wispSync } from "@cldmv/wisp";
|
|
101
|
+
|
|
102
|
+
const data = wispSync("./config.json", {
|
|
103
|
+
validate: (json) => {
|
|
104
|
+
if (!json.version) throw new Error("Version required");
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Options
|
|
110
|
+
|
|
111
|
+
| Option | Type | Description |
|
|
112
|
+
| ---------- | ---------- | ------------------------------------------------------------------------ |
|
|
113
|
+
| `base` | string/URL | Base URL for relative path resolution. Defaults to caller's file URL. |
|
|
114
|
+
| `validate` | function | Validation function. Receives parsed JSON, should throw on invalid data. |
|
|
115
|
+
| `reviver` | function | JSON.parse reviver function for custom parsing. |
|
|
116
|
+
|
|
117
|
+
## Path Resolution
|
|
118
|
+
|
|
119
|
+
`@cldmv/wisp` uses caller-aware path resolution:
|
|
120
|
+
|
|
121
|
+
- Relative paths are resolved relative to the file that calls `wisp` or `wispSync`
|
|
122
|
+
- Absolute paths and URLs are used as-is
|
|
123
|
+
- The `base` option overrides the default caller-based resolution
|
|
124
|
+
|
|
125
|
+
## Fallback Order
|
|
126
|
+
|
|
127
|
+
The module attempts to load JSON in this order:
|
|
128
|
+
|
|
129
|
+
1. `import(url, { with: { type: 'json' } })` (Node ≥ 18.20/20.10/22)
|
|
130
|
+
2. `import(url, { assert: { type: 'json' } })` (Node ≥ 16.14)
|
|
131
|
+
3. `fs.readFile` / `fs.readFileSync` (all supported Node versions)
|
|
132
|
+
|
|
133
|
+
This ensures maximum compatibility across Node.js versions.
|
|
134
|
+
|
|
135
|
+
## Error Handling
|
|
136
|
+
|
|
137
|
+
Validation errors are prefixed with `@cldmv/wisp:` for easy identification:
|
|
138
|
+
|
|
139
|
+
```javascript
|
|
140
|
+
try {
|
|
141
|
+
await wisp("./invalid.json", {
|
|
142
|
+
validate: () => {
|
|
143
|
+
throw new Error("Custom validation failed");
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
} catch (error) {
|
|
147
|
+
console.log(error.message); // "@cldmv/wisp: Custom validation failed"
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## License
|
|
152
|
+
|
|
153
|
+
MIT © CLDMV Inc.
|
package/index.cjs
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview CJS entry point for @cldmv/wisp, providing version-agnostic JSON importing.
|
|
3
|
+
* @module @cldmv/wisp
|
|
4
|
+
* @public
|
|
5
|
+
*
|
|
6
|
+
* @description
|
|
7
|
+
* This module provides CommonJS exports for the wisp and wispSync functions.
|
|
8
|
+
* It uses createRequire to load the ESM implementation and re-exports it.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* const { wisp, wispSync } = require('@cldmv/wisp');
|
|
12
|
+
* const data = wispSync('./data.json');
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
"use strict";
|
|
16
|
+
|
|
17
|
+
const { createRequire } = require("module");
|
|
18
|
+
const requireESM = createRequire(__filename);
|
|
19
|
+
const esm = requireESM("./index.mjs");
|
|
20
|
+
|
|
21
|
+
module.exports = esm.default;
|
|
22
|
+
module.exports.default = esm.default;
|
|
23
|
+
module.exports.wisp = esm.wisp;
|
|
24
|
+
module.exports.wispSync = esm.wispSync;
|
package/index.mjs
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Main entry point for @cldmv/wisp, providing version-agnostic JSON importing.
|
|
3
|
+
* @module @cldmv/wisp
|
|
4
|
+
* @public
|
|
5
|
+
*
|
|
6
|
+
* @description
|
|
7
|
+
* This module exports the wisp and wispSync functions for loading JSON files
|
|
8
|
+
* with support for different Node.js versions and import syntaxes.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* // ESM
|
|
12
|
+
* import { wisp, wispSync } from '@cldmv/wisp';
|
|
13
|
+
* const data = await wisp('./data.json');
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* // CJS
|
|
17
|
+
* const { wisp, wispSync } = require('@cldmv/wisp');
|
|
18
|
+
* const data = wispSync('./data.json');
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export * from "./src/wisp.mjs";
|
|
22
|
+
export { default } from "./src/wisp.mjs";
|
package/package.json
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cldmv/wisp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Version-agnostic JSON importing for Node.js with fallback handling and caller path resolution.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"import": "./index.mjs",
|
|
9
|
+
"require": "./index.cjs"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"test": "mocha --recursive \"test/**/*.mjs\"",
|
|
14
|
+
"test:watch": "mocha --watch \"test/**/*.mjs\"",
|
|
15
|
+
"lint": "eslint src/",
|
|
16
|
+
"build:types": "tsc",
|
|
17
|
+
"test:types": "tsc --noEmit",
|
|
18
|
+
"build:ci": "npm run test && npm run build:types && npm run test:types"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"json",
|
|
22
|
+
"import",
|
|
23
|
+
"nodejs",
|
|
24
|
+
"version-agnostic",
|
|
25
|
+
"fallback",
|
|
26
|
+
"loader",
|
|
27
|
+
"esm",
|
|
28
|
+
"cjs",
|
|
29
|
+
"wisp",
|
|
30
|
+
"api"
|
|
31
|
+
],
|
|
32
|
+
"author": {
|
|
33
|
+
"name": "Shinrai",
|
|
34
|
+
"company": "CLDMV",
|
|
35
|
+
"email": "git+npm@cldmv.net",
|
|
36
|
+
"url": "https://cldmv.net"
|
|
37
|
+
},
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git+https://github.com/CLDMV/wisp.git"
|
|
41
|
+
},
|
|
42
|
+
"contributors": [
|
|
43
|
+
{
|
|
44
|
+
"name": "CLDMV",
|
|
45
|
+
"url": "https://github.com/CLDMV"
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/CLDMV/wisp/issues"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://github.com/CLDMV/wisp#readme",
|
|
52
|
+
"license": "MIT",
|
|
53
|
+
"funding": {
|
|
54
|
+
"type": "github",
|
|
55
|
+
"url": "https://github.com/sponsors/shinrai"
|
|
56
|
+
},
|
|
57
|
+
"files": [
|
|
58
|
+
"src/",
|
|
59
|
+
"types/",
|
|
60
|
+
"index.mjs",
|
|
61
|
+
"index.cjs",
|
|
62
|
+
"README.md",
|
|
63
|
+
"LICENSE",
|
|
64
|
+
"package.json"
|
|
65
|
+
],
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"mocha": "^10.2.0",
|
|
68
|
+
"chai": "^4.3.10",
|
|
69
|
+
"typescript": "^5.2.0",
|
|
70
|
+
"@types/node": "^20.8.0"
|
|
71
|
+
},
|
|
72
|
+
"publishConfig": {
|
|
73
|
+
"access": "public"
|
|
74
|
+
},
|
|
75
|
+
"engines": {
|
|
76
|
+
"node": ">=16.0.0"
|
|
77
|
+
},
|
|
78
|
+
"sideEffects": false
|
|
79
|
+
}
|
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Project: @cldmv/wisp
|
|
3
|
+
* @Filename: /src/lib/resolve-from-caller.mjs
|
|
4
|
+
* @Date: 2025-09-09 13:22:38 -07:00 (1757449358)
|
|
5
|
+
* @Author: Nate Hyson <CLDMV>
|
|
6
|
+
* @Email: <Shinrai@users.noreply.github.com>
|
|
7
|
+
* -----
|
|
8
|
+
* @Last modified by: Nate Hyson <CLDMV> (Shinrai@users.noreply.github.com)
|
|
9
|
+
* @Last modified time: 2025-10-31 07:31:40 -07:00 (1761921100)
|
|
10
|
+
* -----
|
|
11
|
+
* @Copyright: Copyright (c) 2013-2025 Catalyzed Motivation Inc. All rights reserved.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @fileoverview Path resolution utilities for resolving paths from the caller context. Internal file (not exported in package.json).
|
|
16
|
+
* @module @cldmv/wisp.helpers.resolve-from-caller
|
|
17
|
+
* @memberof module:@cldmv/wisp.helpers
|
|
18
|
+
* @internal
|
|
19
|
+
* @package
|
|
20
|
+
*
|
|
21
|
+
* @description
|
|
22
|
+
* Advanced path resolution system that uses V8 stack trace analysis to determine caller context.
|
|
23
|
+
* Provides utilities for resolving relative paths from the caller's directory, implementing
|
|
24
|
+
* sophisticated caller detection algorithms to handle complex module loading scenarios.
|
|
25
|
+
*
|
|
26
|
+
* Key features:
|
|
27
|
+
* - V8 CallSite-based stack trace analysis
|
|
28
|
+
* - Primary base file detection with fallback strategies
|
|
29
|
+
* - Support for both filesystem paths and file:// URLs
|
|
30
|
+
* - Smart handling of slothlet.mjs and index file patterns
|
|
31
|
+
* - Existence-based resolution with automatic fallback
|
|
32
|
+
*
|
|
33
|
+
* Technical implementation:
|
|
34
|
+
* - Uses Error.prepareStackTrace to access V8 CallSite objects
|
|
35
|
+
* - Implements dual-phase resolution (primary + fallback)
|
|
36
|
+
* - Handles edge cases like node:internal modules and helper directories
|
|
37
|
+
* - Provides both path and URL resolution variants
|
|
38
|
+
*
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* // ESM (internal)
|
|
42
|
+
* import { resolvePathFromCaller } from "@cldmv/slothlet/helpers/resolve-from-caller";
|
|
43
|
+
* // Internal example using package.json exports
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* // Relative import (internal)
|
|
47
|
+
* import { resolvePathFromCaller, resolveUrlFromCaller } from "./resolve-from-caller.mjs";
|
|
48
|
+
* const configPath = resolvePathFromCaller("../config.json");
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
import fs from "node:fs";
|
|
52
|
+
import path from "node:path";
|
|
53
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
54
|
+
|
|
55
|
+
/* ---------- tiny utils ---------- */
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @function toFsPath
|
|
59
|
+
* @package
|
|
60
|
+
* @internal
|
|
61
|
+
* @param {any} v - Value to convert (file:// URL, path string, or any other value)
|
|
62
|
+
* @returns {string|null} Filesystem path if URL conversion successful, original string if already a path, null if input is falsy
|
|
63
|
+
*
|
|
64
|
+
* @description
|
|
65
|
+
* Convert file:// URL to filesystem path, or return string as-is.
|
|
66
|
+
* Handles URL-to-path conversion for cross-platform compatibility while preserving
|
|
67
|
+
* non-URL strings unchanged. Provides null-safe operation for invalid inputs.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* // URL conversion
|
|
71
|
+
* toFsPath("file:///project/config.json"); // "/project/config.json" (Unix) or "C:\\project\\config.json" (Windows)
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* // String passthrough
|
|
75
|
+
* toFsPath("/project/config.json"); // "/project/config.json"
|
|
76
|
+
* toFsPath("C:\\project\\config.json"); // "C:\\project\\config.json"
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* // Null-safe handling
|
|
80
|
+
* toFsPath(null); // null
|
|
81
|
+
* toFsPath(undefined); // null
|
|
82
|
+
* toFsPath(""); // null
|
|
83
|
+
*/
|
|
84
|
+
export const toFsPath = (v) => (v && String(v).startsWith("file://") ? fileURLToPath(String(v)) : v ? String(v) : null);
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @function getStack
|
|
88
|
+
* @package
|
|
89
|
+
* @internal
|
|
90
|
+
* @param {Function} [skipFn] - Function to skip in stack trace via Error.captureStackTrace
|
|
91
|
+
* @returns {Array<CallSite>} Array of V8 CallSite objects with methods like getFileName(), getLineNumber(), etc.
|
|
92
|
+
*
|
|
93
|
+
* @description
|
|
94
|
+
* Get V8 stack trace as CallSite array for debugging and caller detection.
|
|
95
|
+
* Temporarily overrides Error.prepareStackTrace to access raw V8 CallSite objects
|
|
96
|
+
* instead of formatted string stack traces. Provides safe restoration of original handler.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* // Get current call stack for debugging
|
|
100
|
+
* const stack = getStack();
|
|
101
|
+
* console.log(stack[0]?.getFileName?.()); // Current file path
|
|
102
|
+
* console.log(stack[0]?.getLineNumber?.()); // Current line number
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* // Skip current function from stack trace
|
|
106
|
+
* function myFunction() {
|
|
107
|
+
* return getStack(myFunction); // Stack starts from caller of myFunction
|
|
108
|
+
* }
|
|
109
|
+
* const callerStack = myFunction();
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* // Stack analysis for caller detection
|
|
113
|
+
* function findCaller() {
|
|
114
|
+
* const stack = getStack(findCaller);
|
|
115
|
+
* for (const frame of stack) {
|
|
116
|
+
* const filename = frame?.getFileName?.();
|
|
117
|
+
* if (filename && !filename.includes("node_modules")) {
|
|
118
|
+
* return filename; // First non-dependency file
|
|
119
|
+
* }
|
|
120
|
+
* }
|
|
121
|
+
* }
|
|
122
|
+
*/
|
|
123
|
+
export function getStack(skipFn) {
|
|
124
|
+
const orig = Error.prepareStackTrace;
|
|
125
|
+
try {
|
|
126
|
+
Error.prepareStackTrace = (_, s) => s; // V8 CallSite[]
|
|
127
|
+
const e = new Error("Stack trace");
|
|
128
|
+
if (skipFn) Error.captureStackTrace(e, skipFn);
|
|
129
|
+
return /** @type {NodeJS.CallSite[]} */ (/** @type {unknown} */ (e.stack)) || [];
|
|
130
|
+
} finally {
|
|
131
|
+
Error.prepareStackTrace = orig;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const THIS_FILE = fileURLToPath(import.meta.url);
|
|
136
|
+
const THIS_DIR = path.dirname(THIS_FILE);
|
|
137
|
+
|
|
138
|
+
/* ---------- base selection (shared) ---------- */
|
|
139
|
+
// Rule you specified:
|
|
140
|
+
// 1) Find the LAST frame whose basename is "slothlet.mjs".
|
|
141
|
+
// 2) Take the NEXT frame; if its basename is "index.[mjs|cjs|js]", take the following one.
|
|
142
|
+
// 3) Fallback: first non-helper frame that isn’t slothlet.mjs.
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* @function pickPrimaryBaseFile
|
|
146
|
+
* @internal
|
|
147
|
+
* @private
|
|
148
|
+
* @returns {string|null} Primary base file path for resolution, null if detection fails
|
|
149
|
+
*
|
|
150
|
+
* @description
|
|
151
|
+
* Find the primary base file using stack trace analysis.
|
|
152
|
+
* Implements sophisticated caller detection algorithm designed for slothlet's module loading patterns.
|
|
153
|
+
* Uses a two-phase approach: locate the last slothlet.mjs frame, then find the actual user code.
|
|
154
|
+
*
|
|
155
|
+
* Algorithm:
|
|
156
|
+
* 1. Scan stack trace to find the LAST frame whose basename is "slothlet.mjs"
|
|
157
|
+
* 2. Take the NEXT frame after slothlet.mjs
|
|
158
|
+
* 3. If that frame is "index.[mjs|cjs|js]", take the following frame instead
|
|
159
|
+
* 4. Return null if no suitable frame found (fallback will be used)
|
|
160
|
+
*
|
|
161
|
+
* This pattern handles slothlet's loading chain: slothlet.mjs → index.mjs → user-code.mjs
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* // Stack trace scenario:
|
|
165
|
+
* // 0: pickPrimaryBaseFile() [this function]
|
|
166
|
+
* // 1: resolveWith() [internal helper]
|
|
167
|
+
* // 2: resolvePathFromCaller() [public API]
|
|
168
|
+
* // 3: slothlet.mjs [slothlet loader] ← LAST slothlet.mjs
|
|
169
|
+
* // 4: index.mjs [entry point] ← Skip this
|
|
170
|
+
* // 5: user-code.mjs [actual caller] ← Return this
|
|
171
|
+
*/
|
|
172
|
+
function pickPrimaryBaseFile() {
|
|
173
|
+
const files = [];
|
|
174
|
+
for (const cs of getStack(pickPrimaryBaseFile)) {
|
|
175
|
+
const f = toFsPath(cs?.getFileName?.());
|
|
176
|
+
if (!f) continue;
|
|
177
|
+
if (f.startsWith?.("node:internal")) continue;
|
|
178
|
+
files.push(f);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let iSloth = -1;
|
|
182
|
+
for (let i = 0; i < files.length; i++) {
|
|
183
|
+
if (path.basename(files[i]).toLowerCase() === "slothlet.mjs") iSloth = i;
|
|
184
|
+
}
|
|
185
|
+
if (iSloth !== -1) {
|
|
186
|
+
const j = iSloth + 1;
|
|
187
|
+
if (j < files.length) {
|
|
188
|
+
const b = path.basename(files[j]).toLowerCase();
|
|
189
|
+
if (/^index\.(mjs|cjs|js)$/.test(b) && j + 1 < files.length) return files[j + 1];
|
|
190
|
+
return files[j];
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* @function pickFallbackBaseFile
|
|
198
|
+
* @internal
|
|
199
|
+
* @private
|
|
200
|
+
* @returns {string} Fallback base file path, guaranteed to return a valid path
|
|
201
|
+
*
|
|
202
|
+
* @description
|
|
203
|
+
* Find fallback base file when primary detection fails.
|
|
204
|
+
* Provides robust fallback strategy by finding the first legitimate user code frame.
|
|
205
|
+
* Filters out internal Node.js modules, helper utilities, and slothlet infrastructure.
|
|
206
|
+
*
|
|
207
|
+
* Fallback algorithm:
|
|
208
|
+
* 1. Iterate through stack frames from top to bottom
|
|
209
|
+
* 2. Skip node:internal modules (Node.js internals)
|
|
210
|
+
* 3. Skip this file itself (resolve-from-caller.mjs)
|
|
211
|
+
* 4. Skip other files in the helpers directory
|
|
212
|
+
* 5. Skip slothlet.mjs infrastructure files
|
|
213
|
+
* 6. Return first remaining frame, or THIS_FILE as ultimate fallback
|
|
214
|
+
*
|
|
215
|
+
* This ensures we always have a base path for resolution, even in edge cases.
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* // Fallback scenarios:
|
|
219
|
+
* // - Primary detection failed (no slothlet.mjs in stack)
|
|
220
|
+
* // - Complex loading chain with multiple loaders
|
|
221
|
+
* // - Edge cases like REPL or test environments
|
|
222
|
+
* // - Direct API usage without slothlet loader
|
|
223
|
+
*/
|
|
224
|
+
function pickFallbackBaseFile() {
|
|
225
|
+
for (const cs of getStack(pickFallbackBaseFile)) {
|
|
226
|
+
const f = toFsPath(cs?.getFileName?.());
|
|
227
|
+
if (!f) continue;
|
|
228
|
+
if (f.startsWith?.("node:internal")) continue;
|
|
229
|
+
if (f === THIS_FILE) continue;
|
|
230
|
+
if (f.startsWith(THIS_DIR + path.sep)) continue; // helper’s own dir
|
|
231
|
+
if (path.basename(f).toLowerCase() === "slothlet.mjs") continue;
|
|
232
|
+
return f;
|
|
233
|
+
}
|
|
234
|
+
return THIS_FILE;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/* ---------- generic resolver (shared) ---------- */
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* @function resolveWith
|
|
241
|
+
* @internal
|
|
242
|
+
* @private
|
|
243
|
+
* @param {string} rel - Relative path to resolve (must be a string)
|
|
244
|
+
* @param {Function} makePrimary - Function to build primary candidate: (baseFile, rel) => candidate
|
|
245
|
+
* @param {Function} exists - Existence check function: (candidate) => boolean
|
|
246
|
+
* @param {Function} makeFallback - Function to build fallback result: (baseFile, rel) => result
|
|
247
|
+
* @returns {string} Resolved path or URL (type depends on make functions)
|
|
248
|
+
* @throws {TypeError} When rel parameter is not a string
|
|
249
|
+
*
|
|
250
|
+
* @description
|
|
251
|
+
* Generic resolver that tries primary base file detection with fallback.
|
|
252
|
+
* Core resolution engine that orchestrates the two-phase detection strategy.
|
|
253
|
+
* Attempts primary resolution first, then falls back if target doesn't exist.
|
|
254
|
+
*
|
|
255
|
+
* Resolution strategy:
|
|
256
|
+
* 1. Get primary base file using sophisticated stack analysis
|
|
257
|
+
* 2. Build primary candidate using makePrimary function
|
|
258
|
+
* 3. Check if primary candidate exists using exists function
|
|
259
|
+
* 4. If exists, return primary candidate
|
|
260
|
+
* 5. Otherwise, get fallback base file and build fallback result
|
|
261
|
+
* 6. Return fallback result (no second existence check)
|
|
262
|
+
*
|
|
263
|
+
* This pattern ensures we try the most accurate resolution first, but always
|
|
264
|
+
* provide a reasonable fallback even if the target doesn't exist yet.
|
|
265
|
+
*
|
|
266
|
+
* @example
|
|
267
|
+
* // Usage pattern for filesystem paths:
|
|
268
|
+
* resolveWith(
|
|
269
|
+
* "../config.json",
|
|
270
|
+
* (base, rel) => path.resolve(path.dirname(base), rel), // makePrimary
|
|
271
|
+
* (candidate) => fs.existsSync(candidate), // exists
|
|
272
|
+
* (base, rel) => path.resolve(path.dirname(base), rel) // makeFallback
|
|
273
|
+
* );
|
|
274
|
+
*
|
|
275
|
+
* @example
|
|
276
|
+
* // Usage pattern for URLs:
|
|
277
|
+
* resolveWith(
|
|
278
|
+
* "../config.json",
|
|
279
|
+
* (base, rel) => new URL(rel, pathToFileURL(base)).href, // makePrimary
|
|
280
|
+
* (href) => fs.existsSync(fileURLToPath(href)), // exists
|
|
281
|
+
* (base, rel) => new URL(rel, pathToFileURL(base)).href // makeFallback
|
|
282
|
+
* );
|
|
283
|
+
*/
|
|
284
|
+
function resolveWith(rel, makePrimary, exists, makeFallback) {
|
|
285
|
+
if (typeof rel !== "string") throw new TypeError("rel must be a string");
|
|
286
|
+
|
|
287
|
+
// absolute / already-URL cases are handled in the public wrappers
|
|
288
|
+
const primaryBase = pickPrimaryBaseFile() ?? pickFallbackBaseFile();
|
|
289
|
+
const primary = makePrimary(primaryBase, rel);
|
|
290
|
+
if (exists(primary)) return primary;
|
|
291
|
+
|
|
292
|
+
const fbBase = pickFallbackBaseFile();
|
|
293
|
+
return makeFallback(fbBase, rel);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/* ---------- public API (thin wrappers) ---------- */
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* @function resolvePathFromCaller
|
|
300
|
+
* @package
|
|
301
|
+
* @internal
|
|
302
|
+
* @param {string} rel - Relative path to resolve (e.g., "../config.json", "./data/file.txt")
|
|
303
|
+
* @returns {string} Absolute filesystem path with platform-specific separators
|
|
304
|
+
* @throws {TypeError} When rel parameter is not a string
|
|
305
|
+
*
|
|
306
|
+
* @description
|
|
307
|
+
* Resolve a relative path from the caller's context to an absolute filesystem path.
|
|
308
|
+
* Primary public API for filesystem path resolution with intelligent caller detection.
|
|
309
|
+
* Uses sophisticated stack trace analysis to determine the appropriate base directory.
|
|
310
|
+
*
|
|
311
|
+
* Resolution behavior:
|
|
312
|
+
* - file:// URLs: Converted to filesystem paths via fileURLToPath()
|
|
313
|
+
* - Absolute paths: Returned unchanged (already absolute)
|
|
314
|
+
* - Relative paths: Resolved using caller detection algorithm
|
|
315
|
+
*
|
|
316
|
+
* Caller detection process:
|
|
317
|
+
* 1. Primary: Use sophisticated slothlet.mjs-aware stack analysis
|
|
318
|
+
* 2. Fallback: Use first non-helper frame if primary fails
|
|
319
|
+
* 3. Existence check: Prefer primary if target exists, otherwise use fallback
|
|
320
|
+
*
|
|
321
|
+
* @example
|
|
322
|
+
* // From a file at /project/src/modules/math.mjs
|
|
323
|
+
* const configPath = resolvePathFromCaller("../config.json");
|
|
324
|
+
* // Returns: /project/config.json (absolute filesystem path)
|
|
325
|
+
*
|
|
326
|
+
* @example
|
|
327
|
+
* // Short-circuit cases
|
|
328
|
+
* resolvePathFromCaller("file:///absolute/path.txt");
|
|
329
|
+
* // Returns: /absolute/path.txt (converted from URL)
|
|
330
|
+
*
|
|
331
|
+
* resolvePathFromCaller("/already/absolute/path.txt");
|
|
332
|
+
* // Returns: /already/absolute/path.txt (unchanged)
|
|
333
|
+
*
|
|
334
|
+
* @example
|
|
335
|
+
* // Relative resolution from different contexts
|
|
336
|
+
* // If called from /project/src/lib/utils.mjs:
|
|
337
|
+
* resolvePathFromCaller("./helpers/format.js");
|
|
338
|
+
* // Returns: /project/src/lib/helpers/format.js
|
|
339
|
+
*
|
|
340
|
+
* resolvePathFromCaller("../../config/settings.json");
|
|
341
|
+
* // Returns: /project/config/settings.json
|
|
342
|
+
*/
|
|
343
|
+
export function resolvePathFromCaller(rel) {
|
|
344
|
+
// short-circuits
|
|
345
|
+
if (rel.startsWith?.("file://")) return fileURLToPath(rel);
|
|
346
|
+
if (path.isAbsolute(rel)) return rel;
|
|
347
|
+
|
|
348
|
+
return resolveWith(
|
|
349
|
+
rel,
|
|
350
|
+
// makePrimary (PATH)
|
|
351
|
+
(baseFile, r) => path.resolve(path.dirname(baseFile), r),
|
|
352
|
+
// exists (PATH)
|
|
353
|
+
(candidate) => fs.existsSync(candidate),
|
|
354
|
+
// makeFallback (PATH)
|
|
355
|
+
(baseFile, r) => path.resolve(path.dirname(baseFile), r)
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* @function resolveUrlFromCaller
|
|
361
|
+
* @package
|
|
362
|
+
* @internal
|
|
363
|
+
* @param {string} rel - Relative path to resolve (e.g., "../config.json", "./data/file.txt")
|
|
364
|
+
* @returns {string} Absolute file:// URL suitable for dynamic imports and URL operations
|
|
365
|
+
* @throws {TypeError} When rel parameter is not a string
|
|
366
|
+
*
|
|
367
|
+
* @description
|
|
368
|
+
* Resolve a relative path from the caller's context to a file:// URL.
|
|
369
|
+
* Companion API to resolvePathFromCaller that returns file:// URLs instead of filesystem paths.
|
|
370
|
+
* Uses identical caller detection algorithm but outputs URL format for ESM imports.
|
|
371
|
+
*
|
|
372
|
+
* Resolution behavior:
|
|
373
|
+
* - file:// URLs: Returned unchanged (already in URL format)
|
|
374
|
+
* - Absolute paths: Converted to file:// URLs via pathToFileURL()
|
|
375
|
+
* - Relative paths: Resolved using caller detection, then converted to URL
|
|
376
|
+
*
|
|
377
|
+
* Caller detection uses the same sophisticated algorithm as resolvePathFromCaller,
|
|
378
|
+
* but the final result is converted to a file:// URL for compatibility with
|
|
379
|
+
* ESM dynamic imports and other URL-based operations.
|
|
380
|
+
*
|
|
381
|
+
* @example
|
|
382
|
+
* // From a file at /project/src/modules/math.mjs
|
|
383
|
+
* const configUrl = resolveUrlFromCaller("../config.json");
|
|
384
|
+
* // Returns: file:///project/config.json (absolute file:// URL)
|
|
385
|
+
*
|
|
386
|
+
* @example
|
|
387
|
+
* // Short-circuit cases
|
|
388
|
+
* resolveUrlFromCaller("file:///absolute/path.txt");
|
|
389
|
+
* // Returns: file:///absolute/path.txt (unchanged)
|
|
390
|
+
*
|
|
391
|
+
* resolveUrlFromCaller("/already/absolute/path.txt");
|
|
392
|
+
* // Returns: file:///already/absolute/path.txt (converted to URL)
|
|
393
|
+
*
|
|
394
|
+
* @example
|
|
395
|
+
* // Dynamic ESM import usage
|
|
396
|
+
* const modulePath = resolveUrlFromCaller("./dynamic-module.mjs");
|
|
397
|
+
* const dynamicModule = await import(modulePath);
|
|
398
|
+
* // Works seamlessly with ESM import() which expects URLs
|
|
399
|
+
*
|
|
400
|
+
* @example
|
|
401
|
+
* // Cross-platform URL handling
|
|
402
|
+
* // Unix: resolveUrlFromCaller("../config.json") → file:///project/config.json
|
|
403
|
+
* // Windows: resolveUrlFromCaller("../config.json") → file:///C:/project/config.json
|
|
404
|
+
*/
|
|
405
|
+
export function resolveUrlFromCaller(rel) {
|
|
406
|
+
// short-circuits
|
|
407
|
+
if (rel.startsWith?.("file://")) return rel;
|
|
408
|
+
if (path.isAbsolute(rel)) return pathToFileURL(rel).href;
|
|
409
|
+
|
|
410
|
+
return resolveWith(
|
|
411
|
+
rel,
|
|
412
|
+
// makePrimary (URL)
|
|
413
|
+
(baseFile, r) => new URL(r, pathToFileURL(baseFile)).href,
|
|
414
|
+
// exists (URL→check target path)
|
|
415
|
+
(href) => fs.existsSync(fileURLToPath(href)),
|
|
416
|
+
// makeFallback (URL)
|
|
417
|
+
(baseFile, r) => new URL(r, pathToFileURL(baseFile)).href
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* @typedef {object} CallSite
|
|
423
|
+
* @property {function(): string|undefined} getFileName
|
|
424
|
+
* @property {function(): number|undefined} getLineNumber
|
|
425
|
+
* @property {function(): string|undefined} getFunctionName
|
|
426
|
+
* @property {function(): string|undefined} getTypeName
|
|
427
|
+
* @property {function(): string|undefined} getMethodName
|
|
428
|
+
* @property {function(): string|undefined} getScriptNameOrSourceURL
|
|
429
|
+
* @property {function(): number|undefined} getColumnNumber
|
|
430
|
+
* @property {function(): boolean|undefined} isNative
|
|
431
|
+
* @property {function(): boolean|undefined} isEval
|
|
432
|
+
* @property {function(): boolean|undefined} isConstructor
|
|
433
|
+
* @property {function(): boolean|undefined} isToplevel
|
|
434
|
+
* @property {function(): boolean|undefined} isAsync
|
|
435
|
+
* @property {function(): boolean|undefined} isPromiseAll
|
|
436
|
+
* @property {function(): number|undefined} getPromiseIndex
|
|
437
|
+
*
|
|
438
|
+
* @description
|
|
439
|
+
* Minimal V8 CallSite object type for stack trace analysis. Only includes methods used in this module.
|
|
440
|
+
*/
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @function getStack
|
|
3
|
+
* @package
|
|
4
|
+
* @internal
|
|
5
|
+
* @param {Function} [skipFn] - Function to skip in stack trace via Error.captureStackTrace
|
|
6
|
+
* @returns {Array<CallSite>} Array of V8 CallSite objects with methods like getFileName(), getLineNumber(), etc.
|
|
7
|
+
*
|
|
8
|
+
* @description
|
|
9
|
+
* Get V8 stack trace as CallSite array for debugging and caller detection.
|
|
10
|
+
* Temporarily overrides Error.prepareStackTrace to access raw V8 CallSite objects
|
|
11
|
+
* instead of formatted string stack traces. Provides safe restoration of original handler.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* // Get current call stack for debugging
|
|
15
|
+
* const stack = getStack();
|
|
16
|
+
* console.log(stack[0]?.getFileName?.()); // Current file path
|
|
17
|
+
* console.log(stack[0]?.getLineNumber?.()); // Current line number
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* // Skip current function from stack trace
|
|
21
|
+
* function myFunction() {
|
|
22
|
+
* return getStack(myFunction); // Stack starts from caller of myFunction
|
|
23
|
+
* }
|
|
24
|
+
* const callerStack = myFunction();
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* // Stack analysis for caller detection
|
|
28
|
+
* function findCaller() {
|
|
29
|
+
* const stack = getStack(findCaller);
|
|
30
|
+
* for (const frame of stack) {
|
|
31
|
+
* const filename = frame?.getFileName?.();
|
|
32
|
+
* if (filename && !filename.includes("node_modules")) {
|
|
33
|
+
* return filename; // First non-dependency file
|
|
34
|
+
* }
|
|
35
|
+
* }
|
|
36
|
+
* }
|
|
37
|
+
*/
|
|
38
|
+
export function getStack(skipFn?: Function): Array<CallSite>;
|
|
39
|
+
/**
|
|
40
|
+
* @function resolvePathFromCaller
|
|
41
|
+
* @package
|
|
42
|
+
* @internal
|
|
43
|
+
* @param {string} rel - Relative path to resolve (e.g., "../config.json", "./data/file.txt")
|
|
44
|
+
* @returns {string} Absolute filesystem path with platform-specific separators
|
|
45
|
+
* @throws {TypeError} When rel parameter is not a string
|
|
46
|
+
*
|
|
47
|
+
* @description
|
|
48
|
+
* Resolve a relative path from the caller's context to an absolute filesystem path.
|
|
49
|
+
* Primary public API for filesystem path resolution with intelligent caller detection.
|
|
50
|
+
* Uses sophisticated stack trace analysis to determine the appropriate base directory.
|
|
51
|
+
*
|
|
52
|
+
* Resolution behavior:
|
|
53
|
+
* - file:// URLs: Converted to filesystem paths via fileURLToPath()
|
|
54
|
+
* - Absolute paths: Returned unchanged (already absolute)
|
|
55
|
+
* - Relative paths: Resolved using caller detection algorithm
|
|
56
|
+
*
|
|
57
|
+
* Caller detection process:
|
|
58
|
+
* 1. Primary: Use sophisticated slothlet.mjs-aware stack analysis
|
|
59
|
+
* 2. Fallback: Use first non-helper frame if primary fails
|
|
60
|
+
* 3. Existence check: Prefer primary if target exists, otherwise use fallback
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* // From a file at /project/src/modules/math.mjs
|
|
64
|
+
* const configPath = resolvePathFromCaller("../config.json");
|
|
65
|
+
* // Returns: /project/config.json (absolute filesystem path)
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* // Short-circuit cases
|
|
69
|
+
* resolvePathFromCaller("file:///absolute/path.txt");
|
|
70
|
+
* // Returns: /absolute/path.txt (converted from URL)
|
|
71
|
+
*
|
|
72
|
+
* resolvePathFromCaller("/already/absolute/path.txt");
|
|
73
|
+
* // Returns: /already/absolute/path.txt (unchanged)
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* // Relative resolution from different contexts
|
|
77
|
+
* // If called from /project/src/lib/utils.mjs:
|
|
78
|
+
* resolvePathFromCaller("./helpers/format.js");
|
|
79
|
+
* // Returns: /project/src/lib/helpers/format.js
|
|
80
|
+
*
|
|
81
|
+
* resolvePathFromCaller("../../config/settings.json");
|
|
82
|
+
* // Returns: /project/config/settings.json
|
|
83
|
+
*/
|
|
84
|
+
export function resolvePathFromCaller(rel: string): string;
|
|
85
|
+
/**
|
|
86
|
+
* @function resolveUrlFromCaller
|
|
87
|
+
* @package
|
|
88
|
+
* @internal
|
|
89
|
+
* @param {string} rel - Relative path to resolve (e.g., "../config.json", "./data/file.txt")
|
|
90
|
+
* @returns {string} Absolute file:// URL suitable for dynamic imports and URL operations
|
|
91
|
+
* @throws {TypeError} When rel parameter is not a string
|
|
92
|
+
*
|
|
93
|
+
* @description
|
|
94
|
+
* Resolve a relative path from the caller's context to a file:// URL.
|
|
95
|
+
* Companion API to resolvePathFromCaller that returns file:// URLs instead of filesystem paths.
|
|
96
|
+
* Uses identical caller detection algorithm but outputs URL format for ESM imports.
|
|
97
|
+
*
|
|
98
|
+
* Resolution behavior:
|
|
99
|
+
* - file:// URLs: Returned unchanged (already in URL format)
|
|
100
|
+
* - Absolute paths: Converted to file:// URLs via pathToFileURL()
|
|
101
|
+
* - Relative paths: Resolved using caller detection, then converted to URL
|
|
102
|
+
*
|
|
103
|
+
* Caller detection uses the same sophisticated algorithm as resolvePathFromCaller,
|
|
104
|
+
* but the final result is converted to a file:// URL for compatibility with
|
|
105
|
+
* ESM dynamic imports and other URL-based operations.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* // From a file at /project/src/modules/math.mjs
|
|
109
|
+
* const configUrl = resolveUrlFromCaller("../config.json");
|
|
110
|
+
* // Returns: file:///project/config.json (absolute file:// URL)
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* // Short-circuit cases
|
|
114
|
+
* resolveUrlFromCaller("file:///absolute/path.txt");
|
|
115
|
+
* // Returns: file:///absolute/path.txt (unchanged)
|
|
116
|
+
*
|
|
117
|
+
* resolveUrlFromCaller("/already/absolute/path.txt");
|
|
118
|
+
* // Returns: file:///already/absolute/path.txt (converted to URL)
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* // Dynamic ESM import usage
|
|
122
|
+
* const modulePath = resolveUrlFromCaller("./dynamic-module.mjs");
|
|
123
|
+
* const dynamicModule = await import(modulePath);
|
|
124
|
+
* // Works seamlessly with ESM import() which expects URLs
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* // Cross-platform URL handling
|
|
128
|
+
* // Unix: resolveUrlFromCaller("../config.json") → file:///project/config.json
|
|
129
|
+
* // Windows: resolveUrlFromCaller("../config.json") → file:///C:/project/config.json
|
|
130
|
+
*/
|
|
131
|
+
export function resolveUrlFromCaller(rel: string): string;
|
|
132
|
+
export function toFsPath(v: any): string | null;
|
|
133
|
+
export type CallSite = {
|
|
134
|
+
getFileName: () => string | undefined;
|
|
135
|
+
getLineNumber: () => number | undefined;
|
|
136
|
+
getFunctionName: () => string | undefined;
|
|
137
|
+
getTypeName: () => string | undefined;
|
|
138
|
+
getMethodName: () => string | undefined;
|
|
139
|
+
getScriptNameOrSourceURL: () => string | undefined;
|
|
140
|
+
getColumnNumber: () => number | undefined;
|
|
141
|
+
isNative: () => boolean | undefined;
|
|
142
|
+
isEval: () => boolean | undefined;
|
|
143
|
+
isConstructor: () => boolean | undefined;
|
|
144
|
+
isToplevel: () => boolean | undefined;
|
|
145
|
+
isAsync: () => boolean | undefined;
|
|
146
|
+
isPromiseAll: () => boolean | undefined;
|
|
147
|
+
getPromiseIndex: () => number | undefined;
|
|
148
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asynchronously loads JSON from a file, trying modern import with 'with', then 'assert', then fs.
|
|
3
|
+
* @public
|
|
4
|
+
* @param {string|URL} input - The path or URL to the JSON file.
|
|
5
|
+
* @param {Object} [options] - Options object.
|
|
6
|
+
* @param {string|URL} [options.base] - Base URL for relative paths.
|
|
7
|
+
* @param {Function} [options.validate] - Validation function for the parsed JSON.
|
|
8
|
+
* @param {Function} [options.reviver] - Reviver function for JSON.parse.
|
|
9
|
+
* @param {string} [options.type='json'] - The type of the file being imported. Defaults to 'json'.
|
|
10
|
+
* @param {string|URL} [options.fallback] - Fallback path or URL if the primary file is missing.
|
|
11
|
+
* @returns {Promise<*>} The parsed JSON value.
|
|
12
|
+
*
|
|
13
|
+
* @description
|
|
14
|
+
* Loads JSON asynchronously with version-agnostic support.
|
|
15
|
+
* Attempts modern import syntax first, falls back to legacy assert, then fs.readFile.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* import { wisp } from '@cldmv/wisp';
|
|
19
|
+
* const data = await wisp('./config.json');
|
|
20
|
+
*/
|
|
21
|
+
export function wisp(input: string | URL, options?: {
|
|
22
|
+
base?: string | URL;
|
|
23
|
+
validate?: Function;
|
|
24
|
+
reviver?: Function;
|
|
25
|
+
type?: string;
|
|
26
|
+
fallback?: string | URL;
|
|
27
|
+
}): Promise<any>;
|
|
28
|
+
/**
|
|
29
|
+
* Synchronously loads JSON from a file using fs.
|
|
30
|
+
* @public
|
|
31
|
+
* @param {string|URL} input - The path or URL to the JSON file.
|
|
32
|
+
* @param {Object} [options] - Options object.
|
|
33
|
+
* @param {string|URL} [options.base] - Base URL for relative paths.
|
|
34
|
+
* @param {Function} [options.validate] - Validation function for the parsed JSON.
|
|
35
|
+
* @param {Function} [options.reviver] - Reviver function for JSON.parse.
|
|
36
|
+
* @param {string} [options.type='json'] - The type of the file being imported. Defaults to 'json'.
|
|
37
|
+
* @param {string|URL} [options.fallback] - Fallback path or URL if the primary file is missing.
|
|
38
|
+
* @returns {*} The parsed JSON value.
|
|
39
|
+
*
|
|
40
|
+
* @description
|
|
41
|
+
* Loads JSON synchronously using fs.readFileSync.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* import { wispSync } from '@cldmv/wisp';
|
|
45
|
+
* const data = wispSync('./config.json');
|
|
46
|
+
*/
|
|
47
|
+
export function wispSync(input: string | URL, options?: {
|
|
48
|
+
base?: string | URL;
|
|
49
|
+
validate?: Function;
|
|
50
|
+
reviver?: Function;
|
|
51
|
+
type?: string;
|
|
52
|
+
fallback?: string | URL;
|
|
53
|
+
}): any;
|
|
54
|
+
export default wisp;
|
package/src/wisp.mjs
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Project: @cldmv/wisp
|
|
3
|
+
* @Filename: /src/wisp.mjs
|
|
4
|
+
* @Date: 2025-10-30 14:12:05 -07:00 (1761858725)
|
|
5
|
+
* @Author: Nate Hyson <CLDMV>
|
|
6
|
+
* @Email: <Shinrai@users.noreply.github.com>
|
|
7
|
+
* -----
|
|
8
|
+
* @Last modified by: Nate Hyson <CLDMV> (Shinrai@users.noreply.github.com)
|
|
9
|
+
* @Last modified time: 2025-10-30 14:59:15 -07:00 (1761861555)
|
|
10
|
+
* -----
|
|
11
|
+
* @Copyright: Copyright (c) 2013-2025 Catalyzed Motivation Inc. All rights reserved.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @fileoverview Internal implementation of @cldmv/wisp. Not exported in package.json.
|
|
16
|
+
* @module @cldmv/wisp.src.wisp
|
|
17
|
+
* @internal
|
|
18
|
+
* @private
|
|
19
|
+
*
|
|
20
|
+
* @description
|
|
21
|
+
* This module provides the core implementation for version-agnostic JSON importing in Node.js.
|
|
22
|
+
* It includes functions to load JSON asynchronously and synchronously, with fallbacks for different Node versions.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* // Internal usage example
|
|
26
|
+
* import { wisp } from './src/wisp.mjs';
|
|
27
|
+
* const data = await wisp('./data.json');
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import fs from "node:fs";
|
|
31
|
+
import { readFile } from "node:fs/promises";
|
|
32
|
+
import path from "node:path";
|
|
33
|
+
import { pathToFileURL } from "node:url";
|
|
34
|
+
import { resolveUrlFromCaller } from "./lib/resolve-from-caller.mjs";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Deep clones a value using structuredClone if available, otherwise JSON.parse/stringify.
|
|
38
|
+
* @private
|
|
39
|
+
* @param {*} v - The value to clone.
|
|
40
|
+
* @returns {*} The cloned value.
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* Deep clones a value using structuredClone if available, otherwise JSON.parse/stringify.
|
|
44
|
+
* @private
|
|
45
|
+
* @param {*} v - The value to clone.
|
|
46
|
+
* @returns {*} The cloned value.
|
|
47
|
+
*/
|
|
48
|
+
function deepClone(v) {
|
|
49
|
+
return typeof globalThis.structuredClone === "function" ? globalThis.structuredClone(v) : JSON.parse(JSON.stringify(v));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Asynchronously loads JSON from a file, trying modern import with 'with', then 'assert', then fs.
|
|
54
|
+
* @public
|
|
55
|
+
* @param {string|URL} input - The path or URL to the JSON file.
|
|
56
|
+
* @param {Object} [options] - Options object.
|
|
57
|
+
* @param {string|URL} [options.base] - Base URL for relative paths.
|
|
58
|
+
* @param {(val: any) => void} [options.validate] - Validation function for the parsed JSON.
|
|
59
|
+
* @param {(this: any, key: string, value: any) => any} [options.reviver] - Reviver function for JSON.parse.
|
|
60
|
+
* @param {string} [options.type='json'] - The type of the file being imported. Defaults to 'json'.
|
|
61
|
+
* @param {string|URL} [options.fallback] - Fallback path or URL if the primary file is missing.
|
|
62
|
+
* @returns {Promise<*>} The parsed JSON value.
|
|
63
|
+
*
|
|
64
|
+
* @description
|
|
65
|
+
* Loads JSON asynchronously with version-agnostic support.
|
|
66
|
+
* Attempts modern import syntax first, falls back to legacy assert, then fs.readFile.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* import { wisp } from '@cldmv/wisp';
|
|
70
|
+
* const data = await wisp('./config.json');
|
|
71
|
+
*/
|
|
72
|
+
export async function wisp(input, options = {}) {
|
|
73
|
+
const { base, validate, reviver, type = "json", fallback } = options;
|
|
74
|
+
let url;
|
|
75
|
+
if (input instanceof URL) url = input;
|
|
76
|
+
else {
|
|
77
|
+
const s = String(input);
|
|
78
|
+
if (s.startsWith("file://")) url = new URL(s);
|
|
79
|
+
else if (path.isAbsolute(s)) url = pathToFileURL(s);
|
|
80
|
+
else if (base) url = new URL(s, new URL(base));
|
|
81
|
+
else url = new URL(resolveUrlFromCaller(s));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const mod = await import(url.href, { with: { type } });
|
|
86
|
+
if (!reviver && !validate) return mod?.default ?? mod;
|
|
87
|
+
let val = deepClone(mod?.default ?? mod);
|
|
88
|
+
if (reviver) val = JSON.parse(JSON.stringify(val), reviver);
|
|
89
|
+
if (validate) {
|
|
90
|
+
try {
|
|
91
|
+
validate(val);
|
|
92
|
+
} catch (e) {
|
|
93
|
+
throw new Error(`@cldmv/wisp: ${e?.message ?? e}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return val;
|
|
97
|
+
} catch {}
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const mod = await import(url.href, { assert: { type } });
|
|
101
|
+
if (!reviver && !validate) return mod?.default ?? mod;
|
|
102
|
+
let val = deepClone(mod?.default ?? mod);
|
|
103
|
+
if (reviver) val = JSON.parse(JSON.stringify(val), reviver);
|
|
104
|
+
if (validate) {
|
|
105
|
+
try {
|
|
106
|
+
validate(val);
|
|
107
|
+
} catch (e) {
|
|
108
|
+
throw new Error(`@cldmv/wisp: ${e?.message ?? e}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return val;
|
|
112
|
+
} catch {}
|
|
113
|
+
|
|
114
|
+
if (type === "json") {
|
|
115
|
+
try {
|
|
116
|
+
const txt = await readFile(url, "utf8");
|
|
117
|
+
const val = deepClone(JSON.parse(txt, reviver));
|
|
118
|
+
if (validate) {
|
|
119
|
+
try {
|
|
120
|
+
validate(val);
|
|
121
|
+
} catch (e) {
|
|
122
|
+
throw new Error(`@cldmv/wisp: ${e?.message ?? e}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return val;
|
|
126
|
+
} catch (e) {
|
|
127
|
+
if (fallback) {
|
|
128
|
+
return wisp(fallback, options);
|
|
129
|
+
}
|
|
130
|
+
throw new Error(`@cldmv/wisp: Failed to load JSON file at ${url.href}: ${e.message}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
throw new Error(`@cldmv/wisp: Unsupported type '${type}' or failed to load module at ${url.href}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Synchronously loads JSON from a file using fs.
|
|
139
|
+
* @public
|
|
140
|
+
* @param {string|URL} input - The path or URL to the JSON file.
|
|
141
|
+
* @param {Object} [options] - Options object.
|
|
142
|
+
* @param {string|URL} [options.base] - Base URL for relative paths.
|
|
143
|
+
* @param {(val: any) => void} [options.validate] - Validation function for the parsed JSON.
|
|
144
|
+
* @param {(this: any, key: string, value: any) => any} [options.reviver] - Reviver function for JSON.parse.
|
|
145
|
+
* @param {string} [options.type='json'] - The type of the file being imported. Defaults to 'json'.
|
|
146
|
+
* @param {string|URL} [options.fallback] - Fallback path or URL if the primary file is missing.
|
|
147
|
+
* @returns {*} The parsed JSON value.
|
|
148
|
+
*
|
|
149
|
+
* @description
|
|
150
|
+
* Loads JSON synchronously using fs.readFileSync.
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* import { wispSync } from '@cldmv/wisp';
|
|
154
|
+
* const data = wispSync('./config.json');
|
|
155
|
+
*/
|
|
156
|
+
export function wispSync(input, options = {}) {
|
|
157
|
+
const { base, validate, reviver, type = "json", fallback } = options;
|
|
158
|
+
let url;
|
|
159
|
+
if (input instanceof URL) url = input;
|
|
160
|
+
else {
|
|
161
|
+
const s = String(input);
|
|
162
|
+
if (s.startsWith("file://")) url = new URL(s);
|
|
163
|
+
else if (path.isAbsolute(s)) url = pathToFileURL(s);
|
|
164
|
+
else if (base) url = new URL(s, new URL(base));
|
|
165
|
+
else url = new URL(resolveUrlFromCaller(s));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const txt = fs.readFileSync(url, "utf8");
|
|
170
|
+
const val = deepClone(JSON.parse(txt, reviver));
|
|
171
|
+
if (validate) {
|
|
172
|
+
try {
|
|
173
|
+
validate(val);
|
|
174
|
+
} catch (e) {
|
|
175
|
+
throw new Error(`@cldmv/wisp: ${e?.message ?? e}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return val;
|
|
179
|
+
} catch (e) {
|
|
180
|
+
if (fallback) {
|
|
181
|
+
return wispSync(fallback, options);
|
|
182
|
+
}
|
|
183
|
+
throw new Error(`@cldmv/wisp: Failed to load JSON file at ${url.href}: ${e.message}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export default wisp;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @function getStack
|
|
3
|
+
* @package
|
|
4
|
+
* @internal
|
|
5
|
+
* @param {Function} [skipFn] - Function to skip in stack trace via Error.captureStackTrace
|
|
6
|
+
* @returns {Array<CallSite>} Array of V8 CallSite objects with methods like getFileName(), getLineNumber(), etc.
|
|
7
|
+
*
|
|
8
|
+
* @description
|
|
9
|
+
* Get V8 stack trace as CallSite array for debugging and caller detection.
|
|
10
|
+
* Temporarily overrides Error.prepareStackTrace to access raw V8 CallSite objects
|
|
11
|
+
* instead of formatted string stack traces. Provides safe restoration of original handler.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* // Get current call stack for debugging
|
|
15
|
+
* const stack = getStack();
|
|
16
|
+
* console.log(stack[0]?.getFileName?.()); // Current file path
|
|
17
|
+
* console.log(stack[0]?.getLineNumber?.()); // Current line number
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* // Skip current function from stack trace
|
|
21
|
+
* function myFunction() {
|
|
22
|
+
* return getStack(myFunction); // Stack starts from caller of myFunction
|
|
23
|
+
* }
|
|
24
|
+
* const callerStack = myFunction();
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* // Stack analysis for caller detection
|
|
28
|
+
* function findCaller() {
|
|
29
|
+
* const stack = getStack(findCaller);
|
|
30
|
+
* for (const frame of stack) {
|
|
31
|
+
* const filename = frame?.getFileName?.();
|
|
32
|
+
* if (filename && !filename.includes("node_modules")) {
|
|
33
|
+
* return filename; // First non-dependency file
|
|
34
|
+
* }
|
|
35
|
+
* }
|
|
36
|
+
* }
|
|
37
|
+
*/
|
|
38
|
+
export function getStack(skipFn?: Function): Array<CallSite>;
|
|
39
|
+
/**
|
|
40
|
+
* @function resolvePathFromCaller
|
|
41
|
+
* @package
|
|
42
|
+
* @internal
|
|
43
|
+
* @param {string} rel - Relative path to resolve (e.g., "../config.json", "./data/file.txt")
|
|
44
|
+
* @returns {string} Absolute filesystem path with platform-specific separators
|
|
45
|
+
* @throws {TypeError} When rel parameter is not a string
|
|
46
|
+
*
|
|
47
|
+
* @description
|
|
48
|
+
* Resolve a relative path from the caller's context to an absolute filesystem path.
|
|
49
|
+
* Primary public API for filesystem path resolution with intelligent caller detection.
|
|
50
|
+
* Uses sophisticated stack trace analysis to determine the appropriate base directory.
|
|
51
|
+
*
|
|
52
|
+
* Resolution behavior:
|
|
53
|
+
* - file:// URLs: Converted to filesystem paths via fileURLToPath()
|
|
54
|
+
* - Absolute paths: Returned unchanged (already absolute)
|
|
55
|
+
* - Relative paths: Resolved using caller detection algorithm
|
|
56
|
+
*
|
|
57
|
+
* Caller detection process:
|
|
58
|
+
* 1. Primary: Use sophisticated slothlet.mjs-aware stack analysis
|
|
59
|
+
* 2. Fallback: Use first non-helper frame if primary fails
|
|
60
|
+
* 3. Existence check: Prefer primary if target exists, otherwise use fallback
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* // From a file at /project/src/modules/math.mjs
|
|
64
|
+
* const configPath = resolvePathFromCaller("../config.json");
|
|
65
|
+
* // Returns: /project/config.json (absolute filesystem path)
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* // Short-circuit cases
|
|
69
|
+
* resolvePathFromCaller("file:///absolute/path.txt");
|
|
70
|
+
* // Returns: /absolute/path.txt (converted from URL)
|
|
71
|
+
*
|
|
72
|
+
* resolvePathFromCaller("/already/absolute/path.txt");
|
|
73
|
+
* // Returns: /already/absolute/path.txt (unchanged)
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* // Relative resolution from different contexts
|
|
77
|
+
* // If called from /project/src/lib/utils.mjs:
|
|
78
|
+
* resolvePathFromCaller("./helpers/format.js");
|
|
79
|
+
* // Returns: /project/src/lib/helpers/format.js
|
|
80
|
+
*
|
|
81
|
+
* resolvePathFromCaller("../../config/settings.json");
|
|
82
|
+
* // Returns: /project/config/settings.json
|
|
83
|
+
*/
|
|
84
|
+
export function resolvePathFromCaller(rel: string): string;
|
|
85
|
+
/**
|
|
86
|
+
* @function resolveUrlFromCaller
|
|
87
|
+
* @package
|
|
88
|
+
* @internal
|
|
89
|
+
* @param {string} rel - Relative path to resolve (e.g., "../config.json", "./data/file.txt")
|
|
90
|
+
* @returns {string} Absolute file:// URL suitable for dynamic imports and URL operations
|
|
91
|
+
* @throws {TypeError} When rel parameter is not a string
|
|
92
|
+
*
|
|
93
|
+
* @description
|
|
94
|
+
* Resolve a relative path from the caller's context to a file:// URL.
|
|
95
|
+
* Companion API to resolvePathFromCaller that returns file:// URLs instead of filesystem paths.
|
|
96
|
+
* Uses identical caller detection algorithm but outputs URL format for ESM imports.
|
|
97
|
+
*
|
|
98
|
+
* Resolution behavior:
|
|
99
|
+
* - file:// URLs: Returned unchanged (already in URL format)
|
|
100
|
+
* - Absolute paths: Converted to file:// URLs via pathToFileURL()
|
|
101
|
+
* - Relative paths: Resolved using caller detection, then converted to URL
|
|
102
|
+
*
|
|
103
|
+
* Caller detection uses the same sophisticated algorithm as resolvePathFromCaller,
|
|
104
|
+
* but the final result is converted to a file:// URL for compatibility with
|
|
105
|
+
* ESM dynamic imports and other URL-based operations.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* // From a file at /project/src/modules/math.mjs
|
|
109
|
+
* const configUrl = resolveUrlFromCaller("../config.json");
|
|
110
|
+
* // Returns: file:///project/config.json (absolute file:// URL)
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* // Short-circuit cases
|
|
114
|
+
* resolveUrlFromCaller("file:///absolute/path.txt");
|
|
115
|
+
* // Returns: file:///absolute/path.txt (unchanged)
|
|
116
|
+
*
|
|
117
|
+
* resolveUrlFromCaller("/already/absolute/path.txt");
|
|
118
|
+
* // Returns: file:///already/absolute/path.txt (converted to URL)
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* // Dynamic ESM import usage
|
|
122
|
+
* const modulePath = resolveUrlFromCaller("./dynamic-module.mjs");
|
|
123
|
+
* const dynamicModule = await import(modulePath);
|
|
124
|
+
* // Works seamlessly with ESM import() which expects URLs
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* // Cross-platform URL handling
|
|
128
|
+
* // Unix: resolveUrlFromCaller("../config.json") → file:///project/config.json
|
|
129
|
+
* // Windows: resolveUrlFromCaller("../config.json") → file:///C:/project/config.json
|
|
130
|
+
*/
|
|
131
|
+
export function resolveUrlFromCaller(rel: string): string;
|
|
132
|
+
export function toFsPath(v: any): string | null;
|
|
133
|
+
export type CallSite = {
|
|
134
|
+
getFileName: () => string | undefined;
|
|
135
|
+
getLineNumber: () => number | undefined;
|
|
136
|
+
getFunctionName: () => string | undefined;
|
|
137
|
+
getTypeName: () => string | undefined;
|
|
138
|
+
getMethodName: () => string | undefined;
|
|
139
|
+
getScriptNameOrSourceURL: () => string | undefined;
|
|
140
|
+
getColumnNumber: () => number | undefined;
|
|
141
|
+
isNative: () => boolean | undefined;
|
|
142
|
+
isEval: () => boolean | undefined;
|
|
143
|
+
isConstructor: () => boolean | undefined;
|
|
144
|
+
isToplevel: () => boolean | undefined;
|
|
145
|
+
isAsync: () => boolean | undefined;
|
|
146
|
+
isPromiseAll: () => boolean | undefined;
|
|
147
|
+
getPromiseIndex: () => number | undefined;
|
|
148
|
+
};
|
|
149
|
+
//# sourceMappingURL=resolve-from-caller.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-from-caller.d.mts","sourceRoot":"","sources":["../../src/lib/resolve-from-caller.mjs"],"names":[],"mappings":"AAqFA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,6CAhCa,KAAK,CAAC,QAAQ,CAAC,CA0C3B;AAqKD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,2CAzCW,MAAM,GACJ,MAAM,CAsDlB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,0CA1CW,MAAM,GACJ,MAAM,CAuDlB;AA/UM,4BAvBI,GAAG,GACD,MAAM,GAAC,IAAI,CAsB+F;;iBAmVzG,MAAY,MAAM,GAAC,SAAS;mBAC5B,MAAY,MAAM,GAAC,SAAS;qBAC5B,MAAY,MAAM,GAAC,SAAS;iBAC5B,MAAY,MAAM,GAAC,SAAS;mBAC5B,MAAY,MAAM,GAAC,SAAS;8BAC5B,MAAY,MAAM,GAAC,SAAS;qBAC5B,MAAY,MAAM,GAAC,SAAS;cAC5B,MAAY,OAAO,GAAC,SAAS;YAC7B,MAAY,OAAO,GAAC,SAAS;mBAC7B,MAAY,OAAO,GAAC,SAAS;gBAC7B,MAAY,OAAO,GAAC,SAAS;aAC7B,MAAY,OAAO,GAAC,SAAS;kBAC7B,MAAY,OAAO,GAAC,SAAS;qBAC7B,MAAY,MAAM,GAAC,SAAS"}
|
package/types/wisp.d.mts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asynchronously loads JSON from a file, trying modern import with 'with', then 'assert', then fs.
|
|
3
|
+
* @public
|
|
4
|
+
* @param {string|URL} input - The path or URL to the JSON file.
|
|
5
|
+
* @param {Object} [options] - Options object.
|
|
6
|
+
* @param {string|URL} [options.base] - Base URL for relative paths.
|
|
7
|
+
* @param {(val: any) => void} [options.validate] - Validation function for the parsed JSON.
|
|
8
|
+
* @param {(this: any, key: string, value: any) => any} [options.reviver] - Reviver function for JSON.parse.
|
|
9
|
+
* @param {string} [options.type='json'] - The type of the file being imported. Defaults to 'json'.
|
|
10
|
+
* @param {string|URL} [options.fallback] - Fallback path or URL if the primary file is missing.
|
|
11
|
+
* @returns {Promise<*>} The parsed JSON value.
|
|
12
|
+
*
|
|
13
|
+
* @description
|
|
14
|
+
* Loads JSON asynchronously with version-agnostic support.
|
|
15
|
+
* Attempts modern import syntax first, falls back to legacy assert, then fs.readFile.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* import { wisp } from '@cldmv/wisp';
|
|
19
|
+
* const data = await wisp('./config.json');
|
|
20
|
+
*/
|
|
21
|
+
export function wisp(input: string | URL, options?: {
|
|
22
|
+
base?: string | URL;
|
|
23
|
+
validate?: (val: any) => void;
|
|
24
|
+
reviver?: (this: any, key: string, value: any) => any;
|
|
25
|
+
type?: string;
|
|
26
|
+
fallback?: string | URL;
|
|
27
|
+
}): Promise<any>;
|
|
28
|
+
/**
|
|
29
|
+
* Synchronously loads JSON from a file using fs.
|
|
30
|
+
* @public
|
|
31
|
+
* @param {string|URL} input - The path or URL to the JSON file.
|
|
32
|
+
* @param {Object} [options] - Options object.
|
|
33
|
+
* @param {string|URL} [options.base] - Base URL for relative paths.
|
|
34
|
+
* @param {(val: any) => void} [options.validate] - Validation function for the parsed JSON.
|
|
35
|
+
* @param {(this: any, key: string, value: any) => any} [options.reviver] - Reviver function for JSON.parse.
|
|
36
|
+
* @param {string} [options.type='json'] - The type of the file being imported. Defaults to 'json'.
|
|
37
|
+
* @param {string|URL} [options.fallback] - Fallback path or URL if the primary file is missing.
|
|
38
|
+
* @returns {*} The parsed JSON value.
|
|
39
|
+
*
|
|
40
|
+
* @description
|
|
41
|
+
* Loads JSON synchronously using fs.readFileSync.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* import { wispSync } from '@cldmv/wisp';
|
|
45
|
+
* const data = wispSync('./config.json');
|
|
46
|
+
*/
|
|
47
|
+
export function wispSync(input: string | URL, options?: {
|
|
48
|
+
base?: string | URL;
|
|
49
|
+
validate?: (val: any) => void;
|
|
50
|
+
reviver?: (this: any, key: string, value: any) => any;
|
|
51
|
+
type?: string;
|
|
52
|
+
fallback?: string | URL;
|
|
53
|
+
}): any;
|
|
54
|
+
export default wisp;
|
|
55
|
+
//# sourceMappingURL=wisp.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wisp.d.mts","sourceRoot":"","sources":["../src/wisp.mjs"],"names":[],"mappings":"AAmDA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,4BAjBW,MAAM,GAAC,GAAG,YAElB;IAA6B,IAAI,GAAzB,MAAM,GAAC,GAAG;IACmB,QAAQ,GAArC,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI;IACoC,OAAO,GAA7D,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,GAAG;IAC1B,IAAI,GAArB,MAAM;IACe,QAAQ,GAA7B,MAAM,GAAC,GAAG;CAClB,GAAU,OAAO,CAAC,GAAC,CAAC,CAyEtB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,gCAhBW,MAAM,GAAC,GAAG,YAElB;IAA6B,IAAI,GAAzB,MAAM,GAAC,GAAG;IACmB,QAAQ,GAArC,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI;IACoC,OAAO,GAA7D,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,GAAG;IAC1B,IAAI,GAArB,MAAM;IACe,QAAQ,GAA7B,MAAM,GAAC,GAAG;CAClB,GAAU,GAAC,CAsCb"}
|