Skip to content

Commit

Permalink
Add screencast task
Browse files Browse the repository at this point in the history
* Automate screencast recordings
* **screencast.js**: Automate screencast.sh, asciinema, svg-term-cli. Removes progress-bar, npm tree data from cast
* **screencast.sh**: Simulate user input, trigger demoed commands
* **screencast-start.js**: Start a shell command and end the process log patterns have been observed
  • Loading branch information
marionebl committed Jan 16, 2018
1 parent 19e0bb1 commit 16a10c1
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 2 deletions.
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,23 @@
"postinstall": "cd packages/react-error-overlay/ && yarn build:prod",
"publish": "tasks/publish.sh",
"start": "cd packages/react-scripts && node bin/react-scripts.js start",
"screencast": "svg-term --cast hItN7sl5yfCPTHxvFg5glhhfp --out screencast.svg --window",
"screencast": "node ./tasks/screencast.js",
"test": "cd packages/react-scripts && node bin/react-scripts.js test --env=jsdom",
"format": "prettier --trailing-comma es5 --single-quote --write 'packages/*/*.js' 'packages/*/!(node_modules)/**/*.js'",
"precommit": "lint-staged"
},
"devDependencies": {
"eslint": "4.15.0",
"execa": "^0.9.0",
"husky": "^0.13.2",
"lerna": "2.6.0",
"lerna-changelog": "^0.6.0",
"lint-staged": "^3.3.1",
"meow": "^4.0.0",
"multimatch": "^2.1.0",
"prettier": "1.6.1",
"svg-term-cli": "^2.0.3"
"svg-term-cli": "^2.0.3",
"tempy": "^0.2.1"
},
"lint-staged": {
"*.js": [
Expand Down
58 changes: 58 additions & 0 deletions tasks/screencast-start.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env node

/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

const execa = require('execa');
const meow = require('meow');
const multimatch = require('multimatch');

main(meow());

function main(cli) {
let count = 0;

const start = Date.now();
const duration = parseInt(cli.flags.timeout, 10) * 1000;
const cp = execa.shell(cli.flags.command);

const target = parseInt(cli.flags.patternCount || '1', 10);

cp.stdout.on('data', data => {
process.stdout.write(data);
const matches = multimatch([String(data)], cli.flags.pattern);
const errMatches = multimatch([String(data)], cli.flags.errorPattern);

if (matches.length > 0) {
count++;
}

if (errMatches.length > 0) {
process.exit(1);
}

if (count >= target) {
setTimeout(() => {
process.exit(0);
}, duration);
}
});

cp.on('exit', e => {
const elapsed = Date.now() - start;

if (elapsed >= duration) {
return;
}

setTimeout(() => {
process.exit(e.code);
}, duration - elapsed);
});
}
76 changes: 76 additions & 0 deletions tasks/screencast.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/usr/bin/env node

/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

const fs = require('fs');
const path = require('path');
const execa = require('execa');
const tempy = require('tempy');

main();

function main() {
const previous = process.cwd();
const cwd = tempy.directory();

const cast = path.join(cwd, 'screencast.json');
const script = path.join(__dirname, 'screencast.sh');
const out = path.join(previous, 'screencast.svg');

const resolveLine = l => l.indexOf('🔍 Resolving packages...') > -1;
const fetchLine = l => l.indexOf('🚚 Fetching packages...') > -1;
const countLine = l => l.match(/Saved [0-9]+ new dependencies/);
const doneLine = l => l.indexOf('✨ Done in') > -1;

try {
process.chdir(cwd);
console.log(`Recording screencast ...`);
execa.sync('asciinema', ['rec', '--command', `sh ${script}`, cast], {
cwd,
stdio: 'inherit',
});

console.log('Cleaning data ...');
const data = require(cast);

cut(data.stdout, { start: resolveLine, end: fetchLine });
cut(data.stdout, { start: countLine, end: doneLine });
replace(data.stdout, [{ in: cwd, out: '~' }]);

fs.writeFileSync(cast, JSON.stringify(data, null, ' '));

console.log('Rendering SVG ...');
execa.sync('svg-term', ['--window', '--in', cast, '--out', out]);

console.log(`Recorded screencast to ${cast}`);
console.log(`Rendered SVG to ${out}`);
} catch (err) {
throw err;
} finally {
process.chdir(previous);
}
}

function cut(frames, { start, end }) {
const si = frames.findIndex(([, l]) => start(l));
const ei = frames.findIndex(([, l]) => end(l));

if (si === -1 || ei === -1) {
return;
}

frames.splice(si + 1, ei - si - 1);
}

function replace(frames, replacements) {
frames.forEach(frame => {
replacements.forEach(r => (frame[1] = frame[1].split(r.in).join(r.out)));
});
}
33 changes: 33 additions & 0 deletions tasks/screencast.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/bin/zsh
# Copyright (c) 2015-present, Facebook, Inc.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

# ******************************************************************************
# This is an end-to-end test intended to be run via screencast.js
# Dependencies: asciinema, pv, core-utils
# ******************************************************************************
set -e

printf '\e[32m%s\e[m' "λ "
echo "npx create-react-app my-app" | pv -qL $[10+(-2 + RANDOM%5)]
npx create-react-app my-app

printf '\e[32m%s\e[m' "λ "
sleep 1
echo "cd my-app" | pv -qL $[10+(-2 + RANDOM%5)]
cd my-app

printf '\e[32m%s\e[m' "λ "
sleep 1
echo "npm start" | pv -qL $[10+(-2 + RANDOM%5)]

BROWSER="none" node "$(dirname $0)/screencast-start.js" \
--command "npm start" \
--pattern="Compiled successfully*" \
--pattern-count 2 \
--error-pattern="*already running on port" \
--timeout 10

echo ""

0 comments on commit 16a10c1

Please sign in to comment.