Skip to content

Commit b9d3711

Browse files
committed
Replace nodejs make.js with a make.js for deno
1 parent f22abf3 commit b9d3711

File tree

2 files changed

+110
-298
lines changed

2 files changed

+110
-298
lines changed

make.js

Lines changed: 110 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,32 @@
1-
#!/usr/bin/env node
2-
// Usage: ./make.js command. Use -h for help.
1+
#!/usr/bin/env deno run --allow-read --allow-write --allow-env --allow-net --allow-run --unstable
2+
// --unstable is required for Puppeteer.
3+
// Usage: ./make.js command. Use -l to list commands.
34
// This is a set of tasks for building and testing Vimium in development.
4-
5-
const fs = require("fs");
6-
const process = require("process");
7-
const child_process = require("child_process");
8-
9-
// Spawns a new process and returns it.
10-
function spawn(procName, optArray, silent = false, sync = true) {
11-
if (process.platform == "win32") {
5+
import * as fs from "https://deno.land/std/fs/mod.ts";
6+
import * as fsCopy from "https://deno.land/[email protected]/fs/copy.ts";
7+
import * as path from "https://deno.land/[email protected]/path/mod.ts";
8+
import { desc, run, task } from "https://deno.land/x/[email protected]/mod.ts";
9+
import puppeteer from "https://deno.land/x/[email protected]/mod.ts";
10+
import * as shoulda from "./tests/vendor/shoulda.js";
11+
12+
const projectPath = new URL(".", import.meta.url).pathname;
13+
14+
async function shell(procName, argsArray = []) {
15+
// NOTE(philc): Does drake's `sh` function work on Windows? If so, that can replace this function.
16+
if (Deno.build.os == "windows") {
1217
// if win32, prefix arguments with "/c {original command}"
1318
// e.g. "mkdir c:\git\vimium" becomes "cmd.exe /c mkdir c:\git\vimium"
1419
optArray.unshift("/c", procName)
1520
procName = "cmd.exe"
1621
}
17-
proc = null
18-
if (sync) {
19-
proc = child_process.spawnSync(procName, optArray, {
20-
stdio: [undefined, process.stdout, process.stderr]
21-
});
22-
} else {
23-
proc = child_process.spawn(procName, optArray)
24-
if (!silent) {
25-
proc.stdout.on('data', (data) => process.stdout.write(data));
26-
proc.stderr.on('data', (data) => process.stderr.write(data));
27-
}
28-
}
29-
return proc;
22+
const p = Deno.run({ cmd: [procName].concat(argsArray) });
23+
const status = await p.status();
24+
if (!status.success)
25+
throw new Error(`${procName} ${argsArray} exited with status ${status.code}`);
3026
}
3127

3228
// Builds a zip file for submission to the Chrome and Firefox stores. The output is in dist/.
33-
function buildStorePackage() {
29+
async function buildStorePackage() {
3430
const excludeList = [
3531
"*.md",
3632
".*",
@@ -43,146 +39,144 @@ function buildStorePackage() {
4339
"test_harnesses",
4440
"tests",
4541
];
46-
const manifestContents = require("./manifest.json");
42+
const fileContents = await Deno.readTextFile("./manifest.json");
43+
const manifestContents = JSON.parse(fileContents);
4744
const rsyncOptions = ["-r", ".", "dist/vimium"].concat(
4845
...excludeList.map((item) => ["--exclude", item])
4946
);
50-
const vimiumVersion = require("./manifest.json").version;
51-
const writeDistManifest = (manifestObject) => {
52-
fs.writeFileSync("dist/vimium/manifest.json", JSON.stringify(manifestObject, null, 2));
47+
const vimiumVersion = manifestContents["version"];
48+
const writeDistManifest = async (manifestObject) => {
49+
await Deno.writeTextFile("dist/vimium/manifest.json", JSON.stringify(manifestObject, null, 2));
5350
};
5451
// cd into "dist/vimium" before building the zip, so that the files in the zip don't each have the
5552
// path prefix "dist/vimium".
5653
// --filesync ensures that files in the archive which are no longer on disk are deleted. It's equivalent to
5754
// removing the zip file before the build.
5855
const zipCommand = "cd dist/vimium && zip -r --filesync ";
5956

60-
spawn("rm", ["-rf", "dist/vimium"]);
61-
spawn("mkdir", ["-p", "dist/vimium", "dist/chrome-canary", "dist/chrome-store", "dist/firefox"]);
62-
spawn("rsync", rsyncOptions);
57+
await shell("rm", ["-rf", "dist/vimium"]);
58+
await shell("mkdir", ["-p", "dist/vimium", "dist/chrome-canary", "dist/chrome-store", "dist/firefox"]);
59+
await shell("rsync", rsyncOptions);
6360

6461
writeDistManifest(Object.assign({}, manifestContents, {
65-
// Chrome considers this key invalid in manifest.json, so we add it during the build phase.
62+
// Chrome considers this key invalid in manifest.json, so we add it only during the Firefox build phase.
6663
browser_specific_settings: {
6764
gecko: {
6865
strict_min_version: "62.0"
6966
},
7067
},
7168
}));
72-
spawn("bash", ["-c", `${zipCommand} ../firefox/vimium-firefox-${vimiumVersion}.zip .`]);
69+
await shell("bash", ["-c", `${zipCommand} ../firefox/vimium-firefox-${vimiumVersion}.zip .`]);
7370

7471
// Build the Chrome Store package. Chrome does not require the clipboardWrite permission.
7572
const permissions = manifestContents.permissions.filter((p) => p != "clipboardWrite");
7673
writeDistManifest(Object.assign({}, manifestContents, {
7774
permissions,
7875
}));
79-
spawn("bash", ["-c", `${zipCommand} ../chrome-store/vimium-chrome-store-${vimiumVersion}.zip .`]);
76+
await shell("bash", ["-c", `${zipCommand} ../chrome-store/vimium-chrome-store-${vimiumVersion}.zip .`]);
8077

8178
// Build the Chrome Store dev package.
8279
writeDistManifest(Object.assign({}, manifestContents, {
8380
name: "Vimium Canary",
8481
description: "This is the development branch of Vimium (it is beta software).",
8582
permissions,
8683
}));
87-
spawn("bash", ["-c", `${zipCommand} ../chrome-canary/vimium-canary-${vimiumVersion}.zip .`]);
84+
await shell("bash", ["-c", `${zipCommand} ../chrome-canary/vimium-canary-${vimiumVersion}.zip .`]);
8885
}
8986

90-
91-
// Returns how many tests failed.
92-
function runUnitTests() {
93-
console.log("Running unit tests...")
94-
const basedir = __dirname + "/tests/unit_tests/";
95-
fs.readdirSync(basedir).forEach((filename) => {
96-
if (filename.endsWith("_test.js")) {
97-
require(basedir + filename);
87+
const runUnitTests = async () => {
88+
// Import every test file.
89+
const dir = path.join(projectPath, "tests/unit_tests");
90+
const files = Array.from(Deno.readDirSync(dir)).map((f) => f.name).sort();
91+
for (let f of files) {
92+
if (f.endsWith("_test.js")) {
93+
await import(path.join(dir, f));
9894
}
99-
});
100-
101-
return Tests.run();
102-
}
95+
}
10396

104-
// Returns how many tests fail.
105-
function runDomTests() {
106-
const puppeteer = require("puppeteer");
97+
await shoulda.run();
98+
};
10799

108-
const testFile = __dirname + "/tests/dom_tests/dom_tests.html";
100+
const runDomTests = async () => {
101+
const testFile = `${projectPath}/tests/dom_tests/dom_tests.html`;
109102

110-
(async () => {
103+
await (async () => {
111104
const browser = await puppeteer.launch({
112105
// NOTE(philc): "Disabling web security" is required for vomnibar_test.js, because we have a file://
113106
// page accessing an iframe, and Chrome prevents this because it's a cross-origin request.
114107
args: ['--disable-web-security']
115108
});
109+
116110
const page = await browser.newPage();
117111
page.on("console", msg => console.log(msg.text()));
118112
page.on("error", (err) => console.log(err));
119113
page.on("pageerror", (err) => console.log(err));
120-
await page.goto("file://" + testFile);
114+
page.on('requestfailed', request =>
115+
console.log(console.log(`${request.failure().errorText} ${request.url()}`)));
116+
117+
// Shoulda.js is an ECMAScript module, and those cannot be loaded over file:/// protocols due to a Chrome
118+
// security restriction, and this test suite loads the dom_tests.html page from the local file system. To
119+
// (painfully) work around this, we're injecting the contents of shoulda.js into the page. We munge the
120+
// file contents and assign it to a string (`shouldaJsContents`), and then have the page itself
121+
// document.write that string during load (the document.write call is in dom_tests.html).
122+
// Another workaround would be to spin up a local file server here and load dom_tests from the network.
123+
// Discussion: https://bugs.chromium.org/p/chromium/issues/detail?id=824651
124+
let shouldaJsContents =
125+
(await Deno.readTextFile("./tests/vendor/shoulda.js")) +
126+
"\n" +
127+
// Export the module contents to window.shoulda, which is what the tests expect.
128+
"window.shoulda = {assert, context, ensureCalled, getStats, reset, run, setup, should, stub, tearDown};";
129+
130+
// Remove the `export` statement from the shoulda.js module. Because we're using document.write to add
131+
// this, an export statement will cause a JS error and halt further parsing.
132+
shouldaJsContents = shouldaJsContents.replace(/export {[^}]+}/, "");
133+
134+
await page.evaluateOnNewDocument((content) => {
135+
window.shouldaJsContents = content;
136+
},
137+
shouldaJsContents);
138+
139+
page.goto("file://" + testFile);
140+
141+
await page.waitForNavigation({ waitUntil: "load" });
142+
121143
const testsFailed = await page.evaluate(() => {
122-
Tests.run();
123-
return Tests.testsFailed;
144+
shoulda.run();
145+
return shoulda.getStats().failed;
124146
});
147+
148+
// NOTE(philc): At one point in development, I noticed that the output from Deno would suddenly pause,
149+
// prior to the tests fully finishing, so closing the browser here may be racy. If it occurs again, we may
150+
// need to add "await delay(200)".
125151
await browser.close();
126152
return testsFailed;
127153
})();
128-
}
129-
130-
// Prints the list of valid commands.
131-
function printHelpString() {
132-
console.log("Usage: ./make.js command\n\nValid commands:");
133-
const keys = Object.keys(commands).sort();
134-
for (let k of keys)
135-
console.log(k, ":", commands[k].help);
136-
}
137-
138-
const commands = []
139-
// Defines a new command.
140-
function command(name, helpString, fn) {
141-
commands[name] = { help: helpString, fn: fn };
142-
}
143-
144-
command(
145-
"test",
146-
"Run all tests",
147-
() => {
148-
const failed = runUnitTests() + runDomTests();
149-
if (failed > 0)
150-
process.exit(1);
151-
});
152-
153-
command(
154-
"test-unit",
155-
"Run unit tests",
156-
() => {
157-
const failed = runUnitTests();
158-
if (failed > 0)
159-
process.exit(1);
160-
});
161-
162-
command(
163-
"test-dom",
164-
"Run DOM tests",
165-
() => {
166-
const failed = runDomTests();
167-
if (failed > 0)
168-
process.exit(1);
169-
});
170-
171-
command(
172-
"package",
173-
"Builds a zip file for submission to the Chrome and Firefox stores. The output is in dist/",
174-
buildStorePackage);
175-
176-
if (process.argv.includes("-h") || process.argv.includes("--help") || process.argv.length == 2) {
177-
printHelpString();
178-
return;
179-
}
180-
181-
commandArg = process.argv[2]
182-
183-
if (commands[commandArg]) {
184-
commands[commandArg].fn();
185-
} else {
186-
printHelpString();
187-
process.exit(1);
188-
}
154+
};
155+
156+
desc("Run unit tests");
157+
task("test-unit", [], async () => {
158+
const failed = await runUnitTests();
159+
if (failed > 0)
160+
console.log("Failed:", failed);
161+
});
162+
163+
desc("Run DOM tests");
164+
task("test-dom", [], async () => {
165+
const failed = await runDomTests();
166+
if (failed > 0)
167+
console.log("Failed:", failed);
168+
});
169+
170+
desc("Run unit and DOM tests");
171+
task("test", [], async () => {
172+
const failed = (await runUnitTests()) + (await runDomTests());
173+
if (failed > 0)
174+
console.log("Failed:", failed);
175+
});
176+
177+
desc("Builds a zip file for submission to the Chrome and Firefox stores. The output is in dist/");
178+
task("package", [], async () => {
179+
await buildStorePackage();
180+
});
181+
182+
run();

0 commit comments

Comments
 (0)