@postman/postman-plugin 0.2.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +58 -10
  2. package/dist/hosts/antigravity.js +47 -0
  3. package/dist/hosts/copilot.js +60 -0
  4. package/dist/hosts/index.js +3 -1
  5. package/dist/hosts/opencode-config.js +147 -0
  6. package/dist/hosts/opencode.js +315 -34
  7. package/dist/hosts/shared.js +10 -3
  8. package/dist/source.js +19 -5
  9. package/dist/system.js +20 -1
  10. package/hooks/session-start-context.md +1 -1
  11. package/mcp.pi.json +2 -2
  12. package/package.json +1 -1
  13. package/skills/ai-readiness/SKILL.md +40 -44
  14. package/skills/api-discovery/SKILL.md +64 -129
  15. package/skills/api-discovery/reference/orbit.md +17 -5
  16. package/skills/api-documentation/SKILL.md +38 -25
  17. package/skills/api-engineer/SKILL.md +78 -23
  18. package/skills/api-mocking/SKILL.md +117 -135
  19. package/skills/api-monitoring/SKILL.md +52 -129
  20. package/skills/api-simulation/SKILL.md +138 -0
  21. package/skills/api-simulation/reference/sim_yaml_schema.md +50 -0
  22. package/skills/api-testing/SKILL.md +48 -95
  23. package/skills/bootstrap/SKILL.md +123 -210
  24. package/skills/bootstrap/reference/cli_installation.md +4 -3
  25. package/skills/ci-integration/SKILL.md +53 -112
  26. package/skills/collection-schema-v3/SKILL.md +43 -204
  27. package/skills/collection-schema-v3/reference/http.md +150 -0
  28. package/skills/datasets/SKILL.md +89 -314
  29. package/skills/datasets/reference/spreadsheets.md +25 -0
  30. package/skills/flows/SKILL.md +57 -206
  31. package/skills/flows/reference/flow_cli_flags.md +3 -2
  32. package/skills/performance-testing/SKILL.md +46 -62
  33. package/skills/performance-testing/references/jmeter-import/README.md +113 -0
  34. package/skills/performance-testing/references/jmeter-import/import-report.md +52 -0
  35. package/skills/performance-testing/references/jmeter-import/mapping-table.md +97 -0
  36. package/skills/performance-testing/references/jmeter-import/postman-perf-capabilities.md +56 -0
  37. package/skills/performance-testing/references/jmeter-import/script-translation.md +98 -0
  38. package/skills/performance-testing/references/jmeter-import/scripts/groovy_to_js.py +83 -0
  39. package/skills/performance-testing/references/jmeter-import/scripts/jmx_to_postman.py +1433 -0
  40. package/skills/postman-mcp-server/SKILL.md +53 -59
  41. package/skills/postman-mcp-server/references/docs.md +0 -1
  42. package/skills/postman-mcp-server/references/learn.md +10 -9
  43. package/skills/postman-mcp-server/references/mock.md +0 -1
  44. package/skills/postman-mcp-server/references/search.md +0 -1
  45. package/skills/postman-mcp-server/references/security.md +0 -1
  46. package/skills/postman-mcp-server/references/setup.md +0 -1
  47. package/skills/postman-mcp-server/references/sync.md +0 -1
  48. package/skills/postman-mcp-server/references/test.md +0 -1
  49. package/skills/variables/SKILL.md +63 -0
  50. package/skills/webhook-testing/SKILL.md +103 -0
  51. package/skills/webhook-testing/reference/forwarding.md +10 -0
  52. package/skills/webhook-testing/reference/outbound_webhooks.md +25 -0
  53. package/skills/webhook-testing/reference/webhook_scripts.md +163 -0
@@ -0,0 +1,97 @@
1
+ # JMeter → Postman mapping table
2
+
3
+ Fidelity: **exact** (behaviour preserved) · **approx** (closest supported equivalent, reported) · **manual** (needs the model/user) · **unsupported** (dropped, reported).
4
+ Owner: **script** (deterministic Python) · **model** (judgment call).
5
+
6
+ ## Structure
7
+
8
+ | JMeter element | XML tag | Postman | Fidelity | Owner |
9
+ |---|---|---|---|---|
10
+ | Test Plan | `TestPlan` | Collection (`info` + `item`) | exact | script |
11
+ | Thread Group | `ThreadGroup` | load config (VUs/duration/profile) + folder when >1 | approx | script |
12
+ | setUp/tearDown Thread Group | `SetupThreadGroup`/`PostThreadGroup` | folder; suggest `--setup-collection`/`--teardown-collection` | approx | script+model |
13
+ | Concurrency Thread Group | `...ConcurrencyThreadGroup` | VUs=`TargetLevel`, duration=`RampUp`+`Hold`, `ramp-up` | approx | script |
14
+ | Stepping Thread Group | `kg.apc...SteppingThreadGroup` | VUs=`num_threads`, duration=`flighttime`, `ramp-up` | approx | script |
15
+ | Ultimate Thread Group | `kg.apc...UltimateThreadGroup` | VUs=max row concurrency, duration=sum of row times | approx | script |
16
+ | Arrivals Thread Group | `...ArrivalsThreadGroup` | treated like concurrency | approx | script |
17
+ | Transaction Controller | `TransactionController` | folder | exact | script |
18
+ | Simple/Generic Controller | `GenericController`/`SimpleController` | folder | exact | script |
19
+ | Loop Controller | `LoopController` | flattened; loop count → duration | approx | script |
20
+ | If Controller | `IfController` | each enclosed request gets a pre-request skip guard (`pm.execution.skipRequest`); `${var}` → `pm.variables.get(...)` | approx | script |
21
+ | While Controller | `WhileController` | flattened, requests run unconditionally; restore looping with `pm.execution.setNextRequest` | approx | model |
22
+ | Recording Controller | `RecordingController` | transparent (children recursed) | exact | script |
23
+ | Module / Include Controller | `ModuleController`/`IncludeController` | flattened children if inline; external refs reported | manual | model |
24
+
25
+ ## Requests
26
+
27
+ | JMeter | XML | Postman | Fidelity | Owner |
28
+ |---|---|---|---|---|
29
+ | HTTP Request | `HTTPSamplerProxy` | request item | exact | script |
30
+ | method / path / domain / port / protocol | `HTTPSampler.*` | `request.method` + `url` (blank protocol → `http`) | exact | script |
31
+ | Query/body params | `Arguments`/`HTTPArgument` | query (GET) / urlencoded (POST/PUT/PATCH) / formdata (multipart) | exact | script |
32
+ | Raw body | `postBodyRaw=true` | `body.mode=raw` | exact | script |
33
+ | Header Manager | `HeaderManager` | `request.header` (merged by scope, later wins) | exact | script |
34
+ | HTTP Request Defaults | `ConfigTestElement`/`HttpDefaultsGui` | fallback for blank domain/port/protocol/path | exact | script |
35
+ | Auth Manager Basic/Digest | `AuthManager` | `request.auth` basic/digest (first entry; multiples reported) | exact | script |
36
+ | Auth Manager Kerberos/NTLM | `AuthManager` mechanism | — | unsupported | script |
37
+ | Cookie / Cache / DNS Manager | `CookieManager`/... | auto per-VU cookies; presets not transferred | unsupported | script |
38
+ | Non-HTTP samplers | `JDBC/FTP/LDAP/Java/TCP/Debug/Dummy...Sampler` | — | unsupported | script (reported) |
39
+
40
+ ## Extractors (post-response → test script `pm.collectionVariables.set`)
41
+
42
+ | JMeter | XML | Fidelity | Owner |
43
+ |---|---|---|---|
44
+ | Regular Expression Extractor | `RegexExtractor` | exact (via `new RegExp`, group from `$n$` template) | script |
45
+ | Boundary Extractor | `BoundaryExtractor` | exact (left/right → non-greedy regex) | script |
46
+ | JSON Extractor (native) | `JSONPostProcessor` | exact for dot/index paths; complex → manual | script→model |
47
+ | JSON Path Extractor (plugin) | `...JSONPathExtractor` | exact for simple paths | script→model |
48
+ | XPath / XPath2 Extractor | `XPathExtractor`/`XPath2Extractor` | manual (no sandbox XPath) | model |
49
+ | CSS/HTML Extractor | `HtmlExtractor` | manual (no sandbox CSS) | model |
50
+
51
+ ## Assertions (→ `pm.test`)
52
+
53
+ | JMeter | XML | Maps to | Fidelity | Owner |
54
+ |---|---|---|---|---|
55
+ | Response Assertion | `ResponseAssertion` | status / contains / equals / matches, honouring `test_type` bitmask + NOT | exact | script |
56
+ | — test_field response_code | | `pm.response.code` | exact | script |
57
+ | — test_field response_data | | `pm.response.text()` | exact | script |
58
+ | — test_field headers/message | | `pm.response.headers`/`.status` | exact | script |
59
+ | Duration Assertion | `DurationAssertion` | `pm.test` responseTime + `--pass-if less_than(p95, ms)` hint | exact | script |
60
+ | Size Assertion | `SizeAssertion` | `pm.test` on `responseSize` | approx | script |
61
+ | JSON Assertion | `JSONPathAssertion` | `pm.test` on simple path | approx | script→model |
62
+
63
+ `ResponseAssertion.test_type` bits: `MATCH=1, CONTAINS=2, NOT=4, EQUALS=8, SUBSTRING=16, OR=32`.
64
+
65
+ ## Variables & data
66
+
67
+ | JMeter | XML | Postman | Fidelity | Owner |
68
+ |---|---|---|---|---|
69
+ | User Defined Variables | `Arguments` under TestPlan/scope | collection `variable[]` | exact | script |
70
+ | `${var}` references | inline | `{{var}}` | exact | script |
71
+ | `${__UUID}` / `${__time}` | function | `{{$guid}}` / `{{$timestamp}}` | exact | script |
72
+ | `${__Random(...)}` and other `__fn` | function | `{{$randomInt}}` or flagged reference | approx/manual | script→model |
73
+ | CSV Data Set Config | `CSVDataSet` | `--data-file` + column list | approx (one file/run; naive CSV) | script |
74
+ | Counter / Random Variable | `CounterConfig`/`RandomVariableConfig` | pre-request script or `{{$randomInt}}` | manual | model |
75
+
76
+ ## Scripting
77
+
78
+ | JMeter | XML | Postman | Fidelity | Owner |
79
+ |---|---|---|---|---|
80
+ | JSR223 PreProcessor | `JSR223PreProcessor` | prerequest script (JS, IIFE-wrapped) | model-assisted | script+model |
81
+ | JSR223 PostProcessor | `JSR223PostProcessor` | test script | model-assisted | script+model |
82
+ | BeanShell Pre/PostProcessor | `BeanShell*Processor` | pre/test script | model-assisted | script+model |
83
+ | JSR223 / BeanShell Sampler | `JSR223Sampler`/`BeanShellSampler` | script-only stub (no HTTP) | manual | script+model |
84
+
85
+ ## Timing / pacing — approximated, always reported
86
+
87
+ Postman has no native think time. A fixed delay is recovered where one exists;
88
+ random/throughput/sync shaping is not reproduced.
89
+
90
+ | JMeter | XML | Mapping |
91
+ |---|---|---|
92
+ | Constant Timer (per-request) | `ConstantTimer` under a sampler | pre-request pause of its delay |
93
+ | Constant / Uniform / Gaussian / Poisson Timer (group scope) | `ConstantTimer`/`UniformRandomTimer`/... | `--delay-request <ms>` (fixed part only) |
94
+ | Constant Throughput Timer | `ConstantThroughputTimer` | not reproduced; reported |
95
+ | Throughput Shaping Timer | plugin | not reproduced; reported |
96
+ | Synchronizing Timer | `SyncTimer` | not reproduced; reported |
97
+ | Test Action (pause/stop) | `TestAction` | dropped; reported |
@@ -0,0 +1,56 @@
1
+ # What Postman performance testing supports (the import target)
2
+
3
+ Grounded in the `postman-cli`, `performance-test-api`, and `postman-performance-test` source — not the marketing docs. This is the ceiling the importer maps onto. When JMeter asks for something not here, it is reported, never faked.
4
+
5
+ ## `postman performance run` CLI flags that matter for import
6
+
7
+ | Concern | Flag | Notes |
8
+ |---------|------|-------|
9
+ | Virtual users | `--vu-count <n>` | default 20; cloud runs enforce a **minimum of 10** |
10
+ | Duration | `-d, --duration <minutes>` | **minutes**, not seconds; default 1; cloud ceiling ~120 min (plan-gated) |
11
+ | Load profile | `-p, --load-profile <p>` | one of `fixed`, `ramp-up`, `spike`, `peak`; default `ramp-up` |
12
+ | Data file | `--data-file <path>` | JSON array or CSV; **local runs only**; distribution is forced to `random` |
13
+ | Dataset | `--dataset-id` + `--dataset-view-id` + `--dataset-distribution` | cloud path; distribution `round-robin`/`fixed`/`random` |
14
+ | Pass/fail | `--pass-if "<fn>(<metric>, <value>)"` | **single, not repeatable** |
15
+ | Environment | `-e, --environment <id>` | |
16
+ | Setup/teardown | `--setup-collection` / `--teardown-collection` | run **once** before/after — not concurrent load |
17
+
18
+ There is **no** `--load-segments`, no `--stages`, no `--iterations`, no think-time flag. A latent YAML "stages" schema exists in the CLI repo but is not wired to the command — do not target it.
19
+
20
+ ## Load profiles
21
+
22
+ Four named shapes only, each derived from `(vu-count, duration)`:
23
+
24
+ - `fixed` — constant VUs.
25
+ - `ramp-up` — ramps from 25% to full over the first quarter.
26
+ - `spike` — low baseline, one spike to full mid-run, back down.
27
+ - `peak` — baseline, ramp up, hold, ramp down.
28
+
29
+ JMeter custom ramp curves, stepping schedules, and arrival-rate shapes **cannot be reproduced**. Pick the closest of the four and report the approximation. Rule used by the importer: any ramp/stepping/concurrency/ultimate/arrivals group → `ramp-up`; a flat group with zero ramp → `fixed`.
30
+
31
+ ## `--pass-if` metrics and operators
32
+
33
+ - Functions (operators): `less_than`, `greater_than`, `less_than_eq`, `greater_than_eq`.
34
+ - Metrics (CLI): `avg`, `p90`, `p95`, `p99` (ms); `error_rate`, `failure_rate` (%); `rps`.
35
+ - **Server-side (cloud) accepts a narrower set**: `responseTime.average/p90/p95/p99`, `throughput`, `errorRate`. `failure_rate` is local-only — if you emit it, note it won't gate a cloud run.
36
+ - Only one condition per run. A JMeter Duration Assertion is the only clean source (→ `less_than(p95, <ms>)`); otherwise suggest `less_than(error_rate, 5)` and tell the user it's a default.
37
+
38
+ ## Scripts under load
39
+
40
+ - Pre-request / test scripts run per VU per iteration via `postman-runtime`.
41
+ - `postman.setNextRequest(...)` **is supported** — flow control within one VU's iteration works.
42
+ - Vault access **in scripts** is disabled; per-request timeouts are forced to infinite; each VU gets its own cookie jar.
43
+ - Known engine caveat: mutating `pm.request` inside a script can bleed across VUs. `pm.iterationData` / `pm.variables` are correctly isolated. Avoid importing JMeter logic into `pm.request` mutation.
44
+
45
+ ## Data
46
+
47
+ - One `--data-file` (JSON array of objects, or CSV) per run. Multiple JMeter CSV Data Sets must be merged or a primary chosen.
48
+ - Rows map to VUs/iterations; rows < VUs repeat. The CLI CSV parser is a naive comma split — **no quoted-field/escape support**, so JMeter CSVs with embedded commas/quotes need cleaning.
49
+
50
+ ## Hard limits to encode in every import
51
+
52
+ 1. One collection, repeated across VUs. **No concurrent thread groups.**
53
+ 2. Time-based, not iteration-based. **Loop counts → duration.**
54
+ 3. One `--pass-if`.
55
+ 4. **No native think time** — timers are approximated (`--delay-request` for group scope, a pre-request pause for per-request), not reproduced exactly.
56
+ 5. **HTTP(S) only.**
@@ -0,0 +1,98 @@
1
+ # Translating JSR223 / BeanShell scripts to Postman JavaScript
2
+
3
+ `scripts/groovy_to_js.py` does a best-effort mechanical pass and the importer wraps every result in an IIFE so it is always syntactically valid. It is **never trusted** — each imported script is flagged as a manual task. This file is how you (the model) finish the job.
4
+
5
+ ## Execution-context map
6
+
7
+ JMeter runs these inside a sampler's processor. Postman runs them as a request's `prerequest` or `test` event. Equivalents:
8
+
9
+ | JMeter (Groovy/BeanShell) | Postman JS | Context |
10
+ |---|---|---|
11
+ | `vars.get("x")` / `vars["x"]` | `pm.variables.get("x")` | both |
12
+ | `vars.put("x", v)` | `pm.variables.set("x", v)` | both |
13
+ | `props.get/put(...)` | `pm.variables.get/set(...)` (approx) | both |
14
+ | `log.info/warn/error(...)` | `console.log/warn/error(...)` | both |
15
+ | `prev.getResponseDataAsString()` | `pm.response.text()` | post only |
16
+ | `prev.getResponseCode()` | `String(pm.response.code)` | post only |
17
+ | `prev.getResponseMessage()` | `pm.response.status` | post only |
18
+ | `prev.getTime()` | `pm.response.responseTime` | post only |
19
+ | `ctx.getPreviousResult()` | `pm.response` | post only |
20
+ | `sampler.getUrl()` | `pm.request.url.toString()` | pre only |
21
+ | `sampler.addArgument(k,v)` | `pm.request.url.query.add({key:k,value:v})` or body edit | pre only |
22
+ | `System.currentTimeMillis()` | `Date.now()` | both |
23
+ | `UUID.randomUUID().toString()` | `require('uuid').v4()` | both |
24
+ | `Integer.parseInt(x)` | `parseInt(x)` | both |
25
+
26
+ ## What the mechanical pass cannot do — fix these by hand
27
+
28
+ - **BeanShell implicit result vars** (`ResponseCode`, `ResponseMessage`, `IsSuccess`, `bsh.args`, `Parameters`). No pm.* equivalent in a processor. For a sampler that sets `IsSuccess=false`, express it as a `pm.test(...)` that fails. For `return X`, delete the return (the IIFE makes it legal, but it's a no-op).
29
+ - **Groovy closures / collection methods** (`.each {}`, `.collect {}`, `->`). Rewrite as `for...of` / `.map()` / arrow functions.
30
+ - **`import` / `new ClassName()` / Java types** (`StringBuilder`, `SimpleDateFormat`, `JsonSlurper`). Replace with JS: string concat, `Date`, `JSON.parse`. `JsonSlurper().parseText(prev...)` → `pm.response.json()`.
31
+ - **`try/catch`, multi-statement logic** — valid JS already; just verify semantics.
32
+ - **GString interpolation** `"${x} and ${y}"` → template literal `` `${x} and ${y}` `` **only if** `x`/`y` are JS locals; if they are JMeter vars use `pm.variables.get`.
33
+
34
+ ## Procedure
35
+
36
+ 1. Read the `// --- imported ... (REVIEW) ---` block in the request's event script.
37
+ 2. Compare against the original `<script>` text in the `.jmx` (the IR keeps the element's name).
38
+ 3. Rewrite line by line using the table. Preserve variable names so `{{var}}` references elsewhere still resolve.
39
+ 4. Remove the `// REVIEW` marker once correct.
40
+ 5. Re-run the node syntax gate from `SKILL.md` step 3.
41
+
42
+ ## Example
43
+
44
+ JMeter (Groovy post-processor):
45
+ ```groovy
46
+ import groovy.json.JsonSlurper
47
+ def json = new JsonSlurper().parseText(prev.getResponseDataAsString())
48
+ def token = json.data.token
49
+ vars.put("authToken", token)
50
+ log.info("got " + token)
51
+ ```
52
+ Postman (test script):
53
+ ```javascript
54
+ const json = pm.response.json();
55
+ const token = json.data.token;
56
+ pm.collectionVariables.set("authToken", token);
57
+ console.log("got " + token);
58
+ ```
59
+
60
+ ## Canonical JS templates (match the script byte-for-byte in fallback mode)
61
+
62
+ When importing by hand, emit exactly these so hand output equals the script's. `<default>` empty-string if JMeter has none. Source is `pm.response.text()` normally, or `pm.response.headers.toString()` when the extractor's `useHeaders` is true.
63
+
64
+ **Regex extractor** (`$n$` template → group `n`):
65
+ ```javascript
66
+ try { var _m = String(pm.response.text()).match(new RegExp("<REGEX>")); pm.collectionVariables.set("<ref>", _m ? _m[<n>] : "<default>"); } catch (e) { pm.collectionVariables.set("<ref>", "<default>"); }
67
+ ```
68
+
69
+ **Boundary extractor** (left/right → non-greedy capture):
70
+ ```javascript
71
+ try { var _b = String(pm.response.text()).match(new RegExp("<lboundary>([\\s\\S]*?)<rboundary>")); pm.collectionVariables.set("<ref>", _b ? _b[1] : "<default>"); } catch (e) { pm.collectionVariables.set("<ref>", "<default>"); }
72
+ ```
73
+
74
+ **JSON extractor** (`JSONPostProcessor` / plugin). Only **simple** paths (`$.a.b`, `$.a[0].c`) import; `$`/`$.` → `pm.response.json()`:
75
+ ```javascript
76
+ try { pm.collectionVariables.set("<ref>", pm.response.json().a.b[0]); } catch (e) { pm.collectionVariables.set("<ref>", "<default>"); }
77
+ ```
78
+ Wildcards / filters / recursive descent (`*`, `..`, `?`, `@`) → do NOT import; emit `// [manual] JSONPath "<expr>" too complex` and add a manual task.
79
+
80
+ **Implicit HTTP success** (first test on every request unless a `response_code`/`response_message` assertion or `assume_success` exists):
81
+ ```javascript
82
+ pm.test('JMeter default success (HTTP 200-399)', function () {
83
+ pm.expect(pm.response.code).to.be.within(200, 399);
84
+ });
85
+ ```
86
+
87
+ **Response Assertion** — decode `test_type` bits (`MATCH=1, CONTAINS=2, NOT=4, EQUALS=8, SUBSTRING=16, OR=32`). Field getter: `response_code`→`pm.response.code`, `response_message`→`pm.response.status`, `response_headers`→`pm.response.headers.toString()`, else `pm.response.text()`. For each test string `s`:
88
+ - **EQUALS (8)** on `response_code`: `pm.expect(pm.response.code).to.eql(<s>)` (number). Other fields: `.to.eql("<s>")`.
89
+ - **SUBSTRING (16)** → literal: `pm.expect(<getter>).to.include("<s>")`.
90
+ - **CONTAINS (2) or MATCH (1)** → regex: `pm.expect(String(<getter>)).to.match(new RegExp("<s>"))`.
91
+ - **NOT (4)** → insert `.not` before `.to`.
92
+ Wrap each in `pm.test("<name>", function () { <expr>; });`.
93
+
94
+ **Duration Assertion** → a test plus a perf `--pass-if` hint:
95
+ ```javascript
96
+ pm.test("Response time < <ms>ms", function () { pm.expect(pm.response.responseTime).to.be.below(<ms>); });
97
+ ```
98
+ and set the run's `--pass-if "less_than(p95, <ms>)"`.
@@ -0,0 +1,83 @@
1
+ """Best-effort, deterministic Groovy / BeanShell -> JavaScript translation.
2
+
3
+ This handles the common mechanical rewrites so the model only has to review and
4
+ fix the genuinely ambiguous parts. Every script that passes through here is ALSO
5
+ flagged as a manual task, because no regex pass can guarantee semantic fidelity
6
+ for arbitrary JSR223/BeanShell code.
7
+
8
+ Keep the rules ordered from most specific to least specific. The model may extend
9
+ this table; see references/script-translation.md for the patterns and rationale.
10
+ """
11
+ import re
12
+
13
+ # (pattern, replacement) applied in order. Patterns are plain regex on source text.
14
+ RULES = [
15
+ # --- JMeter bound variables -> pm equivalents ---
16
+ # vars["x"] / vars['x'] -> pm.variables.get("x") (bracket form, must precede .get/.put rules)
17
+ (r'\bvars\[\s*([\'"][^\'"]*[\'"])\s*\]', r'pm.variables.get(\1)'),
18
+ (r'\bprops\[\s*([\'"][^\'"]*[\'"])\s*\]', r'pm.variables.get(\1)'),
19
+ (r'\bvars\.get\(', 'pm.variables.get('),
20
+ (r'\bvars\.put\(', 'pm.variables.set('),
21
+ (r'\bvars\.getObject\(', 'pm.variables.get('),
22
+ (r'\bvars\.putObject\(', 'pm.variables.set('),
23
+ (r'\bprops\.get\(', 'pm.variables.get('),
24
+ (r'\bprops\.put\(', 'pm.variables.set('),
25
+
26
+ # --- logging ---
27
+ (r'\blog\.info\(', 'console.log('),
28
+ (r'\blog\.warn\(', 'console.warn('),
29
+ (r'\blog\.error\(', 'console.error('),
30
+ (r'\blog\.debug\(', 'console.log('),
31
+
32
+ # --- previous-sample result accessors (post-processor context) ---
33
+ (r'\bprev\.getResponseDataAsString\(\)', 'pm.response.text()'),
34
+ (r'\bprev\.getResponseCode\(\)', 'String(pm.response.code)'),
35
+ (r'\bprev\.getResponseMessage\(\)', 'pm.response.status'),
36
+ (r'\bprev\.getResponseHeaders\(\)', 'pm.response.headers.toString()'),
37
+ (r'\bprev\.getTime\(\)', 'pm.response.responseTime'),
38
+ (r'\bctx\.getPreviousResult\(\)', 'pm.response'),
39
+
40
+ # --- sampler mutation (pre-processor context) ---
41
+ (r'\bsampler\.getUrl\(\)', 'pm.request.url.toString()'),
42
+ (r'\bsampler\.addArgument\(', '// [manual] sampler.addArgument -> set a query/body param; '),
43
+
44
+ # --- JDK calls that have JS equivalents ---
45
+ (r'\bSystem\.currentTimeMillis\(\)', 'Date.now()'),
46
+ (r'\bUUID\.randomUUID\(\)\.toString\(\)', 'require("uuid").v4()'),
47
+ (r'\bInteger\.parseInt\(', 'parseInt('),
48
+ (r'\bLong\.parseLong\(', 'parseInt('),
49
+ (r'\bDouble\.parseDouble\(', 'parseFloat('),
50
+ (r'\bString\.valueOf\(', 'String('),
51
+ (r'\bMath\.', 'Math.'), # identity, keeps it explicit
52
+
53
+ # --- declarations: Groovy def / typed decls -> let ---
54
+ (r'\bdef\s+', 'let '),
55
+ (r'\b(?:String|int|long|double|float|boolean|Object|var)\s+(\w+\s*=)', r'let \1'),
56
+
57
+ # --- Groovy GString interpolation "${x}" stays valid-ish; leave for model ---
58
+ ]
59
+
60
+ # Tokens that, if present after translation, almost certainly need human review.
61
+ REVIEW_MARKERS = [
62
+ 'import ', 'new ', 'class ', '.each', '.collect', '->', 'bsh.', 'ctx.',
63
+ 'OUT.', 'SampleResult', 'Thread.sleep', 'try {', 'catch', '@', '::',
64
+ ]
65
+
66
+
67
+ def translate(source: str, lang: str = 'groovy') -> dict:
68
+ """Return {'js': <translated>, 'needs_review': bool, 'reasons': [...]}."""
69
+ if source is None:
70
+ return {'js': '', 'needs_review': False, 'reasons': []}
71
+ js = source
72
+ for pat, repl in RULES:
73
+ js = re.sub(pat, repl, js)
74
+
75
+ # BeanShell implicit result vars have no clean pm.* mapping.
76
+ reasons = []
77
+ if lang and 'beanshell' in lang.lower():
78
+ reasons.append('BeanShell script: implicit vars (ResponseCode, IsSuccess, bsh.args) have no pm.* equivalent')
79
+ for marker in REVIEW_MARKERS:
80
+ if marker in js:
81
+ reasons.append(f'contains "{marker.strip()}" which needs manual translation')
82
+ needs_review = True # always true: no mechanical pass is trustworthy for scripts
83
+ return {'js': js, 'needs_review': needs_review, 'reasons': sorted(set(reasons))}