@vosjs/render-core 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/record/index.d.ts +169 -97
- package/dist/record/index.js +189 -47
- package/dist/record/index.js.map +1 -1
- package/package.json +2 -2
package/dist/record/index.d.ts
CHANGED
|
@@ -416,6 +416,156 @@ declare function deadWarns(r: DeadReport): boolean;
|
|
|
416
416
|
/** The dead time in words, naming the steps and what to cut. */
|
|
417
417
|
declare function deadLine(r: DeadReport): string;
|
|
418
418
|
|
|
419
|
+
/**
|
|
420
|
+
* `setup`: the steps that run BEFORE the camera rolls. A sign-in form, a
|
|
421
|
+
* cookie banner, the "choose your editor" modal, an onboarding tour: things
|
|
422
|
+
* a take must get past and must not show. They run after the first
|
|
423
|
+
* navigation and before `Page.startScreencast`, with no cursor synthesis,
|
|
424
|
+
* no pace accounting and nothing written to `meta.steps`. Then the recorder
|
|
425
|
+
* navigates to `url` again and the take begins where the setup left it.
|
|
426
|
+
*
|
|
427
|
+
* A `type` step's `text` may be `{ "env": "DEMO_PASSWORD" }`: resolved at run
|
|
428
|
+
* time from the `env` the caller hands in (the shell's for the CLI, the
|
|
429
|
+
* job's own map for a hosted take), never logged, never stored. The
|
|
430
|
+
* guarantee is narrow and real: the secret is never in a file that travels
|
|
431
|
+
* (`actions.json` is committed and pushed with the take; `meta.json` is read
|
|
432
|
+
* by the digest) and never in the footage.
|
|
433
|
+
*
|
|
434
|
+
* `teardown`: the same steps, run AFTER the take, off camera too: what the
|
|
435
|
+
* take created in the product (an API key, a project, an invite) removed
|
|
436
|
+
* again, so a rehearsal or a re-record leaves the account as it found it.
|
|
437
|
+
* They run once the screencast has stopped, on the take's own page and
|
|
438
|
+
* context (a session's cookies apply), and also when the take stops early
|
|
439
|
+
* or throws once its first step has run. A teardown that fails never fails
|
|
440
|
+
* the take: the footage is fine, and what is left behind is reported.
|
|
441
|
+
*/
|
|
442
|
+
|
|
443
|
+
type SetupText = string | {
|
|
444
|
+
env: string;
|
|
445
|
+
};
|
|
446
|
+
type SetupStep = ({
|
|
447
|
+
do: 'wait';
|
|
448
|
+
ms: number;
|
|
449
|
+
} | {
|
|
450
|
+
do: 'click';
|
|
451
|
+
selector: string;
|
|
452
|
+
ms?: number;
|
|
453
|
+
} | {
|
|
454
|
+
do: 'type';
|
|
455
|
+
selector: string;
|
|
456
|
+
text: SetupText;
|
|
457
|
+
ms?: number;
|
|
458
|
+
} | {
|
|
459
|
+
do: 'press';
|
|
460
|
+
key: string;
|
|
461
|
+
ms?: number;
|
|
462
|
+
} | {
|
|
463
|
+
do: 'goto';
|
|
464
|
+
url: string;
|
|
465
|
+
}) & {
|
|
466
|
+
id?: string;
|
|
467
|
+
};
|
|
468
|
+
/** Which list a step belongs to, as its messages and log lines name it. */
|
|
469
|
+
type StepPhase = 'setup' | 'teardown';
|
|
470
|
+
/**
|
|
471
|
+
* Why a page address is not one the recorder opens, or null. A script's
|
|
472
|
+
* `url` and a setup's `goto` open a browser, so only http and https are
|
|
473
|
+
* taken: a `file:` address would read this machine's disk into a take
|
|
474
|
+
* and a `javascript:` one would run in the page.
|
|
475
|
+
*/
|
|
476
|
+
declare function urlSchemeProblem(url: string): string | null;
|
|
477
|
+
/**
|
|
478
|
+
* Structural validation, mirrored on `validateActions` for `steps`. The
|
|
479
|
+
* same verbs and rules hold for `teardown`; `phase` names the list.
|
|
480
|
+
*/
|
|
481
|
+
declare function validateSetup(value: unknown, phase?: StepPhase): string[];
|
|
482
|
+
/** The names a setup reads from its env, so a host can say what it must supply. */
|
|
483
|
+
declare function setupEnvNames(steps: SetupStep[] | undefined): string[];
|
|
484
|
+
/**
|
|
485
|
+
* Every name a script reads from its env, its setup's and its teardown's,
|
|
486
|
+
* each once: what a host must supply before the take begins.
|
|
487
|
+
*/
|
|
488
|
+
declare function scriptEnvNames(script: {
|
|
489
|
+
setup?: SetupStep[];
|
|
490
|
+
teardown?: SetupStep[];
|
|
491
|
+
}): string[];
|
|
492
|
+
/** A `{ env }` text resolved, or thrown in words. The value is never returned to a log. */
|
|
493
|
+
declare function resolveSetupText(t: SetupText, env: Record<string, string | undefined>, phase?: StepPhase): {
|
|
494
|
+
value: string;
|
|
495
|
+
secret: boolean;
|
|
496
|
+
};
|
|
497
|
+
/**
|
|
498
|
+
* The first `{ env }` a teardown reads that `env` does not hold, in words,
|
|
499
|
+
* or null. Asked BEFORE the take: a take that creates something its
|
|
500
|
+
* teardown cannot then remove is the one case teardown exists for.
|
|
501
|
+
*/
|
|
502
|
+
declare function teardownEnvProblem(steps: SetupStep[] | undefined, env: Record<string, string | undefined>): string | null;
|
|
503
|
+
interface SetupResult {
|
|
504
|
+
ran: number;
|
|
505
|
+
/** what the log may say about each step: the verb and the selector, never a typed value */
|
|
506
|
+
lines: string[];
|
|
507
|
+
/**
|
|
508
|
+
* The step that failed: for a setup, a selector that never appeared (the
|
|
509
|
+
* take does not start); for a teardown, the step it stopped at. `error`
|
|
510
|
+
* is the driver's reason, one line, never a typed value.
|
|
511
|
+
*/
|
|
512
|
+
failed?: {
|
|
513
|
+
step: number;
|
|
514
|
+
do: string;
|
|
515
|
+
selector?: string;
|
|
516
|
+
error?: string;
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
/** How long a whole teardown may take before it is stopped and reported. */
|
|
520
|
+
declare const TEARDOWN_BOUND_MS = 60000;
|
|
521
|
+
/**
|
|
522
|
+
* What a teardown did, on the take's result: how many of its steps ran,
|
|
523
|
+
* and where it stopped. Never a typed value.
|
|
524
|
+
*/
|
|
525
|
+
interface TeardownReport {
|
|
526
|
+
ran: number;
|
|
527
|
+
of: number;
|
|
528
|
+
failed?: {
|
|
529
|
+
step: number;
|
|
530
|
+
do: string;
|
|
531
|
+
selector?: string;
|
|
532
|
+
error: string;
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
/** The teardown in one line, for the end of a take or a rehearsal. */
|
|
536
|
+
declare function teardownLine(report: TeardownReport): string;
|
|
537
|
+
/**
|
|
538
|
+
* Run the setup (or the teardown) on a page, with plain Playwright actions:
|
|
539
|
+
* no humanized pointer, no cursor events, no frames. A selector that never
|
|
540
|
+
* appears fails the steps there, because a take that begins at a
|
|
541
|
+
* half-finished sign-in is the wall by another name. With `boundMs`, the
|
|
542
|
+
* steps together run no longer than that: the step running when it passes
|
|
543
|
+
* fails, and the call it left pending is abandoned.
|
|
544
|
+
*/
|
|
545
|
+
declare function runSetup(page: RecorderPage, steps: SetupStep[], opts: {
|
|
546
|
+
sleep: (ms: number) => Promise<void>;
|
|
547
|
+
/** Absent means no env at all: a `{ env }` text then fails in words. */
|
|
548
|
+
env?: Record<string, string | undefined>;
|
|
549
|
+
/** How a `goto` opens its page and waits for it (the take's ready mode); absent, network idle. */
|
|
550
|
+
goto?: (url: string) => Promise<unknown>;
|
|
551
|
+
/** Which list these are, for the log and the messages (default `setup`). */
|
|
552
|
+
phase?: StepPhase;
|
|
553
|
+
/** The longest the steps may take together, in ms. */
|
|
554
|
+
boundMs?: number;
|
|
555
|
+
now?: () => number;
|
|
556
|
+
}): Promise<SetupResult>;
|
|
557
|
+
/** A `{ env }` the shell did not provide: a usage error, not a page's. */
|
|
558
|
+
declare class SetupEnvError extends Error {
|
|
559
|
+
constructor(message: string);
|
|
560
|
+
}
|
|
561
|
+
/** A setup step whose selector never appeared: the take does not start. */
|
|
562
|
+
declare class SetupError extends Error {
|
|
563
|
+
failed: NonNullable<SetupResult['failed']>;
|
|
564
|
+
constructor(failed: NonNullable<SetupResult['failed']>);
|
|
565
|
+
}
|
|
566
|
+
/** `--header name=value`, repeatable: extra request headers on every request. */
|
|
567
|
+
declare function parseHeaders(given: string[] | undefined): Record<string, string>;
|
|
568
|
+
|
|
419
569
|
/**
|
|
420
570
|
* The page's own console errors, as the recorder reports them. A page
|
|
421
571
|
* under recording is someone else's, and its errors are context, never the
|
|
@@ -564,99 +714,6 @@ declare function noticeLine(n: Notice): string;
|
|
|
564
714
|
*/
|
|
565
715
|
declare function noticeAdvice(notices: readonly Pick<Notice, 'kind'>[]): string;
|
|
566
716
|
|
|
567
|
-
/**
|
|
568
|
-
* `setup`: the steps that run BEFORE the camera rolls. A sign-in form, a
|
|
569
|
-
* cookie banner, the "choose your editor" modal, an onboarding tour: things
|
|
570
|
-
* a take must get past and must not show. They run after the first
|
|
571
|
-
* navigation and before `Page.startScreencast`, with no cursor synthesis,
|
|
572
|
-
* no pace accounting and nothing written to `meta.steps`. Then the recorder
|
|
573
|
-
* navigates to `url` again and the take begins where the setup left it.
|
|
574
|
-
*
|
|
575
|
-
* A `type` step's `text` may be `{ "env": "DEMO_PASSWORD" }`: resolved at run
|
|
576
|
-
* time from the `env` the caller hands in (the shell's for the CLI, the
|
|
577
|
-
* job's own map for a hosted take), never logged, never stored. The
|
|
578
|
-
* guarantee is narrow and real: the secret is never in a file that travels
|
|
579
|
-
* (`actions.json` is committed and pushed with the take; `meta.json` is read
|
|
580
|
-
* by the digest) and never in the footage.
|
|
581
|
-
*/
|
|
582
|
-
|
|
583
|
-
type SetupText = string | {
|
|
584
|
-
env: string;
|
|
585
|
-
};
|
|
586
|
-
type SetupStep = ({
|
|
587
|
-
do: 'wait';
|
|
588
|
-
ms: number;
|
|
589
|
-
} | {
|
|
590
|
-
do: 'click';
|
|
591
|
-
selector: string;
|
|
592
|
-
ms?: number;
|
|
593
|
-
} | {
|
|
594
|
-
do: 'type';
|
|
595
|
-
selector: string;
|
|
596
|
-
text: SetupText;
|
|
597
|
-
ms?: number;
|
|
598
|
-
} | {
|
|
599
|
-
do: 'press';
|
|
600
|
-
key: string;
|
|
601
|
-
ms?: number;
|
|
602
|
-
} | {
|
|
603
|
-
do: 'goto';
|
|
604
|
-
url: string;
|
|
605
|
-
}) & {
|
|
606
|
-
id?: string;
|
|
607
|
-
};
|
|
608
|
-
/**
|
|
609
|
-
* Why a page address is not one the recorder opens, or null. A script's
|
|
610
|
-
* `url` and a setup's `goto` open a browser, so only http and https are
|
|
611
|
-
* taken: a `file:` address would read this machine's disk into a take
|
|
612
|
-
* and a `javascript:` one would run in the page.
|
|
613
|
-
*/
|
|
614
|
-
declare function urlSchemeProblem(url: string): string | null;
|
|
615
|
-
/** Structural validation, mirrored on `validateActions` for `steps`. */
|
|
616
|
-
declare function validateSetup(value: unknown): string[];
|
|
617
|
-
/** The names a setup reads from its env, so a host can say what it must supply. */
|
|
618
|
-
declare function setupEnvNames(steps: SetupStep[] | undefined): string[];
|
|
619
|
-
/** A `{ env }` text resolved, or thrown in words. The value is never returned to a log. */
|
|
620
|
-
declare function resolveSetupText(t: SetupText, env: Record<string, string | undefined>): {
|
|
621
|
-
value: string;
|
|
622
|
-
secret: boolean;
|
|
623
|
-
};
|
|
624
|
-
interface SetupResult {
|
|
625
|
-
ran: number;
|
|
626
|
-
/** what the log may say about each step: the verb and the selector, never a typed value */
|
|
627
|
-
lines: string[];
|
|
628
|
-
/** a selector that never appeared; the take does not start */
|
|
629
|
-
failed?: {
|
|
630
|
-
step: number;
|
|
631
|
-
do: string;
|
|
632
|
-
selector?: string;
|
|
633
|
-
};
|
|
634
|
-
}
|
|
635
|
-
/**
|
|
636
|
-
* Run the setup on a page, with plain Playwright actions: no humanized
|
|
637
|
-
* pointer, no cursor events, no frames. A selector that never appears
|
|
638
|
-
* fails the setup, because a take that begins at a half-finished sign-in is
|
|
639
|
-
* the wall by another name.
|
|
640
|
-
*/
|
|
641
|
-
declare function runSetup(page: RecorderPage, steps: SetupStep[], opts: {
|
|
642
|
-
sleep: (ms: number) => Promise<void>;
|
|
643
|
-
/** Absent means no env at all: a `{ env }` text then fails in words. */
|
|
644
|
-
env?: Record<string, string | undefined>;
|
|
645
|
-
/** How a `goto` opens its page and waits for it (the take's ready mode); absent, network idle. */
|
|
646
|
-
goto?: (url: string) => Promise<unknown>;
|
|
647
|
-
}): Promise<SetupResult>;
|
|
648
|
-
/** A `{ env }` the shell did not provide: a usage error, not a page's. */
|
|
649
|
-
declare class SetupEnvError extends Error {
|
|
650
|
-
constructor(message: string);
|
|
651
|
-
}
|
|
652
|
-
/** A setup step whose selector never appeared: the take does not start. */
|
|
653
|
-
declare class SetupError extends Error {
|
|
654
|
-
failed: NonNullable<SetupResult['failed']>;
|
|
655
|
-
constructor(failed: NonNullable<SetupResult['failed']>);
|
|
656
|
-
}
|
|
657
|
-
/** `--header name=value`, repeatable: extra request headers on every request. */
|
|
658
|
-
declare function parseHeaders(given: string[] | undefined): Record<string, string>;
|
|
659
|
-
|
|
660
717
|
/**
|
|
661
718
|
* When a page counts as ready for the take to begin. The default is the
|
|
662
719
|
* DOM: after `load`, the page must make no DOM change for a beat (counted
|
|
@@ -1176,6 +1233,15 @@ interface ActionsFile {
|
|
|
1176
1233
|
* or stored.
|
|
1177
1234
|
*/
|
|
1178
1235
|
setup?: SetupStep[];
|
|
1236
|
+
/**
|
|
1237
|
+
* Run AFTER the take, off camera, with the same verbs as `setup`: what
|
|
1238
|
+
* the take created in the product (an API key, a project, an invite)
|
|
1239
|
+
* removed again. They run once the screencast has stopped, on the take's
|
|
1240
|
+
* own page, after a rehearsal too, and when the take stops early or
|
|
1241
|
+
* throws after its first step. A failing teardown never fails the take;
|
|
1242
|
+
* it stops at the step that failed and is reported.
|
|
1243
|
+
*/
|
|
1244
|
+
teardown?: SetupStep[];
|
|
1179
1245
|
/**
|
|
1180
1246
|
* When the first page counts as ready for step 1: `dom` (default),
|
|
1181
1247
|
* once its DOM has made no change for a beat after `load`, at most 8 s;
|
|
@@ -1683,6 +1749,11 @@ interface RecordResult {
|
|
|
1683
1749
|
actions: ActionsFile;
|
|
1684
1750
|
/** What each peer did, off camera (also `meta.peers`); [] with none. */
|
|
1685
1751
|
peers: PeerRun[];
|
|
1752
|
+
/**
|
|
1753
|
+
* What the script's `teardown` did after the take, off camera; null when
|
|
1754
|
+
* it has none. A failure here is reported and never fails the take.
|
|
1755
|
+
*/
|
|
1756
|
+
teardown: TeardownReport | null;
|
|
1686
1757
|
}
|
|
1687
1758
|
interface RecordOpts {
|
|
1688
1759
|
/** Stop the capture at this many seconds (the hosted cap). */
|
|
@@ -1721,9 +1792,10 @@ interface RecordOpts {
|
|
|
1721
1792
|
*/
|
|
1722
1793
|
context?: RecorderContext;
|
|
1723
1794
|
/**
|
|
1724
|
-
* The environment a setup's `{ env }` texts resolve
|
|
1725
|
-
* for the CLI, the job's own map on the fleet. Absent
|
|
1726
|
-
* all, so a setup that reads one fails in words
|
|
1795
|
+
* The environment a setup's and a teardown's `{ env }` texts resolve
|
|
1796
|
+
* from: the shell's for the CLI, the job's own map on the fleet. Absent
|
|
1797
|
+
* means no env at all, so a setup that reads one fails in words, and a
|
|
1798
|
+
* teardown that reads one refuses the take before it begins.
|
|
1727
1799
|
*/
|
|
1728
1800
|
env?: Record<string, string | undefined>;
|
|
1729
1801
|
/** The platform the take is recorded on, for `meta.platform`. */
|
|
@@ -2267,4 +2339,4 @@ declare function inlinePoints(path: {
|
|
|
2267
2339
|
*/
|
|
2268
2340
|
declare function inlineScript<T extends Pick<ActionsFile, 'steps'>>(actions: T, files: LoadedFiles): T;
|
|
2269
2341
|
|
|
2270
|
-
export { APP_QUIET_MS, APP_SETTLE_CAP_MS, AWAIT_HEARTBEAT_MS, AWAIT_POLL_S, AWAIT_TIMEOUT_S, type ActionStep, type ActionsFile, type Arrival, type AwaitUntil, CHANGE_AGE_PROBE, CLICK_BUTTONS, type ClickButton, type ConditionState, DEAD_SHARE_WARN_PCT, DEAD_STRETCH_WARN_MS, DEFAULT_READY, DOM_CHANGE_PROBE, DOM_QUIET_MS, DOM_SETTLE_CAP_MS, DOWNLOAD_TIMEOUT_S, DRAG_HOLD_MS, DRAG_MODIFIERS, type DeadReport, type DeadStep, type DisabledClick, type DragModifier, EXPECT_TIMEOUT_S, EXPOSURE_ADVICE, EXPOSURE_MATCHERS_SRC, EXPOSURE_PROBE, type EncodePageOptions, type Exposure, type ExposureKind, ExposureLog, type FrameRec, type FrameSink, type FreezeSpan, type FreshPlanOptions, type GuardedStep, type HeardSound, IDLE_CALL_MS, JS_SAID_MAX, JS_VALUE_MAX, KEY_ROUNDTRIP_MS, KEY_SEQUENCE_GAP_MS, type LoadedFiles, MAX_KEYS, MAX_PATH_POINTS, MAX_PEERS, MAX_PEER_STEPS, MEDIA_DRAIN_PROBE, MEDIA_WATCH_SCRIPT, MIN_SKIP_MS, MODIFIERS, MOTION_TICK_MS, MULTI_CLICK_GAP_MS, type MaskReport, type MaskRule, type MediaDrain, type MediaNote, type Modifier, NAV_TIMEOUT_MS, NETWORKIDLE_HYDRATION_MS, NOTICE_SRC, type Notice, NoticeLog, OUTPUT_MAX_BYTES, OVERRUN_MIN_MS, OVERRUN_MIN_PLANNED_MS, OVERRUN_SHARE, type OutputSink, PAGE_ERROR_LINE_CHARS, PASTE_MAX_BYTES, PASTE_READ_WINDOW_MS, PASTE_TYPES, PASTE_UNREAD_MS, PATH_DEFAULT_MS, PEER_VALUE_MAX, PEER_VERBS, POINTER_MAX_MS, POINTER_MIN_MS, POINTER_MS_PER_PX, PRESS_HOLD_MS, PRESS_KEYS, PRESS_LEAD_MS, type PaceReport, type PageError, PageErrorLog, PageSoundLog, type PasteItem, type PathPoint, type PathSample, type Peer, type PeerRun, type PeerStep, type PollResult, type PressKey, QUIET_MS, READY_MODES, READY_SETTLE_CAP_MS, READ_BEAT_MS, RESPONSE_MS, type ReadyMode, type ReadyOpts, type ReadyReport, type RecordOpts, RecordRefusedError, type RecordResult, type RecorderApiResponse, type RecorderBrowser, type RecorderButtonOpts, type RecorderCdp, type RecorderClock, type RecorderConsoleMessage, type RecorderContext, type RecorderDownload, type RecorderLocator, type RecorderLocatorRoot, type RecorderMediaElement, type RecorderPage, type RecorderRect, type RecorderResponse, type RecorderRoute, SCROLL_MAX_MS, SCROLL_MIN_MS, SCROLL_MS_PER_PX, SCROLL_SETTLE_MS, SETTLE_CAP_MS, SETTLE_MS, SETTLE_VERBS, STEP_GUARDS, STEP_MARGIN_MS, type ScannedNotice, type ScriptFiles, SetupEnvError, SetupError, type SetupResult, type SetupStep, type SetupText, type SkippedStep, type SoundSink, StepBound, type StepGuard, type StepOverrun, type StepPace, StepSignals, type StoppedTake, type SurfaceFit, type SurfaceSize, TRAILING_HOLD_MS, TakeClock, WAIT_MARGIN_MS, WALL_PROBE, type WaitCondition, WallError, type WallKind, type WallVerdict, ZOOM_MARK_VERBS, appSettled, askedMs, base64Bytes, bounded, buildEncodePage, buttonNumber, capReached, cappedLine, chordProblem, clampWait, clockMotion, clockTyping, conditionMiss, deadLine, deadTime, deadWarns, domSettled, easeInOutCubic, exposureLine, extForType, fetchInPageScript, fileNameProblem, fitSurfaceToViewport, formatDurationCap, guardVerdict, guardedSteps, hangReason, headersFor, holdLeftMs, hostedRefusals, inlinePoints, inlineScript, isIdpHost, isKeyName, isSignInPath, jpegDims, jsConditionScript, keptValue, keySequencePlanMs, loadScriptFiles, maskInitScript, modifierBits, nameFromUrl, noticeAdvice, noticeLine, outputName, overrunLine, overrunOf, paceLine, paceReport, pageErrorLine, parseHeaders, pasteTypeOf, pathDurationMs, peerValue, peersHostedRefusal, plainName, planFreshTake, pointerTravelMs, pollCondition, pressSequence, publicUrl, readAllBytes, readCondition, readMediaSource, readPath, readyLine, readyProblem, realSleep, recordTake, redirectedAway, reloadUntilReady, resolveChord, resolveModifier, resolveSetupText, runPeer, runSetup, sameJson, sayCondition, sayDuration, sayJs, sayValue, schedulePath, scriptFiles, scrollByScript, scrollTravelMs, settleMs, settleOn, settleVerdict, setupEnvNames, soundSrcForMeta, splitChord, splitSegmentsAtSkips, stageClipboardScript, stepBoundMs, syntheticPasteScript, typeOfName, untilReady, urlSchemeProblem, validateActions, validatePeers, validateSetup, visibleTextScript, wallVerdict, withFittingExport, withPageSound };
|
|
2342
|
+
export { APP_QUIET_MS, APP_SETTLE_CAP_MS, AWAIT_HEARTBEAT_MS, AWAIT_POLL_S, AWAIT_TIMEOUT_S, type ActionStep, type ActionsFile, type Arrival, type AwaitUntil, CHANGE_AGE_PROBE, CLICK_BUTTONS, type ClickButton, type ConditionState, DEAD_SHARE_WARN_PCT, DEAD_STRETCH_WARN_MS, DEFAULT_READY, DOM_CHANGE_PROBE, DOM_QUIET_MS, DOM_SETTLE_CAP_MS, DOWNLOAD_TIMEOUT_S, DRAG_HOLD_MS, DRAG_MODIFIERS, type DeadReport, type DeadStep, type DisabledClick, type DragModifier, EXPECT_TIMEOUT_S, EXPOSURE_ADVICE, EXPOSURE_MATCHERS_SRC, EXPOSURE_PROBE, type EncodePageOptions, type Exposure, type ExposureKind, ExposureLog, type FrameRec, type FrameSink, type FreezeSpan, type FreshPlanOptions, type GuardedStep, type HeardSound, IDLE_CALL_MS, JS_SAID_MAX, JS_VALUE_MAX, KEY_ROUNDTRIP_MS, KEY_SEQUENCE_GAP_MS, type LoadedFiles, MAX_KEYS, MAX_PATH_POINTS, MAX_PEERS, MAX_PEER_STEPS, MEDIA_DRAIN_PROBE, MEDIA_WATCH_SCRIPT, MIN_SKIP_MS, MODIFIERS, MOTION_TICK_MS, MULTI_CLICK_GAP_MS, type MaskReport, type MaskRule, type MediaDrain, type MediaNote, type Modifier, NAV_TIMEOUT_MS, NETWORKIDLE_HYDRATION_MS, NOTICE_SRC, type Notice, NoticeLog, OUTPUT_MAX_BYTES, OVERRUN_MIN_MS, OVERRUN_MIN_PLANNED_MS, OVERRUN_SHARE, type OutputSink, PAGE_ERROR_LINE_CHARS, PASTE_MAX_BYTES, PASTE_READ_WINDOW_MS, PASTE_TYPES, PASTE_UNREAD_MS, PATH_DEFAULT_MS, PEER_VALUE_MAX, PEER_VERBS, POINTER_MAX_MS, POINTER_MIN_MS, POINTER_MS_PER_PX, PRESS_HOLD_MS, PRESS_KEYS, PRESS_LEAD_MS, type PaceReport, type PageError, PageErrorLog, PageSoundLog, type PasteItem, type PathPoint, type PathSample, type Peer, type PeerRun, type PeerStep, type PollResult, type PressKey, QUIET_MS, READY_MODES, READY_SETTLE_CAP_MS, READ_BEAT_MS, RESPONSE_MS, type ReadyMode, type ReadyOpts, type ReadyReport, type RecordOpts, RecordRefusedError, type RecordResult, type RecorderApiResponse, type RecorderBrowser, type RecorderButtonOpts, type RecorderCdp, type RecorderClock, type RecorderConsoleMessage, type RecorderContext, type RecorderDownload, type RecorderLocator, type RecorderLocatorRoot, type RecorderMediaElement, type RecorderPage, type RecorderRect, type RecorderResponse, type RecorderRoute, SCROLL_MAX_MS, SCROLL_MIN_MS, SCROLL_MS_PER_PX, SCROLL_SETTLE_MS, SETTLE_CAP_MS, SETTLE_MS, SETTLE_VERBS, STEP_GUARDS, STEP_MARGIN_MS, type ScannedNotice, type ScriptFiles, SetupEnvError, SetupError, type SetupResult, type SetupStep, type SetupText, type SkippedStep, type SoundSink, StepBound, type StepGuard, type StepOverrun, type StepPace, type StepPhase, StepSignals, type StoppedTake, type SurfaceFit, type SurfaceSize, TEARDOWN_BOUND_MS, TRAILING_HOLD_MS, TakeClock, type TeardownReport, WAIT_MARGIN_MS, WALL_PROBE, type WaitCondition, WallError, type WallKind, type WallVerdict, ZOOM_MARK_VERBS, appSettled, askedMs, base64Bytes, bounded, buildEncodePage, buttonNumber, capReached, cappedLine, chordProblem, clampWait, clockMotion, clockTyping, conditionMiss, deadLine, deadTime, deadWarns, domSettled, easeInOutCubic, exposureLine, extForType, fetchInPageScript, fileNameProblem, fitSurfaceToViewport, formatDurationCap, guardVerdict, guardedSteps, hangReason, headersFor, holdLeftMs, hostedRefusals, inlinePoints, inlineScript, isIdpHost, isKeyName, isSignInPath, jpegDims, jsConditionScript, keptValue, keySequencePlanMs, loadScriptFiles, maskInitScript, modifierBits, nameFromUrl, noticeAdvice, noticeLine, outputName, overrunLine, overrunOf, paceLine, paceReport, pageErrorLine, parseHeaders, pasteTypeOf, pathDurationMs, peerValue, peersHostedRefusal, plainName, planFreshTake, pointerTravelMs, pollCondition, pressSequence, publicUrl, readAllBytes, readCondition, readMediaSource, readPath, readyLine, readyProblem, realSleep, recordTake, redirectedAway, reloadUntilReady, resolveChord, resolveModifier, resolveSetupText, runPeer, runSetup, sameJson, sayCondition, sayDuration, sayJs, sayValue, schedulePath, scriptEnvNames, scriptFiles, scrollByScript, scrollTravelMs, settleMs, settleOn, settleVerdict, setupEnvNames, soundSrcForMeta, splitChord, splitSegmentsAtSkips, stageClipboardScript, stepBoundMs, syntheticPasteScript, teardownEnvProblem, teardownLine, typeOfName, untilReady, urlSchemeProblem, validateActions, validatePeers, validateSetup, visibleTextScript, wallVerdict, withFittingExport, withPageSound };
|
package/dist/record/index.js
CHANGED
|
@@ -701,11 +701,11 @@ function urlSchemeProblem(url) {
|
|
|
701
701
|
return `${url} is not an http(s) address, and the recorder opens only http and https pages`;
|
|
702
702
|
return null;
|
|
703
703
|
}
|
|
704
|
-
function validateSetup(value) {
|
|
704
|
+
function validateSetup(value, phase = "setup") {
|
|
705
705
|
const errors = [];
|
|
706
|
-
if (!Array.isArray(value)) return [
|
|
706
|
+
if (!Array.isArray(value)) return [`${phase} must be an array of steps`];
|
|
707
707
|
value.forEach((raw, i) => {
|
|
708
|
-
const at =
|
|
708
|
+
const at = `${phase}[${i}]`;
|
|
709
709
|
if (typeof raw !== "object" || raw === null) {
|
|
710
710
|
errors.push(`${at}: must be an object`);
|
|
711
711
|
return;
|
|
@@ -750,73 +750,141 @@ function setupEnvNames(steps) {
|
|
|
750
750
|
}
|
|
751
751
|
return [...out];
|
|
752
752
|
}
|
|
753
|
-
function
|
|
753
|
+
function scriptEnvNames(script) {
|
|
754
|
+
return [
|
|
755
|
+
.../* @__PURE__ */ new Set([
|
|
756
|
+
...setupEnvNames(script.setup),
|
|
757
|
+
...setupEnvNames(script.teardown)
|
|
758
|
+
])
|
|
759
|
+
];
|
|
760
|
+
}
|
|
761
|
+
function resolveSetupText(t, env, phase = "setup") {
|
|
754
762
|
if (typeof t === "string") return { value: t, secret: false };
|
|
755
763
|
const v = env[t.env];
|
|
756
764
|
if (v === void 0 || v === "")
|
|
757
765
|
throw new SetupEnvError(
|
|
758
|
-
|
|
766
|
+
`${phase}: the environment variable ${t.env} is not set, and a type step reads it. Export it in the shell that runs vos record; it is never written to a file`
|
|
759
767
|
);
|
|
760
768
|
return { value: v, secret: true };
|
|
761
769
|
}
|
|
770
|
+
function teardownEnvProblem(steps, env) {
|
|
771
|
+
for (const [i, s] of (steps ?? []).entries()) {
|
|
772
|
+
if (s.do !== "type" || typeof s.text !== "object") continue;
|
|
773
|
+
const v = env[s.text.env];
|
|
774
|
+
if (v === void 0 || v === "")
|
|
775
|
+
return `teardown[${i}] reads the environment variable ${s.text.env}, which is not set, so the take could leave behind what its teardown removes. Export it in the shell that runs vos record; it is never written to a file. Nothing was recorded.`;
|
|
776
|
+
}
|
|
777
|
+
return null;
|
|
778
|
+
}
|
|
779
|
+
var TEARDOWN_BOUND_MS = 6e4;
|
|
780
|
+
function teardownLine(report) {
|
|
781
|
+
const f = report.failed;
|
|
782
|
+
if (!f) return `teardown: ${report.ran} step(s) ran`;
|
|
783
|
+
const reason = f.error.replace(/[.\s]+$/, "");
|
|
784
|
+
return `TEARDOWN FAILED at teardown[${f.step}] (${f.do}${f.selector ? ` ${f.selector}` : ""}): ${reason}. What the take created may still be in the product; remove it by hand.`;
|
|
785
|
+
}
|
|
762
786
|
async function runSetup(page, steps, opts) {
|
|
763
787
|
const env = opts.env ?? {};
|
|
788
|
+
const phase = opts.phase ?? "setup";
|
|
789
|
+
const now = opts.now ?? Date.now;
|
|
790
|
+
const deadline = opts.boundMs === void 0 ? void 0 : now() + opts.boundMs;
|
|
764
791
|
const lines = [];
|
|
765
|
-
|
|
792
|
+
let typed = "";
|
|
793
|
+
const runStep = async (step, i) => {
|
|
766
794
|
const settle = "ms" in step && typeof step.ms === "number" ? step.ms : 150;
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
await loc.waitFor({ state: "visible", timeout: 8e3 });
|
|
788
|
-
await loc.click();
|
|
789
|
-
await opts.sleep(settle);
|
|
790
|
-
lines.push(`setup #${i} click ${step.selector}`);
|
|
791
|
-
break;
|
|
792
|
-
}
|
|
793
|
-
case "type": {
|
|
794
|
-
const loc = page.locator(step.selector).first();
|
|
795
|
-
await loc.waitFor({ state: "visible", timeout: 8e3 });
|
|
796
|
-
const { value, secret } = resolveSetupText(step.text, env);
|
|
797
|
-
await loc.fill(value);
|
|
798
|
-
await opts.sleep(settle);
|
|
799
|
-
lines.push(
|
|
800
|
-
`setup #${i} type ${secret ? `\${${step.text.env}}` : `${value.length} chars`} into ${step.selector}`
|
|
801
|
-
);
|
|
802
|
-
break;
|
|
803
|
-
}
|
|
795
|
+
switch (step.do) {
|
|
796
|
+
case "wait":
|
|
797
|
+
await opts.sleep(step.ms);
|
|
798
|
+
return `${phase} #${i} wait ${step.ms}ms`;
|
|
799
|
+
case "goto":
|
|
800
|
+
await (opts.goto ? opts.goto(step.url) : page.goto(step.url, {
|
|
801
|
+
waitUntil: "networkidle",
|
|
802
|
+
timeout: 45e3
|
|
803
|
+
})).catch(() => null);
|
|
804
|
+
return `${phase} #${i} goto ${step.url}`;
|
|
805
|
+
case "press":
|
|
806
|
+
await page.keyboard.press(step.key);
|
|
807
|
+
await opts.sleep(settle);
|
|
808
|
+
return `${phase} #${i} press ${step.key}`;
|
|
809
|
+
case "click": {
|
|
810
|
+
const loc = page.locator(step.selector).first();
|
|
811
|
+
await loc.waitFor({ state: "visible", timeout: 8e3 });
|
|
812
|
+
await loc.click();
|
|
813
|
+
await opts.sleep(settle);
|
|
814
|
+
return `${phase} #${i} click ${step.selector}`;
|
|
804
815
|
}
|
|
816
|
+
case "type": {
|
|
817
|
+
const loc = page.locator(step.selector).first();
|
|
818
|
+
await loc.waitFor({ state: "visible", timeout: 8e3 });
|
|
819
|
+
const { value, secret } = resolveSetupText(step.text, env, phase);
|
|
820
|
+
typed = value;
|
|
821
|
+
await loc.fill(value);
|
|
822
|
+
await opts.sleep(settle);
|
|
823
|
+
return `${phase} #${i} type ${secret ? `\${${step.text.env}}` : `${value.length} chars`} into ${step.selector}`;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
for (const [i, step] of steps.entries()) {
|
|
828
|
+
typed = "";
|
|
829
|
+
try {
|
|
830
|
+
lines.push(
|
|
831
|
+
await withinDeadline(
|
|
832
|
+
runStep(step, i),
|
|
833
|
+
deadline,
|
|
834
|
+
now,
|
|
835
|
+
`the ${phase} ran past its ${sayBound(opts.boundMs ?? 0)} bound`
|
|
836
|
+
)
|
|
837
|
+
);
|
|
805
838
|
} catch (e) {
|
|
806
|
-
if (e instanceof SetupEnvError) throw e;
|
|
839
|
+
if (e instanceof SetupEnvError && phase === "setup") throw e;
|
|
807
840
|
return {
|
|
808
841
|
ran: i,
|
|
809
842
|
lines,
|
|
810
843
|
failed: {
|
|
811
844
|
step: i,
|
|
812
845
|
do: step.do,
|
|
813
|
-
..."selector" in step ? { selector: step.selector } : {}
|
|
846
|
+
..."selector" in step ? { selector: step.selector } : {},
|
|
847
|
+
error: reasonWords(e, typed)
|
|
814
848
|
}
|
|
815
849
|
};
|
|
816
850
|
}
|
|
817
851
|
}
|
|
818
852
|
return { ran: steps.length, lines };
|
|
819
853
|
}
|
|
854
|
+
function sayBound(ms) {
|
|
855
|
+
return ms >= 1e3 ? `${Math.round(ms / 1e3)} s` : `${ms} ms`;
|
|
856
|
+
}
|
|
857
|
+
function withinDeadline(p, deadline, now, message) {
|
|
858
|
+
if (deadline === void 0) return p;
|
|
859
|
+
const left = deadline - now();
|
|
860
|
+
return new Promise((resolve, reject) => {
|
|
861
|
+
const timer = setTimeout(
|
|
862
|
+
() => {
|
|
863
|
+
p.catch(() => {
|
|
864
|
+
});
|
|
865
|
+
reject(new Error(message));
|
|
866
|
+
},
|
|
867
|
+
Math.max(0, left)
|
|
868
|
+
);
|
|
869
|
+
p.then(
|
|
870
|
+
(v) => {
|
|
871
|
+
clearTimeout(timer);
|
|
872
|
+
resolve(v);
|
|
873
|
+
},
|
|
874
|
+
(e) => {
|
|
875
|
+
clearTimeout(timer);
|
|
876
|
+
reject(e);
|
|
877
|
+
}
|
|
878
|
+
);
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
var ANSI_COLOUR = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
|
|
882
|
+
function reasonWords(e, typed) {
|
|
883
|
+
const raw = e instanceof Error ? e.message : String(e);
|
|
884
|
+
const line = (raw.replace(ANSI_COLOUR, "").split("\n")[0] ?? "").trim();
|
|
885
|
+
if (typed && line.includes(typed)) return "the field could not be filled";
|
|
886
|
+
return line || "the step failed";
|
|
887
|
+
}
|
|
820
888
|
var SetupEnvError = class extends Error {
|
|
821
889
|
constructor(message) {
|
|
822
890
|
super(message);
|
|
@@ -2405,6 +2473,8 @@ function validateActions(value) {
|
|
|
2405
2473
|
}
|
|
2406
2474
|
}
|
|
2407
2475
|
if (obj.setup !== void 0) errors.push(...validateSetup(obj.setup));
|
|
2476
|
+
if (obj.teardown !== void 0)
|
|
2477
|
+
errors.push(...validateSetup(obj.teardown, "teardown"));
|
|
2408
2478
|
const ready = readyProblem(obj.ready);
|
|
2409
2479
|
if (ready) errors.push(ready);
|
|
2410
2480
|
if (obj.settle !== void 0 && typeof obj.settle !== "boolean")
|
|
@@ -3071,6 +3141,18 @@ function base64Bytes(data) {
|
|
|
3071
3141
|
return out;
|
|
3072
3142
|
}
|
|
3073
3143
|
async function recordTake(browser, url, actions, sink, log, opts = {}) {
|
|
3144
|
+
const late = {};
|
|
3145
|
+
try {
|
|
3146
|
+
return await recordTakeSteps(browser, url, actions, sink, log, opts, late);
|
|
3147
|
+
} catch (e) {
|
|
3148
|
+
const teardown = late.teardown;
|
|
3149
|
+
late.teardown = void 0;
|
|
3150
|
+
if (teardown) await teardown().catch(() => {
|
|
3151
|
+
});
|
|
3152
|
+
throw e;
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
async function recordTakeSteps(browser, url, actions, sink, log, opts, late) {
|
|
3074
3156
|
const dry = opts.dryRun === true;
|
|
3075
3157
|
const sleep = dry ? (ms) => realSleep(Math.min(ms, DRY_PAUSE_MS)) : realSleep;
|
|
3076
3158
|
const maxSeconds = dry ? Infinity : opts.maxDurationSeconds ?? Infinity;
|
|
@@ -3083,6 +3165,8 @@ async function recordTake(browser, url, actions, sink, log, opts = {}) {
|
|
|
3083
3165
|
`steps[${download}] is a download, and this recorder has nowhere to keep a file. Nothing was recorded.`
|
|
3084
3166
|
);
|
|
3085
3167
|
}
|
|
3168
|
+
const teardownEnv = teardownEnvProblem(actions.teardown, opts.env ?? {});
|
|
3169
|
+
if (teardownEnv) throw new SetupEnvError(teardownEnv);
|
|
3086
3170
|
const read = await loadScriptFiles(actions, opts.files);
|
|
3087
3171
|
if ("problem" in read) throw new RecordRefusedError(read.problem);
|
|
3088
3172
|
const files = read;
|
|
@@ -3126,8 +3210,9 @@ async function recordTake(browser, url, actions, sink, log, opts = {}) {
|
|
|
3126
3210
|
noticeLog.add(step, scan?.notices);
|
|
3127
3211
|
};
|
|
3128
3212
|
const pageErrors = new PageErrorLog(opts.verbose === true);
|
|
3213
|
+
let tearingDown = false;
|
|
3129
3214
|
page.on("console", (m) => {
|
|
3130
|
-
if (m.type() !== "error") return;
|
|
3215
|
+
if (m.type() !== "error" || tearingDown) return;
|
|
3131
3216
|
const line = pageErrors.add(m.text());
|
|
3132
3217
|
if (line) log(line);
|
|
3133
3218
|
});
|
|
@@ -3158,6 +3243,49 @@ async function recordTake(browser, url, actions, sink, log, opts = {}) {
|
|
|
3158
3243
|
log(`goto ${url} (after setup)`);
|
|
3159
3244
|
response = (await open(url)).response;
|
|
3160
3245
|
}
|
|
3246
|
+
const teardownSteps = actions.teardown ?? [];
|
|
3247
|
+
let teardown = null;
|
|
3248
|
+
let teardownRan = false;
|
|
3249
|
+
const runTeardown = async () => {
|
|
3250
|
+
if (!teardownSteps.length || teardownRan) return teardown;
|
|
3251
|
+
teardownRan = true;
|
|
3252
|
+
tearingDown = true;
|
|
3253
|
+
log(`teardown: ${teardownSteps.length} step(s), off camera`);
|
|
3254
|
+
try {
|
|
3255
|
+
const result = await runSetup(page, teardownSteps, {
|
|
3256
|
+
phase: "teardown",
|
|
3257
|
+
sleep: realSleep,
|
|
3258
|
+
env: opts.env ?? {},
|
|
3259
|
+
goto: (target) => open(target),
|
|
3260
|
+
boundMs: TEARDOWN_BOUND_MS
|
|
3261
|
+
});
|
|
3262
|
+
for (const line of result.lines) log(` ${line}`);
|
|
3263
|
+
teardown = {
|
|
3264
|
+
ran: result.ran,
|
|
3265
|
+
of: teardownSteps.length,
|
|
3266
|
+
...result.failed ? {
|
|
3267
|
+
failed: {
|
|
3268
|
+
step: result.failed.step,
|
|
3269
|
+
do: result.failed.do,
|
|
3270
|
+
...result.failed.selector !== void 0 ? { selector: result.failed.selector } : {},
|
|
3271
|
+
error: result.failed.error ?? "the step failed"
|
|
3272
|
+
}
|
|
3273
|
+
} : {}
|
|
3274
|
+
};
|
|
3275
|
+
} catch (e) {
|
|
3276
|
+
teardown = {
|
|
3277
|
+
ran: 0,
|
|
3278
|
+
of: teardownSteps.length,
|
|
3279
|
+
failed: {
|
|
3280
|
+
step: 0,
|
|
3281
|
+
do: teardownSteps[0].do,
|
|
3282
|
+
error: e instanceof Error ? e.message : String(e)
|
|
3283
|
+
}
|
|
3284
|
+
};
|
|
3285
|
+
}
|
|
3286
|
+
if (teardown.failed) log(` ${teardownLine(teardown)}`);
|
|
3287
|
+
return teardown;
|
|
3288
|
+
};
|
|
3161
3289
|
let wall = null;
|
|
3162
3290
|
if (opts.onArrival) {
|
|
3163
3291
|
const seen = await page.evaluate(WALL_PROBE).catch(() => null);
|
|
@@ -3468,6 +3596,13 @@ async function recordTake(browser, url, actions, sink, log, opts = {}) {
|
|
|
3468
3596
|
})
|
|
3469
3597
|
);
|
|
3470
3598
|
signals.begin();
|
|
3599
|
+
late.teardown = async () => {
|
|
3600
|
+
if (!dry) await cdp.send("Page.stopScreencast").catch(() => {
|
|
3601
|
+
});
|
|
3602
|
+
pageBound.end();
|
|
3603
|
+
const report = await runTeardown();
|
|
3604
|
+
if (report && !report.failed) log(` ${teardownLine(report)}`);
|
|
3605
|
+
};
|
|
3471
3606
|
for (const [stepIdx, step] of actions.steps.entries()) {
|
|
3472
3607
|
if (capReached(now(), maxSeconds)) {
|
|
3473
3608
|
capped = true;
|
|
@@ -4179,6 +4314,8 @@ async function recordTake(browser, url, actions, sink, log, opts = {}) {
|
|
|
4179
4314
|
const pageTitle = await page.title().catch(() => "") ?? "";
|
|
4180
4315
|
for (const line of pageErrors.repeats()) log(line);
|
|
4181
4316
|
pageBound.end();
|
|
4317
|
+
late.teardown = void 0;
|
|
4318
|
+
await runTeardown();
|
|
4182
4319
|
pageBound.start(CLOSE_BOUND_MS);
|
|
4183
4320
|
await pageBound.race(context.close().catch(() => {
|
|
4184
4321
|
}));
|
|
@@ -4265,7 +4402,8 @@ async function recordTake(browser, url, actions, sink, log, opts = {}) {
|
|
|
4265
4402
|
// Self-contained: a path a file held is inline, so the take re-records
|
|
4266
4403
|
// from its own script with the file gone.
|
|
4267
4404
|
actions: { ...inlineScript(actions, files), url },
|
|
4268
|
-
peers: peerRuns
|
|
4405
|
+
peers: peerRuns,
|
|
4406
|
+
teardown
|
|
4269
4407
|
};
|
|
4270
4408
|
}
|
|
4271
4409
|
async function keepPageSound(heard, sink, fetchSource, log) {
|
|
@@ -4592,6 +4730,7 @@ export {
|
|
|
4592
4730
|
SetupError,
|
|
4593
4731
|
StepBound,
|
|
4594
4732
|
StepSignals,
|
|
4733
|
+
TEARDOWN_BOUND_MS,
|
|
4595
4734
|
TRAILING_HOLD_MS,
|
|
4596
4735
|
TakeClock,
|
|
4597
4736
|
WAIT_MARGIN_MS,
|
|
@@ -4681,6 +4820,7 @@ export {
|
|
|
4681
4820
|
sayJs,
|
|
4682
4821
|
sayValue,
|
|
4683
4822
|
schedulePath,
|
|
4823
|
+
scriptEnvNames,
|
|
4684
4824
|
scriptFiles,
|
|
4685
4825
|
scrollByScript,
|
|
4686
4826
|
scrollTravelMs,
|
|
@@ -4694,6 +4834,8 @@ export {
|
|
|
4694
4834
|
stageClipboardScript,
|
|
4695
4835
|
stepBoundMs,
|
|
4696
4836
|
syntheticPasteScript,
|
|
4837
|
+
teardownEnvProblem,
|
|
4838
|
+
teardownLine,
|
|
4697
4839
|
typeOfName,
|
|
4698
4840
|
untilReady,
|
|
4699
4841
|
urlSchemeProblem,
|