@o3co/ts.hocon 1.2.0 → 1.4.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.ja.md +4 -2
- package/README.md +59 -8
- package/dist/config-BwjvKeZ5.d.cts +271 -0
- package/dist/config-BwjvKeZ5.d.ts +271 -0
- package/dist/index.cjs +1379 -400
- package/dist/index.d.cts +45 -2
- package/dist/index.d.ts +45 -2
- package/dist/index.js +1368 -399
- package/dist/zod.cjs +1112 -253
- package/dist/zod.d.cts +1 -1
- package/dist/zod.d.ts +1 -1
- package/dist/zod.js +1111 -253
- package/package.json +3 -1
- package/dist/parse-nNbyqnOh.d.cts +0 -53
- package/dist/parse-nNbyqnOh.d.ts +0 -53
package/dist/index.cjs
CHANGED
|
@@ -32,17 +32,47 @@ var src_exports = {};
|
|
|
32
32
|
__export(src_exports, {
|
|
33
33
|
Config: () => Config,
|
|
34
34
|
ConfigError: () => ConfigError,
|
|
35
|
+
NotResolvedError: () => NotResolvedError,
|
|
36
|
+
PackageLookupError: () => PackageLookupError,
|
|
35
37
|
ParseError: () => ParseError,
|
|
36
38
|
ResolveError: () => ResolveError,
|
|
39
|
+
defaultParseOptions: () => defaultParseOptions,
|
|
40
|
+
defaultResolveOptions: () => defaultResolveOptions,
|
|
41
|
+
empty: () => empty,
|
|
42
|
+
fromMap: () => fromMap,
|
|
37
43
|
parse: () => parse,
|
|
38
44
|
parseAsync: () => parseAsync,
|
|
39
45
|
parseFile: () => parseFile,
|
|
40
|
-
parseFileAsync: () => parseFileAsync
|
|
46
|
+
parseFileAsync: () => parseFileAsync,
|
|
47
|
+
parseFileWithOptions: () => parseFileWithOptions,
|
|
48
|
+
parseString: () => parseString,
|
|
49
|
+
parseStringWithOptions: () => parseStringWithOptions
|
|
41
50
|
});
|
|
42
51
|
module.exports = __toCommonJS(src_exports);
|
|
43
52
|
|
|
44
53
|
// src/coerce.ts
|
|
45
54
|
var DECIMAL_NUMBER_RE = /^-?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/;
|
|
55
|
+
function isHoconWs(cp) {
|
|
56
|
+
if (cp === 9 || cp === 10 || cp === 11 || cp === 12 || cp === 13) return true;
|
|
57
|
+
if (cp >= 28 && cp <= 31) return true;
|
|
58
|
+
if (cp === 32 || cp === 160 || cp === 65279) return true;
|
|
59
|
+
if (cp === 5760) return true;
|
|
60
|
+
if (cp >= 8192 && cp <= 8202) return true;
|
|
61
|
+
if (cp === 8232 || cp === 8233 || cp === 8239 || cp === 8287) return true;
|
|
62
|
+
if (cp === 12288) return true;
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
function trimHoconWs(s) {
|
|
66
|
+
let start = 0;
|
|
67
|
+
let end = s.length;
|
|
68
|
+
while (start < end && isHoconWs(s.charCodeAt(start))) {
|
|
69
|
+
start++;
|
|
70
|
+
}
|
|
71
|
+
while (end > start && isHoconWs(s.charCodeAt(end - 1))) {
|
|
72
|
+
end--;
|
|
73
|
+
}
|
|
74
|
+
return s.slice(start, end);
|
|
75
|
+
}
|
|
46
76
|
var TRUTHY = /* @__PURE__ */ new Set(["true", "yes", "on"]);
|
|
47
77
|
var FALSY = /* @__PURE__ */ new Set(["false", "no", "off"]);
|
|
48
78
|
function coerceBoolean(value) {
|
|
@@ -88,22 +118,29 @@ var OUTPUT_DURATION_UNITS = {
|
|
|
88
118
|
d: 864e5
|
|
89
119
|
};
|
|
90
120
|
function parseDuration(value, outputUnit = "ms") {
|
|
91
|
-
const trimmed = value
|
|
121
|
+
const trimmed = trimHoconWs(value);
|
|
92
122
|
let i = 0;
|
|
93
123
|
while (i < trimmed.length) {
|
|
94
124
|
const ch = trimmed[i];
|
|
95
|
-
if (
|
|
125
|
+
if (i === 0 && (ch === "-" || ch === "+")) {
|
|
126
|
+
i++;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (ch !== "." && (ch < "0" || ch > "9")) break;
|
|
96
130
|
i++;
|
|
97
131
|
}
|
|
98
132
|
if (i === 0) return NaN;
|
|
99
133
|
const num = Number(trimmed.slice(0, i));
|
|
100
134
|
if (Number.isNaN(num)) return NaN;
|
|
101
|
-
const unit = trimmed.slice(i)
|
|
135
|
+
const unit = trimHoconWs(trimmed.slice(i)).toLowerCase();
|
|
136
|
+
const divisor = OUTPUT_DURATION_UNITS[outputUnit];
|
|
137
|
+
if (divisor === void 0) return NaN;
|
|
138
|
+
if (unit === "") {
|
|
139
|
+
return num / divisor;
|
|
140
|
+
}
|
|
102
141
|
const mult = DURATION_UNITS[unit];
|
|
103
142
|
if (mult === void 0) return NaN;
|
|
104
143
|
const ms = num * mult;
|
|
105
|
-
const divisor = OUTPUT_DURATION_UNITS[outputUnit];
|
|
106
|
-
if (divisor === void 0) return NaN;
|
|
107
144
|
return ms / divisor;
|
|
108
145
|
}
|
|
109
146
|
var BYTE_UNITS = {
|
|
@@ -134,6 +171,22 @@ var BYTE_UNITS = {
|
|
|
134
171
|
TiB: 1099511627776,
|
|
135
172
|
tebibyte: 1099511627776,
|
|
136
173
|
tebibytes: 1099511627776,
|
|
174
|
+
// S21.4 — HOCON.md L1385: single-letter abbreviations → powers of two (java -Xmx convention).
|
|
175
|
+
// Lightbend typesafe-config 1.4.3 verified: 1K=1024, 1M=1048576, etc.
|
|
176
|
+
// Both upper- and lowercase accepted per Lightbend behaviour.
|
|
177
|
+
// Z/Y (2^70/2^80) are deferred — they overflow MAX_SAFE_INTEGER and require BigInt accessor.
|
|
178
|
+
K: 1024,
|
|
179
|
+
k: 1024,
|
|
180
|
+
M: 1024 ** 2,
|
|
181
|
+
m: 1024 ** 2,
|
|
182
|
+
G: 1024 ** 3,
|
|
183
|
+
g: 1024 ** 3,
|
|
184
|
+
T: 1024 ** 4,
|
|
185
|
+
t: 1024 ** 4,
|
|
186
|
+
P: 1024 ** 5,
|
|
187
|
+
p: 1024 ** 5,
|
|
188
|
+
E: 1024 ** 6,
|
|
189
|
+
e: 1024 ** 6,
|
|
137
190
|
// lowercase short-form aliases
|
|
138
191
|
b: 1,
|
|
139
192
|
kb: 1e3,
|
|
@@ -157,25 +210,39 @@ var OUTPUT_BYTE_UNITS = {
|
|
|
157
210
|
TiB: 1099511627776
|
|
158
211
|
};
|
|
159
212
|
function parseBytes(value, outputUnit = "B") {
|
|
160
|
-
const trimmed = value
|
|
213
|
+
const trimmed = trimHoconWs(value);
|
|
161
214
|
let i = 0;
|
|
162
215
|
while (i < trimmed.length) {
|
|
163
216
|
const ch = trimmed[i];
|
|
164
|
-
if (
|
|
217
|
+
if (i === 0 && (ch === "-" || ch === "+")) {
|
|
218
|
+
i++;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (ch !== "." && (ch < "0" || ch > "9")) break;
|
|
165
222
|
i++;
|
|
166
223
|
}
|
|
167
224
|
if (i === 0) return NaN;
|
|
168
225
|
const num = Number(trimmed.slice(0, i));
|
|
169
226
|
if (Number.isNaN(num)) return NaN;
|
|
170
|
-
const unit = trimmed.slice(i)
|
|
227
|
+
const unit = trimHoconWs(trimmed.slice(i));
|
|
228
|
+
const divisor = OUTPUT_BYTE_UNITS[outputUnit];
|
|
229
|
+
if (divisor === void 0) return NaN;
|
|
230
|
+
if (unit === "") {
|
|
231
|
+
const bytes2 = Math.trunc(num);
|
|
232
|
+
if (Math.abs(bytes2) > Number.MAX_SAFE_INTEGER) {
|
|
233
|
+
throw new RangeError("byte size overflows representable range (max 2^53-1 bytes)");
|
|
234
|
+
}
|
|
235
|
+
return bytes2 / divisor;
|
|
236
|
+
}
|
|
171
237
|
let mult = BYTE_UNITS[unit];
|
|
172
238
|
if (mult === void 0) {
|
|
173
239
|
mult = BYTE_UNITS[unit.toLowerCase()];
|
|
174
240
|
}
|
|
175
241
|
if (mult === void 0) return NaN;
|
|
176
242
|
const bytes = num * mult;
|
|
177
|
-
|
|
178
|
-
|
|
243
|
+
if (Math.abs(bytes) > Number.MAX_SAFE_INTEGER) {
|
|
244
|
+
throw new RangeError("byte size overflows representable range (max 2^53-1 bytes)");
|
|
245
|
+
}
|
|
179
246
|
const result = bytes / divisor;
|
|
180
247
|
return outputUnit === "B" ? Math.round(result) : result;
|
|
181
248
|
}
|
|
@@ -200,6 +267,14 @@ var ResolveError = class extends Error {
|
|
|
200
267
|
this.name = "ResolveError";
|
|
201
268
|
}
|
|
202
269
|
};
|
|
270
|
+
var PackageLookupError = class extends ResolveError {
|
|
271
|
+
constructor(message, identifier, packageFile, line, col) {
|
|
272
|
+
super(message, `${identifier}/${packageFile}`, line, col);
|
|
273
|
+
this.identifier = identifier;
|
|
274
|
+
this.packageFile = packageFile;
|
|
275
|
+
this.name = "PackageLookupError";
|
|
276
|
+
}
|
|
277
|
+
};
|
|
203
278
|
var ConfigError = class extends Error {
|
|
204
279
|
constructor(message, path2) {
|
|
205
280
|
super(message);
|
|
@@ -207,190 +282,155 @@ var ConfigError = class extends Error {
|
|
|
207
282
|
this.name = "ConfigError";
|
|
208
283
|
}
|
|
209
284
|
};
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
if (value.fields.size === 0) return null;
|
|
215
|
-
const canonicalInt = /^(0|[1-9][0-9]*)$/;
|
|
216
|
-
const eligible = [];
|
|
217
|
-
for (const [key, val] of value.fields) {
|
|
218
|
-
if (!canonicalInt.test(key)) continue;
|
|
219
|
-
const n = Number(key);
|
|
220
|
-
if (!Number.isInteger(n) || n > 2147483647) continue;
|
|
221
|
-
eligible.push({ n, v: val });
|
|
285
|
+
var NotResolvedError = class extends ConfigError {
|
|
286
|
+
constructor(path2) {
|
|
287
|
+
super(`value at path "${path2}" is not resolved (call resolve() first)`, path2);
|
|
288
|
+
this.name = "NotResolvedError";
|
|
222
289
|
}
|
|
223
|
-
|
|
224
|
-
eligible.sort((a, b) => a.n - b.n);
|
|
225
|
-
return eligible.map((e) => e.v);
|
|
226
|
-
}
|
|
290
|
+
};
|
|
227
291
|
|
|
228
|
-
// src/
|
|
229
|
-
var
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const v = this.requireScalar(path2);
|
|
250
|
-
const coerced = coerceBoolean(v.raw);
|
|
251
|
-
if (coerced !== void 0) return coerced;
|
|
252
|
-
throw new ConfigError(`expected boolean at ${path2}, got ${v.valueType}`, path2);
|
|
292
|
+
// src/internal/resolver/types.ts
|
|
293
|
+
var separatorValues = /* @__PURE__ */ new WeakSet();
|
|
294
|
+
function isSubst(v) {
|
|
295
|
+
return v._kind === "subst-placeholder";
|
|
296
|
+
}
|
|
297
|
+
function isConcat(v) {
|
|
298
|
+
return v._kind === "concat-placeholder";
|
|
299
|
+
}
|
|
300
|
+
function isAppend(v) {
|
|
301
|
+
return v._kind === "append-placeholder";
|
|
302
|
+
}
|
|
303
|
+
function isResObj(v) {
|
|
304
|
+
return v._kind === "res-obj";
|
|
305
|
+
}
|
|
306
|
+
function makeResObj() {
|
|
307
|
+
return { _kind: "res-obj", fields: /* @__PURE__ */ new Map(), priorValues: /* @__PURE__ */ new Map() };
|
|
308
|
+
}
|
|
309
|
+
function mergeUnresolved(receiver, fallback) {
|
|
310
|
+
const result = makeResObj();
|
|
311
|
+
for (const [k, v] of fallback.fields) {
|
|
312
|
+
result.fields.set(k, v);
|
|
253
313
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
if (v.valueType !== "string" && v.valueType !== "number") {
|
|
257
|
-
throw new ConfigError(`expected duration at ${path2}, got ${v.valueType}`, path2);
|
|
258
|
-
}
|
|
259
|
-
const result = parseDuration(v.raw, unit);
|
|
260
|
-
if (Number.isNaN(result)) throw new ConfigError(`invalid duration at ${path2}: ${JSON.stringify(v.raw)}`, path2);
|
|
261
|
-
return result;
|
|
314
|
+
for (const [k, v] of fallback.priorValues) {
|
|
315
|
+
result.priorValues.set(k, v);
|
|
262
316
|
}
|
|
263
|
-
|
|
264
|
-
const
|
|
265
|
-
if (
|
|
266
|
-
|
|
317
|
+
for (const [k, rv] of receiver.fields) {
|
|
318
|
+
const existing = result.fields.get(k);
|
|
319
|
+
if (existing !== void 0) {
|
|
320
|
+
const recPrior = receiver.priorValues.get(k);
|
|
321
|
+
if (recPrior !== void 0 && !isResObj(recPrior)) {
|
|
322
|
+
result.fields.set(k, rv);
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (isResObj(rv) && isResObj(existing)) {
|
|
326
|
+
result.fields.set(k, mergeUnresolved(rv, existing));
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
result.priorValues.set(k, existing);
|
|
267
330
|
}
|
|
268
|
-
|
|
269
|
-
if (Number.isNaN(result)) throw new ConfigError(`invalid byte size at ${path2}: ${JSON.stringify(v.raw)}`, path2);
|
|
270
|
-
return result;
|
|
271
|
-
}
|
|
272
|
-
getConfig(path2) {
|
|
273
|
-
const v = this.lookupNode(path2);
|
|
274
|
-
if (v === void 0) throw new ConfigError(`path not found: ${path2}`, path2);
|
|
275
|
-
if (v.kind !== "object") throw new ConfigError(`expected object at ${path2}`, path2);
|
|
276
|
-
return new _Config(v);
|
|
331
|
+
result.fields.set(k, rv);
|
|
277
332
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
if (v.kind === "object") {
|
|
282
|
-
const converted = numericObjectToArray(v);
|
|
283
|
-
if (converted !== null) return converted.map(hoconToJs);
|
|
333
|
+
for (const [k, v] of receiver.priorValues) {
|
|
334
|
+
if (receiver.fields.has(k)) {
|
|
335
|
+
result.priorValues.set(k, v);
|
|
284
336
|
}
|
|
285
|
-
if (v.kind !== "array") throw new ConfigError(`expected array at ${path2}`, path2);
|
|
286
|
-
return v.items.map(hoconToJs);
|
|
287
|
-
}
|
|
288
|
-
has(path2) {
|
|
289
|
-
return this.lookupNode(path2) !== void 0;
|
|
290
|
-
}
|
|
291
|
-
keys() {
|
|
292
|
-
return [...this.root.fields.keys()];
|
|
293
|
-
}
|
|
294
|
-
withFallback(fallback) {
|
|
295
|
-
const merged = deepMergeHocon(this.root, fallback.root);
|
|
296
|
-
return new _Config(merged);
|
|
297
|
-
}
|
|
298
|
-
toObject() {
|
|
299
|
-
return hoconToJs(this.root);
|
|
300
337
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
338
|
+
return result;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// src/internal/resolver/utils.ts
|
|
342
|
+
function segmentsToKey(segments) {
|
|
343
|
+
return segments.map((s) => {
|
|
344
|
+
const t = s.text;
|
|
345
|
+
if (t === "" || /[^a-zA-Z0-9\-_]/.test(t)) {
|
|
346
|
+
return `"${t.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
309
347
|
}
|
|
310
|
-
return
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
348
|
+
return t;
|
|
349
|
+
}).join(".");
|
|
350
|
+
}
|
|
351
|
+
function lookupPath(root, segments) {
|
|
352
|
+
const [head, ...tail] = segments;
|
|
353
|
+
if (head === void 0) return void 0;
|
|
354
|
+
const key = head.text;
|
|
355
|
+
const val = root.fields.get(key);
|
|
356
|
+
if (val === void 0) return void 0;
|
|
357
|
+
if (tail.length === 0) return val;
|
|
358
|
+
if (isResObj(val)) return lookupPath(val, tail);
|
|
359
|
+
return void 0;
|
|
360
|
+
}
|
|
361
|
+
function lookupResObj(root, segments) {
|
|
362
|
+
let cur = root;
|
|
363
|
+
for (const seg of segments) {
|
|
364
|
+
const val = cur.fields.get(seg.text);
|
|
365
|
+
if (val === void 0 || !isResObj(val)) return void 0;
|
|
366
|
+
cur = val;
|
|
317
367
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
let closed = false;
|
|
327
|
-
while (i < path2.length) {
|
|
328
|
-
const ch = path2[i];
|
|
329
|
-
if (ch === "\\" && i + 1 < path2.length) {
|
|
330
|
-
const next = path2[i + 1];
|
|
331
|
-
segment += next;
|
|
332
|
-
i += 2;
|
|
333
|
-
continue;
|
|
334
|
-
}
|
|
335
|
-
if (ch === '"') {
|
|
336
|
-
closed = true;
|
|
337
|
-
i++;
|
|
338
|
-
break;
|
|
339
|
-
}
|
|
340
|
-
segment += ch;
|
|
341
|
-
i++;
|
|
342
|
-
}
|
|
343
|
-
if (!closed) throw new ConfigError(`unterminated quoted path segment: ${path2}`, path2);
|
|
344
|
-
segments.push(segment);
|
|
345
|
-
if (i < path2.length && path2[i] === ".") i++;
|
|
368
|
+
return cur;
|
|
369
|
+
}
|
|
370
|
+
function deepMergeHoconValues(base, overlay) {
|
|
371
|
+
const merged = new Map(base.fields);
|
|
372
|
+
for (const [k, v] of overlay.fields) {
|
|
373
|
+
const existing = merged.get(k);
|
|
374
|
+
if (existing?.kind === "object" && v.kind === "object") {
|
|
375
|
+
merged.set(k, deepMergeHoconValues(existing, v));
|
|
346
376
|
} else {
|
|
347
|
-
|
|
348
|
-
if (dot === -1) {
|
|
349
|
-
segments.push(path2.slice(i));
|
|
350
|
-
break;
|
|
351
|
-
}
|
|
352
|
-
segments.push(path2.slice(i, dot));
|
|
353
|
-
i = dot + 1;
|
|
377
|
+
merged.set(k, v);
|
|
354
378
|
}
|
|
355
379
|
}
|
|
356
|
-
return
|
|
380
|
+
return { kind: "object", fields: merged };
|
|
357
381
|
}
|
|
358
|
-
function
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
return raw;
|
|
382
|
+
function deepMergeResObjInto(dst, src) {
|
|
383
|
+
for (const [k, srcVal] of src.fields) {
|
|
384
|
+
const dstVal = dst.fields.get(k);
|
|
385
|
+
if (dstVal !== void 0 && isResObj(dstVal) && isResObj(srcVal)) {
|
|
386
|
+
deepMergeResObjInto(dstVal, srcVal);
|
|
387
|
+
} else {
|
|
388
|
+
if (dstVal !== void 0) dst.priorValues.set(k, dstVal);
|
|
389
|
+
dst.fields.set(k, srcVal);
|
|
390
|
+
}
|
|
368
391
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
case "scalar":
|
|
373
|
-
return scalarToJs(v.raw, v.valueType);
|
|
374
|
-
case "array":
|
|
375
|
-
return v.items.map(hoconToJs);
|
|
376
|
-
case "object": {
|
|
377
|
-
const obj = {};
|
|
378
|
-
for (const [k, val] of v.fields) obj[k] = hoconToJs(val);
|
|
379
|
-
return obj;
|
|
392
|
+
for (const [k, srcPrior] of src.priorValues) {
|
|
393
|
+
if (!dst.priorValues.has(k)) {
|
|
394
|
+
dst.priorValues.set(k, srcPrior);
|
|
380
395
|
}
|
|
381
396
|
}
|
|
382
397
|
}
|
|
383
|
-
function
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
if (
|
|
388
|
-
|
|
398
|
+
function hoconValueToResObj(hv) {
|
|
399
|
+
const obj = makeResObj();
|
|
400
|
+
if (hv.kind !== "object") return obj;
|
|
401
|
+
for (const [key, val] of hv.fields) {
|
|
402
|
+
if (val.kind === "object") {
|
|
403
|
+
obj.fields.set(key, hoconValueToResObj(val));
|
|
389
404
|
} else {
|
|
390
|
-
|
|
405
|
+
obj.fields.set(key, val);
|
|
391
406
|
}
|
|
392
407
|
}
|
|
393
|
-
return
|
|
408
|
+
return obj;
|
|
409
|
+
}
|
|
410
|
+
function isFileNotFoundError(e) {
|
|
411
|
+
if (!(e instanceof Error)) return false;
|
|
412
|
+
const code = e.code;
|
|
413
|
+
if (code === "ENOENT" || code === "MODULE_NOT_FOUND") return true;
|
|
414
|
+
const msg = e.message.toLowerCase();
|
|
415
|
+
return msg.includes("not found") || msg.includes("no such file") || msg.includes("enoent");
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// src/internal/resolver/include-loader.ts
|
|
419
|
+
var import_node_module = require("module");
|
|
420
|
+
var nodePath = __toESM(require("path"), 1);
|
|
421
|
+
|
|
422
|
+
// src/internal/parser/empty-check.ts
|
|
423
|
+
function hasContentTokens(tokens) {
|
|
424
|
+
return tokens.some((t) => t.kind !== "eof" && t.kind !== "newline");
|
|
425
|
+
}
|
|
426
|
+
function assertNonEmptyDocument(tokens, sourceDescriptor) {
|
|
427
|
+
if (!hasContentTokens(tokens)) {
|
|
428
|
+
throw new ParseError(
|
|
429
|
+
`empty file is not a valid HOCON document (HOCON.md L130): ${sourceDescriptor}`,
|
|
430
|
+
1,
|
|
431
|
+
1
|
|
432
|
+
);
|
|
433
|
+
}
|
|
394
434
|
}
|
|
395
435
|
|
|
396
436
|
// src/internal/lexer/lexer.ts
|
|
@@ -520,14 +560,6 @@ var Lexer = class {
|
|
|
520
560
|
continue;
|
|
521
561
|
}
|
|
522
562
|
if (isUnquotedStart(ch)) {
|
|
523
|
-
if (ch === "-" && !isDecimalDigit(this.peek(1))) {
|
|
524
|
-
const after = this.peek(1) === "" ? "EOF" : JSON.stringify(this.peek(1));
|
|
525
|
-
throw new ParseError(
|
|
526
|
-
`unquoted string cannot begin with '-' unless followed by a digit (got '-' then ${after}, HOCON.md L270-276)`,
|
|
527
|
-
sl,
|
|
528
|
-
sc
|
|
529
|
-
);
|
|
530
|
-
}
|
|
531
563
|
let value = "";
|
|
532
564
|
while (this.pos < this.input.length && isUnquotedContinue(this.peek(), () => this.peek(1))) {
|
|
533
565
|
value += this.advance();
|
|
@@ -631,6 +663,10 @@ var Lexer = class {
|
|
|
631
663
|
/**
|
|
632
664
|
* Parse the body of a `${...}` substitution (called after `${` has been consumed).
|
|
633
665
|
* Implements Appendix A state machine from the cross-language spec.
|
|
666
|
+
*
|
|
667
|
+
* S13c extension: if the segment-collection loop encounters `[`, it delegates to
|
|
668
|
+
* `parseListSuffix()` which consumes the literal `[]` and sets `listSuffix=true`.
|
|
669
|
+
* ASCII space/tab before `[` is buffered in `pendingWs` and discarded (E7).
|
|
634
670
|
*/
|
|
635
671
|
parseSubstBody(startLine, startCol) {
|
|
636
672
|
let optional = false;
|
|
@@ -645,6 +681,7 @@ var Lexer = class {
|
|
|
645
681
|
let pendingWs = "";
|
|
646
682
|
const segments = [];
|
|
647
683
|
let lastDot = null;
|
|
684
|
+
let listSuffix = false;
|
|
648
685
|
while (true) {
|
|
649
686
|
if (this.pos >= this.input.length) {
|
|
650
687
|
throw new ParseError("unterminated substitution", startLine, startCol);
|
|
@@ -654,6 +691,23 @@ var Lexer = class {
|
|
|
654
691
|
this.advance();
|
|
655
692
|
pendingWs = "";
|
|
656
693
|
break;
|
|
694
|
+
} else if (ch === "[") {
|
|
695
|
+
if (!curStarted) {
|
|
696
|
+
throw new ParseError("empty segment before '[]' suffix in substitution path", startLine, this.col);
|
|
697
|
+
}
|
|
698
|
+
for (const w of pendingWs) {
|
|
699
|
+
if (w !== " " && w !== " ") {
|
|
700
|
+
throw new ParseError(
|
|
701
|
+
`only ASCII space or tab allowed between substitution path and '[]' suffix (got ${JSON.stringify(w)}, HOCON extra-spec E7)`,
|
|
702
|
+
startLine,
|
|
703
|
+
this.col
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
segments.push({ text: curText, line: curLine, col: curCol });
|
|
708
|
+
pendingWs = "";
|
|
709
|
+
listSuffix = this.parseListSuffix(startLine);
|
|
710
|
+
break;
|
|
657
711
|
} else if (ch === '"') {
|
|
658
712
|
const qLine = startLine;
|
|
659
713
|
const qCol = this.col;
|
|
@@ -713,15 +767,47 @@ var Lexer = class {
|
|
|
713
767
|
throw new ParseError(`unexpected character in substitution path: ${JSON.stringify(ch)}`, startLine, this.col);
|
|
714
768
|
}
|
|
715
769
|
}
|
|
716
|
-
if (
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
770
|
+
if (!listSuffix) {
|
|
771
|
+
if (curStarted) {
|
|
772
|
+
segments.push({ text: curText, line: curLine, col: curCol });
|
|
773
|
+
} else if (segments.length === 0) {
|
|
774
|
+
throw new ParseError("empty substitution path", startLine, startCol);
|
|
775
|
+
} else {
|
|
776
|
+
const [errLine, errCol] = lastDot ?? [startLine, startCol];
|
|
777
|
+
throw new ParseError("empty segment in path", errLine, errCol);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
return { segments, optional, listSuffix };
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Consume the literal `[]` suffix inside a substitution body.
|
|
784
|
+
* Called after the `[` has been detected (but not yet consumed).
|
|
785
|
+
* Requires the next character to be `[`, then exactly `]`, then `}`.
|
|
786
|
+
* Whitespace inside `[]` is not permitted (per spec Decision §1).
|
|
787
|
+
* Returns true (always — only called when listSuffix path is taken).
|
|
788
|
+
*/
|
|
789
|
+
parseListSuffix(startLine) {
|
|
790
|
+
this.advance();
|
|
791
|
+
const afterBracket = this.peek();
|
|
792
|
+
if (afterBracket !== "]") {
|
|
793
|
+
const desc = afterBracket === "" ? "EOF" : JSON.stringify(afterBracket);
|
|
794
|
+
throw new ParseError(
|
|
795
|
+
`expected ']' after '[' in substitution list suffix (got ${desc})`,
|
|
796
|
+
startLine,
|
|
797
|
+
this.col
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
this.advance();
|
|
801
|
+
if (this.peek() !== "}") {
|
|
802
|
+
const desc = this.peek() === "" ? "EOF" : JSON.stringify(this.peek());
|
|
803
|
+
throw new ParseError(
|
|
804
|
+
`expected '}' after '[]' in substitution list suffix (got ${desc})`,
|
|
805
|
+
startLine,
|
|
806
|
+
this.col
|
|
807
|
+
);
|
|
723
808
|
}
|
|
724
|
-
|
|
809
|
+
this.advance();
|
|
810
|
+
return true;
|
|
725
811
|
}
|
|
726
812
|
};
|
|
727
813
|
function tokenize(input) {
|
|
@@ -809,6 +895,14 @@ var Parser = class {
|
|
|
809
895
|
const t = this.peek();
|
|
810
896
|
if (t.kind === "eof" || t.kind === "rbrace") break;
|
|
811
897
|
if (t.kind === "unquoted" && t.value === "include") {
|
|
898
|
+
const next = this.peek(1);
|
|
899
|
+
if (next.kind === "equals" || next.kind === "colon" || next.kind === "plus_equals" || next.kind === "lbrace") {
|
|
900
|
+
throw new ParseError(
|
|
901
|
+
`'include' is reserved at the start of a key path expression; use "include" (quoted) to use it as a key`,
|
|
902
|
+
t.line,
|
|
903
|
+
t.col
|
|
904
|
+
);
|
|
905
|
+
}
|
|
812
906
|
this.advance();
|
|
813
907
|
fields.push(this.parseInclude());
|
|
814
908
|
this.skip("newline");
|
|
@@ -817,7 +911,14 @@ var Parser = class {
|
|
|
817
911
|
continue;
|
|
818
912
|
}
|
|
819
913
|
const keyPos = this.currentPos();
|
|
820
|
-
const key = this.parseKey();
|
|
914
|
+
const { segments: key, firstWasQuoted } = this.parseKey();
|
|
915
|
+
if (key[0] === "include" && !firstWasQuoted) {
|
|
916
|
+
throw new ParseError(
|
|
917
|
+
`'include' is reserved at the start of a key path expression; use "include".foo (quoted) or rename the key`,
|
|
918
|
+
keyPos.line,
|
|
919
|
+
keyPos.col
|
|
920
|
+
);
|
|
921
|
+
}
|
|
821
922
|
this.skip("newline");
|
|
822
923
|
let append = false;
|
|
823
924
|
const sep = this.peek();
|
|
@@ -847,10 +948,12 @@ var Parser = class {
|
|
|
847
948
|
parseKey() {
|
|
848
949
|
const segments = [];
|
|
849
950
|
let trailingDot = false;
|
|
951
|
+
let firstWasQuoted = false;
|
|
850
952
|
while (true) {
|
|
851
953
|
const t = this.peek();
|
|
852
954
|
if (t.kind === "string") {
|
|
853
955
|
this.advance();
|
|
956
|
+
if (segments.length === 0) firstWasQuoted = true;
|
|
854
957
|
segments.push(t.value);
|
|
855
958
|
trailingDot = false;
|
|
856
959
|
} else if (t.kind === "unquoted") {
|
|
@@ -887,64 +990,143 @@ var Parser = class {
|
|
|
887
990
|
}
|
|
888
991
|
break;
|
|
889
992
|
}
|
|
890
|
-
return segments;
|
|
993
|
+
return { segments, firstWasQuoted };
|
|
891
994
|
}
|
|
892
995
|
parseInclude() {
|
|
893
996
|
const p = this.currentPos();
|
|
894
997
|
this.skip("newline");
|
|
895
998
|
const t = this.peek();
|
|
896
|
-
let path2;
|
|
897
|
-
let required = false;
|
|
898
|
-
let isFile = false;
|
|
899
999
|
if (t.kind === "unquoted" && (t.value === "required(" || t.value === "required" || t.value.startsWith("required("))) {
|
|
900
1000
|
if (t.value === "required") {
|
|
901
|
-
const
|
|
902
|
-
if (
|
|
1001
|
+
const next = this.peek(1);
|
|
1002
|
+
if (next.kind !== "unquoted" || !next.value.startsWith("(")) {
|
|
903
1003
|
throw new ParseError("include required must be followed by (", t.line, t.col);
|
|
904
1004
|
}
|
|
905
1005
|
}
|
|
906
|
-
required = true;
|
|
907
1006
|
this.advance();
|
|
908
1007
|
const innerPrefix = t.value.startsWith("required(") ? t.value.slice("required(".length) : "";
|
|
909
|
-
if (innerPrefix.startsWith("url") || innerPrefix.startsWith("classpath")) {
|
|
1008
|
+
if (innerPrefix === "url" || innerPrefix.startsWith("url(") || innerPrefix === "classpath" || innerPrefix.startsWith("classpath(")) {
|
|
910
1009
|
throw new ParseError("include url(...) and classpath(...) are not supported", t.line, t.col);
|
|
911
1010
|
}
|
|
912
|
-
|
|
913
|
-
|
|
1011
|
+
const nextTok = this.peek();
|
|
1012
|
+
if (innerPrefix === "package" || innerPrefix.startsWith("package(") || innerPrefix === "" && nextTok.kind === "unquoted" && (nextTok.value === "package(" || nextTok.value.startsWith("package("))) {
|
|
1013
|
+
const consumePackageToken = innerPrefix === "";
|
|
1014
|
+
const { qualifier, path: path3 } = this.parsePackageArgs(consumePackageToken);
|
|
1015
|
+
return this.makeIncludeField(qualifier, path3, true, p);
|
|
914
1016
|
}
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
1017
|
+
if (innerPrefix === "file" || innerPrefix.startsWith("file(") || innerPrefix === "" && nextTok.kind === "unquoted" && (nextTok.value === "file(" || nextTok.value === "file")) {
|
|
1018
|
+
if (innerPrefix === "") {
|
|
1019
|
+
this.advance();
|
|
1020
|
+
}
|
|
1021
|
+
const path3 = this.parseQuotedPathSkipWrapper(t);
|
|
1022
|
+
return this.makeIncludeField({ kind: "file" }, path3, true, p);
|
|
1023
|
+
}
|
|
1024
|
+
if (innerPrefix === "" && nextTok.kind === "unquoted" && (nextTok.value === "url" || nextTok.value.startsWith("url(") || nextTok.value === "classpath" || nextTok.value.startsWith("classpath("))) {
|
|
1025
|
+
throw new ParseError("include url(...) and classpath(...) are not supported", nextTok.line, nextTok.col);
|
|
1026
|
+
}
|
|
1027
|
+
if (innerPrefix !== "") {
|
|
1028
|
+
if (innerPrefix === ")") {
|
|
1029
|
+
throw new ParseError("include required(...) must contain a path", t.line, t.col);
|
|
1030
|
+
}
|
|
1031
|
+
const name = innerPrefix.replace(/\(.*$/, "").replace(/\).*$/, "");
|
|
1032
|
+
throw new ParseError(`unknown include qualifier inside required(): "${name}"`, t.line, t.col);
|
|
1033
|
+
}
|
|
1034
|
+
const path2 = this.parseQuotedPathSkipWrapper(t);
|
|
1035
|
+
return this.makeIncludeField({ kind: "bare" }, path2, true, p);
|
|
1036
|
+
}
|
|
1037
|
+
if (t.kind === "string") {
|
|
1038
|
+
const path2 = this.advance().value;
|
|
1039
|
+
return this.makeIncludeField({ kind: "bare" }, path2, false, p);
|
|
1040
|
+
}
|
|
1041
|
+
if (t.kind === "unquoted" && (t.value === "file(" || t.value === "file")) {
|
|
930
1042
|
this.advance();
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
1043
|
+
const path2 = this.parseQuotedPathSkipWrapper(t);
|
|
1044
|
+
return this.makeIncludeField({ kind: "file" }, path2, false, p);
|
|
1045
|
+
}
|
|
1046
|
+
if (t.kind === "unquoted" && (t.value === "package(" || t.value.startsWith("package("))) {
|
|
1047
|
+
const { qualifier, path: path2 } = this.parsePackageArgs();
|
|
1048
|
+
return this.makeIncludeField(qualifier, path2, false, p);
|
|
1049
|
+
}
|
|
1050
|
+
if (t.kind === "unquoted" && (t.value === "url" || t.value.startsWith("url("))) {
|
|
936
1051
|
throw new ParseError("include url(...) is not supported", t.line, t.col);
|
|
937
|
-
}
|
|
1052
|
+
}
|
|
1053
|
+
if (t.kind === "unquoted" && (t.value === "classpath" || t.value.startsWith("classpath("))) {
|
|
938
1054
|
throw new ParseError("include classpath(...) is not supported", t.line, t.col);
|
|
939
|
-
} else {
|
|
940
|
-
throw new ParseError(`expected include path, got ${t.kind}`, t.line, t.col);
|
|
941
1055
|
}
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
1056
|
+
throw new ParseError(`expected include path, got ${t.kind}`, t.line, t.col);
|
|
1057
|
+
}
|
|
1058
|
+
makeIncludeField(qualifier, path2, required, pos) {
|
|
1059
|
+
return { key: [], value: { kind: "include", qualifier, path: path2, required, pos }, append: false, pos };
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* Skip past include-syntax wrapper tokens to find the quoted path string.
|
|
1063
|
+
* Strict: only allows `(`, `)`, and bare qualifier keywords (file / file( /
|
|
1064
|
+
* package / package( / url / url( / classpath / classpath() between the
|
|
1065
|
+
* directive and the quoted path. Anything else is a parse error rather than
|
|
1066
|
+
* silently swallowed (see ts#113).
|
|
1067
|
+
*/
|
|
1068
|
+
parseQuotedPathSkipWrapper(errTok) {
|
|
1069
|
+
while (this.isIncludeWrapperToken(this.peek())) this.advance();
|
|
1070
|
+
if (this.peek().kind !== "string") {
|
|
1071
|
+
const tok = this.peek();
|
|
1072
|
+
if (tok.kind === "eof") throw new ParseError("expected include path", errTok.line, errTok.col);
|
|
1073
|
+
const desc = tok.kind === "unquoted" ? `unquoted "${tok.value}"` : tok.kind;
|
|
1074
|
+
throw new ParseError(`expected quoted include path, got ${desc}`, tok.line, tok.col);
|
|
1075
|
+
}
|
|
1076
|
+
const path2 = this.advance().value;
|
|
1077
|
+
while (this.peek().kind === "unquoted" && (this.peek().value === ")" || this.peek().value === "))")) {
|
|
1078
|
+
this.advance();
|
|
1079
|
+
}
|
|
1080
|
+
const after = this.peek();
|
|
1081
|
+
if (after.kind !== "newline" && after.kind !== "rbrace" && after.kind !== "eof" && after.kind !== "comma") {
|
|
1082
|
+
const desc = after.kind === "unquoted" ? `unquoted "${after.value}"` : after.kind;
|
|
1083
|
+
throw new ParseError(`unexpected token after include path: ${desc}`, after.line, after.col);
|
|
1084
|
+
}
|
|
1085
|
+
return path2;
|
|
1086
|
+
}
|
|
1087
|
+
isIncludeWrapperToken(tok) {
|
|
1088
|
+
return tok.kind === "unquoted" && tok.value === "(";
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Parse the body of `package("identifier", "file")`.
|
|
1092
|
+
*
|
|
1093
|
+
* @param consumePackageToken - When true (default), the current token is `package(` and
|
|
1094
|
+
* must be consumed first. When false, the lexer already bundled `package(` into the
|
|
1095
|
+
* preceding token (e.g. `required(package("`), so we are already positioned at the
|
|
1096
|
+
* first string argument.
|
|
1097
|
+
*/
|
|
1098
|
+
parsePackageArgs(consumePackageToken = true) {
|
|
1099
|
+
const pkgTok = consumePackageToken ? this.advance() : this.peek();
|
|
1100
|
+
this.skip("newline");
|
|
1101
|
+
const idTok = this.peek();
|
|
1102
|
+
if (idTok.kind !== "string") {
|
|
1103
|
+
throw new ParseError("include package: expected quoted identifier as first argument", pkgTok.line, pkgTok.col);
|
|
1104
|
+
}
|
|
1105
|
+
const identifier = this.advance().value;
|
|
1106
|
+
this.skip("newline");
|
|
1107
|
+
if (this.peek().kind !== "comma") {
|
|
1108
|
+
const after = this.peek();
|
|
1109
|
+
throw new ParseError(
|
|
1110
|
+
after.kind === "string" ? "include package: missing comma between identifier and file arguments" : "include package: requires exactly two arguments (identifier, file) \u2014 one-arg form is not supported",
|
|
1111
|
+
pkgTok.line,
|
|
1112
|
+
pkgTok.col
|
|
1113
|
+
);
|
|
1114
|
+
}
|
|
1115
|
+
this.advance();
|
|
1116
|
+
this.skip("newline");
|
|
1117
|
+
const fileTok = this.peek();
|
|
1118
|
+
if (fileTok.kind !== "string") {
|
|
1119
|
+
throw new ParseError(
|
|
1120
|
+
"include package: requires exactly two arguments (identifier, file) \u2014 one-arg form is not supported",
|
|
1121
|
+
pkgTok.line,
|
|
1122
|
+
pkgTok.col
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1125
|
+
const filePath = this.advance().value;
|
|
1126
|
+
while (this.peek().kind !== "newline" && this.peek().kind !== "rbrace" && this.peek().kind !== "eof" && this.peek().kind !== "comma") {
|
|
1127
|
+
this.advance();
|
|
1128
|
+
}
|
|
1129
|
+
return { qualifier: { kind: "package", identifier }, path: filePath };
|
|
948
1130
|
}
|
|
949
1131
|
parseValue() {
|
|
950
1132
|
const p = this.currentPos();
|
|
@@ -966,7 +1148,7 @@ var Parser = class {
|
|
|
966
1148
|
throw new ParseError(`internal: subst token missing payload`, t.line, t.col);
|
|
967
1149
|
}
|
|
968
1150
|
const payload = t.subst;
|
|
969
|
-
node = { kind: "subst", segments: payload.segments, optional: payload.optional, pos: { line: t.line, col: t.col } };
|
|
1151
|
+
node = { kind: "subst", segments: payload.segments, optional: payload.optional, listSuffix: payload.listSuffix, pos: { line: t.line, col: t.col } };
|
|
970
1152
|
} else if (t.kind === "string" || t.kind === "triple_string") {
|
|
971
1153
|
this.advance();
|
|
972
1154
|
node = { kind: "scalar", raw: t.value, valueType: "string", pos: { line: t.line, col: t.col } };
|
|
@@ -985,7 +1167,9 @@ var Parser = class {
|
|
|
985
1167
|
parts.push(node);
|
|
986
1168
|
}
|
|
987
1169
|
if (parts.length === 0) throw new ParseError("expected value", this.peek().line, this.peek().col);
|
|
988
|
-
if (parts.length === 1)
|
|
1170
|
+
if (parts.length === 1) {
|
|
1171
|
+
return parts[0];
|
|
1172
|
+
}
|
|
989
1173
|
return { kind: "concat", nodes: parts, pos: p };
|
|
990
1174
|
}
|
|
991
1175
|
parseArray() {
|
|
@@ -1017,107 +1201,10 @@ function parseTokens(tokens) {
|
|
|
1017
1201
|
return new Parser(tokens).parse();
|
|
1018
1202
|
}
|
|
1019
1203
|
|
|
1020
|
-
// src/internal/resolver/types.ts
|
|
1021
|
-
var separatorValues = /* @__PURE__ */ new WeakSet();
|
|
1022
|
-
function isSubst(v) {
|
|
1023
|
-
return v._kind === "subst-placeholder";
|
|
1024
|
-
}
|
|
1025
|
-
function isConcat(v) {
|
|
1026
|
-
return v._kind === "concat-placeholder";
|
|
1027
|
-
}
|
|
1028
|
-
function isAppend(v) {
|
|
1029
|
-
return v._kind === "append-placeholder";
|
|
1030
|
-
}
|
|
1031
|
-
function isResObj(v) {
|
|
1032
|
-
return v._kind === "res-obj";
|
|
1033
|
-
}
|
|
1034
|
-
function makeResObj() {
|
|
1035
|
-
return { _kind: "res-obj", fields: /* @__PURE__ */ new Map(), priorValues: /* @__PURE__ */ new Map() };
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
// src/internal/resolver/utils.ts
|
|
1039
|
-
function segmentsToKey(segments) {
|
|
1040
|
-
return segments.map((s) => {
|
|
1041
|
-
const t = s.text;
|
|
1042
|
-
if (t === "" || /[^a-zA-Z0-9\-_]/.test(t)) {
|
|
1043
|
-
return `"${t.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
1044
|
-
}
|
|
1045
|
-
return t;
|
|
1046
|
-
}).join(".");
|
|
1047
|
-
}
|
|
1048
|
-
function lookupPath(root, segments) {
|
|
1049
|
-
const [head, ...tail] = segments;
|
|
1050
|
-
if (head === void 0) return void 0;
|
|
1051
|
-
const key = head.text;
|
|
1052
|
-
const val = root.fields.get(key);
|
|
1053
|
-
if (val === void 0) return void 0;
|
|
1054
|
-
if (tail.length === 0) return val;
|
|
1055
|
-
if (isResObj(val)) return lookupPath(val, tail);
|
|
1056
|
-
return void 0;
|
|
1057
|
-
}
|
|
1058
|
-
function lookupResObj(root, segments) {
|
|
1059
|
-
let cur = root;
|
|
1060
|
-
for (const seg of segments) {
|
|
1061
|
-
const val = cur.fields.get(seg.text);
|
|
1062
|
-
if (val === void 0 || !isResObj(val)) return void 0;
|
|
1063
|
-
cur = val;
|
|
1064
|
-
}
|
|
1065
|
-
return cur;
|
|
1066
|
-
}
|
|
1067
|
-
function deepMergeHoconValues(base, overlay) {
|
|
1068
|
-
const merged = new Map(base.fields);
|
|
1069
|
-
for (const [k, v] of overlay.fields) {
|
|
1070
|
-
const existing = merged.get(k);
|
|
1071
|
-
if (existing?.kind === "object" && v.kind === "object") {
|
|
1072
|
-
merged.set(k, deepMergeHoconValues(existing, v));
|
|
1073
|
-
} else {
|
|
1074
|
-
merged.set(k, v);
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
return { kind: "object", fields: merged };
|
|
1078
|
-
}
|
|
1079
|
-
function deepMergeResObjInto(dst, src) {
|
|
1080
|
-
for (const [k, srcVal] of src.fields) {
|
|
1081
|
-
const dstVal = dst.fields.get(k);
|
|
1082
|
-
if (dstVal !== void 0 && isResObj(dstVal) && isResObj(srcVal)) {
|
|
1083
|
-
deepMergeResObjInto(dstVal, srcVal);
|
|
1084
|
-
} else {
|
|
1085
|
-
if (dstVal !== void 0) dst.priorValues.set(k, dstVal);
|
|
1086
|
-
dst.fields.set(k, srcVal);
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
for (const [k, srcPrior] of src.priorValues) {
|
|
1090
|
-
if (!dst.priorValues.has(k)) {
|
|
1091
|
-
dst.priorValues.set(k, srcPrior);
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
function hoconValueToResObj(hv) {
|
|
1096
|
-
const obj = makeResObj();
|
|
1097
|
-
if (hv.kind !== "object") return obj;
|
|
1098
|
-
for (const [key, val] of hv.fields) {
|
|
1099
|
-
if (val.kind === "object") {
|
|
1100
|
-
obj.fields.set(key, hoconValueToResObj(val));
|
|
1101
|
-
} else {
|
|
1102
|
-
obj.fields.set(key, val);
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
return obj;
|
|
1106
|
-
}
|
|
1107
|
-
function isFileNotFoundError(e) {
|
|
1108
|
-
if (!(e instanceof Error)) return false;
|
|
1109
|
-
const code = e.code;
|
|
1110
|
-
if (code === "ENOENT" || code === "MODULE_NOT_FOUND") return true;
|
|
1111
|
-
const msg = e.message.toLowerCase();
|
|
1112
|
-
return msg.includes("not found") || msg.includes("no such file") || msg.includes("enoent");
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
// src/internal/resolver/include-loader.ts
|
|
1116
|
-
var nodePath = __toESM(require("path"), 1);
|
|
1117
|
-
|
|
1118
1204
|
// src/internal/properties/properties.ts
|
|
1119
1205
|
function parseProperties(input) {
|
|
1120
1206
|
const root = /* @__PURE__ */ Object.create(null);
|
|
1207
|
+
const pairs = [];
|
|
1121
1208
|
for (const line of input.split("\n")) {
|
|
1122
1209
|
const trimmed = line.trim();
|
|
1123
1210
|
if (trimmed === "" || trimmed.startsWith("#") || trimmed.startsWith("!")) continue;
|
|
@@ -1126,6 +1213,10 @@ function parseProperties(input) {
|
|
|
1126
1213
|
const key = trimmed.slice(0, sepIdx).trim();
|
|
1127
1214
|
const value = trimmed.slice(sepIdx + 1).trim();
|
|
1128
1215
|
if (key === "") continue;
|
|
1216
|
+
pairs.push([key, value]);
|
|
1217
|
+
}
|
|
1218
|
+
pairs.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
1219
|
+
for (const [key, value] of pairs) {
|
|
1129
1220
|
setNested(root, key.split("."), value);
|
|
1130
1221
|
}
|
|
1131
1222
|
return root;
|
|
@@ -1149,6 +1240,7 @@ function setNested(obj, segments, value) {
|
|
|
1149
1240
|
}
|
|
1150
1241
|
const last = segments[segments.length - 1];
|
|
1151
1242
|
if (last === void 0 || DANGEROUS_KEYS.has(last)) return;
|
|
1243
|
+
if (typeof current[last] === "object" && current[last] !== null) return;
|
|
1152
1244
|
current[last] = value;
|
|
1153
1245
|
}
|
|
1154
1246
|
function propertiesToHoconValue(input) {
|
|
@@ -1168,6 +1260,85 @@ function recordToHoconValue(obj) {
|
|
|
1168
1260
|
}
|
|
1169
1261
|
|
|
1170
1262
|
// src/internal/resolver/include-loader.ts
|
|
1263
|
+
var import_meta = {};
|
|
1264
|
+
var _require = (0, import_node_module.createRequire)(import_meta.url);
|
|
1265
|
+
function isPnpPath(p) {
|
|
1266
|
+
return p.includes(".yarn/cache") || p.includes(".pnp.cjs") || p.includes(".pnp.loader");
|
|
1267
|
+
}
|
|
1268
|
+
function validatePackageFile(file, identifier) {
|
|
1269
|
+
if (file.length === 0) {
|
|
1270
|
+
throw new ParseError(`include package("${identifier}", ...): file argument must be non-empty`, 0, 0);
|
|
1271
|
+
}
|
|
1272
|
+
if (file.includes("\\")) {
|
|
1273
|
+
throw new ParseError(
|
|
1274
|
+
`include package("${identifier}", "${file}"): backslash not allowed in file argument (use forward slash)`,
|
|
1275
|
+
0,
|
|
1276
|
+
0
|
|
1277
|
+
);
|
|
1278
|
+
}
|
|
1279
|
+
if (file.startsWith("/")) {
|
|
1280
|
+
throw new ParseError(
|
|
1281
|
+
`include package("${identifier}", "${file}"): absolute path not allowed in file argument`,
|
|
1282
|
+
0,
|
|
1283
|
+
0
|
|
1284
|
+
);
|
|
1285
|
+
}
|
|
1286
|
+
if (/(?:^|\/)\.\.?(?:\/|$)/u.test(file)) {
|
|
1287
|
+
throw new ParseError(
|
|
1288
|
+
`include package("${identifier}", "${file}"): path traversal (. or ..) not allowed in file argument`,
|
|
1289
|
+
0,
|
|
1290
|
+
0
|
|
1291
|
+
);
|
|
1292
|
+
}
|
|
1293
|
+
if (file.includes("//")) {
|
|
1294
|
+
throw new ParseError(
|
|
1295
|
+
`include package("${identifier}", "${file}"): consecutive slashes not allowed in file argument`,
|
|
1296
|
+
0,
|
|
1297
|
+
0
|
|
1298
|
+
);
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
function validatePackageIdentifier(identifier) {
|
|
1302
|
+
if (identifier.length === 0) {
|
|
1303
|
+
throw new ParseError("include package: identifier argument must be non-empty", 0, 0);
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
function defaultPackageResolver(identifier, file, includingFile, baseDir, resolveFrom) {
|
|
1307
|
+
const from = resolveFrom ? Array.isArray(resolveFrom) ? resolveFrom : [resolveFrom] : baseDir ? [baseDir] : includingFile ? [nodePath.dirname(includingFile)] : [process.cwd()];
|
|
1308
|
+
let resolved;
|
|
1309
|
+
try {
|
|
1310
|
+
resolved = _require.resolve(`${identifier}/${file}`, { paths: from });
|
|
1311
|
+
} catch {
|
|
1312
|
+
throw new PackageLookupError(
|
|
1313
|
+
`include package("${identifier}", "${file}"): module not found (starting from: ${from.join(", ")})`,
|
|
1314
|
+
identifier,
|
|
1315
|
+
file,
|
|
1316
|
+
0,
|
|
1317
|
+
0
|
|
1318
|
+
);
|
|
1319
|
+
}
|
|
1320
|
+
if (isPnpPath(resolved)) {
|
|
1321
|
+
throw new ResolveError(
|
|
1322
|
+
`include package("${identifier}", "${file}"): Yarn Berry PnP store paths are not supported. Provide a custom packageResolver option to handle PnP resolution.`,
|
|
1323
|
+
`${identifier}/${file}`,
|
|
1324
|
+
0,
|
|
1325
|
+
0
|
|
1326
|
+
);
|
|
1327
|
+
}
|
|
1328
|
+
const fileParts = file.split("/");
|
|
1329
|
+
const expectedBasename = fileParts[fileParts.length - 1] ?? file;
|
|
1330
|
+
const resolvedBasename = nodePath.basename(resolved);
|
|
1331
|
+
if (resolvedBasename !== expectedBasename) {
|
|
1332
|
+
throw new PackageLookupError(
|
|
1333
|
+
`include package("${identifier}", "${file}"): case mismatch \u2014 resolved "${resolvedBasename}" but expected "${expectedBasename}" (E11 decision 5: case-sensitive)`,
|
|
1334
|
+
identifier,
|
|
1335
|
+
file,
|
|
1336
|
+
0,
|
|
1337
|
+
0
|
|
1338
|
+
);
|
|
1339
|
+
}
|
|
1340
|
+
return resolved;
|
|
1341
|
+
}
|
|
1171
1342
|
var IncludeLoader = class {
|
|
1172
1343
|
constructor(opts) {
|
|
1173
1344
|
this.opts = opts;
|
|
@@ -1247,8 +1418,77 @@ var IncludeLoader = class {
|
|
|
1247
1418
|
}
|
|
1248
1419
|
return merged;
|
|
1249
1420
|
}
|
|
1421
|
+
/**
|
|
1422
|
+
* Resolve and load `include package("identifier", "file")`.
|
|
1423
|
+
* Validates the identifier and file arguments, resolves via the package resolver,
|
|
1424
|
+
* performs cycle detection using a JSON-serialised cycle key, and parses the content.
|
|
1425
|
+
*/
|
|
1426
|
+
// `required` is reserved for future optional-package semantics (E11 decision 7);
|
|
1427
|
+
// currently all package lookup failures throw regardless of this flag.
|
|
1428
|
+
loadPackage(identifier, file, _required) {
|
|
1429
|
+
validatePackageIdentifier(identifier);
|
|
1430
|
+
validatePackageFile(file, identifier);
|
|
1431
|
+
const { includeStack = [], baseDir, resolveFrom, packageResolver, readFileSync: readFileSync2 } = this.opts;
|
|
1432
|
+
const cycleKey = JSON.stringify(["package", identifier, file]);
|
|
1433
|
+
if (includeStack.includes(cycleKey)) {
|
|
1434
|
+
throw new ResolveError(
|
|
1435
|
+
`circular include: package("${identifier}", "${file}")`,
|
|
1436
|
+
`${identifier}/${file}`,
|
|
1437
|
+
0,
|
|
1438
|
+
0
|
|
1439
|
+
);
|
|
1440
|
+
}
|
|
1441
|
+
if (includeStack.length >= 50) {
|
|
1442
|
+
throw new ResolveError(`include depth limit exceeded (max 50)`, `${identifier}/${file}`, 0, 0);
|
|
1443
|
+
}
|
|
1444
|
+
const resolver = packageResolver ?? ((id, f, includingFile, bDir) => defaultPackageResolver(id, f, includingFile, bDir, resolveFrom));
|
|
1445
|
+
const resolvedPath = resolver(identifier, file, void 0, baseDir);
|
|
1446
|
+
const content = readFileSync2(resolvedPath);
|
|
1447
|
+
if (content.length === 0) return makeResObj();
|
|
1448
|
+
const tokens = tokenize(content);
|
|
1449
|
+
assertNonEmptyDocument(tokens, resolvedPath);
|
|
1450
|
+
const ast = parseTokens(tokens);
|
|
1451
|
+
return this.onBuildResObj(ast, {
|
|
1452
|
+
...this.opts,
|
|
1453
|
+
baseDir: nodePath.dirname(resolvedPath),
|
|
1454
|
+
includeStack: [...includeStack, cycleKey]
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
/**
|
|
1458
|
+
* Async variant of `loadPackage`.
|
|
1459
|
+
*/
|
|
1460
|
+
async loadPackageAsync(identifier, file, _required) {
|
|
1461
|
+
validatePackageIdentifier(identifier);
|
|
1462
|
+
validatePackageFile(file, identifier);
|
|
1463
|
+
const { includeStack = [], baseDir, resolveFrom, packageResolver, readFile, readFileSync: readFileSync2 } = this.opts;
|
|
1464
|
+
const cycleKey = JSON.stringify(["package", identifier, file]);
|
|
1465
|
+
if (includeStack.includes(cycleKey)) {
|
|
1466
|
+
throw new ResolveError(
|
|
1467
|
+
`circular include: package("${identifier}", "${file}")`,
|
|
1468
|
+
`${identifier}/${file}`,
|
|
1469
|
+
0,
|
|
1470
|
+
0
|
|
1471
|
+
);
|
|
1472
|
+
}
|
|
1473
|
+
if (includeStack.length >= 50) {
|
|
1474
|
+
throw new ResolveError(`include depth limit exceeded (max 50)`, `${identifier}/${file}`, 0, 0);
|
|
1475
|
+
}
|
|
1476
|
+
const resolver = packageResolver ?? ((id, f, includingFile, bDir) => defaultPackageResolver(id, f, includingFile, bDir, resolveFrom));
|
|
1477
|
+
const resolvedPath = resolver(identifier, file, void 0, baseDir);
|
|
1478
|
+
const read = readFile ? async (p) => readFile(p) : async (p) => readFileSync2(p);
|
|
1479
|
+
const content = await read(resolvedPath);
|
|
1480
|
+
if (content.length === 0) return makeResObj();
|
|
1481
|
+
const tokens = tokenize(content);
|
|
1482
|
+
assertNonEmptyDocument(tokens, resolvedPath);
|
|
1483
|
+
const ast = parseTokens(tokens);
|
|
1484
|
+
return this.onBuildResObjAsync(ast, {
|
|
1485
|
+
...this.opts,
|
|
1486
|
+
baseDir: nodePath.dirname(resolvedPath),
|
|
1487
|
+
includeStack: [...includeStack, cycleKey]
|
|
1488
|
+
});
|
|
1489
|
+
}
|
|
1250
1490
|
loadSingle(candidate) {
|
|
1251
|
-
const { readFileSync: readFileSync2, includeStack = []
|
|
1491
|
+
const { readFileSync: readFileSync2, includeStack = [] } = this.opts;
|
|
1252
1492
|
if (includeStack.includes(candidate)) {
|
|
1253
1493
|
throw new ResolveError(`circular include: ${candidate}`, candidate, 0, 0);
|
|
1254
1494
|
}
|
|
@@ -1262,16 +1502,17 @@ var IncludeLoader = class {
|
|
|
1262
1502
|
if (candidate.endsWith(".properties")) {
|
|
1263
1503
|
return hoconValueToResObj(propertiesToHoconValue(content));
|
|
1264
1504
|
}
|
|
1265
|
-
const
|
|
1505
|
+
const tokens = tokenize(content);
|
|
1506
|
+
assertNonEmptyDocument(tokens, candidate);
|
|
1507
|
+
const ast = parseTokens(tokens);
|
|
1266
1508
|
return this.onBuildResObj(ast, {
|
|
1267
|
-
|
|
1509
|
+
...this.opts,
|
|
1268
1510
|
baseDir: nodePath.dirname(candidate),
|
|
1269
|
-
readFileSync: readFileSync2,
|
|
1270
1511
|
includeStack: [...includeStack, candidate]
|
|
1271
1512
|
});
|
|
1272
1513
|
}
|
|
1273
1514
|
async loadSingleAsync(candidate) {
|
|
1274
|
-
const { readFile, readFileSync: readFileSync2, includeStack = []
|
|
1515
|
+
const { readFile, readFileSync: readFileSync2, includeStack = [] } = this.opts;
|
|
1275
1516
|
const read = readFile ? async (p) => readFile(p) : async (p) => readFileSync2(p);
|
|
1276
1517
|
if (includeStack.includes(candidate)) {
|
|
1277
1518
|
throw new ResolveError(`circular include: ${candidate}`, candidate, 0, 0);
|
|
@@ -1286,12 +1527,12 @@ var IncludeLoader = class {
|
|
|
1286
1527
|
if (candidate.endsWith(".properties")) {
|
|
1287
1528
|
return hoconValueToResObj(propertiesToHoconValue(content));
|
|
1288
1529
|
}
|
|
1289
|
-
const
|
|
1530
|
+
const tokens = tokenize(content);
|
|
1531
|
+
assertNonEmptyDocument(tokens, candidate);
|
|
1532
|
+
const ast = parseTokens(tokens);
|
|
1290
1533
|
return this.onBuildResObjAsync(ast, {
|
|
1291
|
-
|
|
1534
|
+
...this.opts,
|
|
1292
1535
|
baseDir: nodePath.dirname(candidate),
|
|
1293
|
-
readFileSync: readFileSync2,
|
|
1294
|
-
readFile,
|
|
1295
1536
|
includeStack: [...includeStack, candidate]
|
|
1296
1537
|
});
|
|
1297
1538
|
}
|
|
@@ -1300,7 +1541,6 @@ var IncludeLoader = class {
|
|
|
1300
1541
|
// src/internal/resolver/structure-builder.ts
|
|
1301
1542
|
var StructureBuilder = class _StructureBuilder {
|
|
1302
1543
|
constructor(opts) {
|
|
1303
|
-
this.opts = opts;
|
|
1304
1544
|
this.loader = new IncludeLoader(opts);
|
|
1305
1545
|
this.loader.onBuildResObj = (a, o) => new _StructureBuilder(o).build(a);
|
|
1306
1546
|
this.loader.onBuildResObjAsync = async (a, o) => new _StructureBuilder(o).buildAsync(a);
|
|
@@ -1327,7 +1567,13 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1327
1567
|
}
|
|
1328
1568
|
applyField(obj, field, pathPrefix) {
|
|
1329
1569
|
if (field.key.length === 0 && field.value.kind === "include") {
|
|
1330
|
-
const
|
|
1570
|
+
const { qualifier, path: path2, required } = field.value;
|
|
1571
|
+
let included;
|
|
1572
|
+
if (qualifier.kind === "package") {
|
|
1573
|
+
included = this.loader.loadPackage(qualifier.identifier, path2, required);
|
|
1574
|
+
} else {
|
|
1575
|
+
included = this.loader.load(path2, required, qualifier.kind === "file");
|
|
1576
|
+
}
|
|
1331
1577
|
if (pathPrefix.length > 0) {
|
|
1332
1578
|
this.relativizeResObj(included, pathPrefix);
|
|
1333
1579
|
}
|
|
@@ -1369,7 +1615,13 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1369
1615
|
}
|
|
1370
1616
|
async applyFieldAsync(obj, field, pathPrefix) {
|
|
1371
1617
|
if (field.key.length === 0 && field.value.kind === "include") {
|
|
1372
|
-
const
|
|
1618
|
+
const { qualifier, path: path2, required } = field.value;
|
|
1619
|
+
let included;
|
|
1620
|
+
if (qualifier.kind === "package") {
|
|
1621
|
+
included = await this.loader.loadPackageAsync(qualifier.identifier, path2, required);
|
|
1622
|
+
} else {
|
|
1623
|
+
included = await this.loader.loadAsync(path2, required, qualifier.kind === "file");
|
|
1624
|
+
}
|
|
1373
1625
|
if (pathPrefix.length > 0) {
|
|
1374
1626
|
this.relativizeResObj(included, pathPrefix);
|
|
1375
1627
|
}
|
|
@@ -1423,9 +1675,9 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1423
1675
|
return inner;
|
|
1424
1676
|
}
|
|
1425
1677
|
case "subst":
|
|
1426
|
-
return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
|
|
1678
|
+
return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, listSuffix: ast.listSuffix, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
|
|
1427
1679
|
case "concat":
|
|
1428
|
-
return { _kind: "concat-placeholder", nodes: ast.nodes.map((n) => this.astToResolverValue(n, pathPrefix)) };
|
|
1680
|
+
return { _kind: "concat-placeholder", nodes: ast.nodes.map((n) => this.astToResolverValue(n, pathPrefix)), line: ast.pos.line, col: ast.pos.col };
|
|
1429
1681
|
case "include":
|
|
1430
1682
|
return { kind: "scalar", raw: "null", valueType: "null" };
|
|
1431
1683
|
}
|
|
@@ -1447,13 +1699,13 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1447
1699
|
case "object":
|
|
1448
1700
|
return await this.buildAsync(ast, pathPrefix);
|
|
1449
1701
|
case "subst":
|
|
1450
|
-
return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
|
|
1702
|
+
return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, listSuffix: ast.listSuffix, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
|
|
1451
1703
|
case "concat": {
|
|
1452
1704
|
const nodes = [];
|
|
1453
1705
|
for (const n of ast.nodes) {
|
|
1454
1706
|
nodes.push(await this.astToResolverValueAsync(n, pathPrefix));
|
|
1455
1707
|
}
|
|
1456
|
-
return { _kind: "concat-placeholder", nodes };
|
|
1708
|
+
return { _kind: "concat-placeholder", nodes, line: ast.pos.line, col: ast.pos.col };
|
|
1457
1709
|
}
|
|
1458
1710
|
case "include":
|
|
1459
1711
|
return { kind: "scalar", raw: "null", valueType: "null" };
|
|
@@ -1499,6 +1751,23 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1499
1751
|
}
|
|
1500
1752
|
};
|
|
1501
1753
|
|
|
1754
|
+
// src/value/numeric-array.ts
|
|
1755
|
+
function numericObjectToArray(value) {
|
|
1756
|
+
if (value.kind !== "object") return null;
|
|
1757
|
+
if (value.fields.size === 0) return null;
|
|
1758
|
+
const canonicalInt = /^(0|[1-9][0-9]*)$/;
|
|
1759
|
+
const eligible = [];
|
|
1760
|
+
for (const [key, val] of value.fields) {
|
|
1761
|
+
if (!canonicalInt.test(key)) continue;
|
|
1762
|
+
const n = Number(key);
|
|
1763
|
+
if (!Number.isInteger(n) || n > 2147483647) continue;
|
|
1764
|
+
eligible.push({ n, v: val });
|
|
1765
|
+
}
|
|
1766
|
+
if (eligible.length === 0) return null;
|
|
1767
|
+
eligible.sort((a, b) => a.n - b.n);
|
|
1768
|
+
return eligible.map((e) => e.v);
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1502
1771
|
// src/internal/resolver/substitution-resolver.ts
|
|
1503
1772
|
var SubstitutionResolver = class {
|
|
1504
1773
|
constructor(root, opts) {
|
|
@@ -1506,6 +1775,23 @@ var SubstitutionResolver = class {
|
|
|
1506
1775
|
this.opts = opts;
|
|
1507
1776
|
this.resolving = /* @__PURE__ */ new Set();
|
|
1508
1777
|
this.cache = /* @__PURE__ */ new Map();
|
|
1778
|
+
// Tracks ConcatPlaceholder nodes currently being iterated by resolveConcat.
|
|
1779
|
+
// Used by isSelfRef detection: fires only when found === a concat currently
|
|
1780
|
+
// mid-iteration (i.e. we are inside found's own resolution), preventing the
|
|
1781
|
+
// "foofoo" double-count without misfiring when an external field looks up the
|
|
1782
|
+
// same concat via a substitution (multi-reviewer convergence fix).
|
|
1783
|
+
//
|
|
1784
|
+
// Spec deviation: the S13a.13 spec ★1 decision #1 specified path-equality
|
|
1785
|
+
// preservation for self-ref detection. Round-2 multi-agent-review surfaced a
|
|
1786
|
+
// false-positive on external lookups (`a = ${?a}foo; b = ${a}`), so the
|
|
1787
|
+
// criterion was tightened to node-membership-in-iterating-set — strictly
|
|
1788
|
+
// narrower than path-equality. Spec amendment deferred to a follow-up
|
|
1789
|
+
// xx.hocon PR (see Phase 6 #3f close-out notes).
|
|
1790
|
+
this.resolvingConcats = /* @__PURE__ */ new WeakSet();
|
|
1791
|
+
}
|
|
1792
|
+
/** Returns a prefix for error messages, including origin info when set. */
|
|
1793
|
+
originPrefix() {
|
|
1794
|
+
return this.opts.originDescription ? `${this.opts.originDescription}: ` : "";
|
|
1509
1795
|
}
|
|
1510
1796
|
resolve() {
|
|
1511
1797
|
return this.resolveResObj(this.root);
|
|
@@ -1544,7 +1830,15 @@ var SubstitutionResolver = class {
|
|
|
1544
1830
|
}
|
|
1545
1831
|
resolveVal(v, scope) {
|
|
1546
1832
|
if (isSubst(v)) return this.resolveSubst(v, scope);
|
|
1547
|
-
if (isConcat(v))
|
|
1833
|
+
if (isConcat(v)) {
|
|
1834
|
+
this.resolvingConcats.add(v);
|
|
1835
|
+
try {
|
|
1836
|
+
const concatResult = this.resolveConcat(v.nodes, scope, v.line, v.col);
|
|
1837
|
+
return concatResult;
|
|
1838
|
+
} finally {
|
|
1839
|
+
this.resolvingConcats.delete(v);
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1548
1842
|
if (isAppend(v)) return this.resolveAppend(v, scope);
|
|
1549
1843
|
if (isResObj(v)) return this.resolveResObj(v);
|
|
1550
1844
|
const hv = v;
|
|
@@ -1558,15 +1852,12 @@ var SubstitutionResolver = class {
|
|
|
1558
1852
|
}
|
|
1559
1853
|
return hv;
|
|
1560
1854
|
}
|
|
1561
|
-
segmentsEqual(a, b) {
|
|
1562
|
-
return a.length === b.length && a.every((seg, i) => seg.text === b[i]?.text);
|
|
1563
|
-
}
|
|
1564
1855
|
resolveSubst(s, scope) {
|
|
1565
|
-
const key = segmentsToKey(s.segments);
|
|
1856
|
+
const key = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
|
|
1566
1857
|
if (this.cache.has(key)) return this.cache.get(key);
|
|
1567
1858
|
if (this.resolving.has(key)) {
|
|
1568
1859
|
let prior;
|
|
1569
|
-
if (s.
|
|
1860
|
+
if (s.segments.length > 1) {
|
|
1570
1861
|
const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";
|
|
1571
1862
|
const parentScope = lookupResObj(
|
|
1572
1863
|
this.root,
|
|
@@ -1588,7 +1879,7 @@ var SubstitutionResolver = class {
|
|
|
1588
1879
|
}
|
|
1589
1880
|
if (s.optional) return void 0;
|
|
1590
1881
|
throw new ResolveError(
|
|
1591
|
-
`circular substitution: ${key}`,
|
|
1882
|
+
this.originPrefix() + `circular substitution: ${key}`,
|
|
1592
1883
|
key,
|
|
1593
1884
|
s.line,
|
|
1594
1885
|
s.col
|
|
@@ -1598,29 +1889,31 @@ var SubstitutionResolver = class {
|
|
|
1598
1889
|
try {
|
|
1599
1890
|
const found = lookupPath(this.root, s.segments);
|
|
1600
1891
|
if (found !== void 0) {
|
|
1601
|
-
if (
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
if (prior !== void 0) {
|
|
1619
|
-
const result2 = this.resolveVal(prior, scope);
|
|
1620
|
-
if (result2 !== void 0) this.cache.set(key, result2);
|
|
1621
|
-
return result2;
|
|
1622
|
-
}
|
|
1892
|
+
if (isConcat(found) && this.resolvingConcats.has(found)) {
|
|
1893
|
+
let prior;
|
|
1894
|
+
if (s.segments.length > 1) {
|
|
1895
|
+
const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";
|
|
1896
|
+
const parentScope = lookupResObj(
|
|
1897
|
+
this.root,
|
|
1898
|
+
s.segments.slice(0, s.segments.length - 1)
|
|
1899
|
+
);
|
|
1900
|
+
prior = parentScope?.priorValues.get(leafSeg);
|
|
1901
|
+
} else {
|
|
1902
|
+
const rootSeg = s.segments[0]?.text ?? "";
|
|
1903
|
+
prior = scope.priorValues.get(rootSeg) ?? this.root.priorValues.get(rootSeg);
|
|
1904
|
+
}
|
|
1905
|
+
if (prior !== void 0) {
|
|
1906
|
+
const result2 = this.resolveVal(prior, scope);
|
|
1907
|
+
if (result2 !== void 0) this.cache.set(key, result2);
|
|
1908
|
+
return result2;
|
|
1623
1909
|
}
|
|
1910
|
+
if (s.optional) return void 0;
|
|
1911
|
+
throw new ResolveError(
|
|
1912
|
+
`${this.originPrefix()}could not resolve substitution: \${${key}}`,
|
|
1913
|
+
key,
|
|
1914
|
+
s.line,
|
|
1915
|
+
s.col
|
|
1916
|
+
);
|
|
1624
1917
|
}
|
|
1625
1918
|
let result = this.resolveVal(found, scope);
|
|
1626
1919
|
const effectiveLen = s.segments.length - s.prefixLen;
|
|
@@ -1649,16 +1942,45 @@ var SubstitutionResolver = class {
|
|
|
1649
1942
|
if (result !== void 0) this.cache.set(key, result);
|
|
1650
1943
|
return result;
|
|
1651
1944
|
}
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1945
|
+
if (s.listSuffix && this.opts.useSystemEnvironment !== false) {
|
|
1946
|
+
const result = this.resolveEnvList(s);
|
|
1947
|
+
if (result !== void 0) {
|
|
1948
|
+
this.cache.set(key, result);
|
|
1949
|
+
return result;
|
|
1950
|
+
}
|
|
1951
|
+
if (s.optional) return void 0;
|
|
1952
|
+
const envBase = s.segments.map((seg) => seg.text).join(".");
|
|
1953
|
+
throw new ResolveError(
|
|
1954
|
+
`${this.originPrefix()}could not resolve substitution: \${${key}} (no environment variables ${envBase}_0, ${envBase}_1, \u2026 set)`,
|
|
1955
|
+
key,
|
|
1956
|
+
s.line,
|
|
1957
|
+
s.col
|
|
1958
|
+
);
|
|
1959
|
+
} else if (s.listSuffix) {
|
|
1960
|
+
if (s.optional) return void 0;
|
|
1961
|
+
if (this.opts.allowUnresolved) return s;
|
|
1962
|
+
throw new ResolveError(
|
|
1963
|
+
`${this.originPrefix()}could not resolve substitution: \${${key}}`,
|
|
1964
|
+
key,
|
|
1965
|
+
s.line,
|
|
1966
|
+
s.col
|
|
1967
|
+
);
|
|
1968
|
+
}
|
|
1969
|
+
if (this.opts.useSystemEnvironment !== false) {
|
|
1970
|
+
const envKey = s.segments.map((seg) => seg.text).join(".");
|
|
1971
|
+
const envVal = this.opts.env[envKey] ?? (s.prefixLen > 0 ? this.opts.env[s.segments.slice(s.prefixLen).map((seg) => seg.text).join(".")] : void 0);
|
|
1972
|
+
if (envVal !== void 0) {
|
|
1973
|
+
const result = { kind: "scalar", raw: envVal, valueType: "string" };
|
|
1974
|
+
this.cache.set(key, result);
|
|
1975
|
+
return result;
|
|
1976
|
+
}
|
|
1658
1977
|
}
|
|
1659
1978
|
if (s.optional) return void 0;
|
|
1979
|
+
if (this.opts.allowUnresolved) {
|
|
1980
|
+
return s;
|
|
1981
|
+
}
|
|
1660
1982
|
throw new ResolveError(
|
|
1661
|
-
|
|
1983
|
+
`${this.originPrefix()}could not resolve substitution: \${${key}}`,
|
|
1662
1984
|
key,
|
|
1663
1985
|
s.line,
|
|
1664
1986
|
s.col
|
|
@@ -1667,9 +1989,52 @@ var SubstitutionResolver = class {
|
|
|
1667
1989
|
this.resolving.delete(key);
|
|
1668
1990
|
}
|
|
1669
1991
|
}
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1992
|
+
/**
|
|
1993
|
+
* S13c: scan environment for NAME_0, NAME_1, … and return an Array<Scalar(String)>.
|
|
1994
|
+
*
|
|
1995
|
+
* Candidate bases follow the same fully-qualified→bare order used by the scalar
|
|
1996
|
+
* env fallback. The first base whose _0 key is present wins entirely — no
|
|
1997
|
+
* cross-base merging (spec §Relativized fallback precedence rules).
|
|
1998
|
+
*
|
|
1999
|
+
* Returns the HoconValue Array if any candidate base has _0, or undefined if
|
|
2000
|
+
* none do (caller applies optional/required handling).
|
|
2001
|
+
*/
|
|
2002
|
+
resolveEnvList(s) {
|
|
2003
|
+
const bases = this.candidateBases(s);
|
|
2004
|
+
for (const base of bases) {
|
|
2005
|
+
const key0 = base + "_0";
|
|
2006
|
+
if (!(key0 in this.opts.env)) continue;
|
|
2007
|
+
const items = [];
|
|
2008
|
+
for (let i = 0; ; i++) {
|
|
2009
|
+
const k = base + "_" + i;
|
|
2010
|
+
if (!(k in this.opts.env)) break;
|
|
2011
|
+
items.push({ kind: "scalar", raw: this.opts.env[k], valueType: "string" });
|
|
2012
|
+
}
|
|
2013
|
+
return { kind: "array", items };
|
|
2014
|
+
}
|
|
2015
|
+
return void 0;
|
|
2016
|
+
}
|
|
2017
|
+
/**
|
|
2018
|
+
* Return the candidate base names for env-var-list lookup, in precedence order.
|
|
2019
|
+
* Mirrors the fully-qualified→bare fallback used by the scalar env path.
|
|
2020
|
+
*/
|
|
2021
|
+
candidateBases(s) {
|
|
2022
|
+
const full = s.segments.map((seg) => seg.text).join(".");
|
|
2023
|
+
if (s.prefixLen === 0) return [full];
|
|
2024
|
+
const bare = s.segments.slice(s.prefixLen).map((seg) => seg.text).join(".");
|
|
2025
|
+
return [full, bare];
|
|
2026
|
+
}
|
|
2027
|
+
resolveConcat(nodes, scope, line = 0, col = 0) {
|
|
2028
|
+
const rawResolved = nodes.map((n) => this.resolveVal(n, scope));
|
|
2029
|
+
if (this.opts.allowUnresolved) {
|
|
2030
|
+
for (const v of rawResolved) {
|
|
2031
|
+
if (v !== void 0 && isSubst(v)) {
|
|
2032
|
+
return v;
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
const resolved = rawResolved.filter((v) => v !== void 0);
|
|
2037
|
+
if (resolved.length === 0) return void 0;
|
|
1673
2038
|
if (resolved.length === 1) return resolved[0];
|
|
1674
2039
|
const nonSep = resolved.filter((v) => !separatorValues.has(v));
|
|
1675
2040
|
if (nonSep.length === 0) {
|
|
@@ -1685,27 +2050,45 @@ var SubstitutionResolver = class {
|
|
|
1685
2050
|
if (converted !== null) {
|
|
1686
2051
|
return { kind: "array", items: [...left.items, ...converted] };
|
|
1687
2052
|
}
|
|
1688
|
-
|
|
2053
|
+
throw new ResolveError(
|
|
2054
|
+
"cannot concatenate array with object: value concatenation requires same-kind operands (S10.4)",
|
|
2055
|
+
"",
|
|
2056
|
+
line,
|
|
2057
|
+
col
|
|
2058
|
+
);
|
|
1689
2059
|
}
|
|
1690
2060
|
if (left.kind === "object" && right.kind === "array") {
|
|
1691
2061
|
const converted = numericObjectToArray(left);
|
|
1692
2062
|
if (converted !== null) {
|
|
1693
2063
|
return { kind: "array", items: [...converted, ...right.items] };
|
|
1694
2064
|
}
|
|
1695
|
-
|
|
2065
|
+
throw new ResolveError(
|
|
2066
|
+
"cannot concatenate object with array: value concatenation requires same-kind operands (S10.4)",
|
|
2067
|
+
"",
|
|
2068
|
+
line,
|
|
2069
|
+
col
|
|
2070
|
+
);
|
|
1696
2071
|
}
|
|
1697
2072
|
if (left.kind === "array" && right.kind === "array") {
|
|
1698
2073
|
return { kind: "array", items: [...left.items, ...right.items] };
|
|
1699
2074
|
}
|
|
1700
|
-
if (left.kind === "array") {
|
|
1701
|
-
|
|
2075
|
+
if (left.kind === "array" || right.kind === "array") {
|
|
2076
|
+
throw new ResolveError(
|
|
2077
|
+
`cannot concatenate ${left.kind} with ${right.kind}: arrays and objects may not appear in string value concatenation (S10.13)`,
|
|
2078
|
+
"",
|
|
2079
|
+
line,
|
|
2080
|
+
col
|
|
2081
|
+
);
|
|
1702
2082
|
}
|
|
1703
|
-
if (right.kind === "
|
|
1704
|
-
|
|
2083
|
+
if (left.kind === "object" || right.kind === "object") {
|
|
2084
|
+
throw new ResolveError(
|
|
2085
|
+
`cannot concatenate ${left.kind} with ${right.kind}: arrays and objects may not appear in string value concatenation (S10.13)`,
|
|
2086
|
+
"",
|
|
2087
|
+
line,
|
|
2088
|
+
col
|
|
2089
|
+
);
|
|
1705
2090
|
}
|
|
1706
|
-
|
|
1707
|
-
const rightStr = right.kind === "scalar" ? right.raw : JSON.stringify(right);
|
|
1708
|
-
return { kind: "scalar", raw: leftStr + rightStr, valueType: "string" };
|
|
2091
|
+
return { kind: "scalar", raw: left.raw + right.raw, valueType: "string" };
|
|
1709
2092
|
};
|
|
1710
2093
|
const [head, ...tail] = nonSep;
|
|
1711
2094
|
const folded = tail.reduce(joinPair, head);
|
|
@@ -1725,18 +2108,503 @@ var SubstitutionResolver = class {
|
|
|
1725
2108
|
};
|
|
1726
2109
|
|
|
1727
2110
|
// src/internal/resolver/resolver.ts
|
|
2111
|
+
function buildTree(ast, opts) {
|
|
2112
|
+
return new StructureBuilder(opts).build(ast);
|
|
2113
|
+
}
|
|
2114
|
+
function resolveTree(tree, opts) {
|
|
2115
|
+
return new SubstitutionResolver(tree, opts).resolve();
|
|
2116
|
+
}
|
|
2117
|
+
function containsPlaceholders(tree) {
|
|
2118
|
+
for (const val of tree.fields.values()) {
|
|
2119
|
+
if (valContainsPlaceholders(val)) return true;
|
|
2120
|
+
}
|
|
2121
|
+
return false;
|
|
2122
|
+
}
|
|
2123
|
+
function valContainsPlaceholders(v) {
|
|
2124
|
+
const rv = v;
|
|
2125
|
+
if (isSubst(rv) || isConcat(rv) || isAppend(rv)) return true;
|
|
2126
|
+
if (isResObj(rv)) return containsPlaceholders(rv);
|
|
2127
|
+
const hv = v;
|
|
2128
|
+
if (hv?.kind === "array") {
|
|
2129
|
+
return hv.items.some((item) => valContainsPlaceholders(item));
|
|
2130
|
+
}
|
|
2131
|
+
return false;
|
|
2132
|
+
}
|
|
2133
|
+
function buildPartialHoconFromResObj(tree) {
|
|
2134
|
+
const fields = /* @__PURE__ */ new Map();
|
|
2135
|
+
for (const [k, v] of tree.fields) {
|
|
2136
|
+
if (!isSubst(v) && !isConcat(v) && !isAppend(v)) {
|
|
2137
|
+
if (isResObj(v)) {
|
|
2138
|
+
fields.set(k, buildPartialHoconFromResObj(v));
|
|
2139
|
+
} else {
|
|
2140
|
+
if (!valContainsPlaceholders(v)) {
|
|
2141
|
+
fields.set(k, v);
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
return { kind: "object", fields };
|
|
2147
|
+
}
|
|
2148
|
+
function hoconValueToResObj2(value) {
|
|
2149
|
+
const obj = makeResObj();
|
|
2150
|
+
for (const [k, v] of value.fields) {
|
|
2151
|
+
if (v.kind === "object") {
|
|
2152
|
+
obj.fields.set(k, hoconValueToResObj2(v));
|
|
2153
|
+
} else {
|
|
2154
|
+
obj.fields.set(k, v);
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
return obj;
|
|
2158
|
+
}
|
|
1728
2159
|
function resolve2(ast, opts) {
|
|
1729
|
-
const
|
|
1730
|
-
return
|
|
2160
|
+
const tree = buildTree(ast, opts);
|
|
2161
|
+
return resolveTree(tree, { ...opts, useSystemEnvironment: true, allowUnresolved: false });
|
|
1731
2162
|
}
|
|
1732
2163
|
async function resolveAsync(ast, opts) {
|
|
1733
2164
|
const root = await new StructureBuilder(opts).buildAsync(ast);
|
|
1734
|
-
return
|
|
2165
|
+
return resolveTree(root, { ...opts, useSystemEnvironment: true, allowUnresolved: false });
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
// src/config.ts
|
|
2169
|
+
var Config = class _Config {
|
|
2170
|
+
constructor(root, opts) {
|
|
2171
|
+
this.root = root;
|
|
2172
|
+
this._resolved = opts?.resolved ?? true;
|
|
2173
|
+
this._parseBaseDir = opts?.parseBaseDir;
|
|
2174
|
+
this._originDescription = opts?.originDescription;
|
|
2175
|
+
this._resObjRoot = opts?.resObjRoot;
|
|
2176
|
+
this._resolveOpts = opts?.resolveOpts;
|
|
2177
|
+
}
|
|
2178
|
+
/** Returns true when the Config's value tree contains no unresolved
|
|
2179
|
+
* substitution placeholders. Whole-config granularity (E12 decision 11). */
|
|
2180
|
+
isResolved() {
|
|
2181
|
+
return this._resolved;
|
|
2182
|
+
}
|
|
2183
|
+
/** @internal Test-only: construct a resolved Config from a HoconValue. */
|
|
2184
|
+
static _fromResolvedValue(root, opts) {
|
|
2185
|
+
return new _Config(root, { resolved: true, originDescription: opts?.originDescription });
|
|
2186
|
+
}
|
|
2187
|
+
/** @internal Construct an unresolved Config carrying a ResObj tree.
|
|
2188
|
+
* Used by parseStringWithOptions (deferred path) in T6. */
|
|
2189
|
+
static _fromUnresolvedResObj(tree, opts) {
|
|
2190
|
+
const partialRoot = buildPartialHoconFromResObj(tree);
|
|
2191
|
+
return new _Config(partialRoot, {
|
|
2192
|
+
resolved: opts.resolved,
|
|
2193
|
+
parseBaseDir: opts.parseBaseDir,
|
|
2194
|
+
originDescription: opts.originDescription,
|
|
2195
|
+
resObjRoot: tree,
|
|
2196
|
+
resolveOpts: opts.resolveOpts
|
|
2197
|
+
});
|
|
2198
|
+
}
|
|
2199
|
+
get(path2) {
|
|
2200
|
+
const v = this.lookupNode(path2);
|
|
2201
|
+
if (v === void 0) return void 0;
|
|
2202
|
+
return hoconToJs(v);
|
|
2203
|
+
}
|
|
2204
|
+
getString(path2) {
|
|
2205
|
+
const v = this.requireScalar(path2);
|
|
2206
|
+
return v.raw;
|
|
2207
|
+
}
|
|
2208
|
+
getNumber(path2) {
|
|
2209
|
+
const v = this.requireScalar(path2);
|
|
2210
|
+
const coerced = coerceNumber(v.raw);
|
|
2211
|
+
if (coerced !== void 0) return coerced;
|
|
2212
|
+
throw new ConfigError(`expected number at ${path2}, got ${v.valueType}`, path2);
|
|
2213
|
+
}
|
|
2214
|
+
getBoolean(path2) {
|
|
2215
|
+
const v = this.requireScalar(path2);
|
|
2216
|
+
const coerced = coerceBoolean(v.raw);
|
|
2217
|
+
if (coerced !== void 0) return coerced;
|
|
2218
|
+
throw new ConfigError(`expected boolean at ${path2}, got ${v.valueType}`, path2);
|
|
2219
|
+
}
|
|
2220
|
+
getDuration(path2, unit) {
|
|
2221
|
+
const v = this.requireScalar(path2);
|
|
2222
|
+
if (v.valueType !== "string" && v.valueType !== "number") {
|
|
2223
|
+
throw new ConfigError(`expected duration at ${path2}, got ${v.valueType}`, path2);
|
|
2224
|
+
}
|
|
2225
|
+
const result = parseDuration(v.raw, unit);
|
|
2226
|
+
if (Number.isNaN(result)) throw new ConfigError(`invalid duration at ${path2}: ${JSON.stringify(v.raw)}`, path2);
|
|
2227
|
+
return result;
|
|
2228
|
+
}
|
|
2229
|
+
getBytes(path2, unit) {
|
|
2230
|
+
const v = this.requireScalar(path2);
|
|
2231
|
+
if (v.valueType !== "string" && v.valueType !== "number") {
|
|
2232
|
+
throw new ConfigError(`expected byte size at ${path2}, got ${v.valueType}`, path2);
|
|
2233
|
+
}
|
|
2234
|
+
const result = parseBytes(v.raw, unit);
|
|
2235
|
+
if (Number.isNaN(result)) throw new ConfigError(`invalid byte size at ${path2}: ${JSON.stringify(v.raw)}`, path2);
|
|
2236
|
+
if (result < 0) throw new ConfigError(`byte size must be non-negative at ${path2}: ${JSON.stringify(v.raw)}`, path2);
|
|
2237
|
+
return result;
|
|
2238
|
+
}
|
|
2239
|
+
getConfig(path2) {
|
|
2240
|
+
const v = this.lookupNode(path2);
|
|
2241
|
+
if (v === void 0) {
|
|
2242
|
+
if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
|
|
2243
|
+
throw new NotResolvedError(path2);
|
|
2244
|
+
}
|
|
2245
|
+
throw new ConfigError(`path not found: ${path2}`, path2);
|
|
2246
|
+
}
|
|
2247
|
+
if (v.kind !== "object") throw new ConfigError(`expected object at ${path2}`, path2);
|
|
2248
|
+
if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
|
|
2249
|
+
throw new NotResolvedError(path2);
|
|
2250
|
+
}
|
|
2251
|
+
return new _Config(v, { resolved: this._resolved });
|
|
2252
|
+
}
|
|
2253
|
+
getList(path2) {
|
|
2254
|
+
const v = this.lookupNode(path2);
|
|
2255
|
+
if (v === void 0) {
|
|
2256
|
+
if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
|
|
2257
|
+
throw new NotResolvedError(path2);
|
|
2258
|
+
}
|
|
2259
|
+
throw new ConfigError(`path not found: ${path2}`, path2);
|
|
2260
|
+
}
|
|
2261
|
+
if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
|
|
2262
|
+
throw new NotResolvedError(path2);
|
|
2263
|
+
}
|
|
2264
|
+
if (v.kind === "object") {
|
|
2265
|
+
const converted = numericObjectToArray(v);
|
|
2266
|
+
if (converted !== null) return converted.map(hoconToJs);
|
|
2267
|
+
}
|
|
2268
|
+
if (v.kind !== "array") throw new ConfigError(`expected array at ${path2}`, path2);
|
|
2269
|
+
return v.items.map(hoconToJs);
|
|
2270
|
+
}
|
|
2271
|
+
has(path2) {
|
|
2272
|
+
return this.lookupNode(path2) !== void 0;
|
|
2273
|
+
}
|
|
2274
|
+
keys() {
|
|
2275
|
+
return [...this.root.fields.keys()];
|
|
2276
|
+
}
|
|
2277
|
+
withFallback(fallback) {
|
|
2278
|
+
if (fallback == null) return this;
|
|
2279
|
+
const selfTree = this._resObjRoot ?? hoconValueToResObj2(this.root);
|
|
2280
|
+
const fbTree = fallback._resObjRoot ?? hoconValueToResObj2(fallback.root);
|
|
2281
|
+
const merged = mergeUnresolved(selfTree, fbTree);
|
|
2282
|
+
const hasPlaceholders = containsPlaceholders(merged);
|
|
2283
|
+
const partialRoot = buildPartialHoconFromResObj(merged);
|
|
2284
|
+
return new _Config(partialRoot, {
|
|
2285
|
+
resolved: !hasPlaceholders,
|
|
2286
|
+
parseBaseDir: this._parseBaseDir,
|
|
2287
|
+
originDescription: this._originDescription,
|
|
2288
|
+
resObjRoot: merged,
|
|
2289
|
+
resolveOpts: this._resolveOpts ?? fallback._resolveOpts
|
|
2290
|
+
});
|
|
2291
|
+
}
|
|
2292
|
+
/**
|
|
2293
|
+
* Runs substitution resolution (phase 2) on the stored unresolved tree.
|
|
2294
|
+
* Idempotent: calling resolve() on an already-resolved Config returns an
|
|
2295
|
+
* equivalent resolved Config.
|
|
2296
|
+
*
|
|
2297
|
+
* opts.allowUnresolved (default false) — when true, leaves unresolved
|
|
2298
|
+
* non-optional placeholders in place rather than throwing.
|
|
2299
|
+
* opts.useSystemEnvironment (default true) — when false, env var lookups
|
|
2300
|
+
* are suppressed (hermetic resolution).
|
|
2301
|
+
*
|
|
2302
|
+
* E12 decision 3.
|
|
2303
|
+
*/
|
|
2304
|
+
resolve(opts = {}) {
|
|
2305
|
+
if (this._resolved) {
|
|
2306
|
+
return new _Config(this.root, {
|
|
2307
|
+
resolved: true,
|
|
2308
|
+
parseBaseDir: this._parseBaseDir,
|
|
2309
|
+
originDescription: this._originDescription
|
|
2310
|
+
});
|
|
2311
|
+
}
|
|
2312
|
+
const tree = this._resObjRoot;
|
|
2313
|
+
if (!tree) {
|
|
2314
|
+
return new _Config(this.root, { resolved: true });
|
|
2315
|
+
}
|
|
2316
|
+
const resolveOpts = {
|
|
2317
|
+
...this._resolveOpts ?? { env: {}, baseDir: void 0, readFileSync: () => {
|
|
2318
|
+
throw new Error("no files");
|
|
2319
|
+
} },
|
|
2320
|
+
allowUnresolved: opts.allowUnresolved ?? false,
|
|
2321
|
+
useSystemEnvironment: opts.useSystemEnvironment ?? true,
|
|
2322
|
+
originDescription: this._originDescription
|
|
2323
|
+
};
|
|
2324
|
+
const resolved = resolveTree(tree, resolveOpts);
|
|
2325
|
+
if (resolved.kind !== "object") throw new Error("resolve: expected object root");
|
|
2326
|
+
if (opts.allowUnresolved) {
|
|
2327
|
+
const { stripped, hadPlaceholders } = stripPlaceholderFields(resolved);
|
|
2328
|
+
return new _Config(stripped, {
|
|
2329
|
+
resolved: !hadPlaceholders,
|
|
2330
|
+
parseBaseDir: this._parseBaseDir,
|
|
2331
|
+
originDescription: this._originDescription,
|
|
2332
|
+
resObjRoot: hadPlaceholders ? tree : void 0,
|
|
2333
|
+
resolveOpts: hadPlaceholders ? resolveOpts : void 0
|
|
2334
|
+
});
|
|
2335
|
+
}
|
|
2336
|
+
return new _Config(resolved, {
|
|
2337
|
+
resolved: true,
|
|
2338
|
+
parseBaseDir: this._parseBaseDir,
|
|
2339
|
+
originDescription: this._originDescription
|
|
2340
|
+
});
|
|
2341
|
+
}
|
|
2342
|
+
/**
|
|
2343
|
+
* Resolves receiver substitutions using source as lookup context.
|
|
2344
|
+
* Source's keys are NOT merged into the result — differs from
|
|
2345
|
+
* `this.withFallback(source).resolve()` which includes source keys.
|
|
2346
|
+
*
|
|
2347
|
+
* Precondition (E12 decision 10): source must be resolved. If
|
|
2348
|
+
* source.isResolved() is false, throws NotResolvedError.
|
|
2349
|
+
*
|
|
2350
|
+
* On an already-resolved receiver, resolveWith is a no-op (returns an
|
|
2351
|
+
* equivalent resolved Config, source keys not included).
|
|
2352
|
+
*
|
|
2353
|
+
* E12 decisions 9, 10.
|
|
2354
|
+
*/
|
|
2355
|
+
resolveWith(source, opts = {}) {
|
|
2356
|
+
if (!source.isResolved()) {
|
|
2357
|
+
throw new NotResolvedError("source");
|
|
2358
|
+
}
|
|
2359
|
+
if (this._resolved) {
|
|
2360
|
+
return new _Config(this.root, {
|
|
2361
|
+
resolved: true,
|
|
2362
|
+
parseBaseDir: this._parseBaseDir,
|
|
2363
|
+
originDescription: this._originDescription
|
|
2364
|
+
});
|
|
2365
|
+
}
|
|
2366
|
+
const receiverTree = this._resObjRoot ?? hoconValueToResObj2(this.root);
|
|
2367
|
+
const srcTree = hoconValueToResObj2(source.root);
|
|
2368
|
+
const merged = mergeUnresolved(receiverTree, srcTree);
|
|
2369
|
+
const resolveOpts = {
|
|
2370
|
+
...this._resolveOpts ?? { env: {}, baseDir: void 0, readFileSync: () => {
|
|
2371
|
+
throw new Error("no files");
|
|
2372
|
+
} },
|
|
2373
|
+
allowUnresolved: opts.allowUnresolved ?? false,
|
|
2374
|
+
useSystemEnvironment: opts.useSystemEnvironment ?? true,
|
|
2375
|
+
originDescription: this._originDescription
|
|
2376
|
+
};
|
|
2377
|
+
const resolved = resolveTree(merged, resolveOpts);
|
|
2378
|
+
if (resolved.kind !== "object") throw new Error("resolveWith: expected object root");
|
|
2379
|
+
const receiverShapeForFilter = resObjToKeyShape(receiverTree);
|
|
2380
|
+
const filtered = filterByReceiverShape(resolved, receiverShapeForFilter);
|
|
2381
|
+
if (opts.allowUnresolved) {
|
|
2382
|
+
const { stripped, hadPlaceholders } = stripPlaceholderFields(filtered);
|
|
2383
|
+
return new _Config(stripped, {
|
|
2384
|
+
resolved: !hadPlaceholders,
|
|
2385
|
+
parseBaseDir: this._parseBaseDir,
|
|
2386
|
+
originDescription: this._originDescription,
|
|
2387
|
+
resObjRoot: hadPlaceholders ? receiverTree : void 0,
|
|
2388
|
+
resolveOpts: hadPlaceholders ? resolveOpts : void 0
|
|
2389
|
+
});
|
|
2390
|
+
}
|
|
2391
|
+
return new _Config(filtered, {
|
|
2392
|
+
resolved: true,
|
|
2393
|
+
parseBaseDir: this._parseBaseDir,
|
|
2394
|
+
originDescription: this._originDescription
|
|
2395
|
+
});
|
|
2396
|
+
}
|
|
2397
|
+
toObject() {
|
|
2398
|
+
return hoconToJs(this.root);
|
|
2399
|
+
}
|
|
2400
|
+
/**
|
|
2401
|
+
* @internal Test-only: renders this resolved Config's value tree as canonical
|
|
2402
|
+
* JSON (sorted keys, no whitespace). Used by Layer-2 fixture tests to compare
|
|
2403
|
+
* against Lightbend ground truth. NOT part of the public API.
|
|
2404
|
+
*
|
|
2405
|
+
* Throws if the Config is unresolved (contains placeholders).
|
|
2406
|
+
*/
|
|
2407
|
+
_renderJSONForTest() {
|
|
2408
|
+
return renderHoconAsJSON(this.root);
|
|
2409
|
+
}
|
|
2410
|
+
lookupNode(path2) {
|
|
2411
|
+
const segments = splitConfigPath(path2);
|
|
2412
|
+
let current = this.root;
|
|
2413
|
+
for (const seg of segments) {
|
|
2414
|
+
if (current.kind !== "object") return void 0;
|
|
2415
|
+
const next = current.fields.get(seg);
|
|
2416
|
+
if (next === void 0) return void 0;
|
|
2417
|
+
current = next;
|
|
2418
|
+
}
|
|
2419
|
+
return current;
|
|
2420
|
+
}
|
|
2421
|
+
/**
|
|
2422
|
+
* _resObjRootSubtreeHasPlaceholders — walk _resObjRoot to the given path and
|
|
2423
|
+
* return true if the subtree there contains any unresolved placeholder.
|
|
2424
|
+
* Used by getConfig / getList (T2) to detect transitive unresolved placeholders
|
|
2425
|
+
* in object / array subtrees that would otherwise appear to be "resolved" in the
|
|
2426
|
+
* partial HoconValue root (because individual leaf fields are omitted rather than
|
|
2427
|
+
* the whole subtree when their value is a direct placeholder scalar, but object
|
|
2428
|
+
* sub-containers are retained in the partial root with the placeholder-keyed
|
|
2429
|
+
* fields omitted from them — so the object appears "present but empty-ish").
|
|
2430
|
+
*/
|
|
2431
|
+
_resObjRootSubtreeHasPlaceholders(path2) {
|
|
2432
|
+
const tree = this._resObjRoot;
|
|
2433
|
+
if (!tree) return false;
|
|
2434
|
+
const segments = splitConfigPath(path2);
|
|
2435
|
+
let cur = tree;
|
|
2436
|
+
for (const seg of segments) {
|
|
2437
|
+
const val = cur.fields.get(seg);
|
|
2438
|
+
if (val === void 0) return false;
|
|
2439
|
+
if (isResObj(val)) {
|
|
2440
|
+
cur = val;
|
|
2441
|
+
} else {
|
|
2442
|
+
return valContainsPlaceholders(val);
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
return containsPlaceholders(cur);
|
|
2446
|
+
}
|
|
2447
|
+
requireScalar(path2) {
|
|
2448
|
+
const v = this.lookupNode(path2);
|
|
2449
|
+
if (v === void 0) {
|
|
2450
|
+
if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
|
|
2451
|
+
throw new NotResolvedError(path2);
|
|
2452
|
+
}
|
|
2453
|
+
throw new ConfigError(`path not found: ${path2}`, path2);
|
|
2454
|
+
}
|
|
2455
|
+
if (v.kind !== "scalar") throw new ConfigError(`expected scalar at ${path2}, got ${v.kind}`, path2);
|
|
2456
|
+
return v;
|
|
2457
|
+
}
|
|
2458
|
+
};
|
|
2459
|
+
function splitConfigPath(path2) {
|
|
2460
|
+
const segments = [];
|
|
2461
|
+
let i = 0;
|
|
2462
|
+
while (i < path2.length) {
|
|
2463
|
+
if (path2[i] === '"') {
|
|
2464
|
+
i++;
|
|
2465
|
+
let segment = "";
|
|
2466
|
+
let closed = false;
|
|
2467
|
+
while (i < path2.length) {
|
|
2468
|
+
const ch = path2[i];
|
|
2469
|
+
if (ch === "\\" && i + 1 < path2.length) {
|
|
2470
|
+
const next = path2[i + 1];
|
|
2471
|
+
segment += next;
|
|
2472
|
+
i += 2;
|
|
2473
|
+
continue;
|
|
2474
|
+
}
|
|
2475
|
+
if (ch === '"') {
|
|
2476
|
+
closed = true;
|
|
2477
|
+
i++;
|
|
2478
|
+
break;
|
|
2479
|
+
}
|
|
2480
|
+
segment += ch;
|
|
2481
|
+
i++;
|
|
2482
|
+
}
|
|
2483
|
+
if (!closed) throw new ConfigError(`unterminated quoted path segment: ${path2}`, path2);
|
|
2484
|
+
segments.push(segment);
|
|
2485
|
+
if (i < path2.length && path2[i] === ".") i++;
|
|
2486
|
+
} else {
|
|
2487
|
+
const dot = path2.indexOf(".", i);
|
|
2488
|
+
if (dot === -1) {
|
|
2489
|
+
segments.push(path2.slice(i));
|
|
2490
|
+
break;
|
|
2491
|
+
}
|
|
2492
|
+
segments.push(path2.slice(i, dot));
|
|
2493
|
+
i = dot + 1;
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
return segments;
|
|
2497
|
+
}
|
|
2498
|
+
function scalarToJs(raw, valueType) {
|
|
2499
|
+
switch (valueType) {
|
|
2500
|
+
case "null":
|
|
2501
|
+
return null;
|
|
2502
|
+
case "boolean":
|
|
2503
|
+
return raw === "true";
|
|
2504
|
+
case "number":
|
|
2505
|
+
return Number(raw);
|
|
2506
|
+
case "string":
|
|
2507
|
+
return raw;
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
function hoconToJs(v) {
|
|
2511
|
+
switch (v.kind) {
|
|
2512
|
+
case "scalar":
|
|
2513
|
+
return scalarToJs(v.raw, v.valueType);
|
|
2514
|
+
case "array":
|
|
2515
|
+
return v.items.map(hoconToJs);
|
|
2516
|
+
case "object": {
|
|
2517
|
+
const obj = /* @__PURE__ */ Object.create(null);
|
|
2518
|
+
for (const [k, val] of v.fields) obj[k] = hoconToJs(val);
|
|
2519
|
+
return Object.assign({}, obj);
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
function resObjToKeyShape(tree) {
|
|
2524
|
+
const fields = /* @__PURE__ */ new Map();
|
|
2525
|
+
for (const [k, v] of tree.fields) {
|
|
2526
|
+
if (isSubst(v) || isConcat(v) || isAppend(v)) {
|
|
2527
|
+
fields.set(k, { kind: "scalar", raw: "", valueType: "null" });
|
|
2528
|
+
} else if (isResObj(v)) {
|
|
2529
|
+
fields.set(k, resObjToKeyShape(v));
|
|
2530
|
+
} else {
|
|
2531
|
+
fields.set(k, v);
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
return { kind: "object", fields };
|
|
2535
|
+
}
|
|
2536
|
+
function stripPlaceholderFields(v) {
|
|
2537
|
+
const fields = /* @__PURE__ */ new Map();
|
|
2538
|
+
let hadPlaceholders = false;
|
|
2539
|
+
for (const [k, val] of v.fields) {
|
|
2540
|
+
if (val._kind === "subst-placeholder" || val._kind === "concat-placeholder" || val._kind === "append-placeholder") {
|
|
2541
|
+
hadPlaceholders = true;
|
|
2542
|
+
} else if (val.kind === "object") {
|
|
2543
|
+
const inner = stripPlaceholderFields(val);
|
|
2544
|
+
if (inner.hadPlaceholders) hadPlaceholders = true;
|
|
2545
|
+
fields.set(k, inner.stripped);
|
|
2546
|
+
} else {
|
|
2547
|
+
fields.set(k, val);
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
return { stripped: { kind: "object", fields }, hadPlaceholders };
|
|
2551
|
+
}
|
|
2552
|
+
function filterByReceiverShape(resolved, receiverShape) {
|
|
2553
|
+
const fields = /* @__PURE__ */ new Map();
|
|
2554
|
+
for (const [k, rv] of resolved.fields) {
|
|
2555
|
+
if (!receiverShape.fields.has(k)) continue;
|
|
2556
|
+
const receiverVal = receiverShape.fields.get(k);
|
|
2557
|
+
if (rv.kind === "object" && receiverVal.kind === "object") {
|
|
2558
|
+
fields.set(k, filterByReceiverShape(rv, receiverVal));
|
|
2559
|
+
} else {
|
|
2560
|
+
fields.set(k, rv);
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
return { kind: "object", fields };
|
|
2564
|
+
}
|
|
2565
|
+
function renderHoconAsJSON(v) {
|
|
2566
|
+
switch (v.kind) {
|
|
2567
|
+
case "scalar": {
|
|
2568
|
+
switch (v.valueType) {
|
|
2569
|
+
case "null":
|
|
2570
|
+
return "null";
|
|
2571
|
+
case "boolean":
|
|
2572
|
+
return v.raw;
|
|
2573
|
+
case "number": {
|
|
2574
|
+
const n = Number(v.raw);
|
|
2575
|
+
if (Number.isFinite(n)) return v.raw;
|
|
2576
|
+
return JSON.stringify(v.raw);
|
|
2577
|
+
}
|
|
2578
|
+
case "string":
|
|
2579
|
+
return JSON.stringify(v.raw);
|
|
2580
|
+
}
|
|
2581
|
+
break;
|
|
2582
|
+
}
|
|
2583
|
+
case "array": {
|
|
2584
|
+
const items = v.items.map(renderHoconAsJSON);
|
|
2585
|
+
return `[${items.join(",")}]`;
|
|
2586
|
+
}
|
|
2587
|
+
case "object": {
|
|
2588
|
+
const keys = [...v.fields.keys()].sort();
|
|
2589
|
+
const pairs = keys.map((k) => {
|
|
2590
|
+
const val = v.fields.get(k);
|
|
2591
|
+
return `${JSON.stringify(k)}:${renderHoconAsJSON(val)}`;
|
|
2592
|
+
});
|
|
2593
|
+
return `{${pairs.join(",")}}`;
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
throw new Error(`renderHoconAsJSON: unsupported value kind (unresolved placeholder?)`);
|
|
1735
2597
|
}
|
|
1736
2598
|
|
|
1737
2599
|
// src/parse.ts
|
|
1738
2600
|
var fs = __toESM(require("fs"), 1);
|
|
1739
2601
|
var path = __toESM(require("path"), 1);
|
|
2602
|
+
function defaultParseOptions() {
|
|
2603
|
+
return { resolveSubstitutions: true };
|
|
2604
|
+
}
|
|
2605
|
+
function defaultResolveOptions() {
|
|
2606
|
+
return { useSystemEnvironment: true, allowUnresolved: false };
|
|
2607
|
+
}
|
|
1740
2608
|
function getEnv(opts) {
|
|
1741
2609
|
if (opts.env !== void 0) return opts.env;
|
|
1742
2610
|
if (typeof process !== "undefined" && process.env) return process.env;
|
|
@@ -1750,12 +2618,16 @@ async function defaultReadFile(filePath) {
|
|
|
1750
2618
|
}
|
|
1751
2619
|
function buildResolveContext(input, opts) {
|
|
1752
2620
|
const tokens = tokenize(input);
|
|
2621
|
+
assertNonEmptyDocument(tokens, "input");
|
|
1753
2622
|
const ast = parseTokens(tokens);
|
|
1754
2623
|
const resolveOpts = {
|
|
1755
2624
|
env: getEnv(opts),
|
|
1756
2625
|
baseDir: opts.baseDir,
|
|
1757
2626
|
readFileSync: opts.readFileSync ?? defaultReadFileSync,
|
|
1758
|
-
readFile: opts.readFile
|
|
2627
|
+
readFile: opts.readFile,
|
|
2628
|
+
originDescription: opts.originDescription,
|
|
2629
|
+
resolveFrom: opts.resolveFrom,
|
|
2630
|
+
packageResolver: opts.packageResolver
|
|
1759
2631
|
};
|
|
1760
2632
|
return { ast, resolveOpts };
|
|
1761
2633
|
}
|
|
@@ -1763,7 +2635,38 @@ function parse(input, opts = {}) {
|
|
|
1763
2635
|
const { ast, resolveOpts } = buildResolveContext(input, opts);
|
|
1764
2636
|
const value = resolve2(ast, resolveOpts);
|
|
1765
2637
|
if (value.kind !== "object") throw new Error("resolved value is not an object");
|
|
1766
|
-
return new Config(value);
|
|
2638
|
+
return new Config(value, { resolved: true, originDescription: opts.originDescription });
|
|
2639
|
+
}
|
|
2640
|
+
var parseString = parse;
|
|
2641
|
+
function parseStringWithOptions(input, opts = {}) {
|
|
2642
|
+
const resolveSubstitutions = opts.resolveSubstitutions ?? true;
|
|
2643
|
+
const { ast, resolveOpts } = buildResolveContext(input, opts);
|
|
2644
|
+
if (resolveSubstitutions) {
|
|
2645
|
+
const value = resolve2(ast, resolveOpts);
|
|
2646
|
+
if (value.kind !== "object") throw new Error("resolved value is not an object");
|
|
2647
|
+
return new Config(value, { resolved: true, originDescription: opts.originDescription });
|
|
2648
|
+
}
|
|
2649
|
+
const tree = buildTree(ast, resolveOpts);
|
|
2650
|
+
const hasPlaceholders = containsPlaceholders(tree);
|
|
2651
|
+
return Config._fromUnresolvedResObj(tree, {
|
|
2652
|
+
parseBaseDir: opts.baseDir,
|
|
2653
|
+
originDescription: opts.originDescription,
|
|
2654
|
+
resolved: !hasPlaceholders,
|
|
2655
|
+
resolveOpts
|
|
2656
|
+
});
|
|
2657
|
+
}
|
|
2658
|
+
function parseFileWithOptions(filePath, opts = {}) {
|
|
2659
|
+
if (typeof process === "undefined" && !opts.readFileSync) {
|
|
2660
|
+
throw new Error("parseFileWithOptions is not supported in browser environments without opts.readFileSync.");
|
|
2661
|
+
}
|
|
2662
|
+
const resolvedPath = path.resolve(filePath);
|
|
2663
|
+
const readFileSync2 = opts.readFileSync ?? defaultReadFileSync;
|
|
2664
|
+
const input = readFileSync2(resolvedPath);
|
|
2665
|
+
return parseStringWithOptions(input, {
|
|
2666
|
+
...opts,
|
|
2667
|
+
baseDir: opts.baseDir ?? path.dirname(resolvedPath),
|
|
2668
|
+
readFileSync: readFileSync2
|
|
2669
|
+
});
|
|
1767
2670
|
}
|
|
1768
2671
|
async function parseAsync(input, opts = {}) {
|
|
1769
2672
|
const { ast, resolveOpts } = buildResolveContext(input, opts);
|
|
@@ -1798,14 +2701,90 @@ async function parseFileAsync(filePath, opts = {}) {
|
|
|
1798
2701
|
readFileSync: opts.readFileSync ?? defaultReadFileSync
|
|
1799
2702
|
});
|
|
1800
2703
|
}
|
|
2704
|
+
|
|
2705
|
+
// src/value-factory.ts
|
|
2706
|
+
function fromMap(values, originDescription) {
|
|
2707
|
+
const fields = coerceObject(values, "");
|
|
2708
|
+
const root = { kind: "object", fields };
|
|
2709
|
+
return Config._fromResolvedValue(root, { originDescription });
|
|
2710
|
+
}
|
|
2711
|
+
function empty(originDescription) {
|
|
2712
|
+
const root = { kind: "object", fields: /* @__PURE__ */ new Map() };
|
|
2713
|
+
return Config._fromResolvedValue(root, { originDescription });
|
|
2714
|
+
}
|
|
2715
|
+
function coerceObject(obj, atPath) {
|
|
2716
|
+
const fields = /* @__PURE__ */ new Map();
|
|
2717
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
2718
|
+
const childPath = atPath ? `${atPath}.${k}` : k;
|
|
2719
|
+
fields.set(k, coerceValue(v, childPath));
|
|
2720
|
+
}
|
|
2721
|
+
return fields;
|
|
2722
|
+
}
|
|
2723
|
+
function coerceValue(v, atPath) {
|
|
2724
|
+
if (v === null) {
|
|
2725
|
+
return { kind: "scalar", raw: "null", valueType: "null" };
|
|
2726
|
+
}
|
|
2727
|
+
if (v === void 0) {
|
|
2728
|
+
throw new ConfigError(
|
|
2729
|
+
`fromMap: undefined is not a valid HOCON value at path "${atPath}"`,
|
|
2730
|
+
atPath
|
|
2731
|
+
);
|
|
2732
|
+
}
|
|
2733
|
+
switch (typeof v) {
|
|
2734
|
+
case "string":
|
|
2735
|
+
return { kind: "scalar", raw: v, valueType: "string" };
|
|
2736
|
+
case "number": {
|
|
2737
|
+
if (Number.isNaN(v) || !Number.isFinite(v)) {
|
|
2738
|
+
throw new ConfigError(
|
|
2739
|
+
`fromMap: ${v} is not representable in HOCON (NaN/Infinity not allowed) at path "${atPath}"`,
|
|
2740
|
+
atPath
|
|
2741
|
+
);
|
|
2742
|
+
}
|
|
2743
|
+
return { kind: "scalar", raw: String(v), valueType: "number" };
|
|
2744
|
+
}
|
|
2745
|
+
case "boolean":
|
|
2746
|
+
return { kind: "scalar", raw: v ? "true" : "false", valueType: "boolean" };
|
|
2747
|
+
case "bigint": {
|
|
2748
|
+
if (v > BigInt(Number.MAX_SAFE_INTEGER) || v < BigInt(Number.MIN_SAFE_INTEGER)) {
|
|
2749
|
+
throw new ConfigError(
|
|
2750
|
+
`fromMap: bigint ${v} exceeds Number.MAX_SAFE_INTEGER (not representable as HOCON number) at path "${atPath}"`,
|
|
2751
|
+
atPath
|
|
2752
|
+
);
|
|
2753
|
+
}
|
|
2754
|
+
return { kind: "scalar", raw: String(v), valueType: "number" };
|
|
2755
|
+
}
|
|
2756
|
+
case "object": {
|
|
2757
|
+
if (Array.isArray(v)) {
|
|
2758
|
+
const items = v.map((item, i) => coerceValue(item, `${atPath}[${i}]`));
|
|
2759
|
+
return { kind: "array", items };
|
|
2760
|
+
}
|
|
2761
|
+
const fields = coerceObject(v, atPath);
|
|
2762
|
+
return { kind: "object", fields };
|
|
2763
|
+
}
|
|
2764
|
+
default:
|
|
2765
|
+
throw new ConfigError(
|
|
2766
|
+
`fromMap: unsupported value type "${typeof v}" at path "${atPath}"`,
|
|
2767
|
+
atPath
|
|
2768
|
+
);
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
1801
2771
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1802
2772
|
0 && (module.exports = {
|
|
1803
2773
|
Config,
|
|
1804
2774
|
ConfigError,
|
|
2775
|
+
NotResolvedError,
|
|
2776
|
+
PackageLookupError,
|
|
1805
2777
|
ParseError,
|
|
1806
2778
|
ResolveError,
|
|
2779
|
+
defaultParseOptions,
|
|
2780
|
+
defaultResolveOptions,
|
|
2781
|
+
empty,
|
|
2782
|
+
fromMap,
|
|
1807
2783
|
parse,
|
|
1808
2784
|
parseAsync,
|
|
1809
2785
|
parseFile,
|
|
1810
|
-
parseFileAsync
|
|
2786
|
+
parseFileAsync,
|
|
2787
|
+
parseFileWithOptions,
|
|
2788
|
+
parseString,
|
|
2789
|
+
parseStringWithOptions
|
|
1811
2790
|
});
|