@watchforge/browser 0.1.1 → 0.1.4
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/CONFIGURATION_GUIDE.md +134 -1
- package/README.md +19 -0
- package/bin/watchforge.js +258 -0
- package/package.json +10 -2
- package/src/client.js +97 -120
- package/src/contexts.js +110 -0
- package/src/express.js +25 -2
- package/src/index.js +7 -2
- package/src/react.js +8 -2
- package/src/replay.js +128 -0
- package/src/stacktrace.js +233 -0
- package/src/transport.js +45 -3
package/CONFIGURATION_GUIDE.md
CHANGED
|
@@ -68,6 +68,36 @@ After installation, use it the same way:
|
|
|
68
68
|
import { register, ErrorBoundary } from '@watchforge/browser';
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
+
### Next.js Wizard Setup
|
|
72
|
+
|
|
73
|
+
From your Next.js project root:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npx @watchforge/browser -i nextjs --dsn "https://PUBLIC_KEY@dev.watchforges.com/PROJECT_ID" --app-env production
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
With error-triggered Session Replay:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npx @watchforge/browser -i nextjs \
|
|
83
|
+
--dsn "https://PUBLIC_KEY@dev.watchforges.com/PROJECT_ID" \
|
|
84
|
+
--app-env production \
|
|
85
|
+
--replays-on-error 1
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The wizard:
|
|
89
|
+
|
|
90
|
+
1. Installs `@watchforge/browser`
|
|
91
|
+
2. Creates `watchforge.config.ts`
|
|
92
|
+
3. Creates `app/watchforge-init.tsx` (or `src/app/watchforge-init.tsx`)
|
|
93
|
+
4. Patches `app/layout.tsx` to render `<WatchForgeInit />`
|
|
94
|
+
|
|
95
|
+
If you only want file generation and no package install:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
npx @watchforge/browser -i nextjs --dsn "..." --skip-install
|
|
99
|
+
```
|
|
100
|
+
|
|
71
101
|
## Quick Testing
|
|
72
102
|
|
|
73
103
|
### Prerequisites
|
|
@@ -710,7 +740,11 @@ try {
|
|
|
710
740
|
```javascript
|
|
711
741
|
// server.js
|
|
712
742
|
const express = require('express');
|
|
713
|
-
const {
|
|
743
|
+
const {
|
|
744
|
+
register,
|
|
745
|
+
expressRequestMiddleware,
|
|
746
|
+
expressMiddleware,
|
|
747
|
+
} = require('@watchforge/browser');
|
|
714
748
|
|
|
715
749
|
const app = express();
|
|
716
750
|
|
|
@@ -719,6 +753,11 @@ register({
|
|
|
719
753
|
app_env: process.env.NODE_ENV,
|
|
720
754
|
});
|
|
721
755
|
|
|
756
|
+
app.use(express.json());
|
|
757
|
+
app.use(expressRequestMiddleware());
|
|
758
|
+
|
|
759
|
+
// routes here
|
|
760
|
+
|
|
722
761
|
// Add error handler (must be last middleware)
|
|
723
762
|
app.use(expressMiddleware());
|
|
724
763
|
|
|
@@ -735,6 +774,11 @@ import App from './App';
|
|
|
735
774
|
register({
|
|
736
775
|
dsn: process.env.REACT_APP_WATCHFORGE_DSN,
|
|
737
776
|
app_env: process.env.NODE_ENV,
|
|
777
|
+
// Optional: upload the last 60s of masked browser replay events when an error occurs.
|
|
778
|
+
replaysOnErrorSampleRate: 1.0,
|
|
779
|
+
// Optional: continuously sample full sessions. Keep 0 in production unless you need it.
|
|
780
|
+
replaysSessionSampleRate: 0,
|
|
781
|
+
maskAllInputs: true,
|
|
738
782
|
});
|
|
739
783
|
|
|
740
784
|
ReactDOM.render(
|
|
@@ -745,6 +789,95 @@ ReactDOM.render(
|
|
|
745
789
|
);
|
|
746
790
|
```
|
|
747
791
|
|
|
792
|
+
## Stack Trace Source Context
|
|
793
|
+
|
|
794
|
+
The SDK enriches stack frames with Python-style source context when possible:
|
|
795
|
+
|
|
796
|
+
| Field | Description |
|
|
797
|
+
|-------|-------------|
|
|
798
|
+
| `context_line` | The exact line where the error occurred |
|
|
799
|
+
| `pre_context` | Up to 5 lines before the error |
|
|
800
|
+
| `post_context` | Up to 5 lines after the error |
|
|
801
|
+
|
|
802
|
+
### Node.js / Express
|
|
803
|
+
|
|
804
|
+
For local project files, the SDK reads source from disk and attaches context lines automatically. No extra configuration is required.
|
|
805
|
+
|
|
806
|
+
### Browser (development)
|
|
807
|
+
|
|
808
|
+
In dev builds (Next.js, Vite, etc.), the SDK attempts to fetch same-origin script URLs from the stack trace and slice source lines around the reported line number.
|
|
809
|
+
|
|
810
|
+
Works when stack frames point to readable URLs such as:
|
|
811
|
+
|
|
812
|
+
```txt
|
|
813
|
+
http://localhost:3000/_next/static/chunks/...
|
|
814
|
+
http://localhost:3000/src/App.tsx
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
### Browser (production / minified bundles)
|
|
818
|
+
|
|
819
|
+
Production bundles often report locations like:
|
|
820
|
+
|
|
821
|
+
```txt
|
|
822
|
+
https://your-app.com/_next/static/chunks/app-abc123.js:1:98432
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
The SDK still sends filename, line, and column, but **cannot** show original TypeScript/JSX source without **source maps**.
|
|
826
|
+
|
|
827
|
+
**Current status:** source map upload and symbolication are not yet supported (planned for a future release).
|
|
828
|
+
|
|
829
|
+
**Workarounds today:**
|
|
830
|
+
|
|
831
|
+
1. Test with `npm run dev` / unminified builds to see source lines in WatchForge.
|
|
832
|
+
2. Use `release` in `register()` so issues are grouped by deploy version.
|
|
833
|
+
3. Rely on breadcrumbs and the Browser Event Summary in the dashboard to understand user actions before the error.
|
|
834
|
+
|
|
835
|
+
When source map support ships, you will upload maps during deploy and WatchForge will resolve minified frames back to original files.
|
|
836
|
+
|
|
837
|
+
## Session Replay
|
|
838
|
+
|
|
839
|
+
The browser SDK can record a Sentry-style DOM replay using `rrweb`. Replays are **not videos**; they are masked DOM snapshots and incremental browser events that WatchForge can play back in the Issue Detail page.
|
|
840
|
+
|
|
841
|
+
Enable replay capture when registering the SDK:
|
|
842
|
+
|
|
843
|
+
```javascript
|
|
844
|
+
register({
|
|
845
|
+
dsn: "https://PUBLIC_KEY@dev.watchforges.com/PROJECT_ID",
|
|
846
|
+
app_env: "production",
|
|
847
|
+
replaysOnErrorSampleRate: 1.0,
|
|
848
|
+
replaysSessionSampleRate: 0,
|
|
849
|
+
maskAllInputs: true,
|
|
850
|
+
});
|
|
851
|
+
```
|
|
852
|
+
|
|
853
|
+
Replay behavior:
|
|
854
|
+
|
|
855
|
+
- `replaysOnErrorSampleRate`: records in buffer mode and uploads the last 60 seconds when an error is captured.
|
|
856
|
+
- `replaysSessionSampleRate`: continuously samples full browser sessions.
|
|
857
|
+
- Text/input privacy is handled in the browser before upload.
|
|
858
|
+
- Password, email, tel and text inputs are masked when `maskAllInputs` is true.
|
|
859
|
+
- Elements with `.rr-block` are blocked.
|
|
860
|
+
- Elements with `.rr-ignore` ignore input events.
|
|
861
|
+
- Elements with `.rr-mask` mask text.
|
|
862
|
+
|
|
863
|
+
When an error is captured, the SDK attaches:
|
|
864
|
+
|
|
865
|
+
```json
|
|
866
|
+
{
|
|
867
|
+
"replay_id": "...",
|
|
868
|
+
"session_id": "...",
|
|
869
|
+
"contexts": {
|
|
870
|
+
"replay": {
|
|
871
|
+
"replay_id": "...",
|
|
872
|
+
"session_id": "...",
|
|
873
|
+
"sampled": true
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
```
|
|
878
|
+
|
|
879
|
+
The dashboard shows the linked replay in the Issue Detail **Session Replay** tab.
|
|
880
|
+
|
|
748
881
|
## Test Scripts
|
|
749
882
|
|
|
750
883
|
Test scripts are included in the SDK directory:
|
package/README.md
CHANGED
|
@@ -18,6 +18,25 @@ Browser and Node SDK for WatchForge. **One call to `register()`** turns on autom
|
|
|
18
18
|
npm install @watchforge/browser
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
+
## Next.js one-line setup
|
|
22
|
+
|
|
23
|
+
For Next.js apps, run the setup wizard from your app root:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx @watchforge/browser -i nextjs --dsn "https://PUBLIC_KEY@your-host/PROJECT_ID" --app-env production
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
With Session Replay on errors:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx @watchforge/browser -i nextjs \
|
|
33
|
+
--dsn "https://PUBLIC_KEY@your-host/PROJECT_ID" \
|
|
34
|
+
--app-env production \
|
|
35
|
+
--replays-on-error 1
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The wizard installs `@watchforge/browser`, writes `watchforge.config.ts`, creates a client init component, and patches `app/layout.tsx` or `pages/_app.tsx`.
|
|
39
|
+
|
|
21
40
|
## Quick start (all most apps need)
|
|
22
41
|
|
|
23
42
|
Call **`register()` once** as early as possible (e.g. app entry / main bundle), with your **DSN** from the WatchForge project settings.
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import process from "node:process";
|
|
7
|
+
|
|
8
|
+
const HELP = `
|
|
9
|
+
WatchForge setup wizard
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
npx @watchforge/browser -i nextjs --dsn <dsn> [options]
|
|
13
|
+
npx @watchforge/browser init nextjs --dsn <dsn> [options]
|
|
14
|
+
|
|
15
|
+
Options:
|
|
16
|
+
-i, --integration <name> Framework integration. Currently: nextjs
|
|
17
|
+
--dsn <dsn> WatchForge DSN
|
|
18
|
+
--app-env <env> App environment (default: production)
|
|
19
|
+
--debug Enable SDK debug logging
|
|
20
|
+
--replays-on-error <rate> Upload replay when errors happen (default: 0)
|
|
21
|
+
--replays-session <rate> Continuously sample sessions (default: 0)
|
|
22
|
+
--skip-install Do not install @watchforge/browser
|
|
23
|
+
-h, --help Show help
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
npx @watchforge/browser -i nextjs --dsn "https://PUBLIC_KEY@dev.watchforges.com/PROJECT_ID" --app-env development --replays-on-error 1
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
function parseArgs(argv) {
|
|
30
|
+
const args = {
|
|
31
|
+
integration: null,
|
|
32
|
+
dsn: null,
|
|
33
|
+
appEnv: "production",
|
|
34
|
+
debug: false,
|
|
35
|
+
replaysOnError: "0",
|
|
36
|
+
replaysSession: "0",
|
|
37
|
+
skipInstall: false,
|
|
38
|
+
help: false,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
for (let i = 0; i < argv.length; i++) {
|
|
42
|
+
const arg = argv[i];
|
|
43
|
+
const next = argv[i + 1];
|
|
44
|
+
|
|
45
|
+
if (arg === "-h" || arg === "--help") args.help = true;
|
|
46
|
+
else if (arg === "-i" || arg === "--integration") {
|
|
47
|
+
args.integration = next;
|
|
48
|
+
i++;
|
|
49
|
+
} else if (arg === "init" || arg === "setup") {
|
|
50
|
+
args.integration = argv[i + 1] || args.integration;
|
|
51
|
+
i++;
|
|
52
|
+
} else if (arg === "--dsn") {
|
|
53
|
+
args.dsn = next;
|
|
54
|
+
i++;
|
|
55
|
+
} else if (arg === "--app-env" || arg === "--environment") {
|
|
56
|
+
args.appEnv = next || args.appEnv;
|
|
57
|
+
i++;
|
|
58
|
+
} else if (arg === "--debug") {
|
|
59
|
+
args.debug = true;
|
|
60
|
+
} else if (arg === "--replays-on-error") {
|
|
61
|
+
args.replaysOnError = next || "0";
|
|
62
|
+
i++;
|
|
63
|
+
} else if (arg === "--replays-session") {
|
|
64
|
+
args.replaysSession = next || "0";
|
|
65
|
+
i++;
|
|
66
|
+
} else if (arg === "--skip-install") {
|
|
67
|
+
args.skipInstall = true;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return args;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function log(message) {
|
|
75
|
+
console.log(`WatchForge: ${message}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function fail(message) {
|
|
79
|
+
console.error(`WatchForge setup failed: ${message}`);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function fileExists(filePath) {
|
|
84
|
+
return fs.existsSync(filePath) && fs.statSync(filePath).isFile();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function findNextLayout(cwd) {
|
|
88
|
+
const candidates = [
|
|
89
|
+
path.join(cwd, "src", "app", "layout.tsx"),
|
|
90
|
+
path.join(cwd, "src", "app", "layout.jsx"),
|
|
91
|
+
path.join(cwd, "app", "layout.tsx"),
|
|
92
|
+
path.join(cwd, "app", "layout.jsx"),
|
|
93
|
+
];
|
|
94
|
+
return candidates.find(fileExists) || null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function findPagesApp(cwd) {
|
|
98
|
+
const candidates = [
|
|
99
|
+
path.join(cwd, "src", "pages", "_app.tsx"),
|
|
100
|
+
path.join(cwd, "src", "pages", "_app.jsx"),
|
|
101
|
+
path.join(cwd, "pages", "_app.tsx"),
|
|
102
|
+
path.join(cwd, "pages", "_app.jsx"),
|
|
103
|
+
];
|
|
104
|
+
return candidates.find(fileExists) || null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function writeIfChanged(filePath, content) {
|
|
108
|
+
if (fileExists(filePath) && fs.readFileSync(filePath, "utf8") === content) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
112
|
+
fs.writeFileSync(filePath, content);
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function createConfig(cwd, args) {
|
|
117
|
+
const configPath = path.join(cwd, "watchforge.config.ts");
|
|
118
|
+
const content = `export const watchforgeConfig = {
|
|
119
|
+
dsn: ${JSON.stringify(args.dsn)},
|
|
120
|
+
app_env: ${JSON.stringify(args.appEnv)},
|
|
121
|
+
debug: ${args.debug ? "true" : "false"},
|
|
122
|
+
replaysOnErrorSampleRate: ${Number(args.replaysOnError)},
|
|
123
|
+
replaysSessionSampleRate: ${Number(args.replaysSession)},
|
|
124
|
+
maskAllInputs: true,
|
|
125
|
+
};
|
|
126
|
+
`;
|
|
127
|
+
writeIfChanged(configPath, content);
|
|
128
|
+
log(`wrote ${path.relative(cwd, configPath)}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function installPackage(cwd, skipInstall) {
|
|
132
|
+
if (skipInstall) {
|
|
133
|
+
log("skipped npm install");
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const hasPnpm = fileExists(path.join(cwd, "pnpm-lock.yaml"));
|
|
138
|
+
const hasYarn = fileExists(path.join(cwd, "yarn.lock"));
|
|
139
|
+
const hasBun = fileExists(path.join(cwd, "bun.lockb"));
|
|
140
|
+
|
|
141
|
+
const command = hasPnpm
|
|
142
|
+
? "pnpm add @watchforge/browser"
|
|
143
|
+
: hasYarn
|
|
144
|
+
? "yarn add @watchforge/browser"
|
|
145
|
+
: hasBun
|
|
146
|
+
? "bun add @watchforge/browser"
|
|
147
|
+
: "npm install @watchforge/browser";
|
|
148
|
+
|
|
149
|
+
log(`installing package: ${command}`);
|
|
150
|
+
execSync(command, { cwd, stdio: "inherit" });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function patchAppRouter(cwd, layoutPath) {
|
|
154
|
+
const appDir = path.dirname(layoutPath);
|
|
155
|
+
const isSrcApp = appDir.endsWith(path.join("src", "app"));
|
|
156
|
+
const configImport = isSrcApp ? "../../watchforge.config" : "../watchforge.config";
|
|
157
|
+
const initPath = path.join(appDir, "watchforge-init.tsx");
|
|
158
|
+
|
|
159
|
+
const initContent = `"use client";
|
|
160
|
+
|
|
161
|
+
import { useEffect } from "react";
|
|
162
|
+
import { register } from "@watchforge/browser";
|
|
163
|
+
import { watchforgeConfig } from "${configImport}";
|
|
164
|
+
|
|
165
|
+
export default function WatchForgeInit() {
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
register(watchforgeConfig);
|
|
168
|
+
}, []);
|
|
169
|
+
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
`;
|
|
173
|
+
|
|
174
|
+
writeIfChanged(initPath, initContent);
|
|
175
|
+
log(`wrote ${path.relative(cwd, initPath)}`);
|
|
176
|
+
|
|
177
|
+
let layout = fs.readFileSync(layoutPath, "utf8");
|
|
178
|
+
if (!layout.includes("WatchForgeInit")) {
|
|
179
|
+
layout = `import WatchForgeInit from "./watchforge-init";\n${layout}`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (!layout.includes("<WatchForgeInit />")) {
|
|
183
|
+
layout = layout.replace(/<body([^>]*)>/, "<body$1>\n <WatchForgeInit />\n ");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
fs.writeFileSync(layoutPath, layout);
|
|
187
|
+
log(`patched ${path.relative(cwd, layoutPath)}`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function patchPagesRouter(cwd, appPath) {
|
|
191
|
+
const pagesDir = path.dirname(appPath);
|
|
192
|
+
const isSrcPages = pagesDir.endsWith(path.join("src", "pages"));
|
|
193
|
+
const configImport = isSrcPages ? "../../watchforge.config" : "../watchforge.config";
|
|
194
|
+
const content = fs.readFileSync(appPath, "utf8");
|
|
195
|
+
|
|
196
|
+
if (content.includes("register(watchforgeConfig)")) {
|
|
197
|
+
log(`${path.relative(cwd, appPath)} already contains WatchForge setup`);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const patched = `import { useEffect } from "react";
|
|
202
|
+
import { register } from "@watchforge/browser";
|
|
203
|
+
import { watchforgeConfig } from "${configImport}";
|
|
204
|
+
|
|
205
|
+
${content.replace(
|
|
206
|
+
/function\s+App\s*\(([^)]*)\)\s*{/,
|
|
207
|
+
"function App($1) {\n useEffect(() => {\n register(watchforgeConfig);\n }, []);"
|
|
208
|
+
)}`;
|
|
209
|
+
|
|
210
|
+
fs.writeFileSync(appPath, patched);
|
|
211
|
+
log(`patched ${path.relative(cwd, appPath)}`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function initNextjs(args) {
|
|
215
|
+
const cwd = process.cwd();
|
|
216
|
+
if (!fileExists(path.join(cwd, "package.json"))) {
|
|
217
|
+
fail("run this command from the root of your Next.js project");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
createConfig(cwd, args);
|
|
221
|
+
installPackage(cwd, args.skipInstall);
|
|
222
|
+
|
|
223
|
+
const layoutPath = findNextLayout(cwd);
|
|
224
|
+
if (layoutPath) {
|
|
225
|
+
patchAppRouter(cwd, layoutPath);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const appPath = findPagesApp(cwd);
|
|
230
|
+
if (appPath) {
|
|
231
|
+
patchPagesRouter(cwd, appPath);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
fail("could not find app/layout.tsx or pages/_app.tsx");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const args = parseArgs(process.argv.slice(2));
|
|
239
|
+
|
|
240
|
+
if (args.help) {
|
|
241
|
+
console.log(HELP);
|
|
242
|
+
process.exit(0);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (!args.integration) {
|
|
246
|
+
fail("missing integration. Use: -i nextjs");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (args.integration !== "nextjs") {
|
|
250
|
+
fail(`unsupported integration "${args.integration}". Currently supported: nextjs`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (!args.dsn) {
|
|
254
|
+
fail("missing --dsn");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
initNextjs(args);
|
|
258
|
+
log("setup complete");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@watchforge/browser",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"main": "./src/index.js",
|
|
5
5
|
"description": "WatchForge JavaScript SDK for Node.js, Express.js, and React",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,12 +22,19 @@
|
|
|
22
22
|
"./express": "./src/express.js",
|
|
23
23
|
"./react": "./src/react.js"
|
|
24
24
|
},
|
|
25
|
+
"bin": {
|
|
26
|
+
"watchforge": "bin/watchforge.js"
|
|
27
|
+
},
|
|
25
28
|
"files": [
|
|
29
|
+
"bin/**/*.js",
|
|
26
30
|
"src/**/*.js",
|
|
27
31
|
"README.md",
|
|
28
32
|
"CONFIGURATION_GUIDE.md",
|
|
29
33
|
"LICENSE"
|
|
30
34
|
],
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
31
38
|
"type": "module",
|
|
32
39
|
"peerDependencies": {
|
|
33
40
|
"react": ">=16.8.0"
|
|
@@ -43,7 +50,8 @@
|
|
|
43
50
|
"url": false
|
|
44
51
|
},
|
|
45
52
|
"dependencies": {
|
|
46
|
-
"express": "^5.2.1"
|
|
53
|
+
"express": "^5.2.1",
|
|
54
|
+
"rrweb": "^2.0.1"
|
|
47
55
|
},
|
|
48
56
|
"devDependencies": {
|
|
49
57
|
"rollup": "^4.60.0"
|