Merge branch 'master' into wl-open-empty-editor-as-pending

This commit is contained in:
Winston Liu 2019-04-27 00:25:00 -04:00
commit dbb6c7b1b6
No known key found for this signature in database
GPG Key ID: 76A2C5B5B8910B77
194 changed files with 22547 additions and 9775 deletions

View File

@ -10,14 +10,13 @@ branches:
only:
- master
- /^[0-9.]+-releases$/
- /.*-test-travis$/
matrix:
include:
- os: linux
dist: trusty
env: NODE_VERSION=8.9.3 DISPLAY=:99.0 CC=clang CXX=clang++ npm_config_clang=1
sudo: required
env: NODE_VERSION=8.9.3 DISPLAY=:99.0 CC=clang CXX=clang++ npm_config_clang=1 ATOM_JASMINE_REPORTER=list
before_install:
- "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16"
@ -28,11 +27,12 @@ install:
- nvm install $NODE_VERSION
- nvm use --delete-prefix $NODE_VERSION
- npm install --global npm@6.2.0
- script/build --create-debian-package --create-rpm-package --compress-artifacts
- script/bootstrap
script:
- script/lint
- script/test
script: >
script/lint &&
script/build --no-bootstrap --create-debian-package --create-rpm-package --compress-artifacts &&
script/test
cache:
directories:

View File

@ -1,4 +1,4 @@
Copyright (c) 2011-2018 GitHub Inc.
Copyright (c) 2011-2019 GitHub Inc.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

View File

@ -4,7 +4,7 @@
[![Dependency Status](https://david-dm.org/atom/atom.svg)](https://david-dm.org/atom/atom)
[![Join the Atom Community on Slack](https://atom-slack.herokuapp.com/badge.svg)](https://atom-slack.herokuapp.com)
Atom is a hackable text editor for the 21st century, built on [Electron](https://github.com/atom/electron), and based on everything we love about our favorite editors. We designed it to be deeply customizable, but still approachable using the default configuration.
Atom is a hackable text editor for the 21st century, built on [Electron](https://github.com/electron/electron), and based on everything we love about our favorite editors. We designed it to be deeply customizable, but still approachable using the default configuration.
![Atom](https://user-images.githubusercontent.com/378023/49132477-f4b77680-f31f-11e8-8357-ac6491761c6c.png)
@ -52,7 +52,7 @@ Atom is only available for 64-bit Linux systems.
Configure your distribution's package manager to install and update Atom by following the [Linux installation instructions](https://flight-manual.atom.io/getting-started/sections/installing-atom/#platform-linux) in the Flight Manual. You will also find instructions on how to install Atom's official Linux packages without using a package repository, though you will not get automatic updates after installing Atom this way.
### Archive extraction
#### Archive extraction
An archive is available for people who don't want to install `atom` as root.

257
apm/package-lock.json generated
View File

@ -4,10 +4,11 @@
"lockfileVersion": 1,
"dependencies": {
"atom-package-manager": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/atom-package-manager/-/atom-package-manager-2.1.3.tgz",
"integrity": "sha512-DBkHKhcjNGaqr/pQxebU4+O0NPLJ0/ARVNxIU+OhvcQjH+VQg6o/Wt6IGMXxGeXCIZnH8MZa6+D7GQ1cukViQg==",
"version": "2.1.7",
"resolved": "https://registry.npmjs.org/atom-package-manager/-/atom-package-manager-2.1.7.tgz",
"integrity": "sha512-HL5IuZ9HyRMsogN0VHrqS/TW9sE4T1Uzwpais/K8pSxcSaUJGvFhxxCn3VyIxi2snibANEipQnLQexRwsxw/qQ==",
"requires": {
"@atom/plist": "0.4.4",
"asar-require": "0.3.0",
"async": "~0.2.8",
"colors": "~0.6.1",
@ -21,7 +22,6 @@
"node-gyp": "3.4.0",
"npm": "6.2.0",
"open": "0.0.5",
"plist": "git+https://github.com/nathansobo/node-plist.git#bd3a93387f1d4b2cff819b200870d35465796e77",
"q": "~0.9.7",
"read": "~1.0.5",
"request": "^2.87.0",
@ -36,20 +36,29 @@
"yargs": "^3.23.0"
},
"dependencies": {
"@atom/plist": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/@atom/plist/-/plist-0.4.4.tgz",
"integrity": "sha512-EYwsXOY+Jp9vQ2yNWHuWaJufI58vbQWg235OkvGBWnITFVUOdow49OBj7ET9HCksZz560yXbSa1ywzqDIrFPZw==",
"requires": {
"xmlbuilder": "0.4.x",
"xmldom": "0.1.x"
}
},
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
},
"ajv": {
"version": "5.5.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
"integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz",
"integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==",
"requires": {
"co": "^4.6.0",
"fast-deep-equal": "^1.0.0",
"fast-deep-equal": "^2.0.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.3.0"
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"ansi-regex": {
@ -193,7 +202,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
"integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
"optional": true,
"requires": {
"tweetnacl": "^0.14.3"
}
@ -324,11 +332,6 @@
"wrap-ansi": "^2.0.0"
}
},
"co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
"integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ="
},
"code-point-at": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
@ -353,9 +356,9 @@
}
},
"commander": {
"version": "2.18.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.18.0.tgz",
"integrity": "sha512-6CYPa+JP2ftfRU2qkDK+UTVeQYosOg/2GbcjIcKPHfinyOLPVGXu/ovN86RP49Re5ndJK1N0kuiidFFuepc4ZQ=="
"version": "2.20.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz",
"integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ=="
},
"concat-map": {
"version": "0.0.1",
@ -423,9 +426,9 @@
}
},
"decompress-zip": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/decompress-zip/-/decompress-zip-0.3.0.tgz",
"integrity": "sha1-rjvLfjTGWHmt/nfhnDD4ZgK0vbA=",
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/decompress-zip/-/decompress-zip-0.3.2.tgz",
"integrity": "sha512-Ab1QY4LrWMrUuo53lLnmGOby7v8ryqxJ+bKibKSiPisx+25mhut1dScVBXAYx14i/PqSrFZvR2FRRazhLbvL+g==",
"requires": {
"binary": "^0.3.0",
"graceful-fs": "^4.1.3",
@ -467,7 +470,6 @@
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
"integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
"optional": true,
"requires": {
"jsbn": "~0.1.0",
"safer-buffer": "^2.1.0"
@ -493,13 +495,13 @@
}
},
"es5-ext": {
"version": "0.10.46",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.46.tgz",
"integrity": "sha512-24XxRvJXNFwEMpJb3nOkiRJKRoupmjYmOPVlI65Qy2SrtxwOTB+g6ODjBKOtwEHbYrhWRty9xxOWLNdClT2djw==",
"version": "0.10.49",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.49.tgz",
"integrity": "sha512-3NMEhi57E31qdzmYp2jwRArIUsj1HI/RxbQ4bgnSB+AIKIxsAmTiK83bYMifIcpWvEc3P1X30DhUKOqEtF/kvg==",
"requires": {
"es6-iterator": "~2.0.3",
"es6-symbol": "~3.1.1",
"next-tick": "1"
"next-tick": "^1.0.0"
},
"dependencies": {
"d": {
@ -570,9 +572,9 @@
}
},
"expand-template": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-1.1.1.tgz",
"integrity": "sha512-cebqLtV8KOZfw0UI8TEFWxtczxxC1jvyUvx6H4fyp1K1FN7A4Q+uggVUlOsI1K8AGU0rwOGqP8nCapdrw8CYQg=="
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="
},
"extend": {
"version": "3.0.2",
@ -585,9 +587,9 @@
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
},
"fast-deep-equal": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz",
"integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ="
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
"integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk="
},
"fast-json-stable-stringify": {
"version": "2.0.0",
@ -626,23 +628,13 @@
"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
},
"form-data": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz",
"integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=",
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "1.0.6",
"combined-stream": "^1.0.6",
"mime-types": "^2.1.12"
},
"dependencies": {
"combined-stream": {
"version": "1.0.6",
"resolved": "http://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz",
"integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=",
"requires": {
"delayed-stream": "~1.0.0"
}
}
}
},
"fs-constants": {
@ -746,9 +738,9 @@
}
},
"graceful-fs": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
"version": "4.1.15",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
"integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA=="
},
"grim": {
"version": "1.5.0",
@ -764,11 +756,11 @@
"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
},
"har-validator": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz",
"integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==",
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
"integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
"requires": {
"ajv": "^5.3.0",
"ajv": "^6.5.5",
"har-schema": "^2.0.0"
}
},
@ -852,8 +844,7 @@
"jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
"optional": true
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
},
"json-schema": {
"version": "0.2.3",
@ -861,9 +852,9 @@
"integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
},
"json-schema-traverse": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz",
"integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A="
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
},
"json-stringify-safe": {
"version": "5.0.1",
@ -890,18 +881,18 @@
}
},
"keytar": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/keytar/-/keytar-4.3.0.tgz",
"integrity": "sha512-pd++/v+fS0LQKmzWlW6R1lziTXFqhfGeS6sYLfuTIqEy2pDzAbjutbSW8f9tnJdEEMn/9XhAQlT34VAtl9h4MQ==",
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/keytar/-/keytar-4.4.1.tgz",
"integrity": "sha512-6xEe7ybXSR5EZC+z0GI2yqLYZjV1tyPQY2xSZ8rGsBxrrLEh8VR/Lfqv59uGX+I+W+OZxH0jCXN1dU1++ify4g==",
"requires": {
"nan": "2.8.0",
"prebuild-install": "^5.0.0"
"nan": "2.12.1",
"prebuild-install": "5.2.4"
},
"dependencies": {
"nan": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz",
"integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo="
"version": "2.12.1",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz",
"integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw=="
}
}
},
@ -922,16 +913,16 @@
}
},
"mime-db": {
"version": "1.36.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz",
"integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw=="
"version": "1.38.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz",
"integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg=="
},
"mime-types": {
"version": "2.1.20",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz",
"integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==",
"version": "2.1.22",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz",
"integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==",
"requires": {
"mime-db": "~1.36.0"
"mime-db": "~1.38.0"
}
},
"mimic-response": {
@ -971,13 +962,13 @@
"integrity": "sha1-dVSm+Nhxg0zJe1RisSLEwSTW3pE="
},
"mksnapshot": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/mksnapshot/-/mksnapshot-0.3.1.tgz",
"integrity": "sha1-JQHAVldDbXQs6Vik/5LHfkDdN+Y=",
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/mksnapshot/-/mksnapshot-0.3.5.tgz",
"integrity": "sha512-PSBoZaj9h9myC3uRRW62RxmX8mrN3XbOkMEyURUD7v5CeJgtYTar50XU738t7Q0LtG1pBPtp5n5QwDGggRnEvw==",
"requires": {
"decompress-zip": "0.3.0",
"decompress-zip": "0.3.x",
"fs-extra": "0.26.7",
"request": "^2.79.0"
"request": "2.x"
}
},
"ms": {
@ -986,9 +977,9 @@
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"mute-stream": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
"integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s="
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="
},
"mv": {
"version": "2.0.0",
@ -1018,9 +1009,9 @@
}
},
"nan": {
"version": "2.11.1",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz",
"integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA=="
"version": "2.13.2",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz",
"integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw=="
},
"napi-build-utils": {
"version": "1.0.1",
@ -1038,9 +1029,9 @@
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
},
"node-abi": {
"version": "2.4.5",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.4.5.tgz",
"integrity": "sha512-aa/UC6Nr3+tqhHGRsAuw/edz7/q9nnetBrKWxj6rpTtm+0X9T1qU7lIEHMS3yN9JwAbRiKUbRRFy1PLz/y3aaA==",
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.7.1.tgz",
"integrity": "sha512-OV8Bq1OrPh6z+Y4dqwo05HqrRL9YNF7QVMRfq1/pguwKLG+q9UB/Lk0x5qXjO23JjJg+/jqCHSTaG1P3tfKfuw==",
"requires": {
"semver": "^5.4.1"
}
@ -3972,26 +3963,18 @@
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
},
"plist": {
"version": "git+https://github.com/nathansobo/node-plist.git#bd3a93387f1d4b2cff819b200870d35465796e77",
"from": "git+https://github.com/nathansobo/node-plist.git",
"requires": {
"xmlbuilder": "0.4.x",
"xmldom": "0.1.x"
}
},
"prebuild-install": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.2.0.tgz",
"integrity": "sha512-cpuyMS8y30Df0bnN+I8pdmpwtZbm8fj9cQADOhSH/qnS1exb80elZ707FTMohFBJax4NyWjJVSg0chRQXzHSvg==",
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.2.4.tgz",
"integrity": "sha512-CG3JnpTZXdmr92GW4zbcba4jkDha6uHraJ7hW4Fn8j0mExxwOKK20hqho8ZuBDCKYCHYIkFM1P2jhtG+KpP4fg==",
"requires": {
"detect-libc": "^1.0.3",
"expand-template": "^1.0.2",
"expand-template": "^2.0.3",
"github-from-package": "0.0.0",
"minimist": "^1.2.0",
"mkdirp": "^0.5.1",
"napi-build-utils": "^1.0.1",
"node-abi": "^2.2.0",
"node-abi": "^2.7.0",
"noop-logger": "^0.1.1",
"npmlog": "^4.0.1",
"os-homedir": "^1.0.1",
@ -4025,9 +4008,9 @@
}
},
"psl": {
"version": "1.1.29",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz",
"integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ=="
"version": "1.1.31",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz",
"integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw=="
},
"pump": {
"version": "2.0.1",
@ -4039,9 +4022,9 @@
}
},
"punycode": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
},
"q": {
"version": "0.9.7",
@ -4118,11 +4101,11 @@
}
},
"rimraf": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
"integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
"requires": {
"glob": "^7.0.5"
"glob": "^7.1.3"
},
"dependencies": {
"glob": {
@ -4179,9 +4162,9 @@
}
},
"fs-plus": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/fs-plus/-/fs-plus-3.0.2.tgz",
"integrity": "sha1-a19Sp3EolMTd6f2PgfqMYN8EHz0=",
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/fs-plus/-/fs-plus-3.1.1.tgz",
"integrity": "sha512-Se2PJdOWXqos1qVTkvqqjb0CSnfBnwwD+pq+z4ksT+e97mEShod/hrNg0TRCCsXPbJzcIq+NuzQhigunMWMJUA==",
"requires": {
"async": "^1.5.2",
"mkdirp": "^0.5.1",
@ -4192,9 +4175,9 @@
}
},
"semver": {
"version": "5.5.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz",
"integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw=="
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
"integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA=="
},
"set-blocking": {
"version": "2.0.0",
@ -4222,9 +4205,9 @@
}
},
"sshpk": {
"version": "1.14.2",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz",
"integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=",
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
"integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
"requires": {
"asn1": "~0.2.3",
"assert-plus": "^1.0.0",
@ -4394,6 +4377,13 @@
"requires": {
"psl": "^1.1.24",
"punycode": "^1.4.1"
},
"dependencies": {
"punycode": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
}
}
},
"traverse": {
@ -4412,20 +4402,27 @@
"tweetnacl": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
"optional": true
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
},
"underscore": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz",
"integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI="
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz",
"integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg=="
},
"underscore-plus": {
"version": "1.6.8",
"resolved": "https://registry.npmjs.org/underscore-plus/-/underscore-plus-1.6.8.tgz",
"integrity": "sha512-88PrCeMKeAAC1L4xjSiiZ3Fg6kZOYrLpLGVPPeqKq/662DfQe/KTSKdSR/Q/tucKNnfW2MNAUGSCkDf8HmXC5Q==",
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/underscore-plus/-/underscore-plus-1.7.0.tgz",
"integrity": "sha512-A3BEzkeicFLnr+U/Q3EyWwJAQPbA19mtZZ4h+lLq3ttm9kn8WC4R3YpuJZEXmWdLjYP47Zc8aLZm9kwdv+zzvA==",
"requires": {
"underscore": "~1.8.3"
"underscore": "^1.9.1"
}
},
"uri-js": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
"integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
"requires": {
"punycode": "^2.1.0"
}
},
"util-deprecate": {
@ -4500,7 +4497,7 @@
},
"xmlbuilder": {
"version": "0.4.3",
"resolved": "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-0.4.3.tgz",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-0.4.3.tgz",
"integrity": "sha1-xGFLp04K0ZbmCcknLNnh3bKKilg="
},
"xmldom": {

View File

@ -6,6 +6,6 @@
"url": "https://github.com/atom/atom.git"
},
"dependencies": {
"atom-package-manager": "2.1.3"
"atom-package-manager": "2.1.7"
}
}

View File

@ -19,6 +19,8 @@ platform:
environment:
global:
ATOM_DEV_RESOURCE_PATH: c:\projects\atom
ATOM_JASMINE_REPORTER: list
CI: true
TEST_JUNIT_XML_ROOT: c:\projects\junit-test-results
NODE_VERSION: 8.9.3
@ -42,9 +44,9 @@ build_script:
- CD %APPVEYOR_BUILD_FOLDER%
- IF NOT EXIST C:\tmp MKDIR C:\tmp
- SET SQUIRREL_TEMP=C:\tmp
- IF [%APPVEYOR_REPO_BRANCH:~-9%]==[-releases] SET IS_RELEASE_BRANCH=true
- IF [%APPVEYOR_REPO_BRANCH:~-9%]==[-releases] IF NOT DEFINED APPVEYOR_PULL_REQUEST_NUMBER SET IS_RELEASE_BRANCH=true
- IF [%APPVEYOR_REPO_BRANCH%]==[master] IF NOT DEFINED APPVEYOR_PULL_REQUEST_NUMBER SET IS_SIGNED_ZIP_BRANCH=true
- IF [%APPVEYOR_REPO_BRANCH:~0,9%]==[electron-] SET IS_SIGNED_ZIP_BRANCH=true
- IF [%APPVEYOR_REPO_BRANCH:~0,9%]==[electron-] IF NOT DEFINED APPVEYOR_PULL_REQUEST_NUMBER SET IS_SIGNED_ZIP_BRANCH=true
- IF [%TASK%]==[installer] (
IF [%IS_RELEASE_BRANCH%]==[true] (
ECHO Building on release branch - Creating production artifacts &&

43
atom.sh
View File

@ -24,12 +24,26 @@ case $(basename $0) in
;;
esac
export ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT=true
# Only set the ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT env var if it hasn't been set.
if [ -z "$ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT" ]
then
export ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT=true
fi
while getopts ":wtfvh-:" opt; do
ATOM_ADD=false
ATOM_NEW_WINDOW=false
EXIT_CODE_OVERRIDE=
while getopts ":anwtfvh-:" opt; do
case "$opt" in
-)
case "${OPTARG}" in
add)
ATOM_ADD=true
;;
new-window)
ATOM_NEW_WINDOW=true
;;
wait)
WAIT=1
;;
@ -45,6 +59,12 @@ while getopts ":wtfvh-:" opt; do
;;
esac
;;
a)
ATOM_ADD=true
;;
n)
ATOM_NEW_WINDOW=true
;;
w)
WAIT=1
;;
@ -58,6 +78,11 @@ while getopts ":wtfvh-:" opt; do
esac
done
if [ "${ATOM_ADD}" = "true" ] && [ "${ATOM_NEW_WINDOW}" = "true" ]; then
EXPECT_OUTPUT=1
EXIT_CODE_OVERRIDE=1
fi
if [ $REDIRECT_STDERR ]; then
exec 2> /dev/null
fi
@ -115,7 +140,12 @@ if [ $OS == 'Mac' ]; then
if [ $EXPECT_OUTPUT ]; then
"$ATOM_PATH/$ATOM_APP_NAME/Contents/MacOS/$ATOM_EXECUTABLE_NAME" --executed-from="$(pwd)" --pid=$$ "$@"
exit $?
ATOM_EXIT=$?
if [ ${ATOM_EXIT} -eq 0 ] && [ -n "${EXIT_CODE_OVERRIDE}" ]; then
exit "${EXIT_CODE_OVERRIDE}"
else
exit ${ATOM_EXIT}
fi
else
open -a "$ATOM_PATH/$ATOM_APP_NAME" -n --args --executed-from="$(pwd)" --pid=$$ --path-environment="$PATH" "$@"
fi
@ -144,7 +174,12 @@ elif [ $OS == 'Linux' ]; then
if [ $EXPECT_OUTPUT ]; then
"$ATOM_PATH" --executed-from="$(pwd)" --pid=$$ "$@"
exit $?
ATOM_EXIT=$?
if [ ${ATOM_EXIT} -eq 0 ] && [ -n "${EXIT_CODE_OVERRIDE}" ]; then
exit "${EXIT_CODE_OVERRIDE}"
else
exit ${ATOM_EXIT}
fi
else
(
nohup "$ATOM_PATH" --executed-from="$(pwd)" --pid=$$ "$@" > "$ATOM_HOME/nohup.out" 2>&1

View File

@ -1,28 +0,0 @@
# Near-term plans
Want to know what the Atom team is working on and what has our focus over the next few months? You've come to the right place. 🎯
This roadmap is a [living document](https://en.wikipedia.org/wiki/Living_document): it represents our current plans, but we expect these plans to change from time to time. Follow [this link](https://github.com/atom/atom/blob/4fbad81a7cd2f2e3925d7e920086bc1ebf2fe210/docs/focus/README.md) to see the previous major version of this roadmap.
You can find our bi-weekly iteration plans by searching for issues with the [`iteration-plan`](https://github.com/atom/atom/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Aiteration-plan) label.
---
### Core package development is streamlined
Everything in Atom is a package. While this adds to its hackability, it is not always the best path forward. Consolidating packages as well as thinking about other ways to decrease friction for contributors will help pay down some of our tech debt in this area. More information regarding planning was provided in [this RFC](https://github.com/atom/atom/blob/master/docs/rfcs/003-consolidate-core-packages.md)
- [ ] Merge at least 22 packages in to atom/atom
### Improve Communication and Process
- [ ] Refine process for triaging issues and PRs across Atom org repositories
- [ ] Publish a document that outlines merge requirements for community PRs
- [ ] Reactive tickets are incorporated in to 80% of all sprints
- [ ] Automate some aspects of Atom issue and PR triage with Probot, especially around ensuring PRs follow our contribution guidelines
### Establish and Measure
- [ ] Implement Atom metrics dashboard that can be used to drive future decisions
- [ ] Determine what may be helpful to measure in the future building upon work [already in progress](http://blog.atom.io/2018/06/20/atom-metrics.html)

View File

@ -0,0 +1,44 @@
# Decoration ordering
## Status
Accepted
## Summary
Order block decoration items in the DOM in a deterministic and controllable way.
## Motivation
When multiple block decorations are created at the same screen line, they are inserted into the DOM in an order determined by the sequence of their creation; from oldest to newest when `position` is set to `"before"`, from newest to oldest when `position` is set to `"after"`. While this is deterministic, it is limited: it isn't possible to insert decorations within a sequence of existing ones, and it's difficult to control the order of decorations when creating and destroying and moving markers around an editor.
We hit the need for this in [atom/github#1913](https://github.com/atom/github/pull/1913) when we have a block decoration for multiple consecutive collapsed file patches.
## Explanation
[TextEditor::decorateMarker()](https://atom.io/docs/api/v1.34.0/TextEditor#instance-decorateMarker) accepts an additional `order` parameter in its `decorationParams` argument when `type` is "block". When multiple block or overlay decorations occur at the same screen line, they are ordered within the DOM in increasing "order" value.
Block decorations with the same `order` property are rendered in the order they were created, oldest to newest. Block decorations with no `order` property are rendered after those with one, in the order in which they were created, oldest to newest.
## Drawbacks
This is a breaking change for co-located block decorations created with an "after" position - they'll now appear in the reverse order.
When multiple packages create block decorations at the same screen line, they'll need to coordinate their `order` values to have expected behavior. There may not even be a clear, universal answer about how block decorations from distinct packages _should_ be ordered.
This adds another situational parameter to `TextEditor::decorationMarker()`, which already has complicated arguments.
## Rationale and alternatives
Originally I wanted to address the package coordination problem with a similar approach to [the way context menu items are ordered](https://github.com/atom/atom/pull/16661), by allowing individual decorations to specify constraints: "before this block," "after this block," "next to this block" and so forth. I ultimately chose to write up the simpler proposal because:
* Block decoration collisions among packages seem much less likely than context menu collisions.
* Constraint satisfaction problems are complex. There would be a relatively high chance of introducing bugs and performance regressions.
* The order number approach is similar to the APIs already offered to order status bar tiles and custom gutters.
The alternative to having an explicit API for this is at all is to create and destroy decorations to achieve the desired order. That's possible, but requires a great deal of bookkeeping on the package's side to accomplish, especially as decorations are added and removed and text is edited.
## Unresolved questions
- Should overlay decorations respect an `order` parameter in a similar fashion?
- Should screen column effect decoration ordering at all?

3683
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
{
"name": "atom",
"productName": "Atom",
"version": "1.36.0-dev",
"version": "1.38.0-dev",
"description": "A hackable text editor for the 21st Century.",
"main": "./src/main-process/main.js",
"repository": {
@ -12,17 +12,17 @@
"url": "https://github.com/atom/atom/issues"
},
"license": "MIT",
"electronVersion": "2.0.16",
"electronVersion": "2.0.18",
"dependencies": {
"@atom/nsfw": "1.0.18",
"@atom/source-map-support": "^0.3.4",
"@atom/watcher": "1.0.8",
"@atom/watcher": "1.3.1",
"about": "file:packages/about",
"archive-view": "https://www.atom.io/api/packages/archive-view/versions/0.65.1/tarball",
"async": "0.2.6",
"atom-dark-syntax": "file:packages/atom-dark-syntax",
"atom-dark-ui": "file:packages/atom-dark-ui",
"atom-keymap": "8.2.12",
"atom-keymap": "8.2.13",
"atom-light-syntax": "file:packages/atom-light-syntax",
"atom-light-ui": "file:packages/atom-light-ui",
"atom-select-list": "^0.7.2",
@ -30,14 +30,14 @@
"autocomplete-css": "https://www.atom.io/api/packages/autocomplete-css/versions/0.17.5/tarball",
"autocomplete-html": "https://www.atom.io/api/packages/autocomplete-html/versions/0.8.8/tarball",
"autocomplete-plus": "https://www.atom.io/api/packages/autocomplete-plus/versions/2.42.0/tarball",
"autocomplete-snippets": "https://www.atom.io/api/packages/autocomplete-snippets/versions/1.12.0/tarball",
"autocomplete-snippets": "https://www.atom.io/api/packages/autocomplete-snippets/versions/1.12.1/tarball",
"autoflow": "file:packages/autoflow",
"autosave": "https://www.atom.io/api/packages/autosave/versions/0.24.6/tarball",
"babel-core": "5.8.38",
"background-tips": "https://www.atom.io/api/packages/background-tips/versions/0.28.0/tarball",
"base16-tomorrow-dark-theme": "file:packages/base16-tomorrow-dark-theme",
"base16-tomorrow-light-theme": "file:packages/base16-tomorrow-light-theme",
"bookmarks": "https://www.atom.io/api/packages/bookmarks/versions/0.45.1/tarball",
"bookmarks": "https://www.atom.io/api/packages/bookmarks/versions/0.46.0/tarball",
"bracket-matcher": "https://www.atom.io/api/packages/bracket-matcher/versions/0.90.4/tarball",
"cached-run-in-this-context": "0.5.0",
"chai": "3.5.0",
@ -55,41 +55,41 @@
"etch": "^0.12.6",
"event-kit": "^2.5.3",
"exception-reporting": "file:packages/exception-reporting",
"find-and-replace": "https://www.atom.io/api/packages/find-and-replace/versions/0.216.0/tarball",
"find-and-replace": "https://www.atom.io/api/packages/find-and-replace/versions/0.218.9/tarball",
"find-parent-dir": "^0.3.0",
"first-mate": "7.1.3",
"first-mate": "7.3.0",
"focus-trap": "2.4.5",
"fs-admin": "^0.1.7",
"fs-plus": "^3.0.1",
"fstream": "0.1.24",
"fuzzaldrin": "^2.1",
"fuzzy-finder": "https://www.atom.io/api/packages/fuzzy-finder/versions/1.8.2/tarball",
"fuzzy-finder": "https://www.atom.io/api/packages/fuzzy-finder/versions/1.13.1/tarball",
"git-diff": "file:packages/git-diff",
"git-utils": "5.2.1",
"github": "https://www.atom.io/api/packages/github/versions/0.24.0/tarball",
"git-utils": "5.5.0",
"github": "https://www.atom.io/api/packages/github/versions/0.28.0/tarball",
"glob": "^7.1.1",
"go-to-line": "file:packages/go-to-line",
"grammar-selector": "file:packages/grammar-selector",
"grim": "1.5.0",
"image-view": "https://www.atom.io/api/packages/image-view/versions/0.63.1/tarball",
"image-view": "https://www.atom.io/api/packages/image-view/versions/0.64.0/tarball",
"incompatible-packages": "file:packages/incompatible-packages",
"jasmine-json": "~0.0",
"jasmine-reporters": "1.1.0",
"jasmine-tagged": "^1.1.4",
"key-path-helpers": "^0.4.0",
"keybinding-resolver": "https://www.atom.io/api/packages/keybinding-resolver/versions/0.38.4/tarball",
"language-c": "https://www.atom.io/api/packages/language-c/versions/0.60.14/tarball",
"language-clojure": "https://www.atom.io/api/packages/language-clojure/versions/0.22.7/tarball",
"language-coffee-script": "https://www.atom.io/api/packages/language-coffee-script/versions/0.49.3/tarball",
"keybinding-resolver": "https://www.atom.io/api/packages/keybinding-resolver/versions/0.39.0/tarball",
"language-c": "https://www.atom.io/api/packages/language-c/versions/0.60.15/tarball",
"language-clojure": "https://www.atom.io/api/packages/language-clojure/versions/0.22.8/tarball",
"language-coffee-script": "https://www.atom.io/api/packages/language-coffee-script/versions/0.50.0/tarball",
"language-csharp": "https://www.atom.io/api/packages/language-csharp/versions/1.1.0/tarball",
"language-css": "https://www.atom.io/api/packages/language-css/versions/0.43.0/tarball",
"language-css": "https://www.atom.io/api/packages/language-css/versions/0.44.0/tarball",
"language-gfm": "https://www.atom.io/api/packages/language-gfm/versions/0.90.6/tarball",
"language-git": "https://www.atom.io/api/packages/language-git/versions/0.19.1/tarball",
"language-go": "https://www.atom.io/api/packages/language-go/versions/0.46.6/tarball",
"language-go": "https://www.atom.io/api/packages/language-go/versions/0.47.0/tarball",
"language-html": "https://www.atom.io/api/packages/language-html/versions/0.52.0/tarball",
"language-hyperlink": "https://www.atom.io/api/packages/language-hyperlink/versions/0.17.0/tarball",
"language-java": "https://www.atom.io/api/packages/language-java/versions/0.31.1/tarball",
"language-javascript": "https://www.atom.io/api/packages/language-javascript/versions/0.129.19/tarball",
"language-java": "https://www.atom.io/api/packages/language-java/versions/0.31.3/tarball",
"language-javascript": "https://www.atom.io/api/packages/language-javascript/versions/0.130.0/tarball",
"language-json": "https://www.atom.io/api/packages/language-json/versions/0.19.2/tarball",
"language-less": "https://www.atom.io/api/packages/language-less/versions/0.34.3/tarball",
"language-make": "https://www.atom.io/api/packages/language-make/versions/0.23.0/tarball",
@ -98,17 +98,17 @@
"language-perl": "https://www.atom.io/api/packages/language-perl/versions/0.38.1/tarball",
"language-php": "https://www.atom.io/api/packages/language-php/versions/0.44.1/tarball",
"language-property-list": "https://www.atom.io/api/packages/language-property-list/versions/0.9.1/tarball",
"language-python": "https://www.atom.io/api/packages/language-python/versions/0.51.8/tarball",
"language-ruby": "https://www.atom.io/api/packages/language-ruby/versions/0.72.15/tarball",
"language-python": "https://www.atom.io/api/packages/language-python/versions/0.53.1/tarball",
"language-ruby": "https://www.atom.io/api/packages/language-ruby/versions/0.72.16/tarball",
"language-ruby-on-rails": "https://www.atom.io/api/packages/language-ruby-on-rails/versions/0.25.3/tarball",
"language-rust-bundled": "file:packages/language-rust-bundled",
"language-sass": "https://www.atom.io/api/packages/language-sass/versions/0.62.0/tarball",
"language-shellscript": "https://www.atom.io/api/packages/language-shellscript/versions/0.27.9/tarball",
"language-shellscript": "https://www.atom.io/api/packages/language-shellscript/versions/0.27.10/tarball",
"language-source": "https://www.atom.io/api/packages/language-source/versions/0.9.0/tarball",
"language-sql": "https://www.atom.io/api/packages/language-sql/versions/0.25.10/tarball",
"language-text": "https://www.atom.io/api/packages/language-text/versions/0.7.4/tarball",
"language-todo": "https://www.atom.io/api/packages/language-todo/versions/0.29.4/tarball",
"language-toml": "https://www.atom.io/api/packages/language-toml/versions/0.18.2/tarball",
"language-toml": "https://www.atom.io/api/packages/language-toml/versions/0.20.0/tarball",
"language-typescript": "https://www.atom.io/api/packages/language-typescript/versions/0.4.11/tarball",
"language-xml": "https://www.atom.io/api/packages/language-xml/versions/0.35.3/tarball",
"language-yaml": "https://www.atom.io/api/packages/language-yaml/versions/0.32.0/tarball",
@ -116,16 +116,16 @@
"line-ending-selector": "file:packages/line-ending-selector",
"line-top-index": "0.3.1",
"link": "file:packages/link",
"markdown-preview": "https://www.atom.io/api/packages/markdown-preview/versions/0.159.25/tarball",
"markdown-preview": "https://www.atom.io/api/packages/markdown-preview/versions/0.160.0/tarball",
"marked": "^0.3.12",
"metrics": "https://www.atom.io/api/packages/metrics/versions/1.6.2/tarball",
"metrics": "https://www.atom.io/api/packages/metrics/versions/1.7.2/tarball",
"minimatch": "^3.0.3",
"mocha": "2.5.1",
"mocha-junit-reporter": "^1.13.0",
"mocha-multi-reporters": "^1.1.4",
"mock-spawn": "^0.2.6",
"normalize-package-data": "^2.0.0",
"notifications": "https://www.atom.io/api/packages/notifications/versions/0.70.5/tarball",
"notifications": "https://www.atom.io/api/packages/notifications/versions/0.70.6/tarball",
"nslog": "^3",
"one-dark-syntax": "file:packages/one-dark-syntax",
"one-dark-ui": "file:packages/one-dark-ui",
@ -145,26 +145,26 @@
"season": "^6.0.2",
"semver": "^4.3.3",
"service-hub": "^0.7.4",
"settings-view": "https://www.atom.io/api/packages/settings-view/versions/0.258.0/tarball",
"settings-view": "https://www.atom.io/api/packages/settings-view/versions/0.261.1/tarball",
"sinon": "1.17.4",
"snippets": "https://www.atom.io/api/packages/snippets/versions/1.4.0/tarball",
"snippets": "https://www.atom.io/api/packages/snippets/versions/1.4.1/tarball",
"solarized-dark-syntax": "file:packages/solarized-dark-syntax",
"solarized-light-syntax": "file:packages/solarized-light-syntax",
"spell-check": "https://www.atom.io/api/packages/spell-check/versions/0.74.2/tarball",
"spell-check": "https://www.atom.io/api/packages/spell-check/versions/0.74.4/tarball",
"status-bar": "https://www.atom.io/api/packages/status-bar/versions/1.8.17/tarball",
"styleguide": "https://www.atom.io/api/packages/styleguide/versions/0.49.12/tarball",
"symbols-view": "https://www.atom.io/api/packages/symbols-view/versions/0.118.2/tarball",
"tabs": "https://www.atom.io/api/packages/tabs/versions/0.109.2/tarball",
"temp": "^0.8.3",
"tabs": "https://www.atom.io/api/packages/tabs/versions/0.110.0/tarball",
"temp": "^0.9.0",
"text-buffer": "13.15.1",
"timecop": "https://www.atom.io/api/packages/timecop/versions/0.36.2/tarball",
"tree-sitter": "0.13.23",
"tree-sitter-css": "^0.13.7",
"tree-view": "https://www.atom.io/api/packages/tree-view/versions/0.224.5/tarball",
"tree-view": "https://www.atom.io/api/packages/tree-view/versions/0.226.0/tarball",
"typescript-simple": "1.0.0",
"underscore-plus": "^1.6.8",
"update-package-dependencies": "https://www.atom.io/api/packages/update-package-dependencies/versions/0.13.1/tarball",
"welcome": "https://www.atom.io/api/packages/welcome/versions/0.36.7/tarball",
"welcome": "https://www.atom.io/api/packages/welcome/versions/0.36.9/tarball",
"whitespace": "https://www.atom.io/api/packages/whitespace/versions/0.37.7/tarball",
"winreg": "^1.2.1",
"wrap-guide": "https://www.atom.io/api/packages/wrap-guide/versions/0.41.0/tarball",
@ -189,11 +189,11 @@
"autocomplete-css": "0.17.5",
"autocomplete-html": "0.8.8",
"autocomplete-plus": "2.42.0",
"autocomplete-snippets": "1.12.0",
"autocomplete-snippets": "1.12.1",
"autoflow": "file:./packages/autoflow",
"autosave": "0.24.6",
"background-tips": "0.28.0",
"bookmarks": "0.45.1",
"bookmarks": "0.46.0",
"bracket-matcher": "0.90.4",
"command-palette": "0.43.5",
"dalek": "file:./packages/dalek",
@ -201,47 +201,47 @@
"dev-live-reload": "file:./packages/dev-live-reload",
"encoding-selector": "0.23.9",
"exception-reporting": "file:./packages/exception-reporting",
"find-and-replace": "0.216.0",
"fuzzy-finder": "1.8.2",
"github": "0.24.0",
"find-and-replace": "0.218.9",
"fuzzy-finder": "1.13.1",
"github": "0.28.0",
"git-diff": "file:./packages/git-diff",
"go-to-line": "file:./packages/go-to-line",
"grammar-selector": "file:./packages/grammar-selector",
"image-view": "0.63.1",
"image-view": "0.64.0",
"incompatible-packages": "file:./packages/incompatible-packages",
"keybinding-resolver": "0.38.4",
"keybinding-resolver": "0.39.0",
"line-ending-selector": "file:./packages/line-ending-selector",
"link": "file:./packages/link",
"markdown-preview": "0.159.25",
"metrics": "1.6.2",
"notifications": "0.70.5",
"markdown-preview": "0.160.0",
"metrics": "1.7.2",
"notifications": "0.70.6",
"open-on-github": "1.3.1",
"package-generator": "1.3.0",
"settings-view": "0.258.0",
"snippets": "1.4.0",
"spell-check": "0.74.2",
"settings-view": "0.261.1",
"snippets": "1.4.1",
"spell-check": "0.74.4",
"status-bar": "1.8.17",
"styleguide": "0.49.12",
"symbols-view": "0.118.2",
"tabs": "0.109.2",
"tabs": "0.110.0",
"timecop": "0.36.2",
"tree-view": "0.224.5",
"tree-view": "0.226.0",
"update-package-dependencies": "0.13.1",
"welcome": "0.36.7",
"welcome": "0.36.9",
"whitespace": "0.37.7",
"wrap-guide": "0.41.0",
"language-c": "0.60.14",
"language-clojure": "0.22.7",
"language-coffee-script": "0.49.3",
"language-c": "0.60.15",
"language-clojure": "0.22.8",
"language-coffee-script": "0.50.0",
"language-csharp": "1.1.0",
"language-css": "0.43.0",
"language-css": "0.44.0",
"language-gfm": "0.90.6",
"language-git": "0.19.1",
"language-go": "0.46.6",
"language-go": "0.47.0",
"language-html": "0.52.0",
"language-hyperlink": "0.17.0",
"language-java": "0.31.1",
"language-javascript": "0.129.19",
"language-java": "0.31.3",
"language-javascript": "0.130.0",
"language-json": "0.19.2",
"language-less": "0.34.3",
"language-make": "0.23.0",
@ -250,17 +250,17 @@
"language-perl": "0.38.1",
"language-php": "0.44.1",
"language-property-list": "0.9.1",
"language-python": "0.51.8",
"language-ruby": "0.72.15",
"language-python": "0.53.1",
"language-ruby": "0.72.16",
"language-ruby-on-rails": "0.25.3",
"language-rust-bundled": "file:./packages/language-rust-bundled",
"language-sass": "0.62.0",
"language-shellscript": "0.27.9",
"language-shellscript": "0.27.10",
"language-source": "0.9.0",
"language-sql": "0.25.10",
"language-text": "0.7.4",
"language-todo": "0.29.4",
"language-toml": "0.18.2",
"language-toml": "0.20.0",
"language-typescript": "0.4.11",
"language-xml": "0.35.3",
"language-yaml": "0.32.0"

View File

@ -1,4 +1,4 @@
const {CompositeDisposable, Emitter} = require('atom')
const { CompositeDisposable, Emitter } = require('atom')
const AboutView = require('./components/about-view')
// Deferred requires
@ -14,16 +14,22 @@ module.exports = class About {
aboutView: null
}
this.subscriptions.add(atom.workspace.addOpener((uriToOpen) => {
if (uriToOpen === this.state.uri) {
return this.deserialize()
}
}))
this.subscriptions.add(
atom.workspace.addOpener(uriToOpen => {
if (uriToOpen === this.state.uri) {
return this.deserialize()
}
})
)
this.subscriptions.add(atom.commands.add('atom-workspace', 'about:view-release-notes', () => {
shell = shell || require('electron').shell
shell.openExternal(this.state.updateManager.getReleaseNotesURLForCurrentVersion())
}))
this.subscriptions.add(
atom.commands.add('atom-workspace', 'about:view-release-notes', () => {
shell = shell || require('electron').shell
shell.openExternal(
this.state.updateManager.getReleaseNotesURLForCurrentVersion()
)
})
)
}
destroy () {
@ -31,14 +37,14 @@ module.exports = class About {
this.views.aboutView = null
if (this.state.updateManager) this.state.updateManager.dispose()
this.setState({updateManager: null})
this.setState({ updateManager: null })
this.subscriptions.dispose()
}
setState (newState) {
if (newState && typeof newState === 'object') {
let {state} = this
let { state } = this
this.state = Object.assign({}, state, newState)
this.didChange()

View File

@ -1,16 +1,20 @@
const {CompositeDisposable} = require('atom')
const { CompositeDisposable } = require('atom')
const etch = require('etch')
const EtchComponent = require('../etch-component')
const $ = etch.dom
module.exports =
class AboutStatusBar extends EtchComponent {
module.exports = class AboutStatusBar extends EtchComponent {
constructor () {
super()
this.subscriptions = new CompositeDisposable()
this.subscriptions.add(atom.tooltips.add(this.element, {title: 'An update will be installed the next time Atom is relaunched.<br/><br/>Click the squirrel icon for more information.'}))
this.subscriptions.add(
atom.tooltips.add(this.element, {
title:
'An update will be installed the next time Atom is relaunched.<br/><br/>Click the squirrel icon for more information.'
})
)
}
handleClick () {
@ -18,8 +22,12 @@ class AboutStatusBar extends EtchComponent {
}
render () {
return $.div({className: 'about-release-notes inline-block', onclick: this.handleClick.bind(this)},
$.span({type: 'button', className: 'icon icon-squirrel'})
return $.div(
{
className: 'about-release-notes inline-block',
onclick: this.handleClick.bind(this)
},
$.span({ type: 'button', className: 'icon icon-squirrel' })
)
}

View File

@ -1,4 +1,4 @@
const {Disposable} = require('atom')
const { Disposable } = require('atom')
const etch = require('etch')
const shell = require('shell')
const AtomLogo = require('./atom-logo')
@ -7,8 +7,7 @@ const UpdateView = require('./update-view')
const $ = etch.dom
module.exports =
class AboutView extends EtchComponent {
module.exports = class AboutView extends EtchComponent {
handleAtomVersionClick (e) {
e.preventDefault()
atom.clipboard.write(this.props.currentAtomVersion)
@ -31,12 +30,17 @@ class AboutView extends EtchComponent {
handleReleaseNotesClick (e) {
e.preventDefault()
shell.openExternal(this.props.updateManager.getReleaseNotesURLForAvailableVersion())
shell.openExternal(
this.props.updateManager.getReleaseNotesURLForAvailableVersion()
)
}
handleLicenseClick (e) {
e.preventDefault()
atom.commands.dispatch(atom.views.getView(atom.workspace), 'application:open-license')
atom.commands.dispatch(
atom.views.getView(atom.workspace),
'application:open-license'
)
}
handleTermsOfUseClick (e) {
@ -46,7 +50,9 @@ class AboutView extends EtchComponent {
handleHowToUpdateClick (e) {
e.preventDefault()
shell.openExternal('https://flight-manual.atom.io/getting-started/sections/installing-atom/')
shell.openExternal(
'https://flight-manual.atom.io/getting-started/sections/installing-atom/'
)
}
handleShowMoreClick (e) {
@ -66,39 +72,87 @@ class AboutView extends EtchComponent {
}
render () {
return $.div({className: 'pane-item native-key-bindings about'},
$.div({className: 'about-container'},
$.header({className: 'about-header'},
$.a({className: 'about-atom-io', href: 'https://atom.io'},
return $.div(
{ className: 'pane-item native-key-bindings about' },
$.div(
{ className: 'about-container' },
$.header(
{ className: 'about-header' },
$.a(
{ className: 'about-atom-io', href: 'https://atom.io' },
$(AtomLogo)
),
$.div({className: 'about-header-info'},
$.span({className: 'about-version-container inline-block atom', onclick: this.handleAtomVersionClick.bind(this)},
$.span({className: 'about-version'}, `${this.props.currentAtomVersion} ${process.arch}`),
$.span({className: 'icon icon-clippy about-copy-version'})
$.div(
{ className: 'about-header-info' },
$.span(
{
className: 'about-version-container inline-block atom',
onclick: this.handleAtomVersionClick.bind(this)
},
$.span(
{ className: 'about-version' },
`${this.props.currentAtomVersion} ${process.arch}`
),
$.span({ className: 'icon icon-clippy about-copy-version' })
),
$.a({className: 'about-header-release-notes', onclick: this.handleReleaseNotesClick.bind(this)}, 'Release Notes')
$.a(
{
className: 'about-header-release-notes',
onclick: this.handleReleaseNotesClick.bind(this)
},
'Release Notes'
)
),
$.span({className: 'about-version-container inline-block show-more-expand', onclick: this.handleShowMoreClick.bind(this)},
$.span({className: 'about-more-expand'}, 'Show more')
$.span(
{
className:
'about-version-container inline-block show-more-expand',
onclick: this.handleShowMoreClick.bind(this)
},
$.span({ className: 'about-more-expand' }, 'Show more')
),
$.div({className: 'show-more hidden about-more-info'},
$.div({className: 'about-more-info'},
$.span({className: 'about-version-container inline-block electron', onclick: this.handleElectronVersionClick.bind(this)},
$.span({className: 'about-more-version'}, `Electron: ${this.props.currentElectronVersion} `),
$.span({className: 'icon icon-clippy about-copy-version'})
$.div(
{ className: 'show-more hidden about-more-info' },
$.div(
{ className: 'about-more-info' },
$.span(
{
className: 'about-version-container inline-block electron',
onclick: this.handleElectronVersionClick.bind(this)
},
$.span(
{ className: 'about-more-version' },
`Electron: ${this.props.currentElectronVersion} `
),
$.span({ className: 'icon icon-clippy about-copy-version' })
)
),
$.div({className: 'about-more-info'},
$.span({className: 'about-version-container inline-block chrome', onclick: this.handleChromeVersionClick.bind(this)},
$.span({className: 'about-more-version'}, `Chrome: ${this.props.currentChromeVersion} `),
$.span({className: 'icon icon-clippy about-copy-version'})
$.div(
{ className: 'about-more-info' },
$.span(
{
className: 'about-version-container inline-block chrome',
onclick: this.handleChromeVersionClick.bind(this)
},
$.span(
{ className: 'about-more-version' },
`Chrome: ${this.props.currentChromeVersion} `
),
$.span({ className: 'icon icon-clippy about-copy-version' })
)
),
$.div({className: 'about-more-info'},
$.span({className: 'about-version-container inline-block node', onclick: this.handleNodeVersionClick.bind(this)},
$.span({className: 'about-more-version'}, `Node: ${this.props.currentNodeVersion} `),
$.span({className: 'icon icon-clippy about-copy-version'})
$.div(
{ className: 'about-more-info' },
$.span(
{
className: 'about-version-container inline-block node',
onclick: this.handleNodeVersionClick.bind(this)
},
$.span(
{ className: 'about-more-version' },
`Node: ${this.props.currentNodeVersion} `
),
$.span({ className: 'icon icon-clippy about-copy-version' })
)
)
)
@ -112,24 +166,43 @@ class AboutView extends EtchComponent {
viewUpdateInstructions: this.handleHowToUpdateClick.bind(this)
}),
$.div({className: 'about-actions group-item'},
$.div({className: 'btn-group'},
$.button({className: 'btn view-license', onclick: this.handleLicenseClick.bind(this)}, 'License'),
$.button({className: 'btn terms-of-use', onclick: this.handleTermsOfUseClick.bind(this)}, 'Terms of Use')
$.div(
{ className: 'about-actions group-item' },
$.div(
{ className: 'btn-group' },
$.button(
{
className: 'btn view-license',
onclick: this.handleLicenseClick.bind(this)
},
'License'
),
$.button(
{
className: 'btn terms-of-use',
onclick: this.handleTermsOfUseClick.bind(this)
},
'Terms of Use'
)
)
),
$.div({className: 'about-love group-start'},
$.span({className: 'icon icon-code'}),
$.span({className: 'inline'}, ' with '),
$.span({className: 'icon icon-heart'}),
$.span({className: 'inline'}, ' by '),
$.a({className: 'icon icon-logo-github', href: 'https://github.com'})
$.div(
{ className: 'about-love group-start' },
$.span({ className: 'icon icon-code' }),
$.span({ className: 'inline' }, ' with '),
$.span({ className: 'icon icon-heart' }),
$.span({ className: 'inline' }, ' by '),
$.a({ className: 'icon icon-logo-github', href: 'https://github.com' })
),
$.div({className: 'about-credits group-item'},
$.span({className: 'inline'}, 'And the awesome '),
$.a({href: 'https://github.com/atom/atom/contributors'}, 'Atom Community')
$.div(
{ className: 'about-credits group-item' },
$.span({ className: 'inline' }, 'And the awesome '),
$.a(
{ href: 'https://github.com/atom/atom/contributors' },
'Atom Community'
)
)
)
}

View File

@ -3,23 +3,74 @@ const EtchComponent = require('../etch-component')
const $ = etch.dom
module.exports =
class AtomLogo extends EtchComponent {
module.exports = class AtomLogo extends EtchComponent {
render () {
return $.svg({className: 'about-logo', width: '330px', height: '68px', viewBox: '0 0 330 68'},
$.g({stroke: 'none', 'stroke-width': '1', fill: 'none', 'fill-rule': 'evenodd'},
$.g({transform: 'translate(2.000000, 1.000000)'},
$.g({transform: 'translate(96.000000, 8.000000)', fill: 'currentColor'},
$.path({d: 'M185.498,3.399 C185.498,2.417 186.34,1.573 187.324,1.573 L187.674,1.573 C188.447,1.573 189.01,1.995 189.5,2.628 L208.676,30.862 L227.852,2.628 C228.272,1.995 228.905,1.573 229.676,1.573 L230.028,1.573 C231.01,1.573 231.854,2.417 231.854,3.399 L231.854,49.403 C231.854,50.387 231.01,51.231 230.028,51.231 C229.044,51.231 228.202,50.387 228.202,49.403 L228.202,8.246 L210.151,34.515 C209.729,35.148 209.237,35.428 208.606,35.428 C207.973,35.428 207.481,35.148 207.061,34.515 L189.01,8.246 L189.01,49.475 C189.01,50.457 188.237,51.231 187.254,51.231 C186.27,51.231 185.498,50.458 185.498,49.475 L185.498,3.399 L185.498,3.399 Z'}),
$.path({d: 'M113.086,26.507 L113.086,26.367 C113.086,12.952 122.99,0.941 137.881,0.941 C152.77,0.941 162.533,12.811 162.533,26.225 L162.533,26.367 C162.533,39.782 152.629,51.792 137.74,51.792 C122.85,51.792 113.086,39.923 113.086,26.507 M158.74,26.507 L158.74,26.367 C158.74,14.216 149.89,4.242 137.74,4.242 C125.588,4.242 116.879,14.075 116.879,26.225 L116.879,26.367 C116.879,38.518 125.729,48.491 137.881,48.491 C150.031,48.491 158.74,38.658 158.74,26.507'}),
$.path({d: 'M76.705,5.155 L60.972,5.155 C60.06,5.155 59.287,4.384 59.287,3.469 C59.287,2.556 60.059,1.783 60.972,1.783 L96.092,1.783 C97.004,1.783 97.778,2.555 97.778,3.469 C97.778,4.383 97.005,5.155 96.092,5.155 L80.358,5.155 L80.358,49.405 C80.358,50.387 79.516,51.231 78.532,51.231 C77.55,51.231 76.706,50.387 76.706,49.405 L76.706,5.155 L76.705,5.155 Z'}),
$.path({d: 'M0.291,48.562 L21.291,3.05 C21.783,1.995 22.485,1.292 23.75,1.292 L23.891,1.292 C25.155,1.292 25.858,1.995 26.348,3.05 L47.279,48.421 C47.49,48.843 47.56,49.194 47.56,49.546 C47.56,50.458 46.788,51.231 45.803,51.231 C44.961,51.231 44.329,50.599 43.978,49.826 L38.219,37.183 L9.21,37.183 L3.45,49.897 C3.099,50.739 2.538,51.231 1.694,51.231 C0.781,51.231 0.008,50.529 0.008,49.685 C0.009,49.404 0.08,48.983 0.291,48.562 L0.291,48.562 Z M36.673,33.882 L23.749,5.437 L10.755,33.882 L36.673,33.882 L36.673,33.882 Z'})
return $.svg(
{
className: 'about-logo',
width: '330px',
height: '68px',
viewBox: '0 0 330 68'
},
$.g(
{
stroke: 'none',
'stroke-width': '1',
fill: 'none',
'fill-rule': 'evenodd'
},
$.g(
{ transform: 'translate(2.000000, 1.000000)' },
$.g(
{
transform: 'translate(96.000000, 8.000000)',
fill: 'currentColor'
},
$.path({
d:
'M185.498,3.399 C185.498,2.417 186.34,1.573 187.324,1.573 L187.674,1.573 C188.447,1.573 189.01,1.995 189.5,2.628 L208.676,30.862 L227.852,2.628 C228.272,1.995 228.905,1.573 229.676,1.573 L230.028,1.573 C231.01,1.573 231.854,2.417 231.854,3.399 L231.854,49.403 C231.854,50.387 231.01,51.231 230.028,51.231 C229.044,51.231 228.202,50.387 228.202,49.403 L228.202,8.246 L210.151,34.515 C209.729,35.148 209.237,35.428 208.606,35.428 C207.973,35.428 207.481,35.148 207.061,34.515 L189.01,8.246 L189.01,49.475 C189.01,50.457 188.237,51.231 187.254,51.231 C186.27,51.231 185.498,50.458 185.498,49.475 L185.498,3.399 L185.498,3.399 Z'
}),
$.path({
d:
'M113.086,26.507 L113.086,26.367 C113.086,12.952 122.99,0.941 137.881,0.941 C152.77,0.941 162.533,12.811 162.533,26.225 L162.533,26.367 C162.533,39.782 152.629,51.792 137.74,51.792 C122.85,51.792 113.086,39.923 113.086,26.507 M158.74,26.507 L158.74,26.367 C158.74,14.216 149.89,4.242 137.74,4.242 C125.588,4.242 116.879,14.075 116.879,26.225 L116.879,26.367 C116.879,38.518 125.729,48.491 137.881,48.491 C150.031,48.491 158.74,38.658 158.74,26.507'
}),
$.path({
d:
'M76.705,5.155 L60.972,5.155 C60.06,5.155 59.287,4.384 59.287,3.469 C59.287,2.556 60.059,1.783 60.972,1.783 L96.092,1.783 C97.004,1.783 97.778,2.555 97.778,3.469 C97.778,4.383 97.005,5.155 96.092,5.155 L80.358,5.155 L80.358,49.405 C80.358,50.387 79.516,51.231 78.532,51.231 C77.55,51.231 76.706,50.387 76.706,49.405 L76.706,5.155 L76.705,5.155 Z'
}),
$.path({
d:
'M0.291,48.562 L21.291,3.05 C21.783,1.995 22.485,1.292 23.75,1.292 L23.891,1.292 C25.155,1.292 25.858,1.995 26.348,3.05 L47.279,48.421 C47.49,48.843 47.56,49.194 47.56,49.546 C47.56,50.458 46.788,51.231 45.803,51.231 C44.961,51.231 44.329,50.599 43.978,49.826 L38.219,37.183 L9.21,37.183 L3.45,49.897 C3.099,50.739 2.538,51.231 1.694,51.231 C0.781,51.231 0.008,50.529 0.008,49.685 C0.009,49.404 0.08,48.983 0.291,48.562 L0.291,48.562 Z M36.673,33.882 L23.749,5.437 L10.755,33.882 L36.673,33.882 L36.673,33.882 Z'
})
),
$.g({},
$.path({d: 'M40.363,32.075 C40.874,34.44 39.371,36.77 37.006,37.282 C34.641,37.793 32.311,36.29 31.799,33.925 C31.289,31.56 32.791,29.23 35.156,28.718 C37.521,28.207 39.851,29.71 40.363,32.075', fill: 'currentColor'}),
$.path({d: 'M48.578,28.615 C56.851,45.587 58.558,61.581 52.288,64.778 C45.822,68.076 33.326,56.521 24.375,38.969 C15.424,21.418 13.409,4.518 19.874,1.221 C22.689,-0.216 26.648,1.166 30.959,4.629', stroke: 'currentColor', 'stroke-width': '3.08', 'stroke-linecap': 'round'}),
$.path({d: 'M7.64,39.45 C2.806,36.94 -0.009,33.915 0.154,30.79 C0.531,23.542 16.787,18.497 36.462,19.52 C56.137,20.544 71.781,27.249 71.404,34.497 C71.241,37.622 68.127,40.338 63.06,42.333', stroke: 'currentColor', 'stroke-width': '3.08', 'stroke-linecap': 'round'}),
$.path({d: 'M28.828,59.354 C23.545,63.168 18.843,64.561 15.902,62.653 C9.814,58.702 13.572,42.102 24.296,25.575 C35.02,9.048 48.649,-1.149 54.736,2.803 C57.566,4.639 58.269,9.208 57.133,15.232', stroke: 'currentColor', 'stroke-width': '3.08', 'stroke-linecap': 'round'})
$.g(
{},
$.path({
d:
'M40.363,32.075 C40.874,34.44 39.371,36.77 37.006,37.282 C34.641,37.793 32.311,36.29 31.799,33.925 C31.289,31.56 32.791,29.23 35.156,28.718 C37.521,28.207 39.851,29.71 40.363,32.075',
fill: 'currentColor'
}),
$.path({
d:
'M48.578,28.615 C56.851,45.587 58.558,61.581 52.288,64.778 C45.822,68.076 33.326,56.521 24.375,38.969 C15.424,21.418 13.409,4.518 19.874,1.221 C22.689,-0.216 26.648,1.166 30.959,4.629',
stroke: 'currentColor',
'stroke-width': '3.08',
'stroke-linecap': 'round'
}),
$.path({
d:
'M7.64,39.45 C2.806,36.94 -0.009,33.915 0.154,30.79 C0.531,23.542 16.787,18.497 36.462,19.52 C56.137,20.544 71.781,27.249 71.404,34.497 C71.241,37.622 68.127,40.338 63.06,42.333',
stroke: 'currentColor',
'stroke-width': '3.08',
'stroke-linecap': 'round'
}),
$.path({
d:
'M28.828,59.354 C23.545,63.168 18.843,64.561 15.902,62.653 C9.814,58.702 13.572,42.102 24.296,25.575 C35.02,9.048 48.649,-1.149 54.736,2.803 C57.566,4.639 58.269,9.208 57.133,15.232',
stroke: 'currentColor',
'stroke-width': '3.08',
'stroke-linecap': 'round'
})
)
)
)

View File

@ -4,12 +4,14 @@ const UpdateManager = require('../update-manager')
const $ = etch.dom
module.exports =
class UpdateView extends EtchComponent {
module.exports = class UpdateView extends EtchComponent {
constructor (props) {
super(props)
if (this.props.updateManager.getAutoUpdatesEnabled() && this.props.updateManager.getState() === UpdateManager.State.Idle) {
if (
this.props.updateManager.getAutoUpdatesEnabled() &&
this.props.updateManager.getState() === UpdateManager.State.Idle
) {
this.props.updateManager.checkForUpdate()
}
}
@ -19,12 +21,18 @@ class UpdateView extends EtchComponent {
}
shouldUpdateActionButtonBeDisabled () {
let {state} = this.props.updateManager
return state === UpdateManager.State.CheckingForUpdate || state === UpdateManager.State.DownloadingUpdate
let { state } = this.props.updateManager
return (
state === UpdateManager.State.CheckingForUpdate ||
state === UpdateManager.State.DownloadingUpdate
)
}
executeUpdateAction () {
if (this.props.updateManager.state === UpdateManager.State.UpdateAvailableToInstall) {
if (
this.props.updateManager.state ===
UpdateManager.State.UpdateAvailableToInstall
) {
this.props.updateManager.restartAndInstallUpdate()
} else {
this.props.updateManager.checkForUpdate()
@ -36,44 +44,86 @@ class UpdateView extends EtchComponent {
switch (this.props.updateManager.state) {
case UpdateManager.State.Idle:
updateStatus = $.div({className: 'about-updates-item is-shown about-default-update-message'},
this.props.updateManager.getAutoUpdatesEnabled() ? 'Atom will check for updates automatically' : 'Automatic updates are disabled please check manually'
updateStatus = $.div(
{
className:
'about-updates-item is-shown about-default-update-message'
},
this.props.updateManager.getAutoUpdatesEnabled()
? 'Atom will check for updates automatically'
: 'Automatic updates are disabled please check manually'
)
break
case UpdateManager.State.CheckingForUpdate:
updateStatus = $.div({className: 'about-updates-item app-checking-for-updates'},
$.span({className: 'about-updates-label icon icon-search'}, 'Checking for updates...')
updateStatus = $.div(
{ className: 'about-updates-item app-checking-for-updates' },
$.span(
{ className: 'about-updates-label icon icon-search' },
'Checking for updates...'
)
)
break
case UpdateManager.State.DownloadingUpdate:
updateStatus = $.div({className: 'about-updates-item app-downloading-update'},
$.span({className: 'loading loading-spinner-tiny inline-block'}),
$.span({className: 'about-updates-label'}, 'Downloading update')
updateStatus = $.div(
{ className: 'about-updates-item app-downloading-update' },
$.span({ className: 'loading loading-spinner-tiny inline-block' }),
$.span({ className: 'about-updates-label' }, 'Downloading update')
)
break
case UpdateManager.State.UpdateAvailableToInstall:
updateStatus = $.div({className: 'about-updates-item app-update-available-to-install'},
$.span({className: 'about-updates-label icon icon-squirrel'}, 'New update'),
$.span({className: 'about-updates-version'}, this.props.availableVersion),
$.a({className: 'about-updates-release-notes', onclick: this.props.viewUpdateReleaseNotes}, 'Release Notes')
updateStatus = $.div(
{ className: 'about-updates-item app-update-available-to-install' },
$.span(
{ className: 'about-updates-label icon icon-squirrel' },
'New update'
),
$.span(
{ className: 'about-updates-version' },
this.props.availableVersion
),
$.a(
{
className: 'about-updates-release-notes',
onclick: this.props.viewUpdateReleaseNotes
},
'Release Notes'
)
)
break
case UpdateManager.State.UpToDate:
updateStatus = $.div({className: 'about-updates-item app-up-to-date'},
$.span({className: 'icon icon-check'}),
$.span({className: 'about-updates-label is-strong'}, 'Atom is up to date!')
updateStatus = $.div(
{ className: 'about-updates-item app-up-to-date' },
$.span({ className: 'icon icon-check' }),
$.span(
{ className: 'about-updates-label is-strong' },
'Atom is up to date!'
)
)
break
case UpdateManager.State.Unsupported:
updateStatus = $.div({className: 'about-updates-item app-unsupported'},
$.span({className: 'about-updates-label is-strong'}, 'Your system does not support automatic updates'),
$.a({className: 'about-updates-instructions', onclick: this.props.viewUpdateInstructions}, 'How to update')
updateStatus = $.div(
{ className: 'about-updates-item app-unsupported' },
$.span(
{ className: 'about-updates-label is-strong' },
'Your system does not support automatic updates'
),
$.a(
{
className: 'about-updates-instructions',
onclick: this.props.viewUpdateInstructions
},
'How to update'
)
)
break
case UpdateManager.State.Error:
updateStatus = $.div({className: 'about-updates-item app-update-error'},
$.span({className: 'icon icon-x'}),
$.span({className: 'about-updates-label app-error-message is-strong'}, this.props.updateManager.getErrorMessage())
updateStatus = $.div(
{ className: 'about-updates-item app-update-error' },
$.span({ className: 'icon icon-x' }),
$.span(
{ className: 'about-updates-label app-error-message is-strong' },
this.props.updateManager.getErrorMessage()
)
)
break
}
@ -82,37 +132,47 @@ class UpdateView extends EtchComponent {
}
render () {
return $.div({className: 'about-updates group-start'},
$.div({className: 'about-updates-box'},
$.div({className: 'about-updates-status'}, this.renderUpdateStatus()),
return $.div(
{ className: 'about-updates group-start' },
$.div(
{ className: 'about-updates-box' },
$.div({ className: 'about-updates-status' }, this.renderUpdateStatus()),
$.button(
{
className: 'btn about-update-action-button',
disabled: this.shouldUpdateActionButtonBeDisabled(),
onclick: this.executeUpdateAction.bind(this),
style: {
display: this.props.updateManager.state === UpdateManager.State.Unsupported ? 'none' : 'block'
display:
this.props.updateManager.state ===
UpdateManager.State.Unsupported
? 'none'
: 'block'
}
},
this.props.updateManager.state === 'update-available' ? 'Restart and install' : 'Check now'
this.props.updateManager.state === 'update-available'
? 'Restart and install'
: 'Check now'
)
),
$.div(
{
className: 'about-auto-updates',
style: {
display: this.props.updateManager.state === UpdateManager.State.Unsupported ? 'none' : 'block'
display:
this.props.updateManager.state === UpdateManager.State.Unsupported
? 'none'
: 'block'
}
},
$.label({},
$.input(
{
className: 'input-checkbox',
type: 'checkbox',
checked: this.props.updateManager.getAutoUpdatesEnabled(),
onchange: this.handleAutoUpdateCheckbox.bind(this)
}
),
$.label(
{},
$.input({
className: 'input-checkbox',
type: 'checkbox',
checked: this.props.updateManager.getAutoUpdatesEnabled(),
onchange: this.handleAutoUpdateCheckbox.bind(this)
}),
$.span({}, 'Automatically download updates')
)
)

View File

@ -4,8 +4,7 @@ const etch = require('etch')
Public: Abstract class for handling the initialization
boilerplate of an Etch component.
*/
module.exports =
class EtchComponent {
module.exports = class EtchComponent {
constructor (props) {
this.props = props

View File

@ -1,4 +1,4 @@
const {CompositeDisposable} = require('atom')
const { CompositeDisposable } = require('atom')
const semver = require('semver')
const UpdateManager = require('./update-manager')
const About = require('./about')
@ -16,20 +16,33 @@ module.exports = {
this.createModel()
let availableVersion = window.localStorage.getItem(AvailableUpdateVersion)
if (atom.getReleaseChannel() === 'dev' || (availableVersion && semver.lte(availableVersion, atom.getVersion()))) {
if (
atom.getReleaseChannel() === 'dev' ||
(availableVersion && semver.lte(availableVersion, atom.getVersion()))
) {
this.clearUpdateState()
}
this.subscriptions.add(updateManager.onDidChange(() => {
if (updateManager.getState() === UpdateManager.State.UpdateAvailableToInstall) {
window.localStorage.setItem(AvailableUpdateVersion, updateManager.getAvailableVersion())
this.showStatusBarIfNeeded()
}
}))
this.subscriptions.add(
updateManager.onDidChange(() => {
if (
updateManager.getState() ===
UpdateManager.State.UpdateAvailableToInstall
) {
window.localStorage.setItem(
AvailableUpdateVersion,
updateManager.getAvailableVersion()
)
this.showStatusBarIfNeeded()
}
})
)
this.subscriptions.add(atom.commands.add('atom-workspace', 'about:clear-update-state', () => {
this.clearUpdateState()
}))
this.subscriptions.add(
atom.commands.add('atom-workspace', 'about:clear-update-state', () => {
this.clearUpdateState()
})
)
},
deactivate () {

View File

@ -1,4 +1,4 @@
const {Emitter, CompositeDisposable} = require('atom')
const { Emitter, CompositeDisposable } = require('atom')
const Unsupported = 'unsupported'
const Idle = 'idle'
@ -27,7 +27,7 @@ let UpdateManager = class UpdateManager {
atom.autoUpdater.onDidBeginDownloadingUpdate(() => {
this.setState(DownloadingUpdate)
}),
atom.autoUpdater.onDidCompleteDownloadingUpdate(({releaseVersion}) => {
atom.autoUpdater.onDidCompleteDownloadingUpdate(({ releaseVersion }) => {
this.setAvailableVersion(releaseVersion)
}),
atom.autoUpdater.onUpdateNotAvailable(() => {
@ -36,7 +36,7 @@ let UpdateManager = class UpdateManager {
atom.autoUpdater.onUpdateError(() => {
this.setState(ErrorState)
}),
atom.config.observe('core.automaticallyUpdate', (value) => {
atom.config.observe('core.automaticallyUpdate', value => {
this.autoUpdatesEnabled = value
this.emitDidChange()
})
@ -61,7 +61,9 @@ let UpdateManager = class UpdateManager {
}
getAutoUpdatesEnabled () {
return this.autoUpdatesEnabled && this.state !== UpdateManager.State.Unsupported
return (
this.autoUpdatesEnabled && this.state !== UpdateManager.State.Unsupported
)
}
setAutoUpdatesEnabled (enabled) {
@ -82,7 +84,9 @@ let UpdateManager = class UpdateManager {
}
resetState () {
this.state = atom.autoUpdater.platformSupportsUpdates() ? atom.autoUpdater.getState() : Unsupported
this.state = atom.autoUpdater.platformSupportsUpdates()
? atom.autoUpdater.getState()
: Unsupported
this.emitDidChange()
}
@ -128,7 +132,8 @@ let UpdateManager = class UpdateManager {
appVersion = `v${appVersion}`
}
const releaseRepo = appVersion.indexOf('nightly') > -1 ? 'atom-nightly-releases' : 'atom'
const releaseRepo =
appVersion.indexOf('nightly') > -1 ? 'atom-nightly-releases' : 'atom'
return `https://github.com/atom/${releaseRepo}/releases/tag/${appVersion}`
}
}

View File

@ -1,5 +1,3 @@
const {it, fit, ffit, fffit, beforeEach, afterEach} = require('./helpers/async-spec-helpers') // eslint-disable-line no-unused-vars
describe('About', () => {
let workspaceElement
@ -9,7 +7,7 @@ describe('About', () => {
spyOn(window.localStorage, 'setItem').andCallFake((key, value) => {
storage[key] = value
})
spyOn(window.localStorage, 'getItem').andCallFake((key) => {
spyOn(window.localStorage, 'getItem').andCallFake(key => {
return storage[key]
})

View File

@ -1,4 +1,4 @@
const {it, fit, ffit, fffit, beforeEach, afterEach, conditionPromise} = require('./helpers/async-spec-helpers') // eslint-disable-line no-unused-vars
const { conditionPromise } = require('./helpers/async-spec-helpers')
const MockUpdater = require('./mocks/updater')
describe('the status bar', () => {
@ -11,7 +11,7 @@ describe('the status bar', () => {
spyOn(window.localStorage, 'setItem').andCallFake((key, value) => {
storage[key] = value
})
spyOn(window.localStorage, 'getItem').andCallFake((key) => {
spyOn(window.localStorage, 'getItem').andCallFake(key => {
return storage[key]
})
spyOn(atom, 'getVersion').andCallFake(() => {

View File

@ -1,36 +1,7 @@
/** @babel */
const {now} = Date
const {setTimeout} = global
export function beforeEach (fn) {
global.beforeEach(function () {
const result = fn()
if (result instanceof Promise) {
waitsForPromise(() => result)
}
})
}
export function afterEach (fn) {
global.afterEach(function () {
const result = fn()
if (result instanceof Promise) {
waitsForPromise(() => result)
}
})
}
['it', 'fit', 'ffit', 'fffit'].forEach(function (name) {
module.exports[name] = function (description, fn) {
global[name](description, function () {
const result = fn()
if (result instanceof Promise) {
waitsForPromise(() => result)
}
})
}
})
const { now } = Date
const { setTimeout } = global
export async function conditionPromise (condition) {
const startTime = now()
@ -53,13 +24,3 @@ export function timeoutPromise (timeout) {
setTimeout(resolve, timeout)
})
}
function waitsForPromise (fn) {
const promise = fn()
global.waitsFor('spec promise to resolve', function (done) {
promise.then(done, function (error) {
jasmine.getEnv().currentSpec.fail(error)
done()
})
})
}

View File

@ -16,6 +16,8 @@ module.exports = {
},
finishDownloadingUpdate (releaseVersion) {
atom.autoUpdater.emitter.emit('did-complete-downloading-update', {releaseVersion})
atom.autoUpdater.emitter.emit('did-complete-downloading-update', {
releaseVersion
})
}
}

View File

@ -9,14 +9,24 @@ describe('UpdateManager', () => {
describe('::getReleaseNotesURLForVersion', () => {
it('returns atom.io releases when dev version', () => {
expect(updateManager.getReleaseNotesURLForVersion('1.7.0-dev-e44b57d')).toContain('atom.io/releases')
expect(
updateManager.getReleaseNotesURLForVersion('1.7.0-dev-e44b57d')
).toContain('atom.io/releases')
})
it('returns the page for the release when not a dev version', () => {
expect(updateManager.getReleaseNotesURLForVersion('1.7.0')).toContain('atom/atom/releases/tag/v1.7.0')
expect(updateManager.getReleaseNotesURLForVersion('v1.7.0')).toContain('atom/atom/releases/tag/v1.7.0')
expect(updateManager.getReleaseNotesURLForVersion('1.7.0-beta10')).toContain('atom/atom/releases/tag/v1.7.0-beta10')
expect(updateManager.getReleaseNotesURLForVersion('1.7.0-nightly10')).toContain('atom/atom-nightly-releases/releases/tag/v1.7.0-nightly10')
expect(updateManager.getReleaseNotesURLForVersion('1.7.0')).toContain(
'atom/atom/releases/tag/v1.7.0'
)
expect(updateManager.getReleaseNotesURLForVersion('v1.7.0')).toContain(
'atom/atom/releases/tag/v1.7.0'
)
expect(
updateManager.getReleaseNotesURLForVersion('1.7.0-beta10')
).toContain('atom/atom/releases/tag/v1.7.0-beta10')
expect(
updateManager.getReleaseNotesURLForVersion('1.7.0-nightly10')
).toContain('atom/atom-nightly-releases/releases/tag/v1.7.0-nightly10')
})
})
})

View File

@ -1,5 +1,4 @@
const {shell} = require('electron')
const {it, fit, ffit, fffit, beforeEach, afterEach} = require('./helpers/async-spec-helpers') // eslint-disable-line no-unused-vars
const { shell } = require('electron')
const main = require('../lib/main')
const AboutView = require('../lib/components/about-view')
const UpdateView = require('../lib/components/update-view')
@ -17,7 +16,7 @@ describe('UpdateView', () => {
spyOn(window.localStorage, 'setItem').andCallFake((key, value) => {
storage[key] = value
})
spyOn(window.localStorage, 'getItem').andCallFake((key) => {
spyOn(window.localStorage, 'getItem').andCallFake(key => {
return storage[key]
})
@ -45,13 +44,19 @@ describe('UpdateView', () => {
})
it('hides the auto update UI and shows the update instructions link', async () => {
expect(aboutElement.querySelector('.about-update-action-button')).not.toBeVisible()
expect(aboutElement.querySelector('.about-auto-updates')).not.toBeVisible()
expect(
aboutElement.querySelector('.about-update-action-button')
).not.toBeVisible()
expect(
aboutElement.querySelector('.about-auto-updates')
).not.toBeVisible()
})
it('opens the update instructions page when the instructions link is clicked', async () => {
spyOn(shell, 'openExternal')
let link = aboutElement.querySelector('.app-unsupported .about-updates-instructions')
let link = aboutElement.querySelector(
'.app-unsupported .about-updates-instructions'
)
link.click()
let args = shell.openExternal.mostRecentCall.args
@ -72,66 +77,116 @@ describe('UpdateView', () => {
})
it('shows the correct panels when the app checks for updates and there is no update available', async () => {
expect(aboutElement.querySelector('.about-default-update-message')).toBeVisible()
expect(
aboutElement.querySelector('.about-default-update-message')
).toBeVisible()
MockUpdater.checkForUpdate()
await scheduler.getNextUpdatePromise()
expect(aboutElement.querySelector('.app-up-to-date')).not.toBeVisible()
expect(aboutElement.querySelector('.app-checking-for-updates')).toBeVisible()
expect(
aboutElement.querySelector('.app-checking-for-updates')
).toBeVisible()
MockUpdater.updateNotAvailable()
await scheduler.getNextUpdatePromise()
expect(aboutElement.querySelector('.app-up-to-date')).toBeVisible()
expect(aboutElement.querySelector('.app-checking-for-updates')).not.toBeVisible()
expect(
aboutElement.querySelector('.app-checking-for-updates')
).not.toBeVisible()
})
it('shows the correct panels when the app checks for updates and encounters an error', async () => {
expect(aboutElement.querySelector('.about-default-update-message')).toBeVisible()
expect(
aboutElement.querySelector('.about-default-update-message')
).toBeVisible()
MockUpdater.checkForUpdate()
await scheduler.getNextUpdatePromise()
expect(aboutElement.querySelector('.app-up-to-date')).not.toBeVisible()
expect(aboutElement.querySelector('.app-checking-for-updates')).toBeVisible()
expect(
aboutElement.querySelector('.app-checking-for-updates')
).toBeVisible()
spyOn(atom.autoUpdater, 'getErrorMessage').andReturn('an error message')
MockUpdater.updateError()
await scheduler.getNextUpdatePromise()
expect(aboutElement.querySelector('.app-update-error')).toBeVisible()
expect(aboutElement.querySelector('.app-error-message').textContent).toBe('an error message')
expect(aboutElement.querySelector('.app-checking-for-updates')).not.toBeVisible()
expect(aboutElement.querySelector('.about-update-action-button').disabled).toBe(false)
expect(aboutElement.querySelector('.about-update-action-button').textContent).toBe('Check now')
expect(
aboutElement.querySelector('.app-error-message').textContent
).toBe('an error message')
expect(
aboutElement.querySelector('.app-checking-for-updates')
).not.toBeVisible()
expect(
aboutElement.querySelector('.about-update-action-button').disabled
).toBe(false)
expect(
aboutElement.querySelector('.about-update-action-button').textContent
).toBe('Check now')
})
it('shows the correct panels and button states when the app checks for updates and an update is downloaded', async () => {
expect(aboutElement.querySelector('.about-default-update-message')).toBeVisible()
expect(aboutElement.querySelector('.about-update-action-button').disabled).toBe(false)
expect(aboutElement.querySelector('.about-update-action-button').textContent).toBe('Check now')
expect(
aboutElement.querySelector('.about-default-update-message')
).toBeVisible()
expect(
aboutElement.querySelector('.about-update-action-button').disabled
).toBe(false)
expect(
aboutElement.querySelector('.about-update-action-button').textContent
).toBe('Check now')
MockUpdater.checkForUpdate()
await scheduler.getNextUpdatePromise()
expect(aboutElement.querySelector('.app-up-to-date')).not.toBeVisible()
expect(aboutElement.querySelector('.app-checking-for-updates')).toBeVisible()
expect(aboutElement.querySelector('.about-update-action-button').disabled).toBe(true)
expect(aboutElement.querySelector('.about-update-action-button').textContent).toBe('Check now')
expect(
aboutElement.querySelector('.app-checking-for-updates')
).toBeVisible()
expect(
aboutElement.querySelector('.about-update-action-button').disabled
).toBe(true)
expect(
aboutElement.querySelector('.about-update-action-button').textContent
).toBe('Check now')
MockUpdater.downloadUpdate()
await scheduler.getNextUpdatePromise()
expect(aboutElement.querySelector('.app-checking-for-updates')).not.toBeVisible()
expect(aboutElement.querySelector('.app-downloading-update')).toBeVisible()
expect(
aboutElement.querySelector('.app-checking-for-updates')
).not.toBeVisible()
expect(
aboutElement.querySelector('.app-downloading-update')
).toBeVisible()
// TODO: at some point it would be nice to be able to cancel an update download, and then this would be a cancel button
expect(aboutElement.querySelector('.about-update-action-button').disabled).toBe(true)
expect(aboutElement.querySelector('.about-update-action-button').textContent).toBe('Check now')
expect(
aboutElement.querySelector('.about-update-action-button').disabled
).toBe(true)
expect(
aboutElement.querySelector('.about-update-action-button').textContent
).toBe('Check now')
MockUpdater.finishDownloadingUpdate('42.0.0')
await scheduler.getNextUpdatePromise()
expect(aboutElement.querySelector('.app-downloading-update')).not.toBeVisible()
expect(aboutElement.querySelector('.app-update-available-to-install')).toBeVisible()
expect(
aboutElement.querySelector('.app-downloading-update')
).not.toBeVisible()
expect(
aboutElement.querySelector('.app-update-available-to-install')
).toBeVisible()
expect(aboutElement.querySelector('.app-update-available-to-install .about-updates-version').textContent).toBe('42.0.0')
expect(aboutElement.querySelector('.about-update-action-button').disabled).toBe(false)
expect(aboutElement.querySelector('.about-update-action-button').textContent).toBe('Restart and install')
expect(
aboutElement.querySelector(
'.app-update-available-to-install .about-updates-version'
).textContent
).toBe('42.0.0')
expect(
aboutElement.querySelector('.about-update-action-button').disabled
).toBe(false)
expect(
aboutElement.querySelector('.about-update-action-button').textContent
).toBe('Restart and install')
})
it('opens the release notes for the downloaded release when the release notes link are clicked', async () => {
@ -139,7 +194,9 @@ describe('UpdateView', () => {
await scheduler.getNextUpdatePromise()
spyOn(shell, 'openExternal')
let link = aboutElement.querySelector('.app-update-available-to-install .about-updates-release-notes')
let link = aboutElement.querySelector(
'.app-update-available-to-install .about-updates-release-notes'
)
link.click()
let args = shell.openExternal.mostRecentCall.args
@ -168,10 +225,18 @@ describe('UpdateView', () => {
updateManager.resetState()
await scheduler.getNextUpdatePromise()
expect(aboutElement.querySelector('.app-checking-for-updates')).not.toBeVisible()
expect(aboutElement.querySelector('.app-downloading-update')).toBeVisible()
expect(aboutElement.querySelector('.about-update-action-button').disabled).toBe(true)
expect(aboutElement.querySelector('.about-update-action-button').textContent).toBe('Check now')
expect(
aboutElement.querySelector('.app-checking-for-updates')
).not.toBeVisible()
expect(
aboutElement.querySelector('.app-downloading-update')
).toBeVisible()
expect(
aboutElement.querySelector('.about-update-action-button').disabled
).toBe(true)
expect(
aboutElement.querySelector('.about-update-action-button').textContent
).toBe('Check now')
})
describe('when core.automaticallyUpdate is toggled', () => {
@ -181,36 +246,66 @@ describe('UpdateView', () => {
})
it('shows the auto update UI', async () => {
expect(aboutElement.querySelector('.about-auto-updates input').checked).toBe(true)
expect(aboutElement.querySelector('.about-default-update-message')).toBeVisible()
expect(aboutElement.querySelector('.about-default-update-message').textContent).toBe('Atom will check for updates automatically')
expect(
aboutElement.querySelector('.about-auto-updates input').checked
).toBe(true)
expect(
aboutElement.querySelector('.about-default-update-message')
).toBeVisible()
expect(
aboutElement.querySelector('.about-default-update-message')
.textContent
).toBe('Atom will check for updates automatically')
atom.config.set('core.automaticallyUpdate', false)
await scheduler.getNextUpdatePromise()
expect(aboutElement.querySelector('.about-auto-updates input').checked).toBe(false)
expect(aboutElement.querySelector('.about-default-update-message')).toBeVisible()
expect(aboutElement.querySelector('.about-default-update-message').textContent).toBe('Automatic updates are disabled please check manually')
expect(
aboutElement.querySelector('.about-auto-updates input').checked
).toBe(false)
expect(
aboutElement.querySelector('.about-default-update-message')
).toBeVisible()
expect(
aboutElement.querySelector('.about-default-update-message')
.textContent
).toBe('Automatic updates are disabled please check manually')
})
it('updates config and the UI when the checkbox is used to toggle', async () => {
expect(aboutElement.querySelector('.about-auto-updates input').checked).toBe(true)
expect(
aboutElement.querySelector('.about-auto-updates input').checked
).toBe(true)
aboutElement.querySelector('.about-auto-updates input').click()
await scheduler.getNextUpdatePromise()
expect(atom.config.get('core.automaticallyUpdate')).toBe(false)
expect(aboutElement.querySelector('.about-auto-updates input').checked).toBe(false)
expect(aboutElement.querySelector('.about-default-update-message')).toBeVisible()
expect(aboutElement.querySelector('.about-default-update-message').textContent).toBe('Automatic updates are disabled please check manually')
expect(
aboutElement.querySelector('.about-auto-updates input').checked
).toBe(false)
expect(
aboutElement.querySelector('.about-default-update-message')
).toBeVisible()
expect(
aboutElement.querySelector('.about-default-update-message')
.textContent
).toBe('Automatic updates are disabled please check manually')
aboutElement.querySelector('.about-auto-updates input').click()
await scheduler.getNextUpdatePromise()
expect(atom.config.get('core.automaticallyUpdate')).toBe(true)
expect(aboutElement.querySelector('.about-auto-updates input').checked).toBe(true)
expect(aboutElement.querySelector('.about-default-update-message')).toBeVisible()
expect(aboutElement.querySelector('.about-default-update-message').textContent).toBe('Atom will check for updates automatically')
expect(
aboutElement.querySelector('.about-auto-updates input').checked
).toBe(true)
expect(
aboutElement.querySelector('.about-default-update-message')
).toBeVisible()
expect(
aboutElement.querySelector('.about-default-update-message')
.textContent
).toBe('Atom will check for updates automatically')
})
describe('checking for updates', function () {
@ -271,10 +366,20 @@ describe('UpdateView', () => {
updateManager = main.model.state.updateManager
scheduler = AboutView.getScheduler()
expect(aboutElement.querySelector('.app-update-available-to-install')).toBeVisible()
expect(aboutElement.querySelector('.app-update-available-to-install .about-updates-version').textContent).toBe('42.0.0')
expect(aboutElement.querySelector('.about-update-action-button').disabled).toBe(false)
expect(aboutElement.querySelector('.about-update-action-button').textContent).toBe('Restart and install')
expect(
aboutElement.querySelector('.app-update-available-to-install')
).toBeVisible()
expect(
aboutElement.querySelector(
'.app-update-available-to-install .about-updates-version'
).textContent
).toBe('42.0.0')
expect(
aboutElement.querySelector('.about-update-action-button').disabled
).toBe(false)
expect(
aboutElement.querySelector('.about-update-action-button').textContent
).toBe('Restart and install')
})
})
})

View File

@ -13,7 +13,9 @@ module.exports = {
const names = atom.packages.getAvailablePackageNames()
for (let name of names) {
if (atom.packages.isBundledPackage(name)) {
const isDuplicatedPackage = await this.isInstalledAsCommunityPackage(name)
const isDuplicatedPackage = await this.isInstalledAsCommunityPackage(
name
)
if (isDuplicatedPackage) {
duplicatePackages.push(name)
}

View File

@ -17,20 +17,49 @@ describe('dalek', function () {
beforeEach(function () {
availablePackages = {
'an-unduplicated-installed-package': path.join('Users', 'username', '.atom', 'packages', 'an-unduplicated-installed-package'),
'duplicated-package': path.join('Users', 'username', '.atom', 'packages', 'duplicated-package'),
'unduplicated-package': path.join(`${atom.getLoadSettings().resourcePath}`, 'node_modules', 'unduplicated-package')
'an-unduplicated-installed-package': path.join(
'Users',
'username',
'.atom',
'packages',
'an-unduplicated-installed-package'
),
'duplicated-package': path.join(
'Users',
'username',
'.atom',
'packages',
'duplicated-package'
),
'unduplicated-package': path.join(
`${atom.getLoadSettings().resourcePath}`,
'node_modules',
'unduplicated-package'
)
}
atom.devMode = false
bundledPackages = ['duplicated-package', 'unduplicated-package']
packageDirPaths = [path.join('Users', 'username', '.atom', 'packages')]
sandbox = sinon.sandbox.create()
sandbox.stub(dalek, 'realpath').callsFake((filePath) => Promise.resolve(realPaths[filePath] || filePath))
sandbox.stub(atom.packages, 'isBundledPackage').callsFake((packageName) => { return bundledPackages.includes(packageName) })
sandbox.stub(atom.packages, 'getAvailablePackageNames').callsFake(() => Object.keys(availablePackages))
sandbox.stub(atom.packages, 'getPackageDirPaths').callsFake(() => { return packageDirPaths })
sandbox.stub(fs, 'existsSync').callsFake((candidate) => { return Object.values(availablePackages).includes(candidate) && !candidate.includes(atom.getLoadSettings().resourcePath) })
sandbox
.stub(dalek, 'realpath')
.callsFake(filePath => Promise.resolve(realPaths[filePath] || filePath))
sandbox.stub(atom.packages, 'isBundledPackage').callsFake(packageName => {
return bundledPackages.includes(packageName)
})
sandbox
.stub(atom.packages, 'getAvailablePackageNames')
.callsFake(() => Object.keys(availablePackages))
sandbox.stub(atom.packages, 'getPackageDirPaths').callsFake(() => {
return packageDirPaths
})
sandbox.stub(fs, 'existsSync').callsFake(candidate => {
return (
Object.values(availablePackages).includes(candidate) &&
!candidate.includes(atom.getLoadSettings().resourcePath)
)
})
})
afterEach(function () {
@ -54,7 +83,13 @@ describe('dalek', function () {
describe('when a package is symlinked into the package directory', async function () {
beforeEach(function () {
const realPath = path.join('Users', 'username', 'duplicated-package')
const packagePath = path.join('Users', 'username', '.atom', 'packages', 'duplicated-package')
const packagePath = path.join(
'Users',
'username',
'.atom',
'packages',
'duplicated-package'
)
realPaths[packagePath] = realPath
})

View File

@ -1,2 +1,2 @@
const createRunner = require('atom-mocha-test-runner').createRunner
module.exports = createRunner({testSuffixes: ['test.js']})
module.exports = createRunner({ testSuffixes: ['test.js'] })

View File

@ -2,7 +2,7 @@
/** @jsx etch.dom */
import _ from 'underscore-plus'
import {CompositeDisposable} from 'atom'
import { CompositeDisposable } from 'atom'
import etch from 'etch'
import fs from 'fs-plus'
import Grim from 'grim'
@ -11,23 +11,45 @@ import path from 'path'
import shell from 'shell'
export default class DeprecationCopView {
constructor ({uri}) {
constructor ({ uri }) {
this.uri = uri
this.subscriptions = new CompositeDisposable
this.subscriptions.add(Grim.on('updated', () => { etch.update(this) }))
this.subscriptions = new CompositeDisposable()
this.subscriptions.add(
Grim.on('updated', () => {
etch.update(this)
})
)
// TODO: Remove conditional when the new StyleManager deprecation APIs reach stable.
if (atom.styles.onDidUpdateDeprecations) {
this.subscriptions.add(atom.styles.onDidUpdateDeprecations(() => { etch.update(this) }))
this.subscriptions.add(
atom.styles.onDidUpdateDeprecations(() => {
etch.update(this)
})
)
}
etch.initialize(this)
this.subscriptions.add(atom.commands.add(this.element, {
'core:move-up': () => { this.scrollUp() },
'core:move-down': () => { this.scrollDown() },
'core:page-up': () => { this.pageUp() },
'core:page-down': () => { this.pageDown() },
'core:move-to-top': () => { this.scrollToTop() },
'core:move-to-bottom': () => { this.scrollToBottom() }
}))
this.subscriptions.add(
atom.commands.add(this.element, {
'core:move-up': () => {
this.scrollUp()
},
'core:move-down': () => {
this.scrollDown()
},
'core:page-up': () => {
this.pageUp()
},
'core:page-down': () => {
this.pageDown()
},
'core:move-to-top': () => {
this.scrollToTop()
},
'core:move-to-bottom': () => {
this.scrollToBottom()
}
})
)
}
serialize () {
@ -49,25 +71,35 @@ export default class DeprecationCopView {
render () {
return (
<div className='deprecation-cop pane-item native-key-bindings' tabIndex='-1'>
<div
className='deprecation-cop pane-item native-key-bindings'
tabIndex='-1'
>
<div className='panel'>
<div className='padded deprecation-overview'>
<div className='pull-right btn-group'>
<button
className='btn btn-primary check-for-update'
onclick={(event) => {
onclick={event => {
event.preventDefault()
this.checkForUpdates()
}}>Check for Updates</button>
}}
>
Check for Updates
</button>
</div>
</div>
<div className='panel-heading'><span>Deprecated calls</span></div>
<div className='panel-heading'>
<span>Deprecated calls</span>
</div>
<ul className='list-tree has-collapsable-children'>
{this.renderDeprecatedCalls()}
</ul>
<div className='panel-heading'><span>Deprecated selectors</span></div>
<div className='panel-heading'>
<span>Deprecated selectors</span>
</div>
<ul className='selectors list-tree has-collapsable-children'>
{this.renderDeprecatedSelectors()}
</ul>
@ -82,37 +114,57 @@ export default class DeprecationCopView {
if (packageNames.length === 0) {
return <li className='list-item'>No deprecated calls</li>
} else {
return packageNames.sort().map((packageName) => (
return packageNames.sort().map(packageName => (
<li className='deprecation list-nested-item collapsed'>
<div className='deprecation-info list-item' onclick={(event) => event.target.parentElement.classList.toggle('collapsed')}>
<div
className='deprecation-info list-item'
onclick={event =>
event.target.parentElement.classList.toggle('collapsed')
}
>
<span className='text-highlight'>{packageName || 'atom core'}</span>
<span>{` (${_.pluralize(deprecationsByPackageName[packageName].length, 'deprecation')})`}</span>
<span>{` (${_.pluralize(
deprecationsByPackageName[packageName].length,
'deprecation'
)})`}</span>
</div>
<ul className='list'>
{this.renderPackageActionsIfNeeded(packageName)}
{deprecationsByPackageName[packageName].map(({deprecation, stack}) => (
<li className='list-item deprecation-detail'>
<span className='text-warning icon icon-alert' />
<div className='list-item deprecation-message' innerHTML={marked(deprecation.getMessage())} />
{this.renderIssueURLIfNeeded(packageName, deprecation, this.buildIssueURL(packageName, deprecation, stack))}
<div className='stack-trace'>
{stack.map(({functionName, location}) => (
<div className='stack-line'>
<span>{functionName}</span>
<span> - </span>
<a
className='stack-line-location'
href={location}
onclick={(event) => {
event.preventDefault()
this.openLocation(location)
}}>{location}</a>
{deprecationsByPackageName[packageName].map(
({ deprecation, stack }) => (
<li className='list-item deprecation-detail'>
<span className='text-warning icon icon-alert' />
<div
className='list-item deprecation-message'
innerHTML={marked(deprecation.getMessage())}
/>
{this.renderIssueURLIfNeeded(
packageName,
deprecation,
this.buildIssueURL(packageName, deprecation, stack)
)}
<div className='stack-trace'>
{stack.map(({ functionName, location }) => (
<div className='stack-line'>
<span>{functionName}</span>
<span> - </span>
<a
className='stack-line-location'
href={location}
onclick={event => {
event.preventDefault()
this.openLocation(location)
}}
>
{location}
</a>
</div>
))}
</div>
))}
</div>
</li>
))}
</li>
)
)}
</ul>
</li>
))
@ -123,41 +175,61 @@ export default class DeprecationCopView {
const deprecationsByPackageName = this.getDeprecatedSelectorsByPackageName()
const packageNames = Object.keys(deprecationsByPackageName)
if (packageNames.length === 0) {
return (
<li className='list-item'>No deprecated selectors</li>
)
return <li className='list-item'>No deprecated selectors</li>
} else {
return packageNames.map((packageName) => (
return packageNames.map(packageName => (
<li className='deprecation list-nested-item collapsed'>
<div className='deprecation-info list-item' onclick={(event) => event.target.parentElement.classList.toggle('collapsed')}>
<div
className='deprecation-info list-item'
onclick={event =>
event.target.parentElement.classList.toggle('collapsed')
}
>
<span className='text-highlight'>{packageName}</span>
</div>
<ul className='list'>
{this.renderPackageActionsIfNeeded(packageName)}
{deprecationsByPackageName[packageName].map(({packagePath, sourcePath, deprecation}) => {
const relativeSourcePath = path.relative(packagePath, sourcePath)
const issueTitle = `Deprecated selector in \`${relativeSourcePath}\``
const issueBody = `In \`${relativeSourcePath}\`: \n\n${deprecation.message}`
return (
<li className='list-item source-file'>
<a
className='source-url'
href={sourcePath}
onclick={(event) => {
event.preventDefault()
this.openLocation(sourcePath)
}}>{relativeSourcePath}</a>
<ul className='list'>
<li className='list-item deprecation-detail'>
<span className='text-warning icon icon-alert' />
<div className='list-item deprecation-message' innerHTML={marked(deprecation.message)} />
{this.renderSelectorIssueURLIfNeeded(packageName, issueTitle, issueBody)}
</li>
</ul>
</li>
)
})}
{deprecationsByPackageName[packageName].map(
({ packagePath, sourcePath, deprecation }) => {
const relativeSourcePath = path.relative(
packagePath,
sourcePath
)
const issueTitle = `Deprecated selector in \`${relativeSourcePath}\``
const issueBody = `In \`${relativeSourcePath}\`: \n\n${
deprecation.message
}`
return (
<li className='list-item source-file'>
<a
className='source-url'
href={sourcePath}
onclick={event => {
event.preventDefault()
this.openLocation(sourcePath)
}}
>
{relativeSourcePath}
</a>
<ul className='list'>
<li className='list-item deprecation-detail'>
<span className='text-warning icon icon-alert' />
<div
className='list-item deprecation-message'
innerHTML={marked(deprecation.message)}
/>
{this.renderSelectorIssueURLIfNeeded(
packageName,
issueTitle,
issueBody
)}
</li>
</ul>
</li>
)
}
)}
</ul>
</li>
))
@ -171,17 +243,23 @@ export default class DeprecationCopView {
<div className='btn-group'>
<button
className='btn check-for-update'
onclick={(event) => {
onclick={event => {
event.preventDefault()
this.checkForUpdates()
}}>Check for Update</button>
}}
>
Check for Update
</button>
<button
className='btn disable-package'
data-package-name={packageName}
onclick={(event) => {
onclick={event => {
event.preventDefault()
this.disablePackage(packageName)
}}>Disable Package</button>
}}
>
Disable Package
</button>
</div>
</div>
)
@ -191,13 +269,18 @@ export default class DeprecationCopView {
}
encodeURI (str) {
return encodeURI(str).replace(/#/g, '%23').replace(/;/g, '%3B').replace(/%20/g, '+')
return encodeURI(str)
.replace(/#/g, '%23')
.replace(/;/g, '%3B')
.replace(/%20/g, '+')
}
renderSelectorIssueURLIfNeeded (packageName, issueTitle, issueBody) {
const repoURL = this.getRepoURL(packageName)
if (repoURL) {
const issueURL = `${repoURL}/issues/new?title=${this.encodeURI(issueTitle)}&body=${this.encodeURI(issueBody)}`
const issueURL = `${repoURL}/issues/new?title=${this.encodeURI(
issueTitle
)}&body=${this.encodeURI(issueBody)}`
return (
<div className='btn-toolbar'>
<button
@ -205,10 +288,13 @@ export default class DeprecationCopView {
data-issue-title={issueTitle}
data-repo-url={repoURL}
data-issue-url={issueURL}
onclick={(event) => {
onclick={event => {
event.preventDefault()
this.openIssueURL(repoURL, issueURL, issueTitle)
}}>Report Issue</button>
}}
>
Report Issue
</button>
</div>
)
} else {
@ -227,10 +313,13 @@ export default class DeprecationCopView {
data-issue-title={issueTitle}
data-repo-url={repoURL}
data-issue-url={issueURL}
onclick={(event) => {
onclick={event => {
event.preventDefault()
this.openIssueURL(repoURL, issueURL, issueTitle)
}}>Report Issue</button>
}}
>
Report Issue
</button>
</div>
)
} else {
@ -242,9 +331,13 @@ export default class DeprecationCopView {
const repoURL = this.getRepoURL(packageName)
if (repoURL) {
const title = `${deprecation.getOriginName()} is deprecated.`
const stacktrace = stack.map(({functionName, location}) => `${functionName} (${location})`).join("\n")
const stacktrace = stack
.map(({ functionName, location }) => `${functionName} (${location})`)
.join('\n')
const body = `${deprecation.getMessage()}\n\`\`\`\n${stacktrace}\n\`\`\``
return `${repoURL}/issues/new?title=${encodeURI(title)}&body=${encodeURI(body)}`
return `${repoURL}/issues/new?title=${encodeURI(title)}&body=${encodeURI(
body
)}`
} else {
return null
}
@ -266,13 +359,16 @@ export default class DeprecationCopView {
const url = 'https://api.github.com/search/issues'
const repo = repoURL.replace(/http(s)?:\/\/(\d+\.)?github.com\//gi, '')
const query = `${issueTitle} repo:${repo}`
const response = await window.fetch(`${url}?q=${encodeURI(query)}&sort=created`, {
method: 'GET',
headers: {
'Accept': 'application/vnd.github.v3+json',
'Content-Type': 'application/json'
const response = await window.fetch(
`${url}?q=${encodeURI(query)}&sort=created`,
{
method: 'GET',
headers: {
Accept: 'application/vnd.github.v3+json',
'Content-Type': 'application/json'
}
}
})
)
if (response.ok) {
const data = await response.json()
@ -284,21 +380,25 @@ export default class DeprecationCopView {
}
}
return (issues.open || issues.closed)
return issues.open || issues.closed
}
}
}
async shortenURL (url) {
let encodedUrl = encodeURIComponent(url).substr(0, 5000) // is.gd has 5000 char limit
let incompletePercentEncoding = encodedUrl.indexOf('%', encodedUrl.length - 2)
if (incompletePercentEncoding >= 0) { // Handle an incomplete % encoding cut-off
let incompletePercentEncoding = encodedUrl.indexOf(
'%',
encodedUrl.length - 2
)
if (incompletePercentEncoding >= 0) {
// Handle an incomplete % encoding cut-off
encodedUrl = encodedUrl.substr(0, incompletePercentEncoding)
}
let result = await fetch('https://is.gd/create.php?format=simple', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `url=${encodedUrl}`
})
@ -307,8 +407,14 @@ export default class DeprecationCopView {
getRepoURL (packageName) {
const loadedPackage = atom.packages.getLoadedPackage(packageName)
if (loadedPackage && loadedPackage.metadata && loadedPackage.metadata.repository) {
const url = loadedPackage.metadata.repository.url || loadedPackage.metadata.repository
if (
loadedPackage &&
loadedPackage.metadata &&
loadedPackage.metadata.repository
) {
const url =
loadedPackage.metadata.repository.url ||
loadedPackage.metadata.repository
return url.replace(/\.git$/, '')
} else {
return null
@ -330,8 +436,9 @@ export default class DeprecationCopView {
packageName = (this.getPackageName(stack) || '').toLowerCase()
}
deprecatedCallsByPackageName[packageName] = deprecatedCallsByPackageName[packageName] || []
deprecatedCallsByPackageName[packageName].push({deprecation, stack})
deprecatedCallsByPackageName[packageName] =
deprecatedCallsByPackageName[packageName] || []
deprecatedCallsByPackageName[packageName].push({ deprecation, stack })
}
}
return deprecatedCallsByPackageName
@ -352,11 +459,18 @@ export default class DeprecationCopView {
packagePath = ''
} else {
packageName = components[packagesComponentIndex + 1]
packagePath = components.slice(0, packagesComponentIndex + 1).join(path.sep)
packagePath = components
.slice(0, packagesComponentIndex + 1)
.join(path.sep)
}
deprecatedSelectorsByPackageName[packageName] = deprecatedSelectorsByPackageName[packageName] || []
deprecatedSelectorsByPackageName[packageName].push({packagePath, sourcePath, deprecation})
deprecatedSelectorsByPackageName[packageName] =
deprecatedSelectorsByPackageName[packageName] || []
deprecatedSelectorsByPackageName[packageName].push({
packagePath,
sourcePath,
deprecation
})
}
}
@ -366,13 +480,16 @@ export default class DeprecationCopView {
getPackageName (stack) {
const packagePaths = this.getPackagePathsByPackageName()
for (const [packageName, packagePath] of packagePaths) {
if (packagePath.includes('.atom/dev/packages') || packagePath.includes('.atom/packages')) {
if (
packagePath.includes('.atom/dev/packages') ||
packagePath.includes('.atom/packages')
) {
packagePaths.set(packageName, fs.absolute(packagePath))
}
}
for (let i = 1; i < stack.length; i++) {
const {fileName} = stack[i]
const { fileName } = stack[i]
// Empty when it was run from the dev console
if (!fileName) {
@ -426,7 +543,7 @@ export default class DeprecationCopView {
if (process.platform === 'win32') {
pathToOpen = pathToOpen.replace(/^\//, '')
}
atom.open({pathsToOpen: [pathToOpen]})
atom.open({ pathsToOpen: [pathToOpen] })
}
getURI () {

View File

@ -1,4 +1,4 @@
const {Disposable, CompositeDisposable} = require('atom')
const { Disposable, CompositeDisposable } = require('atom')
const DeprecationCopView = require('./deprecation-cop-view')
const DeprecationCopStatusBarView = require('./deprecation-cop-status-bar-view')
const ViewURI = 'atom://deprecation-cop'
@ -6,14 +6,18 @@ const ViewURI = 'atom://deprecation-cop'
class DeprecationCopPackage {
activate () {
this.disposables = new CompositeDisposable()
this.disposables.add(atom.workspace.addOpener((uri) => {
if (uri === ViewURI) {
return this.deserializeDeprecationCopView({uri})
}
}))
this.disposables.add(atom.commands.add('atom-workspace', 'deprecation-cop:view', () => {
atom.workspace.open(ViewURI)
}))
this.disposables.add(
atom.workspace.addOpener(uri => {
if (uri === ViewURI) {
return this.deserializeDeprecationCopView({ uri })
}
})
)
this.disposables.add(
atom.commands.add('atom-workspace', 'deprecation-cop:view', () => {
atom.workspace.open(ViewURI)
})
)
}
deactivate () {
@ -30,9 +34,20 @@ class DeprecationCopPackage {
consumeStatusBar (statusBar) {
const statusBarView = new DeprecationCopStatusBarView()
const statusBarTile = statusBar.addRightTile({item: statusBarView, priority: 150})
this.disposables.add(new Disposable(() => { statusBarView.destroy() }))
this.disposables.add(new Disposable(() => { statusBarTile.destroy() }))
const statusBarTile = statusBar.addRightTile({
item: statusBarView,
priority: 150
})
this.disposables.add(
new Disposable(() => {
statusBarView.destroy()
})
)
this.disposables.add(
new Disposable(() => {
statusBarTile.destroy()
})
)
}
}

View File

@ -2,16 +2,19 @@ const fs = require('fs-plus')
const path = require('path')
const Watcher = require('./watcher')
module.exports =
class BaseThemeWatcher extends Watcher {
module.exports = class BaseThemeWatcher extends Watcher {
constructor () {
super()
this.stylesheetsPath = path.dirname(atom.themes.resolveStylesheet('../static/atom.less'))
this.stylesheetsPath = path.dirname(
atom.themes.resolveStylesheet('../static/atom.less')
)
this.watch()
}
watch () {
const filePaths = fs.readdirSync(this.stylesheetsPath).filter(filePath => path.extname(filePath).includes('less'))
const filePaths = fs
.readdirSync(this.stylesheetsPath)
.filter(filePath => path.extname(filePath).includes('less'))
for (const filePath of filePaths) {
this.watchFile(path.join(this.stylesheetsPath, filePath))

View File

@ -5,7 +5,9 @@ module.exports = {
if (atom.packages.hasActivatedInitialPackages()) {
this.startWatching()
} else {
this.activatedDisposable = atom.packages.onDidActivateInitialPackages(() => this.startWatching())
this.activatedDisposable = atom.packages.onDidActivateInitialPackages(
() => this.startWatching()
)
}
},
@ -17,8 +19,12 @@ module.exports = {
startWatching () {
const UIWatcher = require('./ui-watcher')
this.uiWatcher = new UIWatcher({themeManager: atom.themes})
this.commandDisposable = atom.commands.add('atom-workspace', 'dev-live-reload:reload-all', () => this.uiWatcher.reloadAll())
this.uiWatcher = new UIWatcher({ themeManager: atom.themes })
this.commandDisposable = atom.commands.add(
'atom-workspace',
'dev-live-reload:reload-all',
() => this.uiWatcher.reloadAll()
)
if (this.activatedDisposable) this.activatedDisposable.dispose()
}
}

View File

@ -2,8 +2,7 @@ const fs = require('fs-plus')
const Watcher = require('./watcher')
module.exports =
class PackageWatcher extends Watcher {
module.exports = class PackageWatcher extends Watcher {
static supportsPackage (pack, type) {
if (pack.getType() === type && pack.getStylesheetPaths().length) return true
return false
@ -24,7 +23,9 @@ class PackageWatcher extends Watcher {
const stylesheetsPath = this.pack.getStylesheetsPath()
if (fs.isDirectorySync(stylesheetsPath)) this.watchDirectory(stylesheetsPath)
if (fs.isDirectorySync(stylesheetsPath)) {
this.watchDirectory(stylesheetsPath)
}
const stylesheetPaths = new Set(this.pack.getStylesheetPaths())
const onFile = stylesheetPath => stylesheetPaths.add(stylesheetPath)

View File

@ -1,10 +1,9 @@
const {CompositeDisposable} = require('atom')
const { CompositeDisposable } = require('atom')
const BaseThemeWatcher = require('./base-theme-watcher')
const PackageWatcher = require('./package-watcher')
module.exports =
class UIWatcher {
module.exports = class UIWatcher {
constructor () {
this.subscriptions = new CompositeDisposable()
this.reloadAll = this.reloadAll.bind(this)
@ -16,39 +15,63 @@ class UIWatcher {
watchPackages () {
this.watchedThemes = new Map()
this.watchedPackages = new Map()
for (const theme of atom.themes.getActiveThemes()) { this.watchTheme(theme) }
for (const pack of atom.packages.getActivePackages()) { this.watchPackage(pack) }
for (const theme of atom.themes.getActiveThemes()) {
this.watchTheme(theme)
}
for (const pack of atom.packages.getActivePackages()) {
this.watchPackage(pack)
}
this.watchForPackageChanges()
}
watchForPackageChanges () {
this.subscriptions.add(atom.themes.onDidChangeActiveThemes(() => {
// We need to destroy all theme watchers as all theme packages are destroyed
// when a theme changes.
for (const theme of this.watchedThemes.values()) { theme.destroy() }
this.subscriptions.add(
atom.themes.onDidChangeActiveThemes(() => {
// We need to destroy all theme watchers as all theme packages are destroyed
// when a theme changes.
for (const theme of this.watchedThemes.values()) {
theme.destroy()
}
this.watchedThemes.clear()
this.watchedThemes.clear()
// Rewatch everything!
for (const theme of atom.themes.getActiveThemes()) { this.watchTheme(theme) }
}))
// Rewatch everything!
for (const theme of atom.themes.getActiveThemes()) {
this.watchTheme(theme)
}
})
)
this.subscriptions.add(atom.packages.onDidActivatePackage(pack => this.watchPackage(pack)))
this.subscriptions.add(
atom.packages.onDidActivatePackage(pack => this.watchPackage(pack))
)
this.subscriptions.add(atom.packages.onDidDeactivatePackage(pack => {
// This only handles packages - onDidChangeActiveThemes handles themes
const watcher = this.watchedPackages.get(pack.name)
if (watcher) watcher.destroy()
this.watchedPackages.delete(pack.name)
}))
this.subscriptions.add(
atom.packages.onDidDeactivatePackage(pack => {
// This only handles packages - onDidChangeActiveThemes handles themes
const watcher = this.watchedPackages.get(pack.name)
if (watcher) watcher.destroy()
this.watchedPackages.delete(pack.name)
})
)
}
watchTheme (theme) {
if (PackageWatcher.supportsPackage(theme, 'theme')) this.watchedThemes.set(theme.name, this.createWatcher(new PackageWatcher(theme)))
if (PackageWatcher.supportsPackage(theme, 'theme')) {
this.watchedThemes.set(
theme.name,
this.createWatcher(new PackageWatcher(theme))
)
}
}
watchPackage (pack) {
if (PackageWatcher.supportsPackage(pack, 'atom')) this.watchedPackages.set(pack.name, this.createWatcher(new PackageWatcher(pack)))
if (PackageWatcher.supportsPackage(pack, 'atom')) {
this.watchedPackages.set(
pack.name,
this.createWatcher(new PackageWatcher(pack))
)
}
}
createWatcher (watcher) {
@ -56,7 +79,9 @@ class UIWatcher {
console.log('Global changed, reloading all styles')
this.reloadAll()
})
watcher.onDidDestroy(() => this.watchers.splice(this.watchers.indexOf(watcher), 1))
watcher.onDidDestroy(() =>
this.watchers.splice(this.watchers.indexOf(watcher), 1)
)
this.watchers.push(watcher)
return watcher
}
@ -64,18 +89,26 @@ class UIWatcher {
reloadAll () {
this.baseTheme.loadAllStylesheets()
for (const pack of atom.packages.getActivePackages()) {
if (PackageWatcher.supportsPackage(pack, 'atom')) pack.reloadStylesheets()
if (PackageWatcher.supportsPackage(pack, 'atom')) {
pack.reloadStylesheets()
}
}
for (const theme of atom.themes.getActiveThemes()) {
if (PackageWatcher.supportsPackage(theme, 'theme')) theme.reloadStylesheets()
if (PackageWatcher.supportsPackage(theme, 'theme')) {
theme.reloadStylesheets()
}
}
}
destroy () {
this.subscriptions.dispose()
this.baseTheme.destroy()
for (const pack of this.watchedPackages.values()) { pack.destroy() }
for (const theme of this.watchedThemes.values()) { theme.destroy() }
for (const pack of this.watchedPackages.values()) {
pack.destroy()
}
for (const theme of this.watchedThemes.values()) {
theme.destroy()
}
}
}

View File

@ -1,8 +1,7 @@
const {CompositeDisposable, File, Directory, Emitter} = require('atom')
const { CompositeDisposable, File, Directory, Emitter } = require('atom')
const path = require('path')
module.exports =
class Watcher {
module.exports = class Watcher {
constructor () {
this.destroy = this.destroy.bind(this)
this.emitter = new Emitter()
@ -66,7 +65,10 @@ class Watcher {
}
isInAsarArchive (pathToCheck) {
const {resourcePath} = atom.getLoadSettings()
return pathToCheck.startsWith(`${resourcePath}${path.sep}`) && path.extname(resourcePath) === '.asar'
const { resourcePath } = atom.getLoadSettings()
return (
pathToCheck.startsWith(`${resourcePath}${path.sep}`) &&
path.extname(resourcePath) === '.asar'
)
}
}

View File

@ -1,40 +1,9 @@
/** @babel */
export function beforeEach (fn) {
global.beforeEach(function () {
const result = fn()
if (result instanceof Promise) {
waitsForPromise(() => result)
}
})
}
export function afterEach (fn) {
global.afterEach(function () {
const result = fn()
if (result instanceof Promise) {
waitsForPromise(() => result)
}
})
}
['it', 'fit', 'ffit', 'fffit'].forEach(function (name) {
module.exports[name] = function (description, fn) {
if (fn === undefined) {
global[name](description)
return
}
global[name](description, function () {
const result = fn()
if (result instanceof Promise) {
waitsForPromise(() => result)
}
})
}
})
export async function conditionPromise (condition, description = 'anonymous condition') {
export async function conditionPromise (
condition,
description = 'anonymous condition'
) {
const startTime = Date.now()
while (true) {
@ -55,49 +24,3 @@ export function timeoutPromise (timeout) {
global.setTimeout(resolve, timeout)
})
}
function waitsForPromise (fn) {
const promise = fn()
global.waitsFor('spec promise to resolve', function (done) {
promise.then(done, function (error) {
jasmine.getEnv().currentSpec.fail(error)
done()
})
})
}
export function emitterEventPromise (emitter, event, timeout = 15000) {
return new Promise((resolve, reject) => {
const timeoutHandle = setTimeout(() => {
reject(new Error(`Timed out waiting for '${event}' event`))
}, timeout)
emitter.once(event, () => {
clearTimeout(timeoutHandle)
resolve()
})
})
}
export function promisify (original) {
return function (...args) {
return new Promise((resolve, reject) => {
args.push((err, ...results) => {
if (err) {
reject(err)
} else {
resolve(...results)
}
})
return original(...args)
})
}
}
export function promisifySome (obj, fnNames) {
const result = {}
for (const fnName of fnNames) {
result[fnName] = promisify(obj[fnName])
}
return result
}

View File

@ -1,5 +1,3 @@
const {it, fit, ffit, afterEach, beforeEach} = require('./async-spec-helpers') // eslint-disable-line no-unused-vars
describe('Dev Live Reload', () => {
describe('package activation', () => {
let [pack, mainModule] = []
@ -85,7 +83,9 @@ describe('Dev Live Reload', () => {
it('stops watching all files', async () => {
spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true)
const {mainModule} = await atom.packages.activatePackage('dev-live-reload')
const { mainModule } = await atom.packages.activatePackage(
'dev-live-reload'
)
expect(mainModule.uiWatcher).not.toBeNull()
spyOn(mainModule.uiWatcher, 'destroy')
@ -95,7 +95,9 @@ describe('Dev Live Reload', () => {
})
it('unsubscribes from the onDidActivateInitialPackages subscription if it is disabled before all initial packages are activated', async () => {
const {mainModule} = await atom.packages.activatePackage('dev-live-reload')
const { mainModule } = await atom.packages.activatePackage(
'dev-live-reload'
)
expect(mainModule.activatedDisposable.disposed).toBe(false)
await atom.packages.deactivatePackage('dev-live-reload')
@ -109,16 +111,18 @@ describe('Dev Live Reload', () => {
it('removes its commands', async () => {
spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true)
await atom.packages.activatePackage('dev-live-reload')
expect(atom.commands
.findCommands({target: atom.views.getView(atom.workspace)})
.filter(command => command.name.startsWith('dev-live-reload'))
.length).toBeGreaterThan(0)
expect(
atom.commands
.findCommands({ target: atom.views.getView(atom.workspace) })
.filter(command => command.name.startsWith('dev-live-reload')).length
).toBeGreaterThan(0)
await atom.packages.deactivatePackage('dev-live-reload')
expect(atom.commands
.findCommands({target: atom.views.getView(atom.workspace)})
.filter(command => command.name.startsWith('dev-live-reload'))
.length).toBe(0)
expect(
atom.commands
.findCommands({ target: atom.views.getView(atom.workspace) })
.filter(command => command.name.startsWith('dev-live-reload')).length
).toBe(0)
})
})
})

View File

@ -2,25 +2,31 @@ const path = require('path')
const UIWatcher = require('../lib/ui-watcher')
const {it, fit, ffit, afterEach, beforeEach, conditionPromise} = require('./async-spec-helpers') // eslint-disable-line no-unused-vars
const { conditionPromise } = require('./async-spec-helpers')
describe('UIWatcher', () => {
let uiWatcher = null
beforeEach(() => atom.packages.packageDirPaths.push(path.join(__dirname, 'fixtures')))
beforeEach(() =>
atom.packages.packageDirPaths.push(path.join(__dirname, 'fixtures'))
)
afterEach(() => uiWatcher && uiWatcher.destroy())
describe("when a base theme's file changes", () => {
beforeEach(() => {
spyOn(atom.themes, 'resolveStylesheet').andReturn(path.join(__dirname, 'fixtures', 'static', 'atom.less'))
spyOn(atom.themes, 'resolveStylesheet').andReturn(
path.join(__dirname, 'fixtures', 'static', 'atom.less')
)
uiWatcher = new UIWatcher()
})
it('reloads all the base styles', () => {
spyOn(atom.themes, 'reloadBaseStylesheets')
expect(uiWatcher.baseTheme.entities[0].getPath()).toContain(`${path.sep}static${path.sep}`)
expect(uiWatcher.baseTheme.entities[0].getPath()).toContain(
`${path.sep}static${path.sep}`
)
uiWatcher.baseTheme.entities[0].emitter.emit('did-change')
expect(atom.themes.reloadBaseStylesheets).toHaveBeenCalled()
@ -28,7 +34,11 @@ describe('UIWatcher', () => {
})
it("watches all the style sheets in the theme's styles folder", async () => {
const packagePath = path.join(__dirname, 'fixtures', 'package-with-styles-folder')
const packagePath = path.join(
__dirname,
'fixtures',
'package-with-styles-folder'
)
await atom.packages.activatePackage(packagePath)
uiWatcher = new UIWatcher()
@ -36,15 +46,25 @@ describe('UIWatcher', () => {
const lastWatcher = uiWatcher.watchers[uiWatcher.watchers.length - 1]
expect(lastWatcher.entities.length).toBe(4)
expect(lastWatcher.entities[0].getPath()).toBe(path.join(packagePath, 'styles'))
expect(lastWatcher.entities[1].getPath()).toBe(path.join(packagePath, 'styles', '3.css'))
expect(lastWatcher.entities[2].getPath()).toBe(path.join(packagePath, 'styles', 'sub', '1.css'))
expect(lastWatcher.entities[3].getPath()).toBe(path.join(packagePath, 'styles', 'sub', '2.less'))
expect(lastWatcher.entities[0].getPath()).toBe(
path.join(packagePath, 'styles')
)
expect(lastWatcher.entities[1].getPath()).toBe(
path.join(packagePath, 'styles', '3.css')
)
expect(lastWatcher.entities[2].getPath()).toBe(
path.join(packagePath, 'styles', 'sub', '1.css')
)
expect(lastWatcher.entities[3].getPath()).toBe(
path.join(packagePath, 'styles', 'sub', '2.less')
)
})
describe('when a package stylesheet file changes', async () => {
beforeEach(async () => {
await atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'package-with-styles-manifest'))
await atom.packages.activatePackage(
path.join(__dirname, 'fixtures', 'package-with-styles-manifest')
)
uiWatcher = new UIWatcher()
})
@ -71,7 +91,10 @@ describe('UIWatcher', () => {
describe('when a package global file changes', () => {
beforeEach(async () => {
atom.config.set('core.themes', ['theme-with-ui-variables', 'theme-with-multiple-imported-files'])
atom.config.set('core.themes', [
'theme-with-ui-variables',
'theme-with-multiple-imported-files'
])
await atom.themes.activateThemes()
uiWatcher = new UIWatcher()
@ -85,7 +108,9 @@ describe('UIWatcher', () => {
spyOn(theme, 'reloadStylesheets')
}
for (const entity of uiWatcher.watchedThemes.get('theme-with-multiple-imported-files').entities) {
for (const entity of uiWatcher.watchedThemes.get(
'theme-with-multiple-imported-files'
).entities) {
if (entity.getPath().indexOf('variables') > -1) varEntity = entity
}
varEntity.emitter.emit('did-change')
@ -101,21 +126,31 @@ describe('UIWatcher', () => {
uiWatcher = new UIWatcher()
expect(uiWatcher.watchedPackages.size).toBe(0)
await atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'package-with-styles-folder'))
expect(uiWatcher.watchedPackages.get('package-with-styles-folder')).not.toBeUndefined()
await atom.packages.activatePackage(
path.join(__dirname, 'fixtures', 'package-with-styles-folder')
)
expect(
uiWatcher.watchedPackages.get('package-with-styles-folder')
).not.toBeUndefined()
})
it('unwatches a package after it is deactivated', async () => {
await atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'package-with-styles-folder'))
await atom.packages.activatePackage(
path.join(__dirname, 'fixtures', 'package-with-styles-folder')
)
uiWatcher = new UIWatcher()
const watcher = uiWatcher.watchedPackages.get('package-with-styles-folder')
const watcher = uiWatcher.watchedPackages.get(
'package-with-styles-folder'
)
expect(watcher).not.toBeUndefined()
const watcherDestructionSpy = jasmine.createSpy('watcher-on-did-destroy')
watcher.onDidDestroy(watcherDestructionSpy)
await atom.packages.deactivatePackage('package-with-styles-folder')
expect(uiWatcher.watchedPackages.get('package-with-styles-folder')).toBeUndefined()
expect(
uiWatcher.watchedPackages.get('package-with-styles-folder')
).toBeUndefined()
expect(uiWatcher.watchedPackages.size).toBe(0)
expect(watcherDestructionSpy).toHaveBeenCalled()
})
@ -124,7 +159,9 @@ describe('UIWatcher', () => {
uiWatcher = new UIWatcher()
uiWatcher.destroy()
await atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'package-with-styles-folder'))
await atom.packages.activatePackage(
path.join(__dirname, 'fixtures', 'package-with-styles-folder')
)
expect(uiWatcher.watchedPackages.size).toBe(0)
})
})
@ -132,7 +169,10 @@ describe('UIWatcher', () => {
describe('minimal theme packages', () => {
let pack = null
beforeEach(async () => {
atom.config.set('core.themes', ['theme-with-syntax-variables', 'theme-with-index-less'])
atom.config.set('core.themes', [
'theme-with-syntax-variables',
'theme-with-index-less'
])
await atom.themes.activateThemes()
uiWatcher = new UIWatcher()
pack = atom.themes.getActiveThemes()[0]
@ -157,7 +197,10 @@ describe('UIWatcher', () => {
describe('theme packages', () => {
let pack = null
beforeEach(async () => {
atom.config.set('core.themes', ['theme-with-syntax-variables', 'theme-with-multiple-imported-files'])
atom.config.set('core.themes', [
'theme-with-syntax-variables',
'theme-with-multiple-imported-files'
])
await atom.themes.activateThemes()
uiWatcher = new UIWatcher()
@ -170,7 +213,9 @@ describe('UIWatcher', () => {
spyOn(pack, 'reloadStylesheets')
spyOn(atom.themes, 'reloadBaseStylesheets')
const watcher = uiWatcher.watchedThemes.get('theme-with-multiple-imported-files')
const watcher = uiWatcher.watchedThemes.get(
'theme-with-multiple-imported-files'
)
expect(watcher.entities.length).toBe(6)
@ -186,14 +231,21 @@ describe('UIWatcher', () => {
jasmine.useRealClock()
atom.config.set('core.themes', [])
await conditionPromise(() => !uiWatcher.watchedThemes['theme-with-multiple-imported-files'])
await conditionPromise(
() => !uiWatcher.watchedThemes['theme-with-multiple-imported-files']
)
})
it('watches a new theme when it is deactivated', async () => {
jasmine.useRealClock()
atom.config.set('core.themes', ['theme-with-syntax-variables', 'theme-with-package-file'])
await conditionPromise(() => uiWatcher.watchedThemes.get('theme-with-package-file'))
atom.config.set('core.themes', [
'theme-with-syntax-variables',
'theme-with-package-file'
])
await conditionPromise(() =>
uiWatcher.watchedThemes.get('theme-with-package-file')
)
pack = atom.themes.getActiveThemes()[0]
spyOn(pack, 'reloadStylesheets')

View File

@ -1,6 +1,6 @@
/** @babel */
import {CompositeDisposable} from 'atom'
import { CompositeDisposable } from 'atom'
let reporter
@ -13,36 +13,44 @@ function getReporter () {
}
export default {
activate() {
activate () {
this.subscriptions = new CompositeDisposable()
if (!atom.config.get('exception-reporting.userId')) {
atom.config.set('exception-reporting.userId', require('node-uuid').v4())
}
this.subscriptions.add(atom.onDidThrowError(({message, url, line, column, originalError}) => {
try {
getReporter().reportUncaughtException(originalError)
} catch (secondaryException) {
this.subscriptions.add(
atom.onDidThrowError(({ message, url, line, column, originalError }) => {
try {
console.error("Error reporting uncaught exception", secondaryException)
getReporter().reportUncaughtException(secondaryException)
} catch (error) { }
}
})
)
if (atom.onDidFailAssertion != null) {
this.subscriptions.add(atom.onDidFailAssertion(error => {
try {
getReporter().reportFailedAssertion(error)
getReporter().reportUncaughtException(originalError)
} catch (secondaryException) {
try {
console.error("Error reporting assertion failure", secondaryException)
console.error(
'Error reporting uncaught exception',
secondaryException
)
getReporter().reportUncaughtException(secondaryException)
} catch (error) {}
}
})
)
if (atom.onDidFailAssertion != null) {
this.subscriptions.add(
atom.onDidFailAssertion(error => {
try {
getReporter().reportFailedAssertion(error)
} catch (secondaryException) {
try {
console.error(
'Error reporting assertion failure',
secondaryException
)
getReporter().reportUncaughtException(secondaryException)
} catch (error) {}
}
})
)
}
}

View File

@ -1,6 +1,5 @@
/** @babel */
import _ from 'underscore-plus'
import os from 'os'
import stackTrace from 'stack-trace'
import fs from 'fs-plus'
@ -13,9 +12,15 @@ const StackTraceCache = new WeakMap()
export default class Reporter {
constructor (params = {}) {
this.request = params.request || window.fetch
this.alwaysReport = params.hasOwnProperty('alwaysReport') ? params.alwaysReport : false
this.reportPreviousErrors = params.hasOwnProperty('reportPreviousErrors') ? params.reportPreviousErrors : true
this.resourcePath = this.normalizePath(params.resourcePath || process.resourcesPath)
this.alwaysReport = params.hasOwnProperty('alwaysReport')
? params.alwaysReport
: false
this.reportPreviousErrors = params.hasOwnProperty('reportPreviousErrors')
? params.reportPreviousErrors
: true
this.resourcePath = this.normalizePath(
params.resourcePath || process.resourcesPath
)
this.reportedErrors = []
this.reportedAssertionFailures = []
}
@ -28,22 +33,24 @@ export default class Reporter {
version: LIB_VERSION,
url: 'https://www.atom.io'
},
events: [{
payloadVersion: "2",
exceptions: [this.buildExceptionJSON(error, params.projectRoot)],
severity: params.severity,
user: {
id: params.userId
},
app: {
version: params.appVersion,
releaseStage: params.releaseStage
},
device: {
osVersion: params.osVersion
},
metaData: error.metadata
}]
events: [
{
payloadVersion: '2',
exceptions: [this.buildExceptionJSON(error, params.projectRoot)],
severity: params.severity,
user: {
id: params.userId
},
app: {
version: params.appVersion,
releaseStage: params.releaseStage
},
device: {
osVersion: params.osVersion
},
metaData: error.metadata
}
]
}
}
@ -59,7 +66,8 @@ export default class Reporter {
return this.parseStackTrace(error).map(callSite => {
return {
file: this.scrubPath(callSite.getFileName()),
method: callSite.getMethodName() || callSite.getFunctionName() || "none",
method:
callSite.getMethodName() || callSite.getFunctionName() || 'none',
lineNumber: callSite.getLineNumber(),
columnNumber: callSite.getColumnNumber(),
inProject: !/node_modules/.test(callSite.getFileName())
@ -69,8 +77,8 @@ export default class Reporter {
normalizePath (pathToNormalize) {
return pathToNormalize
.replace('file:///', '') // Sometimes it's a uri
.replace(/\\/g, '/') // Unify path separators across Win/macOS/Linux
.replace('file:///', '') // Sometimes it's a uri
.replace(/\\/g, '/') // Unify path separators across Win/macOS/Linux
}
scrubPath (pathToScrub) {
@ -96,17 +104,17 @@ export default class Reporter {
}
getReleaseChannel (version) {
return (version.indexOf('beta') > -1)
return version.indexOf('beta') > -1
? 'beta'
: (version.indexOf('dev') > -1)
? 'dev'
: 'stable'
: version.indexOf('dev') > -1
? 'dev'
: 'stable'
}
performRequest (json) {
this.request.call(null, 'https://notify.bugsnag.com', {
method: 'POST',
headers: new Headers({'Content-Type': 'application/json'}),
headers: new Headers({ 'Content-Type': 'application/json' }),
body: JSON.stringify(json)
})
}
@ -118,7 +126,10 @@ export default class Reporter {
const topFrame = this.parseStackTrace(error)[0]
const fileName = topFrame ? topFrame.getFileName() : null
return fileName && (this.isBundledFile(fileName) || this.isTeletypeFile(fileName))
return (
fileName &&
(this.isBundledFile(fileName) || this.isTeletypeFile(fileName))
)
}
parseStackTrace (error) {
@ -169,21 +180,24 @@ export default class Reporter {
notification = atom.notifications.addInfo(message, {
detail: error.privateMetadataDescription,
description: "Are you willing to submit this information to a private server for debugging purposes?",
description:
'Are you willing to submit this information to a private server for debugging purposes?',
dismissable: true,
buttons: [
{
text: "No",
text: 'No',
onDidClick: reportWithoutPrivateMetadata
},
{
text: "Yes, Submit for Debugging",
text: 'Yes, Submit for Debugging',
onDidClick: reportWithPrivateMetadata
}
]
})
dismissSubscription = notification.onDidDismiss(reportWithoutPrivateMetadata)
dismissSubscription = notification.onDidDismiss(
reportWithoutPrivateMetadata
)
}
addPackageMetadata (error) {
@ -200,7 +214,9 @@ export default class Reporter {
}
}
if (error.metadata == null) { error.metadata = {} }
if (error.metadata == null) {
error.metadata = {}
}
error.metadata.bundledPackages = bundledPackages
error.metadata.userPackages = userPackages
}
@ -209,8 +225,12 @@ export default class Reporter {
addPreviousErrorsMetadata (error) {
if (!this.reportPreviousErrors) return
if (!error.metadata) error.metadata = {}
error.metadata.previousErrors = this.reportedErrors.map(error => error.message)
error.metadata.previousAssertionFailures = this.reportedAssertionFailures.map(error => error.message)
error.metadata.previousErrors = this.reportedErrors.map(
error => error.message
)
error.metadata.previousAssertionFailures = this.reportedAssertionFailures.map(
error => error.message
)
}
reportUncaughtException (error) {
@ -219,13 +239,20 @@ export default class Reporter {
this.addPackageMetadata(error)
this.addPreviousErrorsMetadata(error)
if ((error.privateMetadata != null) && (error.privateMetadataDescription != null)) {
this.requestPrivateMetadataConsent(error, "The Atom team would like to collect the following information to resolve this error:", error => this.reportUncaughtException(error))
if (
error.privateMetadata != null &&
error.privateMetadataDescription != null
) {
this.requestPrivateMetadataConsent(
error,
'The Atom team would like to collect the following information to resolve this error:',
error => this.reportUncaughtException(error)
)
return
}
let params = this.getDefaultNotificationParams()
params.severity = "error"
params.severity = 'error'
this.performRequest(this.buildNotificationJSON(error, params))
this.reportedErrors.push(error)
}
@ -236,13 +263,20 @@ export default class Reporter {
this.addPackageMetadata(error)
this.addPreviousErrorsMetadata(error)
if ((error.privateMetadata != null) && (error.privateMetadataDescription != null)) {
this.requestPrivateMetadataConsent(error, "The Atom team would like to collect some information to resolve an unexpected condition:", error => this.reportFailedAssertion(error))
if (
error.privateMetadata != null &&
error.privateMetadataDescription != null
) {
this.requestPrivateMetadataConsent(
error,
'The Atom team would like to collect some information to resolve an unexpected condition:',
error => this.reportFailedAssertion(error)
)
return
}
let params = this.getDefaultNotificationParams()
params.severity = "warning"
params.severity = 'warning'
this.performRequest(this.buildNotificationJSON(error, params))
this.reportedAssertionFailures.push(error)
}
@ -258,10 +292,11 @@ export default class Reporter {
isTeletypeFile (fileName) {
const teletypePath = atom.packages.resolvePackagePath('teletype')
return teletypePath && this.normalizePath(fileName).indexOf(teletypePath) === 0
return (
teletypePath && this.normalizePath(fileName).indexOf(teletypePath) === 0
)
}
}
Reporter.API_KEY = API_KEY
Reporter.LIB_VERSION = LIB_VERSION

View File

@ -6,25 +6,31 @@ const fs = require('fs-plus')
let osVersion = `${os.platform()}-${os.arch()}-${os.release()}`
let getReleaseChannel = version => {
return (version.indexOf('beta') > -1)
return version.indexOf('beta') > -1
? 'beta'
: (version.indexOf('dev') > -1)
: version.indexOf('dev') > -1
? 'dev'
: 'stable'
}
describe("Reporter", () => {
let reporter, requests, initialStackTraceLimit, initialFsGetHomeDirectory, mockActivePackages
describe('Reporter', () => {
let reporter,
requests,
initialStackTraceLimit,
initialFsGetHomeDirectory,
mockActivePackages
beforeEach(() => {
reporter = new Reporter({
request: (url, options) => requests.push(Object.assign({url}, options)),
request: (url, options) => requests.push(Object.assign({ url }, options)),
alwaysReport: true,
reportPreviousErrors: false
})
requests = []
mockActivePackages = []
spyOn(atom.packages, 'getActivePackages').andCallFake(() => mockActivePackages)
spyOn(atom.packages, 'getActivePackages').andCallFake(
() => mockActivePackages
)
initialStackTraceLimit = Error.stackTraceLimit
Error.stackTraceLimit = 1
@ -37,11 +43,12 @@ describe("Reporter", () => {
Error.stackTraceLimit = initialStackTraceLimit
})
describe(".reportUncaughtException(error)", () => {
it("posts errors originated inside Atom Core to BugSnag", () => {
describe('.reportUncaughtException(error)', () => {
it('posts errors originated inside Atom Core to BugSnag', () => {
const repositoryRootPath = path.join(__dirname, '..')
reporter = new Reporter({
request: (url, options) => requests.push(Object.assign({url}, options)),
request: (url, options) =>
requests.push(Object.assign({ url }, options)),
alwaysReport: true,
reportPreviousErrors: false,
resourcePath: repositoryRootPath
@ -50,111 +57,123 @@ describe("Reporter", () => {
let error = new Error()
Error.captureStackTrace(error)
reporter.reportUncaughtException(error)
let [lineNumber, columnNumber] = error.stack.match(/.js:(\d+):(\d+)/).slice(1).map(s => parseInt(s))
let [lineNumber, columnNumber] = error.stack
.match(/.js:(\d+):(\d+)/)
.slice(1)
.map(s => parseInt(s))
expect(requests.length).toBe(1)
let [request] = requests
expect(request.method).toBe("POST")
expect(request.url).toBe("https://notify.bugsnag.com")
expect(request.headers.get("Content-Type")).toBe("application/json")
expect(request.method).toBe('POST')
expect(request.url).toBe('https://notify.bugsnag.com')
expect(request.headers.get('Content-Type')).toBe('application/json')
let body = JSON.parse(request.body)
// Delete `inProject` field because tests may fail when run as part of Atom core
// (i.e. when this test file will be located under `node_modules/exception-reporting/spec`)
delete body.events[0].exceptions[0].stacktrace[0].inProject
expect(body).toEqual({
"apiKey": Reporter.API_KEY,
"notifier": {
"name": "Atom",
"version": Reporter.LIB_VERSION,
"url": "https://www.atom.io"
apiKey: Reporter.API_KEY,
notifier: {
name: 'Atom',
version: Reporter.LIB_VERSION,
url: 'https://www.atom.io'
},
"events": [
events: [
{
"payloadVersion": "2",
"exceptions": [
payloadVersion: '2',
exceptions: [
{
"errorClass": "Error",
"message": "",
"stacktrace": [
errorClass: 'Error',
message: '',
stacktrace: [
{
"method": semver.gt(process.versions.electron, '1.6.0') ? 'Spec.it' : 'it',
"file": "spec/reporter-spec.js",
"lineNumber": lineNumber,
"columnNumber": columnNumber
method: semver.gt(process.versions.electron, '1.6.0')
? 'Spec.it'
: 'it',
file: 'spec/reporter-spec.js',
lineNumber: lineNumber,
columnNumber: columnNumber
}
]
}
],
"severity": "error",
"user": {},
"app": {
"version": atom.getVersion(),
"releaseStage": getReleaseChannel(atom.getVersion())
severity: 'error',
user: {},
app: {
version: atom.getVersion(),
releaseStage: getReleaseChannel(atom.getVersion())
},
"device": {
"osVersion": osVersion
device: {
osVersion: osVersion
}
}
]
});})
})
})
it("posts errors originated outside Atom Core to BugSnag", () => {
it('posts errors originated outside Atom Core to BugSnag', () => {
fs.getHomeDirectory = () => path.join(__dirname, '..', '..')
let error = new Error()
Error.captureStackTrace(error)
reporter.reportUncaughtException(error)
let [lineNumber, columnNumber] = error.stack.match(/.js:(\d+):(\d+)/).slice(1).map(s => parseInt(s))
let [lineNumber, columnNumber] = error.stack
.match(/.js:(\d+):(\d+)/)
.slice(1)
.map(s => parseInt(s))
expect(requests.length).toBe(1)
let [request] = requests
expect(request.method).toBe("POST")
expect(request.url).toBe("https://notify.bugsnag.com")
expect(request.headers.get("Content-Type")).toBe("application/json")
expect(request.method).toBe('POST')
expect(request.url).toBe('https://notify.bugsnag.com')
expect(request.headers.get('Content-Type')).toBe('application/json')
let body = JSON.parse(request.body)
// Delete `inProject` field because tests may fail when run as part of Atom core
// (i.e. when this test file will be located under `node_modules/exception-reporting/spec`)
delete body.events[0].exceptions[0].stacktrace[0].inProject
expect(body).toEqual({
"apiKey": Reporter.API_KEY,
"notifier": {
"name": "Atom",
"version": Reporter.LIB_VERSION,
"url": "https://www.atom.io"
apiKey: Reporter.API_KEY,
notifier: {
name: 'Atom',
version: Reporter.LIB_VERSION,
url: 'https://www.atom.io'
},
"events": [
events: [
{
"payloadVersion": "2",
"exceptions": [
payloadVersion: '2',
exceptions: [
{
"errorClass": "Error",
"message": "",
"stacktrace": [
errorClass: 'Error',
message: '',
stacktrace: [
{
"method": semver.gt(process.versions.electron, '1.6.0') ? 'Spec.it' : 'it',
"file": '~/exception-reporting/spec/reporter-spec.js',
"lineNumber": lineNumber,
"columnNumber": columnNumber
method: semver.gt(process.versions.electron, '1.6.0')
? 'Spec.it'
: 'it',
file: '~/exception-reporting/spec/reporter-spec.js',
lineNumber: lineNumber,
columnNumber: columnNumber
}
]
}
],
"severity": "error",
"user": {},
"app": {
"version": atom.getVersion(),
"releaseStage": getReleaseChannel(atom.getVersion())
severity: 'error',
user: {},
app: {
version: atom.getVersion(),
releaseStage: getReleaseChannel(atom.getVersion())
},
"device": {
"osVersion": osVersion
device: {
osVersion: osVersion
}
}
]
});})
})
})
describe("when the error object has `privateMetadata` and `privateMetadataDescription` fields", () => {
describe('when the error object has `privateMetadata` and `privateMetadataDescription` fields', () => {
let [error, notification] = []
beforeEach(() => {
@ -164,17 +183,17 @@ describe("Reporter", () => {
error = new Error()
Error.captureStackTrace(error)
error.metadata = {foo: "bar"}
error.privateMetadata = {baz: "quux"}
error.privateMetadataDescription = "The contents of baz"
error.metadata = { foo: 'bar' }
error.privateMetadata = { baz: 'quux' }
error.privateMetadataDescription = 'The contents of baz'
})
it("posts a notification asking for consent", () => {
it('posts a notification asking for consent', () => {
reporter.reportUncaughtException(error)
expect(atom.notifications.addInfo).toHaveBeenCalled()
})
it("submits the error with the private metadata if the user consents", () => {
it('submits the error with the private metadata if the user consents', () => {
spyOn(reporter, 'reportUncaughtException').andCallThrough()
reporter.reportUncaughtException(error)
reporter.reportUncaughtException.reset()
@ -189,12 +208,12 @@ describe("Reporter", () => {
expect(reporter.reportUncaughtException.callCount).toBe(1)
expect(error.privateMetadata).toBeUndefined()
expect(error.privateMetadataDescription).toBeUndefined()
expect(error.metadata).toEqual({foo: "bar", baz: "quux"})
expect(error.metadata).toEqual({ foo: 'bar', baz: 'quux' })
expect(notification.isDismissed()).toBe(true)
})
it("submits the error without the private metadata if the user does not consent", () => {
it('submits the error without the private metadata if the user does not consent', () => {
spyOn(reporter, 'reportUncaughtException').andCallThrough()
reporter.reportUncaughtException(error)
reporter.reportUncaughtException.reset()
@ -209,12 +228,12 @@ describe("Reporter", () => {
expect(reporter.reportUncaughtException.callCount).toBe(1)
expect(error.privateMetadata).toBeUndefined()
expect(error.privateMetadataDescription).toBeUndefined()
expect(error.metadata).toEqual({foo: "bar"})
expect(error.metadata).toEqual({ foo: 'bar' })
expect(notification.isDismissed()).toBe(true)
})
it("submits the error without the private metadata if the user dismisses the notification", () => {
it('submits the error without the private metadata if the user dismisses the notification', () => {
spyOn(reporter, 'reportUncaughtException').andCallThrough()
reporter.reportUncaughtException(error)
reporter.reportUncaughtException.reset()
@ -226,14 +245,34 @@ describe("Reporter", () => {
expect(reporter.reportUncaughtException.callCount).toBe(1)
expect(error.privateMetadata).toBeUndefined()
expect(error.privateMetadataDescription).toBeUndefined()
expect(error.metadata).toEqual({foo: "bar"});});})
expect(error.metadata).toEqual({ foo: 'bar' })
})
})
it('treats packages located in atom.packages.getPackageDirPaths as user packages', () => {
mockActivePackages = [
{name: 'user-1', path: '/Users/user/.atom/packages/user-1', metadata: {version: '1.0.0'}},
{name: 'user-2', path: '/Users/user/.atom/packages/user-2', metadata: {version: '1.2.0'}},
{name: 'bundled-1', path: '/Applications/Atom.app/Contents/Resources/app.asar/node_modules/bundled-1', metadata: {version: '1.0.0'}},
{name: 'bundled-2', path: '/Applications/Atom.app/Contents/Resources/app.asar/node_modules/bundled-2', metadata: {version: '1.2.0'}},
{
name: 'user-1',
path: '/Users/user/.atom/packages/user-1',
metadata: { version: '1.0.0' }
},
{
name: 'user-2',
path: '/Users/user/.atom/packages/user-2',
metadata: { version: '1.2.0' }
},
{
name: 'bundled-1',
path:
'/Applications/Atom.app/Contents/Resources/app.asar/node_modules/bundled-1',
metadata: { version: '1.0.0' }
},
{
name: 'bundled-2',
path:
'/Applications/Atom.app/Contents/Resources/app.asar/node_modules/bundled-2',
metadata: { version: '1.2.0' }
}
]
const packageDirPaths = ['/Users/user/.atom/packages']
@ -269,69 +308,78 @@ describe("Reporter", () => {
const lastRequest = requests[requests.length - 1]
const body = JSON.parse(lastRequest.body)
console.log(body);
console.log(body)
expect(body.events[0].metaData.previousErrors).toEqual(['A', 'B'])
expect(body.events[0].metaData.previousAssertionFailures).toEqual(['X', 'Y'])
expect(body.events[0].metaData.previousAssertionFailures).toEqual([
'X',
'Y'
])
})
})
describe(".reportFailedAssertion(error)", () => {
it("posts warnings to bugsnag", () => {
describe('.reportFailedAssertion(error)', () => {
it('posts warnings to bugsnag', () => {
fs.getHomeDirectory = () => path.join(__dirname, '..', '..')
let error = new Error()
Error.captureStackTrace(error)
reporter.reportFailedAssertion(error)
let [lineNumber, columnNumber] = error.stack.match(/.js:(\d+):(\d+)/).slice(1).map(s => parseInt(s))
let [lineNumber, columnNumber] = error.stack
.match(/.js:(\d+):(\d+)/)
.slice(1)
.map(s => parseInt(s))
expect(requests.length).toBe(1)
let [request] = requests
expect(request.method).toBe("POST")
expect(request.url).toBe("https://notify.bugsnag.com")
expect(request.headers.get("Content-Type")).toBe("application/json")
expect(request.method).toBe('POST')
expect(request.url).toBe('https://notify.bugsnag.com')
expect(request.headers.get('Content-Type')).toBe('application/json')
let body = JSON.parse(request.body)
// Delete `inProject` field because tests may fail when run as part of Atom core
// (i.e. when this test file will be located under `node_modules/exception-reporting/spec`)
delete body.events[0].exceptions[0].stacktrace[0].inProject
expect(body).toEqual({
"apiKey": Reporter.API_KEY,
"notifier": {
"name": "Atom",
"version": Reporter.LIB_VERSION,
"url": "https://www.atom.io"
apiKey: Reporter.API_KEY,
notifier: {
name: 'Atom',
version: Reporter.LIB_VERSION,
url: 'https://www.atom.io'
},
"events": [
events: [
{
"payloadVersion": "2",
"exceptions": [
payloadVersion: '2',
exceptions: [
{
"errorClass": "Error",
"message": "",
"stacktrace": [
errorClass: 'Error',
message: '',
stacktrace: [
{
"method": semver.gt(process.versions.electron, '1.6.0') ? 'Spec.it' : 'it',
"file": '~/exception-reporting/spec/reporter-spec.js',
"lineNumber": lineNumber,
"columnNumber": columnNumber
method: semver.gt(process.versions.electron, '1.6.0')
? 'Spec.it'
: 'it',
file: '~/exception-reporting/spec/reporter-spec.js',
lineNumber: lineNumber,
columnNumber: columnNumber
}
]
}
],
"severity": "warning",
"user": {},
"app": {
"version": atom.getVersion(),
"releaseStage": getReleaseChannel(atom.getVersion())
severity: 'warning',
user: {},
app: {
version: atom.getVersion(),
releaseStage: getReleaseChannel(atom.getVersion())
},
"device": {
"osVersion": osVersion
device: {
osVersion: osVersion
}
}
]
});})
})
})
describe("when the error object has `privateMetadata` and `privateMetadataDescription` fields", () => {
describe('when the error object has `privateMetadata` and `privateMetadataDescription` fields', () => {
let [error, notification] = []
beforeEach(() => {
@ -341,17 +389,17 @@ describe("Reporter", () => {
error = new Error()
Error.captureStackTrace(error)
error.metadata = {foo: "bar"}
error.privateMetadata = {baz: "quux"}
error.privateMetadataDescription = "The contents of baz"
error.metadata = { foo: 'bar' }
error.privateMetadata = { baz: 'quux' }
error.privateMetadataDescription = 'The contents of baz'
})
it("posts a notification asking for consent", () => {
it('posts a notification asking for consent', () => {
reporter.reportFailedAssertion(error)
expect(atom.notifications.addInfo).toHaveBeenCalled()
})
it("submits the error with the private metadata if the user consents", () => {
it('submits the error with the private metadata if the user consents', () => {
spyOn(reporter, 'reportFailedAssertion').andCallThrough()
reporter.reportFailedAssertion(error)
reporter.reportFailedAssertion.reset()
@ -366,12 +414,12 @@ describe("Reporter", () => {
expect(reporter.reportFailedAssertion.callCount).toBe(1)
expect(error.privateMetadata).toBeUndefined()
expect(error.privateMetadataDescription).toBeUndefined()
expect(error.metadata).toEqual({foo: "bar", baz: "quux"})
expect(error.metadata).toEqual({ foo: 'bar', baz: 'quux' })
expect(notification.isDismissed()).toBe(true)
})
it("submits the error without the private metadata if the user does not consent", () => {
it('submits the error without the private metadata if the user does not consent', () => {
spyOn(reporter, 'reportFailedAssertion').andCallThrough()
reporter.reportFailedAssertion(error)
reporter.reportFailedAssertion.reset()
@ -386,12 +434,12 @@ describe("Reporter", () => {
expect(reporter.reportFailedAssertion.callCount).toBe(1)
expect(error.privateMetadata).toBeUndefined()
expect(error.privateMetadataDescription).toBeUndefined()
expect(error.metadata).toEqual({foo: "bar"})
expect(error.metadata).toEqual({ foo: 'bar' })
expect(notification.isDismissed()).toBe(true)
})
it("submits the error without the private metadata if the user dismisses the notification", () => {
it('submits the error without the private metadata if the user dismisses the notification', () => {
spyOn(reporter, 'reportFailedAssertion').andCallThrough()
reporter.reportFailedAssertion(error)
reporter.reportFailedAssertion.reset()
@ -403,13 +451,17 @@ describe("Reporter", () => {
expect(reporter.reportFailedAssertion.callCount).toBe(1)
expect(error.privateMetadata).toBeUndefined()
expect(error.privateMetadataDescription).toBeUndefined()
expect(error.metadata).toEqual({foo: "bar"})
expect(error.metadata).toEqual({ foo: 'bar' })
})
it("only notifies the user once for a given 'privateMetadataRequestName'", () => {
let fakeStorage = {}
spyOn(global.localStorage, 'setItem').andCallFake((key, value) => fakeStorage[key] = value)
spyOn(global.localStorage, 'getItem').andCallFake(key => fakeStorage[key])
spyOn(global.localStorage, 'setItem').andCallFake(
(key, value) => (fakeStorage[key] = value)
)
spyOn(global.localStorage, 'getItem').andCallFake(
key => fakeStorage[key]
)
error.privateMetadataRequestName = 'foo'
@ -423,7 +475,7 @@ describe("Reporter", () => {
let error2 = new Error()
Error.captureStackTrace(error2)
error2.privateMetadataDescription = 'Something about you'
error2.privateMetadata = {baz: 'quux'}
error2.privateMetadata = { baz: 'quux' }
error2.privateMetadataRequestName = 'bar'
reporter.reportFailedAssertion(error2)
@ -433,10 +485,28 @@ describe("Reporter", () => {
it('treats packages located in atom.packages.getPackageDirPaths as user packages', () => {
mockActivePackages = [
{name: 'user-1', path: '/Users/user/.atom/packages/user-1', metadata: {version: '1.0.0'}},
{name: 'user-2', path: '/Users/user/.atom/packages/user-2', metadata: {version: '1.2.0'}},
{name: 'bundled-1', path: '/Applications/Atom.app/Contents/Resources/app.asar/node_modules/bundled-1', metadata: {version: '1.0.0'}},
{name: 'bundled-2', path: '/Applications/Atom.app/Contents/Resources/app.asar/node_modules/bundled-2', metadata: {version: '1.2.0'}},
{
name: 'user-1',
path: '/Users/user/.atom/packages/user-1',
metadata: { version: '1.0.0' }
},
{
name: 'user-2',
path: '/Users/user/.atom/packages/user-2',
metadata: { version: '1.2.0' }
},
{
name: 'bundled-1',
path:
'/Applications/Atom.app/Contents/Resources/app.asar/node_modules/bundled-1',
metadata: { version: '1.0.0' }
},
{
name: 'bundled-2',
path:
'/Applications/Atom.app/Contents/Resources/app.asar/node_modules/bundled-2',
metadata: { version: '1.2.0' }
}
]
const packageDirPaths = ['/Users/user/.atom/packages']
@ -473,7 +543,10 @@ describe("Reporter", () => {
const body = JSON.parse(lastRequest.body)
expect(body.events[0].metaData.previousErrors).toEqual(['A', 'B'])
expect(body.events[0].metaData.previousAssertionFailures).toEqual(['X', 'Y'])
expect(body.events[0].metaData.previousAssertionFailures).toEqual([
'X',
'Y'
])
})
})
})

View File

@ -1,14 +1,13 @@
const SelectListView = require('atom-select-list')
const {repositoryForPath} = require('./helpers')
const { repositoryForPath } = require('./helpers')
module.exports =
class DiffListView {
module.exports = class DiffListView {
constructor () {
this.selectListView = new SelectListView({
emptyMessage: 'No diffs in file',
items: [],
filterKeyForItem: (diff) => diff.lineText,
elementForItem: (diff) => {
filterKeyForItem: diff => diff.lineText,
elementForItem: diff => {
const li = document.createElement('li')
li.classList.add('two-lines')
@ -19,15 +18,19 @@ class DiffListView {
const secondaryLine = document.createElement('div')
secondaryLine.classList.add('secondary-line')
secondaryLine.textContent = `-${diff.oldStart},${diff.oldLines} +${diff.newStart},${diff.newLines}`
secondaryLine.textContent = `-${diff.oldStart},${diff.oldLines} +${
diff.newStart
},${diff.newLines}`
li.appendChild(secondaryLine)
return li
},
didConfirmSelection: (diff) => {
didConfirmSelection: diff => {
this.cancel()
const bufferRow = diff.newStart > 0 ? diff.newStart - 1 : diff.newStart
this.editor.setCursorBufferPosition([bufferRow, 0], {autoscroll: true})
this.editor.setCursorBufferPosition([bufferRow, 0], {
autoscroll: true
})
this.editor.moveToFirstCharacterOfLine()
},
didCancelSelection: () => {
@ -35,7 +38,10 @@ class DiffListView {
}
})
this.selectListView.element.classList.add('diff-list-view')
this.panel = atom.workspace.addModalPanel({item: this.selectListView, visible: false})
this.panel = atom.workspace.addModalPanel({
item: this.selectListView,
visible: false
})
}
attach () {
@ -66,7 +72,9 @@ class DiffListView {
} else if (editor) {
this.editor = editor
const repository = repositoryForPath(this.editor.getPath())
let diffs = repository ? repository.getLineDiffs(this.editor.getPath(), this.editor.getText()) : []
let diffs = repository
? repository.getLineDiffs(this.editor.getPath(), this.editor.getText())
: []
if (!diffs) diffs = []
for (let diff of diffs) {
const bufferRow = diff.newStart > 0 ? diff.newStart - 1 : diff.newStart
@ -74,7 +82,7 @@ class DiffListView {
diff.lineText = lineText ? lineText.trim() : ''
}
await this.selectListView.update({items: diffs})
await this.selectListView.update({ items: diffs })
this.attach()
}
}

View File

@ -1,10 +1,9 @@
const {CompositeDisposable} = require('atom')
const {repositoryForPath} = require('./helpers')
const { CompositeDisposable } = require('atom')
const { repositoryForPath } = require('./helpers')
const MAX_BUFFER_LENGTH_TO_DIFF = 2 * 1024 * 1024
module.exports =
class GitDiffView {
module.exports = class GitDiffView {
constructor (editor) {
this.updateDiffs = this.updateDiffs.bind(this)
this.editor = editor
@ -22,10 +21,18 @@ class GitDiffView {
this.editor.onDidStopChanging(this.updateDiffs),
this.editor.onDidChangePath(this.updateDiffs),
atom.project.onDidChangePaths(() => this.subscribeToRepository()),
atom.commands.add(editorElement, 'git-diff:move-to-next-diff', () => this.moveToNextDiff()),
atom.commands.add(editorElement, 'git-diff:move-to-previous-diff', () => this.moveToPreviousDiff()),
atom.config.onDidChange('git-diff.showIconsInEditorGutter', () => this.updateIconDecoration()),
atom.config.onDidChange('editor.showLineNumbers', () => this.updateIconDecoration()),
atom.commands.add(editorElement, 'git-diff:move-to-next-diff', () =>
this.moveToNextDiff()
),
atom.commands.add(editorElement, 'git-diff:move-to-previous-diff', () =>
this.moveToPreviousDiff()
),
atom.config.onDidChange('git-diff.showIconsInEditorGutter', () =>
this.updateIconDecoration()
),
atom.config.onDidChange('editor.showLineNumbers', () =>
this.updateIconDecoration()
),
editorElement.onDidAttach(() => this.updateIconDecoration()),
this.editor.onDidDestroy(() => {
this.cancelUpdate()
@ -43,7 +50,7 @@ class GitDiffView {
let nextDiffLineNumber = null
let firstDiffLineNumber = null
if (this.diffs) {
for (const {newStart} of this.diffs) {
for (const { newStart } of this.diffs) {
if (newStart > cursorLineNumber) {
if (nextDiffLineNumber == null) nextDiffLineNumber = newStart - 1
nextDiffLineNumber = Math.min(newStart - 1, nextDiffLineNumber)
@ -55,7 +62,10 @@ class GitDiffView {
}
// Wrap around to the first diff in the file
if (atom.config.get('git-diff.wrapAroundOnMoveToDiff') && nextDiffLineNumber == null) {
if (
atom.config.get('git-diff.wrapAroundOnMoveToDiff') &&
nextDiffLineNumber == null
) {
nextDiffLineNumber = firstDiffLineNumber
}
@ -65,7 +75,10 @@ class GitDiffView {
updateIconDecoration () {
const gutter = this.editor.getElement().querySelector('.gutter')
if (gutter) {
if (atom.config.get('editor.showLineNumbers') && atom.config.get('git-diff.showIconsInEditorGutter')) {
if (
atom.config.get('editor.showLineNumbers') &&
atom.config.get('git-diff.showIconsInEditorGutter')
) {
gutter.classList.add('git-diff-icon')
} else {
gutter.classList.remove('git-diff-icon')
@ -78,16 +91,22 @@ class GitDiffView {
let previousDiffLineNumber = -1
let lastDiffLineNumber = -1
if (this.diffs) {
for (const {newStart} of this.diffs) {
for (const { newStart } of this.diffs) {
if (newStart < cursorLineNumber) {
previousDiffLineNumber = Math.max(newStart - 1, previousDiffLineNumber)
previousDiffLineNumber = Math.max(
newStart - 1,
previousDiffLineNumber
)
}
lastDiffLineNumber = Math.max(newStart - 1, lastDiffLineNumber)
}
}
// Wrap around to the last diff in the file
if (atom.config.get('git-diff.wrapAroundOnMoveToDiff') && previousDiffLineNumber === -1) {
if (
atom.config.get('git-diff.wrapAroundOnMoveToDiff') &&
previousDiffLineNumber === -1
) {
previousDiffLineNumber = lastDiffLineNumber
}
@ -104,12 +123,16 @@ class GitDiffView {
subscribeToRepository () {
this.repository = repositoryForPath(this.editor.getPath())
if (this.repository) {
this.subscriptions.add(this.repository.onDidChangeStatuses(() => {
this.scheduleUpdate()
}))
this.subscriptions.add(this.repository.onDidChangeStatus(changedPath => {
if (changedPath === this.editor.getPath()) this.scheduleUpdate()
}))
this.subscriptions.add(
this.repository.onDidChangeStatuses(() => {
this.scheduleUpdate()
})
)
this.subscriptions.add(
this.repository.onDidChangeStatus(changedPath => {
if (changedPath === this.editor.getPath()) this.scheduleUpdate()
})
)
}
}
@ -126,16 +149,21 @@ class GitDiffView {
if (this.editor.isDestroyed()) return
this.removeDecorations()
const path = this.editor && this.editor.getPath()
if (path && this.editor.getBuffer().getLength() < MAX_BUFFER_LENGTH_TO_DIFF) {
this.diffs = this.repository && this.repository.getLineDiffs(path, this.editor.getText())
if (
path &&
this.editor.getBuffer().getLength() < MAX_BUFFER_LENGTH_TO_DIFF
) {
this.diffs =
this.repository &&
this.repository.getLineDiffs(path, this.editor.getText())
if (this.diffs) this.addDecorations(this.diffs)
}
}
addDecorations (diffs) {
for (const {newStart, oldLines, newLines} of diffs) {
for (const { newStart, oldLines, newLines } of diffs) {
const startRow = newStart - 1
const endRow = (newStart + newLines) - 1
const endRow = newStart + newLines - 1
if (oldLines === 0 && newLines > 0) {
this.markRange(startRow, endRow, 'git-line-added')
} else if (newLines === 0 && oldLines > 0) {
@ -156,8 +184,10 @@ class GitDiffView {
}
markRange (startRow, endRow, klass) {
const marker = this.editor.markBufferRange([[startRow, 0], [endRow, 0]], {invalidate: 'never'})
this.editor.decorateMarker(marker, {type: 'line-number', class: klass})
const marker = this.editor.markBufferRange([[startRow, 0], [endRow, 0]], {
invalidate: 'never'
})
this.editor.decorateMarker(marker, { type: 'line-number', class: klass })
this.markers.push(marker)
}
}

View File

@ -11,10 +11,14 @@ module.exports = {
if (watchedEditors.has(editor)) return
new GitDiffView(editor).start()
atom.commands.add(atom.views.getView(editor), 'git-diff:toggle-diff-list', () => {
if (diffListView == null) diffListView = new DiffListView()
diffListView.toggle()
})
atom.commands.add(
atom.views.getView(editor),
'git-diff:toggle-diff-list',
() => {
if (diffListView == null) diffListView = new DiffListView()
diffListView.toggle()
}
)
watchedEditors.add(editor)
editor.onDidDestroy(() => watchedEditors.delete(editor))

View File

@ -8,7 +8,10 @@ describe('git-diff:toggle-diff-list', () => {
beforeEach(() => {
const projectPath = temp.mkdirSync('git-diff-spec-')
fs.copySync(path.join(__dirname, 'fixtures', 'working-dir'), projectPath)
fs.moveSync(path.join(projectPath, 'git.git'), path.join(projectPath, '.git'))
fs.moveSync(
path.join(projectPath, 'git.git'),
path.join(projectPath, '.git')
)
atom.project.setPaths([projectPath])
jasmine.attachToDOM(atom.workspace.getElement())
@ -19,7 +22,7 @@ describe('git-diff:toggle-diff-list', () => {
runs(() => {
editor = atom.workspace.getActiveTextEditor()
editor.setCursorBufferPosition([4, 29])
editor.setCursorBufferPosition([8, 30])
editor.insertText('a')
atom.commands.dispatch(editor.getElement(), 'git-diff:toggle-diff-list')
})
@ -32,12 +35,15 @@ describe('git-diff:toggle-diff-list', () => {
it('shows a list of all diff hunks', () => {
diffListView = document.querySelector('.diff-list-view ol')
expect(diffListView.textContent).toBe('while(items.length > 0) {a-5,1 +5,1')
expect(diffListView.textContent).toBe('while (items.length > 0) {a-9,1 +9,1')
})
it('moves the cursor to the selected hunk', () => {
editor.setCursorBufferPosition([0, 0])
atom.commands.dispatch(document.querySelector('.diff-list-view'), 'core:confirm')
expect(editor.getCursorBufferPosition()).toEqual([4, 4])
atom.commands.dispatch(
document.querySelector('.diff-list-view'),
'core:confirm'
)
expect(editor.getCursorBufferPosition()).toEqual([8, 4])
})
})

View File

@ -1 +1 @@
90820108a054b6f49c0d21031313244b6f7d69dc
065a272b55ec2ee84530dffd60b6869f7bf5d99c

View File

@ -1,13 +1,19 @@
var quicksort = function () {
var sort = function(items) {
if (items.length <= 1) return items;
var pivot = items.shift(), current, left = [], right = [];
while(items.length > 0) {
current = items.shift();
current < pivot ? left.push(current) : right.push(current);
}
return sort(left).concat(pivot).concat(sort(right));
};
module.exports.quicksort = function () {
var sort = function (items) {
if (items.length <= 1) return items
var pivot = items.shift()
var current
var left = []
var right = []
return sort(Array.apply(this, arguments));
};
while (items.length > 0) {
current = items.shift()
current < pivot ? left.push(current) : right.push(current)
}
return sort(left)
.concat(pivot)
.concat(sort(right))
}
return sort(Array.apply(this, arguments))
}

View File

@ -12,12 +12,17 @@ describe('GitDiff package', () => {
const otherPath = temp.mkdirSync('some-other-path-')
fs.copySync(path.join(__dirname, 'fixtures', 'working-dir'), projectPath)
fs.moveSync(path.join(projectPath, 'git.git'), path.join(projectPath, '.git'))
fs.moveSync(
path.join(projectPath, 'git.git'),
path.join(projectPath, '.git')
)
atom.project.setPaths([otherPath, projectPath])
jasmine.attachToDOM(atom.workspace.getElement())
waitsForPromise(() => atom.workspace.open(path.join(projectPath, 'sample.js')))
waitsForPromise(() =>
atom.workspace.open(path.join(projectPath, 'sample.js'))
)
runs(() => {
editor = atom.workspace.getActiveTextEditor()
@ -29,11 +34,18 @@ describe('GitDiff package', () => {
describe('when the editor has modified lines', () => {
it('highlights the modified lines', () => {
expect(editorElement.querySelectorAll('.git-line-modified').length).toBe(0)
expect(editorElement.querySelectorAll('.git-line-modified').length).toBe(
0
)
editor.insertText('a')
advanceClock(editor.getBuffer().stoppedChangingDelay)
expect(editorElement.querySelectorAll('.git-line-modified').length).toBe(1)
expect(editorElement.querySelector('.git-line-modified')).toHaveData('buffer-row', 0)
expect(editorElement.querySelectorAll('.git-line-modified').length).toBe(
1
)
expect(editorElement.querySelector('.git-line-modified')).toHaveData(
'buffer-row',
0
)
})
})
@ -45,7 +57,10 @@ describe('GitDiff package', () => {
editor.insertText('a')
advanceClock(editor.getBuffer().stoppedChangingDelay)
expect(editorElement.querySelectorAll('.git-line-added').length).toBe(1)
expect(editorElement.querySelector('.git-line-added')).toHaveData('buffer-row', 1)
expect(editorElement.querySelector('.git-line-added')).toHaveData(
'buffer-row',
1
)
})
})
@ -56,7 +71,10 @@ describe('GitDiff package', () => {
editor.deleteLine()
advanceClock(editor.getBuffer().stoppedChangingDelay)
expect(editorElement.querySelectorAll('.git-line-removed').length).toBe(1)
expect(editorElement.querySelector('.git-line-removed')).toHaveData('buffer-row', 4)
expect(editorElement.querySelector('.git-line-removed')).toHaveData(
'buffer-row',
4
)
})
})
@ -66,29 +84,44 @@ describe('GitDiff package', () => {
editor.setCursorBufferPosition([0, 0])
editor.deleteLine()
advanceClock(editor.getBuffer().stoppedChangingDelay)
expect(editorElement.querySelectorAll('.git-previous-line-removed').length).toBe(1)
expect(editorElement.querySelector('.git-previous-line-removed')).toHaveData('buffer-row', 0)
expect(
editorElement.querySelectorAll('.git-previous-line-removed').length
).toBe(1)
expect(
editorElement.querySelector('.git-previous-line-removed')
).toHaveData('buffer-row', 0)
})
})
describe('when a modified line is restored to the HEAD version contents', () => {
it('removes the diff highlight', () => {
expect(editorElement.querySelectorAll('.git-line-modified').length).toBe(0)
expect(editorElement.querySelectorAll('.git-line-modified').length).toBe(
0
)
editor.insertText('a')
advanceClock(editor.getBuffer().stoppedChangingDelay)
expect(editorElement.querySelectorAll('.git-line-modified').length).toBe(1)
expect(editorElement.querySelectorAll('.git-line-modified').length).toBe(
1
)
editor.backspace()
advanceClock(editor.getBuffer().stoppedChangingDelay)
expect(editorElement.querySelectorAll('.git-line-modified').length).toBe(0)
expect(editorElement.querySelectorAll('.git-line-modified').length).toBe(
0
)
})
})
describe('when a modified file is opened', () => {
it('highlights the changed lines', () => {
fs.writeFileSync(path.join(projectPath, 'sample.txt'), 'Some different text.')
fs.writeFileSync(
path.join(projectPath, 'sample.txt'),
'Some different text.'
)
let nextTick = false
waitsForPromise(() => atom.workspace.open(path.join(projectPath, 'sample.txt')))
waitsForPromise(() =>
atom.workspace.open(path.join(projectPath, 'sample.txt'))
)
runs(() => {
editorElement = atom.workspace.getActiveTextEditor().getElement()
@ -101,8 +134,13 @@ describe('GitDiff package', () => {
waitsFor(() => nextTick)
runs(() => {
expect(editorElement.querySelectorAll('.git-line-modified').length).toBe(1)
expect(editorElement.querySelector('.git-line-modified')).toHaveData('buffer-row', 0)
expect(
editorElement.querySelectorAll('.git-line-modified').length
).toBe(1)
expect(editorElement.querySelector('.git-line-modified')).toHaveData(
'buffer-row',
0
)
})
})
})
@ -148,7 +186,9 @@ describe('GitDiff package', () => {
})
describe('when the wrapAroundOnMoveToDiff config option is false', () => {
beforeEach(() => atom.config.set('git-diff.wrapAroundOnMoveToDiff', false))
beforeEach(() =>
atom.config.set('git-diff.wrapAroundOnMoveToDiff', false)
)
it('does not wraps around to the first/last diff in the file', () => {
editor.insertText('a')
@ -177,19 +217,28 @@ describe('GitDiff package', () => {
atom.config.set('git-diff.showIconsInEditorGutter', true)
})
it('the gutter has a git-diff-icon class', () => expect(editorElement.querySelector('.gutter')).toHaveClass('git-diff-icon'))
it('the gutter has a git-diff-icon class', () =>
expect(editorElement.querySelector('.gutter')).toHaveClass(
'git-diff-icon'
))
it('keeps the git-diff-icon class when editor.showLineNumbers is toggled', () => {
atom.config.set('editor.showLineNumbers', false)
expect(editorElement.querySelector('.gutter')).not.toHaveClass('git-diff-icon')
expect(editorElement.querySelector('.gutter')).not.toHaveClass(
'git-diff-icon'
)
atom.config.set('editor.showLineNumbers', true)
expect(editorElement.querySelector('.gutter')).toHaveClass('git-diff-icon')
expect(editorElement.querySelector('.gutter')).toHaveClass(
'git-diff-icon'
)
})
it('removes the git-diff-icon class when the showIconsInEditorGutter config option set to false', () => {
atom.config.set('git-diff.showIconsInEditorGutter', false)
expect(editorElement.querySelector('.gutter')).not.toHaveClass('git-diff-icon')
expect(editorElement.querySelector('.gutter')).not.toHaveClass(
'git-diff-icon'
)
})
})
})

View File

@ -21,7 +21,7 @@ atom-text-editor {
left: 0;
height: 0;
width: 0;
content: " ";
content: "";
border: solid transparent;
border-left-color: @syntax-color-removed;
border-width: @size;
@ -37,22 +37,19 @@ atom-text-editor {
}
.gutter.git-diff-icon .line-number {
width: 100%;
border-left: none;
padding-left: 0.4em;
border-left-width: 0;
padding-left: 1.4em; // space for diff icon
&:before {
.octicon-font();
.octicon-font();
content: "";
display: inline-block;
position: relative;
top: -.05em;
// make sure it doesnt affect the gutter line height.
height: 0px;
position: absolute;
top: .2em;
left: .4em;
height: 0px; // make sure it doesnt affect the gutter line height.
width: 1em;
content: " ";
padding-right: 0.4em;
font-size: .95em;
font-size: .75em;
}
&.git-line-modified:before {
@ -70,13 +67,12 @@ atom-text-editor {
border: none; // reset triangle
content: @dash;
color: @syntax-color-removed;
position: relative;
}
&.git-line-removed:before {
top: .6em;
top: 1em;
}
&.git-previous-line-removed:before {
top: -.6em;
top: 0;
}
}
}

View File

@ -29,13 +29,13 @@ class GoToLineView {
atom.commands.add(this.miniEditor.element, 'core:cancel', () => {
this.close()
})
this.miniEditor.onWillInsertText((arg) => {
this.miniEditor.onWillInsertText(arg => {
if (arg.text.match(/[^0-9:]/)) {
arg.cancel()
}
})
this.miniEditor.onDidChange(() => {
this.navigate({keepOpen: true})
this.navigate({ keepOpen: true })
})
}
@ -62,9 +62,11 @@ class GoToLineView {
const currentRow = editor.getCursorBufferPosition().row
const rowLineNumber = lineNumber.split(/:+/)[0] || ''
const row = rowLineNumber.length > 0 ? parseInt(rowLineNumber) - 1 : currentRow
const row =
rowLineNumber.length > 0 ? parseInt(rowLineNumber) - 1 : currentRow
const columnLineNumber = lineNumber.split(/:+/)[1] || ''
const column = columnLineNumber.length > 0 ? parseInt(columnLineNumber) - 1 : -1
const column =
columnLineNumber.length > 0 ? parseInt(columnLineNumber) - 1 : -1
const position = new Point(row, column)
editor.setCursorBufferPosition(position)
@ -83,7 +85,10 @@ class GoToLineView {
}
restoreFocus () {
if (this.previouslyFocusedElement && this.previouslyFocusedElement.parentElement) {
if (
this.previouslyFocusedElement &&
this.previouslyFocusedElement.parentElement
) {
return this.previouslyFocusedElement.focus()
}
atom.views.getView(atom.workspace).focus()
@ -93,7 +98,8 @@ class GoToLineView {
if (this.panel.isVisible() || !atom.workspace.getActiveTextEditor()) return
this.storeFocusedElement()
this.panel.show()
this.message.textContent = 'Enter a <row> or <row>:<column> to go there. Examples: "3" for row 3 or "2:7" for row 2 and column 7'
this.message.textContent =
'Enter a <row> or <row>:<column> to go there. Examples: "3" for row 3 or "2:7" for row 2 and column 7'
this.miniEditor.element.focus()
}
}

View File

@ -1,70 +1,84 @@
var quicksort = function () {
var sort = function(items) {
if (items.length <= 1) return items;
var pivot = items.shift(), current, left = [], right = [];
while(items.length > 0) {
current = items.shift();
current < pivot ? left.push(current) : right.push(current);
}
return sort(left).concat(pivot).concat(sort(right));
};
var sort = function (items) {
if (items.length <= 1) return items
var pivot = items.shift()
var current
var left = []
var right = []
return sort(Array.apply(this, arguments));
};
while (items.length > 0) {
current = items.shift()
current < pivot ? left.push(current) : right.push(current)
}
return sort(left)
.concat(pivot)
.concat(sort(right))
}
return sort(Array.apply(this, arguments))
}
// adapted from:
// https://github.com/nzakas/computer-science-in-javascript/tree/master/algorithms/sorting/merge-sort-recursive
var mergeSort function (items){
var merge = function (left, right){
var result = [];
var il = 0;
var ir = 0;
var mergeSort = function (items) {
var merge = function (left, right) {
var result = []
var il = 0
var ir = 0
while (il < left.length && ir < right.length){
if (left[il] < right[ir]){
result.push(left[il++]);
while (il < left.length && ir < right.length) {
if (left[il] < right[ir]) {
result.push(left[il++])
} else {
result.push(right[ir++]);
result.push(right[ir++])
}
}
return result.concat(left.slice(il)).concat(right.slice(ir));
};
if (items.length < 2) {
return items;
return result.concat(left.slice(il)).concat(right.slice(ir))
}
var middle = Math.floor(items.length / 2),
left = items.slice(0, middle),
right = items.slice(middle),
params = merge(mergeSort(left), mergeSort(right));
if (items.length < 2) {
return items
}
var middle = Math.floor(items.length / 2)
var left = items.slice(0, middle)
var right = items.slice(middle)
var params = merge(mergeSort(left), mergeSort(right))
// Add the arguments to replace everything between 0 and last item in the array
params.unshift(0, items.length);
items.splice.apply(items, params);
return items;
};
params.unshift(0, items.length)
items.splice.apply(items, params)
return items
}
// adapted from:
// https://github.com/nzakas/computer-science-in-javascript/blob/master/algorithms/sorting/bubble-sort/bubble-sort.js
var bubbleSort = function (items){
var swap = function (items, firstIndex, secondIndex){
var temp = items[firstIndex];
items[firstIndex] = items[secondIndex];
items[secondIndex] = temp;
};
var bubbleSort = function (items) {
var swap = function (items, firstIndex, secondIndex) {
var temp = items[firstIndex]
items[firstIndex] = items[secondIndex]
items[secondIndex] = temp
}
var len = items.length,
i, j, stop;
var len = items.length
var i
var j
var stop
for (i=0; i < len; i++){
for (j=0, stop=len-i; j < stop; j++){
if (items[j] > items[j+1]){
swap(items, j, j+1);
for (i = 0; i < len; i++) {
for (j = 0, stop = len - i; j < stop; j++) {
if (items[j] > items[j + 1]) {
swap(items, j, j + 1)
}
}
}
return items;
};
return items
}
module.exports = {
bubbleSort,
mergeSort,
quicksort
}

View File

@ -51,8 +51,8 @@ describe('GoToLine', () => {
describe('when typing line numbers (auto-navigation)', () => {
it('automatically scrolls to the desired line', () => {
goToLine.miniEditor.insertText('13')
expect(editor.getCursorBufferPosition()).toEqual([12, 0])
goToLine.miniEditor.insertText('19')
expect(editor.getCursorBufferPosition()).toEqual([18, 0])
})
})
@ -76,8 +76,12 @@ describe('GoToLine', () => {
atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm')
const rowsPerPage = editor.getRowsPerPage()
const currentRow = editor.getCursorBufferPosition().row - 1
expect(editor.getFirstVisibleScreenRow()).toBe(currentRow - Math.ceil(rowsPerPage / 2))
expect(editor.getLastVisibleScreenRow()).toBe(currentRow + Math.floor(rowsPerPage / 2))
expect(editor.getFirstVisibleScreenRow()).toBe(
currentRow - Math.ceil(rowsPerPage / 2)
)
expect(editor.getLastVisibleScreenRow()).toBe(
currentRow + Math.floor(rowsPerPage / 2)
)
})
})
@ -86,10 +90,10 @@ describe('GoToLine', () => {
atom.commands.dispatch(editorView, 'go-to-line:toggle')
expect(goToLine.panel.isVisible()).toBeTruthy()
expect(goToLine.miniEditor.getText()).toBe('')
goToLine.miniEditor.insertText('71')
goToLine.miniEditor.insertText('78')
atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm')
expect(goToLine.panel.isVisible()).toBeFalsy()
expect(editor.getCursorBufferPosition()).toEqual([70, 0])
expect(editor.getCursorBufferPosition()).toEqual([77, 0])
})
})
@ -101,7 +105,7 @@ describe('GoToLine', () => {
goToLine.miniEditor.insertText('3:43')
atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm')
expect(goToLine.panel.isVisible()).toBeFalsy()
expect(editor.getCursorBufferPosition()).toEqual([2, 40])
expect(editor.getCursorBufferPosition()).toEqual([2, 39])
})
})
@ -116,12 +120,12 @@ describe('GoToLine', () => {
describe('when the line number entered is nested within foldes', () => {
it('unfolds all folds containing the given row', () => {
expect(editor.indentationForBufferRow(6)).toEqual(3)
expect(editor.indentationForBufferRow(9)).toEqual(3)
editor.foldAll()
expect(editor.screenRowForBufferRow(6)).toEqual(0)
goToLine.miniEditor.insertText('7')
expect(editor.screenRowForBufferRow(9)).toEqual(0)
goToLine.miniEditor.insertText('10')
atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm')
expect(editor.getCursorBufferPosition()).toEqual([6, 6])
expect(editor.getCursorBufferPosition()).toEqual([9, 6])
})
})
})

View File

@ -1,14 +1,13 @@
const SelectListView = require('atom-select-list')
module.exports =
class GrammarListView {
module.exports = class GrammarListView {
constructor () {
this.autoDetect = {name: 'Auto Detect'}
this.autoDetect = { name: 'Auto Detect' }
this.selectListView = new SelectListView({
itemsClassList: ['mark-active'],
items: [],
filterKeyForItem: (grammar) => grammar.name,
elementForItem: (grammar) => {
filterKeyForItem: grammar => grammar.name,
elementForItem: grammar => {
const grammarName = grammar.name || grammar.scopeName
const element = document.createElement('li')
if (grammar === this.currentGrammar) {
@ -29,7 +28,7 @@ class GrammarListView {
return element
},
didConfirmSelection: (grammar) => {
didConfirmSelection: grammar => {
this.cancel()
if (grammar === this.autoDetect) {
atom.textEditors.clearGrammarOverride(this.editor)
@ -64,7 +63,7 @@ class GrammarListView {
attach () {
this.previouslyFocusedElement = document.activeElement
if (this.panel == null) {
this.panel = atom.workspace.addModalPanel({item: this.selectListView})
this.panel = atom.workspace.addModalPanel({ item: this.selectListView })
}
this.selectListView.focus()
this.selectListView.reset()
@ -80,7 +79,7 @@ class GrammarListView {
this.currentGrammar = this.autoDetect
}
const grammars = atom.grammars.getGrammars().filter((grammar) => {
const grammars = atom.grammars.getGrammars().filter(grammar => {
return grammar !== atom.grammars.nullGrammar && grammar.name
})
grammars.sort((a, b) => {
@ -97,7 +96,7 @@ class GrammarListView {
}
})
grammars.unshift(this.autoDetect)
await this.selectListView.update({items: grammars})
await this.selectListView.update({ items: grammars })
this.attach()
}
}

View File

@ -1,7 +1,6 @@
const {Disposable} = require('atom')
const { Disposable } = require('atom')
module.exports =
class GrammarStatusView {
module.exports = class GrammarStatusView {
constructor (statusBar) {
this.statusBar = statusBar
this.element = document.createElement('grammar-selector-status')
@ -10,15 +9,25 @@ class GrammarStatusView {
this.grammarLink.classList.add('inline-block')
this.element.appendChild(this.grammarLink)
this.activeItemSubscription = atom.workspace.observeActiveTextEditor(this.subscribeToActiveTextEditor.bind(this))
this.activeItemSubscription = atom.workspace.observeActiveTextEditor(
this.subscribeToActiveTextEditor.bind(this)
)
this.configSubscription = atom.config.observe('grammar-selector.showOnRightSideOfStatusBar', this.attach.bind(this))
const clickHandler = (event) => {
this.configSubscription = atom.config.observe(
'grammar-selector.showOnRightSideOfStatusBar',
this.attach.bind(this)
)
const clickHandler = event => {
event.preventDefault()
atom.commands.dispatch(atom.views.getView(atom.workspace.getActiveTextEditor()), 'grammar-selector:show')
atom.commands.dispatch(
atom.views.getView(atom.workspace.getActiveTextEditor()),
'grammar-selector:show'
)
}
this.element.addEventListener('click', clickHandler)
this.clickSubscription = new Disposable(() => { this.element.removeEventListener('click', clickHandler) })
this.clickSubscription = new Disposable(() => {
this.element.removeEventListener('click', clickHandler)
})
}
attach () {
@ -27,8 +36,8 @@ class GrammarStatusView {
}
this.tile = atom.config.get('grammar-selector.showOnRightSideOfStatusBar')
? this.statusBar.addRightTile({item: this.element, priority: 10})
: this.statusBar.addLeftTile({item: this.element, priority: 10})
? this.statusBar.addRightTile({ item: this.element, priority: 10 })
: this.statusBar.addLeftTile({ item: this.element, priority: 10 })
}
destroy () {
@ -65,7 +74,9 @@ class GrammarStatusView {
const editor = atom.workspace.getActiveTextEditor()
if (editor) {
this.grammarSubscription = editor.onDidChangeGrammar(this.updateGrammarText.bind(this))
this.grammarSubscription = editor.onDidChangeGrammar(
this.updateGrammarText.bind(this)
)
}
this.updateGrammarText()
}
@ -92,7 +103,9 @@ class GrammarStatusView {
this.grammarLink.dataset.grammar = grammarName
this.element.style.display = ''
this.tooltip = atom.tooltips.add(this.element, {title: `File uses the ${grammarName} grammar`})
this.tooltip = atom.tooltips.add(this.element, {
title: `File uses the ${grammarName} grammar`
})
} else {
this.element.style.display = 'none'
}

View File

@ -7,10 +7,14 @@ let grammarStatusView = null
module.exports = {
activate () {
commandDisposable = atom.commands.add('atom-text-editor', 'grammar-selector:show', () => {
if (!grammarListView) grammarListView = new GrammarListView()
grammarListView.toggle()
})
commandDisposable = atom.commands.add(
'atom-text-editor',
'grammar-selector:show',
() => {
if (!grammarListView) grammarListView = new GrammarListView()
grammarListView.toggle()
}
)
},
deactivate () {

View File

@ -1,42 +0,0 @@
exports.beforeEach = function beforeEach (fn) {
global.beforeEach(function () {
const result = fn()
if (result instanceof Promise) {
waitsForPromise('beforeEach promise', result)
}
})
}
exports.afterEach = function afterEach (fn) {
global.afterEach(function () {
const result = fn()
if (result instanceof Promise) {
waitsForPromise('afterEach promise', result)
}
})
}
;['it', 'fit', 'ffit', 'fffit'].forEach(function (name) {
exports[name] = function (description, fn) {
if (fn === undefined) {
global[name](description)
return
}
global[name](description, function () {
const result = fn()
if (result instanceof Promise) {
waitsForPromise('test promise', result)
}
})
}
})
function waitsForPromise (message, promise) {
global.waitsFor(message, (done) => {
promise.then(done, (error) => {
jasmine.getEnv().currentSpec.fail(error)
done()
})
})
}

View File

@ -1,6 +1,5 @@
const path = require('path')
const SelectListView = require('atom-select-list')
const {it, fit, ffit, beforeEach, afterEach} = require('./async-spec-helpers') // eslint-disable-line
describe('GrammarSelector', () => {
let [editor, textGrammar, jsGrammar] = []
@ -13,7 +12,9 @@ describe('GrammarSelector', () => {
await atom.packages.activatePackage('grammar-selector')
await atom.packages.activatePackage('language-text')
await atom.packages.activatePackage('language-javascript')
await atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'language-with-no-name'))
await atom.packages.activatePackage(
path.join(__dirname, 'fixtures', 'language-with-no-name')
)
editor = await atom.workspace.open('sample.js')
@ -33,15 +34,24 @@ describe('GrammarSelector', () => {
// TODO: Remove once Atom 1.23 reaches stable
if (parseFloat(atom.getVersion()) >= 1.23) {
// Do not take into account the two JS regex grammars or language-with-no-name
expect(grammarView.querySelectorAll('li').length).toBe(atom.grammars.grammars.length - 3)
expect(grammarView.querySelectorAll('li').length).toBe(
atom.grammars.grammars.length - 3
)
} else {
expect(grammarView.querySelectorAll('li').length).toBe(atom.grammars.grammars.length - 1)
expect(grammarView.querySelectorAll('li').length).toBe(
atom.grammars.grammars.length - 1
)
}
expect(grammarView.querySelectorAll('li')[0].textContent).toBe('Auto Detect')
expect(grammarView.querySelectorAll('li')[0].textContent).toBe(
'Auto Detect'
)
expect(grammarView.textContent.includes('source.a')).toBe(false)
grammarView.querySelectorAll('li').forEach(li => expect(li.textContent).not.toBe(atom.grammars.nullGrammar.name))
})
)
grammarView
.querySelectorAll('li')
.forEach(li =>
expect(li.textContent).not.toBe(atom.grammars.nullGrammar.name)
)
}))
describe('when a grammar is selected', () =>
it('sets the new grammar on the editor', async () => {
@ -51,8 +61,7 @@ describe('GrammarSelector', () => {
const grammarView = atom.workspace.getModalPanels()[0].getItem()
grammarView.props.didConfirmSelection(textGrammar)
expect(editor.getGrammar()).toBe(textGrammar)
})
)
}))
describe('when auto-detect is selected', () =>
it('restores the auto-detected grammar on the editor', async () => {
@ -69,8 +78,7 @@ describe('GrammarSelector', () => {
grammarView = atom.workspace.getModalPanels()[0].getItem()
grammarView.props.didConfirmSelection(grammarView.items[0])
expect(editor.getGrammar()).toBe(jsGrammar)
})
)
}))
describe("when the editor's current grammar is the null grammar", () =>
it('displays Auto Detect as the selected grammar', async () => {
@ -79,9 +87,10 @@ describe('GrammarSelector', () => {
await SelectListView.getScheduler().getNextUpdatePromise()
const grammarView = atom.workspace.getModalPanels()[0].getItem().element
expect(grammarView.querySelector('li.active').textContent).toBe('Auto Detect')
})
)
expect(grammarView.querySelector('li.active').textContent).toBe(
'Auto Detect'
)
}))
describe('when editor is untitled', () =>
it('sets the new grammar on the editor', async () => {
@ -94,8 +103,7 @@ describe('GrammarSelector', () => {
const grammarView = atom.workspace.getModalPanels()[0].getItem()
grammarView.props.didConfirmSelection(jsGrammar)
expect(editor.getGrammar()).toBe(jsGrammar)
})
)
}))
describe('Status bar grammar label', () => {
let [grammarStatus, grammarTile, statusBar] = []
@ -114,7 +122,9 @@ describe('GrammarSelector', () => {
it('displays the name of the current grammar', () => {
expect(grammarStatus.querySelector('a').textContent).toBe('JavaScript')
expect(getTooltipText(grammarStatus)).toBe('File uses the JavaScript grammar')
expect(getTooltipText(grammarStatus)).toBe(
'File uses the JavaScript grammar'
)
})
it('displays Plain Text when the current grammar is the null grammar', async () => {
@ -123,7 +133,9 @@ describe('GrammarSelector', () => {
expect(grammarStatus.querySelector('a').textContent).toBe('Plain Text')
expect(grammarStatus).toBeVisible()
expect(getTooltipText(grammarStatus)).toBe('File uses the Plain Text grammar')
expect(getTooltipText(grammarStatus)).toBe(
'File uses the Plain Text grammar'
)
editor.setGrammar(atom.grammars.grammarForScopeName('source.js'))
await atom.views.getNextUpdatePromise()
@ -142,20 +154,31 @@ describe('GrammarSelector', () => {
describe('when the grammar-selector.showOnRightSideOfStatusBar setting changes', () =>
it('moves the item to the preferred side of the status bar', () => {
expect(statusBar.getLeftTiles().map(tile => tile.getItem())).toContain(grammarStatus)
expect(statusBar.getRightTiles().map(tile => tile.getItem())).not.toContain(grammarStatus)
expect(statusBar.getLeftTiles().map(tile => tile.getItem())).toContain(
grammarStatus
)
expect(
statusBar.getRightTiles().map(tile => tile.getItem())
).not.toContain(grammarStatus)
atom.config.set('grammar-selector.showOnRightSideOfStatusBar', true)
expect(statusBar.getLeftTiles().map(tile => tile.getItem())).not.toContain(grammarStatus)
expect(statusBar.getRightTiles().map(tile => tile.getItem())).toContain(grammarStatus)
expect(
statusBar.getLeftTiles().map(tile => tile.getItem())
).not.toContain(grammarStatus)
expect(statusBar.getRightTiles().map(tile => tile.getItem())).toContain(
grammarStatus
)
atom.config.set('grammar-selector.showOnRightSideOfStatusBar', false)
expect(statusBar.getLeftTiles().map(tile => tile.getItem())).toContain(grammarStatus)
expect(statusBar.getRightTiles().map(tile => tile.getItem())).not.toContain(grammarStatus)
})
)
expect(statusBar.getLeftTiles().map(tile => tile.getItem())).toContain(
grammarStatus
)
expect(
statusBar.getRightTiles().map(tile => tile.getItem())
).not.toContain(grammarStatus)
}))
describe("when the editor's grammar changes", () =>
it('displays the new grammar of the editor', async () => {
@ -163,32 +186,37 @@ describe('GrammarSelector', () => {
await atom.views.getNextUpdatePromise()
expect(grammarStatus.querySelector('a').textContent).toBe('Plain Text')
expect(getTooltipText(grammarStatus)).toBe('File uses the Plain Text grammar')
expect(getTooltipText(grammarStatus)).toBe(
'File uses the Plain Text grammar'
)
editor.setGrammar(atom.grammars.grammarForScopeName('source.a'))
await atom.views.getNextUpdatePromise()
expect(grammarStatus.querySelector('a').textContent).toBe('source.a')
expect(getTooltipText(grammarStatus)).toBe('File uses the source.a grammar')
})
)
expect(getTooltipText(grammarStatus)).toBe(
'File uses the source.a grammar'
)
}))
describe('when clicked', () =>
it('shows the grammar selector modal', () => {
const eventHandler = jasmine.createSpy('eventHandler')
atom.commands.add(editor.getElement(), 'grammar-selector:show', eventHandler)
atom.commands.add(
editor.getElement(),
'grammar-selector:show',
eventHandler
)
grammarStatus.click()
expect(eventHandler).toHaveBeenCalled()
})
)
}))
describe('when the package is deactivated', () =>
it('removes the view', () => {
spyOn(grammarTile, 'destroy')
atom.packages.deactivatePackage('grammar-selector')
expect(grammarTile.destroy).toHaveBeenCalled()
})
)
}))
})
})

View File

@ -1,7 +1,6 @@
/** @babel */
/** @jsx etch.dom */
import {BufferedProcess} from 'atom'
import etch from 'etch'
import VIEW_URI from './view-uri'
@ -11,8 +10,8 @@ const REBUILD_SUCCEEDED = 'rebuild-succeeded'
export default class IncompatiblePackagesComponent {
constructor (packageManager) {
this.rebuildStatuses = new Map
this.rebuildFailureOutputs = new Map
this.rebuildStatuses = new Map()
this.rebuildFailureOutputs = new Map()
this.rebuildInProgress = false
this.rebuiltPackageCount = 0
this.packageManager = packageManager
@ -25,13 +24,15 @@ export default class IncompatiblePackagesComponent {
global.setImmediate(this.populateIncompatiblePackages.bind(this))
}
this.element.addEventListener('click', (event) => {
this.element.addEventListener('click', event => {
if (event.target === this.refs.rebuildButton) {
this.rebuildIncompatiblePackages()
} else if (event.target === this.refs.reloadButton) {
atom.reload()
} else if (event.target.classList.contains('view-settings')) {
atom.workspace.open(`atom://config/packages/${event.target.package.name}`)
atom.workspace.open(
`atom://config/packages/${event.target.package.name}`
)
}
})
}
@ -44,7 +45,10 @@ export default class IncompatiblePackagesComponent {
}
return (
<div className='incompatible-packages padded native-key-bindings' tabIndex='-1'>
<div
className='incompatible-packages padded native-key-bindings'
tabIndex='-1'
>
{this.renderHeading()}
{this.renderIncompatiblePackageList()}
</div>
@ -55,15 +59,14 @@ export default class IncompatiblePackagesComponent {
if (this.incompatiblePackages.length > 0) {
if (this.rebuiltPackageCount > 0) {
let alertClass =
(this.rebuiltPackageCount === this.incompatiblePackages.length)
this.rebuiltPackageCount === this.incompatiblePackages.length
? 'alert-success icon-check'
: 'alert-warning icon-bug'
return (
<div className={'alert icon ' + alertClass}>
{this.rebuiltPackageCount} of {this.incompatiblePackages.length} packages
were rebuilt successfully. Reload Atom to activate them.
{this.rebuiltPackageCount} of {this.incompatiblePackages.length}{' '}
packages were rebuilt successfully. Reload Atom to activate them.
<button ref='reloadButton' className='btn pull-right'>
Reload Atom
</button>
@ -72,10 +75,13 @@ export default class IncompatiblePackagesComponent {
} else {
return (
<div className='alert alert-danger icon icon-bug'>
Some installed packages could not be loaded because they contain native
modules that were compiled for an earlier version of Atom.
<button ref='rebuildButton' className='btn pull-right' disabled={this.rebuildInProgress}>
Some installed packages could not be loaded because they contain
native modules that were compiled for an earlier version of Atom.
<button
ref='rebuildButton'
className='btn pull-right'
disabled={this.rebuildInProgress}
>
Rebuild Packages
</button>
</div>
@ -92,9 +98,11 @@ export default class IncompatiblePackagesComponent {
renderIncompatiblePackageList () {
return (
<div>{
this.incompatiblePackages.map(this.renderIncompatiblePackage.bind(this))
}</div>
<div>
{this.incompatiblePackages.map(
this.renderIncompatiblePackage.bind(this)
)}
</div>
)
}
@ -104,15 +112,18 @@ export default class IncompatiblePackagesComponent {
return (
<div className={'incompatible-package'}>
{this.renderRebuildStatusIndicator(rebuildStatus)}
<button className='btn view-settings icon icon-gear pull-right' package={pack}>Package Settings</button>
<button
className='btn view-settings icon icon-gear pull-right'
package={pack}
>
Package Settings
</button>
<h4 className='heading'>
{pack.name} {pack.metadata.version}
</h4>
{
rebuildStatus
{rebuildStatus
? this.renderRebuildOutput(pack)
: this.renderIncompatibleModules(pack)
}
: this.renderIncompatibleModules(pack)}
</div>
)
}
@ -151,23 +162,23 @@ export default class IncompatiblePackagesComponent {
renderIncompatibleModules (pack) {
return (
<ul>{
pack.incompatibleModules.map((nativeModule) =>
<ul>
{pack.incompatibleModules.map(nativeModule => (
<li>
<div className='icon icon-file-binary'>
{nativeModule.name}@{nativeModule.version || 'unknown'} <span className='text-warning'>{nativeModule.error}</span>
{nativeModule.name}@{nativeModule.version || 'unknown'} {' '}
<span className='text-warning'>{nativeModule.error}</span>
</div>
</li>
)
}</ul>
))}
</ul>
)
}
populateIncompatiblePackages () {
this.incompatiblePackages =
this.packageManager
.getLoadedPackages()
.filter(pack => !pack.isCompatible())
this.incompatiblePackages = this.packageManager
.getLoadedPackages()
.filter(pack => !pack.isCompatible())
for (let pack of this.incompatiblePackages) {
let buildFailureOutput = pack.getBuildFailureOutput()
@ -186,7 +197,7 @@ export default class IncompatiblePackagesComponent {
let rebuiltPackageCount = 0
for (let pack of this.incompatiblePackages) {
this.setPackageStatus(pack, REBUILDING)
let {code, stderr} = await pack.rebuild()
let { code, stderr } = await pack.rebuild()
if (code === 0) {
this.setPackageStatus(pack, REBUILD_SUCCEEDED)
rebuiltPackageCount++
@ -223,6 +234,6 @@ export default class IncompatiblePackagesComponent {
}
serialize () {
return {deserializer: 'IncompatiblePackagesComponent'}
return { deserializer: 'IncompatiblePackagesComponent' }
}
}

View File

@ -1,6 +1,6 @@
/** @babel */
import {Disposable, CompositeDisposable} from 'atom'
import { Disposable, CompositeDisposable } from 'atom'
import VIEW_URI from './view-uri'
let disposables = null
@ -8,17 +8,21 @@ let disposables = null
export function activate () {
disposables = new CompositeDisposable()
disposables.add(atom.workspace.addOpener((uri) => {
if (uri === VIEW_URI) {
return deserializeIncompatiblePackagesComponent()
}
}))
disposables.add(
atom.workspace.addOpener(uri => {
if (uri === VIEW_URI) {
return deserializeIncompatiblePackagesComponent()
}
})
)
disposables.add(atom.commands.add('atom-workspace', {
'incompatible-packages:view': () => {
atom.workspace.open(VIEW_URI)
}
}))
disposables.add(
atom.commands.add('atom-workspace', {
'incompatible-packages:view': () => {
atom.workspace.open(VIEW_URI)
}
})
)
}
export function deactivate () {
@ -33,7 +37,7 @@ export function consumeStatusBar (statusBar) {
if (incompatibleCount > 0) {
let icon = createIcon(incompatibleCount)
let tile = statusBar.addRightTile({item: icon, priority: 200})
let tile = statusBar.addRightTile({ item: icon, priority: 200 })
icon.element.addEventListener('click', () => {
atom.commands.dispatch(icon.element, 'incompatible-packages:view')
})
@ -48,5 +52,5 @@ export function deserializeIncompatiblePackagesComponent () {
function createIcon (count) {
const StatusIconComponent = require('./status-icon-component')
return new StatusIconComponent({count})
return new StatusIconComponent({ count })
}

View File

@ -4,7 +4,7 @@
import etch from 'etch'
export default class StatusIconComponent {
constructor ({count}) {
constructor ({ count }) {
this.count = count
etch.initialize(this)
}
@ -14,7 +14,7 @@ export default class StatusIconComponent {
render () {
return (
<div className='incompatible-packages-status inline-block text text-error'>
<span className='icon icon-bug'></span>
<span className='icon icon-bug' />
<span className='incompatible-packages-count'>{this.count}</span>
</div>
)

View File

@ -16,7 +16,7 @@ describe('IncompatiblePackagesComponent', () => {
return false
},
rebuild: function () {
return new Promise((resolve) => this.resolveRebuild = resolve)
return new Promise(resolve => (this.resolveRebuild = resolve))
},
getBuildFailureOutput () {
return null
@ -27,8 +27,8 @@ describe('IncompatiblePackagesComponent', () => {
version: '1.0.0'
},
incompatibleModules: [
{name: 'x', version: '1.0.0', error: 'Expected version X, got Y'},
{name: 'y', version: '1.0.0', error: 'Expected version X, got Z'}
{ name: 'x', version: '1.0.0', error: 'Expected version X, got Y' },
{ name: 'y', version: '1.0.0', error: 'Expected version X, got Z' }
]
},
{
@ -37,7 +37,7 @@ describe('IncompatiblePackagesComponent', () => {
return false
},
rebuild () {
return new Promise((resolve) => this.resolveRebuild = resolve)
return new Promise(resolve => (this.resolveRebuild = resolve))
},
getBuildFailureOutput () {
return null
@ -48,7 +48,7 @@ describe('IncompatiblePackagesComponent', () => {
version: '1.0.0'
},
incompatibleModules: [
{name: 'z', version: '1.0.0', error: 'Expected version X, got Y'}
{ name: 'z', version: '1.0.0', error: 'Expected version X, got Y' }
]
},
{
@ -67,7 +67,7 @@ describe('IncompatiblePackagesComponent', () => {
repository: 'https://github.com/atom/b',
version: '1.0.0'
},
incompatibleModules: [],
incompatibleModules: []
}
]
})
@ -75,18 +75,21 @@ describe('IncompatiblePackagesComponent', () => {
describe('when packages have not finished loading', () => {
it('delays rendering incompatible packages until the end of the tick', () => {
waitsForPromise(async () => {
let component =
new IncompatiblePackagesComponent({
getActivePackages: () => [],
getLoadedPackages: () => packages
})
let {element} = component
let component = new IncompatiblePackagesComponent({
getActivePackages: () => [],
getLoadedPackages: () => packages
})
let { element } = component
expect(element.querySelectorAll('.incompatible-package').length).toEqual(0)
expect(
element.querySelectorAll('.incompatible-package').length
).toEqual(0)
await etchScheduler.getNextUpdatePromise()
expect(element.querySelectorAll('.incompatible-package').length).toBeGreaterThan(0)
expect(
element.querySelectorAll('.incompatible-package').length
).toBeGreaterThan(0)
})
})
})
@ -97,12 +100,11 @@ describe('IncompatiblePackagesComponent', () => {
expect(packages[2].isCompatible()).toBe(true)
let compatiblePackages = [packages[2]]
let component =
new IncompatiblePackagesComponent({
getActivePackages: () => compatiblePackages,
getLoadedPackages: () => compatiblePackages
})
let {element} = component
let component = new IncompatiblePackagesComponent({
getActivePackages: () => compatiblePackages,
getLoadedPackages: () => compatiblePackages
})
let { element } = component
await etchScheduler.getNextUpdatePromise()
@ -119,18 +121,23 @@ describe('IncompatiblePackagesComponent', () => {
return 'The build failed'
}
let component =
new IncompatiblePackagesComponent({
getActivePackages: () => packages,
getLoadedPackages: () => packages
})
let {element} = component
let component = new IncompatiblePackagesComponent({
getActivePackages: () => packages,
getLoadedPackages: () => packages
})
let { element } = component
await etchScheduler.getNextUpdatePromise()
let packageElement = element.querySelector('.incompatible-package:nth-child(2)')
let packageElement = element.querySelector(
'.incompatible-package:nth-child(2)'
)
expect(packageElement.querySelector('.badge').textContent).toBe('Rebuild Failed')
expect(packageElement.querySelector('pre').textContent).toBe('The build failed')
expect(packageElement.querySelector('.badge').textContent).toBe(
'Rebuild Failed'
)
expect(packageElement.querySelector('pre').textContent).toBe(
'The build failed'
)
})
})
})
@ -138,75 +145,101 @@ describe('IncompatiblePackagesComponent', () => {
describe('when there are incompatible packages', () => {
it('renders incompatible packages and the rebuild button', () => {
waitsForPromise(async () => {
let component =
new IncompatiblePackagesComponent({
getActivePackages: () => packages,
getLoadedPackages: () => packages
})
let {element} = component
let component = new IncompatiblePackagesComponent({
getActivePackages: () => packages,
getLoadedPackages: () => packages
})
let { element } = component
await etchScheduler.getNextUpdatePromise()
expect(element.querySelectorAll('.incompatible-package').length).toEqual(2)
expect(
element.querySelectorAll('.incompatible-package').length
).toEqual(2)
expect(element.querySelector('button')).not.toBeNull()
})
})
describe('when the "Rebuild All" button is clicked', () => {
it('rebuilds every incompatible package, updating each package\'s view with status', () => {
it("rebuilds every incompatible package, updating each package's view with status", () => {
waitsForPromise(async () => {
let component =
new IncompatiblePackagesComponent({
getActivePackages: () => packages,
getLoadedPackages: () => packages
})
let {element} = component
let component = new IncompatiblePackagesComponent({
getActivePackages: () => packages,
getLoadedPackages: () => packages
})
let { element } = component
jasmine.attachToDOM(element)
await etchScheduler.getNextUpdatePromise()
component.refs.rebuildButton.dispatchEvent(new CustomEvent('click', {bubbles: true}))
component.refs.rebuildButton.dispatchEvent(
new CustomEvent('click', { bubbles: true })
)
await etchScheduler.getNextUpdatePromise() // view update
expect(component.refs.rebuildButton.disabled).toBe(true)
expect(packages[0].resolveRebuild).toBeDefined()
expect(element.querySelector('.incompatible-package:nth-child(1) .badge').textContent).toBe('Rebuilding')
expect(element.querySelector('.incompatible-package:nth-child(2) .badge')).toBeNull()
expect(
element.querySelector('.incompatible-package:nth-child(1) .badge')
.textContent
).toBe('Rebuilding')
expect(
element.querySelector('.incompatible-package:nth-child(2) .badge')
).toBeNull()
packages[0].resolveRebuild({code: 0}) // simulate rebuild success
packages[0].resolveRebuild({ code: 0 }) // simulate rebuild success
await etchScheduler.getNextUpdatePromise() // view update
expect(packages[1].resolveRebuild).toBeDefined()
expect(element.querySelector('.incompatible-package:nth-child(1) .badge').textContent).toBe('Rebuild Succeeded')
expect(element.querySelector('.incompatible-package:nth-child(2) .badge').textContent).toBe('Rebuilding')
expect(
element.querySelector('.incompatible-package:nth-child(1) .badge')
.textContent
).toBe('Rebuild Succeeded')
expect(
element.querySelector('.incompatible-package:nth-child(2) .badge')
.textContent
).toBe('Rebuilding')
packages[1].resolveRebuild({code: 12, stderr: 'This is an error from the test!'}) // simulate rebuild failure
packages[1].resolveRebuild({
code: 12,
stderr: 'This is an error from the test!'
}) // simulate rebuild failure
await etchScheduler.getNextUpdatePromise() // view update
expect(element.querySelector('.incompatible-package:nth-child(1) .badge').textContent).toBe('Rebuild Succeeded')
expect(element.querySelector('.incompatible-package:nth-child(2) .badge').textContent).toBe('Rebuild Failed')
expect(element.querySelector('.incompatible-package:nth-child(2) pre').textContent).toBe('This is an error from the test!')
expect(
element.querySelector('.incompatible-package:nth-child(1) .badge')
.textContent
).toBe('Rebuild Succeeded')
expect(
element.querySelector('.incompatible-package:nth-child(2) .badge')
.textContent
).toBe('Rebuild Failed')
expect(
element.querySelector('.incompatible-package:nth-child(2) pre')
.textContent
).toBe('This is an error from the test!')
})
})
it('displays a prompt to reload Atom when the packages finish rebuilding', () => {
waitsForPromise(async () => {
let component =
new IncompatiblePackagesComponent({
getActivePackages: () => packages,
getLoadedPackages: () => packages
})
let {element} = component
let component = new IncompatiblePackagesComponent({
getActivePackages: () => packages,
getLoadedPackages: () => packages
})
let { element } = component
jasmine.attachToDOM(element)
await etchScheduler.getNextUpdatePromise() // view update
component.refs.rebuildButton.dispatchEvent(new CustomEvent('click', {bubbles: true}))
expect(packages[0].resolveRebuild({code: 0}))
component.refs.rebuildButton.dispatchEvent(
new CustomEvent('click', { bubbles: true })
)
expect(packages[0].resolveRebuild({ code: 0 }))
await new Promise(global.setImmediate)
expect(packages[1].resolveRebuild({code: 0}))
expect(packages[1].resolveRebuild({ code: 0 }))
await etchScheduler.getNextUpdatePromise() // view update
@ -214,7 +247,9 @@ describe('IncompatiblePackagesComponent', () => {
expect(element.querySelector('.alert').textContent).toMatch(/2 of 2/)
spyOn(atom, 'reload')
component.refs.reloadButton.dispatchEvent(new CustomEvent('click', {bubbles: true}))
component.refs.reloadButton.dispatchEvent(
new CustomEvent('click', { bubbles: true })
)
expect(atom.reload).toHaveBeenCalled()
})
})
@ -223,19 +258,22 @@ describe('IncompatiblePackagesComponent', () => {
describe('when the "Package Settings" button is clicked', () => {
it('opens the settings panel for the package', () => {
waitsForPromise(async () => {
let component =
new IncompatiblePackagesComponent({
getActivePackages: () => packages,
getLoadedPackages: () => packages
})
let {element} = component
let component = new IncompatiblePackagesComponent({
getActivePackages: () => packages,
getLoadedPackages: () => packages
})
let { element } = component
jasmine.attachToDOM(element)
await etchScheduler.getNextUpdatePromise()
spyOn(atom.workspace, 'open')
element.querySelector('.incompatible-package:nth-child(2) button').dispatchEvent(new CustomEvent('click', {bubbles: true}))
expect(atom.workspace.open).toHaveBeenCalledWith('atom://config/packages/incompatible-2')
element
.querySelector('.incompatible-package:nth-child(2) button')
.dispatchEvent(new CustomEvent('click', { bubbles: true }))
expect(atom.workspace.open).toHaveBeenCalledWith(
'atom://config/packages/incompatible-2'
)
})
})
})

View File

@ -36,7 +36,9 @@ describe('Incompatible packages', () => {
)
spyOn(incompatiblePackage, 'isCompatible').andReturn(false)
incompatiblePackage.incompatibleModules = []
waitsForPromise(() => atom.packages.activatePackage("incompatible-packages"))
waitsForPromise(() =>
atom.packages.activatePackage('incompatible-packages')
)
waits(1)
})
@ -63,13 +65,15 @@ describe('Incompatible packages', () => {
describe('when there are no packages with incompatible native modules', () => {
beforeEach(() => {
waitsForPromise(() => atom.packages.activatePackage("incompatible-packages"))
waitsForPromise(() =>
atom.packages.activatePackage('incompatible-packages')
)
})
it('does not add an icon to the status bar', () => {
let statusBarItemClasses = statusBar
.getRightTiles()
.map((tile) => tile.getItem().className)
.map(tile => tile.getItem().className)
expect(statusBarItemClasses).not.toContain('incompatible-packages')
})

View File

@ -10,7 +10,7 @@
"repository": "https://github.com/atom/atom",
"license": "MIT",
"dependencies": {
"tree-sitter-rust": "^0.13.5"
"tree-sitter-rust": "^0.13.7"
},
"engines": {
"atom": ">=1.0.0 <2.0.0"

View File

@ -1,12 +1,17 @@
'use babel'
import _ from 'underscore-plus'
import {CompositeDisposable, Disposable} from 'atom'
import { CompositeDisposable, Disposable } from 'atom'
import SelectListView from 'atom-select-list'
import StatusBarItem from './status-bar-item'
import helpers from './helpers'
const LineEndingRegExp = /\r\n|\n/g
// the following regular expression is executed natively via the `substring` package,
// where `\A` corresponds to the beginning of the string.
// More info: https://github.com/atom/line-ending-selector/pull/56
// eslint-disable-next-line no-useless-escape
const LFRegExp = /(\A|[^\r])\n/g
const CRLFRegExp = /\r\n/g
@ -17,48 +22,60 @@ let lineEndingListView = null
export function activate () {
disposables = new CompositeDisposable()
disposables.add(atom.commands.add('atom-text-editor', {
'line-ending-selector:show': (event) => {
if (!modalPanel) {
lineEndingListView = new SelectListView({
items: [{name: 'LF', value: '\n'}, {name: 'CRLF', value: '\r\n'}],
filterKeyForItem: (lineEnding) => lineEnding.name,
didConfirmSelection: (lineEnding) => {
setLineEnding(atom.workspace.getActiveTextEditor(), lineEnding.value)
modalPanel.hide()
},
didCancelSelection: () => {
modalPanel.hide()
},
elementForItem: (lineEnding) => {
const element = document.createElement('li')
element.textContent = lineEnding.name
return element
}
})
modalPanel = atom.workspace.addModalPanel({item: lineEndingListView})
disposables.add(new Disposable(() => {
lineEndingListView.destroy()
modalPanel.destroy()
modalPanel = null
}))
disposables.add(
atom.commands.add('atom-text-editor', {
'line-ending-selector:show': event => {
if (!modalPanel) {
lineEndingListView = new SelectListView({
items: [
{ name: 'LF', value: '\n' },
{ name: 'CRLF', value: '\r\n' }
],
filterKeyForItem: lineEnding => lineEnding.name,
didConfirmSelection: lineEnding => {
setLineEnding(
atom.workspace.getActiveTextEditor(),
lineEnding.value
)
modalPanel.hide()
},
didCancelSelection: () => {
modalPanel.hide()
},
elementForItem: lineEnding => {
const element = document.createElement('li')
element.textContent = lineEnding.name
return element
}
})
modalPanel = atom.workspace.addModalPanel({
item: lineEndingListView
})
disposables.add(
new Disposable(() => {
lineEndingListView.destroy()
modalPanel.destroy()
modalPanel = null
})
)
}
lineEndingListView.reset()
modalPanel.show()
lineEndingListView.focus()
},
'line-ending-selector:convert-to-LF': event => {
const editorElement = event.target.closest('atom-text-editor')
setLineEnding(editorElement.getModel(), '\n')
},
'line-ending-selector:convert-to-CRLF': event => {
const editorElement = event.target.closest('atom-text-editor')
setLineEnding(editorElement.getModel(), '\r\n')
}
lineEndingListView.reset()
modalPanel.show()
lineEndingListView.focus()
},
'line-ending-selector:convert-to-LF': (event) => {
const editorElement = event.target.closest('atom-text-editor')
setLineEnding(editorElement.getModel(), '\n')
},
'line-ending-selector:convert-to-CRLF': (event) => {
const editorElement = event.target.closest('atom-text-editor')
setLineEnding(editorElement.getModel(), '\r\n')
}
}))
})
)
}
export function deactivate () {
@ -70,8 +87,8 @@ export function consumeStatusBar (statusBar) {
let currentBufferDisposable = null
let tooltipDisposable = null
const updateTile = _.debounce((buffer) => {
getLineEndings(buffer).then((lineEndings) => {
const updateTile = _.debounce(buffer => {
getLineEndings(buffer).then(lineEndings => {
if (lineEndings.size === 0) {
let defaultLineEnding = getDefaultLineEnding()
buffer.setPreferredLineEnding(defaultLineEnding)
@ -81,45 +98,49 @@ export function consumeStatusBar (statusBar) {
})
}, 0)
disposables.add(atom.workspace.observeActiveTextEditor((editor) => {
if (currentBufferDisposable) currentBufferDisposable.dispose()
disposables.add(
atom.workspace.observeActiveTextEditor(editor => {
if (currentBufferDisposable) currentBufferDisposable.dispose()
if (editor && editor.getBuffer) {
let buffer = editor.getBuffer()
updateTile(buffer)
currentBufferDisposable = buffer.onDidChange(({oldText, newText}) => {
if (!statusBarItem.hasLineEnding('\n')) {
if (newText.indexOf('\n') >= 0) {
if (editor && editor.getBuffer) {
let buffer = editor.getBuffer()
updateTile(buffer)
currentBufferDisposable = buffer.onDidChange(({ oldText, newText }) => {
if (!statusBarItem.hasLineEnding('\n')) {
if (newText.indexOf('\n') >= 0) {
updateTile(buffer)
}
} else if (!statusBarItem.hasLineEnding('\r\n')) {
if (newText.indexOf('\r\n') >= 0) {
updateTile(buffer)
}
} else if (oldText.indexOf('\n')) {
updateTile(buffer)
}
} else if (!statusBarItem.hasLineEnding('\r\n')) {
if (newText.indexOf('\r\n') >= 0) {
updateTile(buffer)
}
} else if (oldText.indexOf('\n')) {
updateTile(buffer)
})
} else {
statusBarItem.setLineEndings(new Set())
currentBufferDisposable = null
}
if (tooltipDisposable) {
disposables.remove(tooltipDisposable)
tooltipDisposable.dispose()
}
tooltipDisposable = atom.tooltips.add(statusBarItem.element, {
title () {
return `File uses ${statusBarItem.description()} line endings`
}
})
} else {
statusBarItem.setLineEndings(new Set())
currentBufferDisposable = null
}
if (tooltipDisposable) {
disposables.remove(tooltipDisposable)
tooltipDisposable.dispose()
}
tooltipDisposable = atom.tooltips.add(statusBarItem.element, {
title () {
return `File uses ${statusBarItem.description()} line endings`
}
disposables.add(tooltipDisposable)
})
disposables.add(tooltipDisposable)
}))
)
disposables.add(new Disposable(() => {
if (currentBufferDisposable) currentBufferDisposable.dispose()
}))
disposables.add(
new Disposable(() => {
if (currentBufferDisposable) currentBufferDisposable.dispose()
})
)
statusBarItem.onClick(() => {
const editor = atom.workspace.getActiveTextEditor()
@ -129,7 +150,7 @@ export function consumeStatusBar (statusBar) {
)
})
let tile = statusBar.addRightTile({item: statusBarItem, priority: 200})
let tile = statusBar.addRightTile({ item: statusBarItem, priority: 200 })
disposables.add(new Disposable(() => tile.destroy()))
}
@ -141,23 +162,22 @@ function getDefaultLineEnding () {
return '\r\n'
case 'OS Default':
default:
return (helpers.getProcessPlatform() === 'win32') ? '\r\n' : '\n'
return helpers.getProcessPlatform() === 'win32' ? '\r\n' : '\n'
}
}
function getLineEndings (buffer) {
if (typeof buffer.find === 'function') {
return Promise.all([
buffer.find(LFRegExp),
buffer.find(CRLFRegExp)
]).then(([hasLF, hasCRLF]) => {
const result = new Set()
if (hasLF) result.add('\n')
if (hasCRLF) result.add('\r\n')
return result
})
return Promise.all([buffer.find(LFRegExp), buffer.find(CRLFRegExp)]).then(
([hasLF, hasCRLF]) => {
const result = new Set()
if (hasLF) result.add('\n')
if (hasCRLF) result.add('\r\n')
return result
}
)
} else {
return new Promise((resolve) => {
return new Promise(resolve => {
const result = new Set()
for (let i = 0; i < buffer.getLineCount() - 1; i++) {
result.add(buffer.lineEndingForRow(i))

View File

@ -1,7 +1,6 @@
const {Emitter} = require('atom')
const { Emitter } = require('atom')
module.exports =
class StatusBarItem {
module.exports = class StatusBarItem {
constructor () {
this.element = document.createElement('a')
this.element.className = 'line-ending-tile inline-block'
@ -46,9 +45,13 @@ function lineEndingName (lineEndings) {
function lineEndingDescription (lineEndings) {
switch (lineEndingName(lineEndings)) {
case 'Mixed': return 'mixed'
case 'LF': return 'LF (Unix)'
case 'CRLF': return 'CRLF (Windows)'
default: return 'unknown'
case 'Mixed':
return 'mixed'
case 'LF':
return 'LF (Unix)'
case 'CRLF':
return 'CRLF (Windows)'
default:
return 'unknown'
}
}

View File

@ -1,5 +1,5 @@
const helpers = require('../lib/helpers')
const {TextEditor} = require('atom')
const { TextEditor } = require('atom')
describe('line ending selector', () => {
let lineEndingTile
@ -30,7 +30,7 @@ describe('line ending selector', () => {
beforeEach(() => {
waitsForPromise(() => {
return atom.workspace.open('mixed-endings.md').then((e) => {
return atom.workspace.open('mixed-endings.md').then(e => {
editor = e
editorElement = atom.views.getView(editor)
jasmine.attachToDOM(editorElement)
@ -41,7 +41,10 @@ describe('line ending selector', () => {
describe('When "line-ending-selector:convert-to-LF" is run', () => {
it('converts the file to LF line endings', () => {
editorElement.focus()
atom.commands.dispatch(document.activeElement, 'line-ending-selector:convert-to-LF')
atom.commands.dispatch(
document.activeElement,
'line-ending-selector:convert-to-LF'
)
expect(editor.getText()).toBe('Hello\nGoodbye\nMixed\n')
})
})
@ -49,7 +52,10 @@ describe('line ending selector', () => {
describe('When "line-ending-selector:convert-to-LF" is run', () => {
it('converts the file to CRLF line endings', () => {
editorElement.focus()
atom.commands.dispatch(document.activeElement, 'line-ending-selector:convert-to-CRLF')
atom.commands.dispatch(
document.activeElement,
'line-ending-selector:convert-to-CRLF'
)
expect(editor.getText()).toBe('Hello\r\nGoodbye\r\nMixed\r\n')
})
})
@ -58,30 +64,34 @@ describe('line ending selector', () => {
describe('Status bar tile', () => {
describe('when an empty file is opened', () => {
it('uses the default line endings for the platform', () => {
waitsFor((done) => {
waitsFor(done => {
spyOn(helpers, 'getProcessPlatform').andReturn('win32')
atom.workspace.open('').then((editor) => {
atom.workspace.open('').then(editor => {
const subscription = lineEndingTile.onDidChange(() => {
subscription.dispose()
expect(lineEndingTile.element.textContent).toBe('CRLF')
expect(editor.getBuffer().getPreferredLineEnding()).toBe('\r\n')
expect(getTooltipText(lineEndingTile.element)).toBe('File uses CRLF (Windows) line endings')
expect(getTooltipText(lineEndingTile.element)).toBe(
'File uses CRLF (Windows) line endings'
)
done()
})
})
})
waitsFor((done) => {
waitsFor(done => {
helpers.getProcessPlatform.andReturn('darwin')
atom.workspace.open('').then((editor) => {
atom.workspace.open('').then(editor => {
const subscription = lineEndingTile.onDidChange(() => {
subscription.dispose()
expect(lineEndingTile.element.textContent).toBe('LF')
expect(editor.getBuffer().getPreferredLineEnding()).toBe('\n')
expect(getTooltipText(lineEndingTile.element)).toBe('File uses LF (Unix) line endings')
expect(getTooltipText(lineEndingTile.element)).toBe(
'File uses LF (Unix) line endings'
)
done()
})
@ -95,10 +105,10 @@ describe('line ending selector', () => {
})
it('uses LF line endings, regardless of the platform', () => {
waitsFor((done) => {
waitsFor(done => {
spyOn(helpers, 'getProcessPlatform').andReturn('win32')
atom.workspace.open('').then((editor) => {
atom.workspace.open('').then(editor => {
lineEndingTile.onDidChange(() => {
expect(lineEndingTile.element.textContent).toBe('LF')
expect(editor.getBuffer().getPreferredLineEnding()).toBe('\n')
@ -115,8 +125,8 @@ describe('line ending selector', () => {
})
it('uses CRLF line endings, regardless of the platform', () => {
waitsFor((done) => {
atom.workspace.open('').then((editor) => {
waitsFor(done => {
atom.workspace.open('').then(editor => {
lineEndingTile.onDidChange(() => {
expect(lineEndingTile.element.textContent).toBe('CRLF')
expect(editor.getBuffer().getPreferredLineEnding()).toBe('\r\n')
@ -130,7 +140,7 @@ describe('line ending selector', () => {
describe('when a file is opened that contains only CRLF line endings', () => {
it('displays "CRLF" as the line ending', () => {
waitsFor((done) => {
waitsFor(done => {
atom.workspace.open('windows-endings.md').then(() => {
lineEndingTile.onDidChange(() => {
expect(lineEndingTile.element.textContent).toBe('CRLF')
@ -143,8 +153,8 @@ describe('line ending selector', () => {
describe('when a file is opened that contains only LF line endings', () => {
it('displays "LF" as the line ending', () => {
waitsFor((done) => {
atom.workspace.open('unix-endings.md').then((editor) => {
waitsFor(done => {
atom.workspace.open('unix-endings.md').then(editor => {
lineEndingTile.onDidChange(() => {
expect(lineEndingTile.element.textContent).toBe('LF')
expect(editor.getBuffer().getPreferredLineEnding()).toBe(null)
@ -157,7 +167,7 @@ describe('line ending selector', () => {
describe('when a file is opened that contains mixed line endings', () => {
it('displays "Mixed" as the line ending', () => {
waitsFor((done) => {
waitsFor(done => {
atom.workspace.open('mixed-endings.md').then(() => {
lineEndingTile.onDidChange(() => {
expect(lineEndingTile.element.textContent).toBe('Mixed')
@ -174,10 +184,10 @@ describe('line ending selector', () => {
beforeEach(() => {
jasmine.attachToDOM(atom.views.getView(atom.workspace))
waitsFor((done) =>
atom.workspace.open('unix-endings.md').then(() =>
lineEndingTile.onDidChange(done)
)
waitsFor(done =>
atom.workspace
.open('unix-endings.md')
.then(() => lineEndingTile.onDidChange(done))
)
})
@ -192,7 +202,9 @@ describe('line ending selector', () => {
lineEndingSelector = lineEndingModal.getItem()
expect(lineEndingModal.isVisible()).toBe(true)
expect(lineEndingSelector.element.contains(document.activeElement)).toBe(true)
expect(
lineEndingSelector.element.contains(document.activeElement)
).toBe(true)
let listItems = lineEndingSelector.element.querySelectorAll('li')
expect(listItems[0].textContent).toBe('LF')
expect(listItems[1].textContent).toBe('CRLF')
@ -210,7 +222,9 @@ describe('line ending selector', () => {
lineEndingSelector = lineEndingModal.getItem()
expect(lineEndingModal.isVisible()).toBe(true)
expect(lineEndingSelector.element.contains(document.activeElement)).toBe(true)
expect(
lineEndingSelector.element.contains(document.activeElement)
).toBe(true)
let listItems = lineEndingSelector.element.querySelectorAll('li')
expect(listItems[0].textContent).toBe('LF')
expect(listItems[1].textContent).toBe('CRLF')
@ -264,12 +278,12 @@ describe('line ending selector', () => {
})
})
describe('when the buffer\'s line endings change', () => {
describe("when the buffer's line endings change", () => {
let editor
beforeEach(() => {
waitsFor((done) => {
atom.workspace.open('unix-endings.md').then((e) => {
waitsFor(done => {
atom.workspace.open('unix-endings.md').then(e => {
editor = e
lineEndingTile.onDidChange(done)
})
@ -291,33 +305,47 @@ describe('line ending selector', () => {
})
expect(lineEndingTile.element.textContent).toBe('LF')
expect(getTooltipText(lineEndingTile.element)).toBe('File uses LF (Unix) line endings')
expect(getTooltipText(lineEndingTile.element)).toBe(
'File uses LF (Unix) line endings'
)
waitsFor((done) => {
waitsFor(done => {
editor.setTextInBufferRange([[0, 0], [0, 0]], '... ')
editor.setTextInBufferRange([[0, Infinity], [1, 0]], '\r\n', {normalizeLineEndings: false})
editor.setTextInBufferRange([[0, Infinity], [1, 0]], '\r\n', {
normalizeLineEndings: false
})
lineEndingTile.onDidChange(done)
})
runs(() => {
expect(tileUpdateCount).toBe(1)
expect(lineEndingTile.element.textContent).toBe('Mixed')
expect(getTooltipText(lineEndingTile.element)).toBe('File uses mixed line endings')
expect(getTooltipText(lineEndingTile.element)).toBe(
'File uses mixed line endings'
)
})
waitsFor((done) => {
atom.commands.dispatch(editor.getElement(), 'line-ending-selector:convert-to-CRLF')
waitsFor(done => {
atom.commands.dispatch(
editor.getElement(),
'line-ending-selector:convert-to-CRLF'
)
lineEndingTile.onDidChange(done)
})
runs(() => {
expect(tileUpdateCount).toBe(2)
expect(lineEndingTile.element.textContent).toBe('CRLF')
expect(getTooltipText(lineEndingTile.element)).toBe('File uses CRLF (Windows) line endings')
expect(getTooltipText(lineEndingTile.element)).toBe(
'File uses CRLF (Windows) line endings'
)
})
waitsFor((done) => {
atom.commands.dispatch(editor.getElement(), 'line-ending-selector:convert-to-LF')
waitsFor(done => {
atom.commands.dispatch(
editor.getElement(),
'line-ending-selector:convert-to-LF'
)
lineEndingTile.onDidChange(done)
})

View File

@ -1,12 +1,16 @@
const url = require('url')
const {shell} = require('electron')
const { shell } = require('electron')
const _ = require('underscore-plus')
const LINK_SCOPE_REGEX = /markup\.underline\.link/
module.exports = {
activate () {
this.commandDisposable = atom.commands.add('atom-text-editor', 'link:open', () => this.openLink())
this.commandDisposable = atom.commands.add(
'atom-text-editor',
'link:open',
() => this.openLink()
)
},
deactivate () {
@ -24,8 +28,10 @@ module.exports = {
link = this.linkForName(editor, link)
}
const {protocol} = url.parse(link)
if (protocol === 'http:' || protocol === 'https:' || protocol === 'atom:') shell.openExternal(link)
const { protocol } = url.parse(link)
if (protocol === 'http:' || protocol === 'https:' || protocol === 'atom:') {
shell.openExternal(link)
}
},
// Get the link under the cursor in the editor
@ -47,7 +53,11 @@ module.exports = {
// Returns a {String} link or undefined if no link found.
linkAtPosition (editor, bufferPosition) {
const token = editor.tokenForBufferPosition(bufferPosition)
if (token && token.value && token.scopes.some(scope => LINK_SCOPE_REGEX.test(scope))) {
if (
token &&
token.value &&
token.scopes.some(scope => LINK_SCOPE_REGEX.test(scope))
) {
return token.value
}
},
@ -65,11 +75,18 @@ module.exports = {
// Returns a {String} link
linkForName (editor, linkName) {
let link = linkName
const regex = new RegExp(`^\\s*\\[${_.escapeRegExp(linkName)}\\]\\s*:\\s*(.+)$`, 'g')
editor.backwardsScanInBufferRange(regex, [[0, 0], [Infinity, Infinity]], ({match, stop}) => {
link = match[1]
stop()
})
const regex = new RegExp(
`^\\s*\\[${_.escapeRegExp(linkName)}\\]\\s*:\\s*(.+)$`,
'g'
)
editor.backwardsScanInBufferRange(
regex,
[[0, 0], [Infinity, Infinity]],
({ match, stop }) => {
link = match[1]
stop()
}
)
return link
}
}

View File

@ -1,103 +0,0 @@
/** @babel */
export function beforeEach (fn) {
global.beforeEach(function () {
const result = fn()
if (result instanceof Promise) {
waitsForPromise(() => result)
}
})
}
export function afterEach (fn) {
global.afterEach(function () {
const result = fn()
if (result instanceof Promise) {
waitsForPromise(() => result)
}
})
}
['it', 'fit', 'ffit', 'fffit'].forEach(function (name) {
module.exports[name] = function (description, fn) {
if (fn === undefined) {
global[name](description)
return
}
global[name](description, function () {
const result = fn()
if (result instanceof Promise) {
waitsForPromise(() => result)
}
})
}
})
export async function conditionPromise (condition, description = 'anonymous condition') {
const startTime = Date.now()
while (true) {
await timeoutPromise(100)
if (await condition()) {
return
}
if (Date.now() - startTime > 5000) {
throw new Error('Timed out waiting on ' + description)
}
}
}
export function timeoutPromise (timeout) {
return new Promise(function (resolve) {
global.setTimeout(resolve, timeout)
})
}
function waitsForPromise (fn) {
const promise = fn()
global.waitsFor('spec promise to resolve', function (done) {
promise.then(done, function (error) {
jasmine.getEnv().currentSpec.fail(error)
done()
})
})
}
export function emitterEventPromise (emitter, event, timeout = 15000) {
return new Promise((resolve, reject) => {
const timeoutHandle = setTimeout(() => {
reject(new Error(`Timed out waiting for '${event}' event`))
}, timeout)
emitter.once(event, () => {
clearTimeout(timeoutHandle)
resolve()
})
})
}
export function promisify (original) {
return function (...args) {
return new Promise((resolve, reject) => {
args.push((err, ...results) => {
if (err) {
reject(err)
} else {
resolve(...results)
}
})
return original(...args)
})
}
}
export function promisifySome (obj, fnNames) {
const result = {}
for (const fnName of fnNames) {
result[fnName] = promisify(obj[fnName])
}
return result
}

View File

@ -1,6 +1,4 @@
const {shell} = require('electron')
const {it, fit, ffit, afterEach, beforeEach} = require('./async-spec-helpers') // eslint-disable-line no-unused-vars
const { shell } = require('electron')
describe('link package', () => {
beforeEach(async () => {
@ -47,13 +45,15 @@ describe('link package', () => {
// only works in Atom >= 1.33.0
// https://github.com/atom/link/pull/33#issuecomment-419643655
const atomVersion = atom.getVersion().split('.')
console.error("atomVersion", atomVersion)
console.error('atomVersion', atomVersion)
if (+atomVersion[0] > 1 || +atomVersion[1] >= 33) {
it("opens an 'atom:' link", async () => {
await atom.workspace.open('sample.md')
const editor = atom.workspace.getActiveTextEditor()
editor.setText('// "atom://core/open/file?filename=sample.js&line=1&column=2"')
editor.setText(
'// "atom://core/open/file?filename=sample.js&line=1&column=2"'
)
spyOn(shell, 'openExternal')
atom.commands.dispatch(atom.views.getView(editor), 'link:open')
@ -63,21 +63,27 @@ describe('link package', () => {
atom.commands.dispatch(atom.views.getView(editor), 'link:open')
expect(shell.openExternal).toHaveBeenCalled()
expect(shell.openExternal.argsForCall[0][0]).toBe('atom://core/open/file?filename=sample.js&line=1&column=2')
expect(shell.openExternal.argsForCall[0][0]).toBe(
'atom://core/open/file?filename=sample.js&line=1&column=2'
)
shell.openExternal.reset()
editor.setCursorBufferPosition([0, 8])
atom.commands.dispatch(atom.views.getView(editor), 'link:open')
expect(shell.openExternal).toHaveBeenCalled()
expect(shell.openExternal.argsForCall[0][0]).toBe('atom://core/open/file?filename=sample.js&line=1&column=2')
expect(shell.openExternal.argsForCall[0][0]).toBe(
'atom://core/open/file?filename=sample.js&line=1&column=2'
)
shell.openExternal.reset()
editor.setCursorBufferPosition([0, 60])
atom.commands.dispatch(atom.views.getView(editor), 'link:open')
expect(shell.openExternal).toHaveBeenCalled()
expect(shell.openExternal.argsForCall[0][0]).toBe('atom://core/open/file?filename=sample.js&line=1&column=2')
expect(shell.openExternal.argsForCall[0][0]).toBe(
'atom://core/open/file?filename=sample.js&line=1&column=2'
)
})
}
@ -91,8 +97,7 @@ you should [click][here]
you should not [click][her]
[here]: http://github.com\
`
)
`)
spyOn(shell, 'openExternal')
editor.setCursorBufferPosition([0, 0])
@ -110,8 +115,7 @@ you should not [click][her]
atom.commands.dispatch(atom.views.getView(editor), 'link:open')
expect(shell.openExternal).not.toHaveBeenCalled()
})
)
}))
it('does not open non http/https/atom links', async () => {
await atom.workspace.open('sample.md')

View File

@ -14,31 +14,45 @@ describe(`${themeName} theme`, () => {
it('allows the tab sizing to be set via config', () => {
atom.config.set(`${themeName}.tabSizing`, 'Maximum')
expect(document.documentElement.getAttribute(`theme-${themeName}-tabsizing`)).toBe('maximum')
expect(
document.documentElement.getAttribute(`theme-${themeName}-tabsizing`)
).toBe('maximum')
})
it('allows the tab sizing to be set via config', () => {
atom.config.set(`${themeName}.tabSizing`, 'Minimum')
expect(document.documentElement.getAttribute(`theme-${themeName}-tabsizing`)).toBe('minimum')
expect(
document.documentElement.getAttribute(`theme-${themeName}-tabsizing`)
).toBe('minimum')
})
it('allows the tab close button to be shown on the left via config', () => {
atom.config.set(`${themeName}.tabCloseButton`, 'Left')
expect(document.documentElement.getAttribute(`theme-${themeName}-tab-close-button`)).toBe('left')
expect(
document.documentElement.getAttribute(
`theme-${themeName}-tab-close-button`
)
).toBe('left')
})
it('allows the dock toggle buttons to be hidden via config', () => {
atom.config.set(`${themeName}.hideDockButtons`, true)
expect(document.documentElement.getAttribute(`theme-${themeName}-dock-buttons`)).toBe('hidden')
expect(
document.documentElement.getAttribute(`theme-${themeName}-dock-buttons`)
).toBe('hidden')
})
it('allows the tree-view headers to be sticky via config', () => {
atom.config.set(`${themeName}.stickyHeaders`, true)
expect(document.documentElement.getAttribute(`theme-${themeName}-sticky-headers`)).toBe('sticky')
expect(
document.documentElement.getAttribute(`theme-${themeName}-sticky-headers`)
).toBe('sticky')
})
it('allows the tree-view headers to not be sticky via config', () => {
atom.config.set(`${themeName}.stickyHeaders`, false)
expect(document.documentElement.getAttribute(`theme-${themeName}-sticky-headers`)).toBe(null)
expect(
document.documentElement.getAttribute(`theme-${themeName}-sticky-headers`)
).toBe(null)
})
})

View File

@ -55,9 +55,6 @@
.section-container {
max-width: @ui-size*60;
}
.sub-section {
margin: @ui-padding*3 0;
}
.section,
.section:first-child,
@ -85,6 +82,10 @@
// Cards ------------------------------
.sub-section:not(.collapsed) .package-container {
padding-bottom: @component-padding*3;
}
.package-card {
padding: @ui-padding;
.meta-controls .status-indicator {

View File

@ -14,31 +14,45 @@ describe(`${themeName} theme`, () => {
it('allows the tab sizing to be set via config', () => {
atom.config.set(`${themeName}.tabSizing`, 'Maximum')
expect(document.documentElement.getAttribute(`theme-${themeName}-tabsizing`)).toBe('maximum')
expect(
document.documentElement.getAttribute(`theme-${themeName}-tabsizing`)
).toBe('maximum')
})
it('allows the tab sizing to be set via config', () => {
atom.config.set(`${themeName}.tabSizing`, 'Minimum')
expect(document.documentElement.getAttribute(`theme-${themeName}-tabsizing`)).toBe('minimum')
expect(
document.documentElement.getAttribute(`theme-${themeName}-tabsizing`)
).toBe('minimum')
})
it('allows the tab close button to be shown on the left via config', () => {
atom.config.set(`${themeName}.tabCloseButton`, 'Left')
expect(document.documentElement.getAttribute(`theme-${themeName}-tab-close-button`)).toBe('left')
expect(
document.documentElement.getAttribute(
`theme-${themeName}-tab-close-button`
)
).toBe('left')
})
it('allows the dock toggle buttons to be hidden via config', () => {
atom.config.set(`${themeName}.hideDockButtons`, true)
expect(document.documentElement.getAttribute(`theme-${themeName}-dock-buttons`)).toBe('hidden')
expect(
document.documentElement.getAttribute(`theme-${themeName}-dock-buttons`)
).toBe('hidden')
})
it('allows the tree-view headers to be sticky via config', () => {
atom.config.set(`${themeName}.stickyHeaders`, true)
expect(document.documentElement.getAttribute(`theme-${themeName}-sticky-headers`)).toBe('sticky')
expect(
document.documentElement.getAttribute(`theme-${themeName}-sticky-headers`)
).toBe('sticky')
})
it('allows the tree-view headers to not be sticky via config', () => {
atom.config.set(`${themeName}.stickyHeaders`, false)
expect(document.documentElement.getAttribute(`theme-${themeName}-sticky-headers`)).toBe(null)
expect(
document.documentElement.getAttribute(`theme-${themeName}-sticky-headers`)
).toBe(null)
})
})

View File

@ -55,9 +55,6 @@
.section-container {
max-width: @ui-size*60;
}
.sub-section {
margin: @ui-padding*3 0;
}
.section,
.section:first-child,
@ -85,6 +82,10 @@
// Cards ------------------------------
.sub-section:not(.collapsed) .package-container {
padding-bottom: @component-padding*3;
}
.package-card {
padding: @ui-padding;
.meta-controls .status-indicator {

View File

@ -2,7 +2,7 @@
Name=<%= appName %>
Comment=<%= description %>
GenericName=Text Editor
Exec=<%= installDir %>/bin/<%= appFileName %> %F
Exec=env ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT=false <%= installDir %>/bin/<%= appFileName %> %F
Icon=<%= iconPath %>
Type=Application
StartupNotify=true

View File

@ -34,6 +34,8 @@
<string>AtomApplication</string>
<key>NSSupportsAutomaticGraphicsSwitching</key>
<true/>
<key>NSRequiresAquaSystemAppearance</key>
<string>NO</string>
<key>SUScheduledCheckInterval</key>
<string>3600</string>
<key>CFBundleURLTypes</key>

View File

@ -4,6 +4,8 @@ SET EXPECT_OUTPUT=
SET WAIT=
SET PSARGS=%*
SET ELECTRON_ENABLE_LOGGING=
SET ATOM_ADD=
SET ATOM_NEW_WINDOW=
FOR %%a IN (%*) DO (
IF /I "%%a"=="-f" SET EXPECT_OUTPUT=YES
@ -17,6 +19,10 @@ FOR %%a IN (%*) DO (
IF /I "%%a"=="-v" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="--version" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="--enable-electron-logging" SET ELECTRON_ENABLE_LOGGING=YES
IF /I "%%a"=="-a" SET ATOM_ADD=YES
IF /I "%%a"=="--add" SET ATOM_ADD=YES
IF /I "%%a"=="-n" SET ATOM_NEW_WINDOW=YES
IF /I "%%a"=="--new-window" SET ATOM_NEW_WINDOW=YES
IF /I "%%a"=="-w" (
SET EXPECT_OUTPUT=YES
SET WAIT=YES
@ -27,6 +33,12 @@ FOR %%a IN (%*) DO (
)
)
IF "%ATOM_ADD%"=="YES" (
IF "%ATOM_NEW_WINDOW%"=="YES" (
SET EXPECT_OUTPUT=YES
)
)
IF "%EXPECT_OUTPUT%"=="YES" (
IF "%WAIT%"=="YES" (
powershell -noexit "Start-Process -FilePath \"%~dp0\..\..\atom.exe\" -ArgumentList \"--pid=$pid $env:PSARGS\" ; wait-event"

View File

@ -26,7 +26,7 @@ if (!ci && process.env.CI === 'true' && process.argv.indexOf('--no-ci') === -1)
ci = true
}
verifyMachineRequirements()
verifyMachineRequirements(ci)
if (dependenciesFingerprint.isOutdated()) {
cleanDependencies()

View File

@ -2,9 +2,13 @@
'use strict'
// Run bootstrap first to ensure all the dependencies used later in this script
// are installed.
require('./bootstrap')
if (process.argv.includes('--no-bootstrap')) {
console.log('Skipping bootstrap')
} else {
// Bootstrap first to ensure all the dependencies used later in this script
// are installed.
require('./bootstrap')
}
// Needed so we can require src/module-cache.coffee during generateModuleCache
require('coffee-script/register')
@ -57,6 +61,8 @@ process.on('unhandledRejection', function (e) {
})
const CONFIG = require('./config')
process.env.ELECTRON_VERSION = CONFIG.appMetadata.electronVersion
let binariesPromise = Promise.resolve()
if (!argv.existingBinaries) {

View File

@ -77,6 +77,8 @@ function getApmBinPath () {
}
function getNpmBinPath (external = false) {
if (process.env.NPM_BIN_PATH) return process.env.NPM_BIN_PATH
const npmBinName = process.platform === 'win32' ? 'npm.cmd' : 'npm'
const localNpmBinPath = path.resolve(repositoryRootPath, 'script', 'node_modules', '.bin', npmBinName)
return !external && fs.existsSync(localNpmBinPath) ? localNpmBinPath : npmBinName

View File

@ -35,7 +35,7 @@ function dumpSymbol (binaryPath) {
const symbolDirPath = path.join(CONFIG.symbolsPath, filename, moduleLine[1])
const symbolFilePath = path.join(symbolDirPath, `${filename}.sym`)
fs.mkdirpSync(symbolDirPath)
fs.writeFileSync(symbolFilePath)
fs.writeFileSync(symbolFilePath, content)
resolve()
}
}

View File

@ -30,6 +30,10 @@ module.exports = function (packagedAppPath) {
requiringModuleRelativePath.endsWith(path.join('node_modules/xregexp/xregexp-all.js')) ||
(requiredModuleRelativePath.startsWith(path.join('..', 'src')) && requiredModuleRelativePath.endsWith('-element.js')) ||
requiredModuleRelativePath.startsWith(path.join('..', 'node_modules', 'dugite')) ||
requiredModuleRelativePath.startsWith(path.join('..', 'node_modules', 'markdown-preview', 'node_modules', 'yaml-front-matter')) ||
requiredModuleRelativePath.startsWith(path.join('..', 'node_modules', 'markdown-preview', 'node_modules', 'cheerio')) ||
requiredModuleRelativePath.startsWith(path.join('..', 'node_modules', 'markdown-preview', 'node_modules', 'marked')) ||
requiredModuleRelativePath.startsWith(path.join('..', 'node_modules', 'typescript-simple')) ||
requiredModuleRelativePath.endsWith(path.join('node_modules', 'coffee-script', 'lib', 'coffee-script', 'register.js')) ||
requiredModuleRelativePath.endsWith(path.join('node_modules', 'fs-extra', 'lib', 'index.js')) ||
requiredModuleRelativePath.endsWith(path.join('node_modules', 'graceful-fs', 'graceful-fs.js')) ||
@ -37,6 +41,7 @@ module.exports = function (packagedAppPath) {
requiredModuleRelativePath.endsWith(path.join('node_modules', 'minimatch', 'minimatch.js')) ||
requiredModuleRelativePath.endsWith(path.join('node_modules', 'request', 'index.js')) ||
requiredModuleRelativePath.endsWith(path.join('node_modules', 'request', 'request.js')) ||
requiredModuleRelativePath.endsWith(path.join('node_modules', 'superstring', 'index.js')) ||
requiredModuleRelativePath.endsWith(path.join('node_modules', 'temp', 'lib', 'temp.js')) ||
requiredModuleRelativePath === path.join('..', 'exports', 'atom.js') ||
requiredModuleRelativePath === path.join('..', 'src', 'electron-shims.js') ||
@ -52,7 +57,6 @@ module.exports = function (packagedAppPath) {
requiredModuleRelativePath === path.join('..', 'node_modules', 'less', 'lib', 'less-node', 'index.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'lodash.isequal', 'index.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'node-fetch', 'lib', 'fetch-error.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'superstring', 'index.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'oniguruma', 'src', 'oniguruma.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'resolve', 'index.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'resolve', 'lib', 'core.js') ||
@ -64,7 +68,8 @@ module.exports = function (packagedAppPath) {
requiredModuleRelativePath === path.join('..', 'node_modules', 'tmp', 'lib', 'tmp.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'tree-sitter', 'index.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'yauzl', 'index.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', 'winreg', 'lib', 'registry.js')
requiredModuleRelativePath === path.join('..', 'node_modules', 'winreg', 'lib', 'registry.js') ||
requiredModuleRelativePath === path.join('..', 'node_modules', '@atom', 'fuzzy-native', 'lib', 'main.js')
)
}
}).then(({snapshotScript}) => {

View File

@ -55,6 +55,9 @@ const EXCLUDE_REGEXPS_SOURCES = [
escapeRegExp(path.join('node_modules', '.bin', 'pegjs')),
escapeRegExp(path.join('node_modules', 'spellchecker', 'vendor', 'hunspell') + path.sep) + '.*',
// node_modules of the fuzzy-native package are only required for building it.
escapeRegExp(path.join('node_modules', 'fuzzy-native', 'node_modules')),
// Ignore *.cc and *.h files from native modules
escapeRegExp(path.sep) + '.+\\.(cc|h)$',

View File

@ -6,28 +6,46 @@ const path = require('path')
const CONFIG = require('../config')
module.exports = function () {
module.exports = async function () {
const globPathsToLint = [
path.join(CONFIG.repositoryRootPath, 'exports', '**', '*.js'),
path.join(CONFIG.repositoryRootPath, 'packages', '**', '*.js'),
path.join(CONFIG.repositoryRootPath, 'script', '**', '*.js'),
path.join(CONFIG.repositoryRootPath, 'spec', '**', '*.js'),
path.join(CONFIG.repositoryRootPath, 'src', '**', '*.js'),
path.join(CONFIG.repositoryRootPath, 'static', '*.js')
]
return expandGlobPaths(globPathsToLint).then((paths) => {
return new Promise((resolve, reject) => {
standard.lintFiles(paths, (error, lintOutput) => {
if (error) {
reject(error)
} else {
const errors = []
for (let result of lintOutput.results) {
for (let message of result.messages) {
errors.push({path: result.filePath, lineNumber: message.line, message: message.message, rule: message.ruleId})
}
const globPathsToIgnore = [
path.join(CONFIG.repositoryRootPath, 'spec', 'fixtures', '**', '*.js')
]
const [includePaths, excludePaths] = await Promise.all([
expandGlobPaths(globPathsToLint),
expandGlobPaths(globPathsToIgnore)
])
const paths = includePaths.filter(
myPath => !excludePaths.includes(myPath)
)
return new Promise((resolve, reject) => {
standard.lintFiles(paths, (error, lintOutput) => {
if (error) {
reject(error)
} else {
const errors = []
for (let result of lintOutput.results) {
for (let message of result.messages) {
errors.push({
path: result.filePath,
lineNumber: message.line,
message: message.message,
rule: message.ruleId
})
}
resolve(errors)
}
})
resolve(errors)
}
})
})
}

View File

@ -107,6 +107,7 @@ function buildAsarUnpackGlobExpression () {
path.join('**', 'node_modules', 'spellchecker', '**'),
path.join('**', 'node_modules', 'dugite', 'git', '**'),
path.join('**', 'node_modules', 'github', 'bin', '**'),
path.join('**', 'node_modules', 'vscode-ripgrep', 'bin', '**'),
path.join('**', 'resources', 'atom.png')
]

View File

@ -6,9 +6,9 @@ const path = require('path')
const CONFIG = require('../config')
module.exports = function () {
module.exports = function (ci) {
verifyNode()
verifyNpm()
verifyNpm(ci)
if (process.platform === 'win32') {
verifyPython()
}
@ -27,14 +27,15 @@ function verifyNode () {
}
}
function verifyNpm () {
const stdout = childProcess.execFileSync(CONFIG.getNpmBinPath(), ['--version'], {env: process.env})
function verifyNpm (ci) {
const stdout = childProcess.execFileSync(CONFIG.getNpmBinPath(ci), ['--version'], {env: process.env})
const fullVersion = stdout.toString().trim()
const majorVersion = fullVersion.split('.')[0]
if (majorVersion >= 3) {
const oldestMajorVersionSupported = ci ? 6 : 3
if (majorVersion >= oldestMajorVersionSupported) {
console.log(`Npm:\tv${fullVersion}`)
} else {
throw new Error(`npm v3+ is required to build Atom. npm v${fullVersion} was detected.`)
throw new Error(`npm v${oldestMajorVersionSupported}+ is required to build Atom. npm v${fullVersion} was detected.`)
}
}

512
script/package-lock.json generated
View File

@ -22,6 +22,11 @@
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.0.tgz",
"integrity": "sha512-LAQ1d4OPfSJ/BMbI2DuizmYrrkD9JMaTdi2hQTlI53lQ4kRQPyZQRS4CYQ7O66bnBBnP/oYdRxbk++X0xuFU6A=="
},
"@types/node": {
"version": "11.9.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-11.9.4.tgz",
"integrity": "sha512-Zl8dGvAcEmadgs1tmSPcvwzO1YRsz38bVJQvH1RvRqSR9/5n61Q1ktcDL0ht3FXWR+ZpVmXVwN1LuH4Ax23NsA=="
},
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@ -376,22 +381,6 @@
"postcss-value-parser": "^3.2.3"
}
},
"aws-sdk": {
"version": "2.275.1",
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.275.1.tgz",
"integrity": "sha512-lcpgoiHLhdcolUT7aJdg/CmlYO5ecf+3A+4dIceO72mFovCWZde1Rvr07QNbQ8gT0paqr5j2rs2b6c23Y/K0RQ==",
"requires": {
"buffer": "4.9.1",
"events": "1.1.1",
"ieee754": "1.1.8",
"jmespath": "0.15.0",
"querystring": "0.2.0",
"sax": "1.2.1",
"url": "0.10.3",
"uuid": "3.1.0",
"xml2js": "0.4.19"
}
},
"aws-sign2": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
@ -625,11 +614,6 @@
}
}
},
"base64-js": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz",
"integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw=="
},
"bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
@ -691,6 +675,11 @@
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz",
"integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE="
},
"boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24="
},
"boom": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/boom/-/boom-0.4.2.tgz",
@ -737,16 +726,6 @@
"electron-to-chromium": "^1.3.47"
}
},
"buffer": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
"integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
"requires": {
"base64-js": "^1.0.2",
"ieee754": "^1.1.4",
"isarray": "^1.0.0"
}
},
"buffer-alloc": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
@ -920,6 +899,19 @@
"resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.2.tgz",
"integrity": "sha512-7I/xceXfKyUJmSAn/jw8ve/9DyOP7XxufNYLI9Px7CmsKgEUaZLUTax6nZxGQtaoiZCjpu6cHPj20xC/vqRReQ=="
},
"cheerio": {
"version": "1.0.0-rc.2",
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.2.tgz",
"integrity": "sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=",
"requires": {
"css-select": "~1.2.0",
"dom-serializer": "~0.1.0",
"entities": "~1.1.1",
"htmlparser2": "^3.9.1",
"lodash": "^4.15.0",
"parse5": "^3.0.1"
}
},
"chownr": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz",
@ -969,11 +961,6 @@
"restore-cursor": "^1.0.1"
}
},
"cli-width": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-1.1.1.tgz",
"integrity": "sha1-pNKT72frt7iNSk1CwMzwDE0eNm0="
},
"cliui": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz",
@ -1259,11 +1246,38 @@
"resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.7.0.tgz",
"integrity": "sha1-Mh9s9zeCpv91ERE5D8BeLGV9jJs="
},
"css-select": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
"integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=",
"requires": {
"boolbase": "~1.0.0",
"css-what": "2.1",
"domutils": "1.5.1",
"nth-check": "~1.0.1"
},
"dependencies": {
"domutils": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
"integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=",
"requires": {
"dom-serializer": "0",
"domelementtype": "1"
}
}
}
},
"css-value": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/css-value/-/css-value-0.0.1.tgz",
"integrity": "sha1-Xv1sLupeof1rasV+wEJ7GEUkJOo="
},
"css-what": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz",
"integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg=="
},
"ctype": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz",
@ -1668,14 +1682,6 @@
"is-obj": "^1.0.0"
}
},
"duplexer2": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz",
"integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=",
"requires": {
"readable-stream": "~1.1.9"
}
},
"ecc-jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
@ -1685,11 +1691,6 @@
"jsbn": "~0.1.0"
}
},
"editor": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/editor/-/editor-1.0.0.tgz",
"integrity": "sha1-YMf4e9YrzGqJT6jM1q+3gjok90I="
},
"electron-chromedriver": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/electron-chromedriver/-/electron-chromedriver-2.0.0.tgz",
@ -1732,29 +1733,30 @@
}
},
"electron-link": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/electron-link/-/electron-link-0.3.2.tgz",
"integrity": "sha512-V7QmtujzWgvrW5BI2CKmIRF+q+pkrFO5Lecd8TpibbBz+FfW5WQ4kCN0sZjNaUOMtGGroCib721OqIDEynjwgA==",
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/electron-link/-/electron-link-0.4.0.tgz",
"integrity": "sha512-zhc74EBUj5LnaGCCZY2McC/6EkZa/5+nnDucGQGyC+umIaCmxPAl/Ptyvg6YwJLMYj1VAdyqngIPC2x6nXXOEg==",
"requires": {
"acorn": "^6.1.1",
"ast-util": "^0.6.0",
"encoding-down": "~5.0.0",
"indent-string": "^3.2.0",
"leveldown": "~4.0.0",
"levelup": "~3.0.0",
"recast": "^0.12.6",
"recast": "^0.17.5",
"resolve": "^1.5.0",
"source-map": "^0.5.6"
},
"dependencies": {
"ast-types": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.10.1.tgz",
"integrity": "sha512-UY7+9DPzlJ9VM8eY0b2TUZcZvF+1pO0hzMtAyjBYKhOmnvRlqYNYnWdtsMj0V16CGaMlpL0G1jnLbLo4AyotuQ=="
"acorn": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz",
"integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA=="
},
"core-js": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.1.tgz",
"integrity": "sha512-L72mmmEayPJBejKIWe2pYtGis5r0tQ5NaJekdhyXgeMQTpJoBsH0NL4ElY2LfSoV15xeQWKQ+XTTOZdyero5Xg=="
"ast-types": {
"version": "0.12.3",
"resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.12.3.tgz",
"integrity": "sha512-wJUcAfrdW+IgDoMGNz5MmcvahKgB7BwIbLupdKVVHxHNYt+HVR2k35swdYNv9aZpF8nvlkjbnkp2rrNwxGckZA=="
},
"esprima": {
"version": "4.0.1",
@ -1767,14 +1769,13 @@
"integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok="
},
"recast": {
"version": "0.12.9",
"resolved": "https://registry.npmjs.org/recast/-/recast-0.12.9.tgz",
"integrity": "sha512-y7ANxCWmMW8xLOaiopiRDlyjQ9ajKRENBH+2wjntIbk3A6ZR1+BLQttkmSHMY7Arl+AAZFwJ10grg2T6f1WI8A==",
"version": "0.17.5",
"resolved": "https://registry.npmjs.org/recast/-/recast-0.17.5.tgz",
"integrity": "sha512-K+DgfAMIyEjNKjaFSWgg9TTu7wFgU/4KTyw4E9vl6M5QPDuUYbyt49Yzb0EIDbZks+6lXk/UZ9eTuE4jlLyf2A==",
"requires": {
"ast-types": "0.10.1",
"core-js": "^2.4.1",
"ast-types": "0.12.3",
"esprima": "~4.0.0",
"private": "~0.1.5",
"private": "^0.1.8",
"source-map": "~0.6.1"
},
"dependencies": {
@ -2213,11 +2214,6 @@
"es5-ext": "~0.10.14"
}
},
"events": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
"integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ="
},
"execa": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/execa/-/execa-0.4.0.tgz",
@ -2261,9 +2257,9 @@
}
},
"expand-template": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-1.1.1.tgz",
"integrity": "sha512-cebqLtV8KOZfw0UI8TEFWxtczxxC1jvyUvx6H4fyp1K1FN7A4Q+uggVUlOsI1K8AGU0rwOGqP8nCapdrw8CYQg=="
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="
},
"extend": {
"version": "3.0.1",
@ -2968,49 +2964,6 @@
"assert-plus": "^1.0.0"
}
},
"ghauth": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/ghauth/-/ghauth-2.0.1.tgz",
"integrity": "sha1-ebfWiwvPjn0IUqI7FHU539MUrPY=",
"requires": {
"bl": "~0.9.4",
"hyperquest": "~1.2.0",
"mkdirp": "~0.5.0",
"read": "~1.0.5",
"xtend": "~4.0.0"
},
"dependencies": {
"bl": {
"version": "0.9.5",
"resolved": "https://registry.npmjs.org/bl/-/bl-0.9.5.tgz",
"integrity": "sha1-wGt5evCF6gC8Unr8jvzxHeIjIFQ=",
"requires": {
"readable-stream": "~1.0.26"
}
},
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
},
"readable-stream": {
"version": "1.0.34",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
"integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.1",
"isarray": "0.0.1",
"string_decoder": "~0.10.x"
}
},
"xtend": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
"integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
}
}
},
"github-from-package": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
@ -3026,14 +2979,6 @@
"resolved": "https://registry.npmjs.org/github-url-from-username-repo/-/github-url-from-username-repo-1.0.2.tgz",
"integrity": "sha1-fdeTMNKr5pwQws73lxTJchV5Hfo="
},
"github-url-to-object": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/github-url-to-object/-/github-url-to-object-1.6.0.tgz",
"integrity": "sha1-iR73+7+rqP7XFRCs2xtOk0apcNw=",
"requires": {
"is-url": "^1.1.0"
}
},
"glob": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.0.3.tgz",
@ -3322,47 +3267,6 @@
"sshpk": "^1.7.0"
}
},
"hyperquest": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/hyperquest/-/hyperquest-1.2.0.tgz",
"integrity": "sha1-OeH+9miI3Hzg3sbA3YFPb8iUStU=",
"requires": {
"duplexer2": "~0.0.2",
"through2": "~0.6.3"
},
"dependencies": {
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
},
"readable-stream": {
"version": "1.0.34",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
"integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.1",
"isarray": "0.0.1",
"string_decoder": "~0.10.x"
}
},
"through2": {
"version": "0.6.5",
"resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
"integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=",
"requires": {
"readable-stream": ">=1.0.33-1 <1.1.0-0",
"xtend": ">=4.0.0 <4.1.0-0"
}
},
"xtend": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
"integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
}
}
},
"iconv-lite": {
"version": "0.4.23",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
@ -3371,11 +3275,6 @@
"safer-buffer": ">= 2.1.2 < 3"
}
},
"ieee754": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz",
"integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q="
},
"ignore": {
"version": "3.3.10",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz",
@ -3433,33 +3332,6 @@
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
},
"inquirer": {
"version": "0.8.5",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.8.5.tgz",
"integrity": "sha1-29dAz2yjtzEpamPOb22WGFHzNt8=",
"requires": {
"ansi-regex": "^1.1.1",
"chalk": "^1.0.0",
"cli-width": "^1.0.1",
"figures": "^1.3.5",
"lodash": "^3.3.1",
"readline2": "^0.1.1",
"rx": "^2.4.3",
"through": "^2.3.6"
},
"dependencies": {
"ansi-regex": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz",
"integrity": "sha1-QchHGUZGN15qGl0Qw8oFTvn8mA0="
},
"lodash": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
"integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y="
}
}
},
"invert-kv": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz",
@ -3723,11 +3595,6 @@
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
},
"is-url": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz",
"integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww=="
},
"is-utf8": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
@ -3781,11 +3648,6 @@
"resolved": "https://registry.npmjs.org/jju/-/jju-1.3.0.tgz",
"integrity": "sha1-2t2e8BkkvHKLA/L3l5vb1i96Kqo="
},
"jmespath": {
"version": "0.15.0",
"resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz",
"integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc="
},
"joanna": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/joanna/-/joanna-0.0.10.tgz",
@ -4006,14 +3868,14 @@
}
},
"level-codec": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/level-codec/-/level-codec-9.0.0.tgz",
"integrity": "sha512-OIpVvjCcZNP5SdhcNupnsI1zo5Y9Vpm+k/F1gfG5kXrtctlrwanisakweJtE0uA0OpLukRfOQae+Fg0M5Debhg=="
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/level-codec/-/level-codec-9.0.1.tgz",
"integrity": "sha512-ajFP0kJ+nyq4i6kptSM+mAvJKLOg1X5FiFPtLG9M5gCEZyBmgDi3FkDrvlMkEzrUn1cWxtvVmrvoS4ASyO/q+Q=="
},
"level-errors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/level-errors/-/level-errors-2.0.0.tgz",
"integrity": "sha512-AmY4HCp9h3OiU19uG+3YWkdELgy05OTP/r23aNHaQKWv8DO787yZgsEuGVkoph40uwN+YdUKnANlrxSsoOaaxg==",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/level-errors/-/level-errors-2.0.1.tgz",
"integrity": "sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==",
"requires": {
"errno": "~0.1.1"
}
@ -4058,15 +3920,22 @@
}
},
"leveldown": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/leveldown/-/leveldown-4.0.1.tgz",
"integrity": "sha512-ZlBKVSsglPIPJnz4ggB8o2R0bxDxbsMzuQohbfgoFMVApyTE118DK5LNRG0cRju6rt3OkGxe0V6UYACGlq/byg==",
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/leveldown/-/leveldown-4.0.2.tgz",
"integrity": "sha512-SUgSRTWFh3eeiTdIt2a4Fi9TZO5oWzE9uC/Iw8+fVr1sk8x1S2l151UWwSmrMFZB3GxJhZIf4bQ0n+051Cctpw==",
"requires": {
"abstract-leveldown": "~5.0.0",
"bindings": "~1.3.0",
"fast-future": "~1.0.2",
"nan": "~2.10.0",
"prebuild-install": "^4.0.0"
"nan": "~2.12.1",
"prebuild-install": "~5.2.4"
},
"dependencies": {
"nan": {
"version": "2.12.1",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz",
"integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw=="
}
}
},
"levelup": {
@ -4440,11 +4309,6 @@
"regex-cache": "^0.4.2"
}
},
"mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
},
"mime-db": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz",
@ -4624,11 +4488,6 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"mute-stream": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
"integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s="
},
"mv": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz",
@ -4701,6 +4560,11 @@
}
}
},
"napi-build-utils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.1.tgz",
"integrity": "sha512-boQj1WFgQH3v4clhu3mTNfP+vOBxorDlE8EKiMjUlLG3C4qAESnn9AxIOkFgTR2c9LtzNjPrjS60cT27ZKBhaA=="
},
"natives": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/natives/-/natives-1.1.4.tgz",
@ -4723,17 +4587,17 @@
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
},
"node-abi": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.5.1.tgz",
"integrity": "sha512-oDbFc7vCFx0RWWCweTer3hFm1u+e60N5FtGnmRV6QqvgATGFH/XRR6vqWIeBVosCYCqt6YdIr2L0exLZuEdVcQ==",
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.7.1.tgz",
"integrity": "sha512-OV8Bq1OrPh6z+Y4dqwo05HqrRL9YNF7QVMRfq1/pguwKLG+q9UB/Lk0x5qXjO23JjJg+/jqCHSTaG1P3tfKfuw==",
"requires": {
"semver": "^5.4.1"
},
"dependencies": {
"semver": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
"integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg=="
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
"integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA=="
}
}
},
@ -7549,6 +7413,14 @@
"set-blocking": "~2.0.0"
}
},
"nth-check": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz",
"integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==",
"requires": {
"boolbase": "~1.0.0"
}
},
"nugget": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/nugget/-/nugget-2.0.1.tgz",
@ -7777,6 +7649,14 @@
"error-ex": "^1.2.0"
}
},
"parse5": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz",
"integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==",
"requires": {
"@types/node": "*"
}
},
"pascalcase": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
@ -7881,11 +7761,6 @@
}
}
},
"pkginfo": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.3.1.tgz",
"integrity": "sha1-Wyn2qB9wcXFC4J52W76rl7T4HiE="
},
"plist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/plist/-/plist-1.2.0.tgz",
@ -8174,21 +8049,22 @@
"integrity": "sha1-aJQxe7jUcVflneKkoAnbfm9j4w4="
},
"prebuild-install": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-4.0.0.tgz",
"integrity": "sha512-7tayxeYboJX0RbVzdnKyGl2vhQRWr6qfClEXDhOkXjuaOKCw2q8aiuFhONRYVsG/czia7KhpykIlI2S2VaPunA==",
"version": "5.2.5",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.2.5.tgz",
"integrity": "sha512-6uZgMVg7yDfqlP5CPurVhtq3hUKBFNufiar4J5hZrlHTo59DDBEtyxw01xCdFss9j0Zb9+qzFVf/s4niayba3w==",
"requires": {
"detect-libc": "^1.0.3",
"expand-template": "^1.0.2",
"expand-template": "^2.0.3",
"github-from-package": "0.0.0",
"minimist": "^1.2.0",
"mkdirp": "^0.5.1",
"node-abi": "^2.2.0",
"napi-build-utils": "^1.0.1",
"node-abi": "^2.7.0",
"noop-logger": "^0.1.1",
"npmlog": "^4.0.1",
"os-homedir": "^1.0.1",
"pump": "^2.0.1",
"rc": "^1.1.6",
"rc": "^1.2.7",
"simple-get": "^2.7.0",
"tar-fs": "^1.13.0",
"tunnel-agent": "^0.6.0",
@ -8256,43 +8132,6 @@
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
},
"publish-release": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/publish-release/-/publish-release-1.6.0.tgz",
"integrity": "sha512-t+NFXTQN/VDTg9yJ8Uv5ZWQ7Ud1T5W1tPW+bmuo4g6uYVQTVNiwwRF6Td3EtXFTOafpEXJQEZqGG7IvIJwLwIg==",
"requires": {
"async": "^0.9.0",
"ghauth": "^2.0.0",
"github-url-to-object": "^1.4.2",
"inquirer": "^0.8.2",
"lodash": "^3.6.0",
"mime": "^1.3.4",
"minimist": "^1.1.1",
"pkginfo": "^0.3.0",
"pretty-bytes": "^1.0.4",
"progress-stream": "^1.0.1",
"request": "^2.54.0",
"single-line-log": "^0.4.1",
"string-editor": "^0.1.0"
},
"dependencies": {
"async": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz",
"integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0="
},
"lodash": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
"integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y="
},
"single-line-log": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/single-line-log/-/single-line-log-0.4.1.tgz",
"integrity": "sha1-h6VWSfdJ14PsDc2AToFA2Yc8fO4="
}
}
},
"pump": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
@ -8385,14 +8224,6 @@
"resolved": "https://registry.npmjs.org/rcedit/-/rcedit-0.5.1.tgz",
"integrity": "sha1-0L3PXSgKnRwp2m8RjMzizhU87x0="
},
"read": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz",
"integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=",
"requires": {
"mute-stream": "~0.0.4"
}
},
"read-installed": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/read-installed/-/read-installed-3.1.3.tgz",
@ -8520,35 +8351,6 @@
"once": "^1.3.0"
}
},
"readline2": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/readline2/-/readline2-0.1.1.tgz",
"integrity": "sha1-mUQ7pug7gw7zBRv9fcJBqCco1Wg=",
"requires": {
"mute-stream": "0.0.4",
"strip-ansi": "^2.0.1"
},
"dependencies": {
"ansi-regex": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz",
"integrity": "sha1-QchHGUZGN15qGl0Qw8oFTvn8mA0="
},
"mute-stream": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.4.tgz",
"integrity": "sha1-qSGZYKbV1dBGWXruUSUsZlX3F34="
},
"strip-ansi": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-2.0.1.tgz",
"integrity": "sha1-32LBqpTtLxFOHQ8h/R1QSCt5pg4=",
"requires": {
"ansi-regex": "^1.0.0"
}
}
}
},
"recast": {
"version": "0.10.33",
"resolved": "https://registry.npmjs.org/recast/-/recast-0.10.33.tgz",
@ -8765,24 +8567,6 @@
"uuid": "^3.1.0"
}
},
"request-promise-core": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz",
"integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=",
"requires": {
"lodash": "^4.13.1"
}
},
"request-promise-native": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.5.tgz",
"integrity": "sha1-UoF3D2jgyXGeUWP9P6tIIhX0/aU=",
"requires": {
"request-promise-core": "1.1.1",
"stealthy-require": "^1.1.0",
"tough-cookie": ">=2.3.3"
}
},
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@ -8896,11 +8680,6 @@
"resolved": "https://registry.npmjs.org/run-series/-/run-series-1.1.8.tgz",
"integrity": "sha512-+GztYEPRpIsQoCSraWHDBs9WVy4eVME16zhOtDB4H9J4xN0XRhknnmLOl+4gRgZtu8dpp9N/utSPjKH/xmDzXg=="
},
"rx": {
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/rx/-/rx-2.5.3.tgz",
"integrity": "sha1-Ia3H2A8CACr1Da6X/Z2/JIdV9WY="
},
"rx-lite": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz",
@ -8924,11 +8703,6 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"sax": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz",
"integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o="
},
"season": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/season/-/season-5.3.0.tgz",
@ -9333,19 +9107,6 @@
}
}
},
"stealthy-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
"integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks="
},
"string-editor": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/string-editor/-/string-editor-0.1.2.tgz",
"integrity": "sha1-9f8bWsSu16xsL7jeI20VUbIPYdA=",
"requires": {
"editor": "^1.0.0"
}
},
"string-width": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
@ -9955,15 +9716,20 @@
}
},
"terser": {
"version": "3.8.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-3.8.1.tgz",
"integrity": "sha512-FRin3gKQ0vm0xPPLuxw1FqpVgv1b2pBpYCaFb5qe6A7sD749Fnq1VbDiX3CEFM0BV0fqDzFtBfgmxhxCdzKQIg==",
"version": "3.10.8",
"resolved": "https://registry.npmjs.org/terser/-/terser-3.10.8.tgz",
"integrity": "sha512-GQJHWJ/vbx0EgRk+lBMONMmKaT+ifeo/XgT/hi3KpzEEFOERVyFuJSVXH8grcmJjiqKY35ds8rBCxvABUeyyuQ==",
"requires": {
"commander": "~2.16.0",
"commander": "~2.17.1",
"source-map": "~0.6.1",
"source-map-support": "~0.5.6"
},
"dependencies": {
"commander": {
"version": "2.17.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz",
"integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg=="
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@ -10652,20 +10418,6 @@
"resolved": "https://registry.npmjs.org/x-is-string/-/x-is-string-0.1.0.tgz",
"integrity": "sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI="
},
"xml2js": {
"version": "0.4.19",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz",
"integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==",
"requires": {
"sax": ">=0.6.0",
"xmlbuilder": "~9.0.1"
}
},
"xmlbuilder": {
"version": "9.0.7",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
"integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0="
},
"xmldom": {
"version": "0.1.27",
"resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz",

View File

@ -4,13 +4,13 @@
"dependencies": {
"7zip-bin": "^4.0.2",
"async": "2.0.1",
"aws-sdk": "^2.5.2",
"babel-core": "5.8.38",
"cheerio": "1.0.0-rc.2",
"coffeelint": "1.15.7",
"colors": "1.1.2",
"donna": "1.0.16",
"electron-chromedriver": "~2.0",
"electron-link": "0.3.2",
"electron-link": "0.4.0",
"electron-mksnapshot": "~2.0",
"electron-packager": "7.3.0",
"electron-winstaller": "2.6.4",
@ -28,10 +28,7 @@
"npm": "6.2.0",
"passwd-user": "2.1.0",
"pegjs": "0.9.0",
"publish-release": "^1.6.0",
"random-seed": "^0.3.0",
"request": "^2.87.0",
"request-promise-native": "^1.0.5",
"season": "5.3.0",
"semver": "5.3.0",
"standard": "8.4.0",

View File

@ -0,0 +1,87 @@
#!/usr/bin/env node
const yargs = require('yargs')
const argv = yargs
.usage('Usage: $0 [options]')
.help('help')
.option('search-folder', {
string: true,
demandOption: true,
requiresArg: true,
describe: 'Directory to search for JUnit XML results'
})
.option('test-results-files', {
string: true,
demandOption: true,
requiresArg: true,
describe: 'Glob that matches JUnit XML files within searchFolder'
})
.wrap(yargs.terminalWidth())
.argv
const fs = require('fs')
const path = require('path')
const glob = require('glob')
const cheerio = require('cheerio')
function discoverTestFiles() {
return new Promise((resolve, reject) => {
glob(argv.testResultsFiles, {cwd: argv.searchFolder}, (err, paths) => {
if (err) {
reject(err)
} else {
resolve(paths)
}
})
})
}
async function postProcessJUnitXML(junitXmlPath) {
const fullPath = path.resolve(argv.searchFolder, junitXmlPath)
const friendlyName = path.basename(junitXmlPath, '.xml').replace(/-+/g, ' ')
console.log(`${fullPath}: loading`)
const original = await new Promise((resolve, reject) => {
fs.readFile(fullPath, {encoding: 'utf8'}, (err, content) => {
if (err) {
reject(err)
} else {
resolve(content)
}
})
})
const $ = cheerio.load(original, { xmlMode: true })
$('testcase').attr('name', (i, oldName) => `[${friendlyName}] ${oldName}`)
const modified = $.xml()
await new Promise((resolve, reject) => {
fs.writeFile(fullPath, modified, {encoding: 'utf8'}, err => {
if (err) {
reject(err)
} else {
resolve()
}
})
})
console.log(`${fullPath}: complete`)
}
;(async function() {
const testResultFiles = await discoverTestFiles()
console.log(`Post-processing ${testResultFiles.length} JUnit XML files`)
await Promise.all(
testResultFiles.map(postProcessJUnitXML)
)
console.log(`${testResultFiles.length} JUnit XML files complete`)
})().then(
() => process.exit(0),
err => {
console.error(err.stack || err)
process.exit(1)
}
)

View File

@ -0,0 +1,5 @@
@IF EXIST "%~dp0\node.exe" (
"%~dp0\node.exe" "%~dp0\postprocess-junit-results" %*
) ELSE (
node "%~dp0\postprocess-junit-results" %*
)

View File

@ -1,91 +0,0 @@
#!/usr/bin/env node
'use strict'
const path = require('path')
const glob = require('glob')
const publishRelease = require('publish-release')
const uploadToS3 = require('./lib/upload-to-s3')
const uploadLinuxPackages = require('./lib/upload-linux-packages')
const CONFIG = require('./config')
const yargs = require('yargs')
const argv = yargs
.usage('Usage: $0 [options]')
.help('help')
.describe('assets-path', 'Path to the folder where all release assets are stored')
.describe('s3-path', 'Indicates the S3 path in which the assets should be uploaded')
.describe('create-github-release', 'Creates a GitHub release for this build, draft if release branch or public if Nightly')
.describe('linux-repo-name', 'If specified, uploads Linux packages to the given repo name on packagecloud')
.wrap(yargs.terminalWidth())
.argv
const assetsPath = argv.assetsPath || path.join(CONFIG.repositoryRootPath, 'out')
const assetsPattern = '/**/*(*.exe|*.zip|*.nupkg|*.tar.gz|*.rpm|*.deb|RELEASES*|atom-api.json)'
const assets = glob.sync(assetsPattern, { root: assetsPath, nodir: true })
const bucketPath = argv.s3Path || `releases/v${CONFIG.computedAppVersion}/`
if (!assets || assets.length === 0) {
console.error(`No assets found under specified path: ${assetsPath}`)
process.exit(1)
}
async function uploadRelease() {
console.log(`Uploading ${assets.length} release assets for ${CONFIG.computedAppVersion} to S3 under '${bucketPath}'`)
await uploadToS3(
process.env.ATOM_RELEASES_S3_KEY,
process.env.ATOM_RELEASES_S3_SECRET,
process.env.ATOM_RELEASES_S3_BUCKET,
bucketPath,
assets)
if (argv.linuxRepoName) {
await uploadLinuxPackages(
argv.linuxRepoName,
process.env.PACKAGE_CLOUD_API_KEY,
CONFIG.computedAppVersion,
assets)
} else {
console.log("Skipping upload of Linux packages")
}
if (argv.createGithubRelease) {
console.log(`Creating GitHub release v${CONFIG.computedAppVersion}`)
const release =
await publishReleaseAsync({
token: process.env.GITHUB_TOKEN,
owner: 'atom',
repo: CONFIG.channel !== 'nightly' ? 'atom' : 'atom-nightly-releases',
name: CONFIG.computedAppVersion,
tag: `v${CONFIG.computedAppVersion}`,
draft: false,
prerelease: CONFIG.channel !== 'stable',
reuseRelease: true,
skipIfPublished: true,
assets
})
console.log("Release published successfully: ", release.html_url)
} else {
console.log("Skipping GitHub release creation")
}
}
async function publishReleaseAsync(options) {
return new Promise((resolve, reject) => {
publishRelease(options, (err, release) => {
if (err) {
reject(err)
} else {
resolve(release)
}
})
})
}
uploadRelease().catch((err) => {
console.error('An error occurred while uploading the release:\n\n', err)
process.exit(1)
})

View File

@ -1,5 +0,0 @@
@IF EXIST "%~dp0\node.exe" (
"%~dp0\node.exe" "%~dp0\publish-release" %*
) ELSE (
node "%~dp0\publish-release" %*
)

View File

@ -68,7 +68,8 @@ function prepareEnv (suiteName) {
if (process.env.TEST_JUNIT_XML_ROOT) {
// Tell Jasmine to output this suite's results as a JUnit XML file to a subdirectory of the root, so that a
// CI system can interpret it.
const outputPath = path.join(process.env.TEST_JUNIT_XML_ROOT, suiteName, 'test-results.xml')
const fileName = suiteName + '.xml'
const outputPath = path.join(process.env.TEST_JUNIT_XML_ROOT, fileName)
env.TEST_JUNIT_XML_PATH = outputPath
}
@ -86,7 +87,7 @@ function runCoreMainProcessTests (callback) {
console.log('Executing core main process tests'.bold.green)
const cp = childProcess.spawn(executablePath, testArguments, {stdio: 'inherit', env: testEnv})
cp.on('error', error => { callback(error) })
cp.on('close', exitCode => { callback(null, exitCode) })
cp.on('close', exitCode => { callback(null, {exitCode, step: 'core-main-process'}) })
}
function runCoreRenderProcessTests (callback) {
@ -100,7 +101,7 @@ function runCoreRenderProcessTests (callback) {
console.log('Executing core render process tests'.bold.green)
const cp = childProcess.spawn(executablePath, testArguments, {stdio: 'inherit', env: testEnv})
cp.on('error', error => { callback(error) })
cp.on('close', exitCode => { callback(null, exitCode) })
cp.on('close', exitCode => { callback(null, {exitCode, step: 'core-render-process'}) })
}
// Build an array of functions, each running tests for a different bundled package
@ -117,7 +118,7 @@ for (let packageName in CONFIG.appMetadata.packageDependencies) {
if (!testSubdir) {
packageTestSuites.push(function (callback) {
console.log(`Skipping tests for ${packageName} because no test folder was found`.bold.yellow)
callback(null, 0)
callback(null, {exitCode: 0, step: `package-${packageName}`})
})
continue
}
@ -161,7 +162,7 @@ for (let packageName in CONFIG.appMetadata.packageDependencies) {
console.log(stderrOutput)
}
finalize()
callback(null, exitCode)
callback(null, {exitCode, step: `package-${packageName}`})
})
})
}
@ -174,7 +175,7 @@ function runBenchmarkTests (callback) {
console.log('Executing benchmark tests'.bold.green)
const cp = childProcess.spawn(executablePath, testArguments, {stdio: 'inherit', env: testEnv})
cp.on('error', error => { callback(error) })
cp.on('close', exitCode => { callback(null, exitCode) })
cp.on('close', exitCode => { callback(null, {exitCode, step: 'core-benchmarks'}) })
}
let testSuitesToRun = requestedTestSuites() || testSuitesForPlatform(process.platform)
@ -219,12 +220,17 @@ function testSuitesForPlatform (platform) {
return suites
}
async.series(testSuitesToRun, function (err, exitCodes) {
async.series(testSuitesToRun, function (err, results) {
if (err) {
console.error(err)
process.exit(1)
} else {
const testsPassed = exitCodes.every(exitCode => exitCode === 0)
process.exit(testsPassed ? 0 : 1)
const failedSteps = results.filter(({exitCode}) => exitCode !== 0)
for (const {step} of failedSteps) {
console.error(`Error! The '${step}' test step finished with a non-zero exit code`)
}
process.exit(failedSteps.length === 0 ? 0 : 1)
}
})

View File

@ -13,7 +13,7 @@ const argv = yargs
.argv
async function getReleaseVersion () {
let releaseVersion = appMetadata.version
let releaseVersion = process.env.ATOM_RELEASE_VERSION || appMetadata.version
if (argv.nightly) {
const releases = await request({
url: 'https://api.github.com/repos/atom/atom-nightly-releases/releases',
@ -42,16 +42,17 @@ async function getReleaseVersion () {
if (!process.env.SYSTEM_PULLREQUEST_PULLREQUESTNUMBER) {
// Only set the build number on non-PR builds as it causes build errors when
// non-admins send PRs to the repo
console.log(`##vso[build.updatebuildnumber]${releaseVersion}+${process.env.BUILD_BUILDNUMBER}`)
console.log(`##vso[build.updatebuildnumber]${releaseVersion}+${process.env.BUILD_BUILDID}`)
}
// Write out some variables that indicate whether artifacts should be uploaded
const buildBranch = process.env.BUILD_SOURCEBRANCHNAME
const isReleaseBranch = process.env.IS_RELEASE_BRANCH || argv.nightly || buildBranch.match(/\d\.\d+-releases/) !== null
const isSignedZipBranch =
process.env.IS_SIGNED_ZIP_BRANCH ||
buildBranch.startsWith('electron-') ||
buildBranch === 'master' && !process.env.SYSTEM_PULLREQUEST_PULLREQUESTNUMBER
!isReleaseBranch &&
(process.env.IS_SIGNED_ZIP_BRANCH ||
buildBranch.startsWith('electron-') ||
buildBranch === 'master' && !process.env.SYSTEM_PULLREQUEST_PULLREQUESTNUMBER)
console.log(`##vso[task.setvariable variable=IsReleaseBranch;isOutput=true]${isReleaseBranch}`)
console.log(`##vso[task.setvariable variable=IsSignedZipBranch;isOutput=true]${isSignedZipBranch}`)
}

View File

@ -0,0 +1,135 @@
const semver = require('semver')
const octokit = require('@octokit/rest')()
const changelog = require('pr-changelog')
const childProcess = require('child_process')
module.exports.getRelease = async function (releaseVersion, githubToken) {
if (githubToken) {
octokit.authenticate({
type: 'oauth',
token: githubToken
})
}
const releases = await octokit.repos.getReleases({owner: 'atom', repo: 'atom'})
const release = releases.data.find(r => semver.eq(r.name, releaseVersion))
return {
exists: release !== undefined,
isDraft: release && release.draft,
releaseNotes: release ? release.body : undefined
}
}
module.exports.generateForVersion = async function (releaseVersion, githubToken, oldReleaseNotes) {
let oldVersion = null
let oldVersionName = null
const parsedVersion = semver.parse(releaseVersion)
const newVersionBranch = getBranchForVersion(parsedVersion)
if (githubToken) {
changelog.setGithubAccessToken(githubToken)
octokit.authenticate({
type: 'oauth',
token: githubToken
})
}
if (parsedVersion.prerelease && parsedVersion.prerelease[0] === 'beta0') {
// For beta0 releases, stable hasn't been released yet so compare against
// the stable version's release branch
oldVersion = `${parsedVersion.major}.${parsedVersion.minor - 1}-releases`
oldVersionName = `v${parsedVersion.major}.${parsedVersion.minor - 1}.0`
} else {
let releases = await octokit.repos.getReleases({owner: 'atom', repo: 'atom'})
oldVersion = 'v' + getPreviousRelease(releaseVersion, releases.data).name
oldVersionName = oldVersion
}
const allChangesText = await changelog.getChangelog({
owner: 'atom',
repo: 'atom',
fromTag: oldVersion,
toTag: newVersionBranch,
dependencyKey: 'packageDependencies',
changelogFormatter: function ({pullRequests, owner, repo, fromTag, toTag}) {
let prString = changelog.pullRequestsToString(pullRequests)
let title = repo
if (repo === 'atom') {
title = 'Atom Core'
fromTag = oldVersionName
toTag = releaseVersion
}
return `### [${title}](https://github.com/${owner}/${repo})\n\n${fromTag}...${toTag}\n\n${prString}`
}
})
const writtenReleaseNotes =
extractWrittenReleaseNotes(oldReleaseNotes) ||
'**TODO**: Pull relevant changes here!'
return `## Notable Changes\n
${writtenReleaseNotes}\n
<details>
<summary>All Changes</summary>\n
${allChangesText}
</details>
`
}
module.exports.generateForNightly = async function (releaseVersion, githubToken) {
const releases = await octokit.repos.getReleases({owner: 'atom', repo: 'atom-nightly-releases'})
const previousRelease = getPreviousRelease(releaseVersion, releases.data)
const oldReleaseNotes = previousRelease ? previousRelease.body : undefined
const latestCommitResult = childProcess.spawnSync('git', ['rev-parse', '--short', 'HEAD'])
if (latestCommitResult && oldReleaseNotes) {
const latestCommit = latestCommitResult.stdout.toString().trim()
const extractMatch = oldReleaseNotes.match(/atom\/atom\/compare\/([0-9a-f]{5,40})\.\.\.([0-9a-f]{5,40})/)
if (extractMatch) {
return `### Click [here](https://github.com/atom/atom/compare/${extractMatch[2]}...${latestCommit}) to see the changes included with this release! :atom: :night_with_stars:`
}
}
return undefined
}
function extractWrittenReleaseNotes (oldReleaseNotes) {
if (oldReleaseNotes) {
const extractMatch = oldReleaseNotes.match(/^## Notable Changes\r\n([\s\S]*)<details>/)
if (extractMatch && extractMatch[1]) {
return extractMatch[1].trim()
}
}
return undefined
}
function getPreviousRelease (version, allReleases) {
const versionIsStable = semver.prerelease(version) === null
// Make sure versions are sorted before using them
allReleases.sort((v1, v2) => semver.rcompare(v1.name, v2.name))
for (let release of allReleases) {
if (versionIsStable && semver.prerelease(release.name)) {
continue
}
if (semver.lt(release.name, version)) {
return release
}
}
return null
}
function getBranchForVersion (version) {
let parsedVersion = version
if (!(version instanceof semver.SemVer)) {
parsedVersion = semver.parse(version)
}
return `${parsedVersion.major}.${parsedVersion.minor}-releases`
}

View File

@ -4,7 +4,7 @@ const fs = require('fs')
const path = require('path')
const aws = require('aws-sdk')
module.exports = function (s3Key, s3Secret, s3Bucket, directory, assets) {
module.exports = function (s3Key, s3Secret, s3Bucket, directory, assets, acl = 'public-read') {
const s3 = new aws.S3({
accessKeyId: s3Key,
secretAccessKey: s3Secret,
@ -37,7 +37,7 @@ module.exports = function (s3Key, s3Secret, s3Bucket, directory, assets) {
console.info(`Uploading ${assetPath}`)
const params = {
Key: `${directory}${path.basename(assetPath)}`,
ACL: 'public-read',
ACL: acl,
Body: fs.createReadStream(assetPath)
}

Some files were not shown because too many files have changed in this diff Show More