locter 2.2.1 → 4.0.0-beta.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 +581 -44
- package/dist/index.d.mts +563 -0
- package/dist/index.mjs +1117 -671
- package/dist/index.mjs.map +1 -1
- package/package.json +36 -40
- package/dist/index.cjs +0 -743
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/loader/built-in/conf/index.d.ts +0 -1
- package/dist/loader/built-in/conf/module.d.ts +0 -6
- package/dist/loader/built-in/index.d.ts +0 -3
- package/dist/loader/built-in/json/index.d.ts +0 -1
- package/dist/loader/built-in/json/module.d.ts +0 -5
- package/dist/loader/built-in/module/index.d.ts +0 -3
- package/dist/loader/built-in/module/module.d.ts +0 -15
- package/dist/loader/built-in/module/type.d.ts +0 -8
- package/dist/loader/built-in/module/utils.d.ts +0 -9
- package/dist/loader/built-in/yaml/index.d.ts +0 -1
- package/dist/loader/built-in/yaml/module.d.ts +0 -5
- package/dist/loader/constants.d.ts +0 -6
- package/dist/loader/helpers.d.ts +0 -6
- package/dist/loader/index.d.ts +0 -3
- package/dist/loader/module.d.ts +0 -18
- package/dist/loader/singleton.d.ts +0 -2
- package/dist/loader/type.d.ts +0 -8
- package/dist/locator/async.d.ts +0 -3
- package/dist/locator/index.d.ts +0 -4
- package/dist/locator/sync.d.ts +0 -3
- package/dist/locator/types.d.ts +0 -15
- package/dist/locator/utils.d.ts +0 -7
- package/dist/utils/error.d.ts +0 -1
- package/dist/utils/file-name.d.ts +0 -2
- package/dist/utils/file-path.d.ts +0 -1
- package/dist/utils/has-property.d.ts +0 -2
- package/dist/utils/index.d.ts +0 -8
- package/dist/utils/object.d.ts +0 -2
- package/dist/utils/runtime.d.ts +0 -2
- package/dist/utils/to-array.d.ts +0 -1
- package/dist/utils/typescript.d.ts +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,718 +1,1164 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import fg from
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { BaseError, hasInstanceof, markInstanceof } from "@ebec/core";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import fg from "fast-glob";
|
|
5
|
+
import { destr } from "destr";
|
|
6
|
+
import { flatten, unflatten } from "flat";
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import { createJiti } from "jiti";
|
|
9
|
+
import { pathToFileURL } from "node:url";
|
|
10
|
+
import { isMap, isNode, isScalar, parse, parseDocument, stringify } from "yaml";
|
|
11
|
+
//#region src/errors/base.ts
|
|
12
|
+
const LOCTER_ERROR_MARKER = Symbol.for("@locter/error");
|
|
13
|
+
var LocterError = class extends BaseError {
|
|
14
|
+
static [Symbol.hasInstance](input) {
|
|
15
|
+
return hasInstanceof(input, LOCTER_ERROR_MARKER);
|
|
16
|
+
}
|
|
17
|
+
path;
|
|
18
|
+
constructor(input = {}) {
|
|
19
|
+
if (typeof input === "string") super(input);
|
|
20
|
+
else {
|
|
21
|
+
const { path, ...rest } = input;
|
|
22
|
+
super(rest);
|
|
23
|
+
this.path = path;
|
|
24
|
+
}
|
|
25
|
+
markInstanceof(this, LOCTER_ERROR_MARKER);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/errors/load.ts
|
|
30
|
+
const LOCTER_LOAD_ERROR_MARKER = Symbol.for("@locter/load-error");
|
|
31
|
+
var LocterLoadError = class extends LocterError {
|
|
32
|
+
static [Symbol.hasInstance](input) {
|
|
33
|
+
return hasInstanceof(input, LOCTER_LOAD_ERROR_MARKER);
|
|
34
|
+
}
|
|
35
|
+
constructor(input = {}) {
|
|
36
|
+
super(input);
|
|
37
|
+
markInstanceof(this, LOCTER_LOAD_ERROR_MARKER);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/errors/not-found.ts
|
|
42
|
+
const LOCTER_NOT_FOUND_ERROR_MARKER = Symbol.for("@locter/not-found-error");
|
|
43
|
+
var LocterNotFoundError = class extends LocterError {
|
|
44
|
+
static [Symbol.hasInstance](input) {
|
|
45
|
+
return hasInstanceof(input, LOCTER_NOT_FOUND_ERROR_MARKER);
|
|
46
|
+
}
|
|
47
|
+
constructor(input = {}) {
|
|
48
|
+
super(input);
|
|
49
|
+
markInstanceof(this, LOCTER_NOT_FOUND_ERROR_MARKER);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/errors/unknown-extension.ts
|
|
54
|
+
const LOCTER_UNKNOWN_EXTENSION_ERROR_MARKER = Symbol.for("@locter/unknown-extension-error");
|
|
55
|
+
var LocterUnknownExtensionError = class extends LocterError {
|
|
56
|
+
static [Symbol.hasInstance](input) {
|
|
57
|
+
return hasInstanceof(input, LOCTER_UNKNOWN_EXTENSION_ERROR_MARKER);
|
|
58
|
+
}
|
|
59
|
+
constructor(input = {}) {
|
|
60
|
+
super(input);
|
|
61
|
+
markInstanceof(this, LOCTER_UNKNOWN_EXTENSION_ERROR_MARKER);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region src/utils/has-property.ts
|
|
19
66
|
function hasOwnProperty(obj, prop) {
|
|
20
|
-
|
|
67
|
+
return Object.prototype.hasOwnProperty.call(obj, prop);
|
|
21
68
|
}
|
|
22
69
|
function hasStringProperty(obj, prop) {
|
|
23
|
-
|
|
70
|
+
return hasOwnProperty(obj, prop) && typeof obj[prop] === "string";
|
|
24
71
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
* For the full copyright and license information,
|
|
30
|
-
* view the LICENSE file that was distributed with this source code.
|
|
31
|
-
*/ function isObject(item) {
|
|
32
|
-
return !!item && typeof item === 'object' && !Array.isArray(item);
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/utils/object.ts
|
|
74
|
+
function isObject(item) {
|
|
75
|
+
return !!item && typeof item === "object" && !Array.isArray(item);
|
|
33
76
|
}
|
|
34
77
|
function isSafeObjectKey(key) {
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function handleException(e) {
|
|
39
|
-
if (e instanceof Error) {
|
|
40
|
-
throw e;
|
|
41
|
-
}
|
|
42
|
-
const error = new Error('Can not process thrown exception.');
|
|
43
|
-
if (isObject(e)) {
|
|
44
|
-
if (hasOwnProperty(e, 'message') && typeof e.message === 'string') {
|
|
45
|
-
error.message = e.message;
|
|
46
|
-
}
|
|
47
|
-
if (hasOwnProperty(e, 'stack') && typeof e.stack === 'string') {
|
|
48
|
-
error.stack = e.stack;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
throw error;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function getFileNameExtension(input, allowed) {
|
|
55
|
-
const extension = path.extname(input);
|
|
56
|
-
if (extension === '' || extension === '.') {
|
|
57
|
-
return undefined;
|
|
58
|
-
}
|
|
59
|
-
if (typeof allowed === 'undefined' || allowed.indexOf(extension) !== -1) {
|
|
60
|
-
return extension;
|
|
61
|
-
}
|
|
62
|
-
return undefined;
|
|
63
|
-
}
|
|
64
|
-
function removeFileNameExtension(input, extensions) {
|
|
65
|
-
const extension = getFileNameExtension(input, extensions);
|
|
66
|
-
if (extension) {
|
|
67
|
-
return input.substring(0, input.length - extension.length);
|
|
68
|
-
}
|
|
69
|
-
return input;
|
|
78
|
+
return key !== "__proto__" && key !== "prototype" && key !== "constructor";
|
|
70
79
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
* For the full copyright and license information,
|
|
76
|
-
* view the LICENSE file that was distributed with this source code.
|
|
77
|
-
*/ function toArray(input) {
|
|
78
|
-
return Array.isArray(input) ? input : [
|
|
79
|
-
input
|
|
80
|
-
];
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/utils/to-array.ts
|
|
82
|
+
function toArray(input) {
|
|
83
|
+
return Array.isArray(input) ? input : [input];
|
|
81
84
|
}
|
|
82
|
-
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region src/utils/file-path.ts
|
|
83
87
|
function isFilePath(input) {
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
const extension = path.extname(input);
|
|
89
|
+
return extension && extension !== "";
|
|
86
90
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return process.env && process.env.JEST_WORKER_ID !== undefined;
|
|
91
|
+
//#endregion
|
|
92
|
+
//#region src/utils/runtime.ts
|
|
93
|
+
function isJestRuntimeEnvironment() {
|
|
94
|
+
return process.env && process.env.JEST_WORKER_ID !== void 0;
|
|
95
|
+
}
|
|
96
|
+
function isVitestRuntimeEnvironment() {
|
|
97
|
+
return typeof process !== "undefined" && process.env.VITEST === "true";
|
|
95
98
|
}
|
|
96
99
|
function isTsNodeRuntimeEnvironment() {
|
|
97
|
-
|
|
98
|
-
// @ts-ignore
|
|
99
|
-
return !!process[Symbol.for('ts-node.register.instance')];
|
|
100
|
+
return !!process[Symbol.for("ts-node.register.instance")];
|
|
100
101
|
}
|
|
101
|
-
|
|
102
|
+
const TSX_MARKER_REGEX = /(?:^|=|[\\/])tsx(?:$|[\\/])/;
|
|
103
|
+
function isTsxRuntimeEnvironment() {
|
|
104
|
+
if (typeof process === "undefined") return false;
|
|
105
|
+
if (Array.isArray(process.execArgv) && process.execArgv.some((arg) => TSX_MARKER_REGEX.test(arg))) return true;
|
|
106
|
+
const { _preload_modules: preloadModules } = process;
|
|
107
|
+
return Array.isArray(preloadModules) && preloadModules.some((el) => TSX_MARKER_REGEX.test(el));
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region src/utils/typescript.ts
|
|
102
111
|
function isTypeScriptError(input) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (typeof input.diagnosticCodes !== 'undefined') {
|
|
107
|
-
return true;
|
|
108
|
-
}
|
|
109
|
-
return typeof input.message === 'string' && /TS\d+/.test(input.message);
|
|
112
|
+
if (!isObject(input)) return false;
|
|
113
|
+
if (typeof input.diagnosticCodes !== "undefined") return true;
|
|
114
|
+
return typeof input.message === "string" && /TS\d+/.test(input.message);
|
|
110
115
|
}
|
|
111
|
-
|
|
116
|
+
//#endregion
|
|
117
|
+
//#region src/errors/write.ts
|
|
118
|
+
const LOCTER_WRITE_ERROR_MARKER = Symbol.for("@locter/write-error");
|
|
119
|
+
var LocterWriteError = class extends LocterError {
|
|
120
|
+
static [Symbol.hasInstance](input) {
|
|
121
|
+
return hasInstanceof(input, LOCTER_WRITE_ERROR_MARKER);
|
|
122
|
+
}
|
|
123
|
+
constructor(input = {}) {
|
|
124
|
+
super(input);
|
|
125
|
+
markInstanceof(this, LOCTER_WRITE_ERROR_MARKER);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/errors/wrap.ts
|
|
130
|
+
const NOT_FOUND_CODES = /* @__PURE__ */ new Set([
|
|
131
|
+
"ENOENT",
|
|
132
|
+
"MODULE_NOT_FOUND",
|
|
133
|
+
"ERR_MODULE_NOT_FOUND"
|
|
134
|
+
]);
|
|
135
|
+
function wrapLoaderError(input, path) {
|
|
136
|
+
if (input instanceof LocterError) return input;
|
|
137
|
+
const code = isObject(input) && hasStringProperty(input, "code") ? input.code : void 0;
|
|
138
|
+
const message = isObject(input) && hasStringProperty(input, "message") ? input.message : `Failed to load: ${path}`;
|
|
139
|
+
if (code && NOT_FOUND_CODES.has(code)) return new LocterNotFoundError({
|
|
140
|
+
message,
|
|
141
|
+
code,
|
|
142
|
+
cause: input,
|
|
143
|
+
path
|
|
144
|
+
});
|
|
145
|
+
return new LocterLoadError({
|
|
146
|
+
message,
|
|
147
|
+
code,
|
|
148
|
+
cause: input,
|
|
149
|
+
path
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
function wrapWriteError(input, path) {
|
|
153
|
+
if (input instanceof LocterError) return input;
|
|
154
|
+
const code = isObject(input) && hasStringProperty(input, "code") ? input.code : void 0;
|
|
155
|
+
return new LocterWriteError({
|
|
156
|
+
message: isObject(input) && hasStringProperty(input, "message") ? input.message : `Failed to write: ${path}`,
|
|
157
|
+
code,
|
|
158
|
+
cause: input,
|
|
159
|
+
path
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
//#endregion
|
|
163
|
+
//#region src/utils/twin.ts
|
|
164
|
+
/**
|
|
165
|
+
* Perform one effect inside a twin body: `const x = yield* op(a, s)`.
|
|
166
|
+
*/
|
|
167
|
+
function* op(asyncFn, syncFn) {
|
|
168
|
+
return yield {
|
|
169
|
+
async: asyncFn,
|
|
170
|
+
sync: syncFn
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
async function runTwinAsync(body) {
|
|
174
|
+
let step = body.next();
|
|
175
|
+
while (!step.done) {
|
|
176
|
+
let result;
|
|
177
|
+
try {
|
|
178
|
+
result = await step.value.async();
|
|
179
|
+
} catch (e) {
|
|
180
|
+
step = body.throw(e);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
step = body.next(result);
|
|
184
|
+
}
|
|
185
|
+
return step.value;
|
|
186
|
+
}
|
|
187
|
+
function runTwinSync(body) {
|
|
188
|
+
let step = body.next();
|
|
189
|
+
while (!step.done) {
|
|
190
|
+
let result;
|
|
191
|
+
try {
|
|
192
|
+
result = step.value.sync();
|
|
193
|
+
} catch (e) {
|
|
194
|
+
step = body.throw(e);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
step = body.next(result);
|
|
198
|
+
}
|
|
199
|
+
return step.value;
|
|
200
|
+
}
|
|
201
|
+
//#endregion
|
|
202
|
+
//#region src/locator/utils.ts
|
|
112
203
|
function buildLocatorPatterns(pattern) {
|
|
113
|
-
|
|
114
|
-
pattern
|
|
115
|
-
];
|
|
204
|
+
return Array.isArray(pattern) ? pattern : [pattern];
|
|
116
205
|
}
|
|
117
206
|
function buildLocatorOptions(options = {}) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
onlyDirectories = options.onlyDirectories ?? false;
|
|
130
|
-
onlyFiles = !options.onlyDirectories;
|
|
131
|
-
} else {
|
|
132
|
-
onlyFiles = options.onlyFiles ?? true;
|
|
133
|
-
onlyDirectories = !options.onlyFiles;
|
|
134
|
-
}
|
|
135
|
-
return {
|
|
136
|
-
path: paths,
|
|
137
|
-
ignore,
|
|
138
|
-
onlyDirectories,
|
|
139
|
-
onlyFiles
|
|
140
|
-
};
|
|
207
|
+
const cwd = options.cwd ? toArray(options.cwd) : [];
|
|
208
|
+
const ignore = options.ignore ? toArray(options.ignore) : [];
|
|
209
|
+
if (cwd.length === 0) cwd.push(process.cwd());
|
|
210
|
+
const onlyDirectories = options.onlyDirectories ?? false;
|
|
211
|
+
return {
|
|
212
|
+
cwd,
|
|
213
|
+
ignore,
|
|
214
|
+
onlyDirectories,
|
|
215
|
+
onlyFiles: onlyDirectories ? false : options.onlyFiles ?? true,
|
|
216
|
+
dot: options.dot ?? false
|
|
217
|
+
};
|
|
141
218
|
}
|
|
142
219
|
function pathToLocatorInfo(input, skipResolve) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
220
|
+
if (!skipResolve && !path.isAbsolute(input)) input = path.resolve(process.cwd(), input);
|
|
221
|
+
const info = path.parse(input);
|
|
222
|
+
const directory = info.dir.split("/").join(path.sep);
|
|
223
|
+
const extension = info.ext ? info.ext : void 0;
|
|
224
|
+
const filePath = extension ? path.join(directory, info.name) + extension : path.join(directory, info.name);
|
|
225
|
+
return {
|
|
226
|
+
directory,
|
|
227
|
+
name: info.name,
|
|
228
|
+
extension,
|
|
229
|
+
path: filePath
|
|
230
|
+
};
|
|
152
231
|
}
|
|
153
232
|
function isLocatorInfo(input) {
|
|
154
|
-
|
|
233
|
+
return isObject(input) && typeof input.directory === "string" && typeof input.name === "string" && (typeof input.extension === "undefined" || typeof input.extension === "string") && typeof input.path === "string";
|
|
155
234
|
}
|
|
156
235
|
function buildFilePath(input) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
if (input.extension) {
|
|
161
|
-
return path.join(input.path, input.name) + input.extension;
|
|
162
|
-
}
|
|
163
|
-
return path.join(input.path, input.name);
|
|
164
|
-
}
|
|
165
|
-
function buildFilePathWithoutExtension(input) {
|
|
166
|
-
let info;
|
|
167
|
-
if (typeof input === 'string') {
|
|
168
|
-
info = pathToLocatorInfo(input);
|
|
169
|
-
} else {
|
|
170
|
-
info = input;
|
|
171
|
-
}
|
|
172
|
-
return path.join(info.path, info.name);
|
|
236
|
+
if (typeof input === "string") return input;
|
|
237
|
+
return input.path;
|
|
173
238
|
}
|
|
174
|
-
|
|
239
|
+
//#endregion
|
|
240
|
+
//#region src/locator/core.ts
|
|
241
|
+
function buildGlobJobs(pattern, options) {
|
|
242
|
+
const patterns = buildLocatorPatterns(pattern);
|
|
243
|
+
const opts = buildLocatorOptions(options);
|
|
244
|
+
const jobs = [];
|
|
245
|
+
for (const p of patterns) for (const cwd of opts.cwd) jobs.push({
|
|
246
|
+
pattern: p,
|
|
247
|
+
options: {
|
|
248
|
+
absolute: true,
|
|
249
|
+
cwd,
|
|
250
|
+
ignore: opts.ignore,
|
|
251
|
+
onlyFiles: opts.onlyFiles,
|
|
252
|
+
onlyDirectories: opts.onlyDirectories,
|
|
253
|
+
dot: opts.dot
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
return jobs;
|
|
257
|
+
}
|
|
258
|
+
function* glob(job) {
|
|
259
|
+
return yield* op(() => fg(job.pattern, job.options), () => fg.sync(job.pattern, job.options));
|
|
260
|
+
}
|
|
261
|
+
function* locateBody(pattern, options) {
|
|
262
|
+
for (const job of buildGlobJobs(pattern, options)) {
|
|
263
|
+
const element = (yield* glob(job)).shift();
|
|
264
|
+
if (element) return pathToLocatorInfo(element, true);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function* locateManyBody(pattern, options) {
|
|
268
|
+
const items = [];
|
|
269
|
+
for (const job of buildGlobJobs(pattern, options)) {
|
|
270
|
+
const files = yield* glob(job);
|
|
271
|
+
for (const file of files) items.push(pathToLocatorInfo(file, true));
|
|
272
|
+
}
|
|
273
|
+
return items;
|
|
274
|
+
}
|
|
275
|
+
//#endregion
|
|
276
|
+
//#region src/locator/async.ts
|
|
175
277
|
async function locateMany(pattern, options) {
|
|
176
|
-
|
|
177
|
-
const opts = buildLocatorOptions(options);
|
|
178
|
-
const items = [];
|
|
179
|
-
for(let i = 0; i < patterns.length; i++){
|
|
180
|
-
for(let j = 0; j < opts.path.length; j++){
|
|
181
|
-
const files = await fg(patterns[i], {
|
|
182
|
-
absolute: true,
|
|
183
|
-
cwd: opts.path[j],
|
|
184
|
-
ignore: opts.ignore,
|
|
185
|
-
onlyFiles: opts.onlyFiles,
|
|
186
|
-
onlyDirectories: opts.onlyDirectories
|
|
187
|
-
});
|
|
188
|
-
for(let k = 0; k < files.length; k++){
|
|
189
|
-
items.push(pathToLocatorInfo(files[k], true));
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
return items;
|
|
278
|
+
return runTwinAsync(locateManyBody(pattern, options));
|
|
194
279
|
}
|
|
195
280
|
async function locate(pattern, options) {
|
|
196
|
-
|
|
197
|
-
const opts = buildLocatorOptions(options);
|
|
198
|
-
for(let i = 0; i < patterns.length; i++){
|
|
199
|
-
for(let j = 0; j < opts.path.length; j++){
|
|
200
|
-
const files = await fg(patterns[i], {
|
|
201
|
-
absolute: true,
|
|
202
|
-
cwd: opts.path[j],
|
|
203
|
-
ignore: opts.ignore,
|
|
204
|
-
onlyFiles: opts.onlyFiles,
|
|
205
|
-
onlyDirectories: opts.onlyDirectories
|
|
206
|
-
});
|
|
207
|
-
const element = files.shift();
|
|
208
|
-
if (element) {
|
|
209
|
-
return pathToLocatorInfo(element, true);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
return undefined;
|
|
281
|
+
return runTwinAsync(locateBody(pattern, options));
|
|
214
282
|
}
|
|
215
|
-
|
|
283
|
+
//#endregion
|
|
284
|
+
//#region src/locator/sync.ts
|
|
216
285
|
function locateManySync(pattern, options) {
|
|
217
|
-
|
|
218
|
-
const opts = buildLocatorOptions(options);
|
|
219
|
-
const items = [];
|
|
220
|
-
for(let i = 0; i < patterns.length; i++){
|
|
221
|
-
for(let j = 0; j < opts.path.length; j++){
|
|
222
|
-
const files = fg.sync(patterns[i], {
|
|
223
|
-
absolute: true,
|
|
224
|
-
cwd: opts.path[j],
|
|
225
|
-
ignore: opts.ignore,
|
|
226
|
-
onlyFiles: opts.onlyFiles,
|
|
227
|
-
onlyDirectories: opts.onlyDirectories
|
|
228
|
-
});
|
|
229
|
-
for(let k = 0; k < files.length; k++){
|
|
230
|
-
items.push(pathToLocatorInfo(files[k], true));
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
return items;
|
|
286
|
+
return runTwinSync(locateManyBody(pattern, options));
|
|
235
287
|
}
|
|
236
288
|
function locateSync(pattern, options) {
|
|
237
|
-
|
|
238
|
-
const opts = buildLocatorOptions(options);
|
|
239
|
-
for(let i = 0; i < patterns.length; i++){
|
|
240
|
-
for(let j = 0; j < opts.path.length; j++){
|
|
241
|
-
const files = fg.sync(patterns[i], {
|
|
242
|
-
absolute: true,
|
|
243
|
-
cwd: opts.path[j],
|
|
244
|
-
ignore: opts.ignore,
|
|
245
|
-
onlyFiles: opts.onlyFiles,
|
|
246
|
-
onlyDirectories: opts.onlyDirectories
|
|
247
|
-
});
|
|
248
|
-
const element = files.shift();
|
|
249
|
-
if (element) {
|
|
250
|
-
return pathToLocatorInfo(element, true);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
return undefined;
|
|
289
|
+
return runTwinSync(locateBody(pattern, options));
|
|
255
290
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
try {
|
|
267
|
-
const file = await fs.promises.readFile(filePath, {
|
|
268
|
-
encoding: 'utf-8'
|
|
269
|
-
});
|
|
270
|
-
return this.parse(file);
|
|
271
|
-
} catch (e) {
|
|
272
|
-
return handleException(e);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
executeSync(input) {
|
|
276
|
-
const filePath = buildFilePath(input);
|
|
277
|
-
try {
|
|
278
|
-
const file = fs.readFileSync(filePath, {
|
|
279
|
-
encoding: 'utf-8'
|
|
280
|
-
});
|
|
281
|
-
return this.parse(file);
|
|
282
|
-
} catch (e) {
|
|
283
|
-
return handleException(e);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
parse(contents) {
|
|
287
|
-
const config = {};
|
|
288
|
-
const lines = contents.split(/\n|\r|\r\n/);
|
|
289
|
-
for(let i = 0; i < lines.length; i++){
|
|
290
|
-
const match = lines[i].match(/^\s*([^\s=]+)\s*=\s*(.*)?\s*$/);
|
|
291
|
-
if (!match) {
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
// Key
|
|
295
|
-
const key = match[1];
|
|
296
|
-
if (!key || !isSafeObjectKey(key)) {
|
|
297
|
-
continue;
|
|
298
|
-
}
|
|
299
|
-
const value = destr(match[2].trim());
|
|
300
|
-
if (key.endsWith('[]')) {
|
|
301
|
-
const arrKey = key.slice(0, Math.max(0, key.length - 2));
|
|
302
|
-
config[arrKey] = (config[arrKey] || []).concat(value);
|
|
303
|
-
continue;
|
|
304
|
-
}
|
|
305
|
-
config[key] = value;
|
|
306
|
-
}
|
|
307
|
-
return flat.unflatten(config, {
|
|
308
|
-
overwrite: true
|
|
309
|
-
});
|
|
310
|
-
}
|
|
291
|
+
//#endregion
|
|
292
|
+
//#region src/locator/up.ts
|
|
293
|
+
function startWalk(options) {
|
|
294
|
+
const { cwd, stopAt, ...baseOptions } = options;
|
|
295
|
+
const current = path.resolve(cwd ?? process.cwd());
|
|
296
|
+
return {
|
|
297
|
+
current,
|
|
298
|
+
stopAt: stopAt ? path.resolve(current, stopAt) : path.parse(current).root,
|
|
299
|
+
baseOptions
|
|
300
|
+
};
|
|
311
301
|
}
|
|
312
|
-
|
|
302
|
+
function nextDirectory(current, stopAt) {
|
|
303
|
+
if (current === stopAt) return;
|
|
304
|
+
const parent = path.dirname(current);
|
|
305
|
+
if (parent === current) return;
|
|
306
|
+
return parent;
|
|
307
|
+
}
|
|
308
|
+
function* locateUpBody(pattern, options) {
|
|
309
|
+
let walk = startWalk(options);
|
|
310
|
+
while (true) {
|
|
311
|
+
const found = yield* locateBody(pattern, {
|
|
312
|
+
...walk.baseOptions,
|
|
313
|
+
cwd: walk.current
|
|
314
|
+
});
|
|
315
|
+
if (found) return found;
|
|
316
|
+
const next = nextDirectory(walk.current, walk.stopAt);
|
|
317
|
+
if (!next) return;
|
|
318
|
+
walk = {
|
|
319
|
+
...walk,
|
|
320
|
+
current: next
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
async function locateUp(pattern, options = {}) {
|
|
325
|
+
return runTwinAsync(locateUpBody(pattern, options));
|
|
326
|
+
}
|
|
327
|
+
function locateUpSync(pattern, options = {}) {
|
|
328
|
+
return runTwinSync(locateUpBody(pattern, options));
|
|
329
|
+
}
|
|
330
|
+
//#endregion
|
|
331
|
+
//#region src/format/text-file/reader.ts
|
|
332
|
+
/**
|
|
333
|
+
* Base class for readers of text-based file formats: reads the file as
|
|
334
|
+
* UTF-8, delegates to `parse`, and wraps I/O and parse errors in typed
|
|
335
|
+
* LocterError subclasses. Subclasses implement `parse` only — both
|
|
336
|
+
* `read` variants are derived from the same body.
|
|
337
|
+
*/
|
|
338
|
+
var TextFileReader = class {
|
|
339
|
+
async read(input) {
|
|
340
|
+
return runTwinAsync(this.body(input));
|
|
341
|
+
}
|
|
342
|
+
readSync(input) {
|
|
343
|
+
return runTwinSync(this.body(input));
|
|
344
|
+
}
|
|
345
|
+
*body(input) {
|
|
346
|
+
try {
|
|
347
|
+
const content = yield* op(() => fs.promises.readFile(input, { encoding: "utf-8" }), () => fs.readFileSync(input, { encoding: "utf-8" }));
|
|
348
|
+
return this.parse(content);
|
|
349
|
+
} catch (e) {
|
|
350
|
+
throw wrapLoaderError(e, input);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
//#endregion
|
|
355
|
+
//#region src/format/text-file/writer.ts
|
|
356
|
+
function isNotFound(e) {
|
|
357
|
+
return e instanceof Error && "code" in e && e.code === "ENOENT";
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Base class for writers of text-based file formats: serializes via
|
|
361
|
+
* `stringify`, creates missing parent directories, writes the file as
|
|
362
|
+
* UTF-8 (always terminated by a single trailing newline), and wraps
|
|
363
|
+
* failures in LocterWriteError. Subclasses implement `stringify` only —
|
|
364
|
+
* both `write` variants are derived from the same body.
|
|
365
|
+
*/
|
|
366
|
+
var TextFileWriter = class {
|
|
367
|
+
/**
|
|
368
|
+
* Opt-in: when true, the body reads the target file first (undefined
|
|
369
|
+
* if absent) and passes its content to `stringify` as `existing` —
|
|
370
|
+
* the hook behind format-preserving write-back (YAML comment
|
|
371
|
+
* grafting, JSON indent detection).
|
|
372
|
+
*/
|
|
373
|
+
get usesExistingContent() {
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
376
|
+
async write(input, value) {
|
|
377
|
+
return runTwinAsync(this.body(input, value));
|
|
378
|
+
}
|
|
379
|
+
writeSync(input, value) {
|
|
380
|
+
runTwinSync(this.body(input, value));
|
|
381
|
+
}
|
|
382
|
+
*body(input, value) {
|
|
383
|
+
const filePath = input;
|
|
384
|
+
try {
|
|
385
|
+
let existing;
|
|
386
|
+
if (this.usesExistingContent) existing = yield* op(() => fs.promises.readFile(filePath, { encoding: "utf-8" }).catch((e) => {
|
|
387
|
+
if (isNotFound(e)) return;
|
|
388
|
+
throw e;
|
|
389
|
+
}), () => {
|
|
390
|
+
try {
|
|
391
|
+
return fs.readFileSync(filePath, { encoding: "utf-8" });
|
|
392
|
+
} catch (e) {
|
|
393
|
+
if (isNotFound(e)) return;
|
|
394
|
+
throw e;
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
let content = this.stringify(value, existing);
|
|
398
|
+
if (!content.endsWith("\n")) content += "\n";
|
|
399
|
+
const directory = path.dirname(filePath);
|
|
400
|
+
yield* op(async () => {
|
|
401
|
+
await fs.promises.mkdir(directory, { recursive: true });
|
|
402
|
+
}, () => {
|
|
403
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
404
|
+
});
|
|
405
|
+
yield* op(() => fs.promises.writeFile(filePath, content, { encoding: "utf-8" }), () => fs.writeFileSync(filePath, content, { encoding: "utf-8" }));
|
|
406
|
+
} catch (e) {
|
|
407
|
+
throw wrapWriteError(e, filePath);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
//#endregion
|
|
412
|
+
//#region src/format/built-in/conf/reader.ts
|
|
413
|
+
var ConfReader = class extends TextFileReader {
|
|
414
|
+
parse(contents) {
|
|
415
|
+
const config = {};
|
|
416
|
+
const lines = contents.split(/\n|\r|\r\n/);
|
|
417
|
+
for (const line of lines) {
|
|
418
|
+
const match = line.match(/^\s*([^\s=]+)\s*=\s*(.*)?\s*$/);
|
|
419
|
+
if (!match) continue;
|
|
420
|
+
const key = match[1];
|
|
421
|
+
const rawValue = match[2];
|
|
422
|
+
if (!key || !isSafeObjectKey(key)) continue;
|
|
423
|
+
const value = destr(rawValue ? rawValue.trim() : "");
|
|
424
|
+
if (key.endsWith("[]")) {
|
|
425
|
+
const arrKey = key.slice(0, Math.max(0, key.length - 2));
|
|
426
|
+
config[arrKey] = (config[arrKey] || []).concat(value);
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
config[key] = value;
|
|
430
|
+
}
|
|
431
|
+
return unflatten(config, { overwrite: true });
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
//#endregion
|
|
435
|
+
//#region src/format/built-in/conf/writer.ts
|
|
436
|
+
function serializeValue(value) {
|
|
437
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Serializes an object to `key=value` lines (the inverse of ConfReader):
|
|
441
|
+
* nested objects become dot-separated keys, arrays become repeated
|
|
442
|
+
* `key[]=` lines. Round-trips are structural, not textual — comments,
|
|
443
|
+
* blank lines, and line order of an existing file are not preserved,
|
|
444
|
+
* and a string that parses as another type (e.g. '123') reads back as
|
|
445
|
+
* that type.
|
|
446
|
+
*/
|
|
447
|
+
var ConfWriter = class extends TextFileWriter {
|
|
448
|
+
stringify(value) {
|
|
449
|
+
if (!isObject(value)) throw new Error("The value must be an object for the conf format.");
|
|
450
|
+
const flat = flatten(value, { safe: true });
|
|
451
|
+
const lines = [];
|
|
452
|
+
for (const key of Object.keys(flat)) {
|
|
453
|
+
const entry = flat[key];
|
|
454
|
+
if (typeof entry === "undefined") continue;
|
|
455
|
+
if (Array.isArray(entry)) {
|
|
456
|
+
for (const item of entry) lines.push(`${key}[]=${serializeValue(item)}`);
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
lines.push(`${key}=${serializeValue(entry)}`);
|
|
460
|
+
}
|
|
461
|
+
return lines.join("\n");
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
//#endregion
|
|
465
|
+
//#region src/format/built-in/json/reader.ts
|
|
466
|
+
var JSONReader = class extends TextFileReader {
|
|
467
|
+
parse(content) {
|
|
468
|
+
return JSON.parse(content);
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
//#endregion
|
|
472
|
+
//#region src/format/built-in/json/writer.ts
|
|
473
|
+
function detectIndent(content) {
|
|
474
|
+
const match = /^([ \t]+)\S/m.exec(content);
|
|
475
|
+
return match ? match[1] : void 0;
|
|
476
|
+
}
|
|
477
|
+
var JSONWriter = class extends TextFileWriter {
|
|
478
|
+
options;
|
|
479
|
+
constructor(options = {}) {
|
|
480
|
+
super();
|
|
481
|
+
this.options = options;
|
|
482
|
+
}
|
|
483
|
+
get usesExistingContent() {
|
|
484
|
+
return this.options.indent === "auto";
|
|
485
|
+
}
|
|
486
|
+
stringify(value, existing) {
|
|
487
|
+
let indent;
|
|
488
|
+
if (this.options.indent === "auto") indent = existing ? detectIndent(existing) : void 0;
|
|
489
|
+
else indent = this.options.indent;
|
|
490
|
+
indent = indent ?? 4;
|
|
491
|
+
const output = JSON.stringify(value, null, indent);
|
|
492
|
+
if (typeof output === "undefined") throw new Error("The value is not JSON-serializable.");
|
|
493
|
+
return output;
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
//#endregion
|
|
497
|
+
//#region src/format/built-in/module/constants.ts
|
|
498
|
+
/**
|
|
499
|
+
* Single source of truth for the extensions handled by the module loader:
|
|
500
|
+
* consumed by BOTH the built-in routing registry and the jiti config.
|
|
501
|
+
*/
|
|
502
|
+
const MODULE_FILE_EXTENSIONS = [
|
|
503
|
+
".js",
|
|
504
|
+
".mjs",
|
|
505
|
+
".mts",
|
|
506
|
+
".cjs",
|
|
507
|
+
".cts",
|
|
508
|
+
".ts"
|
|
509
|
+
];
|
|
510
|
+
//#endregion
|
|
511
|
+
//#region src/format/built-in/module/reader.ts
|
|
512
|
+
const require = createRequire(import.meta.url);
|
|
513
|
+
function originalPath(data) {
|
|
514
|
+
return typeof data === "string" ? data : buildFilePath(data);
|
|
515
|
+
}
|
|
516
|
+
function isUnrecoverableError(error) {
|
|
517
|
+
const underlying = error instanceof LocterError ? error.cause : error;
|
|
518
|
+
return underlying instanceof SyntaxError || underlying instanceof ReferenceError || isTypeScriptError(underlying);
|
|
519
|
+
}
|
|
520
|
+
var ModuleReader = class {
|
|
521
|
+
instance;
|
|
522
|
+
loadFn;
|
|
523
|
+
loadSyncFn;
|
|
524
|
+
constructor(options = {}) {
|
|
525
|
+
this.instance = createJiti(void 0, { extensions: [...MODULE_FILE_EXTENSIONS] });
|
|
526
|
+
this.loadFn = options.load;
|
|
527
|
+
this.loadSyncFn = options.loadSync;
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Returns the previous configuration, so callers can restore it.
|
|
531
|
+
*/
|
|
532
|
+
configure(options) {
|
|
533
|
+
const previous = {
|
|
534
|
+
load: this.loadFn,
|
|
535
|
+
loadSync: this.loadSyncFn
|
|
536
|
+
};
|
|
537
|
+
if ("load" in options) this.loadFn = options.load;
|
|
538
|
+
if ("loadSync" in options) this.loadSyncFn = options.loadSync;
|
|
539
|
+
return previous;
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Returns the raw module value (import / require / jiti output) —
|
|
543
|
+
* normalization to a module record happens once, at the
|
|
544
|
+
* FormatRegistry boundary.
|
|
545
|
+
*
|
|
546
|
+
* read/readSync are deliberately NOT derived from a shared body
|
|
547
|
+
* (unlike every other sync/async twin in this package): their
|
|
548
|
+
* recovery paths differ. The async variant falls back to loadSync
|
|
549
|
+
* under ts-node (jiti's async path does not cooperate with ts-node)
|
|
550
|
+
* and to the jiti instance otherwise; the sync variant only has the
|
|
551
|
+
* jiti fallback. The divergence is pinned by the fallback tests in
|
|
552
|
+
* test/unit/format/module.spec.ts.
|
|
553
|
+
*/
|
|
554
|
+
async read(input) {
|
|
555
|
+
try {
|
|
556
|
+
return await this.load(input);
|
|
557
|
+
} catch (e) {
|
|
558
|
+
if (isUnrecoverableError(e)) throw e;
|
|
559
|
+
if (isTsNodeRuntimeEnvironment()) return this.loadSync(input);
|
|
560
|
+
return this.instance(input);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
readSync(input) {
|
|
564
|
+
try {
|
|
565
|
+
return this.loadSync(input);
|
|
566
|
+
} catch (e) {
|
|
567
|
+
if (isUnrecoverableError(e)) throw e;
|
|
568
|
+
return this.instance(input);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
async load(data, options = {}) {
|
|
572
|
+
const id = this.build(data, options);
|
|
573
|
+
try {
|
|
574
|
+
if (this.loadFn) return await this.loadFn(id);
|
|
575
|
+
if (isJestRuntimeEnvironment()) return require(id);
|
|
576
|
+
return await import(id);
|
|
577
|
+
} catch (e) {
|
|
578
|
+
/* istanbul ignore next */
|
|
579
|
+
if (!options.withFilePrefix && isObject(e) && hasStringProperty(e, "code") && (e.code === "ERR_UNSUPPORTED_ESM_URL_SCHEME" || e.code === "UNSUPPORTED_ESM_URL_SCHEME")) return this.load(data, {
|
|
580
|
+
...options,
|
|
581
|
+
withFilePrefix: true
|
|
582
|
+
});
|
|
583
|
+
throw wrapLoaderError(e, originalPath(data));
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
loadSync(data, options = {}) {
|
|
587
|
+
const id = this.build(data, options);
|
|
588
|
+
try {
|
|
589
|
+
if (this.loadSyncFn) return this.loadSyncFn(id);
|
|
590
|
+
return require(id);
|
|
591
|
+
} catch (e) {
|
|
592
|
+
throw wrapLoaderError(e, originalPath(data));
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
build(data, options = {}) {
|
|
596
|
+
if (isLocatorInfo(data) || isFilePath(data)) {
|
|
597
|
+
if (typeof data !== "string") data = buildFilePath(data);
|
|
598
|
+
if (options.withFilePrefix) data = pathToFileURL(data).href;
|
|
599
|
+
}
|
|
600
|
+
return data;
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
//#endregion
|
|
604
|
+
//#region src/format/built-in/module/utils.ts
|
|
605
|
+
/**
|
|
606
|
+
* Brand carried by every record locter's read() produces. Deliberately a
|
|
607
|
+
* module-private Symbol (NOT Symbol.for) so arbitrary parsed data cannot
|
|
608
|
+
* forge it — unlike `__esModule`, its presence is trustworthy. write()
|
|
609
|
+
* uses it to unwrap `.default` on round-trips.
|
|
610
|
+
*/
|
|
611
|
+
const MODULE_RECORD = Symbol("locter.record");
|
|
612
|
+
/**
|
|
613
|
+
* True when the value is a record produced by locter's read()/toModuleRecord.
|
|
614
|
+
* Detection is brand-based; a plain object with an `__esModule` key is NOT
|
|
615
|
+
* a module record.
|
|
616
|
+
*/
|
|
617
|
+
function isModuleRecord(input) {
|
|
618
|
+
return isObject(input) && MODULE_RECORD in input;
|
|
619
|
+
}
|
|
620
|
+
function brand(input) {
|
|
621
|
+
if (isObject(input) && Object.isExtensible(input)) Object.defineProperty(input, MODULE_RECORD, { value: true });
|
|
622
|
+
return input;
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Detect the transpiler interop marker (`__esModule`). Only meaningful for
|
|
626
|
+
* values that came out of a module system — arbitrary parsed data can carry
|
|
627
|
+
* an `__esModule` key without being a module, so callers must decide by
|
|
628
|
+
* provenance whether this check applies (see FormatRegistry).
|
|
629
|
+
*/
|
|
313
630
|
function isESModule(input) {
|
|
314
|
-
|
|
315
|
-
|
|
631
|
+
return isObject(input) && typeof input.__esModule !== "undefined";
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Wrap an arbitrary value in a frozen module record: enumerable keys are
|
|
635
|
+
* re-exposed as named exports and `default` is the value itself.
|
|
636
|
+
*/
|
|
637
|
+
function createModuleRecord(data) {
|
|
638
|
+
const output = Object.create(null, {
|
|
639
|
+
__esModule: { value: true },
|
|
640
|
+
[Symbol.toStringTag]: { value: "Module" },
|
|
641
|
+
[MODULE_RECORD]: { value: true }
|
|
642
|
+
});
|
|
643
|
+
if (isObject(data)) for (const key in data) {
|
|
644
|
+
if (hasOwnProperty(output, key)) continue;
|
|
645
|
+
let descriptor = Object.getOwnPropertyDescriptor(data, key);
|
|
646
|
+
if (!descriptor || "get" in descriptor || descriptor.writable || descriptor.configurable) descriptor = {
|
|
647
|
+
enumerable: true,
|
|
648
|
+
get() {
|
|
649
|
+
return data[key];
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
Object.defineProperty(output, key, descriptor);
|
|
653
|
+
}
|
|
654
|
+
if (!hasOwnProperty(output, "default")) Object.defineProperty(output, "default", {
|
|
655
|
+
value: data,
|
|
656
|
+
enumerable: true
|
|
657
|
+
});
|
|
658
|
+
return Object.freeze(output);
|
|
316
659
|
}
|
|
317
|
-
|
|
660
|
+
/**
|
|
661
|
+
* Normalize module-loader output to a module record. Values already carrying
|
|
662
|
+
* the `__esModule` marker pass through (module provenance makes the marker
|
|
663
|
+
* trustworthy); everything else is wrapped via createModuleRecord.
|
|
664
|
+
*
|
|
665
|
+
* https://2ality.com/2017/01/babel-esm-spec-mode.html
|
|
666
|
+
*/
|
|
318
667
|
function toModuleRecord(data) {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
}
|
|
328
|
-
return data;
|
|
329
|
-
}
|
|
330
|
-
const output = Object.create(null, {
|
|
331
|
-
__esModule: {
|
|
332
|
-
value: true
|
|
333
|
-
},
|
|
334
|
-
[Symbol.toStringTag]: {
|
|
335
|
-
value: 'Module'
|
|
336
|
-
}
|
|
337
|
-
});
|
|
338
|
-
if (isObject(data)) {
|
|
339
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
340
|
-
for(const key in data){
|
|
341
|
-
if (hasOwnProperty(output, key)) {
|
|
342
|
-
continue;
|
|
343
|
-
}
|
|
344
|
-
let descriptor = Object.getOwnPropertyDescriptor(data, key);
|
|
345
|
-
if (!descriptor || 'get' in descriptor || descriptor.writable || descriptor.configurable) {
|
|
346
|
-
descriptor = {
|
|
347
|
-
enumerable: true,
|
|
348
|
-
get () {
|
|
349
|
-
return data[key];
|
|
350
|
-
}
|
|
351
|
-
};
|
|
352
|
-
}
|
|
353
|
-
Object.defineProperty(output, key, descriptor);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
if (!hasOwnProperty(output, 'default')) {
|
|
357
|
-
Object.defineProperty(output, 'default', {
|
|
358
|
-
value: data,
|
|
359
|
-
enumerable: true
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
return Object.freeze(output);
|
|
668
|
+
if (isESModule(data)) {
|
|
669
|
+
if (isESModule(data.default) && data.default.default) return brand({
|
|
670
|
+
...data,
|
|
671
|
+
default: data.default.default
|
|
672
|
+
});
|
|
673
|
+
return brand(data);
|
|
674
|
+
}
|
|
675
|
+
return createModuleRecord(data);
|
|
363
676
|
}
|
|
364
677
|
function getModuleExport(data, filterFn) {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
value: data[keys[i]]
|
|
371
|
-
};
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
return undefined;
|
|
678
|
+
const keys = Object.keys(data);
|
|
679
|
+
for (const key of keys) if (filterFn(key, data[key])) return {
|
|
680
|
+
key,
|
|
681
|
+
value: data[key]
|
|
682
|
+
};
|
|
375
683
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
// issue: https://github.com/nuxt/bridge/issues/228
|
|
388
|
-
if (isTsNodeRuntimeEnvironment()) {
|
|
389
|
-
output = this.loadSync(input);
|
|
390
|
-
} else {
|
|
391
|
-
output = this.instance(input);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
return toModuleRecord(output);
|
|
395
|
-
}
|
|
396
|
-
executeSync(input) {
|
|
397
|
-
let output;
|
|
398
|
-
try {
|
|
399
|
-
output = this.loadSync(input);
|
|
400
|
-
} catch (e) {
|
|
401
|
-
if (e instanceof SyntaxError || e instanceof ReferenceError || isTypeScriptError(e)) {
|
|
402
|
-
throw e;
|
|
403
|
-
}
|
|
404
|
-
output = this.instance(input);
|
|
405
|
-
}
|
|
406
|
-
return toModuleRecord(output);
|
|
407
|
-
}
|
|
408
|
-
// ---------------------------------------------------------------------------
|
|
409
|
-
async load(data, options = {}) {
|
|
410
|
-
const id = this.build(data, options);
|
|
411
|
-
try {
|
|
412
|
-
// segmentation fault
|
|
413
|
-
// issue: https://github.com/nodejs/node/issues/35889
|
|
414
|
-
if (isJestRuntimeEnvironment()) {
|
|
415
|
-
// eslint-disable-next-line global-require,import/no-dynamic-require
|
|
416
|
-
return require(id);
|
|
417
|
-
}
|
|
418
|
-
return await import(id);
|
|
419
|
-
} catch (e) {
|
|
420
|
-
/* istanbul ignore next */ if (isObject(e) && hasStringProperty(e, 'code')) {
|
|
421
|
-
if (!options.withFilePrefix && (e.code === 'ERR_UNSUPPORTED_ESM_URL_SCHEME' || e.code === 'UNSUPPORTED_ESM_URL_SCHEME')) {
|
|
422
|
-
return this.load(data, {
|
|
423
|
-
...options,
|
|
424
|
-
withFilePrefix: true
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
throw new BaseError({
|
|
428
|
-
code: e.code,
|
|
429
|
-
message: hasStringProperty(e, 'message') ? e.message : undefined,
|
|
430
|
-
stack: hasStringProperty(e, 'stack') ? e.stack : undefined
|
|
431
|
-
});
|
|
432
|
-
}
|
|
433
|
-
/* istanbul ignore next */ return handleException(e);
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
loadSync(data, options = {}) {
|
|
437
|
-
const id = this.build(data, options);
|
|
438
|
-
try {
|
|
439
|
-
// eslint-disable-next-line global-require,import/no-dynamic-require
|
|
440
|
-
return require(id);
|
|
441
|
-
} catch (e) {
|
|
442
|
-
/* istanbul ignore next */ if (isObject(e) && hasStringProperty(e, 'code')) {
|
|
443
|
-
throw new BaseError({
|
|
444
|
-
code: e.code,
|
|
445
|
-
message: hasStringProperty(e, 'message') ? e.message : undefined,
|
|
446
|
-
stack: hasStringProperty(e, 'stack') ? e.stack : undefined
|
|
447
|
-
});
|
|
448
|
-
}
|
|
449
|
-
return handleException(e);
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
build(data, options = {}) {
|
|
453
|
-
if (isLocatorInfo(data) || isFilePath(data)) {
|
|
454
|
-
if (typeof data !== 'string') {
|
|
455
|
-
data = buildFilePath(data);
|
|
456
|
-
}
|
|
457
|
-
if (options.withFilePrefix) {
|
|
458
|
-
data = pathToFileURL(data).href;
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
return data;
|
|
462
|
-
}
|
|
463
|
-
constructor(){
|
|
464
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
465
|
-
// @ts-ignore
|
|
466
|
-
this.instance = createJiti(undefined, {
|
|
467
|
-
extensions: [
|
|
468
|
-
'.js',
|
|
469
|
-
'.mjs',
|
|
470
|
-
'.mts',
|
|
471
|
-
'.cjs',
|
|
472
|
-
'.cts',
|
|
473
|
-
'.ts'
|
|
474
|
-
]
|
|
475
|
-
});
|
|
476
|
-
}
|
|
684
|
+
//#endregion
|
|
685
|
+
//#region src/format/built-in/yaml/reader.ts
|
|
686
|
+
var YAMLReader = class extends TextFileReader {
|
|
687
|
+
parse(content) {
|
|
688
|
+
return parse(content);
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
//#endregion
|
|
692
|
+
//#region src/format/built-in/yaml/writer.ts
|
|
693
|
+
function pairKey(key) {
|
|
694
|
+
return isScalar(key) ? String(key.value) : String(key);
|
|
477
695
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
696
|
+
/**
|
|
697
|
+
* Merge a plain object into an existing YAML map node: added/changed keys
|
|
698
|
+
* are set, absent keys are deleted, and nested objects recurse — so
|
|
699
|
+
* comments, anchors, and formatting attached to surviving keys are
|
|
700
|
+
* preserved. Arrays, scalars, and type changes replace the node wholesale
|
|
701
|
+
* (comments inside the replaced subtree are lost).
|
|
702
|
+
*/
|
|
703
|
+
function graftMap(doc, node, value) {
|
|
704
|
+
for (const pair of [...node.items]) if (!hasOwnProperty(value, pairKey(pair.key))) node.delete(pair.key);
|
|
705
|
+
for (const key of Object.keys(value)) {
|
|
706
|
+
const entry = value[key];
|
|
707
|
+
const current = node.get(key, true);
|
|
708
|
+
if (isMap(current) && isObject(entry)) {
|
|
709
|
+
graftMap(doc, current, entry);
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
712
|
+
const replacement = doc.createNode(entry);
|
|
713
|
+
if (isNode(current)) {
|
|
714
|
+
replacement.commentBefore = current.commentBefore;
|
|
715
|
+
replacement.comment = current.comment;
|
|
716
|
+
replacement.spaceBefore = current.spaceBefore;
|
|
717
|
+
}
|
|
718
|
+
node.set(doc.createNode(key), replacement);
|
|
719
|
+
}
|
|
502
720
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
721
|
+
/**
|
|
722
|
+
* Writes YAML preserving hand-edited structure where possible: when the
|
|
723
|
+
* target file already exists, the new value is grafted into its parsed
|
|
724
|
+
* document, so comments and anchors attached to surviving keys survive
|
|
725
|
+
* the write-back. New files are plain `yaml.stringify` output. An
|
|
726
|
+
* existing file that fails to parse throws (wrapped as LocterWriteError)
|
|
727
|
+
* instead of being silently overwritten.
|
|
728
|
+
*/
|
|
729
|
+
var YAMLWriter = class extends TextFileWriter {
|
|
730
|
+
options;
|
|
731
|
+
constructor(options = {}) {
|
|
732
|
+
super();
|
|
733
|
+
this.options = options;
|
|
734
|
+
}
|
|
735
|
+
get usesExistingContent() {
|
|
736
|
+
return true;
|
|
737
|
+
}
|
|
738
|
+
stringify(value, existing) {
|
|
739
|
+
if (typeof existing === "undefined") return stringify(value, this.options);
|
|
740
|
+
const doc = parseDocument(existing);
|
|
741
|
+
if (doc.errors.length > 0) throw doc.errors[0];
|
|
742
|
+
if (isMap(doc.contents) && isObject(value)) graftMap(doc, doc.contents, value);
|
|
743
|
+
else doc.contents = doc.createNode(value);
|
|
744
|
+
return doc.toString(this.options);
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
//#endregion
|
|
748
|
+
//#region src/format/built-in/registry.ts
|
|
749
|
+
/**
|
|
750
|
+
* Every built-in format, in one place. Keys are the format ids; each entry
|
|
751
|
+
* couples routing and construction. The id union, the extension → format
|
|
752
|
+
* table, lazy caching, and the typed `builtInReader()` accessor are all
|
|
753
|
+
* DERIVED from this object. Adding a format = adding one entry.
|
|
754
|
+
*
|
|
755
|
+
* Intentionally NOT re-exported from any barrel (internal API).
|
|
756
|
+
*/
|
|
757
|
+
const BUILT_IN_PRESETS = {
|
|
758
|
+
module: {
|
|
759
|
+
extensions: MODULE_FILE_EXTENSIONS,
|
|
760
|
+
reader: () => new ModuleReader()
|
|
761
|
+
},
|
|
762
|
+
conf: {
|
|
763
|
+
extensions: [".conf"],
|
|
764
|
+
reader: () => new ConfReader(),
|
|
765
|
+
writer: () => new ConfWriter()
|
|
766
|
+
},
|
|
767
|
+
json: {
|
|
768
|
+
extensions: [".json"],
|
|
769
|
+
reader: () => new JSONReader(),
|
|
770
|
+
writer: () => new JSONWriter()
|
|
771
|
+
},
|
|
772
|
+
yaml: {
|
|
773
|
+
extensions: [".yml", ".yaml"],
|
|
774
|
+
reader: () => new YAMLReader(),
|
|
775
|
+
writer: () => new YAMLWriter()
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
//#endregion
|
|
779
|
+
//#region src/format/registry/module.ts
|
|
780
|
+
function compileSlot(slot) {
|
|
781
|
+
if (typeof slot === "undefined") return;
|
|
782
|
+
if (typeof slot !== "function") return () => slot;
|
|
783
|
+
const factory = slot;
|
|
784
|
+
let cached;
|
|
785
|
+
return () => {
|
|
786
|
+
if (!cached) cached = factory();
|
|
787
|
+
return cached;
|
|
788
|
+
};
|
|
527
789
|
}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
790
|
+
var FormatRegistry = class {
|
|
791
|
+
/**
|
|
792
|
+
* User rules only — built-ins live in the extension table instead.
|
|
793
|
+
*/
|
|
794
|
+
rules;
|
|
795
|
+
/**
|
|
796
|
+
* Lazy per-instance cache of built-in reader instances.
|
|
797
|
+
*/
|
|
798
|
+
builtInReaderCache;
|
|
799
|
+
/**
|
|
800
|
+
* Lazy per-instance cache of built-in writer instances.
|
|
801
|
+
*/
|
|
802
|
+
builtInWriterCache;
|
|
803
|
+
/**
|
|
804
|
+
* extension → built-in format id, precomputed once from BUILT_IN_PRESETS.
|
|
805
|
+
*/
|
|
806
|
+
builtInExtensions;
|
|
807
|
+
ruleCounter;
|
|
808
|
+
constructor(options = {}) {
|
|
809
|
+
this.rules = [];
|
|
810
|
+
this.builtInReaderCache = /* @__PURE__ */ new Map();
|
|
811
|
+
this.builtInWriterCache = /* @__PURE__ */ new Map();
|
|
812
|
+
this.builtInExtensions = /* @__PURE__ */ new Map();
|
|
813
|
+
this.ruleCounter = 0;
|
|
814
|
+
const ids = Object.keys(BUILT_IN_PRESETS);
|
|
815
|
+
for (const id of ids) for (const extension of BUILT_IN_PRESETS[id].extensions) {
|
|
816
|
+
const existing = this.builtInExtensions.get(extension);
|
|
817
|
+
if (existing) throw new LocterError({ message: `Extension ${extension} is claimed by two built-in formats: ${existing}, ${id}.` });
|
|
818
|
+
this.builtInExtensions.set(extension, id);
|
|
819
|
+
}
|
|
820
|
+
if (options.rules) for (const rule of options.rules) this.register(rule);
|
|
821
|
+
}
|
|
822
|
+
register(rule) {
|
|
823
|
+
if (typeof rule.id !== "undefined" && hasOwnProperty(BUILT_IN_PRESETS, rule.id)) throw new LocterError({ message: `The id ${rule.id} is reserved by a built-in format.` });
|
|
824
|
+
if (typeof rule.reader === "undefined" && typeof rule.writer === "undefined") throw new LocterError({ message: "A rule requires at least one of: reader, writer." });
|
|
825
|
+
const id = rule.id ?? this.generateRuleId();
|
|
826
|
+
const entry = this.compile(id, rule);
|
|
827
|
+
const index = this.rules.findIndex((item) => item.id === id);
|
|
828
|
+
if (index === -1) this.rules.push(entry);
|
|
829
|
+
else this.rules[index] = entry;
|
|
830
|
+
return {
|
|
831
|
+
id,
|
|
832
|
+
test: rule.test,
|
|
833
|
+
builtIn: false
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Remove a user-registered rule by id. Built-in ids cannot be
|
|
838
|
+
* unregistered (returns false).
|
|
839
|
+
*/
|
|
840
|
+
unregister(id) {
|
|
841
|
+
const index = this.rules.findIndex((item) => item.id === id);
|
|
842
|
+
if (index === -1) return false;
|
|
843
|
+
this.rules.splice(index, 1);
|
|
844
|
+
return true;
|
|
845
|
+
}
|
|
846
|
+
has(id) {
|
|
847
|
+
if (this.rules.some((item) => item.id === id)) return true;
|
|
848
|
+
return hasOwnProperty(BUILT_IN_PRESETS, id);
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* All registrations in effective match order: user rules
|
|
852
|
+
* (registration order) first, then the built-ins.
|
|
853
|
+
*/
|
|
854
|
+
entries() {
|
|
855
|
+
const output = this.rules.map((item) => ({
|
|
856
|
+
id: item.id,
|
|
857
|
+
test: item.test,
|
|
858
|
+
builtIn: false
|
|
859
|
+
}));
|
|
860
|
+
const ids = Object.keys(BUILT_IN_PRESETS);
|
|
861
|
+
for (const id of ids) output.push({
|
|
862
|
+
id,
|
|
863
|
+
test: [...BUILT_IN_PRESETS[id].extensions],
|
|
864
|
+
builtIn: true
|
|
865
|
+
});
|
|
866
|
+
return output;
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* Restore the registry to its construction state: drop all user rules
|
|
870
|
+
* and evict every cached reader instance (including a module reader
|
|
871
|
+
* configured via setModuleReader / configure).
|
|
872
|
+
*/
|
|
873
|
+
reset() {
|
|
874
|
+
this.rules = [];
|
|
875
|
+
this.builtInReaderCache.clear();
|
|
876
|
+
this.builtInWriterCache.clear();
|
|
877
|
+
this.ruleCounter = 0;
|
|
878
|
+
}
|
|
879
|
+
async read(input) {
|
|
880
|
+
return runTwinAsync(this.readBody(input));
|
|
881
|
+
}
|
|
882
|
+
readSync(input) {
|
|
883
|
+
return runTwinSync(this.readBody(input));
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Shared body of read/readSync: dispatch, read, normalize, wrap.
|
|
887
|
+
*/
|
|
888
|
+
*readBody(input) {
|
|
889
|
+
const filePath = buildFilePath(input);
|
|
890
|
+
const reader = this.findReader(filePath);
|
|
891
|
+
if (!reader) throw this.unknownExtensionError(filePath);
|
|
892
|
+
try {
|
|
893
|
+
const output = yield* op(() => reader.read(filePath), () => reader.readSync(filePath));
|
|
894
|
+
return this.toRecord(output, reader);
|
|
895
|
+
} catch (e) {
|
|
896
|
+
throw wrapLoaderError(e, filePath);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
async write(input, value) {
|
|
900
|
+
return runTwinAsync(this.writeBody(input, value));
|
|
901
|
+
}
|
|
902
|
+
writeSync(input, value) {
|
|
903
|
+
runTwinSync(this.writeBody(input, value));
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* Shared body of write/writeSync: dispatch, unwrap records, write, wrap.
|
|
907
|
+
* The inverse boundary of readBody: a record produced by read() (brand
|
|
908
|
+
* detected) is unwrapped to its `.default` value; anything else is
|
|
909
|
+
* written as-is.
|
|
910
|
+
*/
|
|
911
|
+
*writeBody(input, value) {
|
|
912
|
+
const filePath = buildFilePath(input);
|
|
913
|
+
if (!isFilePath(filePath)) throw new LocterWriteError({
|
|
914
|
+
message: `Cannot write to a bare module specifier: ${filePath}`,
|
|
915
|
+
path: filePath
|
|
916
|
+
});
|
|
917
|
+
const writer = this.findWriter(filePath);
|
|
918
|
+
if (!writer) {
|
|
919
|
+
if (this.findReader(filePath)) throw new LocterWriteError({
|
|
920
|
+
message: `The format of extension ${pathToLocatorInfo(filePath).extension} is read-only.`,
|
|
921
|
+
path: filePath
|
|
922
|
+
});
|
|
923
|
+
throw this.unknownExtensionError(filePath);
|
|
924
|
+
}
|
|
925
|
+
const plain = isModuleRecord(value) ? value.default : value;
|
|
926
|
+
try {
|
|
927
|
+
yield* op(() => writer.write(filePath, plain), () => writer.writeSync(filePath, plain));
|
|
928
|
+
} catch (e) {
|
|
929
|
+
throw wrapWriteError(e, filePath);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
/**
|
|
933
|
+
* The single normalization boundary: every read result becomes a module
|
|
934
|
+
* record (`.default` is always the loaded value, top-level keys stay
|
|
935
|
+
* accessible as named exports). Provenance decides how: module-reader
|
|
936
|
+
* output may legitimately already be a record (`__esModule` is meaningful
|
|
937
|
+
* there); any other reader returns arbitrary parsed data, which is always
|
|
938
|
+
* wrapped — even if it happens to contain an `__esModule` key.
|
|
939
|
+
*/
|
|
940
|
+
toRecord(output, reader) {
|
|
941
|
+
if (reader instanceof ModuleReader) return toModuleRecord(output);
|
|
942
|
+
return createModuleRecord(output);
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* Typed accessor for the LIVE built-in reader instance (lazy, cached).
|
|
946
|
+
*/
|
|
947
|
+
builtInReader(id) {
|
|
948
|
+
let reader = this.builtInReaderCache.get(id);
|
|
949
|
+
if (!reader) {
|
|
950
|
+
reader = BUILT_IN_PRESETS[id].reader();
|
|
951
|
+
this.builtInReaderCache.set(id, reader);
|
|
952
|
+
}
|
|
953
|
+
return reader;
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Typed accessor for the LIVE built-in writer instance (lazy, cached).
|
|
957
|
+
* Only writable format ids are accepted — read-only formats (module)
|
|
958
|
+
* are a compile error.
|
|
959
|
+
*/
|
|
960
|
+
builtInWriter(id) {
|
|
961
|
+
let writer = this.builtInWriterCache.get(id);
|
|
962
|
+
if (!writer) {
|
|
963
|
+
writer = BUILT_IN_PRESETS[id].writer();
|
|
964
|
+
this.builtInWriterCache.set(id, writer);
|
|
965
|
+
}
|
|
966
|
+
return writer;
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Dispatch order:
|
|
970
|
+
* 1. bare specifier (no extension) → module reader, always
|
|
971
|
+
* 2. user rules, registration order → first match wins (can override built-ins)
|
|
972
|
+
* 3. built-in extension table
|
|
973
|
+
* 4. undefined → read/readSync throw LocterUnknownExtensionError
|
|
974
|
+
*/
|
|
975
|
+
findReader(input) {
|
|
976
|
+
if (!isFilePath(input)) return this.builtInReader("module");
|
|
977
|
+
const rule = this.findRule(input, (item) => typeof item.getReader !== "undefined");
|
|
978
|
+
if (rule && rule.getReader) return rule.getReader();
|
|
979
|
+
const id = this.findBuiltInId(input);
|
|
980
|
+
if (id) return this.builtInReader(id);
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* Write-side dispatch. No bare-specifier step (there is nothing to
|
|
984
|
+
* write to); only rules with a writer slot participate, then built-in
|
|
985
|
+
* presets that declare a writer.
|
|
986
|
+
*/
|
|
987
|
+
findWriter(input) {
|
|
988
|
+
if (!isFilePath(input)) return;
|
|
989
|
+
const rule = this.findRule(input, (item) => typeof item.getWriter !== "undefined");
|
|
990
|
+
if (rule && rule.getWriter) return rule.getWriter();
|
|
991
|
+
const id = this.findBuiltInId(input);
|
|
992
|
+
if (id && "writer" in BUILT_IN_PRESETS[id]) return this.builtInWriter(id);
|
|
993
|
+
}
|
|
994
|
+
findRule(input, filter) {
|
|
995
|
+
const info = pathToLocatorInfo(input);
|
|
996
|
+
for (const rule of this.rules) {
|
|
997
|
+
if (!filter(rule)) continue;
|
|
998
|
+
const { test } = rule;
|
|
999
|
+
if (Array.isArray(test)) {
|
|
1000
|
+
if (info.extension && test.includes(info.extension)) return rule;
|
|
1001
|
+
} else {
|
|
1002
|
+
test.lastIndex = 0;
|
|
1003
|
+
const matched = test.test(buildFilePath(info));
|
|
1004
|
+
test.lastIndex = 0;
|
|
1005
|
+
if (matched) return rule;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
findBuiltInId(input) {
|
|
1010
|
+
const info = pathToLocatorInfo(input);
|
|
1011
|
+
if (!info.extension) return;
|
|
1012
|
+
return this.builtInExtensions.get(info.extension);
|
|
1013
|
+
}
|
|
1014
|
+
unknownExtensionError(input) {
|
|
1015
|
+
return new LocterUnknownExtensionError({
|
|
1016
|
+
message: `No format registered for extension: ${pathToLocatorInfo(input).extension ?? "unknown"}`,
|
|
1017
|
+
path: input
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
compile(id, rule) {
|
|
1021
|
+
return {
|
|
1022
|
+
id,
|
|
1023
|
+
test: rule.test,
|
|
1024
|
+
getReader: compileSlot(rule.reader),
|
|
1025
|
+
getWriter: compileSlot(rule.writer)
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
generateRuleId() {
|
|
1029
|
+
let id;
|
|
1030
|
+
do {
|
|
1031
|
+
this.ruleCounter++;
|
|
1032
|
+
id = `rule:${this.ruleCounter}`;
|
|
1033
|
+
} while (this.rules.some((item) => item.id === id));
|
|
1034
|
+
return id;
|
|
1035
|
+
}
|
|
1036
|
+
};
|
|
1037
|
+
//#endregion
|
|
1038
|
+
//#region src/format/registry/singleton.ts
|
|
685
1039
|
let instance;
|
|
686
|
-
function
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
instance = new LoaderManager();
|
|
691
|
-
return instance;
|
|
1040
|
+
function useFormatRegistry() {
|
|
1041
|
+
if (typeof instance !== "undefined") return instance;
|
|
1042
|
+
instance = new FormatRegistry();
|
|
1043
|
+
return instance;
|
|
692
1044
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
1045
|
+
//#endregion
|
|
1046
|
+
//#region src/format/helpers.ts
|
|
1047
|
+
function registerFormat(rule) {
|
|
1048
|
+
return useFormatRegistry().register(rule);
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* Remove a rule from the process-global registry by its id
|
|
1052
|
+
* (as returned by registerFormat). Built-in ids cannot be unregistered.
|
|
1053
|
+
*/
|
|
1054
|
+
function unregisterFormat(id) {
|
|
1055
|
+
return useFormatRegistry().unregister(id);
|
|
1056
|
+
}
|
|
1057
|
+
async function read(input) {
|
|
1058
|
+
return useFormatRegistry().read(input);
|
|
1059
|
+
}
|
|
1060
|
+
function readSync(input) {
|
|
1061
|
+
return useFormatRegistry().readSync(input);
|
|
1062
|
+
}
|
|
1063
|
+
async function write(input, value) {
|
|
1064
|
+
return useFormatRegistry().write(input, value);
|
|
1065
|
+
}
|
|
1066
|
+
function writeSync(input, value) {
|
|
1067
|
+
useFormatRegistry().writeSync(input, value);
|
|
1068
|
+
}
|
|
1069
|
+
/**
|
|
1070
|
+
* Override the built-in module reader's `import` / `require` calls.
|
|
1071
|
+
*
|
|
1072
|
+
* Useful for test runners (e.g. Vitest) where the dynamic `import()` inside
|
|
1073
|
+
* `node_modules/locter` would bypass the runner's module graph. Calling this
|
|
1074
|
+
* from user code (e.g. a Vitest setup file) lets the runner rewrite the
|
|
1075
|
+
* `import()` so loaded modules share identity with statically imported ones.
|
|
1076
|
+
*
|
|
1077
|
+
* Returns a restore function that re-applies the previous configuration —
|
|
1078
|
+
* handy for scoped overrides (e.g. test teardown). The restore function is
|
|
1079
|
+
* scoped to the exact reader instance it configured: after a registry
|
|
1080
|
+
* reset() (which discards that instance) it becomes a no-op instead of
|
|
1081
|
+
* re-applying stale configuration to the fresh instance.
|
|
1082
|
+
*
|
|
1083
|
+
* @example
|
|
1084
|
+
* ```ts
|
|
1085
|
+
* // vitest setup file
|
|
1086
|
+
* import { setModuleReader } from 'locter';
|
|
1087
|
+
*
|
|
1088
|
+
* setModuleReader({
|
|
1089
|
+
* load: (id) => import(id),
|
|
1090
|
+
* });
|
|
1091
|
+
* ```
|
|
1092
|
+
*/
|
|
1093
|
+
function setModuleReader(options) {
|
|
1094
|
+
const reader = useFormatRegistry().builtInReader("module");
|
|
1095
|
+
const previous = reader.configure(options);
|
|
1096
|
+
return () => {
|
|
1097
|
+
reader.configure(previous);
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
//#endregion
|
|
1101
|
+
//#region src/format/package-field.ts
|
|
1102
|
+
function extractField(pkg, field) {
|
|
1103
|
+
if (!isObject(pkg) || !hasOwnProperty(pkg, field)) return;
|
|
1104
|
+
return pkg[field];
|
|
1105
|
+
}
|
|
1106
|
+
function* locatePackageBody(options) {
|
|
1107
|
+
if (options.walkUp) return yield* op(() => locateUp("package.json", {
|
|
1108
|
+
cwd: options.cwd,
|
|
1109
|
+
stopAt: options.stopAt
|
|
1110
|
+
}), () => locateUpSync("package.json", {
|
|
1111
|
+
cwd: options.cwd,
|
|
1112
|
+
stopAt: options.stopAt
|
|
1113
|
+
}));
|
|
1114
|
+
return yield* op(() => locate("package.json", { cwd: options.cwd }), () => locateSync("package.json", { cwd: options.cwd }));
|
|
1115
|
+
}
|
|
1116
|
+
function* readPackageFieldBody(field, options) {
|
|
1117
|
+
const info = yield* locatePackageBody(options);
|
|
1118
|
+
if (!info) return;
|
|
1119
|
+
const reader = useFormatRegistry().builtInReader("json");
|
|
1120
|
+
const filePath = buildFilePath(info);
|
|
1121
|
+
try {
|
|
1122
|
+
return extractField(yield* op(() => reader.read(filePath), () => reader.readSync(filePath)), field);
|
|
1123
|
+
} catch (e) {
|
|
1124
|
+
if (e instanceof LocterNotFoundError) return;
|
|
1125
|
+
throw e;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
async function readPackageField(field, options = {}) {
|
|
1129
|
+
return runTwinAsync(readPackageFieldBody(field, options));
|
|
1130
|
+
}
|
|
1131
|
+
function readPackageFieldSync(field, options = {}) {
|
|
1132
|
+
return runTwinSync(readPackageFieldBody(field, options));
|
|
1133
|
+
}
|
|
1134
|
+
function* writePackageFieldBody(field, value, options) {
|
|
1135
|
+
if (!isSafeObjectKey(field)) throw new LocterError({ message: `The field ${field} is not a safe object key.` });
|
|
1136
|
+
const info = yield* locatePackageBody(options);
|
|
1137
|
+
if (!info) throw new LocterNotFoundError({ message: "No package.json could be located." });
|
|
1138
|
+
const reader = useFormatRegistry().builtInReader("json");
|
|
1139
|
+
const filePath = buildFilePath(info);
|
|
1140
|
+
const pkg = yield* op(() => reader.read(filePath), () => reader.readSync(filePath));
|
|
1141
|
+
if (!isObject(pkg)) throw new LocterWriteError({
|
|
1142
|
+
message: "The located package.json does not contain an object.",
|
|
1143
|
+
path: filePath
|
|
1144
|
+
});
|
|
1145
|
+
pkg[field] = value;
|
|
1146
|
+
const writer = new JSONWriter({ indent: "auto" });
|
|
1147
|
+
yield* op(() => writer.write(filePath, pkg), () => writer.writeSync(filePath, pkg));
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Set a top-level field of the nearest `package.json` (same locate
|
|
1151
|
+
* semantics as readPackageField) and write it back, preserving the
|
|
1152
|
+
* file's existing indentation. Passing `undefined` removes the field.
|
|
1153
|
+
* Throws LocterNotFoundError when no package.json could be located.
|
|
1154
|
+
*/
|
|
1155
|
+
async function writePackageField(field, value, options = {}) {
|
|
1156
|
+
return runTwinAsync(writePackageFieldBody(field, value, options));
|
|
1157
|
+
}
|
|
1158
|
+
function writePackageFieldSync(field, value, options = {}) {
|
|
1159
|
+
runTwinSync(writePackageFieldBody(field, value, options));
|
|
715
1160
|
}
|
|
1161
|
+
//#endregion
|
|
1162
|
+
export { ConfReader, ConfWriter, FormatRegistry, JSONReader, JSONWriter, LOCTER_ERROR_MARKER, LOCTER_LOAD_ERROR_MARKER, LOCTER_NOT_FOUND_ERROR_MARKER, LOCTER_UNKNOWN_EXTENSION_ERROR_MARKER, LOCTER_WRITE_ERROR_MARKER, LocterError, LocterLoadError, LocterNotFoundError, LocterUnknownExtensionError, LocterWriteError, MODULE_FILE_EXTENSIONS, ModuleReader, TextFileReader, TextFileWriter, YAMLReader, YAMLWriter, buildFilePath, getModuleExport, isJestRuntimeEnvironment, isLocatorInfo, isModuleRecord, isTsNodeRuntimeEnvironment, isTsxRuntimeEnvironment, isVitestRuntimeEnvironment, locate, locateMany, locateManySync, locateSync, locateUp, locateUpSync, read, readPackageField, readPackageFieldSync, readSync, registerFormat, setModuleReader, unregisterFormat, useFormatRegistry, wrapLoaderError, wrapWriteError, write, writePackageField, writePackageFieldSync, writeSync };
|
|
716
1163
|
|
|
717
|
-
|
|
718
|
-
//# sourceMappingURL=index.mjs.map
|
|
1164
|
+
//# sourceMappingURL=index.mjs.map
|