screencast-axi 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +555 -0
- package/dist/bin/screencast-axi.d.ts +2 -0
- package/dist/bin/screencast-axi.js +16 -0
- package/dist/bin/screencast-axi.js.map +1 -0
- package/dist/src/auth/strategies.d.ts +54 -0
- package/dist/src/auth/strategies.js +137 -0
- package/dist/src/auth/strategies.js.map +1 -0
- package/dist/src/auth/types.d.ts +65 -0
- package/dist/src/auth/types.js +2 -0
- package/dist/src/auth/types.js.map +1 -0
- package/dist/src/browser.d.ts +100 -0
- package/dist/src/browser.js +168 -0
- package/dist/src/browser.js.map +1 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +160 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/commands/auth.d.ts +4 -0
- package/dist/src/commands/auth.js +207 -0
- package/dist/src/commands/auth.js.map +1 -0
- package/dist/src/commands/check.d.ts +12 -0
- package/dist/src/commands/check.js +103 -0
- package/dist/src/commands/check.js.map +1 -0
- package/dist/src/commands/doctor.d.ts +15 -0
- package/dist/src/commands/doctor.js +131 -0
- package/dist/src/commands/doctor.js.map +1 -0
- package/dist/src/commands/guide.d.ts +6 -0
- package/dist/src/commands/guide.js +257 -0
- package/dist/src/commands/guide.js.map +1 -0
- package/dist/src/commands/home.d.ts +12 -0
- package/dist/src/commands/home.js +78 -0
- package/dist/src/commands/home.js.map +1 -0
- package/dist/src/commands/list.d.ts +6 -0
- package/dist/src/commands/list.js +128 -0
- package/dist/src/commands/list.js.map +1 -0
- package/dist/src/commands/record.d.ts +7 -0
- package/dist/src/commands/record.js +266 -0
- package/dist/src/commands/record.js.map +1 -0
- package/dist/src/commands/scaffold.d.ts +4 -0
- package/dist/src/commands/scaffold.js +122 -0
- package/dist/src/commands/scaffold.js.map +1 -0
- package/dist/src/commands/setup.d.ts +14 -0
- package/dist/src/commands/setup.js +137 -0
- package/dist/src/commands/setup.js.map +1 -0
- package/dist/src/config.d.ts +126 -0
- package/dist/src/config.js +275 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/director.d.ts +217 -0
- package/dist/src/director.js +415 -0
- package/dist/src/director.js.map +1 -0
- package/dist/src/duration.d.ts +33 -0
- package/dist/src/duration.js +68 -0
- package/dist/src/duration.js.map +1 -0
- package/dist/src/encode.d.ts +67 -0
- package/dist/src/encode.js +225 -0
- package/dist/src/encode.js.map +1 -0
- package/dist/src/errors.d.ts +19 -0
- package/dist/src/errors.js +27 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/flags.d.ts +39 -0
- package/dist/src/flags.js +130 -0
- package/dist/src/flags.js.map +1 -0
- package/dist/src/forensics.d.ts +51 -0
- package/dist/src/forensics.js +107 -0
- package/dist/src/forensics.js.map +1 -0
- package/dist/src/hooks.d.ts +9 -0
- package/dist/src/hooks.js +20 -0
- package/dist/src/hooks.js.map +1 -0
- package/dist/src/index.d.ts +18 -0
- package/dist/src/index.js +18 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/inventory.d.ts +45 -0
- package/dist/src/inventory.js +89 -0
- package/dist/src/inventory.js.map +1 -0
- package/dist/src/manifest.d.ts +89 -0
- package/dist/src/manifest.js +137 -0
- package/dist/src/manifest.js.map +1 -0
- package/dist/src/output.d.ts +8 -0
- package/dist/src/output.js +2 -0
- package/dist/src/output.js.map +1 -0
- package/dist/src/overlay.d.ts +105 -0
- package/dist/src/overlay.js +302 -0
- package/dist/src/overlay.js.map +1 -0
- package/dist/src/reference.d.ts +1 -0
- package/dist/src/reference.js +108 -0
- package/dist/src/reference.js.map +1 -0
- package/dist/src/run.d.ts +82 -0
- package/dist/src/run.js +321 -0
- package/dist/src/run.js.map +1 -0
- package/dist/src/skill.d.ts +12 -0
- package/dist/src/skill.js +69 -0
- package/dist/src/skill.js.map +1 -0
- package/dist/src/toolchain.d.ts +62 -0
- package/dist/src/toolchain.js +127 -0
- package/dist/src/toolchain.js.map +1 -0
- package/dist/src/types.d.ts +88 -0
- package/dist/src/types.js +18 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/version.d.ts +10 -0
- package/dist/src/version.js +11 -0
- package/dist/src/version.js.map +1 -0
- package/package.json +93 -0
- package/skills/screencast-axi/SKILL.md +49 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { type EncodeSettings } from "./encode.js";
|
|
2
|
+
import { type DeepPartial, type OverlayTheme } from "./overlay.js";
|
|
3
|
+
import { type DefinedScenario, type Viewport } from "./types.js";
|
|
4
|
+
import type { AuthConfig, AuthStrategy } from "./auth/types.js";
|
|
5
|
+
/**
|
|
6
|
+
* Configuration, and finding the scenarios.
|
|
7
|
+
*
|
|
8
|
+
* A config file is the scale-up path, not the entry fee: `record ./clip.ts`
|
|
9
|
+
* works with no config at all, on defaults rooted at the current directory.
|
|
10
|
+
* Someone recording one page of a site they do not own should not have to
|
|
11
|
+
* learn a config format first.
|
|
12
|
+
*/
|
|
13
|
+
export interface BrowserConfig {
|
|
14
|
+
readonly headless: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Persistent Chrome profile directory.
|
|
17
|
+
*
|
|
18
|
+
* Present means a warm profile, so a session a human signed into by hand
|
|
19
|
+
* survives between takes. Absent means an isolated context per take.
|
|
20
|
+
*/
|
|
21
|
+
readonly profileDir?: string;
|
|
22
|
+
/** Overrides a device preset's own scale factor. Rarely wanted. */
|
|
23
|
+
readonly deviceScaleFactor?: number;
|
|
24
|
+
readonly args: readonly string[];
|
|
25
|
+
readonly colorScheme?: "light" | "dark";
|
|
26
|
+
readonly locale?: string;
|
|
27
|
+
readonly timezoneId?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface ScreencastConfig {
|
|
30
|
+
readonly outDir?: string;
|
|
31
|
+
readonly rawDir?: string;
|
|
32
|
+
/** File paths, directories, or `dir/*.ts` patterns. */
|
|
33
|
+
readonly scenarios?: readonly string[];
|
|
34
|
+
readonly baseUrl?: string;
|
|
35
|
+
readonly viewport?: Viewport;
|
|
36
|
+
readonly device?: string;
|
|
37
|
+
readonly pace?: number;
|
|
38
|
+
readonly deliverables?: Partial<EncodeSettings>;
|
|
39
|
+
readonly browser?: Partial<BrowserConfig>;
|
|
40
|
+
readonly overlay?: DeepPartial<OverlayTheme>;
|
|
41
|
+
/** One strategy, or several by name for `--auth <name>`. */
|
|
42
|
+
readonly auth?: AuthConfig;
|
|
43
|
+
readonly timeouts?: {
|
|
44
|
+
readonly setupMs?: number;
|
|
45
|
+
readonly runMs?: number;
|
|
46
|
+
/** Per-action timeout during a rehearsal. Deliberately short. */
|
|
47
|
+
readonly rehearseMs?: number;
|
|
48
|
+
/** Ceiling on how long `goto` waits for the network to go quiet. */
|
|
49
|
+
readonly settleMs?: number;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export interface ResolvedConfig {
|
|
53
|
+
/** Directory every relative path in the config resolves against. */
|
|
54
|
+
readonly rootDir: string;
|
|
55
|
+
/** Absolute path of the config file, or null when running without one. */
|
|
56
|
+
readonly configPath: string | null;
|
|
57
|
+
readonly outDir: string;
|
|
58
|
+
readonly rawDir: string;
|
|
59
|
+
readonly scenarios: readonly string[];
|
|
60
|
+
readonly baseUrl: string;
|
|
61
|
+
readonly viewport: Viewport;
|
|
62
|
+
readonly device?: string;
|
|
63
|
+
readonly pace: number;
|
|
64
|
+
readonly deliverables: EncodeSettings;
|
|
65
|
+
readonly browser: BrowserConfig;
|
|
66
|
+
readonly overlay: DeepPartial<OverlayTheme>;
|
|
67
|
+
readonly auth: Readonly<Record<string, AuthStrategy>>;
|
|
68
|
+
readonly timeouts: {
|
|
69
|
+
readonly setupMs: number;
|
|
70
|
+
readonly runMs: number;
|
|
71
|
+
readonly rehearseMs: number;
|
|
72
|
+
readonly settleMs: number;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export declare const CONFIG_BASENAMES: readonly ["screencast.config.ts", "screencast.config.mts", "screencast.config.js", "screencast.config.mjs"];
|
|
76
|
+
/**
|
|
77
|
+
* Chromium flags that make a capture look deliberate rather than incidental:
|
|
78
|
+
* no scrollbars in frame, sRGB so colours match the design, and no font
|
|
79
|
+
* hinting so text renders the same on every machine that reshoots.
|
|
80
|
+
*/
|
|
81
|
+
export declare const DEFAULT_BROWSER_ARGS: readonly ["--hide-scrollbars", "--force-color-profile=srgb", "--font-render-hinting=none"];
|
|
82
|
+
/** Identity function, for the types and the editor completion. */
|
|
83
|
+
export declare function defineConfig(config: ScreencastConfig): ScreencastConfig;
|
|
84
|
+
/** Walks up from `startDir` looking for a config file. */
|
|
85
|
+
export declare function findConfigPath(startDir: string): string | null;
|
|
86
|
+
export declare function resolveConfigPath(explicit?: string, cwd?: string): string | null;
|
|
87
|
+
export declare function loadConfig(explicit?: string, cwd?: string): Promise<ResolvedConfig>;
|
|
88
|
+
export declare function resolveConfig(raw: ScreencastConfig, configPath: string | null, cwd?: string): ResolvedConfig;
|
|
89
|
+
/**
|
|
90
|
+
* Expands one `scenarios` entry to concrete files.
|
|
91
|
+
*
|
|
92
|
+
* Supports a file path, a directory, `dir/*.ts` and `dir/**` - which is the
|
|
93
|
+
* whole realistic range. A full glob library would be a dependency bought for
|
|
94
|
+
* patterns nobody writes here.
|
|
95
|
+
*/
|
|
96
|
+
export declare function expandScenarioPattern(pattern: string, rootDir: string): string[];
|
|
97
|
+
export interface LoadedScenario {
|
|
98
|
+
readonly scenario: DefinedScenario;
|
|
99
|
+
/** Absolute path of the module it came from, for error messages. */
|
|
100
|
+
readonly file: string;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Loads every scenario the config points at, in declaration order.
|
|
104
|
+
*
|
|
105
|
+
* A module may export its scenario as `default`, as a named export, or several
|
|
106
|
+
* at once: the `defineScenario` stamp is what makes it findable, so nobody has
|
|
107
|
+
* to remember a naming convention.
|
|
108
|
+
*/
|
|
109
|
+
export declare function loadScenarios(config: ResolvedConfig): Promise<LoadedScenario[]>;
|
|
110
|
+
export declare function loadScenarioFiles(files: readonly string[]): Promise<LoadedScenario[]>;
|
|
111
|
+
/**
|
|
112
|
+
* Normalises the config's `auth` into a name -> strategy map.
|
|
113
|
+
*
|
|
114
|
+
* A single strategy becomes `{ default: it }`, so a scenario saying
|
|
115
|
+
* `auth: true` or nothing at all resolves without the config author having to
|
|
116
|
+
* name anything.
|
|
117
|
+
*/
|
|
118
|
+
export declare function normaliseAuth(auth: AuthConfig | undefined): Record<string, AuthStrategy>;
|
|
119
|
+
/**
|
|
120
|
+
* Picks the strategy for a take.
|
|
121
|
+
*
|
|
122
|
+
* `false` on a scenario, or `--no-auth`, means signed out - a deliberate
|
|
123
|
+
* choice worth being able to make per scenario, since a landing page and a
|
|
124
|
+
* dashboard often live in the same config.
|
|
125
|
+
*/
|
|
126
|
+
export declare function selectStrategy(config: ResolvedConfig, scenarioAuth: string | false | undefined, override?: string | false): AuthStrategy | null;
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { ScreencastError } from "./errors.js";
|
|
2
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
3
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import { DEFAULT_ENCODE_SETTINGS } from "./encode.js";
|
|
6
|
+
import { DEFAULT_SETTLE_MS } from "./director.js";
|
|
7
|
+
import { DEFAULT_OVERLAY_THEME } from "./overlay.js";
|
|
8
|
+
import { isScenario } from "./types.js";
|
|
9
|
+
import { noAuth } from "./auth/strategies.js";
|
|
10
|
+
export const CONFIG_BASENAMES = [
|
|
11
|
+
"screencast.config.ts",
|
|
12
|
+
"screencast.config.mts",
|
|
13
|
+
"screencast.config.js",
|
|
14
|
+
"screencast.config.mjs",
|
|
15
|
+
];
|
|
16
|
+
const DEFAULT_VIEWPORT = { width: 1600, height: 1000 };
|
|
17
|
+
/**
|
|
18
|
+
* Chromium flags that make a capture look deliberate rather than incidental:
|
|
19
|
+
* no scrollbars in frame, sRGB so colours match the design, and no font
|
|
20
|
+
* hinting so text renders the same on every machine that reshoots.
|
|
21
|
+
*/
|
|
22
|
+
export const DEFAULT_BROWSER_ARGS = [
|
|
23
|
+
"--hide-scrollbars",
|
|
24
|
+
"--force-color-profile=srgb",
|
|
25
|
+
"--font-render-hinting=none",
|
|
26
|
+
];
|
|
27
|
+
/** Identity function, for the types and the editor completion. */
|
|
28
|
+
export function defineConfig(config) {
|
|
29
|
+
return config;
|
|
30
|
+
}
|
|
31
|
+
/** Walks up from `startDir` looking for a config file. */
|
|
32
|
+
export function findConfigPath(startDir) {
|
|
33
|
+
let dir = resolve(startDir);
|
|
34
|
+
for (;;) {
|
|
35
|
+
for (const name of CONFIG_BASENAMES) {
|
|
36
|
+
const candidate = join(dir, name);
|
|
37
|
+
if (existsSync(candidate))
|
|
38
|
+
return candidate;
|
|
39
|
+
}
|
|
40
|
+
const parent = dirname(dir);
|
|
41
|
+
if (parent === dir)
|
|
42
|
+
return null;
|
|
43
|
+
dir = parent;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function resolveConfigPath(explicit, cwd = process.cwd()) {
|
|
47
|
+
const chosen = explicit ?? process.env["SCREENCAST_CONFIG"];
|
|
48
|
+
if (chosen) {
|
|
49
|
+
const absolute = isAbsolute(chosen) ? chosen : resolve(cwd, chosen);
|
|
50
|
+
if (!existsSync(absolute)) {
|
|
51
|
+
throw new ScreencastError(`Config not found: ${absolute}`, "CONFIG_NOT_FOUND", [
|
|
52
|
+
"Pass `--config <path>` pointing at an existing file",
|
|
53
|
+
"Or drop the flag to search upwards from the current directory",
|
|
54
|
+
]);
|
|
55
|
+
}
|
|
56
|
+
return absolute;
|
|
57
|
+
}
|
|
58
|
+
return findConfigPath(cwd);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Imports a module, registering `tsx` first if Node cannot load TypeScript.
|
|
62
|
+
*
|
|
63
|
+
* The package does not own transpilation. Node's own type stripping does not
|
|
64
|
+
* resolve tsconfig path aliases, which real scenarios use to reach a project's
|
|
65
|
+
* own code, so `tsx` is the documented way and an optional peer.
|
|
66
|
+
*/
|
|
67
|
+
let tsxRegistered = false;
|
|
68
|
+
async function importModule(file) {
|
|
69
|
+
const url = pathToFileURL(file).href;
|
|
70
|
+
try {
|
|
71
|
+
return (await import(url));
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
const code = error.code;
|
|
75
|
+
const needsLoader = code === "ERR_UNKNOWN_FILE_EXTENSION" ||
|
|
76
|
+
(error instanceof SyntaxError && /\.m?ts$/.test(file));
|
|
77
|
+
if (!needsLoader || tsxRegistered)
|
|
78
|
+
throw error;
|
|
79
|
+
try {
|
|
80
|
+
const specifier = "tsx/esm/api";
|
|
81
|
+
const tsx = (await import(specifier));
|
|
82
|
+
tsx.register?.();
|
|
83
|
+
tsxRegistered = true;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
throw new ScreencastError(`Cannot load ${file}: this Node cannot run TypeScript directly`, "TS_LOADER_MISSING", ["Install the loader: `pnpm add -D tsx`", "Or write the file as .mjs instead of .ts"]);
|
|
87
|
+
}
|
|
88
|
+
// Cache-bust so the retry does not get the failed module record back.
|
|
89
|
+
return (await import(`${url}?tsx=1`));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export async function loadConfig(explicit, cwd = process.cwd()) {
|
|
93
|
+
const configPath = resolveConfigPath(explicit, cwd);
|
|
94
|
+
const raw = configPath
|
|
95
|
+
? ((await importModule(configPath))["default"] ?? {})
|
|
96
|
+
: {};
|
|
97
|
+
return resolveConfig(raw, configPath, cwd);
|
|
98
|
+
}
|
|
99
|
+
export function resolveConfig(raw, configPath, cwd = process.cwd()) {
|
|
100
|
+
// Relative paths anchor to the config file, never to the shell's cwd -
|
|
101
|
+
// otherwise the same command means different things from different
|
|
102
|
+
// directories in the same repo.
|
|
103
|
+
const rootDir = configPath ? dirname(configPath) : resolve(cwd);
|
|
104
|
+
const at = (p) => (isAbsolute(p) ? p : resolve(rootDir, p));
|
|
105
|
+
const browser = {
|
|
106
|
+
headless: raw.browser?.headless ?? true,
|
|
107
|
+
...(raw.browser?.profileDir ? { profileDir: at(raw.browser.profileDir) } : {}),
|
|
108
|
+
// No default: a device preset carries its own (2-3 on phones), and a
|
|
109
|
+
// config-level default of 1 would silently override it and throw away the
|
|
110
|
+
// detail that makes a phone clip readable.
|
|
111
|
+
...(raw.browser?.deviceScaleFactor !== undefined
|
|
112
|
+
? { deviceScaleFactor: raw.browser.deviceScaleFactor }
|
|
113
|
+
: {}),
|
|
114
|
+
args: raw.browser?.args ?? DEFAULT_BROWSER_ARGS,
|
|
115
|
+
...(raw.browser?.colorScheme ? { colorScheme: raw.browser.colorScheme } : {}),
|
|
116
|
+
...(raw.browser?.locale ? { locale: raw.browser.locale } : {}),
|
|
117
|
+
...(raw.browser?.timezoneId ? { timezoneId: raw.browser.timezoneId } : {}),
|
|
118
|
+
};
|
|
119
|
+
return {
|
|
120
|
+
rootDir,
|
|
121
|
+
configPath,
|
|
122
|
+
outDir: at(raw.outDir ?? "screencasts"),
|
|
123
|
+
rawDir: at(raw.rawDir ?? ".screencast/raw"),
|
|
124
|
+
scenarios: raw.scenarios ?? [],
|
|
125
|
+
baseUrl: raw.baseUrl ?? "http://localhost:3000",
|
|
126
|
+
viewport: raw.viewport ?? DEFAULT_VIEWPORT,
|
|
127
|
+
...(raw.device ? { device: raw.device } : {}),
|
|
128
|
+
pace: raw.pace ?? 1,
|
|
129
|
+
deliverables: { ...DEFAULT_ENCODE_SETTINGS, ...raw.deliverables },
|
|
130
|
+
browser,
|
|
131
|
+
overlay: { ...DEFAULT_OVERLAY_THEME, ...raw.overlay },
|
|
132
|
+
auth: normaliseAuth(raw.auth),
|
|
133
|
+
timeouts: {
|
|
134
|
+
setupMs: raw.timeouts?.setupMs ?? 120_000,
|
|
135
|
+
runMs: raw.timeouts?.runMs ?? 300_000,
|
|
136
|
+
rehearseMs: raw.timeouts?.rehearseMs ?? 8_000,
|
|
137
|
+
settleMs: raw.timeouts?.settleMs ?? DEFAULT_SETTLE_MS,
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const SCENARIO_EXTENSIONS = [".ts", ".mts", ".js", ".mjs"];
|
|
142
|
+
function isScenarioFile(file) {
|
|
143
|
+
return SCENARIO_EXTENSIONS.some((ext) => file.endsWith(ext)) && !file.endsWith(".d.ts");
|
|
144
|
+
}
|
|
145
|
+
function walk(dir, recursive) {
|
|
146
|
+
const found = [];
|
|
147
|
+
for (const name of readdirSync(dir).sort()) {
|
|
148
|
+
const full = join(dir, name);
|
|
149
|
+
const stats = statSync(full);
|
|
150
|
+
if (stats.isDirectory()) {
|
|
151
|
+
if (recursive)
|
|
152
|
+
found.push(...walk(full, true));
|
|
153
|
+
}
|
|
154
|
+
else if (isScenarioFile(name)) {
|
|
155
|
+
found.push(full);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return found;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Expands one `scenarios` entry to concrete files.
|
|
162
|
+
*
|
|
163
|
+
* Supports a file path, a directory, `dir/*.ts` and `dir/**` - which is the
|
|
164
|
+
* whole realistic range. A full glob library would be a dependency bought for
|
|
165
|
+
* patterns nobody writes here.
|
|
166
|
+
*/
|
|
167
|
+
export function expandScenarioPattern(pattern, rootDir) {
|
|
168
|
+
const absolute = isAbsolute(pattern) ? pattern : join(rootDir, pattern);
|
|
169
|
+
if (!absolute.includes("*")) {
|
|
170
|
+
if (!existsSync(absolute))
|
|
171
|
+
return [];
|
|
172
|
+
return statSync(absolute).isDirectory() ? walk(absolute, false) : [absolute];
|
|
173
|
+
}
|
|
174
|
+
const star = absolute.indexOf("*");
|
|
175
|
+
const base = absolute.slice(0, star);
|
|
176
|
+
const dir = base.endsWith(sep) ? base.slice(0, -1) : dirname(base);
|
|
177
|
+
if (!existsSync(dir))
|
|
178
|
+
return [];
|
|
179
|
+
const recursive = absolute.includes("**");
|
|
180
|
+
const suffix = absolute.slice(absolute.lastIndexOf("*") + 1);
|
|
181
|
+
return walk(dir, recursive).filter((f) => (suffix ? f.endsWith(suffix) : true));
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Loads every scenario the config points at, in declaration order.
|
|
185
|
+
*
|
|
186
|
+
* A module may export its scenario as `default`, as a named export, or several
|
|
187
|
+
* at once: the `defineScenario` stamp is what makes it findable, so nobody has
|
|
188
|
+
* to remember a naming convention.
|
|
189
|
+
*/
|
|
190
|
+
export async function loadScenarios(config) {
|
|
191
|
+
const files = [];
|
|
192
|
+
for (const pattern of config.scenarios) {
|
|
193
|
+
for (const file of expandScenarioPattern(pattern, config.rootDir)) {
|
|
194
|
+
if (!files.includes(file))
|
|
195
|
+
files.push(file);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return loadScenarioFiles(files);
|
|
199
|
+
}
|
|
200
|
+
export async function loadScenarioFiles(files) {
|
|
201
|
+
const loaded = [];
|
|
202
|
+
const seen = new Map();
|
|
203
|
+
for (const file of files) {
|
|
204
|
+
const module = await importModule(file);
|
|
205
|
+
const found = Object.values(module).filter(isScenario);
|
|
206
|
+
if (found.length === 0) {
|
|
207
|
+
throw new ScreencastError(`No scenario exported by ${file}`, "NO_SCENARIO", [
|
|
208
|
+
"Wrap the object: `export default defineScenario({ ... })`",
|
|
209
|
+
"`defineScenario` is imported from `screencast-axi`",
|
|
210
|
+
]);
|
|
211
|
+
}
|
|
212
|
+
for (const scenario of found) {
|
|
213
|
+
const previous = seen.get(scenario.id);
|
|
214
|
+
if (previous) {
|
|
215
|
+
// Two clips writing the same file stem would silently overwrite each
|
|
216
|
+
// other, and whichever ran last would win.
|
|
217
|
+
throw new ScreencastError(`Duplicate scenario id \`${scenario.id}\``, "DUPLICATE_SCENARIO", [
|
|
218
|
+
`Defined in ${relative(process.cwd(), previous)} and ${relative(process.cwd(), file)}`,
|
|
219
|
+
"Ids are the output file stem, so they have to be unique",
|
|
220
|
+
]);
|
|
221
|
+
}
|
|
222
|
+
seen.set(scenario.id, file);
|
|
223
|
+
loaded.push({ scenario, file });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return loaded;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Normalises the config's `auth` into a name -> strategy map.
|
|
230
|
+
*
|
|
231
|
+
* A single strategy becomes `{ default: it }`, so a scenario saying
|
|
232
|
+
* `auth: true` or nothing at all resolves without the config author having to
|
|
233
|
+
* name anything.
|
|
234
|
+
*/
|
|
235
|
+
export function normaliseAuth(auth) {
|
|
236
|
+
if (!auth)
|
|
237
|
+
return { none: noAuth() };
|
|
238
|
+
if (typeof auth.name === "string") {
|
|
239
|
+
return { default: auth };
|
|
240
|
+
}
|
|
241
|
+
return auth;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Picks the strategy for a take.
|
|
245
|
+
*
|
|
246
|
+
* `false` on a scenario, or `--no-auth`, means signed out - a deliberate
|
|
247
|
+
* choice worth being able to make per scenario, since a landing page and a
|
|
248
|
+
* dashboard often live in the same config.
|
|
249
|
+
*/
|
|
250
|
+
export function selectStrategy(config, scenarioAuth, override) {
|
|
251
|
+
const chosen = override !== undefined ? override : scenarioAuth;
|
|
252
|
+
if (chosen === false)
|
|
253
|
+
return null;
|
|
254
|
+
const names = Object.keys(config.auth);
|
|
255
|
+
if (chosen === undefined) {
|
|
256
|
+
// Only a strategy the config supplied on its own - `auth: profileAuth()`,
|
|
257
|
+
// normalised to `default` - applies without being asked for. A named map
|
|
258
|
+
// never does, even when it holds exactly one entry: otherwise adding a
|
|
259
|
+
// second name would silently change what every unmarked scenario does, and
|
|
260
|
+
// a config with one login would quietly sign every take into it.
|
|
261
|
+
const strategy = config.auth["default"];
|
|
262
|
+
return !strategy || strategy.name === "none" ? null : strategy;
|
|
263
|
+
}
|
|
264
|
+
const strategy = config.auth[chosen];
|
|
265
|
+
if (!strategy) {
|
|
266
|
+
throw new ScreencastError(`Unknown auth strategy: ${chosen}`, "UNKNOWN_AUTH", [
|
|
267
|
+
names.length > 0
|
|
268
|
+
? `This config defines: ${names.join(", ")}`
|
|
269
|
+
: "No auth strategies are configured. Add one under `auth` in screencast.config.ts",
|
|
270
|
+
"Use `--no-auth` to record signed out",
|
|
271
|
+
]);
|
|
272
|
+
}
|
|
273
|
+
return strategy.name === "none" ? null : strategy;
|
|
274
|
+
}
|
|
275
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import type { Locator, Page } from "playwright";
|
|
2
|
+
import type { Target } from "./types.js";
|
|
3
|
+
export interface DirectorOptions {
|
|
4
|
+
/** Base URL used to resolve relative paths passed to `goto`. */
|
|
5
|
+
readonly baseUrl: string;
|
|
6
|
+
/**
|
|
7
|
+
* Playback speed multiplier applied to every pause and tween. 1 = natural
|
|
8
|
+
* demo pace, 0.5 = twice as fast. Kept as a knob because the right pace
|
|
9
|
+
* differs between a 6-second GIF and a 30-second explainer.
|
|
10
|
+
*/
|
|
11
|
+
readonly pace: number;
|
|
12
|
+
/** The scenario's narration script, addressed by index from `step()`. */
|
|
13
|
+
readonly steps?: readonly string[];
|
|
14
|
+
/**
|
|
15
|
+
* How long {@link Director.goto} will wait for the network to go quiet.
|
|
16
|
+
*
|
|
17
|
+
* Settling is an optimisation - start acting once the page has stopped
|
|
18
|
+
* moving - not a requirement, and it has to be bounded because plenty of
|
|
19
|
+
* real sites never go quiet at all. GitHub does not: analytics, websockets
|
|
20
|
+
* and polling keep at least one request in flight forever. Waiting on the
|
|
21
|
+
* default timeout there put up to 30 seconds of dead air *inside the clip*,
|
|
22
|
+
* silently, because the failure is caught and ignored.
|
|
23
|
+
*/
|
|
24
|
+
readonly settleMs?: number;
|
|
25
|
+
}
|
|
26
|
+
/** One thing the scenario did, for the run to report afterwards. */
|
|
27
|
+
export interface DirectorAction {
|
|
28
|
+
/** Milliseconds since the Director was created. */
|
|
29
|
+
readonly atMs: number;
|
|
30
|
+
readonly kind: "goto" | "click" | "doubleClick" | "type" | "press" | "drag" | "scroll" | "waitFor" | "step";
|
|
31
|
+
/** What it acted on, as written in the scenario. */
|
|
32
|
+
readonly target?: string;
|
|
33
|
+
/** Typed text, a key, a distance - whatever makes the line readable. */
|
|
34
|
+
readonly detail?: string;
|
|
35
|
+
}
|
|
36
|
+
/** Default ceiling on the post-navigation settle wait. */
|
|
37
|
+
export declare const DEFAULT_SETTLE_MS = 2500;
|
|
38
|
+
/** One stop on a {@link Director.tour}. */
|
|
39
|
+
export interface TourStop {
|
|
40
|
+
/** Path or absolute URL to open. */
|
|
41
|
+
readonly path: string;
|
|
42
|
+
/** Index into the scenario's `steps`. Preferred over `caption`. */
|
|
43
|
+
readonly step?: number;
|
|
44
|
+
/** Ad-hoc caption, for a tour whose narration is not in `steps`. */
|
|
45
|
+
readonly caption?: string;
|
|
46
|
+
/** Scroll after the caption lands: a fraction of the viewport, or pixels. */
|
|
47
|
+
readonly scroll?: number;
|
|
48
|
+
/** Override the shared dwell for this stop. */
|
|
49
|
+
readonly dwellMs?: number;
|
|
50
|
+
/** Wait for this before narrating, so a slow page does not get a jump cut. */
|
|
51
|
+
readonly waitFor?: Target;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The scenario-facing API. Every method is written so the resulting video is
|
|
55
|
+
* *watchable*: the pointer travels rather than teleports, clicks are preceded
|
|
56
|
+
* by a beat, and typing is per-character.
|
|
57
|
+
*
|
|
58
|
+
* Raw Playwright is still available via `director.page` for anything the
|
|
59
|
+
* helpers do not cover - but prefer adding a helper here over reaching past it,
|
|
60
|
+
* so all clips share one visual language.
|
|
61
|
+
*/
|
|
62
|
+
export declare class Director {
|
|
63
|
+
readonly page: Page;
|
|
64
|
+
private readonly opts;
|
|
65
|
+
private readonly contextCreatedAt;
|
|
66
|
+
private pointer;
|
|
67
|
+
/** ms since the context was created, at the moment the clip proper began. */
|
|
68
|
+
private clipStartedAt;
|
|
69
|
+
/** Indices passed to {@link step}, in the order the take showed them. */
|
|
70
|
+
private readonly shown;
|
|
71
|
+
/**
|
|
72
|
+
* Milliseconds spent in pauses this class controls.
|
|
73
|
+
*
|
|
74
|
+
* A take is `fixed + pace x scalable`: the app's own waits - navigation, a
|
|
75
|
+
* network round trip, an animation - do not get slower because the recorder
|
|
76
|
+
* does. Tracking the scalable half is what lets `--duration` solve for a
|
|
77
|
+
* pace exactly rather than assuming the whole clip scales.
|
|
78
|
+
*/
|
|
79
|
+
private pausedMs;
|
|
80
|
+
/**
|
|
81
|
+
* Everything the scenario did, in order.
|
|
82
|
+
*
|
|
83
|
+
* A scenario is arbitrary code driving a real browser, often a signed-in
|
|
84
|
+
* one, so "what will this actually do" is a fair question to want answered
|
|
85
|
+
* before trusting it. Watching a headed run answers it once; this answers it
|
|
86
|
+
* in a form that can be read, diffed and kept.
|
|
87
|
+
*/
|
|
88
|
+
private readonly actions;
|
|
89
|
+
constructor(page: Page, opts: DirectorOptions, contextCreatedAt: number);
|
|
90
|
+
/** Seconds of dead air at the head of the raw video, for the encoder to trim. */
|
|
91
|
+
get trimStartSeconds(): number;
|
|
92
|
+
/** Marks the end of setup. Everything before this is trimmed off the clip. */
|
|
93
|
+
markClipStart(): void;
|
|
94
|
+
private scaled;
|
|
95
|
+
/** Pace-scaled pause. Every wait this class owns goes through here. */
|
|
96
|
+
private pause;
|
|
97
|
+
/** How much of the take so far was pause this class controls, in ms. */
|
|
98
|
+
get scaledPauseMs(): number;
|
|
99
|
+
/** Everything the scenario did, in order. */
|
|
100
|
+
get performed(): readonly DirectorAction[];
|
|
101
|
+
private record;
|
|
102
|
+
/** A deliberate pause so the viewer can read what just happened. */
|
|
103
|
+
beat(ms?: number): Promise<void>;
|
|
104
|
+
goto(path: string): Promise<void>;
|
|
105
|
+
/** Which script lines this take put on screen, in order. */
|
|
106
|
+
get shownSteps(): readonly number[];
|
|
107
|
+
/**
|
|
108
|
+
* Puts line `index` of the scenario's `steps` on screen.
|
|
109
|
+
*
|
|
110
|
+
* Scenarios narrate through this rather than through {@link caption} so the
|
|
111
|
+
* text has one home: the same array becomes the burnt-in caption, the
|
|
112
|
+
* manifest's step list and the text the landing page renders beside the clip.
|
|
113
|
+
* `record.ts` checks afterwards that the take used every line exactly once,
|
|
114
|
+
* in order, which is what stops the written workflow from drifting away from
|
|
115
|
+
* the recorded one.
|
|
116
|
+
*/
|
|
117
|
+
step(index: number, holdMs?: number): Promise<void>;
|
|
118
|
+
/** Shows a caption at the bottom of the frame. Pass `null` to clear it. */
|
|
119
|
+
caption(text: string | null, holdMs?: number): Promise<void>;
|
|
120
|
+
locator(target: Target): Locator;
|
|
121
|
+
/** Whether this target is a field whose value should never be logged. */
|
|
122
|
+
private isSecret;
|
|
123
|
+
private pointOf;
|
|
124
|
+
/** Glides the pointer to `target` along an eased path. */
|
|
125
|
+
moveTo(target: Target, { steps }?: {
|
|
126
|
+
steps?: number;
|
|
127
|
+
}): Promise<void>;
|
|
128
|
+
private glide;
|
|
129
|
+
/** Move, settle, then click - the pause is what makes the click legible. */
|
|
130
|
+
click(target: Target, { settleMs }?: {
|
|
131
|
+
settleMs?: number;
|
|
132
|
+
}): Promise<void>;
|
|
133
|
+
/**
|
|
134
|
+
* The click itself, without logging it.
|
|
135
|
+
*
|
|
136
|
+
* `type` focuses its field by clicking it, which is one action from the
|
|
137
|
+
* scenario's point of view. Logging the inner click too would put a
|
|
138
|
+
* phantom step in the record of what the take did.
|
|
139
|
+
*/
|
|
140
|
+
private performClick;
|
|
141
|
+
doubleClick(target: Target): Promise<void>;
|
|
142
|
+
/**
|
|
143
|
+
* Clicks the field, then types character by character. `clear` selects the
|
|
144
|
+
* existing value first, so typing replaces it instead of appending - what a
|
|
145
|
+
* person does to a field that already holds a default.
|
|
146
|
+
*/
|
|
147
|
+
type(target: Target, text: string, { delay, clear }?: {
|
|
148
|
+
delay?: number;
|
|
149
|
+
clear?: boolean;
|
|
150
|
+
}): Promise<void>;
|
|
151
|
+
press(key: string): Promise<void>;
|
|
152
|
+
/**
|
|
153
|
+
* Press-move-release drag. Deliberately slow and eased: this is the shot the
|
|
154
|
+
* whole harness exists for (reordering a tree row, moving a kanban card), and
|
|
155
|
+
* a fast drag reads as a teleport once it is re-encoded at 30fps.
|
|
156
|
+
*/
|
|
157
|
+
drag(from: Target, to: Target, { steps, liftMs, dropMs, }?: {
|
|
158
|
+
steps?: number;
|
|
159
|
+
liftMs?: number;
|
|
160
|
+
dropMs?: number;
|
|
161
|
+
}): Promise<void>;
|
|
162
|
+
/**
|
|
163
|
+
* Drag for the parts of the product that move things with the HTML5
|
|
164
|
+
* drag-and-drop API (`draggable` + `dragstart`/`dragover`/`drop`) rather than
|
|
165
|
+
* with raw pointer events: the process-mapper components palette, the kanban
|
|
166
|
+
* board.
|
|
167
|
+
*
|
|
168
|
+
* Chromium does not synthesise those events from `mouse.down`/`move`/`up`, so
|
|
169
|
+
* {@link drag} silently does nothing there. This dispatches the protocol
|
|
170
|
+
* itself - one `DataTransfer` shared by every event of the gesture, which is
|
|
171
|
+
* what carries the payload the source sets in `dragstart` to the drop
|
|
172
|
+
* handler - while still moving the real pointer, so the overlay cursor shows
|
|
173
|
+
* a hand carrying the thing across the screen.
|
|
174
|
+
*
|
|
175
|
+
* The mouse button is deliberately *not* pressed. A real `mouse.down` on a
|
|
176
|
+
* `draggable` element makes Chromium open its own native drag session, which
|
|
177
|
+
* then swallows every following `mousemove` - the overlay pointer freezes at
|
|
178
|
+
* the source and the clip shows a thing teleporting. Without the press the
|
|
179
|
+
* pointer travels normally, and `setDragging` puts a ghost chip under it so
|
|
180
|
+
* the gesture still reads as carrying something.
|
|
181
|
+
*/
|
|
182
|
+
dragHtml5(from: Target, to: Target, { steps, liftMs, dropMs, }?: {
|
|
183
|
+
steps?: number;
|
|
184
|
+
liftMs?: number;
|
|
185
|
+
dropMs?: number;
|
|
186
|
+
}): Promise<void>;
|
|
187
|
+
private setDragging;
|
|
188
|
+
/**
|
|
189
|
+
* Dispatches one drag event at `at`, on whatever is under that point.
|
|
190
|
+
*
|
|
191
|
+
* `dragenter`/`dragleave` are kept in step with the element under the pointer
|
|
192
|
+
* because a drop zone that only arms itself on `dragenter` would otherwise
|
|
193
|
+
* never accept the drop. `drop` also ends the gesture with `dragend` on the
|
|
194
|
+
* source, which is where a well-behaved source cleans up its drag state.
|
|
195
|
+
*/
|
|
196
|
+
private dndEvent;
|
|
197
|
+
/** Smooth wheel scroll, in the page or in whatever is under the pointer. */
|
|
198
|
+
scrollBy(deltaY: number, { steps }?: {
|
|
199
|
+
steps?: number;
|
|
200
|
+
}): Promise<void>;
|
|
201
|
+
waitFor(target: Target): Promise<void>;
|
|
202
|
+
/**
|
|
203
|
+
* A multi-page walkthrough: go somewhere, narrate it, let it breathe, move on.
|
|
204
|
+
*
|
|
205
|
+
* This is the shape most requests actually have ("a walkthrough of the five
|
|
206
|
+
* main pages"), and writing it by hand is fifty lines of goto/caption/scroll
|
|
207
|
+
* whose only real content is the paths. Going through one helper also keeps
|
|
208
|
+
* the rhythm identical between stops, which is most of what makes a tour
|
|
209
|
+
* watchable rather than a run of jump cuts.
|
|
210
|
+
*
|
|
211
|
+
* Every pause here is pace-scaled like any other, so `--duration` re-cuts a
|
|
212
|
+
* tour without the scenario changing.
|
|
213
|
+
*/
|
|
214
|
+
tour(stops: readonly TourStop[], { dwellMs }?: {
|
|
215
|
+
dwellMs?: number;
|
|
216
|
+
}): Promise<void>;
|
|
217
|
+
}
|