callman-core 1.35.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 (247) hide show
  1. package/ai-packs/feature-policy.md +1 -6
  2. package/ai-packs/pm-api.md +238 -98
  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 +5 -2
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +3 -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 +33 -0
  19. package/dist/runner/requestRunner.js.map +1 -1
  20. package/dist/runner/runCollection.d.ts.map +1 -1
  21. package/dist/runner/runCollection.js +46 -9
  22. package/dist/runner/runCollection.js.map +1 -1
  23. package/dist/runner/runRequestWithDataset.d.ts.map +1 -1
  24. package/dist/runner/runRequestWithDataset.js +3 -2
  25. package/dist/runner/runRequestWithDataset.js.map +1 -1
  26. package/dist/scenario-runner/adapters/request-executor.d.ts.map +1 -1
  27. package/dist/scenario-runner/adapters/request-executor.js +7 -0
  28. package/dist/scenario-runner/adapters/request-executor.js.map +1 -1
  29. package/dist/scenario-runner/runScenario.d.ts.map +1 -1
  30. package/dist/scenario-runner/runScenario.js +4 -0
  31. package/dist/scenario-runner/runScenario.js.map +1 -1
  32. package/dist/scenario-runner/types.d.ts +11 -0
  33. package/dist/scenario-runner/types.d.ts.map +1 -1
  34. package/dist/script/chain.d.ts +2 -0
  35. package/dist/script/chain.d.ts.map +1 -1
  36. package/dist/script/chain.js +2 -0
  37. package/dist/script/chain.js.map +1 -1
  38. package/dist/script/errors.js +6 -6
  39. package/dist/script/errors.js.map +1 -1
  40. package/dist/script/executor.d.ts.map +1 -1
  41. package/dist/script/executor.js +284 -64
  42. package/dist/script/executor.js.map +1 -1
  43. package/dist/script/features/collectionVariables.d.ts +4 -4
  44. package/dist/script/features/collectionVariables.d.ts.map +1 -1
  45. package/dist/script/features/collectionVariables.js +20 -11
  46. package/dist/script/features/collectionVariables.js.map +1 -1
  47. package/dist/script/features/environment.d.ts +4 -4
  48. package/dist/script/features/environment.d.ts.map +1 -1
  49. package/dist/script/features/environment.js +20 -11
  50. package/dist/script/features/environment.js.map +1 -1
  51. package/dist/script/features/feature-policy.d.ts +1 -1
  52. package/dist/script/features/feature-policy.d.ts.map +1 -1
  53. package/dist/script/features/feature-policy.js +2 -2
  54. package/dist/script/features/feature-policy.js.map +1 -1
  55. package/dist/script/features/info.d.ts.map +1 -1
  56. package/dist/script/features/info.js +6 -3
  57. package/dist/script/features/info.js.map +1 -1
  58. package/dist/script/features/iterationData.d.ts +12 -0
  59. package/dist/script/features/iterationData.d.ts.map +1 -0
  60. package/dist/script/features/iterationData.js +28 -0
  61. package/dist/script/features/iterationData.js.map +1 -0
  62. package/dist/script/features/legacyPostman.d.ts +14 -0
  63. package/dist/script/features/legacyPostman.d.ts.map +1 -0
  64. package/dist/script/features/legacyPostman.js +106 -0
  65. package/dist/script/features/legacyPostman.js.map +1 -0
  66. package/dist/script/features/log.d.ts +35 -8
  67. package/dist/script/features/log.d.ts.map +1 -1
  68. package/dist/script/features/log.js +343 -27
  69. package/dist/script/features/log.js.map +1 -1
  70. package/dist/script/features/tests.d.ts +7 -8
  71. package/dist/script/features/tests.d.ts.map +1 -1
  72. package/dist/script/features/tests.js +90 -64
  73. package/dist/script/features/tests.js.map +1 -1
  74. package/dist/script/features/timers.d.ts +7 -1
  75. package/dist/script/features/timers.d.ts.map +1 -1
  76. package/dist/script/features/timers.js +17 -5
  77. package/dist/script/features/timers.js.map +1 -1
  78. package/dist/script/features/variables.d.ts +19 -4
  79. package/dist/script/features/variables.d.ts.map +1 -1
  80. package/dist/script/features/variables.js +22 -6
  81. package/dist/script/features/variables.js.map +1 -1
  82. package/dist/script/features/visualizer.d.ts +15 -0
  83. package/dist/script/features/visualizer.d.ts.map +1 -0
  84. package/dist/script/features/visualizer.js +52 -0
  85. package/dist/script/features/visualizer.js.map +1 -0
  86. package/dist/script/isolated-runner.d.ts +4 -1
  87. package/dist/script/isolated-runner.d.ts.map +1 -1
  88. package/dist/script/isolated-runner.js +26 -2
  89. package/dist/script/isolated-runner.js.map +1 -1
  90. package/dist/script/legacy.d.ts +1 -0
  91. package/dist/script/legacy.d.ts.map +1 -1
  92. package/dist/script/legacy.js +12 -2
  93. package/dist/script/legacy.js.map +1 -1
  94. package/dist/script/pm-api-declarations.d.ts +45 -0
  95. package/dist/script/pm-api-declarations.d.ts.map +1 -0
  96. package/dist/script/pm-api-declarations.js +1963 -0
  97. package/dist/script/pm-api-declarations.js.map +1 -0
  98. package/dist/script/pm-api-metadata.d.ts +3 -1
  99. package/dist/script/pm-api-metadata.d.ts.map +1 -1
  100. package/dist/script/pm-api-metadata.js +336 -106
  101. package/dist/script/pm-api-metadata.js.map +1 -1
  102. package/dist/script/pm-api-reference.d.ts.map +1 -1
  103. package/dist/script/pm-api-reference.js +24 -8
  104. package/dist/script/pm-api-reference.js.map +1 -1
  105. package/dist/script/postman/chaiCompat.d.ts +4 -0
  106. package/dist/script/postman/chaiCompat.d.ts.map +1 -0
  107. package/dist/script/postman/chaiCompat.js +176 -0
  108. package/dist/script/postman/chaiCompat.js.map +1 -0
  109. package/dist/script/postman/cookies.d.ts +36 -0
  110. package/dist/script/postman/cookies.d.ts.map +1 -0
  111. package/dist/script/postman/cookies.js +163 -0
  112. package/dist/script/postman/cookies.js.map +1 -0
  113. package/dist/script/postman/modules.d.ts +16 -0
  114. package/dist/script/postman/modules.d.ts.map +1 -0
  115. package/dist/script/postman/modules.js +114 -0
  116. package/dist/script/postman/modules.js.map +1 -0
  117. package/dist/script/postman/proxies.d.ts +13 -0
  118. package/dist/script/postman/proxies.d.ts.map +1 -0
  119. package/dist/script/postman/proxies.js +88 -0
  120. package/dist/script/postman/proxies.js.map +1 -0
  121. package/dist/script/postman/requestAdapter.d.ts +7 -0
  122. package/dist/script/postman/requestAdapter.d.ts.map +1 -0
  123. package/dist/script/postman/requestAdapter.js +223 -0
  124. package/dist/script/postman/requestAdapter.js.map +1 -0
  125. package/dist/script/postman/responseAdapter.d.ts +5 -0
  126. package/dist/script/postman/responseAdapter.d.ts.map +1 -0
  127. package/dist/script/postman/responseAdapter.js +78 -0
  128. package/dist/script/postman/responseAdapter.js.map +1 -0
  129. package/dist/script/postman/runtime.d.ts +22 -0
  130. package/dist/script/postman/runtime.d.ts.map +1 -0
  131. package/dist/script/postman/runtime.js +122 -0
  132. package/dist/script/postman/runtime.js.map +1 -0
  133. package/dist/script/scope.d.ts +5 -1
  134. package/dist/script/scope.d.ts.map +1 -1
  135. package/dist/script/scope.js +47 -5
  136. package/dist/script/scope.js.map +1 -1
  137. package/dist/script/types.d.ts +33 -14
  138. package/dist/script/types.d.ts.map +1 -1
  139. package/dist/script/worker-entry.js +9 -0
  140. package/dist/script/worker-entry.js.map +1 -1
  141. package/dist/script/worker-pool.d.ts +3 -1
  142. package/dist/script/worker-pool.d.ts.map +1 -1
  143. package/dist/script/worker-pool.js +23 -2
  144. package/dist/script/worker-pool.js.map +1 -1
  145. package/dist/templating/generator-metadata.d.ts +1 -0
  146. package/dist/templating/generator-metadata.d.ts.map +1 -1
  147. package/dist/templating/generator-metadata.js +14 -0
  148. package/dist/templating/generator-metadata.js.map +1 -1
  149. package/dist/templating/postman-dynamic-catalog.d.ts +7 -0
  150. package/dist/templating/postman-dynamic-catalog.d.ts.map +1 -0
  151. package/dist/templating/postman-dynamic-catalog.js +126 -0
  152. package/dist/templating/postman-dynamic-catalog.js.map +1 -0
  153. package/dist/templating/postman-generators.d.ts +9 -0
  154. package/dist/templating/postman-generators.d.ts.map +1 -1
  155. package/dist/templating/postman-generators.js +10 -1
  156. package/dist/templating/postman-generators.js.map +1 -1
  157. package/dist/types/index.d.ts +22 -1
  158. package/dist/types/index.d.ts.map +1 -1
  159. package/dist/variables/replaceIn.d.ts +10 -0
  160. package/dist/variables/replaceIn.d.ts.map +1 -0
  161. package/dist/variables/replaceIn.js +106 -0
  162. package/dist/variables/replaceIn.js.map +1 -0
  163. package/dist-cjs/ai/prompts/fragments.js +0 -1
  164. package/dist-cjs/ai/prompts/fragments.js.map +1 -1
  165. package/dist-cjs/ai/prompts/unified.js +1 -1
  166. package/dist-cjs/ai/prompts/unified.js.map +1 -1
  167. package/dist-cjs/ai/types.js +1 -1
  168. package/dist-cjs/ai/types.js.map +1 -1
  169. package/dist-cjs/index.js +17 -5
  170. package/dist-cjs/index.js.map +1 -1
  171. package/dist-cjs/runner/requestRunner.js +33 -0
  172. package/dist-cjs/runner/requestRunner.js.map +1 -1
  173. package/dist-cjs/runner/runCollection.js +46 -9
  174. package/dist-cjs/runner/runCollection.js.map +1 -1
  175. package/dist-cjs/runner/runRequestWithDataset.js +3 -2
  176. package/dist-cjs/runner/runRequestWithDataset.js.map +1 -1
  177. package/dist-cjs/scenario-runner/adapters/request-executor.js +7 -0
  178. package/dist-cjs/scenario-runner/adapters/request-executor.js.map +1 -1
  179. package/dist-cjs/scenario-runner/runScenario.js +4 -0
  180. package/dist-cjs/scenario-runner/runScenario.js.map +1 -1
  181. package/dist-cjs/script/chain.js +2 -0
  182. package/dist-cjs/script/chain.js.map +1 -1
  183. package/dist-cjs/script/errors.js +6 -6
  184. package/dist-cjs/script/errors.js.map +1 -1
  185. package/dist-cjs/script/executor.js +284 -64
  186. package/dist-cjs/script/executor.js.map +1 -1
  187. package/dist-cjs/script/features/collectionVariables.js +20 -11
  188. package/dist-cjs/script/features/collectionVariables.js.map +1 -1
  189. package/dist-cjs/script/features/environment.js +20 -11
  190. package/dist-cjs/script/features/environment.js.map +1 -1
  191. package/dist-cjs/script/features/feature-policy.js +2 -2
  192. package/dist-cjs/script/features/feature-policy.js.map +1 -1
  193. package/dist-cjs/script/features/info.js +6 -3
  194. package/dist-cjs/script/features/info.js.map +1 -1
  195. package/dist-cjs/script/features/iterationData.js +32 -0
  196. package/dist-cjs/script/features/iterationData.js.map +1 -0
  197. package/dist-cjs/script/features/legacyPostman.js +112 -0
  198. package/dist-cjs/script/features/legacyPostman.js.map +1 -0
  199. package/dist-cjs/script/features/log.js +347 -28
  200. package/dist-cjs/script/features/log.js.map +1 -1
  201. package/dist-cjs/script/features/tests.js +90 -64
  202. package/dist-cjs/script/features/tests.js.map +1 -1
  203. package/dist-cjs/script/features/timers.js +17 -5
  204. package/dist-cjs/script/features/timers.js.map +1 -1
  205. package/dist-cjs/script/features/variables.js +22 -6
  206. package/dist-cjs/script/features/variables.js.map +1 -1
  207. package/dist-cjs/script/features/visualizer.js +56 -0
  208. package/dist-cjs/script/features/visualizer.js.map +1 -0
  209. package/dist-cjs/script/isolated-runner.js +26 -2
  210. package/dist-cjs/script/isolated-runner.js.map +1 -1
  211. package/dist-cjs/script/legacy.js +14 -3
  212. package/dist-cjs/script/legacy.js.map +1 -1
  213. package/dist-cjs/script/pm-api-declarations.js +1969 -0
  214. package/dist-cjs/script/pm-api-declarations.js.map +1 -0
  215. package/dist-cjs/script/pm-api-metadata.js +336 -106
  216. package/dist-cjs/script/pm-api-metadata.js.map +1 -1
  217. package/dist-cjs/script/pm-api-reference.js +24 -8
  218. package/dist-cjs/script/pm-api-reference.js.map +1 -1
  219. package/dist-cjs/script/postman/chaiCompat.js +181 -0
  220. package/dist-cjs/script/postman/chaiCompat.js.map +1 -0
  221. package/dist-cjs/script/postman/cookies.js +170 -0
  222. package/dist-cjs/script/postman/cookies.js.map +1 -0
  223. package/dist-cjs/script/postman/modules.js +123 -0
  224. package/dist-cjs/script/postman/modules.js.map +1 -0
  225. package/dist-cjs/script/postman/proxies.js +93 -0
  226. package/dist-cjs/script/postman/proxies.js.map +1 -0
  227. package/dist-cjs/script/postman/requestAdapter.js +227 -0
  228. package/dist-cjs/script/postman/requestAdapter.js.map +1 -0
  229. package/dist-cjs/script/postman/responseAdapter.js +83 -0
  230. package/dist-cjs/script/postman/responseAdapter.js.map +1 -0
  231. package/dist-cjs/script/postman/runtime.js +132 -0
  232. package/dist-cjs/script/postman/runtime.js.map +1 -0
  233. package/dist-cjs/script/scope.js +47 -5
  234. package/dist-cjs/script/scope.js.map +1 -1
  235. package/dist-cjs/script/worker-entry.js +9 -0
  236. package/dist-cjs/script/worker-entry.js.map +1 -1
  237. package/dist-cjs/script/worker-pool.js +23 -2
  238. package/dist-cjs/script/worker-pool.js.map +1 -1
  239. package/dist-cjs/templating/generator-metadata.js +15 -1
  240. package/dist-cjs/templating/generator-metadata.js.map +1 -1
  241. package/dist-cjs/templating/postman-dynamic-catalog.js +129 -0
  242. package/dist-cjs/templating/postman-dynamic-catalog.js.map +1 -0
  243. package/dist-cjs/templating/postman-generators.js +13 -3
  244. package/dist-cjs/templating/postman-generators.js.map +1 -1
  245. package/dist-cjs/variables/replaceIn.js +111 -0
  246. package/dist-cjs/variables/replaceIn.js.map +1 -0
  247. 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,27 +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, collectionValues) => {
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
56
  let variablesApi = null;
50
- if (extensions.kafka) {
51
- const kafkaEnabled = featurePolicy ? featurePolicy.kafka : true;
52
- if (kafkaEnabled) {
53
- const api = buildKafkaApi(extensions.kafka, capabilities.allowDeferredKafkaTests ?? false);
54
- pm.kafka = api;
55
- scopeShortcuts.kafka = api;
56
- }
57
- else {
58
- // Disabled kafka — replace with feature-policy guarded value
59
- // (handled at the test-collector level via DisabledFeatureDependencyError).
60
- // We still expose the binding so user scripts don't crash at scope lookup.
61
- void isInsideTestCallback;
62
- }
63
- }
64
57
  if (extensions.redis) {
65
58
  const api = buildRedisApi({
66
59
  capability: extensions.redis,
@@ -102,9 +95,13 @@ const buildSandboxDFeatures = (capabilities, isInsideTestCallback, envValues, gl
102
95
  {
103
96
  const api = buildVariablesApi(envValues, globalValues, {
104
97
  collectionVariables: collectionValues,
98
+ ...(live ? { live } : {}),
99
+ ...(capabilities.iterationData ? { iterationData: capabilities.iterationData.values } : {}),
105
100
  ...(capabilities.localVariables ? { local: capabilities.localVariables.values } : {}),
106
101
  });
107
102
  pm.variables = api;
103
+ // Postman exposes pm.iterationData in every script (empty outside a data run).
104
+ pm.iterationData = buildIterationDataApi(capabilities.iterationData?.values);
108
105
  variablesApi = api;
109
106
  if (extensions.variables) {
110
107
  scopeShortcuts.variables = api;
@@ -197,7 +194,16 @@ const normalizeSendBody = (body) => {
197
194
  if (isPlainRecord(body)) {
198
195
  const mode = body.mode;
199
196
  if (mode === "raw") {
200
- 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" };
201
207
  }
202
208
  if (mode === "urlencoded" && Array.isArray(body.urlencoded)) {
203
209
  const params = new URLSearchParams();
@@ -254,7 +260,33 @@ const resolveSendTemplates = (value, resolve) => {
254
260
  }
255
261
  return value;
256
262
  };
257
- 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 }) => {
258
290
  // Host-injected executor (desktop's real engine) takes precedence; backend
259
291
  // and CLI omit it and fall back to the built-in bare-axios client.
260
292
  const run = executor ?? executeRequest;
@@ -263,20 +295,60 @@ const createPmSendRequest = (tracker, executor, resolveTemplate) => {
263
295
  // pm.environment.set writes made earlier in this script). No resolver
264
296
  // wired → identity passthrough so behaviour is unchanged.
265
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
+ };
266
315
  return (options, callback) => {
267
316
  const promise = (async () => {
268
- const rawOptions = typeof options === "string"
269
- ? { url: options, method: "GET" }
270
- : isPlainRecord(options)
271
- ? options
272
- : {};
317
+ const rawOptions = toDefinition(options);
273
318
  // Resolve templates up-front against the live scope, then normalize.
274
319
  const normalized = resolveSendTemplates(rawOptions, resolve);
275
- const url = normalizeSendUrl(normalized.url);
320
+ const url = urlFrom(normalized.url);
276
321
  const method = String(normalized.method ?? "GET").toUpperCase();
277
322
  const headerInput = normalized.header ?? normalized.headers;
278
323
  const headers = normalizeSendHeaderInput(headerInput);
279
- 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
+ }
280
352
  if (impliedContentType &&
281
353
  !Object.keys(headers).some((k) => k.toLowerCase() === "content-type")) {
282
354
  headers["Content-Type"] = impliedContentType;
@@ -284,6 +356,14 @@ const createPmSendRequest = (tracker, executor, resolveTemplate) => {
284
356
  const timeoutMs = typeof normalized.timeout === "number" && normalized.timeout > 0
285
357
  ? Number(normalized.timeout)
286
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
+ }
287
367
  const result = await run({
288
368
  method,
289
369
  url,
@@ -292,21 +372,47 @@ const createPmSendRequest = (tracker, executor, resolveTemplate) => {
292
372
  body: data,
293
373
  timeoutMs,
294
374
  });
295
- const bodyText = typeof result.data === "string"
296
- ? result.data
297
- : (() => {
298
- try {
299
- return JSON.stringify(result.data ?? null);
300
- }
301
- catch {
302
- return String(result.data ?? "");
303
- }
304
- })();
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
+ }
305
411
  return {
306
412
  code: result.status,
307
413
  status: result.status,
308
414
  statusText: "",
309
- headers: result.headers ?? {},
415
+ headers: responseHeaders,
310
416
  body: result.data,
311
417
  responseTime: result.duration,
312
418
  duration: result.duration,
@@ -323,7 +429,7 @@ const createPmSendRequest = (tracker, executor, resolveTemplate) => {
323
429
  if (typeof callback === "function") {
324
430
  promise.then((response) => {
325
431
  try {
326
- callback(null, response);
432
+ callback(null, response, undefined);
327
433
  }
328
434
  catch (callbackError) {
329
435
  // Surface callback throws to the host log; suppressing
@@ -333,7 +439,7 @@ const createPmSendRequest = (tracker, executor, resolveTemplate) => {
333
439
  }
334
440
  }, (error) => {
335
441
  try {
336
- callback(error instanceof Error ? error : new Error(String(error)), null);
442
+ callback(error instanceof Error ? error : new Error(String(error)), null, undefined);
337
443
  }
338
444
  catch (callbackError) {
339
445
  // eslint-disable-next-line no-console
@@ -378,8 +484,12 @@ const buildPm = (capabilities, logs, collectors, sandboxDPm) => {
378
484
  // interpolates {{var}} exactly like pm.variables.replaceIn(...).
379
485
  const variablesApi = sandboxDPm.variables;
380
486
  pm.sendRequest = createPmSendRequest(collectors.sendRequestTracker, capabilities.extensions?.sendRequest?.execute, variablesApi
381
- ? (template) => variablesApi.replaceIn(template)
382
- : 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
+ });
383
493
  }
384
494
  const extensions = capabilities.extensions ?? {};
385
495
  if (extensions.log) {
@@ -393,7 +503,7 @@ const buildPm = (capabilities, logs, collectors, sandboxDPm) => {
393
503
  }
394
504
  return pm;
395
505
  };
396
- 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, }) => {
397
507
  const exposeLabels = capabilities.workflowContext &&
398
508
  capabilities.exposeLabelsAsGlobals !== false;
399
509
  const labelGlobals = exposeLabels
@@ -412,7 +522,6 @@ const buildScopeBindings = ({ pm, capabilities, consoleApi, envValues, environme
412
522
  expect,
413
523
  console: consoleApi,
414
524
  db: sandboxDShortcuts.db,
415
- kafka: sandboxDShortcuts.kafka,
416
525
  redis: sandboxDShortcuts.redis,
417
526
  random: sandboxDShortcuts.random,
418
527
  counter: sandboxDShortcuts.counter,
@@ -420,14 +529,26 @@ const buildScopeBindings = ({ pm, capabilities, consoleApi, envValues, environme
420
529
  CryptoJS: sandboxDShortcuts.CryptoJS,
421
530
  lodash,
422
531
  timers,
532
+ extraGlobals,
423
533
  labelGlobals,
424
534
  };
425
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)/;
426
537
  export const executeScript = async (input) => {
427
538
  const timeoutMs = input.timeoutMs ?? DEFAULT_TIMEOUT_MS;
428
539
  const filename = input.filename ?? DEFAULT_FILENAME;
429
540
  const stage = input.stage ?? DEFAULT_STAGE;
430
- 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;
431
552
  const extensions = capabilities.extensions ?? {};
432
553
  // Empty / whitespace-only scripts short-circuit with an empty success.
433
554
  if (!input.script.trim()) {
@@ -442,9 +563,6 @@ export const executeScript = async (input) => {
442
563
  if (extensions.counter) {
443
564
  passthrough.counterSnapshot = extensions.counter.snapshot;
444
565
  }
445
- if (capabilities.allowDeferredKafkaTests) {
446
- passthrough.deferredTestNames = [];
447
- }
448
566
  if (capabilities.localVariables) {
449
567
  passthrough.localVariables = { ...capabilities.localVariables.values };
450
568
  }
@@ -470,23 +588,46 @@ export const executeScript = async (input) => {
470
588
  : buildCollectionVariablesApi({}, null);
471
589
  const infoApi = buildInfoApi(capabilities.info, stage);
472
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
+ });
473
596
  const requestBundle = capabilities.request
474
- ? buildRequestApi(capabilities.request)
597
+ ? runtime.sdk
598
+ ? buildSdkRequestApi(runtime.sdk, capabilities.request)
599
+ : buildRequestApi(capabilities.request)
475
600
  : undefined;
476
- const responseApi = buildResponseApi(capabilities.response ?? null) ?? undefined;
477
- const expect = createExpectApi();
478
- const tests = buildTestCollector(stage, {
479
- allowDeferredKafkaTests: capabilities.allowDeferredKafkaTests,
480
- selectedTestNames: capabilities.deferredTestNames,
481
- });
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);
482
611
  const logCollector = extensions.log
483
- ? createLogCollector()
612
+ ? createLogCollector(typeof extensions.log === "object" ? { onEntry: extensions.log.onEntry } : {})
484
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
+ }
485
619
  // Sandbox D feature bundle — depends on env/globals being constructed first
486
620
  // (cryptojs/random/counter don't, but variables/db do).
487
- const sandboxD = buildSandboxDFeatures(capabilities, () => tests.isInsideCallback(), environmentApi?.values ?? capabilities.environment?.values ?? {}, globalsApi?.values ?? capabilities.globals?.values ?? {}, capabilities.response ?? null, collectionVariablesApi.values);
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
+ });
488
626
  const sendRequestTracker = buildSendRequestPendingTracker();
489
- 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
+ });
490
631
  const pm = buildPm(capabilities, { pmLog: logCollector.pmLog }, {
491
632
  test: tests,
492
633
  expect,
@@ -498,9 +639,88 @@ export const executeScript = async (input) => {
498
639
  request: requestBundle,
499
640
  response: responseApi ?? undefined,
500
641
  sendRequestTracker,
642
+ runtime,
501
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
+ }
502
721
  const bindings = buildScopeBindings({
503
722
  pm,
723
+ extraGlobals,
504
724
  capabilities,
505
725
  consoleApi: logCollector.consoleApi,
506
726
  envValues: environmentApi?.values,
@@ -515,6 +735,8 @@ export const executeScript = async (input) => {
515
735
  clearTimeout: timerFeature.api.clearTimeout,
516
736
  setInterval: timerFeature.api.setInterval,
517
737
  clearInterval: timerFeature.api.clearInterval,
738
+ setImmediate: timerFeature.api.setImmediate,
739
+ clearImmediate: timerFeature.api.clearImmediate,
518
740
  },
519
741
  });
520
742
  let compiled;
@@ -537,9 +759,6 @@ export const executeScript = async (input) => {
537
759
  if (sandboxD.counterSnapshotFn) {
538
760
  result.counterSnapshot = sandboxD.counterSnapshotFn();
539
761
  }
540
- if (capabilities.allowDeferredKafkaTests) {
541
- result.deferredTestNames = [];
542
- }
543
762
  if (capabilities.localVariables) {
544
763
  result.localVariables = { ...capabilities.localVariables.values };
545
764
  }
@@ -585,9 +804,6 @@ export const executeScript = async (input) => {
585
804
  if (sandboxD.counterSnapshotFn) {
586
805
  result.counterSnapshot = sandboxD.counterSnapshotFn();
587
806
  }
588
- if (capabilities.allowDeferredKafkaTests) {
589
- result.deferredTestNames = [...tests.deferredTestNames];
590
- }
591
807
  // Only surface executionControl when the host actually wired the
592
808
  // capability — keeps the result shape unchanged for callers that
593
809
  // don't know about pm.execution yet.
@@ -597,6 +813,10 @@ export const executeScript = async (input) => {
597
813
  if (capabilities.localVariables) {
598
814
  result.localVariables = sandboxD.variablesApi.localSnapshot?.() ?? {};
599
815
  }
816
+ const visualization = visualizerFeature.payload();
817
+ if (visualization) {
818
+ result.visualizer = visualization;
819
+ }
600
820
  return result;
601
821
  };
602
822
  // Standalone parse-only check — exposed so the desktop's