Skip to content

Commit

Permalink
feat: expose more utils useful for custom commands
Browse files Browse the repository at this point in the history
  • Loading branch information
AutoSponge committed Mar 30, 2020
1 parent d9adfc4 commit bd4c8e2
Show file tree
Hide file tree
Showing 9 changed files with 89 additions and 29 deletions.
8 changes: 4 additions & 4 deletions lib/events/director.enter.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
const { magenta } = require('kleur');
const dlv = require('dlv');
const PLAYBILL = 'playbill';
const COMMAND_PLAYBILL = `.${PLAYBILL}`;

module.exports = function(scriptwriter) {
module.exports = function (scriptwriter) {
const { director } = scriptwriter.company;
const { link } = scriptwriter.escapes;
const { magenta } = scriptwriter.color;
scriptwriter.assign({ scriptwriter });
director.displayPrompt();
/* istanbul ignore if */
Expand All @@ -16,10 +16,10 @@ module.exports = function(scriptwriter) {
help: `List the scriptwriter's ${PLAYBILL}`,
action() {
const credits = Object.keys(scriptwriter.company).sort();
const maxLength = Math.max(...credits.map(c => c.length)) + 2;
const maxLength = Math.max(...credits.map((c) => c.length)) + 2;
scriptwriter.log(
credits
.map(c =>
.map((c) =>
[
getLink(c),
' '.repeat(maxLength - c.length),
Expand Down
3 changes: 1 addition & 2 deletions lib/events/director.exit.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
const { magenta } = require('kleur');

module.exports = function (scriptwriter) {
const { magenta } = scriptwriter.color;
scriptwriter.log(magenta('repl session ended.'));
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'test') process.exit();
Expand Down
3 changes: 1 addition & 2 deletions lib/events/director.reset.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
const { magenta } = require('kleur');

const handler = async function (scriptwriter) {
const { magenta } = scriptwriter.color;
const { browser, director } = scriptwriter.company;
await browser.close();
await scriptwriter.init();
Expand Down
3 changes: 1 addition & 2 deletions lib/events/page.load.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
const { green, yellow } = require('kleur');

module.exports = async function (scriptwriter) {
const { green, yellow } = scriptwriter.color;
const { page, director } = scriptwriter.company;
const url = await page.url();
const { host } = new URL(url);
Expand Down
37 changes: 30 additions & 7 deletions lib/scriptwriter.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ const EventEmitter = require('events');
const repl = require('repl');
const assert = require('assert');
const dlv = require('dlv');
const color = require('kleur');
const { resolve } = require('path');
const { readdir } = require('fs').promises;
const importGlobal = require('import-global');
const playwright = importGlobal('playwright');
const PrettyError = require('pretty-error');
const ansiEscapes = require('ansi-escapes');
const prettier = require('prettier');

const EVENTS_PATH = resolve(__dirname, 'events');
const EVENTS_DIR = readdir(EVENTS_PATH);
Expand All @@ -19,7 +21,7 @@ const company = new Map();
const completions = new Set();
const prettyError = new PrettyError();

process.on('unhandledRejection', error => {
process.on('unhandledRejection', (error) => {
console.log(prettyError.render(error));
});

Expand All @@ -46,6 +48,8 @@ module.exports = class Scriptwriter extends EventEmitter {
constructor(initialConfig = defaultConfig) {
super();
Object.entries(initialConfig).map(([k, v]) => config.set(k, v));
this.log = this.log.bind(this);
this.errir = this.error.bind(this);
this.on(EVENT_ASSIGN, this.register.bind(this));
this.replServer = null;
}
Expand Down Expand Up @@ -75,9 +79,18 @@ module.exports = class Scriptwriter extends EventEmitter {
get config() {
return Object.fromEntries(config);
}
/**
* @return {Object} ansiEscapes
*/
get escapes() {
return ansiEscapes;
}
/**
* @return {Object} kleur
*/
get color() {
return color;
}
/**
* Assigns the properties of the parameter object
* to the replServer.context (and the "playbill").
Expand Down Expand Up @@ -134,7 +147,9 @@ module.exports = class Scriptwriter extends EventEmitter {
completer,
});
}
Object.keys(repl.repl.commands).forEach(key => completions.add(`.${key}`));
Object.keys(repl.repl.commands).forEach((key) =>
completions.add(`.${key}`)
);
const director = this.replServer;
await this.assign({ director });
const { browserType, launch } = this.config;
Expand All @@ -159,7 +174,7 @@ module.exports = class Scriptwriter extends EventEmitter {
const last = chunks.pop();
// there's only one token
if (!chunks.length) {
completions = this.completions.flatMap(c => {
completions = this.completions.flatMap((c) => {
return c.startsWith(last) ? `${line}${c.substring(last.length)}` : [];
});
break complex;
Expand All @@ -170,17 +185,25 @@ module.exports = class Scriptwriter extends EventEmitter {
if (!obj) break complex;
completions = Reflect.ownKeys(obj)
.concat(Reflect.ownKeys(Reflect.getPrototypeOf(obj) || {}))
.flatMap(c => {
.flatMap((c) => {
if (c === 'constructor') return [];
if (typeof c !== 'string') return [];
return [`${line.replace(/\.[^\.]*$/, '')}.${c}`];
})
.sort();
}
const hits = completions.filter(c => c.startsWith(line));
const hits = completions.filter((c) => c.startsWith(line));
if (hits.length === 1 && hits[0] === line) return [[], line];
return [hits.length ? hits : completions, line];
}
/**
* @param {string} str
* @param {Object} options
* @returns {string}
*/
code(str, options = { parser: 'babel' }) {
return prettier.format(str, options);
}
/**
* @param {...any} args
*/
Expand All @@ -191,11 +214,11 @@ module.exports = class Scriptwriter extends EventEmitter {
console.log(...args);
}
/**
* @param {...any} args
* @param {...any} args
*/
error(...args) {
this.emit('error', args);
if (process.env.NODE_ENV === 'test') return;
console.log(prettyError.render(...args))
console.log(prettyError.render(...args));
}
};
25 changes: 17 additions & 8 deletions lib/scriptwriter.test.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
const test = require('ava');
const Scriptwriter = require('./scriptwriter');
const ansiEscapes = require('ansi-escapes');
const kleur = require('kleur');

test('Scriptwriter is a constructor', t => {
test('Scriptwriter is a constructor', (t) => {
t.plan(1);
const scriptwriter = new Scriptwriter();
t.truthy(scriptwriter);
});

test('exposes color, code, escapes', (t) => {
const scriptwriter = new Scriptwriter();
t.is(scriptwriter.escapes, ansiEscapes);
t.is(scriptwriter.color, kleur);
t.is(scriptwriter.code('var x=1').trim(), 'var x = 1;');
});

/*****************************************
* all async tests must be run in serial *
*****************************************/
test.serial('scriptwriter persists the replServer as director', async t => {
test.serial('scriptwriter persists the replServer as director', async (t) => {
const scriptwriter = new Scriptwriter();
t.is(scriptwriter.replServer, null);
let directorReady = waitFor(scriptwriter, 'assign', ['director']);
Expand All @@ -29,7 +38,7 @@ test.serial('scriptwriter persists the replServer as director', async t => {
scriptwriter.company.director.close();
});

test.serial('page.load updates the repl prompt', async t => {
test.serial('page.load updates the repl prompt', async (t) => {
const scriptwriter = new Scriptwriter();
await scriptwriter.init();
const pageReady = waitFor(scriptwriter, 'assign', ['page']);
Expand All @@ -44,7 +53,7 @@ test.serial('page.load updates the repl prompt', async t => {
scriptwriter.company.director.close();
});

test.serial('scriptwriter creates client for chromium', async t => {
test.serial('scriptwriter creates client for chromium', async (t) => {
const scriptwriter = new Scriptwriter();
const clientReady = waitFor(scriptwriter, 'assign', ['client']);
await scriptwriter.init();
Expand All @@ -53,7 +62,7 @@ test.serial('scriptwriter creates client for chromium', async t => {
scriptwriter.company.director.close();
});

test.serial('scriptwriter creates the .playbill action', async t => {
test.serial('scriptwriter creates the .playbill action', async (t) => {
const scriptwriter = new Scriptwriter();
const clientReady = waitFor(scriptwriter, 'assign', ['client']);
await scriptwriter.init();
Expand All @@ -74,12 +83,12 @@ test.serial('scriptwriter creates the .playbill action', async t => {
'page',
'playwright',
'scriptwriter',
].every(name => msg.includes(name))
].every((name) => msg.includes(name))
);
director.close();
});

test('completer ', async t => {
test('completer ', async (t) => {
const scriptwriter = new Scriptwriter();
const clientReady = waitFor(scriptwriter, 'assign', ['client']);
await scriptwriter.init();
Expand Down Expand Up @@ -144,7 +153,7 @@ test('completer ', async t => {
});

function waitFor(emitter, event, only) {
return new Promise(resolve => {
return new Promise((resolve) => {
emitter.on(event, (...args) => {
if (only && JSON.stringify(only) !== JSON.stringify(args)) return;
resolve(args);
Expand Down
3 changes: 1 addition & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@
"import-global": "^0.1.0",
"kleur": "^3.0.3",
"meow": "^6.1.0",
"prettier": "^2.0.2",
"pretty-error": "^2.1.1"
},
"devDependencies": {
"ava": "^3.5.1",
"cross-env": "^7.0.2",
"npm-run-all": "^4.1.5",
"nyc": "^15.0.0",
"prettier": "^2.0.2",
"prettier-eslint": "^9.0.1",
"prettier-eslint-cli": "^5.0.0"
}
Expand Down
34 changes: 33 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,43 @@ You can also load a config from a file.

`scriptwriter --config iphonex.json`

### Custom Commands

You can load your own commands. Scriptwriter exposes some helpful utility functions.

- director = [node repl](https://nodejs.org/api/repl.html) instance
- scriptwriter.code = [prettier](https://prettier.io/).format
- scriptwriter.color = [kleur](https://www.npmjs.com/package/kleur)
- scriptwriter.error = [pretty-error](https://www.npmjs.com/package/pretty-error)
- scriptwriter.escapes = [ansi-escapes](https://www.npmjs.com/package/ansi-escapes)

Example:

```js
// my-command.js
scriptwriter.completion = '.louder';
director.defineCommand('louder', {
help: `make something louder`,
async action(str) {
const { log, color } = scriptwriter;
log(color.red(`${str.toUpperCase()}!!`));
director.displayPrompt();
},
});
```

```js
// in the scriptwriter repl
> .load my-command.js
> .louder test
TEST!!
```

### Mac Firewall

On a mac, you may get the firewall popup.

1. Open keychain access.
1. Open keychain access.
1. In the top menu, choose `Keychain Access > Certificate Assistant > Create a Certificate`.
1. Name it `Playwright`.
1. Change the `Certificate Type` to `Code Signing`.
Expand Down

0 comments on commit bd4c8e2

Please sign in to comment.