punchout-simulator 0.1.5 → 0.3.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 (23) hide show
  1. package/README.md +95 -12
  2. package/dist/server/cli.js +931 -297
  3. package/dist/web/assets/{cssMode-WA7HerFP.js → cssMode-UKotGf5X.js} +1 -1
  4. package/dist/web/assets/{freemarker2-BTOdUHiQ.js → freemarker2-0DRATxLT.js} +1 -1
  5. package/dist/web/assets/{handlebars-DdTiW10M.js → handlebars-U7ip3Hjn.js} +1 -1
  6. package/dist/web/assets/{html-CP7qpuUD.js → html-pMwW-Db1.js} +1 -1
  7. package/dist/web/assets/{htmlMode-BG3HQ1Sh.js → htmlMode-BZiR_DER.js} +1 -1
  8. package/dist/web/assets/{index-DQ_OSoxK.js → index-fwAo3vG0.js} +204 -204
  9. package/dist/web/assets/{index-CRWJsM13.css → index-sN4D-IAg.css} +1 -1
  10. package/dist/web/assets/{javascript-IQKq-LZB.js → javascript-BNQzFVRF.js} +1 -1
  11. package/dist/web/assets/{jsonMode-B1p3z-UY.js → jsonMode-CSAIHDlB.js} +1 -1
  12. package/dist/web/assets/{liquid-4Fxw7q97.js → liquid--vvS9GUJ.js} +1 -1
  13. package/dist/web/assets/{mdx-00dxYvhf.js → mdx-BJDQflEF.js} +1 -1
  14. package/dist/web/assets/{python-BI6CPDlf.js → python-CepodNht.js} +1 -1
  15. package/dist/web/assets/{razor-DGRBM6nC.js → razor-BQksENSs.js} +1 -1
  16. package/dist/web/assets/{tsMode-BVtwLDYC.js → tsMode-BRKq1Rx4.js} +1 -1
  17. package/dist/web/assets/{typescript-Dgh1yWP4.js → typescript-V-bP8GlZ.js} +1 -1
  18. package/dist/web/assets/{xml-BOb15_Cq.js → xml-Bgme81_M.js} +1 -1
  19. package/dist/web/assets/{yaml-Dv2aUhkS.js → yaml-CkpLYd3y.js} +1 -1
  20. package/dist/web/favicon.svg +8 -0
  21. package/dist/web/index.html +12 -2
  22. package/package.json +1 -1
  23. package/dist/server/cli.js.map +0 -1
package/README.md CHANGED
@@ -30,12 +30,15 @@ That's it — no install, no build. It boots a local server, opens your browser,
30
30
  and seeds a **built-in demo**: a virtual buyer wired to a built-in mock supplier,
31
31
  so you can run the entire roundtrip immediately:
32
32
 
33
- 1. Open the **Demo Buyer → built-in supplier** connection.
33
+ 1. Open the **Demo Buyer → Demo Supplier** connection.
34
34
  2. **Send SetupRequest** → the mock supplier replies with a StartPage.
35
35
  3. **Open the catalog**, set quantities, **return the cart** — the punchback
36
36
  lands back in the app live.
37
- 4. **Send the OrderRequest** (optionally with attachments, optionally with the
38
- dangling-`cid` test) and inspect the supplier's response.
37
+ 4. **Build the OrderRequest**, edit the cXML if you want (tweak `<Comments>`,
38
+ addresses, attachment refs), optionally attach files at the **order or item
39
+ level**, optionally flip on the **dangling-`cid` test**, then **send it** and
40
+ inspect the supplier's response.
41
+ 5. If validation fails, **edit and re-send** — the flow session and cart persist.
39
42
 
40
43
  Every document — inbound and outbound — is validated and logged.
41
44
 
@@ -46,8 +49,10 @@ npm i -g punchout-simulator && punchout-simulator # persistent install
46
49
  ```
47
50
 
48
51
  ```bash
49
- # Docker
50
- docker run -p 8080:8080 -v "$PWD/data:/data" punchout-simulator
52
+ # Docker (binds 0.0.0.0 inside the container ⇒ counts as "exposed" ⇒ /api needs a
53
+ # token). Set your own, or read the auto-generated ?token= URL from the logs:
54
+ docker run -p 127.0.0.1:8080:8080 -e POS_TOKEN=mysecret -v "$PWD/data:/data" punchout-simulator
55
+ # then open http://localhost:8080/?token=mysecret
51
56
  ```
52
57
 
53
58
  ### CLI options
@@ -57,12 +62,25 @@ docker run -p 8080:8080 -v "$PWD/data:/data" punchout-simulator
57
62
  -d, --data-dir <path> Where to store config + logs (default ./data)
58
63
  --public-url <url> Externally reachable base URL (default http://localhost:<port>)
59
64
  Set this when fronting the tool with ngrok/cloudflared.
65
+ --host <addr> Bind address (default 127.0.0.1; use 0.0.0.0 to expose on the LAN)
66
+ --token <secret> Require this token on /api (auto-generated when exposed; or set POS_TOKEN)
60
67
  --no-open Do not open a browser on start
61
- --no-seed Do not seed the built-in demo connections on first run
68
+ --no-seed Do not seed the built-in demo buyer/supplier/connection on first run
62
69
  --dev Dev mode (do not serve the SPA, do not open a browser)
63
70
  -h, --help Show help
64
71
  ```
65
72
 
73
+ ### Exposure & the admin API
74
+
75
+ The control plane (`/api/*` — config CRUD, logs, the live stream) is **bound to
76
+ `127.0.0.1` by default** and is unauthenticated only for that loopback case. The
77
+ moment the tool is **exposed** — a non-loopback `--public-url` (ngrok/cloudflared)
78
+ or `--host 0.0.0.0` — it **requires a token** on `/api/*`: one is auto-generated
79
+ (or pass `--token`/`POS_TOKEN`) and printed as a `…/?token=…` URL to open the UI
80
+ with. The **inbound buyer surface stays open** (`/sim/*`, `/punchout/return`) so a
81
+ real buyer system can still reach Mode B. Shared secrets are **write-only** over
82
+ the API (masked on read) and **redacted from all logs**.
83
+
66
84
  ---
67
85
 
68
86
  ## What it checks (validation / linting)
@@ -73,7 +91,8 @@ document in both directions, and results are surfaced per message in the UI
73
91
  emit correct cXML?"* linter as a transport driver.
74
92
 
75
93
  General checks: well-formedness, `payloadID`/`timestamp` presence, and
76
- `From`/`To`/`Sender` credential + `SharedSecret` consistency with the connection.
94
+ `From`/`To`/`Sender` credentials + `SharedSecret` consistency with the
95
+ connection's buyer/supplier identities.
77
96
 
78
97
  Document-specific:
79
98
 
@@ -97,6 +116,30 @@ receiver detects the missing attachment. `<Comments>` (and therefore
97
116
  `<Attachment>`) is scanned at **both** levels: `OrderRequestHeader` and each
98
117
  `ItemOut`.
99
118
 
119
+ ### Editing the OrderRequest, attachments & retry
120
+
121
+ - The OrderRequest is built from the returned cart into an editable Monaco view.
122
+ **Edit it before sending** — `<Comments>`, addresses, attachment references,
123
+ anything; the exact document you see is what gets sent.
124
+ - **Attachments are scoped per item or per order**, so the `cid` reference lands
125
+ in the right `ItemOut/Comments` or `OrderRequestHeader/Comments`.
126
+ - **Transfer encoding is a per-connection setting** (`attachmentEncoding`):
127
+ `binary` writes the raw bytes into each MIME part (valid over HTTP and more
128
+ compact), while `base64` is the `Content-Transfer-Encoding` most real
129
+ Ariba/Coupa receivers expect. Switch it in the connection editor to exercise
130
+ either ingestion path; the built-in mock supplier (Mode B) decodes whichever
131
+ it receives. Defaults to `binary`.
132
+ - The flow is a **persistent per-connection session**: switching between the
133
+ Flow and Settings tabs (or connections) keeps your in-progress order, and you
134
+ can **edit and re-send** after a supplier rejection. "New session" starts a
135
+ fresh `BuyerCookie`.
136
+ - Every logged message has a **cXML / Raw toggle**. "Raw" shows the full wire
137
+ message — headers plus, for an order with attachments, the reassembled
138
+ `multipart/related` envelope (boundaries, part headers, `Content-ID`, the cXML
139
+ and each attachment part) — with a one-click **Copy**. The raw body is
140
+ reconstructed on demand from the document + the attachments on disk, so the
141
+ log stays free of inlined base64.
142
+
100
143
  ---
101
144
 
102
145
  ## Network reachability
@@ -124,7 +167,7 @@ backend, so there is no CORS between them).
124
167
  | XML parsing | `fast-xml-parser` |
125
168
  | cXML building | template literals (full control over shape/ordering/`xml:lang`) |
126
169
  | multipart/related | hand-assembled (`Buffer` + boundary) |
127
- | Storage | `lowdb` (`config.json`) for connections · append-only JSONL per session for logs · separate files for attachments |
170
+ | Storage | `lowdb` (`config.json`) for buyers/suppliers/connections/profiles · append-only JSONL per session for logs · separate files for attachments |
128
171
 
129
172
  ```
130
173
  src/
@@ -133,14 +176,54 @@ src/
133
176
  └─ cxml/ build · parse · validate · multipart · types
134
177
  ```
135
178
 
179
+ ### Data model
180
+
181
+ The config is normalized into four entities:
182
+
183
+ - **Buyer** — a reusable party holding its own cXML identity (the `From` credential) and an optional **platform profile** reference (see below).
184
+ - **Supplier** — a reusable party holding its cXML identity (`To`) plus its **endpoints** (PunchOut URL, Order URL) and an optional mock catalog. Endpoints are intrinsic to the supplier — defined once, not per relationship.
185
+ - **Connection** — the edge pairing one Buyer with one Supplier. It holds only what is specific to that pair: which side the tool simulates (`mode`), the `sharedSecret`, an optional per-pair Sender identity override (defaults to the buyer's identity), `authStyle`, `deploymentMode`, and `attachmentEncoding`.
186
+ - **Profile** — a reusable **procurement-platform profile** (Ariba/Coupa/Jaggaer/…) referenced by a Buyer. See below.
187
+
188
+ At send time: `From` = buyer identity, `To` = supplier identity, `Sender` = the connection's override (or the buyer), and the request targets the supplier's endpoints. The mock-supplier endpoints are keyed by supplier id (`/sim/<supplierId>/…`).
189
+
190
+ ### Simulating different procurement platforms (Buyer profiles)
191
+
192
+ Real procurement systems emit cXML differently. A **Profile** captures a platform's
193
+ emission behavior and is assigned to a Buyer, so you can drive a supplier as if it were
194
+ Ariba, Coupa, Jaggaer, Oracle, SAP, or Workday:
195
+
196
+ | Profile field | Effect on the documents the buyer emits |
197
+ |---|---|
198
+ | `dtdVersions` | The `<!DOCTYPE … cXML/<version>/cXML.dtd>` **per document type** (e.g. Coupa: SetupRequest `1.2.014`, PunchOutOrderMessage `1.2.023`). |
199
+ | `userAgent` | The `<UserAgent>` in the Sender. |
200
+ | `setupOperation` | `PunchOutSetupRequest@operation` (`create`/`edit`/`inspect`). |
201
+ | `attachmentEncoding` | Default `Content-Transfer-Encoding` for OrderRequest attachments (`binary`/`base64`). |
202
+ | `cartReturnTransport` | How the punchback is returned in Mode B: `cxml-urlencoded`, `cxml-base64`, or `raw`. |
203
+ | `extrinsics` | `<Extrinsic>` templates injected into the setup/order documents (values may use `${buyerCookie}` / `${orderId}`). |
204
+
205
+ Built-in presets (Ariba, Coupa, Jaggaer, Oracle, SAP, Workday, Generic) ship out of the box
206
+ and can be **loaded into the editor** as a starting point, then customized. A Profile holds
207
+ the *defaults*; a Connection's own `attachmentEncoding` overrides it per pair. A Buyer with no
208
+ profile uses the **Generic** defaults (`1.2.045` / `punchout-simulator` / binary /
209
+ cxml-urlencoded) — i.e. the tool's original behavior.
210
+
211
+ > Authentication is **SharedSecret** only. (MAC / `CredentialMac` — used by network-routed
212
+ > Ariba/SAP flows — is intentionally out of scope for now.)
213
+ >
214
+ > SAP SRM in reality speaks **OCI** (form parameters), which this cXML-only tool cannot emit;
215
+ > the SAP profile models the cXML/Business-Network side (base64 attachments + cart return).
216
+
136
217
  ### Endpoints
137
218
 
138
- - `*/api/connections` — CRUD for connection configs
139
- - `POST /api/connections/:id/setup` — send the SetupRequest, capture + validate the response
140
- - `POST /api/connections/:id/order` — send the OrderRequest (multipart if attachments), capture + validate
219
+ - `*/api/buyers`, `*/api/suppliers` — CRUD for the reusable parties
220
+ - `*/api/profiles` — CRUD for procurement-platform profiles · `GET /api/profile-presets` — the built-in preset library
221
+ - `*/api/connections` — CRUD for connection edges (reference a buyer + supplier)
222
+ - `GET /api/connections/:id/setup/preview` · `POST …/setup` — preview / send the SetupRequest
223
+ - `POST /api/connections/:id/order/preview` · `POST …/order` — preview / send the OrderRequest (multipart if attachments)
141
224
  - `POST /punchout/return` — callback receiving the punchback auto-submit
142
225
  - `GET /api/stream` — SSE live log
143
- - `/sim/:id/*` — Mode B mock supplier (punchout · catalog · checkout · order)
226
+ - `/sim/:supplierId/*` — Mode B mock supplier (punchout · catalog · checkout · order)
144
227
 
145
228
  ---
146
229