@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.
- package/README.md +20 -0
- package/dist/studio/client.bundle.js +81 -1
- package/dist/studio/edit.d.ts +22 -0
- package/dist/studio/edit.js +35 -0
- package/dist/studio/import.d.ts +34 -0
- package/dist/studio/import.js +93 -0
- package/dist/studio/index.d.ts +4 -0
- package/dist/studio/index.js +2 -0
- package/dist/studio/recording-id.d.ts +16 -0
- package/dist/studio/recording-id.js +35 -0
- package/dist/studio/server.js +48 -9
- package/dist/studio/stores/apply.d.ts +13 -2
- package/dist/studio/stores/apply.js +68 -1
- package/dist/studio/stores/fs-store.d.ts +11 -1
- package/dist/studio/stores/fs-store.js +26 -1
- package/dist/studio/stores/http-store.d.ts +10 -1
- package/dist/studio/stores/http-store.js +15 -0
- package/dist/studio/stores/local-storage-store.d.ts +4 -1
- package/dist/studio/stores/local-storage-store.js +16 -1
- package/dist/studio/stores/types.d.ts +32 -1
- package/dist/studio/types.d.ts +3 -1
- package/dist/studio/ui/app.js +93 -3
- package/dist/studio/ui/page.d.ts +2 -2
- package/dist/studio/ui/page.js +93 -6
- package/dist/studio/ui/render.d.ts +15 -1
- package/dist/studio/ui/render.js +33 -0
- package/package.json +1 -1
package/dist/studio/ui/page.js
CHANGED
|
@@ -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
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
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:
|
|
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
|
-
|
|
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;
|
package/dist/studio/ui/render.js
CHANGED
|
@@ -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