document360-capture 0.2.0 → 0.2.1
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/LICENSE +10 -16
- package/README.md +1 -1
- package/dist/cli.js +21 -91
- package/dist/commands/init.d.ts +5 -1
- package/dist/config/projectConfig.d.ts +60 -5
- package/dist/config/writerConfigDefaults.d.ts +18 -0
- package/dist/helpers/index.js +1 -3
- package/dist/scripts/postinstall.js +1 -18
- package/package.json +3 -3
- package/dist/annotate/compose.js +0 -65
- package/dist/annotate/compose.js.map +0 -1
- package/dist/annotate/svg.js +0 -42
- package/dist/annotate/svg.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/commands/auth.js +0 -73
- package/dist/commands/auth.js.map +0 -1
- package/dist/commands/capture.js +0 -87
- package/dist/commands/capture.js.map +0 -1
- package/dist/commands/doctor.js +0 -96
- package/dist/commands/doctor.js.map +0 -1
- package/dist/commands/init.js +0 -100
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/list.js +0 -33
- package/dist/commands/list.js.map +0 -1
- package/dist/commands/profile.js +0 -43
- package/dist/commands/profile.js.map +0 -1
- package/dist/commands/status.js +0 -27
- package/dist/commands/status.js.map +0 -1
- package/dist/config/authState.js +0 -27
- package/dist/config/authState.js.map +0 -1
- package/dist/config/projectConfig.js +0 -59
- package/dist/config/projectConfig.js.map +0 -1
- package/dist/config/userConfig.js +0 -24
- package/dist/config/userConfig.js.map +0 -1
- package/dist/helpers/dumpAnnotations.js +0 -39
- package/dist/helpers/dumpAnnotations.js.map +0 -1
- package/dist/helpers/index.js.map +0 -1
- package/dist/helpers/types.js +0 -2
- package/dist/helpers/types.js.map +0 -1
- package/dist/helpers/waitPastLogin.js +0 -7
- package/dist/helpers/waitPastLogin.js.map +0 -1
- package/dist/runner/expiryDetect.js +0 -19
- package/dist/runner/expiryDetect.js.map +0 -1
- package/dist/runner/playwrightConfig.js +0 -71
- package/dist/runner/playwrightConfig.js.map +0 -1
- package/dist/runner/runSpecs.js +0 -88
- package/dist/runner/runSpecs.js.map +0 -1
- package/dist/scripts/postinstall.js.map +0 -1
package/LICENSE
CHANGED
|
@@ -1,21 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
Copyright (c) 2026 Kovai.co. All rights reserved.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This software and its source code are proprietary to Kovai.co and are part
|
|
4
|
+
of the Document360 product family. They are provided solely for use with
|
|
5
|
+
the Document360 platform.
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
in
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
7
|
+
No license is granted to copy, modify, merge, publish, distribute,
|
|
8
|
+
sublicense, sell, or create derivative works of this software, in whole or
|
|
9
|
+
in part, except with the prior written permission of Kovai.co.
|
|
14
10
|
|
|
15
11
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
12
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
13
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
14
|
+
KOVAI.CO BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY ARISING FROM
|
|
15
|
+
THE USE OF THE SOFTWARE.
|
package/README.md
CHANGED
package/dist/cli.js
CHANGED
|
@@ -1,93 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
program
|
|
22
|
-
.name('d360-capture')
|
|
23
|
-
.description('Capture product screenshots from Playwright specs. Login once, capture many.')
|
|
24
|
-
.version(pkg.version);
|
|
25
|
-
program
|
|
26
|
-
.command('init')
|
|
27
|
-
.description('Interactive setup. Writes .d360-capture.json at repo root.')
|
|
28
|
-
.action(async () => {
|
|
29
|
-
await initCommand();
|
|
2
|
+
import{Command as yo}from"commander";import{createRequire as Po}from"node:module";import{input as Lt,confirm as Bt}from"@inquirer/prompts";import{existsSync as _,readFileSync as Mt}from"node:fs";import{basename as _t,join as Ft}from"node:path";import{readFileSync as O,writeFileSync as bt,existsSync as N}from"node:fs";import{basename as Rt,join as T}from"node:path";var At=".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 f(t=process.cwd()){return T(t,At)}function d(t){if(!N(t))throw new Error(`No project config at ${t}. Run \`d360-capture init\` to create one.`);let o=JSON.parse(O(t,"utf8").replace(/^/,""));return Ut(o,t)}function S(t,o){bt(t,JSON.stringify(o,null,2)+`
|
|
3
|
+
`,"utf8")}function It(t){try{return"^"+new URL(t).origin.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}catch{return"."}}function Ut(t,o,e=process.cwd()){kt(t,o);let r=Object.keys(t.profiles),n=t.defaultProfile??(r.length===1?r[0]:r.includes("staging")?"staging":r[0]);return{projectId:t.projectId||Ot(e)||Rt(e),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,o){let e=o??t.defaultProfile,r=t.profiles[e];if(!r){let i=Object.keys(t.profiles).join(", ");throw new x(`Unknown profile "${e}". Available: ${i}`)}let n=r.connection.authBoundaryUrlPattern;return{name:e,connection:{startUrl:r.connection.startUrl,authBoundaryUrlPattern:n||It(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 kt(t,o){if(t.environments&&!t.profiles)throw new x(`Config at ${o} 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 ${o}: needs at least one profile under "profiles".`);for(let[e,r]of Object.entries(t.profiles))if(!r?.connection?.startUrl)throw new x(`Invalid config at ${o}: profile "${e}" is missing connection.startUrl.`);if(t.defaultProfile&&!t.profiles[t.defaultProfile])throw new x(`Invalid config at ${o}: defaultProfile "${t.defaultProfile}" is not in profiles.`)}function Ot(t){let o=T(t,"package.json");if(!N(o))return null;try{return JSON.parse(O(o,"utf8")).name??null}catch{return null}}import{existsSync as E,readFileSync as Nt}from"node:fs";import{join as Tt}from"node:path";var Et=".d360-writer.json";function L(t=process.cwd()){return Tt(t,Et)}function B(t=process.cwd()){return E(L(t))}function M(t=process.cwd()){let o=L(t);if(!E(o))return{};try{let e=JSON.parse(Nt(o,"utf8").replace(/^/,"")),r=e.docsDir?.replace(/\/+$/,"");return{projectId:e.projectId,captureDir:e.captureDir??(r?`${r}/_capture`:void 0),outputDir:e.outputDir??(r?`${r}/_screenshots`:void 0)}}catch{return{}}}async function F(t={}){let o=process.cwd(),e=f(o),r=t.yes===!0;if(_(e)&&!r&&!await Bt({message:`${e} already exists. Overwrite?`,default:!1})){console.log("Init cancelled.");return}let n=M(o),i=n.projectId||Wt(o)||_t(o),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(" + ")}.`)):B(o)&&(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 Lt({message:"Live app URL (where captures start):",default:"https://example.com/",validate:g=>g.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"};S(e,l),console.log(""),console.log(`\u2713 Wrote ${e}`),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 Wt(t){let o=Ft(t,"package.json");if(!_(o))return null;try{return JSON.parse(Mt(o,"utf8")).name??null}catch{return null}}import{chromium as Ht}from"playwright";import{existsSync as W,mkdirSync as Jt,statSync as qt}from"node:fs";import{homedir as zt}from"node:os";import{join as b,dirname as Lo}from"node:path";function J(){return b(zt(),".document360-capture","auth-states")}function C(t,o){return b(J(),t,`${o}.json`)}function q(t){let o=b(J(),t);W(o)||Jt(o,{recursive:!0})}function $(t,o){let e=C(t,o);if(!W(e))return{path:e,exists:!1};let r=qt(e);return{path:e,exists:!0,ageMs:Date.now()-r.mtimeMs}}async function z(t){let o=d(f()),e=v(o,t.profile);q(o.projectId);let r=C(o.projectId,e.name);console.log(""),e.production&&console.log(`\u26A0 Profile "${e.name}" is PRODUCTION.`),console.log(`Opening Chromium against ${e.connection.startUrl}`),console.log(`Auth-state will save to: ${r}`),console.log("");let n=await Ht.launch({headless:!1,args:["--start-maximized"]}),i=await n.newContext({viewport:null}),s=await i.newPage();await s.goto(e.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(e.connection.authBoundaryUrlPattern),l=setInterval(()=>{a.test(s.url())&&i.storageState({path:r}).catch(()=>{})},3e4);try{for(;;){await Gt();let g=s.url();if(a.test(g))break;console.log(""),console.log("\u2717 Login doesn't look complete yet \u2014 the browser is still at:"),console.log(` ${g}`),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 Gt(){return new Promise(t=>{let o=()=>{process.stdin.off("data",o),process.stdin.pause(),t()};process.stdin.resume(),process.stdin.once("data",o)})}import{existsSync as po}from"node:fs";import{isAbsolute as lt,resolve as pt}from"node:path";import{writeFileSync as H,mkdirSync as Xt,existsSync as G}from"node:fs";import{createRequire as Yt}from"node:module";import{join as j,isAbsolute as X,resolve as Y}from"node:path";var R=Yt(import.meta.url);function V(t){let{cwd:o,profileName:e,captureDir:r,viewport:n,storageStatePath:i}=t,s=j(o,".d360-capture-cache");G(s)||Xt(s,{recursive:!0});let a=j(s,`playwright.${e}.config.cjs`),l=X(r)?r:Y(o,r),g=X(i)?i:Y(o,i),u=R.resolve("@playwright/test"),h=`// Generated by document360-capture \u2014 do not edit by hand.
|
|
4
|
+
const { defineConfig } = require(${JSON.stringify(u)});
|
|
5
|
+
module.exports = defineConfig({
|
|
6
|
+
testDir: ${JSON.stringify(l)},
|
|
7
|
+
testMatch: '**/*.spec.ts',
|
|
8
|
+
fullyParallel: false,
|
|
9
|
+
workers: 1,
|
|
10
|
+
retries: 0,
|
|
11
|
+
reporter: [['list']],
|
|
12
|
+
use: {
|
|
13
|
+
headless: false,
|
|
14
|
+
viewport: ${JSON.stringify(n)},
|
|
15
|
+
storageState: ${JSON.stringify(g)},
|
|
16
|
+
actionTimeout: 15000,
|
|
17
|
+
navigationTimeout: 30000,
|
|
18
|
+
contextOptions: { reducedMotion: 'reduce' },
|
|
19
|
+
},
|
|
30
20
|
});
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
.description(
|
|
34
|
-
.option('-p, --profile <name>', 'Profile name (default: defaultProfile from config)')
|
|
35
|
-
.option('-e, --env <name>', false)
|
|
36
|
-
.action(async (opts) => {
|
|
37
|
-
rejectLegacyEnv(opts);
|
|
38
|
-
await authCommand(opts);
|
|
39
|
-
});
|
|
40
|
-
program
|
|
41
|
-
.command('capture [spec-id]')
|
|
42
|
-
.description('Run one spec by id, or all specs if omitted.')
|
|
43
|
-
.option('-p, --profile <name>', 'Profile name (default: defaultProfile from config)')
|
|
44
|
-
.option('-e, --env <name>', false)
|
|
45
|
-
.option('--no-annotate', 'Skip annotation overlay; emit raw screenshots only')
|
|
46
|
-
.action(async (specId, opts) => {
|
|
47
|
-
rejectLegacyEnv(opts);
|
|
48
|
-
const exit = await captureCommand(specId, opts);
|
|
49
|
-
if (exit !== 0)
|
|
50
|
-
process.exit(exit);
|
|
51
|
-
});
|
|
52
|
-
program
|
|
53
|
-
.command('list')
|
|
54
|
-
.description('List discovered specs and their last-capture times.')
|
|
55
|
-
.action(async () => {
|
|
56
|
-
await listCommand();
|
|
57
|
-
});
|
|
58
|
-
program
|
|
59
|
-
.command('doctor')
|
|
60
|
-
.description('Validate config, auth-state, and browser install.')
|
|
61
|
-
.option('-p, --profile <name>', 'Profile to check')
|
|
62
|
-
.option('-e, --env <name>', false)
|
|
63
|
-
.action(async (opts) => {
|
|
64
|
-
rejectLegacyEnv(opts);
|
|
65
|
-
const exit = await doctorCommand(opts);
|
|
66
|
-
if (exit !== 0)
|
|
67
|
-
process.exit(exit);
|
|
68
|
-
});
|
|
69
|
-
program
|
|
70
|
-
.command('status')
|
|
71
|
-
.description('Print resolved config and auth-state freshness.')
|
|
72
|
-
.action(async () => {
|
|
73
|
-
await statusCommand();
|
|
74
|
-
});
|
|
75
|
-
const profileCmd = program.command('profile').description('Manage capture profiles for the current repo');
|
|
76
|
-
profileCmd
|
|
77
|
-
.command('list', { isDefault: true })
|
|
78
|
-
.description('List profiles (● = default)')
|
|
79
|
-
.action(() => profileList());
|
|
80
|
-
profileCmd
|
|
81
|
-
.command('use <name>')
|
|
82
|
-
.description('Set the default profile for this repo')
|
|
83
|
-
.action(name => process.exit(profileUse(name)));
|
|
84
|
-
profileCmd
|
|
85
|
-
.command('show [name]')
|
|
86
|
-
.description('Print the resolved profile')
|
|
87
|
-
.action(name => process.exit(profileShow(name)));
|
|
88
|
-
program.parseAsync(process.argv).catch(err => {
|
|
89
|
-
console.error('');
|
|
90
|
-
console.error(`✗ ${err.message}`);
|
|
91
|
-
process.exit(1);
|
|
92
|
-
});
|
|
93
|
-
//# sourceMappingURL=cli.js.map
|
|
21
|
+
`;H(a,h,"utf8");let c=j(s,`tsconfig.${e}.json`),p=St=>St.replace(/\\/g,"/"),w=R.resolve("document360-capture/helpers"),m=j(R.resolve("@playwright/test/package.json"),".."),y=j(m,"index.mjs"),Ct=G(y)?y:u,jt={compilerOptions:{baseUrl:".",paths:{"document360-capture/helpers":[p(w)],"@playwright/test":[p(Ct)]}}};return H(c,JSON.stringify(jt,null,2),"utf8"),{configPath:a,tsconfigPath:c}}import{spawn as Vt}from"node:child_process";import{existsSync as Kt}from"node:fs";import{createRequire as Qt}from"node:module";import{fileURLToPath as Zt}from"node:url";import{delimiter as to,dirname as A,join as K}from"node:path";var oo=Qt(import.meta.url);async function Q(t){let{cwd:o,configPath:e,tsconfigPath:r,startUrl:n,authBoundaryUrlPattern:i,specId:s,captureDirAbs:a}=t,l=no();if(!l)return{exitCode:127,stdout:"",stderr:"Cannot locate @playwright/test CLI. Try: npm install -g document360-capture (reinstall)"};let g=[l,"test","--config",e,"--tsconfig",r];return s&&g.push(eo(`${s}.spec.ts`)),new Promise(u=>{let h="",c="",p=Vt(process.execPath,g,{cwd:o,env:{...process.env,CAPTURE_START_URL:n,CAPTURE_AUTH_BOUNDARY:i,NODE_PATH:ro()},shell:!1});p.stdout.on("data",w=>{let m=w.toString();h+=m,process.stdout.write(m)}),p.stderr.on("data",w=>{let m=w.toString();c+=m,process.stderr.write(m)}),p.on("error",w=>{c+=`
|
|
22
|
+
[spawn error] ${w.message}`,u({exitCode:127,stdout:h,stderr:c})}),p.on("close",w=>{u({exitCode:w??1,stdout:h,stderr:c})})})}function eo(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function ro(){let t=A(Zt(import.meta.url));for(let e=0;e<4&&!Kt(K(t,"package.json"));e++)t=A(t);let o=[K(t,"node_modules"),A(t)];return process.env.NODE_PATH&&o.push(process.env.NODE_PATH),o.join(to)}function no(){let t=["@playwright/test/cli","@playwright/test/cli.js","playwright/cli","playwright/cli.js"];for(let o of t)try{return oo.resolve(o)}catch{}return null}var io=[/login\.microsoftonline\.com/i,/login\.live\.com/i,/accounts\.google\.com/i,/\/oauth(2)?\/authorize/i,/\/sign[- ]?in/i,/\bauthn\b/i],so=[/waitForURL.*timed?[- ]?out/i,/page\.goto.*redirected/i,/storageState.*invalid/i];function Z(t){return io.some(o=>o.test(t))?!0:so.some(o=>o.test(t))}import{readdirSync as it,readFileSync as ao,existsSync as st,copyFileSync as co}from"node:fs";import{join as at}from"node:path";import I from"sharp";function tt(t){return t.replace(/[<>&"']/g,o=>({"<":"<",">":">","&":"&",'"':""","'":"'"})[o])}function ot(t){let e=Math.max(0,t.x-4),r=Math.max(0,t.y-4),n=t.width+8,i=t.height+8;return`<rect x="${e}" y="${r}" width="${n}" height="${i}" rx="6" ry="6" fill="rgba(255,213,0,0.18)" stroke="#f5a623" stroke-width="2.5"/>`}function et(t){return`<rect x="${t.x}" y="${t.y}" width="${t.width}" height="${t.height}" fill="#1f2328"/>`}function rt(t,o,e,r){let i=e.x+e.width+14+4,s=e.y+e.height/2,a=8,l=13,u=o.length*7.2+a*2,h=l+a*2,c=i+14+6,p=s-h/2;return c+u>r&&(c=e.x-u-14-6,c<0&&(c=e.x,p=e.y-h-6,p<0&&(p=e.y+e.height+6))),[`<circle cx="${i}" cy="${s}" r="14" fill="#d1242f" stroke="#fff" stroke-width="2"/>`,`<text x="${i}" y="${s+5}" font-family="-apple-system,Segoe UI,sans-serif" font-size="14" font-weight="700" fill="#fff" text-anchor="middle">${tt(t)}</text>`,`<rect x="${c}" y="${p}" width="${u}" height="${h}" rx="4" ry="4" fill="#1f2328" stroke="#fff" stroke-width="1.5"/>`,`<text x="${c+a}" y="${p+a+l-2}" font-family="-apple-system,Segoe UI,sans-serif" font-size="${l}" fill="#fff">${tt(o)}</text>`].join("")}async function lo(t){let o=JSON.parse(ao(t,"utf8"));if(!st(o.rawPath))return{id:o.id,ok:!1,reason:`raw missing: ${o.rawPath}`};let e=await I(o.rawPath).metadata(),r=e.width??1440,n=e.height??900,i=[];for(let a of o.highlight)i.push(ot(a.bbox));for(let a of o.redact)i.push(et(a.bbox));for(let a of o.annotations)i.push(rt(a.label,a.text,a.bbox,r));if(i.length===0)return await I(o.rawPath).toFile(o.finalPath),{id:o.id,ok:!0,finalPath:o.finalPath};let s=`<svg xmlns="http://www.w3.org/2000/svg" width="${r}" height="${n}">${i.join("")}</svg>`;return await I(o.rawPath).composite([{input:Buffer.from(s),top:0,left:0}]).toFile(o.finalPath),{id:o.id,ok:!0,finalPath:o.finalPath}}async function nt(t){let o=it(t).filter(r=>r.endsWith(".raw.png")),e=[];for(let r of o){let n=at(t,r),i=n.replace(/\.raw\.png$/,".png");try{co(n,i),e.push({id:r.replace(/\.raw\.png$/,""),ok:!0,finalPath:i})}catch(s){e.push({id:r,ok:!1,reason:String(s)})}}return e}async function ct(t,o){if(!st(t))return[];if(!o)return nt(t);let e=it(t).filter(n=>n.endsWith(".annotations.json"));if(e.length===0)return nt(t);let r=[];for(let n of e){let i=at(t,n);try{r.push(await lo(i))}catch(s){r.push({id:n,ok:!1,reason:String(s)})}}return r}async function ut(t,o){let e=process.cwd(),r=d(f(e)),n=v(r,o.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=lt(n.captureDir)?n.captureDir:pt(e,n.captureDir);if(!po(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:g}=V({cwd:e,profileName:i,captureDir:n.captureDir,viewport:n.viewport,storageStatePath:C(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 Q({cwd:e,configPath:l,tsconfigPath:g,startUrl:n.connection.startUrl,authBoundaryUrlPattern:n.connection.authBoundaryUrlPattern,specId:t,captureDirAbs:a});if(u.exitCode!==0){let y=u.stdout+`
|
|
23
|
+
`+u.stderr;return console.error(""),console.error(`\u2717 Playwright exited with code ${u.exitCode}`),Z(y)&&(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 h=!o.noAnnotate&&r.annotation.enabled,c=lt(n.outputDir)?n.outputDir:pt(e,n.outputDir);console.log(""),console.log(h?"\u25B6 Annotating screenshots...":"\u25B6 Skipping annotation (--no-annotate or config off)");let p=await ct(c,h),w=p.filter(y=>y.ok).length,m=p.filter(y=>!y.ok);if(console.log(""),console.log(`\u2713 ${w} screenshot${w===1?"":"s"} ready in ${n.outputDir}`),m.length>0){console.log(`\u2717 ${m.length} annotation failure${m.length===1?"":"s"}:`);for(let y of m)console.log(` - ${y.id}: ${y.reason}`)}return m.length===0?0:1}import{existsSync as ft,readdirSync as uo,statSync as fo}from"node:fs";import{isAbsolute as gt,join as go,resolve as dt}from"node:path";async function ht(){let t=process.cwd(),o=d(f(t)),e=gt(o.captureDir)?o.captureDir:dt(t,o.captureDir),r=gt(o.outputDir)?o.outputDir:dt(t,o.outputDir);if(!ft(e)){console.log(`Capture dir does not exist: ${e}`);return}let n=uo(e).filter(i=>i.endsWith(".spec.ts")).sort();if(n.length===0){console.log(`No specs found in ${o.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=go(r,`${s}.png`),l=ft(a)?new Date(fo(a).mtimeMs).toISOString().slice(0,19).replace("T"," "):"(never)";console.log(` ${s.padEnd(47)} ${l}`)}console.log("")}import{existsSync as mt,readFileSync as wt}from"node:fs";import{isAbsolute as ho,resolve as mo}from"node:path";import{createRequire as wo}from"node:module";var yt=wo(import.meta.url);async function Pt(t){let o=process.cwd(),e=0,r=c=>console.log(` \u2713 ${c}`),n=c=>console.log(` \u26A0 ${c}`),i=c=>{console.log(` \u2717 ${c}`),e++};console.log(""),console.log("Checking document360-capture setup..."),console.log("");let s=f(o);if(!mt(s))return i(`Project config missing at ${s} \u2014 run \`d360-capture init\``),2;r(`Project config: ${s}`);let a;try{a=d(s),r(`Config parses; projectId="${a.projectId}"`)}catch(c){return i(`Config invalid: ${c.message}`),2}let l;try{l=v(a,t.profile),r(`Profile "${l.name}" resolved (startUrl=${l.connection.startUrl})${l.production?" \u26A0 PRODUCTION":""}`)}catch(c){return i(`Profile resolve failed: ${c.message}`),1}l.authBoundaryDefaulted&&n(`Auth boundary not set \u2014 using host heuristic (${l.connection.authBoundaryUrlPattern}). If auth save/validation misbehaves, set connection.authBoundaryUrlPattern.`);let g=ho(l.captureDir)?l.captureDir:mo(o,l.captureDir);mt(g)?r(`Capture dir exists: ${l.captureDir}`):i(`Capture dir missing: ${l.captureDir}`);let u=$(a.projectId,l.name);if(u.exists){let c=u.ageMs!=null?Math.floor(u.ageMs/864e5):null;r(`Auth-state present (${c}d old): ${u.path}`);try{let p=JSON.parse(wt(u.path,"utf8"));p&&typeof p=="object"?r("Auth-state parses as JSON"):i("Auth-state is not a valid object")}catch(p){i(`Auth-state unreadable: ${p.message}`)}}else i(`Auth-state missing for "${l.name}" \u2014 run \`d360-capture auth --profile ${l.name}\``);try{let c=yt.resolve("playwright/package.json"),p=JSON.parse(wt(c,"utf8"));r(`playwright@${p.version} installed`)}catch{i("playwright not resolvable \u2014 reinstall document360-capture")}return["@playwright/test/cli","@playwright/test/cli.js"].some(c=>{try{return yt.resolve(c),!0}catch{return!1}})?r("@playwright/test CLI resolvable"):i("@playwright/test CLI not resolvable \u2014 reinstall document360-capture"),console.log(""),console.log(e===0?"All checks passed.":`${e} check${e===1?"":"s"} failed.`),console.log(""),e===0?0:1}async function vt(){let t=d(f());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[o,e]of Object.entries(t.profiles)){let r=$(t.projectId,o),n=r.exists?`auth saved ${r.ageMs!=null?Math.floor(r.ageMs/864e5)+"d ago":""}`:"no auth-state",i=`${o===t.defaultProfile?" \u25CF":""}${e.production?" \u26A0 PRODUCTION":""}`;console.log(` ${o.padEnd(10)} ${e.connection.startUrl}${i}`),e.captureDir&&console.log(` captureDir: ${e.captureDir}`),console.log(` ${n}`)}console.log("")}function $t(){let t=d(f());console.log("");for(let[o,e]of Object.entries(t.profiles)){let r=o===t.defaultProfile?"\u25CF ":" ",n=e.production?" \u26A0 PRODUCTION":"",s=$(t.projectId,o).exists?"auth saved":"no auth-state";console.log(`${r}${o} \u2192 ${e.connection.startUrl}${n} [${s}]`)}console.log(""),console.log("\u25CF = default. Switch with: d360-capture profile use <name>"),console.log("")}function xt(t){let o=f(),e=d(o);if(!e.profiles[t])return console.error(`\u2717 Unknown profile "${t}". Available: ${Object.keys(e.profiles).join(", ")}`),1;e.defaultProfile=t,S(o,e);let r=e.profiles[t].production?" \u26A0 PRODUCTION":"";return console.log(`\u2713 Default profile is now "${t}"${r}`),0}function Dt(t){let o=d(f()),e=v(o,t);return console.log(""),console.log(`Profile ${e.name}${e.production?" \u26A0 PRODUCTION":""}`),console.log(` startUrl: ${e.connection.startUrl}`),console.log(` authBoundary:${e.connection.authBoundaryUrlPattern}`),console.log(` captureDir: ${e.captureDir}`),console.log(` outputDir: ${e.outputDir}`),console.log(` viewport: ${e.viewport.width}x${e.viewport.height}`),console.log(""),0}var vo=Po(import.meta.url),$o=vo("../package.json"),P=new yo;function U(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($o.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 F(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=>{U(t),await z(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,o)=>{U(o);let e=await ut(t,o);e!==0&&process.exit(e)});P.command("list").description("List discovered specs and their last-capture times.").action(async()=>{await ht()});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=>{U(t);let o=await Pt(t);o!==0&&process.exit(o)});P.command("status").description("Print resolved config and auth-state freshness.").action(async()=>{await vt()});var k=P.command("profile").description("Manage capture profiles for the current repo");k.command("list",{isDefault:!0}).description("List profiles (\u25CF = default)").action(()=>$t());k.command("use <name>").description("Set the default profile for this repo").action(t=>process.exit(xt(t)));k.command("show [name]").description("Print the resolved profile").action(t=>process.exit(Dt(t)));P.parseAsync(process.argv).catch(t=>{console.error(""),console.error(`\u2717 ${t.message}`),process.exit(1)});
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
/** How to reach a target deployment for capture.
|
|
1
|
+
/** How to reach a target deployment for capture. On disk, only `startUrl` is required —
|
|
2
|
+
`authBoundaryUrlPattern` is filled by resolveProfile (host heuristic) when omitted. */
|
|
2
3
|
export type ProfileConnection = {
|
|
3
4
|
startUrl: string;
|
|
4
|
-
authBoundaryUrlPattern
|
|
5
|
+
authBoundaryUrlPattern?: string;
|
|
5
6
|
};
|
|
6
7
|
/** A named capture profile. `connection` is one aspect; a profile may also override
|
|
7
8
|
the directories/viewport so one repo can target multiple doc-sets/environments. */
|
|
@@ -16,6 +17,27 @@ export type Profile = {
|
|
|
16
17
|
/** Visible warning only — capture is read-only (screenshots), so no hard guard. */
|
|
17
18
|
production?: boolean;
|
|
18
19
|
};
|
|
20
|
+
/**
|
|
21
|
+
* The on-disk shape: a minimal `.d360-capture.json` need only carry a `profiles` map
|
|
22
|
+
* whose profiles each have a `connection.startUrl`. Everything else is filled at load
|
|
23
|
+
* time (see `DEFAULTS` + `normalizeProjectConfig`), so a 3-line config is valid.
|
|
24
|
+
*/
|
|
25
|
+
export type RawProjectConfig = {
|
|
26
|
+
projectId?: string;
|
|
27
|
+
captureDir?: string;
|
|
28
|
+
outputDir?: string;
|
|
29
|
+
viewport?: {
|
|
30
|
+
width: number;
|
|
31
|
+
height: number;
|
|
32
|
+
};
|
|
33
|
+
loginTimeoutMs?: number;
|
|
34
|
+
annotation?: {
|
|
35
|
+
enabled: boolean;
|
|
36
|
+
};
|
|
37
|
+
profiles: Record<string, Profile>;
|
|
38
|
+
defaultProfile?: string;
|
|
39
|
+
};
|
|
40
|
+
/** A fully-resolved config: every default materialized. What every command sees. */
|
|
19
41
|
export type ProjectConfig = {
|
|
20
42
|
projectId: string;
|
|
21
43
|
/** Defaults inherited by any profile that doesn't override them. */
|
|
@@ -32,10 +54,15 @@ export type ProjectConfig = {
|
|
|
32
54
|
profiles: Record<string, Profile>;
|
|
33
55
|
defaultProfile: string;
|
|
34
56
|
};
|
|
35
|
-
/** A profile with
|
|
57
|
+
/** A profile connection with the auth boundary resolved (always a string). */
|
|
58
|
+
export type ResolvedConnection = {
|
|
59
|
+
startUrl: string;
|
|
60
|
+
authBoundaryUrlPattern: string;
|
|
61
|
+
};
|
|
62
|
+
/** A profile with directory/viewport defaults and the auth boundary already merged in. */
|
|
36
63
|
export type ResolvedProfile = {
|
|
37
64
|
name: string;
|
|
38
|
-
connection:
|
|
65
|
+
connection: ResolvedConnection;
|
|
39
66
|
captureDir: string;
|
|
40
67
|
outputDir: string;
|
|
41
68
|
viewport: {
|
|
@@ -43,11 +70,39 @@ export type ResolvedProfile = {
|
|
|
43
70
|
height: number;
|
|
44
71
|
};
|
|
45
72
|
production: boolean;
|
|
73
|
+
/** True when the auth boundary was derived (host heuristic) rather than configured —
|
|
74
|
+
doctor surfaces this so a too-loose/tight pattern is refinable before it bites. */
|
|
75
|
+
authBoundaryDefaulted: boolean;
|
|
46
76
|
};
|
|
47
77
|
export declare const PROJECT_CONFIG_FILENAME = ".d360-capture.json";
|
|
78
|
+
/** Filled in for any field a minimal config omits. */
|
|
79
|
+
export declare const DEFAULTS: {
|
|
80
|
+
readonly captureDir: "user-docs/_capture";
|
|
81
|
+
readonly outputDir: "user-docs/_screenshots";
|
|
82
|
+
readonly viewport: {
|
|
83
|
+
readonly width: 1440;
|
|
84
|
+
readonly height: 900;
|
|
85
|
+
};
|
|
86
|
+
readonly loginTimeoutMs: 0;
|
|
87
|
+
readonly annotation: {
|
|
88
|
+
readonly enabled: true;
|
|
89
|
+
};
|
|
90
|
+
};
|
|
48
91
|
export declare class ProfileConfigError extends Error {
|
|
49
92
|
}
|
|
50
93
|
export declare function findProjectConfigPath(cwd?: string): string;
|
|
51
94
|
export declare function readProjectConfig(path: string): ProjectConfig;
|
|
52
|
-
export declare function writeProjectConfig(path: string, cfg:
|
|
95
|
+
export declare function writeProjectConfig(path: string, cfg: RawProjectConfig): void;
|
|
96
|
+
/**
|
|
97
|
+
* Derive a permissive post-login auth boundary from the profile's start URL: any URL on the
|
|
98
|
+
* same origin. The login/IdP flow legitimately leaves that origin (SSO redirects) and returns
|
|
99
|
+
* to it once authenticated, so "back on the app origin" is a reasonable logged-in signal —
|
|
100
|
+
* good enough as a default, refinable when it isn't. Falls back to match-anything if the URL
|
|
101
|
+
* can't be parsed.
|
|
102
|
+
*/
|
|
103
|
+
export declare function deriveAuthBoundary(startUrl: string): string;
|
|
104
|
+
/** Fill every optional field from DEFAULTS / the cwd, then validate what can't be defaulted. */
|
|
105
|
+
export declare function normalizeProjectConfig(raw: RawProjectConfig & {
|
|
106
|
+
environments?: unknown;
|
|
107
|
+
}, path: string, cwd?: string): ProjectConfig;
|
|
53
108
|
export declare function resolveProfile(cfg: ProjectConfig, name?: string): ResolvedProfile;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* d360-capture rides on d360-writer's setup: when a repo already has `.d360-writer.json`,
|
|
3
|
+
* `init` inherits its project label + doc directories so the only thing left to ask is the
|
|
4
|
+
* live app URL. This reads that file as PLAIN JSON — never an engine import (the packages are
|
|
5
|
+
* deliberately decoupled; this is file I/O, not a code dependency). Tolerant by design:
|
|
6
|
+
* any parse/shape problem yields {} and init falls back to its own defaults.
|
|
7
|
+
*
|
|
8
|
+
* Field overlap is intentional — `.d360-writer.json` already carries `projectId`
|
|
9
|
+
* (its doc comment: "scopes sessions/screenshots"), `captureDir`, `outputDir`, and `docsDir`.
|
|
10
|
+
*/
|
|
11
|
+
export type WriterConfigDefaults = {
|
|
12
|
+
projectId?: string;
|
|
13
|
+
captureDir?: string;
|
|
14
|
+
outputDir?: string;
|
|
15
|
+
};
|
|
16
|
+
export declare function writerConfigPath(cwd?: string): string;
|
|
17
|
+
export declare function hasWriterConfig(cwd?: string): boolean;
|
|
18
|
+
export declare function readWriterConfigDefaults(cwd?: string): WriterConfigDefaults;
|
package/dist/helpers/index.js
CHANGED
|
@@ -1,3 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { dumpAnnotations } from './dumpAnnotations.js';
|
|
3
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
async function m(e,t,n=0){if(t.test(e.url()))return;let a=n>0?n:6e5;await e.waitForURL(t,{timeout:a})}import{writeFileSync as u,mkdirSync as g}from"node:fs";import{dirname as p}from"node:path";async function r(e,t){try{let n=e.locator(t).first();return await n.waitFor({state:"visible",timeout:2e3}),await n.boundingBox()}catch{return null}}async function h(e,t){let n=t.saveTo,a=n.replace(/\.raw\.png$/,".png"),s=[];for(let o of t.highlight??[]){let i=await r(e,o);i&&s.push({selector:o,bbox:i})}let l=[];for(let o of t.annotations??[]){let i=await r(e,o.target);i&&l.push({label:o.label,text:o.text,selector:o.target,bbox:i})}let c=[];for(let o of t.redact??[]){let i=await r(e,o);i&&c.push({selector:o,bbox:i})}let d={id:t.id,rawPath:n,finalPath:a,highlight:s,annotations:l,redact:c},f=n.replace(/\.raw\.png$/,".annotations.json");g(p(f),{recursive:!0}),u(f,JSON.stringify(d,null,2),"utf8")}export{h as dumpAnnotations,m as waitPastLogin};
|
|
@@ -1,18 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { createRequire } from 'node:module';
|
|
3
|
-
if (process.env.npm_config_global !== 'true' && process.env.D360_CAPTURE_FORCE_POSTINSTALL !== 'true') {
|
|
4
|
-
process.exit(0);
|
|
5
|
-
}
|
|
6
|
-
const require = createRequire(import.meta.url);
|
|
7
|
-
let cli;
|
|
8
|
-
try {
|
|
9
|
-
cli = require.resolve('playwright/cli.js');
|
|
10
|
-
}
|
|
11
|
-
catch {
|
|
12
|
-
console.warn('[document360-capture] playwright not found; skipping chromium download');
|
|
13
|
-
process.exit(0);
|
|
14
|
-
}
|
|
15
|
-
console.log('[document360-capture] downloading Chromium (one-time)...');
|
|
16
|
-
const child = spawn(process.execPath, [cli, 'install', 'chromium'], { stdio: 'inherit' });
|
|
17
|
-
child.on('exit', code => process.exit(code ?? 0));
|
|
18
|
-
//# sourceMappingURL=postinstall.js.map
|
|
1
|
+
import{spawn as r}from"node:child_process";import{createRequire as t}from"node:module";process.env.npm_config_global!=="true"&&process.env.D360_CAPTURE_FORCE_POSTINSTALL!=="true"&&process.exit(0);var i=t(import.meta.url),e;try{e=i.resolve("playwright/cli.js")}catch{console.warn("[document360-capture] playwright not found; skipping chromium download"),process.exit(0)}console.log("[document360-capture] downloading Chromium (one-time)...");var c=r(process.execPath,[e,"install","chromium"],{stdio:"inherit"});c.on("exit",o=>process.exit(o??0));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "document360-capture",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "CLI that captures product screenshots from Playwright specs. Login once, capture many. Companion to document360-writer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"LICENSE"
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
|
-
"build": "tsc -b",
|
|
20
|
+
"build": "rimraf dist tsconfig.tsbuildinfo && tsc -b && node ../../scripts/bundle.mjs",
|
|
21
21
|
"clean": "rimraf dist",
|
|
22
22
|
"postinstall": "node -e \"import('./dist/scripts/postinstall.js').catch(() => {})\"",
|
|
23
23
|
"prepublishOnly": "npm run build",
|
|
@@ -56,5 +56,5 @@
|
|
|
56
56
|
"cli",
|
|
57
57
|
"document360"
|
|
58
58
|
],
|
|
59
|
-
"license": "
|
|
59
|
+
"license": "SEE LICENSE IN LICENSE"
|
|
60
60
|
}
|
package/dist/annotate/compose.js
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { readdirSync, readFileSync, existsSync, copyFileSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import sharp from 'sharp';
|
|
4
|
-
import { highlightSvg, redactSvg, annotationSvg } from './svg.js';
|
|
5
|
-
async function annotateOne(jsonPath) {
|
|
6
|
-
const data = JSON.parse(readFileSync(jsonPath, 'utf8'));
|
|
7
|
-
if (!existsSync(data.rawPath))
|
|
8
|
-
return { id: data.id, ok: false, reason: `raw missing: ${data.rawPath}` };
|
|
9
|
-
const meta = await sharp(data.rawPath).metadata();
|
|
10
|
-
const width = meta.width ?? 1440;
|
|
11
|
-
const height = meta.height ?? 900;
|
|
12
|
-
const parts = [];
|
|
13
|
-
for (const h of data.highlight)
|
|
14
|
-
parts.push(highlightSvg(h.bbox));
|
|
15
|
-
for (const r of data.redact)
|
|
16
|
-
parts.push(redactSvg(r.bbox));
|
|
17
|
-
for (const a of data.annotations)
|
|
18
|
-
parts.push(annotationSvg(a.label, a.text, a.bbox, width));
|
|
19
|
-
if (parts.length === 0) {
|
|
20
|
-
await sharp(data.rawPath).toFile(data.finalPath);
|
|
21
|
-
return { id: data.id, ok: true, finalPath: data.finalPath };
|
|
22
|
-
}
|
|
23
|
-
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">${parts.join('')}</svg>`;
|
|
24
|
-
await sharp(data.rawPath)
|
|
25
|
-
.composite([{ input: Buffer.from(svg), top: 0, left: 0 }])
|
|
26
|
-
.toFile(data.finalPath);
|
|
27
|
-
return { id: data.id, ok: true, finalPath: data.finalPath };
|
|
28
|
-
}
|
|
29
|
-
async function copyRawAsFinal(outputDir) {
|
|
30
|
-
const files = readdirSync(outputDir).filter(f => f.endsWith('.raw.png'));
|
|
31
|
-
const results = [];
|
|
32
|
-
for (const f of files) {
|
|
33
|
-
const rawPath = join(outputDir, f);
|
|
34
|
-
const finalPath = rawPath.replace(/\.raw\.png$/, '.png');
|
|
35
|
-
try {
|
|
36
|
-
copyFileSync(rawPath, finalPath);
|
|
37
|
-
results.push({ id: f.replace(/\.raw\.png$/, ''), ok: true, finalPath });
|
|
38
|
-
}
|
|
39
|
-
catch (err) {
|
|
40
|
-
results.push({ id: f, ok: false, reason: String(err) });
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return results;
|
|
44
|
-
}
|
|
45
|
-
export async function composeAnnotations(outputDir, enabled) {
|
|
46
|
-
if (!existsSync(outputDir))
|
|
47
|
-
return [];
|
|
48
|
-
if (!enabled)
|
|
49
|
-
return copyRawAsFinal(outputDir);
|
|
50
|
-
const files = readdirSync(outputDir).filter(f => f.endsWith('.annotations.json'));
|
|
51
|
-
if (files.length === 0)
|
|
52
|
-
return copyRawAsFinal(outputDir);
|
|
53
|
-
const results = [];
|
|
54
|
-
for (const f of files) {
|
|
55
|
-
const jsonPath = join(outputDir, f);
|
|
56
|
-
try {
|
|
57
|
-
results.push(await annotateOne(jsonPath));
|
|
58
|
-
}
|
|
59
|
-
catch (err) {
|
|
60
|
-
results.push({ id: f, ok: false, reason: String(err) });
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return results;
|
|
64
|
-
}
|
|
65
|
-
//# sourceMappingURL=compose.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"compose.js","sourceRoot":"","sources":["../../src/annotate/compose.ts"],"names":[],"mappings":"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"}
|
package/dist/annotate/svg.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
export function escapeXml(s) {
|
|
2
|
-
return s.replace(/[<>&"']/g, c => ({ '<': '<', '>': '>', '&': '&', '"': '"', "'": ''' }[c]));
|
|
3
|
-
}
|
|
4
|
-
export function highlightSvg(bbox) {
|
|
5
|
-
const pad = 4;
|
|
6
|
-
const x = Math.max(0, bbox.x - pad);
|
|
7
|
-
const y = Math.max(0, bbox.y - pad);
|
|
8
|
-
const w = bbox.width + pad * 2;
|
|
9
|
-
const h = bbox.height + pad * 2;
|
|
10
|
-
return `<rect x="${x}" y="${y}" width="${w}" height="${h}" rx="6" ry="6" fill="rgba(255,213,0,0.18)" stroke="#f5a623" stroke-width="2.5"/>`;
|
|
11
|
-
}
|
|
12
|
-
export function redactSvg(bbox) {
|
|
13
|
-
return `<rect x="${bbox.x}" y="${bbox.y}" width="${bbox.width}" height="${bbox.height}" fill="#1f2328"/>`;
|
|
14
|
-
}
|
|
15
|
-
export function annotationSvg(label, text, bbox, canvasW) {
|
|
16
|
-
const r = 14;
|
|
17
|
-
const cx = bbox.x + bbox.width + r + 4;
|
|
18
|
-
const cy = bbox.y + bbox.height / 2;
|
|
19
|
-
const textPad = 8;
|
|
20
|
-
const fontSize = 13;
|
|
21
|
-
const approxCharWidth = 7.2;
|
|
22
|
-
const textWidth = text.length * approxCharWidth + textPad * 2;
|
|
23
|
-
const textHeight = fontSize + textPad * 2;
|
|
24
|
-
let textX = cx + r + 6;
|
|
25
|
-
let textY = cy - textHeight / 2;
|
|
26
|
-
if (textX + textWidth > canvasW) {
|
|
27
|
-
textX = bbox.x - textWidth - r - 6;
|
|
28
|
-
if (textX < 0) {
|
|
29
|
-
textX = bbox.x;
|
|
30
|
-
textY = bbox.y - textHeight - 6;
|
|
31
|
-
if (textY < 0)
|
|
32
|
-
textY = bbox.y + bbox.height + 6;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return [
|
|
36
|
-
`<circle cx="${cx}" cy="${cy}" r="${r}" fill="#d1242f" stroke="#fff" stroke-width="2"/>`,
|
|
37
|
-
`<text x="${cx}" y="${cy + 5}" font-family="-apple-system,Segoe UI,sans-serif" font-size="14" font-weight="700" fill="#fff" text-anchor="middle">${escapeXml(label)}</text>`,
|
|
38
|
-
`<rect x="${textX}" y="${textY}" width="${textWidth}" height="${textHeight}" rx="4" ry="4" fill="#1f2328" stroke="#fff" stroke-width="1.5"/>`,
|
|
39
|
-
`<text x="${textX + textPad}" y="${textY + textPad + fontSize - 2}" font-family="-apple-system,Segoe UI,sans-serif" font-size="${fontSize}" fill="#fff">${escapeXml(text)}</text>`,
|
|
40
|
-
].join('');
|
|
41
|
-
}
|
|
42
|
-
//# sourceMappingURL=svg.js.map
|
package/dist/annotate/svg.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"svg.js","sourceRoot":"","sources":["../../src/annotate/svg.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/auth.js
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { chromium } from 'playwright';
|
|
2
|
-
import { findProjectConfigPath, readProjectConfig, resolveProfile, } from '../config/projectConfig.js';
|
|
3
|
-
import { authStatePath, ensureAuthStateDir } from '../config/authState.js';
|
|
4
|
-
export async function authCommand(opts) {
|
|
5
|
-
const cfg = readProjectConfig(findProjectConfigPath());
|
|
6
|
-
const profile = resolveProfile(cfg, opts.profile);
|
|
7
|
-
ensureAuthStateDir(cfg.projectId);
|
|
8
|
-
const savePath = authStatePath(cfg.projectId, profile.name);
|
|
9
|
-
console.log('');
|
|
10
|
-
if (profile.production)
|
|
11
|
-
console.log(`⚠ Profile "${profile.name}" is PRODUCTION.`);
|
|
12
|
-
console.log(`Opening Chromium against ${profile.connection.startUrl}`);
|
|
13
|
-
console.log(`Auth-state will save to: ${savePath}`);
|
|
14
|
-
console.log('');
|
|
15
|
-
const browser = await chromium.launch({
|
|
16
|
-
headless: false,
|
|
17
|
-
args: ['--start-maximized'],
|
|
18
|
-
});
|
|
19
|
-
const context = await browser.newContext({ viewport: null });
|
|
20
|
-
const page = await context.newPage();
|
|
21
|
-
await page.goto(profile.connection.startUrl, { waitUntil: 'domcontentloaded' });
|
|
22
|
-
console.log('────────────────────────────────────────────────────────────');
|
|
23
|
-
console.log(' Log in fully (MFA included) at your own pace.');
|
|
24
|
-
console.log(' When you reach the post-login app, come back to this');
|
|
25
|
-
console.log(' terminal and press ENTER to save the session.');
|
|
26
|
-
console.log('────────────────────────────────────────────────────────────');
|
|
27
|
-
console.log('');
|
|
28
|
-
const boundary = new RegExp(profile.connection.authBoundaryUrlPattern);
|
|
29
|
-
// Crash-protection autosave — but only once the browser is past the login
|
|
30
|
-
// boundary. Saving mid-login persists a half-baked state (context cookies,
|
|
31
|
-
// no session cookie) that later fails captures while `doctor` reports it
|
|
32
|
-
// as present.
|
|
33
|
-
const safetyInterval = setInterval(() => {
|
|
34
|
-
if (boundary.test(page.url())) {
|
|
35
|
-
void context.storageState({ path: savePath }).catch(() => { });
|
|
36
|
-
}
|
|
37
|
-
}, 30_000);
|
|
38
|
-
try {
|
|
39
|
-
// Refuse to save until the page is back inside the app — pressing ENTER
|
|
40
|
-
// on the identity provider's login page must not persist a broken state.
|
|
41
|
-
for (;;) {
|
|
42
|
-
await waitForEnter();
|
|
43
|
-
const url = page.url();
|
|
44
|
-
if (boundary.test(url))
|
|
45
|
-
break;
|
|
46
|
-
console.log('');
|
|
47
|
-
console.log(`✗ Login doesn't look complete yet — the browser is still at:`);
|
|
48
|
-
console.log(` ${url}`);
|
|
49
|
-
console.log(' Finish logging in until you can see the app, then press ENTER again.');
|
|
50
|
-
console.log(' (Ctrl+C aborts without saving.)');
|
|
51
|
-
}
|
|
52
|
-
await context.storageState({ path: savePath });
|
|
53
|
-
console.log('');
|
|
54
|
-
console.log(`✓ Session saved to ${savePath}`);
|
|
55
|
-
console.log(' This file contains session cookies — never commit it to git.');
|
|
56
|
-
}
|
|
57
|
-
finally {
|
|
58
|
-
clearInterval(safetyInterval);
|
|
59
|
-
await browser.close().catch(() => { });
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
function waitForEnter() {
|
|
63
|
-
return new Promise(resolve => {
|
|
64
|
-
const onData = () => {
|
|
65
|
-
process.stdin.off('data', onData);
|
|
66
|
-
process.stdin.pause();
|
|
67
|
-
resolve();
|
|
68
|
-
};
|
|
69
|
-
process.stdin.resume();
|
|
70
|
-
process.stdin.once('data', onData);
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
//# sourceMappingURL=auth.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,cAAc,GACf,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAM3E,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAiB;IACjD,MAAM,GAAG,GAAG,iBAAiB,CAAC,qBAAqB,EAAE,CAAC,CAAC;IACvD,MAAM,OAAO,GAAG,cAAc,CAAC,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IAClD,kBAAkB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAClC,MAAM,QAAQ,GAAG,aAAa,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAE5D,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,IAAI,OAAO,CAAC,UAAU;QAAE,OAAO,CAAC,GAAG,CAAC,cAAc,OAAO,CAAC,IAAI,kBAAkB,CAAC,CAAC;IAClF,OAAO,CAAC,GAAG,CAAC,4BAA4B,OAAO,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC;IACvE,OAAO,CAAC,GAAG,CAAC,4BAA4B,QAAQ,EAAE,CAAC,CAAC;IACpD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC;QACpC,QAAQ,EAAE,KAAK;QACf,IAAI,EAAE,CAAC,mBAAmB,CAAC;KAC5B,CAAC,CAAC;IACH,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;IAErC,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,kBAAkB,EAAE,CAAC,CAAC;IAEhF,OAAO,CAAC,GAAG,CAAC,8DAA8D,CAAC,CAAC;IAC5E,OAAO,CAAC,GAAG,CAAC,iDAAiD,CAAC,CAAC;IAC/D,OAAO,CAAC,GAAG,CAAC,wDAAwD,CAAC,CAAC;IACtE,OAAO,CAAC,GAAG,CAAC,iDAAiD,CAAC,CAAC;IAC/D,OAAO,CAAC,GAAG,CAAC,8DAA8D,CAAC,CAAC;IAC5E,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,sBAAsB,CAAC,CAAC;IAEvE,0EAA0E;IAC1E,2EAA2E;IAC3E,yEAAyE;IACzE,cAAc;IACd,MAAM,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE;QACtC,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC;YAC9B,KAAK,OAAO,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAChE,CAAC;IACH,CAAC,EAAE,MAAM,CAAC,CAAC;IAEX,IAAI,CAAC;QACH,wEAAwE;QACxE,yEAAyE;QACzE,SAAS,CAAC;YACR,MAAM,YAAY,EAAE,CAAC;YACrB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACvB,IAAI,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC;gBAAE,MAAM;YAC9B,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAChB,OAAO,CAAC,GAAG,CAAC,8DAA8D,CAAC,CAAC;YAC5E,OAAO,CAAC,GAAG,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;YAC1B,OAAO,CAAC,GAAG,CAAC,wEAAwE,CAAC,CAAC;YACtF,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;QACnD,CAAC;QACD,MAAM,OAAO,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC/C,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,sBAAsB,QAAQ,EAAE,CAAC,CAAC;QAC9C,OAAO,CAAC,GAAG,CAAC,gEAAgE,CAAC,CAAC;IAChF,CAAC;YAAS,CAAC;QACT,aAAa,CAAC,cAAc,CAAC,CAAC;QAC9B,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACxC,CAAC;AACH,CAAC;AAED,SAAS,YAAY;IACnB,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;QAC3B,MAAM,MAAM,GAAG,GAAS,EAAE;YACxB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YAClC,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YACtB,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC;QACF,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;QACvB,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;AACL,CAAC"}
|