refactor(@angular/cli): remove dependency on universal-analytics

With this change we remove the dependency on the unmaintained universal-analytics package. We also solve several package deprecation warnings when creating a new workspace.

Closes #16952
This commit is contained in:
Alan Agius 2021-03-01 13:03:14 +01:00 committed by Charles
parent 598190ec73
commit dbce2a3f28
9 changed files with 361 additions and 334 deletions

View File

@ -122,7 +122,6 @@
"@types/semver": "^7.0.0",
"@types/speed-measure-webpack-plugin": "^1.3.0",
"@types/text-table": "^0.2.1",
"@types/universal-analytics": "^0.4.2",
"@types/uuid": "^8.0.0",
"@types/webpack": "^4.41.22",
"@types/webpack-dev-server": "^3.1.7",

View File

@ -76,7 +76,6 @@ ts_library(
"@npm//@types/resolve",
"@npm//@types/rimraf",
"@npm//@types/semver",
"@npm//@types/universal-analytics",
"@npm//@types/uuid",
"@npm//ansi-colors",
"@npm//jsonc-parser",

View File

@ -13,27 +13,14 @@ import { AngularWorkspace, getWorkspaceRaw } from '../../utilities/config';
import { writeErrorToLogFile } from '../../utilities/log-file';
import { findWorkspaceFile } from '../../utilities/project';
export { VERSION, Version } from '../../models/version';
const debugEnv = process.env['NG_DEBUG'];
const isDebug =
debugEnv !== undefined &&
debugEnv !== '0' &&
debugEnv.toLowerCase() !== 'false';
// Same structure as used in framework packages
export class Version {
public readonly major: string;
public readonly minor: string;
public readonly patch: string;
constructor(public readonly full: string) {
this.major = full.split('.')[0];
this.minor = full.split('.')[1];
this.patch = full.split('.').slice(2).join('.');
}
}
export const VERSION = new Version(require('../../package.json').version);
// tslint:disable: no-console
export default async function(options: { testing?: boolean; cliArgs: string[] }) {
// This node version check ensures that the requirements of the project instance of the CLI are met

View File

@ -0,0 +1,317 @@
/**
* @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
*
*/
import { analytics } from '@angular-devkit/core';
import { execSync } from 'child_process';
import * as debug from 'debug';
import * as https from 'https';
import * as os from 'os';
import * as querystring from 'querystring';
import { VERSION } from './version';
interface BaseParameters extends analytics.CustomDimensionsAndMetricsOptions {
[key: string]: string | number | boolean | undefined | (string | number | boolean | undefined)[];
}
interface ScreenviewParameters extends BaseParameters {
/** Screen Name */
cd?: string;
/** Application Name */
an?: string;
/** Application Version */
av?: string;
/** Application ID */
aid?: string;
/** Application Installer ID */
aiid?: string;
}
interface TimingParameters extends BaseParameters {
/** User timing category */
utc?: string;
/** User timing variable name */
utv?: string;
/** User timing time */
utt?: string | number;
/** User timing label */
utl?: string;
}
interface PageviewParameters extends BaseParameters {
/**
* Document Path
* The path portion of the page URL. Should begin with '/'.
*/
dp?: string;
/** Document Host Name */
dh?: string;
/** Document Title */
dt?: string;
/**
* Document location URL
* Use this parameter to send the full URL (document location) of the page on which content resides.
*/
dl?: string;
}
interface EventParameters extends BaseParameters {
/** Event Category */
ec: string;
/** Event Action */
ea: string;
/** Event Label */
el?: string;
/**
* Event Value
* Specifies the event value. Values must be non-negative.
*/
ev?: string | number;
/** Page Path */
p?: string;
/** Page */
dp?: string;
}
/**
* See: https://developers.google.com/analytics/devguides/collection/protocol/v1/devguide
*/
export class AnalyticsCollector implements analytics.Analytics {
private trackingEventsQueue: Record<string, string | number | boolean>[] = [];
private readonly parameters: Record<string, string | number | boolean> = {};
private readonly analyticsLogDebug = debug('ng:analytics:log');
constructor(
trackingId: string,
userId: string,
) {
// API Version
this.parameters['v'] = '1';
// User ID
this.parameters['cid'] = userId;
// Tracking
this.parameters['tid'] = trackingId;
this.parameters['ds'] = 'cli';
this.parameters['ua'] = _buildUserAgentString();
this.parameters['ul'] = _getLanguage();
// @angular/cli with version.
this.parameters['an'] = '@angular/cli';
this.parameters['av'] = VERSION.full;
// We use the application ID for the Node version. This should be "node v12.10.0".
const nodeVersion = `node ${process.version}`;
this.parameters['aid'] = nodeVersion;
// Custom dimentions
// We set custom metrics for values we care about.
this.parameters['cd' + analytics.NgCliAnalyticsDimensions.CpuCount] = os.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.
this.parameters['cd' + analytics.NgCliAnalyticsDimensions.CpuSpeed] = Math.floor(os.cpus()[0].speed);
this.parameters['cd' + analytics.NgCliAnalyticsDimensions.RamInGigabytes] = Math.round(os.totalmem() / (1024 * 1024 * 1024));
this.parameters['cd' + analytics.NgCliAnalyticsDimensions.NodeVersion] = nodeVersion;
}
event(ec: string, ea: string, options: analytics.EventOptions = {}): void {
const { label: el, value: ev, metrics, dimensions } = options;
this.addToQueue('event', { ec, ea, el, ev, metrics, dimensions });
}
pageview(dp: string, options: analytics.PageviewOptions = {}): void {
const { hostname: dh, title: dt, metrics, dimensions } = options;
this.addToQueue('pageview', { dp, dh, dt, metrics, dimensions });
}
timing(utc: string, utv: string, utt: string | number, options: analytics.TimingOptions = {}): void {
const { label: utl, metrics, dimensions } = options;
this.addToQueue('timing', { utc, utv, utt, utl, metrics, dimensions });
}
screenview(cd: string, an: string, options: analytics.ScreenviewOptions = {}): void {
const { appVersion: av, appId: aid, appInstallerId: aiid, metrics, dimensions } = options;
this.addToQueue('screenview', { cd, an, av, aid, aiid, metrics, dimensions });
}
async flush(): Promise<void> {
const pending = this.trackingEventsQueue.length;
this.analyticsLogDebug(`flush queue size: ${pending}`);
if (!pending) {
return;
}
// The below is needed so that if flush is called multiple times,
// we don't report the same event multiple times.
const pendingTrackingEvents = this.trackingEventsQueue;
this.trackingEventsQueue = [];
try {
await this.send(pendingTrackingEvents);
} catch (error) {
// Failure to report analytics shouldn't crash the CLI.
this.analyticsLogDebug('send error: %j', error);
}
}
private addToQueue(eventType: 'event', parameters: EventParameters): void;
private addToQueue(eventType: 'pageview', parameters: PageviewParameters): void;
private addToQueue(eventType: 'timing', parameters: TimingParameters): void;
private addToQueue(eventType: 'screenview', parameters: ScreenviewParameters): void;
private addToQueue(eventType: 'event' | 'pageview' | 'timing' | 'screenview', parameters: BaseParameters): void {
const { metrics, dimensions, ...restParameters } = parameters;
const data = {
...this.parameters,
...restParameters,
...this.customVariables({ metrics, dimensions }),
t: eventType,
};
this.analyticsLogDebug('add event to queue: %j', data);
this.trackingEventsQueue.push(data);
}
private async send(data: Record<string, string | number | boolean>[]): Promise<void> {
this.analyticsLogDebug('send event: %j', data);
return new Promise<void>((resolve, reject) => {
const request = https.request({
host: 'www.google-analytics.com',
method: 'POST',
path: data.length > 1 ? '/batch' : '/collect',
}, response => {
if (response.statusCode !== 200) {
reject(new Error(`Analytics reporting failed with status code: ${response.statusCode}.`));
return;
}
});
request.on('error', reject);
const queryParameters = data.map(p => querystring.stringify(p)).join('\n');
request.write(queryParameters);
request.end(resolve);
});
}
/**
* Creates the dimension and metrics variables to add to the queue.
* @private
*/
private customVariables(options: analytics.CustomDimensionsAndMetricsOptions): Record<string, string | number | boolean> {
const additionals: Record<string, string | number | boolean> = {};
const { dimensions, metrics } = options;
dimensions?.forEach((v, i) => additionals[`cd${i}`] = v);
metrics?.forEach((v, i) => additionals[`cm${i}`] = v);
return additionals;
}
}
// 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: Readonly<{ [os: string]: { [release: string]: string } }> = {
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',
// We stop here because we try to math out the version for anything greater than 10, and it
// works. Those versions are standardized using a calculation now.
},
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. This gets sent to Analytics so it shows the proper OS version.
* @private
*/
function _buildUserAgentString() {
switch (os.platform()) {
case 'darwin': {
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()})`;
}
case 'win32':
return `(Windows NT ${os.release()})`;
case 'linux':
return `(X11; Linux i686; ${os.release()}; ${os.cpus()[0].model})`;
default:
return os.platform() + ' ' + os.release();
}
}
/**
* 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() ||
'??'
); // ¯\_(ツ)_/¯
}
/**
* Attempt to get the Windows Language Code string.
* @private
*/
function _getWindowsLanguageCode(): string | undefined {
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 execSync('wmic.exe os get locale').toString().trim();
} catch { }
return undefined;
}

View File

@ -5,22 +5,17 @@
* 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
*/
import { analytics, json, tags } from '@angular-devkit/core';
import * as child_process from 'child_process';
import { json, tags } from '@angular-devkit/core';
import * as debug from 'debug';
import * as inquirer from 'inquirer';
import * as os from 'os';
import * as ua from 'universal-analytics';
import { v4 as uuidV4 } from 'uuid';
import { colors } from '../utilities/color';
import { getWorkspace, getWorkspaceRaw } from '../utilities/config';
import { isTTY } from '../utilities/tty';
import { AnalyticsCollector } from './analytics-collector';
// 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: string;
export const AnalyticsProperties = {
@ -56,7 +51,7 @@ export const analyticsPackageSafelist = [
'@schematics/update',
];
export function isPackageNameSafeForAnalytics(name: string) {
export function isPackageNameSafeForAnalytics(name: string): boolean {
return analyticsPackageSafelist.some(pattern => {
if (typeof pattern == 'string') {
return pattern === name;
@ -66,284 +61,6 @@ export function isPackageNameSafeForAnalytics(name: string) {
});
}
/**
* Attempt to get the Windows Language Code string.
* @private
*/
function _getWindowsLanguageCode(): string | undefined {
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() {
const name = process.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: { [os: string]: { [release: string]: string } } = {
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',
// We stop here because we try to math out the version for anything greater than 10, and it
// works. Those versions are standardized using a calculation now.
},
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.
*/
export class UniversalAnalytics implements analytics.Analytics {
private _ua: ua.Visitor;
private _dirty = false;
private _metrics: (string | number)[] = [];
private _dimensions: (string | number)[] = [];
/**
* @param trackingId The Google Analytics ID.
* @param uid A User ID.
*/
constructor(trackingId: string, uid: string) {
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[analytics.NgCliAnalyticsDimensions.CpuCount] = _getCpuCount();
this._dimensions[analytics.NgCliAnalyticsDimensions.CpuSpeed] = _getCpuSpeed();
this._dimensions[analytics.NgCliAnalyticsDimensions.RamInGigabytes] = _getRamSize();
this._dimensions[analytics.NgCliAnalyticsDimensions.NodeVersion] = _getNumericNodeVersion();
}
/**
* Creates the dimension and metrics variables to pass to universal-analytics.
* @private
*/
private _customVariables(options: analytics.CustomDimensionsAndMetricsOptions) {
const additionals: { [key: string]: boolean | number | string } = {};
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: string, ea: string, options: analytics.EventOptions = {}) {
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: string, an: string, options: analytics.ScreenviewOptions = {}) {
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: string, options: analytics.PageviewOptions = {}) {
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: string, utv: string, utt: string | number, options: analytics.TimingOptions = {}) {
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(): Promise<void> {
if (!this._dirty) {
return Promise.resolve();
}
this._dirty = false;
return new Promise(resolve => this._ua.send(resolve));
}
}
/**
* 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.
@ -407,12 +124,12 @@ export async function promptGlobalAnalytics(force = false) {
console.log('');
// Send back a ping with the user `optin`.
const ua = new UniversalAnalytics(AnalyticsProperties.AngularCliDefault, 'optin');
const ua = new AnalyticsCollector(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(AnalyticsProperties.AngularCliDefault, 'optout');
const ua = new AnalyticsCollector(AnalyticsProperties.AngularCliDefault, 'optout');
ua.pageview('/telemetry/optout');
await ua.flush();
}
@ -466,12 +183,12 @@ export async function promptProjectAnalytics(force = false): Promise<boolean> {
console.log('');
// Send back a ping with the user `optin`.
const ua = new UniversalAnalytics(AnalyticsProperties.AngularCliDefault, 'optin');
const ua = new AnalyticsCollector(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(AnalyticsProperties.AngularCliDefault, 'optout');
const ua = new AnalyticsCollector(AnalyticsProperties.AngularCliDefault, 'optout');
ua.pageview('/telemetry/project/optout');
await ua.flush();
}
@ -491,7 +208,7 @@ export async function hasGlobalAnalyticsConfiguration(): Promise<boolean> {
if (analyticsConfig !== null && analyticsConfig !== undefined) {
return true;
}
} catch {}
} catch { }
return false;
}
@ -502,7 +219,7 @@ export async function hasGlobalAnalyticsConfiguration(): Promise<boolean> {
*
* If any problem happens, it is considered the user has been opting out of analytics.
*/
export async function getGlobalAnalytics(): Promise<UniversalAnalytics | undefined> {
export async function getGlobalAnalytics(): Promise<AnalyticsCollector | undefined> {
analyticsDebug('getGlobalAnalytics');
const propertyId = AnalyticsProperties.AngularCliDefault;
@ -515,7 +232,7 @@ export async function getGlobalAnalytics(): Promise<UniversalAnalytics | undefin
if (process.env['NG_CLI_ANALYTICS'] === 'ci') {
analyticsDebug('Running in CI mode');
return new UniversalAnalytics(propertyId, 'ci');
return new AnalyticsCollector(propertyId, 'ci');
}
}
@ -550,7 +267,7 @@ export async function getGlobalAnalytics(): Promise<UniversalAnalytics | undefin
return undefined;
}
return new UniversalAnalytics(propertyId, uid);
return new AnalyticsCollector(propertyId, uid);
}
} catch (err) {
analyticsDebug('Error happened during reading of analytics config: %s', err.message);
@ -569,18 +286,18 @@ export async function hasWorkspaceAnalyticsConfiguration(): Promise<boolean> {
if (analyticsConfig !== undefined) {
return true;
}
} catch {}
} catch { }
return false;
}
/**
* Get the workspace analytics object for the user. This returns an instance of UniversalAnalytics,
* Get the workspace analytics object for the user. This returns an instance of AnalyticsCollector,
* or undefined if analytics are disabled.
*
* If any problem happens, it is considered the user has been opting out of analytics.
*/
export async function getWorkspaceAnalytics(): Promise<UniversalAnalytics | undefined> {
export async function getWorkspaceAnalytics(): Promise<AnalyticsCollector | undefined> {
analyticsDebug('getWorkspaceAnalytics');
try {
const globalWorkspace = await getWorkspace('local');
@ -608,7 +325,7 @@ export async function getWorkspaceAnalytics(): Promise<UniversalAnalytics | unde
return undefined;
}
return new UniversalAnalytics(AnalyticsProperties.AngularCliDefault, uid);
return new AnalyticsCollector(AnalyticsProperties.AngularCliDefault, uid);
}
} catch (err) {
analyticsDebug('Error happened during reading of analytics config: %s', err.message);
@ -622,7 +339,7 @@ export async function getWorkspaceAnalytics(): Promise<UniversalAnalytics | unde
* Return the usage analytics sharing setting, which is either a property string (GA-XXXXXXX-XX),
* or undefined if no sharing.
*/
export async function getSharedAnalytics(): Promise<UniversalAnalytics | undefined> {
export async function getSharedAnalytics(): Promise<AnalyticsCollector | undefined> {
analyticsDebug('getSharedAnalytics');
const envVarName = 'NG_CLI_ANALYTICS_SHARE';
@ -644,7 +361,7 @@ export async function getSharedAnalytics(): Promise<UniversalAnalytics | undefin
} else {
analyticsDebug('Analytics sharing info: %j', analyticsConfig);
return new UniversalAnalytics(analyticsConfig.tracking, analyticsConfig.uuid);
return new AnalyticsCollector(analyticsConfig.tracking, analyticsConfig.uuid);
}
} catch (err) {
analyticsDebug('Error happened during reading of analytics sharing config: %s', err.message);

View File

@ -244,13 +244,14 @@ export async function runCommand(
// Flush on an interval (if the event loop is waiting).
let analyticsFlushPromise = Promise.resolve();
setInterval(() => {
const analyticsFlushInterval = setInterval(() => {
analyticsFlushPromise = analyticsFlushPromise.then(() => analytics.flush());
}, 1000);
const result = await command.validateAndRun(parsedOptions);
// Flush one last time.
clearInterval(analyticsFlushInterval);
await analyticsFlushPromise.then(() => analytics.flush());
return result;

View File

@ -0,0 +1,22 @@
/**
* @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
*/
// Same structure as used in framework packages
export class Version {
public readonly major: string;
public readonly minor: string;
public readonly patch: string;
constructor(public readonly full: string) {
this.major = full.split('.')[0];
this.minor = full.split('.')[1];
this.patch = full.split('.').slice(2).join('.');
}
}
export const VERSION = new Version(require('../package.json').version);

View File

@ -45,7 +45,6 @@
"rimraf": "3.0.2",
"semver": "7.3.4",
"symbol-observable": "3.0.0",
"universal-analytics": "0.4.23",
"uuid": "8.3.2"
},
"ng-update": {

View File

@ -2009,11 +2009,6 @@
dependencies:
source-map "^0.6.1"
"@types/universal-analytics@^0.4.2":
version "0.4.4"
resolved "https://registry.yarnpkg.com/@types/universal-analytics/-/universal-analytics-0.4.4.tgz#496a52b92b599a0112bec7c12414062de6ea8449"
integrity sha512-9g3F0SGxVr4UDd6y07bWtFnkpSSX1Ake7U7AGHgSFrwM6pF53/fV85bfxT2JLWS/3sjLCcyzoYzQlCxpkVo7wA==
"@types/uuid@^8.0.0":
version "8.3.0"
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.0.tgz#215c231dff736d5ba92410e6d602050cce7e273f"
@ -12643,15 +12638,6 @@ unique-slug@^2.0.0:
dependencies:
imurmurhash "^0.1.4"
universal-analytics@0.4.23:
version "0.4.23"
resolved "https://registry.yarnpkg.com/universal-analytics/-/universal-analytics-0.4.23.tgz#d915e676850c25c4156762471bdd7cf2eaaca8ac"
integrity sha512-lgMIH7XBI6OgYn1woDEmxhGdj8yDefMKg7GkWdeATAlQZFrMrNyxSkpDzY57iY0/6fdlzTbBV03OawvvzG+q7A==
dependencies:
debug "^4.1.1"
request "^2.88.2"
uuid "^3.0.0"
universal-user-agent@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-3.0.0.tgz#4cc88d68097bffd7ac42e3b7c903e7481424b4b9"
@ -12784,7 +12770,7 @@ uuid@8.3.2:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
uuid@^3.0.0, uuid@^3.3.2, uuid@^3.4.0:
uuid@^3.3.2, uuid@^3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==