feat(GUI): use resin-corvus in AnalyticsService (#1208)

Change-Type: patch
Changelog-Entry: Start reporting errors to Sentry instead of to TrackJS.
Fixes: https://github.com/resin-io/etcher/issues/1027
This commit is contained in:
Ștefan Daniel Mihăilă 2017-04-10 20:06:32 +01:00 committed by Juan Cruz Viotti
parent 1d14f8ad08
commit 91a1c3d107
17 changed files with 163 additions and 776 deletions

View File

@ -44,7 +44,7 @@ install:
./scripts/build/docker/run-command.sh -r "${TARGET_ARCH}" -s "${PWD}" -c "make info && make electron-develop";
else
pip install codespell==1.9.2 awscli;
npm install -g bower asar;
npm install -g asar;
brew install afsctool jq;
make info;
travis_wait make electron-develop;

View File

@ -135,6 +135,18 @@ endif
endif
endif
# ---------------------------------------------------------------------
# Analytics
# ---------------------------------------------------------------------
ifndef ANALYTICS_SENTRY_TOKEN
$(warning No Sentry token found (ANALYTICS_SENTRY_TOKEN is not set))
endif
ifndef ANALYTICS_MIXPANEL_TOKEN
$(warning No Mixpanel token found (ANALYTICS_MIXPANEL_TOKEN is not set))
endif
# ---------------------------------------------------------------------
# Extra variables
# ---------------------------------------------------------------------
@ -189,18 +201,29 @@ $(BUILD_DIRECTORY)/node-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies/node_modu
-t node \
-s "$(TARGET_PLATFORM)"
$(BUILD_DIRECTORY)/electron-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies/bower_components: bower.json \
| $(BUILD_DIRECTORY)/electron-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies
./scripts/build/dependencies-bower.sh -p -x $|
$(BUILD_DIRECTORY)/electron-$(TARGET_PLATFORM)-$(APPLICATION_VERSION)-$(TARGET_ARCH)-app: \
$(BUILD_DIRECTORY)/electron-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies/node_modules \
$(BUILD_DIRECTORY)/electron-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies/bower_components \
| $(BUILD_DIRECTORY)
| $(BUILD_DIRECTORY) $(BUILD_TEMPORARY_DIRECTORY)
./scripts/build/electron-create-resources-app.sh -s . -o $@ \
-v $(APPLICATION_VERSION) \
-f "$(APPLICATION_FILES)"
$(foreach prerequisite,$^,$(call execute-command,cp -RLf $(prerequisite) $@))
cp -RLf $< $@
ifdef ANALYTICS_SENTRY_TOKEN
./scripts/build/jq-insert.sh \
-p ".analytics.sentry.token" \
-v "$(ANALYTICS_SENTRY_TOKEN)" \
-f $@/package.json \
-t $(BUILD_TEMPORARY_DIRECTORY)
endif
ifdef ANALYTICS_MIXPANEL_TOKEN
./scripts/build/jq-insert.sh \
-p ".analytics.mixpanel.token" \
-v "$(ANALYTICS_MIXPANEL_TOKEN)" \
-f $@/package.json \
-t $(BUILD_TEMPORARY_DIRECTORY)
endif
$(BUILD_DIRECTORY)/electron-$(TARGET_PLATFORM)-$(APPLICATION_VERSION)-$(TARGET_ARCH)-app.asar: \
$(BUILD_DIRECTORY)/electron-$(TARGET_PLATFORM)-$(APPLICATION_VERSION)-$(TARGET_ARCH)-app \
@ -484,7 +507,6 @@ electron-develop:
-v "$(ELECTRON_VERSION)" \
-t electron \
-s "$(TARGET_PLATFORM)"
./scripts/build/dependencies-bower.sh
help:
@echo "Available targets: $(TARGETS)"
@ -510,6 +532,5 @@ clean:
distclean: clean
rm -rf node_modules
rm -rf bower_components
.DEFAULT_GOAL = help

View File

@ -27,7 +27,7 @@ matrix:
install:
- ps: Install-Product node $env:nodejs_version x64
- npm install -g npm@4.4.4
- npm install -g bower rimraf asar
- npm install -g rimraf asar
- choco install nsis -version 2.51
- choco install jq
- choco install curl

View File

@ -1,13 +0,0 @@
{
"name": "etcher",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"angular-mixpanel": "~1.1.2"
}
}

View File

@ -10,7 +10,6 @@ Prerequisites
### Common
- [NodeJS](https://nodejs.org) (at least v6)
- [Bower](http://bower.io)
- [Python](https://www.python.org)
- [jq](https://stedolan.github.io/jq/)
- [Asar](https://github.com/electron/asar)

View File

@ -11,17 +11,6 @@
<!-- See https://github.com/visionmedia/debug#browser-support -->
<script>window.localStorage.debug = '*';</script>
<script>
window._trackJs = {
token: '032448bc3d9e4cffb1e9b43d29d6c142',
application: 'etcher',
console: {
error: false
}
};
</script>
<script src="../../node_modules/trackjs/tracker.js"></script>
<script src="./app.js"></script>
</head>
<body ng-app="Etcher">

View File

@ -20,85 +20,31 @@
* @module Etcher.Modules.Analytics
*/
const _ = require('lodash');
const angular = require('angular');
const isRunningInAsar = require('electron-is-running-in-asar');
const errors = require('../../shared/errors');
const os = require('os');
const _ = require('lodash');
const resinCorvus = require('resin-corvus/browser');
const packageJSON = require('../../../package.json');
const arch = require('arch');
const utils = require('../../shared/utils');
const settings = require('../models/settings');
// Force Mixpanel snippet to load Mixpanel locally
// instead of using a CDN for performance reasons
window.MIXPANEL_CUSTOM_LIB_URL = '../../bower_components/mixpanel/mixpanel.js';
require('../../../bower_components/mixpanel/mixpanel-jslib-snippet.js');
require('../../../bower_components/angular-mixpanel/src/angular-mixpanel');
const MODULE_NAME = 'Etcher.Modules.Analytics';
const analytics = angular.module(MODULE_NAME, [
'analytics.mixpanel'
]);
const analytics = angular.module(MODULE_NAME, []);
/**
* @summary Get host architecture
* @function
* @private
*
* @description
* We need this because node's os.arch() returns the process architecture
* See: https://github.com/nodejs/node-v0.x-archive/issues/2862
*
* @returns {String} Host architecture
*
* @example
* if (getHostArchitecture() === 'x64') {
* console.log('Host architecture is x64');
* }
*/
const getHostArchitecture = () => {
return _.includes([ 'ia32', 'x64' ], process.arch) ? _.replace(arch(), 'x86', 'ia32') : process.arch;
};
// Mixpanel integration
// https://github.com/kuhnza/angular-mixpanel
analytics.config(($mixpanelProvider) => {
$mixpanelProvider.apiKey('63e5fc4563e00928da67d1226364dd4c');
$mixpanelProvider.superProperties({
electron: process.versions.electron,
node: process.version,
arch: process.arch,
version: packageJSON.version,
osPlatform: os.platform(),
hostArch: getHostArchitecture(),
osRelease: os.release(),
cpuCores: os.cpus().length,
totalMemory: os.totalmem(),
startFreeMemory: os.freemem()
analytics.run(() => {
resinCorvus.install({
services: {
sentry: _.get(packageJSON, [ 'analytics', 'sentry', 'token' ]),
mixpanel: _.get(packageJSON, [ 'analytics', 'mixpanel', 'token' ])
},
options: {
release: packageJSON.version,
shouldReport: () => {
return settings.get('errorReporting');
}
}
});
});
// TrackJS integration
// http://docs.trackjs.com/tracker/framework-integrations
analytics.run(($window) => {
// Don't configure TrackJS when
// running inside the test suite
if (window.mocha) {
return;
}
$window.trackJs.configure({
userId: os.userInfo().username,
version: packageJSON.version
});
});
analytics.service('AnalyticsService', function($log, $window, $mixpanel) {
analytics.service('AnalyticsService', function() {
/**
* @summary Log a debug message
@ -106,22 +52,14 @@ analytics.service('AnalyticsService', function($log, $window, $mixpanel) {
* @public
*
* @description
* This function sends the debug message to TrackJS only.
* This function sends the debug message to error reporting services.
*
* @param {String} message - message
*
* @example
* AnalyticsService.log('Hello World');
*/
this.logDebug = (message) => {
const debugMessage = `${new Date()} ${message}`;
if (settings.get('errorReporting') && isRunningInAsar()) {
$window.trackJs.console.debug(debugMessage);
}
$log.debug(debugMessage);
};
this.logDebug = resinCorvus.logDebug;
/**
* @summary Log an event
@ -129,7 +67,7 @@ analytics.service('AnalyticsService', function($log, $window, $mixpanel) {
* @public
*
* @description
* This function sends the debug message to TrackJS and Mixpanel.
* This function sends the debug message to product analytics services.
*
* @param {String} message - message
* @param {Object} [data] - event data
@ -139,23 +77,7 @@ analytics.service('AnalyticsService', function($log, $window, $mixpanel) {
* image: '/dev/disk2'
* });
*/
this.logEvent = (message, data) => {
const debugData = utils.hideAbsolutePathsInObject(utils.makeFlatStartCaseObject(data));
if (settings.get('errorReporting') && isRunningInAsar()) {
$mixpanel.track(message, debugData);
}
const debugMessage = _.attempt(() => {
if (debugData) {
return `${message} (${JSON.stringify(debugData)})`;
}
return message;
});
this.logDebug(debugMessage);
};
this.logEvent = resinCorvus.logEvent;
/**
* @summary Log an exception
@ -163,24 +85,14 @@ analytics.service('AnalyticsService', function($log, $window, $mixpanel) {
* @public
*
* @description
* This function logs an exception in TrackJS.
* This function logs an exception to error reporting services.
*
* @param {Error} exception - exception
*
* @example
* AnalyticsService.logException(new Error('Something happened'));
*/
this.logException = (exception) => {
if (_.every([
settings.get('errorReporting'),
isRunningInAsar(),
errors.shouldReport(exception)
])) {
$window.trackJs.track(exception);
}
$log.error(exception);
};
this.logException = resinCorvus.logException;
});

View File

@ -122,29 +122,6 @@ const getUserFriendlyMessageProperty = (error, property) => {
return _.invoke(exports.HUMAN_FRIENDLY, [ code, property ], error);
};
/**
* @summary Check whether an error should be reported to TrackJS
* @function
* @public
*
* @description
* In order to determine whether the error should be reported, we
* check a property called `report`. For backwards compatibility, and
* to properly handle errors that we don't control, an error without
* this property is reported automatically.
*
* @param {Error} error - error
* @returns {Boolean} whether the error should be reported
*
* @example
* if (errors.shouldReport(new Error('foo'))) {
* console.log('We should report this error');
* }
*/
exports.shouldReport = (error) => {
return !_.has(error, [ 'report' ]) || Boolean(error.report);
};
/**
* @summary Check if a string is blank
* @function

View File

@ -1,135 +0,0 @@
/*
* Copyright 2017 resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
const _ = require('lodash');
_.mixin(require('lodash-deep'));
const flatten = require('flat').flatten;
const deepMapKeys = require('deep-map-keys');
const path = require('path');
/**
* @summary Create a flattened copy of the object with all keys transformed in start case
* @function
* @public
*
* @param {*} object - object to transform
* @returns {*} transformed object
*
* @example
* const object = makeFlatStartCaseObject({
* image: {
* size: 10000000000,
* recommendedSize: 10000000000
* }
* })
*
* console.log(object)
* > {
* > 'Image Size': 10000000000,
* > 'Image Recommended Size': 10000000000
* > }
*/
exports.makeFlatStartCaseObject = (object) => {
if (_.isUndefined(object)) {
return object;
}
// Transform primitives to objects
if (!_.isObject(object)) {
return {
Value: object
};
}
if (_.isArray(object)) {
return _.map(object, (property) => {
if (_.isObject(property)) {
return exports.makeFlatStartCaseObject(property);
}
return property;
});
}
const transformedKeysObject = deepMapKeys(object, (key) => {
// Preserve environment variables
const regex = /^[A-Z_]+$/;
if (regex.test(key)) {
return key;
}
return _.startCase(key);
});
return flatten(transformedKeysObject, {
delimiter: ' ',
safe: true
});
};
/**
* @summary Create an object clone with all absolute paths replaced with the path basename
* @function
* @public
*
* @param {Object} object - original object
* @returns {Object} transformed object
*
* @example
* const anonymized = utils.hideAbsolutePathsInObject({
* path1: '/home/john/rpi.img',
* simpleProperty: null,
* nested: {
* path2: '/home/john/another-image.img',
* path3: 'yet-another-image.img',
* otherProperty: false
* }
* });
*
* console.log(anonymized);
* > {
* > path1: 'rpi.img',
* > simpleProperty: null,
* > nested: {
* > path2: 'another-image.img',
* > path3: 'yet-another-image.img',
* > otherProperty: false
* > }
* > }
*/
exports.hideAbsolutePathsInObject = (object) => {
return _.deepMapValues(object, (value) => {
if (!_.isString(value)) {
return value;
}
// Don't alter disk devices, even though they appear as full paths
if (_.some([
_.startsWith(value, '/dev/'),
_.startsWith(value, '\\\\.\\')
])) {
return value;
}
return path.isAbsolute(value) ? path.basename(value) : value;
});
};

83
npm-shrinkwrap.json generated
View File

@ -159,7 +159,7 @@
},
"arch": {
"version": "2.1.0",
"from": "arch@2.1.0",
"from": "arch@>=2.1.0 <3.0.0",
"resolved": "https://registry.npmjs.org/arch/-/arch-2.1.0.tgz"
},
"archiver": {
@ -1050,6 +1050,11 @@
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz",
"dev": true
},
"cookie": {
"version": "0.3.1",
"from": "cookie@0.3.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz"
},
"cookiejar": {
"version": "2.0.1",
"from": "cookiejar@2.0.1",
@ -1266,7 +1271,7 @@
},
"deep-map-keys": {
"version": "1.2.0",
"from": "deep-map-keys@1.2.0",
"from": "deep-map-keys@>=1.2.0 <2.0.0",
"resolved": "https://registry.npmjs.org/deep-map-keys/-/deep-map-keys-1.2.0.tgz"
},
"defaults": {
@ -1396,6 +1401,16 @@
"resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz",
"dev": true
},
"detect-node": {
"version": "2.0.3",
"from": "detect-node@>=2.0.3 <3.0.0",
"resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.3.tgz"
},
"detect-process": {
"version": "1.0.4",
"from": "detect-process@>=1.0.4 <2.0.0",
"resolved": "https://registry.npmjs.org/detect-process/-/detect-process-1.0.4.tgz"
},
"detective": {
"version": "4.5.0",
"from": "detective@>=4.0.0 <5.0.0",
@ -2524,7 +2539,7 @@
},
"flat": {
"version": "2.0.1",
"from": "flat@2.0.1",
"from": "flat@>=2.0.1 <3.0.0",
"resolved": "https://registry.npmjs.org/flat/-/flat-2.0.1.tgz"
},
"flat-cache": {
@ -3345,6 +3360,11 @@
"resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.2.tgz",
"dev": true
},
"is-electron": {
"version": "2.0.0",
"from": "is-electron@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.0.0.tgz"
},
"is-electron-renderer": {
"version": "2.0.1",
"from": "is-electron-renderer@>=2.0.0 <3.0.0",
@ -3452,6 +3472,11 @@
"resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz",
"dev": true
},
"is-phantom": {
"version": "1.0.1",
"from": "is-phantom@>=1.0.1 <2.0.0",
"resolved": "https://registry.npmjs.org/is-phantom/-/is-phantom-1.0.1.tgz"
},
"is-posix-bracket": {
"version": "0.1.1",
"from": "is-posix-bracket@>=0.1.0 <0.2.0",
@ -3595,8 +3620,7 @@
"json-stringify-safe": {
"version": "5.0.1",
"from": "json-stringify-safe@>=5.0.0 <5.1.0",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"dev": true
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz"
},
"json3": {
"version": "3.3.2",
@ -3720,7 +3744,7 @@
},
"lodash-deep": {
"version": "2.0.0",
"from": "lodash-deep@2.0.0",
"from": "lodash-deep@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/lodash-deep/-/lodash-deep-2.0.0.tgz"
},
"lodash-es": {
@ -3924,6 +3948,11 @@
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.1.tgz",
"dev": true
},
"lsmod": {
"version": "1.0.0",
"from": "lsmod@1.0.0",
"resolved": "https://registry.npmjs.org/lsmod/-/lsmod-1.0.0.tgz"
},
"lzma-native": {
"version": "1.5.2",
"from": "lzma-native@>=1.1.0 <2.0.0",
@ -4658,6 +4687,16 @@
}
}
},
"mixpanel": {
"version": "0.6.0",
"from": "mixpanel@>=0.6.0 <0.7.0",
"resolved": "https://registry.npmjs.org/mixpanel/-/mixpanel-0.6.0.tgz"
},
"mixpanel-browser": {
"version": "2.11.0",
"from": "mixpanel-browser@>=2.11.0 <3.0.0",
"resolved": "https://registry.npmjs.org/mixpanel-browser/-/mixpanel-browser-2.11.0.tgz"
},
"mkdirp": {
"version": "0.5.1",
"from": "mkdirp@>=0.5.0 <0.6.0",
@ -5492,6 +5531,23 @@
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.3.tgz",
"dev": true
},
"raven": {
"version": "1.2.1",
"from": "raven@>=1.1.4 <2.0.0",
"resolved": "https://registry.npmjs.org/raven/-/raven-1.2.1.tgz",
"dependencies": {
"uuid": {
"version": "3.0.0",
"from": "uuid@3.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.0.tgz"
}
}
},
"raven-js": {
"version": "3.14.0",
"from": "raven-js@>=3.12.1 <4.0.0",
"resolved": "https://registry.npmjs.org/raven-js/-/raven-js-3.14.0.tgz"
},
"rc": {
"version": "1.1.7",
"from": "rc@>=1.1.2 <2.0.0",
@ -5789,6 +5845,18 @@
}
}
},
"resin-corvus": {
"version": "1.0.0-beta.20",
"from": "resin-corvus@latest",
"resolved": "https://registry.npmjs.org/resin-corvus/-/resin-corvus-1.0.0-beta.20.tgz",
"dependencies": {
"lodash": {
"version": "4.17.4",
"from": "lodash@>=4.17.4 <5.0.0",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz"
}
}
},
"resolve": {
"version": "1.3.2",
"from": "resolve@>=1.1.4 <2.0.0",
@ -6213,8 +6281,7 @@
"stack-trace": {
"version": "0.0.9",
"from": "stack-trace@>=0.0.0 <0.1.0",
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz",
"dev": true
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz"
},
"stream-browserify": {
"version": "2.0.1",

View File

@ -68,22 +68,18 @@
"angular-seconds-to-date": "^1.0.0",
"angular-ui-bootstrap": "^2.5.0",
"angular-ui-router": "^0.4.2",
"arch": "^2.1.0",
"bluebird": "^3.0.5",
"bootstrap-sass": "^3.3.5",
"chalk": "^1.1.3",
"command-join": "^2.0.0",
"deep-map-keys": "^1.2.0",
"drivelist": "^5.0.16",
"electron-is-running-in-asar": "^1.0.0",
"etcher-image-write": "^9.0.1",
"etcher-latest-version": "^1.0.0",
"file-type": "^4.1.0",
"flat": "^2.0.1",
"flexboxgrid": "^6.3.0",
"immutable": "^3.8.1",
"lodash": "^4.5.1",
"lodash-deep": "^2.0.0",
"lzma-native": "^1.5.2",
"mime-types": "^2.1.15",
"mountutils": "^1.0.3",
@ -94,6 +90,7 @@
"redux-localstorage": "^0.4.1",
"resin-cli-form": "^1.4.1",
"resin-cli-visuals": "^1.2.8",
"resin-corvus": "^1.0.0-beta.20",
"rx": "^4.1.0",
"semver": "^5.1.0",
"sudo-prompt": "^6.1.0",

View File

@ -26,7 +26,7 @@ RUN apt-get update && apt-get install -y \
RUN curl -sL https://deb.nodesource.com/setup_6.x | bash - \
&& apt-get install -y nodejs
RUN npm config set spin=false
RUN npm install -g bower asar electron-installer-debian
RUN npm install -g asar electron-installer-debian
# Python
RUN pip install codespell==1.9.2 awscli

View File

@ -26,7 +26,7 @@ RUN apt-get update && apt-get install -y \
RUN curl -sL https://deb.nodesource.com/setup_6.x | bash - \
&& apt-get install -y nodejs
RUN npm config set spin=false
RUN npm install -g bower asar electron-installer-debian
RUN npm install -g asar electron-installer-debian
# Python
RUN pip install codespell==1.9.2 awscli

View File

@ -26,7 +26,7 @@ RUN apt-get update && apt-get install -y \
RUN curl -sL https://deb.nodesource.com/setup_6.x | bash - \
&& apt-get install -y nodejs
RUN npm config set spin=false
RUN npm install -g bower asar electron-installer-debian
RUN npm install -g asar electron-installer-debian
# Python
RUN pip install codespell==1.9.2 awscli

View File

@ -19,42 +19,42 @@
set -u
set -e
./scripts/build/check-dependency.sh bower
./scripts/build/check-dependency.sh jq
function usage() {
echo "Usage: $0"
echo ""
echo "Options"
echo ""
echo " -x <install prefix>"
echo " -p production install"
echo " -p <property>"
echo " -v <value>"
echo " -f <file>"
echo " -t <temporary directory>"
exit 1
}
ARGV_PREFIX=""
ARGV_PRODUCTION=false
ARGV_PROPERTY=""
ARGV_VALUE=""
ARGV_FILE=""
ARGV_TEMPORARY_DIRECTORY=""
while getopts ":x:p" option; do
while getopts ":p:v:f:t:" option; do
case $option in
x) ARGV_PREFIX=$OPTARG ;;
p) ARGV_PRODUCTION=true ;;
p) ARGV_PROPERTY=$OPTARG ;;
v) ARGV_VALUE=$OPTARG ;;
f) ARGV_FILE=$OPTARG ;;
t) ARGV_TEMPORARY_DIRECTORY="$OPTARG" ;;
*) usage ;;
esac
done
INSTALL_OPTS="--allow-root"
if [ "$ARGV_PRODUCTION" == "true" ]; then
INSTALL_OPTS="$INSTALL_OPTS --production"
fi
if [ -n "$ARGV_PREFIX" ]; then
cp "$PWD/bower.json" "$ARGV_PREFIX/bower.json"
pushd "$ARGV_PREFIX"
bower install $INSTALL_OPTS
popd
rm "$ARGV_PREFIX/bower.json"
else
bower install $INSTALL_OPTS
if [ -z "$ARGV_PROPERTY" ] ||
[ -z "$ARGV_VALUE" ] ||
[ -z "$ARGV_FILE" ] ||
[ -z "$ARGV_TEMPORARY_DIRECTORY" ]; then
usage
fi
TEMPORARY_FILE="$ARGV_TEMPORARY_DIRECTORY/$(basename "$ARGV_FILE").TMP"
jq "$ARGV_PROPERTY=\"$ARGV_VALUE\"" "$ARGV_FILE" > "$TEMPORARY_FILE"
mv "$TEMPORARY_FILE" "$ARGV_FILE"

View File

@ -36,97 +36,6 @@ describe('Shared: Errors', function() {
});
describe('.shouldReport()', function() {
it('should return true for a string error', function() {
const error = 'foo';
m.chai.expect(errors.shouldReport(error)).to.be.true;
});
it('should return true for a number 0 error', function() {
const error = 0;
m.chai.expect(errors.shouldReport(error)).to.be.true;
});
it('should return true for a number 1 error', function() {
const error = 1;
m.chai.expect(errors.shouldReport(error)).to.be.true;
});
it('should return true for a number -1 error', function() {
const error = -1;
m.chai.expect(errors.shouldReport(error)).to.be.true;
});
it('should return true for an array error', function() {
const error = [ 1, 2, 3 ];
m.chai.expect(errors.shouldReport(error)).to.be.true;
});
it('should return true for an undefined error', function() {
const error = undefined;
m.chai.expect(errors.shouldReport(error)).to.be.true;
});
it('should return true for a null error', function() {
const error = null;
m.chai.expect(errors.shouldReport(error)).to.be.true;
});
it('should return true for an empty object error', function() {
const error = {};
m.chai.expect(errors.shouldReport(error)).to.be.true;
});
it('should return true for a basic error', function() {
const error = new Error('foo');
m.chai.expect(errors.shouldReport(error)).to.be.true;
});
it('should return true for an error with a report true property', function() {
const error = new Error('foo');
error.report = true;
m.chai.expect(errors.shouldReport(error)).to.be.true;
});
it('should return false for an error with a report false property', function() {
const error = new Error('foo');
error.report = false;
m.chai.expect(errors.shouldReport(error)).to.be.false;
});
it('should return false for an error with a report undefined property', function() {
const error = new Error('foo');
error.report = undefined;
m.chai.expect(errors.shouldReport(error)).to.be.false;
});
it('should return false for an error with a report null property', function() {
const error = new Error('foo');
error.report = null;
m.chai.expect(errors.shouldReport(error)).to.be.false;
});
it('should return false for an error with a report 0 property', function() {
const error = new Error('foo');
error.report = 0;
m.chai.expect(errors.shouldReport(error)).to.be.false;
});
it('should return true for an error with a report 1 property', function() {
const error = new Error('foo');
error.report = 1;
m.chai.expect(errors.shouldReport(error)).to.be.true;
});
it('should cast the report property to boolean', function() {
const error = new Error('foo');
error.report = '';
m.chai.expect(errors.shouldReport(error)).to.be.false;
});
});
describe('.getTitle()', function() {
it('should accept a string', function() {
@ -480,25 +389,25 @@ describe('Shared: Errors', function() {
describe('.createError()', function() {
it('should report the resulting error by default', function() {
it('should not set `error.report` by default', function() {
const error = errors.createError('Foo', 'Something happened');
m.chai.expect(errors.shouldReport(error)).to.be.true;
m.chai.expect(error.report).to.be.undefined;
});
it('should not report the error if report is false', function() {
it('should set `error.report` to false if `options.report` is false', function() {
const error = errors.createError('Foo', 'Something happened', {
report: false
});
m.chai.expect(errors.shouldReport(error)).to.be.false;
m.chai.expect(error.report).to.be.false;
});
it('should not report the error if report evaluates to false', function() {
it('should set `error.report` to false if `options.report` evaluates to false', function() {
const error = errors.createError('Foo', 'Something happened', {
report: 0
});
m.chai.expect(errors.shouldReport(error)).to.be.false;
m.chai.expect(error.report).to.be.false;
});
it('should be an instance of Error', function() {
@ -550,9 +459,9 @@ describe('Shared: Errors', function() {
describe('.createUserError()', function() {
it('should not report the resulting error', function() {
it('should set the `report` flag to `false`', function() {
const error = errors.createUserError('Foo', 'Something happened');
m.chai.expect(errors.shouldReport(error)).to.be.false;
m.chai.expect(error.report).to.be.false;
});
it('should be an instance of Error', function() {

View File

@ -1,336 +0,0 @@
/*
* Copyright 2017 resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
const m = require('mochainon');
const utils = require('../../lib/shared/utils');
const path = require('path');
describe('Shared: Utils', function() {
describe('.makeFlatStartCaseObject()', function() {
it('should return undefined if given undefined', function() {
m.chai.expect(utils.makeFlatStartCaseObject(undefined)).to.be.undefined;
});
it('should return flat object with start case keys if given nested object', function() {
const object = {
person: {
firstName: 'John',
lastName: 'Doe',
address: {
streetNumber: 13,
streetName: 'Elm'
}
}
};
m.chai.expect(utils.makeFlatStartCaseObject(object)).to.deep.equal({
'Person First Name': 'John',
'Person Last Name': 'Doe',
'Person Address Street Number': 13,
'Person Address Street Name': 'Elm'
});
});
it('should return an object with the key `value` if given `false`', function() {
m.chai.expect(utils.makeFlatStartCaseObject(false)).to.deep.equal({
Value: false
});
});
it('should return an object with the key `value` if given `null`', function() {
m.chai.expect(utils.makeFlatStartCaseObject(null)).to.deep.equal({
Value: null
});
});
it('should preserve environment variable', function() {
m.chai.expect(utils.makeFlatStartCaseObject({
ETCHER_DISABLE_UPDATES: true
})).to.deep.equal({
ETCHER_DISABLE_UPDATES: true
});
});
it('should preserve environment variables inside objects', function() {
m.chai.expect(utils.makeFlatStartCaseObject({
foo: {
FOO_BAR_BAZ: 3
}
})).to.deep.equal({
'Foo FOO_BAR_BAZ': 3
});
});
it('should insert space after key starting with number', function() {
m.chai.expect(utils.makeFlatStartCaseObject({
foo: {
'1key': 1
}
})).to.deep.equal({
'Foo 1 Key': 1
});
});
it('should not modify start case keys', function() {
m.chai.expect(utils.makeFlatStartCaseObject({
Foo: {
'Start Case Key': 42
}
})).to.deep.equal({
'Foo Start Case Key': 42
});
});
it('should not modify arrays', function() {
m.chai.expect(utils.makeFlatStartCaseObject([ 1, 2, {
nested: 3
} ])).to.deep.equal([ 1, 2, {
Nested: 3
} ]);
});
it('should not modify nested arrays', function() {
m.chai.expect(utils.makeFlatStartCaseObject({
values: [ 1, 2, {
nested: 3
} ]
})).to.deep.equal({
Values: [ 1, 2, {
Nested: 3
} ]
});
});
it('should leave nested arrays nested', function() {
m.chai.expect(utils.makeFlatStartCaseObject([ 1, 2, [ 3, 4 ] ])).to.deep.equal([ 1, 2, [ 3, 4 ] ]);
});
});
describe('.hideAbsolutePathsInObject()', function() {
it('should return undefined if given undefined', function() {
m.chai.expect(utils.hideAbsolutePathsInObject(undefined)).to.be.undefined;
});
it('should return null if given null', function() {
m.chai.expect(utils.hideAbsolutePathsInObject(null)).to.be.null;
});
it('should return a clone of the object if there are no paths in the object', function() {
const object = {
numberProperty: 1,
nested: {
otherProperty: 'value'
}
};
m.chai.expect(utils.hideAbsolutePathsInObject(object)).to.not.equal(object);
m.chai.expect(utils.hideAbsolutePathsInObject(object)).to.deep.equal(object);
});
describe('given UNIX paths', function() {
beforeEach(function() {
this.isAbsolute = path.isAbsolute;
this.basename = path.basename;
path.isAbsolute = path.posix.isAbsolute;
path.basename = path.posix.basename;
});
afterEach(function() {
path.isAbsolute = this.isAbsolute;
path.basename = this.basename;
});
it('should replace absolute paths with the basename', function() {
const object = {
prop1: 'some value',
prop2: '/home/john/rpi.img'
};
m.chai.expect(utils.hideAbsolutePathsInObject(object)).to.deep.equal({
prop1: 'some value',
prop2: 'rpi.img'
});
});
it('should replace nested absolute paths with the basename', function() {
const object = {
nested: {
path: '/home/john/rpi.img'
}
};
m.chai.expect(utils.hideAbsolutePathsInObject(object)).to.deep.equal({
nested: {
path: 'rpi.img'
}
});
});
it('should not alter /dev/sdb', function() {
const object = {
nested: {
path: '/dev/sdb'
}
};
m.chai.expect(utils.hideAbsolutePathsInObject(object)).to.deep.equal({
nested: {
path: '/dev/sdb'
}
});
});
it('should not alter relative paths', function() {
const object = {
path: 'foo/bar'
};
m.chai.expect(utils.hideAbsolutePathsInObject(object)).to.deep.equal({
path: 'foo/bar'
});
});
it('should handle arrays', function() {
m.chai.expect(utils.hideAbsolutePathsInObject({
foo: 'foo',
bar: [
{
path: '/foo/bar/baz'
},
{
path: '/foo/bar/baz'
},
{
path: '/foo/bar/baz'
}
]
})).to.deep.equal({
foo: 'foo',
bar: [
{
path: 'baz'
},
{
path: 'baz'
},
{
path: 'baz'
}
]
});
});
});
describe('given Windows paths', function() {
beforeEach(function() {
this.isAbsolute = path.isAbsolute;
this.basename = path.basename;
path.isAbsolute = path.win32.isAbsolute;
path.basename = path.win32.basename;
});
afterEach(function() {
path.isAbsolute = this.isAbsolute;
path.basename = this.basename;
});
it('should replace absolute paths with the basename', function() {
const object = {
prop1: 'some value',
prop2: 'C:\\Users\\John\\rpi.img'
};
m.chai.expect(utils.hideAbsolutePathsInObject(object)).to.deep.equal({
prop1: 'some value',
prop2: 'rpi.img'
});
});
it('should replace nested absolute paths with the basename', function() {
const object = {
nested: {
path: 'C:\\Users\\John\\rpi.img'
}
};
m.chai.expect(utils.hideAbsolutePathsInObject(object)).to.deep.equal({
nested: {
path: 'rpi.img'
}
});
});
it('should not alter \\\\.\\PHYSICALDRIVE1', function() {
const object = {
nested: {
path: '\\\\.\\PHYSICALDRIVE1'
}
};
m.chai.expect(utils.hideAbsolutePathsInObject(object)).to.deep.equal({
nested: {
path: '\\\\.\\PHYSICALDRIVE1'
}
});
});
it('should not alter relative paths', function() {
const object = {
path: 'foo\\bar'
};
m.chai.expect(utils.hideAbsolutePathsInObject(object)).to.deep.equal({
path: 'foo\\bar'
});
});
it('should handle arrays', function() {
m.chai.expect(utils.hideAbsolutePathsInObject({
foo: 'foo',
bar: [ {
path: 'C:\\foo\\bar\\baz'
}, {
path: 'C:\\foo\\bar\\baz'
}, {
path: 'C:\\foo\\bar\\baz'
} ]
})).to.deep.equal({
foo: 'foo',
bar: [ {
path: 'baz'
}, {
path: 'baz'
}, {
path: 'baz'
} ]
});
});
});
});
});