@o3co/ts.hocon 1.3.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -6
- package/dist/config-BwjvKeZ5.d.cts +271 -0
- package/dist/config-BwjvKeZ5.d.ts +271 -0
- package/dist/index.cjs +1109 -360
- package/dist/index.d.cts +45 -2
- package/dist/index.d.ts +45 -2
- package/dist/index.js +1098 -359
- package/dist/zod.cjs +829 -200
- package/dist/zod.d.cts +1 -1
- package/dist/zod.d.ts +1 -1
- package/dist/zod.js +828 -200
- package/package.json +3 -1
- package/dist/parse-nNbyqnOh.d.cts +0 -53
- package/dist/parse-nNbyqnOh.d.ts +0 -53
package/dist/index.js
CHANGED
|
@@ -215,6 +215,14 @@ var ResolveError = class extends Error {
|
|
|
215
215
|
this.name = "ResolveError";
|
|
216
216
|
}
|
|
217
217
|
};
|
|
218
|
+
var PackageLookupError = class extends ResolveError {
|
|
219
|
+
constructor(message, identifier, packageFile, line, col) {
|
|
220
|
+
super(message, `${identifier}/${packageFile}`, line, col);
|
|
221
|
+
this.identifier = identifier;
|
|
222
|
+
this.packageFile = packageFile;
|
|
223
|
+
this.name = "PackageLookupError";
|
|
224
|
+
}
|
|
225
|
+
};
|
|
218
226
|
var ConfigError = class extends Error {
|
|
219
227
|
constructor(message, path2) {
|
|
220
228
|
super(message);
|
|
@@ -222,191 +230,155 @@ var ConfigError = class extends Error {
|
|
|
222
230
|
this.name = "ConfigError";
|
|
223
231
|
}
|
|
224
232
|
};
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
if (value.fields.size === 0) return null;
|
|
230
|
-
const canonicalInt = /^(0|[1-9][0-9]*)$/;
|
|
231
|
-
const eligible = [];
|
|
232
|
-
for (const [key, val] of value.fields) {
|
|
233
|
-
if (!canonicalInt.test(key)) continue;
|
|
234
|
-
const n = Number(key);
|
|
235
|
-
if (!Number.isInteger(n) || n > 2147483647) continue;
|
|
236
|
-
eligible.push({ n, v: val });
|
|
233
|
+
var NotResolvedError = class extends ConfigError {
|
|
234
|
+
constructor(path2) {
|
|
235
|
+
super(`value at path "${path2}" is not resolved (call resolve() first)`, path2);
|
|
236
|
+
this.name = "NotResolvedError";
|
|
237
237
|
}
|
|
238
|
-
|
|
239
|
-
eligible.sort((a, b) => a.n - b.n);
|
|
240
|
-
return eligible.map((e) => e.v);
|
|
241
|
-
}
|
|
238
|
+
};
|
|
242
239
|
|
|
243
|
-
// src/
|
|
244
|
-
var
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const v = this.requireScalar(path2);
|
|
265
|
-
const coerced = coerceBoolean(v.raw);
|
|
266
|
-
if (coerced !== void 0) return coerced;
|
|
267
|
-
throw new ConfigError(`expected boolean at ${path2}, got ${v.valueType}`, path2);
|
|
240
|
+
// src/internal/resolver/types.ts
|
|
241
|
+
var separatorValues = /* @__PURE__ */ new WeakSet();
|
|
242
|
+
function isSubst(v) {
|
|
243
|
+
return v._kind === "subst-placeholder";
|
|
244
|
+
}
|
|
245
|
+
function isConcat(v) {
|
|
246
|
+
return v._kind === "concat-placeholder";
|
|
247
|
+
}
|
|
248
|
+
function isAppend(v) {
|
|
249
|
+
return v._kind === "append-placeholder";
|
|
250
|
+
}
|
|
251
|
+
function isResObj(v) {
|
|
252
|
+
return v._kind === "res-obj";
|
|
253
|
+
}
|
|
254
|
+
function makeResObj() {
|
|
255
|
+
return { _kind: "res-obj", fields: /* @__PURE__ */ new Map(), priorValues: /* @__PURE__ */ new Map() };
|
|
256
|
+
}
|
|
257
|
+
function mergeUnresolved(receiver, fallback) {
|
|
258
|
+
const result = makeResObj();
|
|
259
|
+
for (const [k, v] of fallback.fields) {
|
|
260
|
+
result.fields.set(k, v);
|
|
268
261
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
if (v.valueType !== "string" && v.valueType !== "number") {
|
|
272
|
-
throw new ConfigError(`expected duration at ${path2}, got ${v.valueType}`, path2);
|
|
273
|
-
}
|
|
274
|
-
const result = parseDuration(v.raw, unit);
|
|
275
|
-
if (Number.isNaN(result)) throw new ConfigError(`invalid duration at ${path2}: ${JSON.stringify(v.raw)}`, path2);
|
|
276
|
-
return result;
|
|
262
|
+
for (const [k, v] of fallback.priorValues) {
|
|
263
|
+
result.priorValues.set(k, v);
|
|
277
264
|
}
|
|
278
|
-
|
|
279
|
-
const
|
|
280
|
-
if (
|
|
281
|
-
|
|
265
|
+
for (const [k, rv] of receiver.fields) {
|
|
266
|
+
const existing = result.fields.get(k);
|
|
267
|
+
if (existing !== void 0) {
|
|
268
|
+
const recPrior = receiver.priorValues.get(k);
|
|
269
|
+
if (recPrior !== void 0 && !isResObj(recPrior)) {
|
|
270
|
+
result.fields.set(k, rv);
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
if (isResObj(rv) && isResObj(existing)) {
|
|
274
|
+
result.fields.set(k, mergeUnresolved(rv, existing));
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
result.priorValues.set(k, existing);
|
|
282
278
|
}
|
|
283
|
-
|
|
284
|
-
if (Number.isNaN(result)) throw new ConfigError(`invalid byte size at ${path2}: ${JSON.stringify(v.raw)}`, path2);
|
|
285
|
-
if (result < 0) throw new ConfigError(`byte size must be non-negative at ${path2}: ${JSON.stringify(v.raw)}`, path2);
|
|
286
|
-
return result;
|
|
287
|
-
}
|
|
288
|
-
getConfig(path2) {
|
|
289
|
-
const v = this.lookupNode(path2);
|
|
290
|
-
if (v === void 0) throw new ConfigError(`path not found: ${path2}`, path2);
|
|
291
|
-
if (v.kind !== "object") throw new ConfigError(`expected object at ${path2}`, path2);
|
|
292
|
-
return new _Config(v);
|
|
279
|
+
result.fields.set(k, rv);
|
|
293
280
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
if (v.kind === "object") {
|
|
298
|
-
const converted = numericObjectToArray(v);
|
|
299
|
-
if (converted !== null) return converted.map(hoconToJs);
|
|
281
|
+
for (const [k, v] of receiver.priorValues) {
|
|
282
|
+
if (receiver.fields.has(k)) {
|
|
283
|
+
result.priorValues.set(k, v);
|
|
300
284
|
}
|
|
301
|
-
if (v.kind !== "array") throw new ConfigError(`expected array at ${path2}`, path2);
|
|
302
|
-
return v.items.map(hoconToJs);
|
|
303
|
-
}
|
|
304
|
-
has(path2) {
|
|
305
|
-
return this.lookupNode(path2) !== void 0;
|
|
306
|
-
}
|
|
307
|
-
keys() {
|
|
308
|
-
return [...this.root.fields.keys()];
|
|
309
|
-
}
|
|
310
|
-
withFallback(fallback) {
|
|
311
|
-
const merged = deepMergeHocon(this.root, fallback.root);
|
|
312
|
-
return new _Config(merged);
|
|
313
285
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
if (next === void 0) return void 0;
|
|
324
|
-
current = next;
|
|
286
|
+
return result;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// src/internal/resolver/utils.ts
|
|
290
|
+
function segmentsToKey(segments) {
|
|
291
|
+
return segments.map((s) => {
|
|
292
|
+
const t = s.text;
|
|
293
|
+
if (t === "" || /[^a-zA-Z0-9\-_]/.test(t)) {
|
|
294
|
+
return `"${t.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
325
295
|
}
|
|
326
|
-
return
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
296
|
+
return t;
|
|
297
|
+
}).join(".");
|
|
298
|
+
}
|
|
299
|
+
function lookupPath(root, segments) {
|
|
300
|
+
const [head, ...tail] = segments;
|
|
301
|
+
if (head === void 0) return void 0;
|
|
302
|
+
const key = head.text;
|
|
303
|
+
const val = root.fields.get(key);
|
|
304
|
+
if (val === void 0) return void 0;
|
|
305
|
+
if (tail.length === 0) return val;
|
|
306
|
+
if (isResObj(val)) return lookupPath(val, tail);
|
|
307
|
+
return void 0;
|
|
308
|
+
}
|
|
309
|
+
function lookupResObj(root, segments) {
|
|
310
|
+
let cur = root;
|
|
311
|
+
for (const seg of segments) {
|
|
312
|
+
const val = cur.fields.get(seg.text);
|
|
313
|
+
if (val === void 0 || !isResObj(val)) return void 0;
|
|
314
|
+
cur = val;
|
|
333
315
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
let closed = false;
|
|
343
|
-
while (i < path2.length) {
|
|
344
|
-
const ch = path2[i];
|
|
345
|
-
if (ch === "\\" && i + 1 < path2.length) {
|
|
346
|
-
const next = path2[i + 1];
|
|
347
|
-
segment += next;
|
|
348
|
-
i += 2;
|
|
349
|
-
continue;
|
|
350
|
-
}
|
|
351
|
-
if (ch === '"') {
|
|
352
|
-
closed = true;
|
|
353
|
-
i++;
|
|
354
|
-
break;
|
|
355
|
-
}
|
|
356
|
-
segment += ch;
|
|
357
|
-
i++;
|
|
358
|
-
}
|
|
359
|
-
if (!closed) throw new ConfigError(`unterminated quoted path segment: ${path2}`, path2);
|
|
360
|
-
segments.push(segment);
|
|
361
|
-
if (i < path2.length && path2[i] === ".") i++;
|
|
316
|
+
return cur;
|
|
317
|
+
}
|
|
318
|
+
function deepMergeHoconValues(base, overlay) {
|
|
319
|
+
const merged = new Map(base.fields);
|
|
320
|
+
for (const [k, v] of overlay.fields) {
|
|
321
|
+
const existing = merged.get(k);
|
|
322
|
+
if (existing?.kind === "object" && v.kind === "object") {
|
|
323
|
+
merged.set(k, deepMergeHoconValues(existing, v));
|
|
362
324
|
} else {
|
|
363
|
-
|
|
364
|
-
if (dot === -1) {
|
|
365
|
-
segments.push(path2.slice(i));
|
|
366
|
-
break;
|
|
367
|
-
}
|
|
368
|
-
segments.push(path2.slice(i, dot));
|
|
369
|
-
i = dot + 1;
|
|
325
|
+
merged.set(k, v);
|
|
370
326
|
}
|
|
371
327
|
}
|
|
372
|
-
return
|
|
328
|
+
return { kind: "object", fields: merged };
|
|
373
329
|
}
|
|
374
|
-
function
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
return raw;
|
|
330
|
+
function deepMergeResObjInto(dst, src) {
|
|
331
|
+
for (const [k, srcVal] of src.fields) {
|
|
332
|
+
const dstVal = dst.fields.get(k);
|
|
333
|
+
if (dstVal !== void 0 && isResObj(dstVal) && isResObj(srcVal)) {
|
|
334
|
+
deepMergeResObjInto(dstVal, srcVal);
|
|
335
|
+
} else {
|
|
336
|
+
if (dstVal !== void 0) dst.priorValues.set(k, dstVal);
|
|
337
|
+
dst.fields.set(k, srcVal);
|
|
338
|
+
}
|
|
384
339
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
case "scalar":
|
|
389
|
-
return scalarToJs(v.raw, v.valueType);
|
|
390
|
-
case "array":
|
|
391
|
-
return v.items.map(hoconToJs);
|
|
392
|
-
case "object": {
|
|
393
|
-
const obj = {};
|
|
394
|
-
for (const [k, val] of v.fields) obj[k] = hoconToJs(val);
|
|
395
|
-
return obj;
|
|
340
|
+
for (const [k, srcPrior] of src.priorValues) {
|
|
341
|
+
if (!dst.priorValues.has(k)) {
|
|
342
|
+
dst.priorValues.set(k, srcPrior);
|
|
396
343
|
}
|
|
397
344
|
}
|
|
398
345
|
}
|
|
399
|
-
function
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
if (
|
|
404
|
-
|
|
346
|
+
function hoconValueToResObj(hv) {
|
|
347
|
+
const obj = makeResObj();
|
|
348
|
+
if (hv.kind !== "object") return obj;
|
|
349
|
+
for (const [key, val] of hv.fields) {
|
|
350
|
+
if (val.kind === "object") {
|
|
351
|
+
obj.fields.set(key, hoconValueToResObj(val));
|
|
405
352
|
} else {
|
|
406
|
-
|
|
353
|
+
obj.fields.set(key, val);
|
|
407
354
|
}
|
|
408
355
|
}
|
|
409
|
-
return
|
|
356
|
+
return obj;
|
|
357
|
+
}
|
|
358
|
+
function isFileNotFoundError(e) {
|
|
359
|
+
if (!(e instanceof Error)) return false;
|
|
360
|
+
const code = e.code;
|
|
361
|
+
if (code === "ENOENT" || code === "MODULE_NOT_FOUND") return true;
|
|
362
|
+
const msg = e.message.toLowerCase();
|
|
363
|
+
return msg.includes("not found") || msg.includes("no such file") || msg.includes("enoent");
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// src/internal/resolver/include-loader.ts
|
|
367
|
+
import { createRequire } from "module";
|
|
368
|
+
import * as nodePath from "path";
|
|
369
|
+
|
|
370
|
+
// src/internal/parser/empty-check.ts
|
|
371
|
+
function hasContentTokens(tokens) {
|
|
372
|
+
return tokens.some((t) => t.kind !== "eof" && t.kind !== "newline");
|
|
373
|
+
}
|
|
374
|
+
function assertNonEmptyDocument(tokens, sourceDescriptor) {
|
|
375
|
+
if (!hasContentTokens(tokens)) {
|
|
376
|
+
throw new ParseError(
|
|
377
|
+
`empty file is not a valid HOCON document (HOCON.md L130): ${sourceDescriptor}`,
|
|
378
|
+
1,
|
|
379
|
+
1
|
|
380
|
+
);
|
|
381
|
+
}
|
|
410
382
|
}
|
|
411
383
|
|
|
412
384
|
// src/internal/lexer/lexer.ts
|
|
@@ -972,58 +944,137 @@ var Parser = class {
|
|
|
972
944
|
const p = this.currentPos();
|
|
973
945
|
this.skip("newline");
|
|
974
946
|
const t = this.peek();
|
|
975
|
-
let path2;
|
|
976
|
-
let required = false;
|
|
977
|
-
let isFile = false;
|
|
978
947
|
if (t.kind === "unquoted" && (t.value === "required(" || t.value === "required" || t.value.startsWith("required("))) {
|
|
979
948
|
if (t.value === "required") {
|
|
980
|
-
const
|
|
981
|
-
if (
|
|
949
|
+
const next = this.peek(1);
|
|
950
|
+
if (next.kind !== "unquoted" || !next.value.startsWith("(")) {
|
|
982
951
|
throw new ParseError("include required must be followed by (", t.line, t.col);
|
|
983
952
|
}
|
|
984
953
|
}
|
|
985
|
-
required = true;
|
|
986
954
|
this.advance();
|
|
987
955
|
const innerPrefix = t.value.startsWith("required(") ? t.value.slice("required(".length) : "";
|
|
988
|
-
if (innerPrefix.startsWith("url") || innerPrefix.startsWith("classpath")) {
|
|
956
|
+
if (innerPrefix === "url" || innerPrefix.startsWith("url(") || innerPrefix === "classpath" || innerPrefix.startsWith("classpath(")) {
|
|
989
957
|
throw new ParseError("include url(...) and classpath(...) are not supported", t.line, t.col);
|
|
990
958
|
}
|
|
991
|
-
|
|
992
|
-
|
|
959
|
+
const nextTok = this.peek();
|
|
960
|
+
if (innerPrefix === "package" || innerPrefix.startsWith("package(") || innerPrefix === "" && nextTok.kind === "unquoted" && (nextTok.value === "package(" || nextTok.value.startsWith("package("))) {
|
|
961
|
+
const consumePackageToken = innerPrefix === "";
|
|
962
|
+
const { qualifier, path: path3 } = this.parsePackageArgs(consumePackageToken);
|
|
963
|
+
return this.makeIncludeField(qualifier, path3, true, p);
|
|
993
964
|
}
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
965
|
+
if (innerPrefix === "file" || innerPrefix.startsWith("file(") || innerPrefix === "" && nextTok.kind === "unquoted" && (nextTok.value === "file(" || nextTok.value === "file")) {
|
|
966
|
+
if (innerPrefix === "") {
|
|
967
|
+
this.advance();
|
|
968
|
+
}
|
|
969
|
+
const path3 = this.parseQuotedPathSkipWrapper(t);
|
|
970
|
+
return this.makeIncludeField({ kind: "file" }, path3, true, p);
|
|
971
|
+
}
|
|
972
|
+
if (innerPrefix === "" && nextTok.kind === "unquoted" && (nextTok.value === "url" || nextTok.value.startsWith("url(") || nextTok.value === "classpath" || nextTok.value.startsWith("classpath("))) {
|
|
973
|
+
throw new ParseError("include url(...) and classpath(...) are not supported", nextTok.line, nextTok.col);
|
|
974
|
+
}
|
|
975
|
+
if (innerPrefix !== "") {
|
|
976
|
+
if (innerPrefix === ")") {
|
|
977
|
+
throw new ParseError("include required(...) must contain a path", t.line, t.col);
|
|
978
|
+
}
|
|
979
|
+
const name = innerPrefix.replace(/\(.*$/, "").replace(/\).*$/, "");
|
|
980
|
+
throw new ParseError(`unknown include qualifier inside required(): "${name}"`, t.line, t.col);
|
|
981
|
+
}
|
|
982
|
+
const path2 = this.parseQuotedPathSkipWrapper(t);
|
|
983
|
+
return this.makeIncludeField({ kind: "bare" }, path2, true, p);
|
|
984
|
+
}
|
|
985
|
+
if (t.kind === "string") {
|
|
986
|
+
const path2 = this.advance().value;
|
|
987
|
+
return this.makeIncludeField({ kind: "bare" }, path2, false, p);
|
|
988
|
+
}
|
|
989
|
+
if (t.kind === "unquoted" && (t.value === "file(" || t.value === "file")) {
|
|
1009
990
|
this.advance();
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
991
|
+
const path2 = this.parseQuotedPathSkipWrapper(t);
|
|
992
|
+
return this.makeIncludeField({ kind: "file" }, path2, false, p);
|
|
993
|
+
}
|
|
994
|
+
if (t.kind === "unquoted" && (t.value === "package(" || t.value.startsWith("package("))) {
|
|
995
|
+
const { qualifier, path: path2 } = this.parsePackageArgs();
|
|
996
|
+
return this.makeIncludeField(qualifier, path2, false, p);
|
|
997
|
+
}
|
|
998
|
+
if (t.kind === "unquoted" && (t.value === "url" || t.value.startsWith("url("))) {
|
|
1015
999
|
throw new ParseError("include url(...) is not supported", t.line, t.col);
|
|
1016
|
-
}
|
|
1000
|
+
}
|
|
1001
|
+
if (t.kind === "unquoted" && (t.value === "classpath" || t.value.startsWith("classpath("))) {
|
|
1017
1002
|
throw new ParseError("include classpath(...) is not supported", t.line, t.col);
|
|
1018
|
-
} else {
|
|
1019
|
-
throw new ParseError(`expected include path, got ${t.kind}`, t.line, t.col);
|
|
1020
1003
|
}
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1004
|
+
throw new ParseError(`expected include path, got ${t.kind}`, t.line, t.col);
|
|
1005
|
+
}
|
|
1006
|
+
makeIncludeField(qualifier, path2, required, pos) {
|
|
1007
|
+
return { key: [], value: { kind: "include", qualifier, path: path2, required, pos }, append: false, pos };
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Skip past include-syntax wrapper tokens to find the quoted path string.
|
|
1011
|
+
* Strict: only allows `(`, `)`, and bare qualifier keywords (file / file( /
|
|
1012
|
+
* package / package( / url / url( / classpath / classpath() between the
|
|
1013
|
+
* directive and the quoted path. Anything else is a parse error rather than
|
|
1014
|
+
* silently swallowed (see ts#113).
|
|
1015
|
+
*/
|
|
1016
|
+
parseQuotedPathSkipWrapper(errTok) {
|
|
1017
|
+
while (this.isIncludeWrapperToken(this.peek())) this.advance();
|
|
1018
|
+
if (this.peek().kind !== "string") {
|
|
1019
|
+
const tok = this.peek();
|
|
1020
|
+
if (tok.kind === "eof") throw new ParseError("expected include path", errTok.line, errTok.col);
|
|
1021
|
+
const desc = tok.kind === "unquoted" ? `unquoted "${tok.value}"` : tok.kind;
|
|
1022
|
+
throw new ParseError(`expected quoted include path, got ${desc}`, tok.line, tok.col);
|
|
1023
|
+
}
|
|
1024
|
+
const path2 = this.advance().value;
|
|
1025
|
+
while (this.peek().kind === "unquoted" && (this.peek().value === ")" || this.peek().value === "))")) {
|
|
1026
|
+
this.advance();
|
|
1027
|
+
}
|
|
1028
|
+
const after = this.peek();
|
|
1029
|
+
if (after.kind !== "newline" && after.kind !== "rbrace" && after.kind !== "eof" && after.kind !== "comma") {
|
|
1030
|
+
const desc = after.kind === "unquoted" ? `unquoted "${after.value}"` : after.kind;
|
|
1031
|
+
throw new ParseError(`unexpected token after include path: ${desc}`, after.line, after.col);
|
|
1032
|
+
}
|
|
1033
|
+
return path2;
|
|
1034
|
+
}
|
|
1035
|
+
isIncludeWrapperToken(tok) {
|
|
1036
|
+
return tok.kind === "unquoted" && tok.value === "(";
|
|
1037
|
+
}
|
|
1038
|
+
/**
|
|
1039
|
+
* Parse the body of `package("identifier", "file")`.
|
|
1040
|
+
*
|
|
1041
|
+
* @param consumePackageToken - When true (default), the current token is `package(` and
|
|
1042
|
+
* must be consumed first. When false, the lexer already bundled `package(` into the
|
|
1043
|
+
* preceding token (e.g. `required(package("`), so we are already positioned at the
|
|
1044
|
+
* first string argument.
|
|
1045
|
+
*/
|
|
1046
|
+
parsePackageArgs(consumePackageToken = true) {
|
|
1047
|
+
const pkgTok = consumePackageToken ? this.advance() : this.peek();
|
|
1048
|
+
this.skip("newline");
|
|
1049
|
+
const idTok = this.peek();
|
|
1050
|
+
if (idTok.kind !== "string") {
|
|
1051
|
+
throw new ParseError("include package: expected quoted identifier as first argument", pkgTok.line, pkgTok.col);
|
|
1052
|
+
}
|
|
1053
|
+
const identifier = this.advance().value;
|
|
1054
|
+
this.skip("newline");
|
|
1055
|
+
if (this.peek().kind !== "comma") {
|
|
1056
|
+
const after = this.peek();
|
|
1057
|
+
throw new ParseError(
|
|
1058
|
+
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",
|
|
1059
|
+
pkgTok.line,
|
|
1060
|
+
pkgTok.col
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
this.advance();
|
|
1064
|
+
this.skip("newline");
|
|
1065
|
+
const fileTok = this.peek();
|
|
1066
|
+
if (fileTok.kind !== "string") {
|
|
1067
|
+
throw new ParseError(
|
|
1068
|
+
"include package: requires exactly two arguments (identifier, file) \u2014 one-arg form is not supported",
|
|
1069
|
+
pkgTok.line,
|
|
1070
|
+
pkgTok.col
|
|
1071
|
+
);
|
|
1072
|
+
}
|
|
1073
|
+
const filePath = this.advance().value;
|
|
1074
|
+
while (this.peek().kind !== "newline" && this.peek().kind !== "rbrace" && this.peek().kind !== "eof" && this.peek().kind !== "comma") {
|
|
1075
|
+
this.advance();
|
|
1076
|
+
}
|
|
1077
|
+
return { qualifier: { kind: "package", identifier }, path: filePath };
|
|
1027
1078
|
}
|
|
1028
1079
|
parseValue() {
|
|
1029
1080
|
const p = this.currentPos();
|
|
@@ -1064,7 +1115,9 @@ var Parser = class {
|
|
|
1064
1115
|
parts.push(node);
|
|
1065
1116
|
}
|
|
1066
1117
|
if (parts.length === 0) throw new ParseError("expected value", this.peek().line, this.peek().col);
|
|
1067
|
-
if (parts.length === 1)
|
|
1118
|
+
if (parts.length === 1) {
|
|
1119
|
+
return parts[0];
|
|
1120
|
+
}
|
|
1068
1121
|
return { kind: "concat", nodes: parts, pos: p };
|
|
1069
1122
|
}
|
|
1070
1123
|
parseArray() {
|
|
@@ -1096,118 +1149,6 @@ function parseTokens(tokens) {
|
|
|
1096
1149
|
return new Parser(tokens).parse();
|
|
1097
1150
|
}
|
|
1098
1151
|
|
|
1099
|
-
// src/internal/parser/empty-check.ts
|
|
1100
|
-
function hasContentTokens(tokens) {
|
|
1101
|
-
return tokens.some((t) => t.kind !== "eof" && t.kind !== "newline");
|
|
1102
|
-
}
|
|
1103
|
-
function assertNonEmptyDocument(tokens, sourceDescriptor) {
|
|
1104
|
-
if (!hasContentTokens(tokens)) {
|
|
1105
|
-
throw new ParseError(
|
|
1106
|
-
`empty file is not a valid HOCON document (HOCON.md L130): ${sourceDescriptor}`,
|
|
1107
|
-
1,
|
|
1108
|
-
1
|
|
1109
|
-
);
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
// src/internal/resolver/types.ts
|
|
1114
|
-
var separatorValues = /* @__PURE__ */ new WeakSet();
|
|
1115
|
-
function isSubst(v) {
|
|
1116
|
-
return v._kind === "subst-placeholder";
|
|
1117
|
-
}
|
|
1118
|
-
function isConcat(v) {
|
|
1119
|
-
return v._kind === "concat-placeholder";
|
|
1120
|
-
}
|
|
1121
|
-
function isAppend(v) {
|
|
1122
|
-
return v._kind === "append-placeholder";
|
|
1123
|
-
}
|
|
1124
|
-
function isResObj(v) {
|
|
1125
|
-
return v._kind === "res-obj";
|
|
1126
|
-
}
|
|
1127
|
-
function makeResObj() {
|
|
1128
|
-
return { _kind: "res-obj", fields: /* @__PURE__ */ new Map(), priorValues: /* @__PURE__ */ new Map() };
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
// src/internal/resolver/utils.ts
|
|
1132
|
-
function segmentsToKey(segments) {
|
|
1133
|
-
return segments.map((s) => {
|
|
1134
|
-
const t = s.text;
|
|
1135
|
-
if (t === "" || /[^a-zA-Z0-9\-_]/.test(t)) {
|
|
1136
|
-
return `"${t.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
1137
|
-
}
|
|
1138
|
-
return t;
|
|
1139
|
-
}).join(".");
|
|
1140
|
-
}
|
|
1141
|
-
function lookupPath(root, segments) {
|
|
1142
|
-
const [head, ...tail] = segments;
|
|
1143
|
-
if (head === void 0) return void 0;
|
|
1144
|
-
const key = head.text;
|
|
1145
|
-
const val = root.fields.get(key);
|
|
1146
|
-
if (val === void 0) return void 0;
|
|
1147
|
-
if (tail.length === 0) return val;
|
|
1148
|
-
if (isResObj(val)) return lookupPath(val, tail);
|
|
1149
|
-
return void 0;
|
|
1150
|
-
}
|
|
1151
|
-
function lookupResObj(root, segments) {
|
|
1152
|
-
let cur = root;
|
|
1153
|
-
for (const seg of segments) {
|
|
1154
|
-
const val = cur.fields.get(seg.text);
|
|
1155
|
-
if (val === void 0 || !isResObj(val)) return void 0;
|
|
1156
|
-
cur = val;
|
|
1157
|
-
}
|
|
1158
|
-
return cur;
|
|
1159
|
-
}
|
|
1160
|
-
function deepMergeHoconValues(base, overlay) {
|
|
1161
|
-
const merged = new Map(base.fields);
|
|
1162
|
-
for (const [k, v] of overlay.fields) {
|
|
1163
|
-
const existing = merged.get(k);
|
|
1164
|
-
if (existing?.kind === "object" && v.kind === "object") {
|
|
1165
|
-
merged.set(k, deepMergeHoconValues(existing, v));
|
|
1166
|
-
} else {
|
|
1167
|
-
merged.set(k, v);
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
return { kind: "object", fields: merged };
|
|
1171
|
-
}
|
|
1172
|
-
function deepMergeResObjInto(dst, src) {
|
|
1173
|
-
for (const [k, srcVal] of src.fields) {
|
|
1174
|
-
const dstVal = dst.fields.get(k);
|
|
1175
|
-
if (dstVal !== void 0 && isResObj(dstVal) && isResObj(srcVal)) {
|
|
1176
|
-
deepMergeResObjInto(dstVal, srcVal);
|
|
1177
|
-
} else {
|
|
1178
|
-
if (dstVal !== void 0) dst.priorValues.set(k, dstVal);
|
|
1179
|
-
dst.fields.set(k, srcVal);
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
for (const [k, srcPrior] of src.priorValues) {
|
|
1183
|
-
if (!dst.priorValues.has(k)) {
|
|
1184
|
-
dst.priorValues.set(k, srcPrior);
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
}
|
|
1188
|
-
function hoconValueToResObj(hv) {
|
|
1189
|
-
const obj = makeResObj();
|
|
1190
|
-
if (hv.kind !== "object") return obj;
|
|
1191
|
-
for (const [key, val] of hv.fields) {
|
|
1192
|
-
if (val.kind === "object") {
|
|
1193
|
-
obj.fields.set(key, hoconValueToResObj(val));
|
|
1194
|
-
} else {
|
|
1195
|
-
obj.fields.set(key, val);
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
return obj;
|
|
1199
|
-
}
|
|
1200
|
-
function isFileNotFoundError(e) {
|
|
1201
|
-
if (!(e instanceof Error)) return false;
|
|
1202
|
-
const code = e.code;
|
|
1203
|
-
if (code === "ENOENT" || code === "MODULE_NOT_FOUND") return true;
|
|
1204
|
-
const msg = e.message.toLowerCase();
|
|
1205
|
-
return msg.includes("not found") || msg.includes("no such file") || msg.includes("enoent");
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
// src/internal/resolver/include-loader.ts
|
|
1209
|
-
import * as nodePath from "path";
|
|
1210
|
-
|
|
1211
1152
|
// src/internal/properties/properties.ts
|
|
1212
1153
|
function parseProperties(input) {
|
|
1213
1154
|
const root = /* @__PURE__ */ Object.create(null);
|
|
@@ -1267,6 +1208,84 @@ function recordToHoconValue(obj) {
|
|
|
1267
1208
|
}
|
|
1268
1209
|
|
|
1269
1210
|
// src/internal/resolver/include-loader.ts
|
|
1211
|
+
var _require = createRequire(import.meta.url);
|
|
1212
|
+
function isPnpPath(p) {
|
|
1213
|
+
return p.includes(".yarn/cache") || p.includes(".pnp.cjs") || p.includes(".pnp.loader");
|
|
1214
|
+
}
|
|
1215
|
+
function validatePackageFile(file, identifier) {
|
|
1216
|
+
if (file.length === 0) {
|
|
1217
|
+
throw new ParseError(`include package("${identifier}", ...): file argument must be non-empty`, 0, 0);
|
|
1218
|
+
}
|
|
1219
|
+
if (file.includes("\\")) {
|
|
1220
|
+
throw new ParseError(
|
|
1221
|
+
`include package("${identifier}", "${file}"): backslash not allowed in file argument (use forward slash)`,
|
|
1222
|
+
0,
|
|
1223
|
+
0
|
|
1224
|
+
);
|
|
1225
|
+
}
|
|
1226
|
+
if (file.startsWith("/")) {
|
|
1227
|
+
throw new ParseError(
|
|
1228
|
+
`include package("${identifier}", "${file}"): absolute path not allowed in file argument`,
|
|
1229
|
+
0,
|
|
1230
|
+
0
|
|
1231
|
+
);
|
|
1232
|
+
}
|
|
1233
|
+
if (/(?:^|\/)\.\.?(?:\/|$)/u.test(file)) {
|
|
1234
|
+
throw new ParseError(
|
|
1235
|
+
`include package("${identifier}", "${file}"): path traversal (. or ..) not allowed in file argument`,
|
|
1236
|
+
0,
|
|
1237
|
+
0
|
|
1238
|
+
);
|
|
1239
|
+
}
|
|
1240
|
+
if (file.includes("//")) {
|
|
1241
|
+
throw new ParseError(
|
|
1242
|
+
`include package("${identifier}", "${file}"): consecutive slashes not allowed in file argument`,
|
|
1243
|
+
0,
|
|
1244
|
+
0
|
|
1245
|
+
);
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
function validatePackageIdentifier(identifier) {
|
|
1249
|
+
if (identifier.length === 0) {
|
|
1250
|
+
throw new ParseError("include package: identifier argument must be non-empty", 0, 0);
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
function defaultPackageResolver(identifier, file, includingFile, baseDir, resolveFrom) {
|
|
1254
|
+
const from = resolveFrom ? Array.isArray(resolveFrom) ? resolveFrom : [resolveFrom] : baseDir ? [baseDir] : includingFile ? [nodePath.dirname(includingFile)] : [process.cwd()];
|
|
1255
|
+
let resolved;
|
|
1256
|
+
try {
|
|
1257
|
+
resolved = _require.resolve(`${identifier}/${file}`, { paths: from });
|
|
1258
|
+
} catch {
|
|
1259
|
+
throw new PackageLookupError(
|
|
1260
|
+
`include package("${identifier}", "${file}"): module not found (starting from: ${from.join(", ")})`,
|
|
1261
|
+
identifier,
|
|
1262
|
+
file,
|
|
1263
|
+
0,
|
|
1264
|
+
0
|
|
1265
|
+
);
|
|
1266
|
+
}
|
|
1267
|
+
if (isPnpPath(resolved)) {
|
|
1268
|
+
throw new ResolveError(
|
|
1269
|
+
`include package("${identifier}", "${file}"): Yarn Berry PnP store paths are not supported. Provide a custom packageResolver option to handle PnP resolution.`,
|
|
1270
|
+
`${identifier}/${file}`,
|
|
1271
|
+
0,
|
|
1272
|
+
0
|
|
1273
|
+
);
|
|
1274
|
+
}
|
|
1275
|
+
const fileParts = file.split("/");
|
|
1276
|
+
const expectedBasename = fileParts[fileParts.length - 1] ?? file;
|
|
1277
|
+
const resolvedBasename = nodePath.basename(resolved);
|
|
1278
|
+
if (resolvedBasename !== expectedBasename) {
|
|
1279
|
+
throw new PackageLookupError(
|
|
1280
|
+
`include package("${identifier}", "${file}"): case mismatch \u2014 resolved "${resolvedBasename}" but expected "${expectedBasename}" (E11 decision 5: case-sensitive)`,
|
|
1281
|
+
identifier,
|
|
1282
|
+
file,
|
|
1283
|
+
0,
|
|
1284
|
+
0
|
|
1285
|
+
);
|
|
1286
|
+
}
|
|
1287
|
+
return resolved;
|
|
1288
|
+
}
|
|
1270
1289
|
var IncludeLoader = class {
|
|
1271
1290
|
constructor(opts) {
|
|
1272
1291
|
this.opts = opts;
|
|
@@ -1346,8 +1365,77 @@ var IncludeLoader = class {
|
|
|
1346
1365
|
}
|
|
1347
1366
|
return merged;
|
|
1348
1367
|
}
|
|
1368
|
+
/**
|
|
1369
|
+
* Resolve and load `include package("identifier", "file")`.
|
|
1370
|
+
* Validates the identifier and file arguments, resolves via the package resolver,
|
|
1371
|
+
* performs cycle detection using a JSON-serialised cycle key, and parses the content.
|
|
1372
|
+
*/
|
|
1373
|
+
// `required` is reserved for future optional-package semantics (E11 decision 7);
|
|
1374
|
+
// currently all package lookup failures throw regardless of this flag.
|
|
1375
|
+
loadPackage(identifier, file, _required) {
|
|
1376
|
+
validatePackageIdentifier(identifier);
|
|
1377
|
+
validatePackageFile(file, identifier);
|
|
1378
|
+
const { includeStack = [], baseDir, resolveFrom, packageResolver, readFileSync: readFileSync2 } = this.opts;
|
|
1379
|
+
const cycleKey = JSON.stringify(["package", identifier, file]);
|
|
1380
|
+
if (includeStack.includes(cycleKey)) {
|
|
1381
|
+
throw new ResolveError(
|
|
1382
|
+
`circular include: package("${identifier}", "${file}")`,
|
|
1383
|
+
`${identifier}/${file}`,
|
|
1384
|
+
0,
|
|
1385
|
+
0
|
|
1386
|
+
);
|
|
1387
|
+
}
|
|
1388
|
+
if (includeStack.length >= 50) {
|
|
1389
|
+
throw new ResolveError(`include depth limit exceeded (max 50)`, `${identifier}/${file}`, 0, 0);
|
|
1390
|
+
}
|
|
1391
|
+
const resolver = packageResolver ?? ((id, f, includingFile, bDir) => defaultPackageResolver(id, f, includingFile, bDir, resolveFrom));
|
|
1392
|
+
const resolvedPath = resolver(identifier, file, void 0, baseDir);
|
|
1393
|
+
const content = readFileSync2(resolvedPath);
|
|
1394
|
+
if (content.length === 0) return makeResObj();
|
|
1395
|
+
const tokens = tokenize(content);
|
|
1396
|
+
assertNonEmptyDocument(tokens, resolvedPath);
|
|
1397
|
+
const ast = parseTokens(tokens);
|
|
1398
|
+
return this.onBuildResObj(ast, {
|
|
1399
|
+
...this.opts,
|
|
1400
|
+
baseDir: nodePath.dirname(resolvedPath),
|
|
1401
|
+
includeStack: [...includeStack, cycleKey]
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1404
|
+
/**
|
|
1405
|
+
* Async variant of `loadPackage`.
|
|
1406
|
+
*/
|
|
1407
|
+
async loadPackageAsync(identifier, file, _required) {
|
|
1408
|
+
validatePackageIdentifier(identifier);
|
|
1409
|
+
validatePackageFile(file, identifier);
|
|
1410
|
+
const { includeStack = [], baseDir, resolveFrom, packageResolver, readFile, readFileSync: readFileSync2 } = this.opts;
|
|
1411
|
+
const cycleKey = JSON.stringify(["package", identifier, file]);
|
|
1412
|
+
if (includeStack.includes(cycleKey)) {
|
|
1413
|
+
throw new ResolveError(
|
|
1414
|
+
`circular include: package("${identifier}", "${file}")`,
|
|
1415
|
+
`${identifier}/${file}`,
|
|
1416
|
+
0,
|
|
1417
|
+
0
|
|
1418
|
+
);
|
|
1419
|
+
}
|
|
1420
|
+
if (includeStack.length >= 50) {
|
|
1421
|
+
throw new ResolveError(`include depth limit exceeded (max 50)`, `${identifier}/${file}`, 0, 0);
|
|
1422
|
+
}
|
|
1423
|
+
const resolver = packageResolver ?? ((id, f, includingFile, bDir) => defaultPackageResolver(id, f, includingFile, bDir, resolveFrom));
|
|
1424
|
+
const resolvedPath = resolver(identifier, file, void 0, baseDir);
|
|
1425
|
+
const read = readFile ? async (p) => readFile(p) : async (p) => readFileSync2(p);
|
|
1426
|
+
const content = await read(resolvedPath);
|
|
1427
|
+
if (content.length === 0) return makeResObj();
|
|
1428
|
+
const tokens = tokenize(content);
|
|
1429
|
+
assertNonEmptyDocument(tokens, resolvedPath);
|
|
1430
|
+
const ast = parseTokens(tokens);
|
|
1431
|
+
return this.onBuildResObjAsync(ast, {
|
|
1432
|
+
...this.opts,
|
|
1433
|
+
baseDir: nodePath.dirname(resolvedPath),
|
|
1434
|
+
includeStack: [...includeStack, cycleKey]
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1349
1437
|
loadSingle(candidate) {
|
|
1350
|
-
const { readFileSync: readFileSync2, includeStack = []
|
|
1438
|
+
const { readFileSync: readFileSync2, includeStack = [] } = this.opts;
|
|
1351
1439
|
if (includeStack.includes(candidate)) {
|
|
1352
1440
|
throw new ResolveError(`circular include: ${candidate}`, candidate, 0, 0);
|
|
1353
1441
|
}
|
|
@@ -1362,17 +1450,18 @@ var IncludeLoader = class {
|
|
|
1362
1450
|
return hoconValueToResObj(propertiesToHoconValue(content));
|
|
1363
1451
|
}
|
|
1364
1452
|
const tokens = tokenize(content);
|
|
1365
|
-
|
|
1453
|
+
if (!hasContentTokens(tokens)) {
|
|
1454
|
+
return makeResObj();
|
|
1455
|
+
}
|
|
1366
1456
|
const ast = parseTokens(tokens);
|
|
1367
1457
|
return this.onBuildResObj(ast, {
|
|
1368
|
-
|
|
1458
|
+
...this.opts,
|
|
1369
1459
|
baseDir: nodePath.dirname(candidate),
|
|
1370
|
-
readFileSync: readFileSync2,
|
|
1371
1460
|
includeStack: [...includeStack, candidate]
|
|
1372
1461
|
});
|
|
1373
1462
|
}
|
|
1374
1463
|
async loadSingleAsync(candidate) {
|
|
1375
|
-
const { readFile, readFileSync: readFileSync2, includeStack = []
|
|
1464
|
+
const { readFile, readFileSync: readFileSync2, includeStack = [] } = this.opts;
|
|
1376
1465
|
const read = readFile ? async (p) => readFile(p) : async (p) => readFileSync2(p);
|
|
1377
1466
|
if (includeStack.includes(candidate)) {
|
|
1378
1467
|
throw new ResolveError(`circular include: ${candidate}`, candidate, 0, 0);
|
|
@@ -1388,13 +1477,13 @@ var IncludeLoader = class {
|
|
|
1388
1477
|
return hoconValueToResObj(propertiesToHoconValue(content));
|
|
1389
1478
|
}
|
|
1390
1479
|
const tokens = tokenize(content);
|
|
1391
|
-
|
|
1480
|
+
if (!hasContentTokens(tokens)) {
|
|
1481
|
+
return makeResObj();
|
|
1482
|
+
}
|
|
1392
1483
|
const ast = parseTokens(tokens);
|
|
1393
1484
|
return this.onBuildResObjAsync(ast, {
|
|
1394
|
-
|
|
1485
|
+
...this.opts,
|
|
1395
1486
|
baseDir: nodePath.dirname(candidate),
|
|
1396
|
-
readFileSync: readFileSync2,
|
|
1397
|
-
readFile,
|
|
1398
1487
|
includeStack: [...includeStack, candidate]
|
|
1399
1488
|
});
|
|
1400
1489
|
}
|
|
@@ -1403,7 +1492,6 @@ var IncludeLoader = class {
|
|
|
1403
1492
|
// src/internal/resolver/structure-builder.ts
|
|
1404
1493
|
var StructureBuilder = class _StructureBuilder {
|
|
1405
1494
|
constructor(opts) {
|
|
1406
|
-
this.opts = opts;
|
|
1407
1495
|
this.loader = new IncludeLoader(opts);
|
|
1408
1496
|
this.loader.onBuildResObj = (a, o) => new _StructureBuilder(o).build(a);
|
|
1409
1497
|
this.loader.onBuildResObjAsync = async (a, o) => new _StructureBuilder(o).buildAsync(a);
|
|
@@ -1430,7 +1518,13 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1430
1518
|
}
|
|
1431
1519
|
applyField(obj, field, pathPrefix) {
|
|
1432
1520
|
if (field.key.length === 0 && field.value.kind === "include") {
|
|
1433
|
-
const
|
|
1521
|
+
const { qualifier, path: path2, required } = field.value;
|
|
1522
|
+
let included;
|
|
1523
|
+
if (qualifier.kind === "package") {
|
|
1524
|
+
included = this.loader.loadPackage(qualifier.identifier, path2, required);
|
|
1525
|
+
} else {
|
|
1526
|
+
included = this.loader.load(path2, required, qualifier.kind === "file");
|
|
1527
|
+
}
|
|
1434
1528
|
if (pathPrefix.length > 0) {
|
|
1435
1529
|
this.relativizeResObj(included, pathPrefix);
|
|
1436
1530
|
}
|
|
@@ -1472,7 +1566,13 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1472
1566
|
}
|
|
1473
1567
|
async applyFieldAsync(obj, field, pathPrefix) {
|
|
1474
1568
|
if (field.key.length === 0 && field.value.kind === "include") {
|
|
1475
|
-
const
|
|
1569
|
+
const { qualifier, path: path2, required } = field.value;
|
|
1570
|
+
let included;
|
|
1571
|
+
if (qualifier.kind === "package") {
|
|
1572
|
+
included = await this.loader.loadPackageAsync(qualifier.identifier, path2, required);
|
|
1573
|
+
} else {
|
|
1574
|
+
included = await this.loader.loadAsync(path2, required, qualifier.kind === "file");
|
|
1575
|
+
}
|
|
1476
1576
|
if (pathPrefix.length > 0) {
|
|
1477
1577
|
this.relativizeResObj(included, pathPrefix);
|
|
1478
1578
|
}
|
|
@@ -1602,6 +1702,23 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1602
1702
|
}
|
|
1603
1703
|
};
|
|
1604
1704
|
|
|
1705
|
+
// src/value/numeric-array.ts
|
|
1706
|
+
function numericObjectToArray(value) {
|
|
1707
|
+
if (value.kind !== "object") return null;
|
|
1708
|
+
if (value.fields.size === 0) return null;
|
|
1709
|
+
const canonicalInt = /^(0|[1-9][0-9]*)$/;
|
|
1710
|
+
const eligible = [];
|
|
1711
|
+
for (const [key, val] of value.fields) {
|
|
1712
|
+
if (!canonicalInt.test(key)) continue;
|
|
1713
|
+
const n = Number(key);
|
|
1714
|
+
if (!Number.isInteger(n) || n > 2147483647) continue;
|
|
1715
|
+
eligible.push({ n, v: val });
|
|
1716
|
+
}
|
|
1717
|
+
if (eligible.length === 0) return null;
|
|
1718
|
+
eligible.sort((a, b) => a.n - b.n);
|
|
1719
|
+
return eligible.map((e) => e.v);
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1605
1722
|
// src/internal/resolver/substitution-resolver.ts
|
|
1606
1723
|
var SubstitutionResolver = class {
|
|
1607
1724
|
constructor(root, opts) {
|
|
@@ -1623,6 +1740,10 @@ var SubstitutionResolver = class {
|
|
|
1623
1740
|
// xx.hocon PR (see Phase 6 #3f close-out notes).
|
|
1624
1741
|
this.resolvingConcats = /* @__PURE__ */ new WeakSet();
|
|
1625
1742
|
}
|
|
1743
|
+
/** Returns a prefix for error messages, including origin info when set. */
|
|
1744
|
+
originPrefix() {
|
|
1745
|
+
return this.opts.originDescription ? `${this.opts.originDescription}: ` : "";
|
|
1746
|
+
}
|
|
1626
1747
|
resolve() {
|
|
1627
1748
|
return this.resolveResObj(this.root);
|
|
1628
1749
|
}
|
|
@@ -1663,7 +1784,8 @@ var SubstitutionResolver = class {
|
|
|
1663
1784
|
if (isConcat(v)) {
|
|
1664
1785
|
this.resolvingConcats.add(v);
|
|
1665
1786
|
try {
|
|
1666
|
-
|
|
1787
|
+
const concatResult = this.resolveConcat(v.nodes, scope, v.line, v.col);
|
|
1788
|
+
return concatResult;
|
|
1667
1789
|
} finally {
|
|
1668
1790
|
this.resolvingConcats.delete(v);
|
|
1669
1791
|
}
|
|
@@ -1708,7 +1830,7 @@ var SubstitutionResolver = class {
|
|
|
1708
1830
|
}
|
|
1709
1831
|
if (s.optional) return void 0;
|
|
1710
1832
|
throw new ResolveError(
|
|
1711
|
-
`circular substitution: ${key}`,
|
|
1833
|
+
this.originPrefix() + `circular substitution: ${key}`,
|
|
1712
1834
|
key,
|
|
1713
1835
|
s.line,
|
|
1714
1836
|
s.col
|
|
@@ -1738,7 +1860,7 @@ var SubstitutionResolver = class {
|
|
|
1738
1860
|
}
|
|
1739
1861
|
if (s.optional) return void 0;
|
|
1740
1862
|
throw new ResolveError(
|
|
1741
|
-
|
|
1863
|
+
`${this.originPrefix()}could not resolve substitution: \${${key}}`,
|
|
1742
1864
|
key,
|
|
1743
1865
|
s.line,
|
|
1744
1866
|
s.col
|
|
@@ -1771,7 +1893,7 @@ var SubstitutionResolver = class {
|
|
|
1771
1893
|
if (result !== void 0) this.cache.set(key, result);
|
|
1772
1894
|
return result;
|
|
1773
1895
|
}
|
|
1774
|
-
if (s.listSuffix) {
|
|
1896
|
+
if (s.listSuffix && this.opts.useSystemEnvironment !== false) {
|
|
1775
1897
|
const result = this.resolveEnvList(s);
|
|
1776
1898
|
if (result !== void 0) {
|
|
1777
1899
|
this.cache.set(key, result);
|
|
@@ -1780,22 +1902,36 @@ var SubstitutionResolver = class {
|
|
|
1780
1902
|
if (s.optional) return void 0;
|
|
1781
1903
|
const envBase = s.segments.map((seg) => seg.text).join(".");
|
|
1782
1904
|
throw new ResolveError(
|
|
1783
|
-
|
|
1905
|
+
`${this.originPrefix()}could not resolve substitution: \${${key}} (no environment variables ${envBase}_0, ${envBase}_1, \u2026 set)`,
|
|
1906
|
+
key,
|
|
1907
|
+
s.line,
|
|
1908
|
+
s.col
|
|
1909
|
+
);
|
|
1910
|
+
} else if (s.listSuffix) {
|
|
1911
|
+
if (s.optional) return void 0;
|
|
1912
|
+
if (this.opts.allowUnresolved) return s;
|
|
1913
|
+
throw new ResolveError(
|
|
1914
|
+
`${this.originPrefix()}could not resolve substitution: \${${key}}`,
|
|
1784
1915
|
key,
|
|
1785
1916
|
s.line,
|
|
1786
1917
|
s.col
|
|
1787
1918
|
);
|
|
1788
1919
|
}
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1920
|
+
if (this.opts.useSystemEnvironment !== false) {
|
|
1921
|
+
const envKey = s.segments.map((seg) => seg.text).join(".");
|
|
1922
|
+
const envVal = this.opts.env[envKey] ?? (s.prefixLen > 0 ? this.opts.env[s.segments.slice(s.prefixLen).map((seg) => seg.text).join(".")] : void 0);
|
|
1923
|
+
if (envVal !== void 0) {
|
|
1924
|
+
const result = { kind: "scalar", raw: envVal, valueType: "string" };
|
|
1925
|
+
this.cache.set(key, result);
|
|
1926
|
+
return result;
|
|
1927
|
+
}
|
|
1795
1928
|
}
|
|
1796
1929
|
if (s.optional) return void 0;
|
|
1930
|
+
if (this.opts.allowUnresolved) {
|
|
1931
|
+
return s;
|
|
1932
|
+
}
|
|
1797
1933
|
throw new ResolveError(
|
|
1798
|
-
|
|
1934
|
+
`${this.originPrefix()}could not resolve substitution: \${${key}}`,
|
|
1799
1935
|
key,
|
|
1800
1936
|
s.line,
|
|
1801
1937
|
s.col
|
|
@@ -1840,8 +1976,16 @@ var SubstitutionResolver = class {
|
|
|
1840
1976
|
return [full, bare];
|
|
1841
1977
|
}
|
|
1842
1978
|
resolveConcat(nodes, scope, line = 0, col = 0) {
|
|
1843
|
-
const
|
|
1844
|
-
if (
|
|
1979
|
+
const rawResolved = nodes.map((n) => this.resolveVal(n, scope));
|
|
1980
|
+
if (this.opts.allowUnresolved) {
|
|
1981
|
+
for (const v of rawResolved) {
|
|
1982
|
+
if (v !== void 0 && isSubst(v)) {
|
|
1983
|
+
return v;
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
const resolved = rawResolved.filter((v) => v !== void 0);
|
|
1988
|
+
if (resolved.length === 0) return void 0;
|
|
1845
1989
|
if (resolved.length === 1) return resolved[0];
|
|
1846
1990
|
const nonSep = resolved.filter((v) => !separatorValues.has(v));
|
|
1847
1991
|
if (nonSep.length === 0) {
|
|
@@ -1915,24 +2059,509 @@ var SubstitutionResolver = class {
|
|
|
1915
2059
|
};
|
|
1916
2060
|
|
|
1917
2061
|
// src/internal/resolver/resolver.ts
|
|
1918
|
-
function
|
|
1919
|
-
|
|
1920
|
-
return new SubstitutionResolver(root, opts).resolve();
|
|
2062
|
+
function buildTree(ast, opts) {
|
|
2063
|
+
return new StructureBuilder(opts).build(ast);
|
|
1921
2064
|
}
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
return new SubstitutionResolver(root, opts).resolve();
|
|
2065
|
+
function resolveTree(tree, opts) {
|
|
2066
|
+
return new SubstitutionResolver(tree, opts).resolve();
|
|
1925
2067
|
}
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
if (opts.env !== void 0) return opts.env;
|
|
1932
|
-
if (typeof process !== "undefined" && process.env) return process.env;
|
|
1933
|
-
return {};
|
|
2068
|
+
function containsPlaceholders(tree) {
|
|
2069
|
+
for (const val of tree.fields.values()) {
|
|
2070
|
+
if (valContainsPlaceholders(val)) return true;
|
|
2071
|
+
}
|
|
2072
|
+
return false;
|
|
1934
2073
|
}
|
|
1935
|
-
function
|
|
2074
|
+
function valContainsPlaceholders(v) {
|
|
2075
|
+
const rv = v;
|
|
2076
|
+
if (isSubst(rv) || isConcat(rv) || isAppend(rv)) return true;
|
|
2077
|
+
if (isResObj(rv)) return containsPlaceholders(rv);
|
|
2078
|
+
const hv = v;
|
|
2079
|
+
if (hv?.kind === "array") {
|
|
2080
|
+
return hv.items.some((item) => valContainsPlaceholders(item));
|
|
2081
|
+
}
|
|
2082
|
+
return false;
|
|
2083
|
+
}
|
|
2084
|
+
function buildPartialHoconFromResObj(tree) {
|
|
2085
|
+
const fields = /* @__PURE__ */ new Map();
|
|
2086
|
+
for (const [k, v] of tree.fields) {
|
|
2087
|
+
if (!isSubst(v) && !isConcat(v) && !isAppend(v)) {
|
|
2088
|
+
if (isResObj(v)) {
|
|
2089
|
+
fields.set(k, buildPartialHoconFromResObj(v));
|
|
2090
|
+
} else {
|
|
2091
|
+
if (!valContainsPlaceholders(v)) {
|
|
2092
|
+
fields.set(k, v);
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
return { kind: "object", fields };
|
|
2098
|
+
}
|
|
2099
|
+
function hoconValueToResObj2(value) {
|
|
2100
|
+
const obj = makeResObj();
|
|
2101
|
+
for (const [k, v] of value.fields) {
|
|
2102
|
+
if (v.kind === "object") {
|
|
2103
|
+
obj.fields.set(k, hoconValueToResObj2(v));
|
|
2104
|
+
} else {
|
|
2105
|
+
obj.fields.set(k, v);
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
return obj;
|
|
2109
|
+
}
|
|
2110
|
+
function resolve2(ast, opts) {
|
|
2111
|
+
const tree = buildTree(ast, opts);
|
|
2112
|
+
return resolveTree(tree, { ...opts, useSystemEnvironment: true, allowUnresolved: false });
|
|
2113
|
+
}
|
|
2114
|
+
async function resolveAsync(ast, opts) {
|
|
2115
|
+
const root = await new StructureBuilder(opts).buildAsync(ast);
|
|
2116
|
+
return resolveTree(root, { ...opts, useSystemEnvironment: true, allowUnresolved: false });
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2119
|
+
// src/config.ts
|
|
2120
|
+
var Config = class _Config {
|
|
2121
|
+
constructor(root, opts) {
|
|
2122
|
+
this.root = root;
|
|
2123
|
+
this._resolved = opts?.resolved ?? true;
|
|
2124
|
+
this._parseBaseDir = opts?.parseBaseDir;
|
|
2125
|
+
this._originDescription = opts?.originDescription;
|
|
2126
|
+
this._resObjRoot = opts?.resObjRoot;
|
|
2127
|
+
this._resolveOpts = opts?.resolveOpts;
|
|
2128
|
+
}
|
|
2129
|
+
/** Returns true when the Config's value tree contains no unresolved
|
|
2130
|
+
* substitution placeholders. Whole-config granularity (E12 decision 11). */
|
|
2131
|
+
isResolved() {
|
|
2132
|
+
return this._resolved;
|
|
2133
|
+
}
|
|
2134
|
+
/** @internal Test-only: construct a resolved Config from a HoconValue. */
|
|
2135
|
+
static _fromResolvedValue(root, opts) {
|
|
2136
|
+
return new _Config(root, { resolved: true, originDescription: opts?.originDescription });
|
|
2137
|
+
}
|
|
2138
|
+
/** @internal Construct an unresolved Config carrying a ResObj tree.
|
|
2139
|
+
* Used by parseStringWithOptions (deferred path) in T6. */
|
|
2140
|
+
static _fromUnresolvedResObj(tree, opts) {
|
|
2141
|
+
const partialRoot = buildPartialHoconFromResObj(tree);
|
|
2142
|
+
return new _Config(partialRoot, {
|
|
2143
|
+
resolved: opts.resolved,
|
|
2144
|
+
parseBaseDir: opts.parseBaseDir,
|
|
2145
|
+
originDescription: opts.originDescription,
|
|
2146
|
+
resObjRoot: tree,
|
|
2147
|
+
resolveOpts: opts.resolveOpts
|
|
2148
|
+
});
|
|
2149
|
+
}
|
|
2150
|
+
get(path2) {
|
|
2151
|
+
const v = this.lookupNode(path2);
|
|
2152
|
+
if (v === void 0) return void 0;
|
|
2153
|
+
return hoconToJs(v);
|
|
2154
|
+
}
|
|
2155
|
+
getString(path2) {
|
|
2156
|
+
const v = this.requireScalar(path2);
|
|
2157
|
+
return v.raw;
|
|
2158
|
+
}
|
|
2159
|
+
getNumber(path2) {
|
|
2160
|
+
const v = this.requireScalar(path2);
|
|
2161
|
+
const coerced = coerceNumber(v.raw);
|
|
2162
|
+
if (coerced !== void 0) return coerced;
|
|
2163
|
+
throw new ConfigError(`expected number at ${path2}, got ${v.valueType}`, path2);
|
|
2164
|
+
}
|
|
2165
|
+
getBoolean(path2) {
|
|
2166
|
+
const v = this.requireScalar(path2);
|
|
2167
|
+
const coerced = coerceBoolean(v.raw);
|
|
2168
|
+
if (coerced !== void 0) return coerced;
|
|
2169
|
+
throw new ConfigError(`expected boolean at ${path2}, got ${v.valueType}`, path2);
|
|
2170
|
+
}
|
|
2171
|
+
getDuration(path2, unit) {
|
|
2172
|
+
const v = this.requireScalar(path2);
|
|
2173
|
+
if (v.valueType !== "string" && v.valueType !== "number") {
|
|
2174
|
+
throw new ConfigError(`expected duration at ${path2}, got ${v.valueType}`, path2);
|
|
2175
|
+
}
|
|
2176
|
+
const result = parseDuration(v.raw, unit);
|
|
2177
|
+
if (Number.isNaN(result)) throw new ConfigError(`invalid duration at ${path2}: ${JSON.stringify(v.raw)}`, path2);
|
|
2178
|
+
return result;
|
|
2179
|
+
}
|
|
2180
|
+
getBytes(path2, unit) {
|
|
2181
|
+
const v = this.requireScalar(path2);
|
|
2182
|
+
if (v.valueType !== "string" && v.valueType !== "number") {
|
|
2183
|
+
throw new ConfigError(`expected byte size at ${path2}, got ${v.valueType}`, path2);
|
|
2184
|
+
}
|
|
2185
|
+
const result = parseBytes(v.raw, unit);
|
|
2186
|
+
if (Number.isNaN(result)) throw new ConfigError(`invalid byte size at ${path2}: ${JSON.stringify(v.raw)}`, path2);
|
|
2187
|
+
if (result < 0) throw new ConfigError(`byte size must be non-negative at ${path2}: ${JSON.stringify(v.raw)}`, path2);
|
|
2188
|
+
return result;
|
|
2189
|
+
}
|
|
2190
|
+
getConfig(path2) {
|
|
2191
|
+
const v = this.lookupNode(path2);
|
|
2192
|
+
if (v === void 0) {
|
|
2193
|
+
if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
|
|
2194
|
+
throw new NotResolvedError(path2);
|
|
2195
|
+
}
|
|
2196
|
+
throw new ConfigError(`path not found: ${path2}`, path2);
|
|
2197
|
+
}
|
|
2198
|
+
if (v.kind !== "object") throw new ConfigError(`expected object at ${path2}`, path2);
|
|
2199
|
+
if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
|
|
2200
|
+
throw new NotResolvedError(path2);
|
|
2201
|
+
}
|
|
2202
|
+
return new _Config(v, { resolved: this._resolved });
|
|
2203
|
+
}
|
|
2204
|
+
getList(path2) {
|
|
2205
|
+
const v = this.lookupNode(path2);
|
|
2206
|
+
if (v === void 0) {
|
|
2207
|
+
if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
|
|
2208
|
+
throw new NotResolvedError(path2);
|
|
2209
|
+
}
|
|
2210
|
+
throw new ConfigError(`path not found: ${path2}`, path2);
|
|
2211
|
+
}
|
|
2212
|
+
if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
|
|
2213
|
+
throw new NotResolvedError(path2);
|
|
2214
|
+
}
|
|
2215
|
+
if (v.kind === "object") {
|
|
2216
|
+
const converted = numericObjectToArray(v);
|
|
2217
|
+
if (converted !== null) return converted.map(hoconToJs);
|
|
2218
|
+
}
|
|
2219
|
+
if (v.kind !== "array") throw new ConfigError(`expected array at ${path2}`, path2);
|
|
2220
|
+
return v.items.map(hoconToJs);
|
|
2221
|
+
}
|
|
2222
|
+
has(path2) {
|
|
2223
|
+
return this.lookupNode(path2) !== void 0;
|
|
2224
|
+
}
|
|
2225
|
+
keys() {
|
|
2226
|
+
return [...this.root.fields.keys()];
|
|
2227
|
+
}
|
|
2228
|
+
withFallback(fallback) {
|
|
2229
|
+
if (fallback == null) return this;
|
|
2230
|
+
const selfTree = this._resObjRoot ?? hoconValueToResObj2(this.root);
|
|
2231
|
+
const fbTree = fallback._resObjRoot ?? hoconValueToResObj2(fallback.root);
|
|
2232
|
+
const merged = mergeUnresolved(selfTree, fbTree);
|
|
2233
|
+
const hasPlaceholders = containsPlaceholders(merged);
|
|
2234
|
+
const partialRoot = buildPartialHoconFromResObj(merged);
|
|
2235
|
+
return new _Config(partialRoot, {
|
|
2236
|
+
resolved: !hasPlaceholders,
|
|
2237
|
+
parseBaseDir: this._parseBaseDir,
|
|
2238
|
+
originDescription: this._originDescription,
|
|
2239
|
+
resObjRoot: merged,
|
|
2240
|
+
resolveOpts: this._resolveOpts ?? fallback._resolveOpts
|
|
2241
|
+
});
|
|
2242
|
+
}
|
|
2243
|
+
/**
|
|
2244
|
+
* Runs substitution resolution (phase 2) on the stored unresolved tree.
|
|
2245
|
+
* Idempotent: calling resolve() on an already-resolved Config returns an
|
|
2246
|
+
* equivalent resolved Config.
|
|
2247
|
+
*
|
|
2248
|
+
* opts.allowUnresolved (default false) — when true, leaves unresolved
|
|
2249
|
+
* non-optional placeholders in place rather than throwing.
|
|
2250
|
+
* opts.useSystemEnvironment (default true) — when false, env var lookups
|
|
2251
|
+
* are suppressed (hermetic resolution).
|
|
2252
|
+
*
|
|
2253
|
+
* E12 decision 3.
|
|
2254
|
+
*/
|
|
2255
|
+
resolve(opts = {}) {
|
|
2256
|
+
if (this._resolved) {
|
|
2257
|
+
return new _Config(this.root, {
|
|
2258
|
+
resolved: true,
|
|
2259
|
+
parseBaseDir: this._parseBaseDir,
|
|
2260
|
+
originDescription: this._originDescription
|
|
2261
|
+
});
|
|
2262
|
+
}
|
|
2263
|
+
const tree = this._resObjRoot;
|
|
2264
|
+
if (!tree) {
|
|
2265
|
+
return new _Config(this.root, { resolved: true });
|
|
2266
|
+
}
|
|
2267
|
+
const resolveOpts = {
|
|
2268
|
+
...this._resolveOpts ?? { env: {}, baseDir: void 0, readFileSync: () => {
|
|
2269
|
+
throw new Error("no files");
|
|
2270
|
+
} },
|
|
2271
|
+
allowUnresolved: opts.allowUnresolved ?? false,
|
|
2272
|
+
useSystemEnvironment: opts.useSystemEnvironment ?? true,
|
|
2273
|
+
originDescription: this._originDescription
|
|
2274
|
+
};
|
|
2275
|
+
const resolved = resolveTree(tree, resolveOpts);
|
|
2276
|
+
if (resolved.kind !== "object") throw new Error("resolve: expected object root");
|
|
2277
|
+
if (opts.allowUnresolved) {
|
|
2278
|
+
const { stripped, hadPlaceholders } = stripPlaceholderFields(resolved);
|
|
2279
|
+
return new _Config(stripped, {
|
|
2280
|
+
resolved: !hadPlaceholders,
|
|
2281
|
+
parseBaseDir: this._parseBaseDir,
|
|
2282
|
+
originDescription: this._originDescription,
|
|
2283
|
+
resObjRoot: hadPlaceholders ? tree : void 0,
|
|
2284
|
+
resolveOpts: hadPlaceholders ? resolveOpts : void 0
|
|
2285
|
+
});
|
|
2286
|
+
}
|
|
2287
|
+
return new _Config(resolved, {
|
|
2288
|
+
resolved: true,
|
|
2289
|
+
parseBaseDir: this._parseBaseDir,
|
|
2290
|
+
originDescription: this._originDescription
|
|
2291
|
+
});
|
|
2292
|
+
}
|
|
2293
|
+
/**
|
|
2294
|
+
* Resolves receiver substitutions using source as lookup context.
|
|
2295
|
+
* Source's keys are NOT merged into the result — differs from
|
|
2296
|
+
* `this.withFallback(source).resolve()` which includes source keys.
|
|
2297
|
+
*
|
|
2298
|
+
* Precondition (E12 decision 10): source must be resolved. If
|
|
2299
|
+
* source.isResolved() is false, throws NotResolvedError.
|
|
2300
|
+
*
|
|
2301
|
+
* On an already-resolved receiver, resolveWith is a no-op (returns an
|
|
2302
|
+
* equivalent resolved Config, source keys not included).
|
|
2303
|
+
*
|
|
2304
|
+
* E12 decisions 9, 10.
|
|
2305
|
+
*/
|
|
2306
|
+
resolveWith(source, opts = {}) {
|
|
2307
|
+
if (!source.isResolved()) {
|
|
2308
|
+
throw new NotResolvedError("source");
|
|
2309
|
+
}
|
|
2310
|
+
if (this._resolved) {
|
|
2311
|
+
return new _Config(this.root, {
|
|
2312
|
+
resolved: true,
|
|
2313
|
+
parseBaseDir: this._parseBaseDir,
|
|
2314
|
+
originDescription: this._originDescription
|
|
2315
|
+
});
|
|
2316
|
+
}
|
|
2317
|
+
const receiverTree = this._resObjRoot ?? hoconValueToResObj2(this.root);
|
|
2318
|
+
const srcTree = hoconValueToResObj2(source.root);
|
|
2319
|
+
const merged = mergeUnresolved(receiverTree, srcTree);
|
|
2320
|
+
const resolveOpts = {
|
|
2321
|
+
...this._resolveOpts ?? { env: {}, baseDir: void 0, readFileSync: () => {
|
|
2322
|
+
throw new Error("no files");
|
|
2323
|
+
} },
|
|
2324
|
+
allowUnresolved: opts.allowUnresolved ?? false,
|
|
2325
|
+
useSystemEnvironment: opts.useSystemEnvironment ?? true,
|
|
2326
|
+
originDescription: this._originDescription
|
|
2327
|
+
};
|
|
2328
|
+
const resolved = resolveTree(merged, resolveOpts);
|
|
2329
|
+
if (resolved.kind !== "object") throw new Error("resolveWith: expected object root");
|
|
2330
|
+
const receiverShapeForFilter = resObjToKeyShape(receiverTree);
|
|
2331
|
+
const filtered = filterByReceiverShape(resolved, receiverShapeForFilter);
|
|
2332
|
+
if (opts.allowUnresolved) {
|
|
2333
|
+
const { stripped, hadPlaceholders } = stripPlaceholderFields(filtered);
|
|
2334
|
+
return new _Config(stripped, {
|
|
2335
|
+
resolved: !hadPlaceholders,
|
|
2336
|
+
parseBaseDir: this._parseBaseDir,
|
|
2337
|
+
originDescription: this._originDescription,
|
|
2338
|
+
resObjRoot: hadPlaceholders ? receiverTree : void 0,
|
|
2339
|
+
resolveOpts: hadPlaceholders ? resolveOpts : void 0
|
|
2340
|
+
});
|
|
2341
|
+
}
|
|
2342
|
+
return new _Config(filtered, {
|
|
2343
|
+
resolved: true,
|
|
2344
|
+
parseBaseDir: this._parseBaseDir,
|
|
2345
|
+
originDescription: this._originDescription
|
|
2346
|
+
});
|
|
2347
|
+
}
|
|
2348
|
+
toObject() {
|
|
2349
|
+
return hoconToJs(this.root);
|
|
2350
|
+
}
|
|
2351
|
+
/**
|
|
2352
|
+
* @internal Test-only: renders this resolved Config's value tree as canonical
|
|
2353
|
+
* JSON (sorted keys, no whitespace). Used by Layer-2 fixture tests to compare
|
|
2354
|
+
* against Lightbend ground truth. NOT part of the public API.
|
|
2355
|
+
*
|
|
2356
|
+
* Throws if the Config is unresolved (contains placeholders).
|
|
2357
|
+
*/
|
|
2358
|
+
_renderJSONForTest() {
|
|
2359
|
+
return renderHoconAsJSON(this.root);
|
|
2360
|
+
}
|
|
2361
|
+
lookupNode(path2) {
|
|
2362
|
+
const segments = splitConfigPath(path2);
|
|
2363
|
+
let current = this.root;
|
|
2364
|
+
for (const seg of segments) {
|
|
2365
|
+
if (current.kind !== "object") return void 0;
|
|
2366
|
+
const next = current.fields.get(seg);
|
|
2367
|
+
if (next === void 0) return void 0;
|
|
2368
|
+
current = next;
|
|
2369
|
+
}
|
|
2370
|
+
return current;
|
|
2371
|
+
}
|
|
2372
|
+
/**
|
|
2373
|
+
* _resObjRootSubtreeHasPlaceholders — walk _resObjRoot to the given path and
|
|
2374
|
+
* return true if the subtree there contains any unresolved placeholder.
|
|
2375
|
+
* Used by getConfig / getList (T2) to detect transitive unresolved placeholders
|
|
2376
|
+
* in object / array subtrees that would otherwise appear to be "resolved" in the
|
|
2377
|
+
* partial HoconValue root (because individual leaf fields are omitted rather than
|
|
2378
|
+
* the whole subtree when their value is a direct placeholder scalar, but object
|
|
2379
|
+
* sub-containers are retained in the partial root with the placeholder-keyed
|
|
2380
|
+
* fields omitted from them — so the object appears "present but empty-ish").
|
|
2381
|
+
*/
|
|
2382
|
+
_resObjRootSubtreeHasPlaceholders(path2) {
|
|
2383
|
+
const tree = this._resObjRoot;
|
|
2384
|
+
if (!tree) return false;
|
|
2385
|
+
const segments = splitConfigPath(path2);
|
|
2386
|
+
let cur = tree;
|
|
2387
|
+
for (const seg of segments) {
|
|
2388
|
+
const val = cur.fields.get(seg);
|
|
2389
|
+
if (val === void 0) return false;
|
|
2390
|
+
if (isResObj(val)) {
|
|
2391
|
+
cur = val;
|
|
2392
|
+
} else {
|
|
2393
|
+
return valContainsPlaceholders(val);
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
return containsPlaceholders(cur);
|
|
2397
|
+
}
|
|
2398
|
+
requireScalar(path2) {
|
|
2399
|
+
const v = this.lookupNode(path2);
|
|
2400
|
+
if (v === void 0) {
|
|
2401
|
+
if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
|
|
2402
|
+
throw new NotResolvedError(path2);
|
|
2403
|
+
}
|
|
2404
|
+
throw new ConfigError(`path not found: ${path2}`, path2);
|
|
2405
|
+
}
|
|
2406
|
+
if (v.kind !== "scalar") throw new ConfigError(`expected scalar at ${path2}, got ${v.kind}`, path2);
|
|
2407
|
+
return v;
|
|
2408
|
+
}
|
|
2409
|
+
};
|
|
2410
|
+
function splitConfigPath(path2) {
|
|
2411
|
+
const segments = [];
|
|
2412
|
+
let i = 0;
|
|
2413
|
+
while (i < path2.length) {
|
|
2414
|
+
if (path2[i] === '"') {
|
|
2415
|
+
i++;
|
|
2416
|
+
let segment = "";
|
|
2417
|
+
let closed = false;
|
|
2418
|
+
while (i < path2.length) {
|
|
2419
|
+
const ch = path2[i];
|
|
2420
|
+
if (ch === "\\" && i + 1 < path2.length) {
|
|
2421
|
+
const next = path2[i + 1];
|
|
2422
|
+
segment += next;
|
|
2423
|
+
i += 2;
|
|
2424
|
+
continue;
|
|
2425
|
+
}
|
|
2426
|
+
if (ch === '"') {
|
|
2427
|
+
closed = true;
|
|
2428
|
+
i++;
|
|
2429
|
+
break;
|
|
2430
|
+
}
|
|
2431
|
+
segment += ch;
|
|
2432
|
+
i++;
|
|
2433
|
+
}
|
|
2434
|
+
if (!closed) throw new ConfigError(`unterminated quoted path segment: ${path2}`, path2);
|
|
2435
|
+
segments.push(segment);
|
|
2436
|
+
if (i < path2.length && path2[i] === ".") i++;
|
|
2437
|
+
} else {
|
|
2438
|
+
const dot = path2.indexOf(".", i);
|
|
2439
|
+
if (dot === -1) {
|
|
2440
|
+
segments.push(path2.slice(i));
|
|
2441
|
+
break;
|
|
2442
|
+
}
|
|
2443
|
+
segments.push(path2.slice(i, dot));
|
|
2444
|
+
i = dot + 1;
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
return segments;
|
|
2448
|
+
}
|
|
2449
|
+
function scalarToJs(raw, valueType) {
|
|
2450
|
+
switch (valueType) {
|
|
2451
|
+
case "null":
|
|
2452
|
+
return null;
|
|
2453
|
+
case "boolean":
|
|
2454
|
+
return raw === "true";
|
|
2455
|
+
case "number":
|
|
2456
|
+
return Number(raw);
|
|
2457
|
+
case "string":
|
|
2458
|
+
return raw;
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
function hoconToJs(v) {
|
|
2462
|
+
switch (v.kind) {
|
|
2463
|
+
case "scalar":
|
|
2464
|
+
return scalarToJs(v.raw, v.valueType);
|
|
2465
|
+
case "array":
|
|
2466
|
+
return v.items.map(hoconToJs);
|
|
2467
|
+
case "object": {
|
|
2468
|
+
const obj = /* @__PURE__ */ Object.create(null);
|
|
2469
|
+
for (const [k, val] of v.fields) obj[k] = hoconToJs(val);
|
|
2470
|
+
return Object.assign({}, obj);
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
function resObjToKeyShape(tree) {
|
|
2475
|
+
const fields = /* @__PURE__ */ new Map();
|
|
2476
|
+
for (const [k, v] of tree.fields) {
|
|
2477
|
+
if (isSubst(v) || isConcat(v) || isAppend(v)) {
|
|
2478
|
+
fields.set(k, { kind: "scalar", raw: "", valueType: "null" });
|
|
2479
|
+
} else if (isResObj(v)) {
|
|
2480
|
+
fields.set(k, resObjToKeyShape(v));
|
|
2481
|
+
} else {
|
|
2482
|
+
fields.set(k, v);
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
return { kind: "object", fields };
|
|
2486
|
+
}
|
|
2487
|
+
function stripPlaceholderFields(v) {
|
|
2488
|
+
const fields = /* @__PURE__ */ new Map();
|
|
2489
|
+
let hadPlaceholders = false;
|
|
2490
|
+
for (const [k, val] of v.fields) {
|
|
2491
|
+
if (val._kind === "subst-placeholder" || val._kind === "concat-placeholder" || val._kind === "append-placeholder") {
|
|
2492
|
+
hadPlaceholders = true;
|
|
2493
|
+
} else if (val.kind === "object") {
|
|
2494
|
+
const inner = stripPlaceholderFields(val);
|
|
2495
|
+
if (inner.hadPlaceholders) hadPlaceholders = true;
|
|
2496
|
+
fields.set(k, inner.stripped);
|
|
2497
|
+
} else {
|
|
2498
|
+
fields.set(k, val);
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
return { stripped: { kind: "object", fields }, hadPlaceholders };
|
|
2502
|
+
}
|
|
2503
|
+
function filterByReceiverShape(resolved, receiverShape) {
|
|
2504
|
+
const fields = /* @__PURE__ */ new Map();
|
|
2505
|
+
for (const [k, rv] of resolved.fields) {
|
|
2506
|
+
if (!receiverShape.fields.has(k)) continue;
|
|
2507
|
+
const receiverVal = receiverShape.fields.get(k);
|
|
2508
|
+
if (rv.kind === "object" && receiverVal.kind === "object") {
|
|
2509
|
+
fields.set(k, filterByReceiverShape(rv, receiverVal));
|
|
2510
|
+
} else {
|
|
2511
|
+
fields.set(k, rv);
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
return { kind: "object", fields };
|
|
2515
|
+
}
|
|
2516
|
+
function renderHoconAsJSON(v) {
|
|
2517
|
+
switch (v.kind) {
|
|
2518
|
+
case "scalar": {
|
|
2519
|
+
switch (v.valueType) {
|
|
2520
|
+
case "null":
|
|
2521
|
+
return "null";
|
|
2522
|
+
case "boolean":
|
|
2523
|
+
return v.raw;
|
|
2524
|
+
case "number": {
|
|
2525
|
+
const n = Number(v.raw);
|
|
2526
|
+
if (Number.isFinite(n)) return v.raw;
|
|
2527
|
+
return JSON.stringify(v.raw);
|
|
2528
|
+
}
|
|
2529
|
+
case "string":
|
|
2530
|
+
return JSON.stringify(v.raw);
|
|
2531
|
+
}
|
|
2532
|
+
break;
|
|
2533
|
+
}
|
|
2534
|
+
case "array": {
|
|
2535
|
+
const items = v.items.map(renderHoconAsJSON);
|
|
2536
|
+
return `[${items.join(",")}]`;
|
|
2537
|
+
}
|
|
2538
|
+
case "object": {
|
|
2539
|
+
const keys = [...v.fields.keys()].sort();
|
|
2540
|
+
const pairs = keys.map((k) => {
|
|
2541
|
+
const val = v.fields.get(k);
|
|
2542
|
+
return `${JSON.stringify(k)}:${renderHoconAsJSON(val)}`;
|
|
2543
|
+
});
|
|
2544
|
+
return `{${pairs.join(",")}}`;
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
throw new Error(`renderHoconAsJSON: unsupported value kind (unresolved placeholder?)`);
|
|
2548
|
+
}
|
|
2549
|
+
|
|
2550
|
+
// src/parse.ts
|
|
2551
|
+
import * as fs from "fs";
|
|
2552
|
+
import * as path from "path";
|
|
2553
|
+
function defaultParseOptions() {
|
|
2554
|
+
return { resolveSubstitutions: true };
|
|
2555
|
+
}
|
|
2556
|
+
function defaultResolveOptions() {
|
|
2557
|
+
return { useSystemEnvironment: true, allowUnresolved: false };
|
|
2558
|
+
}
|
|
2559
|
+
function getEnv(opts) {
|
|
2560
|
+
if (opts.env !== void 0) return opts.env;
|
|
2561
|
+
if (typeof process !== "undefined" && process.env) return process.env;
|
|
2562
|
+
return {};
|
|
2563
|
+
}
|
|
2564
|
+
function defaultReadFileSync(filePath) {
|
|
1936
2565
|
return fs.readFileSync(filePath, "utf-8");
|
|
1937
2566
|
}
|
|
1938
2567
|
async function defaultReadFile(filePath) {
|
|
@@ -1946,7 +2575,10 @@ function buildResolveContext(input, opts) {
|
|
|
1946
2575
|
env: getEnv(opts),
|
|
1947
2576
|
baseDir: opts.baseDir,
|
|
1948
2577
|
readFileSync: opts.readFileSync ?? defaultReadFileSync,
|
|
1949
|
-
readFile: opts.readFile
|
|
2578
|
+
readFile: opts.readFile,
|
|
2579
|
+
originDescription: opts.originDescription,
|
|
2580
|
+
resolveFrom: opts.resolveFrom,
|
|
2581
|
+
packageResolver: opts.packageResolver
|
|
1950
2582
|
};
|
|
1951
2583
|
return { ast, resolveOpts };
|
|
1952
2584
|
}
|
|
@@ -1954,7 +2586,38 @@ function parse(input, opts = {}) {
|
|
|
1954
2586
|
const { ast, resolveOpts } = buildResolveContext(input, opts);
|
|
1955
2587
|
const value = resolve2(ast, resolveOpts);
|
|
1956
2588
|
if (value.kind !== "object") throw new Error("resolved value is not an object");
|
|
1957
|
-
return new Config(value);
|
|
2589
|
+
return new Config(value, { resolved: true, originDescription: opts.originDescription });
|
|
2590
|
+
}
|
|
2591
|
+
var parseString = parse;
|
|
2592
|
+
function parseStringWithOptions(input, opts = {}) {
|
|
2593
|
+
const resolveSubstitutions = opts.resolveSubstitutions ?? true;
|
|
2594
|
+
const { ast, resolveOpts } = buildResolveContext(input, opts);
|
|
2595
|
+
if (resolveSubstitutions) {
|
|
2596
|
+
const value = resolve2(ast, resolveOpts);
|
|
2597
|
+
if (value.kind !== "object") throw new Error("resolved value is not an object");
|
|
2598
|
+
return new Config(value, { resolved: true, originDescription: opts.originDescription });
|
|
2599
|
+
}
|
|
2600
|
+
const tree = buildTree(ast, resolveOpts);
|
|
2601
|
+
const hasPlaceholders = containsPlaceholders(tree);
|
|
2602
|
+
return Config._fromUnresolvedResObj(tree, {
|
|
2603
|
+
parseBaseDir: opts.baseDir,
|
|
2604
|
+
originDescription: opts.originDescription,
|
|
2605
|
+
resolved: !hasPlaceholders,
|
|
2606
|
+
resolveOpts
|
|
2607
|
+
});
|
|
2608
|
+
}
|
|
2609
|
+
function parseFileWithOptions(filePath, opts = {}) {
|
|
2610
|
+
if (typeof process === "undefined" && !opts.readFileSync) {
|
|
2611
|
+
throw new Error("parseFileWithOptions is not supported in browser environments without opts.readFileSync.");
|
|
2612
|
+
}
|
|
2613
|
+
const resolvedPath = path.resolve(filePath);
|
|
2614
|
+
const readFileSync2 = opts.readFileSync ?? defaultReadFileSync;
|
|
2615
|
+
const input = readFileSync2(resolvedPath);
|
|
2616
|
+
return parseStringWithOptions(input, {
|
|
2617
|
+
...opts,
|
|
2618
|
+
baseDir: opts.baseDir ?? path.dirname(resolvedPath),
|
|
2619
|
+
readFileSync: readFileSync2
|
|
2620
|
+
});
|
|
1958
2621
|
}
|
|
1959
2622
|
async function parseAsync(input, opts = {}) {
|
|
1960
2623
|
const { ast, resolveOpts } = buildResolveContext(input, opts);
|
|
@@ -1989,13 +2652,89 @@ async function parseFileAsync(filePath, opts = {}) {
|
|
|
1989
2652
|
readFileSync: opts.readFileSync ?? defaultReadFileSync
|
|
1990
2653
|
});
|
|
1991
2654
|
}
|
|
2655
|
+
|
|
2656
|
+
// src/value-factory.ts
|
|
2657
|
+
function fromMap(values, originDescription) {
|
|
2658
|
+
const fields = coerceObject(values, "");
|
|
2659
|
+
const root = { kind: "object", fields };
|
|
2660
|
+
return Config._fromResolvedValue(root, { originDescription });
|
|
2661
|
+
}
|
|
2662
|
+
function empty(originDescription) {
|
|
2663
|
+
const root = { kind: "object", fields: /* @__PURE__ */ new Map() };
|
|
2664
|
+
return Config._fromResolvedValue(root, { originDescription });
|
|
2665
|
+
}
|
|
2666
|
+
function coerceObject(obj, atPath) {
|
|
2667
|
+
const fields = /* @__PURE__ */ new Map();
|
|
2668
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
2669
|
+
const childPath = atPath ? `${atPath}.${k}` : k;
|
|
2670
|
+
fields.set(k, coerceValue(v, childPath));
|
|
2671
|
+
}
|
|
2672
|
+
return fields;
|
|
2673
|
+
}
|
|
2674
|
+
function coerceValue(v, atPath) {
|
|
2675
|
+
if (v === null) {
|
|
2676
|
+
return { kind: "scalar", raw: "null", valueType: "null" };
|
|
2677
|
+
}
|
|
2678
|
+
if (v === void 0) {
|
|
2679
|
+
throw new ConfigError(
|
|
2680
|
+
`fromMap: undefined is not a valid HOCON value at path "${atPath}"`,
|
|
2681
|
+
atPath
|
|
2682
|
+
);
|
|
2683
|
+
}
|
|
2684
|
+
switch (typeof v) {
|
|
2685
|
+
case "string":
|
|
2686
|
+
return { kind: "scalar", raw: v, valueType: "string" };
|
|
2687
|
+
case "number": {
|
|
2688
|
+
if (Number.isNaN(v) || !Number.isFinite(v)) {
|
|
2689
|
+
throw new ConfigError(
|
|
2690
|
+
`fromMap: ${v} is not representable in HOCON (NaN/Infinity not allowed) at path "${atPath}"`,
|
|
2691
|
+
atPath
|
|
2692
|
+
);
|
|
2693
|
+
}
|
|
2694
|
+
return { kind: "scalar", raw: String(v), valueType: "number" };
|
|
2695
|
+
}
|
|
2696
|
+
case "boolean":
|
|
2697
|
+
return { kind: "scalar", raw: v ? "true" : "false", valueType: "boolean" };
|
|
2698
|
+
case "bigint": {
|
|
2699
|
+
if (v > BigInt(Number.MAX_SAFE_INTEGER) || v < BigInt(Number.MIN_SAFE_INTEGER)) {
|
|
2700
|
+
throw new ConfigError(
|
|
2701
|
+
`fromMap: bigint ${v} exceeds Number.MAX_SAFE_INTEGER (not representable as HOCON number) at path "${atPath}"`,
|
|
2702
|
+
atPath
|
|
2703
|
+
);
|
|
2704
|
+
}
|
|
2705
|
+
return { kind: "scalar", raw: String(v), valueType: "number" };
|
|
2706
|
+
}
|
|
2707
|
+
case "object": {
|
|
2708
|
+
if (Array.isArray(v)) {
|
|
2709
|
+
const items = v.map((item, i) => coerceValue(item, `${atPath}[${i}]`));
|
|
2710
|
+
return { kind: "array", items };
|
|
2711
|
+
}
|
|
2712
|
+
const fields = coerceObject(v, atPath);
|
|
2713
|
+
return { kind: "object", fields };
|
|
2714
|
+
}
|
|
2715
|
+
default:
|
|
2716
|
+
throw new ConfigError(
|
|
2717
|
+
`fromMap: unsupported value type "${typeof v}" at path "${atPath}"`,
|
|
2718
|
+
atPath
|
|
2719
|
+
);
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
1992
2722
|
export {
|
|
1993
2723
|
Config,
|
|
1994
2724
|
ConfigError,
|
|
2725
|
+
NotResolvedError,
|
|
2726
|
+
PackageLookupError,
|
|
1995
2727
|
ParseError,
|
|
1996
2728
|
ResolveError,
|
|
2729
|
+
defaultParseOptions,
|
|
2730
|
+
defaultResolveOptions,
|
|
2731
|
+
empty,
|
|
2732
|
+
fromMap,
|
|
1997
2733
|
parse,
|
|
1998
2734
|
parseAsync,
|
|
1999
2735
|
parseFile,
|
|
2000
|
-
parseFileAsync
|
|
2736
|
+
parseFileAsync,
|
|
2737
|
+
parseFileWithOptions,
|
|
2738
|
+
parseString,
|
|
2739
|
+
parseStringWithOptions
|
|
2001
2740
|
};
|