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.
Files changed (119) hide show
  1. package/README.md +87 -0
  2. package/bin/astroshot.mjs +71 -0
  3. package/node_modules/@archastro/astroshot/LICENSE +21 -0
  4. package/node_modules/@archastro/astroshot/README.md +96 -0
  5. package/node_modules/@archastro/astroshot/bin/astroshot.mjs +276 -0
  6. package/node_modules/@archastro/astroshot/bin/demo.mjs +275 -0
  7. package/node_modules/@archastro/astroshot/bin/doctor.mjs +492 -0
  8. package/node_modules/@archastro/astroshot/bin/mac-preferences.mjs +346 -0
  9. package/node_modules/@archastro/astroshot/bin/templates.mjs +186 -0
  10. package/node_modules/@archastro/astroshot/fixtures/demo/fixtures.json +42 -0
  11. package/node_modules/@archastro/astroshot/fixtures/demo/journey.png +0 -0
  12. package/node_modules/@archastro/astroshot/fixtures/demo/journey.webm +0 -0
  13. package/node_modules/@archastro/astroshot/fixtures/demo/next-steps.png +0 -0
  14. package/node_modules/@archastro/astroshot/fixtures/demo/welcome.png +0 -0
  15. package/node_modules/@archastro/astroshot/ink.d.ts +5 -0
  16. package/node_modules/@archastro/astroshot/ink.js +1 -0
  17. package/node_modules/@archastro/astroshot/package.json +79 -0
  18. package/node_modules/@archastro/astroshot/pty.d.ts +8 -0
  19. package/node_modules/@archastro/astroshot/pty.js +1 -0
  20. package/node_modules/@archastro/astroshot/react.d.ts +1 -0
  21. package/node_modules/@archastro/astroshot/react.js +1 -0
  22. package/node_modules/@archastro/astroshot/tui.d.ts +1 -0
  23. package/node_modules/@archastro/astroshot/tui.js +1 -0
  24. package/node_modules/@archastro/movie-harness/README.md +138 -0
  25. package/node_modules/@archastro/movie-harness/bin/astroshot-movie.mjs +5 -0
  26. package/node_modules/@archastro/movie-harness/dist/cli.d.ts +1 -0
  27. package/node_modules/@archastro/movie-harness/dist/cli.js +389 -0
  28. package/node_modules/@archastro/movie-harness/dist/encode.d.ts +14 -0
  29. package/node_modules/@archastro/movie-harness/dist/encode.js +184 -0
  30. package/node_modules/@archastro/movie-harness/dist/index.d.ts +14 -0
  31. package/node_modules/@archastro/movie-harness/dist/index.js +11 -0
  32. package/node_modules/@archastro/movie-harness/dist/paths.d.ts +9 -0
  33. package/node_modules/@archastro/movie-harness/dist/paths.js +57 -0
  34. package/node_modules/@archastro/movie-harness/dist/png.d.ts +8 -0
  35. package/node_modules/@archastro/movie-harness/dist/png.js +79 -0
  36. package/node_modules/@archastro/movie-harness/dist/session.d.ts +45 -0
  37. package/node_modules/@archastro/movie-harness/dist/session.js +170 -0
  38. package/node_modules/@archastro/movie-harness/dist/sink.d.ts +7 -0
  39. package/node_modules/@archastro/movie-harness/dist/sink.js +113 -0
  40. package/node_modules/@archastro/movie-harness/dist/source-help.d.ts +29 -0
  41. package/node_modules/@archastro/movie-harness/dist/source-help.js +222 -0
  42. package/node_modules/@archastro/movie-harness/dist/sources/browser.d.ts +7 -0
  43. package/node_modules/@archastro/movie-harness/dist/sources/browser.js +87 -0
  44. package/node_modules/@archastro/movie-harness/dist/sources/desktop-macos.d.ts +110 -0
  45. package/node_modules/@archastro/movie-harness/dist/sources/desktop-macos.js +505 -0
  46. package/node_modules/@archastro/movie-harness/dist/sources/frames-store.d.ts +19 -0
  47. package/node_modules/@archastro/movie-harness/dist/sources/frames-store.js +165 -0
  48. package/node_modules/@archastro/movie-harness/dist/sources/pty.d.ts +15 -0
  49. package/node_modules/@archastro/movie-harness/dist/sources/pty.js +330 -0
  50. package/node_modules/@archastro/movie-harness/dist/terminal-paint.d.ts +27 -0
  51. package/node_modules/@archastro/movie-harness/dist/terminal-paint.js +190 -0
  52. package/node_modules/@archastro/movie-harness/dist/types.d.ts +140 -0
  53. package/node_modules/@archastro/movie-harness/dist/types.js +1 -0
  54. package/node_modules/@archastro/movie-harness/native/macos/WindowTools.swift +164 -0
  55. package/node_modules/@archastro/movie-harness/package.json +67 -0
  56. package/node_modules/@archastro/react-shot/LICENSE +21 -0
  57. package/node_modules/@archastro/react-shot/README.md +181 -0
  58. package/node_modules/@archastro/react-shot/bin/react-shot.mjs +2 -0
  59. package/node_modules/@archastro/react-shot/dist/batch-paths.d.ts +4 -0
  60. package/node_modules/@archastro/react-shot/dist/batch-paths.d.ts.map +1 -0
  61. package/node_modules/@archastro/react-shot/dist/batch-paths.js +34 -0
  62. package/node_modules/@archastro/react-shot/dist/batch-paths.js.map +1 -0
  63. package/node_modules/@archastro/react-shot/dist/cli.d.ts +3 -0
  64. package/node_modules/@archastro/react-shot/dist/cli.d.ts.map +1 -0
  65. package/node_modules/@archastro/react-shot/dist/cli.js +217 -0
  66. package/node_modules/@archastro/react-shot/dist/cli.js.map +1 -0
  67. package/node_modules/@archastro/react-shot/dist/config.d.ts +5 -0
  68. package/node_modules/@archastro/react-shot/dist/config.d.ts.map +1 -0
  69. package/node_modules/@archastro/react-shot/dist/config.js +75 -0
  70. package/node_modules/@archastro/react-shot/dist/config.js.map +1 -0
  71. package/node_modules/@archastro/react-shot/dist/create-server.d.ts +13 -0
  72. package/node_modules/@archastro/react-shot/dist/create-server.d.ts.map +1 -0
  73. package/node_modules/@archastro/react-shot/dist/create-server.js +254 -0
  74. package/node_modules/@archastro/react-shot/dist/create-server.js.map +1 -0
  75. package/node_modules/@archastro/react-shot/dist/index.d.ts +4 -0
  76. package/node_modules/@archastro/react-shot/dist/index.d.ts.map +1 -0
  77. package/node_modules/@archastro/react-shot/dist/index.js +3 -0
  78. package/node_modules/@archastro/react-shot/dist/index.js.map +1 -0
  79. package/node_modules/@archastro/react-shot/dist/meta.d.ts +9 -0
  80. package/node_modules/@archastro/react-shot/dist/meta.d.ts.map +1 -0
  81. package/node_modules/@archastro/react-shot/dist/meta.js +23 -0
  82. package/node_modules/@archastro/react-shot/dist/meta.js.map +1 -0
  83. package/node_modules/@archastro/react-shot/dist/shot.d.ts +4 -0
  84. package/node_modules/@archastro/react-shot/dist/shot.d.ts.map +1 -0
  85. package/node_modules/@archastro/react-shot/dist/shot.js +259 -0
  86. package/node_modules/@archastro/react-shot/dist/shot.js.map +1 -0
  87. package/node_modules/@archastro/react-shot/dist/stubs.d.ts +6 -0
  88. package/node_modules/@archastro/react-shot/dist/stubs.d.ts.map +1 -0
  89. package/node_modules/@archastro/react-shot/dist/stubs.js +49 -0
  90. package/node_modules/@archastro/react-shot/dist/stubs.js.map +1 -0
  91. package/node_modules/@archastro/react-shot/dist/types.d.ts +79 -0
  92. package/node_modules/@archastro/react-shot/dist/types.d.ts.map +1 -0
  93. package/node_modules/@archastro/react-shot/dist/types.js +2 -0
  94. package/node_modules/@archastro/react-shot/dist/types.js.map +1 -0
  95. package/node_modules/@archastro/react-shot/host/index.html +22 -0
  96. package/node_modules/@archastro/react-shot/package.json +81 -0
  97. package/node_modules/@archastro/tui-shot/LICENSE +21 -0
  98. package/node_modules/@archastro/tui-shot/README.md +145 -0
  99. package/node_modules/@archastro/tui-shot/bin/tui-shot.mjs +23 -0
  100. package/node_modules/@archastro/tui-shot/dist/batch-paths.d.ts +3 -0
  101. package/node_modules/@archastro/tui-shot/dist/batch-paths.js +33 -0
  102. package/node_modules/@archastro/tui-shot/dist/cli.d.ts +2 -0
  103. package/node_modules/@archastro/tui-shot/dist/cli.js +224 -0
  104. package/node_modules/@archastro/tui-shot/dist/index.d.ts +3 -0
  105. package/node_modules/@archastro/tui-shot/dist/index.js +2 -0
  106. package/node_modules/@archastro/tui-shot/dist/pty-exit-wrapper.d.ts +1 -0
  107. package/node_modules/@archastro/tui-shot/dist/pty-exit-wrapper.js +50 -0
  108. package/node_modules/@archastro/tui-shot/dist/pty-shot.d.ts +2 -0
  109. package/node_modules/@archastro/tui-shot/dist/pty-shot.js +483 -0
  110. package/node_modules/@archastro/tui-shot/dist/render-ink.d.ts +10 -0
  111. package/node_modules/@archastro/tui-shot/dist/render-ink.js +87 -0
  112. package/node_modules/@archastro/tui-shot/dist/shot.d.ts +24 -0
  113. package/node_modules/@archastro/tui-shot/dist/shot.js +250 -0
  114. package/node_modules/@archastro/tui-shot/dist/terminal-html.d.ts +14 -0
  115. package/node_modules/@archastro/tui-shot/dist/terminal-html.js +144 -0
  116. package/node_modules/@archastro/tui-shot/dist/types.d.ts +81 -0
  117. package/node_modules/@archastro/tui-shot/dist/types.js +1 -0
  118. package/node_modules/@archastro/tui-shot/package.json +90 -0
  119. 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,2 @@
1
+ #!/usr/bin/env node
2
+ import "../dist/cli.js";
@@ -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,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}