screenci 0.0.83 → 0.0.85
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/dist/cli.d.ts +16 -3
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +138 -32
- package/dist/cli.js.map +1 -1
- package/dist/docs/video-sources/camera-and-zooming.screenci.js +1 -2
- package/dist/docs/video-sources/camera-and-zooming.screenci.js.map +1 -1
- package/dist/docs/video-sources/installation.screenci.js +1 -2
- package/dist/docs/video-sources/installation.screenci.js.map +1 -1
- package/dist/docs/video-sources/languages.screenci.js +3 -1
- package/dist/docs/video-sources/languages.screenci.js.map +1 -1
- package/dist/docs/video-sources/locator-highlight-animated.screenci.js +0 -1
- package/dist/docs/video-sources/locator-highlight-animated.screenci.js.map +1 -1
- package/dist/docs/video-sources/locator-highlight-still.screenci.js +0 -1
- package/dist/docs/video-sources/locator-highlight-still.screenci.js.map +1 -1
- package/dist/docs/video-sources/narration.screenci.js +3 -1
- package/dist/docs/video-sources/narration.screenci.js.map +1 -1
- package/dist/docs/video-sources/public-urls-and-embeds.screenci.js +3 -1
- package/dist/docs/video-sources/public-urls-and-embeds.screenci.js.map +1 -1
- package/dist/docs/video-sources/speaking-style.screenci.js +79 -14
- package/dist/docs/video-sources/speaking-style.screenci.js.map +1 -1
- package/dist/docs/video-sources/studio.screenci.js +3 -1
- package/dist/docs/video-sources/studio.screenci.js.map +1 -1
- package/dist/docs/video-sources/vertical-video.screenci.js +1 -1
- package/dist/src/anonSession.d.ts +40 -0
- package/dist/src/anonSession.d.ts.map +1 -0
- package/dist/src/anonSession.js +91 -0
- package/dist/src/anonSession.js.map +1 -0
- package/dist/src/asset.d.ts +11 -1
- package/dist/src/asset.d.ts.map +1 -1
- package/dist/src/asset.js +122 -52
- package/dist/src/asset.js.map +1 -1
- package/dist/src/audio.d.ts.map +1 -1
- package/dist/src/audio.js +2 -2
- package/dist/src/audio.js.map +1 -1
- package/dist/src/cue.d.ts.map +1 -1
- package/dist/src/cue.js +12 -4
- package/dist/src/cue.js.map +1 -1
- package/dist/src/disableAnimations.d.ts +33 -0
- package/dist/src/disableAnimations.d.ts.map +1 -0
- package/dist/src/disableAnimations.js +50 -0
- package/dist/src/disableAnimations.js.map +1 -0
- package/dist/src/events.d.ts +5 -1
- package/dist/src/events.d.ts.map +1 -1
- package/dist/src/events.js +4 -0
- package/dist/src/events.js.map +1 -1
- package/dist/src/git.d.ts +2 -0
- package/dist/src/git.d.ts.map +1 -1
- package/dist/src/git.js +1 -1
- package/dist/src/git.js.map +1 -1
- package/dist/src/init.d.ts +30 -8
- package/dist/src/init.d.ts.map +1 -1
- package/dist/src/init.js +112 -57
- package/dist/src/init.js.map +1 -1
- package/dist/src/instrument.d.ts.map +1 -1
- package/dist/src/instrument.js +5 -2
- package/dist/src/instrument.js.map +1 -1
- package/dist/src/linkSession.d.ts +13 -10
- package/dist/src/linkSession.d.ts.map +1 -1
- package/dist/src/linkSession.js +33 -29
- package/dist/src/linkSession.js.map +1 -1
- package/dist/src/missingAssetLog.d.ts +8 -0
- package/dist/src/missingAssetLog.d.ts.map +1 -0
- package/dist/src/missingAssetLog.js +16 -0
- package/dist/src/missingAssetLog.js.map +1 -0
- package/dist/src/recordingData.d.ts +1 -1
- package/dist/src/recordingData.d.ts.map +1 -1
- package/dist/src/runtimeContext.d.ts +17 -0
- package/dist/src/runtimeContext.d.ts.map +1 -1
- package/dist/src/runtimeContext.js +3 -0
- package/dist/src/runtimeContext.js.map +1 -1
- package/dist/src/runtimeMode.d.ts.map +1 -1
- package/dist/src/runtimeMode.js +8 -0
- package/dist/src/runtimeMode.js.map +1 -1
- package/dist/src/screenshot.d.ts.map +1 -1
- package/dist/src/screenshot.js +7 -0
- package/dist/src/screenshot.js.map +1 -1
- package/dist/src/types.d.ts +37 -1
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js.map +1 -1
- package/dist/src/video.d.ts.map +1 -1
- package/dist/src/video.js +7 -0
- package/dist/src/video.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/skills/screenci/SKILL.md +10 -9
- package/skills/screenci/references/init.md +5 -6
- package/skills/screenci/references/record.md +9 -7
- package/dist/docs/video-sources/landing.screenci.d.ts +0 -2
- package/dist/docs/video-sources/landing.screenci.d.ts.map +0 -1
- package/dist/docs/video-sources/landing.screenci.js +0 -34
- package/dist/docs/video-sources/landing.screenci.js.map +0 -1
package/dist/cli.d.ts
CHANGED
|
@@ -2,7 +2,9 @@ import type { RecordingCustomVoiceRef, RecordingData } from './src/events.js';
|
|
|
2
2
|
import type { VoiceKey } from './src/voices.js';
|
|
3
3
|
import type { RecordUploadPolicy, ScreenCIConfig } from './src/types.js';
|
|
4
4
|
import { getCliLinkSessionApiUrl, getDevBackendUrl, getDevFrontendUrl } from './src/linkSession.js';
|
|
5
|
+
import { type CliCredential } from './src/anonSession.js';
|
|
5
6
|
export { getCliLinkSessionApiUrl, getDevBackendUrl, getDevFrontendUrl };
|
|
7
|
+
export { secretCredential, anonCredential } from './src/anonSession.js';
|
|
6
8
|
type PlaywrightListReportSuite = {
|
|
7
9
|
title?: string;
|
|
8
10
|
specs?: Array<{
|
|
@@ -26,7 +28,7 @@ export declare function detectInteractiveSession(env?: NodeJS.ProcessEnv, stdout
|
|
|
26
28
|
}): boolean;
|
|
27
29
|
export declare function collectPlaywrightListTitles(suites: readonly PlaywrightListReportSuite[]): string[];
|
|
28
30
|
/** What an uploaded file actually is, used to label it in the upload log. */
|
|
29
|
-
type UploadAssetKind = 'overlay' | 'audio' | 'clip' | 'voice';
|
|
31
|
+
type UploadAssetKind = 'overlay' | 'audio' | 'clip' | 'voice' | 'cursor';
|
|
30
32
|
type PreparedUploadAsset = {
|
|
31
33
|
kind: UploadAssetKind;
|
|
32
34
|
fileHash: string;
|
|
@@ -101,7 +103,7 @@ export type ResolveAssetsResponse = {
|
|
|
101
103
|
* Returns the assets that could not be resolved (no previous version), so the
|
|
102
104
|
* caller can fail the recording with actionable guidance.
|
|
103
105
|
*/
|
|
104
|
-
export declare function resolveMissingUploadAssets(assets: PreparedUploadAsset[], projectName: string, videoName: string, apiUrl: string,
|
|
106
|
+
export declare function resolveMissingUploadAssets(assets: PreparedUploadAsset[], projectName: string, videoName: string, apiUrl: string, credential: CliCredential, signal: AbortSignal, progressReporter?: {
|
|
105
107
|
info: (message: string) => void;
|
|
106
108
|
}): Promise<PreparedUploadAsset[]>;
|
|
107
109
|
/**
|
|
@@ -109,7 +111,7 @@ export declare function resolveMissingUploadAssets(assets: PreparedUploadAsset[]
|
|
|
109
111
|
* previously uploaded version to reuse.
|
|
110
112
|
*/
|
|
111
113
|
export declare function formatUnresolvedAssetMessage(videoName: string, unresolved: PreparedUploadAsset[]): string;
|
|
112
|
-
export declare function uploadRecordings(screenciDir: string, projectName: string, apiUrl: string,
|
|
114
|
+
export declare function uploadRecordings(screenciDir: string, projectName: string, apiUrl: string, credential: CliCredential, specificEntry?: string, verbose?: boolean): Promise<{
|
|
113
115
|
projectId: string | null;
|
|
114
116
|
recordId: string | null;
|
|
115
117
|
hadFailures: boolean;
|
|
@@ -135,6 +137,17 @@ export declare function requireScreenCISecret(configPath?: string): Promise<{
|
|
|
135
137
|
apiUrl: string;
|
|
136
138
|
}>;
|
|
137
139
|
export declare function extractGrep(args: string[]): string | undefined;
|
|
140
|
+
/**
|
|
141
|
+
* Resolves the credential `record`'s upload should authenticate with. A real
|
|
142
|
+
* SCREENCI_SECRET wins outright. Otherwise, checks the locally stored anon
|
|
143
|
+
* trial token: `claimed` self-upgrades by writing the real secret into `.env`
|
|
144
|
+
* and deleting the local anon state (no manual step required); `not_found`/
|
|
145
|
+
* `expired` nags and starts a fresh trial; `pending` proceeds anonymously.
|
|
146
|
+
*/
|
|
147
|
+
export declare function resolveUploadCredential(screenciDir: string, apiUrl: string, envFilePath: string, secretFromEnv: string | undefined): Promise<{
|
|
148
|
+
credential: CliCredential;
|
|
149
|
+
usedAnonCredential: boolean;
|
|
150
|
+
}>;
|
|
138
151
|
export declare function main(): Promise<void>;
|
|
139
152
|
export declare function parseRecordCliArgs(args: string[]): {
|
|
140
153
|
configPath: string | undefined;
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../cli.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../cli.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.js
CHANGED
|
@@ -10,16 +10,22 @@ import { Command, CommanderError } from 'commander';
|
|
|
10
10
|
import { confirm } from '@inquirer/prompts';
|
|
11
11
|
import pc from 'picocolors';
|
|
12
12
|
import { logger } from './src/logger.js';
|
|
13
|
-
import { determinePackageManager, parsePackageManager, runInit, } from './src/init.js';
|
|
13
|
+
import { determinePackageManager, initToggleOptionsFromCommander, parsePackageManager, registerInitToggleOptions, runInit, } from './src/init.js';
|
|
14
14
|
import { SCREENCI_DISABLE_RECORDING_TIMINGS_ENV, SCREENCI_MOCK_RECORD_ENV, SCREENCI_LANGUAGES_ENV, SCREENCI_VALUES_OVERRIDES_ENV, SCREENCI_RECORD_OPTIONS_ENV, isUploadExistingEnabled, } from './src/runtimeMode.js';
|
|
15
15
|
import { DEFAULT_RECORD_UPLOAD_POLICY } from './src/defaults.js';
|
|
16
16
|
import { findDuplicateTitles, formatDuplicateTitlesMessage, } from './src/titleValidation.js';
|
|
17
|
-
import { getCliLinkSessionApiUrl, getDevBackendUrl, getDevFrontendUrl, getScreenCISecretsUrl, } from './src/linkSession.js';
|
|
17
|
+
import { getCliLinkSessionApiUrl, getDevBackendUrl, getDevFrontendUrl, getScreenCISecretsUrl, persistScreenCISecret, } from './src/linkSession.js';
|
|
18
18
|
import { OVERLAY_CACHE_DIR_NAME } from './src/htmlRasterizer.js';
|
|
19
19
|
import { maybeExtractVoiceSampleAudio } from './src/voiceSampleAudio.js';
|
|
20
|
+
import { anonCredential, checkAnonSessionStatus, deleteAnonSessionFile, getOrCreateAnonToken, secretCredential, } from './src/anonSession.js';
|
|
20
21
|
// Re-export the environment-aware URL helpers so existing importers (and tests)
|
|
21
22
|
// can keep importing them from the CLI entrypoint.
|
|
22
23
|
export { getCliLinkSessionApiUrl, getDevBackendUrl, getDevFrontendUrl };
|
|
24
|
+
// Re-exported so test files that mock fs/fs/promises can obtain these via the
|
|
25
|
+
// same dynamic `await import('./cli')` they already use, rather than a static
|
|
26
|
+
// top-level import — a static import of `./src/anonSession.js` (which imports
|
|
27
|
+
// fs) would resolve before the test file's own mock variables initialize.
|
|
28
|
+
export { secretCredential, anonCredential } from './src/anonSession.js';
|
|
23
29
|
const SCREENCI_MOCK_RECORD_DOCS_URL = 'https://screenci.com/docs/reference/cli/#--mock-record';
|
|
24
30
|
const SCREENCI_RECORD_DOCS_URL = 'https://screenci.com/docs/reference/cli/#screenci-record';
|
|
25
31
|
// Records the recordId of the most recent `screenci record` upload so
|
|
@@ -218,6 +224,8 @@ const UPLOAD_ASSET_LABEL = {
|
|
|
218
224
|
// (e.g. via `narration({ en: { intro: { media } } })`).
|
|
219
225
|
clip: 'Narration',
|
|
220
226
|
voice: 'Voice',
|
|
227
|
+
// A custom cursor image set via `renderOptions.mouse.image`.
|
|
228
|
+
cursor: 'Cursor',
|
|
221
229
|
};
|
|
222
230
|
export function formatStudioUrl(appUrl, projectId, videoId) {
|
|
223
231
|
// The video hub resolves `?studio` to the right language page and scrolls it
|
|
@@ -346,7 +354,7 @@ async function loadUploadCandidate(screenciDir, entry, verbose) {
|
|
|
346
354
|
preparedUploadAssets,
|
|
347
355
|
};
|
|
348
356
|
}
|
|
349
|
-
async function uploadRecordingCandidate(candidate, screenciDir, projectName, apiUrl,
|
|
357
|
+
async function uploadRecordingCandidate(candidate, screenciDir, projectName, apiUrl, credential, verbose, uploadAbort, progressReporter, progressIndex, recordId, expectedScreenshotCount) {
|
|
350
358
|
const { entry, videoName, data: rawData, preparedUploadAssets } = candidate;
|
|
351
359
|
let projectId = null;
|
|
352
360
|
let videoId = null;
|
|
@@ -377,7 +385,7 @@ async function uploadRecordingCandidate(candidate, screenciDir, projectName, api
|
|
|
377
385
|
// for overlays, no hash. Recover their identity from a previous upload of
|
|
378
386
|
// this video before starting, so the recording data references them by hash
|
|
379
387
|
// and the backend existence check confirms they are still stored.
|
|
380
|
-
const unresolved = await resolveMissingUploadAssets(preparedUploadAssets, projectName, videoName, apiUrl,
|
|
388
|
+
const unresolved = await resolveMissingUploadAssets(preparedUploadAssets, projectName, videoName, apiUrl, credential, uploadAbort.signal, progressReporter);
|
|
381
389
|
if (unresolved.length > 0) {
|
|
382
390
|
progressReporter.complete(progressIndex, 'failure');
|
|
383
391
|
return {
|
|
@@ -395,7 +403,7 @@ async function uploadRecordingCandidate(candidate, screenciDir, projectName, api
|
|
|
395
403
|
method: 'POST',
|
|
396
404
|
headers: {
|
|
397
405
|
'Content-Type': 'application/json',
|
|
398
|
-
|
|
406
|
+
[credential.header]: credential.value,
|
|
399
407
|
},
|
|
400
408
|
body: JSON.stringify({
|
|
401
409
|
projectName,
|
|
@@ -438,7 +446,7 @@ async function uploadRecordingCandidate(candidate, screenciDir, projectName, api
|
|
|
438
446
|
videoId: null,
|
|
439
447
|
hadFailure: true,
|
|
440
448
|
videoName,
|
|
441
|
-
failureMessage: formatUploadStartFailureMessage(videoName, startResponse.status, text,
|
|
449
|
+
failureMessage: formatUploadStartFailureMessage(videoName, startResponse.status, text, credential.value),
|
|
442
450
|
recordId,
|
|
443
451
|
};
|
|
444
452
|
}
|
|
@@ -463,7 +471,7 @@ async function uploadRecordingCandidate(candidate, screenciDir, projectName, api
|
|
|
463
471
|
logger.info(`recordingId=${recordingId} projectId=${projectId}`);
|
|
464
472
|
logger.info(`assets=${preparedUploadAssets.length} recordingHash=${recordingHash ?? 'none'}`);
|
|
465
473
|
}
|
|
466
|
-
await uploadAssets(preparedUploadAssets, apiUrl,
|
|
474
|
+
await uploadAssets(preparedUploadAssets, apiUrl, credential, recordingId, uploadAbort.signal, uploadAbort.throwIfAborted, progressReporter);
|
|
467
475
|
uploadAbort.throwIfAborted();
|
|
468
476
|
const fileStat = await stat(recordingPath);
|
|
469
477
|
if (verbose) {
|
|
@@ -481,7 +489,7 @@ async function uploadRecordingCandidate(candidate, screenciDir, projectName, api
|
|
|
481
489
|
headers: {
|
|
482
490
|
'Content-Type': recordingContentType,
|
|
483
491
|
'Content-Length': String(fileStat.size),
|
|
484
|
-
|
|
492
|
+
[credential.header]: credential.value,
|
|
485
493
|
},
|
|
486
494
|
body: stream,
|
|
487
495
|
signal: uploadAbort.signal,
|
|
@@ -501,7 +509,7 @@ async function uploadRecordingCandidate(candidate, screenciDir, projectName, api
|
|
|
501
509
|
videoId,
|
|
502
510
|
hadFailure: true,
|
|
503
511
|
videoName,
|
|
504
|
-
failureMessage: `Failed to upload recording for "${videoName}": ${recordingResponse.status} ${extractBackendError(text)}${hint401(recordingResponse.status,
|
|
512
|
+
failureMessage: `Failed to upload recording for "${videoName}": ${recordingResponse.status} ${extractBackendError(text)}${hint401(recordingResponse.status, credential.value)}`,
|
|
505
513
|
recordId,
|
|
506
514
|
...(plan !== null && { plan }),
|
|
507
515
|
};
|
|
@@ -1095,6 +1103,37 @@ export async function collectUploadAssets(data, configDir) {
|
|
|
1095
1103
|
for (const asset of await prepareCustomVoiceAssets(data, configDir)) {
|
|
1096
1104
|
assets.set(`path:${asset.path}`, asset);
|
|
1097
1105
|
}
|
|
1106
|
+
// Custom cursor image (`renderOptions.mouse.image`). Unlike overlays it is not
|
|
1107
|
+
// referenced by a timeline event, so it is collected here. It is a local path
|
|
1108
|
+
// only before upload; a `{ assetPath, fileHash }` value has already been
|
|
1109
|
+
// uploaded (e.g. re-running the CLI on annotated data) and needs no work.
|
|
1110
|
+
const cursorImage = data.renderOptions?.mouse?.image;
|
|
1111
|
+
if (typeof cursorImage === 'string' && !assets.has(`path:${cursorImage}`)) {
|
|
1112
|
+
const resolvedFile = await readRecordingFile(cursorImage, configDir, sourceFilePath);
|
|
1113
|
+
if (resolvedFile === null) {
|
|
1114
|
+
// The local file is gone (e.g. gitignored on CI). Reference it by path so
|
|
1115
|
+
// its identity can be recovered from a previous upload of this video.
|
|
1116
|
+
assets.set(`path:${cursorImage}`, {
|
|
1117
|
+
kind: 'cursor',
|
|
1118
|
+
fileHash: '',
|
|
1119
|
+
path: cursorImage,
|
|
1120
|
+
size: 0,
|
|
1121
|
+
needsResolve: true,
|
|
1122
|
+
});
|
|
1123
|
+
}
|
|
1124
|
+
else {
|
|
1125
|
+
assets.set(`path:${cursorImage}`, {
|
|
1126
|
+
kind: 'cursor',
|
|
1127
|
+
fileHash: createHash('sha256')
|
|
1128
|
+
.update(resolvedFile.buffer)
|
|
1129
|
+
.digest('hex'),
|
|
1130
|
+
path: cursorImage,
|
|
1131
|
+
size: resolvedFile.buffer.byteLength,
|
|
1132
|
+
fileBuffer: resolvedFile.buffer,
|
|
1133
|
+
contentType: contentTypeForPath(resolvedFile.resolvedPath),
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1098
1137
|
return [...assets.values()];
|
|
1099
1138
|
}
|
|
1100
1139
|
export function stripVoicePath(voice, byPath) {
|
|
@@ -1122,8 +1161,26 @@ export function annotateRecordingDataWithAssetHashes(data, assets) {
|
|
|
1122
1161
|
byName.set(asset.name, asset.fileHash);
|
|
1123
1162
|
byPath.set(asset.path, asset.fileHash);
|
|
1124
1163
|
}
|
|
1164
|
+
// Rewrite a custom cursor image (`renderOptions.mouse.image`) from its local
|
|
1165
|
+
// path to `{ assetPath, fileHash }` so the renderer can resolve it by content
|
|
1166
|
+
// hash. Leave it untouched when it is already an object or has no known hash.
|
|
1167
|
+
const renderOptions = (() => {
|
|
1168
|
+
const ro = data.renderOptions;
|
|
1169
|
+
const mouse = ro?.mouse;
|
|
1170
|
+
const image = mouse?.image;
|
|
1171
|
+
if (mouse === undefined || typeof image !== 'string')
|
|
1172
|
+
return ro;
|
|
1173
|
+
const fileHash = byPath.get(image);
|
|
1174
|
+
if (fileHash === undefined)
|
|
1175
|
+
return ro;
|
|
1176
|
+
return {
|
|
1177
|
+
...ro,
|
|
1178
|
+
mouse: { ...mouse, image: { assetPath: image, fileHash } },
|
|
1179
|
+
};
|
|
1180
|
+
})();
|
|
1125
1181
|
return {
|
|
1126
1182
|
...data,
|
|
1183
|
+
renderOptions,
|
|
1127
1184
|
events: data.events.map((event) => {
|
|
1128
1185
|
if (event.type === 'assetStart') {
|
|
1129
1186
|
if ('studio' in event || 'dependency' in event)
|
|
@@ -1296,7 +1353,7 @@ export function displayAssetPath(assetPath) {
|
|
|
1296
1353
|
* Returns the assets that could not be resolved (no previous version), so the
|
|
1297
1354
|
* caller can fail the recording with actionable guidance.
|
|
1298
1355
|
*/
|
|
1299
|
-
export async function resolveMissingUploadAssets(assets, projectName, videoName, apiUrl,
|
|
1356
|
+
export async function resolveMissingUploadAssets(assets, projectName, videoName, apiUrl, credential, signal, progressReporter) {
|
|
1300
1357
|
const pending = assets.filter((asset) => asset.needsResolve === true);
|
|
1301
1358
|
if (pending.length === 0)
|
|
1302
1359
|
return [];
|
|
@@ -1309,14 +1366,14 @@ export async function resolveMissingUploadAssets(assets, projectName, videoName,
|
|
|
1309
1366
|
method: 'POST',
|
|
1310
1367
|
headers: {
|
|
1311
1368
|
'Content-Type': 'application/json',
|
|
1312
|
-
|
|
1369
|
+
[credential.header]: credential.value,
|
|
1313
1370
|
},
|
|
1314
1371
|
body: JSON.stringify({ projectName, videoName, assets: refs }),
|
|
1315
1372
|
signal,
|
|
1316
1373
|
}), signal);
|
|
1317
1374
|
if (!res.ok) {
|
|
1318
1375
|
const text = await res.text();
|
|
1319
|
-
throw new UploadAssetError(`Failed to resolve previously uploaded assets for "${videoName}": ${res.status} ${extractBackendError(text)}${hint401(res.status,
|
|
1376
|
+
throw new UploadAssetError(`Failed to resolve previously uploaded assets for "${videoName}": ${res.status} ${extractBackendError(text)}${hint401(res.status, credential.value)}`);
|
|
1320
1377
|
}
|
|
1321
1378
|
const body = (await res.json());
|
|
1322
1379
|
const resolved = Array.isArray(body.resolved) ? body.resolved : [];
|
|
@@ -1359,7 +1416,7 @@ export function formatUnresolvedAssetMessage(videoName, unresolved) {
|
|
|
1359
1416
|
'Record once with these files present so they are uploaded, or commit them so they are available here.',
|
|
1360
1417
|
].join('\n');
|
|
1361
1418
|
}
|
|
1362
|
-
async function uploadAssets(assets, apiUrl,
|
|
1419
|
+
async function uploadAssets(assets, apiUrl, credential, recordingId, signal, throwIfAborted, progressReporter) {
|
|
1363
1420
|
const logInfo = (message) => {
|
|
1364
1421
|
if (progressReporter) {
|
|
1365
1422
|
progressReporter.info(message);
|
|
@@ -1377,7 +1434,7 @@ async function uploadAssets(assets, apiUrl, secret, recordingId, signal, throwIf
|
|
|
1377
1434
|
method: 'POST',
|
|
1378
1435
|
headers: {
|
|
1379
1436
|
'Content-Type': 'application/json',
|
|
1380
|
-
|
|
1437
|
+
[credential.header]: credential.value,
|
|
1381
1438
|
},
|
|
1382
1439
|
body: JSON.stringify({
|
|
1383
1440
|
fileHash: asset.fileHash,
|
|
@@ -1390,7 +1447,7 @@ async function uploadAssets(assets, apiUrl, secret, recordingId, signal, throwIf
|
|
|
1390
1447
|
}), signal);
|
|
1391
1448
|
if (!checkRes.ok) {
|
|
1392
1449
|
const text = await checkRes.text();
|
|
1393
|
-
throw new UploadAssetError(`Failed to check asset ${displayAssetPath(asset.path)}: ${checkRes.status} ${extractBackendError(text)}${hint401(checkRes.status,
|
|
1450
|
+
throw new UploadAssetError(`Failed to check asset ${displayAssetPath(asset.path)}: ${checkRes.status} ${extractBackendError(text)}${hint401(checkRes.status, credential.value)}`);
|
|
1394
1451
|
}
|
|
1395
1452
|
const checkBody = (await checkRes.json());
|
|
1396
1453
|
if (checkBody.exists) {
|
|
@@ -1420,7 +1477,7 @@ async function uploadAssets(assets, apiUrl, secret, recordingId, signal, throwIf
|
|
|
1420
1477
|
headers: {
|
|
1421
1478
|
'Content-Type': contentType,
|
|
1422
1479
|
'Content-Length': String(fileBuffer.byteLength),
|
|
1423
|
-
|
|
1480
|
+
[credential.header]: credential.value,
|
|
1424
1481
|
'X-ScreenCI-File-Hash': asset.fileHash,
|
|
1425
1482
|
'X-ScreenCI-Asset-Size': String(asset.size),
|
|
1426
1483
|
'X-ScreenCI-Asset-Path': encodeURIComponent(asset.path),
|
|
@@ -1434,7 +1491,7 @@ async function uploadAssets(assets, apiUrl, secret, recordingId, signal, throwIf
|
|
|
1434
1491
|
logInfo(`${pc.green('✔')} ${label} already exists: ${displayAssetPath(asset.path)}`);
|
|
1435
1492
|
}
|
|
1436
1493
|
else {
|
|
1437
|
-
throw new UploadAssetError(`Failed to upload asset ${displayAssetPath(asset.path)}: ${res.status} ${extractBackendError(text)}${hint401(res.status,
|
|
1494
|
+
throw new UploadAssetError(`Failed to upload asset ${displayAssetPath(asset.path)}: ${res.status} ${extractBackendError(text)}${hint401(res.status, credential.value)}`);
|
|
1438
1495
|
}
|
|
1439
1496
|
}
|
|
1440
1497
|
else {
|
|
@@ -1452,7 +1509,7 @@ async function uploadAssets(assets, apiUrl, secret, recordingId, signal, throwIf
|
|
|
1452
1509
|
}
|
|
1453
1510
|
}
|
|
1454
1511
|
}
|
|
1455
|
-
export async function uploadRecordings(screenciDir, projectName, apiUrl,
|
|
1512
|
+
export async function uploadRecordings(screenciDir, projectName, apiUrl, credential, specificEntry, verbose = false) {
|
|
1456
1513
|
const uploadAbort = createUploadAbortController('upload');
|
|
1457
1514
|
const recordId = randomUUID();
|
|
1458
1515
|
let entries;
|
|
@@ -1500,7 +1557,7 @@ export async function uploadRecordings(screenciDir, projectName, apiUrl, secret,
|
|
|
1500
1557
|
// waits for all of them to land before dispatching the batch, so it needs
|
|
1501
1558
|
// to know how many to expect.
|
|
1502
1559
|
const screenshotCount = candidates.filter((candidate) => candidate.data.output === 'screenshot').length;
|
|
1503
|
-
const results = await Promise.all(candidates.map(async (candidate, index) => await uploadRecordingCandidate(candidate, screenciDir, projectName, apiUrl,
|
|
1560
|
+
const results = await Promise.all(candidates.map(async (candidate, index) => await uploadRecordingCandidate(candidate, screenciDir, projectName, apiUrl, credential, verbose, uploadAbort, progressReporter, index, recordId, screenshotCount)));
|
|
1504
1561
|
firstProjectId =
|
|
1505
1562
|
results.find((result) => result.projectId !== null)?.projectId ?? null;
|
|
1506
1563
|
const resolvedPlan = results.find((result) => result.plan !== undefined)?.plan ?? null;
|
|
@@ -1761,10 +1818,10 @@ export async function requireScreenCISecret(configPath) {
|
|
|
1761
1818
|
const { resolvedConfigPath, screenciConfig } = await loadScreenCIConfigAndEnv(configPath);
|
|
1762
1819
|
const secret = process.env.SCREENCI_SECRET;
|
|
1763
1820
|
if (!secret) {
|
|
1764
|
-
//
|
|
1765
|
-
//
|
|
1821
|
+
// These commands need a real account (unlike `record`, which can upload
|
|
1822
|
+
// anonymously): guide the user to copy their secret and exit non-zero.
|
|
1766
1823
|
const envFilePath = await resolveProjectEnvFilePath(resolvedConfigPath);
|
|
1767
|
-
logger.error(`No SCREENCI_SECRET configured. Copy your secret from ${pc.cyan(getScreenCISecretsUrl())} into ${envFilePath}
|
|
1824
|
+
logger.error(`No SCREENCI_SECRET configured. Copy your secret from ${pc.cyan(getScreenCISecretsUrl())} into ${envFilePath}.`);
|
|
1768
1825
|
logScreenCISecretGuide();
|
|
1769
1826
|
process.exit(1);
|
|
1770
1827
|
}
|
|
@@ -1930,6 +1987,39 @@ async function printInfo(configPath) {
|
|
|
1930
1987
|
const data = await res.json();
|
|
1931
1988
|
process.stdout.write(`${JSON.stringify(data, null, 2)}\n`);
|
|
1932
1989
|
}
|
|
1990
|
+
/**
|
|
1991
|
+
* Resolves the credential `record`'s upload should authenticate with. A real
|
|
1992
|
+
* SCREENCI_SECRET wins outright. Otherwise, checks the locally stored anon
|
|
1993
|
+
* trial token: `claimed` self-upgrades by writing the real secret into `.env`
|
|
1994
|
+
* and deleting the local anon state (no manual step required); `not_found`/
|
|
1995
|
+
* `expired` nags and starts a fresh trial; `pending` proceeds anonymously.
|
|
1996
|
+
*/
|
|
1997
|
+
export async function resolveUploadCredential(screenciDir, apiUrl, envFilePath, secretFromEnv) {
|
|
1998
|
+
if (secretFromEnv) {
|
|
1999
|
+
return {
|
|
2000
|
+
credential: secretCredential(secretFromEnv),
|
|
2001
|
+
usedAnonCredential: false,
|
|
2002
|
+
};
|
|
2003
|
+
}
|
|
2004
|
+
const token = await getOrCreateAnonToken(screenciDir);
|
|
2005
|
+
const status = await checkAnonSessionStatus(token, { backendUrl: apiUrl });
|
|
2006
|
+
if (status.status === 'claimed') {
|
|
2007
|
+
await persistScreenCISecret(envFilePath, status.secret);
|
|
2008
|
+
await deleteAnonSessionFile(screenciDir);
|
|
2009
|
+
logger.info('Linked to your ScreenCI account. Future uploads use your API key.');
|
|
2010
|
+
return {
|
|
2011
|
+
credential: secretCredential(status.secret),
|
|
2012
|
+
usedAnonCredential: false,
|
|
2013
|
+
};
|
|
2014
|
+
}
|
|
2015
|
+
if (status.status === 'not_found' || status.status === 'expired') {
|
|
2016
|
+
logger.info('Your previous trial expired or was already claimed elsewhere. Starting a new one.');
|
|
2017
|
+
await deleteAnonSessionFile(screenciDir);
|
|
2018
|
+
const freshToken = await getOrCreateAnonToken(screenciDir);
|
|
2019
|
+
return { credential: anonCredential(freshToken), usedAnonCredential: true };
|
|
2020
|
+
}
|
|
2021
|
+
return { credential: anonCredential(token), usedAnonCredential: true };
|
|
2022
|
+
}
|
|
1933
2023
|
// Uploads the recordings already written under `.screenci` for the resolved
|
|
1934
2024
|
// config. Shared by `record` (after a Playwright run) and `retry` (which
|
|
1935
2025
|
// re-sends the existing recordings without re-running Playwright). A non-null
|
|
@@ -1950,17 +2040,17 @@ async function uploadRecordedVideosForConfig(configPath, playwrightFailure, verb
|
|
|
1950
2040
|
: resolve(dirname(resolvedConfigPath), '.env'), true);
|
|
1951
2041
|
const apiUrl = getDevBackendUrl();
|
|
1952
2042
|
const appUrl = getDevFrontendUrl();
|
|
1953
|
-
const secret = process.env.SCREENCI_SECRET;
|
|
1954
2043
|
const uploadPolicy = resolveRecordUploadPolicy(screenciConfig);
|
|
1955
2044
|
const configDir = dirname(resolvedConfigPath);
|
|
1956
2045
|
const screenciDir = resolve(configDir, '.screenci');
|
|
2046
|
+
const envFilePath = screenciConfig.envFile
|
|
2047
|
+
? resolve(configDir, screenciConfig.envFile)
|
|
2048
|
+
: resolve(configDir, '.env');
|
|
1957
2049
|
const completedRecordingCount = await countCompletedRecordings(screenciDir);
|
|
2050
|
+
const { credential, usedAnonCredential } = await resolveUploadCredential(screenciDir, apiUrl, envFilePath, process.env.SCREENCI_SECRET);
|
|
1958
2051
|
if (playwrightFailure !== null && completedRecordingCount === 0) {
|
|
1959
2052
|
logger.info('All recordings failed.');
|
|
1960
2053
|
}
|
|
1961
|
-
else if (!secret) {
|
|
1962
|
-
logger.info(`No SCREENCI_SECRET configured for uploads. Copy your secret from ${getScreenCISecretsUrl()} into the project env file, then rerun ${getSuggestedScreenciCommand('record')}.`);
|
|
1963
|
-
}
|
|
1964
2054
|
else if (playwrightFailure !== null &&
|
|
1965
2055
|
uploadPolicy === 'all-or-nothing') {
|
|
1966
2056
|
logger.info('Some recordings failed, skipping upload because record.upload is "all-or-nothing".');
|
|
@@ -1981,7 +2071,7 @@ async function uploadRecordedVideosForConfig(configPath, playwrightFailure, verb
|
|
|
1981
2071
|
plan: null,
|
|
1982
2072
|
};
|
|
1983
2073
|
try {
|
|
1984
|
-
uploadResult = await uploadRecordings(screenciDir, screenciConfig.projectName, apiUrl,
|
|
2074
|
+
uploadResult = await uploadRecordings(screenciDir, screenciConfig.projectName, apiUrl, credential, undefined, verbose);
|
|
1985
2075
|
}
|
|
1986
2076
|
catch (err) {
|
|
1987
2077
|
if (isUploadCancelledError(err)) {
|
|
@@ -2024,6 +2114,9 @@ async function uploadRecordedVideosForConfig(configPath, playwrightFailure, verb
|
|
|
2024
2114
|
: 'Recording finished, rendering in progress. Results available at:');
|
|
2025
2115
|
logger.info(pc.cyan(projectUrl));
|
|
2026
2116
|
}
|
|
2117
|
+
if (usedAnonCredential && (recordId !== null || projectId !== null)) {
|
|
2118
|
+
logger.info('Recorded without an account. Open the link above to view it, and sign up to keep it (by continuing you agree to the Terms).');
|
|
2119
|
+
}
|
|
2027
2120
|
if (notices.length > 0) {
|
|
2028
2121
|
logger.info('');
|
|
2029
2122
|
for (const notice of notices) {
|
|
@@ -2194,20 +2287,22 @@ export async function main() {
|
|
|
2194
2287
|
await deleteVideoCommand(id, options['yes'] ?? false, options['config']);
|
|
2195
2288
|
});
|
|
2196
2289
|
// init command
|
|
2197
|
-
program
|
|
2290
|
+
const initCommand = program
|
|
2198
2291
|
.command('init [name]')
|
|
2199
2292
|
.description('Initialize a new screenci project')
|
|
2200
2293
|
.option('--agent <name>', 'target agent for skills install, e.g. opencode. Supported agents: https://github.com/vercel-labs/skills#supported-agents')
|
|
2201
2294
|
.option('--package-manager <manager>', `package manager to use: npm, pnpm, or yarn 2+ (default: ${defaultPackageManager})`)
|
|
2202
2295
|
.option('-y, --yes', 'accept init defaults')
|
|
2203
|
-
.option('-v, --verbose', 'verbose output')
|
|
2204
|
-
|
|
2296
|
+
.option('-v, --verbose', 'verbose output');
|
|
2297
|
+
registerInitToggleOptions(initCommand);
|
|
2298
|
+
initCommand.action(async (name, options) => {
|
|
2205
2299
|
const agent = options['agent'];
|
|
2206
2300
|
await runInit(name, {
|
|
2207
2301
|
verbose: options['verbose'] ?? false,
|
|
2208
2302
|
yes: options['yes'] ?? false,
|
|
2209
2303
|
packageManager: parsePackageManager(options['packageManager'], process.env['SCREENCI_INIT_CWD'] ?? process.cwd()),
|
|
2210
2304
|
...(agent !== undefined ? { agent } : {}),
|
|
2305
|
+
...initToggleOptionsFromCommander(options),
|
|
2211
2306
|
});
|
|
2212
2307
|
});
|
|
2213
2308
|
try {
|
|
@@ -2353,6 +2448,11 @@ function validateArgs(args) {
|
|
|
2353
2448
|
* code seeds, and a recording is never blocked by this fetch.
|
|
2354
2449
|
*/
|
|
2355
2450
|
async function fetchTextOverridesEnv(configPath, languages, verbose) {
|
|
2451
|
+
// Studio config only exists for a real (non-anonymous) org, and record must
|
|
2452
|
+
// still work without an account, so skip the fetch entirely rather than
|
|
2453
|
+
// calling requireScreenCISecret, which would hard-exit without a secret.
|
|
2454
|
+
if (!process.env.SCREENCI_SECRET)
|
|
2455
|
+
return {};
|
|
2356
2456
|
try {
|
|
2357
2457
|
const { screenciConfig, secret, apiUrl } = await requireScreenCISecret(configPath);
|
|
2358
2458
|
const params = new URLSearchParams({
|
|
@@ -2393,6 +2493,11 @@ async function fetchTextOverridesEnv(configPath, languages, verbose) {
|
|
|
2393
2493
|
* is never blocked by this fetch.
|
|
2394
2494
|
*/
|
|
2395
2495
|
async function fetchRecordOptionsEnv(configPath, verbose) {
|
|
2496
|
+
// Studio config only exists for a real (non-anonymous) org, and record must
|
|
2497
|
+
// still work without an account, so skip the fetch entirely rather than
|
|
2498
|
+
// calling requireScreenCISecret, which would hard-exit without a secret.
|
|
2499
|
+
if (!process.env.SCREENCI_SECRET)
|
|
2500
|
+
return {};
|
|
2396
2501
|
try {
|
|
2397
2502
|
const { screenciConfig, secret, apiUrl } = await requireScreenCISecret(configPath);
|
|
2398
2503
|
const params = new URLSearchParams({
|
|
@@ -2427,9 +2532,10 @@ async function run(command, additionalArgs, customConfigPath, verbose = false, m
|
|
|
2427
2532
|
if (command === 'test' || process.env.SCREENCI_RECORDING !== 'true') {
|
|
2428
2533
|
await loadEnvFileFromConfigSource(configPath, false);
|
|
2429
2534
|
}
|
|
2430
|
-
// Only validate args for record command
|
|
2535
|
+
// Only validate args for record command. No secret is required here: record
|
|
2536
|
+
// can upload anonymously (see resolveUploadCredential), so this must not
|
|
2537
|
+
// hard-exit the way requireScreenCISecret does for account-only commands.
|
|
2431
2538
|
if (command === 'record') {
|
|
2432
|
-
await requireScreenCISecret(configPath);
|
|
2433
2539
|
validateArgs(additionalArgs);
|
|
2434
2540
|
const screenciDir = resolve(dirname(configPath), '.screenci');
|
|
2435
2541
|
clearRecordingDirectories(screenciDir);
|