@reshotdev/screenshot 0.0.1-beta.2 → 0.0.1-beta.21

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
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+
3
+ const path = require("path");
4
+ const fs = require("fs-extra");
5
+
6
+ const DEFAULT_FORMATS = ["mp4", "webm", "poster"];
7
+ const VALID_FORMATS = new Set(["mp4", "webm", "poster", "gif"]);
8
+
9
+ async function resolveComposeContext(file, options = {}) {
10
+ const compositionPath = path.resolve(process.cwd(), file);
11
+ if (!(await fs.pathExists(compositionPath))) {
12
+ throw new Error(
13
+ `Composition file not found: ${compositionPath}\n` +
14
+ "Pass the path to a .compose.tsx file.",
15
+ );
16
+ }
17
+
18
+ const slug = options.slug || deriveSlug(compositionPath);
19
+ const compositionDir = path.dirname(compositionPath);
20
+ const metadataPath = resolveMetadataPath(compositionDir, slug);
21
+ const metadata = await readMetadata(metadataPath, slug);
22
+ const capturePath = resolveCapturePath({
23
+ metadata,
24
+ compositionDir,
25
+ slug,
26
+ });
27
+ await assertCaptureExists(capturePath, slug);
28
+
29
+ return {
30
+ compositionPath,
31
+ slug,
32
+ compositionDir,
33
+ metadataPath,
34
+ metadata,
35
+ capturePath,
36
+ };
37
+ }
38
+
39
+ function deriveSlug(filePath) {
40
+ const filename = path.basename(filePath);
41
+ const withoutKnownSuffix = filename.replace(/\.(compose\.)?[cm]?[tj]sx?$/i, "");
42
+ return withoutKnownSuffix || path.basename(filePath, path.extname(filePath));
43
+ }
44
+
45
+ function resolveOutBase(compositionPath, out) {
46
+ return path.resolve(
47
+ process.cwd(),
48
+ out || path.join(path.dirname(compositionPath), `${deriveSlug(compositionPath)}.composed`),
49
+ );
50
+ }
51
+
52
+ function resolveMetadataPath(compositionDir, slug) {
53
+ return path.join(compositionDir, `${slug}.metadata.json`);
54
+ }
55
+
56
+ async function readMetadata(metadataPath, slug) {
57
+ if (!(await fs.pathExists(metadataPath))) {
58
+ throw new Error(
59
+ `Missing metadata file for slug "${slug}": ${metadataPath}\n` +
60
+ `Expected a sibling ${slug}.metadata.json file. Re-record the clip with metadata enabled or pass --slug matching an existing metadata file.`,
61
+ );
62
+ }
63
+
64
+ try {
65
+ const metadata = await fs.readJson(metadataPath);
66
+ if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
67
+ throw new Error("metadata must be a JSON object");
68
+ }
69
+ return metadata;
70
+ } catch (error) {
71
+ const message = error instanceof Error ? error.message : String(error);
72
+ throw new Error(`Could not read metadata file: ${metadataPath}\n${message}`);
73
+ }
74
+ }
75
+
76
+ function resolveCapturePath({ metadata, compositionDir, slug }) {
77
+ const declaredPath =
78
+ metadata.capturePath ||
79
+ metadata.workflowCapturePath ||
80
+ metadata.capture?.path ||
81
+ metadata.source?.capturePath ||
82
+ metadata.source?.path;
83
+
84
+ if (typeof declaredPath === "string" && declaredPath.trim()) {
85
+ return path.resolve(compositionDir, declaredPath);
86
+ }
87
+
88
+ return path.join(compositionDir, `workflow-capture-${slug}.mp4`);
89
+ }
90
+
91
+ async function assertCaptureExists(capturePath, slug) {
92
+ if (await fs.pathExists(capturePath)) {
93
+ return;
94
+ }
95
+
96
+ throw new Error(
97
+ `Missing workflow capture for slug "${slug}": ${capturePath}\n` +
98
+ `Expected workflow-capture-${slug}.mp4 next to the composition, or a capturePath in ${slug}.metadata.json.`,
99
+ );
100
+ }
101
+
102
+ function parseSize(value) {
103
+ const match = /^(\d+)x(\d+)$/i.exec(String(value || "").trim());
104
+ if (!match) {
105
+ throw new Error(`Invalid --size "${value}". Use WIDTHxHEIGHT, for example 1440x900.`);
106
+ }
107
+
108
+ const width = Number(match[1]);
109
+ const height = Number(match[2]);
110
+ if (!Number.isSafeInteger(width) || !Number.isSafeInteger(height) || width <= 0 || height <= 0) {
111
+ throw new Error(`Invalid --size "${value}". Width and height must be positive integers.`);
112
+ }
113
+
114
+ return { width, height };
115
+ }
116
+
117
+ function parseFormats(value, includeGif = false) {
118
+ const rawFormats = value == null || value === "" ? DEFAULT_FORMATS : String(value).split(",");
119
+ const formats = [];
120
+
121
+ for (const rawFormat of rawFormats) {
122
+ const format = rawFormat.trim().toLowerCase();
123
+ if (!format) continue;
124
+ if (!VALID_FORMATS.has(format)) {
125
+ throw new Error(
126
+ `Unknown compose format "${rawFormat}". Supported formats: ${Array.from(VALID_FORMATS).join(", ")}.`,
127
+ );
128
+ }
129
+ if (!formats.includes(format)) {
130
+ formats.push(format);
131
+ }
132
+ }
133
+
134
+ if (includeGif && !formats.includes("gif")) {
135
+ formats.push("gif");
136
+ }
137
+
138
+ if (formats.length === 0) {
139
+ throw new Error(`No compose formats selected. Use --formats=${DEFAULT_FORMATS.join(",")}.`);
140
+ }
141
+
142
+ return formats;
143
+ }
144
+
145
+ module.exports = {
146
+ DEFAULT_FORMATS,
147
+ assertCaptureExists,
148
+ deriveSlug,
149
+ parseFormats,
150
+ parseSize,
151
+ readMetadata,
152
+ resolveCapturePath,
153
+ resolveComposeContext,
154
+ resolveMetadataPath,
155
+ resolveOutBase,
156
+ };
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs-extra");
4
+
5
+ async function packFromExistingOutputs(outBase) {
6
+ const pack = {
7
+ mp4: `${outBase}.mp4`,
8
+ webm: `${outBase}.webm`,
9
+ poster: `${outBase}.webp`,
10
+ };
11
+ const gifPath = `${outBase}.gif`;
12
+ if (await fs.pathExists(gifPath)) {
13
+ pack.gif = gifPath;
14
+ }
15
+ return pack;
16
+ }
17
+
18
+ async function assertUploadPackExists(pack, skipRender) {
19
+ const required = [
20
+ ["rendered_mp4", pack.mp4],
21
+ ["rendered_webm", pack.webm],
22
+ ["rendered_poster", pack.poster],
23
+ ];
24
+
25
+ for (const [field, filePath] of required) {
26
+ if (!filePath || !(await fs.pathExists(filePath))) {
27
+ const rerenderHint = skipRender
28
+ ? " Run `reshot compose <file>` first or remove --skip-render."
29
+ : "";
30
+ throw new Error(`Missing ${field} output: ${filePath || "(not produced)"}.${rerenderHint}`);
31
+ }
32
+ }
33
+
34
+ if (pack.gif && !(await fs.pathExists(pack.gif))) {
35
+ throw new Error(`Missing rendered_gif output: ${pack.gif}.`);
36
+ }
37
+ }
38
+
39
+ module.exports = {
40
+ assertUploadPackExists,
41
+ packFromExistingOutputs,
42
+ };
@@ -0,0 +1,34 @@
1
+ // compose-runtime.js — locate the @reshot/compose build the CLI ships with.
2
+ //
3
+ // The compose engine is vendored (self-contained: motion-core and the pure-JS
4
+ // image deps are inlined; only esbuild + playwright-core stay external and are
5
+ // declared as CLI dependencies). The published package ships vendor/compose/dist;
6
+ // when developing inside the monorepo we fall back to the live package build.
7
+
8
+ const path = require("path");
9
+ const fs = require("fs-extra");
10
+
11
+ let cached;
12
+
13
+ /** Absolute path to the compose dist directory (capture.cjs, render.mjs, …). */
14
+ function composeDistDir() {
15
+ if (cached) return cached;
16
+ const candidates = [
17
+ // Shipped with the published CLI (also present in the standalone repo).
18
+ path.resolve(__dirname, "../../vendor/compose/dist"),
19
+ // Monorepo development: packages/compose/dist.
20
+ path.resolve(__dirname, "../../../../packages/compose/dist"),
21
+ ];
22
+ for (const dir of candidates) {
23
+ if (fs.existsSync(dir)) {
24
+ cached = dir;
25
+ return dir;
26
+ }
27
+ }
28
+ throw new Error(
29
+ "@reshot/compose build not found. Expected vendor/compose/dist " +
30
+ "(shipped with the CLI) or packages/compose/dist (monorepo dev).",
31
+ );
32
+ }
33
+
34
+ module.exports = { composeDistDir };
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs-extra");
4
+ const axios = require("axios");
5
+ const FormData = require("form-data");
6
+ const config = require("./config");
7
+ const apiClient = require("./api-client");
8
+
9
+ function resolveComposeProjectContext({ projectOption, settings }) {
10
+ const resolvedSettings =
11
+ settings !== undefined ? settings : readSettingsSafe();
12
+ const apiKey = process.env.RESHOT_API_KEY || resolvedSettings?.apiKey;
13
+ const projectId =
14
+ projectOption ||
15
+ process.env.RESHOT_PROJECT_ID ||
16
+ resolvedSettings?.projectId;
17
+
18
+ if (!apiKey) {
19
+ throw new Error(
20
+ "No API key found. Set RESHOT_API_KEY or run `reshot auth` to create .reshot/settings.json.",
21
+ );
22
+ }
23
+
24
+ if (!projectId) {
25
+ throw new Error(
26
+ "No project ID found. Pass --project, set RESHOT_PROJECT_ID, or authenticate with `reshot auth`.",
27
+ );
28
+ }
29
+
30
+ return { apiKey, projectId };
31
+ }
32
+
33
+ function readSettingsSafe() {
34
+ try {
35
+ return config.readSettings();
36
+ } catch {
37
+ return null;
38
+ }
39
+ }
40
+
41
+ async function uploadComposition({
42
+ apiBaseUrl,
43
+ apiKey,
44
+ projectId,
45
+ name,
46
+ slug,
47
+ sourceTsx,
48
+ metadataJson,
49
+ pack,
50
+ autoApprove = false,
51
+ httpClient = axios,
52
+ }) {
53
+ const formData = new FormData();
54
+ formData.append("name", name);
55
+ formData.append("slug", slug);
56
+ formData.append("source_tsx", sourceTsx);
57
+ formData.append("metadata_json", metadataJson);
58
+ if (autoApprove) {
59
+ formData.append("auto_approve", "true");
60
+ }
61
+ formData.append("rendered_mp4", fs.createReadStream(pack.mp4));
62
+ formData.append("rendered_webm", fs.createReadStream(pack.webm));
63
+ formData.append("rendered_poster", fs.createReadStream(pack.poster));
64
+ if (pack.gif) {
65
+ formData.append("rendered_gif", fs.createReadStream(pack.gif));
66
+ }
67
+
68
+ const endpoint = `${apiBaseUrl.replace(/\/+$/, "")}/projects/${encodeURIComponent(projectId)}/compositions`;
69
+ const response = await httpClient.post(endpoint, formData, {
70
+ headers: {
71
+ ...formData.getHeaders(),
72
+ Authorization: `Bearer ${apiKey}`,
73
+ },
74
+ timeout: 180000,
75
+ maxBodyLength: Infinity,
76
+ maxContentLength: Infinity,
77
+ });
78
+
79
+ const data = unwrapApiResponse(response.data);
80
+ const attributionWarning = readResponseHeader(
81
+ response.headers,
82
+ "x-reshot-attribution-warning",
83
+ );
84
+
85
+ if (attributionWarning) {
86
+ return { ...data, attributionWarning };
87
+ }
88
+
89
+ return data;
90
+ }
91
+
92
+ function unwrapApiResponse(body) {
93
+ if (body && typeof body === "object" && body.success && body.data !== undefined) {
94
+ return body.data;
95
+ }
96
+ return body;
97
+ }
98
+
99
+ function readResponseHeader(headers, name) {
100
+ if (!headers) return null;
101
+ if (typeof headers.get === "function") {
102
+ return headers.get(name);
103
+ }
104
+ return headers[name] || headers[name.toLowerCase()] || null;
105
+ }
106
+
107
+ function buildDashboardUrl(response, apiBaseUrl, projectId) {
108
+ const platformUrl = apiBaseUrl.replace(/\/api\/?$/, "");
109
+ const dashboardUrl =
110
+ response?.dashboardUrl ||
111
+ response?.composition?.dashboardUrl ||
112
+ response?.render?.dashboardUrl;
113
+ if (dashboardUrl) {
114
+ return dashboardUrl;
115
+ }
116
+
117
+ const compositionId = response?.composition?.id || response?.id;
118
+ const base = `${platformUrl}/app/projects/${encodeURIComponent(projectId)}/compositions`;
119
+ return compositionId ? `${base}/${encodeURIComponent(compositionId)}` : base;
120
+ }
121
+
122
+ function humanizeName(slug) {
123
+ return String(slug)
124
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
125
+ .replace(/[-_]+/g, " ")
126
+ .trim() || slug;
127
+ }
128
+
129
+ function getComposeApiBaseUrl(explicitApiBaseUrl) {
130
+ return explicitApiBaseUrl || apiClient.getApiBaseUrl();
131
+ }
132
+
133
+ module.exports = {
134
+ buildDashboardUrl,
135
+ getComposeApiBaseUrl,
136
+ humanizeName,
137
+ readResponseHeader,
138
+ readSettingsSafe,
139
+ resolveComposeProjectContext,
140
+ unwrapApiResponse,
141
+ uploadComposition,
142
+ };