rrvideo: improve the video quality and add a progress bar for the CLI tool (#1197)
* refactor rrvideo: use playwright rather than puppeteer * add a progress bar for the tool * add tests for cli.ts * fix build error * add change log * update readme file * Apply a scaling method to improve the resolution of the output video
This commit is contained in:
5
.changeset/lazy-toes-confess.md
Normal file
5
.changeset/lazy-toes-confess.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'rrvideo': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Refactor: Improve the video quality and add a progress bar for the CLI tool
|
||||||
@@ -4,11 +4,12 @@
|
|||||||
|
|
||||||
rrvideo is a tool for transforming the session recorded by [rrweb](https://github.com/rrweb-io/rrweb) into a video.
|
rrvideo is a tool for transforming the session recorded by [rrweb](https://github.com/rrweb-io/rrweb) into a video.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Install rrvideo
|
## Install rrvideo
|
||||||
|
|
||||||
1. Install [ffmpeg](https://ffmpeg.org/download.html)。
|
1. Install [Node.JS](https://nodejs.org/en/download/)。
|
||||||
2. Install [Node.JS](https://nodejs.org/en/download/)。
|
2. Run `npm i -g rrvideo` to install the rrvideo CLI.
|
||||||
3. Run `npm i -g rrvideo` to install the rrvideo CLI。
|
|
||||||
|
|
||||||
## Use rrvideo
|
## Use rrvideo
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@ rrvideo is a tool for transforming the session recorded by [rrweb](https://githu
|
|||||||
rrvideo --input PATH_TO_YOUR_RRWEB_EVENTS_FILE
|
rrvideo --input PATH_TO_YOUR_RRWEB_EVENTS_FILE
|
||||||
```
|
```
|
||||||
|
|
||||||
Running this command will output a `rrvideo-output.mp4` file in the current working directory.
|
Running this command will output a `rrvideo-output.webm` file in the current working directory.
|
||||||
|
|
||||||
### Config the output path
|
### Config the output path
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,12 @@
|
|||||||
|
|
||||||
rrvideo 是用于将 [rrweb](https://github.com/rrweb-io/rrweb) 录制的数据转为视频格式的工具。
|
rrvideo 是用于将 [rrweb](https://github.com/rrweb-io/rrweb) 录制的数据转为视频格式的工具。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## 安装 rrvideo
|
## 安装 rrvideo
|
||||||
|
|
||||||
1. 安装 [ffmpeg](https://ffmpeg.org/download.html)。
|
1. 安装 [Node.JS](https://nodejs.org/en/download/)。
|
||||||
2. 安装 [Node.JS](https://nodejs.org/en/download/)。
|
2. 执行 `npm i -g rrvideo` 以安装 rrvideo CLI。
|
||||||
3. 执行 `npm i -g rrvideo` 以安装 rrvideo CLI。
|
|
||||||
|
|
||||||
## 使用 rrvideo
|
## 使用 rrvideo
|
||||||
|
|
||||||
@@ -16,7 +17,7 @@ rrvideo 是用于将 [rrweb](https://github.com/rrweb-io/rrweb) 录制的数据
|
|||||||
rrvideo --input PATH_TO_YOUR_RRWEB_EVENTS_FILE
|
rrvideo --input PATH_TO_YOUR_RRWEB_EVENTS_FILE
|
||||||
```
|
```
|
||||||
|
|
||||||
运行以上命令会在执行文件夹中生成一个 `rrvideo-output.mp4` 文件。
|
运行以上命令会在执行文件夹中生成一个 `rrvideo-output.webm` 文件。
|
||||||
|
|
||||||
### 指定输出路径
|
### 指定输出路径
|
||||||
|
|
||||||
|
|||||||
BIN
packages/rrvideo/demo/demo.gif
Normal file
BIN
packages/rrvideo/demo/demo.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 616 KiB |
6
packages/rrvideo/jest.config.js
Normal file
6
packages/rrvideo/jest.config.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// eslint-disable-next-line tsdoc/syntax
|
||||||
|
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
|
||||||
|
module.exports = {
|
||||||
|
preset: 'ts-jest',
|
||||||
|
testEnvironment: 'node',
|
||||||
|
};
|
||||||
@@ -11,18 +11,28 @@
|
|||||||
],
|
],
|
||||||
"types": "build/index.d.ts",
|
"types": "build/index.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"install": "playwright install",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
|
"test": "jest",
|
||||||
|
"check-types": "tsc -noEmit",
|
||||||
"prepublish": "yarn build"
|
"prepublish": "yarn build"
|
||||||
},
|
},
|
||||||
"author": "yanzhen@smartx.com",
|
"author": "yanzhen@smartx.com",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/fs-extra": "11.0.1",
|
||||||
|
"@types/jest": "^27.4.1",
|
||||||
"@types/minimist": "^1.2.1",
|
"@types/minimist": "^1.2.1",
|
||||||
|
"@types/node": "^18.15.11",
|
||||||
|
"jest": "^27.5.1",
|
||||||
|
"ts-jest": "^27.1.3",
|
||||||
"@rrweb/types": "^2.0.0-alpha.8"
|
"@rrweb/types": "^2.0.0-alpha.8"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@open-tech-world/cli-progress-bar": "^2.0.2",
|
||||||
|
"fs-extra": "^11.1.1",
|
||||||
"minimist": "^1.2.5",
|
"minimist": "^1.2.5",
|
||||||
"puppeteer": "^19.7.2",
|
"playwright": "^1.32.1",
|
||||||
"rrweb-player": "^2.0.0-alpha.8"
|
"rrweb-player": "^2.0.0-alpha.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import minimist from 'minimist';
|
import minimist from 'minimist';
|
||||||
|
import { ProgressBar } from '@open-tech-world/cli-progress-bar';
|
||||||
import type { RRwebPlayerOptions } from 'rrweb-player';
|
import type { RRwebPlayerOptions } from 'rrweb-player';
|
||||||
import { transformToVideo } from './index';
|
import { transformToVideo } from './index';
|
||||||
|
|
||||||
@@ -24,10 +25,18 @@ if (argv.config) {
|
|||||||
>;
|
>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pBar = new ProgressBar({ prefix: 'Transforming' });
|
||||||
|
const onProgressUpdate = (percent: number) => {
|
||||||
|
if (percent < 1) pBar.run({ value: percent * 100, total: 100 });
|
||||||
|
else
|
||||||
|
pBar.run({ value: 100, total: 100, prefix: 'Transformation Completed!' });
|
||||||
|
};
|
||||||
|
|
||||||
transformToVideo({
|
transformToVideo({
|
||||||
input: argv.input as string,
|
input: argv.input as string,
|
||||||
output: argv.output as string,
|
output: argv.output as string,
|
||||||
rrwebPlayer: config,
|
rrwebPlayer: config,
|
||||||
|
onProgressUpdate,
|
||||||
})
|
})
|
||||||
.then((file) => {
|
.then((file) => {
|
||||||
console.log(`Successfully transformed into "${file}".`);
|
console.log(`Successfully transformed into "${file}".`);
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs-extra';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { spawn } from 'child_process';
|
import { chromium } from 'playwright';
|
||||||
import puppeteer from 'puppeteer';
|
import { EventType, eventWithTime } from '@rrweb/types';
|
||||||
import type { Page, Browser } from 'puppeteer';
|
|
||||||
import type { eventWithTime } from '@rrweb/types';
|
|
||||||
import type { RRwebPlayerOptions } from 'rrweb-player';
|
import type { RRwebPlayerOptions } from 'rrweb-player';
|
||||||
|
|
||||||
const rrwebScriptPath = path.resolve(
|
const rrwebScriptPath = path.resolve(
|
||||||
@@ -13,38 +11,38 @@ const rrwebScriptPath = path.resolve(
|
|||||||
const rrwebStylePath = path.resolve(rrwebScriptPath, '../style.css');
|
const rrwebStylePath = path.resolve(rrwebScriptPath, '../style.css');
|
||||||
const rrwebRaw = fs.readFileSync(rrwebScriptPath, 'utf-8');
|
const rrwebRaw = fs.readFileSync(rrwebScriptPath, 'utf-8');
|
||||||
const rrwebStyle = fs.readFileSync(rrwebStylePath, 'utf-8');
|
const rrwebStyle = fs.readFileSync(rrwebStylePath, 'utf-8');
|
||||||
|
// The max valid scale value for the scaling method which can improve the video quality.
|
||||||
|
const MaxScaleValue = 2.5;
|
||||||
|
|
||||||
type RRvideoConfig = {
|
type RRvideoConfig = {
|
||||||
input: string;
|
input: string;
|
||||||
output?: string;
|
output?: string;
|
||||||
headless?: boolean;
|
headless?: boolean;
|
||||||
fps?: number;
|
// A number between 0 and 1. The higher the value, the better the quality of the video.
|
||||||
cb?: (file: string, error: null | Error) => void;
|
resolutionRatio?: number;
|
||||||
// start playback delay time
|
// A callback function that will be called when the progress of the replay is updated.
|
||||||
startDelayTime?: number;
|
onProgressUpdate?: (percent: number) => void;
|
||||||
rrwebPlayer?: Omit<RRwebPlayerOptions['props'], 'events'>;
|
rrwebPlayer?: Omit<RRwebPlayerOptions['props'], 'events'>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultConfig: Required<RRvideoConfig> = {
|
const defaultConfig: Required<RRvideoConfig> = {
|
||||||
input: '',
|
input: '',
|
||||||
output: 'rrvideo-output.mp4',
|
output: 'rrvideo-output.webm',
|
||||||
headless: true,
|
headless: true,
|
||||||
fps: 15,
|
// A good trade-off value between quality and file size.
|
||||||
cb: () => {
|
resolutionRatio: 0.8,
|
||||||
|
onProgressUpdate: () => {
|
||||||
//
|
//
|
||||||
},
|
},
|
||||||
startDelayTime: 1000,
|
|
||||||
rrwebPlayer: {},
|
rrwebPlayer: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
function getHtml(
|
function getHtml(events: Array<eventWithTime>, config?: RRvideoConfig): string {
|
||||||
events: Array<eventWithTime>,
|
|
||||||
config?: Omit<RRwebPlayerOptions['props'], 'events'>,
|
|
||||||
): string {
|
|
||||||
return `
|
return `
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<style>${rrwebStyle}</style>
|
<style>${rrwebStyle}</style>
|
||||||
|
<style>html, body {padding: 0; border: none; margin: 0;}</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script>
|
<script>
|
||||||
@@ -55,163 +53,122 @@ function getHtml(
|
|||||||
'<\\/script>',
|
'<\\/script>',
|
||||||
)};
|
)};
|
||||||
/*-->*/
|
/*-->*/
|
||||||
const userConfig = ${JSON.stringify(config || {})};
|
const userConfig = ${JSON.stringify(config?.rrwebPlayer || {})};
|
||||||
window.replayer = new rrwebPlayer({
|
window.replayer = new rrwebPlayer({
|
||||||
target: document.body,
|
target: document.body,
|
||||||
|
width: userConfig.width,
|
||||||
|
height: userConfig.height,
|
||||||
props: {
|
props: {
|
||||||
...userConfig,
|
...userConfig,
|
||||||
events,
|
events,
|
||||||
showController: false,
|
showController: false,
|
||||||
autoPlay: false, // autoPlay off by default
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
window.replayer.addEventListener('finish', () => window.onReplayFinish());
|
window.replayer.addEventListener('finish', () => window.onReplayFinish());
|
||||||
|
window.replayer.addEventListener('ui-update-progress', (payload)=> window.onReplayProgressUpdate
|
||||||
|
(payload));
|
||||||
|
window.replayer.addEventListener('resize',()=>document.querySelector('.replayer-wrapper').style.transform = 'scale(${
|
||||||
|
(config?.resolutionRatio ?? 1) * MaxScaleValue
|
||||||
|
}) translate(-50%, -50%)');
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RRvideo {
|
/**
|
||||||
private browser!: Browser;
|
* Preprocess all events to get a maximum view port size.
|
||||||
private page!: Page;
|
*/
|
||||||
private state: 'idle' | 'recording' | 'closed' = 'idle';
|
function getMaxViewport(events: eventWithTime[]) {
|
||||||
private config = {
|
let maxWidth = 0,
|
||||||
...defaultConfig,
|
maxHeight = 0;
|
||||||
};
|
events.forEach((event) => {
|
||||||
|
if (event.type !== EventType.Meta) return;
|
||||||
constructor(config: RRvideoConfig) {
|
if (event.data.width > maxWidth) maxWidth = event.data.width;
|
||||||
this.updateConfig(config);
|
if (event.data.height > maxHeight) maxHeight = event.data.height;
|
||||||
}
|
|
||||||
|
|
||||||
public async transform() {
|
|
||||||
try {
|
|
||||||
this.browser = await puppeteer.launch({
|
|
||||||
headless: this.config.headless,
|
|
||||||
});
|
|
||||||
this.page = await this.browser.newPage();
|
|
||||||
await this.page.goto('about:blank');
|
|
||||||
|
|
||||||
await this.page.exposeFunction('onReplayFinish', () => {
|
|
||||||
void this.finishRecording();
|
|
||||||
});
|
|
||||||
|
|
||||||
const eventsPath = path.isAbsolute(this.config.input)
|
|
||||||
? this.config.input
|
|
||||||
: path.resolve(process.cwd(), this.config.input);
|
|
||||||
const events = JSON.parse(
|
|
||||||
fs.readFileSync(eventsPath, 'utf-8'),
|
|
||||||
) as eventWithTime[];
|
|
||||||
|
|
||||||
await this.page.setContent(getHtml(events, this.config.rrwebPlayer));
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
void this.startRecording().then(() => {
|
|
||||||
return this.page.evaluate('window.replayer.play();');
|
|
||||||
});
|
|
||||||
}, this.config.startDelayTime);
|
|
||||||
} catch (error) {
|
|
||||||
this.config.cb('', error as Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public updateConfig(config: RRvideoConfig) {
|
|
||||||
if (!config.input) throw new Error('input is required');
|
|
||||||
config.output = config.output || defaultConfig.output;
|
|
||||||
Object.assign(this.config, defaultConfig, config);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async startRecording() {
|
|
||||||
this.state = 'recording';
|
|
||||||
let wrapperSelector = '.replayer-wrapper';
|
|
||||||
if (this.config.rrwebPlayer.width && this.config.rrwebPlayer.height) {
|
|
||||||
wrapperSelector = '.rr-player';
|
|
||||||
}
|
|
||||||
const wrapperEl = await this.page.$(wrapperSelector);
|
|
||||||
|
|
||||||
if (!wrapperEl) {
|
|
||||||
throw new Error('failed to get replayer element');
|
|
||||||
}
|
|
||||||
|
|
||||||
// start ffmpeg
|
|
||||||
const args = [
|
|
||||||
// fps
|
|
||||||
'-framerate',
|
|
||||||
this.config.fps.toString(),
|
|
||||||
// input
|
|
||||||
'-f',
|
|
||||||
'image2pipe',
|
|
||||||
'-i',
|
|
||||||
'-',
|
|
||||||
// output
|
|
||||||
'-y',
|
|
||||||
this.config.output,
|
|
||||||
];
|
|
||||||
|
|
||||||
const ffmpegProcess = spawn('ffmpeg', args);
|
|
||||||
ffmpegProcess.stderr.setEncoding('utf-8');
|
|
||||||
ffmpegProcess.stderr.on('data', console.log);
|
|
||||||
|
|
||||||
let processError: Error | null = null;
|
|
||||||
|
|
||||||
const timer = setInterval(() => {
|
|
||||||
if (this.state === 'recording' && !processError) {
|
|
||||||
void wrapperEl
|
|
||||||
.screenshot({
|
|
||||||
encoding: 'binary',
|
|
||||||
})
|
|
||||||
.then((buffer) => ffmpegProcess.stdin.write(buffer))
|
|
||||||
.catch();
|
|
||||||
} else {
|
|
||||||
clearInterval(timer);
|
|
||||||
if (this.state === 'closed' && !processError) {
|
|
||||||
ffmpegProcess.stdin.end();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 1000 / this.config.fps);
|
|
||||||
|
|
||||||
const outputPath = path.isAbsolute(this.config.output)
|
|
||||||
? this.config.output
|
|
||||||
: path.resolve(process.cwd(), this.config.output);
|
|
||||||
ffmpegProcess.on('close', () => {
|
|
||||||
if (processError) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.config.cb(outputPath, null);
|
|
||||||
});
|
|
||||||
ffmpegProcess.on('error', (error) => {
|
|
||||||
if (processError) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
processError = error;
|
|
||||||
this.config.cb(outputPath, error);
|
|
||||||
});
|
|
||||||
ffmpegProcess.stdin.on('error', (error) => {
|
|
||||||
if (processError) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
processError = error;
|
|
||||||
this.config.cb(outputPath, error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async finishRecording() {
|
|
||||||
this.state = 'closed';
|
|
||||||
await this.browser.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function transformToVideo(config: RRvideoConfig): Promise<string> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const rrvideo = new RRvideo({
|
|
||||||
...config,
|
|
||||||
cb(file, error) {
|
|
||||||
if (error) {
|
|
||||||
return reject(error);
|
|
||||||
}
|
|
||||||
resolve(file);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
void rrvideo.transform();
|
|
||||||
});
|
});
|
||||||
|
return {
|
||||||
|
width: maxWidth,
|
||||||
|
height: maxHeight,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function transformToVideo(options: RRvideoConfig) {
|
||||||
|
const defaultVideoDir = '__rrvideo__temp__';
|
||||||
|
const config = { ...defaultConfig };
|
||||||
|
if (!options.input) throw new Error('input is required');
|
||||||
|
// If the output is not specified or undefined, use the default value.
|
||||||
|
if (!options.output) delete options.output;
|
||||||
|
Object.assign(config, options);
|
||||||
|
if (config.resolutionRatio > 1) config.resolutionRatio = 1; // The max value is 1.
|
||||||
|
|
||||||
|
const eventsPath = path.isAbsolute(config.input)
|
||||||
|
? config.input
|
||||||
|
: path.resolve(process.cwd(), config.input);
|
||||||
|
const outputPath = path.isAbsolute(config.output)
|
||||||
|
? config.output
|
||||||
|
: path.resolve(process.cwd(), config.output);
|
||||||
|
const events = JSON.parse(
|
||||||
|
fs.readFileSync(eventsPath, 'utf-8'),
|
||||||
|
) as eventWithTime[];
|
||||||
|
|
||||||
|
// Make the browser viewport fit the player size.
|
||||||
|
const maxViewport = getMaxViewport(events);
|
||||||
|
// Use the scaling method to improve the video quality.
|
||||||
|
const scaledViewport = {
|
||||||
|
width: Math.round(
|
||||||
|
maxViewport.width * (config.resolutionRatio ?? 1) * MaxScaleValue,
|
||||||
|
),
|
||||||
|
height: Math.round(
|
||||||
|
maxViewport.height * (config.resolutionRatio ?? 1) * MaxScaleValue,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
Object.assign(config.rrwebPlayer, scaledViewport);
|
||||||
|
const browser = await chromium.launch({
|
||||||
|
headless: config.headless,
|
||||||
|
});
|
||||||
|
const context = await browser.newContext({
|
||||||
|
viewport: scaledViewport,
|
||||||
|
recordVideo: {
|
||||||
|
dir: defaultVideoDir,
|
||||||
|
size: scaledViewport,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const page = await context.newPage();
|
||||||
|
await page.goto('about:blank');
|
||||||
|
await page.exposeFunction(
|
||||||
|
'onReplayProgressUpdate',
|
||||||
|
(data: { payload: number }) => {
|
||||||
|
config.onProgressUpdate(data.payload);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait for the replay to finish
|
||||||
|
await new Promise<void>(
|
||||||
|
(resolve) =>
|
||||||
|
void page
|
||||||
|
.exposeFunction('onReplayFinish', () => resolve())
|
||||||
|
.then(() => page.setContent(getHtml(events, config))),
|
||||||
|
);
|
||||||
|
const videoPath = (await page.video()?.path()) || '';
|
||||||
|
const cleanFiles = async (videoPath: string) => {
|
||||||
|
await fs.remove(videoPath);
|
||||||
|
if ((await fs.readdir(defaultVideoDir)).length === 0) {
|
||||||
|
await fs.remove(defaultVideoDir);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
await context.close();
|
||||||
|
await Promise.all([
|
||||||
|
fs
|
||||||
|
.move(videoPath, outputPath, { overwrite: true })
|
||||||
|
.catch((e) => {
|
||||||
|
console.error(
|
||||||
|
"Can't create video file. Please check the output path.",
|
||||||
|
e,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.finally(() => void cleanFiles(videoPath)),
|
||||||
|
browser.close(),
|
||||||
|
]);
|
||||||
|
return outputPath;
|
||||||
}
|
}
|
||||||
|
|||||||
45
packages/rrvideo/test/cli.test.ts
Normal file
45
packages/rrvideo/test/cli.test.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { execSync } from 'child_process';
|
||||||
|
import * as fs from 'fs-extra';
|
||||||
|
import * as path from 'path';
|
||||||
|
import exampleEvents from './events/example';
|
||||||
|
|
||||||
|
describe('should be able to run cli', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
fs.mkdirSync(path.resolve(__dirname, './generated'));
|
||||||
|
fs.writeJsonSync(
|
||||||
|
path.resolve(__dirname, './generated/example.json'),
|
||||||
|
exampleEvents,
|
||||||
|
{
|
||||||
|
spaces: 2,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
afterAll(async () => {
|
||||||
|
await fs.remove(path.resolve(__dirname, './generated'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error without input path', () => {
|
||||||
|
expect(() => {
|
||||||
|
execSync('node ./build/cli.js', { stdio: 'pipe' });
|
||||||
|
}).toThrowError(/.*please pass --input to your rrweb events file.*/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate a video without output path', () => {
|
||||||
|
execSync('node ./build/cli.js --input ./test/generated/example.json', {
|
||||||
|
stdio: 'pipe',
|
||||||
|
});
|
||||||
|
const outputFile = path.resolve(__dirname, '../rrvideo-output.webm');
|
||||||
|
expect(fs.existsSync(outputFile)).toBe(true);
|
||||||
|
fs.removeSync(outputFile);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate a video with specific output path', () => {
|
||||||
|
const outputFile = path.resolve(__dirname, './generated/output.webm');
|
||||||
|
execSync(
|
||||||
|
`node ./build/cli.js --input ./test/generated/example.json --output ${outputFile}`,
|
||||||
|
{ stdio: 'pipe' },
|
||||||
|
);
|
||||||
|
expect(fs.existsSync(outputFile)).toBe(true);
|
||||||
|
fs.removeSync(outputFile);
|
||||||
|
});
|
||||||
|
});
|
||||||
147
packages/rrvideo/test/events/example.ts
Normal file
147
packages/rrvideo/test/events/example.ts
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
import { EventType, IncrementalSource } from '@rrweb/types';
|
||||||
|
import type { eventWithTime } from '@rrweb/types';
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
const events: eventWithTime[] = [
|
||||||
|
{
|
||||||
|
type: EventType.DomContentLoaded,
|
||||||
|
data: {},
|
||||||
|
timestamp: now,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: EventType.Load,
|
||||||
|
data: {},
|
||||||
|
timestamp: now + 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: EventType.Meta,
|
||||||
|
data: {
|
||||||
|
href: 'http://localhost',
|
||||||
|
width: 1000,
|
||||||
|
height: 800,
|
||||||
|
},
|
||||||
|
timestamp: now + 100,
|
||||||
|
},
|
||||||
|
// full snapshot:
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
node: {
|
||||||
|
id: 1,
|
||||||
|
type: 0,
|
||||||
|
childNodes: [
|
||||||
|
{ id: 2, name: 'html', type: 1, publicId: '', systemId: '' },
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
type: 2,
|
||||||
|
tagName: 'html',
|
||||||
|
attributes: { lang: 'en' },
|
||||||
|
childNodes: [
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
type: 2,
|
||||||
|
tagName: 'head',
|
||||||
|
attributes: {},
|
||||||
|
childNodes: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
type: 2,
|
||||||
|
tagName: 'body',
|
||||||
|
attributes: {},
|
||||||
|
childNodes: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
initialOffset: { top: 0, left: 0 },
|
||||||
|
},
|
||||||
|
type: EventType.FullSnapshot,
|
||||||
|
timestamp: now + 100,
|
||||||
|
},
|
||||||
|
// mutation that adds select elements
|
||||||
|
{
|
||||||
|
type: EventType.IncrementalSnapshot,
|
||||||
|
data: {
|
||||||
|
source: IncrementalSource.Mutation,
|
||||||
|
texts: [],
|
||||||
|
attributes: [],
|
||||||
|
removes: [],
|
||||||
|
adds: [
|
||||||
|
{
|
||||||
|
parentId: 5,
|
||||||
|
nextId: null,
|
||||||
|
node: {
|
||||||
|
type: 2,
|
||||||
|
tagName: 'select',
|
||||||
|
childNodes: [],
|
||||||
|
attributes: {},
|
||||||
|
id: 26,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
parentId: 26,
|
||||||
|
nextId: null,
|
||||||
|
node: {
|
||||||
|
type: 2,
|
||||||
|
tagName: 'option',
|
||||||
|
attributes: { value: 'valueC' },
|
||||||
|
childNodes: [],
|
||||||
|
id: 27,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
parentId: 27,
|
||||||
|
nextId: null,
|
||||||
|
node: { type: 3, textContent: 'C', id: 28 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
parentId: 26,
|
||||||
|
nextId: 27,
|
||||||
|
node: {
|
||||||
|
type: 2,
|
||||||
|
tagName: 'option',
|
||||||
|
attributes: { value: 'valueB', selected: true },
|
||||||
|
childNodes: [],
|
||||||
|
id: 29,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
parentId: 26,
|
||||||
|
nextId: 29,
|
||||||
|
node: {
|
||||||
|
type: 2,
|
||||||
|
tagName: 'option',
|
||||||
|
attributes: { value: 'valueA' },
|
||||||
|
childNodes: [],
|
||||||
|
id: 30,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
parentId: 30,
|
||||||
|
nextId: null,
|
||||||
|
node: { type: 3, textContent: 'A', id: 31 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
parentId: 29,
|
||||||
|
nextId: null,
|
||||||
|
node: { type: 3, textContent: 'B', id: 32 },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
timestamp: now + 200,
|
||||||
|
},
|
||||||
|
// input event
|
||||||
|
{
|
||||||
|
type: EventType.IncrementalSnapshot,
|
||||||
|
data: {
|
||||||
|
source: IncrementalSource.Input,
|
||||||
|
text: 'valueA',
|
||||||
|
isChecked: false,
|
||||||
|
id: 26,
|
||||||
|
},
|
||||||
|
timestamp: now + 300,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default events;
|
||||||
3
packages/rrvideo/test/tsconfig.json
Normal file
3
packages/rrvideo/test/tsconfig.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {}
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"forceConsistentCasingInFileNames": true
|
"forceConsistentCasingInFileNames": true
|
||||||
},
|
},
|
||||||
|
"exclude": ["build", "node_modules", "test"],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"path": "../rrweb-player"
|
"path": "../rrweb-player"
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
"@types/chai": "^4.1.4",
|
"@types/chai": "^4.1.4",
|
||||||
"@types/jest": "^27.0.2",
|
"@types/jest": "^27.0.2",
|
||||||
"@types/jsdom": "^20.0.0",
|
"@types/jsdom": "^20.0.0",
|
||||||
"@types/node": "^10.11.3",
|
"@types/node": "^18.15.11",
|
||||||
"@types/puppeteer": "^1.12.4",
|
"@types/puppeteer": "^1.12.4",
|
||||||
"cross-env": "^5.2.0",
|
"cross-env": "^5.2.0",
|
||||||
"jest": "^27.2.4",
|
"jest": "^27.2.4",
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
"@types/inquirer": "^8.2.1",
|
"@types/inquirer": "^8.2.1",
|
||||||
"@types/jest": "^27.4.1",
|
"@types/jest": "^27.4.1",
|
||||||
"@types/jest-image-snapshot": "^5.1.0",
|
"@types/jest-image-snapshot": "^5.1.0",
|
||||||
"@types/node": "^17.0.21",
|
"@types/node": "^18.15.11",
|
||||||
"@types/offscreencanvas": "^2019.6.4",
|
"@types/offscreencanvas": "^2019.6.4",
|
||||||
"@types/puppeteer": "^5.4.4",
|
"@types/puppeteer": "^5.4.4",
|
||||||
"construct-style-sheets-polyfill": "^3.1.0",
|
"construct-style-sheets-polyfill": "^3.1.0",
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ const events: eventWithTime[] = [
|
|||||||
node: {
|
node: {
|
||||||
type: 2,
|
type: 2,
|
||||||
tagName: 'select',
|
tagName: 'select',
|
||||||
|
attributes: {},
|
||||||
childNodes: [],
|
childNodes: [],
|
||||||
id: 26,
|
id: 26,
|
||||||
},
|
},
|
||||||
|
|||||||
166
yarn.lock
166
yarn.lock
@@ -2517,6 +2517,24 @@
|
|||||||
"@nodelib/fs.scandir" "2.1.5"
|
"@nodelib/fs.scandir" "2.1.5"
|
||||||
fastq "^1.6.0"
|
fastq "^1.6.0"
|
||||||
|
|
||||||
|
"@open-tech-world/cli-progress-bar@^2.0.2":
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@open-tech-world/cli-progress-bar/-/cli-progress-bar-2.0.2.tgz#9a4f3470ee460224e7a38790d0d3f257fe9d6812"
|
||||||
|
integrity sha512-miNmdKNdKp7Lhy295wxnJcWFrbIAoQmypoCynlG8HTQPxsG5dhOpPL5udsZFoW99RxcLAj2JTCaEhrosTzPE3g==
|
||||||
|
dependencies:
|
||||||
|
"@open-tech-world/es-cli-styles" "^0.3.0"
|
||||||
|
"@open-tech-world/es-utils" "^0.10.0"
|
||||||
|
|
||||||
|
"@open-tech-world/es-cli-styles@^0.3.0":
|
||||||
|
version "0.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@open-tech-world/es-cli-styles/-/es-cli-styles-0.3.0.tgz#316c3c2934ed87629366a5827bc2353fa4c061c7"
|
||||||
|
integrity sha512-0SyxBAUkUDkjt83snx1QvPDk6zJJIb/xRbY0daWGG6ONCfx5RITxc0qty2tLmyBNsCM41+V1shtHNeSrEBu8KQ==
|
||||||
|
|
||||||
|
"@open-tech-world/es-utils@^0.10.0":
|
||||||
|
version "0.10.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@open-tech-world/es-utils/-/es-utils-0.10.0.tgz#97ca72c7dad63dcf9a3534b01f3d763f187bafb2"
|
||||||
|
integrity sha512-JqpxcVk3Go4RAArI3oB18fhIMeeuNSThwmSQM7ScygVv2/AHRAxkkiodtggC7QniJvLs7i5AzL9pILNvAEBYDg==
|
||||||
|
|
||||||
"@polka/url@^0.5.0":
|
"@polka/url@^0.5.0":
|
||||||
version "0.5.0"
|
version "0.5.0"
|
||||||
resolved "https://registry.npmjs.org/@polka/url/-/url-0.5.0.tgz"
|
resolved "https://registry.npmjs.org/@polka/url/-/url-0.5.0.tgz"
|
||||||
@@ -2805,6 +2823,14 @@
|
|||||||
resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz"
|
resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz"
|
||||||
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
|
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
|
||||||
|
|
||||||
|
"@types/fs-extra@11.0.1":
|
||||||
|
version "11.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-11.0.1.tgz#f542ec47810532a8a252127e6e105f487e0a6ea5"
|
||||||
|
integrity sha512-MxObHvNl4A69ofaTRU8DFqvgzzv8s9yRtaPPm5gud9HDNvpB3GPQFvNuTWAI59B9huVGV5jXYJwbCsmBsOGYWA==
|
||||||
|
dependencies:
|
||||||
|
"@types/jsonfile" "*"
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/graceful-fs@^4.1.2":
|
"@types/graceful-fs@^4.1.2":
|
||||||
version "4.1.5"
|
version "4.1.5"
|
||||||
resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz"
|
resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz"
|
||||||
@@ -2885,6 +2911,13 @@
|
|||||||
resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz"
|
resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz"
|
||||||
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
|
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
|
||||||
|
|
||||||
|
"@types/jsonfile@*":
|
||||||
|
version "6.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/jsonfile/-/jsonfile-6.1.1.tgz#ac84e9aefa74a2425a0fb3012bdea44f58970f1b"
|
||||||
|
integrity sha512-GSgiRCVeapDN+3pqA35IkQwasaCh/0YFH5dEF6S88iDvEn901DjOeH3/QPY+XYP1DFzDZPvIvfeEgk+7br5png==
|
||||||
|
dependencies:
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/lodash.mergewith@4.6.6":
|
"@types/lodash.mergewith@4.6.6":
|
||||||
version "4.6.6"
|
version "4.6.6"
|
||||||
resolved "https://registry.yarnpkg.com/@types/lodash.mergewith/-/lodash.mergewith-4.6.6.tgz#c4698f5b214a433ff35cb2c75ee6ec7f99d79f10"
|
resolved "https://registry.yarnpkg.com/@types/lodash.mergewith/-/lodash.mergewith-4.6.6.tgz#c4698f5b214a433ff35cb2c75ee6ec7f99d79f10"
|
||||||
@@ -2917,20 +2950,15 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.24.tgz#c37ac69cb2948afb4cef95f424fa0037971a9a5c"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.24.tgz#c37ac69cb2948afb4cef95f424fa0037971a9a5c"
|
||||||
integrity sha512-yxDeaQIAJlMav7fH5AQqPH1u8YIuhYJXYBzxaQ4PifsU0GDO38MSdmEDeRlIxrKbC6NbEaaEHDanWb+y30U8SQ==
|
integrity sha512-yxDeaQIAJlMav7fH5AQqPH1u8YIuhYJXYBzxaQ4PifsU0GDO38MSdmEDeRlIxrKbC6NbEaaEHDanWb+y30U8SQ==
|
||||||
|
|
||||||
"@types/node@^10.11.3":
|
|
||||||
version "10.17.60"
|
|
||||||
resolved "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz"
|
|
||||||
integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==
|
|
||||||
|
|
||||||
"@types/node@^12.7.1":
|
"@types/node@^12.7.1":
|
||||||
version "12.20.55"
|
version "12.20.55"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240"
|
||||||
integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==
|
integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==
|
||||||
|
|
||||||
"@types/node@^17.0.21":
|
"@types/node@^18.15.11":
|
||||||
version "17.0.21"
|
version "18.15.11"
|
||||||
resolved "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.11.tgz#b3b790f09cb1696cffcec605de025b088fa4225f"
|
||||||
integrity sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==
|
integrity sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==
|
||||||
|
|
||||||
"@types/normalize-package-data@^2.4.0":
|
"@types/normalize-package-data@^2.4.0":
|
||||||
version "2.4.1"
|
version "2.4.1"
|
||||||
@@ -4292,13 +4320,6 @@ chrome-launcher@0.15.0:
|
|||||||
is-wsl "^2.2.0"
|
is-wsl "^2.2.0"
|
||||||
lighthouse-logger "^1.0.0"
|
lighthouse-logger "^1.0.0"
|
||||||
|
|
||||||
chromium-bidi@0.4.5:
|
|
||||||
version "0.4.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/chromium-bidi/-/chromium-bidi-0.4.5.tgz#a352e755536dde609bd2c77e4b1f0906bff8784e"
|
|
||||||
integrity sha512-rkav9YzRfAshSTG3wNXF7P7yNiI29QAo1xBXElPoCoSQR5n20q3cOyVhDv6S7+GlF/CJ/emUxlQiR0xOPurkGg==
|
|
||||||
dependencies:
|
|
||||||
mitt "3.0.0"
|
|
||||||
|
|
||||||
ci-info@^2.0.0:
|
ci-info@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz"
|
resolved "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz"
|
||||||
@@ -4645,16 +4666,6 @@ core-util-is@^1.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
|
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
|
||||||
integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
|
integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
|
||||||
|
|
||||||
cosmiconfig@8.1.0:
|
|
||||||
version "8.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.1.0.tgz#947e174c796483ccf0a48476c24e4fefb7e1aea8"
|
|
||||||
integrity sha512-0tLZ9URlPGU7JsKq0DQOQ3FoRsYX8xDZ7xMiATQfaiGMz7EHowNkbU9u1coAOmnh9p/1ySpm0RB3JNWRXM5GCg==
|
|
||||||
dependencies:
|
|
||||||
import-fresh "^3.2.1"
|
|
||||||
js-yaml "^4.1.0"
|
|
||||||
parse-json "^5.0.0"
|
|
||||||
path-type "^4.0.0"
|
|
||||||
|
|
||||||
cosmiconfig@^5.0.0:
|
cosmiconfig@^5.0.0:
|
||||||
version "5.2.1"
|
version "5.2.1"
|
||||||
resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz"
|
resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz"
|
||||||
@@ -5147,11 +5158,6 @@ devtools-protocol@0.0.1036444:
|
|||||||
resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.1036444.tgz#a570d3cdde61527c82f9b03919847b8ac7b1c2b9"
|
resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.1036444.tgz#a570d3cdde61527c82f9b03919847b8ac7b1c2b9"
|
||||||
integrity sha512-0y4f/T8H9lsESV9kKP1HDUXgHxCdniFeJh6Erq+FbdOEvp/Ydp9t8kcAAM5gOd17pMrTDlFWntoHtzzeTUWKNw==
|
integrity sha512-0y4f/T8H9lsESV9kKP1HDUXgHxCdniFeJh6Erq+FbdOEvp/Ydp9t8kcAAM5gOd17pMrTDlFWntoHtzzeTUWKNw==
|
||||||
|
|
||||||
devtools-protocol@0.0.1094867:
|
|
||||||
version "0.0.1094867"
|
|
||||||
resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.1094867.tgz#2ab93908e9376bd85d4e0604aa2651258f13e374"
|
|
||||||
integrity sha512-pmMDBKiRVjh0uKK6CT1WqZmM3hBVSgD+N2MrgyV1uNizAZMw4tx6i/RTc+/uCsKSCmg0xXx7arCP/OFcIwTsiQ==
|
|
||||||
|
|
||||||
devtools-protocol@0.0.869402:
|
devtools-protocol@0.0.869402:
|
||||||
version "0.0.869402"
|
version "0.0.869402"
|
||||||
resolved "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.869402.tgz"
|
resolved "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.869402.tgz"
|
||||||
@@ -6626,6 +6632,15 @@ fs-extra@^10.1.0:
|
|||||||
jsonfile "^6.0.1"
|
jsonfile "^6.0.1"
|
||||||
universalify "^2.0.0"
|
universalify "^2.0.0"
|
||||||
|
|
||||||
|
fs-extra@^11.1.1:
|
||||||
|
version "11.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.1.1.tgz#da69f7c39f3b002378b0954bb6ae7efdc0876e2d"
|
||||||
|
integrity sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==
|
||||||
|
dependencies:
|
||||||
|
graceful-fs "^4.2.0"
|
||||||
|
jsonfile "^6.0.1"
|
||||||
|
universalify "^2.0.0"
|
||||||
|
|
||||||
fs-extra@^7.0.1, fs-extra@~7.0.1:
|
fs-extra@^7.0.1, fs-extra@~7.0.1:
|
||||||
version "7.0.1"
|
version "7.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"
|
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"
|
||||||
@@ -6873,16 +6888,6 @@ glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
|
|||||||
once "^1.3.0"
|
once "^1.3.0"
|
||||||
path-is-absolute "^1.0.0"
|
path-is-absolute "^1.0.0"
|
||||||
|
|
||||||
glob@^9.2.0:
|
|
||||||
version "9.3.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/glob/-/glob-9.3.0.tgz#be6e50d172d025c3fcf87903ae25b36b787c0bb0"
|
|
||||||
integrity sha512-EAZejC7JvnQINayvB/7BJbpZpNOJ8Lrw2OZNEvQxe0vaLn1SuwMcfV7/MNaX8L/T0wmptBFI4YMtDvSBxYDc7w==
|
|
||||||
dependencies:
|
|
||||||
fs.realpath "^1.0.0"
|
|
||||||
minimatch "^7.4.1"
|
|
||||||
minipass "^4.2.4"
|
|
||||||
path-scurry "^1.6.1"
|
|
||||||
|
|
||||||
glob@~7.2.0:
|
glob@~7.2.0:
|
||||||
version "7.2.2"
|
version "7.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.2.tgz#29deb38e1ef90f132d5958abe9c3ee8e87f3c318"
|
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.2.tgz#29deb38e1ef90f132d5958abe9c3ee8e87f3c318"
|
||||||
@@ -9406,11 +9411,6 @@ lru-cache@^6.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
yallist "^4.0.0"
|
yallist "^4.0.0"
|
||||||
|
|
||||||
lru-cache@^7.14.1:
|
|
||||||
version "7.18.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89"
|
|
||||||
integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==
|
|
||||||
|
|
||||||
magic-string@^0.25.7:
|
magic-string@^0.25.7:
|
||||||
version "0.25.7"
|
version "0.25.7"
|
||||||
resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz"
|
resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz"
|
||||||
@@ -9700,13 +9700,6 @@ minimatch@^5.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion "^2.0.1"
|
brace-expansion "^2.0.1"
|
||||||
|
|
||||||
minimatch@^7.4.1:
|
|
||||||
version "7.4.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-7.4.2.tgz#157e847d79ca671054253b840656720cb733f10f"
|
|
||||||
integrity sha512-xy4q7wou3vUoC9k1xGTXc+awNdGaGVHtFUaey8tiX4H1QRc04DZ/rmDFwNm2EBsuYEhAZ6SgMmYf3InGY6OauA==
|
|
||||||
dependencies:
|
|
||||||
brace-expansion "^2.0.1"
|
|
||||||
|
|
||||||
minimatch@~3.0.5:
|
minimatch@~3.0.5:
|
||||||
version "3.0.8"
|
version "3.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.8.tgz#5e6a59bd11e2ab0de1cfb843eb2d82e546c321c1"
|
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.8.tgz#5e6a59bd11e2ab0de1cfb843eb2d82e546c321c1"
|
||||||
@@ -9728,12 +9721,7 @@ minimist@^1.2.0, minimist@^1.2.5:
|
|||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
||||||
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
||||||
|
|
||||||
minipass@^4.0.2, minipass@^4.2.4:
|
mitt@^3.0.0:
|
||||||
version "4.2.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.5.tgz#9e0e5256f1e3513f8c34691dd68549e85b2c8ceb"
|
|
||||||
integrity sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q==
|
|
||||||
|
|
||||||
mitt@3.0.0, mitt@^3.0.0:
|
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.0.tgz#69ef9bd5c80ff6f57473e8d89326d01c414be0bd"
|
resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.0.tgz#69ef9bd5c80ff6f57473e8d89326d01c414be0bd"
|
||||||
integrity sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==
|
integrity sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==
|
||||||
@@ -10371,14 +10359,6 @@ path-parse@^1.0.6, path-parse@^1.0.7:
|
|||||||
resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz"
|
resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz"
|
||||||
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
|
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
|
||||||
|
|
||||||
path-scurry@^1.6.1:
|
|
||||||
version "1.6.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.6.1.tgz#dab45f7bb1d3f45a0e271ab258999f4ab7e23132"
|
|
||||||
integrity sha512-OW+5s+7cw6253Q4E+8qQ/u1fVvcJQCJo/VFD8pje+dbJCF1n5ZRMV2AEHbGp+5Q7jxQIYJxkHopnj6nzdGeZLA==
|
|
||||||
dependencies:
|
|
||||||
lru-cache "^7.14.1"
|
|
||||||
minipass "^4.0.2"
|
|
||||||
|
|
||||||
path-strip-sep@^1.0.10:
|
path-strip-sep@^1.0.10:
|
||||||
version "1.0.10"
|
version "1.0.10"
|
||||||
resolved "https://registry.npmjs.org/path-strip-sep/-/path-strip-sep-1.0.10.tgz"
|
resolved "https://registry.npmjs.org/path-strip-sep/-/path-strip-sep-1.0.10.tgz"
|
||||||
@@ -10520,6 +10500,18 @@ pkg-dir@4.2.0, pkg-dir@^4.1.0, pkg-dir@^4.2.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
find-up "^5.0.0"
|
find-up "^5.0.0"
|
||||||
|
|
||||||
|
playwright-core@1.32.1:
|
||||||
|
version "1.32.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.32.1.tgz#5a10c32403323b07d75ea428ebeed866a80b76a1"
|
||||||
|
integrity sha512-KZYUQC10mXD2Am1rGlidaalNGYk3LU1vZqqNk0gT4XPty1jOqgup8KDP8l2CUlqoNKhXM5IfGjWgW37xvGllBA==
|
||||||
|
|
||||||
|
playwright@^1.32.1:
|
||||||
|
version "1.32.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.32.1.tgz#c48195850740fbdbd7702f37e5a891b13259f689"
|
||||||
|
integrity sha512-GnEizysWMvoqHC3I9l8+4/ZxeLwLNdJJG76xdKGxzOcIZDcw5RSk/FKrFb5CuA+zcLpjIM2p9eR9Z4CuUDkWXg==
|
||||||
|
dependencies:
|
||||||
|
playwright-core "1.32.1"
|
||||||
|
|
||||||
pngjs@^3.4.0:
|
pngjs@^3.4.0:
|
||||||
version "3.4.0"
|
version "3.4.0"
|
||||||
resolved "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz"
|
resolved "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz"
|
||||||
@@ -11081,23 +11073,6 @@ pupa@^2.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
escape-goat "^2.0.0"
|
escape-goat "^2.0.0"
|
||||||
|
|
||||||
puppeteer-core@19.7.5:
|
|
||||||
version "19.7.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-19.7.5.tgz#cedc8eb7862fe7a8aa2a25ed167c0f1230de72b2"
|
|
||||||
integrity sha512-EJuNha+SxPfaYFbkoWU80H3Wb1SiQH5fFyb2xdbWda0ziax5mhV63UMlqNfPeTDIWarwtR4OIcq/9VqY8HPOsg==
|
|
||||||
dependencies:
|
|
||||||
chromium-bidi "0.4.5"
|
|
||||||
cross-fetch "3.1.5"
|
|
||||||
debug "4.3.4"
|
|
||||||
devtools-protocol "0.0.1094867"
|
|
||||||
extract-zip "2.0.1"
|
|
||||||
https-proxy-agent "5.0.1"
|
|
||||||
proxy-from-env "1.1.0"
|
|
||||||
rimraf "4.4.0"
|
|
||||||
tar-fs "2.1.1"
|
|
||||||
unbzip2-stream "1.4.3"
|
|
||||||
ws "8.12.1"
|
|
||||||
|
|
||||||
puppeteer@^11.0.0:
|
puppeteer@^11.0.0:
|
||||||
version "11.0.0"
|
version "11.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-11.0.0.tgz#0808719c38e15315ecc1b1c28911f1c9054d201f"
|
resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-11.0.0.tgz#0808719c38e15315ecc1b1c28911f1c9054d201f"
|
||||||
@@ -11133,17 +11108,6 @@ puppeteer@^17.1.3:
|
|||||||
unbzip2-stream "1.4.3"
|
unbzip2-stream "1.4.3"
|
||||||
ws "8.8.1"
|
ws "8.8.1"
|
||||||
|
|
||||||
puppeteer@^19.7.2:
|
|
||||||
version "19.7.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-19.7.5.tgz#d7db0dfcc80ca2cdf8eb0100bae1ce888a841389"
|
|
||||||
integrity sha512-UqD8K+yaZa6/hwzP54AATCiHrEYGGxzQcse9cZzrtsVGd8wT0llCdYhsBp8n+zvnb1ofY0YFgI3TYZ/MiX5uXQ==
|
|
||||||
dependencies:
|
|
||||||
cosmiconfig "8.1.0"
|
|
||||||
https-proxy-agent "5.0.1"
|
|
||||||
progress "2.0.3"
|
|
||||||
proxy-from-env "1.1.0"
|
|
||||||
puppeteer-core "19.7.5"
|
|
||||||
|
|
||||||
puppeteer@^9.1.1:
|
puppeteer@^9.1.1:
|
||||||
version "9.1.1"
|
version "9.1.1"
|
||||||
resolved "https://registry.npmjs.org/puppeteer/-/puppeteer-9.1.1.tgz"
|
resolved "https://registry.npmjs.org/puppeteer/-/puppeteer-9.1.1.tgz"
|
||||||
@@ -11647,13 +11611,6 @@ rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
glob "^7.1.3"
|
glob "^7.1.3"
|
||||||
|
|
||||||
rimraf@4.4.0:
|
|
||||||
version "4.4.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-4.4.0.tgz#c7a9f45bb2ec058d2e60ef9aca5167974313d605"
|
|
||||||
integrity sha512-X36S+qpCUR0HjXlkDe4NAOhS//aHH0Z+h8Ckf2auGJk3PTnx5rLmrHkwNdbVQuCSUhOyFrlRvFEllZOYE+yZGQ==
|
|
||||||
dependencies:
|
|
||||||
glob "^9.2.0"
|
|
||||||
|
|
||||||
rimraf@^2.6.2:
|
rimraf@^2.6.2:
|
||||||
version "2.7.1"
|
version "2.7.1"
|
||||||
resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz"
|
resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz"
|
||||||
@@ -13656,11 +13613,6 @@ ws@7.4.6:
|
|||||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c"
|
resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c"
|
||||||
integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==
|
integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==
|
||||||
|
|
||||||
ws@8.12.1:
|
|
||||||
version "8.12.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.12.1.tgz#c51e583d79140b5e42e39be48c934131942d4a8f"
|
|
||||||
integrity sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew==
|
|
||||||
|
|
||||||
ws@8.2.3:
|
ws@8.2.3:
|
||||||
version "8.2.3"
|
version "8.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba"
|
resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba"
|
||||||
|
|||||||
Reference in New Issue
Block a user