@reshotdev/screenshot 0.0.1-beta.1 → 0.0.1-beta.11
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 +65 -7
- package/package.json +9 -2
- package/src/commands/auth.js +108 -26
- package/src/commands/certify.js +62 -0
- package/src/commands/ci-run.js +57 -2
- package/src/commands/ci-setup.js +5 -5
- package/src/commands/doctor-release.js +67 -0
- package/src/commands/doctor-target.js +49 -0
- package/src/commands/drifts.js +5 -70
- package/src/commands/import-tests.js +13 -13
- package/src/commands/ingest.js +10 -10
- package/src/commands/init.js +16 -277
- package/src/commands/publish.js +204 -237
- package/src/commands/pull.js +253 -23
- package/src/commands/run.js +292 -12
- package/src/commands/setup-wizard.js +277 -499
- package/src/commands/setup.js +41 -13
- package/src/commands/status.js +313 -125
- package/src/commands/sync.js +28 -236
- package/src/commands/ui.js +1 -1
- package/src/commands/verify-publish.js +46 -0
- package/src/index.js +194 -94
- package/src/lib/api-client.js +121 -35
- package/src/lib/capture-engine.js +103 -7
- package/src/lib/capture-script-runner.js +359 -58
- package/src/lib/certification.js +865 -0
- package/src/lib/config.js +181 -76
- package/src/lib/record-cdp.js +288 -16
- package/src/lib/record-config.js +1 -1
- package/src/lib/release-doctor.js +313 -0
- package/src/lib/run-manifest.js +103 -0
- package/src/lib/standalone-mode.js +1 -1
- package/src/lib/storage-providers.js +4 -4
- package/src/lib/target-contract.js +292 -0
- package/src/lib/ui-api.js +6 -7
- package/web/manager/dist/assets/{index--ZgioErz.js → index-D2qqcFNN.js} +1 -1
- package/web/manager/dist/index.html +1 -1
- package/src/commands/validate-docs.js +0 -529
package/src/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// index.js - Reshot CLI entry point
|
|
4
|
-
// Consolidated command structure for ease of use while maintaining power features
|
|
5
4
|
|
|
6
5
|
require("dotenv").config({
|
|
7
6
|
path: require("path").join(__dirname, "..", ".env"),
|
|
@@ -15,7 +14,7 @@ const program = new Command();
|
|
|
15
14
|
|
|
16
15
|
program
|
|
17
16
|
.name("reshot")
|
|
18
|
-
.description("Visual
|
|
17
|
+
.description("Visual capture, publishing, and governance for your UI")
|
|
19
18
|
.version(pkg.version);
|
|
20
19
|
|
|
21
20
|
// ============================================================================
|
|
@@ -25,8 +24,9 @@ program
|
|
|
25
24
|
// Setup: Interactive wizard for initial configuration
|
|
26
25
|
program
|
|
27
26
|
.command("setup")
|
|
28
|
-
.description("
|
|
29
|
-
.option("--offline", "
|
|
27
|
+
.description("Set up the local or hosted workflow from scratch")
|
|
28
|
+
.option("--offline", "Stay local-only and skip hosted authentication")
|
|
29
|
+
.option("--no-studio", "Skip offering to launch Studio after setup")
|
|
30
30
|
.option("--force", "Force re-initialization even if already set up")
|
|
31
31
|
.action(async (options) => {
|
|
32
32
|
try {
|
|
@@ -38,13 +38,23 @@ program
|
|
|
38
38
|
}
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
program.addHelpText(
|
|
42
|
+
"after",
|
|
43
|
+
`
|
|
44
|
+
Primary workflow:
|
|
45
|
+
reshot setup Configure the project and choose local-only or hosted mode
|
|
46
|
+
reshot run Run configured scenarios with fail-fast preflight checks
|
|
47
|
+
reshot publish Upload captured assets to the Reshot platform
|
|
48
|
+
reshot pull Pull approved CDN-backed asset references into your repo
|
|
49
|
+
reshot doctor target Audit certified-target readiness before capture
|
|
50
|
+
`,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// Sync: Upload Playwright traces to platform
|
|
42
54
|
program
|
|
43
55
|
.command("sync")
|
|
44
|
-
.description("Upload Playwright traces
|
|
56
|
+
.description("Upload Playwright traces to Reshot")
|
|
45
57
|
.option("--trace-dir <path>", "Path to test-results directory")
|
|
46
|
-
.option("--traces", "Sync traces only")
|
|
47
|
-
.option("--docs", "Sync documentation only")
|
|
48
58
|
.option("--dry-run", "Preview what would be synced")
|
|
49
59
|
.option("-v, --verbose", "Show detailed output")
|
|
50
60
|
.action(async (options) => {
|
|
@@ -52,8 +62,6 @@ program
|
|
|
52
62
|
const syncCommand = require("./commands/sync");
|
|
53
63
|
await syncCommand({
|
|
54
64
|
traceDir: options.traceDir,
|
|
55
|
-
traces: options.traces,
|
|
56
|
-
docs: options.docs,
|
|
57
65
|
dryRun: options.dryRun,
|
|
58
66
|
verbose: options.verbose,
|
|
59
67
|
});
|
|
@@ -63,7 +71,7 @@ program
|
|
|
63
71
|
}
|
|
64
72
|
});
|
|
65
73
|
|
|
66
|
-
// Status: View project status
|
|
74
|
+
// Status: View project status and drift summary
|
|
67
75
|
program
|
|
68
76
|
.command("status")
|
|
69
77
|
.description("View project status, sync history, and drift summary")
|
|
@@ -99,27 +107,6 @@ program
|
|
|
99
107
|
}
|
|
100
108
|
});
|
|
101
109
|
|
|
102
|
-
// Validate: Check configuration and documentation bindings
|
|
103
|
-
program
|
|
104
|
-
.command("validate")
|
|
105
|
-
.description("Validate configuration and documentation bindings")
|
|
106
|
-
.option("--strict", "Fail on warnings (zombie links, unbound files)")
|
|
107
|
-
.option("--fix", "Attempt to auto-fix issues where possible")
|
|
108
|
-
.option("-v, --verbose", "Show detailed validation output")
|
|
109
|
-
.action(async (options) => {
|
|
110
|
-
try {
|
|
111
|
-
const validateCommand = require("./commands/validate-docs");
|
|
112
|
-
if (typeof validateCommand.validateDocSync === "function") {
|
|
113
|
-
await validateCommand.validateDocSync(options);
|
|
114
|
-
} else {
|
|
115
|
-
await validateCommand.validateDocs(options);
|
|
116
|
-
}
|
|
117
|
-
} catch (error) {
|
|
118
|
-
console.error(chalk.red("Error:"), error.message);
|
|
119
|
-
process.exit(1);
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
|
|
123
110
|
// ============================================================================
|
|
124
111
|
// VISUAL CAPTURE COMMANDS
|
|
125
112
|
// ============================================================================
|
|
@@ -179,19 +166,96 @@ program
|
|
|
179
166
|
}
|
|
180
167
|
});
|
|
181
168
|
|
|
169
|
+
// Capture: Compatibility alias for older docs and snippets
|
|
170
|
+
program
|
|
171
|
+
.command("capture")
|
|
172
|
+
.description("Compatibility alias for `reshot run` (prefer `reshot run` and `reshot publish`)")
|
|
173
|
+
.option("-s, --scenarios <keys>", "Comma-separated list of scenario keys")
|
|
174
|
+
.option("--mode <mode>", "Compatibility mode: preview | publish", "preview")
|
|
175
|
+
.option("--config <path>", "Compatibility config path (defaults to reshot.config.json)")
|
|
176
|
+
.option("--base-url <url>", "Temporary base URL override for compatibility flows")
|
|
177
|
+
.option("--tag <tag>", "Version tag when mode=publish")
|
|
178
|
+
.option("-m, --message <message>", "Publish message when mode=publish")
|
|
179
|
+
.option("--no-headless", "Run browser in visible mode")
|
|
180
|
+
.option("-c, --concurrency <n>", "Number of parallel browser workers", parseInt)
|
|
181
|
+
.option("--debug", "Enable verbose debug logging")
|
|
182
|
+
.action(async (options) => {
|
|
183
|
+
if (options.debug) {
|
|
184
|
+
process.env.RESHOT_DEBUG = "1";
|
|
185
|
+
}
|
|
186
|
+
if (options.config && options.config !== "reshot.config.json") {
|
|
187
|
+
console.log(
|
|
188
|
+
chalk.yellow(
|
|
189
|
+
"Compatibility mode currently uses reshot.config.json from the working directory.",
|
|
190
|
+
),
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
if (options.baseUrl) {
|
|
194
|
+
process.env.RESHOT_BASE_URL = options.baseUrl;
|
|
195
|
+
console.log(
|
|
196
|
+
chalk.gray(
|
|
197
|
+
`Using temporary base URL override for this run: ${options.baseUrl}`,
|
|
198
|
+
),
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
try {
|
|
202
|
+
const runCommand = require("./commands/run");
|
|
203
|
+
const scenarioKeys = options.scenarios
|
|
204
|
+
? options.scenarios.split(",").map((s) => s.trim())
|
|
205
|
+
: null;
|
|
206
|
+
await runCommand({
|
|
207
|
+
scenarioKeys,
|
|
208
|
+
headless: options.headless,
|
|
209
|
+
concurrency: options.concurrency,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
if (String(options.mode || "preview").toLowerCase() === "publish") {
|
|
213
|
+
const publishCommand = require("./commands/publish");
|
|
214
|
+
await publishCommand({
|
|
215
|
+
tag: options.tag,
|
|
216
|
+
message: options.message,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
} catch (error) {
|
|
220
|
+
console.error(chalk.red("Error:"), error.message);
|
|
221
|
+
if (options.debug && error.stack) {
|
|
222
|
+
console.error(chalk.gray(error.stack));
|
|
223
|
+
}
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
182
228
|
// Record: Interactive visual recording from live browser
|
|
183
229
|
program
|
|
184
230
|
.command("record [title]")
|
|
185
231
|
.description("Interactively record visuals from a live browser session")
|
|
232
|
+
.option("--name <title>", "Compatibility alias for the scenario title")
|
|
186
233
|
.option("--browser", "Launch Chrome with remote debugging before recording")
|
|
187
234
|
.option("-p, --port <port>", "Chrome debugging port (default: 9222)")
|
|
188
235
|
.option("--url <url>", "URL to open when launching browser")
|
|
236
|
+
.option("--refresh-session", "Only refresh the auth session (no recording prompts)")
|
|
189
237
|
.option("--debug", "Enable verbose debug logging")
|
|
190
238
|
.action(async (title, options) => {
|
|
191
239
|
if (options.debug) {
|
|
192
240
|
process.env.RESHOT_DEBUG = "1";
|
|
193
241
|
}
|
|
242
|
+
const resolvedTitle = title || options.name;
|
|
194
243
|
try {
|
|
244
|
+
// If --refresh-session, just sync the session and exit
|
|
245
|
+
if (options.refreshSession) {
|
|
246
|
+
const { autoSyncSessionFromCDP, getDefaultSessionPath } = require("./lib/record-cdp");
|
|
247
|
+
const sessionPath = getDefaultSessionPath();
|
|
248
|
+
console.log(chalk.gray(" Syncing session from active browser..."));
|
|
249
|
+
const result = await autoSyncSessionFromCDP(sessionPath);
|
|
250
|
+
if (result.synced) {
|
|
251
|
+
console.log(chalk.green(" ✔ Session refreshed at " + sessionPath));
|
|
252
|
+
} else {
|
|
253
|
+
console.log(chalk.yellow(" ⚠ No active CDP browser found. Launch Chrome with remote debugging first:"));
|
|
254
|
+
console.log(chalk.gray(" reshot record --browser --refresh-session"));
|
|
255
|
+
}
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
195
259
|
// If --browser flag, launch Chrome first
|
|
196
260
|
if (options.browser) {
|
|
197
261
|
const chromeCommand = require("./commands/chrome");
|
|
@@ -204,7 +268,7 @@ program
|
|
|
204
268
|
}
|
|
205
269
|
|
|
206
270
|
const recordCommand = require("./commands/record");
|
|
207
|
-
await recordCommand(
|
|
271
|
+
await recordCommand(resolvedTitle);
|
|
208
272
|
} catch (error) {
|
|
209
273
|
console.error(chalk.red("Error:"), error.message);
|
|
210
274
|
if (options.debug && error.stack) {
|
|
@@ -246,13 +310,18 @@ program
|
|
|
246
310
|
.option("--video <path>", "Explicit video file to upload with this publish")
|
|
247
311
|
.option("--dry-run", "Preview without uploading")
|
|
248
312
|
.option("-f, --force", "Skip confirmation prompts")
|
|
313
|
+
.option("--all-output", "Publish from the full .reshot/output tree instead of the latest successful run manifest")
|
|
249
314
|
.option("--output-json", "Write structured result to .reshot/output/publish-result.json")
|
|
315
|
+
.option("--auto-approve", "Automatically approve published visuals (skip review queue)")
|
|
316
|
+
.option("--skip-release-doctor", "Skip the composed release gate precheck")
|
|
250
317
|
.action(async (options) => {
|
|
251
318
|
try {
|
|
252
319
|
const publishCommand = require("./commands/publish");
|
|
253
320
|
await publishCommand({
|
|
254
321
|
...options,
|
|
255
322
|
outputJson: options.outputJson,
|
|
323
|
+
autoApprove: options.autoApprove,
|
|
324
|
+
skipReleaseDoctor: options.skipReleaseDoctor,
|
|
256
325
|
});
|
|
257
326
|
} catch (error) {
|
|
258
327
|
console.error(chalk.red("Error:"), error.message);
|
|
@@ -283,6 +352,78 @@ program
|
|
|
283
352
|
}
|
|
284
353
|
});
|
|
285
354
|
|
|
355
|
+
// Doctor: Validate target contract and readiness
|
|
356
|
+
const doctor = program.command("doctor").description("Validate target configuration and readiness");
|
|
357
|
+
|
|
358
|
+
doctor
|
|
359
|
+
.command("target")
|
|
360
|
+
.description("Audit the certified target contract before capture")
|
|
361
|
+
.option("-s, --scenarios <keys>", "Comma-separated list of scenario keys")
|
|
362
|
+
.option("--json", "Output JSON report")
|
|
363
|
+
.action(async (options) => {
|
|
364
|
+
try {
|
|
365
|
+
const doctorTargetCommand = require("./commands/doctor-target");
|
|
366
|
+
await doctorTargetCommand(options);
|
|
367
|
+
} catch (error) {
|
|
368
|
+
console.error(chalk.red("Error:"), error.message);
|
|
369
|
+
process.exit(1);
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
doctor
|
|
374
|
+
.command("release")
|
|
375
|
+
.description("Run the combined release gate: preflight, target doctor, and docs assets")
|
|
376
|
+
.option("-s, --scenarios <keys>", "Comma-separated list of scenario keys")
|
|
377
|
+
.option("--json", "Output JSON report")
|
|
378
|
+
.action(async (options) => {
|
|
379
|
+
try {
|
|
380
|
+
const doctorReleaseCommand = require("./commands/doctor-release");
|
|
381
|
+
await doctorReleaseCommand(options);
|
|
382
|
+
} catch (error) {
|
|
383
|
+
console.error(chalk.red("Error:"), error.message);
|
|
384
|
+
process.exit(1);
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// Verify: Publish/pull/hosted delivery verification
|
|
389
|
+
const verify = program.command("verify").description("Verify publish and delivery flows");
|
|
390
|
+
|
|
391
|
+
verify
|
|
392
|
+
.command("publish")
|
|
393
|
+
.description("Verify publish, pull, and hosted asset delivery")
|
|
394
|
+
.option("-s, --scenarios <keys>", "Comma-separated list of scenario keys")
|
|
395
|
+
.option("--tag <tag>", "Version tag for verification publish")
|
|
396
|
+
.option("-m, --message <message>", "Publish message override")
|
|
397
|
+
.option("--json", "Output JSON report")
|
|
398
|
+
.action(async (options) => {
|
|
399
|
+
try {
|
|
400
|
+
const verifyPublishCommand = require("./commands/verify-publish");
|
|
401
|
+
await verifyPublishCommand(options);
|
|
402
|
+
} catch (error) {
|
|
403
|
+
console.error(chalk.red("Error:"), error.message);
|
|
404
|
+
process.exit(1);
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
// Certify: full certified-target release gate
|
|
409
|
+
program
|
|
410
|
+
.command("certify")
|
|
411
|
+
.description("Run the full certified target pipeline")
|
|
412
|
+
.option("-s, --scenarios <keys>", "Comma-separated list of scenario keys")
|
|
413
|
+
.option("--tag <tag>", "Version tag for certification publish")
|
|
414
|
+
.option("-m, --message <message>", "Publish message override")
|
|
415
|
+
.option("--skip-release-doctor", "Skip the composed release gate precheck")
|
|
416
|
+
.option("--json", "Output JSON report")
|
|
417
|
+
.action(async (options) => {
|
|
418
|
+
try {
|
|
419
|
+
const certifyCommand = require("./commands/certify");
|
|
420
|
+
await certifyCommand(options);
|
|
421
|
+
} catch (error) {
|
|
422
|
+
console.error(chalk.red("Error:"), error.message);
|
|
423
|
+
process.exit(1);
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
|
|
286
427
|
// CI: CI/CD integration commands
|
|
287
428
|
const ciCommand = program
|
|
288
429
|
.command("ci")
|
|
@@ -304,11 +445,12 @@ ciCommand
|
|
|
304
445
|
ciCommand
|
|
305
446
|
.command("run")
|
|
306
447
|
.description("Run capture + publish in one step (CI-optimized)")
|
|
307
|
-
.option("-c, --config <path>", "Path to
|
|
448
|
+
.option("-c, --config <path>", "Path to reshot.config.json")
|
|
308
449
|
.option("--tag <tag>", "Version tag for publish")
|
|
309
450
|
.option("-m, --message <message>", "Commit message for publish")
|
|
310
451
|
.option("--dry-run", "Preview without uploading")
|
|
311
452
|
.option("--no-publish", "Run capture only, skip publish")
|
|
453
|
+
.option("--skip-release-doctor", "Skip the composed release gate precheck")
|
|
312
454
|
.action(async (options) => {
|
|
313
455
|
try {
|
|
314
456
|
const ciRun = require("./commands/ci-run");
|
|
@@ -323,10 +465,10 @@ ciCommand
|
|
|
323
465
|
// DRIFT MANAGEMENT COMMANDS
|
|
324
466
|
// ============================================================================
|
|
325
467
|
|
|
326
|
-
// Drifts: View and manage
|
|
468
|
+
// Drifts: View and manage visual drifts
|
|
327
469
|
program
|
|
328
470
|
.command("drifts [action] [id]")
|
|
329
|
-
.description("View and manage
|
|
471
|
+
.description("View and manage visual drifts")
|
|
330
472
|
.addHelpText(
|
|
331
473
|
"after",
|
|
332
474
|
`
|
|
@@ -339,7 +481,6 @@ Actions:
|
|
|
339
481
|
sync <id> Mark as manually synced (external_host)
|
|
340
482
|
approve-all Approve all pending drifts
|
|
341
483
|
reject-all Reject all pending drifts
|
|
342
|
-
validate Validate journey bindings
|
|
343
484
|
`,
|
|
344
485
|
)
|
|
345
486
|
.option("--status <status>", "Filter: PENDING, APPROVED, REJECTED, IGNORED")
|
|
@@ -348,7 +489,6 @@ Actions:
|
|
|
348
489
|
.action(async (action, id, options) => {
|
|
349
490
|
try {
|
|
350
491
|
const driftsCommand = require("./commands/drifts");
|
|
351
|
-
// Map action/id to the expected subcommand/args format
|
|
352
492
|
const subcommand = action || "list";
|
|
353
493
|
const args = id ? [id] : [];
|
|
354
494
|
await driftsCommand(subcommand, args, options);
|
|
@@ -363,9 +503,22 @@ Actions:
|
|
|
363
503
|
// ============================================================================
|
|
364
504
|
|
|
365
505
|
// Auth: Standalone authentication (for re-auth scenarios)
|
|
366
|
-
program
|
|
367
|
-
.command("auth"
|
|
368
|
-
.description("Authenticate with Reshot Cloud")
|
|
506
|
+
const auth = program
|
|
507
|
+
.command("auth")
|
|
508
|
+
.description("Authenticate with Reshot Cloud (or set RESHOT_API_KEY + RESHOT_PROJECT_ID env vars)")
|
|
509
|
+
.action(async () => {
|
|
510
|
+
try {
|
|
511
|
+
const authCommand = require("./commands/auth");
|
|
512
|
+
await authCommand();
|
|
513
|
+
} catch (error) {
|
|
514
|
+
console.error(chalk.red("Error:"), error.message);
|
|
515
|
+
process.exit(1);
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
auth
|
|
520
|
+
.command("login")
|
|
521
|
+
.description("Compatibility alias for `reshot auth`")
|
|
369
522
|
.action(async () => {
|
|
370
523
|
try {
|
|
371
524
|
const authCommand = require("./commands/auth");
|
|
@@ -406,57 +559,4 @@ program
|
|
|
406
559
|
}
|
|
407
560
|
});
|
|
408
561
|
|
|
409
|
-
// Ingest: Alias for sync (backward compatibility)
|
|
410
|
-
program
|
|
411
|
-
.command("ingest", { hidden: true })
|
|
412
|
-
.description("Upload traces and docs (alias for sync)")
|
|
413
|
-
.option("--trace-dir <path>", "Path to test-results directory")
|
|
414
|
-
.option("--dry-run", "Preview what would be synced")
|
|
415
|
-
.action(async (options) => {
|
|
416
|
-
console.log(
|
|
417
|
-
chalk.yellow("Note: 'ingest' is now 'sync'. Running sync...\n"),
|
|
418
|
-
);
|
|
419
|
-
try {
|
|
420
|
-
const syncCommand = require("./commands/sync");
|
|
421
|
-
await syncCommand({
|
|
422
|
-
traceDir: options.traceDir,
|
|
423
|
-
dryRun: options.dryRun,
|
|
424
|
-
});
|
|
425
|
-
} catch (error) {
|
|
426
|
-
console.error(chalk.red("Error:"), error.message);
|
|
427
|
-
process.exit(1);
|
|
428
|
-
}
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
// UI: Alias for studio (backward compatibility)
|
|
432
|
-
program
|
|
433
|
-
.command("ui", { hidden: true })
|
|
434
|
-
.description("Launch Reshot Studio (alias for studio)")
|
|
435
|
-
.option("--port <port>", "Port for web UI", "4300")
|
|
436
|
-
.option("--host <host>", "Host for web UI", "127.0.0.1")
|
|
437
|
-
.option("--no-open", "Do not automatically open browser")
|
|
438
|
-
.action(async (options) => {
|
|
439
|
-
try {
|
|
440
|
-
const uiCommand = require("./commands/ui");
|
|
441
|
-
await uiCommand(options);
|
|
442
|
-
} catch (error) {
|
|
443
|
-
console.error(chalk.red("Error:"), error.message);
|
|
444
|
-
process.exit(1);
|
|
445
|
-
}
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
// Init-docs: Alias hidden (merged into setup wizard)
|
|
449
|
-
program
|
|
450
|
-
.command("init-docs", { hidden: true })
|
|
451
|
-
.description("Initialize documentation sync (use setup instead)")
|
|
452
|
-
.action(async () => {
|
|
453
|
-
try {
|
|
454
|
-
const initDocsCommand = require("./commands/init-docs");
|
|
455
|
-
await initDocsCommand();
|
|
456
|
-
} catch (error) {
|
|
457
|
-
console.error(chalk.red("Error:"), error.message);
|
|
458
|
-
process.exit(1);
|
|
459
|
-
}
|
|
460
|
-
});
|
|
461
|
-
|
|
462
562
|
program.parse(process.argv);
|
package/src/lib/api-client.js
CHANGED
|
@@ -3,15 +3,80 @@ const axios = require("axios");
|
|
|
3
3
|
const FormData = require("form-data");
|
|
4
4
|
const fs = require("fs");
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"
|
|
6
|
+
const PRODUCTION_API_URL = "https://reshot.dev/api";
|
|
7
|
+
|
|
8
|
+
function summarizeApiBody(body) {
|
|
9
|
+
if (body == null) return "";
|
|
10
|
+
if (typeof body === "string") return body.trim();
|
|
11
|
+
if (typeof body !== "object") return String(body);
|
|
12
|
+
|
|
13
|
+
const candidates = [
|
|
14
|
+
body.message,
|
|
15
|
+
body.error?.message,
|
|
16
|
+
body.error,
|
|
17
|
+
body.reason,
|
|
18
|
+
body.details,
|
|
19
|
+
].filter(Boolean);
|
|
20
|
+
|
|
21
|
+
for (const candidate of candidates) {
|
|
22
|
+
if (typeof candidate === "string" && candidate.trim()) {
|
|
23
|
+
return candidate.trim();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
return JSON.stringify(body);
|
|
29
|
+
} catch {
|
|
30
|
+
return String(body);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function createApiError(kind, endpoint, error) {
|
|
35
|
+
const status = error.response?.status || null;
|
|
36
|
+
const statusText = error.response?.statusText || "";
|
|
37
|
+
const bodySummary = summarizeApiBody(error.response?.data);
|
|
38
|
+
const baseMessage = bodySummary || error.message || "Unknown API error";
|
|
39
|
+
const statusLabel = status ? `${status}${statusText ? ` ${statusText}` : ""}` : "request failed";
|
|
40
|
+
const wrapped = new Error(`${kind} (${statusLabel}) at ${endpoint}: ${baseMessage}`);
|
|
41
|
+
wrapped.response = error.response;
|
|
42
|
+
wrapped.code = error.code;
|
|
43
|
+
wrapped.reshot = {
|
|
44
|
+
kind,
|
|
45
|
+
endpoint,
|
|
46
|
+
status,
|
|
47
|
+
statusText,
|
|
48
|
+
bodySummary,
|
|
49
|
+
};
|
|
50
|
+
return wrapped;
|
|
51
|
+
}
|
|
10
52
|
|
|
11
53
|
function getApiBaseUrl() {
|
|
12
|
-
|
|
54
|
+
// 1. Explicit env var override (for CI or local dev)
|
|
55
|
+
if (process.env.RESHOT_API_BASE_URL) {
|
|
56
|
+
return process.env.RESHOT_API_BASE_URL;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 2. Read from settings.json (set during auth/setup)
|
|
60
|
+
try {
|
|
61
|
+
const path = require("path");
|
|
62
|
+
const settingsPath = path.join(process.cwd(), ".reshot", "settings.json");
|
|
63
|
+
if (fs.existsSync(settingsPath)) {
|
|
64
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
|
|
65
|
+
if (settings.platformUrl) {
|
|
66
|
+
return settings.platformUrl.replace(/\/+$/, "") + "/api";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
// Settings don't exist yet (first auth) — fall through to default
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 3. Default to production
|
|
74
|
+
return PRODUCTION_API_URL;
|
|
13
75
|
}
|
|
14
76
|
|
|
77
|
+
// Resolved once at module load — all API calls use this
|
|
78
|
+
const baseUrl = getApiBaseUrl();
|
|
79
|
+
|
|
15
80
|
/**
|
|
16
81
|
* Sleep helper for retry delays
|
|
17
82
|
*/
|
|
@@ -190,20 +255,7 @@ async function publishAssetsV1(apiKey, metadata, assets) {
|
|
|
190
255
|
});
|
|
191
256
|
return response.data;
|
|
192
257
|
} catch (error) {
|
|
193
|
-
|
|
194
|
-
const status = error.response.status;
|
|
195
|
-
const errorMsg = error.response.data?.error || error.message;
|
|
196
|
-
|
|
197
|
-
// Create an error that preserves the response for auth detection
|
|
198
|
-
const err = new Error(
|
|
199
|
-
status === 401 || status === 403
|
|
200
|
-
? `Authentication failed: ${errorMsg}`
|
|
201
|
-
: `Failed to publish assets: ${errorMsg}`,
|
|
202
|
-
);
|
|
203
|
-
err.response = error.response;
|
|
204
|
-
throw err;
|
|
205
|
-
}
|
|
206
|
-
throw new Error(`Failed to publish assets: ${error.message}`);
|
|
258
|
+
throw createApiError("publish_ingest_failure", `${baseUrl}/v1/publish`, error);
|
|
207
259
|
}
|
|
208
260
|
}
|
|
209
261
|
|
|
@@ -586,6 +638,30 @@ async function publishTransactional(apiKey, payload) {
|
|
|
586
638
|
return response.data;
|
|
587
639
|
}
|
|
588
640
|
|
|
641
|
+
/**
|
|
642
|
+
* Batch publish metadata for multiple scenarios in one request
|
|
643
|
+
* @param {string} apiKey - API key for authentication
|
|
644
|
+
* @param {Object} payload - { commits: Array<{ metadata, assets }> }
|
|
645
|
+
* @returns {Promise<Object>}
|
|
646
|
+
*/
|
|
647
|
+
async function publishBatch(apiKey, payload) {
|
|
648
|
+
if (!apiKey) {
|
|
649
|
+
throw new Error("API key is required to publish");
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const response = await axios.post(`${baseUrl}/v1/publish/batch`, payload, {
|
|
653
|
+
headers: {
|
|
654
|
+
"Content-Type": "application/json",
|
|
655
|
+
Authorization: `Bearer ${apiKey}`,
|
|
656
|
+
},
|
|
657
|
+
timeout: 60000,
|
|
658
|
+
maxBodyLength: Infinity,
|
|
659
|
+
maxContentLength: Infinity,
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
return response.data;
|
|
663
|
+
}
|
|
664
|
+
|
|
589
665
|
/**
|
|
590
666
|
* Check which hashes already exist in storage (for deduplication)
|
|
591
667
|
* @param {string} apiKey - API key for authentication
|
|
@@ -648,7 +724,7 @@ async function getBaselines(projectId, apiKey) {
|
|
|
648
724
|
*/
|
|
649
725
|
async function exportVisuals(projectId, options = {}) {
|
|
650
726
|
const { format = "json", status = "approved" } = options;
|
|
651
|
-
const settings = require("./config").
|
|
727
|
+
const settings = require("./config").readSettings();
|
|
652
728
|
const apiKey = settings?.apiKey;
|
|
653
729
|
|
|
654
730
|
if (!apiKey) {
|
|
@@ -656,15 +732,23 @@ async function exportVisuals(projectId, options = {}) {
|
|
|
656
732
|
}
|
|
657
733
|
|
|
658
734
|
return withRetry(async () => {
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
735
|
+
try {
|
|
736
|
+
const response = await axios.get(
|
|
737
|
+
`${baseUrl}/projects/${projectId}/visuals/export`,
|
|
738
|
+
{
|
|
739
|
+
params: { format, status },
|
|
740
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
741
|
+
timeout: 60000,
|
|
742
|
+
},
|
|
743
|
+
);
|
|
744
|
+
return response.data;
|
|
745
|
+
} catch (error) {
|
|
746
|
+
throw createApiError(
|
|
747
|
+
"export_visuals_failure",
|
|
748
|
+
`${baseUrl}/projects/${projectId}/visuals/export`,
|
|
749
|
+
error,
|
|
750
|
+
);
|
|
751
|
+
}
|
|
668
752
|
});
|
|
669
753
|
}
|
|
670
754
|
|
|
@@ -688,7 +772,7 @@ async function post(endpoint, data, options = {}) {
|
|
|
688
772
|
}
|
|
689
773
|
|
|
690
774
|
/**
|
|
691
|
-
*
|
|
775
|
+
* Initialize ingestion job with manifest
|
|
692
776
|
*/
|
|
693
777
|
async function initIngest(apiKey, projectId, manifest) {
|
|
694
778
|
return withRetry(async () => {
|
|
@@ -705,7 +789,7 @@ async function initIngest(apiKey, projectId, manifest) {
|
|
|
705
789
|
}
|
|
706
790
|
|
|
707
791
|
/**
|
|
708
|
-
*
|
|
792
|
+
* Commit ingestion job after uploads complete
|
|
709
793
|
*/
|
|
710
794
|
async function commitIngest(apiKey, projectId, uploadResults, git, cli) {
|
|
711
795
|
return withRetry(async () => {
|
|
@@ -722,7 +806,7 @@ async function commitIngest(apiKey, projectId, uploadResults, git, cli) {
|
|
|
722
806
|
}
|
|
723
807
|
|
|
724
808
|
/**
|
|
725
|
-
*
|
|
809
|
+
* Get drift records for a project
|
|
726
810
|
*/
|
|
727
811
|
async function getDrifts(apiKey, projectId, options = {}) {
|
|
728
812
|
return withRetry(async () => {
|
|
@@ -742,7 +826,7 @@ async function getDrifts(apiKey, projectId, options = {}) {
|
|
|
742
826
|
}
|
|
743
827
|
|
|
744
828
|
/**
|
|
745
|
-
*
|
|
829
|
+
* Get sync jobs for a project
|
|
746
830
|
*/
|
|
747
831
|
async function getSyncJobs(apiKey, projectId, options = {}) {
|
|
748
832
|
return withRetry(async () => {
|
|
@@ -762,7 +846,7 @@ async function getSyncJobs(apiKey, projectId, options = {}) {
|
|
|
762
846
|
}
|
|
763
847
|
|
|
764
848
|
/**
|
|
765
|
-
*
|
|
849
|
+
* Perform action on a drift record
|
|
766
850
|
*/
|
|
767
851
|
async function driftAction(apiKey, projectId, driftId, action, options = {}) {
|
|
768
852
|
return withRetry(async () => {
|
|
@@ -794,18 +878,20 @@ module.exports = {
|
|
|
794
878
|
getProjectConfig,
|
|
795
879
|
postChangelogDrafts,
|
|
796
880
|
getApiBaseUrl,
|
|
881
|
+
createApiError,
|
|
797
882
|
syncPushAssets,
|
|
798
883
|
getSyncStatus,
|
|
799
884
|
// New transactional flow
|
|
800
885
|
signAssets,
|
|
801
886
|
uploadToPresignedUrl,
|
|
802
887
|
publishTransactional,
|
|
888
|
+
publishBatch,
|
|
803
889
|
checkExistingHashes,
|
|
804
890
|
// Diffing support
|
|
805
891
|
getBaselines,
|
|
806
892
|
// Export support
|
|
807
893
|
exportVisuals,
|
|
808
|
-
//
|
|
894
|
+
// Reshot
|
|
809
895
|
post,
|
|
810
896
|
initIngest,
|
|
811
897
|
commitIngest,
|