callman-core 1.34.0 → 1.36.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.
Files changed (266) hide show
  1. package/ai-packs/feature-policy.md +1 -6
  2. package/ai-packs/pm-api.md +246 -97
  3. package/ai-packs/sandbox-globals.md +6 -5
  4. package/dist/ai/prompts/fragments.d.ts.map +1 -1
  5. package/dist/ai/prompts/fragments.js +0 -1
  6. package/dist/ai/prompts/fragments.js.map +1 -1
  7. package/dist/ai/prompts/unified.js +1 -1
  8. package/dist/ai/prompts/unified.js.map +1 -1
  9. package/dist/ai/types.d.ts +1 -1
  10. package/dist/ai/types.d.ts.map +1 -1
  11. package/dist/ai/types.js +1 -1
  12. package/dist/ai/types.js.map +1 -1
  13. package/dist/index.d.ts +7 -2
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +5 -1
  16. package/dist/index.js.map +1 -1
  17. package/dist/runner/requestRunner.d.ts.map +1 -1
  18. package/dist/runner/requestRunner.js +339 -1
  19. package/dist/runner/requestRunner.js.map +1 -1
  20. package/dist/runner/runCollection.d.ts +1 -1
  21. package/dist/runner/runCollection.d.ts.map +1 -1
  22. package/dist/runner/runCollection.js +60 -7
  23. package/dist/runner/runCollection.js.map +1 -1
  24. package/dist/runner/runRequestWithDataset.d.ts.map +1 -1
  25. package/dist/runner/runRequestWithDataset.js +5 -0
  26. package/dist/runner/runRequestWithDataset.js.map +1 -1
  27. package/dist/scenario-runner/adapters/request-executor.d.ts.map +1 -1
  28. package/dist/scenario-runner/adapters/request-executor.js +7 -0
  29. package/dist/scenario-runner/adapters/request-executor.js.map +1 -1
  30. package/dist/scenario-runner/runScenario.d.ts.map +1 -1
  31. package/dist/scenario-runner/runScenario.js +4 -0
  32. package/dist/scenario-runner/runScenario.js.map +1 -1
  33. package/dist/scenario-runner/types.d.ts +11 -0
  34. package/dist/scenario-runner/types.d.ts.map +1 -1
  35. package/dist/script/chain.d.ts +56 -0
  36. package/dist/script/chain.d.ts.map +1 -0
  37. package/dist/script/chain.js +121 -0
  38. package/dist/script/chain.js.map +1 -0
  39. package/dist/script/errors.d.ts.map +1 -1
  40. package/dist/script/errors.js +6 -4
  41. package/dist/script/errors.js.map +1 -1
  42. package/dist/script/executor.d.ts.map +1 -1
  43. package/dist/script/executor.js +298 -64
  44. package/dist/script/executor.js.map +1 -1
  45. package/dist/script/features/collectionVariables.d.ts +4 -4
  46. package/dist/script/features/collectionVariables.d.ts.map +1 -1
  47. package/dist/script/features/collectionVariables.js +20 -11
  48. package/dist/script/features/collectionVariables.js.map +1 -1
  49. package/dist/script/features/environment.d.ts +4 -4
  50. package/dist/script/features/environment.d.ts.map +1 -1
  51. package/dist/script/features/environment.js +20 -11
  52. package/dist/script/features/environment.js.map +1 -1
  53. package/dist/script/features/feature-policy.d.ts +1 -1
  54. package/dist/script/features/feature-policy.d.ts.map +1 -1
  55. package/dist/script/features/feature-policy.js +2 -2
  56. package/dist/script/features/feature-policy.js.map +1 -1
  57. package/dist/script/features/info.d.ts.map +1 -1
  58. package/dist/script/features/info.js +6 -3
  59. package/dist/script/features/info.js.map +1 -1
  60. package/dist/script/features/iterationData.d.ts +12 -0
  61. package/dist/script/features/iterationData.d.ts.map +1 -0
  62. package/dist/script/features/iterationData.js +28 -0
  63. package/dist/script/features/iterationData.js.map +1 -0
  64. package/dist/script/features/legacyPostman.d.ts +14 -0
  65. package/dist/script/features/legacyPostman.d.ts.map +1 -0
  66. package/dist/script/features/legacyPostman.js +106 -0
  67. package/dist/script/features/legacyPostman.js.map +1 -0
  68. package/dist/script/features/log.d.ts +35 -8
  69. package/dist/script/features/log.d.ts.map +1 -1
  70. package/dist/script/features/log.js +343 -27
  71. package/dist/script/features/log.js.map +1 -1
  72. package/dist/script/features/tests.d.ts +7 -8
  73. package/dist/script/features/tests.d.ts.map +1 -1
  74. package/dist/script/features/tests.js +90 -64
  75. package/dist/script/features/tests.js.map +1 -1
  76. package/dist/script/features/timers.d.ts +7 -1
  77. package/dist/script/features/timers.d.ts.map +1 -1
  78. package/dist/script/features/timers.js +17 -5
  79. package/dist/script/features/timers.js.map +1 -1
  80. package/dist/script/features/variables.d.ts +25 -4
  81. package/dist/script/features/variables.d.ts.map +1 -1
  82. package/dist/script/features/variables.js +26 -9
  83. package/dist/script/features/variables.js.map +1 -1
  84. package/dist/script/features/visualizer.d.ts +15 -0
  85. package/dist/script/features/visualizer.d.ts.map +1 -0
  86. package/dist/script/features/visualizer.js +52 -0
  87. package/dist/script/features/visualizer.js.map +1 -0
  88. package/dist/script/isolated-runner.d.ts +4 -1
  89. package/dist/script/isolated-runner.d.ts.map +1 -1
  90. package/dist/script/isolated-runner.js +26 -2
  91. package/dist/script/isolated-runner.js.map +1 -1
  92. package/dist/script/layers.d.ts +46 -0
  93. package/dist/script/layers.d.ts.map +1 -0
  94. package/dist/script/layers.js +52 -0
  95. package/dist/script/layers.js.map +1 -0
  96. package/dist/script/legacy.d.ts +6 -1
  97. package/dist/script/legacy.d.ts.map +1 -1
  98. package/dist/script/legacy.js +15 -5
  99. package/dist/script/legacy.js.map +1 -1
  100. package/dist/script/pm-api-declarations.d.ts +45 -0
  101. package/dist/script/pm-api-declarations.d.ts.map +1 -0
  102. package/dist/script/pm-api-declarations.js +1963 -0
  103. package/dist/script/pm-api-declarations.js.map +1 -0
  104. package/dist/script/pm-api-metadata.d.ts +3 -1
  105. package/dist/script/pm-api-metadata.d.ts.map +1 -1
  106. package/dist/script/pm-api-metadata.js +336 -106
  107. package/dist/script/pm-api-metadata.js.map +1 -1
  108. package/dist/script/pm-api-reference.d.ts.map +1 -1
  109. package/dist/script/pm-api-reference.js +32 -7
  110. package/dist/script/pm-api-reference.js.map +1 -1
  111. package/dist/script/postman/chaiCompat.d.ts +4 -0
  112. package/dist/script/postman/chaiCompat.d.ts.map +1 -0
  113. package/dist/script/postman/chaiCompat.js +176 -0
  114. package/dist/script/postman/chaiCompat.js.map +1 -0
  115. package/dist/script/postman/cookies.d.ts +36 -0
  116. package/dist/script/postman/cookies.d.ts.map +1 -0
  117. package/dist/script/postman/cookies.js +163 -0
  118. package/dist/script/postman/cookies.js.map +1 -0
  119. package/dist/script/postman/modules.d.ts +16 -0
  120. package/dist/script/postman/modules.d.ts.map +1 -0
  121. package/dist/script/postman/modules.js +114 -0
  122. package/dist/script/postman/modules.js.map +1 -0
  123. package/dist/script/postman/proxies.d.ts +13 -0
  124. package/dist/script/postman/proxies.d.ts.map +1 -0
  125. package/dist/script/postman/proxies.js +88 -0
  126. package/dist/script/postman/proxies.js.map +1 -0
  127. package/dist/script/postman/requestAdapter.d.ts +7 -0
  128. package/dist/script/postman/requestAdapter.d.ts.map +1 -0
  129. package/dist/script/postman/requestAdapter.js +223 -0
  130. package/dist/script/postman/requestAdapter.js.map +1 -0
  131. package/dist/script/postman/responseAdapter.d.ts +5 -0
  132. package/dist/script/postman/responseAdapter.d.ts.map +1 -0
  133. package/dist/script/postman/responseAdapter.js +78 -0
  134. package/dist/script/postman/responseAdapter.js.map +1 -0
  135. package/dist/script/postman/runtime.d.ts +22 -0
  136. package/dist/script/postman/runtime.d.ts.map +1 -0
  137. package/dist/script/postman/runtime.js +122 -0
  138. package/dist/script/postman/runtime.js.map +1 -0
  139. package/dist/script/scope.d.ts +5 -1
  140. package/dist/script/scope.d.ts.map +1 -1
  141. package/dist/script/scope.js +47 -5
  142. package/dist/script/scope.js.map +1 -1
  143. package/dist/script/types.d.ts +42 -14
  144. package/dist/script/types.d.ts.map +1 -1
  145. package/dist/script/worker-entry.js +9 -0
  146. package/dist/script/worker-entry.js.map +1 -1
  147. package/dist/script/worker-pool.d.ts +3 -1
  148. package/dist/script/worker-pool.d.ts.map +1 -1
  149. package/dist/script/worker-pool.js +23 -2
  150. package/dist/script/worker-pool.js.map +1 -1
  151. package/dist/templating/generator-metadata.d.ts +1 -0
  152. package/dist/templating/generator-metadata.d.ts.map +1 -1
  153. package/dist/templating/generator-metadata.js +14 -0
  154. package/dist/templating/generator-metadata.js.map +1 -1
  155. package/dist/templating/postman-dynamic-catalog.d.ts +7 -0
  156. package/dist/templating/postman-dynamic-catalog.d.ts.map +1 -0
  157. package/dist/templating/postman-dynamic-catalog.js +126 -0
  158. package/dist/templating/postman-dynamic-catalog.js.map +1 -0
  159. package/dist/templating/postman-generators.d.ts +9 -0
  160. package/dist/templating/postman-generators.d.ts.map +1 -1
  161. package/dist/templating/postman-generators.js +10 -1
  162. package/dist/templating/postman-generators.js.map +1 -1
  163. package/dist/types/index.d.ts +44 -2
  164. package/dist/types/index.d.ts.map +1 -1
  165. package/dist/variables/envStore.d.ts +3 -0
  166. package/dist/variables/envStore.d.ts.map +1 -1
  167. package/dist/variables/envStore.js +5 -0
  168. package/dist/variables/envStore.js.map +1 -1
  169. package/dist/variables/replaceIn.d.ts +10 -0
  170. package/dist/variables/replaceIn.d.ts.map +1 -0
  171. package/dist/variables/replaceIn.js +106 -0
  172. package/dist/variables/replaceIn.js.map +1 -0
  173. package/dist/variables/resolver.d.ts.map +1 -1
  174. package/dist/variables/resolver.js +16 -2
  175. package/dist/variables/resolver.js.map +1 -1
  176. package/dist-cjs/ai/prompts/fragments.js +0 -1
  177. package/dist-cjs/ai/prompts/fragments.js.map +1 -1
  178. package/dist-cjs/ai/prompts/unified.js +1 -1
  179. package/dist-cjs/ai/prompts/unified.js.map +1 -1
  180. package/dist-cjs/ai/types.js +1 -1
  181. package/dist-cjs/ai/types.js.map +1 -1
  182. package/dist-cjs/index.js +23 -5
  183. package/dist-cjs/index.js.map +1 -1
  184. package/dist-cjs/runner/requestRunner.js +339 -1
  185. package/dist-cjs/runner/requestRunner.js.map +1 -1
  186. package/dist-cjs/runner/runCollection.js +60 -7
  187. package/dist-cjs/runner/runCollection.js.map +1 -1
  188. package/dist-cjs/runner/runRequestWithDataset.js +5 -0
  189. package/dist-cjs/runner/runRequestWithDataset.js.map +1 -1
  190. package/dist-cjs/scenario-runner/adapters/request-executor.js +7 -0
  191. package/dist-cjs/scenario-runner/adapters/request-executor.js.map +1 -1
  192. package/dist-cjs/scenario-runner/runScenario.js +4 -0
  193. package/dist-cjs/scenario-runner/runScenario.js.map +1 -1
  194. package/dist-cjs/script/chain.js +126 -0
  195. package/dist-cjs/script/chain.js.map +1 -0
  196. package/dist-cjs/script/errors.js +6 -4
  197. package/dist-cjs/script/errors.js.map +1 -1
  198. package/dist-cjs/script/executor.js +298 -64
  199. package/dist-cjs/script/executor.js.map +1 -1
  200. package/dist-cjs/script/features/collectionVariables.js +20 -11
  201. package/dist-cjs/script/features/collectionVariables.js.map +1 -1
  202. package/dist-cjs/script/features/environment.js +20 -11
  203. package/dist-cjs/script/features/environment.js.map +1 -1
  204. package/dist-cjs/script/features/feature-policy.js +2 -2
  205. package/dist-cjs/script/features/feature-policy.js.map +1 -1
  206. package/dist-cjs/script/features/info.js +6 -3
  207. package/dist-cjs/script/features/info.js.map +1 -1
  208. package/dist-cjs/script/features/iterationData.js +32 -0
  209. package/dist-cjs/script/features/iterationData.js.map +1 -0
  210. package/dist-cjs/script/features/legacyPostman.js +112 -0
  211. package/dist-cjs/script/features/legacyPostman.js.map +1 -0
  212. package/dist-cjs/script/features/log.js +347 -28
  213. package/dist-cjs/script/features/log.js.map +1 -1
  214. package/dist-cjs/script/features/tests.js +90 -64
  215. package/dist-cjs/script/features/tests.js.map +1 -1
  216. package/dist-cjs/script/features/timers.js +17 -5
  217. package/dist-cjs/script/features/timers.js.map +1 -1
  218. package/dist-cjs/script/features/variables.js +26 -9
  219. package/dist-cjs/script/features/variables.js.map +1 -1
  220. package/dist-cjs/script/features/visualizer.js +56 -0
  221. package/dist-cjs/script/features/visualizer.js.map +1 -0
  222. package/dist-cjs/script/isolated-runner.js +26 -2
  223. package/dist-cjs/script/isolated-runner.js.map +1 -1
  224. package/dist-cjs/script/layers.js +57 -0
  225. package/dist-cjs/script/layers.js.map +1 -0
  226. package/dist-cjs/script/legacy.js +20 -6
  227. package/dist-cjs/script/legacy.js.map +1 -1
  228. package/dist-cjs/script/pm-api-declarations.js +1969 -0
  229. package/dist-cjs/script/pm-api-declarations.js.map +1 -0
  230. package/dist-cjs/script/pm-api-metadata.js +336 -106
  231. package/dist-cjs/script/pm-api-metadata.js.map +1 -1
  232. package/dist-cjs/script/pm-api-reference.js +32 -7
  233. package/dist-cjs/script/pm-api-reference.js.map +1 -1
  234. package/dist-cjs/script/postman/chaiCompat.js +181 -0
  235. package/dist-cjs/script/postman/chaiCompat.js.map +1 -0
  236. package/dist-cjs/script/postman/cookies.js +170 -0
  237. package/dist-cjs/script/postman/cookies.js.map +1 -0
  238. package/dist-cjs/script/postman/modules.js +123 -0
  239. package/dist-cjs/script/postman/modules.js.map +1 -0
  240. package/dist-cjs/script/postman/proxies.js +93 -0
  241. package/dist-cjs/script/postman/proxies.js.map +1 -0
  242. package/dist-cjs/script/postman/requestAdapter.js +227 -0
  243. package/dist-cjs/script/postman/requestAdapter.js.map +1 -0
  244. package/dist-cjs/script/postman/responseAdapter.js +83 -0
  245. package/dist-cjs/script/postman/responseAdapter.js.map +1 -0
  246. package/dist-cjs/script/postman/runtime.js +132 -0
  247. package/dist-cjs/script/postman/runtime.js.map +1 -0
  248. package/dist-cjs/script/scope.js +47 -5
  249. package/dist-cjs/script/scope.js.map +1 -1
  250. package/dist-cjs/script/worker-entry.js +9 -0
  251. package/dist-cjs/script/worker-entry.js.map +1 -1
  252. package/dist-cjs/script/worker-pool.js +23 -2
  253. package/dist-cjs/script/worker-pool.js.map +1 -1
  254. package/dist-cjs/templating/generator-metadata.js +15 -1
  255. package/dist-cjs/templating/generator-metadata.js.map +1 -1
  256. package/dist-cjs/templating/postman-dynamic-catalog.js +129 -0
  257. package/dist-cjs/templating/postman-dynamic-catalog.js.map +1 -0
  258. package/dist-cjs/templating/postman-generators.js +13 -3
  259. package/dist-cjs/templating/postman-generators.js.map +1 -1
  260. package/dist-cjs/variables/envStore.js +5 -0
  261. package/dist-cjs/variables/envStore.js.map +1 -1
  262. package/dist-cjs/variables/replaceIn.js +111 -0
  263. package/dist-cjs/variables/replaceIn.js.map +1 -0
  264. package/dist-cjs/variables/resolver.js +16 -2
  265. package/dist-cjs/variables/resolver.js.map +1 -1
  266. package/package.json +12 -2
@@ -4,7 +4,7 @@
4
4
  // Composes:
5
5
  // - sandbox (vm.createContext + scope Proxy + reserved globals)
6
6
  // - pm API (assertions + tests + env/globals + request/response + node + log)
7
- // - sandbox-D extensions (db, kafka, redis, random, counter, cryptojs, variables)
7
+ // - sandbox-D extensions (db, redis, random, counter, cryptojs, variables)
8
8
  // - timeout (vm-level + outer Promise race)
9
9
  // - error serialization (line/column attribution)
10
10
  //
@@ -13,13 +13,19 @@
13
13
  // distinguish "the sandbox itself blew up" should check `success === false`.
14
14
  import { executeRequest } from "../request/executeRequest.js";
15
15
  import { buildEnvironmentApi } from "./features/environment.js";
16
- import { createExpectApi } from "./features/assertions.js";
16
+ import { splitSetCookieHeader } from "./legacy.js";
17
+ import { buildLegacyGlobals, scriptOptsOutOfLegacy } from "./features/legacyPostman.js";
18
+ import { createSandboxRequire, preloadScriptModules } from "./postman/modules.js";
19
+ import { buildPmCookies, createMemoryCookieJar, parseSetCookieLine } from "./postman/cookies.js";
20
+ import { buildVisualizerFeature } from "./features/visualizer.js";
21
+ import { buildSdkRequestApi } from "./postman/requestAdapter.js";
22
+ import { attachRequestAssertions, buildSdkResponseApi } from "./postman/responseAdapter.js";
23
+ import { loadPostmanRuntime, scriptNeedsSdk } from "./postman/runtime.js";
17
24
  import { buildLabelGlobals, buildNodeApi } from "./features/node.js";
18
25
  import { buildRequestApi } from "./features/request.js";
19
26
  import { buildResponseApi } from "./features/response.js";
20
27
  import { buildTestCollector } from "./features/tests.js";
21
28
  import { createLogCollector, createSilentLogCollector, } from "./features/log.js";
22
- import { buildKafkaApi } from "./features/kafka.js";
23
29
  import { buildRedisApi } from "./features/redis.js";
24
30
  import { buildRandomApi } from "./features/random.js";
25
31
  import { buildCounterFeature } from "./features/counter.js";
@@ -28,6 +34,7 @@ import { buildVariablesApi } from "./features/variables.js";
28
34
  import { buildDbApi } from "./features/db.js";
29
35
  import { buildTimerFeature } from "./features/timers.js";
30
36
  import { buildCollectionVariablesApi } from "./features/collectionVariables.js";
37
+ import { buildIterationDataApi } from "./features/iterationData.js";
31
38
  import { buildInfoApi } from "./features/info.js";
32
39
  import { buildExecutionFeature } from "./features/execution.js";
33
40
  // Lodash is exposed unconditionally as the `_` global — Postman
@@ -40,26 +47,13 @@ import { toHostPromise, withAsyncTimeout } from "./timeout.js";
40
47
  const DEFAULT_TIMEOUT_MS = 15_000;
41
48
  const DEFAULT_FILENAME = "callman-script.js";
42
49
  const DEFAULT_STAGE = "script";
43
- const buildSandboxDFeatures = (capabilities, isInsideTestCallback, envValues, globalValues, responseTemplateRoot) => {
50
+ const buildSandboxDFeatures = (capabilities, isInsideTestCallback, envValues, globalValues, responseTemplateRoot, collectionValues, live) => {
44
51
  const extensions = capabilities.extensions ?? {};
45
52
  const featurePolicy = capabilities.featurePolicy;
46
53
  const pm = {};
47
54
  const scopeShortcuts = {};
48
55
  let counterSnapshotFn = null;
49
- if (extensions.kafka) {
50
- const kafkaEnabled = featurePolicy ? featurePolicy.kafka : true;
51
- if (kafkaEnabled) {
52
- const api = buildKafkaApi(extensions.kafka, capabilities.allowDeferredKafkaTests ?? false);
53
- pm.kafka = api;
54
- scopeShortcuts.kafka = api;
55
- }
56
- else {
57
- // Disabled kafka — replace with feature-policy guarded value
58
- // (handled at the test-collector level via DisabledFeatureDependencyError).
59
- // We still expose the binding so user scripts don't crash at scope lookup.
60
- void isInsideTestCallback;
61
- }
62
- }
56
+ let variablesApi = null;
63
57
  if (extensions.redis) {
64
58
  const api = buildRedisApi({
65
59
  capability: extensions.redis,
@@ -99,8 +93,16 @@ const buildSandboxDFeatures = (capabilities, isInsideTestCallback, envValues, gl
99
93
  // (top-level `variables` identifier) is only registered when the
100
94
  // extension was explicitly requested, to preserve the prior surface.
101
95
  {
102
- const api = buildVariablesApi(envValues, globalValues);
96
+ const api = buildVariablesApi(envValues, globalValues, {
97
+ collectionVariables: collectionValues,
98
+ ...(live ? { live } : {}),
99
+ ...(capabilities.iterationData ? { iterationData: capabilities.iterationData.values } : {}),
100
+ ...(capabilities.localVariables ? { local: capabilities.localVariables.values } : {}),
101
+ });
103
102
  pm.variables = api;
103
+ // Postman exposes pm.iterationData in every script (empty outside a data run).
104
+ pm.iterationData = buildIterationDataApi(capabilities.iterationData?.values);
105
+ variablesApi = api;
104
106
  if (extensions.variables) {
105
107
  scopeShortcuts.variables = api;
106
108
  }
@@ -118,7 +120,7 @@ const buildSandboxDFeatures = (capabilities, isInsideTestCallback, envValues, gl
118
120
  pm.db = api;
119
121
  scopeShortcuts.db = api;
120
122
  }
121
- return { pm, scopeShortcuts, counterSnapshotFn };
123
+ return { pm, scopeShortcuts, counterSnapshotFn, variablesApi: variablesApi };
122
124
  };
123
125
  const buildSendRequestPendingTracker = () => {
124
126
  const pending = [];
@@ -192,7 +194,16 @@ const normalizeSendBody = (body) => {
192
194
  if (isPlainRecord(body)) {
193
195
  const mode = body.mode;
194
196
  if (mode === "raw") {
195
- return { data: body.raw ?? "", impliedContentType: null };
197
+ // Postman picks the Content-Type from the raw language (Text → text/plain);
198
+ // without one axios would label a string body as form-urlencoded.
199
+ const language = String(body.options?.raw?.language ?? "text").toLowerCase();
200
+ const byLanguage = {
201
+ json: "application/json",
202
+ xml: "application/xml",
203
+ html: "text/html",
204
+ javascript: "application/javascript",
205
+ };
206
+ return { data: body.raw ?? "", impliedContentType: byLanguage[language] ?? "text/plain" };
196
207
  }
197
208
  if (mode === "urlencoded" && Array.isArray(body.urlencoded)) {
198
209
  const params = new URLSearchParams();
@@ -249,7 +260,33 @@ const resolveSendTemplates = (value, resolve) => {
249
260
  }
250
261
  return value;
251
262
  };
252
- const createPmSendRequest = (tracker, executor, resolveTemplate) => {
263
+ const MULTIPART_BOUNDARY_PREFIX = "----CallmanFormBoundary";
264
+ // Multipart is built as a STRING body so every host executor (axios, the
265
+ // desktop's engine over worker RPC) sends it the same way. Text parts only —
266
+ // a file part has no bytes to read inside the sandbox.
267
+ const buildMultipartBody = (entries) => {
268
+ const boundary = `${MULTIPART_BOUNDARY_PREFIX}${Math.random().toString(16).slice(2)}${Date.now().toString(16)}`;
269
+ const parts = [];
270
+ for (const entry of entries) {
271
+ if (entry.disabled === true)
272
+ continue;
273
+ const key = String(entry.key ?? "").trim();
274
+ if (!key)
275
+ continue;
276
+ if (entry.type === "file") {
277
+ throw new Error(`pm.sendRequest: file part "${key}" is not supported (the sandbox cannot read files).`);
278
+ }
279
+ parts.push(`--${boundary}\r\nContent-Disposition: form-data; name="${key.replace(/"/g, "%22")}"\r\n\r\n${String(entry.value ?? "")}\r\n`);
280
+ }
281
+ return { data: `${parts.join("")}--${boundary}--\r\n`, contentType: `multipart/form-data; boundary=${boundary}` };
282
+ };
283
+ const setCookieLines = (headers) => {
284
+ const raw = Object.entries(headers).find(([key]) => key.toLowerCase() === "set-cookie")?.[1];
285
+ if (raw === undefined || raw === null)
286
+ return [];
287
+ return Array.isArray(raw) ? raw.map(String) : String(raw).split(/\r?\n/);
288
+ };
289
+ const createPmSendRequest = (tracker, executor, resolveTemplate, context = { sdk: null, chai: null }) => {
253
290
  // Host-injected executor (desktop's real engine) takes precedence; backend
254
291
  // and CLI omit it and fall back to the built-in bare-axios client.
255
292
  const run = executor ?? executeRequest;
@@ -258,20 +295,60 @@ const createPmSendRequest = (tracker, executor, resolveTemplate) => {
258
295
  // pm.environment.set writes made earlier in this script). No resolver
259
296
  // wired → identity passthrough so behaviour is unchanged.
260
297
  const resolve = resolveTemplate ?? ((template) => template);
298
+ const { sdk, chai, jar } = context;
299
+ // Request objects (sdk.Request) and the Url/HeaderList inside them become the
300
+ // plain definition the rest of this function understands.
301
+ const toDefinition = (options) => {
302
+ if (typeof options === "string")
303
+ return { url: options, method: "GET" };
304
+ if (sdk && options && typeof options === "object" && sdk.Request.isRequest(options)) {
305
+ return options.toJSON();
306
+ }
307
+ return isPlainRecord(options) ? options : {};
308
+ };
309
+ const urlFrom = (input) => {
310
+ if (sdk && input && typeof input === "object" && !Array.isArray(input) && !("raw" in input) && "host" in input) {
311
+ return new sdk.Url(input).toString();
312
+ }
313
+ return normalizeSendUrl(input);
314
+ };
261
315
  return (options, callback) => {
262
316
  const promise = (async () => {
263
- const rawOptions = typeof options === "string"
264
- ? { url: options, method: "GET" }
265
- : isPlainRecord(options)
266
- ? options
267
- : {};
317
+ const rawOptions = toDefinition(options);
268
318
  // Resolve templates up-front against the live scope, then normalize.
269
319
  const normalized = resolveSendTemplates(rawOptions, resolve);
270
- const url = normalizeSendUrl(normalized.url);
320
+ const url = urlFrom(normalized.url);
271
321
  const method = String(normalized.method ?? "GET").toUpperCase();
272
322
  const headerInput = normalized.header ?? normalized.headers;
273
323
  const headers = normalizeSendHeaderInput(headerInput);
274
- const { data, impliedContentType } = normalizeSendBody(normalized.body);
324
+ const body = normalized.body;
325
+ let data;
326
+ let impliedContentType = null;
327
+ if (isPlainRecord(body) && body.mode === "formdata" && Array.isArray(body.formdata)) {
328
+ const multipart = buildMultipartBody(body.formdata);
329
+ data = multipart.data;
330
+ impliedContentType = multipart.contentType;
331
+ }
332
+ else if (isPlainRecord(body) && body.mode === "graphql" && isPlainRecord(body.graphql)) {
333
+ const graphql = body.graphql;
334
+ let variables = graphql.variables;
335
+ if (typeof variables === "string" && variables.trim()) {
336
+ try {
337
+ variables = JSON.parse(variables);
338
+ }
339
+ catch {
340
+ // leave the text as the user wrote it
341
+ }
342
+ }
343
+ data = JSON.stringify({ query: graphql.query, ...(variables ? { variables } : {}) });
344
+ impliedContentType = "application/json";
345
+ }
346
+ else if (isPlainRecord(body) && body.mode === "file") {
347
+ throw new Error("pm.sendRequest: body mode \"file\" is not supported (the sandbox cannot read files).");
348
+ }
349
+ else {
350
+ ({ data, impliedContentType } = normalizeSendBody(body));
351
+ }
275
352
  if (impliedContentType &&
276
353
  !Object.keys(headers).some((k) => k.toLowerCase() === "content-type")) {
277
354
  headers["Content-Type"] = impliedContentType;
@@ -279,6 +356,14 @@ const createPmSendRequest = (tracker, executor, resolveTemplate) => {
279
356
  const timeoutMs = typeof normalized.timeout === "number" && normalized.timeout > 0
280
357
  ? Number(normalized.timeout)
281
358
  : undefined;
359
+ // Hosts that provide a persistent cookie jar get Postman's behaviour:
360
+ // matching cookies are sent, Set-Cookie responses are stored.
361
+ if (jar && !Object.keys(headers).some((key) => key.toLowerCase() === "cookie")) {
362
+ const stored = await jar.getAll(url);
363
+ if (stored.length > 0) {
364
+ headers.Cookie = stored.map((cookie) => `${cookie.name}=${cookie.value}`).join("; ");
365
+ }
366
+ }
282
367
  const result = await run({
283
368
  method,
284
369
  url,
@@ -287,21 +372,47 @@ const createPmSendRequest = (tracker, executor, resolveTemplate) => {
287
372
  body: data,
288
373
  timeoutMs,
289
374
  });
290
- const bodyText = typeof result.data === "string"
291
- ? result.data
292
- : (() => {
293
- try {
294
- return JSON.stringify(result.data ?? null);
295
- }
296
- catch {
297
- return String(result.data ?? "");
298
- }
299
- })();
375
+ const bodyText = typeof result.rawText === "string"
376
+ ? result.rawText
377
+ : typeof result.data === "string"
378
+ ? result.data
379
+ : (() => {
380
+ try {
381
+ return JSON.stringify(result.data ?? null);
382
+ }
383
+ catch {
384
+ return String(result.data ?? "");
385
+ }
386
+ })();
387
+ const responseHeaders = (result.headers ?? {});
388
+ const cookieLines = setCookieLines(responseHeaders);
389
+ if (jar) {
390
+ for (const line of cookieLines) {
391
+ const cookie = parseSetCookieLine(line);
392
+ if (cookie)
393
+ await jar.set(url, cookie);
394
+ }
395
+ }
396
+ if (sdk && chai) {
397
+ return buildSdkResponseApi(sdk, chai, {
398
+ ok: result.status >= 200 && result.status < 400,
399
+ status: result.status,
400
+ statusText: "",
401
+ headers: responseHeaders,
402
+ body: result.data,
403
+ jsonBody: result.data !== null && typeof result.data === "object" ? result.data : null,
404
+ rawBody: bodyText,
405
+ contentType: Object.entries(responseHeaders).find(([key]) => key.toLowerCase() === "content-type")?.[1] ?? "",
406
+ durationMs: result.duration ?? 0,
407
+ sizeBytes: bodyText.length,
408
+ cookies: cookieLines,
409
+ });
410
+ }
300
411
  return {
301
412
  code: result.status,
302
413
  status: result.status,
303
414
  statusText: "",
304
- headers: result.headers ?? {},
415
+ headers: responseHeaders,
305
416
  body: result.data,
306
417
  responseTime: result.duration,
307
418
  duration: result.duration,
@@ -318,7 +429,7 @@ const createPmSendRequest = (tracker, executor, resolveTemplate) => {
318
429
  if (typeof callback === "function") {
319
430
  promise.then((response) => {
320
431
  try {
321
- callback(null, response);
432
+ callback(null, response, undefined);
322
433
  }
323
434
  catch (callbackError) {
324
435
  // Surface callback throws to the host log; suppressing
@@ -328,7 +439,7 @@ const createPmSendRequest = (tracker, executor, resolveTemplate) => {
328
439
  }
329
440
  }, (error) => {
330
441
  try {
331
- callback(error instanceof Error ? error : new Error(String(error)), null);
442
+ callback(error instanceof Error ? error : new Error(String(error)), null, undefined);
332
443
  }
333
444
  catch (callbackError) {
334
445
  // eslint-disable-next-line no-console
@@ -373,8 +484,12 @@ const buildPm = (capabilities, logs, collectors, sandboxDPm) => {
373
484
  // interpolates {{var}} exactly like pm.variables.replaceIn(...).
374
485
  const variablesApi = sandboxDPm.variables;
375
486
  pm.sendRequest = createPmSendRequest(collectors.sendRequestTracker, capabilities.extensions?.sendRequest?.execute, variablesApi
376
- ? (template) => variablesApi.replaceIn(template)
377
- : undefined);
487
+ ? (template) => String(variablesApi.replaceIn(template))
488
+ : undefined, {
489
+ sdk: collectors.runtime?.sdk ?? null,
490
+ chai: collectors.runtime?.chai ?? null,
491
+ jar: capabilities.cookies?.jar,
492
+ });
378
493
  }
379
494
  const extensions = capabilities.extensions ?? {};
380
495
  if (extensions.log) {
@@ -388,7 +503,7 @@ const buildPm = (capabilities, logs, collectors, sandboxDPm) => {
388
503
  }
389
504
  return pm;
390
505
  };
391
- const buildScopeBindings = ({ pm, capabilities, consoleApi, envValues, environmentApi, request, response, expect, test, sandboxDShortcuts, timers, }) => {
506
+ const buildScopeBindings = ({ pm, capabilities, consoleApi, envValues, environmentApi, request, response, expect, test, sandboxDShortcuts, timers, extraGlobals, }) => {
392
507
  const exposeLabels = capabilities.workflowContext &&
393
508
  capabilities.exposeLabelsAsGlobals !== false;
394
509
  const labelGlobals = exposeLabels
@@ -407,7 +522,6 @@ const buildScopeBindings = ({ pm, capabilities, consoleApi, envValues, environme
407
522
  expect,
408
523
  console: consoleApi,
409
524
  db: sandboxDShortcuts.db,
410
- kafka: sandboxDShortcuts.kafka,
411
525
  redis: sandboxDShortcuts.redis,
412
526
  random: sandboxDShortcuts.random,
413
527
  counter: sandboxDShortcuts.counter,
@@ -415,14 +529,26 @@ const buildScopeBindings = ({ pm, capabilities, consoleApi, envValues, environme
415
529
  CryptoJS: sandboxDShortcuts.CryptoJS,
416
530
  lodash,
417
531
  timers,
532
+ extraGlobals,
418
533
  labelGlobals,
419
534
  };
420
535
  };
536
+ const NUMERIC_STATUS_COMPARISON = /(?:\b(?:pm|cm|callman)\.response\.status\s*(?:===?|!==?|<=?|>=?)\s*\d{3}\b)|(?:\b\d{3}\s*(?:===?|!==?|<=?|>=?)\s*(?:pm|cm|callman)\.response\.status\b)|(?:expect\(\s*(?:pm|cm|callman)\.response\.status\s*\)[^;\n]*\b\d{3}\b)/;
421
537
  export const executeScript = async (input) => {
422
538
  const timeoutMs = input.timeoutMs ?? DEFAULT_TIMEOUT_MS;
423
539
  const filename = input.filename ?? DEFAULT_FILENAME;
424
540
  const stage = input.stage ?? DEFAULT_STAGE;
425
- const capabilities = input.capabilities;
541
+ // A host may hand over several Set-Cookie headers joined into one string
542
+ // ("a=1; Path=/, b=2; …"); split them so each cookie is its own entry.
543
+ const capabilities = input.capabilities.response?.cookies?.some((line) => /,\s*[^;,=\s]+=/.test(line))
544
+ ? {
545
+ ...input.capabilities,
546
+ response: {
547
+ ...input.capabilities.response,
548
+ cookies: input.capabilities.response.cookies.flatMap(splitSetCookieHeader),
549
+ },
550
+ }
551
+ : input.capabilities;
426
552
  const extensions = capabilities.extensions ?? {};
427
553
  // Empty / whitespace-only scripts short-circuit with an empty success.
428
554
  if (!input.script.trim()) {
@@ -437,8 +563,8 @@ export const executeScript = async (input) => {
437
563
  if (extensions.counter) {
438
564
  passthrough.counterSnapshot = extensions.counter.snapshot;
439
565
  }
440
- if (capabilities.allowDeferredKafkaTests) {
441
- passthrough.deferredTestNames = [];
566
+ if (capabilities.localVariables) {
567
+ passthrough.localVariables = { ...capabilities.localVariables.values };
442
568
  }
443
569
  return passthrough;
444
570
  }
@@ -462,23 +588,46 @@ export const executeScript = async (input) => {
462
588
  : buildCollectionVariablesApi({}, null);
463
589
  const infoApi = buildInfoApi(capabilities.info, stage);
464
590
  const executionFeature = buildExecutionFeature(capabilities.execution);
591
+ // pm.request / pm.response are real `postman-collection` objects and pm.expect
592
+ // is chai: load them before the (synchronous) sandbox is assembled.
593
+ const runtime = await loadPostmanRuntime({
594
+ sdk: scriptNeedsSdk(input.script, Boolean(capabilities.request || capabilities.response)),
595
+ });
465
596
  const requestBundle = capabilities.request
466
- ? buildRequestApi(capabilities.request)
597
+ ? runtime.sdk
598
+ ? buildSdkRequestApi(runtime.sdk, capabilities.request)
599
+ : buildRequestApi(capabilities.request)
467
600
  : undefined;
468
- const responseApi = buildResponseApi(capabilities.response ?? null) ?? undefined;
469
- const expect = createExpectApi();
470
- const tests = buildTestCollector(stage, {
471
- allowDeferredKafkaTests: capabilities.allowDeferredKafkaTests,
472
- selectedTestNames: capabilities.deferredTestNames,
473
- });
601
+ if (requestBundle && runtime.sdk) {
602
+ attachRequestAssertions(runtime.chai, requestBundle.api);
603
+ }
604
+ const responseApi = capabilities.response
605
+ ? runtime.sdk
606
+ ? buildSdkResponseApi(runtime.sdk, runtime.chai, capabilities.response)
607
+ : (buildResponseApi(capabilities.response) ?? undefined)
608
+ : undefined;
609
+ const expect = runtime.chai.expect;
610
+ const tests = buildTestCollector(stage);
474
611
  const logCollector = extensions.log
475
- ? createLogCollector()
612
+ ? createLogCollector(typeof extensions.log === "object" ? { onEntry: extensions.log.onEntry } : {})
476
613
  : createSilentLogCollector();
614
+ // `pm.response.status` is Postman's reason phrase ("OK"); scripts written
615
+ // against Callman's old numeric status silently stop matching. Say so once.
616
+ if (extensions.log && NUMERIC_STATUS_COMPARISON.test(input.script)) {
617
+ logCollector.consoleApi.warn('pm.response.status is the reason phrase (e.g. "OK") like in Postman — compare pm.response.code for the numeric status.');
618
+ }
477
619
  // Sandbox D feature bundle — depends on env/globals being constructed first
478
620
  // (cryptojs/random/counter don't, but variables/db do).
479
- const sandboxD = buildSandboxDFeatures(capabilities, () => tests.isInsideCallback(), environmentApi?.values ?? capabilities.environment?.values ?? {}, globalsApi?.values ?? capabilities.globals?.values ?? {}, capabilities.response ?? null);
621
+ const sandboxD = buildSandboxDFeatures(capabilities, () => tests.isInsideCallback(), environmentApi?.values ?? capabilities.environment?.values ?? {}, globalsApi?.values ?? capabilities.globals?.values ?? {}, capabilities.response ?? null, collectionVariablesApi.values, {
622
+ ...(environmentApi ? { environment: () => environmentApi.toObject() } : {}),
623
+ ...(globalsApi ? { globals: () => globalsApi.toObject() } : {}),
624
+ collection: () => collectionVariablesApi.toObject(),
625
+ });
480
626
  const sendRequestTracker = buildSendRequestPendingTracker();
481
- const timerFeature = buildTimerFeature();
627
+ const timerFeature = buildTimerFeature((kind, error) => {
628
+ const detail = error instanceof Error ? (error.stack ?? error.message) : String(error);
629
+ logCollector.consoleApi.error(`Uncaught error in ${kind === "timeout" ? "setTimeout" : "setInterval"} callback: ${detail}`);
630
+ });
482
631
  const pm = buildPm(capabilities, { pmLog: logCollector.pmLog }, {
483
632
  test: tests,
484
633
  expect,
@@ -490,9 +639,88 @@ export const executeScript = async (input) => {
490
639
  request: requestBundle,
491
640
  response: responseApi ?? undefined,
492
641
  sendRequestTracker,
642
+ runtime,
493
643
  }, sandboxD.pm);
644
+ // Postman APIs Callman does not implement: fail with an explanation instead
645
+ // of "Cannot read properties of undefined".
646
+ const unsupportedApi = (name) => new Proxy(function unsupported() {
647
+ return undefined;
648
+ }, {
649
+ get: (_target, property) => {
650
+ if (property === Symbol.toPrimitive || property === "then" || property === "toJSON")
651
+ return undefined;
652
+ throw new Error(`${name} is not supported in Callman yet.`);
653
+ },
654
+ apply: () => {
655
+ throw new Error(`${name} is not supported in Callman yet.`);
656
+ },
657
+ });
658
+ for (const name of ["vault", "datasets", "mock", "state", "message"]) {
659
+ if (!(name in pm))
660
+ pm[name] = unsupportedApi(`pm.${name}`);
661
+ }
662
+ if (pm.execution && typeof pm.execution === "object" && !("runRequest" in pm.execution)) {
663
+ pm.execution.runRequest = async () => {
664
+ throw new Error("pm.execution.runRequest is not supported in Callman yet.");
665
+ };
666
+ }
667
+ const visualizerFeature = buildVisualizerFeature();
668
+ pm.visualizer = visualizerFeature.api;
669
+ pm.getData = visualizerFeature.getData;
670
+ // pm.cookies (CookieList of the request's domain) + pm.cookies.jar().
671
+ pm.cookies = buildPmCookies({
672
+ sdk: runtime.sdk,
673
+ lines: capabilities.cookies?.snapshot ?? capabilities.response?.cookies ?? [],
674
+ jar: capabilities.cookies?.jar ?? createMemoryCookieJar(),
675
+ });
676
+ // require() / pm.require() and Postman's legacy globals (postman.*, tests[],
677
+ // responseBody…). Modules are loaded up front: the sandbox itself is sync.
678
+ const optsOutOfLegacy = scriptOptsOutOfLegacy(input.script);
679
+ const legacyEnabled = !optsOutOfLegacy && stage !== "script";
680
+ const legacyLibraries = legacyEnabled
681
+ ? ["tv4", "cheerio"].filter((name) => new RegExp(`\\b${name}\\b`).test(input.script))
682
+ : [];
683
+ const wantsXml2Json = legacyEnabled && /\bxml2Json\b/.test(input.script);
684
+ const loadedModules = await preloadScriptModules([
685
+ input.script,
686
+ ...legacyLibraries.map((name) => `require("${name}")`),
687
+ ...(wantsXml2Json ? [`require("xml2js")`] : []),
688
+ ].join("\n"), {
689
+ sdk: runtime.sdk,
690
+ chai: runtime.chai,
691
+ cryptoJs: sandboxD.scopeShortcuts.CryptoJS,
692
+ timers: timerFeature.api,
693
+ });
694
+ pm.require = createSandboxRequire(loadedModules, "pm.require");
695
+ const extraGlobals = {
696
+ require: createSandboxRequire(loadedModules),
697
+ };
698
+ if (legacyEnabled) {
699
+ Object.assign(extraGlobals, buildLegacyGlobals({
700
+ pm,
701
+ environmentApi,
702
+ globalsApi,
703
+ response: capabilities.response ?? null,
704
+ testFn: tests.test,
705
+ iterationDataValues: capabilities.iterationData?.values ?? {},
706
+ }));
707
+ for (const name of legacyLibraries) {
708
+ extraGlobals[name] = loadedModules.get(name);
709
+ }
710
+ if (wantsXml2Json) {
711
+ const xml2js = loadedModules.get("xml2js");
712
+ extraGlobals.xml2Json = (xml) => {
713
+ let out;
714
+ xml2js?.parseString(String(xml ?? ""), { explicitArray: false, async: false }, (_error, result) => {
715
+ out = result;
716
+ });
717
+ return out;
718
+ };
719
+ }
720
+ }
494
721
  const bindings = buildScopeBindings({
495
722
  pm,
723
+ extraGlobals,
496
724
  capabilities,
497
725
  consoleApi: logCollector.consoleApi,
498
726
  envValues: environmentApi?.values,
@@ -507,6 +735,8 @@ export const executeScript = async (input) => {
507
735
  clearTimeout: timerFeature.api.clearTimeout,
508
736
  setInterval: timerFeature.api.setInterval,
509
737
  clearInterval: timerFeature.api.clearInterval,
738
+ setImmediate: timerFeature.api.setImmediate,
739
+ clearImmediate: timerFeature.api.clearImmediate,
510
740
  },
511
741
  });
512
742
  let compiled;
@@ -529,8 +759,8 @@ export const executeScript = async (input) => {
529
759
  if (sandboxD.counterSnapshotFn) {
530
760
  result.counterSnapshot = sandboxD.counterSnapshotFn();
531
761
  }
532
- if (capabilities.allowDeferredKafkaTests) {
533
- result.deferredTestNames = [];
762
+ if (capabilities.localVariables) {
763
+ result.localVariables = { ...capabilities.localVariables.values };
534
764
  }
535
765
  return result;
536
766
  }
@@ -574,15 +804,19 @@ export const executeScript = async (input) => {
574
804
  if (sandboxD.counterSnapshotFn) {
575
805
  result.counterSnapshot = sandboxD.counterSnapshotFn();
576
806
  }
577
- if (capabilities.allowDeferredKafkaTests) {
578
- result.deferredTestNames = [...tests.deferredTestNames];
579
- }
580
807
  // Only surface executionControl when the host actually wired the
581
808
  // capability — keeps the result shape unchanged for callers that
582
809
  // don't know about pm.execution yet.
583
810
  if (capabilities.execution) {
584
811
  result.executionControl = executionFeature.getControl();
585
812
  }
813
+ if (capabilities.localVariables) {
814
+ result.localVariables = sandboxD.variablesApi.localSnapshot?.() ?? {};
815
+ }
816
+ const visualization = visualizerFeature.payload();
817
+ if (visualization) {
818
+ result.visualizer = visualization;
819
+ }
586
820
  return result;
587
821
  };
588
822
  // Standalone parse-only check — exposed so the desktop's