@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.
Files changed (81) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +138 -47
  3. package/package.json +27 -16
  4. package/src/commands/auth.js +159 -30
  5. package/src/commands/capture-dom.js +50 -0
  6. package/src/commands/certify.js +62 -0
  7. package/src/commands/compose.js +220 -0
  8. package/src/commands/doctor-release.js +74 -0
  9. package/src/commands/doctor-target.js +108 -0
  10. package/src/commands/drifts.js +16 -69
  11. package/src/commands/import-tests.js +13 -13
  12. package/src/commands/init.js +16 -277
  13. package/src/commands/publish.js +484 -257
  14. package/src/commands/pull.js +302 -35
  15. package/src/commands/refresh.js +166 -0
  16. package/src/commands/run.js +292 -12
  17. package/src/commands/setup-wizard.js +348 -496
  18. package/src/commands/status.js +334 -126
  19. package/src/commands/sync.js +28 -236
  20. package/src/commands/ui.js +1 -1
  21. package/src/commands/variation.js +194 -0
  22. package/src/commands/verify-publish.js +46 -0
  23. package/src/index.js +383 -118
  24. package/src/lib/api-client.js +172 -60
  25. package/src/lib/auto-update/refresh.js +598 -0
  26. package/src/lib/auto-update/scene-runtime.compose.tsx +73 -0
  27. package/src/lib/auto-update/spec.js +89 -0
  28. package/src/lib/capture-engine.js +179 -9
  29. package/src/lib/capture-script-runner.js +639 -214
  30. package/src/lib/certification.js +887 -0
  31. package/src/lib/compose-context.js +156 -0
  32. package/src/lib/compose-pack.js +42 -0
  33. package/src/lib/compose-runtime.js +34 -0
  34. package/src/lib/compose-upload.js +142 -0
  35. package/src/lib/config.js +186 -81
  36. package/src/lib/dom-capture.js +64 -0
  37. package/src/lib/ensure-browser.js +147 -0
  38. package/src/lib/output-path-template.js +3 -3
  39. package/src/lib/record-cdp.js +288 -16
  40. package/src/lib/record-clip.js +83 -3
  41. package/src/lib/record-config.js +1 -5
  42. package/src/lib/release-doctor.js +321 -0
  43. package/src/lib/resolve-targets.js +60 -0
  44. package/src/lib/run-manifest.js +148 -0
  45. package/src/lib/standalone-mode.js +1 -1
  46. package/src/lib/storage-providers.js +5 -5
  47. package/src/lib/style-engine.js +5 -5
  48. package/src/lib/target-contract.js +292 -0
  49. package/src/lib/ui-api-helpers.js +118 -0
  50. package/src/lib/ui-api.js +31 -824
  51. package/src/lib/ui-asset-cleanup.js +62 -0
  52. package/src/lib/ui-output-versions.js +165 -0
  53. package/src/lib/ui-recorder-routes.js +341 -0
  54. package/src/lib/ui-scenario-metadata.js +161 -0
  55. package/vendor/compose/dist/auto-update.cjs +5544 -0
  56. package/vendor/compose/dist/auto-update.mjs +5518 -0
  57. package/vendor/compose/dist/capture.cjs +1450 -0
  58. package/vendor/compose/dist/capture.mjs +1416 -0
  59. package/vendor/compose/dist/eligibility.cjs +5331 -0
  60. package/vendor/compose/dist/eligibility.mjs +5313 -0
  61. package/vendor/compose/dist/index.cjs +2046 -0
  62. package/vendor/compose/dist/index.mjs +1997 -0
  63. package/vendor/compose/dist/jsx-dev-runtime.cjs +55 -0
  64. package/vendor/compose/dist/jsx-dev-runtime.mjs +27 -0
  65. package/vendor/compose/dist/jsx-runtime.cjs +58 -0
  66. package/vendor/compose/dist/jsx-runtime.mjs +31 -0
  67. package/vendor/compose/dist/render.cjs +558 -0
  68. package/vendor/compose/dist/render.mjs +515 -0
  69. package/vendor/compose/dist/verify-cli.cjs +3806 -0
  70. package/vendor/compose/dist/verify-cli.mjs +3812 -0
  71. package/vendor/compose/dist/verify.cjs +3880 -0
  72. package/vendor/compose/dist/verify.mjs +3858 -0
  73. package/web/manager/dist/assets/index-D0S2otug.js +507 -0
  74. package/web/manager/dist/index.html +1 -1
  75. package/src/commands/ci-run.js +0 -123
  76. package/src/commands/ci-setup.js +0 -288
  77. package/src/commands/ingest.js +0 -458
  78. package/src/commands/setup.js +0 -137
  79. package/src/commands/validate-docs.js +0 -529
  80. package/src/lib/playwright-runner.js +0 -252
  81. package/web/manager/dist/assets/index--ZgioErz.js +0 -507
@@ -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 baseUrl =
7
- process.env.RESHOT_API_BASE_URL ||
8
- process.env.DOCSYNC_API_BASE_URL ||
9
- "http://localhost:3000/api";
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
- return baseUrl;
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
- if (error.response) {
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").loadSettings();
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
- const response = await axios.get(
660
- `${baseUrl}/projects/${projectId}/visuals/export`,
661
- {
662
- params: { format, status },
663
- headers: { Authorization: `Bearer ${apiKey}` },
664
- timeout: 60000,
665
- },
666
- );
667
- return response.data;
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
- * DocSync: Initialize ingestion job with manifest
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
- * DocSync: Commit ingestion job after uploads complete
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
- * DocSync: Get drift records for a project
824
+ * Get drift records for a project
726
825
  */
727
826
  async function getDrifts(apiKey, projectId, options = {}) {
728
- return withRetry(async () => {
729
- const params = new URLSearchParams();
730
- if (options.status) params.set("status", options.status);
731
- if (options.journeyKey) params.set("journeyKey", options.journeyKey);
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
- const response = await axios.get(
734
- `${baseUrl}/v1/projects/${projectId}/drifts?${params.toString()}`,
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
- return response.data;
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
- * DocSync: Get sync jobs for a project
848
+ * Get sync jobs for a project
746
849
  */
747
850
  async function getSyncJobs(apiKey, projectId, options = {}) {
748
- return withRetry(async () => {
749
- const response = await axios.post(
750
- `${baseUrl}/v1/projects/${projectId}/sync-jobs`,
751
- {
752
- limit: options.limit || 10,
753
- status: options.status,
754
- },
755
- {
756
- headers: { Authorization: `Bearer ${apiKey}` },
757
- timeout: 30000,
758
- },
759
- );
760
- return response.data;
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
- * DocSync: Perform action on a drift record
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
- // DocSync
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
  };