@reshotdev/screenshot 0.0.1-beta.2 → 0.0.1-beta.20
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 +1 -1
- package/README.md +138 -47
- package/package.json +27 -16
- package/src/commands/auth.js +159 -30
- package/src/commands/capture-dom.js +50 -0
- package/src/commands/certify.js +62 -0
- package/src/commands/compose.js +220 -0
- package/src/commands/doctor-release.js +74 -0
- package/src/commands/doctor-target.js +108 -0
- package/src/commands/drifts.js +16 -69
- package/src/commands/import-tests.js +13 -13
- package/src/commands/init.js +16 -277
- package/src/commands/publish.js +484 -257
- package/src/commands/pull.js +302 -35
- package/src/commands/refresh.js +166 -0
- package/src/commands/run.js +292 -12
- package/src/commands/setup-wizard.js +348 -496
- package/src/commands/status.js +334 -126
- package/src/commands/sync.js +28 -236
- package/src/commands/ui.js +1 -1
- package/src/commands/variation.js +194 -0
- package/src/commands/verify-publish.js +46 -0
- package/src/index.js +383 -118
- package/src/lib/api-client.js +172 -60
- package/src/lib/auto-update/refresh.js +598 -0
- package/src/lib/auto-update/scene-runtime.compose.tsx +73 -0
- package/src/lib/auto-update/spec.js +89 -0
- package/src/lib/capture-engine.js +179 -9
- package/src/lib/capture-script-runner.js +639 -214
- package/src/lib/certification.js +887 -0
- package/src/lib/compose-context.js +156 -0
- package/src/lib/compose-pack.js +42 -0
- package/src/lib/compose-runtime.js +34 -0
- package/src/lib/compose-upload.js +142 -0
- package/src/lib/config.js +186 -81
- package/src/lib/dom-capture.js +64 -0
- package/src/lib/ensure-browser.js +147 -0
- package/src/lib/output-path-template.js +3 -3
- package/src/lib/record-cdp.js +288 -16
- package/src/lib/record-clip.js +83 -3
- package/src/lib/record-config.js +1 -5
- package/src/lib/release-doctor.js +321 -0
- package/src/lib/resolve-targets.js +60 -0
- package/src/lib/run-manifest.js +148 -0
- package/src/lib/standalone-mode.js +1 -1
- package/src/lib/storage-providers.js +5 -5
- package/src/lib/style-engine.js +5 -5
- package/src/lib/target-contract.js +292 -0
- package/src/lib/ui-api-helpers.js +118 -0
- package/src/lib/ui-api.js +31 -824
- package/src/lib/ui-asset-cleanup.js +62 -0
- package/src/lib/ui-output-versions.js +165 -0
- package/src/lib/ui-recorder-routes.js +341 -0
- package/src/lib/ui-scenario-metadata.js +161 -0
- package/vendor/compose/dist/auto-update.cjs +5544 -0
- package/vendor/compose/dist/auto-update.mjs +5518 -0
- package/vendor/compose/dist/capture.cjs +1450 -0
- package/vendor/compose/dist/capture.mjs +1416 -0
- package/vendor/compose/dist/eligibility.cjs +5331 -0
- package/vendor/compose/dist/eligibility.mjs +5313 -0
- package/vendor/compose/dist/index.cjs +2046 -0
- package/vendor/compose/dist/index.mjs +1997 -0
- package/vendor/compose/dist/jsx-dev-runtime.cjs +55 -0
- package/vendor/compose/dist/jsx-dev-runtime.mjs +27 -0
- package/vendor/compose/dist/jsx-runtime.cjs +58 -0
- package/vendor/compose/dist/jsx-runtime.mjs +31 -0
- package/vendor/compose/dist/render.cjs +558 -0
- package/vendor/compose/dist/render.mjs +515 -0
- package/vendor/compose/dist/verify-cli.cjs +3806 -0
- package/vendor/compose/dist/verify-cli.mjs +3812 -0
- package/vendor/compose/dist/verify.cjs +3880 -0
- package/vendor/compose/dist/verify.mjs +3858 -0
- package/web/manager/dist/assets/index-D0S2otug.js +507 -0
- package/web/manager/dist/index.html +1 -1
- package/src/commands/ci-run.js +0 -123
- package/src/commands/ci-setup.js +0 -288
- package/src/commands/ingest.js +0 -458
- package/src/commands/setup.js +0 -137
- package/src/commands/validate-docs.js +0 -529
- package/src/lib/playwright-runner.js +0 -252
- package/web/manager/dist/assets/index--ZgioErz.js +0 -507
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
|
*/
|
|
@@ -54,6 +119,17 @@ async function withRetry(fn, options = {}) {
|
|
|
54
119
|
throw error;
|
|
55
120
|
}
|
|
56
121
|
|
|
122
|
+
// Respect Retry-After header on 429 rate limit responses
|
|
123
|
+
const retryAfterHeader = error.response?.headers?.["retry-after"];
|
|
124
|
+
if (retryAfterHeader && statusCode === 429) {
|
|
125
|
+
const retryMs = (parseInt(retryAfterHeader, 10) || 5) * 1000;
|
|
126
|
+
console.log(
|
|
127
|
+
` ⚠ Rate limited (attempt ${attempt}/${maxRetries}), retrying in ${retryMs / 1000}s...`,
|
|
128
|
+
);
|
|
129
|
+
await sleep(retryMs);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
57
133
|
// Calculate delay with exponential backoff + jitter
|
|
58
134
|
const delay = Math.min(
|
|
59
135
|
initialDelay * Math.pow(2, attempt - 1) + Math.random() * 1000,
|
|
@@ -190,20 +266,7 @@ async function publishAssetsV1(apiKey, metadata, assets) {
|
|
|
190
266
|
});
|
|
191
267
|
return response.data;
|
|
192
268
|
} 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}`);
|
|
269
|
+
throw createApiError("publish_ingest_failure", `${baseUrl}/v1/publish`, error);
|
|
207
270
|
}
|
|
208
271
|
}
|
|
209
272
|
|
|
@@ -586,6 +649,34 @@ async function publishTransactional(apiKey, payload) {
|
|
|
586
649
|
return response.data;
|
|
587
650
|
}
|
|
588
651
|
|
|
652
|
+
/**
|
|
653
|
+
* Batch publish metadata for multiple scenarios in one request
|
|
654
|
+
* @param {string} apiKey - API key for authentication
|
|
655
|
+
* @param {Object} payload - { commits: Array<{ metadata, assets }> }
|
|
656
|
+
* @returns {Promise<Object>}
|
|
657
|
+
*/
|
|
658
|
+
async function publishBatch(apiKey, payload) {
|
|
659
|
+
if (!apiKey) {
|
|
660
|
+
throw new Error("API key is required to publish");
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
return withRetry(
|
|
664
|
+
async () => {
|
|
665
|
+
const response = await axios.post(`${baseUrl}/v1/publish/batch`, payload, {
|
|
666
|
+
headers: {
|
|
667
|
+
"Content-Type": "application/json",
|
|
668
|
+
Authorization: `Bearer ${apiKey}`,
|
|
669
|
+
},
|
|
670
|
+
timeout: 60000,
|
|
671
|
+
maxBodyLength: Infinity,
|
|
672
|
+
maxContentLength: Infinity,
|
|
673
|
+
});
|
|
674
|
+
return response.data;
|
|
675
|
+
},
|
|
676
|
+
{ maxRetries: 2, retryOn: [500, 502, 503, 504, 429, "ECONNRESET", "ETIMEDOUT"] },
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
|
|
589
680
|
/**
|
|
590
681
|
* Check which hashes already exist in storage (for deduplication)
|
|
591
682
|
* @param {string} apiKey - API key for authentication
|
|
@@ -648,7 +739,7 @@ async function getBaselines(projectId, apiKey) {
|
|
|
648
739
|
*/
|
|
649
740
|
async function exportVisuals(projectId, options = {}) {
|
|
650
741
|
const { format = "json", status = "approved" } = options;
|
|
651
|
-
const settings = require("./config").
|
|
742
|
+
const settings = require("./config").readSettings();
|
|
652
743
|
const apiKey = settings?.apiKey;
|
|
653
744
|
|
|
654
745
|
if (!apiKey) {
|
|
@@ -656,15 +747,23 @@ async function exportVisuals(projectId, options = {}) {
|
|
|
656
747
|
}
|
|
657
748
|
|
|
658
749
|
return withRetry(async () => {
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
750
|
+
try {
|
|
751
|
+
const response = await axios.get(
|
|
752
|
+
`${baseUrl}/projects/${projectId}/visuals/export`,
|
|
753
|
+
{
|
|
754
|
+
params: { format, status },
|
|
755
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
756
|
+
timeout: 60000,
|
|
757
|
+
},
|
|
758
|
+
);
|
|
759
|
+
return response.data;
|
|
760
|
+
} catch (error) {
|
|
761
|
+
throw createApiError(
|
|
762
|
+
"export_visuals_failure",
|
|
763
|
+
`${baseUrl}/projects/${projectId}/visuals/export`,
|
|
764
|
+
error,
|
|
765
|
+
);
|
|
766
|
+
}
|
|
668
767
|
});
|
|
669
768
|
}
|
|
670
769
|
|
|
@@ -688,7 +787,7 @@ async function post(endpoint, data, options = {}) {
|
|
|
688
787
|
}
|
|
689
788
|
|
|
690
789
|
/**
|
|
691
|
-
*
|
|
790
|
+
* Initialize ingestion job with manifest
|
|
692
791
|
*/
|
|
693
792
|
async function initIngest(apiKey, projectId, manifest) {
|
|
694
793
|
return withRetry(async () => {
|
|
@@ -705,7 +804,7 @@ async function initIngest(apiKey, projectId, manifest) {
|
|
|
705
804
|
}
|
|
706
805
|
|
|
707
806
|
/**
|
|
708
|
-
*
|
|
807
|
+
* Commit ingestion job after uploads complete
|
|
709
808
|
*/
|
|
710
809
|
async function commitIngest(apiKey, projectId, uploadResults, git, cli) {
|
|
711
810
|
return withRetry(async () => {
|
|
@@ -722,47 +821,56 @@ async function commitIngest(apiKey, projectId, uploadResults, git, cli) {
|
|
|
722
821
|
}
|
|
723
822
|
|
|
724
823
|
/**
|
|
725
|
-
*
|
|
824
|
+
* Get drift records for a project
|
|
726
825
|
*/
|
|
727
826
|
async function getDrifts(apiKey, projectId, options = {}) {
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
827
|
+
const params = new URLSearchParams();
|
|
828
|
+
if (options.status) params.set("status", options.status);
|
|
829
|
+
if (options.journeyKey) params.set("journeyKey", options.journeyKey);
|
|
830
|
+
const endpoint = `${baseUrl}/v1/projects/${projectId}/drifts?${params.toString()}`;
|
|
732
831
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
{
|
|
832
|
+
try {
|
|
833
|
+
return await withRetry(async () => {
|
|
834
|
+
const response = await axios.get(endpoint, {
|
|
736
835
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
737
836
|
timeout: 30000,
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
})
|
|
837
|
+
});
|
|
838
|
+
return response.data;
|
|
839
|
+
});
|
|
840
|
+
} catch (error) {
|
|
841
|
+
// Surface HTTP status + endpoint + server message instead of axios's
|
|
842
|
+
// bare "Request failed with status code N".
|
|
843
|
+
throw createApiError("Drifts request failed", endpoint, error);
|
|
844
|
+
}
|
|
742
845
|
}
|
|
743
846
|
|
|
744
847
|
/**
|
|
745
|
-
*
|
|
848
|
+
* Get sync jobs for a project
|
|
746
849
|
*/
|
|
747
850
|
async function getSyncJobs(apiKey, projectId, options = {}) {
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
851
|
+
const endpoint = `${baseUrl}/v1/projects/${projectId}/sync-jobs`;
|
|
852
|
+
try {
|
|
853
|
+
return await withRetry(async () => {
|
|
854
|
+
const response = await axios.post(
|
|
855
|
+
endpoint,
|
|
856
|
+
{
|
|
857
|
+
limit: options.limit || 10,
|
|
858
|
+
status: options.status,
|
|
859
|
+
},
|
|
860
|
+
{
|
|
861
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
862
|
+
timeout: 30000,
|
|
863
|
+
},
|
|
864
|
+
);
|
|
865
|
+
return response.data;
|
|
866
|
+
});
|
|
867
|
+
} catch (error) {
|
|
868
|
+
throw createApiError("Sync jobs request failed", endpoint, error);
|
|
869
|
+
}
|
|
762
870
|
}
|
|
763
871
|
|
|
764
872
|
/**
|
|
765
|
-
*
|
|
873
|
+
* Perform action on a drift record
|
|
766
874
|
*/
|
|
767
875
|
async function driftAction(apiKey, projectId, driftId, action, options = {}) {
|
|
768
876
|
return withRetry(async () => {
|
|
@@ -794,22 +902,26 @@ module.exports = {
|
|
|
794
902
|
getProjectConfig,
|
|
795
903
|
postChangelogDrafts,
|
|
796
904
|
getApiBaseUrl,
|
|
905
|
+
createApiError,
|
|
797
906
|
syncPushAssets,
|
|
798
907
|
getSyncStatus,
|
|
799
908
|
// New transactional flow
|
|
800
909
|
signAssets,
|
|
801
910
|
uploadToPresignedUrl,
|
|
802
911
|
publishTransactional,
|
|
912
|
+
publishBatch,
|
|
803
913
|
checkExistingHashes,
|
|
804
914
|
// Diffing support
|
|
805
915
|
getBaselines,
|
|
806
916
|
// Export support
|
|
807
917
|
exportVisuals,
|
|
808
|
-
//
|
|
918
|
+
// Reshot
|
|
809
919
|
post,
|
|
810
920
|
initIngest,
|
|
811
921
|
commitIngest,
|
|
812
922
|
getDrifts,
|
|
813
923
|
getSyncJobs,
|
|
814
924
|
driftAction,
|
|
925
|
+
// Testing
|
|
926
|
+
withRetry,
|
|
815
927
|
};
|