@reshotdev/screenshot 0.0.1-beta.15 → 0.0.1-beta.17
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/package.json +1 -1
- package/src/commands/auth.js +19 -2
- package/src/commands/publish.js +59 -17
- package/src/commands/pull.js +30 -7
- package/src/index.js +24 -3
package/package.json
CHANGED
package/src/commands/auth.js
CHANGED
|
@@ -150,11 +150,12 @@ async function waitForCompletion(
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
async function verifyApiKey(apiBaseUrl, apiKey, httpClient = axios) {
|
|
153
|
-
await httpClient.get(`${apiBaseUrl}/auth/cli/verify`, {
|
|
153
|
+
const response = await httpClient.get(`${apiBaseUrl}/auth/cli/verify`, {
|
|
154
154
|
headers: {
|
|
155
155
|
Authorization: `Bearer ${apiKey}`,
|
|
156
156
|
},
|
|
157
157
|
});
|
|
158
|
+
return response?.data || null;
|
|
158
159
|
}
|
|
159
160
|
|
|
160
161
|
async function authCommand(options = {}) {
|
|
@@ -172,19 +173,35 @@ async function authCommand(options = {}) {
|
|
|
172
173
|
const timeoutMs = Number(options.timeoutMs || DEFAULT_AUTH_TIMEOUT_MS);
|
|
173
174
|
if (envApiKey && envProjectId) {
|
|
174
175
|
const platformUrl = process.env.RESHOT_PLATFORM_URL || "https://reshot.dev";
|
|
176
|
+
// Best-effort resolve the project name so settings + the setup report
|
|
177
|
+
// don't record `projectName: null` on this non-interactive path.
|
|
178
|
+
let projectName = null;
|
|
179
|
+
try {
|
|
180
|
+
const apiBaseUrl = options.apiBaseUrl || getApiBaseUrl();
|
|
181
|
+
const verified = await verifyApiKeyFn(apiBaseUrl, envApiKey, httpClient);
|
|
182
|
+
// /auth/cli/verify wraps its payload in an { data: … } envelope.
|
|
183
|
+
const payload = verified?.data || verified;
|
|
184
|
+
projectName = payload?.project?.name || null;
|
|
185
|
+
} catch {
|
|
186
|
+
// Verification is best-effort here; keep going without the name.
|
|
187
|
+
}
|
|
175
188
|
writeSettingsFn({
|
|
176
189
|
projectId: envProjectId,
|
|
190
|
+
projectName,
|
|
177
191
|
apiKey: envApiKey,
|
|
178
192
|
platformUrl,
|
|
179
193
|
linkedAt: new Date().toISOString(),
|
|
180
194
|
cliVersion: pkg.version,
|
|
181
195
|
});
|
|
182
196
|
console.log(chalk.green("✔ Authenticated via environment variables"));
|
|
183
|
-
console.log(
|
|
197
|
+
console.log(
|
|
198
|
+
chalk.gray(` Project: ${projectName || envProjectId}`),
|
|
199
|
+
);
|
|
184
200
|
console.log(chalk.gray(` Platform: ${platformUrl}`));
|
|
185
201
|
return {
|
|
186
202
|
mode: "cloud-connected",
|
|
187
203
|
projectId: envProjectId,
|
|
204
|
+
projectName,
|
|
188
205
|
platformUrl,
|
|
189
206
|
};
|
|
190
207
|
}
|
package/src/commands/publish.js
CHANGED
|
@@ -666,6 +666,7 @@ async function publishWithTransactionalFlow(
|
|
|
666
666
|
let successCount = 0;
|
|
667
667
|
let failCount = 0;
|
|
668
668
|
let skippedCount = 0;
|
|
669
|
+
let reviewPendingCount = 0;
|
|
669
670
|
let viewUrl = null;
|
|
670
671
|
|
|
671
672
|
// Flatten all assets with metadata
|
|
@@ -878,7 +879,16 @@ async function publishWithTransactionalFlow(
|
|
|
878
879
|
contentType: file.contentType,
|
|
879
880
|
});
|
|
880
881
|
|
|
881
|
-
|
|
882
|
+
// Label the artifact type so PNG + MHTML-sidecar lines for the same
|
|
883
|
+
// visual don't read as a duplicate-upload bug (audit run-10 F3).
|
|
884
|
+
const artifactLabel = file._isThumbnail
|
|
885
|
+
? "thumbnail"
|
|
886
|
+
: file._isDomScene
|
|
887
|
+
? "dom scene .mhtml"
|
|
888
|
+
: (path.extname(file.path || "").replace(/^\./, "") || "asset");
|
|
889
|
+
console.log(
|
|
890
|
+
chalk.green(` ✔ Uploaded ${file.visualKey} (${artifactLabel})`),
|
|
891
|
+
);
|
|
882
892
|
return { success: true, file, s3Path: urlInfo.path };
|
|
883
893
|
} catch (err) {
|
|
884
894
|
console.log(
|
|
@@ -1008,6 +1018,10 @@ async function publishWithTransactionalFlow(
|
|
|
1008
1018
|
// Unwrap API envelope: response may be { data: { results, ... } } or { results, ... }
|
|
1009
1019
|
const batchResult = rawBatchResult.data || rawBatchResult;
|
|
1010
1020
|
|
|
1021
|
+
// Count only NEWLY-pending (new/changed) captures, so the final summary
|
|
1022
|
+
// doesn't imply a re-publish reset already-live captures (audit F1).
|
|
1023
|
+
reviewPendingCount += batchResult.reviewQueueItems || 0;
|
|
1024
|
+
|
|
1011
1025
|
for (const r of batchResult.results || []) {
|
|
1012
1026
|
if (r.status === "ok") {
|
|
1013
1027
|
const count = r.assetsProcessed || 0;
|
|
@@ -1051,7 +1065,14 @@ async function publishWithTransactionalFlow(
|
|
|
1051
1065
|
}
|
|
1052
1066
|
}
|
|
1053
1067
|
|
|
1054
|
-
return {
|
|
1068
|
+
return {
|
|
1069
|
+
successCount,
|
|
1070
|
+
failCount,
|
|
1071
|
+
skippedCount,
|
|
1072
|
+
reviewPendingCount,
|
|
1073
|
+
viewUrl,
|
|
1074
|
+
failedUploadKeys,
|
|
1075
|
+
};
|
|
1055
1076
|
}
|
|
1056
1077
|
|
|
1057
1078
|
/**
|
|
@@ -1697,6 +1718,7 @@ async function publishCommand(options = {}) {
|
|
|
1697
1718
|
let successCount = 0;
|
|
1698
1719
|
let failCount = 0;
|
|
1699
1720
|
let skippedCount = 0;
|
|
1721
|
+
let reviewPendingCount = 0;
|
|
1700
1722
|
let viewUrl = null;
|
|
1701
1723
|
|
|
1702
1724
|
// Load diff manifests for attaching diff data to screenshot assets
|
|
@@ -1743,6 +1765,7 @@ async function publishCommand(options = {}) {
|
|
|
1743
1765
|
successCount = result.successCount;
|
|
1744
1766
|
failCount = result.failCount;
|
|
1745
1767
|
skippedCount = result.skippedCount || 0;
|
|
1768
|
+
reviewPendingCount = result.reviewPendingCount || 0;
|
|
1746
1769
|
viewUrl = result.viewUrl || null;
|
|
1747
1770
|
} catch (txError) {
|
|
1748
1771
|
// Fall back to legacy flow if transactional fails
|
|
@@ -1809,21 +1832,40 @@ async function publishCommand(options = {}) {
|
|
|
1809
1832
|
}
|
|
1810
1833
|
|
|
1811
1834
|
if (!autoApprove && successCount > 0) {
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1835
|
+
// Only NEW/CHANGED captures await review; unchanged captures dedup to
|
|
1836
|
+
// their existing (often already-approved) version and stay live. Report
|
|
1837
|
+
// the accurate pending count so a re-publish doesn't look like it reset
|
|
1838
|
+
// already-approved work (audit run-10 F1).
|
|
1839
|
+
const pending = reviewPendingCount;
|
|
1840
|
+
const keptLive = Math.max(0, successCount - pending);
|
|
1841
|
+
if (pending > 0) {
|
|
1842
|
+
console.log(
|
|
1843
|
+
chalk.cyan(
|
|
1844
|
+
`\n ℹ ${pending} new/changed visual${pending === 1 ? "" : "s"} awaiting review (PENDING)`,
|
|
1845
|
+
),
|
|
1846
|
+
);
|
|
1847
|
+
if (keptLive > 0) {
|
|
1848
|
+
console.log(
|
|
1849
|
+
chalk.gray(
|
|
1850
|
+
` ${keptLive} unchanged visual${keptLive === 1 ? "" : "s"} stayed live (no re-approval needed).`,
|
|
1851
|
+
),
|
|
1852
|
+
);
|
|
1853
|
+
}
|
|
1854
|
+
console.log(
|
|
1855
|
+
chalk.gray(
|
|
1856
|
+
" To skip review for first-time captures, re-run with `reshot publish --auto-approve`",
|
|
1857
|
+
),
|
|
1858
|
+
);
|
|
1859
|
+
console.log(
|
|
1860
|
+
chalk.gray(" or approve them in the studio link above."),
|
|
1861
|
+
);
|
|
1862
|
+
} else {
|
|
1863
|
+
console.log(
|
|
1864
|
+
chalk.cyan(
|
|
1865
|
+
`\n ℹ No new captures to review — all ${successCount} published visual${successCount === 1 ? "" : "s"} were unchanged and stayed live.`,
|
|
1866
|
+
),
|
|
1867
|
+
);
|
|
1868
|
+
}
|
|
1827
1869
|
}
|
|
1828
1870
|
|
|
1829
1871
|
// Helpful guidance about diff percentages
|
package/src/commands/pull.js
CHANGED
|
@@ -109,6 +109,23 @@ function normalizeAssetMap(assets) {
|
|
|
109
109
|
return { assets: normalized, repairs };
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Pick a real {group, visualKey, context} from a normalized asset map so the
|
|
114
|
+
* printed usage example is copy-pasteable for THIS project (prefers the
|
|
115
|
+
* "default" context). Returns null if the map is empty.
|
|
116
|
+
*/
|
|
117
|
+
function deriveUsageSample(assets) {
|
|
118
|
+
for (const group of Object.keys(assets || {})) {
|
|
119
|
+
for (const visualKey of Object.keys(assets[group] || {})) {
|
|
120
|
+
const contexts = Object.keys(assets[group][visualKey] || {});
|
|
121
|
+
if (contexts.length === 0) continue;
|
|
122
|
+
const context = contexts.includes("default") ? "default" : contexts[0];
|
|
123
|
+
return { group, visualKey, context };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
112
129
|
function validateHostedAssetUrl(urlString) {
|
|
113
130
|
if (!urlString || typeof urlString !== "string") {
|
|
114
131
|
return;
|
|
@@ -456,6 +473,13 @@ async function pullCommand(options = {}) {
|
|
|
456
473
|
console.log(chalk.green(`✓ Generated ${contentType} file: ${outputPath}`));
|
|
457
474
|
console.log(chalk.gray(` ${assetCount} visuals exported\n`));
|
|
458
475
|
|
|
476
|
+
// Derive a REAL key path from the pulled map so the printed usage example
|
|
477
|
+
// is copy-pasteable (was hardcoded to a non-existent `dashboard.mainView`).
|
|
478
|
+
const sample = deriveUsageSample(normalizedAssets);
|
|
479
|
+
const sGroup = sample?.group ?? "dashboard";
|
|
480
|
+
const sVisual = sample?.visualKey ?? "mainView";
|
|
481
|
+
const sCtx = sample?.context ?? "default";
|
|
482
|
+
|
|
459
483
|
// Show format-specific usage instructions
|
|
460
484
|
if (format === "ts") {
|
|
461
485
|
console.log(chalk.blue("━━━ TypeScript Usage ━━━\n"));
|
|
@@ -463,10 +487,9 @@ async function pullCommand(options = {}) {
|
|
|
463
487
|
console.log(chalk.cyan(' import { ReshotVisuals } from "./visuals";\n'));
|
|
464
488
|
console.log(chalk.white("Use with full autocomplete support:\n"));
|
|
465
489
|
console.log(chalk.cyan(" // Single context (flattened)"));
|
|
466
|
-
console.log(chalk.cyan(
|
|
490
|
+
console.log(chalk.cyan(` <img src={ReshotVisuals.${sGroup}.${sVisual}} />\n`));
|
|
467
491
|
console.log(chalk.cyan(" // Multiple contexts (dark mode, locales)"));
|
|
468
|
-
console.log(chalk.cyan(
|
|
469
|
-
console.log(chalk.cyan(" <img src={ReshotVisuals.dashboard.mainView.dark} />\n"));
|
|
492
|
+
console.log(chalk.cyan(` <img src={ReshotVisuals.${sGroup}.${sVisual}.${sCtx}} />\n`));
|
|
470
493
|
if (!full) {
|
|
471
494
|
console.log(chalk.gray(" Tip: Use --full flag to include width, height, and alt text\n"));
|
|
472
495
|
}
|
|
@@ -476,10 +499,10 @@ async function pullCommand(options = {}) {
|
|
|
476
499
|
console.log(chalk.cyan(' import assets from "./reshot-assets.json";\n'));
|
|
477
500
|
console.log(chalk.white("Access assets with dot notation:\n"));
|
|
478
501
|
console.log(chalk.cyan(" <img"));
|
|
479
|
-
console.log(chalk.cyan(
|
|
480
|
-
console.log(chalk.cyan(
|
|
481
|
-
console.log(chalk.cyan(
|
|
482
|
-
console.log(chalk.cyan(
|
|
502
|
+
console.log(chalk.cyan(` src={assets.assets.${sGroup}.${sVisual}.${sCtx}.src}`));
|
|
503
|
+
console.log(chalk.cyan(` width={assets.assets.${sGroup}.${sVisual}.${sCtx}.width}`));
|
|
504
|
+
console.log(chalk.cyan(` height={assets.assets.${sGroup}.${sVisual}.${sCtx}.height}`));
|
|
505
|
+
console.log(chalk.cyan(` alt={assets.assets.${sGroup}.${sVisual}.${sCtx}.alt}`));
|
|
483
506
|
console.log(chalk.cyan(" />\n"));
|
|
484
507
|
} else if (format === "csv") {
|
|
485
508
|
console.log(chalk.blue("━━━ CSV Usage ━━━\n"));
|
package/src/index.js
CHANGED
|
@@ -159,6 +159,7 @@ program
|
|
|
159
159
|
.command("run [target]")
|
|
160
160
|
.description("Execute visual capture scenarios from config")
|
|
161
161
|
.option("-s, --scenarios <keys>", "Comma-separated list of scenario keys")
|
|
162
|
+
.option("--scenario <keys>", "Alias for --scenarios")
|
|
162
163
|
.option("--no-headless", "Run browser in visible mode")
|
|
163
164
|
.option("--variant <json>", "Override variant configuration as JSON")
|
|
164
165
|
.option("--all-variants", "Run all configured variant combinations")
|
|
@@ -431,7 +432,24 @@ program
|
|
|
431
432
|
});
|
|
432
433
|
|
|
433
434
|
// Doctor: Validate target contract and readiness
|
|
434
|
-
const doctor = program
|
|
435
|
+
const doctor = program
|
|
436
|
+
.command("doctor")
|
|
437
|
+
.description("Validate target configuration and readiness")
|
|
438
|
+
.option("-s, --scenarios <keys>", "Comma-separated list of scenario keys")
|
|
439
|
+
.option("--timeout <ms>", "Per-step timeout in milliseconds (default 15000)")
|
|
440
|
+
.option("--json", "Output JSON report")
|
|
441
|
+
.action(async (options) => {
|
|
442
|
+
// Bare `reshot doctor` runs the target readiness audit by default instead
|
|
443
|
+
// of just printing help (audit run-10 F5). Use `doctor release` for the
|
|
444
|
+
// full release gate.
|
|
445
|
+
try {
|
|
446
|
+
const doctorTargetCommand = require("./commands/doctor-target");
|
|
447
|
+
await doctorTargetCommand(options || {});
|
|
448
|
+
} catch (error) {
|
|
449
|
+
console.error(chalk.red("Error:"), error.message);
|
|
450
|
+
process.exit(1);
|
|
451
|
+
}
|
|
452
|
+
});
|
|
435
453
|
|
|
436
454
|
doctor
|
|
437
455
|
.command("target")
|
|
@@ -640,8 +658,11 @@ function resolveRecordClipScenarioKeys(target, options = {}) {
|
|
|
640
658
|
}
|
|
641
659
|
|
|
642
660
|
function resolveScenarioKeysFromTarget(target, options = {}) {
|
|
643
|
-
|
|
644
|
-
|
|
661
|
+
// Accept the singular `--scenario` as an alias for `--scenarios` — an easy
|
|
662
|
+
// trap that otherwise hard-fails the run (audit run-10 F2).
|
|
663
|
+
const scenarioList = options.scenarios || options.scenario;
|
|
664
|
+
if (scenarioList) {
|
|
665
|
+
return scenarioList.split(",").map((value) => value.trim()).filter(Boolean);
|
|
645
666
|
}
|
|
646
667
|
|
|
647
668
|
if (!target) {
|