@o3co/ts.hocon 1.3.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/dist/zod.js CHANGED
@@ -195,6 +195,10 @@ function parseBytes(value, outputUnit = "B") {
195
195
  return outputUnit === "B" ? Math.round(result) : result;
196
196
  }
197
197
 
198
+ // src/parse.ts
199
+ import * as fs from "fs";
200
+ import * as path from "path";
201
+
198
202
  // src/errors.ts
199
203
  var ParseError = class extends Error {
200
204
  constructor(message, line, col, file) {
@@ -215,6 +219,14 @@ var ResolveError = class extends Error {
215
219
  this.name = "ResolveError";
216
220
  }
217
221
  };
222
+ var PackageLookupError = class extends ResolveError {
223
+ constructor(message, identifier, packageFile, line, col) {
224
+ super(message, `${identifier}/${packageFile}`, line, col);
225
+ this.identifier = identifier;
226
+ this.packageFile = packageFile;
227
+ this.name = "PackageLookupError";
228
+ }
229
+ };
218
230
  var ConfigError = class extends Error {
219
231
  constructor(message, path2) {
220
232
  super(message);
@@ -222,6 +234,156 @@ var ConfigError = class extends Error {
222
234
  this.name = "ConfigError";
223
235
  }
224
236
  };
237
+ var NotResolvedError = class extends ConfigError {
238
+ constructor(path2) {
239
+ super(`value at path "${path2}" is not resolved (call resolve() first)`, path2);
240
+ this.name = "NotResolvedError";
241
+ }
242
+ };
243
+
244
+ // src/internal/resolver/types.ts
245
+ var separatorValues = /* @__PURE__ */ new WeakSet();
246
+ function isSubst(v) {
247
+ return v._kind === "subst-placeholder";
248
+ }
249
+ function isConcat(v) {
250
+ return v._kind === "concat-placeholder";
251
+ }
252
+ function isAppend(v) {
253
+ return v._kind === "append-placeholder";
254
+ }
255
+ function isResObj(v) {
256
+ return v._kind === "res-obj";
257
+ }
258
+ function makeResObj() {
259
+ return { _kind: "res-obj", fields: /* @__PURE__ */ new Map(), priorValues: /* @__PURE__ */ new Map() };
260
+ }
261
+ function mergeUnresolved(receiver, fallback) {
262
+ const result = makeResObj();
263
+ for (const [k, v] of fallback.fields) {
264
+ result.fields.set(k, v);
265
+ }
266
+ for (const [k, v] of fallback.priorValues) {
267
+ result.priorValues.set(k, v);
268
+ }
269
+ for (const [k, rv] of receiver.fields) {
270
+ const existing = result.fields.get(k);
271
+ if (existing !== void 0) {
272
+ const recPrior = receiver.priorValues.get(k);
273
+ if (recPrior !== void 0 && !isResObj(recPrior)) {
274
+ result.fields.set(k, rv);
275
+ continue;
276
+ }
277
+ if (isResObj(rv) && isResObj(existing)) {
278
+ result.fields.set(k, mergeUnresolved(rv, existing));
279
+ continue;
280
+ }
281
+ result.priorValues.set(k, existing);
282
+ }
283
+ result.fields.set(k, rv);
284
+ }
285
+ for (const [k, v] of receiver.priorValues) {
286
+ if (receiver.fields.has(k)) {
287
+ result.priorValues.set(k, v);
288
+ }
289
+ }
290
+ return result;
291
+ }
292
+
293
+ // src/internal/resolver/utils.ts
294
+ function segmentsToKey(segments) {
295
+ return segments.map((s) => {
296
+ const t = s.text;
297
+ if (t === "" || /[^a-zA-Z0-9\-_]/.test(t)) {
298
+ return `"${t.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
299
+ }
300
+ return t;
301
+ }).join(".");
302
+ }
303
+ function lookupPath(root, segments) {
304
+ const [head, ...tail] = segments;
305
+ if (head === void 0) return void 0;
306
+ const key = head.text;
307
+ const val = root.fields.get(key);
308
+ if (val === void 0) return void 0;
309
+ if (tail.length === 0) return val;
310
+ if (isResObj(val)) return lookupPath(val, tail);
311
+ return void 0;
312
+ }
313
+ function lookupResObj(root, segments) {
314
+ let cur = root;
315
+ for (const seg of segments) {
316
+ const val = cur.fields.get(seg.text);
317
+ if (val === void 0 || !isResObj(val)) return void 0;
318
+ cur = val;
319
+ }
320
+ return cur;
321
+ }
322
+ function deepMergeHoconValues(base, overlay) {
323
+ const merged = new Map(base.fields);
324
+ for (const [k, v] of overlay.fields) {
325
+ const existing = merged.get(k);
326
+ if (existing?.kind === "object" && v.kind === "object") {
327
+ merged.set(k, deepMergeHoconValues(existing, v));
328
+ } else {
329
+ merged.set(k, v);
330
+ }
331
+ }
332
+ return { kind: "object", fields: merged };
333
+ }
334
+ function deepMergeResObjInto(dst, src) {
335
+ for (const [k, srcVal] of src.fields) {
336
+ const dstVal = dst.fields.get(k);
337
+ if (dstVal !== void 0 && isResObj(dstVal) && isResObj(srcVal)) {
338
+ deepMergeResObjInto(dstVal, srcVal);
339
+ } else {
340
+ if (dstVal !== void 0) dst.priorValues.set(k, dstVal);
341
+ dst.fields.set(k, srcVal);
342
+ }
343
+ }
344
+ for (const [k, srcPrior] of src.priorValues) {
345
+ if (!dst.priorValues.has(k)) {
346
+ dst.priorValues.set(k, srcPrior);
347
+ }
348
+ }
349
+ }
350
+ function hoconValueToResObj(hv) {
351
+ const obj = makeResObj();
352
+ if (hv.kind !== "object") return obj;
353
+ for (const [key, val] of hv.fields) {
354
+ if (val.kind === "object") {
355
+ obj.fields.set(key, hoconValueToResObj(val));
356
+ } else {
357
+ obj.fields.set(key, val);
358
+ }
359
+ }
360
+ return obj;
361
+ }
362
+ function isFileNotFoundError(e) {
363
+ if (!(e instanceof Error)) return false;
364
+ const code = e.code;
365
+ if (code === "ENOENT" || code === "MODULE_NOT_FOUND") return true;
366
+ const msg = e.message.toLowerCase();
367
+ return msg.includes("not found") || msg.includes("no such file") || msg.includes("enoent");
368
+ }
369
+
370
+ // src/internal/resolver/include-loader.ts
371
+ import { createRequire } from "module";
372
+ import * as nodePath from "path";
373
+
374
+ // src/internal/parser/empty-check.ts
375
+ function hasContentTokens(tokens) {
376
+ return tokens.some((t) => t.kind !== "eof" && t.kind !== "newline");
377
+ }
378
+ function assertNonEmptyDocument(tokens, sourceDescriptor) {
379
+ if (!hasContentTokens(tokens)) {
380
+ throw new ParseError(
381
+ `empty file is not a valid HOCON document (HOCON.md L130): ${sourceDescriptor}`,
382
+ 1,
383
+ 1
384
+ );
385
+ }
386
+ }
225
387
 
226
388
  // src/internal/lexer/lexer.ts
227
389
  function isHoconWhitespace(ch) {
@@ -786,58 +948,137 @@ var Parser = class {
786
948
  const p = this.currentPos();
787
949
  this.skip("newline");
788
950
  const t = this.peek();
789
- let path2;
790
- let required = false;
791
- let isFile = false;
792
951
  if (t.kind === "unquoted" && (t.value === "required(" || t.value === "required" || t.value.startsWith("required("))) {
793
952
  if (t.value === "required") {
794
- const next2 = this.peek(1);
795
- if (next2.kind !== "unquoted" || !next2.value.startsWith("(")) {
953
+ const next = this.peek(1);
954
+ if (next.kind !== "unquoted" || !next.value.startsWith("(")) {
796
955
  throw new ParseError("include required must be followed by (", t.line, t.col);
797
956
  }
798
957
  }
799
- required = true;
800
958
  this.advance();
801
959
  const innerPrefix = t.value.startsWith("required(") ? t.value.slice("required(".length) : "";
802
- if (innerPrefix.startsWith("url") || innerPrefix.startsWith("classpath")) {
960
+ if (innerPrefix === "url" || innerPrefix.startsWith("url(") || innerPrefix === "classpath" || innerPrefix.startsWith("classpath(")) {
803
961
  throw new ParseError("include url(...) and classpath(...) are not supported", t.line, t.col);
804
962
  }
805
- if (innerPrefix.startsWith("file")) {
806
- isFile = true;
963
+ const nextTok = this.peek();
964
+ if (innerPrefix === "package" || innerPrefix.startsWith("package(") || innerPrefix === "" && nextTok.kind === "unquoted" && (nextTok.value === "package(" || nextTok.value.startsWith("package("))) {
965
+ const consumePackageToken = innerPrefix === "";
966
+ const { qualifier, path: path3 } = this.parsePackageArgs(consumePackageToken);
967
+ return this.makeIncludeField(qualifier, path3, true, p);
807
968
  }
808
- const next = this.peek();
809
- if (next.kind === "unquoted" && (next.value === "url" || next.value.startsWith("url(") || next.value === "classpath" || next.value.startsWith("classpath("))) {
810
- throw new ParseError("include url(...) and classpath(...) are not supported", next.line, next.col);
811
- }
812
- if (next.kind === "unquoted" && (next.value === "file(" || next.value === "file" || next.value.startsWith("file("))) {
813
- isFile = true;
814
- }
815
- while (this.peek().kind !== "string" && this.peek().kind !== "eof") this.advance();
816
- if (this.peek().kind === "eof") throw new ParseError("expected include path", t.line, t.col);
817
- path2 = this.advance().value;
818
- while (this.peek().kind !== "newline" && this.peek().kind !== "rbrace" && this.peek().kind !== "eof" && this.peek().kind !== "comma") this.advance();
819
- } else if (t.kind === "string") {
820
- path2 = this.advance().value;
821
- } else if (t.kind === "unquoted" && (t.value === "file(" || t.value === "file")) {
822
- isFile = true;
969
+ if (innerPrefix === "file" || innerPrefix.startsWith("file(") || innerPrefix === "" && nextTok.kind === "unquoted" && (nextTok.value === "file(" || nextTok.value === "file")) {
970
+ if (innerPrefix === "") {
971
+ this.advance();
972
+ }
973
+ const path3 = this.parseQuotedPathSkipWrapper(t);
974
+ return this.makeIncludeField({ kind: "file" }, path3, true, p);
975
+ }
976
+ if (innerPrefix === "" && nextTok.kind === "unquoted" && (nextTok.value === "url" || nextTok.value.startsWith("url(") || nextTok.value === "classpath" || nextTok.value.startsWith("classpath("))) {
977
+ throw new ParseError("include url(...) and classpath(...) are not supported", nextTok.line, nextTok.col);
978
+ }
979
+ if (innerPrefix !== "") {
980
+ if (innerPrefix === ")") {
981
+ throw new ParseError("include required(...) must contain a path", t.line, t.col);
982
+ }
983
+ const name = innerPrefix.replace(/\(.*$/, "").replace(/\).*$/, "");
984
+ throw new ParseError(`unknown include qualifier inside required(): "${name}"`, t.line, t.col);
985
+ }
986
+ const path2 = this.parseQuotedPathSkipWrapper(t);
987
+ return this.makeIncludeField({ kind: "bare" }, path2, true, p);
988
+ }
989
+ if (t.kind === "string") {
990
+ const path2 = this.advance().value;
991
+ return this.makeIncludeField({ kind: "bare" }, path2, false, p);
992
+ }
993
+ if (t.kind === "unquoted" && (t.value === "file(" || t.value === "file")) {
823
994
  this.advance();
824
- while (this.peek().kind !== "string" && this.peek().kind !== "eof") this.advance();
825
- if (this.peek().kind === "eof") throw new ParseError("expected include path", t.line, t.col);
826
- path2 = this.advance().value;
827
- while (this.peek().kind !== "newline" && this.peek().kind !== "rbrace" && this.peek().kind !== "eof" && this.peek().kind !== "comma") this.advance();
828
- } else if (t.kind === "unquoted" && (t.value === "url" || t.value.startsWith("url("))) {
995
+ const path2 = this.parseQuotedPathSkipWrapper(t);
996
+ return this.makeIncludeField({ kind: "file" }, path2, false, p);
997
+ }
998
+ if (t.kind === "unquoted" && (t.value === "package(" || t.value.startsWith("package("))) {
999
+ const { qualifier, path: path2 } = this.parsePackageArgs();
1000
+ return this.makeIncludeField(qualifier, path2, false, p);
1001
+ }
1002
+ if (t.kind === "unquoted" && (t.value === "url" || t.value.startsWith("url("))) {
829
1003
  throw new ParseError("include url(...) is not supported", t.line, t.col);
830
- } else if (t.kind === "unquoted" && (t.value === "classpath" || t.value.startsWith("classpath("))) {
1004
+ }
1005
+ if (t.kind === "unquoted" && (t.value === "classpath" || t.value.startsWith("classpath("))) {
831
1006
  throw new ParseError("include classpath(...) is not supported", t.line, t.col);
832
- } else {
833
- throw new ParseError(`expected include path, got ${t.kind}`, t.line, t.col);
834
1007
  }
835
- return {
836
- key: [],
837
- value: { kind: "include", path: path2, required, ...isFile ? { isFile: true } : {}, pos: p },
838
- append: false,
839
- pos: p
840
- };
1008
+ throw new ParseError(`expected include path, got ${t.kind}`, t.line, t.col);
1009
+ }
1010
+ makeIncludeField(qualifier, path2, required, pos) {
1011
+ return { key: [], value: { kind: "include", qualifier, path: path2, required, pos }, append: false, pos };
1012
+ }
1013
+ /**
1014
+ * Skip past include-syntax wrapper tokens to find the quoted path string.
1015
+ * Strict: only allows `(`, `)`, and bare qualifier keywords (file / file( /
1016
+ * package / package( / url / url( / classpath / classpath() between the
1017
+ * directive and the quoted path. Anything else is a parse error rather than
1018
+ * silently swallowed (see ts#113).
1019
+ */
1020
+ parseQuotedPathSkipWrapper(errTok) {
1021
+ while (this.isIncludeWrapperToken(this.peek())) this.advance();
1022
+ if (this.peek().kind !== "string") {
1023
+ const tok = this.peek();
1024
+ if (tok.kind === "eof") throw new ParseError("expected include path", errTok.line, errTok.col);
1025
+ const desc = tok.kind === "unquoted" ? `unquoted "${tok.value}"` : tok.kind;
1026
+ throw new ParseError(`expected quoted include path, got ${desc}`, tok.line, tok.col);
1027
+ }
1028
+ const path2 = this.advance().value;
1029
+ while (this.peek().kind === "unquoted" && (this.peek().value === ")" || this.peek().value === "))")) {
1030
+ this.advance();
1031
+ }
1032
+ const after = this.peek();
1033
+ if (after.kind !== "newline" && after.kind !== "rbrace" && after.kind !== "eof" && after.kind !== "comma") {
1034
+ const desc = after.kind === "unquoted" ? `unquoted "${after.value}"` : after.kind;
1035
+ throw new ParseError(`unexpected token after include path: ${desc}`, after.line, after.col);
1036
+ }
1037
+ return path2;
1038
+ }
1039
+ isIncludeWrapperToken(tok) {
1040
+ return tok.kind === "unquoted" && tok.value === "(";
1041
+ }
1042
+ /**
1043
+ * Parse the body of `package("identifier", "file")`.
1044
+ *
1045
+ * @param consumePackageToken - When true (default), the current token is `package(` and
1046
+ * must be consumed first. When false, the lexer already bundled `package(` into the
1047
+ * preceding token (e.g. `required(package("`), so we are already positioned at the
1048
+ * first string argument.
1049
+ */
1050
+ parsePackageArgs(consumePackageToken = true) {
1051
+ const pkgTok = consumePackageToken ? this.advance() : this.peek();
1052
+ this.skip("newline");
1053
+ const idTok = this.peek();
1054
+ if (idTok.kind !== "string") {
1055
+ throw new ParseError("include package: expected quoted identifier as first argument", pkgTok.line, pkgTok.col);
1056
+ }
1057
+ const identifier = this.advance().value;
1058
+ this.skip("newline");
1059
+ if (this.peek().kind !== "comma") {
1060
+ const after = this.peek();
1061
+ throw new ParseError(
1062
+ 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",
1063
+ pkgTok.line,
1064
+ pkgTok.col
1065
+ );
1066
+ }
1067
+ this.advance();
1068
+ this.skip("newline");
1069
+ const fileTok = this.peek();
1070
+ if (fileTok.kind !== "string") {
1071
+ throw new ParseError(
1072
+ "include package: requires exactly two arguments (identifier, file) \u2014 one-arg form is not supported",
1073
+ pkgTok.line,
1074
+ pkgTok.col
1075
+ );
1076
+ }
1077
+ const filePath = this.advance().value;
1078
+ while (this.peek().kind !== "newline" && this.peek().kind !== "rbrace" && this.peek().kind !== "eof" && this.peek().kind !== "comma") {
1079
+ this.advance();
1080
+ }
1081
+ return { qualifier: { kind: "package", identifier }, path: filePath };
841
1082
  }
842
1083
  parseValue() {
843
1084
  const p = this.currentPos();
@@ -878,7 +1119,9 @@ var Parser = class {
878
1119
  parts.push(node);
879
1120
  }
880
1121
  if (parts.length === 0) throw new ParseError("expected value", this.peek().line, this.peek().col);
881
- if (parts.length === 1) return parts[0];
1122
+ if (parts.length === 1) {
1123
+ return parts[0];
1124
+ }
882
1125
  return { kind: "concat", nodes: parts, pos: p };
883
1126
  }
884
1127
  parseArray() {
@@ -910,118 +1153,6 @@ function parseTokens(tokens) {
910
1153
  return new Parser(tokens).parse();
911
1154
  }
912
1155
 
913
- // src/internal/parser/empty-check.ts
914
- function hasContentTokens(tokens) {
915
- return tokens.some((t) => t.kind !== "eof" && t.kind !== "newline");
916
- }
917
- function assertNonEmptyDocument(tokens, sourceDescriptor) {
918
- if (!hasContentTokens(tokens)) {
919
- throw new ParseError(
920
- `empty file is not a valid HOCON document (HOCON.md L130): ${sourceDescriptor}`,
921
- 1,
922
- 1
923
- );
924
- }
925
- }
926
-
927
- // src/internal/resolver/types.ts
928
- var separatorValues = /* @__PURE__ */ new WeakSet();
929
- function isSubst(v) {
930
- return v._kind === "subst-placeholder";
931
- }
932
- function isConcat(v) {
933
- return v._kind === "concat-placeholder";
934
- }
935
- function isAppend(v) {
936
- return v._kind === "append-placeholder";
937
- }
938
- function isResObj(v) {
939
- return v._kind === "res-obj";
940
- }
941
- function makeResObj() {
942
- return { _kind: "res-obj", fields: /* @__PURE__ */ new Map(), priorValues: /* @__PURE__ */ new Map() };
943
- }
944
-
945
- // src/internal/resolver/utils.ts
946
- function segmentsToKey(segments) {
947
- return segments.map((s) => {
948
- const t = s.text;
949
- if (t === "" || /[^a-zA-Z0-9\-_]/.test(t)) {
950
- return `"${t.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
951
- }
952
- return t;
953
- }).join(".");
954
- }
955
- function lookupPath(root, segments) {
956
- const [head, ...tail] = segments;
957
- if (head === void 0) return void 0;
958
- const key = head.text;
959
- const val = root.fields.get(key);
960
- if (val === void 0) return void 0;
961
- if (tail.length === 0) return val;
962
- if (isResObj(val)) return lookupPath(val, tail);
963
- return void 0;
964
- }
965
- function lookupResObj(root, segments) {
966
- let cur = root;
967
- for (const seg of segments) {
968
- const val = cur.fields.get(seg.text);
969
- if (val === void 0 || !isResObj(val)) return void 0;
970
- cur = val;
971
- }
972
- return cur;
973
- }
974
- function deepMergeHoconValues(base, overlay) {
975
- const merged = new Map(base.fields);
976
- for (const [k, v] of overlay.fields) {
977
- const existing = merged.get(k);
978
- if (existing?.kind === "object" && v.kind === "object") {
979
- merged.set(k, deepMergeHoconValues(existing, v));
980
- } else {
981
- merged.set(k, v);
982
- }
983
- }
984
- return { kind: "object", fields: merged };
985
- }
986
- function deepMergeResObjInto(dst, src) {
987
- for (const [k, srcVal] of src.fields) {
988
- const dstVal = dst.fields.get(k);
989
- if (dstVal !== void 0 && isResObj(dstVal) && isResObj(srcVal)) {
990
- deepMergeResObjInto(dstVal, srcVal);
991
- } else {
992
- if (dstVal !== void 0) dst.priorValues.set(k, dstVal);
993
- dst.fields.set(k, srcVal);
994
- }
995
- }
996
- for (const [k, srcPrior] of src.priorValues) {
997
- if (!dst.priorValues.has(k)) {
998
- dst.priorValues.set(k, srcPrior);
999
- }
1000
- }
1001
- }
1002
- function hoconValueToResObj(hv) {
1003
- const obj = makeResObj();
1004
- if (hv.kind !== "object") return obj;
1005
- for (const [key, val] of hv.fields) {
1006
- if (val.kind === "object") {
1007
- obj.fields.set(key, hoconValueToResObj(val));
1008
- } else {
1009
- obj.fields.set(key, val);
1010
- }
1011
- }
1012
- return obj;
1013
- }
1014
- function isFileNotFoundError(e) {
1015
- if (!(e instanceof Error)) return false;
1016
- const code = e.code;
1017
- if (code === "ENOENT" || code === "MODULE_NOT_FOUND") return true;
1018
- const msg = e.message.toLowerCase();
1019
- return msg.includes("not found") || msg.includes("no such file") || msg.includes("enoent");
1020
- }
1021
-
1022
- // src/internal/resolver/include-loader.ts
1023
- import * as nodePath from "path";
1024
-
1025
1156
  // src/internal/properties/properties.ts
1026
1157
  function parseProperties(input) {
1027
1158
  const root = /* @__PURE__ */ Object.create(null);
@@ -1081,6 +1212,84 @@ function recordToHoconValue(obj) {
1081
1212
  }
1082
1213
 
1083
1214
  // src/internal/resolver/include-loader.ts
1215
+ var _require = createRequire(import.meta.url);
1216
+ function isPnpPath(p) {
1217
+ return p.includes(".yarn/cache") || p.includes(".pnp.cjs") || p.includes(".pnp.loader");
1218
+ }
1219
+ function validatePackageFile(file, identifier) {
1220
+ if (file.length === 0) {
1221
+ throw new ParseError(`include package("${identifier}", ...): file argument must be non-empty`, 0, 0);
1222
+ }
1223
+ if (file.includes("\\")) {
1224
+ throw new ParseError(
1225
+ `include package("${identifier}", "${file}"): backslash not allowed in file argument (use forward slash)`,
1226
+ 0,
1227
+ 0
1228
+ );
1229
+ }
1230
+ if (file.startsWith("/")) {
1231
+ throw new ParseError(
1232
+ `include package("${identifier}", "${file}"): absolute path not allowed in file argument`,
1233
+ 0,
1234
+ 0
1235
+ );
1236
+ }
1237
+ if (/(?:^|\/)\.\.?(?:\/|$)/u.test(file)) {
1238
+ throw new ParseError(
1239
+ `include package("${identifier}", "${file}"): path traversal (. or ..) not allowed in file argument`,
1240
+ 0,
1241
+ 0
1242
+ );
1243
+ }
1244
+ if (file.includes("//")) {
1245
+ throw new ParseError(
1246
+ `include package("${identifier}", "${file}"): consecutive slashes not allowed in file argument`,
1247
+ 0,
1248
+ 0
1249
+ );
1250
+ }
1251
+ }
1252
+ function validatePackageIdentifier(identifier) {
1253
+ if (identifier.length === 0) {
1254
+ throw new ParseError("include package: identifier argument must be non-empty", 0, 0);
1255
+ }
1256
+ }
1257
+ function defaultPackageResolver(identifier, file, includingFile, baseDir, resolveFrom) {
1258
+ const from = resolveFrom ? Array.isArray(resolveFrom) ? resolveFrom : [resolveFrom] : baseDir ? [baseDir] : includingFile ? [nodePath.dirname(includingFile)] : [process.cwd()];
1259
+ let resolved;
1260
+ try {
1261
+ resolved = _require.resolve(`${identifier}/${file}`, { paths: from });
1262
+ } catch {
1263
+ throw new PackageLookupError(
1264
+ `include package("${identifier}", "${file}"): module not found (starting from: ${from.join(", ")})`,
1265
+ identifier,
1266
+ file,
1267
+ 0,
1268
+ 0
1269
+ );
1270
+ }
1271
+ if (isPnpPath(resolved)) {
1272
+ throw new ResolveError(
1273
+ `include package("${identifier}", "${file}"): Yarn Berry PnP store paths are not supported. Provide a custom packageResolver option to handle PnP resolution.`,
1274
+ `${identifier}/${file}`,
1275
+ 0,
1276
+ 0
1277
+ );
1278
+ }
1279
+ const fileParts = file.split("/");
1280
+ const expectedBasename = fileParts[fileParts.length - 1] ?? file;
1281
+ const resolvedBasename = nodePath.basename(resolved);
1282
+ if (resolvedBasename !== expectedBasename) {
1283
+ throw new PackageLookupError(
1284
+ `include package("${identifier}", "${file}"): case mismatch \u2014 resolved "${resolvedBasename}" but expected "${expectedBasename}" (E11 decision 5: case-sensitive)`,
1285
+ identifier,
1286
+ file,
1287
+ 0,
1288
+ 0
1289
+ );
1290
+ }
1291
+ return resolved;
1292
+ }
1084
1293
  var IncludeLoader = class {
1085
1294
  constructor(opts) {
1086
1295
  this.opts = opts;
@@ -1160,8 +1369,77 @@ var IncludeLoader = class {
1160
1369
  }
1161
1370
  return merged;
1162
1371
  }
1372
+ /**
1373
+ * Resolve and load `include package("identifier", "file")`.
1374
+ * Validates the identifier and file arguments, resolves via the package resolver,
1375
+ * performs cycle detection using a JSON-serialised cycle key, and parses the content.
1376
+ */
1377
+ // `required` is reserved for future optional-package semantics (E11 decision 7);
1378
+ // currently all package lookup failures throw regardless of this flag.
1379
+ loadPackage(identifier, file, _required) {
1380
+ validatePackageIdentifier(identifier);
1381
+ validatePackageFile(file, identifier);
1382
+ const { includeStack = [], baseDir, resolveFrom, packageResolver, readFileSync: readFileSync2 } = this.opts;
1383
+ const cycleKey = JSON.stringify(["package", identifier, file]);
1384
+ if (includeStack.includes(cycleKey)) {
1385
+ throw new ResolveError(
1386
+ `circular include: package("${identifier}", "${file}")`,
1387
+ `${identifier}/${file}`,
1388
+ 0,
1389
+ 0
1390
+ );
1391
+ }
1392
+ if (includeStack.length >= 50) {
1393
+ throw new ResolveError(`include depth limit exceeded (max 50)`, `${identifier}/${file}`, 0, 0);
1394
+ }
1395
+ const resolver = packageResolver ?? ((id, f, includingFile, bDir) => defaultPackageResolver(id, f, includingFile, bDir, resolveFrom));
1396
+ const resolvedPath = resolver(identifier, file, void 0, baseDir);
1397
+ const content = readFileSync2(resolvedPath);
1398
+ if (content.length === 0) return makeResObj();
1399
+ const tokens = tokenize(content);
1400
+ assertNonEmptyDocument(tokens, resolvedPath);
1401
+ const ast = parseTokens(tokens);
1402
+ return this.onBuildResObj(ast, {
1403
+ ...this.opts,
1404
+ baseDir: nodePath.dirname(resolvedPath),
1405
+ includeStack: [...includeStack, cycleKey]
1406
+ });
1407
+ }
1408
+ /**
1409
+ * Async variant of `loadPackage`.
1410
+ */
1411
+ async loadPackageAsync(identifier, file, _required) {
1412
+ validatePackageIdentifier(identifier);
1413
+ validatePackageFile(file, identifier);
1414
+ const { includeStack = [], baseDir, resolveFrom, packageResolver, readFile, readFileSync: readFileSync2 } = this.opts;
1415
+ const cycleKey = JSON.stringify(["package", identifier, file]);
1416
+ if (includeStack.includes(cycleKey)) {
1417
+ throw new ResolveError(
1418
+ `circular include: package("${identifier}", "${file}")`,
1419
+ `${identifier}/${file}`,
1420
+ 0,
1421
+ 0
1422
+ );
1423
+ }
1424
+ if (includeStack.length >= 50) {
1425
+ throw new ResolveError(`include depth limit exceeded (max 50)`, `${identifier}/${file}`, 0, 0);
1426
+ }
1427
+ const resolver = packageResolver ?? ((id, f, includingFile, bDir) => defaultPackageResolver(id, f, includingFile, bDir, resolveFrom));
1428
+ const resolvedPath = resolver(identifier, file, void 0, baseDir);
1429
+ const read = readFile ? async (p) => readFile(p) : async (p) => readFileSync2(p);
1430
+ const content = await read(resolvedPath);
1431
+ if (content.length === 0) return makeResObj();
1432
+ const tokens = tokenize(content);
1433
+ assertNonEmptyDocument(tokens, resolvedPath);
1434
+ const ast = parseTokens(tokens);
1435
+ return this.onBuildResObjAsync(ast, {
1436
+ ...this.opts,
1437
+ baseDir: nodePath.dirname(resolvedPath),
1438
+ includeStack: [...includeStack, cycleKey]
1439
+ });
1440
+ }
1163
1441
  loadSingle(candidate) {
1164
- const { readFileSync: readFileSync2, includeStack = [], env } = this.opts;
1442
+ const { readFileSync: readFileSync2, includeStack = [] } = this.opts;
1165
1443
  if (includeStack.includes(candidate)) {
1166
1444
  throw new ResolveError(`circular include: ${candidate}`, candidate, 0, 0);
1167
1445
  }
@@ -1179,14 +1457,13 @@ var IncludeLoader = class {
1179
1457
  assertNonEmptyDocument(tokens, candidate);
1180
1458
  const ast = parseTokens(tokens);
1181
1459
  return this.onBuildResObj(ast, {
1182
- env,
1460
+ ...this.opts,
1183
1461
  baseDir: nodePath.dirname(candidate),
1184
- readFileSync: readFileSync2,
1185
1462
  includeStack: [...includeStack, candidate]
1186
1463
  });
1187
1464
  }
1188
1465
  async loadSingleAsync(candidate) {
1189
- const { readFile, readFileSync: readFileSync2, includeStack = [], env } = this.opts;
1466
+ const { readFile, readFileSync: readFileSync2, includeStack = [] } = this.opts;
1190
1467
  const read = readFile ? async (p) => readFile(p) : async (p) => readFileSync2(p);
1191
1468
  if (includeStack.includes(candidate)) {
1192
1469
  throw new ResolveError(`circular include: ${candidate}`, candidate, 0, 0);
@@ -1205,10 +1482,8 @@ var IncludeLoader = class {
1205
1482
  assertNonEmptyDocument(tokens, candidate);
1206
1483
  const ast = parseTokens(tokens);
1207
1484
  return this.onBuildResObjAsync(ast, {
1208
- env,
1485
+ ...this.opts,
1209
1486
  baseDir: nodePath.dirname(candidate),
1210
- readFileSync: readFileSync2,
1211
- readFile,
1212
1487
  includeStack: [...includeStack, candidate]
1213
1488
  });
1214
1489
  }
@@ -1217,7 +1492,6 @@ var IncludeLoader = class {
1217
1492
  // src/internal/resolver/structure-builder.ts
1218
1493
  var StructureBuilder = class _StructureBuilder {
1219
1494
  constructor(opts) {
1220
- this.opts = opts;
1221
1495
  this.loader = new IncludeLoader(opts);
1222
1496
  this.loader.onBuildResObj = (a, o) => new _StructureBuilder(o).build(a);
1223
1497
  this.loader.onBuildResObjAsync = async (a, o) => new _StructureBuilder(o).buildAsync(a);
@@ -1244,7 +1518,13 @@ var StructureBuilder = class _StructureBuilder {
1244
1518
  }
1245
1519
  applyField(obj, field, pathPrefix) {
1246
1520
  if (field.key.length === 0 && field.value.kind === "include") {
1247
- const included = this.loader.load(field.value.path, field.value.required, field.value.isFile);
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
+ }
1248
1528
  if (pathPrefix.length > 0) {
1249
1529
  this.relativizeResObj(included, pathPrefix);
1250
1530
  }
@@ -1286,7 +1566,13 @@ var StructureBuilder = class _StructureBuilder {
1286
1566
  }
1287
1567
  async applyFieldAsync(obj, field, pathPrefix) {
1288
1568
  if (field.key.length === 0 && field.value.kind === "include") {
1289
- const included = await this.loader.loadAsync(field.value.path, field.value.required, field.value.isFile);
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
+ }
1290
1576
  if (pathPrefix.length > 0) {
1291
1577
  this.relativizeResObj(included, pathPrefix);
1292
1578
  }
@@ -1454,6 +1740,10 @@ var SubstitutionResolver = class {
1454
1740
  // xx.hocon PR (see Phase 6 #3f close-out notes).
1455
1741
  this.resolvingConcats = /* @__PURE__ */ new WeakSet();
1456
1742
  }
1743
+ /** Returns a prefix for error messages, including origin info when set. */
1744
+ originPrefix() {
1745
+ return this.opts.originDescription ? `${this.opts.originDescription}: ` : "";
1746
+ }
1457
1747
  resolve() {
1458
1748
  return this.resolveResObj(this.root);
1459
1749
  }
@@ -1494,7 +1784,8 @@ var SubstitutionResolver = class {
1494
1784
  if (isConcat(v)) {
1495
1785
  this.resolvingConcats.add(v);
1496
1786
  try {
1497
- return this.resolveConcat(v.nodes, scope, v.line, v.col);
1787
+ const concatResult = this.resolveConcat(v.nodes, scope, v.line, v.col);
1788
+ return concatResult;
1498
1789
  } finally {
1499
1790
  this.resolvingConcats.delete(v);
1500
1791
  }
@@ -1539,7 +1830,7 @@ var SubstitutionResolver = class {
1539
1830
  }
1540
1831
  if (s.optional) return void 0;
1541
1832
  throw new ResolveError(
1542
- `circular substitution: ${key}`,
1833
+ this.originPrefix() + `circular substitution: ${key}`,
1543
1834
  key,
1544
1835
  s.line,
1545
1836
  s.col
@@ -1569,7 +1860,7 @@ var SubstitutionResolver = class {
1569
1860
  }
1570
1861
  if (s.optional) return void 0;
1571
1862
  throw new ResolveError(
1572
- `could not resolve substitution: \${${key}}`,
1863
+ `${this.originPrefix()}could not resolve substitution: \${${key}}`,
1573
1864
  key,
1574
1865
  s.line,
1575
1866
  s.col
@@ -1602,7 +1893,7 @@ var SubstitutionResolver = class {
1602
1893
  if (result !== void 0) this.cache.set(key, result);
1603
1894
  return result;
1604
1895
  }
1605
- if (s.listSuffix) {
1896
+ if (s.listSuffix && this.opts.useSystemEnvironment !== false) {
1606
1897
  const result = this.resolveEnvList(s);
1607
1898
  if (result !== void 0) {
1608
1899
  this.cache.set(key, result);
@@ -1611,22 +1902,36 @@ var SubstitutionResolver = class {
1611
1902
  if (s.optional) return void 0;
1612
1903
  const envBase = s.segments.map((seg) => seg.text).join(".");
1613
1904
  throw new ResolveError(
1614
- `could not resolve substitution: \${${key}} (no environment variables ${envBase}_0, ${envBase}_1, \u2026 set)`,
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}}`,
1615
1915
  key,
1616
1916
  s.line,
1617
1917
  s.col
1618
1918
  );
1619
1919
  }
1620
- const envKey = s.segments.map((seg) => seg.text).join(".");
1621
- const envVal = this.opts.env[envKey] ?? (s.prefixLen > 0 ? this.opts.env[s.segments.slice(s.prefixLen).map((seg) => seg.text).join(".")] : void 0);
1622
- if (envVal !== void 0) {
1623
- const result = { kind: "scalar", raw: envVal, valueType: "string" };
1624
- this.cache.set(key, result);
1625
- return result;
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
+ }
1626
1928
  }
1627
1929
  if (s.optional) return void 0;
1930
+ if (this.opts.allowUnresolved) {
1931
+ return s;
1932
+ }
1628
1933
  throw new ResolveError(
1629
- `could not resolve substitution: \${${key}}`,
1934
+ `${this.originPrefix()}could not resolve substitution: \${${key}}`,
1630
1935
  key,
1631
1936
  s.line,
1632
1937
  s.col
@@ -1671,8 +1976,16 @@ var SubstitutionResolver = class {
1671
1976
  return [full, bare];
1672
1977
  }
1673
1978
  resolveConcat(nodes, scope, line = 0, col = 0) {
1674
- const resolved = nodes.map((n) => this.resolveVal(n, scope)).filter((v) => v !== void 0);
1675
- if (resolved.length === 0) return { kind: "scalar", raw: "null", valueType: "null" };
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;
1676
1989
  if (resolved.length === 1) return resolved[0];
1677
1990
  const nonSep = resolved.filter((v) => !separatorValues.has(v));
1678
1991
  if (nonSep.length === 0) {
@@ -1746,15 +2059,89 @@ var SubstitutionResolver = class {
1746
2059
  };
1747
2060
 
1748
2061
  // src/internal/resolver/resolver.ts
2062
+ function buildTree(ast, opts) {
2063
+ return new StructureBuilder(opts).build(ast);
2064
+ }
2065
+ function resolveTree(tree, opts) {
2066
+ return new SubstitutionResolver(tree, opts).resolve();
2067
+ }
2068
+ function containsPlaceholders(tree) {
2069
+ for (const val of tree.fields.values()) {
2070
+ if (valContainsPlaceholders(val)) return true;
2071
+ }
2072
+ return false;
2073
+ }
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
+ }
1749
2110
  function resolve2(ast, opts) {
1750
- const root = new StructureBuilder(opts).build(ast);
1751
- return new SubstitutionResolver(root, opts).resolve();
2111
+ const tree = buildTree(ast, opts);
2112
+ return resolveTree(tree, { ...opts, useSystemEnvironment: true, allowUnresolved: false });
1752
2113
  }
1753
2114
 
1754
2115
  // src/config.ts
1755
2116
  var Config = class _Config {
1756
- constructor(root) {
2117
+ constructor(root, opts) {
1757
2118
  this.root = root;
2119
+ this._resolved = opts?.resolved ?? true;
2120
+ this._parseBaseDir = opts?.parseBaseDir;
2121
+ this._originDescription = opts?.originDescription;
2122
+ this._resObjRoot = opts?.resObjRoot;
2123
+ this._resolveOpts = opts?.resolveOpts;
2124
+ }
2125
+ /** Returns true when the Config's value tree contains no unresolved
2126
+ * substitution placeholders. Whole-config granularity (E12 decision 11). */
2127
+ isResolved() {
2128
+ return this._resolved;
2129
+ }
2130
+ /** @internal Test-only: construct a resolved Config from a HoconValue. */
2131
+ static _fromResolvedValue(root, opts) {
2132
+ return new _Config(root, { resolved: true, originDescription: opts?.originDescription });
2133
+ }
2134
+ /** @internal Construct an unresolved Config carrying a ResObj tree.
2135
+ * Used by parseStringWithOptions (deferred path) in T6. */
2136
+ static _fromUnresolvedResObj(tree, opts) {
2137
+ const partialRoot = buildPartialHoconFromResObj(tree);
2138
+ return new _Config(partialRoot, {
2139
+ resolved: opts.resolved,
2140
+ parseBaseDir: opts.parseBaseDir,
2141
+ originDescription: opts.originDescription,
2142
+ resObjRoot: tree,
2143
+ resolveOpts: opts.resolveOpts
2144
+ });
1758
2145
  }
1759
2146
  get(path2) {
1760
2147
  const v = this.lookupNode(path2);
@@ -1798,13 +2185,29 @@ var Config = class _Config {
1798
2185
  }
1799
2186
  getConfig(path2) {
1800
2187
  const v = this.lookupNode(path2);
1801
- if (v === void 0) throw new ConfigError(`path not found: ${path2}`, path2);
2188
+ if (v === void 0) {
2189
+ if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
2190
+ throw new NotResolvedError(path2);
2191
+ }
2192
+ throw new ConfigError(`path not found: ${path2}`, path2);
2193
+ }
1802
2194
  if (v.kind !== "object") throw new ConfigError(`expected object at ${path2}`, path2);
1803
- return new _Config(v);
2195
+ if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
2196
+ throw new NotResolvedError(path2);
2197
+ }
2198
+ return new _Config(v, { resolved: this._resolved });
1804
2199
  }
1805
2200
  getList(path2) {
1806
2201
  const v = this.lookupNode(path2);
1807
- if (v === void 0) throw new ConfigError(`path not found: ${path2}`, path2);
2202
+ if (v === void 0) {
2203
+ if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
2204
+ throw new NotResolvedError(path2);
2205
+ }
2206
+ throw new ConfigError(`path not found: ${path2}`, path2);
2207
+ }
2208
+ if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
2209
+ throw new NotResolvedError(path2);
2210
+ }
1808
2211
  if (v.kind === "object") {
1809
2212
  const converted = numericObjectToArray(v);
1810
2213
  if (converted !== null) return converted.map(hoconToJs);
@@ -1819,12 +2222,138 @@ var Config = class _Config {
1819
2222
  return [...this.root.fields.keys()];
1820
2223
  }
1821
2224
  withFallback(fallback) {
1822
- const merged = deepMergeHocon(this.root, fallback.root);
1823
- return new _Config(merged);
2225
+ if (fallback == null) return this;
2226
+ const selfTree = this._resObjRoot ?? hoconValueToResObj2(this.root);
2227
+ const fbTree = fallback._resObjRoot ?? hoconValueToResObj2(fallback.root);
2228
+ const merged = mergeUnresolved(selfTree, fbTree);
2229
+ const hasPlaceholders = containsPlaceholders(merged);
2230
+ const partialRoot = buildPartialHoconFromResObj(merged);
2231
+ return new _Config(partialRoot, {
2232
+ resolved: !hasPlaceholders,
2233
+ parseBaseDir: this._parseBaseDir,
2234
+ originDescription: this._originDescription,
2235
+ resObjRoot: merged,
2236
+ resolveOpts: this._resolveOpts ?? fallback._resolveOpts
2237
+ });
2238
+ }
2239
+ /**
2240
+ * Runs substitution resolution (phase 2) on the stored unresolved tree.
2241
+ * Idempotent: calling resolve() on an already-resolved Config returns an
2242
+ * equivalent resolved Config.
2243
+ *
2244
+ * opts.allowUnresolved (default false) — when true, leaves unresolved
2245
+ * non-optional placeholders in place rather than throwing.
2246
+ * opts.useSystemEnvironment (default true) — when false, env var lookups
2247
+ * are suppressed (hermetic resolution).
2248
+ *
2249
+ * E12 decision 3.
2250
+ */
2251
+ resolve(opts = {}) {
2252
+ if (this._resolved) {
2253
+ return new _Config(this.root, {
2254
+ resolved: true,
2255
+ parseBaseDir: this._parseBaseDir,
2256
+ originDescription: this._originDescription
2257
+ });
2258
+ }
2259
+ const tree = this._resObjRoot;
2260
+ if (!tree) {
2261
+ return new _Config(this.root, { resolved: true });
2262
+ }
2263
+ const resolveOpts = {
2264
+ ...this._resolveOpts ?? { env: {}, baseDir: void 0, readFileSync: () => {
2265
+ throw new Error("no files");
2266
+ } },
2267
+ allowUnresolved: opts.allowUnresolved ?? false,
2268
+ useSystemEnvironment: opts.useSystemEnvironment ?? true,
2269
+ originDescription: this._originDescription
2270
+ };
2271
+ const resolved = resolveTree(tree, resolveOpts);
2272
+ if (resolved.kind !== "object") throw new Error("resolve: expected object root");
2273
+ if (opts.allowUnresolved) {
2274
+ const { stripped, hadPlaceholders } = stripPlaceholderFields(resolved);
2275
+ return new _Config(stripped, {
2276
+ resolved: !hadPlaceholders,
2277
+ parseBaseDir: this._parseBaseDir,
2278
+ originDescription: this._originDescription,
2279
+ resObjRoot: hadPlaceholders ? tree : void 0,
2280
+ resolveOpts: hadPlaceholders ? resolveOpts : void 0
2281
+ });
2282
+ }
2283
+ return new _Config(resolved, {
2284
+ resolved: true,
2285
+ parseBaseDir: this._parseBaseDir,
2286
+ originDescription: this._originDescription
2287
+ });
2288
+ }
2289
+ /**
2290
+ * Resolves receiver substitutions using source as lookup context.
2291
+ * Source's keys are NOT merged into the result — differs from
2292
+ * `this.withFallback(source).resolve()` which includes source keys.
2293
+ *
2294
+ * Precondition (E12 decision 10): source must be resolved. If
2295
+ * source.isResolved() is false, throws NotResolvedError.
2296
+ *
2297
+ * On an already-resolved receiver, resolveWith is a no-op (returns an
2298
+ * equivalent resolved Config, source keys not included).
2299
+ *
2300
+ * E12 decisions 9, 10.
2301
+ */
2302
+ resolveWith(source, opts = {}) {
2303
+ if (!source.isResolved()) {
2304
+ throw new NotResolvedError("source");
2305
+ }
2306
+ if (this._resolved) {
2307
+ return new _Config(this.root, {
2308
+ resolved: true,
2309
+ parseBaseDir: this._parseBaseDir,
2310
+ originDescription: this._originDescription
2311
+ });
2312
+ }
2313
+ const receiverTree = this._resObjRoot ?? hoconValueToResObj2(this.root);
2314
+ const srcTree = hoconValueToResObj2(source.root);
2315
+ const merged = mergeUnresolved(receiverTree, srcTree);
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(merged, resolveOpts);
2325
+ if (resolved.kind !== "object") throw new Error("resolveWith: expected object root");
2326
+ const receiverShapeForFilter = resObjToKeyShape(receiverTree);
2327
+ const filtered = filterByReceiverShape(resolved, receiverShapeForFilter);
2328
+ if (opts.allowUnresolved) {
2329
+ const { stripped, hadPlaceholders } = stripPlaceholderFields(filtered);
2330
+ return new _Config(stripped, {
2331
+ resolved: !hadPlaceholders,
2332
+ parseBaseDir: this._parseBaseDir,
2333
+ originDescription: this._originDescription,
2334
+ resObjRoot: hadPlaceholders ? receiverTree : void 0,
2335
+ resolveOpts: hadPlaceholders ? resolveOpts : void 0
2336
+ });
2337
+ }
2338
+ return new _Config(filtered, {
2339
+ resolved: true,
2340
+ parseBaseDir: this._parseBaseDir,
2341
+ originDescription: this._originDescription
2342
+ });
1824
2343
  }
1825
2344
  toObject() {
1826
2345
  return hoconToJs(this.root);
1827
2346
  }
2347
+ /**
2348
+ * @internal Test-only: renders this resolved Config's value tree as canonical
2349
+ * JSON (sorted keys, no whitespace). Used by Layer-2 fixture tests to compare
2350
+ * against Lightbend ground truth. NOT part of the public API.
2351
+ *
2352
+ * Throws if the Config is unresolved (contains placeholders).
2353
+ */
2354
+ _renderJSONForTest() {
2355
+ return renderHoconAsJSON(this.root);
2356
+ }
1828
2357
  lookupNode(path2) {
1829
2358
  const segments = splitConfigPath(path2);
1830
2359
  let current = this.root;
@@ -1836,9 +2365,40 @@ var Config = class _Config {
1836
2365
  }
1837
2366
  return current;
1838
2367
  }
2368
+ /**
2369
+ * _resObjRootSubtreeHasPlaceholders — walk _resObjRoot to the given path and
2370
+ * return true if the subtree there contains any unresolved placeholder.
2371
+ * Used by getConfig / getList (T2) to detect transitive unresolved placeholders
2372
+ * in object / array subtrees that would otherwise appear to be "resolved" in the
2373
+ * partial HoconValue root (because individual leaf fields are omitted rather than
2374
+ * the whole subtree when their value is a direct placeholder scalar, but object
2375
+ * sub-containers are retained in the partial root with the placeholder-keyed
2376
+ * fields omitted from them — so the object appears "present but empty-ish").
2377
+ */
2378
+ _resObjRootSubtreeHasPlaceholders(path2) {
2379
+ const tree = this._resObjRoot;
2380
+ if (!tree) return false;
2381
+ const segments = splitConfigPath(path2);
2382
+ let cur = tree;
2383
+ for (const seg of segments) {
2384
+ const val = cur.fields.get(seg);
2385
+ if (val === void 0) return false;
2386
+ if (isResObj(val)) {
2387
+ cur = val;
2388
+ } else {
2389
+ return valContainsPlaceholders(val);
2390
+ }
2391
+ }
2392
+ return containsPlaceholders(cur);
2393
+ }
1839
2394
  requireScalar(path2) {
1840
2395
  const v = this.lookupNode(path2);
1841
- if (v === void 0) throw new ConfigError(`path not found: ${path2}`, path2);
2396
+ if (v === void 0) {
2397
+ if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
2398
+ throw new NotResolvedError(path2);
2399
+ }
2400
+ throw new ConfigError(`path not found: ${path2}`, path2);
2401
+ }
1842
2402
  if (v.kind !== "scalar") throw new ConfigError(`expected scalar at ${path2}, got ${v.kind}`, path2);
1843
2403
  return v;
1844
2404
  }
@@ -1901,28 +2461,89 @@ function hoconToJs(v) {
1901
2461
  case "array":
1902
2462
  return v.items.map(hoconToJs);
1903
2463
  case "object": {
1904
- const obj = {};
2464
+ const obj = /* @__PURE__ */ Object.create(null);
1905
2465
  for (const [k, val] of v.fields) obj[k] = hoconToJs(val);
1906
- return obj;
2466
+ return Object.assign({}, obj);
2467
+ }
2468
+ }
2469
+ }
2470
+ function resObjToKeyShape(tree) {
2471
+ const fields = /* @__PURE__ */ new Map();
2472
+ for (const [k, v] of tree.fields) {
2473
+ if (isSubst(v) || isConcat(v) || isAppend(v)) {
2474
+ fields.set(k, { kind: "scalar", raw: "", valueType: "null" });
2475
+ } else if (isResObj(v)) {
2476
+ fields.set(k, resObjToKeyShape(v));
2477
+ } else {
2478
+ fields.set(k, v);
1907
2479
  }
1908
2480
  }
2481
+ return { kind: "object", fields };
1909
2482
  }
1910
- function deepMergeHocon(receiver, fallback) {
1911
- const merged = new Map(fallback.fields);
1912
- for (const [k, v] of receiver.fields) {
1913
- const fb = merged.get(k);
1914
- if (fb?.kind === "object" && v.kind === "object") {
1915
- merged.set(k, deepMergeHocon(v, fb));
2483
+ function stripPlaceholderFields(v) {
2484
+ const fields = /* @__PURE__ */ new Map();
2485
+ let hadPlaceholders = false;
2486
+ for (const [k, val] of v.fields) {
2487
+ if (val._kind === "subst-placeholder" || val._kind === "concat-placeholder" || val._kind === "append-placeholder") {
2488
+ hadPlaceholders = true;
2489
+ } else if (val.kind === "object") {
2490
+ const inner = stripPlaceholderFields(val);
2491
+ if (inner.hadPlaceholders) hadPlaceholders = true;
2492
+ fields.set(k, inner.stripped);
1916
2493
  } else {
1917
- merged.set(k, v);
2494
+ fields.set(k, val);
1918
2495
  }
1919
2496
  }
1920
- return { kind: "object", fields: merged };
2497
+ return { stripped: { kind: "object", fields }, hadPlaceholders };
2498
+ }
2499
+ function filterByReceiverShape(resolved, receiverShape) {
2500
+ const fields = /* @__PURE__ */ new Map();
2501
+ for (const [k, rv] of resolved.fields) {
2502
+ if (!receiverShape.fields.has(k)) continue;
2503
+ const receiverVal = receiverShape.fields.get(k);
2504
+ if (rv.kind === "object" && receiverVal.kind === "object") {
2505
+ fields.set(k, filterByReceiverShape(rv, receiverVal));
2506
+ } else {
2507
+ fields.set(k, rv);
2508
+ }
2509
+ }
2510
+ return { kind: "object", fields };
2511
+ }
2512
+ function renderHoconAsJSON(v) {
2513
+ switch (v.kind) {
2514
+ case "scalar": {
2515
+ switch (v.valueType) {
2516
+ case "null":
2517
+ return "null";
2518
+ case "boolean":
2519
+ return v.raw;
2520
+ case "number": {
2521
+ const n = Number(v.raw);
2522
+ if (Number.isFinite(n)) return v.raw;
2523
+ return JSON.stringify(v.raw);
2524
+ }
2525
+ case "string":
2526
+ return JSON.stringify(v.raw);
2527
+ }
2528
+ break;
2529
+ }
2530
+ case "array": {
2531
+ const items = v.items.map(renderHoconAsJSON);
2532
+ return `[${items.join(",")}]`;
2533
+ }
2534
+ case "object": {
2535
+ const keys = [...v.fields.keys()].sort();
2536
+ const pairs = keys.map((k) => {
2537
+ const val = v.fields.get(k);
2538
+ return `${JSON.stringify(k)}:${renderHoconAsJSON(val)}`;
2539
+ });
2540
+ return `{${pairs.join(",")}}`;
2541
+ }
2542
+ }
2543
+ throw new Error(`renderHoconAsJSON: unsupported value kind (unresolved placeholder?)`);
1921
2544
  }
1922
2545
 
1923
2546
  // src/parse.ts
1924
- import * as fs from "fs";
1925
- import * as path from "path";
1926
2547
  function getEnv(opts) {
1927
2548
  if (opts.env !== void 0) return opts.env;
1928
2549
  if (typeof process !== "undefined" && process.env) return process.env;
@@ -1939,7 +2560,10 @@ function buildResolveContext(input, opts) {
1939
2560
  env: getEnv(opts),
1940
2561
  baseDir: opts.baseDir,
1941
2562
  readFileSync: opts.readFileSync ?? defaultReadFileSync,
1942
- readFile: opts.readFile
2563
+ readFile: opts.readFile,
2564
+ originDescription: opts.originDescription,
2565
+ resolveFrom: opts.resolveFrom,
2566
+ packageResolver: opts.packageResolver
1943
2567
  };
1944
2568
  return { ast, resolveOpts };
1945
2569
  }
@@ -1947,7 +2571,7 @@ function parse(input, opts = {}) {
1947
2571
  const { ast, resolveOpts } = buildResolveContext(input, opts);
1948
2572
  const value = resolve2(ast, resolveOpts);
1949
2573
  if (value.kind !== "object") throw new Error("resolved value is not an object");
1950
- return new Config(value);
2574
+ return new Config(value, { resolved: true, originDescription: opts.originDescription });
1951
2575
  }
1952
2576
  function parseFile(filePath, opts = {}) {
1953
2577
  if (typeof process === "undefined" && !opts.readFileSync) {