document360-capture 0.2.7 → 0.2.9
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/cli.js +10 -7
- package/dist/config/projectConfig.d.ts +5 -3
- package/dist/lib/updateNudge.d.ts +3 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{Command as
|
|
3
|
-
`,"utf8")}function
|
|
2
|
+
import{Command as Ke}from"commander";import{createRequire as Xe}from"node:module";import{input as Vt,confirm as Zt}from"@inquirer/prompts";import{existsSync as Q,readFileSync as te}from"node:fs";import{basename as ee,join as oe}from"node:path";import{readFileSync as W,writeFileSync as Ft,existsSync as q,mkdirSync as Jt}from"node:fs";import{basename as Ht,dirname as Wt,join as U}from"node:path";var qt=U(".d360-writer","capture.json"),j={captureDir:".d360-writer/user-docs/_capture",outputDir:".d360-writer/user-docs/_screenshots",viewport:{width:1440,height:900},loginTimeoutMs:0,annotation:{enabled:!0}},b=class extends Error{};function d(t=process.cwd()){return U(t,qt)}function m(t){if(!q(t))throw new Error(`No project config at ${t}. Run \`d360-capture init\` to create one.`);let e=JSON.parse(W(t,"utf8").replace(/^/,""));return zt(e,t)}function O(t,e){Jt(Wt(t),{recursive:!0}),Ft(t,JSON.stringify(e,null,2)+`
|
|
3
|
+
`,"utf8")}function Gt(t){try{return"^"+new URL(t).origin.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}catch{return"."}}function zt(t,e,o=process.cwd()){Kt(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||Xt(o)||Ht(o),captureDir:t.captureDir??j.captureDir,outputDir:t.outputDir??j.outputDir,viewport:t.viewport??{...j.viewport},loginTimeoutMs:t.loginTimeoutMs??j.loginTimeoutMs,annotation:t.annotation??{...j.annotation},profiles:t.profiles,defaultProfile:n}}function $(t,e){let o=e??t.defaultProfile,r=t.profiles[o];if(!r){let i=Object.keys(t.profiles).join(", ");throw new b(`Unknown profile "${o}". Available: ${i}`)}let n=r.connection.authBoundaryUrlPattern;return{name:o,connection:{startUrl:r.connection.startUrl,authBoundaryUrlPattern:n||Gt(r.connection.startUrl)},captureDir:r.captureDir??t.captureDir,outputDir:r.outputDir??t.outputDir,viewport:r.viewport??t.viewport,production:r.production===!0,scope:r.scope??{},authBoundaryDefaulted:!n}}function Kt(t,e){if(t.environments&&!t.profiles)throw new b(`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 b(`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 b(`Invalid config at ${e}: profile "${o}" is missing connection.startUrl.`);if(t.defaultProfile&&!t.profiles[t.defaultProfile])throw new b(`Invalid config at ${e}: defaultProfile "${t.defaultProfile}" is not in profiles.`)}function Xt(t){let e=U(t,"package.json");if(!q(e))return null;try{return JSON.parse(W(e,"utf8")).name??null}catch{return null}}import{existsSync as G,readFileSync as Yt}from"node:fs";import{join as z}from"node:path";var Qt=z(".d360-writer","config.json");function K(t=process.cwd()){return z(t,Qt)}function X(t=process.cwd()){return G(K(t))}function Y(t=process.cwd()){let e=K(t);if(!G(e))return{};try{let o=JSON.parse(Yt(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 V(t={}){let e=process.cwd(),o=d(e),r=t.yes===!0;if(Q(o)&&!r&&!await Zt({message:`${o} already exists. Overwrite?`,default:!1})){console.log("Init cancelled.");return}let n=Y(e),i=n.projectId||re(e)||ee(e),s=[n.projectId&&"project name",n.captureDir&&"directories"].filter(Boolean);s.length>0?(console.log(""),console.log(`Found .d360-writer/config.json \u2014 inheriting ${s.join(" + ")}.`)):X(e)&&(console.log(""),console.log("Found .d360-writer/config.json (could not read defaults from it \u2014 using built-in defaults)."));let a=r?"https://example.com/":await Vt({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"};O(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-writer/capture.json."),console.log("")}function re(t){let e=oe(t,"package.json");if(!Q(e))return null;try{return JSON.parse(te(e,"utf8")).name??null}catch{return null}}import{chromium as ae}from"playwright";import{existsSync as Z,mkdirSync as ne,statSync as ie}from"node:fs";import{homedir as se}from"node:os";import{join as N,dirname as go}from"node:path";function tt(){return N(se(),".document360-capture","auth-states")}function R(t,e){return N(tt(),t,`${e}.json`)}function et(t){let e=N(tt(),t);Z(e)||ne(e,{recursive:!0})}function x(t,e){let o=R(t,e);if(!Z(o))return{path:o,exists:!1};let r=ie(o);return{path:o,exists:!0,ageMs:Date.now()-r.mtimeMs}}async function ot(t){let e=m(d()),o=$(e,t.profile);et(e.projectId);let r=R(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 ae.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 ce();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 ce(){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 je,readdirSync as Re}from"node:fs";import{isAbsolute as xt,resolve as bt}from"node:path";import{writeFileSync as rt,mkdirSync as le,existsSync as nt}from"node:fs";import{createRequire as pe}from"node:module";import{join as k,isAbsolute as it,resolve as st}from"node:path";var T=pe(import.meta.url);function at(t){let{cwd:e,profileName:o,captureDir:r,viewport:n,storageStatePath:i}=t,s=k(e,".d360-writer","cache");nt(s)||le(s,{recursive:!0});let a=k(s,`playwright.${o}.config.cjs`),l=it(r)?r:st(e,r),p=it(i)?i:st(e,i),f=T.resolve("@playwright/test"),c=`// Generated by document360-capture \u2014 do not edit by hand.
|
|
4
4
|
const { defineConfig } = require(${JSON.stringify(f)});
|
|
5
5
|
module.exports = defineConfig({
|
|
6
6
|
testDir: ${JSON.stringify(l)},
|
|
@@ -18,8 +18,11 @@ module.exports = defineConfig({
|
|
|
18
18
|
contextOptions: { reducedMotion: 'reduce' },
|
|
19
19
|
},
|
|
20
20
|
});
|
|
21
|
-
`;
|
|
22
|
-
[spawn error] ${
|
|
23
|
-
\u23F8 Stopping after the current spec\u2026 (press again to stop now)`))}),
|
|
24
|
-
\u25B6 ${g}`);let I=await
|
|
25
|
-
${I.stderr}`)&&(
|
|
21
|
+
`;rt(a,c,"utf8");let u=k(s,`tsconfig.${o}.json`),P=A=>A.replace(/\\/g,"/"),y=T.resolve("document360-capture/helpers"),w=k(T.resolve("@playwright/test/package.json"),".."),h=k(w,"index.mjs"),S=nt(h)?h:f,C={compilerOptions:{baseUrl:".",paths:{"document360-capture/helpers":[P(y)],"@playwright/test":[P(S)]}}};return rt(u,JSON.stringify(C,null,2),"utf8"),{configPath:a,tsconfigPath:u}}import{spawn as ue}from"node:child_process";import{existsSync as fe}from"node:fs";import{createRequire as ge}from"node:module";import{fileURLToPath as de}from"node:url";import{delimiter as me,dirname as E,join as ct}from"node:path";function he(t,e){return t!==0?"failed":/\b\d+\s+skipped\b/.test(e)&&!/\b\d+\s+passed\b/.test(e)?"skipped":"passed"}var ye=ge(import.meta.url);async function lt(t){let{cwd:e,configPath:o,tsconfigPath:r,startUrl:n,authBoundaryUrlPattern:i,specId:s,captureDirAbs:a}=t,l=ve();if(!l)return{exitCode:127,stdout:"",stderr:"Cannot locate @playwright/test CLI. Try: npm install -g document360-capture (reinstall)",outcome:"failed"};let p=[l,"test","--config",o,"--tsconfig",r];return s&&p.push(we(`${s}.spec.ts`)),new Promise(f=>{let c="",u="",P=ue(process.execPath,p,{cwd:e,env:{...process.env,CAPTURE_START_URL:n,CAPTURE_AUTH_BOUNDARY:i,CAPTURE_SCOPE:JSON.stringify(t.scope??{}),NODE_PATH:Pe()},shell:!1}),y=()=>{try{P.kill()}catch{}};t.signal&&(t.signal.aborted?y():t.signal.addEventListener("abort",y,{once:!0})),P.stdout.on("data",w=>{let h=w.toString();c+=h,process.stdout.write(h)}),P.stderr.on("data",w=>{let h=w.toString();u+=h,process.stderr.write(h)}),P.on("error",w=>{t.signal?.removeEventListener("abort",y),u+=`
|
|
22
|
+
[spawn error] ${w.message}`,f({exitCode:127,stdout:c,stderr:u,outcome:"failed"})}),P.on("close",w=>{t.signal?.removeEventListener("abort",y);let h=w??1;f({exitCode:h,stdout:c,stderr:u,outcome:he(h,c)})})})}function we(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function Pe(){let t=E(de(import.meta.url));for(let o=0;o<4&&!fe(ct(t,"package.json"));o++)t=E(t);let e=[ct(t,"node_modules"),E(t)];return process.env.NODE_PATH&&e.push(process.env.NODE_PATH),e.join(me)}function ve(){let t=["@playwright/test/cli","@playwright/test/cli.js","playwright/cli","playwright/cli.js"];for(let e of t)try{return ye.resolve(e)}catch{}return null}var $e=[/login\.microsoftonline\.com/i,/login\.live\.com/i,/accounts\.google\.com/i,/\/oauth(2)?\/authorize/i,/\/sign[- ]?in/i,/\bauthn\b/i],xe=[/waitForURL.*timed?[- ]?out/i,/page\.goto.*redirected/i,/storageState.*invalid/i];function pt(t){return $e.some(e=>e.test(t))?!0:xe.some(e=>e.test(t))}import{readdirSync as wt,readFileSync as Se,existsSync as Pt,copyFileSync as Ce}from"node:fs";import{join as vt}from"node:path";import B from"sharp";function L(t){return t.replace(/[<>&"']/g,e=>({"<":"<",">":">","&":"&",'"':""","'":"'"})[e])}function ft(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 gt(t){return`<rect x="${t.x}" y="${t.y}" width="${t.width}" height="${t.height}" fill="#1f2328"/>`}var M="-apple-system,Segoe UI,sans-serif",ut=(t,e,o)=>Math.max(e,Math.min(o,t)),be=13;function dt(t,e,o,r){let n=be,i=ut(e.x,n,Math.max(n,o-n)),s=ut(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="${M}" font-size="13" font-weight="700" fill="#fff" text-anchor="middle">${L(t)}</text>`}function mt(t,e){return t===0?0:e.pad*2+t*e.lineHeight}function ht(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,f)=>{let c=o+n+f*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="${M}" font-size="12" font-weight="700" fill="#fff" text-anchor="middle">${L(p.label)}</text>`,`<text x="${a+s+12}" y="${c+5}" font-family="${M}" font-size="14" fill="#1f2328">${L(p.text)}</text>`)}),l.join("")}async function De(t){let e=JSON.parse(Se(t,"utf8"));if(!Pt(e.rawPath))return{id:e.id,ok:!1,reason:`raw missing: ${e.rawPath}`};let o=await B(e.rawPath).metadata(),r=o.width??1440,n=o.height??900,i={pad:16,lineHeight:30},s=mt(e.annotations.length,i),a=n+s,l=[];for(let c of e.highlight)l.push(ft(c.bbox));for(let c of e.redact)l.push(gt(c.bbox));for(let c of e.annotations)l.push(dt(c.label,c.bbox,r,n));if(s>0&&l.push(`<rect x="0" y="${n}" width="${r}" height="${s}" fill="#ffffff"/>`,ht(e.annotations.map(c=>({label:c.label,text:c.text})),{top:n,width:r,...i})),l.length===0)return await B(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>`,f=B(e.rawPath);return s>0&&(f=f.extend({bottom:s,background:"#ffffff"})),await f.composite([{input:Buffer.from(p),top:0,left:0}]).toFile(e.finalPath),{id:e.id,ok:!0,finalPath:e.finalPath}}async function yt(t){let e=wt(t).filter(r=>r.endsWith(".raw.png")),o=[];for(let r of e){let n=vt(t,r),i=n.replace(/\.raw\.png$/,".png");try{Ce(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 $t(t,e){if(!Pt(t))return[];if(!e)return yt(t);let o=wt(t).filter(n=>n.endsWith(".annotations.json"));if(o.length===0)return yt(t);let r=[];for(let n of o){let i=vt(t,n);try{r.push(await De(i))}catch(s){r.push({id:n,ok:!1,reason:String(s)})}}return r}async function St(t,e){let o=process.cwd(),r=m(d(o)),n=$(r,e.profile),i=n.name,s=x(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=xt(n.captureDir)?n.captureDir:bt(o,n.captureDir);if(!je(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}=at({cwd:o,profileName:i,captureDir:n.captureDir,viewport:n.viewport,storageStatePath:R(r.projectId,i)}),f=t?[t]:ke(a);if(f.length===0)return console.error(`\u2717 No .spec.ts files in ${n.captureDir}.`),2;console.log(""),n.production&&console.log(`\u26A0 Profile "${i}" is PRODUCTION (read-only capture).`),console.log(`\u25B6 Running ${f.length} spec${f.length===1?"":"s"} against ${i} (${n.connection.startUrl})`),t||console.log(" Press Esc (or Ctrl+C) in this terminal to stop after the current spec; press again to stop now.");let c=!1,u=null,P=Ae(()=>{c?u?.abort():(c=!0,console.log(`
|
|
23
|
+
\u23F8 Stopping after the current spec\u2026 (press again to stop now)`))}),y=[],w=!1;try{for(let g of f){if(c)break;u=new AbortController,console.log(`
|
|
24
|
+
\u25B6 ${g}`);let I=await lt({cwd:o,configPath:l,tsconfigPath:p,startUrl:n.connection.startUrl,authBoundaryUrlPattern:n.connection.authBoundaryUrlPattern,specId:g,captureDirAbs:a,signal:u.signal,scope:n.scope});if(y.push({id:g,outcome:I.outcome}),I.exitCode!==0&&!w&&pt(`${I.stdout}
|
|
25
|
+
${I.stderr}`)&&(w=!0,console.error(""),console.error(" Output mentions a login page \u2014 if specs that used to pass now fail this way, your"),console.error(` saved session may have expired. Re-auth: d360-capture auth --profile ${i}`),console.error(" (Selector/route timeouts are spec bugs, not auth bugs \u2014 inspect the failure above.)")),u.signal.aborted)break}}finally{P(),u=null}let h=y.filter(g=>g.outcome==="passed").length,S=y.filter(g=>g.outcome==="failed"),C=y.filter(g=>g.outcome==="skipped"),A=f.length-y.length,H=!e.noAnnotate&&r.annotation.enabled,_t=xt(n.outputDir)?n.outputDir:bt(o,n.outputDir);console.log(""),console.log(H?"\u25B6 Annotating screenshots\u2026":"\u25B6 Skipping annotation (--no-annotate or config off)");let D=(await $t(_t,H)).filter(g=>!g.ok);console.log(""),console.log(`\u2713 ${h} captured`+(C.length?` \xB7 \u2298 ${C.length} skipped`:"")+(S.length?` \xB7 \u2717 ${S.length} failed`:"")+(A?` \xB7 ${A} not run (stopped)`:"")+` \u2192 ${n.outputDir}`);for(let g of C)console.log(` \u2298 ${g.id}`);for(let g of S)console.log(` \u2717 ${g.id}`);if(D.length>0){console.log(`\u2717 ${D.length} annotation failure${D.length===1?"":"s"}:`);for(let g of D)console.log(` - ${g.id}: ${g.reason}`)}return S.length===0&&D.length===0?0:1}function ke(t){return Re(t).filter(e=>e.endsWith(".spec.ts")).map(e=>e.slice(0,-8)).sort()}function Ae(t){let e=process.stdin;if(e.isTTY){let r=n=>{let i=n[0];(i===27||i===3||i===113||i===81)&&t()};return e.setRawMode(!0),e.resume(),e.on("data",r),()=>{e.off("data",r);try{e.setRawMode(!1)}catch{}e.pause()}}let o=()=>t();return process.on("SIGINT",o),()=>{process.off("SIGINT",o)}}import{existsSync as Ct,readdirSync as Ie,statSync as Oe}from"node:fs";import{isAbsolute as Dt,join as Ue,resolve as jt}from"node:path";async function Rt(){let t=process.cwd(),e=m(d(t)),o=Dt(e.captureDir)?e.captureDir:jt(t,e.captureDir),r=Dt(e.outputDir)?e.outputDir:jt(t,e.outputDir);if(!Ct(o)){console.log(`Capture dir does not exist: ${o}`);return}let n=Ie(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=Ue(r,`${s}.png`),l=Ct(a)?new Date(Oe(a).mtimeMs).toISOString().slice(0,19).replace("T"," "):"(never)";console.log(` ${s.padEnd(47)} ${l}`)}console.log("")}import{existsSync as kt,readFileSync as At}from"node:fs";import{isAbsolute as Ne,resolve as Te}from"node:path";import{createRequire as Ee}from"node:module";var It=Ee(import.meta.url);async function Ot(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=d(e);if(!kt(i))return n(`Project config missing at ${i} \u2014 run \`d360-capture init\``),2;r(`Project config: ${i}`);let s;try{s=m(i),r(`Config parses; projectId="${s.projectId}"`)}catch(c){return n(`Config invalid: ${c.message}`),2}let a;try{a=$(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=Ne(a.captureDir)?a.captureDir:Te(e,a.captureDir);kt(l)?r(`Capture dir exists: ${a.captureDir}`):n(`Capture dir missing: ${a.captureDir}`);let p=x(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 u=JSON.parse(At(p.path,"utf8"));u&&typeof u=="object"?r("Auth-state parses as JSON"):n("Auth-state is not a valid object")}catch(u){n(`Auth-state unreadable: ${u.message}`)}}else n(`Auth-state missing for "${a.name}" \u2014 run \`d360-capture auth --profile ${a.name}\``);try{let c=It.resolve("playwright/package.json"),u=JSON.parse(At(c,"utf8"));r(`playwright@${u.version} installed`)}catch{n("playwright not resolvable \u2014 reinstall document360-capture")}return["@playwright/test/cli","@playwright/test/cli.js"].some(c=>{try{return It.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 Ut(){let t=m(d());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=x(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 Nt(){let t=m(d());console.log("");for(let[e,o]of Object.entries(t.profiles)){let r=e===t.defaultProfile?"\u25CF ":" ",n=o.production?" \u26A0 PRODUCTION":"",s=x(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 Tt(t){let e=d(),o=m(e);if(!o.profiles[t])return console.error(`\u2717 Unknown profile "${t}". Available: ${Object.keys(o.profiles).join(", ")}`),1;o.defaultProfile=t,O(e,o);let r=o.profiles[t].production?" \u26A0 PRODUCTION":"";return console.log(`\u2713 Default profile is now "${t}"${r}`),0}function Et(t){let e=m(d()),o=$(e,t);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}`);let r=Object.keys(o.scope);return r.length>0&&console.log(` scope: ${r.map(n=>`${n}=${o.scope[n]}`).join(", ")}`),console.log(""),0}import{homedir as Le}from"node:os";import{join as Me,dirname as Be}from"node:path";import{mkdirSync as _e,readFileSync as Fe,writeFileSync as Je}from"node:fs";var Lt="document360-capture",_=Me(Le(),".document360-capture","update-cache.json"),He=10800*1e3;function We(t,e){let o=i=>{let[s="",a=""]=i.trim().replace(/^v/,"").split("-",2),l=s.split(".").map(p=>parseInt(p,10)||0);return{n:[l[0]??0,l[1]??0,l[2]??0],pre:a}},r=o(t),n=o(e);for(let i=0;i<3;i++)if(r.n[i]!==n.n[i])return r.n[i]<n.n[i]?-1:1;return r.pre===n.pre?0:r.pre?n.pre?r.pre<n.pre?-1:1:-1:1}function qe(){try{return JSON.parse(Fe(_,"utf8"))}catch{return null}}function Ge(){let t=(process.argv[1]??"").replace(/\\/g,"/");return/\/_npx\//.test(t)||/\/packages\/capture\//.test(t)?!1:/\/node_modules\/document360-capture\//.test(t)||!/\/packages\//.test(t)}async function ze(){try{let t=new AbortController,e=setTimeout(()=>t.abort(),2500),o=await fetch(`https://registry.npmjs.org/${Lt}/latest`,{signal:t.signal,headers:{accept:"application/json"}});if(clearTimeout(e),!o.ok)return;let r=await o.json(),n=typeof r.version=="string"?r.version:null;if(!n)return;_e(Be(_),{recursive:!0}),Je(_,`${JSON.stringify({checkedAt:new Date().toISOString(),latest:n})}
|
|
26
|
+
`)}catch{}}function Mt(t){try{if(!Ge())return;let e=qe();e?.latest&&We(t,e.latest)<0&&console.error(`
|
|
27
|
+
\u2B06 d360-capture ${t} \u2192 ${e.latest} available \u2014 run: npm i -g ${Lt}@latest
|
|
28
|
+
`),e?.checkedAt&&Date.now()-new Date(e.checkedAt).getTime()<He||ze()}catch{}}var Ye=Xe(import.meta.url),Bt=Ye("../package.json");Mt(Bt.version);var v=new Ke;function F(t){t.env&&(console.error("\u2717 --env was replaced by --profile (connection profiles). Use: --profile <name>"),process.exit(2))}v.name("d360-capture").description("Capture product screenshots from Playwright specs. Login once, capture many.").version(Bt.version);v.command("init").description("Setup. Inherits from .d360-writer/config.json when present; asks only the app URL. Writes .d360-writer/capture.json.").option("-y, --yes","Non-interactive: scaffold with defaults + a placeholder URL, no prompts").action(async t=>{await V(t)});v.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=>{F(t),await ot(t)});v.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)=>{F(e);let o=await St(t,e);o!==0&&process.exit(o)});v.command("list").description("List discovered specs and their last-capture times.").action(async()=>{await Rt()});v.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=>{F(t);let e=await Ot(t);e!==0&&process.exit(e)});v.command("status").description("Print resolved config and auth-state freshness.").action(async()=>{await Ut()});var J=v.command("profile").description("Manage capture profiles for the current repo");J.command("list",{isDefault:!0}).description("List profiles (\u25CF = default)").action(()=>Nt());J.command("use <name>").description("Set the default profile for this repo").action(t=>process.exit(Tt(t)));J.command("show [name]").description("Print the resolved profile").action(t=>process.exit(Et(t)));v.parseAsync(process.argv).catch(t=>{console.error(""),console.error(`\u2717 ${t.message}`),process.exit(1)});
|
|
@@ -82,10 +82,12 @@ export type ResolvedProfile = {
|
|
|
82
82
|
authBoundaryDefaulted: boolean;
|
|
83
83
|
};
|
|
84
84
|
export declare const PROJECT_CONFIG_FILENAME: string;
|
|
85
|
-
/** Filled in for any field a minimal config omits.
|
|
85
|
+
/** Filled in for any field a minimal config omits. Default location lives under .d360-writer/ to
|
|
86
|
+
match the writer's single-footprint layout (the capture dirs derive from the writer's docsDir
|
|
87
|
+
when present; these are the bare fallback). */
|
|
86
88
|
export declare const DEFAULTS: {
|
|
87
|
-
readonly captureDir: "user-docs/_capture";
|
|
88
|
-
readonly outputDir: "user-docs/_screenshots";
|
|
89
|
+
readonly captureDir: ".d360-writer/user-docs/_capture";
|
|
90
|
+
readonly outputDir: ".d360-writer/user-docs/_screenshots";
|
|
89
91
|
readonly viewport: {
|
|
90
92
|
readonly width: 1440;
|
|
91
93
|
readonly height: 900;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** Print a one-line update hint (from the cached answer) when a newer d360-capture is published, then
|
|
2
|
+
kick off a background cache refresh for next time. Best-effort; never blocks or throws. */
|
|
3
|
+
export declare function maybeNudgeCaptureUpdate(currentVersion: string): void;
|
package/package.json
CHANGED