Alan Agius f6e1fd84a2 refactor(@angular-devkit/schematics-cli): move @schematics/schematics into this package
This change removes the need for the `@schematics/schematics` package and moves it inside `@angular-devkit/schematics-cli`.
2021-04-02 08:09:07 +02:00

370 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @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 { json, tags } from '@angular-devkit/core';
import * as debug from 'debug';
import * as inquirer from 'inquirer';
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.
let _defaultAngularCliPropertyCache: string;
export const AnalyticsProperties = {
AngularCliProd: 'UA-8594346-29',
AngularCliStaging: 'UA-8594346-32',
get AngularCliDefault(): string {
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 = AnalyticsProperties.AngularCliProd;
} else {
_defaultAngularCliPropertyCache = AnalyticsProperties.AngularCliStaging;
}
return _defaultAngularCliPropertyCache;
},
};
/**
* This is the ultimate safelist for checking if a package name is safe to report to analytics.
*/
export const analyticsPackageSafelist = [
/^@angular\//,
/^@angular-devkit\//,
/^@ngtools\//,
'@schematics/angular',
];
export function isPackageNameSafeForAnalytics(name: string): boolean {
return analyticsPackageSafelist.some(pattern => {
if (typeof pattern == 'string') {
return pattern === name;
} else {
return pattern.test(name);
}
});
}
/**
* 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.
*/
export function setAnalyticsConfig(level: 'global' | 'local', value: string | boolean) {
analyticsDebug('setting %s level analytics to: %s', level, value);
const [config, configPath] = getWorkspaceRaw(level);
if (!config || !configPath) {
throw new Error(`Could not find ${level} workspace.`);
}
const cli = config.get(['cli']);
if (cli !== undefined && !json.isJsonObject(cli as json.JsonValue)) {
throw new Error(`Invalid config found at ${configPath}. CLI should be an object.`);
}
if (value === true) {
value = uuidV4();
}
config.modify(['cli', 'analytics'], value);
config.save();
analyticsDebug('done');
}
/**
* 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.
*/
export async function promptGlobalAnalytics(force = false) {
analyticsDebug('prompting global analytics.');
if (force || isTTY()) {
const answers = await inquirer.prompt<{ analytics: boolean }>([
{
type: 'confirm',
name: 'analytics',
message: tags.stripIndents`
Would you like to share anonymous usage data with the Angular Team at Google under
Googles Privacy Policy at https://policies.google.com/privacy? For more details and
how to change this setting, see https://angular.io/analytics.
`,
default: false,
},
]);
setAnalyticsConfig('global', answers.analytics);
if (answers.analytics) {
console.log('');
console.log(tags.stripIndent`
Thank you for sharing anonymous usage data. If you change your mind, the following
command will disable this feature entirely:
${colors.yellow('ng analytics off')}
`);
console.log('');
// Send back a ping with the user `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 AnalyticsCollector(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;
}
/**
* 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.
*/
export async function promptProjectAnalytics(force = false): Promise<boolean> {
analyticsDebug('prompting user');
const [config, configPath] = getWorkspaceRaw('local');
if (!config || !configPath) {
throw new Error(`Could not find a local workspace. Are you in a project?`);
}
if (force || isTTY()) {
const answers = await inquirer.prompt<{ analytics: boolean }>([
{
type: 'confirm',
name: 'analytics',
message: tags.stripIndents`
Would you like to share anonymous usage data about this project with the Angular Team at
Google under Googles Privacy Policy at https://policies.google.com/privacy? For more
details and how to change this setting, see https://angular.io/analytics.
`,
default: false,
},
]);
setAnalyticsConfig('local', answers.analytics);
if (answers.analytics) {
console.log('');
console.log(tags.stripIndent`
Thank you for sharing anonymous usage data. Would you change your mind, the following
command will disable this feature entirely:
${colors.yellow('ng analytics project off')}
`);
console.log('');
// Send back a ping with the user `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 AnalyticsCollector(AnalyticsProperties.AngularCliDefault, 'optout');
ua.pageview('/telemetry/project/optout');
await ua.flush();
}
return true;
}
return false;
}
export async function hasGlobalAnalyticsConfiguration(): Promise<boolean> {
try {
const globalWorkspace = await getWorkspace('global');
const analyticsConfig: string | undefined | null | { uid?: string } =
globalWorkspace && globalWorkspace.getCli() && globalWorkspace.getCli()['analytics'];
if (analyticsConfig !== null && analyticsConfig !== undefined) {
return true;
}
} catch { }
return false;
}
/**
* 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.
*/
export async function getGlobalAnalytics(): Promise<AnalyticsCollector | undefined> {
analyticsDebug('getGlobalAnalytics');
const propertyId = 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 AnalyticsCollector(propertyId, 'ci');
}
}
// If anything happens we just keep the NOOP analytics.
try {
const globalWorkspace = await getWorkspace('global');
const analyticsConfig: string | undefined | null | { uid?: string } =
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: string | undefined = 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 AnalyticsCollector(propertyId, uid);
}
} catch (err) {
analyticsDebug('Error happened during reading of analytics config: %s', err.message);
return undefined;
}
}
export async function hasWorkspaceAnalyticsConfiguration(): Promise<boolean> {
try {
const globalWorkspace = await getWorkspace('local');
const analyticsConfig: string | undefined | null | { uid?: string } = globalWorkspace
&& globalWorkspace.getCli()
&& globalWorkspace.getCli()['analytics'];
if (analyticsConfig !== undefined) {
return true;
}
} catch { }
return false;
}
/**
* 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<AnalyticsCollector | undefined> {
analyticsDebug('getWorkspaceAnalytics');
try {
const globalWorkspace = await getWorkspace('local');
const analyticsConfig: string | undefined | null | { uid?: string } = globalWorkspace?.getCli()['analytics'];
analyticsDebug('Workspace 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.');
return undefined;
} else {
let uid: string | undefined = 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 AnalyticsCollector(AnalyticsProperties.AngularCliDefault, uid);
}
} catch (err) {
analyticsDebug('Error happened during reading of analytics config: %s', err.message);
return undefined;
}
}
/**
* 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<AnalyticsCollector | undefined> {
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 = await getWorkspace('global');
const analyticsConfig = globalWorkspace?.getCli()['analyticsSharing'];
if (!analyticsConfig || !analyticsConfig.tracking || !analyticsConfig.uuid) {
return undefined;
} else {
analyticsDebug('Analytics sharing info: %j', analyticsConfig);
return new AnalyticsCollector(analyticsConfig.tracking, analyticsConfig.uuid);
}
} catch (err) {
analyticsDebug('Error happened during reading of analytics sharing config: %s', err.message);
return undefined;
}
}