├── .jshintrc
├── .npm
└── package
│ ├── .gitignore
│ ├── README
│ └── npm-shrinkwrap.json
├── .versions
├── LICENSE
├── README.md
├── app
├── .jshintrc
├── autoUpdater.js
├── main.js
├── menu.js
├── package.json
├── preload.js
└── proxyWindowEvents.js
├── client
├── .jshintrc
└── index.js
├── docs
└── overview.png
├── package.js
├── server
├── .jshintrc
├── createBinaries.js
├── downloadUrls.js
├── index.js
├── launchApp.js
├── serve.js
├── serveDownloadUrl.js
└── serveUpdateFeed.js
└── tests
└── server
├── .jshintrc
└── downloadUrlsTest.js
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | // If you are using SublimeLinter, after modifying this config file, be sure to close and reopen
3 | // the file(s) you were editing to see the changes take effect.
4 |
5 | // Suppress warnings about using [] notation when it can be expressed in dot notation,
6 | // so that we can use [] notation to highlight when objects are used as maps.
7 | "sub": true,
8 |
9 | // Prohibit the use of undeclared variables. Define globals and/or use JSHint pre-defined
10 | // environments (http://jshint.com/docs/options/#environments) as appropriate. You can set
11 | // environments for specific folders (e.g. client vs. server) by extending this file:
12 | // http://stackoverflow.com/a/25213836/495611.
13 | "undef": true,
14 |
15 | // Warn for unused variables and function parameters.
16 | "unused": true
17 |
18 | // We don't whitelist global variables here because there's nothing that's shared between the
19 | // client, the server, _and_ the app.
20 | }
21 |
--------------------------------------------------------------------------------
/.npm/package/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/.npm/package/README:
--------------------------------------------------------------------------------
1 | This directory and the files immediately inside it are automatically generated
2 | when you change this package's NPM dependencies. Commit the files in this
3 | directory (npm-shrinkwrap.json, .gitignore, and this README) to source control
4 | so that others run the same versions of sub-dependencies.
5 |
6 | You should NOT check in the node_modules directory that Meteor automatically
7 | creates; if you are using git, the .gitignore file tells git to ignore it.
8 |
--------------------------------------------------------------------------------
/.npm/package/npm-shrinkwrap.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "electron-packager": {
4 | "version": "https://github.com/mixmaxhq/electron-packager/archive/f511e2680efa39c014d8bedca872168e585f8daf.tar.gz",
5 | "dependencies": {
6 | "asar": {
7 | "version": "0.8.3",
8 | "dependencies": {
9 | "chromium-pickle-js": {
10 | "version": "0.1.0"
11 | },
12 | "commander": {
13 | "version": "2.3.0"
14 | },
15 | "cuint": {
16 | "version": "0.1.5"
17 | },
18 | "minimatch": {
19 | "version": "2.0.4",
20 | "dependencies": {
21 | "brace-expansion": {
22 | "version": "1.1.2",
23 | "dependencies": {
24 | "balanced-match": {
25 | "version": "0.3.0"
26 | },
27 | "concat-map": {
28 | "version": "0.0.1"
29 | }
30 | }
31 | }
32 | }
33 | },
34 | "mksnapshot": {
35 | "version": "0.1.0",
36 | "dependencies": {
37 | "decompress-zip": {
38 | "version": "0.1.0",
39 | "dependencies": {
40 | "binary": {
41 | "version": "0.3.0",
42 | "dependencies": {
43 | "chainsaw": {
44 | "version": "0.1.0",
45 | "dependencies": {
46 | "traverse": {
47 | "version": "0.3.9"
48 | }
49 | }
50 | },
51 | "buffers": {
52 | "version": "0.1.1"
53 | }
54 | }
55 | },
56 | "graceful-fs": {
57 | "version": "3.0.8"
58 | },
59 | "mkpath": {
60 | "version": "0.1.0"
61 | },
62 | "nopt": {
63 | "version": "3.0.6",
64 | "dependencies": {
65 | "abbrev": {
66 | "version": "1.0.7"
67 | }
68 | }
69 | },
70 | "q": {
71 | "version": "1.4.1"
72 | },
73 | "readable-stream": {
74 | "version": "1.1.13",
75 | "dependencies": {
76 | "core-util-is": {
77 | "version": "1.0.2"
78 | },
79 | "isarray": {
80 | "version": "0.0.1"
81 | },
82 | "string_decoder": {
83 | "version": "0.10.31"
84 | },
85 | "inherits": {
86 | "version": "2.0.1"
87 | }
88 | }
89 | },
90 | "touch": {
91 | "version": "0.0.3",
92 | "dependencies": {
93 | "nopt": {
94 | "version": "1.0.10",
95 | "dependencies": {
96 | "abbrev": {
97 | "version": "1.0.7"
98 | }
99 | }
100 | }
101 | }
102 | }
103 | }
104 | },
105 | "fs-extra": {
106 | "version": "0.18.2",
107 | "dependencies": {
108 | "graceful-fs": {
109 | "version": "3.0.8"
110 | },
111 | "jsonfile": {
112 | "version": "2.2.3"
113 | }
114 | }
115 | },
116 | "request": {
117 | "version": "2.55.0",
118 | "dependencies": {
119 | "bl": {
120 | "version": "0.9.4",
121 | "dependencies": {
122 | "readable-stream": {
123 | "version": "1.0.33",
124 | "dependencies": {
125 | "core-util-is": {
126 | "version": "1.0.2"
127 | },
128 | "isarray": {
129 | "version": "0.0.1"
130 | },
131 | "string_decoder": {
132 | "version": "0.10.31"
133 | },
134 | "inherits": {
135 | "version": "2.0.1"
136 | }
137 | }
138 | }
139 | }
140 | },
141 | "caseless": {
142 | "version": "0.9.0"
143 | },
144 | "forever-agent": {
145 | "version": "0.6.1"
146 | },
147 | "form-data": {
148 | "version": "0.2.0",
149 | "dependencies": {
150 | "async": {
151 | "version": "0.9.2"
152 | }
153 | }
154 | },
155 | "json-stringify-safe": {
156 | "version": "5.0.1"
157 | },
158 | "mime-types": {
159 | "version": "2.0.14",
160 | "dependencies": {
161 | "mime-db": {
162 | "version": "1.12.0"
163 | }
164 | }
165 | },
166 | "node-uuid": {
167 | "version": "1.4.7"
168 | },
169 | "qs": {
170 | "version": "2.4.2"
171 | },
172 | "tunnel-agent": {
173 | "version": "0.4.2"
174 | },
175 | "tough-cookie": {
176 | "version": "2.2.1"
177 | },
178 | "http-signature": {
179 | "version": "0.10.1",
180 | "dependencies": {
181 | "assert-plus": {
182 | "version": "0.1.5"
183 | },
184 | "asn1": {
185 | "version": "0.1.11"
186 | },
187 | "ctype": {
188 | "version": "0.5.3"
189 | }
190 | }
191 | },
192 | "oauth-sign": {
193 | "version": "0.6.0"
194 | },
195 | "hawk": {
196 | "version": "2.3.1",
197 | "dependencies": {
198 | "hoek": {
199 | "version": "2.16.3"
200 | },
201 | "boom": {
202 | "version": "2.10.1"
203 | },
204 | "cryptiles": {
205 | "version": "2.0.5"
206 | },
207 | "sntp": {
208 | "version": "1.0.9"
209 | }
210 | }
211 | },
212 | "aws-sign2": {
213 | "version": "0.5.0"
214 | },
215 | "stringstream": {
216 | "version": "0.0.5"
217 | },
218 | "combined-stream": {
219 | "version": "0.0.7",
220 | "dependencies": {
221 | "delayed-stream": {
222 | "version": "0.0.5"
223 | }
224 | }
225 | },
226 | "isstream": {
227 | "version": "0.1.2"
228 | },
229 | "har-validator": {
230 | "version": "1.8.0",
231 | "dependencies": {
232 | "bluebird": {
233 | "version": "2.10.2"
234 | },
235 | "chalk": {
236 | "version": "1.1.1",
237 | "dependencies": {
238 | "ansi-styles": {
239 | "version": "2.1.0"
240 | },
241 | "escape-string-regexp": {
242 | "version": "1.0.3"
243 | },
244 | "has-ansi": {
245 | "version": "2.0.0",
246 | "dependencies": {
247 | "ansi-regex": {
248 | "version": "2.0.0"
249 | }
250 | }
251 | },
252 | "strip-ansi": {
253 | "version": "3.0.0",
254 | "dependencies": {
255 | "ansi-regex": {
256 | "version": "2.0.0"
257 | }
258 | }
259 | },
260 | "supports-color": {
261 | "version": "2.0.0"
262 | }
263 | }
264 | },
265 | "commander": {
266 | "version": "2.9.0",
267 | "dependencies": {
268 | "graceful-readlink": {
269 | "version": "1.0.1"
270 | }
271 | }
272 | },
273 | "is-my-json-valid": {
274 | "version": "2.12.3",
275 | "dependencies": {
276 | "generate-function": {
277 | "version": "2.0.0"
278 | },
279 | "generate-object-property": {
280 | "version": "1.2.0",
281 | "dependencies": {
282 | "is-property": {
283 | "version": "1.0.2"
284 | }
285 | }
286 | },
287 | "jsonpointer": {
288 | "version": "2.0.0"
289 | },
290 | "xtend": {
291 | "version": "4.0.1"
292 | }
293 | }
294 | }
295 | }
296 | }
297 | }
298 | }
299 | }
300 | },
301 | "glob": {
302 | "version": "5.0.15",
303 | "dependencies": {
304 | "inflight": {
305 | "version": "1.0.4",
306 | "dependencies": {
307 | "wrappy": {
308 | "version": "1.0.1"
309 | }
310 | }
311 | },
312 | "inherits": {
313 | "version": "2.0.1"
314 | },
315 | "once": {
316 | "version": "1.3.3",
317 | "dependencies": {
318 | "wrappy": {
319 | "version": "1.0.1"
320 | }
321 | }
322 | },
323 | "path-is-absolute": {
324 | "version": "1.0.0"
325 | }
326 | }
327 | }
328 | }
329 | },
330 | "electron-download": {
331 | "version": "1.4.1",
332 | "dependencies": {
333 | "debug": {
334 | "version": "2.2.0",
335 | "dependencies": {
336 | "ms": {
337 | "version": "0.7.1"
338 | }
339 | }
340 | },
341 | "home-path": {
342 | "version": "1.0.1"
343 | },
344 | "nugget": {
345 | "version": "1.6.0",
346 | "dependencies": {
347 | "pretty-bytes": {
348 | "version": "1.0.4",
349 | "dependencies": {
350 | "get-stdin": {
351 | "version": "4.0.1"
352 | },
353 | "meow": {
354 | "version": "3.6.0",
355 | "dependencies": {
356 | "camelcase-keys": {
357 | "version": "2.0.0",
358 | "dependencies": {
359 | "camelcase": {
360 | "version": "2.0.1"
361 | },
362 | "map-obj": {
363 | "version": "1.0.1"
364 | }
365 | }
366 | },
367 | "loud-rejection": {
368 | "version": "1.2.0",
369 | "dependencies": {
370 | "signal-exit": {
371 | "version": "2.1.2"
372 | }
373 | }
374 | },
375 | "normalize-package-data": {
376 | "version": "2.3.5",
377 | "dependencies": {
378 | "hosted-git-info": {
379 | "version": "2.1.4"
380 | },
381 | "is-builtin-module": {
382 | "version": "1.0.0",
383 | "dependencies": {
384 | "builtin-modules": {
385 | "version": "1.1.0"
386 | }
387 | }
388 | },
389 | "validate-npm-package-license": {
390 | "version": "3.0.1",
391 | "dependencies": {
392 | "spdx-correct": {
393 | "version": "1.0.2",
394 | "dependencies": {
395 | "spdx-license-ids": {
396 | "version": "1.1.0"
397 | }
398 | }
399 | },
400 | "spdx-expression-parse": {
401 | "version": "1.0.2",
402 | "dependencies": {
403 | "spdx-exceptions": {
404 | "version": "1.0.4"
405 | },
406 | "spdx-license-ids": {
407 | "version": "1.1.0"
408 | }
409 | }
410 | }
411 | }
412 | }
413 | }
414 | },
415 | "object-assign": {
416 | "version": "4.0.1"
417 | },
418 | "read-pkg-up": {
419 | "version": "1.0.1",
420 | "dependencies": {
421 | "find-up": {
422 | "version": "1.1.0",
423 | "dependencies": {
424 | "path-exists": {
425 | "version": "2.1.0"
426 | },
427 | "pinkie-promise": {
428 | "version": "2.0.0",
429 | "dependencies": {
430 | "pinkie": {
431 | "version": "2.0.1"
432 | }
433 | }
434 | }
435 | }
436 | },
437 | "read-pkg": {
438 | "version": "1.1.0",
439 | "dependencies": {
440 | "load-json-file": {
441 | "version": "1.1.0",
442 | "dependencies": {
443 | "graceful-fs": {
444 | "version": "4.1.2"
445 | },
446 | "parse-json": {
447 | "version": "2.2.0",
448 | "dependencies": {
449 | "error-ex": {
450 | "version": "1.3.0",
451 | "dependencies": {
452 | "is-arrayish": {
453 | "version": "0.2.1"
454 | }
455 | }
456 | }
457 | }
458 | },
459 | "pify": {
460 | "version": "2.3.0"
461 | },
462 | "pinkie-promise": {
463 | "version": "2.0.0",
464 | "dependencies": {
465 | "pinkie": {
466 | "version": "2.0.1"
467 | }
468 | }
469 | },
470 | "strip-bom": {
471 | "version": "2.0.0",
472 | "dependencies": {
473 | "is-utf8": {
474 | "version": "0.2.0"
475 | }
476 | }
477 | }
478 | }
479 | },
480 | "path-type": {
481 | "version": "1.1.0",
482 | "dependencies": {
483 | "graceful-fs": {
484 | "version": "4.1.2"
485 | },
486 | "pify": {
487 | "version": "2.3.0"
488 | },
489 | "pinkie-promise": {
490 | "version": "2.0.0",
491 | "dependencies": {
492 | "pinkie": {
493 | "version": "2.0.1"
494 | }
495 | }
496 | }
497 | }
498 | }
499 | }
500 | }
501 | }
502 | },
503 | "redent": {
504 | "version": "1.0.0",
505 | "dependencies": {
506 | "indent-string": {
507 | "version": "2.1.0",
508 | "dependencies": {
509 | "repeating": {
510 | "version": "2.0.0",
511 | "dependencies": {
512 | "is-finite": {
513 | "version": "1.0.1",
514 | "dependencies": {
515 | "number-is-nan": {
516 | "version": "1.0.0"
517 | }
518 | }
519 | }
520 | }
521 | }
522 | }
523 | },
524 | "strip-indent": {
525 | "version": "1.0.1"
526 | }
527 | }
528 | },
529 | "trim-newlines": {
530 | "version": "1.0.0"
531 | }
532 | }
533 | }
534 | }
535 | },
536 | "progress-stream": {
537 | "version": "1.2.0",
538 | "dependencies": {
539 | "through2": {
540 | "version": "0.2.3",
541 | "dependencies": {
542 | "readable-stream": {
543 | "version": "1.1.13",
544 | "dependencies": {
545 | "core-util-is": {
546 | "version": "1.0.2"
547 | },
548 | "isarray": {
549 | "version": "0.0.1"
550 | },
551 | "string_decoder": {
552 | "version": "0.10.31"
553 | },
554 | "inherits": {
555 | "version": "2.0.1"
556 | }
557 | }
558 | },
559 | "xtend": {
560 | "version": "2.1.2",
561 | "dependencies": {
562 | "object-keys": {
563 | "version": "0.4.0"
564 | }
565 | }
566 | }
567 | }
568 | },
569 | "speedometer": {
570 | "version": "0.1.4"
571 | }
572 | }
573 | },
574 | "request": {
575 | "version": "2.67.0",
576 | "dependencies": {
577 | "bl": {
578 | "version": "1.0.0",
579 | "dependencies": {
580 | "readable-stream": {
581 | "version": "2.0.4",
582 | "dependencies": {
583 | "core-util-is": {
584 | "version": "1.0.2"
585 | },
586 | "inherits": {
587 | "version": "2.0.1"
588 | },
589 | "isarray": {
590 | "version": "0.0.1"
591 | },
592 | "process-nextick-args": {
593 | "version": "1.0.6"
594 | },
595 | "string_decoder": {
596 | "version": "0.10.31"
597 | },
598 | "util-deprecate": {
599 | "version": "1.0.2"
600 | }
601 | }
602 | }
603 | }
604 | },
605 | "caseless": {
606 | "version": "0.11.0"
607 | },
608 | "extend": {
609 | "version": "3.0.0"
610 | },
611 | "forever-agent": {
612 | "version": "0.6.1"
613 | },
614 | "form-data": {
615 | "version": "1.0.0-rc3",
616 | "dependencies": {
617 | "async": {
618 | "version": "1.5.0"
619 | }
620 | }
621 | },
622 | "json-stringify-safe": {
623 | "version": "5.0.1"
624 | },
625 | "mime-types": {
626 | "version": "2.1.8",
627 | "dependencies": {
628 | "mime-db": {
629 | "version": "1.20.0"
630 | }
631 | }
632 | },
633 | "node-uuid": {
634 | "version": "1.4.7"
635 | },
636 | "qs": {
637 | "version": "5.2.0"
638 | },
639 | "tunnel-agent": {
640 | "version": "0.4.2"
641 | },
642 | "tough-cookie": {
643 | "version": "2.2.1"
644 | },
645 | "http-signature": {
646 | "version": "1.1.0",
647 | "dependencies": {
648 | "assert-plus": {
649 | "version": "0.1.5"
650 | },
651 | "jsprim": {
652 | "version": "1.2.2",
653 | "dependencies": {
654 | "extsprintf": {
655 | "version": "1.0.2"
656 | },
657 | "json-schema": {
658 | "version": "0.2.2"
659 | },
660 | "verror": {
661 | "version": "1.3.6"
662 | }
663 | }
664 | },
665 | "sshpk": {
666 | "version": "1.7.1",
667 | "dependencies": {
668 | "asn1": {
669 | "version": "0.2.3"
670 | },
671 | "assert-plus": {
672 | "version": "0.2.0"
673 | },
674 | "dashdash": {
675 | "version": "1.10.1",
676 | "dependencies": {
677 | "assert-plus": {
678 | "version": "0.1.5"
679 | }
680 | }
681 | },
682 | "jsbn": {
683 | "version": "0.1.0"
684 | },
685 | "tweetnacl": {
686 | "version": "0.13.2"
687 | },
688 | "jodid25519": {
689 | "version": "1.0.2"
690 | },
691 | "ecc-jsbn": {
692 | "version": "0.1.1"
693 | }
694 | }
695 | }
696 | }
697 | },
698 | "oauth-sign": {
699 | "version": "0.8.0"
700 | },
701 | "hawk": {
702 | "version": "3.1.2",
703 | "dependencies": {
704 | "hoek": {
705 | "version": "2.16.3"
706 | },
707 | "boom": {
708 | "version": "2.10.1"
709 | },
710 | "cryptiles": {
711 | "version": "2.0.5"
712 | },
713 | "sntp": {
714 | "version": "1.0.9"
715 | }
716 | }
717 | },
718 | "aws-sign2": {
719 | "version": "0.6.0"
720 | },
721 | "stringstream": {
722 | "version": "0.0.5"
723 | },
724 | "combined-stream": {
725 | "version": "1.0.5",
726 | "dependencies": {
727 | "delayed-stream": {
728 | "version": "1.0.0"
729 | }
730 | }
731 | },
732 | "isstream": {
733 | "version": "0.1.2"
734 | },
735 | "is-typedarray": {
736 | "version": "1.0.0"
737 | },
738 | "har-validator": {
739 | "version": "2.0.3",
740 | "dependencies": {
741 | "chalk": {
742 | "version": "1.1.1",
743 | "dependencies": {
744 | "ansi-styles": {
745 | "version": "2.1.0"
746 | },
747 | "escape-string-regexp": {
748 | "version": "1.0.3"
749 | },
750 | "has-ansi": {
751 | "version": "2.0.0",
752 | "dependencies": {
753 | "ansi-regex": {
754 | "version": "2.0.0"
755 | }
756 | }
757 | },
758 | "strip-ansi": {
759 | "version": "3.0.0",
760 | "dependencies": {
761 | "ansi-regex": {
762 | "version": "2.0.0"
763 | }
764 | }
765 | },
766 | "supports-color": {
767 | "version": "2.0.0"
768 | }
769 | }
770 | },
771 | "commander": {
772 | "version": "2.9.0",
773 | "dependencies": {
774 | "graceful-readlink": {
775 | "version": "1.0.1"
776 | }
777 | }
778 | },
779 | "is-my-json-valid": {
780 | "version": "2.12.3",
781 | "dependencies": {
782 | "generate-function": {
783 | "version": "2.0.0"
784 | },
785 | "generate-object-property": {
786 | "version": "1.2.0",
787 | "dependencies": {
788 | "is-property": {
789 | "version": "1.0.2"
790 | }
791 | }
792 | },
793 | "jsonpointer": {
794 | "version": "2.0.0"
795 | },
796 | "xtend": {
797 | "version": "4.0.1"
798 | }
799 | }
800 | },
801 | "pinkie-promise": {
802 | "version": "2.0.0",
803 | "dependencies": {
804 | "pinkie": {
805 | "version": "2.0.1"
806 | }
807 | }
808 | }
809 | }
810 | }
811 | }
812 | },
813 | "single-line-log": {
814 | "version": "0.4.1"
815 | },
816 | "throttleit": {
817 | "version": "0.0.2"
818 | }
819 | }
820 | },
821 | "path-exists": {
822 | "version": "1.0.0"
823 | },
824 | "rc": {
825 | "version": "1.1.5",
826 | "dependencies": {
827 | "deep-extend": {
828 | "version": "0.4.0"
829 | },
830 | "ini": {
831 | "version": "1.3.4"
832 | },
833 | "strip-json-comments": {
834 | "version": "1.0.4"
835 | }
836 | }
837 | }
838 | }
839 | },
840 | "extract-zip": {
841 | "version": "1.3.0",
842 | "dependencies": {
843 | "async": {
844 | "version": "1.5.0"
845 | },
846 | "concat-stream": {
847 | "version": "1.5.0",
848 | "dependencies": {
849 | "inherits": {
850 | "version": "2.0.1"
851 | },
852 | "typedarray": {
853 | "version": "0.0.6"
854 | },
855 | "readable-stream": {
856 | "version": "2.0.4",
857 | "dependencies": {
858 | "core-util-is": {
859 | "version": "1.0.2"
860 | },
861 | "isarray": {
862 | "version": "0.0.1"
863 | },
864 | "process-nextick-args": {
865 | "version": "1.0.6"
866 | },
867 | "string_decoder": {
868 | "version": "0.10.31"
869 | },
870 | "util-deprecate": {
871 | "version": "1.0.2"
872 | }
873 | }
874 | }
875 | }
876 | },
877 | "debug": {
878 | "version": "0.7.4"
879 | },
880 | "mkdirp": {
881 | "version": "0.5.0",
882 | "dependencies": {
883 | "minimist": {
884 | "version": "0.0.8"
885 | }
886 | }
887 | },
888 | "yauzl": {
889 | "version": "2.3.1",
890 | "dependencies": {
891 | "fd-slicer": {
892 | "version": "1.0.1"
893 | },
894 | "pend": {
895 | "version": "1.2.0"
896 | }
897 | }
898 | }
899 | }
900 | },
901 | "minimist": {
902 | "version": "1.2.0"
903 | },
904 | "mv": {
905 | "version": "2.1.1"
906 | },
907 | "plist": {
908 | "version": "1.2.0",
909 | "dependencies": {
910 | "base64-js": {
911 | "version": "0.0.8"
912 | },
913 | "xmlbuilder": {
914 | "version": "4.0.0",
915 | "dependencies": {
916 | "lodash": {
917 | "version": "3.10.1"
918 | }
919 | }
920 | },
921 | "xmldom": {
922 | "version": "0.1.19"
923 | },
924 | "util-deprecate": {
925 | "version": "1.0.2"
926 | }
927 | }
928 | },
929 | "rcedit": {
930 | "version": "0.3.0"
931 | },
932 | "run-series": {
933 | "version": "1.1.4"
934 | }
935 | }
936 | },
937 | "electron-rebuild": {
938 | "version": "1.0.1",
939 | "dependencies": {
940 | "babel-runtime": {
941 | "version": "5.8.35",
942 | "dependencies": {
943 | "core-js": {
944 | "version": "1.2.6"
945 | }
946 | }
947 | },
948 | "lodash": {
949 | "version": "3.10.1"
950 | },
951 | "npm": {
952 | "version": "2.14.18",
953 | "dependencies": {
954 | "abbrev": {
955 | "version": "1.0.7"
956 | },
957 | "ansi": {
958 | "version": "0.3.1"
959 | },
960 | "ansicolors": {
961 | "version": "0.3.2"
962 | },
963 | "ansistyles": {
964 | "version": "0.1.3"
965 | },
966 | "archy": {
967 | "version": "1.0.0"
968 | },
969 | "async-some": {
970 | "version": "1.0.2"
971 | },
972 | "block-stream": {
973 | "version": "0.0.8"
974 | },
975 | "char-spinner": {
976 | "version": "1.0.1"
977 | },
978 | "chmodr": {
979 | "version": "1.0.2"
980 | },
981 | "chownr": {
982 | "version": "1.0.1"
983 | },
984 | "cmd-shim": {
985 | "version": "2.0.1",
986 | "dependencies": {
987 | "graceful-fs": {
988 | "version": "3.0.8"
989 | }
990 | }
991 | },
992 | "columnify": {
993 | "version": "1.5.4",
994 | "dependencies": {
995 | "wcwidth": {
996 | "version": "1.0.0",
997 | "dependencies": {
998 | "defaults": {
999 | "version": "1.0.3",
1000 | "dependencies": {
1001 | "clone": {
1002 | "version": "1.0.2"
1003 | }
1004 | }
1005 | }
1006 | }
1007 | }
1008 | }
1009 | },
1010 | "config-chain": {
1011 | "version": "1.1.10",
1012 | "dependencies": {
1013 | "proto-list": {
1014 | "version": "1.2.4"
1015 | }
1016 | }
1017 | },
1018 | "dezalgo": {
1019 | "version": "1.0.3",
1020 | "dependencies": {
1021 | "asap": {
1022 | "version": "2.0.3"
1023 | }
1024 | }
1025 | },
1026 | "editor": {
1027 | "version": "1.0.0"
1028 | },
1029 | "fs-vacuum": {
1030 | "version": "1.2.7"
1031 | },
1032 | "fs-write-stream-atomic": {
1033 | "version": "1.0.8",
1034 | "dependencies": {
1035 | "iferr": {
1036 | "version": "0.1.5"
1037 | }
1038 | }
1039 | },
1040 | "fstream": {
1041 | "version": "1.0.8"
1042 | },
1043 | "fstream-npm": {
1044 | "version": "1.0.7",
1045 | "dependencies": {
1046 | "fstream-ignore": {
1047 | "version": "1.0.3"
1048 | }
1049 | }
1050 | },
1051 | "github-url-from-git": {
1052 | "version": "1.4.0"
1053 | },
1054 | "github-url-from-username-repo": {
1055 | "version": "1.0.2"
1056 | },
1057 | "glob": {
1058 | "version": "5.0.15",
1059 | "dependencies": {
1060 | "path-is-absolute": {
1061 | "version": "1.0.0"
1062 | }
1063 | }
1064 | },
1065 | "graceful-fs": {
1066 | "version": "4.1.3"
1067 | },
1068 | "hosted-git-info": {
1069 | "version": "2.1.4"
1070 | },
1071 | "inflight": {
1072 | "version": "1.0.4"
1073 | },
1074 | "inherits": {
1075 | "version": "2.0.1"
1076 | },
1077 | "ini": {
1078 | "version": "1.3.4"
1079 | },
1080 | "init-package-json": {
1081 | "version": "1.9.3",
1082 | "dependencies": {
1083 | "glob": {
1084 | "version": "6.0.4",
1085 | "dependencies": {
1086 | "path-is-absolute": {
1087 | "version": "1.0.0"
1088 | }
1089 | }
1090 | },
1091 | "promzard": {
1092 | "version": "0.3.0"
1093 | }
1094 | }
1095 | },
1096 | "lockfile": {
1097 | "version": "1.0.1"
1098 | },
1099 | "lru-cache": {
1100 | "version": "3.2.0",
1101 | "dependencies": {
1102 | "pseudomap": {
1103 | "version": "1.0.1"
1104 | }
1105 | }
1106 | },
1107 | "minimatch": {
1108 | "version": "3.0.0",
1109 | "dependencies": {
1110 | "brace-expansion": {
1111 | "version": "1.1.1",
1112 | "dependencies": {
1113 | "balanced-match": {
1114 | "version": "0.2.1"
1115 | },
1116 | "concat-map": {
1117 | "version": "0.0.1"
1118 | }
1119 | }
1120 | }
1121 | }
1122 | },
1123 | "mkdirp": {
1124 | "version": "0.5.1",
1125 | "dependencies": {
1126 | "minimist": {
1127 | "version": "0.0.8"
1128 | }
1129 | }
1130 | },
1131 | "node-gyp": {
1132 | "version": "3.2.1",
1133 | "dependencies": {
1134 | "glob": {
1135 | "version": "4.5.3",
1136 | "dependencies": {
1137 | "minimatch": {
1138 | "version": "2.0.10",
1139 | "dependencies": {
1140 | "brace-expansion": {
1141 | "version": "1.1.2",
1142 | "dependencies": {
1143 | "balanced-match": {
1144 | "version": "0.3.0"
1145 | },
1146 | "concat-map": {
1147 | "version": "0.0.1"
1148 | }
1149 | }
1150 | }
1151 | }
1152 | }
1153 | }
1154 | },
1155 | "minimatch": {
1156 | "version": "1.0.0",
1157 | "dependencies": {
1158 | "lru-cache": {
1159 | "version": "2.7.3"
1160 | },
1161 | "sigmund": {
1162 | "version": "1.0.1"
1163 | }
1164 | }
1165 | },
1166 | "npmlog": {
1167 | "version": "1.2.1",
1168 | "dependencies": {
1169 | "are-we-there-yet": {
1170 | "version": "1.0.5",
1171 | "dependencies": {
1172 | "delegates": {
1173 | "version": "0.1.0"
1174 | }
1175 | }
1176 | },
1177 | "gauge": {
1178 | "version": "1.2.2",
1179 | "dependencies": {
1180 | "has-unicode": {
1181 | "version": "1.0.1"
1182 | },
1183 | "lodash.pad": {
1184 | "version": "3.1.1",
1185 | "dependencies": {
1186 | "lodash._basetostring": {
1187 | "version": "3.0.1"
1188 | },
1189 | "lodash._createpadding": {
1190 | "version": "3.6.1",
1191 | "dependencies": {
1192 | "lodash.repeat": {
1193 | "version": "3.0.1"
1194 | }
1195 | }
1196 | }
1197 | }
1198 | },
1199 | "lodash.padleft": {
1200 | "version": "3.1.1",
1201 | "dependencies": {
1202 | "lodash._basetostring": {
1203 | "version": "3.0.1"
1204 | },
1205 | "lodash._createpadding": {
1206 | "version": "3.6.1",
1207 | "dependencies": {
1208 | "lodash.repeat": {
1209 | "version": "3.0.1"
1210 | }
1211 | }
1212 | }
1213 | }
1214 | },
1215 | "lodash.padright": {
1216 | "version": "3.1.1",
1217 | "dependencies": {
1218 | "lodash._basetostring": {
1219 | "version": "3.0.1"
1220 | },
1221 | "lodash._createpadding": {
1222 | "version": "3.6.1",
1223 | "dependencies": {
1224 | "lodash.repeat": {
1225 | "version": "3.0.1"
1226 | }
1227 | }
1228 | }
1229 | }
1230 | }
1231 | }
1232 | }
1233 | }
1234 | },
1235 | "path-array": {
1236 | "version": "1.0.0",
1237 | "dependencies": {
1238 | "array-index": {
1239 | "version": "0.1.1",
1240 | "dependencies": {
1241 | "debug": {
1242 | "version": "2.2.0",
1243 | "dependencies": {
1244 | "ms": {
1245 | "version": "0.7.1"
1246 | }
1247 | }
1248 | }
1249 | }
1250 | }
1251 | }
1252 | }
1253 | }
1254 | },
1255 | "nopt": {
1256 | "version": "3.0.6"
1257 | },
1258 | "normalize-git-url": {
1259 | "version": "3.0.1"
1260 | },
1261 | "normalize-package-data": {
1262 | "version": "2.3.5",
1263 | "dependencies": {
1264 | "is-builtin-module": {
1265 | "version": "1.0.0",
1266 | "dependencies": {
1267 | "builtin-modules": {
1268 | "version": "1.1.0"
1269 | }
1270 | }
1271 | }
1272 | }
1273 | },
1274 | "npm-cache-filename": {
1275 | "version": "1.0.2"
1276 | },
1277 | "npm-install-checks": {
1278 | "version": "1.0.6",
1279 | "dependencies": {
1280 | "npmlog": {
1281 | "version": "1.2.1",
1282 | "dependencies": {
1283 | "are-we-there-yet": {
1284 | "version": "1.0.4",
1285 | "dependencies": {
1286 | "delegates": {
1287 | "version": "0.1.0"
1288 | }
1289 | }
1290 | },
1291 | "gauge": {
1292 | "version": "1.2.2",
1293 | "dependencies": {
1294 | "has-unicode": {
1295 | "version": "1.0.1"
1296 | },
1297 | "lodash.pad": {
1298 | "version": "3.1.1",
1299 | "dependencies": {
1300 | "lodash._basetostring": {
1301 | "version": "3.0.1"
1302 | },
1303 | "lodash._createpadding": {
1304 | "version": "3.6.1",
1305 | "dependencies": {
1306 | "lodash.repeat": {
1307 | "version": "3.0.1"
1308 | }
1309 | }
1310 | }
1311 | }
1312 | },
1313 | "lodash.padleft": {
1314 | "version": "3.1.1",
1315 | "dependencies": {
1316 | "lodash._basetostring": {
1317 | "version": "3.0.1"
1318 | },
1319 | "lodash._createpadding": {
1320 | "version": "3.6.1",
1321 | "dependencies": {
1322 | "lodash.repeat": {
1323 | "version": "3.0.1"
1324 | }
1325 | }
1326 | }
1327 | }
1328 | },
1329 | "lodash.padright": {
1330 | "version": "3.1.1",
1331 | "dependencies": {
1332 | "lodash._basetostring": {
1333 | "version": "3.0.1"
1334 | },
1335 | "lodash._createpadding": {
1336 | "version": "3.6.1",
1337 | "dependencies": {
1338 | "lodash.repeat": {
1339 | "version": "3.0.1"
1340 | }
1341 | }
1342 | }
1343 | }
1344 | }
1345 | }
1346 | }
1347 | }
1348 | }
1349 | }
1350 | },
1351 | "npm-package-arg": {
1352 | "version": "4.1.0"
1353 | },
1354 | "npm-registry-client": {
1355 | "version": "7.0.9",
1356 | "dependencies": {
1357 | "concat-stream": {
1358 | "version": "1.5.1",
1359 | "dependencies": {
1360 | "typedarray": {
1361 | "version": "0.0.6"
1362 | },
1363 | "readable-stream": {
1364 | "version": "2.0.4",
1365 | "dependencies": {
1366 | "core-util-is": {
1367 | "version": "1.0.2"
1368 | },
1369 | "isarray": {
1370 | "version": "0.0.1"
1371 | },
1372 | "process-nextick-args": {
1373 | "version": "1.0.6"
1374 | },
1375 | "string_decoder": {
1376 | "version": "0.10.31"
1377 | },
1378 | "util-deprecate": {
1379 | "version": "1.0.2"
1380 | }
1381 | }
1382 | }
1383 | }
1384 | },
1385 | "retry": {
1386 | "version": "0.8.0"
1387 | }
1388 | }
1389 | },
1390 | "npm-user-validate": {
1391 | "version": "0.1.2"
1392 | },
1393 | "npmlog": {
1394 | "version": "2.0.2",
1395 | "dependencies": {
1396 | "are-we-there-yet": {
1397 | "version": "1.0.6",
1398 | "dependencies": {
1399 | "delegates": {
1400 | "version": "1.0.0"
1401 | }
1402 | }
1403 | },
1404 | "gauge": {
1405 | "version": "1.2.5",
1406 | "dependencies": {
1407 | "has-unicode": {
1408 | "version": "2.0.0"
1409 | },
1410 | "lodash.pad": {
1411 | "version": "3.2.2",
1412 | "dependencies": {
1413 | "lodash.repeat": {
1414 | "version": "3.1.2"
1415 | }
1416 | }
1417 | },
1418 | "lodash.padleft": {
1419 | "version": "3.1.1",
1420 | "dependencies": {
1421 | "lodash._basetostring": {
1422 | "version": "3.0.1"
1423 | },
1424 | "lodash._createpadding": {
1425 | "version": "3.6.1",
1426 | "dependencies": {
1427 | "lodash.repeat": {
1428 | "version": "3.1.2"
1429 | }
1430 | }
1431 | }
1432 | }
1433 | },
1434 | "lodash.padright": {
1435 | "version": "3.1.1",
1436 | "dependencies": {
1437 | "lodash._basetostring": {
1438 | "version": "3.0.1"
1439 | },
1440 | "lodash._createpadding": {
1441 | "version": "3.6.1",
1442 | "dependencies": {
1443 | "lodash.repeat": {
1444 | "version": "3.1.2"
1445 | }
1446 | }
1447 | }
1448 | }
1449 | }
1450 | }
1451 | }
1452 | }
1453 | },
1454 | "once": {
1455 | "version": "1.3.3"
1456 | },
1457 | "opener": {
1458 | "version": "1.4.1"
1459 | },
1460 | "osenv": {
1461 | "version": "0.1.3",
1462 | "dependencies": {
1463 | "os-homedir": {
1464 | "version": "1.0.0"
1465 | },
1466 | "os-tmpdir": {
1467 | "version": "1.0.1"
1468 | }
1469 | }
1470 | },
1471 | "path-is-inside": {
1472 | "version": "1.0.1"
1473 | },
1474 | "read": {
1475 | "version": "1.0.7",
1476 | "dependencies": {
1477 | "mute-stream": {
1478 | "version": "0.0.5"
1479 | }
1480 | }
1481 | },
1482 | "read-installed": {
1483 | "version": "4.0.3",
1484 | "dependencies": {
1485 | "debuglog": {
1486 | "version": "1.0.1"
1487 | },
1488 | "readdir-scoped-modules": {
1489 | "version": "1.0.2"
1490 | },
1491 | "util-extend": {
1492 | "version": "1.0.1"
1493 | }
1494 | }
1495 | },
1496 | "read-package-json": {
1497 | "version": "2.0.3",
1498 | "dependencies": {
1499 | "glob": {
1500 | "version": "6.0.4",
1501 | "dependencies": {
1502 | "path-is-absolute": {
1503 | "version": "1.0.0"
1504 | }
1505 | }
1506 | },
1507 | "json-parse-helpfulerror": {
1508 | "version": "1.0.3",
1509 | "dependencies": {
1510 | "jju": {
1511 | "version": "1.2.1"
1512 | }
1513 | }
1514 | }
1515 | }
1516 | },
1517 | "readable-stream": {
1518 | "version": "1.1.13",
1519 | "dependencies": {
1520 | "core-util-is": {
1521 | "version": "1.0.1"
1522 | },
1523 | "isarray": {
1524 | "version": "0.0.1"
1525 | },
1526 | "string_decoder": {
1527 | "version": "0.10.31"
1528 | }
1529 | }
1530 | },
1531 | "realize-package-specifier": {
1532 | "version": "3.0.1"
1533 | },
1534 | "request": {
1535 | "version": "2.69.0",
1536 | "dependencies": {
1537 | "aws-sign2": {
1538 | "version": "0.6.0"
1539 | },
1540 | "aws4": {
1541 | "version": "1.2.1",
1542 | "dependencies": {
1543 | "lru-cache": {
1544 | "version": "2.7.3"
1545 | }
1546 | }
1547 | },
1548 | "bl": {
1549 | "version": "1.0.2",
1550 | "dependencies": {
1551 | "readable-stream": {
1552 | "version": "2.0.5",
1553 | "dependencies": {
1554 | "core-util-is": {
1555 | "version": "1.0.2"
1556 | },
1557 | "isarray": {
1558 | "version": "0.0.1"
1559 | },
1560 | "process-nextick-args": {
1561 | "version": "1.0.6"
1562 | },
1563 | "string_decoder": {
1564 | "version": "0.10.31"
1565 | },
1566 | "util-deprecate": {
1567 | "version": "1.0.2"
1568 | }
1569 | }
1570 | }
1571 | }
1572 | },
1573 | "caseless": {
1574 | "version": "0.11.0"
1575 | },
1576 | "combined-stream": {
1577 | "version": "1.0.5",
1578 | "dependencies": {
1579 | "delayed-stream": {
1580 | "version": "1.0.0"
1581 | }
1582 | }
1583 | },
1584 | "extend": {
1585 | "version": "3.0.0"
1586 | },
1587 | "forever-agent": {
1588 | "version": "0.6.1"
1589 | },
1590 | "form-data": {
1591 | "version": "1.0.0-rc3",
1592 | "dependencies": {
1593 | "async": {
1594 | "version": "1.5.2"
1595 | }
1596 | }
1597 | },
1598 | "har-validator": {
1599 | "version": "2.0.6",
1600 | "dependencies": {
1601 | "chalk": {
1602 | "version": "1.1.1",
1603 | "dependencies": {
1604 | "ansi-styles": {
1605 | "version": "2.1.0"
1606 | },
1607 | "escape-string-regexp": {
1608 | "version": "1.0.4"
1609 | },
1610 | "has-ansi": {
1611 | "version": "2.0.0"
1612 | },
1613 | "supports-color": {
1614 | "version": "2.0.0"
1615 | }
1616 | }
1617 | },
1618 | "commander": {
1619 | "version": "2.9.0",
1620 | "dependencies": {
1621 | "graceful-readlink": {
1622 | "version": "1.0.1"
1623 | }
1624 | }
1625 | },
1626 | "is-my-json-valid": {
1627 | "version": "2.12.4",
1628 | "dependencies": {
1629 | "generate-function": {
1630 | "version": "2.0.0"
1631 | },
1632 | "generate-object-property": {
1633 | "version": "1.2.0",
1634 | "dependencies": {
1635 | "is-property": {
1636 | "version": "1.0.2"
1637 | }
1638 | }
1639 | },
1640 | "jsonpointer": {
1641 | "version": "2.0.0"
1642 | },
1643 | "xtend": {
1644 | "version": "4.0.1"
1645 | }
1646 | }
1647 | },
1648 | "pinkie-promise": {
1649 | "version": "2.0.0",
1650 | "dependencies": {
1651 | "pinkie": {
1652 | "version": "2.0.4"
1653 | }
1654 | }
1655 | }
1656 | }
1657 | },
1658 | "hawk": {
1659 | "version": "3.1.3",
1660 | "dependencies": {
1661 | "hoek": {
1662 | "version": "2.16.3"
1663 | },
1664 | "boom": {
1665 | "version": "2.10.1"
1666 | },
1667 | "cryptiles": {
1668 | "version": "2.0.5"
1669 | },
1670 | "sntp": {
1671 | "version": "1.0.9"
1672 | }
1673 | }
1674 | },
1675 | "http-signature": {
1676 | "version": "1.1.1",
1677 | "dependencies": {
1678 | "assert-plus": {
1679 | "version": "0.2.0"
1680 | },
1681 | "jsprim": {
1682 | "version": "1.2.2",
1683 | "dependencies": {
1684 | "extsprintf": {
1685 | "version": "1.0.2"
1686 | },
1687 | "json-schema": {
1688 | "version": "0.2.2"
1689 | },
1690 | "verror": {
1691 | "version": "1.3.6"
1692 | }
1693 | }
1694 | },
1695 | "sshpk": {
1696 | "version": "1.7.3",
1697 | "dependencies": {
1698 | "asn1": {
1699 | "version": "0.2.3"
1700 | },
1701 | "dashdash": {
1702 | "version": "1.12.2"
1703 | },
1704 | "jsbn": {
1705 | "version": "0.1.0"
1706 | },
1707 | "tweetnacl": {
1708 | "version": "0.13.3"
1709 | },
1710 | "jodid25519": {
1711 | "version": "1.0.2"
1712 | },
1713 | "ecc-jsbn": {
1714 | "version": "0.1.1"
1715 | }
1716 | }
1717 | }
1718 | }
1719 | },
1720 | "is-typedarray": {
1721 | "version": "1.0.0"
1722 | },
1723 | "isstream": {
1724 | "version": "0.1.2"
1725 | },
1726 | "json-stringify-safe": {
1727 | "version": "5.0.1"
1728 | },
1729 | "mime-types": {
1730 | "version": "2.1.9",
1731 | "dependencies": {
1732 | "mime-db": {
1733 | "version": "1.21.0"
1734 | }
1735 | }
1736 | },
1737 | "node-uuid": {
1738 | "version": "1.4.7"
1739 | },
1740 | "oauth-sign": {
1741 | "version": "0.8.1"
1742 | },
1743 | "qs": {
1744 | "version": "6.0.2"
1745 | },
1746 | "stringstream": {
1747 | "version": "0.0.5"
1748 | },
1749 | "tough-cookie": {
1750 | "version": "2.2.1"
1751 | },
1752 | "tunnel-agent": {
1753 | "version": "0.4.2"
1754 | }
1755 | }
1756 | },
1757 | "retry": {
1758 | "version": "0.9.0"
1759 | },
1760 | "rimraf": {
1761 | "version": "2.5.1",
1762 | "dependencies": {
1763 | "glob": {
1764 | "version": "6.0.4",
1765 | "dependencies": {
1766 | "path-is-absolute": {
1767 | "version": "1.0.0"
1768 | }
1769 | }
1770 | }
1771 | }
1772 | },
1773 | "semver": {
1774 | "version": "5.1.0"
1775 | },
1776 | "sha": {
1777 | "version": "2.0.1",
1778 | "dependencies": {
1779 | "readable-stream": {
1780 | "version": "2.0.2",
1781 | "dependencies": {
1782 | "core-util-is": {
1783 | "version": "1.0.1"
1784 | },
1785 | "isarray": {
1786 | "version": "0.0.1"
1787 | },
1788 | "process-nextick-args": {
1789 | "version": "1.0.3"
1790 | },
1791 | "string_decoder": {
1792 | "version": "0.10.31"
1793 | },
1794 | "util-deprecate": {
1795 | "version": "1.0.1"
1796 | }
1797 | }
1798 | }
1799 | }
1800 | },
1801 | "slide": {
1802 | "version": "1.1.6"
1803 | },
1804 | "sorted-object": {
1805 | "version": "1.0.0"
1806 | },
1807 | "spdx-license-ids": {
1808 | "version": "1.2.0"
1809 | },
1810 | "tar": {
1811 | "version": "2.2.1"
1812 | },
1813 | "text-table": {
1814 | "version": "0.2.0"
1815 | },
1816 | "uid-number": {
1817 | "version": "0.0.6"
1818 | },
1819 | "umask": {
1820 | "version": "1.1.0"
1821 | },
1822 | "validate-npm-package-license": {
1823 | "version": "3.0.1",
1824 | "dependencies": {
1825 | "spdx-correct": {
1826 | "version": "1.0.2"
1827 | },
1828 | "spdx-expression-parse": {
1829 | "version": "1.0.2",
1830 | "dependencies": {
1831 | "spdx-exceptions": {
1832 | "version": "1.0.4"
1833 | }
1834 | }
1835 | }
1836 | }
1837 | },
1838 | "validate-npm-package-name": {
1839 | "version": "2.2.2",
1840 | "dependencies": {
1841 | "builtins": {
1842 | "version": "0.0.7"
1843 | }
1844 | }
1845 | },
1846 | "which": {
1847 | "version": "1.2.4",
1848 | "dependencies": {
1849 | "is-absolute": {
1850 | "version": "0.1.7",
1851 | "dependencies": {
1852 | "is-relative": {
1853 | "version": "0.1.3"
1854 | }
1855 | }
1856 | },
1857 | "isexe": {
1858 | "version": "1.1.1"
1859 | }
1860 | }
1861 | },
1862 | "wrappy": {
1863 | "version": "1.0.1"
1864 | },
1865 | "write-file-atomic": {
1866 | "version": "1.1.4"
1867 | },
1868 | "ansi-regex": {
1869 | "version": "2.0.0"
1870 | },
1871 | "imurmurhash": {
1872 | "version": "0.1.4"
1873 | },
1874 | "strip-ansi": {
1875 | "version": "3.0.0"
1876 | }
1877 | }
1878 | },
1879 | "nslog": {
1880 | "version": "3.0.0",
1881 | "dependencies": {
1882 | "nan": {
1883 | "version": "2.2.0"
1884 | }
1885 | }
1886 | },
1887 | "promise": {
1888 | "version": "7.1.1",
1889 | "dependencies": {
1890 | "asap": {
1891 | "version": "2.0.3"
1892 | }
1893 | }
1894 | },
1895 | "yargs": {
1896 | "version": "3.32.0",
1897 | "dependencies": {
1898 | "camelcase": {
1899 | "version": "2.1.0"
1900 | },
1901 | "cliui": {
1902 | "version": "3.1.0",
1903 | "dependencies": {
1904 | "strip-ansi": {
1905 | "version": "3.0.0",
1906 | "dependencies": {
1907 | "ansi-regex": {
1908 | "version": "2.0.0"
1909 | }
1910 | }
1911 | },
1912 | "wrap-ansi": {
1913 | "version": "1.0.0"
1914 | }
1915 | }
1916 | },
1917 | "decamelize": {
1918 | "version": "1.1.2",
1919 | "dependencies": {
1920 | "escape-string-regexp": {
1921 | "version": "1.0.4"
1922 | }
1923 | }
1924 | },
1925 | "os-locale": {
1926 | "version": "1.4.0",
1927 | "dependencies": {
1928 | "lcid": {
1929 | "version": "1.0.0",
1930 | "dependencies": {
1931 | "invert-kv": {
1932 | "version": "1.0.0"
1933 | }
1934 | }
1935 | }
1936 | }
1937 | },
1938 | "string-width": {
1939 | "version": "1.0.1",
1940 | "dependencies": {
1941 | "code-point-at": {
1942 | "version": "1.0.0",
1943 | "dependencies": {
1944 | "number-is-nan": {
1945 | "version": "1.0.0"
1946 | }
1947 | }
1948 | },
1949 | "is-fullwidth-code-point": {
1950 | "version": "1.0.0",
1951 | "dependencies": {
1952 | "number-is-nan": {
1953 | "version": "1.0.0"
1954 | }
1955 | }
1956 | },
1957 | "strip-ansi": {
1958 | "version": "3.0.0",
1959 | "dependencies": {
1960 | "ansi-regex": {
1961 | "version": "2.0.0"
1962 | }
1963 | }
1964 | }
1965 | }
1966 | },
1967 | "window-size": {
1968 | "version": "0.1.4"
1969 | },
1970 | "y18n": {
1971 | "version": "3.2.0"
1972 | }
1973 | }
1974 | }
1975 | }
1976 | },
1977 | "is-running": {
1978 | "version": "1.0.5"
1979 | },
1980 | "lucy-dirsum": {
1981 | "version": "https://github.com/mixmaxhq/lucy-dirsum/archive/08299b483cd0f79d18cd0fa1c5081dcab67c5649.tar.gz"
1982 | },
1983 | "mkdirp": {
1984 | "version": "0.5.1",
1985 | "dependencies": {
1986 | "minimist": {
1987 | "version": "0.0.8"
1988 | }
1989 | }
1990 | },
1991 | "ncp": {
1992 | "version": "2.0.0"
1993 | },
1994 | "rimraf": {
1995 | "version": "2.4.4",
1996 | "dependencies": {
1997 | "glob": {
1998 | "version": "5.0.15",
1999 | "dependencies": {
2000 | "inflight": {
2001 | "version": "1.0.4",
2002 | "dependencies": {
2003 | "wrappy": {
2004 | "version": "1.0.1"
2005 | }
2006 | }
2007 | },
2008 | "inherits": {
2009 | "version": "2.0.1"
2010 | },
2011 | "minimatch": {
2012 | "version": "3.0.0",
2013 | "dependencies": {
2014 | "brace-expansion": {
2015 | "version": "1.1.2",
2016 | "dependencies": {
2017 | "balanced-match": {
2018 | "version": "0.3.0"
2019 | },
2020 | "concat-map": {
2021 | "version": "0.0.1"
2022 | }
2023 | }
2024 | }
2025 | }
2026 | },
2027 | "once": {
2028 | "version": "1.3.3",
2029 | "dependencies": {
2030 | "wrappy": {
2031 | "version": "1.0.1"
2032 | }
2033 | }
2034 | },
2035 | "path-is-absolute": {
2036 | "version": "1.0.0"
2037 | }
2038 | }
2039 | }
2040 | }
2041 | },
2042 | "semver": {
2043 | "version": "5.1.0"
2044 | },
2045 | "url-join": {
2046 | "version": "0.0.1"
2047 | }
2048 | }
2049 | }
2050 |
--------------------------------------------------------------------------------
/.versions:
--------------------------------------------------------------------------------
1 | babel-compiler@5.8.24_1
2 | babel-runtime@0.1.4
3 | base64@1.0.4
4 | binary-heap@1.0.4
5 | blaze@2.1.3
6 | blaze-tools@1.0.4
7 | boilerplate-generator@1.0.4
8 | callback-hook@1.0.4
9 | check@1.1.0
10 | ddp@1.2.2
11 | ddp-client@1.2.1
12 | ddp-common@1.2.2
13 | ddp-server@1.2.2
14 | deps@1.0.9
15 | diff-sequence@1.0.1
16 | ecmascript@0.1.6
17 | ecmascript-runtime@0.2.6
18 | ejson@1.0.7
19 | geojson-utils@1.0.4
20 | html-tools@1.0.5
21 | htmljs@1.0.5
22 | id-map@1.0.4
23 | jquery@1.11.4
24 | local-test:meson:electron@0.1.3
25 | logging@1.0.8
26 | meson:electron@0.1.3
27 | meteor@1.1.10
28 | minimongo@1.0.10
29 | mongo@1.1.3
30 | mongo-id@1.0.1
31 | mongo-livedata@1.0.9
32 | npm-mongo@1.4.39_1
33 | observe-sequence@1.0.7
34 | ordered-dict@1.0.4
35 | promise@0.5.1
36 | random@1.0.5
37 | reactive-var@1.0.6
38 | retry@1.0.4
39 | routepolicy@1.0.6
40 | spacebars@1.0.7
41 | spacebars-compiler@1.0.7
42 | tinytest@1.0.6
43 | tracker@1.0.9
44 | ui@1.0.8
45 | underscore@1.0.4
46 | webapp@1.2.3
47 | webapp-hashing@1.0.5
48 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Michael Risse
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # meteor-electron
2 |
3 | meteor-electron lets you easily transform your Meteor webapp to a desktop app. Its ultimate goal is
4 | to build `meteor add-platform desktop`.
5 |
6 | Some of the things it does:
7 |
8 | * automatically builds and launches a desktop application, rebuilding when the native code changes
9 | * defines feature detection APIs and a bridge between web and native code
10 | * serves downloads of your application and update feeds
11 |
12 | 
13 |
14 | ## Getting Started
15 |
16 | `meteor add meson:electron`
17 |
18 | meteor-electron will download the Electron binary for your system and build and launch an Electron
19 | app pointing to your local development server. The download process may take a few minutes based on
20 | your Internet connection but only needs to be done once.
21 |
22 | The app, as well as the ready-to-distribute binaries (see [Deploy](#deploy)), is built within
23 | `YOUR_PROJECT_DIRECTORY/.meteor-electron`. This allows the apps to be easily located as well as the
24 | builds to be cached for speedier startup. You should add this directory to your `.gitignore`.
25 |
26 | ## Configuration
27 |
28 | Configuration is possible via `Meteor.settings.electron`. For example,
29 |
30 | ```json
31 | {
32 | "electron": {
33 | "name": "MyApp",
34 | "icon": {
35 | "darwin": "private/MyApp.icns",
36 | "win32": "private/MyApp.ico"
37 | },
38 | "version": "0.1.0",
39 | "description": "A really cool app.",
40 | "rootUrl": "https://myapp.com",
41 | "launchPath": "/app/landing",
42 | "downloadUrls": {
43 | "win32": "https://myapp.com/download/win/",
44 | "darwin": "https://myapp.com/download/osx/{{version}}/MyApp.zip"
45 | },
46 | "sign": "Developer ID Application: ...",
47 | "height": 768,
48 | "width": 1024,
49 | "frame": true,
50 | "title-bar-style": "hidden",
51 | "resizable": true,
52 | "protocols": [{
53 | "name": "MyApp",
54 | "schemes": ["myapp"]
55 | }],
56 | "appSrcDir": "private/app"
57 | }
58 | }
59 | ```
60 |
61 |
62 | - icon
63 | - platform dependent icon paths relative to application root
64 | - version
65 | - must confirm to semver
66 | - rootUrl
67 | - If unset, defaults to the `APP_ROOT_URL` and then `ROOT_URL` environment variables, in that order.
68 | - launchPath
69 | - If you want your app to open to a non-root URL. Will be appended to the root URL.
70 | - downloadUrls
71 | - URLs from which downloads are served. A CDN is recommended, but any HTTP server will do.
72 | - downloadUrls.win32
-
73 |
- Copy the output of `grunt-electron-installer` (see Building and serving an auto-updating Windows app) to this location. Do not rename the files. If you wish to host the Windows
74 | installers at versioned URLs for caching or archival reasons, specify this as an object with the
75 | following keys.
76 | - downloadUrls.win32.releases
77 | - Copy the output of `grunt-electron-installer` (see Building and serving an auto-updating Windows app) to this location. Do not rename the files.
78 | - downloadUrls.win32.installer
79 | - If you like, you may copy the `Setup.exe` file created by `grunt-electron-installer` to this
80 | location rather than the "releases" location. If the URL contains '{{version}}', it will be
81 | replaced with `version`.
82 | - downloadUrls.darwin
83 | - Place the latest app at this location. If the URL contains '{{version}}', it will be replaced
84 | with `version`.
85 | - sign
86 | - Must be set to enable auto-updates on Mac.
87 | - appSrcDir
88 | - A directory of code to use instead of meteor-electron's default application, relative to your
89 | app's project directory. See warning below.
90 |
91 |
92 | ## Electron-specific code
93 |
94 | By default, all client web code will be executed in Electron. To include/exclude code use `Electron.isDesktop`
95 |
96 | ```javascript
97 | if (!Electron.isDesktop()){
98 | showModal("Have you considered downloading our Electron app?");
99 | }
100 | ```
101 |
102 | ## Deploying
103 |
104 | Hot code push will work to update your app's UI just like it does on the web, since the app is loading the UI
105 | _from_ the web. If you want to update the part of the app that interfaces with the OS, though—to change
106 | the app icon, to add a menu bar icon, etc.—you'll need to distribute a new version of the `.app` or
107 | `.exe`. Here's how to do that.
108 |
109 | ### Building and serving an auto-updating Mac app
110 |
111 | 1. Set `Meteor.settings.electron.autoPackage` to `true` to ZIP your app for distribution after it is
112 | built.
113 | 2. If you wish to enable remote updates, you will need to codesign your application. This requires
114 | that you build your app on a Mac with a [Developer ID certificate](https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/AppDistributionGuide/DistributingApplicationsOutside/DistributingApplicationsOutside.html) installed.
115 | Set `Meteor.settings.electron.sign` to the name of that certificate.
116 | 3. Wait for the app to finish building and packaging, then copy
117 | `YOUR_PROJECT_DIRECTORY/.meteor-electron/darwin-x64/final/YOUR_APP_NAME.zip` to a publically-accessible
118 | location.
119 | 4. Set `downloadUrls.darwin` in `Meteor.settings.electron` to the URL of the location where you copied the ZIP.
120 |
121 | Downloads of the Mac app will be served at your webapp's `ROOT_URL` + `/app/download?platform=darwin`.
122 |
123 | ### Building and serving an auto-updating Windows app
124 |
125 | 0. Make sure that you have specified `name`, `version`, and `description` in `Meteor.settings.electron`.
126 | 1. Build the app [on a Mac](#building-for-windows-on-mac), because changing a Windows application icon
127 | [does not work on Windows at present](https://github.com/maxogden/electron-packager/issues/53).
128 | 2. Ensure the URL specified by `Meteor.settings.electron.downloadUrls.win32` has an empty `RELEASES` file.
129 | 2. On a Windows machine or in a Windows VM ([not a Mac, at present](https://github.com/atom/grunt-electron-installer/issues/90)),
130 | run the [electron installer grunt plugin](https://github.com/atom/grunt-electron-installer) against your app.
131 | Your Gruntfile should look something like https://github.com/rissem/meteor-electron-test/tree/master/.test.
132 | The value of `remoteReleases` should be your webapp's `ROOT_URL` + '/app/latest'.
133 | 3. Copy the output to the server serving `Meteor.settings.electron.downloadUrls.win32`, to be served
134 | from that location.
135 | 4. When you publish a new update, run the installer again and it will generate diffs, a new `RELEASES` file,
136 | and new installers. After copying these to `Meteor.settings.electron.downloadUrls.win32` again (overwriting
137 | the `RELEASES` file and installers), apps that check for updates should receive a new version.
138 |
139 | Downloads of the Windows installer will be served at your webapp's `ROOT_URL` + `/app/download?platform=win32`.
140 |
141 | ## Building for Windows on Mac
142 |
143 | 1. Install [homebrew](http://brew.sh/)
144 | 2. `brew update`
145 | 3. `brew install wine`
146 | 4. Specify a Windows build in your settings (otherwise defaults to current platform (mac)).
147 |
148 | ```json
149 | {
150 | "electron": {
151 | "builds": [
152 | {"platform": "win32",
153 | "arch": "ia32"}
154 | ]
155 | }
156 | }
157 | ```
158 |
159 | ## Example
160 |
161 | [TODO] Link to an awesome chat app
162 |
163 | ## Q&A
164 |
165 | ### Q: How is this different from all the other Meteor electron packages?
166 |
167 | This package differs from [Electrometeor](https://github.com/sircharleswatson/Electrometeor) and
168 | [Electrify](https://github.com/arboleya/electrify) by *not* baking Meteor into the packaged app.
169 | This makes things significantly simpler, but if you need strong offline support, one of them is a
170 | better solution.
171 |
172 | ### Q: How can I create new browser windows, set app notifications and all the other awesome native functionality that Electron gives me?
173 |
174 | This project selectively exposes such functionality to the client, in a way that is safe and avoids
175 | memory leaks, via the `Electron` module--see [`client.js`](client.js). To request that this module
176 | expose additional functionality, please [submit a pull request](https://github.com/rissem/meteor-electron/pull/new/master)
177 | or [file an issue](https://github.com/rissem/meteor-electron/issues/new).
178 |
179 | You may also substitute your own application code for `meteor-electron`'s default application by
180 | setting the `appSrcDir` settings option. `meteor-electron` will continue to package your application
181 | and serve the application update feed and download URLs, but in-app functionality will be your
182 | responsibility. **Warning**: this responsibility includes setting up your application window and menu,
183 | checking for remote updates, registering the `Electron` module (that defines `Electron.isDesktop`),
184 | and possibly other things. If you take this route, it's recommended that you start by copying
185 | `meteor-electron`'s `app` directory.
186 |
187 | Also, you also probably want to keep your application code in a subdirectory of your application's
188 | `private` directory so that Meteor will observe changes to it and restart the server; when it does
189 | so, `meteor-electron` will rebuild and relaunch the app.
190 |
191 | ### Q: How do I prevent the Electron app from being automatically built and launched?
192 |
193 | Set `Meteor.settings.electron.autoBuild` to `"false"`.
194 |
--------------------------------------------------------------------------------
/app/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | // If you are using SublimeLinter, after modifying this config file, be sure to close and reopen
3 | // the file(s) you were editing to see the changes take effect.
4 |
5 | "extends": "../.jshintrc",
6 |
7 | // Whitelist Node's global variables so JSHint doesn't complain about them being undefined.
8 | "node": true,
9 |
10 | // Whitelist additional global variables.
11 | "globals": {
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/autoUpdater.js:
--------------------------------------------------------------------------------
1 | var _ = require('underscore');
2 | var app = require('electron').app;
3 | var autoUpdater = require('electron').autoUpdater;
4 | var dialog = require('electron').dialog;
5 |
6 | // Daily.
7 | var SCHEDULED_CHECK_INTERVAL = 24 * 60 * 60 * 1000;
8 |
9 | var Updater = function() {
10 | autoUpdater.on('error', this._onUpdateError.bind(this));
11 | autoUpdater.on('update-not-available', this._onUpdateNotAvailable.bind(this));
12 | autoUpdater.on('update-downloaded', this._onUpdateDownloaded.bind(this));
13 | };
14 |
15 | _.extend(Updater.prototype, {
16 | setFeedURL: function(url) {
17 | autoUpdater.setFeedURL(url);
18 | },
19 |
20 | checkForUpdates: function(userTriggered /* optional */) {
21 | // Asking the updater to check while it's already checking may result in an error.
22 | if (this._checkPending) return;
23 |
24 | this._clearScheduledCheck();
25 | if (this._updatePending) {
26 | this._askToApplyUpdate();
27 | return;
28 | }
29 |
30 | this._checkPending = true;
31 | if (userTriggered) this._userCheckPending = true;
32 |
33 | autoUpdater.checkForUpdates();
34 | },
35 |
36 | _onUpdateError: function() {
37 | this._checkPending = false;
38 | if (this._userCheckPending) {
39 | this._userCheckPending = false;
40 |
41 | dialog.showMessageBox({
42 | type: 'error',
43 | message: 'An error occurred while checking for updates.',
44 | buttons: ['Ok']
45 | });
46 | }
47 |
48 | this._scheduleCheck();
49 | },
50 |
51 | _onUpdateNotAvailable: function() {
52 | this._checkPending = false;
53 | if (this._userCheckPending) {
54 | this._userCheckPending = false;
55 |
56 | dialog.showMessageBox({
57 | type: 'info',
58 | message: 'An update is not available.',
59 | buttons: ['Ok']
60 | });
61 | }
62 |
63 | this._scheduleCheck();
64 | },
65 |
66 | _onUpdateDownloaded: function() {
67 | this._checkPending = false;
68 | this._userCheckPending = false;
69 | this._updatePending = true;
70 | this._askToApplyUpdate();
71 | },
72 |
73 | _askToApplyUpdate: function() {
74 | var self = this;
75 |
76 | dialog.showMessageBox({
77 | type: 'question',
78 | message: 'An update is available! Would you like to quit to install it? The application will then restart.',
79 | buttons: ['Ask me later', 'Quit and install']
80 | }, function(result) {
81 | if (result > 0) {
82 | // Emit the 'before-quit' event since the app won't quit otherwise
83 | // (https://app.asana.com/0/19141607276671/74169390751974) and the app won't:
84 | // https://github.com/atom/electron/issues/3837
85 | var event = {
86 | _defaultPrevented: false,
87 | isDefaultPrevented: function() {
88 | return this._defaultPrevented;
89 | },
90 | preventDefault: function() {
91 | this._defaultPrevented = true;
92 | }
93 | };
94 |
95 | app.emit('before-quit', event);
96 | if (event.isDefaultPrevented()) return;
97 |
98 | autoUpdater.quitAndInstall();
99 | } else {
100 | self._scheduleCheck();
101 | }
102 | });
103 | },
104 |
105 | _clearScheduledCheck: function() {
106 | if (this._scheduledCheck) {
107 | clearTimeout(this._scheduledCheck);
108 | this._scheduledCheck = null;
109 | }
110 | },
111 |
112 | _scheduleCheck: function() {
113 | this._clearScheduledCheck();
114 | this._scheduledCheck = setTimeout(this.checkForUpdates.bind(this), SCHEDULED_CHECK_INTERVAL);
115 | }
116 | });
117 |
118 | module.exports = new Updater();
119 |
--------------------------------------------------------------------------------
/app/main.js:
--------------------------------------------------------------------------------
1 | var app = require('electron').app; // Module to control application life.
2 | var childProcess = require("child_process");
3 | var path = require("path");
4 | var fs = require("fs");
5 |
6 | // var log = function(msg){
7 | // fs.appendFile("C:\\Users\\Michael\\electron.log", msg + "\n", function(err){
8 | // if (err){
9 | // throw err;
10 | // }
11 | // })
12 | // };
13 |
14 | var log = function(){};
15 |
16 | var installShortcut = function(callback){
17 | var updateDotExe = path.resolve(path.dirname(process.execPath), '..', 'update.exe');
18 | var child = childProcess.spawn(updateDotExe, ["--createShortcut", "mixmax.exe"], { detached: true });
19 | child.on('close', function(code) {
20 | callback();
21 | });
22 | };
23 |
24 | var handleStartupEvent = function() {
25 | if (process.platform !== 'win32') {
26 | return false;
27 | }
28 |
29 | var squirrelCommand = process.argv[1];
30 | switch (squirrelCommand) {
31 | case '--squirrel-install':
32 | log("SQUIRREL INSTALL");
33 |
34 | case '--squirrel-updated':
35 | log("SQUIRREL UPDATED");
36 | // Optionally do things such as:
37 | //
38 | // - Install desktop and start menu shortcuts
39 | // - Add your .exe to the PATH
40 | // - Write to the registry for things like file associations and
41 | // explorer context menus
42 |
43 | // Always quit when done
44 | installShortcut(function(){
45 | app.quit();
46 | })
47 |
48 | return true;
49 | case '--squirrel-uninstall':
50 | log("SQUIRREL UNINSTALL");
51 |
52 | // Undo anything you did in the --squirrel-install and
53 | // --squirrel-updated handlers
54 |
55 | // Always quit when done
56 | app.quit();
57 |
58 | return true;
59 | case '--squirrel-obsolete':
60 | log("SQUIRREL OBSOLETE");
61 | // This is called on the outgoing version of your app before
62 | // we update to the new version - it's the opposite of
63 | // --squirrel-updated
64 | app.quit();
65 | return true;
66 | }
67 | };
68 |
69 | app.on("window-all-closed", function(){
70 | if (process.platform !== "darwin"){
71 | app.quit();
72 | }
73 | })
74 |
75 | if (handleStartupEvent()) {
76 | return;
77 | }
78 |
79 | var BrowserWindow = require('electron').BrowserWindow; // Module to create native browser window.
80 | var autoUpdater = require('./autoUpdater');
81 | var path = require("path");
82 | var fs = require("fs");
83 | var createDefaultMenu = require('./menu.js');
84 | var proxyWindowEvents = require('./proxyWindowEvents');
85 |
86 | require('electron-debug')({
87 | showDevTools: false
88 | });
89 |
90 | var electronSettings = JSON.parse(fs.readFileSync(
91 | path.join(__dirname, "electronSettings.json"), "utf-8"));
92 |
93 | var checkForUpdates;
94 | if (electronSettings.updateFeedUrl) {
95 | autoUpdater.setFeedURL(electronSettings.updateFeedUrl + '?version=' + electronSettings.version);
96 | autoUpdater.checkForUpdates();
97 | checkForUpdates = function() {
98 | autoUpdater.checkForUpdates(true /* userTriggered */);
99 | };
100 | }
101 |
102 | var launchUrl = electronSettings.rootUrl;
103 | if (electronSettings.launchPath) {
104 | launchUrl += electronSettings.launchPath;
105 | }
106 |
107 | var windowOptions = {
108 | width: electronSettings.width || 800,
109 | height: electronSettings.height || 600,
110 | resizable: true,
111 | frame: true,
112 | /**
113 | * Disable Electron's Node integration so that browser dependencies like `moment` will load themselves
114 | * like normal i.e. into the window rather than into modules, and also to prevent untrusted client
115 | * code from having access to the process and file system:
116 | * - https://github.com/atom/electron/issues/254
117 | * - https://github.com/atom/electron/issues/1753
118 | */
119 | webPreferences: {
120 | nodeIntegration: false,
121 | // See comments at the top of `preload.js`.
122 | preload: path.join(__dirname, 'preload.js')
123 | }
124 | };
125 |
126 | if (electronSettings.resizable === false){
127 | windowOptions.resizable = false;
128 | }
129 |
130 | if (electronSettings['title-bar-style']) {
131 | windowOptions['title-bar-style'] = electronSettings['title-bar-style'];
132 | }
133 |
134 | if (electronSettings.minWidth) {
135 | windowOptions.minWidth = electronSettings.minWidth;
136 | }
137 |
138 | if (electronSettings.maxWidth) {
139 | windowOptions.maxWidth = electronSettings.maxWidth;
140 | }
141 |
142 | if (electronSettings.minHeight) {
143 | windowOptions.minHeight = electronSettings.minHeight;
144 | }
145 |
146 | if (electronSettings.maxHeight) {
147 | windowOptions.maxHeight = electronSettings.maxHeight;
148 | }
149 |
150 | if (electronSettings.frame === false){
151 | windowOptions.frame = false;
152 | }
153 |
154 | // Keep a global reference of the window object so that it won't be garbage collected
155 | // and the window closed.
156 | var mainWindow = null;
157 | var getMainWindow = function() {
158 | return mainWindow;
159 | };
160 |
161 | // Unfortunately, we must set the menu before the application becomes ready and so before the main
162 | // window is available to be passed directly to `createDefaultMenu`.
163 | createDefaultMenu(app, getMainWindow, checkForUpdates);
164 |
165 | app.on("ready", function(){
166 | mainWindow = new BrowserWindow(windowOptions);
167 | proxyWindowEvents(mainWindow);
168 |
169 | // Hide the main window instead of closing it, so that we can bring it back
170 | // more quickly.
171 | mainWindow.on('close', hideInsteadofClose);
172 |
173 | mainWindow.focus();
174 | mainWindow.loadURL(launchUrl);
175 | });
176 |
177 | var hideInsteadofClose = function(e) {
178 | mainWindow.hide();
179 | e.preventDefault();
180 | };
181 |
182 | app.on("before-quit", function(){
183 | // We need to remove our close event handler from the main window,
184 | // otherwise the app will not quit.
185 | mainWindow.removeListener('close', hideInsteadofClose);
186 | });
187 |
188 | app.on("activate", function(){
189 | // Show the main window when the customer clicks on the app icon.
190 | if (!mainWindow.isVisible()) mainWindow.show();
191 | });
192 |
--------------------------------------------------------------------------------
/app/menu.js:
--------------------------------------------------------------------------------
1 | var BrowserWindow = require('browser-window');
2 | var Menu = require('menu');
3 |
4 | /**
5 | * Creates a default menu. Modeled after https://github.com/atom/electron/pull/1863, augmented with
6 | * the roles from https://github.com/atom/electron/blob/master/docs/api/menu.md.
7 | */
8 | var createDefaultMenu = function(app, getMainWindow, checkForUpdates) {
9 | app.once('ready', function() {
10 | var template;
11 | if (process.platform == 'darwin') {
12 | template = [
13 | {
14 | label: app.getName(),
15 | submenu: [
16 | {
17 | label: 'About ' + app.getName(),
18 | role: 'about',
19 | },
20 | {
21 | type: 'separator'
22 | },
23 | {
24 | label: 'Services',
25 | role: 'services',
26 | submenu: []
27 | },
28 | {
29 | type: 'separator'
30 | },
31 | {
32 | label: 'Hide ' + app.getName(),
33 | accelerator: 'Command+H',
34 | role: 'hide'
35 | },
36 | {
37 | label: 'Hide Others',
38 | accelerator: 'Command+Shift+H',
39 | role: 'hideothers'
40 | },
41 | {
42 | label: 'Show All',
43 | role: 'unhide'
44 | },
45 | {
46 | type: 'separator'
47 | },
48 | {
49 | label: 'Quit',
50 | accelerator: 'Command+Q',
51 | click: function() { app.quit(); }
52 | },
53 | ]
54 | },
55 | {
56 | label: 'File',
57 | submenu: [
58 | {
59 | label: 'Refresh',
60 | accelerator: 'Command+R',
61 | click: function() {
62 | var focusedWindow = BrowserWindow.getFocusedWindow();
63 | if (focusedWindow) {
64 | focusedWindow.reload();
65 | }
66 | }
67 | },
68 | {
69 | label: 'Close',
70 | accelerator: 'Command+W',
71 | role: 'close'
72 | }
73 | ]
74 | },
75 | {
76 | label: 'Edit',
77 | submenu: [
78 | {
79 | label: 'Undo',
80 | accelerator: 'Command+Z',
81 | role: 'undo'
82 | },
83 | {
84 | label: 'Redo',
85 | accelerator: 'Shift+Command+Z',
86 | role: 'redo'
87 | },
88 | {
89 | type: 'separator'
90 | },
91 | {
92 | label: 'Cut',
93 | accelerator: 'Command+X',
94 | role: 'cut'
95 | },
96 | {
97 | label: 'Copy',
98 | accelerator: 'Command+C',
99 | role: 'copy'
100 | },
101 | {
102 | label: 'Paste',
103 | accelerator: 'Command+V',
104 | role: 'paste'
105 | },
106 | {
107 | label: 'Select All',
108 | accelerator: 'Command+A',
109 | role: 'selectall'
110 | },
111 | ]
112 | },
113 | {
114 | label: 'Window',
115 | submenu: [
116 | {
117 | label: 'Minimize',
118 | accelerator: 'Command+M',
119 | role: 'minimize'
120 | },
121 | {
122 | label: 'Toggle Full Screen',
123 | accelerator: 'Ctrl+Command+F',
124 | click: function() {
125 | var focusedWindow = BrowserWindow.getFocusedWindow();
126 | if (focusedWindow) {
127 | focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
128 | }
129 | }
130 | },
131 | {
132 | type: 'separator'
133 | },
134 | {
135 | label: 'Main Window',
136 | accelerator: 'Command+1',
137 | click: function() {
138 | var mainWindow = getMainWindow();
139 | if (mainWindow) {
140 | mainWindow.show();
141 | }
142 | }
143 | },
144 | {
145 | type: 'separator'
146 | },
147 | {
148 | label: 'Bring All to Front',
149 | role: 'front'
150 | },
151 | ]
152 | }
153 | ];
154 |
155 | if (checkForUpdates) {
156 | // Add 'Check for Updates' below the 'About' menu item.
157 | template[0].submenu.splice(1, 0, {
158 | label: 'Check for Updates',
159 | click: checkForUpdates
160 | });
161 | }
162 | } else {
163 | template = [
164 | {
165 | label: '&File',
166 | submenu: [
167 | {
168 | label: '&Open',
169 | accelerator: 'Ctrl+O',
170 | },
171 | {
172 | label: '&Refresh',
173 | accelerator: 'Ctrl+R',
174 | click: function() {
175 | var focusedWindow = BrowserWindow.getFocusedWindow();
176 | if (focusedWindow) {
177 | focusedWindow.reload();
178 | }
179 | }
180 | },
181 | {
182 | label: '&Close',
183 | accelerator: 'Ctrl+W',
184 | click: function() {
185 | var focusedWindow = BrowserWindow.getFocusedWindow();
186 | if (focusedWindow) {
187 | focusedWindow.close();
188 | }
189 | }
190 | },
191 | ]
192 | },
193 | {
194 | label: '&Window',
195 | submenu: [
196 | {
197 | label: 'Toggle &Full Screen',
198 | accelerator: 'F11',
199 | click: function() {
200 | var focusedWindow = BrowserWindow.getFocusedWindow();
201 | if (focusedWindow) {
202 | focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
203 | }
204 | }
205 | }
206 | ]
207 | }
208 | ];
209 |
210 | if (checkForUpdates) {
211 | // Add a separator and 'Check for Updates' at the bottom of the 'File' menu.
212 | template[0].submenu.push({
213 | type: 'separator'
214 | }, {
215 | label: '&Check for Updates',
216 | click: checkForUpdates
217 | });
218 | }
219 | }
220 |
221 | var menu = Menu.buildFromTemplate(template);
222 | Menu.setApplicationMenu(menu);
223 | });
224 | };
225 |
226 | module.exports = createDefaultMenu;
227 |
--------------------------------------------------------------------------------
/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "electron",
3 | "productName": "Electron",
4 | "main": "main.js",
5 | "dependencies": {
6 | "electron-debug": "^0.5.1",
7 | "underscore": "^1.8.3"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/app/preload.js:
--------------------------------------------------------------------------------
1 | /* global ElectronImplementation:true */
2 |
3 | /**
4 | * Since we've disabled Node integration in the browser window, we must selectively expose
5 | * main-process/Node modules via this script.
6 | *
7 | * @WARNING This file must take care not to leak the imported modules to the browser window!
8 | * In particular, do not save the following variables as properties of `ElectronImplementation`.
9 | * See https://github.com/atom/electron/issues/1753#issuecomment-104719851.
10 | */
11 | var _ = require('underscore');
12 | var ipc = require('electron').ipcRenderer;
13 | var remote = require('electron').remote;
14 | var shell = require('electron').shell;
15 |
16 | /**
17 | * Defines methods with which to extend the `Electron` module defined in `client.js`.
18 | * This must be a global in order to escape the preload script and be available to `client.js`.
19 | */
20 | ElectronImplementation = {
21 | /**
22 | * Open the given external protocol URL in the desktop's default manner. (For example, http(s):
23 | * URLs in the user's default browser.)
24 | *
25 | * @param {String} url - The URL to open.
26 | */
27 | openExternal: shell.openExternal,
28 |
29 | /**
30 | * Determines if the browser window is currently in fullscreen mode.
31 | *
32 | * "Fullscreen" here refers to the state triggered by toggling the native controls, not that
33 | * toggled by the HTML API.
34 | *
35 | * To detect when the browser window changes fullscreen state, observe the 'enter-full-screen'
36 | * and 'leave-full-screen' events using `onWindowEvent`.
37 | *
38 | * @return {Boolean} `true` if the browser window is in fullscreen mode, `false` otherwise.
39 | */
40 | isFullScreen: function() {
41 | return remote.getCurrentWindow().isFullScreen();
42 | },
43 |
44 | /**
45 | * Invokes _callback_ when the specified `BrowserWindow` event is fired.
46 | *
47 | * This differs from `onEvent` in that it directs Electron to start emitting the relevant window
48 | * event.
49 | *
50 | * See https://github.com/atom/electron/blob/master/docs/api/browser-window.md#events for a list
51 | * of events.
52 | *
53 | * The implementation of this API, in particular the use of the `ipc` vs. `remote` modules, is
54 | * designed to avoid memory leaks as described by
55 | * https://github.com/atom/electron/blob/master/docs/api/remote.md#passing-callbacks-to-the-main-process.
56 | *
57 | * @param {String} event - The name of a `BrowserWindow` event.
58 | * @param {Function} callback - A function to invoke when `event` is triggered. Takes no arguments
59 | * and returns no value.
60 | */
61 | onWindowEvent: function(event, callback) {
62 | this.onEvent(event, callback);
63 | ipc.send('observe-window-event', event);
64 | },
65 |
66 | /**
67 | * Invokes _callback_ when the specified IPC event is fired.
68 | *
69 | * @param {String} event - The name of an event.
70 | * @param {Function} callback - A function to invoke when `event` is triggered. Takes no arguments
71 | * and returns no value.
72 | */
73 | onEvent: function(event, callback) {
74 | var listeners = this._eventListeners[event];
75 | if (!listeners) {
76 | listeners = this._eventListeners[event] = [];
77 | ipc.on(event, function() {
78 | _.invoke(listeners, 'call');
79 | });
80 | }
81 | listeners.push(callback);
82 | },
83 |
84 | _eventListeners: {}
85 | };
86 |
--------------------------------------------------------------------------------
/app/proxyWindowEvents.js:
--------------------------------------------------------------------------------
1 | var _ = require('underscore');
2 | var ipc = require('ipc-main');
3 |
4 | /**
5 | * Proxies `BrowserWindow` events to renderer processes as directed by those processes and in a way
6 | * that avoids memory leaks.
7 | *
8 | * For each event that the renderer process wishes to observe, it should send the 'onWindowEvent'
9 | * message with the event name as argument:
10 | *
11 | * require('electron').ipcRenderer.send('onWindowEvent', 'enter-full-screen')
12 | *
13 | * The renderer process will then receive the 'triggerWindowEvent' message when the event occurs:
14 | *
15 | * require('electron').ipcRenderer.on('triggerWindowEvent', function(event, arg) {
16 | * console.log(arg); // prints 'enter-full-screen'
17 | * });
18 | *
19 | * This module, in particular the use of the `ipc` vs. `remote` module, is motivated by
20 | * https://github.com/atom/electron/blob/master/docs/api/remote.md#passing-callbacks-to-the-main-process.
21 | *
22 | * @param {BrowserWindow} window - The window whose events to proxy.
23 | */
24 | var proxyWindowEvents = function(window) {
25 | var eventsObserved = {};
26 |
27 | ipc.on('observe-window-event', function(event, arg) {
28 | if ((event.sender === window.webContents) && !eventsObserved[arg]) {
29 | eventsObserved[arg] = function() {
30 | window.webContents.send(arg);
31 | };
32 | window.on(arg, eventsObserved[arg]);
33 | }
34 | });
35 |
36 | // Clear our listeners when the page starts (re)loading i.e. its listeners have been purged.
37 | // TODO(wearhere): I'm not sure this is the right event for reload but it seems to work.
38 | window.webContents.on('did-start-loading', function() {
39 | _.each(eventsObserved, function(listener, event) {
40 | window.removeListener(event, listener);
41 | });
42 | eventsObserved = {};
43 | });
44 | };
45 |
46 | module.exports = proxyWindowEvents;
47 |
--------------------------------------------------------------------------------
/client/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | // If you are using SublimeLinter, after modifying this config file, be sure to close and reopen
3 | // the file(s) you were editing to see the changes take effect.
4 |
5 | "extends": "../.jshintrc",
6 |
7 | // Whitelist the browser's global variables so JSHint doesn't complain about them being undefined.
8 | "browser": true,
9 |
10 | // Whitelist additional global variables.
11 | "globals": {
12 | // Globals set by `preload.js`.
13 | "ElectronImplementation": false,
14 |
15 | // Meteor's globals.
16 | "Meteor": false,
17 |
18 | // Our dependencies.
19 | "_": false,
20 |
21 | // Our exports.
22 | "Electron": true
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/client/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Defines useful client-side functionality.
3 | */
4 | Electron = {
5 | /**
6 | * @return {Boolean} `true` if the app is running in Electron, `false` otherwise.
7 | */
8 | isDesktop: function(){
9 | return /Electron/.test(navigator.userAgent);
10 | },
11 |
12 | /**
13 | * @return {Boolean} `true` if the app is running in Windows, `false` otherwise.
14 | */
15 | isWindows: function(){
16 | return /Windows NT/.test(navigator.userAgent);
17 | },
18 |
19 |
20 |
21 | // When the app is running in Electron, the following methods will be implemented by `preload.js`.
22 | // Stub them out in case the client tries to call them even when not running in Electron.
23 |
24 | /**
25 | * Open the given external protocol URL in the desktop's default manner. (For example, http(s):
26 | * URLs in the user's default browser.)
27 | *
28 | * @param {String} url - The URL to open.
29 | */
30 | openExternal: function() {},
31 |
32 | /**
33 | * Determines if the browser window is currently in fullscreen mode.
34 | *
35 | * "Fullscreen" here refers to the state triggered by toggling the native controls, not that
36 | * toggled by the HTML API.
37 | *
38 | * To detect when the browser window changes fullscreen state, observe the 'enter-full-screen'
39 | * and 'leave-full-screen' events using `onWindowEvent`.
40 | *
41 | * @return {Boolean} `true` if the browser window is in fullscreen mode, `false` otherwise.
42 | */
43 | isFullScreen: function() {},
44 |
45 | /**
46 | * Invokes _callback_ when the specified `BrowserWindow` event is fired.
47 | *
48 | * See https://github.com/atom/electron/blob/master/docs/api/browser-window.md#events for a list
49 | * of events.
50 | *
51 | * @param {String} event - The name of a `BrowserWindow` event.
52 | * @param {Function} callback - A function to invoke when `event` is triggered. Takes no arguments
53 | * and returns no value.
54 | */
55 | onWindowEvent: function() {}
56 | };
57 |
58 | // Read `ElectronImplementation` from the window vs. doing `typeof ElectronImplementation` because
59 | // Meteor will shadow it with a local variable in the latter case.
60 | if (!_.isUndefined(window.ElectronImplementation)) {
61 | // The app is running in Electron. Merge the implementations from `preload.js`.
62 | _.extend(Electron, window.ElectronImplementation);
63 | }
64 |
--------------------------------------------------------------------------------
/docs/overview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/electron-webapps/meteor-electron/5e2faaed015c7a8b14d1d25bde259a736cb5de74/docs/overview.png
--------------------------------------------------------------------------------
/package.js:
--------------------------------------------------------------------------------
1 | /* global Package:false, Npm:false */
2 |
3 | Package.describe({
4 | name: 'meson:electron',
5 | summary: "Electron",
6 | version: "0.1.4",
7 | git: "https://github.com/electron-webapps/meteor-electron"
8 | });
9 |
10 | Npm.depends({
11 | "electron-packager": "https://github.com/mixmaxhq/electron-packager/archive/f511e2680efa39c014d8bedca872168e585f8daf.tar.gz",
12 | "is-running": "1.0.5",
13 | "lucy-dirsum": "https://github.com/mixmaxhq/lucy-dirsum/archive/08299b483cd0f79d18cd0fa1c5081dcab67c5649.tar.gz",
14 | "mkdirp": "0.5.1",
15 | "ncp": "2.0.0",
16 | "rimraf": "2.4.4",
17 | "semver": "5.1.0",
18 | "url-join": "0.0.1",
19 | "electron-rebuild": "1.0.1"
20 | });
21 |
22 | Package.onUse(function (api) {
23 | api.versionsFrom("METEOR@1.0");
24 | api.use(["mongo-livedata", "webapp", "ejson", "promise@0.6.7"], "server");
25 | api.use("underscore", ["server", "client"]);
26 | api.use(["iron:router@0.9.4||1.0.0"], {weak: true});
27 | api.use("meteorhacks:picker@1.0.0", "server", {weak: true});
28 |
29 | api.addFiles([
30 | 'server/createBinaries.js',
31 | 'server/downloadUrls.js',
32 | 'server/launchApp.js',
33 | 'server/serve.js',
34 | 'server/serveDownloadUrl.js',
35 | 'server/serveUpdateFeed.js',
36 | // Must go last so that its dependencies have been defined.
37 | 'server/index.js'
38 | ], 'server');
39 |
40 | var assets = [
41 | "app/autoUpdater.js",
42 | "app/main.js",
43 | "app/menu.js",
44 | "app/package.json",
45 | "app/preload.js",
46 | "app/proxyWindowEvents.js"
47 | ];
48 |
49 | // Use Meteor 1.2+ API, but fall back to the pre-1.2 API if necessary
50 | if (api.addAssets) {
51 | api.addAssets(assets, "server");
52 | } else {
53 | api.addFiles(assets, "server", {isAsset: true});
54 | }
55 |
56 | api.addFiles(['client/index.js'], "client");
57 |
58 | // Test exports.
59 | api.export([
60 | 'parseMacDownloadUrl',
61 | 'parseWindowsDownloadUrls'
62 | ], 'server', {
63 | testOnly: true
64 | });
65 |
66 | // Public exports.
67 | api.export("Electron", ["client"]);
68 | });
69 |
70 | Package.onTest(function(api) {
71 | api.use(['meson:electron', 'tinytest']);
72 |
73 | api.addFiles('tests/server/downloadUrlsTest.js', 'server');
74 | });
75 |
--------------------------------------------------------------------------------
/server/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | // If you are using SublimeLinter, after modifying this config file, be sure to close and reopen
3 | // the file(s) you were editing to see the changes take effect.
4 |
5 | "extends": "../.jshintrc",
6 |
7 | // Whitelist Node's global variables so JSHint doesn't complain about them being undefined.
8 | "node": true,
9 |
10 | // Whitelist additional global variables.
11 | "globals": {
12 | // Meteor's globals.
13 | "Assets": false,
14 | "Meteor": false,
15 | "Npm": false,
16 | "Package": false,
17 | "Promise": false,
18 |
19 | // Our dependencies.
20 | "_": false,
21 | "Mongo": false,
22 | "WebApp": false,
23 |
24 | // Global functions.
25 | "canServeUpdates": true,
26 | "createBinaries": true,
27 | "launchApp": true,
28 | "parseMacDownloadUrl": true,
29 | "parseWindowsDownloadUrls": true,
30 | "serve": true,
31 | "serveDir": true,
32 | "serveDownloadUrl": true,
33 | "serveUpdateFeed": true,
34 |
35 | // Global variables.
36 | "DOWNLOAD_URLS": true,
37 | "UPDATE_FEED_PATH": true
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/server/createBinaries.js:
--------------------------------------------------------------------------------
1 | var electronPackager = Meteor.wrapAsync(Npm.require("electron-packager"));
2 | var electronRebuild = Npm.require('electron-rebuild');
3 | var fs = Npm.require('fs');
4 | var mkdirp = Meteor.wrapAsync(Npm.require('mkdirp'));
5 | var path = Npm.require('path');
6 | var proc = Npm.require('child_process');
7 | var dirsum = Meteor.wrapAsync(Npm.require('lucy-dirsum'));
8 | var readFile = Meteor.wrapAsync(fs.readFile);
9 | var writeFile = Meteor.wrapAsync(fs.writeFile);
10 | var stat = Meteor.wrapAsync(fs.stat);
11 | var util = Npm.require('util');
12 | var rimraf = Meteor.wrapAsync(Npm.require('rimraf'));
13 | var ncp = Meteor.wrapAsync(Npm.require('ncp'));
14 |
15 | var exec = Meteor.wrapAsync(function(command, options, callback){
16 | proc.exec(command, options, function(err, stdout, stderr){
17 | callback(err, {stdout: stdout, stderr: stderr});
18 | });
19 | });
20 |
21 | var exists = function(path) {
22 | try {
23 | stat(path);
24 | return true;
25 | } catch(e) {
26 | return false;
27 | }
28 | };
29 |
30 | var projectRoot = function(){
31 | if (process.platform === "win32"){
32 | return process.env.METEOR_SHELL_DIR.split(".meteor")[0];
33 | } else {
34 | return process.env.PWD;
35 | }
36 | };
37 |
38 | var ELECTRON_VERSION = '0.36.7';
39 |
40 | var electronSettings = Meteor.settings.electron || {};
41 |
42 | var IS_MAC = (process.platform === 'darwin');
43 |
44 | /* Entry Point */
45 | createBinaries = function() {
46 | var results = {};
47 | var builds;
48 | if (electronSettings.builds){
49 | builds = electronSettings.builds;
50 | } else {
51 | //just build for the current platform/architecture
52 | if (process.platform === "darwin"){
53 | builds = [{platform: process.platform, arch: process.arch}];
54 | } else if (process.platform === "win32"){
55 | //arch detection doesn't always work on windows, and ia32 works everywhere
56 | builds = [{platform: process.platform, arch: "ia32"}];
57 | } else {
58 | console.error('You must specify one or more builds in Meteor.settings.electron.');
59 | return results;
60 | }
61 | }
62 |
63 | if (_.isEmpty(builds)) {
64 | console.error('No builds available for this platform.');
65 | return results;
66 | }
67 |
68 | builds.forEach(function(buildInfo){
69 | var buildRequired = false;
70 |
71 | var buildDirs = createBuildDirectories(buildInfo);
72 |
73 | /* Write out Electron application files */
74 | var appVersion = electronSettings.version;
75 | var appName = electronSettings.name || "electron";
76 | var appDescription = electronSettings.description;
77 |
78 | var resolvedAppSrcDir;
79 | if (electronSettings.appSrcDir) {
80 | resolvedAppSrcDir = path.join(projectRoot(), electronSettings.appSrcDir);
81 | } else {
82 | // See http://stackoverflow.com/a/29745318/495611 for how the package asset directory is derived.
83 | // We can't read this from the project directory like the user-specified app directory since
84 | // we may be loaded from Atmosphere rather than locally.
85 | resolvedAppSrcDir = path.join(process.cwd(), 'assets', 'packages', 'meson_electron', 'app');
86 | }
87 |
88 | // Check if the package.json has changed before copying over the app files, to account for
89 | // changes made in the app source dir.
90 | var packagePath = packageJSONPath(resolvedAppSrcDir);
91 | var packageJSON = Npm.require(packagePath);
92 |
93 | // Fill in missing package.json fields (note: before the comparison).
94 | // This isn't just a convenience--`Squirrel.Windows` requires the description and version.
95 | packageJSON = _.defaults(packageJSON, {
96 | name: appName && appName.toLowerCase().replace(/\s/g, '-'),
97 | productName: appName,
98 | description: appDescription,
99 | version: appVersion
100 | });
101 | // Check if the package has changed before we possibly copy over the app source since that will
102 | // of course sync `package.json`.
103 | var packageHasChanged = packageJSONHasChanged(packageJSON, buildDirs.app);
104 |
105 | var didOverwriteNodeModules = false;
106 |
107 | if (appHasChanged(resolvedAppSrcDir, buildDirs.working)) {
108 | buildRequired = true;
109 |
110 | // Copy the app directory over while also pruning old files.
111 | if (IS_MAC) {
112 | // Ensure that the app source directory ends in a slash so we copy its contents.
113 | // Except node_modules from pruning since we prune that below.
114 | // TODO(wearhere): `rsync` also uses checksums to only copy what's necessary so theoretically we
115 | // could always `rsync` rather than checking if the directory's changed first.
116 | exec(util.format('rsync -a --delete --force --filter="P node_modules" "%s" "%s"',
117 | path.join(resolvedAppSrcDir, '/'), buildDirs.app));
118 | } else {
119 | // TODO(wearhere): More efficient sync on Windows (where `rsync` isn't available.)
120 | rimraf(buildDirs.app);
121 | mkdirp(buildDirs.app);
122 | ncp(resolvedAppSrcDir, buildDirs.app);
123 | didOverwriteNodeModules = true;
124 | }
125 | }
126 |
127 | /* Write out the application package.json */
128 | // Do this after writing out the application files, since that will overwrite `package.json`.
129 | // This logic is a little bit inefficient: it's not the case that _every_ change to package.json
130 | // means that we have to reinstall the node modules; and if we overwrote the node modules, we
131 | // don't necessarily have to rewrite `package.json`. But doing it altogether is simplest.
132 | if (packageHasChanged || didOverwriteNodeModules) {
133 | buildRequired = true;
134 |
135 | // For some reason when this file isn't manually removed it fails to be overwritten with an
136 | // EACCES error.
137 | rimraf(packageJSONPath(buildDirs.app));
138 | writeFile(packageJSONPath(buildDirs.app), JSON.stringify(packageJSON));
139 |
140 | exec("npm install && npm prune", {cwd: buildDirs.app});
141 |
142 | // Rebuild native modules if any.
143 | // TODO(jeff): Start using the pre-gyp fix if someone asks for it, so we can make sure it works:
144 | // https://github.com/electronjs/electron-rebuild#node-pre-gyp-workaround
145 | Promise.await(electronRebuild.installNodeHeaders(ELECTRON_VERSION, null /* nodeDistUrl */,
146 | null /* headersDir */, buildInfo.arch));
147 | Promise.await(electronRebuild.rebuildNativeModules(ELECTRON_VERSION,
148 | path.join(buildDirs.app, 'node_modules'), null /* headersDir */, buildInfo.arch));
149 | }
150 |
151 | /* Write out Electron Settings */
152 | var settings = _.defaults({}, electronSettings, {
153 | rootUrl: process.env.ROOT_URL
154 | });
155 |
156 | var signingIdentity = electronSettings.sign;
157 | var signingIdentityRequiredAndMissing = false;
158 | if (canServeUpdates(buildInfo.platform)) {
159 | // Enable the auto-updater if possible.
160 | if ((buildInfo.platform === 'darwin') && !signingIdentity) {
161 | // If the app isn't signed and we try to use the auto-updater, it will
162 | // throw an exception. Log an error if the settings have changed, below.
163 | signingIdentityRequiredAndMissing = true;
164 | } else {
165 | settings.updateFeedUrl = settings.rootUrl + UPDATE_FEED_PATH;
166 | }
167 | }
168 |
169 | if (settingsHaveChanged(settings, buildDirs.app)) {
170 | if (signingIdentityRequiredAndMissing) {
171 | console.error('Developer ID signing identity is missing: remote updates will not work.');
172 | }
173 | buildRequired = true;
174 | writeFile(settingsPath(buildDirs.app), JSON.stringify(settings));
175 | }
176 |
177 | var packagerSettings = getPackagerSettings(buildInfo, buildDirs);
178 | if (packagerSettings.icon && iconHasChanged(packagerSettings.icon, buildDirs.working)) {
179 | buildRequired = true;
180 | }
181 |
182 | // TODO(wearhere): If/when the signing identity expires, does its name change? If not, we'll need
183 | // to force the app to be rebuilt somehow.
184 |
185 | if (packagerSettingsHaveChanged(packagerSettings, buildDirs.working)) {
186 | buildRequired = true;
187 | }
188 |
189 | var app = appPath(appName, buildInfo.platform, buildInfo.arch, buildDirs.build);
190 | if (!exists(app)) {
191 | buildRequired = true;
192 | }
193 |
194 | /* Create Build */
195 | if (buildRequired) {
196 | var build = electronPackager(packagerSettings)[0];
197 | console.log("Build created for ", buildInfo.platform, buildInfo.arch, "at", build);
198 | }
199 |
200 | /* Package the build for download if specified. */
201 | // TODO(rissem): make this platform independent
202 |
203 | if (electronSettings.autoPackage && (buildInfo.platform === 'darwin')) {
204 | // The auto-updater framework only supports installing ZIP releases:
205 | // https://github.com/Squirrel/Squirrel.Mac#update-json-format
206 | var downloadName = (appName || "app") + ".zip";
207 | var compressedDownload = path.join(buildDirs.final, downloadName);
208 |
209 | if (buildRequired || !exists(compressedDownload)) {
210 | // Use `ditto` to ZIP the app because I couldn't find a good npm module to do it and also that's
211 | // what a couple of other related projects do:
212 | // - https://github.com/Squirrel/Squirrel.Mac/blob/8caa2fa2007b29a253f7f5be8fc9f36ace6aa30e/Squirrel/SQRLZipArchiver.h#L24
213 | // - https://github.com/jenslind/electron-release/blob/4a2a701c18664ec668c3570c3907c0fee72f5e2a/index.js#L109
214 | exec('ditto -ck --sequesterRsrc --keepParent "' + app + '" "' + compressedDownload + '"');
215 | console.log("Downloadable created at", compressedDownload);
216 | }
217 | }
218 |
219 | results[buildInfo.platform + "-" + buildInfo.arch] = {
220 | app: app,
221 | buildRequired: buildRequired
222 | };
223 | });
224 |
225 | return results;
226 | };
227 |
228 | function createBuildDirectories(build){
229 | // Use a predictable directory so that other scripts can locate the builds, also so that the builds
230 | // may be cached:
231 |
232 | var workingDir = path.join(projectRoot(), '.meteor-electron', build.platform + "-" + build.arch);
233 | mkdirp(workingDir);
234 |
235 | //TODO consider seeding the binaryDir from package assets so package
236 | //could work without an internet connection
237 |
238 | // *binaryDir* holds the vanilla electron apps
239 | var binaryDir = path.join(workingDir, "releases");
240 | mkdirp(binaryDir);
241 |
242 | // *appDir* holds the electron application that points to a meteor app
243 | var appDir = path.join(workingDir, "apps");
244 | mkdirp(appDir);
245 |
246 | // *buildDir* contains the uncompressed apps
247 | var buildDir = path.join(workingDir, "builds");
248 | mkdirp(buildDir);
249 |
250 | // *finalDir* contains zipped apps ready to be downloaded
251 | var finalDir = path.join(workingDir, "final");
252 | mkdirp(finalDir);
253 |
254 | return {
255 | working: workingDir,
256 | binary: binaryDir,
257 | app: appDir,
258 | build: buildDir,
259 | final: finalDir
260 | };
261 | }
262 |
263 | function getPackagerSettings(buildInfo, dirs){
264 | var packagerSettings = {
265 | dir: dirs.app,
266 | name: electronSettings.name || "Electron",
267 | platform: buildInfo.platform,
268 | arch: buildInfo.arch,
269 | version: ELECTRON_VERSION,
270 | out: dirs.build,
271 | cache: dirs.binary,
272 | overwrite: true,
273 | // The EXE's `ProductName` is the preferred title of application shortcuts created by `Squirrel.Windows`.
274 | // If we don't set it, it will default to "Electron".
275 | 'version-string': {
276 | ProductName: electronSettings.name || 'Electron'
277 | }
278 | };
279 |
280 | if (electronSettings.version) {
281 | packagerSettings['app-version'] = electronSettings.version;
282 | }
283 | if (electronSettings.icon) {
284 | var icon = electronSettings.icon[buildInfo.platform];
285 | if (icon) {
286 | var iconPath = path.join(projectRoot(), icon);
287 | packagerSettings.icon = iconPath;
288 | }
289 | }
290 | if (electronSettings.sign) {
291 | packagerSettings.sign = electronSettings.sign;
292 | }
293 | if (electronSettings.protocols) {
294 | packagerSettings.protocols = electronSettings.protocols;
295 | }
296 | return packagerSettings;
297 | }
298 |
299 | function settingsPath(appDir) {
300 | return path.join(appDir, 'electronSettings.json');
301 | }
302 |
303 | function settingsHaveChanged(settings, appDir) {
304 | var electronSettingsPath = settingsPath(appDir);
305 | var existingElectronSettings;
306 | try {
307 | existingElectronSettings = Npm.require(electronSettingsPath);
308 | } catch(e) {
309 | // No existing settings.
310 | }
311 | return !existingElectronSettings || !_.isEqual(settings, existingElectronSettings);
312 | }
313 |
314 | function appHasChanged(appSrcDir, workingDir) {
315 | var appChecksumPath = path.join(workingDir, 'appChecksum.txt');
316 | var existingAppChecksum;
317 | try {
318 | existingAppChecksum = readFile(appChecksumPath, 'utf8');
319 | } catch(e) {
320 | // No existing checksum.
321 | }
322 |
323 | var appChecksum = dirsum(appSrcDir);
324 | if (appChecksum !== existingAppChecksum) {
325 | writeFile(appChecksumPath, appChecksum);
326 | return true;
327 | } else {
328 | return false;
329 | }
330 | }
331 |
332 | function packageJSONPath(appDir) {
333 | return path.join(appDir, 'package.json');
334 | }
335 |
336 | function packageJSONHasChanged(packageJSON, appDir) {
337 | var packagePath = packageJSONPath(appDir);
338 | var existingPackageJSON;
339 | try {
340 | existingPackageJSON = Npm.require(packagePath);
341 | } catch(e) {
342 | // No existing package.
343 | }
344 |
345 | return !existingPackageJSON || !_.isEqual(packageJSON, existingPackageJSON);
346 | }
347 |
348 | function packagerSettingsHaveChanged(settings, workingDir) {
349 | var settingsPath = path.join(workingDir, 'lastUsedPackagerSettings.json');
350 | var existingPackagerSettings;
351 | try {
352 | existingPackagerSettings = Npm.require(settingsPath);
353 | } catch(e) {
354 | // No existing settings.
355 | }
356 |
357 | if (!existingPackagerSettings || !_.isEqual(settings, existingPackagerSettings)) {
358 | writeFile(settingsPath, JSON.stringify(settings));
359 | return true;
360 | } else {
361 | return false;
362 | }
363 | }
364 |
365 | function iconHasChanged(iconPath, workingDir) {
366 | var iconChecksumPath = path.join(workingDir, 'iconChecksum.txt');
367 | var existingIconChecksum;
368 | try {
369 | existingIconChecksum = readFile(iconChecksumPath, 'utf8');
370 | } catch(e) {
371 | // No existing checksum.
372 | }
373 |
374 | // `dirsum` works for files too.
375 | var iconChecksum = dirsum(iconPath);
376 | if (iconChecksum !== existingIconChecksum) {
377 | writeFile(iconChecksumPath, iconChecksum);
378 | return true;
379 | } else {
380 | return false;
381 | }
382 | }
383 |
384 | function appPath(appName, platform, arch, buildDir) {
385 | var appExtension = (platform === 'darwin') ? '.app' : '.exe';
386 | return path.join(buildDir, [appName, platform, arch].join('-'), appName + appExtension);
387 | }
388 |
--------------------------------------------------------------------------------
/server/downloadUrls.js:
--------------------------------------------------------------------------------
1 | var urlJoin = Npm.require('url-join');
2 |
3 | // Global for tests.
4 | parseMacDownloadUrl = function(electronSettings) {
5 | if (!electronSettings || !electronSettings.downloadUrls || !electronSettings.downloadUrls.darwin) return;
6 |
7 | return electronSettings.downloadUrls.darwin.replace('{{version}}', electronSettings.version);
8 | };
9 |
10 | // Global for tests.
11 | parseWindowsDownloadUrls = function(electronSettings) {
12 | if (!electronSettings || !electronSettings.downloadUrls || !electronSettings.downloadUrls.win32) return;
13 |
14 | // The default value here is what `createBinaries` writes into the app's package.json, which is
15 | // what is read by `grunt-electron-installer` to name the installer.
16 | var appName = electronSettings.name || 'electron';
17 |
18 | var releasesUrl, installerUrl;
19 | var installerUrlIsVersioned = false;
20 |
21 | if (_.isString(electronSettings.downloadUrls.win32)) {
22 | if (electronSettings.downloadUrls.win32.indexOf('{{version}}') > -1) {
23 | console.error('Only the Windows installer URL may be versioned. Specify `downloadUrls.win32.installer`.');
24 | return;
25 | }
26 | releasesUrl = electronSettings.downloadUrls.win32;
27 | // 'AppSetup.exe' refers to the output of `grunt-electron-installer`.
28 | installerUrl = urlJoin(electronSettings.downloadUrls.win32, appName + 'Setup.exe');
29 | } else {
30 | releasesUrl = electronSettings.downloadUrls.win32.releases;
31 | if (releasesUrl.indexOf('{{version}}') > -1) {
32 | console.error('Only the Windows installer URL may be versioned.');
33 | return;
34 | }
35 | installerUrl = electronSettings.downloadUrls.win32.installer;
36 | if (installerUrl.indexOf('{{version}}') > -1) {
37 | installerUrl = installerUrl.replace('{{version}}', electronSettings.version);
38 | installerUrlIsVersioned = true;
39 | }
40 | }
41 |
42 | // Cachebust the installer URL if it's not versioned.
43 | // (The releases URL will also be cachebusted, but by `serveUpdateFeed` since we've got to append
44 | // the particular paths requested by the client).
45 | if (!installerUrlIsVersioned) {
46 | installerUrl = cachebustedUrl(installerUrl);
47 | }
48 |
49 | return {
50 | releases: releasesUrl,
51 | installer: installerUrl
52 | };
53 | };
54 |
55 | function cachebustedUrl(url) {
56 | var querySeparator = (url.indexOf('?') > -1) ? '&' : '?';
57 | return url + querySeparator + 'cb=' + Date.now();
58 | }
59 |
60 | DOWNLOAD_URLS = {
61 | darwin: parseMacDownloadUrl(Meteor.settings.electron),
62 | win32: parseWindowsDownloadUrls(Meteor.settings.electron)
63 | };
64 |
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | var electronSettings = Meteor.settings.electron || {};
2 |
3 | if ((process.env.NODE_ENV === 'development') && (electronSettings.autoBuild !== false)) {
4 | var buildResults = createBinaries();
5 | var buildResultForThisPlatform = buildResults[process.platform + '-' + process.arch];
6 | if (buildResultForThisPlatform) {
7 | launchApp(buildResultForThisPlatform.app, buildResultForThisPlatform.buildRequired);
8 | }
9 | }
10 |
11 | serveDownloadUrl();
12 | serveUpdateFeed();
13 |
--------------------------------------------------------------------------------
/server/launchApp.js:
--------------------------------------------------------------------------------
1 | var isRunning = Meteor.wrapAsync(Npm.require("is-running"));
2 | var path = Npm.require('path');
3 | var proc = Npm.require('child_process');
4 |
5 | var ElectronProcesses = new Mongo.Collection("processes");
6 |
7 | var ProcessManager = {
8 | add: function(pid){
9 | ElectronProcesses.insert({ pid: pid });
10 | },
11 |
12 | running: function(){
13 | var runningProcess;
14 | ElectronProcesses.find().forEach(function(proc){
15 | if (isRunning(proc.pid)){
16 | runningProcess = proc.pid;
17 | } else {
18 | ElectronProcesses.remove({ _id: proc._id });
19 | }
20 | });
21 | return runningProcess;
22 | },
23 |
24 | stop: function(pid) {
25 | process.kill(pid);
26 | ElectronProcesses.remove({ pid: pid });
27 | }
28 | };
29 |
30 | launchApp = function(app, appIsNew) {
31 | // Safeguard.
32 | if (process.env.NODE_ENV !== 'development') return;
33 |
34 | var runningProcess = ProcessManager.running();
35 | if (runningProcess) {
36 | if (!appIsNew) {
37 | return;
38 | } else {
39 | ProcessManager.stop(runningProcess);
40 | }
41 | }
42 |
43 | var electronExecutable, child;
44 | if (process.platform === 'win32') {
45 | electronExecutable = app;
46 | child = proc.spawn(electronExecutable);
47 | } else {
48 | electronExecutable = path.join(app, "Contents", "MacOS", "Electron");
49 | var appDir = path.join(app, "Contents", "Resources", "app");
50 |
51 | //TODO figure out how to handle case where electron executable or
52 | //app dir don't exist
53 |
54 | child = proc.spawn(electronExecutable, [appDir]);
55 | }
56 |
57 | child.stdout.on("data", function(data){
58 | console.log("ATOM:", data.toString());
59 | });
60 |
61 | child.stderr.on("data", function(data){
62 | console.log("ATOM:", data.toString());
63 | });
64 |
65 | ProcessManager.add(child.pid);
66 | };
67 |
--------------------------------------------------------------------------------
/server/serve.js:
--------------------------------------------------------------------------------
1 | serve = function(path, handler) {
2 | if (Package["iron:router"]){
3 | Package["iron:router"].Router.route(path, function(){
4 | handler(this.request, this.response, this.next);
5 | }, {where: "server"});
6 | } else if (Package["meteorhacks:picker"]){
7 | Package["meteorhacks:picker"].Picker.route(path, function(params, req, res, next){
8 | req.query = params.query;
9 | handler(req, res, next);
10 | });
11 | } else {
12 | WebApp.rawConnectHandlers.use(function(req, res, next){
13 | if (req.path === path) {
14 | handler(req, res, next);
15 | } else {
16 | next();
17 | }
18 | });
19 | }
20 | };
21 |
22 | serveDir = function(dir, handler){
23 | //path starts with dir
24 | if (Package["iron:router"]){
25 | Package["iron:router"].Router.route(dir + "/:stuff", function(){
26 | handler(this.request, this.response, this.next);
27 | }, {where: "server"});
28 | } else if (Package["meteorhacks:picker"]){
29 | Package["meteorhacks:picker"].Picker.route(dir + "/:stuff", function(params, req, res, next){
30 | req.query = params.query;
31 | handler(req, res, next);
32 | });
33 | } else {
34 | var regex = new RegExp("^" + dir);
35 | WebApp.rawConnectHandlers.use(function(req, res, next){
36 | if (regex.test(req.path)) {
37 | handler(req, res, next);
38 | } else {
39 | next();
40 | }
41 | });
42 | }
43 | };
44 |
--------------------------------------------------------------------------------
/server/serveDownloadUrl.js:
--------------------------------------------------------------------------------
1 | serveDownloadUrl = function() {
2 | serve('/app/download', function(req, res, next) {
3 | var installerUrl = DOWNLOAD_URLS[req.query.platform];
4 | if (_.isObject(installerUrl)) {
5 | installerUrl = installerUrl.installer;
6 | }
7 | if (installerUrl) {
8 | res.statusCode = 302; // Moved Temporarily
9 | res.setHeader('Location', installerUrl);
10 | res.end();
11 | } else {
12 | res.statusCode = 404;
13 | res.end();
14 | }
15 | });
16 | };
17 |
--------------------------------------------------------------------------------
/server/serveUpdateFeed.js:
--------------------------------------------------------------------------------
1 | var semver = Npm.require('semver');
2 | var urlJoin = Npm.require('url-join');
3 | var electronSettings = Meteor.settings.electron || {};
4 | var latestVersion = electronSettings.version;
5 |
6 | canServeUpdates = function(platform) {
7 | if (!latestVersion){
8 | return false;
9 | }
10 |
11 | return !!DOWNLOAD_URLS[platform];
12 | };
13 |
14 | UPDATE_FEED_PATH = "/app/latest";
15 |
16 | serveUpdateFeed = function() {
17 | // https://github.com/Squirrel/Squirrel.Mac#server-support
18 | if (canServeUpdates("darwin")){
19 | serve(UPDATE_FEED_PATH, function(req, res, next) {
20 | var appVersion = req.query.version;
21 | if (semver.valid(appVersion) && semver.gte(appVersion, latestVersion)) {
22 | res.statusCode = 204; // No content.
23 | res.end();
24 | } else {
25 | res.statusCode = 200;
26 | res.setHeader('Content-Type', 'application/json');
27 | res.end(JSON.stringify({
28 | url: DOWNLOAD_URLS['darwin']
29 | }));
30 | }
31 | });
32 | }
33 |
34 | // https://github.com/squirrel/squirrel.windows
35 | // (Summary 'cause those docs are scant: the Windows app is going to expect the update feed URL
36 | // to represent a directory from within which it can fetch the RELEASES file and packages. The
37 | // above `serve` call serves _just_ '/app/latest', whereas this serves its contents.)
38 | if (canServeUpdates("win32")) {
39 | // `path.dirname` works even on Windows.
40 | var releasesUrl = DOWNLOAD_URLS['win32'].releases;
41 | serveDir(UPDATE_FEED_PATH, function(req, res, next){
42 | //first strip off the UPDATE_FEED_PATH
43 | var path = req.url.split(UPDATE_FEED_PATH)[1];
44 | res.statusCode = 302;
45 | // Cache-bust the RELEASES file.
46 | if (/RELEASES/.test(path)) {
47 | path += (/\?/.test(path) ? '&' : '?') + 'cb=' + Date.now();
48 | }
49 | res.setHeader("Location", urlJoin(releasesUrl, path));
50 | res.end();
51 | });
52 | }
53 | };
54 |
--------------------------------------------------------------------------------
/tests/server/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | // If you are using SublimeLinter, after modifying this config file, be sure to close and reopen
3 | // the file(s) you were editing to see the changes take effect.
4 |
5 | "extends": "../../server/.jshintrc",
6 |
7 | // Whitelist additional global variables.
8 | "globals": {
9 | // Meteor's globals.
10 | "Tinytest": false
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/tests/server/downloadUrlsTest.js:
--------------------------------------------------------------------------------
1 | /******************************************************************************/
2 | /* Mac */
3 | /******************************************************************************/
4 |
5 | Tinytest.add('download URL parsing - Mac - returns undefined if `downloadUrls.darwin` not specified', function(test) {
6 | test.isUndefined(parseMacDownloadUrl());
7 | test.isUndefined(parseMacDownloadUrl({
8 | downloadUrls: {}
9 | }));
10 | });
11 |
12 | Tinytest.add('download URL parsing - Mac - returns the specified URL', function(test) {
13 | test.equal(parseMacDownloadUrl({
14 | downloadUrls: {
15 | darwin: 'https://myapp.com/download/osx/MyApp.zip'
16 | }
17 | }), 'https://myapp.com/download/osx/MyApp.zip');
18 | });
19 |
20 | Tinytest.add('download URL parsing - Mac - versions the specified URL', function(test) {
21 | test.equal(parseMacDownloadUrl({
22 | version: '1.1.1',
23 | downloadUrls: {
24 | darwin: 'https://myapp.com/download/osx/{{version}}/MyApp.zip'
25 | }
26 | }), 'https://myapp.com/download/osx/1.1.1/MyApp.zip');
27 | });
28 |
29 | /******************************************************************************/
30 | /* Windows */
31 | /******************************************************************************/
32 |
33 | /*
34 | * a unified URL
35 | */
36 | Tinytest.add('download URL parsing - Windows - a unified URL - returns undefined if `downloadUrls.win32` not specified', function(test) {
37 | test.isUndefined(parseWindowsDownloadUrls());
38 | test.isUndefined(parseWindowsDownloadUrls({
39 | downloadUrls: {}
40 | }));
41 | });
42 |
43 | Tinytest.add('download URL parsing - Windows - a unified URL - attempting to version fails', function(test) {
44 | var origConsoleErr = console.error;
45 | var consoleErrorWasCalled = false;
46 | console.error = function() { consoleErrorWasCalled = true; };
47 |
48 | try {
49 | test.isUndefined(parseWindowsDownloadUrls({
50 | downloadUrls: {
51 | win32: 'https://myapp.com/download/win32/{{version}}'
52 | }
53 | }));
54 | test.isTrue(consoleErrorWasCalled);
55 | } finally {
56 | console.error = origConsoleErr;
57 | }
58 | });
59 |
60 | /* the releases URL */
61 | Tinytest.add('download URL parsing - Windows - a unified URL - the releases URL - is returned properly', function(test) {
62 | var releasesUrl = parseWindowsDownloadUrls({
63 | downloadUrls: {
64 | win32: 'https://myapp.com/download/win32'
65 | }
66 | }).releases;
67 |
68 | test.equal(releasesUrl, 'https://myapp.com/download/win32');
69 | });
70 |
71 | /* the installer URL */
72 | Tinytest.add('download URL parsing - Windows - a unified URL - the installer URL - is returned properly', function(test) {
73 | var installerUrl = parseWindowsDownloadUrls({
74 | name: 'MyApp',
75 | downloadUrls: {
76 | win32: 'https://myapp.com/download/win32'
77 | }
78 | }).installer;
79 |
80 | // Not `equal` because of cachebusting (tested next).
81 | test.isTrue(installerUrl && (installerUrl.indexOf('https://myapp.com/download/win32/MyAppSetup.exe') === 0));
82 | });
83 |
84 | Tinytest.add('download URL parsing - Windows - a unified URL - the installer URL - is cachebusted', function(test) {
85 | var installerUrl = parseWindowsDownloadUrls({
86 | name: 'MyApp',
87 | downloadUrls: {
88 | win32: 'https://myapp.com/download/win32'
89 | }
90 | }).installer;
91 |
92 | test.isTrue(installerUrl && (installerUrl.indexOf('?cb=') > -1));
93 | });
94 |
95 | /*
96 | * separate URLs
97 | */
98 |
99 | /* the releases URL */
100 | Tinytest.add('download URL parsing - Windows - separate URLs - the releases URL - attempting to version fails', function(test) {
101 | var origConsoleErr = console.error;
102 | var consoleErrorWasCalled = false;
103 | console.error = function() { consoleErrorWasCalled = true; };
104 |
105 | try {
106 | test.isUndefined(parseWindowsDownloadUrls({
107 | downloadUrls: {
108 | win32: {
109 | releases: 'https://myapp.com/download/win32/{{version}}',
110 | installer: 'https://myapp.com/download/win32/{{version}}/MyAppSetup.exe'
111 | }
112 | }
113 | }));
114 | test.isTrue(consoleErrorWasCalled);
115 | } finally {
116 | console.error = origConsoleErr;
117 | }
118 | });
119 |
120 | Tinytest.add('download URL parsing - Windows - separate URLs - the releases URL - is returned as specified', function(test) {
121 | var releasesUrl = parseWindowsDownloadUrls({
122 | downloadUrls: {
123 | win32: {
124 | releases: 'https://myapp.com/download/win32',
125 | installer: 'https://myapp.com/download/win32/MyAppSetup.exe'
126 | }
127 | }
128 | }).releases;
129 |
130 | test.equal(releasesUrl, 'https://myapp.com/download/win32');
131 | });
132 |
133 | /* the installer URL */
134 | Tinytest.add('download URL parsing - Windows - separate URLs - the installer URL - is returned as specified', function(test) {
135 | var installerUrl = parseWindowsDownloadUrls({
136 | downloadUrls: {
137 | win32: {
138 | releases: 'https://myapp.com/download/win32',
139 | installer: 'https://myapp.com/download/win32/MyAppSetup.exe'
140 | }
141 | }
142 | }).installer;
143 |
144 | // Not `equal` because of cachebusting (tested next).
145 | test.isTrue(installerUrl && (installerUrl.indexOf('https://myapp.com/download/win32') === 0));
146 | });
147 |
148 | Tinytest.add('download URL parsing - Windows - separate URLs - the installer URL - is cachebusted if not versioned', function(test) {
149 | var installerUrl = parseWindowsDownloadUrls({
150 | downloadUrls: {
151 | win32: {
152 | releases: 'https://myapp.com/download/win32',
153 | installer: 'https://myapp.com/download/win32/MyAppSetup.exe'
154 | }
155 | }
156 | }).installer;
157 |
158 | test.isTrue(installerUrl && (installerUrl.indexOf('?cb=') > -1));
159 | });
160 |
161 | Tinytest.add('download URL parsing - Windows - separate URLs - the installer URL - is versioned as specified (and then not cachebusted)', function(test) {
162 | var installerUrl = parseWindowsDownloadUrls({
163 | version: '1.1.1',
164 | downloadUrls: {
165 | win32: {
166 | releases: 'https://myapp.com/download/win32',
167 | installer: 'https://myapp.com/download/win32/{{version}}/MyAppSetup.exe'
168 | }
169 | }
170 | }).installer;
171 |
172 | test.equal(installerUrl, 'https://myapp.com/download/win32/1.1.1/MyAppSetup.exe');
173 | });
174 |
--------------------------------------------------------------------------------