dashcam 0.3.3 → 0.4.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 +93 -87
- package/examples/crash-test.js +2 -2
- package/index.js +23 -0
- package/lib.js +46 -2
- package/package.json +2 -1
- package/recorder.js +66 -0
package/README.md
CHANGED
|
@@ -1,113 +1,58 @@
|
|
|
1
|
-
<img src="https://
|
|
1
|
+
<img src="https://github.com/replayableio/cli/assets/318295/c8019bca-e4d8-42a2-af55-0f4c83ce133e" height="60px" />
|
|
2
2
|
|
|
3
|
-
# Dashcam
|
|
3
|
+
# Dashcam CLI
|
|
4
4
|
|
|
5
|
-
Add Dashcam to your app or workflow. This package allows you to control the Dashcam desktop application
|
|
5
|
+
Add Dashcam to your app or workflow. This package allows you to capture logs and control the Dashcam desktop application via CLI.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
You can easily embed desktop replays within git commits, pull requests, bug reports, jira tickets, and even within log files. Desktop replays are a great way to share context behind problems and document the application state within logs, tickets and more.
|
|
10
|
-
|
|
11
|
-
Requires that you [install Dashcam Desktop](https://dashcam.io). Dashcam Desktop runs in the background giving you access to a buffer of video.
|
|
7
|
+
Requires that you [install Dashcam Desktop](https://dashcam.io).
|
|
12
8
|
|
|
13
9
|
## Table of contents
|
|
14
10
|
|
|
15
|
-
- [
|
|
16
|
-
- [
|
|
17
|
-
- [
|
|
18
|
-
- [
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
- [
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
- [
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
- [GitHub
|
|
34
|
-
|
|
35
|
-
- [Create a github pull request with a replay in the description](#create-a-github-pull-request-with-a-replay-in-the-description)
|
|
36
|
-
- [Append a 30 second replay to a commit](#append-a-30-second-replay-to-a-commit)
|
|
37
|
-
- [Advanced Usage](#advanced-usage)
|
|
38
|
-
- [Ideas](#ideas)
|
|
11
|
+
- [CLI](#cli)
|
|
12
|
+
- [Setup](#setup)
|
|
13
|
+
- [Record CLI](#record-cli)
|
|
14
|
+
- [Create a Dash](#create-a-replay)
|
|
15
|
+
- [Return a rich markdown link](#return-a-rich-markdown-link)
|
|
16
|
+
- [Set a Dash title](#set-a-replay-title)
|
|
17
|
+
- [Attach the last 20 CLI commands to the Dash](#attach-the-last-20-cli-commands-to-the-replay)
|
|
18
|
+
- [Attach a logfile to the replay](#attach-a-logfile-to-the-replay)
|
|
19
|
+
- [Web SDK](#web)
|
|
20
|
+
- [Setup](#setup)
|
|
21
|
+
- [HTML Anchor Tag](#html-anchor-tag)
|
|
22
|
+
- [JS Error Handler](#js-error-handler)
|
|
23
|
+
- [NodeJS SDK](#nodejs-sdk)
|
|
24
|
+
- [Setup](#setup)
|
|
25
|
+
- [Create a Dash](#create-a-replay)
|
|
26
|
+
- [Error Handler](#error-handler)
|
|
27
|
+
- [GitHub CLI](#github-cli)
|
|
28
|
+
- [Create a GitHub issue with a Dash in the description](#create-a-github-issue-with-a-replay-in-the-description)
|
|
29
|
+
- [Create a GitHub pull request with a Dash in the description](#create-a-github-pull-request-with-a-replay-in-the-description)
|
|
30
|
+
- [Append a 30 second Dash to a commit](#append-a-30-second-replay-to-a-commit)
|
|
39
31
|
|
|
40
32
|
# Examples
|
|
41
33
|
|
|
42
34
|
Also see [the examples folder](https://github.com/replayableio/cli/tree/main/examples).
|
|
43
35
|
|
|
44
|
-
##
|
|
45
|
-
|
|
46
|
-
### Setup
|
|
47
|
-
|
|
48
|
-
Nothing! The app exposes the protocol to the system natively via `dashcam://`.
|
|
49
|
-
|
|
50
|
-
### HTML Anchor Tag
|
|
51
|
-
|
|
52
|
-
```html
|
|
53
|
-
<a href="dashcam://replay/create" target="_blank">Create a Replay</a>
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### JS Error Handler
|
|
57
|
-
|
|
58
|
-
```js
|
|
59
|
-
window.onerror = function myErrorHandler() {
|
|
60
|
-
window.open("dashcam://replay/create", "_blank");
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
setTimeout(() => {
|
|
64
|
-
throw new Error("Throw makes it go boom!");
|
|
65
|
-
}, 3000);
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## NodeJS SDK
|
|
36
|
+
## CLI
|
|
69
37
|
|
|
70
38
|
### Setup
|
|
71
39
|
|
|
72
40
|
```sh
|
|
73
|
-
npm install dashcam
|
|
41
|
+
npm install dashcam -g
|
|
74
42
|
```
|
|
75
43
|
|
|
76
|
-
|
|
44
|
+
## Record CLI
|
|
77
45
|
|
|
78
|
-
|
|
79
|
-
const dashcam = require("dashcam");
|
|
46
|
+
To record the CLI in the Dashcam app, use the following command
|
|
80
47
|
|
|
81
|
-
let replay = await dashcam.createReplay({
|
|
82
|
-
title: "My New Replay",
|
|
83
|
-
description: `This **renders markdown** or plaintext in monospace font.`,
|
|
84
|
-
});
|
|
85
48
|
```
|
|
86
|
-
|
|
87
|
-
### Error Handler
|
|
88
|
-
|
|
89
|
-
```js
|
|
90
|
-
const dashcam = require("dashcam");
|
|
91
|
-
|
|
92
|
-
process.on("uncaughtException", async (err) => {
|
|
93
|
-
let replay = await dashcam.createReplay({
|
|
94
|
-
title: "uncaughtException",
|
|
95
|
-
description: err,
|
|
96
|
-
});
|
|
97
|
-
console.log("Dashcam", replay);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
setTimeout(() => {
|
|
101
|
-
throw new Error("Throw makes it go boom!");
|
|
102
|
-
}, 3000);
|
|
49
|
+
dashcam record
|
|
103
50
|
```
|
|
104
51
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
### Setup
|
|
52
|
+
Anything you type in your terminal will appear in your dash. To exit, simply type `exit`.
|
|
108
53
|
|
|
109
|
-
```
|
|
110
|
-
|
|
54
|
+
```
|
|
55
|
+
exit
|
|
111
56
|
```
|
|
112
57
|
|
|
113
58
|
### Create a Replay
|
|
@@ -189,6 +134,67 @@ Options:
|
|
|
189
134
|
-h, --help display help for command
|
|
190
135
|
```
|
|
191
136
|
|
|
137
|
+
## Web
|
|
138
|
+
|
|
139
|
+
### Setup
|
|
140
|
+
|
|
141
|
+
Nothing! The app exposes the protocol to the system natively via `dashcam://`.
|
|
142
|
+
|
|
143
|
+
### HTML Anchor Tag
|
|
144
|
+
|
|
145
|
+
```html
|
|
146
|
+
<a href="dashcam://replay/create" target="_blank">Create a Replay</a>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### JS Error Handler
|
|
150
|
+
|
|
151
|
+
```js
|
|
152
|
+
window.onerror = function myErrorHandler() {
|
|
153
|
+
window.open("dashcam://replay/create", "_blank");
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
setTimeout(() => {
|
|
157
|
+
throw new Error("Throw makes it go boom!");
|
|
158
|
+
}, 3000);
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## NodeJS SDK
|
|
162
|
+
|
|
163
|
+
### Setup
|
|
164
|
+
|
|
165
|
+
```sh
|
|
166
|
+
npm install dashcam
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Create a Replay
|
|
170
|
+
|
|
171
|
+
```js
|
|
172
|
+
const dashcam = require("dashcam");
|
|
173
|
+
|
|
174
|
+
let replay = await dashcam.createReplay({
|
|
175
|
+
title: "My New Replay",
|
|
176
|
+
description: `This **renders markdown** or plaintext in monospace font.`,
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Error Handler
|
|
181
|
+
|
|
182
|
+
```js
|
|
183
|
+
const dashcam = require("dashcam");
|
|
184
|
+
|
|
185
|
+
process.on("uncaughtException", async (err) => {
|
|
186
|
+
let replay = await dashcam.createReplay({
|
|
187
|
+
title: "uncaughtException",
|
|
188
|
+
description: err,
|
|
189
|
+
});
|
|
190
|
+
console.log("Dashcam", replay);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
setTimeout(() => {
|
|
194
|
+
throw new Error("Throw makes it go boom!");
|
|
195
|
+
}, 3000);
|
|
196
|
+
```
|
|
197
|
+
|
|
192
198
|
## Ideas
|
|
193
199
|
|
|
194
200
|
It would be possible to string this along in [a git hook](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) to publish with every commit.
|
package/examples/crash-test.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const replayable = require("
|
|
1
|
+
const replayable = require("../index");
|
|
2
2
|
|
|
3
3
|
process.on("uncaughtException", async (err) => {
|
|
4
4
|
console.error(err);
|
|
5
|
-
let replay = await replayable.createReplay();
|
|
5
|
+
let replay = await replayable.createReplay({ description: err.stack });
|
|
6
6
|
console.log("Replayable", replay);
|
|
7
7
|
});
|
|
8
8
|
|
package/index.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const crypto = require("crypto");
|
|
3
5
|
const lib = require("./lib");
|
|
6
|
+
const Recorder = require("./recorder");
|
|
4
7
|
|
|
5
8
|
if (module.parent) {
|
|
6
9
|
module.exports = lib;
|
|
@@ -53,6 +56,26 @@ program
|
|
|
53
56
|
process.exit(0);
|
|
54
57
|
});
|
|
55
58
|
|
|
59
|
+
program
|
|
60
|
+
.command("record")
|
|
61
|
+
.description(
|
|
62
|
+
"Start a recording terminal to be included in your dashcam video recording"
|
|
63
|
+
)
|
|
64
|
+
.action(async function (str, options) {
|
|
65
|
+
try {
|
|
66
|
+
const dashcam = new lib.PersistantDashcamIPC();
|
|
67
|
+
const id = crypto.randomUUID();
|
|
68
|
+
const logFile = lib.getLogFilePath(id);
|
|
69
|
+
|
|
70
|
+
dashcam.onConnected = () => dashcam.emit("track-cli", logFile);
|
|
71
|
+
fs.appendFileSync(logFile, "");
|
|
72
|
+
const recorder = new Recorder(logFile);
|
|
73
|
+
recorder.start();
|
|
74
|
+
} catch (e) {
|
|
75
|
+
console.log("Error: ", e);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
56
79
|
if (process.stdin.isTTY) {
|
|
57
80
|
program.parse(process.argv);
|
|
58
81
|
} else {
|
package/lib.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
const
|
|
1
|
+
const os = require("os");
|
|
2
|
+
const path = require("path");
|
|
2
3
|
const clc = require("cli-color");
|
|
4
|
+
const ipc = require("node-ipc").default;
|
|
3
5
|
|
|
4
6
|
ipc.config.id = "dashcam-cli";
|
|
5
7
|
ipc.config.retry = 1500;
|
|
6
8
|
ipc.config.silent = true;
|
|
7
9
|
ipc.config.maxRetries = 0;
|
|
8
10
|
|
|
11
|
+
const persistantIPC = new ipc.IPC();
|
|
12
|
+
persistantIPC.config.retry = 500;
|
|
13
|
+
persistantIPC.config.silent = true;
|
|
14
|
+
|
|
9
15
|
const connectToIpc = function () {
|
|
10
16
|
return new Promise((resolve, reject) => {
|
|
11
17
|
ipc.connectTo("dashcam");
|
|
@@ -61,4 +67,42 @@ const createReplay = async function (options = {}) {
|
|
|
61
67
|
});
|
|
62
68
|
};
|
|
63
69
|
|
|
64
|
-
|
|
70
|
+
let singleInstance = null;
|
|
71
|
+
class PersistantDashcamIPC {
|
|
72
|
+
#isConnected = false;
|
|
73
|
+
onConnected = null;
|
|
74
|
+
|
|
75
|
+
constructor() {
|
|
76
|
+
if (singleInstance) return singleInstance;
|
|
77
|
+
singleInstance = this;
|
|
78
|
+
|
|
79
|
+
persistantIPC.connectTo("dashcam");
|
|
80
|
+
|
|
81
|
+
persistantIPC.of.dashcam.on("connect", () => {
|
|
82
|
+
this.#isConnected = true;
|
|
83
|
+
if (this.onConnected && typeof this.onConnected === "function")
|
|
84
|
+
this.onConnected();
|
|
85
|
+
});
|
|
86
|
+
persistantIPC.of.dashcam.on("disconnect", () => {
|
|
87
|
+
this.#isConnected = false;
|
|
88
|
+
});
|
|
89
|
+
persistantIPC.of.dashcam.on("error", () => {
|
|
90
|
+
this.#isConnected = false;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
emit(event, payload) {
|
|
95
|
+
if (!this.#isConnected) return;
|
|
96
|
+
persistantIPC.of.dashcam.emit(event, payload);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const getLogFilePath = (id) => {
|
|
101
|
+
return path.join(os.tmpdir(), `dashcam_cli_recording_${id}.log`);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
module.exports = {
|
|
105
|
+
createReplay,
|
|
106
|
+
getLogFilePath,
|
|
107
|
+
PersistantDashcamIPC,
|
|
108
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dashcam",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Fix bugs, close pulls, and update your team with desktop instant replay.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"commander": "^9.4.0",
|
|
18
18
|
"husky": "^7.0.4",
|
|
19
19
|
"node-ipc": "^10.1.0",
|
|
20
|
+
"node-pty-prebuilt-multiarch": "^0.10.1-pre.5",
|
|
20
21
|
"yargs": "^17.3.1"
|
|
21
22
|
},
|
|
22
23
|
"devDependencies": {
|
package/recorder.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const pty = require("node-pty-prebuilt-multiarch");
|
|
3
|
+
|
|
4
|
+
class Recorder {
|
|
5
|
+
#ptyProcess = null;
|
|
6
|
+
#logFile = null;
|
|
7
|
+
|
|
8
|
+
constructor(logFile) {
|
|
9
|
+
// This way we don't run the recording script recursively, especially
|
|
10
|
+
// if it's inside bash/zsh configs
|
|
11
|
+
if (process.env.DASHCAM_TERMINAL_RECORDING) {
|
|
12
|
+
console.log("The current terminal is already being recorded");
|
|
13
|
+
process.exit(0);
|
|
14
|
+
}
|
|
15
|
+
this.#logFile = logFile;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
#onInput(data) {
|
|
19
|
+
this.#ptyProcess.write(data);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
#onData(data) {
|
|
23
|
+
process.stdout.write(data);
|
|
24
|
+
fs.appendFileSync(this.#logFile, data, "ascii");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
start() {
|
|
28
|
+
console.log(
|
|
29
|
+
"This session is being recorded by Dashcam and dumped to",
|
|
30
|
+
this.#logFile
|
|
31
|
+
);
|
|
32
|
+
console.log("Type `exit` to stop recording");
|
|
33
|
+
|
|
34
|
+
// TODO: Find a way to consistently get the current shell this is running from
|
|
35
|
+
// instead of using the default user shell (Maybe use parent processId to find
|
|
36
|
+
// the process filepath)
|
|
37
|
+
const shell = process.env.SHELL;
|
|
38
|
+
const args = [];
|
|
39
|
+
if (!shell.toLowerCase().includes("powershell")) args.push("-l");
|
|
40
|
+
this.#ptyProcess = pty.spawn(shell, args, {
|
|
41
|
+
// Inject a terminal variable to let the child processes know
|
|
42
|
+
// of the active recording so they we don't record recursively
|
|
43
|
+
env: { ...process.env, DASHCAM_TERMINAL_RECORDING: "TRUE" },
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
process.stdin.on("data", this.#onInput.bind(this));
|
|
47
|
+
this.#ptyProcess.on("data", this.#onData.bind(this));
|
|
48
|
+
this.#ptyProcess.on("exit", this.stop.bind(this));
|
|
49
|
+
|
|
50
|
+
process.stdout.setDefaultEncoding("utf8");
|
|
51
|
+
process.stdin.setEncoding("utf8");
|
|
52
|
+
process.stdin.setRawMode(true);
|
|
53
|
+
process.stdin.resume();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
stop() {
|
|
57
|
+
process.stdin.removeListener("data", this.#onInput.bind(this));
|
|
58
|
+
process.stdin.setRawMode(false);
|
|
59
|
+
process.stdin.pause();
|
|
60
|
+
console.clear();
|
|
61
|
+
process.kill(process.ppid, "SIGTERM");
|
|
62
|
+
process.exit();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = Recorder;
|