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,492 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `astroshot doctor` — report every way Astroshots setup silently fails.
|
|
3
|
+
*
|
|
4
|
+
* Read-only by contract: it never installs, launches, or writes preferences.
|
|
5
|
+
* Each check prints pass/fail plus the exact remediation command, and required
|
|
6
|
+
* failures make the process exit non-zero.
|
|
7
|
+
*/
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import os from "node:os";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { createRequire } from "node:module";
|
|
12
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
|
|
15
|
+
import { loadDemoFixtures } from "./demo.mjs";
|
|
16
|
+
import {
|
|
17
|
+
ASTROSHOTS_DOMAIN,
|
|
18
|
+
evaluateWatchCoverage,
|
|
19
|
+
preferenceToolsAvailable,
|
|
20
|
+
readWatchConfiguration,
|
|
21
|
+
} from "./mac-preferences.mjs";
|
|
22
|
+
|
|
23
|
+
const APP_PROCESS_NAME = "Astroshots";
|
|
24
|
+
|
|
25
|
+
export function doctorHelp() {
|
|
26
|
+
return `astroshot doctor — diagnose Astroshots capture setup (read-only)
|
|
27
|
+
|
|
28
|
+
Usage:
|
|
29
|
+
astroshot doctor [options]
|
|
30
|
+
|
|
31
|
+
Options:
|
|
32
|
+
--root <dir> Project directory to test for watch coverage (default: cwd)
|
|
33
|
+
--json Machine-readable report
|
|
34
|
+
--skip-screen Skip the macOS Screen Recording probe (avoids a Swift run)
|
|
35
|
+
-h, --help Show this help
|
|
36
|
+
|
|
37
|
+
Reports Node version, watched-folder coverage, whether the Astroshots app is
|
|
38
|
+
installed and running, the managed Chromium runtime, and macOS Screen Recording
|
|
39
|
+
state for desktop.window. Exits non-zero when a required check fails.
|
|
40
|
+
doctor never installs anything and never changes app state.`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function readEngines() {
|
|
44
|
+
const packageJSON = JSON.parse(
|
|
45
|
+
fs.readFileSync(new URL("../package.json", import.meta.url), "utf8"),
|
|
46
|
+
);
|
|
47
|
+
return packageJSON.engines?.node ?? ">=22.14.0";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function parseVersion(value) {
|
|
51
|
+
const match = /(\d+)\.(\d+)\.(\d+)/.exec(value);
|
|
52
|
+
if (!match) return null;
|
|
53
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function compareVersions(a, b) {
|
|
57
|
+
for (let index = 0; index < 3; index += 1) {
|
|
58
|
+
if (a[index] !== b[index]) return a[index] < b[index] ? -1 : 1;
|
|
59
|
+
}
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function checkNode(range) {
|
|
64
|
+
const minimum = parseVersion(range);
|
|
65
|
+
const current = parseVersion(process.versions.node);
|
|
66
|
+
if (!minimum || !current) {
|
|
67
|
+
return {
|
|
68
|
+
id: "node",
|
|
69
|
+
title: "Node.js version",
|
|
70
|
+
required: true,
|
|
71
|
+
status: "warn",
|
|
72
|
+
detail: `could not compare ${process.versions.node} with "${range}"`,
|
|
73
|
+
remediation: "Install Node.js 22.14.0 or newer: https://nodejs.org/en/download",
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const ok = compareVersions(current, minimum) >= 0;
|
|
77
|
+
return {
|
|
78
|
+
id: "node",
|
|
79
|
+
title: "Node.js version",
|
|
80
|
+
required: true,
|
|
81
|
+
status: ok ? "pass" : "fail",
|
|
82
|
+
detail: ok
|
|
83
|
+
? `${process.versions.node} satisfies ${range}`
|
|
84
|
+
: `${process.versions.node} is older than required ${range}`,
|
|
85
|
+
remediation: "nvm install 22.14.0 && nvm use 22.14.0",
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function checkDemoFixtures() {
|
|
90
|
+
try {
|
|
91
|
+
const index = loadDemoFixtures();
|
|
92
|
+
const movies = index.shots.filter((shot) => shot.video).length;
|
|
93
|
+
return {
|
|
94
|
+
id: "demo-fixtures",
|
|
95
|
+
title: "Bundled demo payload",
|
|
96
|
+
required: true,
|
|
97
|
+
status: "pass",
|
|
98
|
+
detail: `${index.shots.length} fixtures (${movies} movie) ready for "astroshot demo"`,
|
|
99
|
+
remediation: "npm install --global @archastro/astroshot",
|
|
100
|
+
};
|
|
101
|
+
} catch (error) {
|
|
102
|
+
return {
|
|
103
|
+
id: "demo-fixtures",
|
|
104
|
+
title: "Bundled demo payload",
|
|
105
|
+
required: true,
|
|
106
|
+
status: "fail",
|
|
107
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
108
|
+
remediation: "npm install --global @archastro/astroshot",
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function checkWatchCoverage(
|
|
114
|
+
projectPath,
|
|
115
|
+
platform,
|
|
116
|
+
// Seam for tests: the tool-failure path is otherwise only reachable by
|
|
117
|
+
// breaking the host's /usr/bin.
|
|
118
|
+
{ readConfiguration = readWatchConfiguration } = {},
|
|
119
|
+
) {
|
|
120
|
+
const base = { id: "watch-roots", title: "Watched folder covers this project" };
|
|
121
|
+
if (platform !== "darwin") {
|
|
122
|
+
return {
|
|
123
|
+
...base,
|
|
124
|
+
required: false,
|
|
125
|
+
status: "skip",
|
|
126
|
+
detail: "the Astroshots app is macOS-only; .astroshot files are still written",
|
|
127
|
+
remediation: "Run captures on macOS to review them in the Astroshots tray",
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const configuration = readConfiguration();
|
|
132
|
+
const coverage = evaluateWatchCoverage(projectPath, configuration);
|
|
133
|
+
const source =
|
|
134
|
+
configuration.source === "plist-file"
|
|
135
|
+
? ` (read from ${configuration.plistPath}; cfprefsd unavailable)`
|
|
136
|
+
: "";
|
|
137
|
+
const legacy = configuration.usedLegacyKey ? " via legacy watchRoot key" : "";
|
|
138
|
+
|
|
139
|
+
switch (coverage.state) {
|
|
140
|
+
case "inside-root":
|
|
141
|
+
return {
|
|
142
|
+
...base,
|
|
143
|
+
required: true,
|
|
144
|
+
status: "pass",
|
|
145
|
+
detail: `${coverage.projectPath} is inside ${coverage.matchedRoot}${legacy}${source}`,
|
|
146
|
+
remediation: "Astroshots menu-bar icon → gear → Add folders…",
|
|
147
|
+
data: coverage,
|
|
148
|
+
};
|
|
149
|
+
case "setup-incomplete":
|
|
150
|
+
return {
|
|
151
|
+
...base,
|
|
152
|
+
required: true,
|
|
153
|
+
status: "fail",
|
|
154
|
+
detail:
|
|
155
|
+
"first-launch folder setup has not completed, so Astroshots watches nothing yet",
|
|
156
|
+
remediation:
|
|
157
|
+
"open -a Astroshots # then choose the folder that holds your projects",
|
|
158
|
+
data: coverage,
|
|
159
|
+
};
|
|
160
|
+
case "outside-roots":
|
|
161
|
+
return {
|
|
162
|
+
...base,
|
|
163
|
+
required: true,
|
|
164
|
+
status: "fail",
|
|
165
|
+
detail: `${coverage.projectPath} is outside every watched folder (${coverage.roots.join(", ")})`,
|
|
166
|
+
remediation:
|
|
167
|
+
"Astroshots menu-bar icon → gear → Add folders… → add a parent folder of this project",
|
|
168
|
+
data: coverage,
|
|
169
|
+
};
|
|
170
|
+
default: {
|
|
171
|
+
// The configuration could not be read. Say exactly that: claiming
|
|
172
|
+
// "setup incomplete" here would tell a correctly configured user to redo
|
|
173
|
+
// work they already did.
|
|
174
|
+
const tools = preferenceToolsAvailable({ platform });
|
|
175
|
+
const toolProblem =
|
|
176
|
+
coverage.reason === "tool-unavailable" || !tools.available;
|
|
177
|
+
const missing = tools.missing.length
|
|
178
|
+
? ` (missing ${tools.missing.join(", ")})`
|
|
179
|
+
: "";
|
|
180
|
+
return {
|
|
181
|
+
...base,
|
|
182
|
+
required: true,
|
|
183
|
+
status: "warn",
|
|
184
|
+
detail: toolProblem
|
|
185
|
+
? `watch coverage is UNKNOWN, not unconfigured: could not read ${ASTROSHOTS_DOMAIN} because the macOS preference tools are unavailable${missing}`
|
|
186
|
+
: `watch coverage is UNKNOWN: could not read ${ASTROSHOTS_DOMAIN} preferences (${coverage.reason ?? "unknown"})`,
|
|
187
|
+
remediation: toolProblem
|
|
188
|
+
? "Re-run with /usr/bin on PATH — doctor needs /usr/bin/defaults and /usr/bin/plutil"
|
|
189
|
+
: "Install and launch Astroshots once: https://github.com/ArchAstro/astroshots/releases",
|
|
190
|
+
data: coverage,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function findInstalledApp({ home = os.homedir() } = {}) {
|
|
197
|
+
const candidates = [
|
|
198
|
+
"/Applications/Astroshots.app",
|
|
199
|
+
path.join(home, "Applications", "Astroshots.app"),
|
|
200
|
+
];
|
|
201
|
+
for (const candidate of candidates) {
|
|
202
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
203
|
+
}
|
|
204
|
+
const found = spawnSync(
|
|
205
|
+
"/usr/bin/mdfind",
|
|
206
|
+
["-0", `kMDItemCFBundleIdentifier == '${ASTROSHOTS_DOMAIN}'`],
|
|
207
|
+
{ encoding: "utf8" },
|
|
208
|
+
);
|
|
209
|
+
if (found.status === 0 && found.stdout) {
|
|
210
|
+
const hit = found.stdout
|
|
211
|
+
.split("\0")
|
|
212
|
+
.map((entry) => entry.trim())
|
|
213
|
+
.find((entry) => entry.endsWith(".app") && fs.existsSync(entry));
|
|
214
|
+
if (hit) return hit;
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function appVersion(appPath) {
|
|
220
|
+
const plist = path.join(appPath, "Contents", "Info.plist");
|
|
221
|
+
if (!fs.existsSync(plist)) return null;
|
|
222
|
+
const result = spawnSync(
|
|
223
|
+
"/usr/bin/plutil",
|
|
224
|
+
["-extract", "CFBundleShortVersionString", "raw", "-o", "-", plist],
|
|
225
|
+
{ encoding: "utf8" },
|
|
226
|
+
);
|
|
227
|
+
if (result.status !== 0) return null;
|
|
228
|
+
return result.stdout.trim() || null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function checkApp(platform) {
|
|
232
|
+
const base = { id: "app", title: "Astroshots app installed" };
|
|
233
|
+
if (platform !== "darwin") {
|
|
234
|
+
return {
|
|
235
|
+
...base,
|
|
236
|
+
required: false,
|
|
237
|
+
status: "skip",
|
|
238
|
+
detail: "macOS-only review app",
|
|
239
|
+
remediation: "Review captured files directly on this platform",
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
const appPath = findInstalledApp();
|
|
243
|
+
if (!appPath) {
|
|
244
|
+
return {
|
|
245
|
+
...base,
|
|
246
|
+
required: true,
|
|
247
|
+
status: "fail",
|
|
248
|
+
detail: "no Astroshots.app found in /Applications or ~/Applications",
|
|
249
|
+
remediation:
|
|
250
|
+
"Download the latest DMG: https://github.com/ArchAstro/astroshots/releases",
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
const version = appVersion(appPath);
|
|
254
|
+
return {
|
|
255
|
+
...base,
|
|
256
|
+
required: true,
|
|
257
|
+
status: "pass",
|
|
258
|
+
detail: `${appPath}${version ? ` (${version})` : ""}`,
|
|
259
|
+
remediation: "open -a Astroshots",
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function checkAppRunning(platform) {
|
|
264
|
+
const base = { id: "app-running", title: "Astroshots app running" };
|
|
265
|
+
if (platform !== "darwin") {
|
|
266
|
+
return {
|
|
267
|
+
...base,
|
|
268
|
+
required: false,
|
|
269
|
+
status: "skip",
|
|
270
|
+
detail: "macOS-only review app",
|
|
271
|
+
remediation: "Review captured files directly on this platform",
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
const result = spawnSync("/usr/bin/pgrep", ["-x", APP_PROCESS_NAME], {
|
|
275
|
+
encoding: "utf8",
|
|
276
|
+
});
|
|
277
|
+
const running = result.status === 0 && Boolean(result.stdout.trim());
|
|
278
|
+
return {
|
|
279
|
+
...base,
|
|
280
|
+
required: false,
|
|
281
|
+
status: running ? "pass" : "warn",
|
|
282
|
+
detail: running
|
|
283
|
+
? `pid ${result.stdout.trim().split(/\s+/).join(", ")} (menu-bar only, no Dock icon)`
|
|
284
|
+
: "not running, so new captures will not stream or flash an overlay",
|
|
285
|
+
remediation: "open -a Astroshots",
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function resolvePlaywright() {
|
|
290
|
+
// playwright belongs to the engine packages, so resolve it from the engine
|
|
291
|
+
// rather than assuming a hoisted install next to this CLI. It is CommonJS,
|
|
292
|
+
// so require it: ESM interop does not expose the `chromium` named export.
|
|
293
|
+
const engineEntry = fileURLToPath(
|
|
294
|
+
import.meta.resolve("@archastro/react-shot"),
|
|
295
|
+
);
|
|
296
|
+
return createRequire(engineEntry)("playwright");
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function checkChromium() {
|
|
300
|
+
const base = {
|
|
301
|
+
id: "chromium",
|
|
302
|
+
title: "Managed Chromium runtime",
|
|
303
|
+
required: false,
|
|
304
|
+
remediation: "astroshot install-browser # add --with-deps on Linux CI",
|
|
305
|
+
};
|
|
306
|
+
let executablePath;
|
|
307
|
+
try {
|
|
308
|
+
executablePath = resolvePlaywright().chromium.executablePath();
|
|
309
|
+
} catch (error) {
|
|
310
|
+
return {
|
|
311
|
+
...base,
|
|
312
|
+
status: "warn",
|
|
313
|
+
detail: `could not resolve Playwright (${error instanceof Error ? error.message.split("\n")[0] : error}); react/ink/pty and browser movies need it`,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
const present = Boolean(executablePath) && fs.existsSync(executablePath);
|
|
317
|
+
return {
|
|
318
|
+
...base,
|
|
319
|
+
status: present ? "pass" : "warn",
|
|
320
|
+
detail: present
|
|
321
|
+
? executablePath
|
|
322
|
+
: `not installed at ${executablePath} — needed for react/ink/pty stills and browser movies (not for "astroshot demo")`,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function checkScreenRecording(platform, { skip = false } = {}) {
|
|
327
|
+
const base = {
|
|
328
|
+
id: "screen-recording",
|
|
329
|
+
title: "Screen Recording permission (movie --source desktop.window)",
|
|
330
|
+
required: false,
|
|
331
|
+
};
|
|
332
|
+
if (platform !== "darwin") {
|
|
333
|
+
return {
|
|
334
|
+
...base,
|
|
335
|
+
status: "skip",
|
|
336
|
+
detail: "desktop.window is macOS-only; use --source browser, pty, or frames",
|
|
337
|
+
remediation: "astroshot movie which-source \"<intent>\"",
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
if (skip) {
|
|
341
|
+
return {
|
|
342
|
+
...base,
|
|
343
|
+
status: "skip",
|
|
344
|
+
detail: "skipped with --skip-screen",
|
|
345
|
+
remediation: "astroshot movie check-screen-access",
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const engineEntry = fileURLToPath(
|
|
350
|
+
import.meta.resolve("@archastro/movie-harness"),
|
|
351
|
+
);
|
|
352
|
+
const harnessBin = path.join(
|
|
353
|
+
path.dirname(path.dirname(engineEntry)),
|
|
354
|
+
"bin",
|
|
355
|
+
"astroshot-movie.mjs",
|
|
356
|
+
);
|
|
357
|
+
const result = spawnSync(
|
|
358
|
+
process.execPath,
|
|
359
|
+
[harnessBin, "check-screen-access"],
|
|
360
|
+
{ encoding: "utf8" },
|
|
361
|
+
);
|
|
362
|
+
let report = null;
|
|
363
|
+
try {
|
|
364
|
+
report = JSON.parse(result.stdout ?? "");
|
|
365
|
+
} catch {
|
|
366
|
+
report = null;
|
|
367
|
+
}
|
|
368
|
+
if (!report) {
|
|
369
|
+
return {
|
|
370
|
+
...base,
|
|
371
|
+
status: "warn",
|
|
372
|
+
detail: `could not probe Screen Recording (${(result.stderr ?? "").trim().split("\n")[0] || `status ${result.status}`})`,
|
|
373
|
+
remediation: "xcode-select --install && astroshot movie check-screen-access",
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
return {
|
|
377
|
+
...base,
|
|
378
|
+
status: report.granted ? "pass" : "warn",
|
|
379
|
+
detail: report.granted
|
|
380
|
+
? `granted for ${report.enableApp}`
|
|
381
|
+
: `denied — enable "${report.enableApp}" then quit and reopen it`,
|
|
382
|
+
remediation: report.granted
|
|
383
|
+
? "astroshot movie check-screen-access"
|
|
384
|
+
: "astroshot movie open-screen-settings",
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const SYMBOLS = { pass: "PASS", fail: "FAIL", warn: "WARN", skip: "SKIP" };
|
|
389
|
+
|
|
390
|
+
export function collectDoctorChecks({
|
|
391
|
+
projectPath = process.cwd(),
|
|
392
|
+
platform = process.platform,
|
|
393
|
+
skipScreen = false,
|
|
394
|
+
} = {}) {
|
|
395
|
+
return [
|
|
396
|
+
checkNode(readEngines()),
|
|
397
|
+
checkDemoFixtures(),
|
|
398
|
+
checkApp(platform),
|
|
399
|
+
checkAppRunning(platform),
|
|
400
|
+
checkWatchCoverage(projectPath, platform),
|
|
401
|
+
checkChromium(),
|
|
402
|
+
checkScreenRecording(platform, { skip: skipScreen }),
|
|
403
|
+
];
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export function summarizeDoctor(checks) {
|
|
407
|
+
const failures = checks.filter(
|
|
408
|
+
(check) => check.required && check.status === "fail",
|
|
409
|
+
);
|
|
410
|
+
const warnings = checks.filter((check) => check.status === "warn");
|
|
411
|
+
return {
|
|
412
|
+
ok: failures.length === 0,
|
|
413
|
+
failures: failures.map((check) => check.id),
|
|
414
|
+
warnings: warnings.map((check) => check.id),
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export function runDoctor(argv, { log = console.log } = {}) {
|
|
419
|
+
const options = { root: undefined, json: false, skipScreen: false };
|
|
420
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
421
|
+
const token = argv[index];
|
|
422
|
+
if (token === "-h" || token === "--help" || token === "help") {
|
|
423
|
+
log(doctorHelp());
|
|
424
|
+
return 0;
|
|
425
|
+
}
|
|
426
|
+
if (token === "--json") {
|
|
427
|
+
options.json = true;
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
if (token === "--skip-screen") {
|
|
431
|
+
options.skipScreen = true;
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
if (token === "--root") {
|
|
435
|
+
const value = argv[index + 1];
|
|
436
|
+
if (!value || value.startsWith("-")) throw new Error("--root requires a value");
|
|
437
|
+
options.root = value;
|
|
438
|
+
index += 1;
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
throw new Error(`Unknown doctor argument: ${token}`);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const projectPath = resolveProjectPath(options.root);
|
|
445
|
+
const checks = collectDoctorChecks({
|
|
446
|
+
projectPath,
|
|
447
|
+
skipScreen: options.skipScreen,
|
|
448
|
+
});
|
|
449
|
+
const summary = summarizeDoctor(checks);
|
|
450
|
+
|
|
451
|
+
if (options.json) {
|
|
452
|
+
log(JSON.stringify({ project: projectPath, ...summary, checks }, null, 2));
|
|
453
|
+
return summary.ok ? 0 : 1;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
log(`astroshot doctor — ${projectPath}`);
|
|
457
|
+
log("");
|
|
458
|
+
for (const check of checks) {
|
|
459
|
+
const scope = check.required ? "required" : "optional";
|
|
460
|
+
log(`${SYMBOLS[check.status]} ${check.title} [${scope}]`);
|
|
461
|
+
log(` ${check.detail}`);
|
|
462
|
+
if (check.status === "fail" || check.status === "warn") {
|
|
463
|
+
log(` fix: ${check.remediation}`);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
log("");
|
|
467
|
+
if (summary.ok) {
|
|
468
|
+
log(
|
|
469
|
+
summary.warnings.length
|
|
470
|
+
? `All required checks pass (${summary.warnings.length} optional warning${summary.warnings.length === 1 ? "" : "s"} above).`
|
|
471
|
+
: "All checks pass. Try: astroshot demo",
|
|
472
|
+
);
|
|
473
|
+
return 0;
|
|
474
|
+
}
|
|
475
|
+
log(
|
|
476
|
+
`${summary.failures.length} required check${summary.failures.length === 1 ? "" : "s"} failed: ${summary.failures.join(", ")}`,
|
|
477
|
+
);
|
|
478
|
+
log("Run the fix line under each failure, then re-run: astroshot doctor");
|
|
479
|
+
return 1;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function resolveProjectPath(explicitRoot) {
|
|
483
|
+
if (explicitRoot) return path.resolve(explicitRoot);
|
|
484
|
+
try {
|
|
485
|
+
return execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
486
|
+
encoding: "utf8",
|
|
487
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
488
|
+
}).trim();
|
|
489
|
+
} catch {
|
|
490
|
+
return process.cwd();
|
|
491
|
+
}
|
|
492
|
+
}
|