Skip to content
This repository was archived by the owner on Oct 3, 2024. It is now read-only.

Commit 2870dda

Browse files
Merge all bin commands into a single command with subcommands
This makes using this module with `npx` much easier
1 parent f64fb3f commit 2870dda

File tree

12 files changed

+447
-433
lines changed

12 files changed

+447
-433
lines changed

README.md

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,14 @@ First of all, because this project is not an official browser extension yet, you
1111

1212
Then you must make sure that you have node.js installed (version 8 or higher): [https://nodejs.org/en/download/](https://nodejs.org/en/download/).
1313

14-
You can now install Openrunner using your terminal:
14+
You can open the Openrunner IDE using your terminal:
1515

1616
```bash
17-
npm install --global openrunner@latest
18-
```
19-
20-
Using a different command you can open the Openrunner IDE whenever you would like to use it:
21-
22-
```bash
23-
openrunner-ide --firefox '/Applications/Firefox Nightly.app/Contents/MacOS/firefox'
17+
npx openrunner ide --firefox '/Applications/Firefox Nightly.app/Contents/MacOS/firefox'
2418
```
2519
(Update the path to firefox as needed)
2620

27-
After starting you'll see Firefox with an icon of a running person in the menu bar, click this to launch the Openrunner browser extension.
21+
After executing this command, Firefox will launch and you can click on the Openrunner Icon ![](icons/openrunner-16.png) to start the Openrunner IDE.
2822

2923
Openrunner will launch with a small example script to get you started. The buttons on top of the screen can be used to open or save a script, execute or stop it. The two numbers are for the interval and the amount of runs you'd like to do (by default it's set to 1 run every 60 seconds), the last field is the current status.
3024

@@ -35,10 +29,10 @@ The bottom half of the screen shows the result of the run in json-format. Also,
3529
Much more documentation on how to create scripts is available on the wiki on github: https://github.com/computestdev/Openrunner/wiki/Scripting-guide-(with-examples)
3630

3731
## Running scripts using your terminal
38-
Assuming Openrunner has been installed (see [Getting started](#getting-started)), you can run saved scripts using your terminal:
32+
You can run saved scripts using your terminal:
3933

4034
```bash
41-
openrunner --firefox '/Applications/Firefox Nightly.app/Contents/MacOS/firefox' --script myScript.js --result myResult.json --headless
42-
```
35+
npx openrunner run --firefox '/Applications/Firefox Nightly.app/Contents/MacOS/firefox' --script myScript.js --result myResult.json --headless
36+
```
4337

4438
After this command has completed, you can inspect/parse all the results in the `myResult.json` file.
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
'use strict';
2+
const {resolve: resolvePath, join: joinPath} = require('path');
3+
const fs = require('fs-extra');
4+
const {JSDOM} = require('jsdom');
5+
6+
const {checkOptionFileAccess, checkOptionIsDirectory} = require('../../lib/node/cli');
7+
const {version} = require('../../package.json');
8+
9+
const {R_OK, X_OK} = fs.constants;
10+
const appIcon = require.resolve('../../icons/openrunner.icns');
11+
const bootstrapScript = `#!/usr/bin/env bash
12+
MY_PATH="$( cd "$(dirname "$0")" ; pwd -P )"
13+
"$MY_PATH/firefox" --no-remote --profile "$MY_PATH/../profile" "$@"
14+
`;
15+
16+
const modifyInfoPlist = async path => {
17+
const input = await fs.readFile(path, 'utf8');
18+
const dom = new JSDOM(input, {
19+
contentType: 'text/xml',
20+
});
21+
const {document} = dom.window;
22+
const findKeyElement = keyName => {
23+
return document.evaluate(`/plist/dict/key[text() = "${keyName}"]`, document, null, 9, null).singleNodeValue;
24+
};
25+
const valueElementForKey = keyName => {
26+
// note: injection in the xpath expression here. however we are only using this function by passing our own constant values
27+
const keyElement = findKeyElement(keyName);
28+
return keyElement.nextElementSibling;
29+
};
30+
const removeKey = keyName => {
31+
const keyElement = findKeyElement(keyName);
32+
const valueElement = keyElement.nextElementSibling;
33+
keyElement.remove();
34+
valueElement.remove();
35+
};
36+
37+
const firefoxVersionElement = valueElementForKey('CFBundleShortVersionString');
38+
const firefoxVersion = firefoxVersionElement.textContent;
39+
valueElementForKey('CFBundleIdentifier').textContent = `nl.computest.openrunner`;
40+
valueElementForKey('CFBundleName').textContent = `Openrunner`;
41+
valueElementForKey('CFBundleGetInfoString').textContent = `Openrunner ${version} (F${firefoxVersion})`;
42+
valueElementForKey('CFBundleVersion').textContent = version;
43+
valueElementForKey('CFBundleExecutable').textContent = 'openrunner';
44+
valueElementForKey('CFBundleIconFile').textContent = `openrunner.icns`;
45+
removeKey('CFBundleSignature');
46+
47+
const output = dom.serialize();
48+
await fs.writeFile(path, output);
49+
};
50+
51+
const buildFirefoxMacBundleHandler = async argv => {
52+
const optionValidationResults = await Promise.all([
53+
checkOptionIsDirectory(argv, 'profile'),
54+
checkOptionFileAccess(argv, 'profile', R_OK | X_OK),
55+
checkOptionIsDirectory(argv, 'app'),
56+
checkOptionFileAccess(argv, 'app', R_OK | X_OK),
57+
]);
58+
59+
if (!Math.min(...optionValidationResults)) {
60+
return 1;
61+
}
62+
63+
const profilePath = resolvePath(argv.profile);
64+
const appPath = resolvePath(argv.app);
65+
const outputPath = resolvePath(argv.output);
66+
67+
console.log(
68+
'*** Creating a new macOS application bundle with the profile',
69+
profilePath,
70+
'and firefox application bundle',
71+
appPath,
72+
'at',
73+
outputPath
74+
);
75+
76+
await fs.copy(appPath, outputPath, {preserveTimestamps: true});
77+
await Promise.all([
78+
fs.copy(profilePath, joinPath(outputPath, 'Contents', 'profile'), {preserveTimestamps: true}),
79+
fs.copy(
80+
appIcon,
81+
joinPath(outputPath, 'Contents', 'Resources', 'openrunner.icns'),
82+
{preserveTimestamps: true}
83+
),
84+
fs.writeFile(joinPath(outputPath, 'Contents', 'MacOS', 'openrunner'), bootstrapScript, {mode: 0o777}),
85+
modifyInfoPlist(joinPath(outputPath, 'Contents', 'Info.plist')),
86+
]);
87+
88+
console.log('*** Done!');
89+
return 0;
90+
};
91+
92+
exports.handler = buildFirefoxMacBundleHandler;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use strict';
2+
const {resolve: resolvePath} = require('path');
3+
4+
const {buildFirefoxProfile} = require('../..');
5+
6+
const buildFirefoxProfileHandler = async argv => {
7+
const inputPath = resolvePath(argv.input);
8+
const outputPath = resolvePath(argv.output);
9+
console.log('*** Creating a new profile from', inputPath, 'at', outputPath);
10+
11+
await buildFirefoxProfile({
12+
sourceBuildInput: inputPath,
13+
outputPath,
14+
});
15+
console.log('*** Done!');
16+
return 0;
17+
};
18+
19+
exports.handler = buildFirefoxProfileHandler;

bin/_subcommands/build-source.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use strict';
2+
const {resolve: resolvePath} = require('path');
3+
4+
const {buildSources} = require('../..');
5+
6+
const buildSourceHandler = async argv => {
7+
const outputPath = resolvePath(argv.output);
8+
console.log('*** Building sources at', outputPath);
9+
10+
await buildSources({
11+
outputPath,
12+
cncPort: argv.cncPort,
13+
instrumentCoverage: argv.coverage,
14+
});
15+
console.log('*** Done!');
16+
return 0;
17+
};
18+
19+
exports.handler = buildSourceHandler;

bin/_subcommands/ide.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use strict';
2+
const {resolve: resolvePath} = require('path');
3+
const fs = require('fs-extra');
4+
const Promise = require('bluebird');
5+
6+
const {checkOptionFileAccess, checkOptionIsFile, checkOptionIsDirectory} = require('../../lib/node/cli');
7+
const {buildTempFirefoxProfile} = require('../../building');
8+
const {startFirefox} = require('../../lib/node/firefoxProcess');
9+
10+
const {R_OK, X_OK, W_OK} = fs.constants;
11+
12+
const ideHandler = async argv => {
13+
const optionValidationResults = await Promise.all([
14+
checkOptionIsFile(argv, 'firefox'),
15+
checkOptionFileAccess(argv, 'firefox', R_OK | X_OK),
16+
checkOptionIsDirectory(argv, 'tmp'),
17+
checkOptionFileAccess(argv, 'tmp', R_OK | W_OK | X_OK),
18+
]);
19+
20+
if (!Math.min(...optionValidationResults)) {
21+
return 1;
22+
}
23+
24+
const tempDirectory = resolvePath(argv.tmp);
25+
const firefoxPath = resolvePath(argv.firefox);
26+
const profileOptions = {tempDirectory};
27+
28+
await Promise.using(buildTempFirefoxProfile(profileOptions), async profilePath => {
29+
return await Promise.using(startFirefox({firefoxPath, profilePath}), async firefoxProcess => {
30+
await firefoxProcess.waitForChildStop();
31+
});
32+
});
33+
34+
return 0;
35+
};
36+
37+
exports.handler = ideHandler;

bin/_subcommands/run.js

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
'use strict';
2+
const {resolve: resolvePath, basename} = require('path');
3+
const fs = require('fs-extra');
4+
const Promise = require('bluebird');
5+
6+
const {CnCServer} = require('../..');
7+
const {buildTempFirefoxProfile, buildCachedTempFirefoxProfile} = require('../../building');
8+
const {checkOptionFileAccess, checkOptionIsFile, checkOptionIsDirectory} = require('../../lib/node/cli');
9+
const {ERROR_FAILED_TO_CREATE_PROFILE_CACHE, startFirefox} = require('../../lib/node/firefoxProcess');
10+
const {ERROR_FAILED_TO_OPEN_RESULT_FILE, resultFileOutput} = require('../../lib/node/runResult');
11+
const log = require('../../lib/logger')({MODULE: 'bin/openrunner'});
12+
13+
const {R_OK, X_OK, W_OK} = fs.constants;
14+
15+
const runHandler = async argv => {
16+
try {
17+
const optionValidationResults = await Promise.all([
18+
checkOptionIsFile(argv, 'firefox'),
19+
checkOptionFileAccess(argv, 'firefox', R_OK | X_OK),
20+
checkOptionIsFile(argv, 'script'),
21+
checkOptionFileAccess(argv, 'script', R_OK),
22+
checkOptionIsDirectory(argv, 'tmp'),
23+
checkOptionFileAccess(argv, 'tmp', R_OK | W_OK | X_OK),
24+
]);
25+
26+
if (!Math.min(...optionValidationResults)) {
27+
return 1;
28+
}
29+
30+
if (argv.cncPort < 1 && argv.profileCache) {
31+
// cncPort=0 means that the OS will pick any unused one. And since we have to hard code
32+
// the cncPort into the profile, the cache would be useless
33+
console.error('The --profileCache option may not be used if the --cncPort option is set to 0');
34+
return 1;
35+
}
36+
37+
const {cncPort, result: resultFile, headless} = argv;
38+
const tempDirectory = resolvePath(argv.tmp);
39+
const firefoxPath = resolvePath(argv.firefox);
40+
const scriptFile = resolvePath(argv.script);
41+
const profileCache = argv.profileCache && resolvePath(argv.profileCache);
42+
const scriptContent = await fs.readFile(scriptFile, 'utf8');
43+
const stackFileName = basename(scriptFile);
44+
45+
const startAndRunScript = async ({profilePath, cncServer}) => {
46+
return await Promise.using(startFirefox({firefoxPath, profilePath, headless}), async () => {
47+
log.debug('Waiting for C&C connection...');
48+
await cncServer.waitForActiveConnection();
49+
log.info({stackFileName}, 'Sending runScript command...');
50+
return await cncServer.runScript({scriptContent, stackFileName});
51+
});
52+
};
53+
54+
// open the result file before starting the script, so that we catch errors early
55+
await Promise.using(resultFileOutput(resultFile), CnCServer.promiseDisposer(cncPort), async (writeResultFile, cncServer) => {
56+
const profileOptions = {tempDirectory, cncPort: cncServer.listenPort, profileCache};
57+
58+
let scriptResult;
59+
if (profileCache) {
60+
const profilePath = await buildCachedTempFirefoxProfile({tempDirectory, cncPort: cncServer.listenPort, profileCache});
61+
scriptResult = await startAndRunScript({profilePath, cncServer});
62+
}
63+
else {
64+
scriptResult = await Promise.using(buildTempFirefoxProfile(profileOptions), async profilePath => {
65+
return await startAndRunScript({profilePath, cncServer});
66+
});
67+
}
68+
69+
log.info({stackFileName}, 'Script run completed');
70+
if (writeResultFile) {
71+
await writeResultFile(JSON.stringify(scriptResult, null, 4));
72+
}
73+
});
74+
75+
return 0;
76+
}
77+
catch (err) {
78+
if (err[ERROR_FAILED_TO_OPEN_RESULT_FILE]) {
79+
console.error(`Invalid value for option --result : ${err.message}`);
80+
return 1;
81+
}
82+
83+
if (err[ERROR_FAILED_TO_CREATE_PROFILE_CACHE]) {
84+
console.error(`Invalid value for option --profileCache : ${err.message}`);
85+
return 1;
86+
}
87+
88+
throw err;
89+
}
90+
};
91+
92+
exports.handler = runHandler;

bin/build-firefox-profile.js

Lines changed: 0 additions & 50 deletions
This file was deleted.

0 commit comments

Comments
 (0)