@wasao/kagemusha 0.2.0 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -453
- package/dist/commands/capture.js +57 -35
- package/dist/commands/capture.js.map +1 -1
- package/dist/commands/edit.d.ts +1 -1
- package/dist/commands/edit.d.ts.map +1 -1
- package/dist/commands/edit.js +43 -7
- package/dist/commands/edit.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +75 -44
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/login.js +3 -14
- package/dist/commands/login.js.map +1 -1
- package/dist/editor/inject-script/annotations.d.ts +11 -0
- package/dist/editor/inject-script/annotations.d.ts.map +1 -0
- package/dist/editor/inject-script/annotations.js +409 -0
- package/dist/editor/inject-script/annotations.js.map +1 -0
- package/dist/editor/inject-script/bridge.d.ts +13 -0
- package/dist/editor/inject-script/bridge.d.ts.map +1 -0
- package/dist/editor/inject-script/bridge.js +33 -0
- package/dist/editor/inject-script/bridge.js.map +1 -0
- package/dist/editor/inject-script/crop.d.ts +9 -0
- package/dist/editor/inject-script/crop.d.ts.map +1 -0
- package/dist/editor/inject-script/crop.js +236 -0
- package/dist/editor/inject-script/crop.js.map +1 -0
- package/dist/editor/inject-script/dom.d.ts +7 -0
- package/dist/editor/inject-script/dom.d.ts.map +1 -0
- package/dist/editor/inject-script/dom.js +32 -0
- package/dist/editor/inject-script/dom.js.map +1 -0
- package/dist/editor/inject-script/index.d.ts +2 -0
- package/dist/editor/inject-script/index.d.ts.map +1 -0
- package/dist/editor/inject-script/index.js +56 -0
- package/dist/editor/inject-script/index.js.map +1 -0
- package/dist/editor/inject-script/record.d.ts +5 -0
- package/dist/editor/inject-script/record.d.ts.map +1 -0
- package/dist/editor/inject-script/record.js +398 -0
- package/dist/editor/inject-script/record.js.map +1 -0
- package/dist/editor/inject-script/selector.d.ts +6 -0
- package/dist/editor/inject-script/selector.d.ts.map +1 -0
- package/dist/editor/inject-script/selector.js +112 -0
- package/dist/editor/inject-script/selector.js.map +1 -0
- package/dist/editor/inject-script/state.d.ts +27 -0
- package/dist/editor/inject-script/state.d.ts.map +1 -0
- package/dist/editor/inject-script/state.js +26 -0
- package/dist/editor/inject-script/state.js.map +1 -0
- package/dist/editor/inject-script/svg.d.ts +7 -0
- package/dist/editor/inject-script/svg.d.ts.map +1 -0
- package/dist/editor/inject-script/svg.js +39 -0
- package/dist/editor/inject-script/svg.js.map +1 -0
- package/dist/editor/inject-script/toolbar.d.ts +14 -0
- package/dist/editor/inject-script/toolbar.d.ts.map +1 -0
- package/dist/editor/inject-script/toolbar.js +240 -0
- package/dist/editor/inject-script/toolbar.js.map +1 -0
- package/dist/editor/inject-script/types.d.ts +102 -0
- package/dist/editor/inject-script/types.d.ts.map +1 -0
- package/dist/editor/inject-script/types.js +5 -0
- package/dist/editor/inject-script/types.js.map +1 -0
- package/dist/editor/inject-script.js +1248 -699
- package/dist/index.js +9 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/canonical.d.ts +35 -3
- package/dist/lib/canonical.d.ts.map +1 -1
- package/dist/lib/canonical.js +85 -25
- package/dist/lib/canonical.js.map +1 -1
- package/dist/lib/diff.d.ts +23 -4
- package/dist/lib/diff.d.ts.map +1 -1
- package/dist/lib/diff.js +5 -6
- package/dist/lib/diff.js.map +1 -1
- package/dist/lib/page-ready.d.ts +18 -0
- package/dist/lib/page-ready.d.ts.map +1 -0
- package/dist/lib/page-ready.js +19 -0
- package/dist/lib/page-ready.js.map +1 -0
- package/dist/lib/screenshot.d.ts +4 -1
- package/dist/lib/screenshot.d.ts.map +1 -1
- package/dist/lib/screenshot.js +31 -5
- package/dist/lib/screenshot.js.map +1 -1
- package/dist/lib/staging.d.ts +0 -1
- package/dist/lib/staging.d.ts.map +1 -1
- package/dist/lib/staging.js +0 -3
- package/dist/lib/staging.js.map +1 -1
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +20 -10
- package/dist/editor/inject-script.d.ts +0 -2
- package/dist/editor/inject-script.d.ts.map +0 -1
- package/dist/editor/inject-script.js.map +0 -1
package/README.md
CHANGED
|
@@ -5,562 +5,212 @@ The shadow warrior for your documentation.
|
|
|
5
5
|
|
|
6
6
|
## What it does
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
`kagemusha capture` walks your app with Playwright, diffs each screenshot against the canonical version on S3, and pushes only what changed. Your help articles embed a stable URL once and the image refreshes itself on every merge to main.
|
|
9
9
|
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
- **Visual
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
16
|
-
- **Local mode** — Optional output dir for testing; never committed to git
|
|
17
|
-
- **GitHub Actions ready** — Runs on every merge to main
|
|
10
|
+
- **One verb (`capture`)** — capture → diff → push, all in one command
|
|
11
|
+
- **S3-first** — `<id>/latest.png` is the canonical, embedded directly into help articles
|
|
12
|
+
- **Component-level capture** — Playwright-powered, full-page / crop, pre-capture actions, element hiding
|
|
13
|
+
- **Visual editor** — draw rectangles, arrows, labels; pick crop range by drag (`kagemusha edit`)
|
|
14
|
+
- **Login once** — store `storageState` locally, or run a scripted login on every CI run
|
|
15
|
+
- **Slack-ready** — `reports/summary.json` ships before/after URLs so notifications include image previews
|
|
18
16
|
|
|
19
17
|
## Quick Start
|
|
20
18
|
|
|
21
19
|
```bash
|
|
22
|
-
# Install
|
|
23
20
|
npm install -D @wasao/kagemusha
|
|
24
21
|
|
|
25
22
|
# Interactive setup: config → login → discover pages → workflow
|
|
26
23
|
npx kagemusha init
|
|
27
24
|
|
|
28
|
-
# Capture, diff vs canonical, publish what changed
|
|
25
|
+
# Capture, diff vs canonical, publish what changed
|
|
29
26
|
npx kagemusha capture
|
|
30
27
|
|
|
31
|
-
# Preview only — no canonical update
|
|
28
|
+
# Preview only — no canonical update
|
|
32
29
|
npx kagemusha capture --dry-run
|
|
33
30
|
```
|
|
34
31
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
## Workflow
|
|
38
|
-
|
|
39
|
-
### 1. First-time setup — `init`
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
npx kagemusha init
|
|
43
|
-
```
|
|
32
|
+
## Commands
|
|
44
33
|
|
|
45
|
-
|
|
34
|
+
| Command | Description |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `kagemusha init` | Interactive setup (config + login + discover + workflow) |
|
|
37
|
+
| `kagemusha login` | Refresh the saved login session (interactive or scripted) |
|
|
38
|
+
| `kagemusha discover` | Re-crawl the app and add newly-found pages to definitions |
|
|
39
|
+
| `kagemusha add <path>` | Add a single screenshot definition |
|
|
40
|
+
| `kagemusha list` | List all definitions |
|
|
41
|
+
| `kagemusha edit --id <id>` | Open the visual editor (crop range + annotations) |
|
|
42
|
+
| `kagemusha capture` | Capture → diff → publish (use `--dry-run` to preview) |
|
|
43
|
+
| `kagemusha validate` | Validate config and definition files |
|
|
46
44
|
|
|
47
|
-
|
|
48
|
-
2. **Login** — if the app requires auth, opens a browser so you can sign in manually. The session is saved to `.kagemusha/auth-state.json` and reused afterwards.
|
|
49
|
-
3. **Discover** — crawls the app (clicks nav links + BFS on `<a>`) and shows a checklist of found pages
|
|
50
|
-
4. **Workflow** — optionally generates `.github/workflows/kagemusha.yml`
|
|
51
|
-
5. **Gitignore** — adds `outputDir/`, `.kagemusha/.staging/`, `reports/`, auth files to `.gitignore`
|
|
45
|
+
## Configuration
|
|
52
46
|
|
|
53
|
-
|
|
47
|
+
`init` generates these files:
|
|
54
48
|
|
|
55
49
|
```
|
|
56
50
|
kagemusha.config.yaml # base URL, viewport, publish destination
|
|
57
51
|
.kagemusha/definitions.json # one entry per screenshot
|
|
58
|
-
.kagemusha/
|
|
52
|
+
.kagemusha/login.mjs # optional scripted login (CI-friendly)
|
|
59
53
|
.github/workflows/kagemusha.yml
|
|
60
54
|
```
|
|
61
55
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
| Command | Use when |
|
|
65
|
-
|---------|----------|
|
|
66
|
-
| `npx kagemusha discover` | Re-crawl and pick up newly added routes |
|
|
67
|
-
| `npx kagemusha add <path>` | Add a single page manually, e.g. `npx kagemusha add /settings` |
|
|
68
|
-
| `npx kagemusha add <path> --id custom-id` | Add a second variant of the same page with a custom ID |
|
|
69
|
-
| `npx kagemusha login` | Refresh the login session (run if you get redirected to the login page during capture) |
|
|
70
|
-
| `npx kagemusha list` | Inspect the current definitions, grouped by URL |
|
|
71
|
-
|
|
72
|
-
One page can have multiple screenshots — use `add` with `--id` to stack states (e.g. `dashboard-empty`, `dashboard-with-data`).
|
|
73
|
-
|
|
74
|
-
### 3. Editing capture range + annotations — `edit`
|
|
75
|
-
|
|
76
|
-
```bash
|
|
77
|
-
npx kagemusha edit --id dashboard
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
Opens the real page in a Playwright browser with a toolbar overlay. Two groups of tools:
|
|
81
|
-
|
|
82
|
-
**Capture** — what area to screenshot
|
|
83
|
-
- **📷 Full** — capture the whole page (default)
|
|
84
|
-
- **✂️ Crop** — drag a rectangle on the page; re-drag to replace
|
|
85
|
-
|
|
86
|
-
**Annotate** — decorations drawn on top of the captured image
|
|
87
|
-
- **▭ Rect / → Arrow / T Label** — drag or click to place; drag existing ones to move; Delete to remove
|
|
88
|
-
|
|
89
|
-
Hit **💾 Save** — both capture range and decorations are written back to `.kagemusha/definitions.json`. The same editor restores everything on next open, so adjusting is iterative.
|
|
90
|
-
|
|
91
|
-
### 4. Capture — the only verb you need
|
|
92
|
-
|
|
93
|
-
```bash
|
|
94
|
-
npx kagemusha capture # capture, diff, push changed/new to canonical
|
|
95
|
-
npx kagemusha capture --dry-run # preview only, no canonical update
|
|
96
|
-
npx kagemusha capture --ids a,b # only those IDs
|
|
97
|
-
npx kagemusha capture --threshold 0.001 # 0.1% pixel diff = flagged
|
|
98
|
-
npx kagemusha capture --open # open changed/new results in default viewer
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
What happens:
|
|
102
|
-
|
|
103
|
-
1. Captures fresh screenshots (with annotations) into `.kagemusha/.staging/` (internal, git-ignored)
|
|
104
|
-
2. **Pulls canonical** from the configured destination:
|
|
105
|
-
- `s3` mode: downloads `<id>/latest.png` from S3 into your local `outputDir/` (= the working mirror)
|
|
106
|
-
- `local` mode: reads `outputDir/<id>.png` directly
|
|
107
|
-
3. Diffs each staging file against canonical using [pixelmatch](https://github.com/mapbox/pixelmatch)
|
|
108
|
-
4. Writes diff visualizations to `reports/diff/<id>.diff.png` for changed files
|
|
109
|
-
5. **Default**: for changed/new files only, pushes staging → S3 (or copies into local `outputDir/`). Unchanged files are left alone (history snapshots `<id>/<timestamp>.png` keep prior versions for rollback)
|
|
110
|
-
6. **With `--dry-run`**: nothing is published — exit code 1 if any pixel-diff is over threshold (CI gate use case)
|
|
111
|
-
|
|
112
|
-
Output (default — push happened):
|
|
113
|
-
|
|
114
|
-
```
|
|
115
|
-
🥷 Kagemusha — Capture
|
|
116
|
-
canonical: https://kagemusha.example.com
|
|
117
|
-
|
|
118
|
-
📸 Capturing 3 screenshot(s) to staging...
|
|
119
|
-
|
|
120
|
-
✓ engagements-overview
|
|
121
|
-
~ admin-groups (2.34%) → updated
|
|
122
|
-
↳ reports/diff/admin-groups.diff.png
|
|
123
|
-
+ new-page (added to canonical)
|
|
124
|
-
|
|
125
|
-
changed: 1 / unchanged: 1 / new: 1
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
Output (`--dry-run`):
|
|
129
|
-
|
|
130
|
-
```
|
|
131
|
-
🥷 Kagemusha — Capture (dry-run)
|
|
132
|
-
canonical: https://kagemusha.example.com
|
|
133
|
-
|
|
134
|
-
✓ engagements-overview
|
|
135
|
-
~ admin-groups (2.34%) → would update
|
|
136
|
-
+ new-page (would be added)
|
|
137
|
-
|
|
138
|
-
changed: 1 / unchanged: 1 / new: 1
|
|
139
|
-
|
|
140
|
-
Drop --dry-run to update canonical (https://kagemusha.example.com).
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
**Single canonical, kept out of git:**
|
|
144
|
-
|
|
145
|
-
- For `s3` destination: S3 IS the truth. Local `outputDir/` is just a download mirror, git-ignored
|
|
146
|
-
- For `local` destination: `outputDir/` is the truth, also git-ignored — use it for local testing only
|
|
147
|
-
- No `baselines/` directory; the canonical store (S3 or outputDir) IS the baseline. S3 history snapshots are kept as `<id>/<timestamp>.png` for rollback
|
|
56
|
+
`kagemusha.config.yaml`:
|
|
148
57
|
|
|
58
|
+
```yaml
|
|
59
|
+
app:
|
|
60
|
+
baseUrl: https://your-app.example.com
|
|
61
|
+
screenshot:
|
|
62
|
+
defaultViewport: { width: 1440, height: 900, deviceScaleFactor: 2 }
|
|
63
|
+
defaultDiffThreshold: 0.005 # 0.5% pixel diff = flagged
|
|
64
|
+
publish:
|
|
65
|
+
destination: s3 # or "local" for testing
|
|
66
|
+
cdnBucket: your-bucket
|
|
67
|
+
cdnBaseUrl: https://your-bucket.s3.ap-northeast-1.amazonaws.com
|
|
149
68
|
```
|
|
150
|
-
<outputDir>/ # local working mirror (git-ignored)
|
|
151
|
-
.kagemusha/.staging/ # internal capture staging (git-ignored)
|
|
152
|
-
reports/diff/ # diff visualizations (git-ignored)
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
`init` adds these to `.gitignore` automatically.
|
|
156
|
-
|
|
157
|
-
## Commands
|
|
158
|
-
|
|
159
|
-
| Command | Description |
|
|
160
|
-
|---------|-------------|
|
|
161
|
-
| `kagemusha init` | Interactive setup (config + login + discover + workflow) |
|
|
162
|
-
| `kagemusha login` | Open browser and save login session |
|
|
163
|
-
| `kagemusha discover` | Re-crawl the app and add newly found pages |
|
|
164
|
-
| `kagemusha add <path>` | Add a single screenshot definition |
|
|
165
|
-
| `kagemusha list` | List all definitions, grouped by URL |
|
|
166
|
-
| `kagemusha edit --id <id>` | Open the visual editor (capture range + annotations) |
|
|
167
|
-
| `kagemusha capture` | Capture, diff vs canonical, push changed/new (use `--dry-run` to preview) |
|
|
168
|
-
| `kagemusha validate` | Validate config and definition files |
|
|
169
|
-
| `kagemusha publish` | Publish to Intercom / Zendesk (coming soon) |
|
|
170
|
-
|
|
171
|
-
## Definition example
|
|
172
69
|
|
|
173
|
-
`.kagemusha/definitions.json`
|
|
70
|
+
Definition (`.kagemusha/definitions.json`):
|
|
174
71
|
|
|
175
72
|
```json
|
|
176
73
|
[
|
|
177
74
|
{
|
|
178
75
|
"id": "dashboard",
|
|
179
|
-
"name": "dashboard",
|
|
180
76
|
"url": "/dashboard",
|
|
181
77
|
"capture": { "mode": "fullPage" },
|
|
182
78
|
"hideElements": [".intercom-launcher"],
|
|
183
79
|
"decorations": [
|
|
184
|
-
{
|
|
185
|
-
"type": "rect",
|
|
186
|
-
"target": { "x": 32, "y": 120, "width": 310, "height": 120 },
|
|
187
|
-
"style": { "color": "#FF0000", "strokeWidth": 2 }
|
|
188
|
-
}
|
|
80
|
+
{ "type": "rect", "target": { "x": 32, "y": 120, "width": 310, "height": 120 } }
|
|
189
81
|
]
|
|
190
|
-
},
|
|
191
|
-
{
|
|
192
|
-
"id": "dashboard-hero",
|
|
193
|
-
"url": "/dashboard",
|
|
194
|
-
"capture": {
|
|
195
|
-
"mode": "crop",
|
|
196
|
-
"crop": { "start": { "x": 0, "y": 0 }, "end": { "x": 1280, "y": 400 } }
|
|
197
|
-
},
|
|
198
|
-
"decorations": []
|
|
199
82
|
}
|
|
200
83
|
]
|
|
201
84
|
```
|
|
202
85
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
## Deploying to GitHub Actions
|
|
206
|
-
|
|
207
|
-
To run kagemusha in CI, you need to set up secrets and verify your workflow file.
|
|
208
|
-
|
|
209
|
-
### 1. Workflow file
|
|
210
|
-
|
|
211
|
-
`kagemusha init` generates `.github/workflows/kagemusha.yml`. If you've already initialized, verify it exists. For monorepos where kagemusha config lives in a subdirectory, add `defaults.run.working-directory: <subdir>` to the job.
|
|
212
|
-
|
|
213
|
-
### 2. AWS credentials (for S3 publish)
|
|
214
|
-
|
|
215
|
-
The IAM identity (user or role) needs `s3:GetObject` / `s3:PutObject` on your bucket. Pick one auth strategy:
|
|
216
|
-
|
|
217
|
-
**A. Long-lived access keys** (simpler)
|
|
218
|
-
|
|
219
|
-
\`\`\`bash
|
|
220
|
-
gh secret set AWS_ACCESS_KEY_ID --body "AKIA..."
|
|
221
|
-
gh secret set AWS_SECRET_ACCESS_KEY --body "..."
|
|
222
|
-
\`\`\`
|
|
223
|
-
|
|
224
|
-
**B. OIDC** (recommended for orgs with established trust policies)
|
|
86
|
+
Run `kagemusha edit --id dashboard` to set the crop range and add decorations visually.
|
|
225
87
|
|
|
226
|
-
|
|
88
|
+
## Avoiding loading-state screenshots
|
|
227
89
|
|
|
228
|
-
|
|
229
|
-
permissions:
|
|
230
|
-
id-token: write
|
|
231
|
-
contents: read
|
|
90
|
+
After `page.goto` kagemusha waits for `load` event + 3s of best-effort `networkidle` + 500ms hydration buffer. This handles most pages; SPAs with component-level skeletons may still capture mid-loading. Add a `beforeCapture` step per definition:
|
|
232
91
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
### 3. Login credentials (if your app needs auth)
|
|
243
|
-
|
|
244
|
-
If you wrote a `.kagemusha/login.mjs` that reads `process.env.EMAIL` / `PASSWORD`:
|
|
245
|
-
|
|
246
|
-
\`\`\`bash
|
|
247
|
-
gh secret set EMAIL --body "ci-bot@example.com"
|
|
248
|
-
gh secret set PASSWORD --body "..."
|
|
249
|
-
\`\`\`
|
|
250
|
-
|
|
251
|
-
Pass them to the workflow step (already templated in `kagemusha init`):
|
|
252
|
-
|
|
253
|
-
\`\`\`yaml
|
|
254
|
-
- run: npx kagemusha capture
|
|
255
|
-
env:
|
|
256
|
-
EMAIL: \${{ secrets.EMAIL }}
|
|
257
|
-
PASSWORD: \${{ secrets.PASSWORD }}
|
|
258
|
-
\`\`\`
|
|
259
|
-
|
|
260
|
-
For SSO / MFA cases where login can't be scripted, fall back to `KAGEMUSHA_STORAGE_STATE` (see Authentication section below).
|
|
261
|
-
|
|
262
|
-
### 4. AWS region
|
|
263
|
-
|
|
264
|
-
Auto-detected from `publish.cdnBaseUrl` (e.g. `https://bucket.s3.ap-northeast-1.amazonaws.com` → `ap-northeast-1`). No explicit `AWS_REGION` env needed.
|
|
265
|
-
|
|
266
|
-
### 5. First-time test
|
|
267
|
-
|
|
268
|
-
Run the workflow manually before relying on the auto trigger:
|
|
269
|
-
|
|
270
|
-
\`\`\`bash
|
|
271
|
-
gh workflow run "Kagemusha - Screenshot Update"
|
|
272
|
-
gh run watch
|
|
273
|
-
\`\`\`
|
|
274
|
-
|
|
275
|
-
If it fails, check:
|
|
276
|
-
- `gh run view --log-failed` — see step-level errors
|
|
277
|
-
- AWS auth: workflow logs should show `Configure AWS credentials` succeeding
|
|
278
|
-
- Login: friendly errors from `aws-error.ts` (`✗ AWS authentication failed`)
|
|
279
|
-
- Login script: errors from `.kagemusha/login.mjs` will surface in the capture step
|
|
280
|
-
|
|
281
|
-
## Authentication for login-required apps
|
|
282
|
-
|
|
283
|
-
There are two ways to handle login:
|
|
284
|
-
|
|
285
|
-
### Local dev: passing env vars
|
|
286
|
-
|
|
287
|
-
kagemusha doesn't auto-load `.env`. Pick whichever fits your project:
|
|
288
|
-
|
|
289
|
-
```bash
|
|
290
|
-
# Direct shell export
|
|
291
|
-
export EMAIL=demo@example.com
|
|
292
|
-
export PASSWORD=local-dev-password
|
|
293
|
-
npx kagemusha capture
|
|
294
|
-
|
|
295
|
-
# Or wrap with dotenv-cli (works with your existing .env)
|
|
296
|
-
npx dotenv -e .env -- kagemusha capture
|
|
297
|
-
|
|
298
|
-
# Or Node 20.6+ built-in
|
|
299
|
-
node --env-file=.env $(which kagemusha) capture
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"id": "analytics-overview",
|
|
95
|
+
"url": "/analytics/overview",
|
|
96
|
+
"beforeCapture": [
|
|
97
|
+
{ "action": "waitForSelector", "selector": "text=Overview", "timeout": 15000 },
|
|
98
|
+
{ "action": "wait", "ms": 3000 }
|
|
99
|
+
]
|
|
100
|
+
}
|
|
300
101
|
```
|
|
301
102
|
|
|
302
|
-
|
|
103
|
+
Wait for a known page-specific element (page title, chart canvas, first table row, etc.), then a short buffer. Playwright's `text=` selector matches any rendered text.
|
|
303
104
|
|
|
304
|
-
|
|
105
|
+
## Authentication
|
|
305
106
|
|
|
306
|
-
|
|
107
|
+
If your app needs login, `init` generates a `.kagemusha/login.mjs` skeleton:
|
|
307
108
|
|
|
308
109
|
```js
|
|
309
110
|
/** @param {import('playwright-chromium').Page} page */
|
|
310
111
|
export const login = async (page) => {
|
|
311
112
|
await page.goto("/login");
|
|
312
|
-
|
|
313
|
-
await page.fill('input[name="
|
|
113
|
+
// Pick env names that fit your project (NOT EMAIL/PASSWORD, NOT KAGEMUSHA_*).
|
|
114
|
+
await page.fill('input[name="email"]', process.env.MY_APP_EMAIL ?? "");
|
|
115
|
+
await page.fill('input[name="password"]', process.env.MY_APP_PASSWORD ?? "");
|
|
314
116
|
await page.click('button[type="submit"]');
|
|
315
117
|
await page.waitForURL((url) => !url.pathname.startsWith("/login"));
|
|
316
118
|
};
|
|
317
119
|
```
|
|
318
120
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
More variations:
|
|
322
|
-
|
|
323
|
-
```js
|
|
324
|
-
// HTTP Basic Auth — no script needed, set in kagemusha.config.yaml:
|
|
325
|
-
// auth:
|
|
326
|
-
// httpCredentials: { username: ..., password: ... }
|
|
327
|
-
// (coming soon)
|
|
328
|
-
|
|
329
|
-
// Token-based:
|
|
330
|
-
export const login = async (page) => {
|
|
331
|
-
await page.context().setExtraHTTPHeaders({
|
|
332
|
-
Authorization: \`Bearer \${process.env.TOKEN}\`,
|
|
333
|
-
});
|
|
334
|
-
};
|
|
335
|
-
|
|
336
|
-
// Multi-step (e.g. email → check inbox → magic link):
|
|
337
|
-
export const login = async (page) => {
|
|
338
|
-
await page.goto("/login");
|
|
339
|
-
await page.fill('input[name="email"]', process.env.EMAIL);
|
|
340
|
-
await page.click('button[type="submit"]');
|
|
341
|
-
// ...fetch the link from a test mailbox API, then navigate to it...
|
|
342
|
-
};
|
|
343
|
-
```
|
|
121
|
+
`kagemusha capture` auto-runs this on first invocation when no saved session exists, so CI just needs `npx kagemusha capture` — no separate login step. `baseURL` is set from your config, so relative paths work.
|
|
344
122
|
|
|
345
|
-
|
|
123
|
+
For SSO / MFA / OAuth where scripting is impossible: run `kagemusha login` locally to save `.kagemusha/auth-state.json`, then `base64 -i .kagemusha/auth-state.json | pbcopy` and store the result as a `KAGEMUSHA_STORAGE_STATE` GitHub Secret. The generated workflow shows the restore step (commented out).
|
|
346
124
|
|
|
347
|
-
|
|
125
|
+
## Deploying to GitHub Actions
|
|
348
126
|
|
|
349
|
-
|
|
350
|
-
2. Session is saved to `.kagemusha/auth-state.json`
|
|
351
|
-
3. For CI: `base64 -i .kagemusha/auth-state.json | pbcopy` and save as `KAGEMUSHA_STORAGE_STATE` secret
|
|
352
|
-
4. CI workflow restores it before capture (commented snippet in the generated `kagemusha.yml`)
|
|
127
|
+
`init` generates `.github/workflows/kagemusha.yml`. Required secrets:
|
|
353
128
|
|
|
354
|
-
|
|
129
|
+
- `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` — IAM with `s3:GetObject` and `s3:PutObject` on your bucket, or use OIDC with `aws-actions/configure-aws-credentials@v4`
|
|
130
|
+
- Login credentials (named whatever your `login.mjs` reads)
|
|
131
|
+
- `SLACK_WEBHOOK_URL` — optional, see Notifications below
|
|
355
132
|
|
|
356
|
-
|
|
133
|
+
Region is auto-detected from `publish.cdnBaseUrl` (`*.s3.<region>.amazonaws.com`), so no `AWS_REGION` env needed.
|
|
357
134
|
|
|
358
|
-
|
|
359
|
-
name: Kagemusha
|
|
360
|
-
on:
|
|
361
|
-
pull_request:
|
|
362
|
-
types: [closed]
|
|
363
|
-
branches: [main]
|
|
364
|
-
workflow_dispatch:
|
|
365
|
-
|
|
366
|
-
# Serialize runs so two merges can't race the S3 canonical
|
|
367
|
-
concurrency:
|
|
368
|
-
group: kagemusha
|
|
369
|
-
cancel-in-progress: false
|
|
370
|
-
|
|
371
|
-
jobs:
|
|
372
|
-
update-screenshots:
|
|
373
|
-
if: github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch'
|
|
374
|
-
runs-on: ubuntu-latest
|
|
375
|
-
steps:
|
|
376
|
-
- uses: actions/checkout@v4
|
|
377
|
-
- uses: actions/setup-node@v4
|
|
378
|
-
with:
|
|
379
|
-
node-version: 20
|
|
380
|
-
- run: npm ci
|
|
381
|
-
- run: npx playwright install chromium
|
|
382
|
-
|
|
383
|
-
# capture auto-runs .kagemusha/login.mjs (if present) on first invocation,
|
|
384
|
-
# then pulls canonical from S3, diffs, pushes only what changed.
|
|
385
|
-
- run: npx kagemusha capture
|
|
386
|
-
env:
|
|
387
|
-
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
388
|
-
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
389
|
-
EMAIL: ${{ secrets.EMAIL }}
|
|
390
|
-
PASSWORD: ${{ secrets.PASSWORD }}
|
|
391
|
-
|
|
392
|
-
# Optional: keep diff visualizations as artifacts for later review
|
|
393
|
-
- if: always()
|
|
394
|
-
uses: actions/upload-artifact@v4
|
|
395
|
-
with:
|
|
396
|
-
name: kagemusha-diffs
|
|
397
|
-
path: reports/diff/
|
|
398
|
-
if-no-files-found: ignore
|
|
399
|
-
```
|
|
135
|
+
The workflow triggers on `push: main` and runs `kagemusha capture` automatically.
|
|
400
136
|
|
|
401
137
|
## Notifications
|
|
402
138
|
|
|
403
|
-
`kagemusha capture` writes
|
|
404
|
-
|
|
405
|
-
### Public API: `reports/summary.json`
|
|
406
|
-
|
|
407
|
-
Schema versioned and **part of kagemusha's public API** — additive changes go in minor releases, removals/renames in majors.
|
|
139
|
+
`kagemusha capture` writes `reports/summary.json` with before/after URLs (when destination is S3 and a real push happened):
|
|
408
140
|
|
|
409
141
|
```json
|
|
410
142
|
{
|
|
411
143
|
"schemaVersion": "1",
|
|
412
|
-
"timestamp": "2026-05-
|
|
144
|
+
"timestamp": "2026-05-15T12:34:56.789Z",
|
|
413
145
|
"dryRun": false,
|
|
414
|
-
"canonical": "https://
|
|
146
|
+
"canonical": "https://your-bucket.s3.ap-northeast-1.amazonaws.com",
|
|
415
147
|
"counts": { "changed": 1, "unchanged": 5, "new": 2, "missing": 0 },
|
|
416
148
|
"results": [
|
|
417
|
-
{
|
|
418
|
-
|
|
419
|
-
|
|
149
|
+
{
|
|
150
|
+
"id": "engagements-overview",
|
|
151
|
+
"status": "changed",
|
|
152
|
+
"reason": "pixel-diff",
|
|
153
|
+
"diffPercentage": 2.34,
|
|
154
|
+
"urls": {
|
|
155
|
+
"before": "https://.../engagements-overview/previous.png",
|
|
156
|
+
"after": "https://.../engagements-overview/latest.png"
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
{ "id": "new-page", "status": "new", "urls": { "after": "https://.../new-page/latest.png" } },
|
|
420
160
|
{ "id": "dashboard", "status": "unchanged" }
|
|
421
161
|
]
|
|
422
162
|
}
|
|
423
163
|
```
|
|
424
164
|
|
|
425
|
-
|
|
165
|
+
The schema is **part of kagemusha's public API** — additive changes stay on `schemaVersion: "1"`, removals/renames bump it. kagemusha intentionally does not publish a pre-rendered diff image; consumers compare `before` vs `after` raw images (= Slack auto-unfurls both side by side).
|
|
426
166
|
|
|
427
|
-
|
|
167
|
+
Slack notification (the generated workflow includes this; just set `SLACK_WEBHOOK_URL`):
|
|
428
168
|
|
|
429
169
|
```yaml
|
|
430
|
-
- run: npx kagemusha capture
|
|
431
|
-
|
|
432
170
|
- name: Slack notify
|
|
433
|
-
if: always()
|
|
434
|
-
run: |
|
|
435
|
-
[ -f reports/summary.json ] || exit 0
|
|
436
|
-
BODY=$(jq -r '
|
|
437
|
-
[.results[] | select(.status == "changed" or .status == "new")] as $items
|
|
438
|
-
| if ($items | length) == 0 then empty
|
|
439
|
-
else
|
|
440
|
-
"📸 *kagemusha*: \($items | length) screenshot(s)\n" +
|
|
441
|
-
($items | map(
|
|
442
|
-
if .status == "changed" then
|
|
443
|
-
"~ \(.id) (\(.diffPercentage // .reason))"
|
|
444
|
-
else
|
|
445
|
-
"+ \(.id) (new)"
|
|
446
|
-
end
|
|
447
|
-
) | join("\n"))
|
|
448
|
-
end
|
|
449
|
-
' reports/summary.json)
|
|
450
|
-
[ -n "$BODY" ] || exit 0
|
|
451
|
-
curl -X POST "$SLACK_WEBHOOK_URL" \
|
|
452
|
-
-H 'Content-Type: application/json' \
|
|
453
|
-
-d "$(jq -n --arg t "$BODY" '{text: $t}')"
|
|
454
171
|
env:
|
|
455
172
|
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
|
456
|
-
```
|
|
457
|
-
|
|
458
|
-
`[ -n "$BODY" ] || exit 0` makes the step a no-op when nothing changed. The generated workflow template includes this commented out — uncomment + set the secret to enable.
|
|
459
|
-
|
|
460
|
-
### Example: Discord (same payload, different key)
|
|
461
|
-
|
|
462
|
-
```yaml
|
|
463
|
-
- name: Discord notify
|
|
464
|
-
if: always()
|
|
465
173
|
run: |
|
|
466
|
-
[ -
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
|
174
|
+
[ -n "$SLACK_WEBHOOK_URL" ] || exit 0
|
|
175
|
+
jq -c -f .kagemusha/notify-slack.jq reports/summary.json | while IFS= read -r payload; do
|
|
176
|
+
[ -z "$payload" ] && continue
|
|
177
|
+
curl -sS -X POST "$SLACK_WEBHOOK_URL" \
|
|
178
|
+
-H 'Content-Type: application/json' \
|
|
179
|
+
--data "$payload"
|
|
180
|
+
done
|
|
474
181
|
```
|
|
475
182
|
|
|
476
|
-
|
|
183
|
+
One message per changed/new screenshot — Slack unfurls the `before`/`after` URLs per-message, so each post shows its own image previews instead of a single message that just lists URLs as plain text.
|
|
477
184
|
|
|
478
|
-
|
|
479
|
-
- name: PR comment
|
|
480
|
-
if: github.event_name == 'pull_request'
|
|
481
|
-
uses: actions/github-script@v7
|
|
482
|
-
with:
|
|
483
|
-
script: |
|
|
484
|
-
const fs = require('fs');
|
|
485
|
-
if (!fs.existsSync('reports/summary.json')) return;
|
|
486
|
-
const r = JSON.parse(fs.readFileSync('reports/summary.json', 'utf8'));
|
|
487
|
-
const items = r.results.filter(x => x.status === 'changed' || x.status === 'new');
|
|
488
|
-
if (items.length === 0) return;
|
|
489
|
-
const body = `## 📸 kagemusha\n\n` + items.map(x =>
|
|
490
|
-
x.status === 'changed' ? `- ~ \`${x.id}\` (${x.diffPercentage?.toFixed(2) ?? x.reason}%)` : `- + \`${x.id}\` (new)`
|
|
491
|
-
).join('\n');
|
|
492
|
-
await github.rest.issues.createComment({
|
|
493
|
-
...context.repo,
|
|
494
|
-
issue_number: context.issue.number,
|
|
495
|
-
body,
|
|
496
|
-
});
|
|
497
|
-
```
|
|
498
|
-
|
|
499
|
-
### Local debugging
|
|
500
|
-
|
|
501
|
-
`reports/summary.json` is written every run, including local. Inspect with:
|
|
185
|
+
The message format is defined in `.kagemusha/notify-slack.jq` (generated by `kagemusha init`). Each line of jq output becomes one Slack `chat.postMessage` body, so you can customize freely (add `blocks`, override `channel`, etc). Test locally:
|
|
502
186
|
|
|
503
187
|
```bash
|
|
504
|
-
|
|
505
|
-
cat reports/summary.json | jq '.results[] | select(.status == "changed")'
|
|
188
|
+
jq -c -f .kagemusha/notify-slack.jq reports/summary.json
|
|
506
189
|
```
|
|
507
190
|
|
|
191
|
+
The same `summary.json` works for Discord (swap `text` → `content`) or PR comments via `actions/github-script` — copy the jq file and tweak.
|
|
192
|
+
|
|
508
193
|
## Positioning
|
|
509
194
|
|
|
510
195
|
**kagemusha is NOT a PR-gating VRT tool.** For per-commit baselines and PR diff review with hosted HTML reports, use [reg-suit](https://github.com/reg-viz/reg-suit), [Chromatic](https://www.chromatic.com/), or [Percy](https://percy.io/).
|
|
511
196
|
|
|
512
|
-
kagemusha is for **post-merge auto-update of help center screenshots**, where
|
|
513
|
-
- Stable embeddable URLs matter more than per-commit baseline correctness
|
|
514
|
-
- One config + one command should cover capture, diff, and publish
|
|
515
|
-
- The canonical IS the served asset — no separate baseline-vs-published split
|
|
516
|
-
|
|
517
|
-
## Try it locally
|
|
518
|
-
|
|
519
|
-
```bash
|
|
520
|
-
cd example
|
|
521
|
-
pnpm install # or `npm install`, or `bun install`
|
|
522
|
-
pnpm run serve # Start sample app
|
|
523
|
-
npx kagemusha init # Set up kagemusha
|
|
524
|
-
npx kagemusha capture --open
|
|
525
|
-
```
|
|
526
|
-
|
|
527
|
-
## Roadmap
|
|
528
|
-
|
|
529
|
-
- [x] Screenshot capture with Playwright
|
|
530
|
-
- [x] Annotations (rect, arrow, label)
|
|
531
|
-
- [x] S3 upload with stable URLs
|
|
532
|
-
- [x] Auto-discover pages (SPA-aware BFS crawl)
|
|
533
|
-
- [x] Login via browser (`storageState`) + scripted CI auto-login (`.kagemusha/login.mjs`)
|
|
534
|
-
- [x] Visual editor for capture range (fullPage / crop)
|
|
535
|
-
- [x] **Visual regression — unified `capture` command (publish by default, `--dry-run` to preview)**
|
|
536
|
-
- [ ] HTML diff report (side-by-side, hosted as CI artifact)
|
|
537
|
-
- [ ] Stabilization helpers (clock freezing, animation off, mask regions)
|
|
538
|
-
- [ ] Slack / PR notifications with affected article IDs
|
|
539
|
-
- [ ] Intercom / Zendesk auto-patching
|
|
540
|
-
- [ ] LLM-powered diff descriptions ("what changed in plain English")
|
|
197
|
+
kagemusha is for **post-merge auto-update of help center screenshots**, where stable embeddable URLs matter more than per-commit baseline correctness.
|
|
541
198
|
|
|
542
199
|
## Releasing
|
|
543
200
|
|
|
544
|
-
|
|
201
|
+
Automated via [release-please](https://github.com/googleapis/release-please).
|
|
545
202
|
|
|
546
|
-
1.
|
|
547
|
-
|
|
548
|
-
-
|
|
549
|
-
-
|
|
550
|
-
-
|
|
551
|
-
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
- `pnpm publish --provenance` to npm (uses `NPM_TOKEN` secret)
|
|
556
|
-
|
|
557
|
-
Or via CLI:
|
|
558
|
-
|
|
559
|
-
```bash
|
|
560
|
-
gh release create v0.2.0 --generate-notes
|
|
561
|
-
```
|
|
203
|
+
1. **Write PRs with [Conventional Commits](https://www.conventionalcommits.org/) titles**:
|
|
204
|
+
- `feat: ...` → minor bump (= 0.2.0 → 0.3.0)
|
|
205
|
+
- `fix: ...` → patch bump (= 0.2.0 → 0.2.1)
|
|
206
|
+
- `feat!: ...` or `BREAKING CHANGE:` in body → major (= 1.0.0+)
|
|
207
|
+
- `chore: ...` / `docs: ...` → no version bump
|
|
208
|
+
- Squash merge propagates the PR title as the commit on `main`.
|
|
209
|
+
2. **release-please opens a "Release PR" automatically** whenever there's anything to release. It bumps `package.json`, updates `.release-please-manifest.json`, and regenerates `CHANGELOG.md`.
|
|
210
|
+
3. **Merge the Release PR** → release-please tags `v0.X.Y` and publishes a GitHub Release.
|
|
211
|
+
4. **`release.yml` triggers on `release: published`** → builds and runs `pnpm publish --provenance` to npm. Requires the `NPM_TOKEN` secret.
|
|
562
212
|
|
|
563
|
-
|
|
213
|
+
The `kagemusha` CLI reads its version from `package.json` at runtime, so release-please only needs to touch one file.
|
|
564
214
|
|
|
565
215
|
## License
|
|
566
216
|
|