document360-capture 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +80 -0
  3. package/dist/annotate/compose.d.ts +7 -0
  4. package/dist/annotate/compose.js +65 -0
  5. package/dist/annotate/compose.js.map +1 -0
  6. package/dist/annotate/svg.d.ts +5 -0
  7. package/dist/annotate/svg.js +42 -0
  8. package/dist/annotate/svg.js.map +1 -0
  9. package/dist/cli.d.ts +2 -0
  10. package/dist/cli.js +66 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/commands/auth.d.ts +4 -0
  13. package/dist/commands/auth.js +52 -0
  14. package/dist/commands/auth.js.map +1 -0
  15. package/dist/commands/capture.d.ts +5 -0
  16. package/dist/commands/capture.js +77 -0
  17. package/dist/commands/capture.js.map +1 -0
  18. package/dist/commands/doctor.d.ts +4 -0
  19. package/dist/commands/doctor.js +88 -0
  20. package/dist/commands/doctor.js.map +1 -0
  21. package/dist/commands/init.d.ts +1 -0
  22. package/dist/commands/init.js +97 -0
  23. package/dist/commands/init.js.map +1 -0
  24. package/dist/commands/list.d.ts +1 -0
  25. package/dist/commands/list.js +33 -0
  26. package/dist/commands/list.js.map +1 -0
  27. package/dist/commands/status.d.ts +1 -0
  28. package/dist/commands/status.js +24 -0
  29. package/dist/commands/status.js.map +1 -0
  30. package/dist/config/authState.d.ts +10 -0
  31. package/dist/config/authState.js +27 -0
  32. package/dist/config/authState.js.map +1 -0
  33. package/dist/config/projectConfig.d.ts +27 -0
  34. package/dist/config/projectConfig.js +43 -0
  35. package/dist/config/projectConfig.js.map +1 -0
  36. package/dist/config/userConfig.d.ts +6 -0
  37. package/dist/config/userConfig.js +24 -0
  38. package/dist/config/userConfig.js.map +1 -0
  39. package/dist/helpers/dumpAnnotations.d.ts +3 -0
  40. package/dist/helpers/dumpAnnotations.js +39 -0
  41. package/dist/helpers/dumpAnnotations.js.map +1 -0
  42. package/dist/helpers/index.d.ts +3 -0
  43. package/dist/helpers/index.js +3 -0
  44. package/dist/helpers/index.js.map +1 -0
  45. package/dist/helpers/types.d.ts +37 -0
  46. package/dist/helpers/types.js +2 -0
  47. package/dist/helpers/types.js.map +1 -0
  48. package/dist/helpers/waitPastLogin.d.ts +2 -0
  49. package/dist/helpers/waitPastLogin.js +7 -0
  50. package/dist/helpers/waitPastLogin.js.map +1 -0
  51. package/dist/runner/expiryDetect.d.ts +1 -0
  52. package/dist/runner/expiryDetect.js +19 -0
  53. package/dist/runner/expiryDetect.js.map +1 -0
  54. package/dist/runner/playwrightConfig.d.ts +8 -0
  55. package/dist/runner/playwrightConfig.js +33 -0
  56. package/dist/runner/playwrightConfig.js.map +1 -0
  57. package/dist/runner/runSpecs.d.ts +14 -0
  58. package/dist/runner/runSpecs.js +62 -0
  59. package/dist/runner/runSpecs.js.map +1 -0
  60. package/dist/scripts/postinstall.d.ts +1 -0
  61. package/dist/scripts/postinstall.js +18 -0
  62. package/dist/scripts/postinstall.js.map +1 -0
  63. package/package.json +59 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Saravana Kumar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
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.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # document360-capture
2
+
3
+ CLI that captures product screenshots from Playwright specs. Login once, capture many. Companion to [document360-writer](../writer/).
4
+
5
+ ## Why
6
+
7
+ Doc-screenshot pipelines hit two walls in practice:
8
+
9
+ 1. **MFA + per-run login.** Spinning up Chromium for every capture and asking the human to log in each time burns time and rules out repeatable runs.
10
+ 2. **Annotation by hand.** Adding arrows, numbered callouts, and redactions in an image editor is the slowest part of doc maintenance.
11
+
12
+ This CLI solves both: `d360-capture auth` opens Chromium once, the human logs in (MFA at their own pace), and a Playwright `storageState` is saved. Every `d360-capture capture` after that reuses the session. Specs declare what to highlight/redact/annotate; the CLI composites SVG overlays via `sharp` after the raw capture lands.
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ npm install -g document360-capture
18
+ ```
19
+
20
+ Postinstall downloads the Chromium binary via `npx playwright install chromium`.
21
+
22
+ ## Quick start
23
+
24
+ ```bash
25
+ cd <your-repo>
26
+ d360-capture init # one-time per project — writes .d360-capture.json
27
+ d360-capture auth --env staging # one-time per machine — log in, press Enter when done
28
+ d360-capture capture # run every spec in captureDir
29
+ d360-capture capture <spec-id> # run one spec
30
+ ```
31
+
32
+ ## Commands
33
+
34
+ | Command | Description |
35
+ |---|---|
36
+ | `d360-capture init` | Interactive setup. Writes `.d360-capture.json` at repo root. |
37
+ | `d360-capture auth [--env <name>]` | Open Chromium; log in at your own pace; press Enter to save session. |
38
+ | `d360-capture capture [<spec-id>] [--env <name>] [--no-annotate]` | Run one or all specs. Default-on annotation overlay. |
39
+ | `d360-capture list` | List discovered specs and last-capture times. |
40
+ | `d360-capture doctor` | Validate config, auth-state, browser, and capture dir. |
41
+ | `d360-capture status` | Print resolved config (secrets redacted). |
42
+
43
+ ## Spec helpers
44
+
45
+ Specs in your `captureDir` import three helpers:
46
+
47
+ ```typescript
48
+ import { test } from '@playwright/test';
49
+ import { waitPastLogin, dumpAnnotations, type Placeholder } from 'document360-capture/helpers';
50
+
51
+ const PLACEHOLDER: Placeholder = {
52
+ id: 'settings-variables-add-modal',
53
+ saveTo: 'user-docs/_screenshots/settings-variables-add-modal.raw.png',
54
+ highlight: ['button:has-text("+ Add")'],
55
+ annotations: [{ target: 'input[name="key"]', label: '1', text: 'Use proj.* prefix' }],
56
+ redact: ['[data-testid="topbar-user-email"]'],
57
+ };
58
+
59
+ test(PLACEHOLDER.id, async ({ page }) => {
60
+ await page.goto(process.env.CAPTURE_START_URL!);
61
+ await waitPastLogin(page, new RegExp(process.env.CAPTURE_AUTH_BOUNDARY!));
62
+
63
+ await page.click('[aria-label="Open Settings"]');
64
+ await page.click('a:has-text("Variables")');
65
+ await page.click('button:has-text("+ Add")');
66
+ await page.waitForSelector('[role="dialog"]', { state: 'visible' });
67
+
68
+ await page.locator('[role="dialog"]').first().screenshot({ path: PLACEHOLDER.saveTo });
69
+ await dumpAnnotations(page, PLACEHOLDER);
70
+ });
71
+ ```
72
+
73
+ ## Config files
74
+
75
+ - **`<repo>/.d360-capture.json`** (committed): paths, viewport, environments (startUrl + authBoundaryUrlPattern per env), annotation toggle.
76
+ - **`~/.document360-capture/auth-states/<projectId>/<env>.json`** (per-machine, never commit): Playwright `storageState`.
77
+
78
+ ## License
79
+
80
+ MIT
@@ -0,0 +1,7 @@
1
+ export type AnnotateResult = {
2
+ id: string;
3
+ ok: boolean;
4
+ reason?: string;
5
+ finalPath?: string;
6
+ };
7
+ export declare function composeAnnotations(outputDir: string, enabled: boolean): Promise<AnnotateResult[]>;
@@ -0,0 +1,65 @@
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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose.js","sourceRoot":"","sources":["../../src/annotate/compose.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { Bbox } from '../helpers/types.js';
2
+ export declare function escapeXml(s: string): string;
3
+ export declare function highlightSvg(bbox: Bbox): string;
4
+ export declare function redactSvg(bbox: Bbox): string;
5
+ export declare function annotationSvg(label: string, text: string, bbox: Bbox, canvasW: number): string;
@@ -0,0 +1,42 @@
1
+ export function escapeXml(s) {
2
+ return s.replace(/[<>&"']/g, c => ({ '<': '&lt;', '>': '&gt;', '&': '&amp;', '"': '&quot;', "'": '&apos;' }[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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"svg.js","sourceRoot":"","sources":["../../src/annotate/svg.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { createRequire } from 'node:module';
4
+ import { initCommand } from './commands/init.js';
5
+ import { authCommand } from './commands/auth.js';
6
+ import { captureCommand } from './commands/capture.js';
7
+ import { listCommand } from './commands/list.js';
8
+ import { doctorCommand } from './commands/doctor.js';
9
+ import { statusCommand } from './commands/status.js';
10
+ const require = createRequire(import.meta.url);
11
+ const pkg = require('../package.json');
12
+ const program = new Command();
13
+ program
14
+ .name('d360-capture')
15
+ .description('Capture product screenshots from Playwright specs. Login once, capture many.')
16
+ .version(pkg.version);
17
+ program
18
+ .command('init')
19
+ .description('Interactive setup. Writes .d360-capture.json at repo root.')
20
+ .action(async () => {
21
+ await initCommand();
22
+ });
23
+ program
24
+ .command('auth')
25
+ .description('Open Chromium for interactive login. Press ENTER when done to save the session.')
26
+ .option('-e, --env <name>', 'Environment name (default: defaultEnvironment from config)')
27
+ .action(async (opts) => {
28
+ await authCommand(opts);
29
+ });
30
+ program
31
+ .command('capture [spec-id]')
32
+ .description('Run one spec by id, or all specs if omitted.')
33
+ .option('-e, --env <name>', 'Environment name (default: defaultEnvironment from config)')
34
+ .option('--no-annotate', 'Skip annotation overlay; emit raw screenshots only')
35
+ .action(async (specId, opts) => {
36
+ const exit = await captureCommand(specId, opts);
37
+ if (exit !== 0)
38
+ process.exit(exit);
39
+ });
40
+ program
41
+ .command('list')
42
+ .description('List discovered specs and their last-capture times.')
43
+ .action(async () => {
44
+ await listCommand();
45
+ });
46
+ program
47
+ .command('doctor')
48
+ .description('Validate config, auth-state, and browser install.')
49
+ .option('-e, --env <name>', 'Environment to check')
50
+ .action(async (opts) => {
51
+ const exit = await doctorCommand(opts);
52
+ if (exit !== 0)
53
+ process.exit(exit);
54
+ });
55
+ program
56
+ .command('status')
57
+ .description('Print resolved config and auth-state freshness.')
58
+ .action(async () => {
59
+ await statusCommand();
60
+ });
61
+ program.parseAsync(process.argv).catch(err => {
62
+ console.error('');
63
+ console.error(`✗ ${err.message}`);
64
+ process.exit(1);
65
+ });
66
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export type AuthOptions = {
2
+ env?: string;
3
+ };
4
+ export declare function authCommand(opts: AuthOptions): Promise<void>;
@@ -0,0 +1,52 @@
1
+ import { chromium } from 'playwright';
2
+ import { findProjectConfigPath, readProjectConfig, resolveEnv, } 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 { name: envName, env } = resolveEnv(cfg, opts.env);
7
+ ensureAuthStateDir(cfg.projectId);
8
+ const savePath = authStatePath(cfg.projectId, envName);
9
+ console.log('');
10
+ console.log(`Opening Chromium against ${env.startUrl}`);
11
+ console.log(`Auth-state will save to: ${savePath}`);
12
+ console.log('');
13
+ const browser = await chromium.launch({
14
+ headless: false,
15
+ args: ['--start-maximized'],
16
+ });
17
+ const context = await browser.newContext({ viewport: null });
18
+ const page = await context.newPage();
19
+ await page.goto(env.startUrl, { waitUntil: 'domcontentloaded' });
20
+ console.log('────────────────────────────────────────────────────────────');
21
+ console.log(' Log in fully (MFA included) at your own pace.');
22
+ console.log(' When you reach the post-login app, come back to this');
23
+ console.log(' terminal and press ENTER to save the session.');
24
+ console.log('────────────────────────────────────────────────────────────');
25
+ console.log('');
26
+ const safetyInterval = setInterval(() => {
27
+ void context.storageState({ path: savePath }).catch(() => { });
28
+ }, 30_000);
29
+ try {
30
+ await waitForEnter();
31
+ await context.storageState({ path: savePath });
32
+ console.log('');
33
+ console.log(`✓ Session saved to ${savePath}`);
34
+ console.log(' This file contains session cookies — never commit it to git.');
35
+ }
36
+ finally {
37
+ clearInterval(safetyInterval);
38
+ await browser.close().catch(() => { });
39
+ }
40
+ }
41
+ function waitForEnter() {
42
+ return new Promise(resolve => {
43
+ const onData = () => {
44
+ process.stdin.off('data', onData);
45
+ process.stdin.pause();
46
+ resolve();
47
+ };
48
+ process.stdin.resume();
49
+ process.stdin.once('data', onData);
50
+ });
51
+ }
52
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export type CaptureOptions = {
2
+ env?: string;
3
+ noAnnotate?: boolean;
4
+ };
5
+ export declare function captureCommand(specId: string | undefined, opts: CaptureOptions): Promise<number>;
@@ -0,0 +1,77 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { isAbsolute, resolve } from 'node:path';
3
+ import { findProjectConfigPath, readProjectConfig, resolveEnv, } from '../config/projectConfig.js';
4
+ import { authStatePath, getAuthStateInfo } from '../config/authState.js';
5
+ import { writeRuntimePlaywrightConfig } from '../runner/playwrightConfig.js';
6
+ import { runSpecs } from '../runner/runSpecs.js';
7
+ import { looksLikeSessionExpiry } from '../runner/expiryDetect.js';
8
+ import { composeAnnotations } from '../annotate/compose.js';
9
+ export async function captureCommand(specId, opts) {
10
+ const cwd = process.cwd();
11
+ const cfg = readProjectConfig(findProjectConfigPath(cwd));
12
+ const { name: envName, env } = resolveEnv(cfg, opts.env);
13
+ const info = getAuthStateInfo(cfg.projectId, envName);
14
+ if (!info.exists) {
15
+ console.error('');
16
+ console.error(`✗ No saved session for env "${envName}".`);
17
+ console.error(` Expected at: ${info.path}`);
18
+ console.error(` Run: d360-capture auth --env ${envName}`);
19
+ return 2;
20
+ }
21
+ const captureDirAbs = isAbsolute(cfg.captureDir) ? cfg.captureDir : resolve(cwd, cfg.captureDir);
22
+ if (!existsSync(captureDirAbs)) {
23
+ console.error(`✗ Capture directory missing: ${captureDirAbs}`);
24
+ console.error(' Generate at least one spec there before running capture.');
25
+ return 2;
26
+ }
27
+ const configPath = writeRuntimePlaywrightConfig({
28
+ cwd,
29
+ cfg,
30
+ envName,
31
+ storageStatePath: authStatePath(cfg.projectId, envName),
32
+ });
33
+ console.log('');
34
+ console.log(`▶ Running specs against ${envName} (${env.startUrl})`);
35
+ if (specId)
36
+ console.log(` Single spec: ${specId}`);
37
+ else
38
+ console.log(` All specs in ${cfg.captureDir}`);
39
+ console.log('');
40
+ const result = await runSpecs({
41
+ cwd,
42
+ configPath,
43
+ startUrl: env.startUrl,
44
+ authBoundaryUrlPattern: env.authBoundaryUrlPattern,
45
+ specId,
46
+ captureDirAbs,
47
+ });
48
+ if (result.exitCode !== 0) {
49
+ const combined = result.stdout + '\n' + result.stderr;
50
+ if (looksLikeSessionExpiry(combined)) {
51
+ console.error('');
52
+ console.error(`✗ Session expired or invalid for env "${envName}".`);
53
+ console.error(` Run: d360-capture auth --env ${envName}`);
54
+ console.error(' Then re-run d360-capture capture.');
55
+ return 2;
56
+ }
57
+ console.error('');
58
+ console.error(`✗ Playwright exited with code ${result.exitCode}`);
59
+ return result.exitCode;
60
+ }
61
+ const annotateEnabled = !opts.noAnnotate && cfg.annotation.enabled;
62
+ const outputDirAbs = isAbsolute(cfg.outputDir) ? cfg.outputDir : resolve(cwd, cfg.outputDir);
63
+ console.log('');
64
+ console.log(annotateEnabled ? '▶ Annotating screenshots...' : '▶ Skipping annotation (--no-annotate or config off)');
65
+ const annotated = await composeAnnotations(outputDirAbs, annotateEnabled);
66
+ const ok = annotated.filter(r => r.ok).length;
67
+ const failed = annotated.filter(r => !r.ok);
68
+ console.log('');
69
+ console.log(`✓ ${ok} screenshot${ok === 1 ? '' : 's'} ready in ${cfg.outputDir}`);
70
+ if (failed.length > 0) {
71
+ console.log(`✗ ${failed.length} annotation failure${failed.length === 1 ? '' : 's'}:`);
72
+ for (const f of failed)
73
+ console.log(` - ${f.id}: ${f.reason}`);
74
+ }
75
+ return failed.length === 0 ? 0 : 1;
76
+ }
77
+ //# sourceMappingURL=capture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture.js","sourceRoot":"","sources":["../../src/commands/capture.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,UAAU,GACX,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,EAAE,4BAA4B,EAAE,MAAM,+BAA+B,CAAC;AAC7E,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAO5D,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,MAA0B,EAAE,IAAoB;IACnF,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC1B,MAAM,GAAG,GAAG,iBAAiB,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1D,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;IAEzD,MAAM,IAAI,GAAG,gBAAgB,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IACtD,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;QACjB,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,+BAA+B,OAAO,IAAI,CAAC,CAAC;QAC1D,OAAO,CAAC,KAAK,CAAC,kBAAkB,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAC7C,OAAO,CAAC,KAAK,CAAC,kCAAkC,OAAO,EAAE,CAAC,CAAC;QAC3D,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,aAAa,GAAG,UAAU,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;IACjG,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QAC/B,OAAO,CAAC,KAAK,CAAC,gCAAgC,aAAa,EAAE,CAAC,CAAC;QAC/D,OAAO,CAAC,KAAK,CAAC,4DAA4D,CAAC,CAAC;QAC5E,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,UAAU,GAAG,4BAA4B,CAAC;QAC9C,GAAG;QACH,GAAG;QACH,OAAO;QACP,gBAAgB,EAAE,aAAa,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC;KACxD,CAAC,CAAC;IAEH,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,2BAA2B,OAAO,KAAK,GAAG,CAAC,QAAQ,GAAG,CAAC,CAAC;IACpE,IAAI,MAAM;QAAE,OAAO,CAAC,GAAG,CAAC,kBAAkB,MAAM,EAAE,CAAC,CAAC;;QAC/C,OAAO,CAAC,GAAG,CAAC,kBAAkB,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC;IACrD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC;QAC5B,GAAG;QACH,UAAU;QACV,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,sBAAsB,EAAE,GAAG,CAAC,sBAAsB;QAClD,MAAM;QACN,aAAa;KACd,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,GAAG,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC;QACtD,IAAI,sBAAsB,CAAC,QAAQ,CAAC,EAAE,CAAC;YACrC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAClB,OAAO,CAAC,KAAK,CAAC,yCAAyC,OAAO,IAAI,CAAC,CAAC;YACpE,OAAO,CAAC,KAAK,CAAC,kCAAkC,OAAO,EAAE,CAAC,CAAC;YAC3D,OAAO,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;YACrD,OAAO,CAAC,CAAC;QACX,CAAC;QACD,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,iCAAiC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;QAClE,OAAO,MAAM,CAAC,QAAQ,CAAC;IACzB,CAAC;IAED,MAAM,eAAe,GAAG,CAAC,IAAI,CAAC,UAAU,IAAI,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC;IACnE,MAAM,YAAY,GAAG,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC;IAC7F,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,6BAA6B,CAAC,CAAC,CAAC,qDAAqD,CAAC,CAAC;IAErH,MAAM,SAAS,GAAG,MAAM,kBAAkB,CAAC,YAAY,EAAE,eAAe,CAAC,CAAC;IAC1E,MAAM,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC;IAC9C,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAE5C,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,cAAc,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,aAAa,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC;IAClF,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,OAAO,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,MAAM,sBAAsB,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;QACvF,KAAK,MAAM,CAAC,IAAI,MAAM;YAAE,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACrC,CAAC"}
@@ -0,0 +1,4 @@
1
+ export type DoctorOptions = {
2
+ env?: string;
3
+ };
4
+ export declare function doctorCommand(opts: DoctorOptions): Promise<number>;
@@ -0,0 +1,88 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { isAbsolute, resolve } from 'node:path';
3
+ import { createRequire } from 'node:module';
4
+ import { findProjectConfigPath, readProjectConfig, resolveEnv, } from '../config/projectConfig.js';
5
+ import { getAuthStateInfo } from '../config/authState.js';
6
+ const require = createRequire(import.meta.url);
7
+ export async function doctorCommand(opts) {
8
+ const cwd = process.cwd();
9
+ let failures = 0;
10
+ const ok = (msg) => console.log(` ✓ ${msg}`);
11
+ const fail = (msg) => {
12
+ console.log(` ✗ ${msg}`);
13
+ failures++;
14
+ };
15
+ console.log('');
16
+ console.log('Checking document360-capture setup...');
17
+ console.log('');
18
+ const cfgPath = findProjectConfigPath(cwd);
19
+ if (!existsSync(cfgPath)) {
20
+ fail(`Project config missing at ${cfgPath} — run \`d360-capture init\``);
21
+ return 2;
22
+ }
23
+ ok(`Project config: ${cfgPath}`);
24
+ let cfg;
25
+ try {
26
+ cfg = readProjectConfig(cfgPath);
27
+ ok(`Config parses; projectId="${cfg.projectId}"`);
28
+ }
29
+ catch (err) {
30
+ fail(`Config invalid: ${err.message}`);
31
+ return 2;
32
+ }
33
+ const captureDirAbs = isAbsolute(cfg.captureDir) ? cfg.captureDir : resolve(cwd, cfg.captureDir);
34
+ if (existsSync(captureDirAbs))
35
+ ok(`Capture dir exists: ${cfg.captureDir}`);
36
+ else
37
+ fail(`Capture dir missing: ${cfg.captureDir}`);
38
+ let env;
39
+ try {
40
+ env = resolveEnv(cfg, opts.env);
41
+ ok(`Environment "${env.name}" resolved (startUrl=${env.env.startUrl})`);
42
+ }
43
+ catch (err) {
44
+ fail(`Environment resolve failed: ${err.message}`);
45
+ return 1;
46
+ }
47
+ const info = getAuthStateInfo(cfg.projectId, env.name);
48
+ if (info.exists) {
49
+ const ageDays = info.ageMs != null ? Math.floor(info.ageMs / 86_400_000) : null;
50
+ ok(`Auth-state present (${ageDays}d old): ${info.path}`);
51
+ try {
52
+ const parsed = JSON.parse(readFileSync(info.path, 'utf8'));
53
+ if (parsed && typeof parsed === 'object')
54
+ ok('Auth-state parses as JSON');
55
+ else
56
+ fail('Auth-state is not a valid object');
57
+ }
58
+ catch (err) {
59
+ fail(`Auth-state unreadable: ${err.message}`);
60
+ }
61
+ }
62
+ else {
63
+ fail(`Auth-state missing for "${env.name}" — run \`d360-capture auth --env ${env.name}\``);
64
+ }
65
+ try {
66
+ const playwrightPkg = require.resolve('playwright/package.json');
67
+ const pkg = JSON.parse(readFileSync(playwrightPkg, 'utf8'));
68
+ ok(`playwright@${pkg.version} installed`);
69
+ }
70
+ catch {
71
+ fail('playwright not resolvable — reinstall document360-capture');
72
+ }
73
+ try {
74
+ require.resolve('@playwright/test/cli.js');
75
+ ok('@playwright/test CLI resolvable');
76
+ }
77
+ catch {
78
+ fail('@playwright/test CLI not resolvable — reinstall document360-capture');
79
+ }
80
+ console.log('');
81
+ if (failures === 0)
82
+ console.log('All checks passed.');
83
+ else
84
+ console.log(`${failures} check${failures === 1 ? '' : 's'} failed.`);
85
+ console.log('');
86
+ return failures === 0 ? 0 : 1;
87
+ }
88
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare function initCommand(): Promise<void>;