astroshot 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/README.md +87 -0
- package/bin/astroshot.mjs +71 -0
- package/node_modules/@archastro/astroshot/LICENSE +21 -0
- package/node_modules/@archastro/astroshot/README.md +96 -0
- package/node_modules/@archastro/astroshot/bin/astroshot.mjs +276 -0
- package/node_modules/@archastro/astroshot/bin/demo.mjs +275 -0
- package/node_modules/@archastro/astroshot/bin/doctor.mjs +492 -0
- package/node_modules/@archastro/astroshot/bin/mac-preferences.mjs +346 -0
- package/node_modules/@archastro/astroshot/bin/templates.mjs +186 -0
- package/node_modules/@archastro/astroshot/fixtures/demo/fixtures.json +42 -0
- package/node_modules/@archastro/astroshot/fixtures/demo/journey.png +0 -0
- package/node_modules/@archastro/astroshot/fixtures/demo/journey.webm +0 -0
- package/node_modules/@archastro/astroshot/fixtures/demo/next-steps.png +0 -0
- package/node_modules/@archastro/astroshot/fixtures/demo/welcome.png +0 -0
- package/node_modules/@archastro/astroshot/ink.d.ts +5 -0
- package/node_modules/@archastro/astroshot/ink.js +1 -0
- package/node_modules/@archastro/astroshot/package.json +79 -0
- package/node_modules/@archastro/astroshot/pty.d.ts +8 -0
- package/node_modules/@archastro/astroshot/pty.js +1 -0
- package/node_modules/@archastro/astroshot/react.d.ts +1 -0
- package/node_modules/@archastro/astroshot/react.js +1 -0
- package/node_modules/@archastro/astroshot/tui.d.ts +1 -0
- package/node_modules/@archastro/astroshot/tui.js +1 -0
- package/node_modules/@archastro/movie-harness/README.md +138 -0
- package/node_modules/@archastro/movie-harness/bin/astroshot-movie.mjs +5 -0
- package/node_modules/@archastro/movie-harness/dist/cli.d.ts +1 -0
- package/node_modules/@archastro/movie-harness/dist/cli.js +389 -0
- package/node_modules/@archastro/movie-harness/dist/encode.d.ts +14 -0
- package/node_modules/@archastro/movie-harness/dist/encode.js +184 -0
- package/node_modules/@archastro/movie-harness/dist/index.d.ts +14 -0
- package/node_modules/@archastro/movie-harness/dist/index.js +11 -0
- package/node_modules/@archastro/movie-harness/dist/paths.d.ts +9 -0
- package/node_modules/@archastro/movie-harness/dist/paths.js +57 -0
- package/node_modules/@archastro/movie-harness/dist/png.d.ts +8 -0
- package/node_modules/@archastro/movie-harness/dist/png.js +79 -0
- package/node_modules/@archastro/movie-harness/dist/session.d.ts +45 -0
- package/node_modules/@archastro/movie-harness/dist/session.js +170 -0
- package/node_modules/@archastro/movie-harness/dist/sink.d.ts +7 -0
- package/node_modules/@archastro/movie-harness/dist/sink.js +113 -0
- package/node_modules/@archastro/movie-harness/dist/source-help.d.ts +29 -0
- package/node_modules/@archastro/movie-harness/dist/source-help.js +222 -0
- package/node_modules/@archastro/movie-harness/dist/sources/browser.d.ts +7 -0
- package/node_modules/@archastro/movie-harness/dist/sources/browser.js +87 -0
- package/node_modules/@archastro/movie-harness/dist/sources/desktop-macos.d.ts +110 -0
- package/node_modules/@archastro/movie-harness/dist/sources/desktop-macos.js +505 -0
- package/node_modules/@archastro/movie-harness/dist/sources/frames-store.d.ts +19 -0
- package/node_modules/@archastro/movie-harness/dist/sources/frames-store.js +165 -0
- package/node_modules/@archastro/movie-harness/dist/sources/pty.d.ts +15 -0
- package/node_modules/@archastro/movie-harness/dist/sources/pty.js +330 -0
- package/node_modules/@archastro/movie-harness/dist/terminal-paint.d.ts +27 -0
- package/node_modules/@archastro/movie-harness/dist/terminal-paint.js +190 -0
- package/node_modules/@archastro/movie-harness/dist/types.d.ts +140 -0
- package/node_modules/@archastro/movie-harness/dist/types.js +1 -0
- package/node_modules/@archastro/movie-harness/native/macos/WindowTools.swift +164 -0
- package/node_modules/@archastro/movie-harness/package.json +67 -0
- package/node_modules/@archastro/react-shot/LICENSE +21 -0
- package/node_modules/@archastro/react-shot/README.md +181 -0
- package/node_modules/@archastro/react-shot/bin/react-shot.mjs +2 -0
- package/node_modules/@archastro/react-shot/dist/batch-paths.d.ts +4 -0
- package/node_modules/@archastro/react-shot/dist/batch-paths.d.ts.map +1 -0
- package/node_modules/@archastro/react-shot/dist/batch-paths.js +34 -0
- package/node_modules/@archastro/react-shot/dist/batch-paths.js.map +1 -0
- package/node_modules/@archastro/react-shot/dist/cli.d.ts +3 -0
- package/node_modules/@archastro/react-shot/dist/cli.d.ts.map +1 -0
- package/node_modules/@archastro/react-shot/dist/cli.js +217 -0
- package/node_modules/@archastro/react-shot/dist/cli.js.map +1 -0
- package/node_modules/@archastro/react-shot/dist/config.d.ts +5 -0
- package/node_modules/@archastro/react-shot/dist/config.d.ts.map +1 -0
- package/node_modules/@archastro/react-shot/dist/config.js +75 -0
- package/node_modules/@archastro/react-shot/dist/config.js.map +1 -0
- package/node_modules/@archastro/react-shot/dist/create-server.d.ts +13 -0
- package/node_modules/@archastro/react-shot/dist/create-server.d.ts.map +1 -0
- package/node_modules/@archastro/react-shot/dist/create-server.js +254 -0
- package/node_modules/@archastro/react-shot/dist/create-server.js.map +1 -0
- package/node_modules/@archastro/react-shot/dist/index.d.ts +4 -0
- package/node_modules/@archastro/react-shot/dist/index.d.ts.map +1 -0
- package/node_modules/@archastro/react-shot/dist/index.js +3 -0
- package/node_modules/@archastro/react-shot/dist/index.js.map +1 -0
- package/node_modules/@archastro/react-shot/dist/meta.d.ts +9 -0
- package/node_modules/@archastro/react-shot/dist/meta.d.ts.map +1 -0
- package/node_modules/@archastro/react-shot/dist/meta.js +23 -0
- package/node_modules/@archastro/react-shot/dist/meta.js.map +1 -0
- package/node_modules/@archastro/react-shot/dist/shot.d.ts +4 -0
- package/node_modules/@archastro/react-shot/dist/shot.d.ts.map +1 -0
- package/node_modules/@archastro/react-shot/dist/shot.js +259 -0
- package/node_modules/@archastro/react-shot/dist/shot.js.map +1 -0
- package/node_modules/@archastro/react-shot/dist/stubs.d.ts +6 -0
- package/node_modules/@archastro/react-shot/dist/stubs.d.ts.map +1 -0
- package/node_modules/@archastro/react-shot/dist/stubs.js +49 -0
- package/node_modules/@archastro/react-shot/dist/stubs.js.map +1 -0
- package/node_modules/@archastro/react-shot/dist/types.d.ts +79 -0
- package/node_modules/@archastro/react-shot/dist/types.d.ts.map +1 -0
- package/node_modules/@archastro/react-shot/dist/types.js +2 -0
- package/node_modules/@archastro/react-shot/dist/types.js.map +1 -0
- package/node_modules/@archastro/react-shot/host/index.html +22 -0
- package/node_modules/@archastro/react-shot/package.json +81 -0
- package/node_modules/@archastro/tui-shot/LICENSE +21 -0
- package/node_modules/@archastro/tui-shot/README.md +145 -0
- package/node_modules/@archastro/tui-shot/bin/tui-shot.mjs +23 -0
- package/node_modules/@archastro/tui-shot/dist/batch-paths.d.ts +3 -0
- package/node_modules/@archastro/tui-shot/dist/batch-paths.js +33 -0
- package/node_modules/@archastro/tui-shot/dist/cli.d.ts +2 -0
- package/node_modules/@archastro/tui-shot/dist/cli.js +224 -0
- package/node_modules/@archastro/tui-shot/dist/index.d.ts +3 -0
- package/node_modules/@archastro/tui-shot/dist/index.js +2 -0
- package/node_modules/@archastro/tui-shot/dist/pty-exit-wrapper.d.ts +1 -0
- package/node_modules/@archastro/tui-shot/dist/pty-exit-wrapper.js +50 -0
- package/node_modules/@archastro/tui-shot/dist/pty-shot.d.ts +2 -0
- package/node_modules/@archastro/tui-shot/dist/pty-shot.js +483 -0
- package/node_modules/@archastro/tui-shot/dist/render-ink.d.ts +10 -0
- package/node_modules/@archastro/tui-shot/dist/render-ink.js +87 -0
- package/node_modules/@archastro/tui-shot/dist/shot.d.ts +24 -0
- package/node_modules/@archastro/tui-shot/dist/shot.js +250 -0
- package/node_modules/@archastro/tui-shot/dist/terminal-html.d.ts +14 -0
- package/node_modules/@archastro/tui-shot/dist/terminal-html.js +144 -0
- package/node_modules/@archastro/tui-shot/dist/types.d.ts +81 -0
- package/node_modules/@archastro/tui-shot/dist/types.js +1 -0
- package/node_modules/@archastro/tui-shot/package.json +90 -0
- package/package.json +68 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Window inventory + Screen Recording TCC helpers for desktop.window.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* swift WindowTools.swift list
|
|
6
|
+
* swift WindowTools.swift screen-access # preflight only (JSON)
|
|
7
|
+
* swift WindowTools.swift screen-access --request # may show system prompt
|
|
8
|
+
*/
|
|
9
|
+
import AppKit
|
|
10
|
+
import CoreGraphics
|
|
11
|
+
import Foundation
|
|
12
|
+
|
|
13
|
+
struct WindowRow: Encodable {
|
|
14
|
+
let id: Int
|
|
15
|
+
let pid: Int
|
|
16
|
+
let owner: String
|
|
17
|
+
let title: String
|
|
18
|
+
let bundleId: String?
|
|
19
|
+
let width: Int
|
|
20
|
+
let height: Int
|
|
21
|
+
let x: Int
|
|
22
|
+
let y: Int
|
|
23
|
+
let onScreen: Bool
|
|
24
|
+
/// CGWindow layer (0 = normal). Popovers / floating chrome often use > 0.
|
|
25
|
+
let layer: Int
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
struct ScreenAccessReport: Encodable {
|
|
29
|
+
let granted: Bool
|
|
30
|
+
/// True when we invoked CGRequestScreenCaptureAccess (may have shown a prompt).
|
|
31
|
+
let requested: Bool
|
|
32
|
+
let hostApp: String
|
|
33
|
+
let hostBundleId: String?
|
|
34
|
+
let settingsHint: String
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
func listWindows() -> [WindowRow] {
|
|
38
|
+
// Include off-screen windows so agents can still target them.
|
|
39
|
+
let opts = CGWindowListOption(arrayLiteral: .excludeDesktopElements)
|
|
40
|
+
guard let info = CGWindowListCopyWindowInfo(opts, kCGNullWindowID) as? [[String: Any]]
|
|
41
|
+
else {
|
|
42
|
+
return []
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
var bundleByPid: [Int32: String] = [:]
|
|
46
|
+
for app in NSWorkspace.shared.runningApplications {
|
|
47
|
+
if let bid = app.bundleIdentifier {
|
|
48
|
+
bundleByPid[app.processIdentifier] = bid
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
var rows: [WindowRow] = []
|
|
53
|
+
for w in info {
|
|
54
|
+
let layer = w[kCGWindowLayer as String] as? Int ?? -1
|
|
55
|
+
// Layer 0 = normal windows. Menu-bar trays / NSPopover often sit above 0
|
|
56
|
+
// (floating ~3, modal panel ~8). Skip Dock / main menu / status chrome
|
|
57
|
+
// (typically ≥20) which are huge, blank under screencapture, or unusable.
|
|
58
|
+
guard layer >= 0, layer <= 15 else { continue }
|
|
59
|
+
|
|
60
|
+
let id = w[kCGWindowNumber as String] as? Int ?? 0
|
|
61
|
+
let pid = w[kCGWindowOwnerPID as String] as? Int ?? 0
|
|
62
|
+
let owner = w[kCGWindowOwnerName as String] as? String ?? ""
|
|
63
|
+
let title = w[kCGWindowName as String] as? String ?? ""
|
|
64
|
+
let onScreen = w[kCGWindowIsOnscreen as String] as? Bool ?? false
|
|
65
|
+
let bounds = w[kCGWindowBounds as String] as? [String: Any]
|
|
66
|
+
let width = (bounds?["Width"] as? NSNumber)?.intValue ?? 0
|
|
67
|
+
let height = (bounds?["Height"] as? NSNumber)?.intValue ?? 0
|
|
68
|
+
let x = (bounds?["X"] as? NSNumber)?.intValue ?? 0
|
|
69
|
+
let y = (bounds?["Y"] as? NSNumber)?.intValue ?? 0
|
|
70
|
+
// Popovers can be small; keep a low floor but drop 1×1 placeholders.
|
|
71
|
+
if width < 2 || height < 2 { continue }
|
|
72
|
+
|
|
73
|
+
rows.append(
|
|
74
|
+
WindowRow(
|
|
75
|
+
id: id,
|
|
76
|
+
pid: pid,
|
|
77
|
+
owner: owner,
|
|
78
|
+
title: title,
|
|
79
|
+
bundleId: bundleByPid[Int32(pid)],
|
|
80
|
+
width: width,
|
|
81
|
+
height: height,
|
|
82
|
+
x: x,
|
|
83
|
+
y: y,
|
|
84
|
+
onScreen: onScreen,
|
|
85
|
+
layer: layer
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Prefer larger + on-screen + lower layer so normal windows win over
|
|
91
|
+
// tiny floating helpers when multiple match a bundle id.
|
|
92
|
+
return rows.sorted {
|
|
93
|
+
let area0 = $0.width * $0.height
|
|
94
|
+
let area1 = $1.width * $1.height
|
|
95
|
+
if area0 != area1 { return area0 > area1 }
|
|
96
|
+
if $0.onScreen != $1.onScreen { return $0.onScreen && !$1.onScreen }
|
|
97
|
+
return $0.layer < $1.layer
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
func hostIdentity() -> (name: String, bundleId: String?) {
|
|
102
|
+
let app = NSRunningApplication.current
|
|
103
|
+
let name =
|
|
104
|
+
app.localizedName
|
|
105
|
+
?? ProcessInfo.processInfo.processName
|
|
106
|
+
return (name, app.bundleIdentifier)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
func screenAccess(request: Bool) -> ScreenAccessReport {
|
|
110
|
+
let host = hostIdentity()
|
|
111
|
+
var granted = CGPreflightScreenCaptureAccess()
|
|
112
|
+
var didRequest = false
|
|
113
|
+
if request && !granted {
|
|
114
|
+
// May show the system consent dialog (not always; Settings may still be required).
|
|
115
|
+
granted = CGRequestScreenCaptureAccess()
|
|
116
|
+
didRequest = true
|
|
117
|
+
}
|
|
118
|
+
return ScreenAccessReport(
|
|
119
|
+
granted: granted,
|
|
120
|
+
requested: didRequest,
|
|
121
|
+
hostApp: host.name,
|
|
122
|
+
hostBundleId: host.bundleId,
|
|
123
|
+
settingsHint:
|
|
124
|
+
"System Settings → Privacy & Security → Screen Recording → enable \(host.name), then quit & reopen it"
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
func emitJSON<T: Encodable>(_ value: T) {
|
|
129
|
+
let encoder = JSONEncoder()
|
|
130
|
+
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
|
|
131
|
+
let data = try! encoder.encode(value)
|
|
132
|
+
FileHandle.standardOutput.write(data)
|
|
133
|
+
fputs("\n", stdout)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
let args = Array(CommandLine.arguments.dropFirst())
|
|
137
|
+
let command = args.first ?? "list"
|
|
138
|
+
|
|
139
|
+
if command == "list" || command == "--json" {
|
|
140
|
+
emitJSON(listWindows())
|
|
141
|
+
exit(0)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if command == "screen-access" {
|
|
145
|
+
let request = args.contains("--request") || args.contains("-r")
|
|
146
|
+
let report = screenAccess(request: request)
|
|
147
|
+
emitJSON(report)
|
|
148
|
+
// Exit 0 when granted, 2 when denied (easy for shells).
|
|
149
|
+
exit(report.granted ? 0 : 2)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
fputs(
|
|
153
|
+
"""
|
|
154
|
+
usage:
|
|
155
|
+
WindowTools.swift list
|
|
156
|
+
WindowTools.swift screen-access [--request]
|
|
157
|
+
|
|
158
|
+
list JSON windows for desktop.window matching
|
|
159
|
+
screen-access Screen Recording TCC preflight (optional --request prompt)
|
|
160
|
+
|
|
161
|
+
""",
|
|
162
|
+
stderr
|
|
163
|
+
)
|
|
164
|
+
exit(2)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@archastro/movie-harness",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Universal movie capture harness for browser, PTY, desktop, and raw frames into .astroshot/",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"astroshots",
|
|
7
|
+
"movie",
|
|
8
|
+
"screencast",
|
|
9
|
+
"playwright",
|
|
10
|
+
"pty",
|
|
11
|
+
"harness"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/ArchAstro/astroshots.git",
|
|
17
|
+
"directory": "packages/movie-harness"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://github.com/ArchAstro/astroshots#readme",
|
|
20
|
+
"bugs": "https://github.com/ArchAstro/astroshots/issues",
|
|
21
|
+
"type": "module",
|
|
22
|
+
"main": "./dist/index.js",
|
|
23
|
+
"bin": {
|
|
24
|
+
"astroshot-movie": "bin/astroshot-movie.mjs"
|
|
25
|
+
},
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"import": "./dist/index.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"bin",
|
|
35
|
+
"dist",
|
|
36
|
+
"native",
|
|
37
|
+
"README.md"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsc -p tsconfig.build.json",
|
|
41
|
+
"clean": "rm -rf dist",
|
|
42
|
+
"test": "npm run build && vitest run",
|
|
43
|
+
"typecheck": "tsc --noEmit",
|
|
44
|
+
"prepack": "npm run clean && npm run build"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=22.14.0"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public",
|
|
51
|
+
"provenance": true,
|
|
52
|
+
"registry": "https://registry.npmjs.org/"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"@xterm/headless": "^5.5.0",
|
|
56
|
+
"playwright": "1.61.1",
|
|
57
|
+
"yaml": "^2.8.0"
|
|
58
|
+
},
|
|
59
|
+
"optionalDependencies": {
|
|
60
|
+
"node-pty": "1.2.0-beta.14"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@types/node": "^22.0.0",
|
|
64
|
+
"typescript": "^5.9.0",
|
|
65
|
+
"vitest": "^4.1.0"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ArchAstro
|
|
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.
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# @archastro/react-shot
|
|
2
|
+
|
|
3
|
+
Capture deterministic PNG screenshots from small React or TSX fixtures. The
|
|
4
|
+
CLI starts an isolated Vite page, renders the fixture in Chromium with
|
|
5
|
+
Playwright, and captures either a CSS selector or the full page.
|
|
6
|
+
|
|
7
|
+
This package is the React rendering engine. Use the unified
|
|
8
|
+
`@archastro/astroshot` package for command-line capture.
|
|
9
|
+
|
|
10
|
+
Use this for component documentation, release assets, and repeatable visual
|
|
11
|
+
fixtures. Use browser automation against your application when the screenshot
|
|
12
|
+
needs real authentication, routing, server data, or a complete product flow.
|
|
13
|
+
|
|
14
|
+
## Quick start
|
|
15
|
+
|
|
16
|
+
Node.js 22.14 or newer is required. Install the package's compatible Chromium
|
|
17
|
+
build once:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx astroshot install-browser
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
On a Linux machine that also needs Chromium's operating-system libraries, run
|
|
24
|
+
`npx astroshot install-browser --with-deps`.
|
|
25
|
+
|
|
26
|
+
Then create `example.shot.tsx`:
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
import type { ReactShotFixture } from "@archastro/astroshot/react";
|
|
30
|
+
|
|
31
|
+
function WelcomeCard() {
|
|
32
|
+
return (
|
|
33
|
+
<main
|
|
34
|
+
data-card
|
|
35
|
+
style={{ width: 480, padding: 32, background: "#f8fafc" }}
|
|
36
|
+
>
|
|
37
|
+
<h1>Hello from React</h1>
|
|
38
|
+
</main>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export default {
|
|
43
|
+
width: 800,
|
|
44
|
+
height: 600,
|
|
45
|
+
selector: "[data-card]",
|
|
46
|
+
component: <WelcomeCard />,
|
|
47
|
+
} satisfies ReactShotFixture;
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Capture it without a permanent install:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npx astroshot react example.shot.tsx --out welcome.png
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Or install the unified package and use `astroshot` in project scripts:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm install --save-dev @archastro/astroshot
|
|
60
|
+
npx astroshot react example.shot.tsx -o welcome.png
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Fixture API
|
|
64
|
+
|
|
65
|
+
The fixture's default export accepts:
|
|
66
|
+
|
|
67
|
+
| Field | Purpose |
|
|
68
|
+
| --- | --- |
|
|
69
|
+
| `component` | React tree to render; required |
|
|
70
|
+
| `width`, `height` | Viewport in CSS pixels; defaults to 1280 by 800 |
|
|
71
|
+
| `background` | Page background; defaults to transparent |
|
|
72
|
+
| `selector` | Element to capture; defaults to the fixture root |
|
|
73
|
+
| `waitFor` | CSS selector or `text=...` gate to await |
|
|
74
|
+
| `settleMs` | Extra layout settling time; defaults to 150 ms |
|
|
75
|
+
| `fullPage` | Capture the full page instead of one element |
|
|
76
|
+
| `stripOverlay` | Isolate the target from a full-screen parent overlay |
|
|
77
|
+
| `omitBackground` | Preserve transparent PNG pixels |
|
|
78
|
+
|
|
79
|
+
Selectors containing `[role=dialog]` automatically enable `stripOverlay` and
|
|
80
|
+
`omitBackground`. This avoids including a modal's full-screen dimmer and keeps
|
|
81
|
+
rounded corners transparent. Set either option explicitly to override it.
|
|
82
|
+
|
|
83
|
+
CLI `--width` and `--height` values override fixture dimensions. Values must be
|
|
84
|
+
whole CSS pixels between 1 and 10,000, and output paths must end in `.png`.
|
|
85
|
+
|
|
86
|
+
## Project configuration
|
|
87
|
+
|
|
88
|
+
Place `react-shot.config.ts` beside your application package. It is discovered
|
|
89
|
+
by walking upward from the fixture, or can be selected with `--config`.
|
|
90
|
+
Filesystem paths are resolved relative to the config file.
|
|
91
|
+
|
|
92
|
+
Without a config or `--root`, react-shot uses the closest directory containing
|
|
93
|
+
a `package.json`, so fixtures can import sibling application source files.
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
export default {
|
|
97
|
+
root: ".",
|
|
98
|
+
alias: {
|
|
99
|
+
"@": "./src",
|
|
100
|
+
},
|
|
101
|
+
styles: ["./src/global.css"],
|
|
102
|
+
postcssConfig: "./postcss.config.mjs",
|
|
103
|
+
dedupe: ["some-react-library"],
|
|
104
|
+
stubModules: ["server-only"],
|
|
105
|
+
};
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Common imports from `next/navigation`, `next/link`, `next/image`,
|
|
109
|
+
`next/dynamic`, and `server-only` receive lightweight browser stubs. These
|
|
110
|
+
stubs are conveniences for presentation fixtures, not substitutes for testing
|
|
111
|
+
Next.js behavior.
|
|
112
|
+
|
|
113
|
+
If `@tailwindcss/vite` is installed in the target project, react-shot loads it
|
|
114
|
+
automatically. Otherwise Vite uses the configured PostCSS pipeline.
|
|
115
|
+
|
|
116
|
+
## Batch captures
|
|
117
|
+
|
|
118
|
+
Create a YAML or JSON manifest:
|
|
119
|
+
|
|
120
|
+
```yaml
|
|
121
|
+
root: .
|
|
122
|
+
shots:
|
|
123
|
+
- fixture: fixtures/welcome.shot.tsx
|
|
124
|
+
out: screenshots/welcome.png
|
|
125
|
+
width: 900
|
|
126
|
+
height: 700
|
|
127
|
+
- fixture: fixtures/settings.shot.tsx
|
|
128
|
+
out: screenshots/settings.png
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Paths in a manifest, including per-shot `root` and `config`, are relative to
|
|
132
|
+
the manifest file. Destinations are normalized and checked as a complete batch
|
|
133
|
+
before capture; duplicate and case-only colliding output paths are rejected:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
npx astroshot react batch shots.yaml
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Programmatic API
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
import { closeSharedBrowser, takeShot } from "@archastro/react-shot";
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
await takeShot({
|
|
146
|
+
fixturePath: "fixtures/welcome.shot.tsx",
|
|
147
|
+
outPath: "screenshots/welcome.png",
|
|
148
|
+
});
|
|
149
|
+
} finally {
|
|
150
|
+
await closeSharedBrowser();
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Batch CLI runs reuse one browser process. Programmatic callers should close
|
|
155
|
+
the shared browser during shutdown.
|
|
156
|
+
|
|
157
|
+
## Security
|
|
158
|
+
|
|
159
|
+
Fixtures and config files are executable code. Vite loads application modules
|
|
160
|
+
and `react-shot.config.*`; they run with the permissions of the current user.
|
|
161
|
+
Only capture trusted repositories and fixtures. Do not run the CLI on an
|
|
162
|
+
untrusted pull request, package, or downloaded manifest without reviewing it.
|
|
163
|
+
|
|
164
|
+
The Vite server binds only to `127.0.0.1` and restricts file serving to the
|
|
165
|
+
package, fixture, configured alias/style paths, and this tool's runtime files.
|
|
166
|
+
It does not provide a sandbox for fixture code.
|
|
167
|
+
|
|
168
|
+
## Troubleshooting
|
|
169
|
+
|
|
170
|
+
- `Executable doesn't exist`: run
|
|
171
|
+
`npx astroshot install-browser`.
|
|
172
|
+
- Import failures: pass `--root` or define `root` and `alias` in the config.
|
|
173
|
+
- Missing styles: add their entry files to `styles`; CSS is not inferred.
|
|
174
|
+
- A modal includes a dimmer: target `[role=dialog]` or set
|
|
175
|
+
`stripOverlay: true`.
|
|
176
|
+
- A fixture depends on application providers or network state: wrap it with
|
|
177
|
+
deterministic providers, or capture the running application instead.
|
|
178
|
+
|
|
179
|
+
Run
|
|
180
|
+
`npx astroshot react --help`
|
|
181
|
+
for all CLI options.
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { BatchEntry } from "./types.js";
|
|
2
|
+
/** Resolve every destination before capture and reject accidental overwrites. */
|
|
3
|
+
export declare function resolveBatchOutputPaths(entries: BatchEntry[], manifestDirectory: string): string[];
|
|
4
|
+
//# sourceMappingURL=batch-paths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batch-paths.d.ts","sourceRoot":"","sources":["../src/batch-paths.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAmB7C,iFAAiF;AACjF,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,UAAU,EAAE,EACrB,iBAAiB,EAAE,MAAM,GACxB,MAAM,EAAE,CAgBV"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
function collisionKey(filePath) {
|
|
4
|
+
// Keep manifests portable across case-sensitive and case-insensitive hosts.
|
|
5
|
+
let existingAncestor = path.resolve(filePath);
|
|
6
|
+
const missingSegments = [];
|
|
7
|
+
while (!fs.existsSync(existingAncestor)) {
|
|
8
|
+
const parent = path.dirname(existingAncestor);
|
|
9
|
+
if (parent === existingAncestor)
|
|
10
|
+
break;
|
|
11
|
+
missingSegments.unshift(path.basename(existingAncestor));
|
|
12
|
+
existingAncestor = parent;
|
|
13
|
+
}
|
|
14
|
+
const canonicalAncestor = fs.realpathSync.native(existingAncestor);
|
|
15
|
+
return path
|
|
16
|
+
.join(canonicalAncestor, ...missingSegments)
|
|
17
|
+
.normalize("NFC")
|
|
18
|
+
.toLowerCase();
|
|
19
|
+
}
|
|
20
|
+
/** Resolve every destination before capture and reject accidental overwrites. */
|
|
21
|
+
export function resolveBatchOutputPaths(entries, manifestDirectory) {
|
|
22
|
+
const destinations = entries.map((entry) => path.resolve(manifestDirectory, entry.out));
|
|
23
|
+
const seen = new Map();
|
|
24
|
+
for (const destination of destinations) {
|
|
25
|
+
const key = collisionKey(destination);
|
|
26
|
+
const previous = seen.get(key);
|
|
27
|
+
if (previous) {
|
|
28
|
+
throw new Error(`Batch outputs resolve to the same destination: ${previous} and ${destination}`);
|
|
29
|
+
}
|
|
30
|
+
seen.set(key, destination);
|
|
31
|
+
}
|
|
32
|
+
return destinations;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=batch-paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batch-paths.js","sourceRoot":"","sources":["../src/batch-paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAI7B,SAAS,YAAY,CAAC,QAAgB;IACpC,4EAA4E;IAC5E,IAAI,gBAAgB,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC9C,MAAM,eAAe,GAAa,EAAE,CAAC;IACrC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,gBAAgB,CAAC,EAAE,CAAC;QACxC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;QAC9C,IAAI,MAAM,KAAK,gBAAgB;YAAE,MAAM;QACvC,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC,CAAC;QACzD,gBAAgB,GAAG,MAAM,CAAC;IAC5B,CAAC;IACD,MAAM,iBAAiB,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;IACnE,OAAO,IAAI;SACR,IAAI,CAAC,iBAAiB,EAAE,GAAG,eAAe,CAAC;SAC3C,SAAS,CAAC,KAAK,CAAC;SAChB,WAAW,EAAE,CAAC;AACnB,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,uBAAuB,CACrC,OAAqB,EACrB,iBAAyB;IAEzB,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CACzC,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,KAAK,CAAC,GAAG,CAAC,CAC3C,CAAC;IACF,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IACvC,KAAK,MAAM,WAAW,IAAI,YAAY,EAAE,CAAC;QACvC,MAAM,GAAG,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC;QACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CACb,kDAAkD,QAAQ,QAAQ,WAAW,EAAE,CAChF,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;IAC7B,CAAC;IACD,OAAO,YAAY,CAAC;AACtB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|