@ytspar/sweetlink 1.18.0 → 1.19.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/cli/sweetlink.js +163 -0
- package/dist/cli/sweetlink.js.map +1 -1
- package/dist/simulator/android.d.ts +35 -0
- package/dist/simulator/android.d.ts.map +1 -0
- package/dist/simulator/android.js +119 -0
- package/dist/simulator/android.js.map +1 -0
- package/dist/simulator/ios.d.ts +39 -0
- package/dist/simulator/ios.d.ts.map +1 -0
- package/dist/simulator/ios.js +115 -0
- package/dist/simulator/ios.js.map +1 -0
- package/dist/term/ansi.d.ts +37 -0
- package/dist/term/ansi.d.ts.map +1 -0
- package/dist/term/ansi.js +170 -0
- package/dist/term/ansi.js.map +1 -0
- package/dist/term/player.d.ts +25 -0
- package/dist/term/player.d.ts.map +1 -0
- package/dist/term/player.js +243 -0
- package/dist/term/player.js.map +1 -0
- package/dist/term/recorder.d.ts +33 -0
- package/dist/term/recorder.d.ts.map +1 -0
- package/dist/term/recorder.js +77 -0
- package/dist/term/recorder.js.map +1 -0
- package/package.json +1 -1
package/dist/cli/sweetlink.js
CHANGED
|
@@ -1875,6 +1875,43 @@ const COMMAND_HELP = {
|
|
|
1875
1875
|
pnpm sweetlink snapshot -i
|
|
1876
1876
|
pnpm sweetlink click @e3
|
|
1877
1877
|
pnpm sweetlink record stop`,
|
|
1878
|
+
sim: ` sim <ios|android> <command...>
|
|
1879
|
+
Record iOS Simulator or Android Emulator screen while running a command.
|
|
1880
|
+
Wraps \`xcrun simctl io booted recordVideo\` (iOS) or
|
|
1881
|
+
\`adb shell screenrecord\` (Android), writing an .mp4 of what was on
|
|
1882
|
+
screen during your XCUITest / Espresso / fastlane / appium run.
|
|
1883
|
+
|
|
1884
|
+
Options:
|
|
1885
|
+
--output <path> .mp4 path (default: .sweetlink/sim/<label>-<stamp>.mp4)
|
|
1886
|
+
--label <text> Embedded in filename
|
|
1887
|
+
--device <name|udid> Pick a specific simulator/emulator (default: first booted)
|
|
1888
|
+
--time-limit <sec> Android only — caps screen recording (max 180)
|
|
1889
|
+
--ignore-exit Don't propagate the recorded command's exit code
|
|
1890
|
+
|
|
1891
|
+
Requirements:
|
|
1892
|
+
iOS: Xcode + a booted Simulator (Simulator.app)
|
|
1893
|
+
Android: Android Platform Tools (\`adb\`) + a running emulator
|
|
1894
|
+
|
|
1895
|
+
Examples:
|
|
1896
|
+
pnpm sweetlink sim ios "fastlane scan" --device "iPhone 15"
|
|
1897
|
+
pnpm sweetlink sim android "./gradlew connectedAndroidTest"`,
|
|
1898
|
+
term: ` term <command...>
|
|
1899
|
+
Record a shell command's stdout/stderr into asciicast v2 + a self-contained
|
|
1900
|
+
HTML player. Captures real timing; the player has play/pause, 0.1×–4×
|
|
1901
|
+
speed, seek bar, and ANSI colour rendering.
|
|
1902
|
+
|
|
1903
|
+
Options:
|
|
1904
|
+
--output <path> .cast file path (default: .sweetlink/term/<label>-<stamp>.cast)
|
|
1905
|
+
--label <text> Label embedded in the .cast title + filename
|
|
1906
|
+
--shell <path> Shell to invoke the command in (default: /bin/sh)
|
|
1907
|
+
--cols <n> Reported terminal width (default: 120)
|
|
1908
|
+
--rows <n> Reported terminal height (default: 30)
|
|
1909
|
+
--ignore-exit Don't propagate the recorded command's exit code
|
|
1910
|
+
|
|
1911
|
+
Examples:
|
|
1912
|
+
pnpm sweetlink term "pytest -v" --label api-tests
|
|
1913
|
+
pnpm sweetlink term "go test ./..." --label go-tests
|
|
1914
|
+
pnpm sweetlink term "make build" --output .sweetlink/term/build.cast`,
|
|
1878
1915
|
sessions: ` sessions [list|open]
|
|
1879
1916
|
List or open all recorded sessions in this project.
|
|
1880
1917
|
|
|
@@ -2047,6 +2084,8 @@ if (hasFlag('--output-schema')) {
|
|
|
2047
2084
|
'fill',
|
|
2048
2085
|
'console',
|
|
2049
2086
|
'record',
|
|
2087
|
+
'term',
|
|
2088
|
+
'sim',
|
|
2050
2089
|
'sessions',
|
|
2051
2090
|
'proof',
|
|
2052
2091
|
'report',
|
|
@@ -2711,6 +2750,130 @@ async function handleStatusCommand() {
|
|
|
2711
2750
|
}
|
|
2712
2751
|
break;
|
|
2713
2752
|
}
|
|
2753
|
+
case 'sim': {
|
|
2754
|
+
// Record iOS Simulator or Android Emulator screen while running a command.
|
|
2755
|
+
// Example: sweetlink sim ios "fastlane scan" --device "iPhone 15"
|
|
2756
|
+
const platform = args[1];
|
|
2757
|
+
if (platform !== 'ios' && platform !== 'android') {
|
|
2758
|
+
console.error('[Sweetlink] Usage: sweetlink sim <ios|android> "<command>" [--output path] [--device <name>]');
|
|
2759
|
+
process.exit(1);
|
|
2760
|
+
}
|
|
2761
|
+
const flagsWithValues = new Set(['--output', '--label', '--device', '--time-limit']);
|
|
2762
|
+
const positional = [];
|
|
2763
|
+
for (let i = 2; i < args.length; i++) {
|
|
2764
|
+
const a = args[i];
|
|
2765
|
+
if (a.startsWith('--')) {
|
|
2766
|
+
if (flagsWithValues.has(a))
|
|
2767
|
+
i++;
|
|
2768
|
+
continue;
|
|
2769
|
+
}
|
|
2770
|
+
positional.push(a);
|
|
2771
|
+
}
|
|
2772
|
+
const command = positional.join(' ').trim();
|
|
2773
|
+
if (!command) {
|
|
2774
|
+
console.error(`[Sweetlink] Error: sim ${platform} requires a command. Example: sweetlink sim ${platform} "fastlane scan"`);
|
|
2775
|
+
process.exit(1);
|
|
2776
|
+
}
|
|
2777
|
+
const label = getArg('--label');
|
|
2778
|
+
const labelSlug = label
|
|
2779
|
+
? label.replace(/[^a-z0-9]/gi, '-').toLowerCase().slice(0, 40)
|
|
2780
|
+
: `sim-${platform}`;
|
|
2781
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
2782
|
+
const defaultDir = path.join(findProjectRoot(), '.sweetlink', 'sim');
|
|
2783
|
+
const output = getArg('--output') ?? path.join(defaultDir, `${labelSlug}-${stamp}.mp4`);
|
|
2784
|
+
ensureDir(output);
|
|
2785
|
+
const device = getArg('--device');
|
|
2786
|
+
console.log(`[Sweetlink] Recording ${platform} simulator: ${command}`);
|
|
2787
|
+
let recResult;
|
|
2788
|
+
if (platform === 'ios') {
|
|
2789
|
+
const { recordIosSimulator } = await import('../simulator/ios.js');
|
|
2790
|
+
recResult = await recordIosSimulator({ command, output, device });
|
|
2791
|
+
}
|
|
2792
|
+
else {
|
|
2793
|
+
const { recordAndroidEmulator } = await import('../simulator/android.js');
|
|
2794
|
+
const tl = getArg('--time-limit');
|
|
2795
|
+
recResult = await recordAndroidEmulator({
|
|
2796
|
+
command, output, device,
|
|
2797
|
+
timeLimit: tl ? parseInt(tl, 10) : undefined,
|
|
2798
|
+
});
|
|
2799
|
+
}
|
|
2800
|
+
let sizeKb = '?';
|
|
2801
|
+
try {
|
|
2802
|
+
sizeKb = String(Math.round(fs.statSync(output).size / 1024));
|
|
2803
|
+
}
|
|
2804
|
+
catch { /* file may not exist if recordingClosed is false */ }
|
|
2805
|
+
console.log(`[Sweetlink] ${recResult.recordingClosed ? '✓' : '⚠'} ${getRelativePath(output)} · ` +
|
|
2806
|
+
`${recResult.durationSec.toFixed(1)}s · ${sizeKb}KB · ${recResult.device} · exit=${recResult.exitCode}` +
|
|
2807
|
+
(recResult.recordingClosed ? '' : ' (recording was force-killed; mp4 may be incomplete)'));
|
|
2808
|
+
result = {
|
|
2809
|
+
path: output,
|
|
2810
|
+
device: recResult.device,
|
|
2811
|
+
durationSec: recResult.durationSec,
|
|
2812
|
+
exitCode: recResult.exitCode,
|
|
2813
|
+
recordingClosed: recResult.recordingClosed,
|
|
2814
|
+
};
|
|
2815
|
+
if (recResult.exitCode !== 0 && !hasFlag('--ignore-exit')) {
|
|
2816
|
+
process.exit(recResult.exitCode);
|
|
2817
|
+
}
|
|
2818
|
+
break;
|
|
2819
|
+
}
|
|
2820
|
+
case 'term': {
|
|
2821
|
+
// Record a shell command's stdout/stderr into asciicast v2 + HTML player.
|
|
2822
|
+
// Example: sweetlink term "pytest -v" --label api-tests
|
|
2823
|
+
const flagsWithValues = new Set(['--output', '--label', '--shell', '--cols', '--rows']);
|
|
2824
|
+
const positional = [];
|
|
2825
|
+
for (let i = 1; i < args.length; i++) {
|
|
2826
|
+
const a = args[i];
|
|
2827
|
+
if (a.startsWith('--')) {
|
|
2828
|
+
if (flagsWithValues.has(a))
|
|
2829
|
+
i++;
|
|
2830
|
+
continue;
|
|
2831
|
+
}
|
|
2832
|
+
positional.push(a);
|
|
2833
|
+
}
|
|
2834
|
+
const command = positional.join(' ').trim();
|
|
2835
|
+
if (!command) {
|
|
2836
|
+
console.error('[Sweetlink] Error: term requires a command. Example: sweetlink term "pytest tests/"');
|
|
2837
|
+
process.exit(1);
|
|
2838
|
+
}
|
|
2839
|
+
const label = getArg('--label');
|
|
2840
|
+
const labelSlug = label
|
|
2841
|
+
? label.replace(/[^a-z0-9]/gi, '-').toLowerCase().slice(0, 40)
|
|
2842
|
+
: 'term';
|
|
2843
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
2844
|
+
const defaultDir = path.join(findProjectRoot(), '.sweetlink', 'term');
|
|
2845
|
+
const output = getArg('--output') ?? path.join(defaultDir, `${labelSlug}-${stamp}.cast`);
|
|
2846
|
+
ensureDir(output);
|
|
2847
|
+
console.log(`[Sweetlink] Recording terminal: ${command}`);
|
|
2848
|
+
const { captureTerminal } = await import('../term/recorder.js');
|
|
2849
|
+
const { generatePlayer } = await import('../term/player.js');
|
|
2850
|
+
const cap = await captureTerminal({
|
|
2851
|
+
command,
|
|
2852
|
+
output,
|
|
2853
|
+
label,
|
|
2854
|
+
shell: getArg('--shell'),
|
|
2855
|
+
cols: getArg('--cols') ? parseInt(getArg('--cols'), 10) : undefined,
|
|
2856
|
+
rows: getArg('--rows') ? parseInt(getArg('--rows'), 10) : undefined,
|
|
2857
|
+
});
|
|
2858
|
+
const playerPath = await generatePlayer({ castPath: output });
|
|
2859
|
+
console.log(`[Sweetlink] ✓ ${getRelativePath(output)} · ${cap.durationSec.toFixed(1)}s · ` +
|
|
2860
|
+
`${cap.events} events · ${(cap.bytes / 1024).toFixed(0)}KB · exit=${cap.exitCode}`);
|
|
2861
|
+
console.log(`[Sweetlink] ▶ ${getRelativePath(playerPath)}`);
|
|
2862
|
+
result = {
|
|
2863
|
+
castPath: output,
|
|
2864
|
+
playerPath,
|
|
2865
|
+
durationSec: cap.durationSec,
|
|
2866
|
+
bytes: cap.bytes,
|
|
2867
|
+
events: cap.events,
|
|
2868
|
+
exitCode: cap.exitCode,
|
|
2869
|
+
};
|
|
2870
|
+
// Propagate the recorded command's exit code by default so CI fails
|
|
2871
|
+
// when the wrapped tests fail.
|
|
2872
|
+
if (cap.exitCode !== 0 && !hasFlag('--ignore-exit')) {
|
|
2873
|
+
process.exit(cap.exitCode);
|
|
2874
|
+
}
|
|
2875
|
+
break;
|
|
2876
|
+
}
|
|
2714
2877
|
case 'sessions': {
|
|
2715
2878
|
const sub = args[1];
|
|
2716
2879
|
const projRoot = findProjectRoot();
|