@recordreplay/recordings-cli
Advanced tools
Comparing version 0.8.0 to 0.9.0
{ | ||
"name": "@recordreplay/recordings-cli", | ||
"version": "0.8.0", | ||
"version": "0.9.0", | ||
"description": "CLI tool for uploading and managing recordings", | ||
@@ -5,0 +5,0 @@ "bin": { |
@@ -49,2 +49,6 @@ # recordings-cli | ||
### process <id> | ||
Upload a recording, and then process it to ensure it can be replayed successfully. | ||
### upload-all | ||
@@ -70,2 +74,6 @@ | ||
### update-browsers | ||
Updates any installed browsers used for recording in automation: [playwright](https://www.npmjs.com/package/@recordreplay/playwright), [puppeteer](https://www.npmjs.com/package/@recordreplay/puppeteer), and [cypress](https://www.npmjs.com/package/@recordreplay/cypress). | ||
## Node Module Usage | ||
@@ -97,2 +105,6 @@ | ||
### processRecording(id, opts) | ||
Equivalent to `replay-recordings process <id>`, returns a promise that resolves with a recording ID if the upload and processing succeeded, or null if either failed. | ||
### uploadAllRecordings(opts) | ||
@@ -117,1 +129,5 @@ | ||
Equivalent to `replay-recordings rm-all`. | ||
### updateBrowsers(opts) | ||
Equivalent to `replay-recordings update-browsers`. |
@@ -5,2 +5,3 @@ const { program } = require("commander"); | ||
uploadRecording, | ||
processRecording, | ||
uploadAllRecordings, | ||
@@ -11,2 +12,3 @@ viewRecording, | ||
removeAllRecordings, | ||
updateBrowsers, | ||
} = require("./main"); | ||
@@ -41,2 +43,19 @@ | ||
program | ||
.command("process <id>") | ||
.description("Upload a recording to the remote server and process it.") | ||
.option( | ||
"--directory <dir>", | ||
"Alternate recording directory." | ||
) | ||
.option( | ||
"--server <address>", | ||
"Alternate server to upload recordings to." | ||
) | ||
.option( | ||
"--api-key <key>", | ||
"Authentication API Key" | ||
) | ||
.action(commandProcessRecording); | ||
program | ||
.command("upload-all") | ||
@@ -111,2 +130,11 @@ .description("Upload all recordings to the remote server.") | ||
program | ||
.command("update-browsers") | ||
.description("Update browsers used in automation.") | ||
.option( | ||
"--directory <dir>", | ||
"Alternate recording directory." | ||
) | ||
.action(commandUpdateBrowsers); | ||
program | ||
.parseAsync() | ||
@@ -129,2 +157,7 @@ .catch((err) => { | ||
async function commandProcessRecording(id, opts) { | ||
const recordingId = await processRecording(id, { ...opts, verbose: true }); | ||
process.exit(recordingId ? 0 : 1); | ||
} | ||
async function commandUploadAllRecordings(opts) { | ||
@@ -154,1 +187,6 @@ const uploadedAll = await uploadAllRecordings({ ...opts, verbose: true }); | ||
} | ||
async function commandUpdateBrowsers(opts) { | ||
await updateBrowsers({ ...opts, verbose: true }); | ||
process.exit(0); | ||
} |
@@ -19,2 +19,4 @@ const WebSocket = require("ws"); | ||
this.socket.on("message", (message) => this.onMessage(message)); | ||
this.eventListeners = new Map(); | ||
} | ||
@@ -40,6 +42,6 @@ | ||
async sendCommand(method, params, data) { | ||
async sendCommand(method, params, data, sessionId) { | ||
const id = this.nextMessageId++; | ||
this.socket.send( | ||
JSON.stringify({ id, method, params, binary: data ? true : undefined }) | ||
JSON.stringify({ id, method, params, binary: data ? true : undefined, sessionId }) | ||
); | ||
@@ -54,2 +56,6 @@ if (data) { | ||
setEventListener(method, callback) { | ||
this.eventListeners.set(method, callback); | ||
} | ||
onMessage(contents) { | ||
@@ -65,4 +71,6 @@ const msg = JSON.parse(contents); | ||
} | ||
} else if (this.eventListeners.has(msg.method)) { | ||
this.eventListeners.get(msg.method)(msg.params); | ||
} else { | ||
throw new Error("Events NYI"); | ||
console.log(`Received event without listener: ${msg.method}`); | ||
} | ||
@@ -69,0 +77,0 @@ } |
@@ -13,3 +13,3 @@ // Manage installation of browsers for other NPM packages. | ||
if (["all", "gecko"].includes(kind)) { | ||
await installReplayBrowser("macOS-replay-playwright.tar.xz", "firefox", "firefox"); | ||
await installReplayBrowser("macOS-replay-playwright.tar.xz", "playwright", "firefox", "firefox"); | ||
} | ||
@@ -19,6 +19,6 @@ break; | ||
if (["all", "gecko"].includes(kind)) { | ||
await installReplayBrowser("linux-replay-playwright.tar.xz", "firefox", "firefox"); | ||
await installReplayBrowser("linux-replay-playwright.tar.xz", "playwright", "firefox", "firefox"); | ||
} | ||
if (["all", "chromium"].includes(kind)) { | ||
await installReplayBrowser("linux-replay-chromium.tar.xz", "replay-chromium", "chrome-linux"); | ||
await installReplayBrowser("linux-replay-chromium.tar.xz", "playwright", "replay-chromium", "chrome-linux"); | ||
} | ||
@@ -29,2 +29,15 @@ break; | ||
async function updateBrowsers(opts = {}) { | ||
switch (process.platform) { | ||
case "darwin": | ||
await updateReplayBrowser("macOS-replay-playwright.tar.xz", "playwright", "firefox", "firefox", opts); | ||
break; | ||
case "linux": | ||
await updateReplayBrowser("linux-replay-playwright.tar.xz", "playwright", "firefox", "firefox", opts); | ||
await updateReplayBrowser("linux-replay-chromium.tar.xz", "playwright", "replay-chromium", "chrome-linux", opts); | ||
await updateReplayBrowser("linux-replay-chromium.tar.xz", "puppeteer", "replay-chromium", "chrome-linux", opts); | ||
break; | ||
} | ||
} | ||
function getPlaywrightBrowserPath(kind) { | ||
@@ -44,7 +57,8 @@ const replayDir = getDirectory(); | ||
async function installReplayBrowser(name, srcName, dstName) { | ||
// Installs a browser if it isn't already installed. | ||
async function installReplayBrowser(name, subdir, srcName, dstName) { | ||
const replayDir = getDirectory(); | ||
const playwrightDir = path.join(replayDir, "playwright"); | ||
const browserDir = path.join(replayDir, subdir); | ||
if (fs.existsSync(path.join(playwrightDir, dstName))) { | ||
if (fs.existsSync(path.join(browserDir, dstName))) { | ||
return; | ||
@@ -55,3 +69,3 @@ } | ||
for (const dir of [replayDir, playwrightDir]) { | ||
for (const dir of [replayDir, browserDir]) { | ||
if (!fs.existsSync(dir)) { | ||
@@ -61,11 +75,36 @@ fs.mkdirSync(dir); | ||
} | ||
fs.writeFileSync(path.join(playwrightDir, name), contents); | ||
spawnSync("tar", ["xf", name], { cwd: playwrightDir }); | ||
fs.unlinkSync(path.join(playwrightDir, name)); | ||
fs.writeFileSync(path.join(browserDir, name), contents); | ||
spawnSync("tar", ["xf", name], { cwd: browserDir }); | ||
fs.unlinkSync(path.join(browserDir, name)); | ||
if (srcName != dstName) { | ||
fs.renameSync(path.join(playwrightDir, srcName), path.join(playwrightDir, dstName)); | ||
fs.renameSync(path.join(browserDir, srcName), path.join(browserDir, dstName)); | ||
} | ||
} | ||
// Updates a browser if it is already installed. | ||
async function updateReplayBrowser(name, subdir, srcName, dstName, opts) { | ||
const replayDir = getDirectory(opts); | ||
const browserDir = path.join(replayDir, subdir); | ||
const dstDir = path.join(browserDir, dstName); | ||
if (fs.existsSync(dstDir)) { | ||
// Remove the browser so installReplayBrowser will reinstall it. We don't have a way | ||
// to see that the current browser is up to date. | ||
fs.rmSync(dstDir, { force: true, recursive: true }); | ||
} else { | ||
return; | ||
} | ||
if (opts.verbose) { | ||
console.log(`Updating browser ${subdir} ${dstName}...`); | ||
} | ||
await installReplayBrowser(name, subdir, srcName, dstName); | ||
if (opts.verbose) { | ||
console.log(`Updated.`); | ||
} | ||
} | ||
async function downloadReplayFile(downloadFile) { | ||
@@ -105,2 +144,6 @@ const options = { | ||
module.exports = { ensurePlaywrightBrowsersInstalled, getPlaywrightBrowserPath }; | ||
module.exports = { | ||
ensurePlaywrightBrowsersInstalled, | ||
getPlaywrightBrowserPath, | ||
updateBrowsers, | ||
}; |
@@ -7,2 +7,3 @@ const fs = require("fs"); | ||
connectionProcessRecording, | ||
connectionWaitForProcessed, | ||
connectionUploadRecording, | ||
@@ -15,2 +16,3 @@ closeConnection, | ||
getPlaywrightBrowserPath, | ||
updateBrowsers, | ||
} = require("./install"); | ||
@@ -222,2 +224,6 @@ const { getDirectory, maybeLog } = require("./utils"); | ||
maybeLog(verbose, `Starting upload for ${recording.id}...`); | ||
if (recording.status == "uploaded" && recording.recordingId) { | ||
maybeLog(verbose, `Already uploaded: ${recording.recordingId}`); | ||
return recording.recordingId; | ||
} | ||
const reason = uploadSkipReason(recording); | ||
@@ -269,2 +275,36 @@ if (reason) { | ||
async function processUploadedRecording(recordingId, opts) { | ||
const server = getServer(opts); | ||
const { apiKey, verbose } = opts; | ||
maybeLog(verbose, `Processing recording ${recordingId}...`); | ||
if (!(await initConnection(server, apiKey, verbose))) { | ||
maybeLog(verbose, `Processing failed: can't connect to server ${server}`); | ||
return false; | ||
} | ||
try { | ||
const error = await connectionWaitForProcessed(recordingId); | ||
if (error) { | ||
maybeLog(verbose, `Processing failed: ${error}`); | ||
return false; | ||
} | ||
} finally { | ||
closeConnection(); | ||
} | ||
maybeLog(verbose, "Finished processing."); | ||
return true; | ||
} | ||
async function processRecording(id, opts = {}) { | ||
const recordingId = await uploadRecording(id, opts); | ||
if (!recordingId) { | ||
return null; | ||
} | ||
const succeeded = await processUploadedRecording(recordingId, opts); | ||
return succeeded ? recordingId : null; | ||
} | ||
async function uploadAllRecordings(opts = {}) { | ||
@@ -400,3 +440,7 @@ const server = getServer(opts); | ||
recordings.forEach(maybeRemoveRecordingFile); | ||
fs.unlinkSync(getRecordingsFile(dir)); | ||
const file = getRecordingsFile(dir); | ||
if (fs.existsSync(file)) { | ||
fs.unlinkSync(file); | ||
} | ||
} | ||
@@ -407,2 +451,3 @@ | ||
uploadRecording, | ||
processRecording, | ||
uploadAllRecordings, | ||
@@ -413,2 +458,3 @@ viewRecording, | ||
removeAllRecordings, | ||
updateBrowsers, | ||
@@ -415,0 +461,0 @@ // These methods aren't documented or available via the CLI, and are used by other |
@@ -20,3 +20,2 @@ const ProtocolClient = require("./client"); | ||
onClose() { | ||
maybeLog(verbose, `Server connection closed.`); | ||
resolve(false); | ||
@@ -67,2 +66,24 @@ }, | ||
async function connectionWaitForProcessed(recordingId) { | ||
const { sessionId } = await gClient.sendCommand("Recording.createSession", { recordingId }); | ||
const waiter = defer(); | ||
gClient.setEventListener( | ||
"Recording.sessionError", | ||
({ message }) => waiter.resolve(`session error ${sessionId}: ${message}`) | ||
); | ||
gClient.setEventListener("Session.unprocessedRegions", () => {}); | ||
gClient.sendCommand( | ||
"Session.ensureProcessed", | ||
{ level: "basic" }, | ||
null, | ||
sessionId | ||
).then(() => waiter.resolve(null)); | ||
const error = await waiter.promise; | ||
return error; | ||
} | ||
// Granularity for splitting up a recording into chunks for uploading. | ||
@@ -104,2 +125,3 @@ const ChunkGranularity = 1024 * 1024; | ||
connectionProcessRecording, | ||
connectionWaitForProcessed, | ||
connectionUploadRecording, | ||
@@ -106,0 +128,0 @@ closeConnection, |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
34887
904
130
21433