screenci 0.0.44 → 0.0.46
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/LICENCE +21 -0
- package/README.md +77 -195
- package/dist/cli.d.ts +22 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +640 -767
- package/dist/cli.js.map +1 -1
- package/dist/docs/manifest.d.ts +529 -0
- package/dist/docs/manifest.d.ts.map +1 -0
- package/dist/docs/manifest.js +243 -0
- package/dist/docs/manifest.js.map +1 -0
- package/dist/docs/video-sources/assets-and-overlays.video.d.ts +2 -0
- package/dist/docs/video-sources/assets-and-overlays.video.d.ts.map +1 -0
- package/dist/docs/video-sources/assets-and-overlays.video.js +40 -0
- package/dist/docs/video-sources/assets-and-overlays.video.js.map +1 -0
- package/dist/docs/video-sources/camera-and-zooming.video.d.ts +2 -0
- package/dist/docs/video-sources/camera-and-zooming.video.d.ts.map +1 -0
- package/dist/docs/video-sources/camera-and-zooming.video.js +37 -0
- package/dist/docs/video-sources/camera-and-zooming.video.js.map +1 -0
- package/dist/docs/video-sources/ci-setup.video.d.ts +2 -0
- package/dist/docs/video-sources/ci-setup.video.d.ts.map +1 -0
- package/dist/docs/video-sources/ci-setup.video.js +37 -0
- package/dist/docs/video-sources/ci-setup.video.js.map +1 -0
- package/dist/docs/video-sources/cli.video.d.ts +2 -0
- package/dist/docs/video-sources/cli.video.d.ts.map +1 -0
- package/dist/docs/video-sources/cli.video.js +37 -0
- package/dist/docs/video-sources/cli.video.js.map +1 -0
- package/dist/docs/video-sources/docs-shared.d.ts +5 -0
- package/dist/docs/video-sources/docs-shared.d.ts.map +1 -0
- package/dist/docs/video-sources/docs-shared.js +14 -0
- package/dist/docs/video-sources/docs-shared.js.map +1 -0
- package/dist/docs/video-sources/generating-videos.video.d.ts +2 -0
- package/dist/docs/video-sources/generating-videos.video.d.ts.map +1 -0
- package/dist/docs/video-sources/generating-videos.video.js +37 -0
- package/dist/docs/video-sources/generating-videos.video.js.map +1 -0
- package/dist/docs/video-sources/installation.video.d.ts +2 -0
- package/dist/docs/video-sources/installation.video.d.ts.map +1 -0
- package/dist/docs/video-sources/installation.video.js +24 -0
- package/dist/docs/video-sources/installation.video.js.map +1 -0
- package/dist/docs/video-sources/narration-and-localization.video.d.ts +2 -0
- package/dist/docs/video-sources/narration-and-localization.video.d.ts.map +1 -0
- package/dist/docs/video-sources/narration-and-localization.video.js +40 -0
- package/dist/docs/video-sources/narration-and-localization.video.js.map +1 -0
- package/dist/docs/video-sources/public-urls-and-embeds.video.d.ts +2 -0
- package/dist/docs/video-sources/public-urls-and-embeds.video.d.ts.map +1 -0
- package/dist/docs/video-sources/public-urls-and-embeds.video.js +37 -0
- package/dist/docs/video-sources/public-urls-and-embeds.video.js.map +1 -0
- package/dist/docs/video-sources/record-and-publish.video.d.ts +2 -0
- package/dist/docs/video-sources/record-and-publish.video.d.ts.map +1 -0
- package/dist/docs/video-sources/record-and-publish.video.js +37 -0
- package/dist/docs/video-sources/record-and-publish.video.js.map +1 -0
- package/dist/docs/video-sources/run-and-debug-videos.video.d.ts +2 -0
- package/dist/docs/video-sources/run-and-debug-videos.video.d.ts.map +1 -0
- package/dist/docs/video-sources/run-and-debug-videos.video.js +37 -0
- package/dist/docs/video-sources/run-and-debug-videos.video.js.map +1 -0
- package/dist/docs/video-sources/write-video-scripts.video.d.ts +2 -0
- package/dist/docs/video-sources/write-video-scripts.video.d.ts.map +1 -0
- package/dist/docs/video-sources/write-video-scripts.video.js +40 -0
- package/dist/docs/video-sources/write-video-scripts.video.js.map +1 -0
- package/dist/docs/videos.d.ts +94 -0
- package/dist/docs/videos.d.ts.map +1 -0
- package/dist/docs/videos.js +54 -0
- package/dist/docs/videos.js.map +1 -0
- package/dist/index.d.ts +4 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -6
- package/dist/index.js.map +1 -1
- package/dist/src/asset.d.ts +10 -9
- package/dist/src/asset.d.ts.map +1 -1
- package/dist/src/asset.js +7 -13
- package/dist/src/asset.js.map +1 -1
- package/dist/src/autoZoom.d.ts +3 -33
- package/dist/src/autoZoom.d.ts.map +1 -1
- package/dist/src/autoZoom.js +46 -51
- package/dist/src/autoZoom.js.map +1 -1
- package/dist/src/changeFocus.d.ts.map +1 -1
- package/dist/src/changeFocus.js +3 -2
- package/dist/src/changeFocus.js.map +1 -1
- package/dist/src/config.d.ts +5 -7
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js +44 -71
- package/dist/src/config.js.map +1 -1
- package/dist/src/cue.d.ts +13 -26
- package/dist/src/cue.d.ts.map +1 -1
- package/dist/src/cue.js +61 -97
- package/dist/src/cue.js.map +1 -1
- package/dist/src/customVoiceRef.d.ts +3 -0
- package/dist/src/customVoiceRef.d.ts.map +1 -0
- package/dist/src/customVoiceRef.js +7 -0
- package/dist/src/customVoiceRef.js.map +1 -0
- package/dist/src/defaults.d.ts +5 -5
- package/dist/src/defaults.d.ts.map +1 -1
- package/dist/src/defaults.js +13 -7
- package/dist/src/defaults.js.map +1 -1
- package/dist/src/events.d.ts +7 -6
- package/dist/src/events.d.ts.map +1 -1
- package/dist/src/events.js +17 -0
- package/dist/src/events.js.map +1 -1
- package/dist/src/fileSystemName.d.ts +2 -0
- package/dist/src/fileSystemName.d.ts.map +1 -0
- package/dist/src/fileSystemName.js +44 -0
- package/dist/src/fileSystemName.js.map +1 -0
- package/dist/src/hide.d.ts +1 -1
- package/dist/src/hide.d.ts.map +1 -1
- package/dist/src/hide.js +12 -15
- package/dist/src/hide.js.map +1 -1
- package/dist/src/init.d.ts +12 -0
- package/dist/src/init.d.ts.map +1 -0
- package/dist/src/init.js +674 -0
- package/dist/src/init.js.map +1 -0
- package/dist/src/instrument.d.ts +1 -0
- package/dist/src/instrument.d.ts.map +1 -1
- package/dist/src/instrument.js +29 -10
- package/dist/src/instrument.js.map +1 -1
- package/dist/src/manualZoom.d.ts.map +1 -1
- package/dist/src/manualZoom.js +13 -13
- package/dist/src/manualZoom.js.map +1 -1
- package/dist/src/mouse.d.ts.map +1 -1
- package/dist/src/mouse.js +4 -1
- package/dist/src/mouse.js.map +1 -1
- package/dist/src/recording.d.ts +2 -2
- package/dist/src/recording.d.ts.map +1 -1
- package/dist/src/runtimeContext.d.ts +81 -0
- package/dist/src/runtimeContext.d.ts.map +1 -0
- package/dist/src/runtimeContext.js +95 -0
- package/dist/src/runtimeContext.js.map +1 -0
- package/dist/src/runtimeMode.d.ts +8 -0
- package/dist/src/runtimeMode.d.ts.map +1 -0
- package/dist/src/runtimeMode.js +22 -0
- package/dist/src/runtimeMode.js.map +1 -0
- package/dist/src/titleValidation.d.ts +3 -0
- package/dist/src/titleValidation.d.ts.map +1 -0
- package/dist/src/titleValidation.js +19 -0
- package/dist/src/titleValidation.js.map +1 -0
- package/dist/src/types.d.ts +42 -15
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js.map +1 -1
- package/dist/src/video.d.ts +6 -1
- package/dist/src/video.d.ts.map +1 -1
- package/dist/src/video.js +94 -46
- package/dist/src/video.js.map +1 -1
- package/dist/src/voices.d.ts +8 -11
- package/dist/src/voices.d.ts.map +1 -1
- package/dist/src/voices.js +13 -8
- package/dist/src/voices.js.map +1 -1
- package/dist/src/zoom.d.ts +1 -1
- package/dist/src/zoom.d.ts.map +1 -1
- package/dist/test-fixtures/record-all-or-nothing.config.d.ts +8 -0
- package/dist/test-fixtures/record-all-or-nothing.config.d.ts.map +1 -0
- package/dist/test-fixtures/record-all-or-nothing.config.js +7 -0
- package/dist/test-fixtures/record-all-or-nothing.config.js.map +1 -0
- package/dist/test-fixtures/record-upload-all-or-nothing.config.d.ts +8 -0
- package/dist/test-fixtures/record-upload-all-or-nothing.config.d.ts.map +1 -0
- package/dist/test-fixtures/record-upload-all-or-nothing.config.js +7 -0
- package/dist/test-fixtures/record-upload-all-or-nothing.config.js.map +1 -0
- package/dist/test-fixtures/record-upload.config.d.ts +5 -0
- package/dist/test-fixtures/record-upload.config.d.ts.map +1 -0
- package/dist/test-fixtures/record-upload.config.js +4 -0
- package/dist/test-fixtures/record-upload.config.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -2
- package/skills/screenci/SKILL.md +6 -1
- package/skills/screenci/references/init.md +10 -12
- package/dist/src/reporter.d.ts +0 -9
- package/dist/src/reporter.d.ts.map +0 -1
- package/dist/src/reporter.js +0 -50
- package/dist/src/reporter.js.map +0 -1
- package/dist/src/sanitize.d.ts +0 -5
- package/dist/src/sanitize.d.ts.map +0 -1
- package/dist/src/sanitize.js +0 -11
- package/dist/src/sanitize.js.map +0 -1
package/dist/src/init.js
ADDED
|
@@ -0,0 +1,674 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { existsSync, realpathSync } from 'fs';
|
|
3
|
+
import { appendFile, mkdir, readFile, writeFile } from 'fs/promises';
|
|
4
|
+
import { basename, delimiter, dirname, resolve } from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { Command, CommanderError } from 'commander';
|
|
7
|
+
import { input } from '@inquirer/prompts';
|
|
8
|
+
import ora from 'ora';
|
|
9
|
+
import pc from 'picocolors';
|
|
10
|
+
import { logger } from './logger.js';
|
|
11
|
+
const PLAYWRIGHT_TEST_VERSION = '^1.59.0';
|
|
12
|
+
const PLAYWRIGHT_CLI_VERSION = 'latest';
|
|
13
|
+
const NODE_TYPES_VERSION = '^25.9.1';
|
|
14
|
+
export function determinePackageManager() {
|
|
15
|
+
const userAgent = process.env.npm_config_user_agent;
|
|
16
|
+
if (userAgent?.includes('pnpm')) {
|
|
17
|
+
return 'pnpm';
|
|
18
|
+
}
|
|
19
|
+
return 'npm';
|
|
20
|
+
}
|
|
21
|
+
function resolveSpawnSpec(cmd, args) {
|
|
22
|
+
if (process.platform !== 'win32') {
|
|
23
|
+
return { command: cmd, args };
|
|
24
|
+
}
|
|
25
|
+
const windowsCmdShims = new Set(['npm', 'npx', 'playwright', 'pnpm']);
|
|
26
|
+
if (!windowsCmdShims.has(cmd)) {
|
|
27
|
+
return { command: cmd, args };
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
command: process.env.comspec ?? 'cmd.exe',
|
|
31
|
+
args: ['/d', '/s', '/c', `"${buildWindowsBatchCommandLine(cmd, args)}"`],
|
|
32
|
+
windowsVerbatimArguments: true,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function quoteWindowsBatchArg(arg) {
|
|
36
|
+
if (arg.length === 0) {
|
|
37
|
+
return '""';
|
|
38
|
+
}
|
|
39
|
+
return `"${arg
|
|
40
|
+
.replace(/(\\*)"/g, '$1$1\\"')
|
|
41
|
+
.replace(/(\\+)$/g, '$1$1')
|
|
42
|
+
.replace(/%/g, '%%')}"`;
|
|
43
|
+
}
|
|
44
|
+
function buildWindowsBatchCommandLine(cmd, args) {
|
|
45
|
+
return [resolveWindowsCmdShim(cmd), ...args]
|
|
46
|
+
.map(quoteWindowsBatchArg)
|
|
47
|
+
.join(' ');
|
|
48
|
+
}
|
|
49
|
+
function resolveWindowsCmdShim(cmd) {
|
|
50
|
+
const shimName = `${cmd}.cmd`;
|
|
51
|
+
const pathEntries = process.env.PATH?.split(delimiter) ?? [];
|
|
52
|
+
for (const entry of pathEntries) {
|
|
53
|
+
if (!entry)
|
|
54
|
+
continue;
|
|
55
|
+
const shimPath = resolve(entry, shimName);
|
|
56
|
+
if (existsSync(shimPath)) {
|
|
57
|
+
return shimPath;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const bundledShimCommands = new Set(['npm', 'npx']);
|
|
61
|
+
if (bundledShimCommands.has(cmd)) {
|
|
62
|
+
const bundledShimPath = resolve(dirname(process.execPath), shimName);
|
|
63
|
+
if (existsSync(bundledShimPath)) {
|
|
64
|
+
return bundledShimPath;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return shimName;
|
|
68
|
+
}
|
|
69
|
+
function forwardChildSignals(child, activityLabel) {
|
|
70
|
+
let forwardedSignal = null;
|
|
71
|
+
const forwardSignal = (signal) => {
|
|
72
|
+
if (forwardedSignal !== null)
|
|
73
|
+
return;
|
|
74
|
+
forwardedSignal = signal;
|
|
75
|
+
if (process.env.SCREENCI_SIGNAL_LOGGING !== 'silent') {
|
|
76
|
+
logger.info(`Received ${signal}, stopping ${activityLabel}...`);
|
|
77
|
+
}
|
|
78
|
+
if (!child.killed) {
|
|
79
|
+
child.kill(signal);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const cleanup = () => {
|
|
83
|
+
process.off('SIGINT', onSigint);
|
|
84
|
+
process.off('SIGTERM', onSigterm);
|
|
85
|
+
};
|
|
86
|
+
const onSigint = () => forwardSignal('SIGINT');
|
|
87
|
+
const onSigterm = () => forwardSignal('SIGTERM');
|
|
88
|
+
process.on('SIGINT', onSigint);
|
|
89
|
+
process.on('SIGTERM', onSigterm);
|
|
90
|
+
return {
|
|
91
|
+
cleanup,
|
|
92
|
+
getForwardedSignal: () => forwardedSignal,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function spawnSilent(cmd, args, cwd) {
|
|
96
|
+
return new Promise((resolve, reject) => {
|
|
97
|
+
const spawnSpec = resolveSpawnSpec(cmd, args);
|
|
98
|
+
const child = spawn(spawnSpec.command, spawnSpec.args, {
|
|
99
|
+
stdio: 'pipe',
|
|
100
|
+
...(spawnSpec.shell !== undefined ? { shell: spawnSpec.shell } : {}),
|
|
101
|
+
...(spawnSpec.windowsVerbatimArguments !== undefined
|
|
102
|
+
? {
|
|
103
|
+
windowsVerbatimArguments: spawnSpec.windowsVerbatimArguments,
|
|
104
|
+
}
|
|
105
|
+
: {}),
|
|
106
|
+
...(cwd ? { cwd } : {}),
|
|
107
|
+
});
|
|
108
|
+
const childSignals = forwardChildSignals(child, cmd);
|
|
109
|
+
child.on('close', (code, signal) => {
|
|
110
|
+
const forwardedSignal = childSignals.getForwardedSignal();
|
|
111
|
+
childSignals.cleanup();
|
|
112
|
+
if (forwardedSignal) {
|
|
113
|
+
process.kill(process.pid, forwardedSignal);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (signal) {
|
|
117
|
+
process.kill(process.pid, signal);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (code === 0) {
|
|
121
|
+
resolve();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
reject(new Error(`${cmd} exited with code ${code}`));
|
|
125
|
+
});
|
|
126
|
+
child.on('error', (err) => {
|
|
127
|
+
childSignals.cleanup();
|
|
128
|
+
reject(err);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
function spawnInherited(cmd, args, cwd, activityLabel = cmd) {
|
|
133
|
+
const spawnSpec = resolveSpawnSpec(cmd, args);
|
|
134
|
+
const child = spawn(spawnSpec.command, spawnSpec.args, {
|
|
135
|
+
stdio: 'inherit',
|
|
136
|
+
...(spawnSpec.shell !== undefined ? { shell: spawnSpec.shell } : {}),
|
|
137
|
+
...(spawnSpec.windowsVerbatimArguments !== undefined
|
|
138
|
+
? {
|
|
139
|
+
windowsVerbatimArguments: spawnSpec.windowsVerbatimArguments,
|
|
140
|
+
}
|
|
141
|
+
: {}),
|
|
142
|
+
...(cwd ? { cwd } : {}),
|
|
143
|
+
});
|
|
144
|
+
const childSignals = forwardChildSignals(child, activityLabel);
|
|
145
|
+
return new Promise((resolve, reject) => {
|
|
146
|
+
child.on('close', (code, signal) => {
|
|
147
|
+
const forwardedSignal = childSignals.getForwardedSignal();
|
|
148
|
+
childSignals.cleanup();
|
|
149
|
+
if (forwardedSignal) {
|
|
150
|
+
process.kill(process.pid, forwardedSignal);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (signal) {
|
|
154
|
+
process.kill(process.pid, signal);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (code === 0) {
|
|
158
|
+
resolve();
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
reject(new Error(`${cmd} exited with code ${code}`));
|
|
162
|
+
});
|
|
163
|
+
child.on('error', (err) => {
|
|
164
|
+
childSignals.cleanup();
|
|
165
|
+
reject(err);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
export function parsePackageManager(value) {
|
|
170
|
+
if (value === undefined) {
|
|
171
|
+
return determinePackageManager();
|
|
172
|
+
}
|
|
173
|
+
if (value === 'npm' || value === 'pnpm') {
|
|
174
|
+
return value;
|
|
175
|
+
}
|
|
176
|
+
throw new Error('Expected package manager to be one of: npm, pnpm');
|
|
177
|
+
}
|
|
178
|
+
function getPackageManagerCommand(packageManager) {
|
|
179
|
+
if (packageManager === 'pnpm') {
|
|
180
|
+
return {
|
|
181
|
+
screenciRun: 'pnpm exec screenci',
|
|
182
|
+
playwrightRun: 'pnpm exec playwright',
|
|
183
|
+
installCommand: 'pnpm',
|
|
184
|
+
installArgs: (pkg) => ['add', '--save-dev', pkg],
|
|
185
|
+
skillsCommand: 'pnpm',
|
|
186
|
+
skillsArgs: (skills, agent) => [
|
|
187
|
+
'dlx',
|
|
188
|
+
'skills',
|
|
189
|
+
'add',
|
|
190
|
+
'screenci/screenci',
|
|
191
|
+
...(agent ? ['--agent', agent] : []),
|
|
192
|
+
...skills.flatMap((skillName) => ['--skill', skillName]),
|
|
193
|
+
'-y',
|
|
194
|
+
],
|
|
195
|
+
skillsManualCommand: (skills, agent) => ['pnpm', 'dlx', 'skills', 'add', 'screenci/screenci']
|
|
196
|
+
.concat(agent ? ['--agent', agent] : [])
|
|
197
|
+
.concat(skills.flatMap((skillName) => ['--skill', skillName]))
|
|
198
|
+
.concat(['-y'])
|
|
199
|
+
.join(' '),
|
|
200
|
+
cacheName: 'pnpm',
|
|
201
|
+
lockfileName: 'pnpm-lock.yaml',
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
screenciRun: 'npx screenci',
|
|
206
|
+
playwrightRun: 'npx playwright',
|
|
207
|
+
installCommand: 'npm',
|
|
208
|
+
installArgs: (pkg) => ['install', '--save-dev', pkg],
|
|
209
|
+
skillsCommand: 'npm',
|
|
210
|
+
skillsArgs: (skills, agent) => [
|
|
211
|
+
'exec',
|
|
212
|
+
'--yes',
|
|
213
|
+
'--package=skills',
|
|
214
|
+
'--',
|
|
215
|
+
'skills',
|
|
216
|
+
'add',
|
|
217
|
+
'screenci/screenci',
|
|
218
|
+
...(agent ? ['--agent', agent] : []),
|
|
219
|
+
...skills.flatMap((skillName) => ['--skill', skillName]),
|
|
220
|
+
'-y',
|
|
221
|
+
],
|
|
222
|
+
skillsManualCommand: (skills, agent) => [
|
|
223
|
+
'npm',
|
|
224
|
+
'exec',
|
|
225
|
+
'--yes',
|
|
226
|
+
'--package=skills',
|
|
227
|
+
'--',
|
|
228
|
+
'skills',
|
|
229
|
+
'add',
|
|
230
|
+
'screenci/screenci',
|
|
231
|
+
]
|
|
232
|
+
.concat(agent ? ['--agent', agent] : [])
|
|
233
|
+
.concat(skills.flatMap((skillName) => ['--skill', skillName]))
|
|
234
|
+
.concat(['-y'])
|
|
235
|
+
.join(' '),
|
|
236
|
+
cacheName: 'npm',
|
|
237
|
+
lockfileName: 'package-lock.json',
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
function generateEmptyPackageJson() {
|
|
241
|
+
return '{\n "type": "module"\n}\n';
|
|
242
|
+
}
|
|
243
|
+
async function readCurrentScreenciVersion() {
|
|
244
|
+
const currentFileDir = dirname(fileURLToPath(import.meta.url));
|
|
245
|
+
const packageJsonPaths = [
|
|
246
|
+
resolve(currentFileDir, 'package.json'),
|
|
247
|
+
resolve(currentFileDir, '../package.json'),
|
|
248
|
+
];
|
|
249
|
+
for (const packageJsonPath of packageJsonPaths) {
|
|
250
|
+
try {
|
|
251
|
+
const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf-8'));
|
|
252
|
+
if (typeof packageJson.version === 'string') {
|
|
253
|
+
return packageJson.version;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
// Try the next candidate path.
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return 'latest';
|
|
261
|
+
}
|
|
262
|
+
function generateGitignore() {
|
|
263
|
+
return `# ScreenCI
|
|
264
|
+
.screenci
|
|
265
|
+
.playwright-cli/
|
|
266
|
+
.env
|
|
267
|
+
|
|
268
|
+
# Playwright
|
|
269
|
+
node_modules/
|
|
270
|
+
/test-results/
|
|
271
|
+
/playwright-report/
|
|
272
|
+
/blob-report/
|
|
273
|
+
/playwright/.cache/
|
|
274
|
+
/playwright/.auth/
|
|
275
|
+
`;
|
|
276
|
+
}
|
|
277
|
+
async function writeInitGitignore(projectDir) {
|
|
278
|
+
const gitignorePath = resolve(projectDir, '.gitignore');
|
|
279
|
+
if (!existsSync(gitignorePath)) {
|
|
280
|
+
await writeFile(gitignorePath, generateGitignore());
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const existing = await readFile(gitignorePath, 'utf-8');
|
|
284
|
+
const separator = existing.length === 0
|
|
285
|
+
? ''
|
|
286
|
+
: existing.endsWith('\n\n')
|
|
287
|
+
? ''
|
|
288
|
+
: existing.endsWith('\n')
|
|
289
|
+
? '\n'
|
|
290
|
+
: '\n\n';
|
|
291
|
+
await appendFile(gitignorePath, `${separator}${generateGitignore()}`);
|
|
292
|
+
}
|
|
293
|
+
async function installInitDependencies(projectDir, verbose, screenciDependency, includePlaywrightCli, packageManager) {
|
|
294
|
+
const commands = getPackageManagerCommand(packageManager);
|
|
295
|
+
const installSteps = [
|
|
296
|
+
{
|
|
297
|
+
message: 'Installing Playwright Test...',
|
|
298
|
+
args: commands.installArgs(`@playwright/test@${PLAYWRIGHT_TEST_VERSION}`),
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
message: 'Installing ScreenCI...',
|
|
302
|
+
args: commands.installArgs(`screenci@${screenciDependency}`),
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
message: 'Installing Node.js types...',
|
|
306
|
+
args: commands.installArgs(`@types/node@${NODE_TYPES_VERSION}`),
|
|
307
|
+
},
|
|
308
|
+
];
|
|
309
|
+
if (includePlaywrightCli) {
|
|
310
|
+
installSteps.push({
|
|
311
|
+
message: 'Installing playwright-cli...',
|
|
312
|
+
args: commands.installArgs(`@playwright/cli@${PLAYWRIGHT_CLI_VERSION}`),
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
for (const step of installSteps) {
|
|
316
|
+
if (verbose) {
|
|
317
|
+
logger.info(`Running '${commands.installCommand} ${step.args.join(' ')}'...`);
|
|
318
|
+
await spawnInherited(commands.installCommand, step.args, projectDir, 'screenci init');
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
const spinner = ora(step.message).start();
|
|
322
|
+
try {
|
|
323
|
+
await spawnSilent(commands.installCommand, step.args, projectDir);
|
|
324
|
+
spinner.succeed(step.message.replace(/\.\.\.$/, ' complete'));
|
|
325
|
+
}
|
|
326
|
+
catch (err) {
|
|
327
|
+
spinner.fail(step.message.replace(/\.\.\.$/, ' failed'));
|
|
328
|
+
throw err;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
function printInitNextSteps(projectDir, packageManager) {
|
|
334
|
+
const resolvedProjectDir = realpathSync(projectDir);
|
|
335
|
+
const commands = getPackageManagerCommand(packageManager);
|
|
336
|
+
logger.info(`${pc.green('✔ Success!')} Created a ScreenCI project at ${resolvedProjectDir}`);
|
|
337
|
+
logger.info('');
|
|
338
|
+
logger.info('Inside that directory, you can run several commands:');
|
|
339
|
+
logger.info('');
|
|
340
|
+
logger.info(` ${pc.cyan(`${commands.screenciRun} test`)}`);
|
|
341
|
+
logger.info(' Runs your video scripts in Playwright.');
|
|
342
|
+
logger.info('');
|
|
343
|
+
logger.info(` ${pc.cyan(`${commands.screenciRun} test --ui`)}`);
|
|
344
|
+
logger.info(' Starts the interactive UI mode.');
|
|
345
|
+
logger.info('');
|
|
346
|
+
logger.info(` ${pc.cyan(`${commands.screenciRun} test videos/example.video.ts`)}`);
|
|
347
|
+
logger.info(' Runs the example video script.');
|
|
348
|
+
logger.info('');
|
|
349
|
+
logger.info(` ${pc.cyan(`${commands.screenciRun} record`)}`);
|
|
350
|
+
logger.info(' Records and uploads videos.');
|
|
351
|
+
logger.info('');
|
|
352
|
+
logger.info('We suggest that you begin by typing:');
|
|
353
|
+
logger.info('');
|
|
354
|
+
logger.info(` ${pc.cyan(`${commands.screenciRun} test`)}`);
|
|
355
|
+
logger.info('');
|
|
356
|
+
logger.info('And check out the following files:');
|
|
357
|
+
logger.info(' - ./videos/example.video.ts - Example video script');
|
|
358
|
+
logger.info(' - ./screenci.config.ts - ScreenCI configuration');
|
|
359
|
+
logger.info('');
|
|
360
|
+
logger.info(`Visit ${pc.cyan('https://screenci.com/docs')} for more information.`);
|
|
361
|
+
logger.info('');
|
|
362
|
+
logger.info('Happy hacking! 🎥');
|
|
363
|
+
}
|
|
364
|
+
function generateGithubAction(packageManager) {
|
|
365
|
+
const commands = getPackageManagerCommand(packageManager);
|
|
366
|
+
return `name: ScreenCI
|
|
367
|
+
|
|
368
|
+
on:
|
|
369
|
+
push:
|
|
370
|
+
branches: [main]
|
|
371
|
+
workflow_dispatch:
|
|
372
|
+
|
|
373
|
+
jobs:
|
|
374
|
+
record:
|
|
375
|
+
runs-on: ubuntu-latest
|
|
376
|
+
environment:
|
|
377
|
+
name: screenci
|
|
378
|
+
url: \${{ steps.record.outputs.screenci_project_url }}
|
|
379
|
+
steps:
|
|
380
|
+
- name: Check SCREENCI_SECRET
|
|
381
|
+
env:
|
|
382
|
+
SCREENCI_SECRET: \${{ secrets.SCREENCI_SECRET }}
|
|
383
|
+
run: |
|
|
384
|
+
if [ -z "$SCREENCI_SECRET" ]; then
|
|
385
|
+
echo "::error::SCREENCI_SECRET is not set. Copy it from https://app.screenci.com/secrets or ./.env, add it under Settings → Secrets and variables → Actions → Repository secrets, and then rerun this action."
|
|
386
|
+
exit 1
|
|
387
|
+
fi
|
|
388
|
+
|
|
389
|
+
- uses: actions/checkout@v4
|
|
390
|
+
|
|
391
|
+
- uses: actions/setup-node@v4
|
|
392
|
+
with:
|
|
393
|
+
node-version: 24
|
|
394
|
+
cache: ${commands.cacheName}
|
|
395
|
+
cache-dependency-path: ${commands.lockfileName}
|
|
396
|
+
|
|
397
|
+
- name: Install dependencies
|
|
398
|
+
working-directory: .
|
|
399
|
+
env:
|
|
400
|
+
HUSKY: 0
|
|
401
|
+
npm_config_strict_dep_builds: false
|
|
402
|
+
run: ${packageManager === 'pnpm' ? 'pnpm install --frozen-lockfile' : 'npm ci'}
|
|
403
|
+
|
|
404
|
+
- name: Cache Playwright Chromium
|
|
405
|
+
uses: actions/cache@v5
|
|
406
|
+
id: pw-cache
|
|
407
|
+
with:
|
|
408
|
+
path: ~/.cache/ms-playwright
|
|
409
|
+
key: playwright-\${{ runner.os }}-\${{ hashFiles('${commands.lockfileName}') }}
|
|
410
|
+
|
|
411
|
+
- name: Install Chromium
|
|
412
|
+
if: steps.pw-cache.outputs.cache-hit != 'true'
|
|
413
|
+
working-directory: .
|
|
414
|
+
run: ${commands.playwrightRun} install chromium
|
|
415
|
+
|
|
416
|
+
- id: record
|
|
417
|
+
name: Record
|
|
418
|
+
working-directory: .
|
|
419
|
+
env:
|
|
420
|
+
SCREENCI_SECRET: \${{ secrets.SCREENCI_SECRET }}
|
|
421
|
+
run: ${commands.screenciRun} record
|
|
422
|
+
`;
|
|
423
|
+
}
|
|
424
|
+
function generateExampleVideo() {
|
|
425
|
+
return `import { autoZoom, createNarration, hide, video, voices } from 'screenci'
|
|
426
|
+
|
|
427
|
+
const narration = createNarration({
|
|
428
|
+
voice: { name: voices.Sophie },
|
|
429
|
+
languages: {
|
|
430
|
+
en: {
|
|
431
|
+
cues: {
|
|
432
|
+
intro:
|
|
433
|
+
'This video shows how to get started with ScreenCI [pronounce: screen see eye].',
|
|
434
|
+
docs: 'You can find the documentation linked right on the front page.',
|
|
435
|
+
},
|
|
436
|
+
},
|
|
437
|
+
},
|
|
438
|
+
})
|
|
439
|
+
|
|
440
|
+
video('How to get started', async ({ page }) => {
|
|
441
|
+
await hide(async () => {
|
|
442
|
+
await page.goto('https://screenci.com')
|
|
443
|
+
await page.waitForLoadState('networkidle');
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
await narration.intro()
|
|
447
|
+
await narration.docs()
|
|
448
|
+
|
|
449
|
+
await autoZoom(async () => {
|
|
450
|
+
await page.getByRole('link', { name: 'View Documentation' }).click()
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
})
|
|
454
|
+
`;
|
|
455
|
+
}
|
|
456
|
+
function getInitProjectRoot() {
|
|
457
|
+
return process.env['SCREENCI_INIT_CWD'] ?? process.cwd();
|
|
458
|
+
}
|
|
459
|
+
function getDefaultInitProjectName() {
|
|
460
|
+
const directoryName = basename(getInitProjectRoot());
|
|
461
|
+
return directoryName.length > 0 ? directoryName : 'screenci-project';
|
|
462
|
+
}
|
|
463
|
+
async function promptProjectName() {
|
|
464
|
+
return input({
|
|
465
|
+
message: 'Project name:',
|
|
466
|
+
default: getDefaultInitProjectName(),
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
async function promptYesNo(message, defaultValue) {
|
|
470
|
+
const answer = await input({
|
|
471
|
+
message,
|
|
472
|
+
default: defaultValue ? 'y' : 'n',
|
|
473
|
+
validate: (value) => {
|
|
474
|
+
const normalized = value.trim().toLowerCase();
|
|
475
|
+
if (normalized === '' ||
|
|
476
|
+
normalized === 'y' ||
|
|
477
|
+
normalized === 'yes' ||
|
|
478
|
+
normalized === 'n' ||
|
|
479
|
+
normalized === 'no') {
|
|
480
|
+
return true;
|
|
481
|
+
}
|
|
482
|
+
return 'Enter y or n';
|
|
483
|
+
},
|
|
484
|
+
});
|
|
485
|
+
const normalized = answer.trim().toLowerCase();
|
|
486
|
+
if (normalized === '')
|
|
487
|
+
return defaultValue;
|
|
488
|
+
return normalized === 'y' || normalized === 'yes';
|
|
489
|
+
}
|
|
490
|
+
async function promptInitGithubActionWorkflow() {
|
|
491
|
+
return promptYesNo('Add a GitHub Actions workflow? (Y/n)', true);
|
|
492
|
+
}
|
|
493
|
+
async function promptInitPlaywrightBrowsersForPackageManager(packageManager) {
|
|
494
|
+
const commands = getPackageManagerCommand(packageManager);
|
|
495
|
+
return promptYesNo(`Install Playwright browsers (can be done manually via '${commands.playwrightRun} install chromium')? (Y/n)`, true);
|
|
496
|
+
}
|
|
497
|
+
async function promptInitPlaywrightOsDependenciesForPackageManager(packageManager) {
|
|
498
|
+
const commands = getPackageManagerCommand(packageManager);
|
|
499
|
+
return promptYesNo(`Install Playwright operating system dependencies (might require sudo / root and can be done manually via '${commands.playwrightRun} install-deps chromium')? (y/N)`, false);
|
|
500
|
+
}
|
|
501
|
+
async function promptInitScreenCISkill(packageManager) {
|
|
502
|
+
const commands = getPackageManagerCommand(packageManager);
|
|
503
|
+
return promptYesNo(`Install the ScreenCI skill for AI agents (can be done manually via '${commands.skillsManualCommand(['screenci'])}')? (Y/n)`, true);
|
|
504
|
+
}
|
|
505
|
+
async function promptInitPlaywrightCliSkillForPackageManager(packageManager) {
|
|
506
|
+
const installPlaywrightCli = packageManager === 'pnpm'
|
|
507
|
+
? 'pnpm add --save-dev @playwright/cli'
|
|
508
|
+
: 'npm install @playwright/cli';
|
|
509
|
+
const commands = getPackageManagerCommand(packageManager);
|
|
510
|
+
return promptYesNo(`Install playwright-cli for URL-based browser inspection (can be done manually via '${commands.skillsManualCommand(['playwright-cli'])} && ${installPlaywrightCli}')? (Y/n)`, true);
|
|
511
|
+
}
|
|
512
|
+
function getInitScreenciDependencyOverride() {
|
|
513
|
+
return process.env['SCREENCI_INIT_SCREENCI_DEPENDENCY'];
|
|
514
|
+
}
|
|
515
|
+
export async function runInit(projectNameArg, options) {
|
|
516
|
+
const { verbose, yes, agent, packageManager } = options;
|
|
517
|
+
const commands = getPackageManagerCommand(packageManager);
|
|
518
|
+
const initCwd = getInitProjectRoot();
|
|
519
|
+
let projectName = projectNameArg?.trim();
|
|
520
|
+
if (!projectName) {
|
|
521
|
+
projectName = yes ? getDefaultInitProjectName() : await promptProjectName();
|
|
522
|
+
}
|
|
523
|
+
if (!projectName) {
|
|
524
|
+
logger.error('Error: Project name is required');
|
|
525
|
+
process.exit(1);
|
|
526
|
+
}
|
|
527
|
+
const projectDir = initCwd;
|
|
528
|
+
const githubWorkflowsDir = resolve(projectDir, '.github', 'workflows');
|
|
529
|
+
const githubActionPath = resolve(githubWorkflowsDir, 'screenci.yaml');
|
|
530
|
+
const shouldAddGithubActionWorkflow = yes
|
|
531
|
+
? true
|
|
532
|
+
: await promptInitGithubActionWorkflow();
|
|
533
|
+
const shouldInstallPlaywrightBrowsers = yes
|
|
534
|
+
? true
|
|
535
|
+
: await promptInitPlaywrightBrowsersForPackageManager(packageManager);
|
|
536
|
+
const shouldInstallPlaywrightOsDependencies = yes
|
|
537
|
+
? false
|
|
538
|
+
: await promptInitPlaywrightOsDependenciesForPackageManager(packageManager);
|
|
539
|
+
const shouldInstallScreenCISkill = yes
|
|
540
|
+
? true
|
|
541
|
+
: await promptInitScreenCISkill(packageManager);
|
|
542
|
+
const shouldInstallPlaywrightCli = yes
|
|
543
|
+
? true
|
|
544
|
+
: await promptInitPlaywrightCliSkillForPackageManager(packageManager);
|
|
545
|
+
if (shouldAddGithubActionWorkflow && existsSync(githubActionPath)) {
|
|
546
|
+
logger.error('Error: GitHub Actions workflow ".github/workflows/screenci.yaml" already exists');
|
|
547
|
+
process.exit(1);
|
|
548
|
+
}
|
|
549
|
+
const skills = [];
|
|
550
|
+
if (shouldInstallScreenCISkill) {
|
|
551
|
+
skills.push('screenci');
|
|
552
|
+
}
|
|
553
|
+
if (shouldInstallPlaywrightCli) {
|
|
554
|
+
skills.push('playwright-cli');
|
|
555
|
+
}
|
|
556
|
+
const skillsArgs = skills.length === 0 ? null : commands.skillsArgs(skills, agent);
|
|
557
|
+
const skillsCommand = skillsArgs === null
|
|
558
|
+
? null
|
|
559
|
+
: `${commands.skillsCommand} ${skillsArgs.join(' ')}`;
|
|
560
|
+
const screenciDependency = getInitScreenciDependencyOverride() ?? (await readCurrentScreenciVersion());
|
|
561
|
+
const packageJsonPath = resolve(projectDir, 'package.json');
|
|
562
|
+
const hasExistingPackageJson = existsSync(packageJsonPath);
|
|
563
|
+
logger.info("Initializing project in '.'");
|
|
564
|
+
await mkdir(resolve(projectDir, 'videos'), { recursive: true });
|
|
565
|
+
if (shouldAddGithubActionWorkflow) {
|
|
566
|
+
await mkdir(githubWorkflowsDir, { recursive: true });
|
|
567
|
+
}
|
|
568
|
+
await writeFile(resolve(projectDir, 'screenci.config.ts'), generateConfig(projectName));
|
|
569
|
+
if (!hasExistingPackageJson) {
|
|
570
|
+
await writeFile(packageJsonPath, generateEmptyPackageJson());
|
|
571
|
+
}
|
|
572
|
+
await writeInitGitignore(projectDir);
|
|
573
|
+
await writeFile(resolve(projectDir, 'videos', 'example.video.ts'), generateExampleVideo());
|
|
574
|
+
if (shouldAddGithubActionWorkflow) {
|
|
575
|
+
await writeFile(githubActionPath, generateGithubAction(packageManager));
|
|
576
|
+
}
|
|
577
|
+
if (skillsArgs !== null) {
|
|
578
|
+
if (verbose) {
|
|
579
|
+
logger.info(`Running '${skillsCommand}'...`);
|
|
580
|
+
await spawnInherited(commands.skillsCommand, skillsArgs, projectDir, 'screenci init');
|
|
581
|
+
}
|
|
582
|
+
else {
|
|
583
|
+
const spinner = ora('Adding selected AI skills...').start();
|
|
584
|
+
try {
|
|
585
|
+
await spawnSilent(commands.skillsCommand, skillsArgs, projectDir);
|
|
586
|
+
spinner.succeed('Selected AI skills added');
|
|
587
|
+
}
|
|
588
|
+
catch (err) {
|
|
589
|
+
spinner.fail('AI skills install failed');
|
|
590
|
+
throw err;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
await installInitDependencies(projectDir, verbose, screenciDependency, shouldInstallPlaywrightCli, packageManager);
|
|
595
|
+
if (shouldInstallPlaywrightBrowsers) {
|
|
596
|
+
logger.info(`Installing Playwright Chromium with '${commands.playwrightRun} install chromium'...`);
|
|
597
|
+
await spawnInherited(packageManager === 'pnpm' ? 'pnpm' : 'npx', packageManager === 'pnpm'
|
|
598
|
+
? ['exec', 'playwright', 'install', 'chromium']
|
|
599
|
+
: ['playwright', 'install', 'chromium'], projectDir, 'screenci init');
|
|
600
|
+
logger.info(`${pc.green('✔')} Playwright Chromium installed successfully`);
|
|
601
|
+
}
|
|
602
|
+
if (shouldInstallPlaywrightOsDependencies) {
|
|
603
|
+
logger.info(`Installing Playwright operating system dependencies with '${commands.playwrightRun} install-deps chromium'...`);
|
|
604
|
+
await spawnInherited(packageManager === 'pnpm' ? 'pnpm' : 'npx', packageManager === 'pnpm'
|
|
605
|
+
? ['exec', 'playwright', 'install-deps', 'chromium']
|
|
606
|
+
: ['playwright', 'install-deps', 'chromium'], projectDir, 'screenci init');
|
|
607
|
+
logger.info(`${pc.green('✔')} Playwright operating system dependencies installed successfully`);
|
|
608
|
+
}
|
|
609
|
+
printInitNextSteps(projectDir, packageManager);
|
|
610
|
+
}
|
|
611
|
+
function handleCreateCommanderError(err) {
|
|
612
|
+
if (!(err instanceof CommanderError)) {
|
|
613
|
+
throw err;
|
|
614
|
+
}
|
|
615
|
+
if (err.code === 'commander.help' || err.code === 'commander.helpDisplayed') {
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
logger.error(`Error: ${err.message}`);
|
|
619
|
+
process.exit(1);
|
|
620
|
+
}
|
|
621
|
+
export async function runCreateScreenciCli(argv = process.argv) {
|
|
622
|
+
const defaultPackageManager = determinePackageManager();
|
|
623
|
+
const program = new Command();
|
|
624
|
+
program.name('create-screenci');
|
|
625
|
+
program.description('Initialize a new screenci project');
|
|
626
|
+
program.argument('[name]');
|
|
627
|
+
program.exitOverride();
|
|
628
|
+
program.option('--agent <name>', 'target agent for skills install, e.g. opencode. Supported agents: https://github.com/vercel-labs/skills#supported-agents');
|
|
629
|
+
program.option('--package-manager <manager>', `package manager to use: npm or pnpm (default: ${defaultPackageManager})`);
|
|
630
|
+
program.option('-y, --yes', 'accept init defaults');
|
|
631
|
+
program.option('-v, --verbose', 'verbose output');
|
|
632
|
+
program.action(async (name, options) => {
|
|
633
|
+
const agent = options['agent'];
|
|
634
|
+
await runInit(name, {
|
|
635
|
+
verbose: options['verbose'] ?? false,
|
|
636
|
+
yes: options['yes'] ?? false,
|
|
637
|
+
packageManager: parsePackageManager(options['packageManager']),
|
|
638
|
+
...(agent !== undefined ? { agent } : {}),
|
|
639
|
+
});
|
|
640
|
+
});
|
|
641
|
+
try {
|
|
642
|
+
await program.parseAsync(argv);
|
|
643
|
+
}
|
|
644
|
+
catch (err) {
|
|
645
|
+
handleCreateCommanderError(err);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
function generateConfig(projectName) {
|
|
649
|
+
return `import { defineConfig } from 'screenci'
|
|
650
|
+
|
|
651
|
+
export default defineConfig({
|
|
652
|
+
projectName: ${JSON.stringify(projectName)},
|
|
653
|
+
envFile: '.env',
|
|
654
|
+
videoDir: './videos',
|
|
655
|
+
/* Run videos in files in parallel */
|
|
656
|
+
fullyParallel: true,
|
|
657
|
+
/* Opt out of parallel tests on CI. */
|
|
658
|
+
workers: process.env.CI ? 1 : undefined,
|
|
659
|
+
use: {
|
|
660
|
+
recordOptions: {
|
|
661
|
+
aspectRatio: '16:9',
|
|
662
|
+
quality: '1080p',
|
|
663
|
+
fps: 60,
|
|
664
|
+
},
|
|
665
|
+
},
|
|
666
|
+
projects: [
|
|
667
|
+
{
|
|
668
|
+
name: 'chromium',
|
|
669
|
+
},
|
|
670
|
+
],
|
|
671
|
+
})
|
|
672
|
+
`;
|
|
673
|
+
}
|
|
674
|
+
//# sourceMappingURL=init.js.map
|