screenci 0.0.34 → 0.0.38
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 +1 -2
- package/bin/screenci.js +8 -0
- package/dist/cli.d.ts +0 -7
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +121 -465
- package/dist/cli.js.map +1 -1
- package/dist/src/browserLaunchOptions.d.ts +1 -1
- package/dist/src/browserLaunchOptions.d.ts.map +1 -1
- package/dist/src/browserLaunchOptions.js +1 -4
- package/dist/src/browserLaunchOptions.js.map +1 -1
- package/dist/src/config.d.ts +1 -1
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js +17 -52
- package/dist/src/config.js.map +1 -1
- package/dist/src/defaults.d.ts +1 -1
- package/dist/src/defaults.js +4 -4
- package/dist/src/defaults.js.map +1 -1
- package/dist/src/types.d.ts +13 -6
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/video.d.ts +0 -1
- package/dist/src/video.d.ts.map +1 -1
- package/dist/src/video.js +47 -247
- package/dist/src/video.js.map +1 -1
- package/dist/test-fixtures/env-file.config.d.ts +6 -0
- package/dist/test-fixtures/env-file.config.d.ts.map +1 -0
- package/dist/test-fixtures/env-file.config.js +5 -0
- package/dist/test-fixtures/env-file.config.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/skills/screenci/SKILL.md +1 -1
- package/skills/screenci/references/init.md +2 -3
- package/skills/screenci/references/record.md +1 -1
- package/dist/Dockerfile +0 -41
- package/dist/src/chromiumProfile.d.ts +0 -6
- package/dist/src/chromiumProfile.d.ts.map +0 -1
- package/dist/src/chromiumProfile.js +0 -20
- package/dist/src/chromiumProfile.js.map +0 -1
- package/dist/src/xvfb.d.ts +0 -22
- package/dist/src/xvfb.d.ts.map +0 -1
- package/dist/src/xvfb.js +0 -87
- package/dist/src/xvfb.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
import { spawn, spawnSync } from 'child_process';
|
|
1
|
+
import { spawn } from 'child_process';
|
|
3
2
|
import { createReadStream } from 'fs';
|
|
4
|
-
import { existsSync, mkdirSync,
|
|
3
|
+
import { existsSync, mkdirSync, readdirSync, realpathSync, rmSync } from 'fs';
|
|
5
4
|
import { createHash } from 'crypto';
|
|
6
5
|
import { createServer } from 'http';
|
|
7
6
|
import { appendFile, mkdir, readdir, readFile, stat, writeFile, } from 'fs/promises';
|
|
@@ -107,68 +106,6 @@ function createUploadAbortController(activityLabel) {
|
|
|
107
106
|
cleanup,
|
|
108
107
|
};
|
|
109
108
|
}
|
|
110
|
-
function parseDockerfileVersion(dockerfilePath) {
|
|
111
|
-
let content;
|
|
112
|
-
try {
|
|
113
|
-
content = readFileSync(dockerfilePath, 'utf-8');
|
|
114
|
-
}
|
|
115
|
-
catch {
|
|
116
|
-
return 'unknown';
|
|
117
|
-
}
|
|
118
|
-
const fromLine = content
|
|
119
|
-
.split('\n')
|
|
120
|
-
.find((line) => line.trim().toUpperCase().startsWith('FROM'));
|
|
121
|
-
if (!fromLine)
|
|
122
|
-
return 'unknown';
|
|
123
|
-
const match = fromLine.match(/:([^\s@]+)/);
|
|
124
|
-
return match?.[1] ?? 'unknown';
|
|
125
|
-
}
|
|
126
|
-
const CONTAINER_RUNTIME_DOCS_URL = 'https://screenci.com/docs/guides/getting-started/#prerequisites';
|
|
127
|
-
function prerequisitesMessage() {
|
|
128
|
-
return `See prerequisites: ${pc.blue(CONTAINER_RUNTIME_DOCS_URL)}`;
|
|
129
|
-
}
|
|
130
|
-
const MIN_CONTAINER_RUNTIME_MAJOR_VERSION = {
|
|
131
|
-
podman: 3,
|
|
132
|
-
docker: 27,
|
|
133
|
-
};
|
|
134
|
-
function parseContainerRuntimeMajorVersion(versionOutput) {
|
|
135
|
-
const match = versionOutput.match(/(\d+)(?:\.\d+){0,2}/);
|
|
136
|
-
if (!match)
|
|
137
|
-
return null;
|
|
138
|
-
const majorVersion = Number.parseInt(match[1] ?? '', 10);
|
|
139
|
-
return Number.isNaN(majorVersion) ? null : majorVersion;
|
|
140
|
-
}
|
|
141
|
-
function checkContainerRuntime(runtime) {
|
|
142
|
-
const result = spawnSync(runtime, ['--version'], { encoding: 'utf8' });
|
|
143
|
-
if (result.status !== 0 || result.error !== undefined) {
|
|
144
|
-
return null;
|
|
145
|
-
}
|
|
146
|
-
const version = `${result.stdout ?? ''}${result.stderr ?? ''}`.trim();
|
|
147
|
-
return {
|
|
148
|
-
runtime,
|
|
149
|
-
version,
|
|
150
|
-
majorVersion: parseContainerRuntimeMajorVersion(version),
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
function getPreferredContainerRuntime() {
|
|
154
|
-
const podman = checkContainerRuntime('podman');
|
|
155
|
-
if (podman)
|
|
156
|
-
return podman;
|
|
157
|
-
return checkContainerRuntime('docker');
|
|
158
|
-
}
|
|
159
|
-
function exitContainerRuntimeNotFound(runtime) {
|
|
160
|
-
logger.error(`Error: ${runtime} not found.`);
|
|
161
|
-
logger.error(`Install ${runtime} or remove the --${runtime} flag.`);
|
|
162
|
-
logger.error(prerequisitesMessage());
|
|
163
|
-
process.exit(1);
|
|
164
|
-
}
|
|
165
|
-
function warnIfContainerRuntimeVersionIsOld(runtimeCheck) {
|
|
166
|
-
const minimumVersion = MIN_CONTAINER_RUNTIME_MAJOR_VERSION[runtimeCheck.runtime];
|
|
167
|
-
if (runtimeCheck.majorVersion !== null &&
|
|
168
|
-
runtimeCheck.majorVersion < minimumVersion) {
|
|
169
|
-
logger.warn(`Your ${runtimeCheck.runtime} version (${runtimeCheck.version}) is quite old. We recommend updating. ${prerequisitesMessage()}`);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
109
|
function spawnSilent(cmd, args, cwd) {
|
|
173
110
|
return new Promise((resolve, reject) => {
|
|
174
111
|
const child = spawn(cmd, args, { stdio: 'pipe', ...(cwd ? { cwd } : {}) });
|
|
@@ -235,58 +172,6 @@ function forwardChildSignals(child, activityLabel) {
|
|
|
235
172
|
getForwardedSignal: () => forwardedSignal,
|
|
236
173
|
};
|
|
237
174
|
}
|
|
238
|
-
const CONTAINER_LOG_FILTER = [
|
|
239
|
-
/^Running ScreenCI /,
|
|
240
|
-
/^Using config:/,
|
|
241
|
-
/^Starting Xvfb /,
|
|
242
|
-
/^Xvfb started /,
|
|
243
|
-
/^Recording video to:/,
|
|
244
|
-
/^Recording with /,
|
|
245
|
-
/^Stopping recording\.\.\./,
|
|
246
|
-
/^FFmpeg exited /,
|
|
247
|
-
/^Video saved to:/,
|
|
248
|
-
/^Events saved to:/,
|
|
249
|
-
];
|
|
250
|
-
function spawnContainerRecording(cmd, args) {
|
|
251
|
-
return new Promise((resolve, reject) => {
|
|
252
|
-
const child = spawn(cmd, args, { stdio: ['inherit', 'pipe', 'pipe'] });
|
|
253
|
-
const childSignals = forwardChildSignals(child, 'screenci record');
|
|
254
|
-
function forwardFiltered(chunk, out) {
|
|
255
|
-
const lines = chunk.toString().split('\n');
|
|
256
|
-
for (const line of lines) {
|
|
257
|
-
if (line === '')
|
|
258
|
-
continue;
|
|
259
|
-
if (!CONTAINER_LOG_FILTER.some((re) => re.test(line.trimStart()))) {
|
|
260
|
-
out.write(line + '\n');
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
child.stdout?.on('data', (chunk) => forwardFiltered(chunk, process.stdout));
|
|
265
|
-
child.stderr?.on('data', (chunk) => forwardFiltered(chunk, process.stderr));
|
|
266
|
-
child.on('close', (code, signal) => {
|
|
267
|
-
const forwardedSignal = childSignals.getForwardedSignal();
|
|
268
|
-
childSignals.cleanup();
|
|
269
|
-
if (forwardedSignal) {
|
|
270
|
-
process.kill(process.pid, forwardedSignal);
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
if (signal) {
|
|
274
|
-
process.kill(process.pid, signal);
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
else if (code === 0) {
|
|
278
|
-
resolve();
|
|
279
|
-
}
|
|
280
|
-
else {
|
|
281
|
-
reject(new Error(`${cmd} exited with code ${code}`));
|
|
282
|
-
}
|
|
283
|
-
});
|
|
284
|
-
child.on('error', (err) => {
|
|
285
|
-
childSignals.cleanup();
|
|
286
|
-
reject(err);
|
|
287
|
-
});
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
175
|
function clearDirectory(dir) {
|
|
291
176
|
mkdirSync(dir, { recursive: true });
|
|
292
177
|
for (const entry of readdirSync(dir)) {
|
|
@@ -876,17 +761,34 @@ async function loadScreenCIConfigAndEnv(configPath) {
|
|
|
876
761
|
return { resolvedConfigPath, screenciConfig };
|
|
877
762
|
}
|
|
878
763
|
function loadEnvFile(envFilePath, warnOnFailure) {
|
|
879
|
-
if (process.env.CI)
|
|
880
|
-
return;
|
|
881
764
|
try {
|
|
882
765
|
process.loadEnvFile(envFilePath);
|
|
883
766
|
}
|
|
884
767
|
catch (err) {
|
|
885
|
-
if (warnOnFailure) {
|
|
768
|
+
if (warnOnFailure && !isMissingFileError(err)) {
|
|
886
769
|
logger.warn(`Failed to load env file ${envFilePath}:`, err);
|
|
887
770
|
}
|
|
888
771
|
}
|
|
889
772
|
}
|
|
773
|
+
function isMissingFileError(err) {
|
|
774
|
+
return (typeof err === 'object' &&
|
|
775
|
+
err !== null &&
|
|
776
|
+
'code' in err &&
|
|
777
|
+
err.code === 'ENOENT');
|
|
778
|
+
}
|
|
779
|
+
async function loadEnvFileFromConfigSource(resolvedConfigPath, warnOnFailure) {
|
|
780
|
+
try {
|
|
781
|
+
const screenciConfig = await tryReadConfigFromSource(resolvedConfigPath);
|
|
782
|
+
if (screenciConfig.envFile) {
|
|
783
|
+
const envFilePath = resolve(dirname(resolvedConfigPath), screenciConfig.envFile);
|
|
784
|
+
loadEnvFile(envFilePath, warnOnFailure);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
catch {
|
|
788
|
+
// Config import may require Playwright context or dynamic values. Continue with
|
|
789
|
+
// the existing process env; Playwright will still load the config normally.
|
|
790
|
+
}
|
|
791
|
+
}
|
|
890
792
|
export function extractConfigStringLiteral(configSource, property) {
|
|
891
793
|
const singleQuoteMatch = configSource.match(new RegExp(property + "\\s*:\\s*'([^'\\n]+)'"));
|
|
892
794
|
if (singleQuoteMatch)
|
|
@@ -971,11 +873,7 @@ async function updateVideoVisibility(videoId, isPublic, configPath) {
|
|
|
971
873
|
}
|
|
972
874
|
logger.info(`${isPublic ? 'Made public' : 'Made private'}: ${videoId}`);
|
|
973
875
|
}
|
|
974
|
-
function generateConfig(projectName
|
|
975
|
-
const baseURLBlock = initTarget
|
|
976
|
-
? ` baseURL: ${JSON.stringify(initTarget.baseURL)},
|
|
977
|
-
`
|
|
978
|
-
: '';
|
|
876
|
+
function generateConfig(projectName) {
|
|
979
877
|
return `import { defineConfig } from 'screenci'
|
|
980
878
|
|
|
981
879
|
export default defineConfig({
|
|
@@ -983,7 +881,7 @@ export default defineConfig({
|
|
|
983
881
|
envFile: '.env',
|
|
984
882
|
videoDir: './videos',
|
|
985
883
|
use: {
|
|
986
|
-
|
|
884
|
+
recordOptions: {
|
|
987
885
|
aspectRatio: '16:9',
|
|
988
886
|
quality: '1080p',
|
|
989
887
|
fps: 30,
|
|
@@ -997,13 +895,12 @@ ${baseURLBlock} recordOptions: {
|
|
|
997
895
|
})
|
|
998
896
|
`;
|
|
999
897
|
}
|
|
1000
|
-
function generatePackageJson(
|
|
898
|
+
function generatePackageJson(includePlaywrightCli = false, screenciDependency = 'latest') {
|
|
1001
899
|
const devDependencies = {};
|
|
1002
900
|
if (includePlaywrightCli) {
|
|
1003
901
|
devDependencies['@playwright/cli'] = 'latest';
|
|
1004
902
|
}
|
|
1005
903
|
return (JSON.stringify({
|
|
1006
|
-
name: packageName,
|
|
1007
904
|
type: 'module',
|
|
1008
905
|
scripts: {
|
|
1009
906
|
record: 'screenci record',
|
|
@@ -1012,6 +909,7 @@ function generatePackageJson(packageName, includePlaywrightCli = false, screenci
|
|
|
1012
909
|
},
|
|
1013
910
|
dependencies: {
|
|
1014
911
|
screenci: screenciDependency,
|
|
912
|
+
'@playwright/test': '^1.59.0',
|
|
1015
913
|
},
|
|
1016
914
|
devDependencies,
|
|
1017
915
|
}, null, 2) + '\n');
|
|
@@ -1075,15 +973,6 @@ Learn more: https://screenci.com/docs/intro/
|
|
|
1075
973
|
4. View results on screenci.com and optionally enable a public URL to embed the video on your site.
|
|
1076
974
|
`;
|
|
1077
975
|
}
|
|
1078
|
-
function generateDockerfile() {
|
|
1079
|
-
return `FROM ghcr.io/screenci/record:latest
|
|
1080
|
-
|
|
1081
|
-
COPY package.json ./
|
|
1082
|
-
RUN npm install
|
|
1083
|
-
COPY screenci.config.ts ./
|
|
1084
|
-
COPY videos ./videos
|
|
1085
|
-
`;
|
|
1086
|
-
}
|
|
1087
976
|
function generateGitignore() {
|
|
1088
977
|
return `/playwright-report/
|
|
1089
978
|
.screenci
|
|
@@ -1092,8 +981,12 @@ node_modules/
|
|
|
1092
981
|
.env
|
|
1093
982
|
`;
|
|
1094
983
|
}
|
|
1095
|
-
function generateGithubAction() {
|
|
1096
|
-
|
|
984
|
+
function generateGithubAction(workingDirectory) {
|
|
985
|
+
const packageLockPath = workingDirectory === '.'
|
|
986
|
+
? 'package-lock.json'
|
|
987
|
+
: `${workingDirectory}/package-lock.json`;
|
|
988
|
+
const envFilePath = workingDirectory === '.' ? './.env' : `./${workingDirectory}/.env`;
|
|
989
|
+
return `name: ScreenCI
|
|
1097
990
|
|
|
1098
991
|
on:
|
|
1099
992
|
push:
|
|
@@ -1112,27 +1005,37 @@ jobs:
|
|
|
1112
1005
|
SCREENCI_SECRET: \${{ secrets.SCREENCI_SECRET }}
|
|
1113
1006
|
run: |
|
|
1114
1007
|
if [ -z "$SCREENCI_SECRET" ]; then
|
|
1115
|
-
echo "::error::SCREENCI_SECRET is not set. Copy it from https://app.screenci.com/secrets
|
|
1008
|
+
echo "::error::SCREENCI_SECRET is not set. Copy it from https://app.screenci.com/secrets or ${envFilePath}, add it under Settings → Secrets and variables → Actions → Repository secrets, and then rerun this action."
|
|
1116
1009
|
exit 1
|
|
1117
1010
|
fi
|
|
1118
1011
|
|
|
1119
1012
|
- uses: actions/checkout@v4
|
|
1120
1013
|
|
|
1121
|
-
- uses: actions/setup-node@
|
|
1014
|
+
- uses: actions/setup-node@v4
|
|
1122
1015
|
with:
|
|
1123
|
-
node-version:
|
|
1016
|
+
node-version: 24
|
|
1017
|
+
cache: npm
|
|
1018
|
+
cache-dependency-path: ${packageLockPath}
|
|
1124
1019
|
|
|
1125
1020
|
- name: Install dependencies
|
|
1126
|
-
working-directory:
|
|
1127
|
-
run: npm
|
|
1021
|
+
working-directory: ${workingDirectory}
|
|
1022
|
+
run: npm ci
|
|
1023
|
+
|
|
1024
|
+
- name: Cache Playwright Chromium
|
|
1025
|
+
uses: actions/cache@v5
|
|
1026
|
+
id: pw-cache
|
|
1027
|
+
with:
|
|
1028
|
+
path: ~/.cache/ms-playwright
|
|
1029
|
+
key: playwright-\${{ runner.os }}-\${{ hashFiles('${packageLockPath}') }}
|
|
1128
1030
|
|
|
1129
1031
|
- name: Install Chromium
|
|
1130
|
-
|
|
1032
|
+
if: steps.pw-cache.outputs.cache-hit != 'true'
|
|
1033
|
+
working-directory: ${workingDirectory}
|
|
1131
1034
|
run: npx playwright install chromium --with-deps
|
|
1132
1035
|
|
|
1133
1036
|
- id: record
|
|
1134
1037
|
name: Record
|
|
1135
|
-
working-directory:
|
|
1038
|
+
working-directory: ${workingDirectory}
|
|
1136
1039
|
env:
|
|
1137
1040
|
SCREENCI_SECRET: \${{ secrets.SCREENCI_SECRET }}
|
|
1138
1041
|
run: npm run record
|
|
@@ -1196,15 +1099,11 @@ const narration = createNarration({
|
|
|
1196
1099
|
languages: {
|
|
1197
1100
|
en: {
|
|
1198
1101
|
cues: {
|
|
1199
|
-
intro:
|
|
1200
|
-
'This example opens your app at the configured base URL first, then uses ScreenCI [pronounce: screen see eye] to show the next steps for creating your first videos.',
|
|
1201
1102
|
docs: 'Use the guide sidebar to open the AI-Supported Editing guide and review the next steps for writing your own videos.',
|
|
1202
1103
|
},
|
|
1203
1104
|
},
|
|
1204
1105
|
es: {
|
|
1205
1106
|
cues: {
|
|
1206
|
-
intro:
|
|
1207
|
-
'Este ejemplo abre primero tu aplicacion en la URL base configurada y despues usa ScreenCI [pronounce: screen see eye] para mostrar los siguientes pasos para crear tus primeros videos.',
|
|
1208
1107
|
docs: 'Usa la barra lateral de guias para abrir la guia de edicion asistida por IA y revisar los siguientes pasos para escribir tus propios videos.',
|
|
1209
1108
|
},
|
|
1210
1109
|
},
|
|
@@ -1212,14 +1111,6 @@ const narration = createNarration({
|
|
|
1212
1111
|
})
|
|
1213
1112
|
|
|
1214
1113
|
video('See the next steps in ScreenCI docs', async ({ page }) => {
|
|
1215
|
-
await hide(async () => {
|
|
1216
|
-
await page.goto('/')
|
|
1217
|
-
await page.waitForLoadState('domcontentloaded')
|
|
1218
|
-
})
|
|
1219
|
-
|
|
1220
|
-
await narration.intro
|
|
1221
|
-
await page.waitForTimeout(1000)
|
|
1222
|
-
|
|
1223
1114
|
await hide(async () => {
|
|
1224
1115
|
await page.goto('https://screenci.com/')
|
|
1225
1116
|
await page.getByText('ScreenCI', { exact: true }).first().waitFor()
|
|
@@ -1262,95 +1153,30 @@ async function promptInitGithubActionCi() {
|
|
|
1262
1153
|
default: true,
|
|
1263
1154
|
});
|
|
1264
1155
|
}
|
|
1265
|
-
async function
|
|
1156
|
+
async function promptInitRepositoryMode() {
|
|
1266
1157
|
return select({
|
|
1267
|
-
message: '
|
|
1158
|
+
message: 'Initialize ScreenCI as a standalone project or part of the existing repository?',
|
|
1159
|
+
default: 'standalone',
|
|
1268
1160
|
choices: [
|
|
1269
|
-
{
|
|
1270
|
-
|
|
1161
|
+
{
|
|
1162
|
+
name: 'Standalone project',
|
|
1163
|
+
value: 'standalone',
|
|
1164
|
+
description: 'Create a project directory with its own GitHub Action.',
|
|
1165
|
+
},
|
|
1166
|
+
{
|
|
1167
|
+
name: 'Part of existing repository',
|
|
1168
|
+
value: 'existing-repository',
|
|
1169
|
+
description: 'Create ./screenci and add the GitHub Action at the repository root.',
|
|
1170
|
+
},
|
|
1271
1171
|
],
|
|
1272
|
-
default: 'local',
|
|
1273
|
-
});
|
|
1274
|
-
}
|
|
1275
|
-
async function promptInitTargetUrl(mode) {
|
|
1276
|
-
return input({
|
|
1277
|
-
message: mode === 'local' ? 'Local development server URL:' : 'Public app URL:',
|
|
1278
|
-
default: mode === 'local' ? 'http://localhost:3000' : 'https://screenci.com',
|
|
1279
1172
|
});
|
|
1280
1173
|
}
|
|
1281
|
-
function
|
|
1282
|
-
|
|
1283
|
-
return new URL(url.trim()).toString();
|
|
1284
|
-
}
|
|
1285
|
-
catch {
|
|
1286
|
-
logger.error(`Error: Invalid URL "${url}"`);
|
|
1287
|
-
process.exit(1);
|
|
1288
|
-
}
|
|
1174
|
+
function projectNameToDirectoryName(projectName) {
|
|
1175
|
+
return projectName.trim().replace(/\s+/g, '-');
|
|
1289
1176
|
}
|
|
1290
1177
|
function getInitProjectRoot() {
|
|
1291
1178
|
return process.env['SCREENCI_INIT_CWD'] ?? process.cwd();
|
|
1292
1179
|
}
|
|
1293
|
-
function isSourceCliEntrypoint(entrypoint) {
|
|
1294
|
-
return (entrypoint?.endsWith('/cli.ts') === true ||
|
|
1295
|
-
entrypoint?.endsWith('\\cli.ts') === true);
|
|
1296
|
-
}
|
|
1297
|
-
function getDevScreenciPackageRoot() {
|
|
1298
|
-
const explicitRoot = process.env.SCREENCI_DEV_PACKAGE_ROOT?.trim();
|
|
1299
|
-
if (explicitRoot)
|
|
1300
|
-
return resolve(explicitRoot);
|
|
1301
|
-
if (!isSourceCliEntrypoint(process.argv[1]))
|
|
1302
|
-
return undefined;
|
|
1303
|
-
return dirname(fileURLToPath(import.meta.url));
|
|
1304
|
-
}
|
|
1305
|
-
function getLocalScreenciDependency(packageRoot, projectDir) {
|
|
1306
|
-
const relativePath = pathRelative(projectDir, packageRoot) || '.';
|
|
1307
|
-
const normalizedPath = relativePath.replace(/\\/g, '/');
|
|
1308
|
-
return `file:${normalizedPath.startsWith('.') ? normalizedPath : `./${normalizedPath}`}`;
|
|
1309
|
-
}
|
|
1310
|
-
async function buildLocalScreenciPackage(packageRoot) {
|
|
1311
|
-
logger.info(`Using local screenci package: ${packageRoot}`);
|
|
1312
|
-
logger.info("Running 'npm run build' for local screenci package...");
|
|
1313
|
-
await spawnInherited('npm', ['run', 'build'], packageRoot, 'screenci dev build');
|
|
1314
|
-
}
|
|
1315
|
-
async function installLocalScreenciPackage(projectDir, packageRoot) {
|
|
1316
|
-
const packageJsonPath = resolve(projectDir, 'package.json');
|
|
1317
|
-
const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf-8'));
|
|
1318
|
-
packageJson.dependencies = {
|
|
1319
|
-
...packageJson.dependencies,
|
|
1320
|
-
screenci: getLocalScreenciDependency(packageRoot, projectDir),
|
|
1321
|
-
};
|
|
1322
|
-
await writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`);
|
|
1323
|
-
logger.info('Installing local screenci package into this project...');
|
|
1324
|
-
await spawnInherited('npm', ['install', '--install-links'], projectDir, 'screenci dev install');
|
|
1325
|
-
}
|
|
1326
|
-
function buildChildEnv() {
|
|
1327
|
-
const { PATH, HOME, USER, LOGNAME, TMPDIR, TEMP, TMP, CI } = process.env;
|
|
1328
|
-
return {
|
|
1329
|
-
PATH,
|
|
1330
|
-
HOME,
|
|
1331
|
-
USER,
|
|
1332
|
-
LOGNAME,
|
|
1333
|
-
TMPDIR,
|
|
1334
|
-
TEMP,
|
|
1335
|
-
TMP,
|
|
1336
|
-
CI,
|
|
1337
|
-
SCREENCI_SECRET: process.env.SCREENCI_SECRET,
|
|
1338
|
-
SCREENCI_INIT_CWD: process.env.SCREENCI_INIT_CWD,
|
|
1339
|
-
DEV_FRONTEND_PORT: process.env.DEV_FRONTEND_PORT,
|
|
1340
|
-
DEV_BACKEND_PORT: process.env.DEV_BACKEND_PORT,
|
|
1341
|
-
SCREENCI_LOCAL_IMAGE: process.env.SCREENCI_LOCAL_IMAGE,
|
|
1342
|
-
SCREENCI_IN_CONTAINER: process.env.SCREENCI_IN_CONTAINER,
|
|
1343
|
-
SCREENCI_RECORD: process.env.SCREENCI_RECORD,
|
|
1344
|
-
SCREENCI_SIGNAL_LOGGING: process.env.SCREENCI_SIGNAL_LOGGING,
|
|
1345
|
-
SCREENCI_DEV_PACKAGE_ROOT: process.env.SCREENCI_DEV_PACKAGE_ROOT,
|
|
1346
|
-
};
|
|
1347
|
-
}
|
|
1348
|
-
function requireContainerRuntime() {
|
|
1349
|
-
const runtime = detectContainerRuntime();
|
|
1350
|
-
if (runtime === 'podman' || runtime === 'docker')
|
|
1351
|
-
return runtime;
|
|
1352
|
-
throw new Error('No container runtime available');
|
|
1353
|
-
}
|
|
1354
1180
|
async function runInitAuth() {
|
|
1355
1181
|
const appUrl = getDevFrontendUrl();
|
|
1356
1182
|
try {
|
|
@@ -1387,18 +1213,10 @@ async function ensureScreenciSecret() {
|
|
|
1387
1213
|
return undefined;
|
|
1388
1214
|
}
|
|
1389
1215
|
}
|
|
1390
|
-
function checkNodeVersion() {
|
|
1391
|
-
const [major] = process.versions.node.split('.').map(Number);
|
|
1392
|
-
if (major === undefined || major < 18) {
|
|
1393
|
-
logger.error(`Error: Node.js 18 or higher is required (current: v${process.versions.node})`);
|
|
1394
|
-
process.exit(1);
|
|
1395
|
-
}
|
|
1396
|
-
}
|
|
1397
1216
|
async function runInit(projectNameArg, options) {
|
|
1398
1217
|
const { verbose, install, yes, skill, ci } = options;
|
|
1399
|
-
checkNodeVersion();
|
|
1400
|
-
checkContainerRuntimeForInit();
|
|
1401
1218
|
const initCwd = getInitProjectRoot();
|
|
1219
|
+
const existingRepositoryDetected = existsSync(resolve(initCwd, '.git'));
|
|
1402
1220
|
let projectName = projectNameArg?.trim();
|
|
1403
1221
|
if (!projectName) {
|
|
1404
1222
|
projectName = await promptProjectName();
|
|
@@ -1407,25 +1225,28 @@ async function runInit(projectNameArg, options) {
|
|
|
1407
1225
|
logger.error('Error: Project name is required');
|
|
1408
1226
|
process.exit(1);
|
|
1409
1227
|
}
|
|
1410
|
-
|
|
1228
|
+
if (existingRepositoryDetected) {
|
|
1229
|
+
logger.info('Existing repository detected');
|
|
1230
|
+
}
|
|
1231
|
+
const repositoryMode = existingRepositoryDetected
|
|
1232
|
+
? yes
|
|
1233
|
+
? 'standalone'
|
|
1234
|
+
: await promptInitRepositoryMode()
|
|
1235
|
+
: 'standalone';
|
|
1236
|
+
const isPartOfExistingRepository = repositoryMode === 'existing-repository';
|
|
1237
|
+
const dirName = isPartOfExistingRepository
|
|
1238
|
+
? 'screenci'
|
|
1239
|
+
: projectNameToDirectoryName(projectName);
|
|
1411
1240
|
const projectDir = resolve(initCwd, dirName);
|
|
1412
|
-
const
|
|
1241
|
+
const githubRootDir = isPartOfExistingRepository ? initCwd : projectDir;
|
|
1242
|
+
const githubDir = resolve(githubRootDir, '.github');
|
|
1413
1243
|
const githubWorkflowsDir = resolve(githubDir, 'workflows');
|
|
1414
1244
|
const githubActionPath = resolve(githubWorkflowsDir, 'screenci.yaml');
|
|
1245
|
+
const githubActionProjectDir = isPartOfExistingRepository ? 'screenci' : '.';
|
|
1415
1246
|
if (existsSync(projectDir)) {
|
|
1416
1247
|
logger.error(`Error: Directory "${dirName}" already exists`);
|
|
1417
1248
|
process.exit(1);
|
|
1418
1249
|
}
|
|
1419
|
-
const initTarget = yes
|
|
1420
|
-
? undefined
|
|
1421
|
-
: await (async () => {
|
|
1422
|
-
const mode = await promptInitTargetMode();
|
|
1423
|
-
const baseURL = normalizeInitUrl(await promptInitTargetUrl(mode));
|
|
1424
|
-
return {
|
|
1425
|
-
mode,
|
|
1426
|
-
baseURL,
|
|
1427
|
-
};
|
|
1428
|
-
})();
|
|
1429
1250
|
const shouldInstallDependencies = yes
|
|
1430
1251
|
? true
|
|
1431
1252
|
: install
|
|
@@ -1456,13 +1277,7 @@ async function runInit(projectNameArg, options) {
|
|
|
1456
1277
|
'-y',
|
|
1457
1278
|
];
|
|
1458
1279
|
const skillsCommand = `npx ${skillsArgs.join(' ')}`;
|
|
1459
|
-
const
|
|
1460
|
-
const screenciDependency = devScreenciPackageRoot
|
|
1461
|
-
? getLocalScreenciDependency(devScreenciPackageRoot, projectDir)
|
|
1462
|
-
: await readCurrentScreenciVersion();
|
|
1463
|
-
if (devScreenciPackageRoot) {
|
|
1464
|
-
await buildLocalScreenciPackage(devScreenciPackageRoot);
|
|
1465
|
-
}
|
|
1280
|
+
const screenciDependency = await readCurrentScreenciVersion();
|
|
1466
1281
|
await mkdir(resolve(projectDir, 'videos'), { recursive: true });
|
|
1467
1282
|
if (shouldAddGithubActionCi) {
|
|
1468
1283
|
if (!existsSync(githubDir)) {
|
|
@@ -1472,15 +1287,14 @@ async function runInit(projectNameArg, options) {
|
|
|
1472
1287
|
await mkdir(githubWorkflowsDir);
|
|
1473
1288
|
}
|
|
1474
1289
|
}
|
|
1475
|
-
await writeFile(resolve(projectDir, 'screenci.config.ts'), generateConfig(projectName
|
|
1476
|
-
await writeFile(resolve(projectDir, 'package.json'), generatePackageJson(
|
|
1290
|
+
await writeFile(resolve(projectDir, 'screenci.config.ts'), generateConfig(projectName));
|
|
1291
|
+
await writeFile(resolve(projectDir, 'package.json'), generatePackageJson(shouldAddPlaywrightCli, screenciDependency));
|
|
1477
1292
|
await writeFile(resolve(projectDir, 'tsconfig.json'), generateTsconfig());
|
|
1478
1293
|
await writeFile(resolve(projectDir, 'README.md'), generateReadme(projectName));
|
|
1479
|
-
await writeFile(resolve(projectDir, 'Dockerfile'), generateDockerfile());
|
|
1480
1294
|
await writeFile(resolve(projectDir, '.gitignore'), generateGitignore());
|
|
1481
1295
|
await writeFile(resolve(projectDir, 'videos', 'example.video.ts'), generateExampleVideo());
|
|
1482
1296
|
if (shouldAddGithubActionCi) {
|
|
1483
|
-
await writeFile(githubActionPath, generateGithubAction());
|
|
1297
|
+
await writeFile(githubActionPath, generateGithubAction(githubActionProjectDir));
|
|
1484
1298
|
}
|
|
1485
1299
|
await writeFile(resolve(projectDir, '.env'), '');
|
|
1486
1300
|
logger.info(`Initialized screenci project "${projectName}" in ${projectDir}/`);
|
|
@@ -1489,11 +1303,13 @@ async function runInit(projectNameArg, options) {
|
|
|
1489
1303
|
logger.info(' package.json');
|
|
1490
1304
|
logger.info(' tsconfig.json');
|
|
1491
1305
|
logger.info(' README.md');
|
|
1492
|
-
logger.info(' Dockerfile');
|
|
1493
1306
|
logger.info(' .gitignore');
|
|
1494
1307
|
logger.info(' videos/example.video.ts');
|
|
1495
1308
|
if (shouldAddGithubActionCi) {
|
|
1496
|
-
|
|
1309
|
+
const githubActionDisplayPath = isPartOfExistingRepository
|
|
1310
|
+
? '.github/workflows/screenci.yaml (outside ./screenci, at repository root)'
|
|
1311
|
+
: '.github/workflows/screenci.yaml';
|
|
1312
|
+
logger.info(` ${githubActionDisplayPath}`);
|
|
1497
1313
|
}
|
|
1498
1314
|
logger.info(' .env (empty placeholder)');
|
|
1499
1315
|
logger.info('');
|
|
@@ -1514,24 +1330,14 @@ async function runInit(projectNameArg, options) {
|
|
|
1514
1330
|
}
|
|
1515
1331
|
}
|
|
1516
1332
|
if (verbose) {
|
|
1517
|
-
const installArgs =
|
|
1518
|
-
? ['install', '--include=dev', '--install-links']
|
|
1519
|
-
: ['install', '--include=dev'];
|
|
1333
|
+
const installArgs = ['install', '--include=dev'];
|
|
1520
1334
|
logger.info(`Running 'npm ${installArgs.join(' ')}'...`);
|
|
1521
1335
|
await spawnInherited('npm', installArgs, projectDir, 'screenci init');
|
|
1522
1336
|
}
|
|
1523
1337
|
else {
|
|
1524
1338
|
const spinner = ora('Running npm install...').start();
|
|
1525
1339
|
try {
|
|
1526
|
-
const installArgs =
|
|
1527
|
-
? [
|
|
1528
|
-
'install',
|
|
1529
|
-
'--include=dev',
|
|
1530
|
-
'--install-links',
|
|
1531
|
-
'--prefix',
|
|
1532
|
-
projectDir,
|
|
1533
|
-
]
|
|
1534
|
-
: ['install', '--include=dev', '--prefix', projectDir];
|
|
1340
|
+
const installArgs = ['install', '--include=dev', '--prefix', projectDir];
|
|
1535
1341
|
await spawnSilent('npm', installArgs);
|
|
1536
1342
|
spinner.succeed('npm install complete');
|
|
1537
1343
|
}
|
|
@@ -1575,42 +1381,8 @@ export async function main() {
|
|
|
1575
1381
|
.allowUnknownOption(true)
|
|
1576
1382
|
.action(async () => {
|
|
1577
1383
|
const parsed = parseRecordCliArgs(getSubcommandArgv('record'));
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
process.exit(1);
|
|
1581
|
-
}
|
|
1582
|
-
const useContainer = process.env.SCREENCI_IN_CONTAINER !== 'true';
|
|
1583
|
-
// Validate early so we don't build the container unnecessarily
|
|
1584
|
-
if (useContainer) {
|
|
1585
|
-
validateArgs(parsed.otherArgs);
|
|
1586
|
-
}
|
|
1587
|
-
// On the host, load .env so SCREENCI_SECRET is available for uploads
|
|
1588
|
-
if (process.env.SCREENCI_IN_CONTAINER !== 'true') {
|
|
1589
|
-
const resolvedConfigForSecret = findScreenCIConfig(parsed.configPath);
|
|
1590
|
-
if (resolvedConfigForSecret) {
|
|
1591
|
-
try {
|
|
1592
|
-
const screenciConfig = await loadRecordConfigWithoutPlaywrightCollision(resolvedConfigForSecret);
|
|
1593
|
-
if (screenciConfig.envFile) {
|
|
1594
|
-
const envFilePath = resolve(dirname(resolvedConfigForSecret), screenciConfig.envFile);
|
|
1595
|
-
loadEnvFile(envFilePath, false);
|
|
1596
|
-
}
|
|
1597
|
-
}
|
|
1598
|
-
catch {
|
|
1599
|
-
// Config import failed — continue with whatever is already in env
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
}
|
|
1603
|
-
if (useContainer) {
|
|
1604
|
-
await ensureScreenciSecret();
|
|
1605
|
-
}
|
|
1606
|
-
if (useContainer) {
|
|
1607
|
-
await runWithContainer(parsed.otherArgs, parsed.configPath, parsed.verbose, parsed.forcedRuntime);
|
|
1608
|
-
}
|
|
1609
|
-
else {
|
|
1610
|
-
await run('record', parsed.otherArgs, parsed.configPath);
|
|
1611
|
-
}
|
|
1612
|
-
// Upload only from the host, not from inside the container
|
|
1613
|
-
if (process.env.SCREENCI_IN_CONTAINER === 'true')
|
|
1384
|
+
await run('record', parsed.otherArgs, parsed.configPath);
|
|
1385
|
+
if (process.env.SCREENCI_RECORDING === 'true')
|
|
1614
1386
|
return;
|
|
1615
1387
|
// After recording, upload results to API if configured
|
|
1616
1388
|
const resolvedConfigPath = findScreenCIConfig(parsed.configPath);
|
|
@@ -1632,6 +1404,7 @@ export async function main() {
|
|
|
1632
1404
|
const screenciDir = resolve(configDir, '.screenci');
|
|
1633
1405
|
let projectId = null;
|
|
1634
1406
|
try {
|
|
1407
|
+
logger.info('');
|
|
1635
1408
|
projectId = await uploadRecordings(screenciDir, screenciConfig.projectName, apiUrl, secret);
|
|
1636
1409
|
}
|
|
1637
1410
|
catch (err) {
|
|
@@ -1659,9 +1432,23 @@ export async function main() {
|
|
|
1659
1432
|
.allowUnknownOption(true)
|
|
1660
1433
|
.action(async () => {
|
|
1661
1434
|
const parsed = parseConfigCliArgs(getSubcommandArgv('test'));
|
|
1435
|
+
const resolvedConfigPath = findScreenCIConfig(parsed.configPath);
|
|
1436
|
+
if (resolvedConfigPath) {
|
|
1437
|
+
try {
|
|
1438
|
+
const screenciConfig = await loadRecordConfigWithoutPlaywrightCollision(resolvedConfigPath);
|
|
1439
|
+
if (screenciConfig.envFile) {
|
|
1440
|
+
const envFilePath = resolve(dirname(resolvedConfigPath), screenciConfig.envFile);
|
|
1441
|
+
loadEnvFile(envFilePath, true);
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
catch (err) {
|
|
1445
|
+
logger.warn('Failed to load config for test env:', err);
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1662
1448
|
await run('test', parsed.otherArgs, parsed.configPath);
|
|
1663
|
-
if (process.env.
|
|
1449
|
+
if (process.env.SCREENCI_RECORDING === 'true')
|
|
1664
1450
|
return;
|
|
1451
|
+
logger.info('');
|
|
1665
1452
|
logger.info(`Tests passed. Run ${pc.cyan('npx screenci record')} to render the videos.`);
|
|
1666
1453
|
});
|
|
1667
1454
|
program
|
|
@@ -1756,7 +1543,6 @@ function getSubcommandArgv(command) {
|
|
|
1756
1543
|
function parseRecordCliArgs(args) {
|
|
1757
1544
|
let configPath;
|
|
1758
1545
|
let verbose = false;
|
|
1759
|
-
let forcedRuntime;
|
|
1760
1546
|
const otherArgs = [];
|
|
1761
1547
|
for (let i = 0; i < args.length; i++) {
|
|
1762
1548
|
const arg = args[i];
|
|
@@ -1774,12 +1560,6 @@ function parseRecordCliArgs(args) {
|
|
|
1774
1560
|
else if (arg === '--verbose' || arg === '-v') {
|
|
1775
1561
|
verbose = true;
|
|
1776
1562
|
}
|
|
1777
|
-
else if (arg === '--podman') {
|
|
1778
|
-
forcedRuntime = forcedRuntime === 'docker' ? 'both' : 'podman';
|
|
1779
|
-
}
|
|
1780
|
-
else if (arg === '--docker') {
|
|
1781
|
-
forcedRuntime = forcedRuntime === 'podman' ? 'both' : 'docker';
|
|
1782
|
-
}
|
|
1783
1563
|
else {
|
|
1784
1564
|
otherArgs.push(arg);
|
|
1785
1565
|
}
|
|
@@ -1787,7 +1567,6 @@ function parseRecordCliArgs(args) {
|
|
|
1787
1567
|
return {
|
|
1788
1568
|
configPath,
|
|
1789
1569
|
verbose,
|
|
1790
|
-
forcedRuntime,
|
|
1791
1570
|
otherArgs,
|
|
1792
1571
|
};
|
|
1793
1572
|
}
|
|
@@ -1860,122 +1639,6 @@ function spawnInherited(cmd, args, cwd, activityLabel = cmd) {
|
|
|
1860
1639
|
});
|
|
1861
1640
|
});
|
|
1862
1641
|
}
|
|
1863
|
-
export function detectContainerRuntime(forcedRuntime) {
|
|
1864
|
-
const runtimeCheck = forcedRuntime
|
|
1865
|
-
? checkContainerRuntime(forcedRuntime)
|
|
1866
|
-
: getPreferredContainerRuntime();
|
|
1867
|
-
if (runtimeCheck) {
|
|
1868
|
-
warnIfContainerRuntimeVersionIsOld(runtimeCheck);
|
|
1869
|
-
return runtimeCheck.runtime;
|
|
1870
|
-
}
|
|
1871
|
-
if (forcedRuntime) {
|
|
1872
|
-
exitContainerRuntimeNotFound(forcedRuntime);
|
|
1873
|
-
}
|
|
1874
|
-
logger.error('Error: Neither podman nor docker found.');
|
|
1875
|
-
logger.error('Please install podman (recommended) or docker to use screenci record.');
|
|
1876
|
-
logger.error(prerequisitesMessage());
|
|
1877
|
-
process.exit(1);
|
|
1878
|
-
}
|
|
1879
|
-
function checkContainerRuntimeForInit() {
|
|
1880
|
-
const runtimeCheck = getPreferredContainerRuntime();
|
|
1881
|
-
if (!runtimeCheck) {
|
|
1882
|
-
logger.warn('Neither podman nor docker found. Install one before running screenci record.');
|
|
1883
|
-
logger.warn(prerequisitesMessage());
|
|
1884
|
-
return;
|
|
1885
|
-
}
|
|
1886
|
-
warnIfContainerRuntimeVersionIsOld(runtimeCheck);
|
|
1887
|
-
}
|
|
1888
|
-
async function buildProjectImage(containerRuntime, dockerfilePath, configDir, verbose) {
|
|
1889
|
-
if (verbose) {
|
|
1890
|
-
await spawnInherited(containerRuntime, ['build', '-f', dockerfilePath, '-t', 'screenci', configDir], undefined, 'Building project image');
|
|
1891
|
-
return;
|
|
1892
|
-
}
|
|
1893
|
-
await spawnSilent(containerRuntime, [
|
|
1894
|
-
'build',
|
|
1895
|
-
'-f',
|
|
1896
|
-
dockerfilePath,
|
|
1897
|
-
'-t',
|
|
1898
|
-
'screenci',
|
|
1899
|
-
configDir,
|
|
1900
|
-
]);
|
|
1901
|
-
}
|
|
1902
|
-
async function runWithContainer(additionalArgs, customConfigPath, verbose = false, forcedRuntime) {
|
|
1903
|
-
const configPath = findScreenCIConfig(customConfigPath);
|
|
1904
|
-
if (!configPath) {
|
|
1905
|
-
const errorMsg = customConfigPath
|
|
1906
|
-
? `Error: Config file not found: ${customConfigPath}`
|
|
1907
|
-
: 'Error: screenci.config.ts not found in current directory';
|
|
1908
|
-
logger.error(errorMsg);
|
|
1909
|
-
process.exit(1);
|
|
1910
|
-
}
|
|
1911
|
-
const configDir = dirname(configPath);
|
|
1912
|
-
const repoRoot = findRepoRoot(configDir);
|
|
1913
|
-
if (!repoRoot) {
|
|
1914
|
-
logger.error('Error: Could not find repository root (.git or pnpm-workspace.yaml)');
|
|
1915
|
-
process.exit(1);
|
|
1916
|
-
}
|
|
1917
|
-
logger.info('Preparing ScreenCI recording container...');
|
|
1918
|
-
const containerRuntime = detectContainerRuntime(forcedRuntime);
|
|
1919
|
-
const imageName = process.env['SCREENCI_LOCAL_IMAGE']
|
|
1920
|
-
? 'screenci'
|
|
1921
|
-
: 'ghcr.io/screenci/record:latest';
|
|
1922
|
-
logger.info(`Using ${containerRuntime} with image ${imageName}`);
|
|
1923
|
-
if (process.env['SCREENCI_LOCAL_IMAGE']) {
|
|
1924
|
-
logger.info('SCREENCI_LOCAL_IMAGE set — skipping screenci image build');
|
|
1925
|
-
}
|
|
1926
|
-
else {
|
|
1927
|
-
const imageExists = spawnSync(containerRuntime, ['image', 'exists', imageName], {
|
|
1928
|
-
stdio: 'ignore',
|
|
1929
|
-
}).status === 0;
|
|
1930
|
-
if (!imageExists) {
|
|
1931
|
-
logger.info(`Image ${imageName} not found locally, pulling...`);
|
|
1932
|
-
if (verbose) {
|
|
1933
|
-
await spawnInherited(containerRuntime, ['pull', imageName]);
|
|
1934
|
-
}
|
|
1935
|
-
else {
|
|
1936
|
-
await spawnSilent(containerRuntime, ['pull', imageName]);
|
|
1937
|
-
}
|
|
1938
|
-
}
|
|
1939
|
-
}
|
|
1940
|
-
clearDirectory(resolve(configDir, '.screenci'));
|
|
1941
|
-
const secret = process.env['SCREENCI_SECRET'];
|
|
1942
|
-
if (secret === undefined) {
|
|
1943
|
-
logger.error('Error: SCREENCI_SECRET is not set');
|
|
1944
|
-
process.exit(1);
|
|
1945
|
-
}
|
|
1946
|
-
logger.info('Starting ScreenCI recording container...');
|
|
1947
|
-
const containerBaseHost = containerRuntime === 'docker'
|
|
1948
|
-
? 'host.docker.internal'
|
|
1949
|
-
: 'host.containers.internal';
|
|
1950
|
-
await spawnContainerRecording(containerRuntime, [
|
|
1951
|
-
'run',
|
|
1952
|
-
'--rm',
|
|
1953
|
-
...(containerRuntime === 'docker'
|
|
1954
|
-
? ['--add-host', 'host.docker.internal:host-gateway']
|
|
1955
|
-
: []),
|
|
1956
|
-
...(process.env.CI !== undefined ? ['-e', `CI=${process.env.CI}`] : []),
|
|
1957
|
-
'-e',
|
|
1958
|
-
'SCREENCI_IN_CONTAINER=true',
|
|
1959
|
-
'-e',
|
|
1960
|
-
`SCREENCI_CONTAINER_BASE_HOST=${containerBaseHost}`,
|
|
1961
|
-
'-e',
|
|
1962
|
-
'SCREENCI_RECORD=true',
|
|
1963
|
-
'-e',
|
|
1964
|
-
`SCREENCI_SECRET=${secret}`,
|
|
1965
|
-
'-e',
|
|
1966
|
-
'SCREENCI_SIGNAL_LOGGING=silent',
|
|
1967
|
-
'-v',
|
|
1968
|
-
`${configDir}/.screenci:/app/.screenci`,
|
|
1969
|
-
'-v',
|
|
1970
|
-
`${configPath}:/app/screenci.config.ts`,
|
|
1971
|
-
'-v',
|
|
1972
|
-
`${configDir}/videos:/app/videos`,
|
|
1973
|
-
imageName,
|
|
1974
|
-
'screenci',
|
|
1975
|
-
'record',
|
|
1976
|
-
...additionalArgs,
|
|
1977
|
-
]);
|
|
1978
|
-
}
|
|
1979
1642
|
async function run(command, additionalArgs, customConfigPath) {
|
|
1980
1643
|
const configPath = findScreenCIConfig(customConfigPath);
|
|
1981
1644
|
if (!configPath) {
|
|
@@ -1985,6 +1648,10 @@ async function run(command, additionalArgs, customConfigPath) {
|
|
|
1985
1648
|
logger.error(errorMsg);
|
|
1986
1649
|
process.exit(1);
|
|
1987
1650
|
}
|
|
1651
|
+
if (command === 'test' || process.env.SCREENCI_RECORDING !== 'true') {
|
|
1652
|
+
await loadEnvFileFromConfigSource(configPath, false);
|
|
1653
|
+
}
|
|
1654
|
+
const envForChild = { ...process.env };
|
|
1988
1655
|
// Only validate args for record command
|
|
1989
1656
|
if (command === 'record') {
|
|
1990
1657
|
await ensureScreenciSecret();
|
|
@@ -1992,30 +1659,16 @@ async function run(command, additionalArgs, customConfigPath) {
|
|
|
1992
1659
|
const screenciDir = resolve(dirname(configPath), '.screenci');
|
|
1993
1660
|
clearDirectory(screenciDir);
|
|
1994
1661
|
}
|
|
1995
|
-
|
|
1996
|
-
if (process.env.SCREENCI_IN_CONTAINER !== 'true') {
|
|
1997
|
-
logger.info(`Running ScreenCI ${mode} with npx...`);
|
|
1662
|
+
if (process.env.SCREENCI_RECORDING !== 'true') {
|
|
1998
1663
|
logger.info(`Using config: ${configPath}`);
|
|
1999
1664
|
}
|
|
2000
|
-
const
|
|
2001
|
-
|
|
2002
|
-
const configDir = dirname(configPath);
|
|
2003
|
-
await buildLocalScreenciPackage(devScreenciPackageRoot);
|
|
2004
|
-
await installLocalScreenciPackage(configDir, devScreenciPackageRoot);
|
|
2005
|
-
}
|
|
2006
|
-
const playwrightArgs = [
|
|
2007
|
-
'playwright',
|
|
2008
|
-
'test',
|
|
2009
|
-
'--config',
|
|
2010
|
-
configPath,
|
|
2011
|
-
...additionalArgs,
|
|
2012
|
-
];
|
|
2013
|
-
const child = spawn('npx', playwrightArgs, {
|
|
1665
|
+
const playwrightArgs = ['test', '--config', configPath, ...additionalArgs];
|
|
1666
|
+
const child = spawn('playwright', playwrightArgs, {
|
|
2014
1667
|
stdio: 'inherit',
|
|
2015
1668
|
env: {
|
|
2016
|
-
...
|
|
1669
|
+
...envForChild,
|
|
2017
1670
|
// Enable recording only for record command
|
|
2018
|
-
...(command === 'record' ? {
|
|
1671
|
+
...(command === 'record' ? { SCREENCI_RECORDING: 'true' } : {}),
|
|
2019
1672
|
},
|
|
2020
1673
|
});
|
|
2021
1674
|
const childSignals = forwardChildSignals(child, `screenci ${command}`);
|
|
@@ -2048,8 +1701,11 @@ async function run(command, additionalArgs, customConfigPath) {
|
|
|
2048
1701
|
// Check if this module is the main module (handles symlinks properly)
|
|
2049
1702
|
const currentFile = fileURLToPath(import.meta.url);
|
|
2050
1703
|
const mainFile = process.argv[1] ? realpathSync(process.argv[1]) : null;
|
|
1704
|
+
const currentRealFile = realpathSync(currentFile);
|
|
2051
1705
|
if (mainFile &&
|
|
2052
|
-
(currentFile === mainFile ||
|
|
1706
|
+
(currentFile === mainFile ||
|
|
1707
|
+
currentRealFile === mainFile ||
|
|
1708
|
+
currentFile === realpathSync(mainFile))) {
|
|
2053
1709
|
main().catch((error) => {
|
|
2054
1710
|
logger.error('Error:', error.message);
|
|
2055
1711
|
process.exit(1);
|