document360-capture 0.2.2 → 0.2.4
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/dist/annotate/svg.d.ts +28 -1
- package/dist/cli.js +7 -7
- package/package.json +1 -1
package/dist/annotate/svg.d.ts
CHANGED
|
@@ -2,4 +2,31 @@ import type { Bbox } from '../helpers/types.js';
|
|
|
2
2
|
export declare function escapeXml(s: string): string;
|
|
3
3
|
export declare function highlightSvg(bbox: Bbox): string;
|
|
4
4
|
export declare function redactSvg(bbox: Bbox): string;
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Just the numbered pin, placed on the target's top-left corner (clamped into the image). Pins
|
|
7
|
+
* only MARK targets — the explanation lives in the legend band — so they stay small and never
|
|
8
|
+
* cover the surrounding UI, which is what doomed inline captions on busy screens.
|
|
9
|
+
*/
|
|
10
|
+
export declare function pinSvg(label: string, bbox: Bbox, canvasW: number, canvasH: number): string;
|
|
11
|
+
export type LegendItem = {
|
|
12
|
+
label: string;
|
|
13
|
+
text: string;
|
|
14
|
+
};
|
|
15
|
+
export type LegendLayout = {
|
|
16
|
+
/** Y where the band starts (== original image height). */
|
|
17
|
+
top: number;
|
|
18
|
+
width: number;
|
|
19
|
+
/** Vertical padding above the first / below the last entry. */
|
|
20
|
+
pad: number;
|
|
21
|
+
/** Per-entry row height. */
|
|
22
|
+
lineHeight: number;
|
|
23
|
+
};
|
|
24
|
+
/** Height a legend band needs for `n` entries — the canvas is extended by exactly this much. */
|
|
25
|
+
export declare function legendBandHeight(n: number, layout: Pick<LegendLayout, 'pad' | 'lineHeight'>): number;
|
|
26
|
+
/**
|
|
27
|
+
* The legend band: a numbered list in the appended strip below the screenshot. Deterministic,
|
|
28
|
+
* always-safe placement (the band is added space, so nothing on the page is ever covered) — no
|
|
29
|
+
* collision detection or whitespace-hunting required. Each row reuses the pin's numbered chip so
|
|
30
|
+
* the legend entry and its on-image pin read as the same marker.
|
|
31
|
+
*/
|
|
32
|
+
export declare function legendSvg(items: LegendItem[], layout: LegendLayout): string;
|
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{Command as
|
|
3
|
-
`,"utf8")}function
|
|
4
|
-
const { defineConfig } = require(${JSON.stringify(
|
|
2
|
+
import{Command as xe}from"commander";import{createRequire as De}from"node:module";import{input as Ft,confirm as Ht}from"@inquirer/prompts";import{existsSync as H,readFileSync as Jt}from"node:fs";import{basename as Wt,join as qt}from"node:path";import{readFileSync as T,writeFileSync as It,existsSync as E}from"node:fs";import{basename as Ut,join as L}from"node:path";var Ot=".d360-capture.json",D={captureDir:"user-docs/_capture",outputDir:"user-docs/_screenshots",viewport:{width:1440,height:900},loginTimeoutMs:0,annotation:{enabled:!0}},x=class extends Error{};function g(t=process.cwd()){return L(t,Ot)}function d(t){if(!E(t))throw new Error(`No project config at ${t}. Run \`d360-capture init\` to create one.`);let e=JSON.parse(T(t,"utf8").replace(/^/,""));return Tt(e,t)}function C(t,e){It(t,JSON.stringify(e,null,2)+`
|
|
3
|
+
`,"utf8")}function Nt(t){try{return"^"+new URL(t).origin.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}catch{return"."}}function Tt(t,e,o=process.cwd()){Et(t,e);let r=Object.keys(t.profiles),n=t.defaultProfile??(r.length===1?r[0]:r.includes("staging")?"staging":r[0]);return{projectId:t.projectId||Lt(o)||Ut(o),captureDir:t.captureDir??D.captureDir,outputDir:t.outputDir??D.outputDir,viewport:t.viewport??{...D.viewport},loginTimeoutMs:t.loginTimeoutMs??D.loginTimeoutMs,annotation:t.annotation??{...D.annotation},profiles:t.profiles,defaultProfile:n}}function v(t,e){let o=e??t.defaultProfile,r=t.profiles[o];if(!r){let i=Object.keys(t.profiles).join(", ");throw new x(`Unknown profile "${o}". Available: ${i}`)}let n=r.connection.authBoundaryUrlPattern;return{name:o,connection:{startUrl:r.connection.startUrl,authBoundaryUrlPattern:n||Nt(r.connection.startUrl)},captureDir:r.captureDir??t.captureDir,outputDir:r.outputDir??t.outputDir,viewport:r.viewport??t.viewport,production:r.production===!0,authBoundaryDefaulted:!n}}function Et(t,e){if(t.environments&&!t.profiles)throw new x(`Config at ${e} uses the old "environments" shape, which is no longer supported. It is now "profiles" (each with a "connection" { startUrl }) + "defaultProfile". Re-run \`d360-capture init\`, then \`d360-capture auth --profile <name>\`.`);if(!t.profiles||Object.keys(t.profiles).length===0)throw new x(`Invalid config at ${e}: needs at least one profile under "profiles".`);for(let[o,r]of Object.entries(t.profiles))if(!r?.connection?.startUrl)throw new x(`Invalid config at ${e}: profile "${o}" is missing connection.startUrl.`);if(t.defaultProfile&&!t.profiles[t.defaultProfile])throw new x(`Invalid config at ${e}: defaultProfile "${t.defaultProfile}" is not in profiles.`)}function Lt(t){let e=L(t,"package.json");if(!E(e))return null;try{return JSON.parse(T(e,"utf8")).name??null}catch{return null}}import{existsSync as M,readFileSync as Mt}from"node:fs";import{join as Bt}from"node:path";var _t=".d360-writer.json";function B(t=process.cwd()){return Bt(t,_t)}function _(t=process.cwd()){return M(B(t))}function F(t=process.cwd()){let e=B(t);if(!M(e))return{};try{let o=JSON.parse(Mt(e,"utf8").replace(/^/,"")),r=o.docsDir?.replace(/\/+$/,"");return{projectId:o.projectId,captureDir:o.captureDir??(r?`${r}/_capture`:void 0),outputDir:o.outputDir??(r?`${r}/_screenshots`:void 0)}}catch{return{}}}async function J(t={}){let e=process.cwd(),o=g(e),r=t.yes===!0;if(H(o)&&!r&&!await Ht({message:`${o} already exists. Overwrite?`,default:!1})){console.log("Init cancelled.");return}let n=F(e),i=n.projectId||zt(e)||Wt(e),s=[n.projectId&&"project name",n.captureDir&&"directories"].filter(Boolean);s.length>0?(console.log(""),console.log(`Found .d360-writer.json \u2014 inheriting ${s.join(" + ")}.`)):_(e)&&(console.log(""),console.log("Found .d360-writer.json (could not read defaults from it \u2014 using built-in defaults)."));let a=r?"https://example.com/":await Ft({message:"Live app URL (where captures start):",default:"https://example.com/",validate:p=>p.startsWith("http")?!0:"Must be a full URL"}),l={projectId:i,...n.captureDir?{captureDir:n.captureDir}:{},...n.outputDir?{outputDir:n.outputDir}:{},profiles:{staging:{connection:{startUrl:a}}},defaultProfile:"staging"};C(o,l),console.log(""),console.log(`\u2713 Wrote ${o}`),r&&a==="https://example.com/"&&console.log(" \u26A0 Set the staging profile's connection.startUrl to your real app URL."),console.log(""),console.log("Next steps:"),console.log(" 1. d360-capture auth # log in once, no timeout"),console.log(" 2. d360-capture capture # run all specs in the capture dir"),console.log(""),console.log("Add a prod profile or tune the auth boundary later by editing .d360-capture.json."),console.log("")}function zt(t){let e=qt(t,"package.json");if(!H(e))return null;try{return JSON.parse(Jt(e,"utf8")).name??null}catch{return null}}import{chromium as Vt}from"playwright";import{existsSync as W,mkdirSync as Gt,statSync as Xt}from"node:fs";import{homedir as Yt}from"node:os";import{join as S,dirname as Fe}from"node:path";function q(){return S(Yt(),".document360-capture","auth-states")}function b(t,e){return S(q(),t,`${e}.json`)}function z(t){let e=S(q(),t);W(e)||Gt(e,{recursive:!0})}function $(t,e){let o=b(t,e);if(!W(o))return{path:o,exists:!1};let r=Xt(o);return{path:o,exists:!0,ageMs:Date.now()-r.mtimeMs}}async function G(t){let e=d(g()),o=v(e,t.profile);z(e.projectId);let r=b(e.projectId,o.name);console.log(""),o.production&&console.log(`\u26A0 Profile "${o.name}" is PRODUCTION.`),console.log(`Opening Chromium against ${o.connection.startUrl}`),console.log(`Auth-state will save to: ${r}`),console.log("");let n=await Vt.launch({headless:!1,args:["--start-maximized"]}),i=await n.newContext({viewport:null}),s=await i.newPage();await s.goto(o.connection.startUrl,{waitUntil:"domcontentloaded"}),console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),console.log(" Log in fully (MFA included) at your own pace."),console.log(" When you reach the post-login app, come back to this"),console.log(" terminal and press ENTER to save the session."),console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),console.log("");let a=new RegExp(o.connection.authBoundaryUrlPattern),l=setInterval(()=>{a.test(s.url())&&i.storageState({path:r}).catch(()=>{})},3e4);try{for(;;){await Kt();let p=s.url();if(a.test(p))break;console.log(""),console.log("\u2717 Login doesn't look complete yet \u2014 the browser is still at:"),console.log(` ${p}`),console.log(" Finish logging in until you can see the app, then press ENTER again."),console.log(" (Ctrl+C aborts without saving.)")}await i.storageState({path:r}),console.log(""),console.log(`\u2713 Session saved to ${r}`),console.log(" This file contains session cookies \u2014 never commit it to git.")}finally{clearInterval(l),await n.close().catch(()=>{})}}function Kt(){return new Promise(t=>{let e=()=>{process.stdin.off("data",e),process.stdin.pause(),t()};process.stdin.resume(),process.stdin.once("data",e)})}import{existsSync as me}from"node:fs";import{isAbsolute as gt,resolve as dt}from"node:path";import{writeFileSync as X,mkdirSync as Qt,existsSync as Y}from"node:fs";import{createRequire as Zt}from"node:module";import{join as j,isAbsolute as V,resolve as K}from"node:path";var R=Zt(import.meta.url);function Q(t){let{cwd:e,profileName:o,captureDir:r,viewport:n,storageStatePath:i}=t,s=j(e,".d360-capture-cache");Y(s)||Qt(s,{recursive:!0});let a=j(s,`playwright.${o}.config.cjs`),l=V(r)?r:K(e,r),p=V(i)?i:K(e,i),u=R.resolve("@playwright/test"),c=`// Generated by document360-capture \u2014 do not edit by hand.
|
|
4
|
+
const { defineConfig } = require(${JSON.stringify(u)});
|
|
5
5
|
module.exports = defineConfig({
|
|
6
|
-
testDir: ${JSON.stringify(
|
|
6
|
+
testDir: ${JSON.stringify(l)},
|
|
7
7
|
testMatch: '**/*.spec.ts',
|
|
8
8
|
fullyParallel: false,
|
|
9
9
|
workers: 1,
|
|
@@ -18,6 +18,6 @@ module.exports = defineConfig({
|
|
|
18
18
|
contextOptions: { reducedMotion: 'reduce' },
|
|
19
19
|
},
|
|
20
20
|
});
|
|
21
|
-
`;
|
|
22
|
-
[spawn error] ${
|
|
23
|
-
`+
|
|
21
|
+
`;X(a,c,"utf8");let f=j(s,`tsconfig.${o}.json`),y=kt=>kt.replace(/\\/g,"/"),h=R.resolve("document360-capture/helpers"),m=j(R.resolve("@playwright/test/package.json"),".."),w=j(m,"index.mjs"),Rt=Y(w)?w:u,At={compilerOptions:{baseUrl:".",paths:{"document360-capture/helpers":[y(h)],"@playwright/test":[y(Rt)]}}};return X(f,JSON.stringify(At,null,2),"utf8"),{configPath:a,tsconfigPath:f}}import{spawn as te}from"node:child_process";import{existsSync as ee}from"node:fs";import{createRequire as oe}from"node:module";import{fileURLToPath as re}from"node:url";import{delimiter as ne,dirname as A,join as Z}from"node:path";var ie=oe(import.meta.url);async function tt(t){let{cwd:e,configPath:o,tsconfigPath:r,startUrl:n,authBoundaryUrlPattern:i,specId:s,captureDirAbs:a}=t,l=ce();if(!l)return{exitCode:127,stdout:"",stderr:"Cannot locate @playwright/test CLI. Try: npm install -g document360-capture (reinstall)"};let p=[l,"test","--config",o,"--tsconfig",r];return s&&p.push(se(`${s}.spec.ts`)),new Promise(u=>{let c="",f="",y=te(process.execPath,p,{cwd:e,env:{...process.env,CAPTURE_START_URL:n,CAPTURE_AUTH_BOUNDARY:i,NODE_PATH:ae()},shell:!1});y.stdout.on("data",h=>{let m=h.toString();c+=m,process.stdout.write(m)}),y.stderr.on("data",h=>{let m=h.toString();f+=m,process.stderr.write(m)}),y.on("error",h=>{f+=`
|
|
22
|
+
[spawn error] ${h.message}`,u({exitCode:127,stdout:c,stderr:f})}),y.on("close",h=>{u({exitCode:h??1,stdout:c,stderr:f})})})}function se(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function ae(){let t=A(re(import.meta.url));for(let o=0;o<4&&!ee(Z(t,"package.json"));o++)t=A(t);let e=[Z(t,"node_modules"),A(t)];return process.env.NODE_PATH&&e.push(process.env.NODE_PATH),e.join(ne)}function ce(){let t=["@playwright/test/cli","@playwright/test/cli.js","playwright/cli","playwright/cli.js"];for(let e of t)try{return ie.resolve(e)}catch{}return null}var le=[/login\.microsoftonline\.com/i,/login\.live\.com/i,/accounts\.google\.com/i,/\/oauth(2)?\/authorize/i,/\/sign[- ]?in/i,/\bauthn\b/i],pe=[/waitForURL.*timed?[- ]?out/i,/page\.goto.*redirected/i,/storageState.*invalid/i];function et(t){return le.some(e=>e.test(t))?!0:pe.some(e=>e.test(t))}import{readdirSync as lt,readFileSync as fe,existsSync as pt,copyFileSync as ge}from"node:fs";import{join as ut}from"node:path";import U from"sharp";function k(t){return t.replace(/[<>&"']/g,e=>({"<":"<",">":">","&":"&",'"':""","'":"'"})[e])}function rt(t){let o=Math.max(0,t.x-4),r=Math.max(0,t.y-4),n=t.width+8,i=t.height+8;return`<rect x="${o}" y="${r}" width="${n}" height="${i}" rx="6" ry="6" fill="rgba(255,213,0,0.18)" stroke="#f5a623" stroke-width="2.5"/>`}function nt(t){return`<rect x="${t.x}" y="${t.y}" width="${t.width}" height="${t.height}" fill="#1f2328"/>`}var I="-apple-system,Segoe UI,sans-serif",ot=(t,e,o)=>Math.max(e,Math.min(o,t)),ue=13;function it(t,e,o,r){let n=ue,i=ot(e.x,n,Math.max(n,o-n)),s=ot(e.y,n,Math.max(n,r-n));return`<circle cx="${i}" cy="${s}" r="${n}" fill="#d1242f" stroke="#fff" stroke-width="2"/><text x="${i}" y="${s+4}" font-family="${I}" font-size="13" font-weight="700" fill="#fff" text-anchor="middle">${k(t)}</text>`}function st(t,e){return t===0?0:e.pad*2+t*e.lineHeight}function at(t,e){let{top:o,width:r,pad:n,lineHeight:i}=e,s=11,a=20+s,l=[`<line x1="0" y1="${o}" x2="${r}" y2="${o}" stroke="#d0d7de" stroke-width="1"/>`];return t.forEach((p,u)=>{let c=o+n+u*i+i/2;l.push(`<circle cx="${a}" cy="${c}" r="${s}" fill="#d1242f" stroke="#fff" stroke-width="1.5"/>`,`<text x="${a}" y="${c+4}" font-family="${I}" font-size="12" font-weight="700" fill="#fff" text-anchor="middle">${k(p.label)}</text>`,`<text x="${a+s+12}" y="${c+5}" font-family="${I}" font-size="14" fill="#1f2328">${k(p.text)}</text>`)}),l.join("")}async function de(t){let e=JSON.parse(fe(t,"utf8"));if(!pt(e.rawPath))return{id:e.id,ok:!1,reason:`raw missing: ${e.rawPath}`};let o=await U(e.rawPath).metadata(),r=o.width??1440,n=o.height??900,i={pad:16,lineHeight:30},s=st(e.annotations.length,i),a=n+s,l=[];for(let c of e.highlight)l.push(rt(c.bbox));for(let c of e.redact)l.push(nt(c.bbox));for(let c of e.annotations)l.push(it(c.label,c.bbox,r,n));if(s>0&&l.push(`<rect x="0" y="${n}" width="${r}" height="${s}" fill="#ffffff"/>`,at(e.annotations.map(c=>({label:c.label,text:c.text})),{top:n,width:r,...i})),l.length===0)return await U(e.rawPath).toFile(e.finalPath),{id:e.id,ok:!0,finalPath:e.finalPath};let p=`<svg xmlns="http://www.w3.org/2000/svg" width="${r}" height="${a}">${l.join("")}</svg>`,u=U(e.rawPath);return s>0&&(u=u.extend({bottom:s,background:"#ffffff"})),await u.composite([{input:Buffer.from(p),top:0,left:0}]).toFile(e.finalPath),{id:e.id,ok:!0,finalPath:e.finalPath}}async function ct(t){let e=lt(t).filter(r=>r.endsWith(".raw.png")),o=[];for(let r of e){let n=ut(t,r),i=n.replace(/\.raw\.png$/,".png");try{ge(n,i),o.push({id:r.replace(/\.raw\.png$/,""),ok:!0,finalPath:i})}catch(s){o.push({id:r,ok:!1,reason:String(s)})}}return o}async function ft(t,e){if(!pt(t))return[];if(!e)return ct(t);let o=lt(t).filter(n=>n.endsWith(".annotations.json"));if(o.length===0)return ct(t);let r=[];for(let n of o){let i=ut(t,n);try{r.push(await de(i))}catch(s){r.push({id:n,ok:!1,reason:String(s)})}}return r}async function mt(t,e){let o=process.cwd(),r=d(g(o)),n=v(r,e.profile),i=n.name,s=$(r.projectId,i);if(!s.exists)return console.error(""),console.error(`\u2717 No saved session for profile "${i}".`),console.error(` Expected at: ${s.path}`),console.error(` Run: d360-capture auth --profile ${i}`),2;let a=gt(n.captureDir)?n.captureDir:dt(o,n.captureDir);if(!me(a))return console.error(`\u2717 Capture directory missing: ${a}`),console.error(" Generate at least one spec there before running capture."),2;let{configPath:l,tsconfigPath:p}=Q({cwd:o,profileName:i,captureDir:n.captureDir,viewport:n.viewport,storageStatePath:b(r.projectId,i)});console.log(""),n.production&&console.log(`\u26A0 Profile "${i}" is PRODUCTION (read-only capture).`),console.log(`\u25B6 Running specs against ${i} (${n.connection.startUrl})`),console.log(t?` Single spec: ${t}`:` All specs in ${n.captureDir}`),console.log("");let u=await tt({cwd:o,configPath:l,tsconfigPath:p,startUrl:n.connection.startUrl,authBoundaryUrlPattern:n.connection.authBoundaryUrlPattern,specId:t,captureDirAbs:a});if(u.exitCode!==0){let w=u.stdout+`
|
|
23
|
+
`+u.stderr;return console.error(""),console.error(`\u2717 Playwright exited with code ${u.exitCode}`),et(w)&&(console.error(""),console.error(" The output mentions a login page \u2014 if specs that previously passed now"),console.error(" fail this way, your saved session may have expired. Re-auth with:"),console.error(` d360-capture auth --profile ${i}`),console.error(" Otherwise, inspect the failure above (selector timeouts are spec bugs,"),console.error(" not auth bugs) and the error-context file Playwright printed.")),u.exitCode}let c=!e.noAnnotate&&r.annotation.enabled,f=gt(n.outputDir)?n.outputDir:dt(o,n.outputDir);console.log(""),console.log(c?"\u25B6 Annotating screenshots...":"\u25B6 Skipping annotation (--no-annotate or config off)");let y=await ft(f,c),h=y.filter(w=>w.ok).length,m=y.filter(w=>!w.ok);if(console.log(""),console.log(`\u2713 ${h} screenshot${h===1?"":"s"} ready in ${n.outputDir}`),m.length>0){console.log(`\u2717 ${m.length} annotation failure${m.length===1?"":"s"}:`);for(let w of m)console.log(` - ${w.id}: ${w.reason}`)}return m.length===0?0:1}import{existsSync as ht,readdirSync as he,statSync as we}from"node:fs";import{isAbsolute as wt,join as ye,resolve as yt}from"node:path";async function Pt(){let t=process.cwd(),e=d(g(t)),o=wt(e.captureDir)?e.captureDir:yt(t,e.captureDir),r=wt(e.outputDir)?e.outputDir:yt(t,e.outputDir);if(!ht(o)){console.log(`Capture dir does not exist: ${o}`);return}let n=he(o).filter(i=>i.endsWith(".spec.ts")).sort();if(n.length===0){console.log(`No specs found in ${e.captureDir}.`);return}console.log(""),console.log(" Spec ID Last capture"),console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");for(let i of n){let s=i.replace(/\.spec\.ts$/,""),a=ye(r,`${s}.png`),l=ht(a)?new Date(we(a).mtimeMs).toISOString().slice(0,19).replace("T"," "):"(never)";console.log(` ${s.padEnd(47)} ${l}`)}console.log("")}import{existsSync as vt,readFileSync as $t}from"node:fs";import{isAbsolute as Pe,resolve as ve}from"node:path";import{createRequire as $e}from"node:module";var xt=$e(import.meta.url);async function Dt(t){let e=process.cwd(),o=0,r=c=>console.log(` \u2713 ${c}`),n=c=>{console.log(` \u2717 ${c}`),o++};console.log(""),console.log("Checking document360-capture setup..."),console.log("");let i=g(e);if(!vt(i))return n(`Project config missing at ${i} \u2014 run \`d360-capture init\``),2;r(`Project config: ${i}`);let s;try{s=d(i),r(`Config parses; projectId="${s.projectId}"`)}catch(c){return n(`Config invalid: ${c.message}`),2}let a;try{a=v(s,t.profile),r(`Profile "${a.name}" resolved (startUrl=${a.connection.startUrl})${a.production?" \u26A0 PRODUCTION":""}`)}catch(c){return n(`Profile resolve failed: ${c.message}`),1}a.authBoundaryDefaulted&&r(`Auth boundary: ${a.connection.authBoundaryUrlPattern} (auto-derived from start URL; set connection.authBoundaryUrlPattern to override)`);let l=Pe(a.captureDir)?a.captureDir:ve(e,a.captureDir);vt(l)?r(`Capture dir exists: ${a.captureDir}`):n(`Capture dir missing: ${a.captureDir}`);let p=$(s.projectId,a.name);if(p.exists){let c=p.ageMs!=null?Math.floor(p.ageMs/864e5):null;r(`Auth-state present (${c}d old): ${p.path}`);try{let f=JSON.parse($t(p.path,"utf8"));f&&typeof f=="object"?r("Auth-state parses as JSON"):n("Auth-state is not a valid object")}catch(f){n(`Auth-state unreadable: ${f.message}`)}}else n(`Auth-state missing for "${a.name}" \u2014 run \`d360-capture auth --profile ${a.name}\``);try{let c=xt.resolve("playwright/package.json"),f=JSON.parse($t(c,"utf8"));r(`playwright@${f.version} installed`)}catch{n("playwright not resolvable \u2014 reinstall document360-capture")}return["@playwright/test/cli","@playwright/test/cli.js"].some(c=>{try{return xt.resolve(c),!0}catch{return!1}})?r("@playwright/test CLI resolvable"):n("@playwright/test CLI not resolvable \u2014 reinstall document360-capture"),console.log(""),console.log(o===0?"All checks passed.":`${o} check${o===1?"":"s"} failed.`),console.log(""),o===0?0:1}async function bt(){let t=d(g());console.log(""),console.log(`projectId: ${t.projectId}`),console.log(`captureDir: ${t.captureDir} (default)`),console.log(`outputDir: ${t.outputDir} (default)`),console.log(`viewport: ${t.viewport.width}x${t.viewport.height} (default)`),console.log(`annotation enabled: ${t.annotation.enabled}`),console.log(`defaultProfile: ${t.defaultProfile}`),console.log(""),console.log("Profiles:");for(let[e,o]of Object.entries(t.profiles)){let r=$(t.projectId,e),n=r.exists?`auth saved ${r.ageMs!=null?Math.floor(r.ageMs/864e5)+"d ago":""}`:"no auth-state",i=`${e===t.defaultProfile?" \u25CF":""}${o.production?" \u26A0 PRODUCTION":""}`;console.log(` ${e.padEnd(10)} ${o.connection.startUrl}${i}`),o.captureDir&&console.log(` captureDir: ${o.captureDir}`),console.log(` ${n}`)}console.log("")}function jt(){let t=d(g());console.log("");for(let[e,o]of Object.entries(t.profiles)){let r=e===t.defaultProfile?"\u25CF ":" ",n=o.production?" \u26A0 PRODUCTION":"",s=$(t.projectId,e).exists?"auth saved":"no auth-state";console.log(`${r}${e} \u2192 ${o.connection.startUrl}${n} [${s}]`)}console.log(""),console.log("\u25CF = default. Switch with: d360-capture profile use <name>"),console.log("")}function Ct(t){let e=g(),o=d(e);if(!o.profiles[t])return console.error(`\u2717 Unknown profile "${t}". Available: ${Object.keys(o.profiles).join(", ")}`),1;o.defaultProfile=t,C(e,o);let r=o.profiles[t].production?" \u26A0 PRODUCTION":"";return console.log(`\u2713 Default profile is now "${t}"${r}`),0}function St(t){let e=d(g()),o=v(e,t);return console.log(""),console.log(`Profile ${o.name}${o.production?" \u26A0 PRODUCTION":""}`),console.log(` startUrl: ${o.connection.startUrl}`),console.log(` authBoundary:${o.connection.authBoundaryUrlPattern}`),console.log(` captureDir: ${o.captureDir}`),console.log(` outputDir: ${o.outputDir}`),console.log(` viewport: ${o.viewport.width}x${o.viewport.height}`),console.log(""),0}var be=De(import.meta.url),je=be("../package.json"),P=new xe;function O(t){t.env&&(console.error("\u2717 --env was replaced by --profile (connection profiles). Use: --profile <name>"),process.exit(2))}P.name("d360-capture").description("Capture product screenshots from Playwright specs. Login once, capture many.").version(je.version);P.command("init").description("Setup. Inherits from .d360-writer.json when present; asks only the app URL. Writes .d360-capture.json.").option("-y, --yes","Non-interactive: scaffold with defaults + a placeholder URL, no prompts").action(async t=>{await J(t)});P.command("auth").description("Open Chromium for interactive login. Press ENTER when done to save the session.").option("-p, --profile <name>","Profile name (default: defaultProfile from config)").option("-e, --env <name>",!1).action(async t=>{O(t),await G(t)});P.command("capture [spec-id]").description("Run one spec by id, or all specs if omitted.").option("-p, --profile <name>","Profile name (default: defaultProfile from config)").option("-e, --env <name>",!1).option("--no-annotate","Skip annotation overlay; emit raw screenshots only").action(async(t,e)=>{O(e);let o=await mt(t,e);o!==0&&process.exit(o)});P.command("list").description("List discovered specs and their last-capture times.").action(async()=>{await Pt()});P.command("doctor").description("Validate config, auth-state, and browser install.").option("-p, --profile <name>","Profile to check").option("-e, --env <name>",!1).action(async t=>{O(t);let e=await Dt(t);e!==0&&process.exit(e)});P.command("status").description("Print resolved config and auth-state freshness.").action(async()=>{await bt()});var N=P.command("profile").description("Manage capture profiles for the current repo");N.command("list",{isDefault:!0}).description("List profiles (\u25CF = default)").action(()=>jt());N.command("use <name>").description("Set the default profile for this repo").action(t=>process.exit(Ct(t)));N.command("show [name]").description("Print the resolved profile").action(t=>process.exit(St(t)));P.parseAsync(process.argv).catch(t=>{console.error(""),console.error(`\u2717 ${t.message}`),process.exit(1)});
|
package/package.json
CHANGED