@apiquest/fracture 1.0.2 → 1.0.4

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.
Files changed (168) hide show
  1. package/README.md +119 -0
  2. package/bin/cli.js +2 -2
  3. package/dist/CollectionRunner.js +3 -3
  4. package/dist/ScriptEngine.js +4 -4
  5. package/dist/cli/plugin-commands.d.ts.map +1 -1
  6. package/dist/cli/plugin-commands.js +2 -1
  7. package/dist/cli/plugin-commands.js.map +1 -1
  8. package/package.json +55 -50
  9. package/src/CollectionAnalyzer.ts +102 -102
  10. package/src/CollectionRunner.ts +1423 -1423
  11. package/src/CollectionRunner.types.ts +9 -9
  12. package/src/CollectionValidator.ts +289 -289
  13. package/src/ConsoleReporter.ts +143 -143
  14. package/src/CookieJar.ts +258 -258
  15. package/src/DagScheduler.ts +439 -439
  16. package/src/Logger.ts +85 -85
  17. package/src/PluginLoader.ts +126 -126
  18. package/src/PluginManager.ts +208 -208
  19. package/src/PluginResolver.ts +154 -154
  20. package/src/QuestAPI.ts +764 -764
  21. package/src/QuestAPI.types.ts +33 -33
  22. package/src/QuestTestAPI.ts +164 -164
  23. package/src/RequestFilter.ts +224 -224
  24. package/src/ScriptEngine.ts +219 -219
  25. package/src/ScriptValidator.ts +428 -428
  26. package/src/TaskGraph.ts +598 -598
  27. package/src/TestCounter.ts +109 -109
  28. package/src/VariableResolver.ts +114 -114
  29. package/src/cli/index.ts +480 -480
  30. package/src/cli/plugin-commands.ts +342 -341
  31. package/src/cli/plugin-discovery.ts +44 -44
  32. package/src/index.ts +24 -24
  33. package/src/utils.ts +52 -52
  34. package/tsconfig.json +20 -20
  35. package/tsconfig.test.json +5 -5
  36. package/vitest.config.ts +22 -22
  37. package/dist/ExecutionTree.d.ts +0 -77
  38. package/dist/ExecutionTree.d.ts.map +0 -1
  39. package/dist/ExecutionTree.js +0 -265
  40. package/dist/ExecutionTree.js.map +0 -1
  41. package/dist/fracture/src/CollectionAnalyzer.d.ts +0 -17
  42. package/dist/fracture/src/CollectionAnalyzer.d.ts.map +0 -1
  43. package/dist/fracture/src/CollectionAnalyzer.js +0 -70
  44. package/dist/fracture/src/CollectionAnalyzer.js.map +0 -1
  45. package/dist/fracture/src/CollectionRunner.d.ts +0 -39
  46. package/dist/fracture/src/CollectionRunner.d.ts.map +0 -1
  47. package/dist/fracture/src/CollectionRunner.js +0 -802
  48. package/dist/fracture/src/CollectionRunner.js.map +0 -1
  49. package/dist/fracture/src/CollectionRunner.types.d.ts +0 -8
  50. package/dist/fracture/src/CollectionRunner.types.d.ts.map +0 -1
  51. package/dist/fracture/src/CollectionRunner.types.js +0 -2
  52. package/dist/fracture/src/CollectionRunner.types.js.map +0 -1
  53. package/dist/fracture/src/CollectionValidator.d.ts +0 -14
  54. package/dist/fracture/src/CollectionValidator.d.ts.map +0 -1
  55. package/dist/fracture/src/CollectionValidator.js +0 -145
  56. package/dist/fracture/src/CollectionValidator.js.map +0 -1
  57. package/dist/fracture/src/ConsoleReporter.d.ts +0 -24
  58. package/dist/fracture/src/ConsoleReporter.d.ts.map +0 -1
  59. package/dist/fracture/src/ConsoleReporter.js +0 -123
  60. package/dist/fracture/src/ConsoleReporter.js.map +0 -1
  61. package/dist/fracture/src/CookieJar.d.ts +0 -70
  62. package/dist/fracture/src/CookieJar.d.ts.map +0 -1
  63. package/dist/fracture/src/CookieJar.js +0 -233
  64. package/dist/fracture/src/CookieJar.js.map +0 -1
  65. package/dist/fracture/src/ExecutionTree.d.ts +0 -77
  66. package/dist/fracture/src/ExecutionTree.d.ts.map +0 -1
  67. package/dist/fracture/src/ExecutionTree.js +0 -258
  68. package/dist/fracture/src/ExecutionTree.js.map +0 -1
  69. package/dist/fracture/src/Logger.d.ts +0 -25
  70. package/dist/fracture/src/Logger.d.ts.map +0 -1
  71. package/dist/fracture/src/Logger.js +0 -78
  72. package/dist/fracture/src/Logger.js.map +0 -1
  73. package/dist/fracture/src/PluginLoader.d.ts +0 -23
  74. package/dist/fracture/src/PluginLoader.d.ts.map +0 -1
  75. package/dist/fracture/src/PluginLoader.js +0 -102
  76. package/dist/fracture/src/PluginLoader.js.map +0 -1
  77. package/dist/fracture/src/PluginManager.d.ts +0 -64
  78. package/dist/fracture/src/PluginManager.d.ts.map +0 -1
  79. package/dist/fracture/src/PluginManager.js +0 -162
  80. package/dist/fracture/src/PluginManager.js.map +0 -1
  81. package/dist/fracture/src/PluginResolver.d.ts +0 -35
  82. package/dist/fracture/src/PluginResolver.d.ts.map +0 -1
  83. package/dist/fracture/src/PluginResolver.js +0 -128
  84. package/dist/fracture/src/PluginResolver.js.map +0 -1
  85. package/dist/fracture/src/QuestAPI.d.ts +0 -9
  86. package/dist/fracture/src/QuestAPI.d.ts.map +0 -1
  87. package/dist/fracture/src/QuestAPI.js +0 -679
  88. package/dist/fracture/src/QuestAPI.js.map +0 -1
  89. package/dist/fracture/src/QuestAPI.types.d.ts +0 -35
  90. package/dist/fracture/src/QuestAPI.types.d.ts.map +0 -1
  91. package/dist/fracture/src/QuestAPI.types.js +0 -3
  92. package/dist/fracture/src/QuestAPI.types.js.map +0 -1
  93. package/dist/fracture/src/QuestTestAPI.d.ts +0 -12
  94. package/dist/fracture/src/QuestTestAPI.d.ts.map +0 -1
  95. package/dist/fracture/src/QuestTestAPI.js +0 -133
  96. package/dist/fracture/src/QuestTestAPI.js.map +0 -1
  97. package/dist/fracture/src/ScriptEngine.d.ts +0 -21
  98. package/dist/fracture/src/ScriptEngine.d.ts.map +0 -1
  99. package/dist/fracture/src/ScriptEngine.js +0 -183
  100. package/dist/fracture/src/ScriptEngine.js.map +0 -1
  101. package/dist/fracture/src/ScriptValidator.d.ts +0 -68
  102. package/dist/fracture/src/ScriptValidator.d.ts.map +0 -1
  103. package/dist/fracture/src/ScriptValidator.js +0 -351
  104. package/dist/fracture/src/ScriptValidator.js.map +0 -1
  105. package/dist/fracture/src/TestCounter.d.ts +0 -18
  106. package/dist/fracture/src/TestCounter.d.ts.map +0 -1
  107. package/dist/fracture/src/TestCounter.js +0 -82
  108. package/dist/fracture/src/TestCounter.js.map +0 -1
  109. package/dist/fracture/src/VariableResolver.d.ts +0 -20
  110. package/dist/fracture/src/VariableResolver.d.ts.map +0 -1
  111. package/dist/fracture/src/VariableResolver.js +0 -100
  112. package/dist/fracture/src/VariableResolver.js.map +0 -1
  113. package/dist/fracture/src/cli/index.d.ts +0 -3
  114. package/dist/fracture/src/cli/index.d.ts.map +0 -1
  115. package/dist/fracture/src/cli/index.js +0 -347
  116. package/dist/fracture/src/cli/index.js.map +0 -1
  117. package/dist/fracture/src/cli/plugin-commands.d.ts +0 -6
  118. package/dist/fracture/src/cli/plugin-commands.d.ts.map +0 -1
  119. package/dist/fracture/src/cli/plugin-commands.js +0 -263
  120. package/dist/fracture/src/cli/plugin-commands.js.map +0 -1
  121. package/dist/fracture/src/cli/plugin-discovery.d.ts +0 -11
  122. package/dist/fracture/src/cli/plugin-discovery.d.ts.map +0 -1
  123. package/dist/fracture/src/cli/plugin-discovery.js +0 -64
  124. package/dist/fracture/src/cli/plugin-discovery.js.map +0 -1
  125. package/dist/fracture/src/index.d.ts +0 -13
  126. package/dist/fracture/src/index.d.ts.map +0 -1
  127. package/dist/fracture/src/index.js +0 -17
  128. package/dist/fracture/src/index.js.map +0 -1
  129. package/dist/fracture/src/utils.d.ts +0 -28
  130. package/dist/fracture/src/utils.d.ts.map +0 -1
  131. package/dist/fracture/src/utils.js +0 -48
  132. package/dist/fracture/src/utils.js.map +0 -1
  133. package/dist/plugin-auth/src/apikey-auth.d.ts +0 -3
  134. package/dist/plugin-auth/src/apikey-auth.d.ts.map +0 -1
  135. package/dist/plugin-auth/src/apikey-auth.js +0 -73
  136. package/dist/plugin-auth/src/apikey-auth.js.map +0 -1
  137. package/dist/plugin-auth/src/basic-auth.d.ts +0 -3
  138. package/dist/plugin-auth/src/basic-auth.d.ts.map +0 -1
  139. package/dist/plugin-auth/src/basic-auth.js +0 -61
  140. package/dist/plugin-auth/src/basic-auth.js.map +0 -1
  141. package/dist/plugin-auth/src/bearer-auth.d.ts +0 -3
  142. package/dist/plugin-auth/src/bearer-auth.d.ts.map +0 -1
  143. package/dist/plugin-auth/src/bearer-auth.js +0 -49
  144. package/dist/plugin-auth/src/bearer-auth.js.map +0 -1
  145. package/dist/plugin-auth/src/helpers.d.ts +0 -3
  146. package/dist/plugin-auth/src/helpers.d.ts.map +0 -1
  147. package/dist/plugin-auth/src/helpers.js +0 -8
  148. package/dist/plugin-auth/src/helpers.js.map +0 -1
  149. package/dist/plugin-auth/src/index.d.ts +0 -10
  150. package/dist/plugin-auth/src/index.d.ts.map +0 -1
  151. package/dist/plugin-auth/src/index.js +0 -25
  152. package/dist/plugin-auth/src/index.js.map +0 -1
  153. package/dist/plugin-auth/src/oauth2-auth.d.ts +0 -35
  154. package/dist/plugin-auth/src/oauth2-auth.d.ts.map +0 -1
  155. package/dist/plugin-auth/src/oauth2-auth.js +0 -266
  156. package/dist/plugin-auth/src/oauth2-auth.js.map +0 -1
  157. package/dist/plugin-http/src/index.d.ts +0 -4
  158. package/dist/plugin-http/src/index.d.ts.map +0 -1
  159. package/dist/plugin-http/src/index.js +0 -266
  160. package/dist/plugin-http/src/index.js.map +0 -1
  161. package/dist/plugin-vault-file/src/index.d.ts +0 -67
  162. package/dist/plugin-vault-file/src/index.d.ts.map +0 -1
  163. package/dist/plugin-vault-file/src/index.js +0 -171
  164. package/dist/plugin-vault-file/src/index.js.map +0 -1
  165. package/dist/types.d.ts +0 -374
  166. package/dist/types.d.ts.map +0 -1
  167. package/dist/types.js +0 -13
  168. package/dist/types.js.map +0 -1
@@ -1,224 +1,224 @@
1
- import type { Collection, CollectionItem, Folder, Request, PathType } from '@apiquest/types';
2
-
3
- export interface FilterOptions {
4
- filter?: string;
5
- excludeDeps?: boolean;
6
- pruneEmptyFolders?: boolean; // Default: true
7
- }
8
-
9
- interface ItemWithPath {
10
- item: CollectionItem;
11
- path: PathType;
12
- }
13
-
14
- export class RequestFilter {
15
- /**
16
- * Filter collection removing non-matching requests and empty folders
17
- * Returns filtered copy or original if no filtering
18
- */
19
- static filterCollection(collection: Collection, options: FilterOptions): Collection {
20
- if (options.filter === undefined) {
21
- return collection;
22
- }
23
-
24
- const filterSet = this.getFilterSet(collection, options);
25
- if (filterSet === null) {
26
- return collection;
27
- }
28
-
29
- const pruneEmpty = options.pruneEmptyFolders !== false;
30
-
31
- // Clone collection and filter items
32
- const filtered: Collection = {
33
- ...collection,
34
- items: this.filterItems(collection.items, filterSet, pruneEmpty)
35
- };
36
-
37
- return filtered;
38
- }
39
-
40
- /**
41
- * Recursively filter items keeping only requests in filterSet
42
- * Optionally prune empty folders
43
- */
44
- private static filterItems(
45
- items: CollectionItem[],
46
- filterSet: Set<string>,
47
- pruneEmpty: boolean
48
- ): CollectionItem[] {
49
- const filtered: CollectionItem[] = [];
50
-
51
- for (const item of items) {
52
- if (item.type === 'request') {
53
- if (filterSet.has(item.id)) {
54
- filtered.push(item);
55
- }
56
- } else {
57
- // Folder: recursively filter children
58
- const filteredChildren = this.filterItems(item.items, filterSet, pruneEmpty);
59
-
60
- if (!pruneEmpty || filteredChildren.length > 0) {
61
- filtered.push({
62
- ...item,
63
- items: filteredChildren
64
- });
65
- }
66
- }
67
- }
68
-
69
- return filtered;
70
- }
71
-
72
- /**
73
- * Get set of request IDs to execute
74
- * Returns null if no filtering needed
75
- */
76
- private static getFilterSet(collection: Collection, options: FilterOptions): Set<string> | null {
77
- if (options.filter === undefined) {
78
- return null;
79
- }
80
-
81
- // Collect requests matching filter
82
- const matchingIds = new Set<string>();
83
- try {
84
- const filterRegex = new RegExp(options.filter);
85
-
86
- // Walk collection structure and match paths
87
- this.collectMatchingRequests(collection.items, 'collection:/', filterRegex, matchingIds);
88
- } catch (error) {
89
- // Invalid regex - no filtering
90
- return null;
91
- }
92
-
93
- if (options.excludeDeps !== true) {
94
- return this.includeDependencies(collection, matchingIds);
95
- }
96
-
97
- return matchingIds;
98
- }
99
-
100
- /**
101
- * Build path with proper type prefix (same logic as TaskGraph.buildPath)
102
- */
103
- private static buildPath(parent: string, name: string, type: 'folder' | 'request'): PathType {
104
- // If parent is collection:/
105
- if (parent === 'collection:/') {
106
- return `${type}:/${name}` as PathType;
107
- }
108
-
109
- // Remove type prefix from parent path
110
- const basePath = parent.replace(/^(folder|request):\//, '');
111
- return `${type}:/${basePath}/${name}` as PathType;
112
- }
113
-
114
- /**
115
- * Collect requests matching path filter (regex)
116
- * Matches against both folder and request paths
117
- * If a folder matches, all requests in that folder are included
118
- */
119
- private static collectMatchingRequests(
120
- items: CollectionItem[],
121
- parentPath: string,
122
- filterRegex: RegExp,
123
- result: Set<string>
124
- ): void {
125
- for (const item of items) {
126
- if (item.type === 'folder') {
127
- const folderPath = this.buildPath(parentPath, item.name, 'folder');
128
- const folderMatches = filterRegex.test(folderPath);
129
-
130
- if (folderMatches) {
131
- // Folder matches - include ALL requests in this folder
132
- this.collectAllRequests(item.items, result);
133
- }
134
-
135
- // Always recurse to children
136
- this.collectMatchingRequests(item.items, folderPath, filterRegex, result);
137
- } else {
138
- // Request
139
- const requestPath = this.buildPath(parentPath, item.name, 'request');
140
- const requestMatches = filterRegex.test(requestPath);
141
-
142
- if (requestMatches) {
143
- result.add(item.id);
144
- }
145
- }
146
- }
147
- }
148
-
149
- /**
150
- * Collect all requests from items (helper for when folder matches)
151
- */
152
- private static collectAllRequests(items: CollectionItem[], result: Set<string>): void {
153
- for (const item of items) {
154
- if (item.type === 'request') {
155
- result.add(item.id);
156
- } else {
157
- this.collectAllRequests(item.items, result);
158
- }
159
- }
160
- }
161
-
162
- /**
163
- * Include dependencies by walking collection and resolving dependsOn
164
- */
165
- private static includeDependencies(
166
- collection: Collection,
167
- matchingIds: Set<string>
168
- ): Set<string> {
169
- const result = new Set<string>();
170
- const depMap = new Map<string, string[]>();
171
-
172
- // Build dependency map
173
- this.buildDependencyMap(collection.items, depMap);
174
-
175
- // Resolve dependencies recursively
176
- for (const requestId of matchingIds) {
177
- this.resolveDependencies(requestId, depMap, result);
178
- }
179
-
180
- return result;
181
- }
182
-
183
- /**
184
- * Build map of request/folder ID -> dependencies
185
- */
186
- private static buildDependencyMap(
187
- items: CollectionItem[],
188
- depMap: Map<string, string[]>
189
- ): void {
190
- for (const item of items) {
191
- if (item.type === 'request') {
192
- depMap.set(item.id, item.dependsOn ?? []);
193
- } else {
194
- // Folders can also have dependencies
195
- const folder = item;
196
- const folderDeps = folder.dependsOn;
197
- if (folderDeps !== undefined && folderDeps.length > 0) {
198
- depMap.set(folder.id, folderDeps);
199
- }
200
-
201
- // Recurse to children
202
- this.buildDependencyMap(folder.items, depMap);
203
- }
204
- }
205
- }
206
-
207
- /**
208
- * Recursively resolve dependencies for a request/folder
209
- */
210
- private static resolveDependencies(
211
- itemId: string,
212
- depMap: Map<string, string[]>,
213
- result: Set<string>
214
- ): void {
215
- result.add(itemId);
216
-
217
- const deps = depMap.get(itemId) ?? [];
218
- for (const depId of deps) {
219
- if (!result.has(depId)) {
220
- this.resolveDependencies(depId, depMap, result);
221
- }
222
- }
223
- }
224
- }
1
+ import type { Collection, CollectionItem, Folder, Request, PathType } from '@apiquest/types';
2
+
3
+ export interface FilterOptions {
4
+ filter?: string;
5
+ excludeDeps?: boolean;
6
+ pruneEmptyFolders?: boolean; // Default: true
7
+ }
8
+
9
+ interface ItemWithPath {
10
+ item: CollectionItem;
11
+ path: PathType;
12
+ }
13
+
14
+ export class RequestFilter {
15
+ /**
16
+ * Filter collection removing non-matching requests and empty folders
17
+ * Returns filtered copy or original if no filtering
18
+ */
19
+ static filterCollection(collection: Collection, options: FilterOptions): Collection {
20
+ if (options.filter === undefined) {
21
+ return collection;
22
+ }
23
+
24
+ const filterSet = this.getFilterSet(collection, options);
25
+ if (filterSet === null) {
26
+ return collection;
27
+ }
28
+
29
+ const pruneEmpty = options.pruneEmptyFolders !== false;
30
+
31
+ // Clone collection and filter items
32
+ const filtered: Collection = {
33
+ ...collection,
34
+ items: this.filterItems(collection.items, filterSet, pruneEmpty)
35
+ };
36
+
37
+ return filtered;
38
+ }
39
+
40
+ /**
41
+ * Recursively filter items keeping only requests in filterSet
42
+ * Optionally prune empty folders
43
+ */
44
+ private static filterItems(
45
+ items: CollectionItem[],
46
+ filterSet: Set<string>,
47
+ pruneEmpty: boolean
48
+ ): CollectionItem[] {
49
+ const filtered: CollectionItem[] = [];
50
+
51
+ for (const item of items) {
52
+ if (item.type === 'request') {
53
+ if (filterSet.has(item.id)) {
54
+ filtered.push(item);
55
+ }
56
+ } else {
57
+ // Folder: recursively filter children
58
+ const filteredChildren = this.filterItems(item.items, filterSet, pruneEmpty);
59
+
60
+ if (!pruneEmpty || filteredChildren.length > 0) {
61
+ filtered.push({
62
+ ...item,
63
+ items: filteredChildren
64
+ });
65
+ }
66
+ }
67
+ }
68
+
69
+ return filtered;
70
+ }
71
+
72
+ /**
73
+ * Get set of request IDs to execute
74
+ * Returns null if no filtering needed
75
+ */
76
+ private static getFilterSet(collection: Collection, options: FilterOptions): Set<string> | null {
77
+ if (options.filter === undefined) {
78
+ return null;
79
+ }
80
+
81
+ // Collect requests matching filter
82
+ const matchingIds = new Set<string>();
83
+ try {
84
+ const filterRegex = new RegExp(options.filter);
85
+
86
+ // Walk collection structure and match paths
87
+ this.collectMatchingRequests(collection.items, 'collection:/', filterRegex, matchingIds);
88
+ } catch (error) {
89
+ // Invalid regex - no filtering
90
+ return null;
91
+ }
92
+
93
+ if (options.excludeDeps !== true) {
94
+ return this.includeDependencies(collection, matchingIds);
95
+ }
96
+
97
+ return matchingIds;
98
+ }
99
+
100
+ /**
101
+ * Build path with proper type prefix (same logic as TaskGraph.buildPath)
102
+ */
103
+ private static buildPath(parent: string, name: string, type: 'folder' | 'request'): PathType {
104
+ // If parent is collection:/
105
+ if (parent === 'collection:/') {
106
+ return `${type}:/${name}` as PathType;
107
+ }
108
+
109
+ // Remove type prefix from parent path
110
+ const basePath = parent.replace(/^(folder|request):\//, '');
111
+ return `${type}:/${basePath}/${name}` as PathType;
112
+ }
113
+
114
+ /**
115
+ * Collect requests matching path filter (regex)
116
+ * Matches against both folder and request paths
117
+ * If a folder matches, all requests in that folder are included
118
+ */
119
+ private static collectMatchingRequests(
120
+ items: CollectionItem[],
121
+ parentPath: string,
122
+ filterRegex: RegExp,
123
+ result: Set<string>
124
+ ): void {
125
+ for (const item of items) {
126
+ if (item.type === 'folder') {
127
+ const folderPath = this.buildPath(parentPath, item.name, 'folder');
128
+ const folderMatches = filterRegex.test(folderPath);
129
+
130
+ if (folderMatches) {
131
+ // Folder matches - include ALL requests in this folder
132
+ this.collectAllRequests(item.items, result);
133
+ }
134
+
135
+ // Always recurse to children
136
+ this.collectMatchingRequests(item.items, folderPath, filterRegex, result);
137
+ } else {
138
+ // Request
139
+ const requestPath = this.buildPath(parentPath, item.name, 'request');
140
+ const requestMatches = filterRegex.test(requestPath);
141
+
142
+ if (requestMatches) {
143
+ result.add(item.id);
144
+ }
145
+ }
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Collect all requests from items (helper for when folder matches)
151
+ */
152
+ private static collectAllRequests(items: CollectionItem[], result: Set<string>): void {
153
+ for (const item of items) {
154
+ if (item.type === 'request') {
155
+ result.add(item.id);
156
+ } else {
157
+ this.collectAllRequests(item.items, result);
158
+ }
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Include dependencies by walking collection and resolving dependsOn
164
+ */
165
+ private static includeDependencies(
166
+ collection: Collection,
167
+ matchingIds: Set<string>
168
+ ): Set<string> {
169
+ const result = new Set<string>();
170
+ const depMap = new Map<string, string[]>();
171
+
172
+ // Build dependency map
173
+ this.buildDependencyMap(collection.items, depMap);
174
+
175
+ // Resolve dependencies recursively
176
+ for (const requestId of matchingIds) {
177
+ this.resolveDependencies(requestId, depMap, result);
178
+ }
179
+
180
+ return result;
181
+ }
182
+
183
+ /**
184
+ * Build map of request/folder ID -> dependencies
185
+ */
186
+ private static buildDependencyMap(
187
+ items: CollectionItem[],
188
+ depMap: Map<string, string[]>
189
+ ): void {
190
+ for (const item of items) {
191
+ if (item.type === 'request') {
192
+ depMap.set(item.id, item.dependsOn ?? []);
193
+ } else {
194
+ // Folders can also have dependencies
195
+ const folder = item;
196
+ const folderDeps = folder.dependsOn;
197
+ if (folderDeps !== undefined && folderDeps.length > 0) {
198
+ depMap.set(folder.id, folderDeps);
199
+ }
200
+
201
+ // Recurse to children
202
+ this.buildDependencyMap(folder.items, depMap);
203
+ }
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Recursively resolve dependencies for a request/folder
209
+ */
210
+ private static resolveDependencies(
211
+ itemId: string,
212
+ depMap: Map<string, string[]>,
213
+ result: Set<string>
214
+ ): void {
215
+ result.add(itemId);
216
+
217
+ const deps = depMap.get(itemId) ?? [];
218
+ for (const depId of deps) {
219
+ if (!result.has(depId)) {
220
+ this.resolveDependencies(depId, depMap, result);
221
+ }
222
+ }
223
+ }
224
+ }