@avivbenhamo/polly-kit 0.6.0 → 0.8.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.
@@ -4,15 +4,31 @@
4
4
  */
5
5
  export const STUDIO_MARKUP = `
6
6
  <div class="polly-studio">
7
+ <!--
8
+ Import sits beside the title, not inside the recordings list.
9
+ It makes a recording rather than picking one, so under the list it read as a
10
+ footer of that list — something you do *to* the thing above it — and it
11
+ pushed the list it was supposedly part of into a shorter and shorter box.
12
+ -->
7
13
  <header class="ps-bar">
8
14
  <div class="ps-brand">
9
15
  <h1>polly-studio</h1>
10
16
  <p class="ps-tagline">Change what a recorded request replies with, then save it back to the HAR.</p>
11
17
  </div>
12
- <p class="warn banner">Polly's <code>keepUnusedRequests</code> defaults to <code>false</code>:
13
- the next record run deletes every entry it did not exercise, including the ones you edit here.
14
- Set <code>persisterOptions.keepUnusedRequests: true</code> before editing.</p>
18
+ <form id="import" class="ps-import">
19
+ <h2 class="ps-import-title">Import</h2>
20
+ <label for="import-name">A HAR from the swagger-recorder extension</label>
21
+ <div class="ps-import-row">
22
+ <input id="import-name" name="recordingName" placeholder="recording name, e.g. my-app" />
23
+ <input id="import-file" name="har" type="file" accept=".har,application/json" />
24
+ <button type="submit">Import</button>
25
+ </div>
26
+ <div id="import-report" aria-live="polite"></div>
27
+ </form>
15
28
  </header>
29
+ <p class="warn banner">Polly's <code>keepUnusedRequests</code> defaults to <code>false</code>:
30
+ the next record run deletes every entry it did not exercise, including the ones you edit here.
31
+ Set <code>persisterOptions.keepUnusedRequests: true</code> before editing.</p>
16
32
  <main class="ps-panes">
17
33
  <section class="ps-pane ps-pane--list">
18
34
  <h2 class="ps-pane-title">Recordings</h2>
@@ -115,6 +131,7 @@ export const STUDIO_STYLE = `
115
131
  --ps-ok: #0a6b3d; --ps-ok-line: #b6ddc6; --ps-ok-soft: #e8f4ed;
116
132
  --ps-warn: #875700; --ps-warn-line: #ecd3a3; --ps-warn-soft: #fbf2e1;
117
133
  --ps-bad: #b3261e; --ps-bad-line: #edbdb9; --ps-bad-soft: #fceceb;
134
+ --ps-lift: 0 1px 2px rgba(17,23,34,.05), 0 10px 22px -14px rgba(17,23,34,.35);
118
135
  --ps-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
119
136
 
120
137
  color-scheme: light;
@@ -130,8 +147,12 @@ export const STUDIO_STYLE = `
130
147
  }
131
148
 
132
149
  /* Title bar ------------------------------------------------------------ */
150
+ /* The title on the left, import on the right, and the warning on its own line
151
+ underneath: three things competing for one row left the import squeezed into
152
+ whatever the banner had not taken. */
133
153
  .polly-studio .ps-bar {
134
- display: flex; flex-wrap: wrap; align-items: center; gap: .6rem 1.75rem;
154
+ display: flex; flex-wrap: wrap; align-items: flex-start;
155
+ justify-content: space-between; gap: .9rem 1.75rem;
135
156
  padding: .8rem 1.25rem;
136
157
  background: var(--ps-surface);
137
158
  border-bottom: 1px solid var(--ps-line);
@@ -140,8 +161,7 @@ export const STUDIO_STYLE = `
140
161
  .polly-studio h1 { margin: 0; font-size: 1.0625rem; font-weight: 650; letter-spacing: -.015em; }
141
162
  .polly-studio .ps-tagline { margin: 0; font-size: .8125rem; color: var(--ps-ink-soft); }
142
163
  .polly-studio .banner {
143
- flex: 1 1 26rem; min-width: 16rem; margin: 0;
144
- padding: .45rem .7rem;
164
+ margin: .9rem 1.25rem 0; padding: .45rem .7rem;
145
165
  font-size: .78rem; line-height: 1.45;
146
166
  background: var(--ps-warn-soft);
147
167
  border: 1px solid var(--ps-warn-line); border-left-width: 3px;
@@ -163,6 +183,9 @@ export const STUDIO_STYLE = `
163
183
  display: flex; flex-direction: column; min-height: 0;
164
184
  border: 1px solid var(--ps-line); border-radius: 12px;
165
185
  background: var(--ps-surface);
186
+ /* Lifted off the paper, so where one region ends and the next begins is a
187
+ shadow rather than a hairline somebody has to look for. */
188
+ box-shadow: var(--ps-lift);
166
189
  overflow: hidden;
167
190
  }
168
191
  /* The two browsers read as one surface you pick from; the editor reads as the page you
@@ -210,6 +233,69 @@ export const STUDIO_STYLE = `
210
233
  }
211
234
  .polly-studio .ps-rec-note { margin: .1rem 0 .3rem .5rem; font-size: .75rem; line-height: 1.4; }
212
235
 
236
+ /* Import form ------------------------------------------------------------ */
237
+ /* A card of its own in the title bar. Bounded width so a long page does not
238
+ stretch it across the screen, and wide enough that the three controls sit on
239
+ one line at the sizes this is actually used at. */
240
+ .polly-studio .ps-import {
241
+ display: grid; gap: .35rem;
242
+ flex: 0 1 34rem; min-width: 17rem;
243
+ padding: .6rem .7rem;
244
+ background: var(--ps-sunken);
245
+ border: 1px solid var(--ps-line); border-radius: 12px;
246
+ box-shadow: var(--ps-lift);
247
+ }
248
+ .polly-studio .ps-import-title {
249
+ margin: 0; font-size: .8125rem; font-weight: 650; color: var(--ps-ink);
250
+ }
251
+ .polly-studio .ps-import-row {
252
+ display: grid; gap: .4rem;
253
+ grid-template-columns: minmax(7rem, 1fr) minmax(8rem, 1.2fr) auto;
254
+ align-items: center;
255
+ }
256
+ .polly-studio .ps-import label {
257
+ font-size: .75rem; font-weight: 600; color: var(--ps-ink-soft);
258
+ }
259
+ .polly-studio .ps-import input {
260
+ width: 100%; min-width: 0;
261
+ font-family: var(--ps-mono); font-size: .8125rem; color: var(--ps-ink);
262
+ background: var(--ps-surface);
263
+ border: 1px solid var(--ps-line-strong); border-radius: 8px;
264
+ padding: .4rem .55rem;
265
+ transition: border-color .12s ease, box-shadow .12s ease;
266
+ }
267
+ .polly-studio .ps-import input:hover { border-color: #a9b5c6; }
268
+ .polly-studio .ps-import input:focus {
269
+ outline: none; border-color: var(--ps-accent); box-shadow: 0 0 0 3px var(--ps-accent-soft);
270
+ }
271
+ .polly-studio .ps-import input[type="file"] { font-size: .75rem; padding: .3rem .4rem; }
272
+ .polly-studio .ps-import button {
273
+ font: inherit; font-size: .8125rem; font-weight: 600;
274
+ justify-self: stretch; white-space: nowrap;
275
+ padding: .45rem .95rem; border-radius: 8px; cursor: pointer;
276
+ border: 1px solid transparent;
277
+ background: var(--ps-accent); color: #fff;
278
+ transition: background-color .12s ease;
279
+ }
280
+ .polly-studio .ps-import button:hover { background: var(--ps-accent-deep); }
281
+ .polly-studio .ps-import button:focus-visible { outline: 2px solid var(--ps-accent); outline-offset: 2px; }
282
+ .polly-studio .ps-import-result {
283
+ margin: .2rem 0 0; padding: .5rem .6rem; font-size: .78rem; line-height: 1.5;
284
+ background: var(--ps-sunken); border: 1px solid var(--ps-line);
285
+ border-left-width: 3px; border-radius: 0 8px 8px 0;
286
+ }
287
+ /* max-height + overflow-y: auto rather than relying on the pane's own scroll: the pane
288
+ around this (.ps-pane) is overflow: hidden, so without its own scroll a long skip
289
+ list would clip silently instead of growing the pane. */
290
+ .polly-studio .ps-import-skips {
291
+ display: grid; gap: .25rem; margin: .4rem 0 0;
292
+ max-height: 10rem; overflow-y: auto;
293
+ }
294
+ .polly-studio .ps-import-skips li {
295
+ padding-left: .5rem; border-left: 2px solid var(--ps-line-strong);
296
+ font-size: .75rem; line-height: 1.4; color: var(--ps-ink-soft);
297
+ }
298
+
213
299
  /* Request list --------------------------------------------------------- */
214
300
  .polly-studio button.entry {
215
301
  display: grid; grid-template-columns: auto auto minmax(0, 1fr) auto;
@@ -350,6 +436,7 @@ export const STUDIO_STYLE = `
350
436
  .polly-studio .ps-pane--list .ps-scroll { max-height: 18rem; }
351
437
  }
352
438
  @media (max-width: 34rem) {
439
+ .polly-studio .ps-import-row { grid-template-columns: minmax(0, 1fr); }
353
440
  .polly-studio .ps-fields { grid-template-columns: minmax(0, 1fr); }
354
441
  .polly-studio .header-row { grid-template-columns: minmax(0, 1fr); }
355
442
  .polly-studio .header-row input[name^="headerName"] { border-right: 0; border-bottom: 1px solid var(--ps-line); }
@@ -1,4 +1,4 @@
1
- import type { RecordingSummary } from '../stores/types.js';
1
+ import type { ImportResult, RecordingSummary } from '../stores/types.js';
2
2
  import type { HarEntry } from '../types.js';
3
3
  /**
4
4
  * Rendering is string-in, string-out and lives apart from the DOM so it can be tested
@@ -21,5 +21,19 @@ import type { HarEntry } from '../types.js';
21
21
  */
22
22
  export declare function escapeHtml(value: string | number | boolean | null | undefined): string;
23
23
  export declare function renderRecordingList(recordings: RecordingSummary[]): string;
24
+ /**
25
+ * What an import did, stated rather than implied.
26
+ *
27
+ * `replaced` is the line that has to be here: new-wins means an import can
28
+ * overwrite a response edited in this very UI, and the only thing standing
29
+ * between that and a silent loss is saying so.
30
+ *
31
+ * `pollyWillReject` is the other half: `ImportResult extends ReadResult`, so a
32
+ * successful import can still land in a file Polly's own `findRecording` will
33
+ * refuse wholesale — a hand-copied plain HAR whose `creator.name` is still
34
+ * `swagger-recorder`, say. Without this, the report says "Created" and nothing
35
+ * else, which reads as success right up until Polly disagrees.
36
+ */
37
+ export declare function renderImportResult(result: ImportResult): string;
24
38
  export declare function renderEntryRow(entry: HarEntry): string;
25
39
  export declare function renderEntryDetail(entry: HarEntry): string;
@@ -66,6 +66,34 @@ export function renderRecordingList(recordings) {
66
66
  .join('');
67
67
  return `<ul class="recordings">${rows}</ul>`;
68
68
  }
69
+ /**
70
+ * What an import did, stated rather than implied.
71
+ *
72
+ * `replaced` is the line that has to be here: new-wins means an import can
73
+ * overwrite a response edited in this very UI, and the only thing standing
74
+ * between that and a silent loss is saying so.
75
+ *
76
+ * `pollyWillReject` is the other half: `ImportResult extends ReadResult`, so a
77
+ * successful import can still land in a file Polly's own `findRecording` will
78
+ * refuse wholesale — a hand-copied plain HAR whose `creator.name` is still
79
+ * `swagger-recorder`, say. Without this, the report says "Created" and nothing
80
+ * else, which reads as success right up until Polly disagrees.
81
+ */
82
+ export function renderImportResult(result) {
83
+ const verb = result.created ? 'Created' : 'Updated';
84
+ const counts = [`added ${result.added}`, `replaced ${result.replaced}`];
85
+ if (result.skipped.length > 0)
86
+ counts.push(`skipped ${result.skipped.length}`);
87
+ const skips = result.skipped.length === 0
88
+ ? ''
89
+ : `<ul class="ps-import-skips">${result.skipped
90
+ .map((skip) => `<li><code>${escapeHtml(skip.url)}</code> — ${escapeHtml(skip.reason)}</li>`)
91
+ .join('')}</ul>`;
92
+ const rejectWarning = result.pollyWillReject === null
93
+ ? ''
94
+ : `<p class="ps-notice warn">Polly will reject this: ${escapeHtml(result.pollyWillReject)}</p>`;
95
+ return `<p class="ps-import-result">${verb} <code>${escapeHtml(result.id)}</code> — ${escapeHtml(counts.join(', '))}.</p>${skips}${rejectWarning}`;
96
+ }
69
97
  /**
70
98
  * The other half of the same problem `escapeHtml`'s `String(value)` closes, and the half
71
99
  * stringifying a leaf cannot reach. `isHarShaped` checks only that `log.entries` is an
@@ -164,8 +192,13 @@ function entryDetail(entry) {
164
192
 
165
193
  <div class="actions">
166
194
  <button name="save">Save response</button>
195
+ <button name="duplicate" title="Append a copy to this request's group, replayed the next time the same request is made">Duplicate for next call</button>
167
196
  <button name="delete" class="danger">Delete entry</button>
168
197
  </div>
198
+ <p class="note">Duplicating adds a second answer to the <em>same</em> request:
199
+ Polly replays <code>_order 0</code> on the first call, this copy on the second.
200
+ It cannot become a different request — the <code>_id</code> is a hash of the
201
+ request, and an edited URL under the old hash would match nothing.</p>
169
202
  </div>
170
203
  </section>`;
171
204
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@avivbenhamo/polly-kit",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "Drop-in record/replay of an app's HTTP traffic with Polly.JS, plus the polly-studio editor. Bring your own host policy.",
5
5
  "license": "MIT",
6
6
  "type": "module",