| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529 |
- "use strict";
- Object.defineProperty(exports, "__esModule", { value: true });
- /**
- * @license
- * Copyright Google Inc. All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.io/license
- */
- const core_1 = require("@angular-devkit/core");
- const child_process = require("child_process");
- const debug = require("debug");
- const fs_1 = require("fs");
- const inquirer = require("inquirer");
- const os = require("os");
- const ua = require("universal-analytics");
- const uuid_1 = require("uuid");
- const color_1 = require("../utilities/color");
- const config_1 = require("../utilities/config");
- const tty_1 = require("../utilities/tty");
- // tslint:disable: no-console
- const analyticsDebug = debug('ng:analytics'); // Generate analytics, including settings and users.
- const analyticsLogDebug = debug('ng:analytics:log'); // Actual logs of events.
- const BYTES_PER_GIGABYTES = 1024 * 1024 * 1024;
- let _defaultAngularCliPropertyCache;
- exports.AnalyticsProperties = {
- AngularCliProd: 'UA-8594346-29',
- AngularCliStaging: 'UA-8594346-32',
- get AngularCliDefault() {
- if (_defaultAngularCliPropertyCache) {
- return _defaultAngularCliPropertyCache;
- }
- const v = require('../package.json').version;
- // The logic is if it's a full version then we should use the prod GA property.
- if (/^\d+\.\d+\.\d+$/.test(v) && v !== '0.0.0') {
- _defaultAngularCliPropertyCache = exports.AnalyticsProperties.AngularCliProd;
- }
- else {
- _defaultAngularCliPropertyCache = exports.AnalyticsProperties.AngularCliStaging;
- }
- return _defaultAngularCliPropertyCache;
- },
- };
- /**
- * This is the ultimate safelist for checking if a package name is safe to report to analytics.
- */
- exports.analyticsPackageSafelist = [
- /^@angular\//,
- /^@angular-devkit\//,
- /^@ngtools\//,
- '@schematics/angular',
- '@schematics/schematics',
- '@schematics/update',
- ];
- function isPackageNameSafeForAnalytics(name) {
- return exports.analyticsPackageSafelist.some(pattern => {
- if (typeof pattern == 'string') {
- return pattern === name;
- }
- else {
- return pattern.test(name);
- }
- });
- }
- exports.isPackageNameSafeForAnalytics = isPackageNameSafeForAnalytics;
- /**
- * Attempt to get the Windows Language Code string.
- * @private
- */
- function _getWindowsLanguageCode() {
- if (!os.platform().startsWith('win')) {
- return undefined;
- }
- try {
- // This is true on Windows XP, 7, 8 and 10 AFAIK. Would return empty string or fail if it
- // doesn't work.
- return child_process
- .execSync('wmic.exe os get locale')
- .toString()
- .trim();
- }
- catch (_) { }
- return undefined;
- }
- /**
- * Get a language code.
- * @private
- */
- function _getLanguage() {
- // Note: Windows does not expose the configured language by default.
- return (process.env.LANG || // Default Unix env variable.
- process.env.LC_CTYPE || // For C libraries. Sometimes the above isn't set.
- process.env.LANGSPEC || // For Windows, sometimes this will be set (not always).
- _getWindowsLanguageCode() ||
- '??'); // ¯\_(ツ)_/¯
- }
- /**
- * Return the number of CPUs.
- * @private
- */
- function _getCpuCount() {
- const cpus = os.cpus();
- // Return "(count)x(average speed)".
- return cpus.length;
- }
- /**
- * Get the first CPU's speed. It's very rare to have multiple CPUs of different speed (in most
- * non-ARM configurations anyway), so that's all we care about.
- * @private
- */
- function _getCpuSpeed() {
- const cpus = os.cpus();
- return Math.floor(cpus[0].speed);
- }
- /**
- * Get the amount of memory, in megabytes.
- * @private
- */
- function _getRamSize() {
- // Report in gigabytes (or closest). Otherwise it's too much noise.
- return Math.round(os.totalmem() / BYTES_PER_GIGABYTES);
- }
- /**
- * Get the Node name and version. This returns a string like "Node 10.11", or "io.js 3.5".
- * @private
- */
- function _getNodeVersion() {
- // We use any here because p.release is a new Node construct in Node 10 (and our typings are the
- // minimal version of Node we support).
- const p = process; // tslint:disable-line:no-any
- const name = (typeof p.release == 'object' && typeof p.release.name == 'string' && p.release.name) ||
- process.argv0;
- return name + ' ' + process.version;
- }
- /**
- * Get a numerical MAJOR.MINOR version of node. We report this as a metric.
- * @private
- */
- function _getNumericNodeVersion() {
- const p = process.version;
- const m = p.match(/\d+\.\d+/);
- return (m && m[0] && parseFloat(m[0])) || 0;
- }
- // These are just approximations of UA strings. We just try to fool Google Analytics to give us the
- // data we want.
- // See https://developers.whatismybrowser.com/useragents/
- const osVersionMap = {
- darwin: {
- '1.3.1': '10_0_4',
- '1.4.1': '10_1_0',
- '5.1': '10_1_1',
- '5.2': '10_1_5',
- '6.0.1': '10_2',
- '6.8': '10_2_8',
- '7.0': '10_3_0',
- '7.9': '10_3_9',
- '8.0': '10_4_0',
- '8.11': '10_4_11',
- '9.0': '10_5_0',
- '9.8': '10_5_8',
- '10.0': '10_6_0',
- '10.8': '10_6_8',
- },
- win32: {
- '6.3.9600': 'Windows 8.1',
- '6.2.9200': 'Windows 8',
- '6.1.7601': 'Windows 7 SP1',
- '6.1.7600': 'Windows 7',
- '6.0.6002': 'Windows Vista SP2',
- '6.0.6000': 'Windows Vista',
- '5.1.2600': 'Windows XP',
- },
- };
- /**
- * Build a fake User Agent string for OSX. This gets sent to Analytics so it shows the proper OS,
- * versions and others.
- * @private
- */
- function _buildUserAgentStringForOsx() {
- let v = osVersionMap.darwin[os.release()];
- if (!v) {
- // Remove 4 to tie Darwin version to OSX version, add other info.
- const x = parseFloat(os.release());
- if (x > 10) {
- v = `10_` + (x - 4).toString().replace('.', '_');
- }
- }
- const cpuModel = os.cpus()[0].model.match(/^[a-z]+/i);
- const cpu = cpuModel ? cpuModel[0] : os.cpus()[0].model;
- return `(Macintosh; ${cpu} Mac OS X ${v || os.release()})`;
- }
- /**
- * Build a fake User Agent string for Windows. This gets sent to Analytics so it shows the proper
- * OS, versions and others.
- * @private
- */
- function _buildUserAgentStringForWindows() {
- return `(Windows NT ${os.release()})`;
- }
- /**
- * Build a fake User Agent string for Linux. This gets sent to Analytics so it shows the proper OS,
- * versions and others.
- * @private
- */
- function _buildUserAgentStringForLinux() {
- return `(X11; Linux i686; ${os.release()}; ${os.cpus()[0].model})`;
- }
- /**
- * Build a fake User Agent string. This gets sent to Analytics so it shows the proper OS version.
- * @private
- */
- function _buildUserAgentString() {
- switch (os.platform()) {
- case 'darwin':
- return _buildUserAgentStringForOsx();
- case 'win32':
- return _buildUserAgentStringForWindows();
- case 'linux':
- return _buildUserAgentStringForLinux();
- default:
- return os.platform() + ' ' + os.release();
- }
- }
- /**
- * Implementation of the Analytics interface for using `universal-analytics` package.
- */
- class UniversalAnalytics {
- /**
- * @param trackingId The Google Analytics ID.
- * @param uid A User ID.
- */
- constructor(trackingId, uid) {
- this._dirty = false;
- this._metrics = [];
- this._dimensions = [];
- this._ua = ua(trackingId, uid, {
- enableBatching: true,
- batchSize: 5,
- });
- // Add persistent params for appVersion.
- this._ua.set('ds', 'cli');
- this._ua.set('ua', _buildUserAgentString());
- this._ua.set('ul', _getLanguage());
- // @angular/cli with version.
- this._ua.set('an', require('../package.json').name);
- this._ua.set('av', require('../package.json').version);
- // We use the application ID for the Node version. This should be "node 10.10.0".
- // We also use a custom metrics, but
- this._ua.set('aid', _getNodeVersion());
- // We set custom metrics for values we care about.
- this._dimensions[core_1.analytics.NgCliAnalyticsDimensions.CpuCount] = _getCpuCount();
- this._dimensions[core_1.analytics.NgCliAnalyticsDimensions.CpuSpeed] = _getCpuSpeed();
- this._dimensions[core_1.analytics.NgCliAnalyticsDimensions.RamInGigabytes] = _getRamSize();
- this._dimensions[core_1.analytics.NgCliAnalyticsDimensions.NodeVersion] = _getNumericNodeVersion();
- }
- /**
- * Creates the dimension and metrics variables to pass to universal-analytics.
- * @private
- */
- _customVariables(options) {
- const additionals = {};
- this._dimensions.forEach((v, i) => (additionals['cd' + i] = v));
- (options.dimensions || []).forEach((v, i) => (additionals['cd' + i] = v));
- this._metrics.forEach((v, i) => (additionals['cm' + i] = v));
- (options.metrics || []).forEach((v, i) => (additionals['cm' + i] = v));
- return additionals;
- }
- event(ec, ea, options = {}) {
- const vars = this._customVariables(options);
- analyticsLogDebug('event ec=%j, ea=%j, %j', ec, ea, vars);
- const { label: el, value: ev } = options;
- this._dirty = true;
- this._ua.event({ ec, ea, el, ev, ...vars });
- }
- screenview(cd, an, options = {}) {
- const vars = this._customVariables(options);
- analyticsLogDebug('screenview cd=%j, an=%j, %j', cd, an, vars);
- const { appVersion: av, appId: aid, appInstallerId: aiid } = options;
- this._dirty = true;
- this._ua.screenview({ cd, an, av, aid, aiid, ...vars });
- }
- pageview(dp, options = {}) {
- const vars = this._customVariables(options);
- analyticsLogDebug('pageview dp=%j, %j', dp, vars);
- const { hostname: dh, title: dt } = options;
- this._dirty = true;
- this._ua.pageview({ dp, dh, dt, ...vars });
- }
- timing(utc, utv, utt, options = {}) {
- const vars = this._customVariables(options);
- analyticsLogDebug('timing utc=%j, utv=%j, utl=%j, %j', utc, utv, utt, vars);
- const { label: utl } = options;
- this._dirty = true;
- this._ua.timing({ utc, utv, utt, utl, ...vars });
- }
- flush() {
- if (!this._dirty) {
- return Promise.resolve();
- }
- this._dirty = false;
- return new Promise(resolve => this._ua.send(resolve));
- }
- }
- exports.UniversalAnalytics = UniversalAnalytics;
- /**
- * Set analytics settings. This does not work if the user is not inside a project.
- * @param level Which config to use. "global" for user-level, and "local" for project-level.
- * @param value Either a user ID, true to generate a new User ID, or false to disable analytics.
- */
- function setAnalyticsConfig(level, value) {
- analyticsDebug('setting %s level analytics to: %s', level, value);
- const [config, configPath] = config_1.getWorkspaceRaw(level);
- if (!config || !configPath) {
- throw new Error(`Could not find ${level} workspace.`);
- }
- const configValue = config.value;
- const cli = configValue['cli'] || (configValue['cli'] = {});
- if (!core_1.json.isJsonObject(cli)) {
- throw new Error(`Invalid config found at ${configPath}. CLI should be an object.`);
- }
- if (value === true) {
- value = uuid_1.v4();
- }
- cli['analytics'] = value;
- const output = JSON.stringify(configValue, null, 2);
- fs_1.writeFileSync(configPath, output);
- analyticsDebug('done');
- }
- exports.setAnalyticsConfig = setAnalyticsConfig;
- /**
- * Prompt the user for usage gathering permission.
- * @param force Whether to ask regardless of whether or not the user is using an interactive shell.
- * @return Whether or not the user was shown a prompt.
- */
- async function promptGlobalAnalytics(force = false) {
- analyticsDebug('prompting global analytics.');
- if (force || tty_1.isTTY()) {
- const answers = await inquirer.prompt([
- {
- type: 'confirm',
- name: 'analytics',
- message: core_1.tags.stripIndents `
- Would you like to share anonymous usage data with the Angular Team at Google under
- Google’s Privacy Policy at https://policies.google.com/privacy? For more details and
- how to change this setting, see http://angular.io/analytics.
- `,
- default: false,
- },
- ]);
- setAnalyticsConfig('global', answers.analytics);
- if (answers.analytics) {
- console.log('');
- console.log(core_1.tags.stripIndent `
- Thank you for sharing anonymous usage data. If you change your mind, the following
- command will disable this feature entirely:
- ${color_1.colors.yellow('ng analytics off')}
- `);
- console.log('');
- // Send back a ping with the user `optin`.
- const ua = new UniversalAnalytics(exports.AnalyticsProperties.AngularCliDefault, 'optin');
- ua.pageview('/telemetry/optin');
- await ua.flush();
- }
- else {
- // Send back a ping with the user `optout`. This is the only thing we send.
- const ua = new UniversalAnalytics(exports.AnalyticsProperties.AngularCliDefault, 'optout');
- ua.pageview('/telemetry/optout');
- await ua.flush();
- }
- return true;
- }
- else {
- analyticsDebug('Either STDOUT or STDIN are not TTY and we skipped the prompt.');
- }
- return false;
- }
- exports.promptGlobalAnalytics = promptGlobalAnalytics;
- /**
- * Prompt the user for usage gathering permission for the local project. Fails if there is no
- * local workspace.
- * @param force Whether to ask regardless of whether or not the user is using an interactive shell.
- * @return Whether or not the user was shown a prompt.
- */
- async function promptProjectAnalytics(force = false) {
- analyticsDebug('prompting user');
- const [config, configPath] = config_1.getWorkspaceRaw('local');
- if (!config || !configPath) {
- throw new Error(`Could not find a local workspace. Are you in a project?`);
- }
- if (force || tty_1.isTTY()) {
- const answers = await inquirer.prompt([
- {
- type: 'confirm',
- name: 'analytics',
- message: core_1.tags.stripIndents `
- Would you like to share anonymous usage data about this project with the Angular Team at
- Google under Google’s Privacy Policy at https://policies.google.com/privacy? For more
- details and how to change this setting, see http://angular.io/analytics.
- `,
- default: false,
- },
- ]);
- setAnalyticsConfig('local', answers.analytics);
- if (answers.analytics) {
- console.log('');
- console.log(core_1.tags.stripIndent `
- Thank you for sharing anonymous usage data. Would you change your mind, the following
- command will disable this feature entirely:
- ${color_1.colors.yellow('ng analytics project off')}
- `);
- console.log('');
- // Send back a ping with the user `optin`.
- const ua = new UniversalAnalytics(exports.AnalyticsProperties.AngularCliDefault, 'optin');
- ua.pageview('/telemetry/project/optin');
- await ua.flush();
- }
- else {
- // Send back a ping with the user `optout`. This is the only thing we send.
- const ua = new UniversalAnalytics(exports.AnalyticsProperties.AngularCliDefault, 'optout');
- ua.pageview('/telemetry/project/optout');
- await ua.flush();
- }
- return true;
- }
- return false;
- }
- exports.promptProjectAnalytics = promptProjectAnalytics;
- function hasGlobalAnalyticsConfiguration() {
- try {
- const globalWorkspace = config_1.getWorkspace('global');
- const analyticsConfig = globalWorkspace && globalWorkspace.getCli() && globalWorkspace.getCli()['analytics'];
- if (analyticsConfig !== null && analyticsConfig !== undefined) {
- return true;
- }
- }
- catch (_a) { }
- return false;
- }
- exports.hasGlobalAnalyticsConfiguration = hasGlobalAnalyticsConfiguration;
- /**
- * Get the global analytics object for the user. This returns an instance of UniversalAnalytics,
- * or undefined if analytics are disabled.
- *
- * If any problem happens, it is considered the user has been opting out of analytics.
- */
- function getGlobalAnalytics() {
- analyticsDebug('getGlobalAnalytics');
- const propertyId = exports.AnalyticsProperties.AngularCliDefault;
- if ('NG_CLI_ANALYTICS' in process.env) {
- if (process.env['NG_CLI_ANALYTICS'] == 'false' || process.env['NG_CLI_ANALYTICS'] == '') {
- analyticsDebug('NG_CLI_ANALYTICS is false');
- return undefined;
- }
- if (process.env['NG_CLI_ANALYTICS'] === 'ci') {
- analyticsDebug('Running in CI mode');
- return new UniversalAnalytics(propertyId, 'ci');
- }
- }
- // If anything happens we just keep the NOOP analytics.
- try {
- const globalWorkspace = config_1.getWorkspace('global');
- const analyticsConfig = globalWorkspace && globalWorkspace.getCli() && globalWorkspace.getCli()['analytics'];
- analyticsDebug('Client Analytics config found: %j', analyticsConfig);
- if (analyticsConfig === false) {
- analyticsDebug('Analytics disabled. Ignoring all analytics.');
- return undefined;
- }
- else if (analyticsConfig === undefined || analyticsConfig === null) {
- analyticsDebug('Analytics settings not found. Ignoring all analytics.');
- // globalWorkspace can be null if there is no file. analyticsConfig would be null in this
- // case. Since there is no file, the user hasn't answered and the expected return value is
- // undefined.
- return undefined;
- }
- else {
- let uid = undefined;
- if (typeof analyticsConfig == 'string') {
- uid = analyticsConfig;
- }
- else if (typeof analyticsConfig == 'object' && typeof analyticsConfig['uid'] == 'string') {
- uid = analyticsConfig['uid'];
- }
- analyticsDebug('client id: %j', uid);
- if (uid == undefined) {
- return undefined;
- }
- return new UniversalAnalytics(propertyId, uid);
- }
- }
- catch (err) {
- analyticsDebug('Error happened during reading of analytics config: %s', err.message);
- return undefined;
- }
- }
- exports.getGlobalAnalytics = getGlobalAnalytics;
- /**
- * Return the usage analytics sharing setting, which is either a property string (GA-XXXXXXX-XX),
- * or undefined if no sharing.
- */
- function getSharedAnalytics() {
- analyticsDebug('getSharedAnalytics');
- const envVarName = 'NG_CLI_ANALYTICS_SHARE';
- if (envVarName in process.env) {
- if (process.env[envVarName] == 'false' || process.env[envVarName] == '') {
- analyticsDebug('NG_CLI_ANALYTICS is false');
- return undefined;
- }
- }
- // If anything happens we just keep the NOOP analytics.
- try {
- const globalWorkspace = config_1.getWorkspace('global');
- const analyticsConfig = globalWorkspace && globalWorkspace.getCli() && globalWorkspace.getCli()['analyticsSharing'];
- if (!analyticsConfig || !analyticsConfig.tracking || !analyticsConfig.uuid) {
- return undefined;
- }
- else {
- analyticsDebug('Analytics sharing info: %j', analyticsConfig);
- return new UniversalAnalytics(analyticsConfig.tracking, analyticsConfig.uuid);
- }
- }
- catch (err) {
- analyticsDebug('Error happened during reading of analytics sharing config: %s', err.message);
- return undefined;
- }
- }
- exports.getSharedAnalytics = getSharedAnalytics;
|