postman-playwright 1.0.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 +58 -0
- package/dist/captureStream.d.ts +33 -0
- package/dist/captureStream.js +97 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +14 -0
- package/dist/networkLogger.d.ts +1 -0
- package/dist/networkLogger.js +160 -0
- package/dist/store.d.ts +26 -0
- package/dist/store.js +75 -0
- package/dist/types.d.ts +78 -0
- package/dist/types.js +8 -0
- package/dist/utils/fileUtils.d.ts +10 -0
- package/dist/utils/fileUtils.js +24 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.js +16 -0
- package/dist/utils/logUtils.d.ts +26 -0
- package/dist/utils/logUtils.js +27 -0
- package/dist/utils/requestUtils.d.ts +14 -0
- package/dist/utils/requestUtils.js +31 -0
- package/dist/utils/testMapKey.d.ts +10 -0
- package/dist/utils/testMapKey.js +16 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<br>
|
|
2
|
+
|
|
3
|
+
<div align="left">
|
|
4
|
+
<a href="https://www.getpostman.com/">
|
|
5
|
+
<img src="https://voyager.postman.com/logo/postman-logo-orange.svg" alt="Postman Logo" width="400" height="200">
|
|
6
|
+
</a>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<br>
|
|
10
|
+
|
|
11
|
+
# postman-playwright
|
|
12
|
+
|
|
13
|
+
The Postman Playwright integration. This plugin captures API traffic during your Playwright UI tests so the [Postman CLI](https://www.npmjs.com/package/postman-cli) can analyse it — match observed calls to your Postman collections, run `pm.test` assertions against the responses, and surface contract drift, coverage gaps, and silent failures the UI alone can't catch.
|
|
14
|
+
|
|
15
|
+
This package is the capture half of the workflow. It is meant to be used **alongside `postman-cli`**.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install --save-dev postman-cli postman-playwright
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`@playwright/test` (`>=1.40.0`) is a peer dependency — bring your own version.
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
Wrap Playwright's `test` with `attachNetworkCapture` in your spec files:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { test as baseTest, expect } from '@playwright/test';
|
|
31
|
+
import { attachNetworkCapture } from 'postman-playwright';
|
|
32
|
+
|
|
33
|
+
const test = attachNetworkCapture(baseTest);
|
|
34
|
+
|
|
35
|
+
test('has title', async ({ page }) => {
|
|
36
|
+
await page.goto('https://playwright.dev/');
|
|
37
|
+
await expect(page).toHaveTitle(/Playwright/);
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
That is the only change in your spec files. The `page` fixture, `test.describe`, `test.beforeEach`, tags — everything else works exactly as before.
|
|
42
|
+
|
|
43
|
+
Run your tests through the Postman CLI to capture and analyse in one step:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
postman application test -- npx playwright test
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Captured traffic is written to `pm-results/` and analysed against your Postman collections. Use `--environment` to inject collection variables.
|
|
50
|
+
|
|
51
|
+
## Resources
|
|
52
|
+
|
|
53
|
+
- **[Playwright Integration Guide]()** — End-to-end workflow, coverage policies, noise filters, and capturing traffic into collections _(link coming soon)_
|
|
54
|
+
|
|
55
|
+
## Support
|
|
56
|
+
|
|
57
|
+
- [Postman Support](https://support.postman.com/) — For general support and troubleshooting
|
|
58
|
+
- [GitHub Issues](https://github.com/postmanlabs/postman-app-support/issues) — For bug reports and feature requests
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Append-only NDJSON: `t: log` per response; `meta_start` at test begin; `meta_end` when the test finishes.
|
|
3
|
+
* Legacy streams used a single `t: meta` line (same payload as `meta_end`); the CLI still accepts that.
|
|
4
|
+
*/
|
|
5
|
+
import type { NetworkLog, TestStatusEntry } from './types';
|
|
6
|
+
export declare const CAPTURE_STREAM_VERSION: 1;
|
|
7
|
+
export type CaptureStreamLogLine = {
|
|
8
|
+
v: typeof CAPTURE_STREAM_VERSION;
|
|
9
|
+
t: 'log';
|
|
10
|
+
testRunId: string;
|
|
11
|
+
displayKey: string;
|
|
12
|
+
log: NetworkLog;
|
|
13
|
+
};
|
|
14
|
+
export type CaptureStreamMetaStartLine = {
|
|
15
|
+
v: typeof CAPTURE_STREAM_VERSION;
|
|
16
|
+
t: 'meta_start';
|
|
17
|
+
testRunId: string;
|
|
18
|
+
displayKey: string;
|
|
19
|
+
meta: TestStatusEntry;
|
|
20
|
+
};
|
|
21
|
+
export type CaptureStreamMetaEndLine = {
|
|
22
|
+
v: typeof CAPTURE_STREAM_VERSION;
|
|
23
|
+
t: 'meta_end';
|
|
24
|
+
testRunId: string;
|
|
25
|
+
displayKey: string;
|
|
26
|
+
meta: TestStatusEntry;
|
|
27
|
+
};
|
|
28
|
+
export declare function appendCaptureLog(outputDir: string, testRunId: string, displayKey: string, log: NetworkLog): void;
|
|
29
|
+
export declare function appendCaptureMetaStart(outputDir: string, testRunId: string, displayKey: string, meta: TestStatusEntry): void;
|
|
30
|
+
export declare function appendCaptureMetaEnd(outputDir: string, testRunId: string, displayKey: string, meta: TestStatusEntry): void;
|
|
31
|
+
export declare function flushCaptureStream(): void;
|
|
32
|
+
export declare function closeCaptureStream(): void;
|
|
33
|
+
export declare function getCaptureStreamPath(): string | null;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Append-only NDJSON: `t: log` per response; `meta_start` at test begin; `meta_end` when the test finishes.
|
|
4
|
+
* Legacy streams used a single `t: meta` line (same payload as `meta_end`); the CLI still accepts that.
|
|
5
|
+
*/
|
|
6
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
|
+
};
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.CAPTURE_STREAM_VERSION = void 0;
|
|
11
|
+
exports.appendCaptureLog = appendCaptureLog;
|
|
12
|
+
exports.appendCaptureMetaStart = appendCaptureMetaStart;
|
|
13
|
+
exports.appendCaptureMetaEnd = appendCaptureMetaEnd;
|
|
14
|
+
exports.flushCaptureStream = flushCaptureStream;
|
|
15
|
+
exports.closeCaptureStream = closeCaptureStream;
|
|
16
|
+
exports.getCaptureStreamPath = getCaptureStreamPath;
|
|
17
|
+
const crypto_1 = require("crypto");
|
|
18
|
+
const fs_1 = __importDefault(require("fs"));
|
|
19
|
+
const path_1 = __importDefault(require("path"));
|
|
20
|
+
const fileUtils_1 = require("./utils/fileUtils");
|
|
21
|
+
exports.CAPTURE_STREAM_VERSION = 1;
|
|
22
|
+
let captureFd = null;
|
|
23
|
+
let capturePath = null;
|
|
24
|
+
function pidSuffix() {
|
|
25
|
+
return typeof process !== 'undefined' && process.pid != null ? process.pid : 0;
|
|
26
|
+
}
|
|
27
|
+
function cwdKey() {
|
|
28
|
+
const cwd = typeof process !== 'undefined' ? process.cwd() : '';
|
|
29
|
+
return (0, crypto_1.createHash)('sha256').update(cwd).digest('hex').slice(0, 10);
|
|
30
|
+
}
|
|
31
|
+
function ensureCaptureFd(outputDir) {
|
|
32
|
+
if (captureFd !== null) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
(0, fileUtils_1.ensureDir)(outputDir);
|
|
36
|
+
capturePath = path_1.default.join(outputDir, `network-capture-cwd-${cwdKey()}-${pidSuffix()}.ndjson`);
|
|
37
|
+
captureFd = fs_1.default.openSync(capturePath, 'a');
|
|
38
|
+
}
|
|
39
|
+
function appendCaptureLog(outputDir, testRunId, displayKey, log) {
|
|
40
|
+
ensureCaptureFd(outputDir);
|
|
41
|
+
const line = {
|
|
42
|
+
v: exports.CAPTURE_STREAM_VERSION,
|
|
43
|
+
t: 'log',
|
|
44
|
+
testRunId,
|
|
45
|
+
displayKey,
|
|
46
|
+
log,
|
|
47
|
+
};
|
|
48
|
+
fs_1.default.writeSync(captureFd, `${JSON.stringify(line)}\n`);
|
|
49
|
+
}
|
|
50
|
+
function appendCaptureMetaStart(outputDir, testRunId, displayKey, meta) {
|
|
51
|
+
ensureCaptureFd(outputDir);
|
|
52
|
+
const line = {
|
|
53
|
+
v: exports.CAPTURE_STREAM_VERSION,
|
|
54
|
+
t: 'meta_start',
|
|
55
|
+
testRunId,
|
|
56
|
+
displayKey,
|
|
57
|
+
meta,
|
|
58
|
+
};
|
|
59
|
+
fs_1.default.writeSync(captureFd, `${JSON.stringify(line)}\n`);
|
|
60
|
+
}
|
|
61
|
+
function appendCaptureMetaEnd(outputDir, testRunId, displayKey, meta) {
|
|
62
|
+
ensureCaptureFd(outputDir);
|
|
63
|
+
const line = {
|
|
64
|
+
v: exports.CAPTURE_STREAM_VERSION,
|
|
65
|
+
t: 'meta_end',
|
|
66
|
+
testRunId,
|
|
67
|
+
displayKey,
|
|
68
|
+
meta,
|
|
69
|
+
};
|
|
70
|
+
fs_1.default.writeSync(captureFd, `${JSON.stringify(line)}\n`);
|
|
71
|
+
}
|
|
72
|
+
function flushCaptureStream() {
|
|
73
|
+
if (captureFd !== null) {
|
|
74
|
+
fs_1.default.fsyncSync(captureFd);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function closeCaptureStream() {
|
|
78
|
+
if (captureFd !== null) {
|
|
79
|
+
try {
|
|
80
|
+
fs_1.default.fsyncSync(captureFd);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
/* ignore */
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
fs_1.default.closeSync(captureFd);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
/* ignore */
|
|
90
|
+
}
|
|
91
|
+
captureFd = null;
|
|
92
|
+
capturePath = null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function getCaptureStreamPath() {
|
|
96
|
+
return capturePath;
|
|
97
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pm-network-capture — Public API
|
|
3
|
+
*
|
|
4
|
+
* Usage flow:
|
|
5
|
+
* 1. In your test file: import { test as baseTest, expect } from '@playwright/test'
|
|
6
|
+
* 2. import { attachNetworkCapture } from 'pm-network-capture'
|
|
7
|
+
* 3. const test = attachNetworkCapture(baseTest)
|
|
8
|
+
* 4. Use `test` as usual; network requests are captured and saved per test.
|
|
9
|
+
*/
|
|
10
|
+
export { attachNetworkCapture } from './networkLogger';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* pm-network-capture — Public API
|
|
4
|
+
*
|
|
5
|
+
* Usage flow:
|
|
6
|
+
* 1. In your test file: import { test as baseTest, expect } from '@playwright/test'
|
|
7
|
+
* 2. import { attachNetworkCapture } from 'pm-network-capture'
|
|
8
|
+
* 3. const test = attachNetworkCapture(baseTest)
|
|
9
|
+
* 4. Use `test` as usual; network requests are captured and saved per test.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.attachNetworkCapture = void 0;
|
|
13
|
+
var networkLogger_1 = require("./networkLogger");
|
|
14
|
+
Object.defineProperty(exports, "attachNetworkCapture", { enumerable: true, get: function () { return networkLogger_1.attachNetworkCapture; } });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function attachNetworkCapture(test: any): any;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.attachNetworkCapture = attachNetworkCapture;
|
|
37
|
+
const logUtils_1 = require("./utils/logUtils");
|
|
38
|
+
const requestUtils_1 = require("./utils/requestUtils");
|
|
39
|
+
const store = __importStar(require("./store"));
|
|
40
|
+
const captureStream_1 = require("./captureStream");
|
|
41
|
+
const fileUtils_1 = require("./utils/fileUtils");
|
|
42
|
+
const testMapKey_1 = require("./utils/testMapKey");
|
|
43
|
+
const DEFAULT_OUTPUT_DIR = 'pm-results';
|
|
44
|
+
class NetworkLogger {
|
|
45
|
+
constructor() {
|
|
46
|
+
this.logs = [];
|
|
47
|
+
this.currentTestRunId = null;
|
|
48
|
+
this.currentDisplayKey = '';
|
|
49
|
+
}
|
|
50
|
+
setCurrentTestRunId(testRunId) {
|
|
51
|
+
this.currentTestRunId = testRunId;
|
|
52
|
+
}
|
|
53
|
+
setDisplayKey(displayKey) {
|
|
54
|
+
this.currentDisplayKey = displayKey;
|
|
55
|
+
}
|
|
56
|
+
async attach(contextOrPage) {
|
|
57
|
+
contextOrPage.on('response', async (response) => {
|
|
58
|
+
try {
|
|
59
|
+
const req = response.request();
|
|
60
|
+
const [responseBody, requestBody] = await Promise.all([
|
|
61
|
+
(0, requestUtils_1.getResponseBody)(response),
|
|
62
|
+
Promise.resolve((0, requestUtils_1.getRequestBody)(req)),
|
|
63
|
+
]);
|
|
64
|
+
const networkLog = (0, logUtils_1.buildNetworkLog)({
|
|
65
|
+
method: req.method(),
|
|
66
|
+
url: req.url(),
|
|
67
|
+
statusCode: response.status(),
|
|
68
|
+
statusText: response.statusText(),
|
|
69
|
+
requestHeaders: req.headers(),
|
|
70
|
+
responseHeaders: response.headers(),
|
|
71
|
+
requestBody,
|
|
72
|
+
responseBody,
|
|
73
|
+
});
|
|
74
|
+
this.logs.push(networkLog);
|
|
75
|
+
store.addToGlobalLogs(networkLog);
|
|
76
|
+
if (this.currentTestRunId) {
|
|
77
|
+
store.addToTestMap(this.currentTestRunId, networkLog);
|
|
78
|
+
(0, captureStream_1.appendCaptureLog)(DEFAULT_OUTPUT_DIR, this.currentTestRunId, this.currentDisplayKey, networkLog);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
console.error('Failed to capture network log:', error);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
getLogs() {
|
|
87
|
+
return this.logs;
|
|
88
|
+
}
|
|
89
|
+
clearLogs() {
|
|
90
|
+
this.logs = [];
|
|
91
|
+
}
|
|
92
|
+
static clearGlobalTestMap() {
|
|
93
|
+
store.clearGlobalStore();
|
|
94
|
+
}
|
|
95
|
+
static getGlobalTestMap() {
|
|
96
|
+
return store.getGlobalTestMap();
|
|
97
|
+
}
|
|
98
|
+
static getGlobalLogs() {
|
|
99
|
+
return store.getGlobalLogs();
|
|
100
|
+
}
|
|
101
|
+
static saveAll(outputDir = DEFAULT_OUTPUT_DIR) {
|
|
102
|
+
(0, captureStream_1.flushCaptureStream)();
|
|
103
|
+
(0, fileUtils_1.ensureDir)(outputDir);
|
|
104
|
+
(0, captureStream_1.closeCaptureStream)();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const networkCaptureFixtures = {
|
|
108
|
+
page: async ({ page }, use, testInfo) => {
|
|
109
|
+
const logger = new NetworkLogger();
|
|
110
|
+
const projectName = testInfo.project?.name ?? '';
|
|
111
|
+
const titlePath = Array.isArray(testInfo.titlePath) && testInfo.titlePath.length > 0
|
|
112
|
+
? testInfo.titlePath
|
|
113
|
+
: [testInfo.title];
|
|
114
|
+
const displayKey = (0, testMapKey_1.buildNetworkTestMapKey)(projectName, titlePath);
|
|
115
|
+
const testRunId = (typeof testInfo.testId === 'string' && testInfo.testId.length > 0)
|
|
116
|
+
? testInfo.testId
|
|
117
|
+
: displayKey;
|
|
118
|
+
logger.setCurrentTestRunId(testRunId);
|
|
119
|
+
logger.setDisplayKey(displayKey);
|
|
120
|
+
await logger.attach(page.context());
|
|
121
|
+
const startStatusEntry = {
|
|
122
|
+
title_path: titlePath,
|
|
123
|
+
status: 'running',
|
|
124
|
+
duration: 0,
|
|
125
|
+
file: testInfo.file ?? '',
|
|
126
|
+
test_id: testInfo.testId ?? '',
|
|
127
|
+
test_info_errors: [],
|
|
128
|
+
tags: (testInfo.tags ?? []),
|
|
129
|
+
project: projectName,
|
|
130
|
+
};
|
|
131
|
+
(0, captureStream_1.appendCaptureMetaStart)(DEFAULT_OUTPUT_DIR, testRunId, displayKey, startStatusEntry);
|
|
132
|
+
store.setTestStatus(testRunId, startStatusEntry);
|
|
133
|
+
try {
|
|
134
|
+
await use(page);
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
const testInfoErrors = (testInfo.errors ?? []).map((e) => ({
|
|
138
|
+
...(e?.message !== undefined && { message: e.message }),
|
|
139
|
+
...(e?.stack !== undefined && { stack: e.stack }),
|
|
140
|
+
...(e?.value !== undefined && { value: e.value }),
|
|
141
|
+
}));
|
|
142
|
+
const statusEntry = {
|
|
143
|
+
title_path: titlePath,
|
|
144
|
+
status: testInfo.status ?? 'interrupted',
|
|
145
|
+
duration: testInfo.duration ?? 0,
|
|
146
|
+
file: testInfo.file ?? '',
|
|
147
|
+
test_id: testInfo.testId ?? '',
|
|
148
|
+
test_info_errors: testInfoErrors,
|
|
149
|
+
tags: (testInfo.tags ?? []),
|
|
150
|
+
project: projectName,
|
|
151
|
+
};
|
|
152
|
+
store.setTestStatus(testRunId, statusEntry);
|
|
153
|
+
(0, captureStream_1.appendCaptureMetaEnd)(DEFAULT_OUTPUT_DIR, testRunId, displayKey, statusEntry);
|
|
154
|
+
NetworkLogger.saveAll(DEFAULT_OUTPUT_DIR);
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
function attachNetworkCapture(test) {
|
|
159
|
+
return test.extend(networkCaptureFixtures);
|
|
160
|
+
}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global store for captured network logs
|
|
3
|
+
*
|
|
4
|
+
* Flow:
|
|
5
|
+
* - NetworkLogger.attach() captures requests and adds logs via addToGlobalLogs / addToTestMap.
|
|
6
|
+
* - NetworkLogger.saveAll() flushes the NDJSON capture stream (`network-capture-<pid>.ndjson`).
|
|
7
|
+
* - clearGlobalStore() resets state (e.g. between runs or in tests).
|
|
8
|
+
*
|
|
9
|
+
* In-memory maps use Playwright `testInfo.testId` as the key (short, stable on the hot path).
|
|
10
|
+
* The CLI reads `network-capture-*.ndjson` only.
|
|
11
|
+
*/
|
|
12
|
+
import type { NetworkLog, TestNetworkMap, TestStatusEntry, TestStatusMap, TestStatusSummary } from './types';
|
|
13
|
+
export declare function getGlobalTestMap(): TestNetworkMap;
|
|
14
|
+
export declare function getGlobalLogs(): NetworkLog[];
|
|
15
|
+
export declare function clearGlobalStore(): void;
|
|
16
|
+
export declare function addToGlobalLogs(log: NetworkLog): void;
|
|
17
|
+
/** @param testRunId - Playwright `testInfo.testId` (or display key fallback from the fixture) */
|
|
18
|
+
export declare function addToTestMap(testRunId: string, log: NetworkLog): void;
|
|
19
|
+
/** @param testRunId - Same id passed to {@link addToTestMap} for this test run */
|
|
20
|
+
export declare function setTestStatus(testRunId: string, entry: TestStatusEntry): void;
|
|
21
|
+
export declare function getTestStatusMap(): TestStatusMap;
|
|
22
|
+
/**
|
|
23
|
+
* Compute aggregate pass/fail/skip counts from the current status map.
|
|
24
|
+
* Useful for quick reporting without iterating the map yourself.
|
|
25
|
+
*/
|
|
26
|
+
export declare function getTestStatusSummary(): TestStatusSummary;
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Global store for captured network logs
|
|
4
|
+
*
|
|
5
|
+
* Flow:
|
|
6
|
+
* - NetworkLogger.attach() captures requests and adds logs via addToGlobalLogs / addToTestMap.
|
|
7
|
+
* - NetworkLogger.saveAll() flushes the NDJSON capture stream (`network-capture-<pid>.ndjson`).
|
|
8
|
+
* - clearGlobalStore() resets state (e.g. between runs or in tests).
|
|
9
|
+
*
|
|
10
|
+
* In-memory maps use Playwright `testInfo.testId` as the key (short, stable on the hot path).
|
|
11
|
+
* The CLI reads `network-capture-*.ndjson` only.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.getGlobalTestMap = getGlobalTestMap;
|
|
15
|
+
exports.getGlobalLogs = getGlobalLogs;
|
|
16
|
+
exports.clearGlobalStore = clearGlobalStore;
|
|
17
|
+
exports.addToGlobalLogs = addToGlobalLogs;
|
|
18
|
+
exports.addToTestMap = addToTestMap;
|
|
19
|
+
exports.setTestStatus = setTestStatus;
|
|
20
|
+
exports.getTestStatusMap = getTestStatusMap;
|
|
21
|
+
exports.getTestStatusSummary = getTestStatusSummary;
|
|
22
|
+
const captureStream_1 = require("./captureStream");
|
|
23
|
+
let globalTestMap = {};
|
|
24
|
+
let globalLogs = [];
|
|
25
|
+
let globalTestStatusMap = {};
|
|
26
|
+
function getGlobalTestMap() {
|
|
27
|
+
return globalTestMap;
|
|
28
|
+
}
|
|
29
|
+
function getGlobalLogs() {
|
|
30
|
+
return globalLogs;
|
|
31
|
+
}
|
|
32
|
+
function clearGlobalStore() {
|
|
33
|
+
(0, captureStream_1.closeCaptureStream)();
|
|
34
|
+
globalTestMap = {};
|
|
35
|
+
globalLogs = [];
|
|
36
|
+
globalTestStatusMap = {};
|
|
37
|
+
}
|
|
38
|
+
function addToGlobalLogs(log) {
|
|
39
|
+
globalLogs.push(log);
|
|
40
|
+
}
|
|
41
|
+
/** @param testRunId - Playwright `testInfo.testId` (or display key fallback from the fixture) */
|
|
42
|
+
function addToTestMap(testRunId, log) {
|
|
43
|
+
if (!globalTestMap[testRunId]) {
|
|
44
|
+
globalTestMap[testRunId] = [];
|
|
45
|
+
}
|
|
46
|
+
globalTestMap[testRunId].push(log);
|
|
47
|
+
}
|
|
48
|
+
// ── Test-status store ──────────────────────────────────────────────────────
|
|
49
|
+
/** @param testRunId - Same id passed to {@link addToTestMap} for this test run */
|
|
50
|
+
function setTestStatus(testRunId, entry) {
|
|
51
|
+
globalTestStatusMap[testRunId] = entry;
|
|
52
|
+
}
|
|
53
|
+
function getTestStatusMap() {
|
|
54
|
+
return globalTestStatusMap;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Compute aggregate pass/fail/skip counts from the current status map.
|
|
58
|
+
* Useful for quick reporting without iterating the map yourself.
|
|
59
|
+
*/
|
|
60
|
+
function getTestStatusSummary() {
|
|
61
|
+
const summary = {
|
|
62
|
+
total: 0,
|
|
63
|
+
passed: 0,
|
|
64
|
+
failed: 0,
|
|
65
|
+
skipped: 0,
|
|
66
|
+
timedOut: 0,
|
|
67
|
+
interrupted: 0,
|
|
68
|
+
running: 0,
|
|
69
|
+
};
|
|
70
|
+
for (const entry of Object.values(globalTestStatusMap)) {
|
|
71
|
+
summary.total += 1;
|
|
72
|
+
summary[entry.status] = (summary[entry.status] ?? 0) + 1;
|
|
73
|
+
}
|
|
74
|
+
return summary;
|
|
75
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for network capture
|
|
3
|
+
*
|
|
4
|
+
* Used by: networkLogger, store, utils/logUtils, and consumers.
|
|
5
|
+
* Keeps a single definition of NetworkLog and TestNetworkMap.
|
|
6
|
+
*/
|
|
7
|
+
/** A single captured HTTP request/response. */
|
|
8
|
+
export interface NetworkLog {
|
|
9
|
+
method: string;
|
|
10
|
+
url: string;
|
|
11
|
+
statusCode: number;
|
|
12
|
+
statusText: string;
|
|
13
|
+
requestHeaders: Record<string, string>;
|
|
14
|
+
requestBody?: string;
|
|
15
|
+
responseHeaders: Record<string, string>;
|
|
16
|
+
responseBody?: string;
|
|
17
|
+
timestamp: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Map of internal test run id → network logs (in-memory store).
|
|
21
|
+
* Keys are Playwright `testInfo.testId` when present (fixture fallback: display key).
|
|
22
|
+
*/
|
|
23
|
+
export interface TestNetworkMap {
|
|
24
|
+
[testRunId: string]: NetworkLog[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Result status for a single test, mirroring Playwright's TestInfo.status values.
|
|
28
|
+
* `running` is used only on {@link TestStatusEntry} written at capture start (NDJSON `meta_start`).
|
|
29
|
+
*/
|
|
30
|
+
export type TestResultStatus = 'passed' | 'failed' | 'timedOut' | 'skipped' | 'interrupted' | 'running';
|
|
31
|
+
/** Raw error object from Playwright's testInfo.errors — mirrors TestInfoError. */
|
|
32
|
+
export interface TestInfoError {
|
|
33
|
+
/** Human-readable error message. */
|
|
34
|
+
message?: string;
|
|
35
|
+
/** Stack trace string, if available. */
|
|
36
|
+
stack?: string;
|
|
37
|
+
/** Serialized error value, if available. */
|
|
38
|
+
value?: string;
|
|
39
|
+
}
|
|
40
|
+
/** Status data captured for a single test. */
|
|
41
|
+
export interface TestStatusEntry {
|
|
42
|
+
/** Playwright-reported outcome for the test. */
|
|
43
|
+
status: TestResultStatus;
|
|
44
|
+
/** Wall-clock duration of the test in milliseconds. */
|
|
45
|
+
duration: number;
|
|
46
|
+
/** Absolute path to the spec file that contains this test (testInfo.file). */
|
|
47
|
+
file: string;
|
|
48
|
+
/** Unique test identifier assigned by Playwright (testInfo.testId). */
|
|
49
|
+
test_id: string;
|
|
50
|
+
/**
|
|
51
|
+
* Title segments after the project (file → describe → test), aligned with Playwright’s
|
|
52
|
+
* `titlePath` or `[title]` when `titlePath` is empty. Together with `project`, the merged JSON
|
|
53
|
+
* map key is `project + ' › ' + title_path.join(' › ')` (or only `title_path` joined when
|
|
54
|
+
* `project` is empty).
|
|
55
|
+
*/
|
|
56
|
+
title_path: string[];
|
|
57
|
+
/** Full error objects from testInfo.errors — includes message, stack, and value. */
|
|
58
|
+
test_info_errors: TestInfoError[];
|
|
59
|
+
/** Playwright tags attached to the test (testInfo.tags). */
|
|
60
|
+
tags: string[];
|
|
61
|
+
/** Playwright project name the test ran under (testInfo.project.name). */
|
|
62
|
+
project: string;
|
|
63
|
+
}
|
|
64
|
+
/** Map of internal test run id → status entry, accumulated across the worker process. */
|
|
65
|
+
export interface TestStatusMap {
|
|
66
|
+
[testRunId: string]: TestStatusEntry;
|
|
67
|
+
}
|
|
68
|
+
/** Aggregate counts computed from a TestStatusMap. */
|
|
69
|
+
export interface TestStatusSummary {
|
|
70
|
+
total: number;
|
|
71
|
+
passed: number;
|
|
72
|
+
failed: number;
|
|
73
|
+
skipped: number;
|
|
74
|
+
timedOut: number;
|
|
75
|
+
interrupted: number;
|
|
76
|
+
/** Tests currently in progress (capture `meta_start` only; cleared when final status is stored). */
|
|
77
|
+
running: number;
|
|
78
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File system utilities
|
|
3
|
+
*
|
|
4
|
+
* Used by NetworkLogger.saveAll() to write output. Can be reused for custom
|
|
5
|
+
* output paths or formats (e.g. ensureDir before writing, writeJsonFile for JSON).
|
|
6
|
+
*/
|
|
7
|
+
/** Ensure a directory exists, creating it recursively if needed. */
|
|
8
|
+
export declare function ensureDir(dir: string): void;
|
|
9
|
+
/** Write JSON to a file with pretty-printing. */
|
|
10
|
+
export declare function writeJsonFile(filePath: string, data: unknown): void;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* File system utilities
|
|
4
|
+
*
|
|
5
|
+
* Used by NetworkLogger.saveAll() to write output. Can be reused for custom
|
|
6
|
+
* output paths or formats (e.g. ensureDir before writing, writeJsonFile for JSON).
|
|
7
|
+
*/
|
|
8
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.ensureDir = ensureDir;
|
|
13
|
+
exports.writeJsonFile = writeJsonFile;
|
|
14
|
+
const fs_1 = __importDefault(require("fs"));
|
|
15
|
+
/** Ensure a directory exists, creating it recursively if needed. */
|
|
16
|
+
function ensureDir(dir) {
|
|
17
|
+
if (!fs_1.default.existsSync(dir)) {
|
|
18
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** Write JSON to a file with pretty-printing. */
|
|
22
|
+
function writeJsonFile(filePath, data) {
|
|
23
|
+
fs_1.default.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
24
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utils barrel — re-exports file, log, and request helpers for use by networkLogger and by consumers.
|
|
3
|
+
*/
|
|
4
|
+
export { ensureDir, writeJsonFile } from './fileUtils';
|
|
5
|
+
export { isDuplicateLog, buildNetworkLog, type BuildNetworkLogParams } from './logUtils';
|
|
6
|
+
export { getResponseBody, getRequestBody, UNABLE_TO_RETRIEVE_BODY } from './requestUtils';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Utils barrel — re-exports file, log, and request helpers for use by networkLogger and by consumers.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.UNABLE_TO_RETRIEVE_BODY = exports.getRequestBody = exports.getResponseBody = exports.buildNetworkLog = exports.isDuplicateLog = exports.writeJsonFile = exports.ensureDir = void 0;
|
|
7
|
+
var fileUtils_1 = require("./fileUtils");
|
|
8
|
+
Object.defineProperty(exports, "ensureDir", { enumerable: true, get: function () { return fileUtils_1.ensureDir; } });
|
|
9
|
+
Object.defineProperty(exports, "writeJsonFile", { enumerable: true, get: function () { return fileUtils_1.writeJsonFile; } });
|
|
10
|
+
var logUtils_1 = require("./logUtils");
|
|
11
|
+
Object.defineProperty(exports, "isDuplicateLog", { enumerable: true, get: function () { return logUtils_1.isDuplicateLog; } });
|
|
12
|
+
Object.defineProperty(exports, "buildNetworkLog", { enumerable: true, get: function () { return logUtils_1.buildNetworkLog; } });
|
|
13
|
+
var requestUtils_1 = require("./requestUtils");
|
|
14
|
+
Object.defineProperty(exports, "getResponseBody", { enumerable: true, get: function () { return requestUtils_1.getResponseBody; } });
|
|
15
|
+
Object.defineProperty(exports, "getRequestBody", { enumerable: true, get: function () { return requestUtils_1.getRequestBody; } });
|
|
16
|
+
Object.defineProperty(exports, "UNABLE_TO_RETRIEVE_BODY", { enumerable: true, get: function () { return requestUtils_1.UNABLE_TO_RETRIEVE_BODY; } });
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers for network log data
|
|
3
|
+
*
|
|
4
|
+
* Flow in capture:
|
|
5
|
+
* 1. buildNetworkLog() turns raw request/response data into a NetworkLog.
|
|
6
|
+
* 2. isDuplicateLog() checks if the same method+url was already logged (avoids duplicates).
|
|
7
|
+
*
|
|
8
|
+
* Reusable for building or filtering logs outside of Playwright (e.g. from HAR or other sources).
|
|
9
|
+
*/
|
|
10
|
+
import type { NetworkLog } from '../types';
|
|
11
|
+
/** Check if a log with the same method+url already exists in the list. */
|
|
12
|
+
export declare function isDuplicateLog(logs: NetworkLog[], entry: NetworkLog): boolean;
|
|
13
|
+
/** Input shape for buildNetworkLog (timestamp is optional; default is now). */
|
|
14
|
+
export interface BuildNetworkLogParams {
|
|
15
|
+
method: string;
|
|
16
|
+
url: string;
|
|
17
|
+
statusCode: number;
|
|
18
|
+
statusText: string;
|
|
19
|
+
requestHeaders: Record<string, string>;
|
|
20
|
+
responseHeaders: Record<string, string>;
|
|
21
|
+
requestBody?: string;
|
|
22
|
+
responseBody?: string;
|
|
23
|
+
timestamp?: string;
|
|
24
|
+
}
|
|
25
|
+
/** Build a NetworkLog from request/response data. Omits empty requestBody; includes responseBody when present. */
|
|
26
|
+
export declare function buildNetworkLog(params: BuildNetworkLogParams): NetworkLog;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Pure helpers for network log data
|
|
4
|
+
*
|
|
5
|
+
* Flow in capture:
|
|
6
|
+
* 1. buildNetworkLog() turns raw request/response data into a NetworkLog.
|
|
7
|
+
* 2. isDuplicateLog() checks if the same method+url was already logged (avoids duplicates).
|
|
8
|
+
*
|
|
9
|
+
* Reusable for building or filtering logs outside of Playwright (e.g. from HAR or other sources).
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.isDuplicateLog = isDuplicateLog;
|
|
13
|
+
exports.buildNetworkLog = buildNetworkLog;
|
|
14
|
+
/** Check if a log with the same method+url already exists in the list. */
|
|
15
|
+
function isDuplicateLog(logs, entry) {
|
|
16
|
+
return logs.some((log) => log.method === entry.method && log.url === entry.url);
|
|
17
|
+
}
|
|
18
|
+
/** Build a NetworkLog from request/response data. Omits empty requestBody; includes responseBody when present. */
|
|
19
|
+
function buildNetworkLog(params) {
|
|
20
|
+
const { requestBody, responseBody, ...rest } = params;
|
|
21
|
+
return {
|
|
22
|
+
...rest,
|
|
23
|
+
timestamp: params.timestamp ?? new Date().toISOString(),
|
|
24
|
+
...(requestBody != null && requestBody !== '' && { requestBody }),
|
|
25
|
+
...(responseBody != null && { responseBody }),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for reading request/response bodies from Playwright (or similar) objects.
|
|
3
|
+
* Used by NetworkLogger when capturing each response.
|
|
4
|
+
*/
|
|
5
|
+
export declare const UNABLE_TO_RETRIEVE_BODY = "[Unable to retrieve response body]";
|
|
6
|
+
/** Safely read response body; return placeholder on failure. */
|
|
7
|
+
export declare function getResponseBody(response: {
|
|
8
|
+
text(): Promise<string>;
|
|
9
|
+
}): Promise<string>;
|
|
10
|
+
/** Safely read request body (JSON or raw post data). */
|
|
11
|
+
export declare function getRequestBody(request: {
|
|
12
|
+
postDataJSON?: () => unknown;
|
|
13
|
+
postData?: () => string | undefined;
|
|
14
|
+
}): string | undefined;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Helpers for reading request/response bodies from Playwright (or similar) objects.
|
|
4
|
+
* Used by NetworkLogger when capturing each response.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.UNABLE_TO_RETRIEVE_BODY = void 0;
|
|
8
|
+
exports.getResponseBody = getResponseBody;
|
|
9
|
+
exports.getRequestBody = getRequestBody;
|
|
10
|
+
exports.UNABLE_TO_RETRIEVE_BODY = '[Unable to retrieve response body]';
|
|
11
|
+
/** Safely read response body; return placeholder on failure. */
|
|
12
|
+
async function getResponseBody(response) {
|
|
13
|
+
try {
|
|
14
|
+
return await response.text();
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return exports.UNABLE_TO_RETRIEVE_BODY;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** Safely read request body (JSON or raw post data). */
|
|
21
|
+
function getRequestBody(request) {
|
|
22
|
+
try {
|
|
23
|
+
const postData = request.postDataJSON != null
|
|
24
|
+
? JSON.stringify(request.postDataJSON())
|
|
25
|
+
: request.postData?.();
|
|
26
|
+
return postData ?? undefined;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical display key for merged analysis (matches NDJSON `displayKey` / CLI nesting).
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Stable map key for merged JSON and CLI consumption.
|
|
6
|
+
*
|
|
7
|
+
* @param project - `testInfo.project.name` (empty when no project)
|
|
8
|
+
* @param titlePath - File → describe → test segments; same as `_meta.title_path`
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildNetworkTestMapKey(project: string, titlePath: string[]): string;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Canonical display key for merged analysis (matches NDJSON `displayKey` / CLI nesting).
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.buildNetworkTestMapKey = buildNetworkTestMapKey;
|
|
7
|
+
/**
|
|
8
|
+
* Stable map key for merged JSON and CLI consumption.
|
|
9
|
+
*
|
|
10
|
+
* @param project - `testInfo.project.name` (empty when no project)
|
|
11
|
+
* @param titlePath - File → describe → test segments; same as `_meta.title_path`
|
|
12
|
+
*/
|
|
13
|
+
function buildNetworkTestMapKey(project, titlePath) {
|
|
14
|
+
const segments = project ? [project, ...titlePath] : [...titlePath];
|
|
15
|
+
return segments.join(' › ');
|
|
16
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "postman-playwright",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Playwright plugin that captures network traffic during UI tests for use with the Postman CLI.",
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"keywords": [
|
|
20
|
+
"postman",
|
|
21
|
+
"playwright",
|
|
22
|
+
"playwright-plugin",
|
|
23
|
+
"network-capture",
|
|
24
|
+
"api-testing",
|
|
25
|
+
"contract-testing",
|
|
26
|
+
"ui-testing",
|
|
27
|
+
"e2e"
|
|
28
|
+
],
|
|
29
|
+
"homepage": "https://www.postman.com/",
|
|
30
|
+
"author": "Postman Labs <help@postman.com>",
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@playwright/test": ">=1.40.0"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public",
|
|
39
|
+
"registry": "https://registry.npmjs.org"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "rm -rf dist && tsc",
|
|
43
|
+
"prepublishOnly": "npm run build"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^25.4.0",
|
|
47
|
+
"typescript": "^5.3.3"
|
|
48
|
+
}
|
|
49
|
+
}
|