@reshotdev/screenshot 0.0.1-beta.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/LICENSE +190 -0
- package/README.md +388 -0
- package/package.json +64 -0
- package/src/commands/auth.js +259 -0
- package/src/commands/chrome.js +140 -0
- package/src/commands/ci-run.js +123 -0
- package/src/commands/ci-setup.js +288 -0
- package/src/commands/drifts.js +423 -0
- package/src/commands/import-tests.js +309 -0
- package/src/commands/ingest.js +458 -0
- package/src/commands/init.js +633 -0
- package/src/commands/publish.js +1721 -0
- package/src/commands/pull.js +303 -0
- package/src/commands/record.js +94 -0
- package/src/commands/run.js +476 -0
- package/src/commands/setup-wizard.js +740 -0
- package/src/commands/setup.js +137 -0
- package/src/commands/status.js +275 -0
- package/src/commands/sync.js +621 -0
- package/src/commands/ui.js +248 -0
- package/src/commands/validate-docs.js +529 -0
- package/src/index.js +462 -0
- package/src/lib/api-client.js +815 -0
- package/src/lib/capture-engine.js +1623 -0
- package/src/lib/capture-script-runner.js +3120 -0
- package/src/lib/ci-detect.js +137 -0
- package/src/lib/config.js +1240 -0
- package/src/lib/diff-engine.js +642 -0
- package/src/lib/hash.js +74 -0
- package/src/lib/image-crop.js +396 -0
- package/src/lib/matrix.js +89 -0
- package/src/lib/output-path-template.js +318 -0
- package/src/lib/playwright-runner.js +252 -0
- package/src/lib/polished-clip.js +553 -0
- package/src/lib/privacy-engine.js +408 -0
- package/src/lib/progress-tracker.js +142 -0
- package/src/lib/record-browser-injection.js +654 -0
- package/src/lib/record-cdp.js +612 -0
- package/src/lib/record-clip.js +343 -0
- package/src/lib/record-config.js +623 -0
- package/src/lib/record-screenshot.js +360 -0
- package/src/lib/record-terminal.js +123 -0
- package/src/lib/recorder-service.js +781 -0
- package/src/lib/secrets.js +51 -0
- package/src/lib/selector-strategies.js +859 -0
- package/src/lib/standalone-mode.js +400 -0
- package/src/lib/storage-providers.js +569 -0
- package/src/lib/style-engine.js +684 -0
- package/src/lib/ui-api.js +4677 -0
- package/src/lib/ui-assets.js +373 -0
- package/src/lib/ui-executor.js +587 -0
- package/src/lib/variant-injector.js +591 -0
- package/src/lib/viewport-presets.js +454 -0
- package/src/lib/worker-pool.js +118 -0
- package/web/cropper/index.html +436 -0
- package/web/manager/dist/assets/index--ZgioErz.js +507 -0
- package/web/manager/dist/assets/index-n468W0Wr.css +1 -0
- package/web/manager/dist/index.html +27 -0
- package/web/subtitle-editor/index.html +295 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// ui.js - Launch Reshot management web UI
|
|
2
|
+
const express = require("express");
|
|
3
|
+
const cors = require("cors");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const chalk = require("chalk");
|
|
6
|
+
const { spawn } = require("child_process");
|
|
7
|
+
const fs = require("fs-extra");
|
|
8
|
+
const http = require("http");
|
|
9
|
+
const { Server: SocketIOServer } = require("socket.io");
|
|
10
|
+
// `open` is ESM-only in the version we use; when required from CommonJS
|
|
11
|
+
// its callable export is exposed on the `default` property.
|
|
12
|
+
const openModule = require("open");
|
|
13
|
+
const open = openModule.default || openModule;
|
|
14
|
+
const getPortModule = require("get-port");
|
|
15
|
+
const getPort = getPortModule.default || getPortModule;
|
|
16
|
+
const { readSettings, readConfig, configExists } = require("../lib/config");
|
|
17
|
+
const { attachApiRoutes } = require("../lib/ui-api");
|
|
18
|
+
const RecorderService = require("../lib/recorder-service");
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Build the frontend if it doesn't exist
|
|
22
|
+
*/
|
|
23
|
+
async function ensureFrontendBuilt(managerDir, staticDir) {
|
|
24
|
+
const indexHtmlPath = path.join(staticDir, "index.html");
|
|
25
|
+
|
|
26
|
+
if (fs.existsSync(indexHtmlPath)) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
console.log(chalk.cyan("\n📦 Building Reshot Studio UI (web/manager)...\n"));
|
|
31
|
+
|
|
32
|
+
// Check if node_modules exists, if not run npm install
|
|
33
|
+
const nodeModulesPath = path.join(managerDir, "node_modules");
|
|
34
|
+
if (!fs.existsSync(nodeModulesPath)) {
|
|
35
|
+
console.log(chalk.gray("Installing dependencies..."));
|
|
36
|
+
await runCommand("npm", ["install"], { cwd: managerDir, stdio: "inherit" });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Run build
|
|
40
|
+
console.log(chalk.gray("Building frontend..."));
|
|
41
|
+
const buildSuccess = await runCommand("npm", ["run", "build"], {
|
|
42
|
+
cwd: managerDir,
|
|
43
|
+
stdio: "inherit",
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (!buildSuccess || !fs.existsSync(indexHtmlPath)) {
|
|
47
|
+
console.error(
|
|
48
|
+
chalk.red("\n❌ Failed to build frontend."),
|
|
49
|
+
"\nYou can manually build it by running:",
|
|
50
|
+
chalk.cyan(" npm run ui:build"),
|
|
51
|
+
"\nOr from the web/manager directory:",
|
|
52
|
+
chalk.cyan(" cd web/manager && npm install && npm run build\n")
|
|
53
|
+
);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
console.log(chalk.green("✔ Frontend built successfully\n"));
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Run a command and wait for it to complete
|
|
63
|
+
*/
|
|
64
|
+
function runCommand(command, args, options = {}) {
|
|
65
|
+
return new Promise((resolve) => {
|
|
66
|
+
const child = spawn(command, args, {
|
|
67
|
+
...options,
|
|
68
|
+
shell: process.platform === "win32",
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
child.on("close", (code) => {
|
|
72
|
+
resolve(code === 0);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
child.on("error", (error) => {
|
|
76
|
+
console.error(chalk.red(`Failed to run ${command}:`), error.message);
|
|
77
|
+
resolve(false);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = async function uiCommand(options = {}) {
|
|
83
|
+
const requestedPort = parseInt(options.port || "4300", 10);
|
|
84
|
+
const host = options.host || "127.0.0.1";
|
|
85
|
+
const shouldOpen = options.open !== false; // Default to true, allow --no-open flag
|
|
86
|
+
|
|
87
|
+
// Find an available port, preferring the requested port
|
|
88
|
+
const port = await getPort({ port: requestedPort });
|
|
89
|
+
|
|
90
|
+
if (port !== requestedPort) {
|
|
91
|
+
console.log(
|
|
92
|
+
chalk.yellow(
|
|
93
|
+
`⚠ Port ${requestedPort} is in use. Using port ${port} instead.`
|
|
94
|
+
)
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 1) Try to read settings, but allow degraded mode
|
|
99
|
+
let settings = null;
|
|
100
|
+
let isAuthenticated = false;
|
|
101
|
+
try {
|
|
102
|
+
settings = readSettings();
|
|
103
|
+
isAuthenticated = !!(settings?.apiKey && settings?.projectId);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.warn(
|
|
106
|
+
chalk.yellow("⚠ Warning:"),
|
|
107
|
+
"No CLI settings found. Some features may be limited."
|
|
108
|
+
);
|
|
109
|
+
console.warn(
|
|
110
|
+
chalk.gray(
|
|
111
|
+
" Run `reshot auth` to authenticate and unlock full functionality.\n"
|
|
112
|
+
)
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 2) Check if config exists, but allow UI to start anyway (it can pull from platform)
|
|
117
|
+
let config = null;
|
|
118
|
+
if (configExists()) {
|
|
119
|
+
try {
|
|
120
|
+
config = readConfig();
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.warn(
|
|
123
|
+
chalk.yellow("Warning:"),
|
|
124
|
+
"Config file exists but is invalid. UI will allow you to fix it or pull from platform."
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
console.log(
|
|
129
|
+
chalk.yellow("Info:"),
|
|
130
|
+
"No docsync.config.json found. Use the UI to pull config from platform or create a new one."
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 3) Ensure frontend is built
|
|
135
|
+
const managerDir = path.join(__dirname, "..", "..", "web", "manager");
|
|
136
|
+
const staticDir = path.join(managerDir, "dist");
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
await ensureFrontendBuilt(managerDir, staticDir);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.error(chalk.red("Failed to build frontend:"), error.message);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const app = express();
|
|
146
|
+
app.use(cors());
|
|
147
|
+
app.use(express.json({ limit: "2mb" }));
|
|
148
|
+
|
|
149
|
+
// Create HTTP server and Socket.io instance
|
|
150
|
+
const httpServer = http.createServer(app);
|
|
151
|
+
const io = new SocketIOServer(httpServer, {
|
|
152
|
+
cors: {
|
|
153
|
+
origin: "*",
|
|
154
|
+
methods: ["GET", "POST"],
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Create RecorderService instance with Socket.io
|
|
159
|
+
const recorderService = new RecorderService({
|
|
160
|
+
io,
|
|
161
|
+
logger: console.log,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// 4) Attach JSON API routes (pass settings, io, and recorderService)
|
|
165
|
+
attachApiRoutes(app, { settings, io, recorderService });
|
|
166
|
+
|
|
167
|
+
// Socket.io connection handling
|
|
168
|
+
io.on("connection", (socket) => {
|
|
169
|
+
console.log(chalk.gray("[Socket.io] Client connected"));
|
|
170
|
+
|
|
171
|
+
// Send current recorder status on connect
|
|
172
|
+
socket.emit("recorder:status", recorderService.getStatus());
|
|
173
|
+
socket.emit("recorder:steps", { steps: recorderService.getSteps() });
|
|
174
|
+
|
|
175
|
+
socket.on("disconnect", () => {
|
|
176
|
+
console.log(chalk.gray("[Socket.io] Client disconnected"));
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// 5) Serve static frontend
|
|
181
|
+
app.use(express.static(staticDir));
|
|
182
|
+
app.get("*", (req, res) => {
|
|
183
|
+
res.sendFile(path.join(staticDir, "index.html"));
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
httpServer.listen(port, host, () => {
|
|
187
|
+
const url = `http://${host}:${port}`;
|
|
188
|
+
console.log(chalk.cyan("\n✨ Reshot Studio is running"));
|
|
189
|
+
console.log(chalk.gray(` Local studio website: ${chalk.underline(url)}`));
|
|
190
|
+
console.log(chalk.gray(` Recording controls: Available via Socket.io`));
|
|
191
|
+
console.log(chalk.gray(` Press Ctrl+C to stop the server\n`));
|
|
192
|
+
|
|
193
|
+
// Auto-open browser unless --no-open flag is set
|
|
194
|
+
if (shouldOpen) {
|
|
195
|
+
setTimeout(() => {
|
|
196
|
+
open(url).catch((err) => {
|
|
197
|
+
// Silently fail if browser can't be opened
|
|
198
|
+
console.warn(
|
|
199
|
+
chalk.yellow("Could not auto-open browser. Please open manually:"),
|
|
200
|
+
url
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
}, 500);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Graceful shutdown helper
|
|
208
|
+
let isShuttingDown = false;
|
|
209
|
+
async function shutdown(signal) {
|
|
210
|
+
if (isShuttingDown) return;
|
|
211
|
+
isShuttingDown = true;
|
|
212
|
+
|
|
213
|
+
console.log(chalk.gray(`\n\nShutting down Reshot Studio (${signal})...`));
|
|
214
|
+
|
|
215
|
+
// Force exit after 3 seconds if graceful shutdown hangs
|
|
216
|
+
const forceExitTimer = setTimeout(() => {
|
|
217
|
+
console.log(chalk.yellow("Force exiting..."));
|
|
218
|
+
process.exit(0);
|
|
219
|
+
}, 3000);
|
|
220
|
+
|
|
221
|
+
// Clean up recorder session if active
|
|
222
|
+
try {
|
|
223
|
+
await recorderService.forceCleanup();
|
|
224
|
+
} catch (e) {
|
|
225
|
+
// Ignore cleanup errors
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Close all socket.io connections first (this is key!)
|
|
229
|
+
try {
|
|
230
|
+
io.close();
|
|
231
|
+
} catch (e) {
|
|
232
|
+
// Ignore
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Now close the HTTP server
|
|
236
|
+
httpServer.close(() => {
|
|
237
|
+
clearTimeout(forceExitTimer);
|
|
238
|
+
console.log(chalk.green("✔ Server closed"));
|
|
239
|
+
process.exit(0);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
244
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
245
|
+
|
|
246
|
+
// Keep process alive
|
|
247
|
+
return new Promise(() => {});
|
|
248
|
+
};
|