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.
3
4
// 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" ) {
12
17
// if win32, prefix arguments with "/c {original command}"
13
18
// e.g. "mkdir c:\git\vimium" becomes "cmd.exe /c mkdir c:\git\vimium"
14
19
optArray . unshift ( "/c" , procName )
15
20
procName = "cmd.exe"
16
21
}
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 } ` ) ;
30
26
}
31
27
32
28
// Builds a zip file for submission to the Chrome and Firefox stores. The output is in dist/.
33
- function buildStorePackage ( ) {
29
+ async function buildStorePackage ( ) {
34
30
const excludeList = [
35
31
"*.md" ,
36
32
".*" ,
@@ -43,146 +39,144 @@ function buildStorePackage() {
43
39
"test_harnesses" ,
44
40
"tests" ,
45
41
] ;
46
- const manifestContents = require ( "./manifest.json" ) ;
42
+ const fileContents = await Deno . readTextFile ( "./manifest.json" ) ;
43
+ const manifestContents = JSON . parse ( fileContents ) ;
47
44
const rsyncOptions = [ "-r" , "." , "dist/vimium" ] . concat (
48
45
...excludeList . map ( ( item ) => [ "--exclude" , item ] )
49
46
) ;
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 ) ) ;
53
50
} ;
54
51
// cd into "dist/vimium" before building the zip, so that the files in the zip don't each have the
55
52
// path prefix "dist/vimium".
56
53
// --filesync ensures that files in the archive which are no longer on disk are deleted. It's equivalent to
57
54
// removing the zip file before the build.
58
55
const zipCommand = "cd dist/vimium && zip -r --filesync " ;
59
56
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 ) ;
63
60
64
61
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.
66
63
browser_specific_settings : {
67
64
gecko : {
68
65
strict_min_version : "62.0"
69
66
} ,
70
67
} ,
71
68
} ) ) ;
72
- spawn ( "bash" , [ "-c" , `${ zipCommand } ../firefox/vimium-firefox-${ vimiumVersion } .zip .` ] ) ;
69
+ await shell ( "bash" , [ "-c" , `${ zipCommand } ../firefox/vimium-firefox-${ vimiumVersion } .zip .` ] ) ;
73
70
74
71
// Build the Chrome Store package. Chrome does not require the clipboardWrite permission.
75
72
const permissions = manifestContents . permissions . filter ( ( p ) => p != "clipboardWrite" ) ;
76
73
writeDistManifest ( Object . assign ( { } , manifestContents , {
77
74
permissions,
78
75
} ) ) ;
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 .` ] ) ;
80
77
81
78
// Build the Chrome Store dev package.
82
79
writeDistManifest ( Object . assign ( { } , manifestContents , {
83
80
name : "Vimium Canary" ,
84
81
description : "This is the development branch of Vimium (it is beta software)." ,
85
82
permissions,
86
83
} ) ) ;
87
- spawn ( "bash" , [ "-c" , `${ zipCommand } ../chrome-canary/vimium-canary-${ vimiumVersion } .zip .` ] ) ;
84
+ await shell ( "bash" , [ "-c" , `${ zipCommand } ../chrome-canary/vimium-canary-${ vimiumVersion } .zip .` ] ) ;
88
85
}
89
86
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 ) ) ;
98
94
}
99
- } ) ;
100
-
101
- return Tests . run ( ) ;
102
- }
95
+ }
103
96
104
- // Returns how many tests fail.
105
- function runDomTests ( ) {
106
- const puppeteer = require ( "puppeteer" ) ;
97
+ await shoulda . run ( ) ;
98
+ } ;
107
99
108
- const testFile = __dirname + "/tests/dom_tests/dom_tests.html" ;
100
+ const runDomTests = async ( ) => {
101
+ const testFile = `${ projectPath } /tests/dom_tests/dom_tests.html` ;
109
102
110
- ( async ( ) => {
103
+ await ( async ( ) => {
111
104
const browser = await puppeteer . launch ( {
112
105
// NOTE(philc): "Disabling web security" is required for vomnibar_test.js, because we have a file://
113
106
// page accessing an iframe, and Chrome prevents this because it's a cross-origin request.
114
107
args : [ '--disable-web-security' ]
115
108
} ) ;
109
+
116
110
const page = await browser . newPage ( ) ;
117
111
page . on ( "console" , msg => console . log ( msg . text ( ) ) ) ;
118
112
page . on ( "error" , ( err ) => console . log ( err ) ) ;
119
113
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 ( / e x p o r t { [ ^ } ] + } / , "" ) ;
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
+
121
143
const testsFailed = await page . evaluate ( ( ) => {
122
- Tests . run ( ) ;
123
- return Tests . testsFailed ;
144
+ shoulda . run ( ) ;
145
+ return shoulda . getStats ( ) . failed ;
124
146
} ) ;
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)".
125
151
await browser . close ( ) ;
126
152
return testsFailed ;
127
153
} ) ( ) ;
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