@avivbenhamo/polly-kit 0.1.0 → 0.5.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/LICENSE +21 -0
- package/README.md +298 -138
- package/dist/adapters/encoding.d.ts +7 -0
- package/dist/adapters/encoding.js +46 -0
- package/dist/adapters/undici.d.ts +36 -0
- package/dist/adapters/undici.js +231 -0
- package/dist/browser.d.ts +13 -5
- package/dist/browser.js +21 -25
- package/dist/cli.d.ts +21 -0
- package/dist/cli.js +141 -0
- package/dist/defaults.d.ts +40 -0
- package/dist/defaults.js +40 -0
- package/dist/node.d.ts +56 -0
- package/dist/node.js +108 -0
- package/dist/studio/body.d.ts +28 -0
- package/dist/studio/body.js +66 -0
- package/dist/studio/browser.d.ts +22 -0
- package/dist/studio/browser.js +24 -0
- package/dist/studio/client.bundle.js +290 -0
- package/dist/studio/edit.d.ts +42 -0
- package/dist/studio/edit.js +90 -0
- package/dist/studio/etag.d.ts +6 -0
- package/dist/studio/etag.js +14 -0
- package/dist/studio/har.d.ts +19 -0
- package/dist/studio/har.js +36 -0
- package/dist/studio/headers.d.ts +31 -0
- package/dist/studio/headers.js +59 -0
- package/dist/studio/index.d.ts +9 -0
- package/dist/studio/index.js +5 -0
- package/dist/studio/server.d.ts +35 -0
- package/dist/studio/server.js +262 -0
- package/dist/studio/stores/apply.d.ts +6 -0
- package/dist/studio/stores/apply.js +20 -0
- package/dist/studio/stores/fs-store.d.ts +70 -0
- package/dist/studio/stores/fs-store.js +186 -0
- package/dist/studio/stores/http-store.d.ts +30 -0
- package/dist/studio/stores/http-store.js +67 -0
- package/dist/studio/stores/local-storage-store.d.ts +25 -0
- package/dist/studio/stores/local-storage-store.js +109 -0
- package/dist/studio/stores/types.d.ts +45 -0
- package/dist/studio/stores/types.js +7 -0
- package/dist/studio/types.d.ts +92 -0
- package/dist/studio/types.js +7 -0
- package/dist/studio/ui/app.d.ts +12 -0
- package/dist/studio/ui/app.js +180 -0
- package/dist/studio/ui/client.d.ts +2 -0
- package/dist/studio/ui/client.js +6 -0
- package/dist/studio/ui/page.d.ts +53 -0
- package/dist/studio/ui/page.js +360 -0
- package/dist/studio/ui/render.d.ts +25 -0
- package/dist/studio/ui/render.js +171 -0
- package/dist/vite.d.ts +0 -17
- package/dist/vite.js +44 -7
- package/package.json +55 -7
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { decodeContent } from '../body.js';
|
|
2
|
+
import { getHeader } from '../headers.js';
|
|
3
|
+
/**
|
|
4
|
+
* Rendering is string-in, string-out and lives apart from the DOM so it can be tested
|
|
5
|
+
* without a browser. Recorded bodies are attacker-shaped by nature — they came off the
|
|
6
|
+
* network — so everything interpolated goes through `escapeHtml`.
|
|
7
|
+
*
|
|
8
|
+
* The parameter is not `string`, and the body opens with `String(value)`. `parseHar`'s
|
|
9
|
+
* `isHarShaped` (`src/har.ts`) is a shape check, not a schema validator: it confirms
|
|
10
|
+
* `log.entries` is an array and casts the rest of the parsed JSON to `Har`, so a field
|
|
11
|
+
* this module's types call `string` can be a number, `null` or `undefined` on disk — and
|
|
12
|
+
* `.replaceAll` does not exist on any of those. Design spec §7 requires the UI to open
|
|
13
|
+
* exactly the recordings Polly would reject, so that is an expected input.
|
|
14
|
+
*
|
|
15
|
+
* It is a union of the JSON primitives rather than `unknown`, which is the narrowest
|
|
16
|
+
* type that accepts everything a malformed HAR can put in a text field while still
|
|
17
|
+
* failing the build on `escapeHtml(someObject)` — a call that is always a mistake, and
|
|
18
|
+
* that `unknown` would have accepted silently as `[object Object]`. Callers holding an
|
|
19
|
+
* `unknown` say `String(value)` themselves, which makes the coercion visible at the site
|
|
20
|
+
* that chose it.
|
|
21
|
+
*/
|
|
22
|
+
export function escapeHtml(value) {
|
|
23
|
+
return String(value)
|
|
24
|
+
.replaceAll('&', '&')
|
|
25
|
+
.replaceAll('<', '<')
|
|
26
|
+
.replaceAll('>', '>')
|
|
27
|
+
.replaceAll('"', '"')
|
|
28
|
+
.replaceAll("'", ''');
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A render-boundary guard for fields the *type* declares `number` but that actually
|
|
32
|
+
* came off disk. `isHarShaped` (`src/har.ts`) is a shape check, not a schema
|
|
33
|
+
* validator — it confirms `log.entries` is an array and casts the rest of the parsed
|
|
34
|
+
* JSON to `Har` unchecked, so `_order`, `response.status` and `content.size` are
|
|
35
|
+
* whatever the file says they are. Design spec §7 requires this UI to list and open
|
|
36
|
+
* recordings Polly itself would reject, so a hand-edited or foreign HAR is an expected
|
|
37
|
+
* input, not an edge case. Reasoning per-field about which numbers are "safe" is what
|
|
38
|
+
* produced the gap this closes — a value with the shape `'0" onmouseover=alert(1) x="'`
|
|
39
|
+
* sails straight through a template literal typed as `number`. Stringifying and escaping
|
|
40
|
+
* uniformly, at every site that interpolates one of these fields, closes it without
|
|
41
|
+
* having to trust the type checker over the file.
|
|
42
|
+
*/
|
|
43
|
+
function renderNumeric(value) {
|
|
44
|
+
return escapeHtml(String(value));
|
|
45
|
+
}
|
|
46
|
+
export function renderRecordingList(recordings) {
|
|
47
|
+
if (recordings.length === 0) {
|
|
48
|
+
return '<p class="empty">No recordings here yet. Run your app in Polly\'s record mode first.</p>';
|
|
49
|
+
}
|
|
50
|
+
const rows = recordings
|
|
51
|
+
.map((recording) => {
|
|
52
|
+
// The note sits outside the button: it describes the recording rather than offering
|
|
53
|
+
// an action, and a warning nested inside a control reads as part of the label.
|
|
54
|
+
const note = recording.problem
|
|
55
|
+
? `<p class="ps-rec-note problem">${escapeHtml(recording.problem)}</p>`
|
|
56
|
+
: recording.pollyWillReject
|
|
57
|
+
? `<p class="ps-rec-note warn">Polly will reject this: ${escapeHtml(recording.pollyWillReject)}</p>`
|
|
58
|
+
: '';
|
|
59
|
+
// The count moved inside the button so the row is one target with the id and its
|
|
60
|
+
// size on one line. `app.ts` finds the button with `closest('button')` and reads
|
|
61
|
+
// `dataset.id` off it, so nesting spans inside changes nothing for the click.
|
|
62
|
+
return (`<li><button class="recording" data-id="${escapeHtml(recording.id)}">` +
|
|
63
|
+
`<span class="ps-rec-name">${escapeHtml(recording.id)}</span>` +
|
|
64
|
+
`<span class="count">${renderNumeric(recording.entryCount)}</span></button>${note}</li>`);
|
|
65
|
+
})
|
|
66
|
+
.join('');
|
|
67
|
+
return `<ul class="recordings">${rows}</ul>`;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The other half of the same problem `escapeHtml`'s `String(value)` closes, and the half
|
|
71
|
+
* stringifying a leaf cannot reach. `isHarShaped` checks only that `log.entries` is an
|
|
72
|
+
* array — so `entries: [{}]` is a shape-valid HAR whose entries have no `request` and no
|
|
73
|
+
* `response` at all, and `entry.request.method` or `response.headers.map(...)` dies on a
|
|
74
|
+
* property access before any value reaches `escapeHtml`.
|
|
75
|
+
*
|
|
76
|
+
* Spec §7 wants such a recording listed and opened rather than hidden, so each entry is
|
|
77
|
+
* rendered inside its own boundary: an unreadable entry becomes a row or panel saying so,
|
|
78
|
+
* the readable entries beside it still render, and nothing escapes to the caller. The
|
|
79
|
+
* alternative — validating every field in `parseHar` — is a far larger change and would
|
|
80
|
+
* turn "shows you what is wrong with your file" back into "refuses to open it".
|
|
81
|
+
*
|
|
82
|
+
* The message is escaped like any other interpolated value: it can quote data from the
|
|
83
|
+
* file that produced it.
|
|
84
|
+
*/
|
|
85
|
+
function unrenderable(err) {
|
|
86
|
+
return escapeHtml(err instanceof Error ? err.message : String(err));
|
|
87
|
+
}
|
|
88
|
+
export function renderEntryRow(entry) {
|
|
89
|
+
try {
|
|
90
|
+
// _order is the only thing distinguishing two entries with the same _id, so it is
|
|
91
|
+
// shown rather than hidden — a list with two identical-looking rows is unusable.
|
|
92
|
+
const order = entry._order > 0 ? ` <span class="order">#${renderNumeric(entry._order)}</span>` : '';
|
|
93
|
+
return (`<li><button class="entry" data-id="${escapeHtml(entry._id)}" data-order="${renderNumeric(entry._order)}">` +
|
|
94
|
+
`<span class="method">${escapeHtml(entry.request.method)}</span> ` +
|
|
95
|
+
`<span class="status s${Math.floor(entry.response.status / 100)}">${renderNumeric(entry.response.status)}</span> ` +
|
|
96
|
+
`<span class="url">${escapeHtml(entry.request.url)}</span>${order}</button></li>`);
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
// The catch wraps the whole entry body, so it swallows a genuine bug in this file
|
|
100
|
+
// just as readily as a malformed recording. The message reaches the page, but a
|
|
101
|
+
// stack does not — log it, or a defect on an uncommon path leaves no trace anywhere.
|
|
102
|
+
console.error('polly-studio: could not render entry row', err);
|
|
103
|
+
return `<li class="problem">this entry could not be rendered: ${unrenderable(err)}</li>`;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
export function renderEntryDetail(entry) {
|
|
107
|
+
try {
|
|
108
|
+
return entryDetail(entry);
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
// Logged for the same reason as the row guard above: the message reaches the page,
|
|
112
|
+
// the stack would otherwise reach nothing.
|
|
113
|
+
console.error('polly-studio: could not render entry detail', err);
|
|
114
|
+
// Deliberately not `class="detail"`: `app.ts`'s click handler finds the entry a Save
|
|
115
|
+
// or Delete button belongs to with `closest('.detail')` and reads `_id`/`_order` off
|
|
116
|
+
// its dataset. This panel has neither button and no trustworthy id, so it must not
|
|
117
|
+
// look like one to that handler.
|
|
118
|
+
return `<section class="problem"><p>this entry could not be rendered: ${unrenderable(err)}</p></section>`;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function entryDetail(entry) {
|
|
122
|
+
const response = entry.response;
|
|
123
|
+
const decoded = decodeContent(response.content);
|
|
124
|
+
// Deliberately not a `readonly` textarea for the binary case, however much tidier two
|
|
125
|
+
// identical-looking fields would be: `readPatch` (`src/ui/app.ts`) reads `bodyText`
|
|
126
|
+
// by name and cannot tell a disabled field from an edited one, so the only safe way to
|
|
127
|
+
// say "this body is not editable" is for the field not to exist.
|
|
128
|
+
const body = decoded.kind === 'binary'
|
|
129
|
+
? `<p class="ps-notice warn">This body is binary (${escapeHtml(getHeader(response.headers, 'content-type') ?? 'unknown type')}, ${renderNumeric(response.content.size)} bytes). Editing it as text would replace the recorded bytes with a UTF-8 rendering of them, so it is shown read-only.</p>`
|
|
130
|
+
: `<textarea name="bodyText" rows="16" spellcheck="false" aria-label="Response body">${escapeHtml(decoded.text)}</textarea>`;
|
|
131
|
+
// `name` immediately followed by `value`, with one space and nothing between: the
|
|
132
|
+
// fixed attribute order the DOM-free test harness in `test/http-store.test.ts` reads
|
|
133
|
+
// a field's current value back with. Anything else goes after `value`.
|
|
134
|
+
const headers = response.headers
|
|
135
|
+
.map((header, index) => `<div class="header-row"><input name="headerName${index}" value="${escapeHtml(header.name)}" class="ps-header-name" spellcheck="false" aria-label="Header name"><input name="headerValue${index}" value="${escapeHtml(header.value)}" class="ps-header-value" spellcheck="false" aria-label="Header value"></div>`)
|
|
136
|
+
.join('');
|
|
137
|
+
return `
|
|
138
|
+
<section class="detail" data-id="${escapeHtml(entry._id)}" data-order="${renderNumeric(entry._order)}">
|
|
139
|
+
<div class="ps-block ps-block--request">
|
|
140
|
+
<h2>Request <small>read-only</small></h2>
|
|
141
|
+
<p class="note">Polly matches a replayed request on <code>_id</code>, an md5 of the request built
|
|
142
|
+
from your <code>matchRequestsBy</code> config — and that config is not stored in the recording.
|
|
143
|
+
Editing any of this would leave an entry that looks right and never matches again.</p>
|
|
144
|
+
<dl>
|
|
145
|
+
<dt>Method</dt><dd>${escapeHtml(entry.request.method)}</dd>
|
|
146
|
+
<dt>URL</dt><dd>${escapeHtml(entry.request.url)}</dd>
|
|
147
|
+
<dt>Recorded</dt><dd>${escapeHtml(entry.startedDateTime)}</dd>
|
|
148
|
+
<dt>_id / _order</dt><dd><code>${escapeHtml(entry._id)}</code> / ${renderNumeric(entry._order)}</dd>
|
|
149
|
+
</dl>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
<div class="ps-block ps-block--response">
|
|
153
|
+
<h2>Response</h2>
|
|
154
|
+
<div class="ps-fields">
|
|
155
|
+
<label class="ps-field"><span class="ps-field-label">Status</span><input name="status" type="number" value="${renderNumeric(response.status)}" class="ps-input" min="100" max="599"></label>
|
|
156
|
+
<label class="ps-field"><span class="ps-field-label">Status text</span><input name="statusText" value="${escapeHtml(response.statusText)}" class="ps-input" spellcheck="false"></label>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
<h3 class="ps-group-title">Headers</h3>
|
|
160
|
+
${headers === '' ? '<p class="empty">This response was recorded without headers.</p>' : `<div class="headers">${headers}</div>`}
|
|
161
|
+
|
|
162
|
+
<h3 class="ps-group-title">Body</h3>
|
|
163
|
+
${body}
|
|
164
|
+
|
|
165
|
+
<div class="actions">
|
|
166
|
+
<button name="save">Save response</button>
|
|
167
|
+
<button name="delete" class="danger">Delete entry</button>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
</section>`;
|
|
171
|
+
}
|
package/dist/vite.d.ts
CHANGED
|
@@ -1,20 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* One Vite plugin: the recording store plus the polly-studio editor.
|
|
3
|
-
*
|
|
4
|
-
* Two plugins would be honest about the two concerns, but they always go
|
|
5
|
-
* together and each needed the same `recordingsDir` — so the host app's
|
|
6
|
-
* vite.config gains one import and one line.
|
|
7
|
-
*
|
|
8
|
-
* WHY THE STUDIO'S `basePath` IS NOT SET. Connect strips the mount path before
|
|
9
|
-
* the handler runs — `GET /__polly/api/recordings` arrives as
|
|
10
|
-
* `/api/recordings` — so the handler must not strip it a second time. The
|
|
11
|
-
* browser side does not need to be told either: the page derives its API base
|
|
12
|
-
* from `location.pathname`. Set `basePath` here and every API call 404s.
|
|
13
|
-
*
|
|
14
|
-
* Dev only, deliberately. This hooks `configureServer`, which a production build
|
|
15
|
-
* never calls, and `pollyUi()` disables itself when `NODE_ENV === 'production'`
|
|
16
|
-
* anyway — recordings hold real recorded request and response bodies.
|
|
17
|
-
*/
|
|
18
1
|
import type { Plugin } from 'vite';
|
|
19
2
|
export interface PollyKitOptions {
|
|
20
3
|
/** Where HARs are read from and written to. Gitignore it. */
|
package/dist/vite.js
CHANGED
|
@@ -1,6 +1,40 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* One Vite plugin: the recording store plus the polly-studio editor.
|
|
3
|
+
*
|
|
4
|
+
* Two plugins would be honest about the two concerns, but they always go
|
|
5
|
+
* together and each needed the same `recordingsDir` — so the host app's
|
|
6
|
+
* vite.config gains one import and one line.
|
|
7
|
+
*
|
|
8
|
+
* WHY THE STUDIO'S `basePath` IS NOT SET. Connect strips the mount path before
|
|
9
|
+
* the handler runs — `GET /__polly/api/recordings` arrives as
|
|
10
|
+
* `/api/recordings` — so the handler must not strip it a second time. The
|
|
11
|
+
* browser side does not need to be told either: the page derives its API base
|
|
12
|
+
* from `location.pathname`. Set `basePath` here and every API call 404s.
|
|
13
|
+
*
|
|
14
|
+
* Dev only, deliberately. This hooks `configureServer`, which a production build
|
|
15
|
+
* never calls, and `pollyUi()` disables itself when `NODE_ENV === 'production'`
|
|
16
|
+
* anyway — recordings hold real recorded request and response bodies.
|
|
17
|
+
*/
|
|
18
|
+
import { createRequire } from 'node:module';
|
|
19
|
+
import { pollyUi } from './studio/index.js';
|
|
2
20
|
import { recordingStore, STORE_NAMESPACE } from './store.js';
|
|
3
21
|
const STUDIO_PATH = '/__polly';
|
|
22
|
+
/**
|
|
23
|
+
* The npm `buffer` package, resolved to an absolute path from THIS file.
|
|
24
|
+
*
|
|
25
|
+
* Resolving it from here rather than aliasing to the bare specifier `buffer/`
|
|
26
|
+
* matters twice. Vite warns on a non-absolute alias replacement because it
|
|
27
|
+
* cannot dedupe the result ("will lead to duplicated modules"). And the bare
|
|
28
|
+
* form is resolved from the consuming app's root, so it only works when the
|
|
29
|
+
* installer has hoisted `buffer` out of this package and into theirs. Measured
|
|
30
|
+
* with a `file:` link, where npm does not: the dep optimizer fails with "Cannot
|
|
31
|
+
* optimize dependency: buffer" and every response then errors on `Buffer.from`.
|
|
32
|
+
* A strict node_modules layout (pnpm's default, Yarn PnP) should hit the same
|
|
33
|
+
* thing for the same reason — not measured.
|
|
34
|
+
*
|
|
35
|
+
* `buffer` is a dependency of this package, so this resolution always succeeds.
|
|
36
|
+
*/
|
|
37
|
+
const BUFFER_PATH = createRequire(import.meta.url).resolve('buffer/');
|
|
4
38
|
export default function pollyKit(options = {}) {
|
|
5
39
|
const { recordingsDir = 'recordings', studioPath = STUDIO_PATH, apiNamespace = STORE_NAMESPACE, studio = true, } = options;
|
|
6
40
|
return {
|
|
@@ -39,18 +73,21 @@ export default function pollyKit(options = {}) {
|
|
|
39
73
|
// resolves to a stub and every response errors with
|
|
40
74
|
// "Cannot read properties of undefined (reading 'from')".
|
|
41
75
|
//
|
|
42
|
-
// The
|
|
43
|
-
// `buffer
|
|
44
|
-
//
|
|
76
|
+
// The anchored regex keeps it from also rewriting
|
|
77
|
+
// `buffer/...`; see BUFFER_PATH for why the replacement is
|
|
78
|
+
// an absolute path from this package rather than `buffer/`.
|
|
45
79
|
find: /^buffer$/,
|
|
46
|
-
replacement:
|
|
80
|
+
replacement: BUFFER_PATH,
|
|
47
81
|
},
|
|
48
82
|
],
|
|
49
83
|
},
|
|
50
84
|
optimizeDeps: {
|
|
51
85
|
// `buffer`: pre-bundled alongside the Polly packages that import
|
|
52
86
|
// it, so the dep optimizer does not treat it as an unresolved
|
|
53
|
-
// external on a cold start.
|
|
87
|
+
// external on a cold start. By absolute path for the same reason
|
|
88
|
+
// the alias is — a bare `'buffer'` here is resolved from the
|
|
89
|
+
// consuming app's root, which has no `buffer` unless npm hoisted
|
|
90
|
+
// ours into it.
|
|
54
91
|
//
|
|
55
92
|
// This package itself: without it, Vite discovers the dependency
|
|
56
93
|
// when the entry module first requests it, optimizes, and triggers
|
|
@@ -60,7 +97,7 @@ export default function pollyKit(options = {}) {
|
|
|
60
97
|
// `.vite` cache missed every request, the reload after it recorded
|
|
61
98
|
// fine. Naming it here makes the optimizer run before the server
|
|
62
99
|
// accepts requests, so the first load is the optimized one.
|
|
63
|
-
include: [
|
|
100
|
+
include: [BUFFER_PATH, '@avivbenhamo/polly-kit'],
|
|
64
101
|
// The `define` above does not reach pre-bundled dependencies, and
|
|
65
102
|
// persister-rest is one. This is the half that actually fixes it.
|
|
66
103
|
esbuildOptions: { define: { global: 'globalThis' } },
|
package/package.json
CHANGED
|
@@ -1,21 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avivbenhamo/polly-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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",
|
|
7
|
+
"//engines": "The README states this; without the field npm never enforces it. >=22.6 is what the undici adapter's handler protocol and the studio's server both assume.",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=22.6"
|
|
10
|
+
},
|
|
7
11
|
"files": [
|
|
8
12
|
"dist",
|
|
9
13
|
"README.md"
|
|
10
14
|
],
|
|
15
|
+
"//bin": "The studio on its own, for a checkout with no dev server to mount it in. The mounted form stays the default.",
|
|
16
|
+
"bin": {
|
|
17
|
+
"polly-kit": "./dist/cli.js"
|
|
18
|
+
},
|
|
11
19
|
"main": "./dist/browser.js",
|
|
12
20
|
"types": "./dist/browser.d.ts",
|
|
13
21
|
"//typesVersions": "For consumers still on moduleResolution 'node', which ignores `exports`. Drop once everything is on 'bundler'.",
|
|
14
22
|
"typesVersions": {
|
|
15
23
|
"*": {
|
|
16
|
-
"vite": [
|
|
17
|
-
|
|
18
|
-
|
|
24
|
+
"vite": [
|
|
25
|
+
"./dist/vite.d.ts"
|
|
26
|
+
],
|
|
27
|
+
"store": [
|
|
28
|
+
"./dist/store.d.ts"
|
|
29
|
+
],
|
|
30
|
+
"global-shim": [
|
|
31
|
+
"./dist/global-shim.d.ts"
|
|
32
|
+
],
|
|
33
|
+
"node": [
|
|
34
|
+
"./dist/node.d.ts"
|
|
35
|
+
],
|
|
36
|
+
"studio": [
|
|
37
|
+
"./dist/studio/index.d.ts"
|
|
38
|
+
],
|
|
39
|
+
"studio/browser": [
|
|
40
|
+
"./dist/studio/browser.d.ts"
|
|
41
|
+
]
|
|
19
42
|
}
|
|
20
43
|
},
|
|
21
44
|
"exports": {
|
|
@@ -34,21 +57,43 @@
|
|
|
34
57
|
"./global-shim": {
|
|
35
58
|
"types": "./dist/global-shim.d.ts",
|
|
36
59
|
"default": "./dist/global-shim.js"
|
|
60
|
+
},
|
|
61
|
+
"./node": {
|
|
62
|
+
"types": "./dist/node.d.ts",
|
|
63
|
+
"default": "./dist/node.js"
|
|
64
|
+
},
|
|
65
|
+
"./studio": {
|
|
66
|
+
"types": "./dist/studio/index.d.ts",
|
|
67
|
+
"default": "./dist/studio/index.js"
|
|
68
|
+
},
|
|
69
|
+
"./studio/browser": {
|
|
70
|
+
"types": "./dist/studio/browser.d.ts",
|
|
71
|
+
"default": "./dist/studio/browser.js"
|
|
37
72
|
}
|
|
38
73
|
},
|
|
39
74
|
"publishConfig": {
|
|
40
75
|
"access": "public"
|
|
41
76
|
},
|
|
42
77
|
"//scripts": "dist/ is gitignored, so `prepack` builds it — npm runs prepack before both `npm pack` and `npm publish`. Deliberately not `prepare`: that would also fire in a consumer's tree, where there is no src/ or tsconfig to compile.",
|
|
78
|
+
"//test": "Against dist/, not src/: src imports with `.js` specifiers that Node cannot resolve to the `.ts` files on disk. The build is also what gets published, so every test exercises the artifact.",
|
|
43
79
|
"scripts": {
|
|
44
|
-
"build": "tsc -p tsconfig.json",
|
|
45
|
-
"prepack": "tsc -p tsconfig.json"
|
|
80
|
+
"build": "tsc -p tsconfig.json && npm run bundle",
|
|
81
|
+
"prepack": "tsc -p tsconfig.json && npm run bundle",
|
|
82
|
+
"//bundle": "From dist/, not src/: the client imports `./app.js`, which exists only once tsc has emitted it. server.ts resolves the bundle as `new URL('./client.bundle.js', import.meta.url)` relative to the compiled dist/studio/server.js, so this outfile is the path it looks for.",
|
|
83
|
+
"bundle": "esbuild dist/studio/ui/client.js --bundle --format=esm --target=es2022 --outfile=dist/studio/client.bundle.js",
|
|
84
|
+
"fixture": "node --experimental-strip-types --disable-warning=ExperimentalWarning test/studio/fixtures/record.ts",
|
|
85
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
86
|
+
"test": "npm run build && node --experimental-strip-types --disable-warning=ExperimentalWarning --test \"test/**/*.test.ts\"",
|
|
87
|
+
"studio": "node --experimental-strip-types --disable-warning=ExperimentalWarning src/cli.ts studio"
|
|
46
88
|
},
|
|
47
89
|
"dependencies": {
|
|
48
|
-
"@
|
|
90
|
+
"@pollyjs/adapter": "^6.0.6",
|
|
91
|
+
"@pollyjs/adapter-fetch": "^6.0.7",
|
|
92
|
+
"@pollyjs/adapter-node-http": "^6.0.6",
|
|
49
93
|
"@pollyjs/adapter-xhr": "^6.0.6",
|
|
50
94
|
"@pollyjs/core": "^6.0.6",
|
|
51
95
|
"@pollyjs/node-server": "^6.0.6",
|
|
96
|
+
"@pollyjs/persister-fs": "^6.0.6",
|
|
52
97
|
"@pollyjs/persister-rest": "^6.0.6",
|
|
53
98
|
"buffer": "^6.0.3"
|
|
54
99
|
},
|
|
@@ -62,6 +107,9 @@
|
|
|
62
107
|
},
|
|
63
108
|
"//devDependencies": "vite is here only so `./vite` typechecks standalone; it stays an optional peer at runtime.",
|
|
64
109
|
"devDependencies": {
|
|
110
|
+
"@types/node": "^22.20.4",
|
|
111
|
+
"axios": "^1.20.0",
|
|
112
|
+
"esbuild": "^0.24.2",
|
|
65
113
|
"typescript": "^5.3.3",
|
|
66
114
|
"vite": "^5.0.0"
|
|
67
115
|
}
|