@hey-api/shared 0.4.0 โ†’ 0.4.2

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/index.mjs CHANGED
@@ -5,7 +5,7 @@ import open from "open";
5
5
  import { sync } from "cross-spawn";
6
6
  import * as semver from "semver";
7
7
  import { getResolvedInput, sendRequest } from "@hey-api/json-schema-ref-parser";
8
- import { fromRef, ref } from "@hey-api/codegen-core";
8
+ import { fromRef, log, ref } from "@hey-api/codegen-core";
9
9
  import { EOL } from "node:os";
10
10
  //#region src/tsConfig.ts
11
11
  function findPackageJson(initialDir) {
@@ -628,13 +628,21 @@ function getLogs(userLogs) {
628
628
  //#endregion
629
629
  //#region src/config/output/postprocess.ts
630
630
  function postprocessOutput(config, postProcessors, jobPrefix) {
631
+ if (!fs.existsSync(config.path) || !fs.readdirSync(config.path).length) return;
631
632
  for (const processor of config.postProcess) {
632
633
  const resolved = typeof processor === "string" ? postProcessors[processor] : processor;
633
634
  if (!resolved) continue;
634
635
  const name = resolved.name ?? resolved.command;
635
636
  const args = resolved.args.map((arg) => arg.replace("{{path}}", config.path));
636
637
  console.log(`${jobPrefix}๐Ÿงน Running ${colors.cyanBright(name)}`);
637
- sync(resolved.command, args);
638
+ const result = sync(resolved.command, args);
639
+ if (result.error) throw new ConfigError(`Post-processor "${name}" failed to run: ${result.error.message}`);
640
+ if (result.status !== null && result.status !== 0) {
641
+ let message = `Post-processor "${name}" exited with code ${result.status}`;
642
+ const stderr = result.stderr?.toString().trim();
643
+ if (stderr) message += `:\n${stderr}`;
644
+ throw new ConfigError(message);
645
+ }
638
646
  }
639
647
  }
640
648
  //#endregion
@@ -1294,28 +1302,28 @@ function operationToId({ context, count = 1, id, method, path, state }) {
1294
1302
  //#endregion
1295
1303
  //#region src/debug/ir.ts
1296
1304
  const indent = (level) => " ".repeat(level);
1297
- const log = (message, level) => console.log(`${indent(level ?? 0)}${message}`);
1305
+ const log$1 = (message, level) => console.log(`${indent(level ?? 0)}${message}`);
1298
1306
  const print = (ir, options = {}) => {
1299
1307
  const { depth = 2, section = "all", verbosity = "summary" } = options;
1300
1308
  const printObject = (obj, level, kind = "generic") => {
1301
1309
  if (verbosity === "summary" && obj && typeof obj === "object") if (kind === "responses") {
1302
1310
  const count = Object.keys(obj).length;
1303
1311
  const noun = count === 1 ? "code" : "codes";
1304
- log(`responses: ${colors.yellow(`${count} ${noun}`)}`, level);
1305
- } else if (kind === "requestBody") log(`requestBody: ${Object.keys(obj).join(", ")}`, level);
1306
- else if (kind === "schema") log(`schema keys: ${Object.keys(obj).join(", ")}`, level);
1307
- else log(`keys: ${Object.keys(obj).join(", ")}`, level);
1308
- else log(JSON.stringify(obj, null, depth), level);
1312
+ log$1(`responses: ${colors.yellow(`${count} ${noun}`)}`, level);
1313
+ } else if (kind === "requestBody") log$1(`requestBody: ${Object.keys(obj).join(", ")}`, level);
1314
+ else if (kind === "schema") log$1(`schema keys: ${Object.keys(obj).join(", ")}`, level);
1315
+ else log$1(`keys: ${Object.keys(obj).join(", ")}`, level);
1316
+ else log$1(JSON.stringify(obj, null, depth), level);
1309
1317
  };
1310
1318
  const printPathItem = (key, item, base = 1) => {
1311
1319
  if ("$ref" in item) {
1312
- log(`${colors.cyan(key)} is a $ref โ†’ ${colors.yellow(item.$ref)}`, base);
1320
+ log$1(`${colors.cyan(key)} is a $ref โ†’ ${colors.yellow(item.$ref)}`, base);
1313
1321
  return;
1314
1322
  }
1315
1323
  for (const method of Object.keys(item)) {
1316
1324
  if (!httpMethods.includes(method)) continue;
1317
1325
  const operation = item[method];
1318
- log(`${colors.green(method.toUpperCase())} ${colors.cyan(key)} (${colors.magenta(operation.operationId ?? "")})`, base);
1326
+ log$1(`${colors.green(method.toUpperCase())} ${colors.cyan(key)} (${colors.magenta(operation.operationId ?? "")})`, base);
1319
1327
  if (operation.body) printObject(operation.body, base + 1, "requestBody");
1320
1328
  if (operation.responses) printObject(operation.responses, base + 1, "responses");
1321
1329
  }
@@ -1324,20 +1332,20 @@ const print = (ir, options = {}) => {
1324
1332
  for (const section of sections) switch (section) {
1325
1333
  case "components":
1326
1334
  if (ir.components?.schemas) {
1327
- log(`Components: ${Object.keys(ir.components.schemas).length} schemas`);
1335
+ log$1(`Components: ${Object.keys(ir.components.schemas).length} schemas`);
1328
1336
  for (const [, schema] of Object.entries(ir.components.schemas)) printObject(schema, 1, "schema");
1329
1337
  }
1330
1338
  break;
1331
1339
  case "paths": {
1332
1340
  const paths = ir.paths || {};
1333
- log(`paths (${Object.keys(paths).length} items):`);
1341
+ log$1(`paths (${Object.keys(paths).length} items):`);
1334
1342
  for (const [path, item] of Object.entries(paths)) printPathItem(path, item);
1335
1343
  break;
1336
1344
  }
1337
1345
  case "servers": break;
1338
1346
  case "webhooks": {
1339
1347
  const webhooks = ir.webhooks || {};
1340
- log(`webhooks (${Object.keys(webhooks).length} items):`);
1348
+ log$1(`webhooks (${Object.keys(webhooks).length} items):`);
1341
1349
  for (const [path, item] of Object.entries(webhooks)) printPathItem(path, item);
1342
1350
  break;
1343
1351
  }
@@ -1540,24 +1548,38 @@ var MinHeap = class {
1540
1548
  * added to the graph.
1541
1549
  */
1542
1550
  const walkDeclarations = (graph, callback, options) => {
1543
- const pointers = Array.from(graph.nodes.keys());
1544
1551
  if (options?.preferGroups && options.preferGroups.length) {
1545
- const emitted = /* @__PURE__ */ new Set();
1546
- if (options.matchPointerToGroup) for (const kind of options.preferGroups) for (const pointer of pointers) {
1547
- const result = options.matchPointerToGroup(pointer);
1548
- if (!result.matched) continue;
1549
- if (result.kind === kind) {
1550
- emitted.add(pointer);
1551
- callback(pointer, graph.nodes.get(pointer));
1552
+ const preferGroupsSet = new Set(options.preferGroups);
1553
+ const buckets = /* @__PURE__ */ new Map();
1554
+ const unmatched = [];
1555
+ for (const pointer of graph.nodes.keys()) {
1556
+ if (options.matchPointerToGroup) {
1557
+ const result = options.matchPointerToGroup(pointer);
1558
+ if (result.matched) {
1559
+ if (preferGroupsSet.has(result.kind)) {
1560
+ let bucket = buckets.get(result.kind);
1561
+ if (!bucket) {
1562
+ bucket = [];
1563
+ buckets.set(result.kind, bucket);
1564
+ }
1565
+ bucket.push(pointer);
1566
+ } else unmatched.push(pointer);
1567
+ continue;
1568
+ }
1552
1569
  }
1570
+ unmatched.push(pointer);
1553
1571
  }
1554
- for (const pointer of pointers) {
1555
- if (emitted.has(pointer)) continue;
1556
- callback(pointer, graph.nodes.get(pointer));
1572
+ const emittedGroups = /* @__PURE__ */ new Set();
1573
+ for (const kind of options.preferGroups) {
1574
+ if (emittedGroups.has(kind)) continue;
1575
+ emittedGroups.add(kind);
1576
+ const pointers = buckets.get(kind);
1577
+ if (pointers) for (const pointer of pointers) callback(pointer, graph.nodes.get(pointer));
1557
1578
  }
1579
+ for (const pointer of unmatched) callback(pointer, graph.nodes.get(pointer));
1558
1580
  return;
1559
1581
  }
1560
- for (const pointer of pointers) callback(pointer, graph.nodes.get(pointer));
1582
+ for (const [pointer, node] of graph.nodes) callback(pointer, node);
1561
1583
  };
1562
1584
  /**
1563
1585
  * Walks the nodes of the graph in topological order (dependencies before dependents).
@@ -1569,36 +1591,32 @@ const walkDeclarations = (graph, callback, options) => {
1569
1591
  */
1570
1592
  const walkTopological = (graph, callback, options) => {
1571
1593
  const pointers = Array.from(graph.nodes.keys());
1572
- const baseIndex = /* @__PURE__ */ new Map();
1573
- pointers.forEach((pointer, index) => baseIndex.set(pointer, index));
1574
1594
  const declIndex = /* @__PURE__ */ new Map();
1575
- for (const pointer of pointers) {
1576
- const composite = (options?.getPointerPriority?.(pointer) ?? 10) * 1e6 + (baseIndex.get(pointer) ?? 0);
1577
- declIndex.set(pointer, composite);
1578
- }
1579
1595
  const depsOf = /* @__PURE__ */ new Map();
1580
- for (const pointer of pointers) {
1596
+ const inDegree = /* @__PURE__ */ new Map();
1597
+ const dependents = /* @__PURE__ */ new Map();
1598
+ const heap = new MinHeap(declIndex);
1599
+ pointers.forEach((pointer, index) => {
1600
+ const priority = options?.getPointerPriority?.(pointer) ?? 10;
1601
+ declIndex.set(pointer, priority * 1e6 + index);
1581
1602
  const raw = graph.subtreeDependencies?.get(pointer) ?? /* @__PURE__ */ new Set();
1582
- const filtered = /* @__PURE__ */ new Set();
1603
+ const deps = /* @__PURE__ */ new Set();
1583
1604
  for (const rawPointer of raw) {
1584
1605
  if (rawPointer === pointer) continue;
1585
- if (graph.nodes.has(rawPointer)) filtered.add(rawPointer);
1606
+ if (graph.nodes.has(rawPointer)) deps.add(rawPointer);
1586
1607
  }
1587
- depsOf.set(pointer, filtered);
1588
- }
1589
- const inDegree = /* @__PURE__ */ new Map();
1590
- const dependents = /* @__PURE__ */ new Map();
1591
- for (const pointer of pointers) inDegree.set(pointer, 0);
1592
- for (const [pointer, deps] of depsOf) {
1608
+ depsOf.set(pointer, deps);
1593
1609
  inDegree.set(pointer, deps.size);
1594
1610
  for (const d of deps) {
1595
- if (!dependents.has(d)) dependents.set(d, /* @__PURE__ */ new Set());
1596
- dependents.get(d).add(pointer);
1611
+ let dep = dependents.get(d);
1612
+ if (!dep) {
1613
+ dep = /* @__PURE__ */ new Set();
1614
+ dependents.set(d, dep);
1615
+ }
1616
+ dep.add(pointer);
1597
1617
  }
1598
- }
1599
- const sortByDecl = (arr) => arr.sort((a, b) => declIndex.get(a) - declIndex.get(b));
1600
- const heap = new MinHeap(declIndex);
1601
- for (const pointer of pointers) if ((inDegree.get(pointer) ?? 0) === 0) heap.push(pointer);
1618
+ if (deps.size === 0) heap.push(pointer);
1619
+ });
1602
1620
  const emitted = /* @__PURE__ */ new Set();
1603
1621
  const order = [];
1604
1622
  while (!heap.isEmpty()) {
@@ -1615,7 +1633,7 @@ const walkTopological = (graph, callback, options) => {
1615
1633
  }
1616
1634
  }
1617
1635
  const remaining = pointers.filter((pointer) => !emitted.has(pointer));
1618
- sortByDecl(remaining);
1636
+ remaining.sort((a, b) => declIndex.get(a) - declIndex.get(b));
1619
1637
  for (const pointer of remaining) {
1620
1638
  emitted.add(pointer);
1621
1639
  order.push(pointer);
@@ -1634,16 +1652,27 @@ const walkTopological = (graph, callback, options) => {
1634
1652
  }
1635
1653
  return options.preferGroups.length;
1636
1654
  };
1655
+ const orderIndex = /* @__PURE__ */ new Map();
1656
+ for (let i = 0; i < order.length; i++) orderIndex.set(order[i], i);
1657
+ const groupCache = /* @__PURE__ */ new Map();
1658
+ const getCachedGroup = (pointer) => {
1659
+ let g = groupCache.get(pointer);
1660
+ if (g === void 0) {
1661
+ g = getGroup(pointer);
1662
+ groupCache.set(pointer, g);
1663
+ }
1664
+ return g;
1665
+ };
1637
1666
  const proposed = [...order].sort((a, b) => {
1638
- const ga = getGroup(a);
1639
- const gb = getGroup(b);
1640
- return ga !== gb ? ga - gb : order.indexOf(a) - order.indexOf(b);
1667
+ const ga = getCachedGroup(a);
1668
+ const gb = getCachedGroup(b);
1669
+ return ga !== gb ? ga - gb : orderIndex.get(a) - orderIndex.get(b);
1641
1670
  });
1642
1671
  const proposedIndex = /* @__PURE__ */ new Map();
1643
1672
  for (let i = 0; i < proposed.length; i++) proposedIndex.set(proposed[i], i);
1644
1673
  if (!(() => {
1645
1674
  for (const [node, deps] of depsOf) for (const dep of deps) {
1646
- if (getGroup(dep) <= getGroup(node)) continue;
1675
+ if (getCachedGroup(dep) <= getCachedGroup(node)) continue;
1647
1676
  if (proposedIndex.get(dep) >= proposedIndex.get(node)) return true;
1648
1677
  }
1649
1678
  return false;
@@ -1719,15 +1748,12 @@ const getIrPointerPriority = (pointer) => {
1719
1748
  };
1720
1749
  //#endregion
1721
1750
  //#region src/utils/ref.ts
1722
- const jsonPointerSlash = /~1/g;
1723
- const jsonPointerTilde = /~0/g;
1724
1751
  /**
1725
1752
  * Returns the reusable component name from `$ref`.
1726
1753
  */
1727
1754
  function refToName($ref) {
1728
1755
  const path = jsonPointerToPath($ref);
1729
- const name = path[path.length - 1];
1730
- return decodeURI(name);
1756
+ return path[path.length - 1];
1731
1757
  }
1732
1758
  /**
1733
1759
  * Encodes a path segment for use in a JSON Pointer (RFC 6901).
@@ -1742,7 +1768,7 @@ function refToName($ref) {
1742
1768
  * @returns The encoded segment as a string.
1743
1769
  */
1744
1770
  function encodeJsonPointerSegment(segment) {
1745
- return String(segment).replace(/~/g, "~0").replace(/\//g, "~1");
1771
+ return String(segment).replaceAll("~", "~0").replaceAll("/", "~1");
1746
1772
  }
1747
1773
  /**
1748
1774
  * Converts a JSON Pointer string (RFC 6901) to an array of path segments.
@@ -1760,7 +1786,7 @@ function jsonPointerToPath(pointer) {
1760
1786
  if (clean.startsWith("#")) clean = clean.slice(1);
1761
1787
  if (clean.startsWith("/")) clean = clean.slice(1);
1762
1788
  if (!clean) return [];
1763
- return clean.split("/").map((part) => part.replace(jsonPointerSlash, "/").replace(jsonPointerTilde, "~"));
1789
+ return clean.split("/").map((part) => part.replaceAll("~1", "/").replaceAll("~0", "~"));
1764
1790
  }
1765
1791
  /**
1766
1792
  * Normalizes a JSON Pointer string to a canonical form.
@@ -1810,7 +1836,7 @@ function isTopLevelComponent(refOrPath) {
1810
1836
  return false;
1811
1837
  }
1812
1838
  function resolveRef({ $ref, spec }) {
1813
- const path = jsonPointerToPath(decodeURI($ref));
1839
+ const path = jsonPointerToPath($ref);
1814
1840
  let current = spec;
1815
1841
  for (const part of path) {
1816
1842
  const segment = part;
@@ -2576,7 +2602,11 @@ function collectOperations({ filters, parameters, requestBodies, resourceMetadat
2576
2602
  case "body": return !requestBodies.has(dependency);
2577
2603
  case "parameter": return !parameters.has(dependency);
2578
2604
  case "response": return !responses.has(dependency);
2579
- case "schema": return !schemas.has(dependency);
2605
+ case "schema":
2606
+ if (schemas.has(dependency)) return false;
2607
+ if (filters.schemas.exclude.has(dependency)) return true;
2608
+ schemas.add(dependency);
2609
+ return false;
2580
2610
  default: return false;
2581
2611
  }
2582
2612
  })) continue;
@@ -3261,24 +3291,25 @@ const propertiesRequiredByDefaultTransform = ({ spec }) => {
3261
3291
  */
3262
3292
  const deepEqual = (a, b) => {
3263
3293
  if (a === b) return true;
3264
- if (a === null || b === null) return a === b;
3265
- const typeA = typeof a;
3266
- if (typeA !== typeof b) return false;
3267
- if (typeA !== "object") return false;
3268
- if (Array.isArray(a) || Array.isArray(b)) {
3269
- if (!Array.isArray(a) || !Array.isArray(b)) return false;
3270
- if (a.length !== b.length) return false;
3271
- for (let i = 0; i < a.length; i++) if (!deepEqual(a[i], b[i])) return false;
3294
+ if (!a || !b || typeof a !== "object" || typeof b !== "object") return false;
3295
+ if (a.constructor !== b.constructor) return false;
3296
+ if (Array.isArray(a)) {
3297
+ const arrA = a;
3298
+ const arrB = b;
3299
+ let len = arrA.length;
3300
+ if (len !== arrB.length) return false;
3301
+ while (len--) if (!deepEqual(arrA[len], arrB[len])) return false;
3272
3302
  return true;
3273
3303
  }
3274
3304
  const objA = a;
3275
3305
  const objB = b;
3276
- const keysA = Object.keys(objA).sort();
3277
- const keysB = Object.keys(objB).sort();
3278
- if (keysA.length !== keysB.length) return false;
3279
- for (let i = 0; i < keysA.length; i++) if (keysA[i] !== keysB[i]) return false;
3280
- for (const key of keysA) if (!deepEqual(objA[key], objB[key])) return false;
3281
- return true;
3306
+ let len = 0;
3307
+ for (const key in objA) if (Object.hasOwn(objA, key)) {
3308
+ ++len;
3309
+ if (!Object.hasOwn(objB, key)) return false;
3310
+ if (!deepEqual(objA[key], objB[key])) return false;
3311
+ }
3312
+ return Object.keys(objB).length === len;
3282
3313
  };
3283
3314
  //#endregion
3284
3315
  //#region src/openApi/shared/utils/graph.ts
@@ -3356,85 +3387,35 @@ const collectPointerDependencies = ({ cache, graph, pointer, visited }) => {
3356
3387
  };
3357
3388
  };
3358
3389
  /**
3359
- * Propagates scopes through the graph using a worklist algorithm.
3360
- * Each node's scopes will be updated to include any scopes inherited via $ref dependencies, combinator/child relationships, and parent relationships.
3361
- * Handles cycles and deep chains efficiently.
3390
+ * Propagates scopes through the graph using a multi-pass linear scan.
3391
+ *
3392
+ * Nodes are visited in reverse DFS-pre-order (bottom-up): children tend to
3393
+ * appear before their parents so each node can push its scopes to its parent
3394
+ * within the same pass. For typical OpenAPI specs (components declared after
3395
+ * paths) $ref targets also appear before $ref sources in this order, meaning
3396
+ * both tree propagation and $ref propagation usually converge in a single pass.
3362
3397
  *
3363
- * Whenever a node's scopes change, all dependents are notified:
3364
- * - Its parent (if any)
3365
- * - All nodes that reference it via $ref (reverse dependencies)
3366
- * - Combinator parents (allOf/anyOf/oneOf) if applicable
3398
+ * The outer `while (changed)` loop guarantees correctness for any ordering:
3399
+ * it re-runs until no new scope values were added anywhere. Because scopes
3400
+ * can only grow (at most 3 values: 'normal', 'read', 'write'), the loop
3401
+ * terminates in at most a handful of passes even for pathological specs.
3367
3402
  *
3368
- * @param graph - The Graph structure containing nodes, dependencies, and reverseNodeDependencies.
3403
+ * @param graph - The Graph structure containing nodes and dependencies.
3369
3404
  */
3370
3405
  const propagateScopes = (graph) => {
3371
- const worklist = new Set(Array.from(graph.nodes.entries()).filter(([, nodeInfo]) => nodeInfo.scopes && nodeInfo.scopes.size > 0).map(([pointer]) => pointer));
3372
- /**
3373
- * Notifies all dependents of a node that its scopes may have changed.
3374
- * Dependents include:
3375
- * - The parent node (if any)
3376
- * - All nodes that reference this node via $ref (reverse dependencies)
3377
- * - Combinator parents (allOf/anyOf/oneOf) if this node is a combinator child
3378
- *
3379
- * @param pointer - The JSON pointer of the node whose dependents to notify
3380
- * @param nodeInfo - The NodeInfo of the node
3381
- * @param childPointer - (Optional) The pointer of the child, used to detect combinator parents
3382
- */
3383
- const notifyAllDependents = (pointer, nodeInfo, childPointer) => {
3384
- if (nodeInfo.parentPointer) worklist.add(nodeInfo.parentPointer);
3385
- const reverseNodeDependencies = graph.reverseNodeDependencies.get(pointer);
3386
- if (reverseNodeDependencies) for (const dependentPointer of reverseNodeDependencies) worklist.add(dependentPointer);
3387
- if (childPointer) {
3388
- const combinatorChildMatch = childPointer.match(/(.*)\/(allOf|anyOf|oneOf)\/\d+$/);
3389
- if (combinatorChildMatch) {
3390
- const combinatorParentPointer = combinatorChildMatch[1];
3391
- if (combinatorParentPointer) worklist.add(combinatorParentPointer);
3392
- }
3393
- }
3394
- };
3395
- /**
3396
- * Propagates scopes from a child node to its parent node.
3397
- * If the parent's scopes change, notifies all dependents.
3398
- *
3399
- * @param pointer - The parent node's pointer
3400
- * @param nodeInfo - The parent node's NodeInfo
3401
- * @param childPointer - The child node's pointer
3402
- */
3403
- const propagateChildScopes = (pointer, nodeInfo, childPointer) => {
3404
- if (!nodeInfo?.scopes) return;
3405
- const childInfo = graph.nodes.get(childPointer);
3406
- if (!childInfo?.scopes) return;
3407
- if (propagateScopesToNode(childInfo, nodeInfo)) notifyAllDependents(pointer, nodeInfo, childPointer);
3408
- };
3409
- while (worklist.size > 0) {
3410
- const pointer = worklist.values().next().value;
3411
- worklist.delete(pointer);
3412
- const nodeInfo = graph.nodes.get(pointer);
3413
- if (!nodeInfo) continue;
3414
- if (!nodeInfo.scopes) nodeInfo.scopes = /* @__PURE__ */ new Set();
3415
- const node = nodeInfo.node;
3416
- for (const [keyword, type] of childSchemaRelationships) {
3417
- if (!node || typeof node !== "object" || !(keyword in node)) continue;
3418
- const value = node[keyword];
3419
- if (type === "array" && value instanceof Array) for (let index = 0; index < value.length; index++) propagateChildScopes(pointer, nodeInfo, `${pointer}/${keyword}/${index}`);
3420
- else if (type === "objectMap" && typeof value === "object" && value !== null && !(value instanceof Array)) for (const key of Object.keys(value)) propagateChildScopes(pointer, nodeInfo, `${pointer}/${keyword}/${key}`);
3421
- else if (type === "single" && typeof value === "object" && value !== null) propagateChildScopes(pointer, nodeInfo, `${pointer}/${keyword}`);
3422
- else if (type === "singleOrArray") {
3423
- if (value instanceof Array) for (let index = 0; index < value.length; index++) propagateChildScopes(pointer, nodeInfo, `${pointer}/${keyword}/${index}`);
3424
- else if (typeof value === "object" && value !== null) propagateChildScopes(pointer, nodeInfo, `${pointer}/${keyword}`);
3406
+ const nodesBottomUp = [...graph.nodes].reverse();
3407
+ let changed = true;
3408
+ while (changed) {
3409
+ changed = false;
3410
+ for (const [pointer, nodeInfo] of nodesBottomUp) {
3411
+ const nodeDeps = graph.nodeDependencies.get(pointer);
3412
+ if (nodeDeps) for (const depPointer of nodeDeps) {
3413
+ const depInfo = graph.nodes.get(depPointer);
3414
+ if (depInfo?.scopes && propagateScopesToNode(depInfo, nodeInfo)) changed = true;
3425
3415
  }
3426
- }
3427
- const nodeDependencies = graph.nodeDependencies.get(pointer);
3428
- if (nodeDependencies) for (const depPointer of nodeDependencies) {
3429
- const depNode = graph.nodes.get(depPointer);
3430
- if (depNode?.scopes) {
3431
- if (propagateScopesToNode(depNode, nodeInfo)) notifyAllDependents(pointer, nodeInfo);
3432
- }
3433
- }
3434
- if (nodeInfo.parentPointer) {
3435
- const parentInfo = graph.nodes.get(nodeInfo.parentPointer);
3436
- if (parentInfo) {
3437
- if (propagateScopesToNode(nodeInfo, parentInfo)) notifyAllDependents(nodeInfo.parentPointer, parentInfo);
3416
+ if (nodeInfo.scopes && nodeInfo.parentPointer) {
3417
+ const parentInfo = graph.nodes.get(nodeInfo.parentPointer);
3418
+ if (parentInfo && propagateScopesToNode(nodeInfo, parentInfo)) changed = true;
3438
3419
  }
3439
3420
  }
3440
3421
  }
@@ -3459,52 +3440,29 @@ const propagateScopesToNode = (fromNodeInfo, toNodeInfo) => {
3459
3440
  return changed;
3460
3441
  };
3461
3442
  /**
3462
- * Seeds each node in the graph with its local access scope(s) based on its own properties.
3463
- * - 'read' if readOnly: true
3464
- * - 'write' if writeOnly: true
3465
- * - 'normal' if node is an object property
3466
- *
3467
- * Only non-array objects are considered for scope seeding.
3468
- *
3469
- * @param nodes - Map of JSON Pointer to NodeInfo.
3470
- */
3471
- const seedLocalScopes = (nodes) => {
3472
- for (const [pointer, nodeInfo] of nodes) {
3473
- const { node } = nodeInfo;
3474
- if (typeof node !== "object" || node === null || node instanceof Array) continue;
3475
- if ("readOnly" in node && node.readOnly === true) nodeInfo.scopes = new Set(["read"]);
3476
- else if ("writeOnly" in node && node.writeOnly === true) nodeInfo.scopes = new Set(["write"]);
3477
- else if (pointer.match(/\/properties\/[^/]+$/)) nodeInfo.scopes = new Set(["normal"]);
3478
- }
3479
- };
3480
- /**
3481
3443
  * Builds a graph of all nodes in an OpenAPI spec, indexed by normalized JSON Pointer,
3482
3444
  * and tracks all $ref dependencies and reverse dependencies between nodes.
3483
3445
  *
3484
3446
  * - All keys in the returned maps are normalized JSON Pointers (RFC 6901, always starting with '#').
3485
3447
  * - The `nodes` map allows fast lookup of any node and its parent/key context.
3486
3448
  * - The `dependencies` map records, for each node, the set of normalized pointers it references via $ref.
3487
- * - The `reverseNodeDependencies` map records, for each node, the set of nodes that reference it via $ref.
3488
3449
  * - After construction, all nodes will have their local and propagated scopes annotated.
3489
3450
  *
3490
3451
  * @param root The root object (e.g., the OpenAPI spec)
3491
3452
  * @returns An object with:
3492
3453
  * - nodes: Map from normalized JSON Pointer string to NodeInfo
3493
3454
  * - dependencies: Map from normalized JSON Pointer string to Set of referenced normalized JSON Pointers
3494
- * - reverseNodeDependencies: Map from normalized JSON Pointer string to Set of referencing normalized JSON Pointers
3495
3455
  */
3496
3456
  function buildGraph(root, logger) {
3497
3457
  const eventBuildGraph = logger.timeEvent("build-graph");
3498
3458
  const graph = {
3499
3459
  nodeDependencies: /* @__PURE__ */ new Map(),
3500
3460
  nodes: /* @__PURE__ */ new Map(),
3501
- reverseNodeDependencies: /* @__PURE__ */ new Map(),
3502
3461
  subtreeDependencies: /* @__PURE__ */ new Map(),
3503
3462
  transitiveDependencies: /* @__PURE__ */ new Map()
3504
3463
  };
3505
- const walk = ({ key, node, parentPointer, path }) => {
3464
+ const walk = ({ key, node, parentPointer, pointer }) => {
3506
3465
  if (typeof node !== "object" || node === null) return;
3507
- const pointer = pathToJsonPointer(path);
3508
3466
  let deprecated;
3509
3467
  let tags;
3510
3468
  if (typeof node === "object" && node !== null) {
@@ -3514,7 +3472,7 @@ function buildGraph(root, logger) {
3514
3472
  if (!graph.nodeDependencies.has(pointer)) graph.nodeDependencies.set(pointer, /* @__PURE__ */ new Set());
3515
3473
  graph.nodeDependencies.get(pointer).add(refPointer);
3516
3474
  }
3517
- if ("tags" in node && node.tags instanceof Array) tags = new Set(node.tags.filter((tag) => typeof tag === "string"));
3475
+ if ("tags" in node && Array.isArray(node.tags)) tags = new Set(node.tags.filter((tag) => typeof tag === "string"));
3518
3476
  }
3519
3477
  graph.nodes.set(pointer, {
3520
3478
  deprecated,
@@ -3523,24 +3481,24 @@ function buildGraph(root, logger) {
3523
3481
  parentPointer,
3524
3482
  tags
3525
3483
  });
3526
- if (node instanceof Array) node.forEach((item, index) => walk({
3484
+ if (Array.isArray(node)) node.forEach((item, index) => walk({
3527
3485
  key: index,
3528
3486
  node: item,
3529
3487
  parentPointer: pointer,
3530
- path: [...path, index]
3488
+ pointer: pointer + "/" + encodeJsonPointerSegment(index)
3531
3489
  }));
3532
3490
  else for (const [childKey, value] of Object.entries(node)) walk({
3533
3491
  key: childKey,
3534
3492
  node: value,
3535
3493
  parentPointer: pointer,
3536
- path: [...path, childKey]
3494
+ pointer: pointer + "/" + encodeJsonPointerSegment(childKey)
3537
3495
  });
3538
3496
  };
3539
3497
  walk({
3540
3498
  key: null,
3541
3499
  node: root,
3542
3500
  parentPointer: null,
3543
- path: []
3501
+ pointer: "#"
3544
3502
  });
3545
3503
  const cache = {
3546
3504
  parentToChildren: /* @__PURE__ */ new Map(),
@@ -3549,15 +3507,25 @@ function buildGraph(root, logger) {
3549
3507
  };
3550
3508
  for (const [pointer, nodeInfo] of graph.nodes) {
3551
3509
  const parent = nodeInfo.parentPointer;
3552
- if (!parent) continue;
3553
- if (!cache.parentToChildren.has(parent)) cache.parentToChildren.set(parent, []);
3554
- cache.parentToChildren.get(parent).push(pointer);
3555
- }
3556
- for (const [pointerFrom, pointers] of graph.nodeDependencies) for (const pointerTo of pointers) {
3557
- if (!graph.reverseNodeDependencies.has(pointerTo)) graph.reverseNodeDependencies.set(pointerTo, /* @__PURE__ */ new Set());
3558
- graph.reverseNodeDependencies.get(pointerTo).add(pointerFrom);
3510
+ if (parent) {
3511
+ let arr = cache.parentToChildren.get(parent);
3512
+ if (!arr) {
3513
+ arr = [];
3514
+ cache.parentToChildren.set(parent, arr);
3515
+ }
3516
+ arr.push(pointer);
3517
+ }
3518
+ const { node } = nodeInfo;
3519
+ if (typeof node === "object" && node !== null && !Array.isArray(node)) if ("readOnly" in node && node.readOnly === true) nodeInfo.scopes = new Set(["read"]);
3520
+ else if ("writeOnly" in node && node.writeOnly === true) nodeInfo.scopes = new Set(["write"]);
3521
+ else {
3522
+ const lastSlash = pointer.lastIndexOf("/");
3523
+ if (lastSlash > 0) {
3524
+ const prevSlash = pointer.lastIndexOf("/", lastSlash - 1);
3525
+ if (prevSlash >= 0 && pointer.slice(prevSlash + 1, lastSlash) === "properties") nodeInfo.scopes = new Set(["normal"]);
3526
+ }
3527
+ }
3559
3528
  }
3560
- seedLocalScopes(graph.nodes);
3561
3529
  propagateScopes(graph);
3562
3530
  annotateChildScopes(graph.nodes);
3563
3531
  for (const pointer of graph.nodes.keys()) {
@@ -4616,7 +4584,7 @@ function parseRef$2({ context, schema, state }) {
4616
4584
  return irSchema;
4617
4585
  }
4618
4586
  }
4619
- irSchema.$ref = decodeURI(schema.$ref);
4587
+ irSchema.$ref = schema.$ref;
4620
4588
  irSchema.$ref = irSchema.$ref.replace(/#\/definitions\/([^/]+)/g, "#/components/schemas/$1");
4621
4589
  if (!state.circularReferenceTracker.has(schema.$ref)) {
4622
4590
  const refSchema = context.resolveRef(schema.$ref);
@@ -5872,6 +5840,7 @@ function parseAnyOf$1({ context, schema, state }) {
5872
5840
  logicalOperator: "and"
5873
5841
  };
5874
5842
  }
5843
+ if (schema.discriminator && irSchema.logicalOperator === "or") irSchema.discriminator = { propertyName: schema.discriminator.propertyName };
5875
5844
  return irSchema;
5876
5845
  }
5877
5846
  function parseEnum$1({ context, schema, state }) {
@@ -5964,6 +5933,7 @@ function parseOneOf$1({ context, schema, state }) {
5964
5933
  logicalOperator: "and"
5965
5934
  };
5966
5935
  }
5936
+ if (schema.discriminator && irSchema.logicalOperator === "or") irSchema.discriminator = { propertyName: schema.discriminator.propertyName };
5967
5937
  return irSchema;
5968
5938
  }
5969
5939
  function parseRef$1({ context, schema, state }) {
@@ -5982,7 +5952,7 @@ function parseRef$1({ context, schema, state }) {
5982
5952
  }
5983
5953
  }
5984
5954
  const irSchema = {};
5985
- irSchema.$ref = decodeURI(schema.$ref);
5955
+ irSchema.$ref = schema.$ref;
5986
5956
  if (!state.circularReferenceTracker.has(schema.$ref)) {
5987
5957
  const refSchema = context.resolveRef(schema.$ref);
5988
5958
  const originalRef = state.$ref;
@@ -7264,6 +7234,7 @@ function parseAnyOf({ context, schema, state }) {
7264
7234
  logicalOperator: "and"
7265
7235
  };
7266
7236
  }
7237
+ if (schema.discriminator && irSchema.logicalOperator === "or") irSchema.discriminator = { propertyName: schema.discriminator.propertyName };
7267
7238
  return irSchema;
7268
7239
  }
7269
7240
  function parseEnum({ context, schema, state }) {
@@ -7359,6 +7330,7 @@ function parseOneOf({ context, schema, state }) {
7359
7330
  logicalOperator: "and"
7360
7331
  };
7361
7332
  }
7333
+ if (schema.discriminator && irSchema.logicalOperator === "or") irSchema.discriminator = { propertyName: schema.discriminator.propertyName };
7362
7334
  return irSchema;
7363
7335
  }
7364
7336
  function parseRef({ context, schema, state }) {
@@ -7382,7 +7354,7 @@ function parseRef({ context, schema, state }) {
7382
7354
  schema
7383
7355
  });
7384
7356
  const irRefSchema = {};
7385
- irRefSchema.$ref = decodeURI(schema.$ref);
7357
+ irRefSchema.$ref = schema.$ref;
7386
7358
  if (!state.circularReferenceTracker.has(schema.$ref)) {
7387
7359
  const refSchema = context.resolveRef(schema.$ref);
7388
7360
  const originalRef = state.$ref;
@@ -8508,6 +8480,36 @@ async function patchOpenApiSpec({ patchOptions, spec: _spec }) {
8508
8480
  }
8509
8481
  }
8510
8482
  //#endregion
8483
+ //#region src/plugins/duplicate.ts
8484
+ function stableStringify(value) {
8485
+ return JSON.stringify(value, (_, v) => {
8486
+ if (typeof v === "function") return `[function:${v.toString()}]`;
8487
+ if (v && typeof v === "object" && !Array.isArray(v)) return Object.fromEntries(Object.entries(v).sort(([a], [b]) => a.localeCompare(b)));
8488
+ return v;
8489
+ });
8490
+ }
8491
+ function normalizePluginEntry(plugin) {
8492
+ if (typeof plugin === "string") return {
8493
+ name: plugin,
8494
+ serialized: "{}"
8495
+ };
8496
+ const { name, ...config } = plugin;
8497
+ return {
8498
+ name,
8499
+ serialized: stableStringify(config)
8500
+ };
8501
+ }
8502
+ function warnOnConflictingDuplicatePlugins(plugins) {
8503
+ const seen = /* @__PURE__ */ new Map();
8504
+ for (const plugin of plugins) {
8505
+ const { name, serialized } = normalizePluginEntry(plugin);
8506
+ if (!name) continue;
8507
+ const previous = seen.get(name);
8508
+ if (previous !== void 0 && previous !== serialized) log.warn(`Plugin "${name}" is configured multiple times. Only the last instance will take effect.`);
8509
+ seen.set(name, serialized);
8510
+ }
8511
+ }
8512
+ //#endregion
8511
8513
  //#region src/plugins/shared/utils/config.ts
8512
8514
  const definePluginConfig = (defaultConfig) => (userConfig) => ({
8513
8515
  ...defaultConfig,
@@ -8711,9 +8713,9 @@ function pathToName(path, options) {
8711
8713
  } else if (STRUCTURAL_SUFFIX[segment]) names.push(STRUCTURAL_SUFFIX[segment]);
8712
8714
  index++;
8713
8715
  }
8714
- return decodeURI(names.join("-"));
8716
+ return names.join("-");
8715
8717
  }
8716
8718
  //#endregion
8717
- export { ConfigError, ConfigValidationError, Context, HeyApiError, InputError, IntentContext, JobError, MinHeap, OperationPath, OperationStrategy, PluginInstance, addItemsToSchema, applyNaming, buildGraph, buildSymbolIn, checkNodeVersion, childContext, compileInputPath, createOperationKey, createSchemaProcessor, createSchemaWalker, debugTools, deduplicateSchema, defaultPaginationKeywords, definePluginConfig, dependencyFactory, encodeJsonPointerSegment, ensureDirSync, escapeComment, findPackageJson, findTsConfigPath, getInput, getInputError, getLogs, getParser, getSpec, hasOperationDataRequired, hasParameterGroupObjectRequired, hasParametersObjectRequired, heyApiRegistryBaseUrl, inputToApiRegistry, isEnvironment, isTopLevelComponent, jsonPointerToPath, loadPackageJson, logCrashReport, logInputPaths, mappers, normalizeJsonPointer, openGitHubIssueWithCrashReport, operationPagination, operationResponsesMap, outputHeaderToPrefix, parameterWithPagination, parseOpenApiSpec, parseUrl, parseV2_0_X, parseV3_0_X, parseV3_1_X, patchOpenApiSpec, pathToJsonPointer, pathToName, postprocessOutput, printCliIntro, printCrashReport, refToName, requestValidatorLayers, resolveNaming, resolveRef, resolveSource, resolveValidatorLayer, satisfies, shouldReportCrash, statusCodeToGroup, toCase, utils, valueToObject };
8719
+ export { ConfigError, ConfigValidationError, Context, HeyApiError, InputError, IntentContext, JobError, MinHeap, OperationPath, OperationStrategy, PluginInstance, addItemsToSchema, applyNaming, buildGraph, buildSymbolIn, checkNodeVersion, childContext, compileInputPath, createOperationKey, createSchemaProcessor, createSchemaWalker, debugTools, deduplicateSchema, defaultPaginationKeywords, definePluginConfig, dependencyFactory, encodeJsonPointerSegment, ensureDirSync, escapeComment, findPackageJson, findTsConfigPath, getInput, getInputError, getLogs, getParser, getSpec, hasOperationDataRequired, hasParameterGroupObjectRequired, hasParametersObjectRequired, heyApiRegistryBaseUrl, inputToApiRegistry, isEnvironment, isTopLevelComponent, jsonPointerToPath, loadPackageJson, logCrashReport, logInputPaths, mappers, normalizeJsonPointer, openGitHubIssueWithCrashReport, operationPagination, operationResponsesMap, outputHeaderToPrefix, parameterWithPagination, parseOpenApiSpec, parseUrl, parseV2_0_X, parseV3_0_X, parseV3_1_X, patchOpenApiSpec, pathToJsonPointer, pathToName, postprocessOutput, printCliIntro, printCrashReport, refToName, requestValidatorLayers, resolveNaming, resolveRef, resolveSource, resolveValidatorLayer, satisfies, shouldReportCrash, statusCodeToGroup, toCase, utils, valueToObject, warnOnConflictingDuplicatePlugins };
8718
8720
 
8719
8721
  //# sourceMappingURL=index.mjs.map