mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-15 18:13:38 +08:00
feat(@angular/cli): add ng analytics info
command
With this change we add a subcommand to `ng analytics`. This command can be used tp display analytics gathering and reporting configuration. Example: ``` $ ng analytics info Global setting: disabled Local setting: enabled Effective status: disabled ```
This commit is contained in:
parent
afafa5788f
commit
bb550436a4
@ -63,10 +63,11 @@ export function isPackageNameSafeForAnalytics(name: string): boolean {
|
||||
|
||||
/**
|
||||
* 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 global 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) {
|
||||
export function setAnalyticsConfig(global: boolean, value: string | boolean): void {
|
||||
const level = global ? 'global' : 'local';
|
||||
analyticsDebug('setting %s level analytics to: %s', level, value);
|
||||
const [config, configPath] = getWorkspaceRaw(level);
|
||||
if (!config || !configPath) {
|
||||
@ -79,6 +80,8 @@ export function setAnalyticsConfig(level: 'global' | 'local', value: string | bo
|
||||
throw new Error(`Invalid config found at ${configPath}. CLI should be an object.`);
|
||||
}
|
||||
|
||||
console.log(`Configured ${level} analytics to "${analyticsConfigValueToHumanFormat(value)}".`);
|
||||
|
||||
if (value === true) {
|
||||
value = uuidV4();
|
||||
}
|
||||
@ -89,6 +92,18 @@ export function setAnalyticsConfig(level: 'global' | 'local', value: string | bo
|
||||
analyticsDebug('done');
|
||||
}
|
||||
|
||||
export function analyticsConfigValueToHumanFormat(value: unknown): 'on' | 'off' | 'not set' | 'ci' {
|
||||
if (value === false) {
|
||||
return 'off';
|
||||
} else if (value === 'ci') {
|
||||
return 'ci';
|
||||
} else if (typeof value === 'string' || value === true) {
|
||||
return 'on';
|
||||
} else {
|
||||
return 'not set';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt the user for usage gathering permission.
|
||||
* @param force Whether to ask regardless of whether or not the user is using an interactive shell.
|
||||
@ -110,7 +125,7 @@ export async function promptGlobalAnalytics(force = false) {
|
||||
},
|
||||
]);
|
||||
|
||||
setAnalyticsConfig('global', answers.analytics);
|
||||
setAnalyticsConfig(true, answers.analytics);
|
||||
|
||||
if (answers.analytics) {
|
||||
console.log('');
|
||||
@ -169,7 +184,7 @@ export async function promptProjectAnalytics(force = false): Promise<boolean> {
|
||||
},
|
||||
]);
|
||||
|
||||
setAnalyticsConfig('local', answers.analytics);
|
||||
setAnalyticsConfig(false, answers.analytics);
|
||||
|
||||
if (answers.analytics) {
|
||||
console.log('');
|
||||
|
@ -43,6 +43,7 @@ export interface CommandContext {
|
||||
positional: string[];
|
||||
options: {
|
||||
help: boolean;
|
||||
jsonHelp: boolean;
|
||||
} & Record<string, unknown>;
|
||||
};
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ import { VersionCommandModule } from '../commands/version/cli';
|
||||
import { colors } from '../utilities/color';
|
||||
import { AngularWorkspace } from '../utilities/config';
|
||||
import { CommandContext, CommandModuleError, CommandScope } from './command-module';
|
||||
import { addCommandModuleToYargs, demandCommandFailureMessage } from './utilities/command';
|
||||
import { jsonHelpUsage } from './utilities/json-help';
|
||||
|
||||
const COMMANDS = [
|
||||
@ -75,6 +76,7 @@ export async function runCommand(
|
||||
positional: positional.map((v) => v.toString()),
|
||||
options: {
|
||||
help,
|
||||
jsonHelp,
|
||||
...rest,
|
||||
},
|
||||
},
|
||||
@ -90,23 +92,7 @@ export async function runCommand(
|
||||
}
|
||||
}
|
||||
|
||||
const commandModule = new CommandModule(context);
|
||||
const describe = jsonHelp ? commandModule.fullDescribe : commandModule.describe;
|
||||
|
||||
localYargs = localYargs.command({
|
||||
command: commandModule.command,
|
||||
aliases: 'aliases' in commandModule ? commandModule.aliases : undefined,
|
||||
describe:
|
||||
// We cannot add custom fields in help, such as long command description which is used in AIO.
|
||||
// Therefore, we get around this by adding a complex object as a string which we later parse when generating the help files.
|
||||
describe !== undefined && typeof describe === 'object'
|
||||
? JSON.stringify(describe)
|
||||
: describe,
|
||||
deprecated: 'deprecated' in commandModule ? commandModule.deprecated : undefined,
|
||||
builder: (argv) => commandModule.builder(argv) as yargs.Argv,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
handler: (args: any) => commandModule.handler(args),
|
||||
});
|
||||
localYargs = addCommandModuleToYargs(localYargs, CommandModule, context);
|
||||
}
|
||||
|
||||
if (jsonHelp) {
|
||||
@ -142,7 +128,7 @@ export async function runCommand(
|
||||
'deprecated: %s': colors.yellow('deprecated:') + ' %s',
|
||||
'Did you mean %s?': 'Unknown command. Did you mean %s?',
|
||||
})
|
||||
.demandCommand()
|
||||
.demandCommand(1, demandCommandFailureMessage)
|
||||
.recommendCommands()
|
||||
.version(false)
|
||||
.showHelpOnFail(false)
|
||||
|
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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 { Argv } from 'yargs';
|
||||
import { CommandContext, CommandModule, CommandModuleImplementation } from '../command-module';
|
||||
|
||||
export const demandCommandFailureMessage = `You need to specify a command before moving on. Use '--help' to view the available commands.`;
|
||||
|
||||
export function addCommandModuleToYargs<
|
||||
T,
|
||||
U extends Partial<CommandModuleImplementation> & {
|
||||
new (context: CommandContext): Partial<CommandModuleImplementation> & CommandModule;
|
||||
},
|
||||
>(localYargs: Argv<T>, commandModule: U, context: CommandContext): Argv<T> {
|
||||
const cmd = new commandModule(context);
|
||||
const describe = context.args.options.jsonHelp ? cmd.fullDescribe : cmd.describe;
|
||||
|
||||
return localYargs.command({
|
||||
command: cmd.command,
|
||||
aliases: cmd.aliases,
|
||||
describe:
|
||||
// We cannot add custom fields in help, such as long command description which is used in AIO.
|
||||
// Therefore, we get around this by adding a complex object as a string which we later parse when generating the help files.
|
||||
typeof describe === 'object' ? JSON.stringify(describe) : describe,
|
||||
deprecated: cmd.deprecated,
|
||||
builder: (argv) => cmd.builder(argv) as Argv<T>,
|
||||
handler: (args) => cmd.handler(args),
|
||||
});
|
||||
}
|
@ -6,61 +6,45 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import { join } from 'path';
|
||||
import { Argv } from 'yargs';
|
||||
import {
|
||||
promptGlobalAnalytics,
|
||||
promptProjectAnalytics,
|
||||
setAnalyticsConfig,
|
||||
} from '../../analytics/analytics';
|
||||
import { CommandModule, Options } from '../../command-builder/command-module';
|
||||
CommandModule,
|
||||
CommandModuleImplementation,
|
||||
Options,
|
||||
} from '../../command-builder/command-module';
|
||||
import {
|
||||
addCommandModuleToYargs,
|
||||
demandCommandFailureMessage,
|
||||
} from '../../command-builder/utilities/command';
|
||||
import { AnalyticsInfoCommandModule } from './info/cli';
|
||||
import {
|
||||
AnalyticsCIModule,
|
||||
AnalyticsOffModule,
|
||||
AnalyticsOnModule,
|
||||
AnalyticsPromptModule,
|
||||
} from './settings/cli';
|
||||
|
||||
interface AnalyticsCommandArgs {
|
||||
setting: 'on' | 'off' | 'prompt' | 'ci' | string;
|
||||
global: boolean;
|
||||
}
|
||||
export class AnalyticsCommandModule extends CommandModule implements CommandModuleImplementation {
|
||||
command = 'analytics';
|
||||
describe =
|
||||
'Configures the gathering of Angular CLI usage metrics. See https://angular.io/cli/usage-analytics-gathering';
|
||||
longDescriptionPath?: string | undefined;
|
||||
|
||||
export class AnalyticsCommandModule extends CommandModule<AnalyticsCommandArgs> {
|
||||
command = 'analytics <setting>';
|
||||
describe = 'Configures the gathering of Angular CLI usage metrics.';
|
||||
longDescriptionPath = join(__dirname, 'long-description.md');
|
||||
builder(localYargs: Argv): Argv {
|
||||
const subcommands = [
|
||||
AnalyticsCIModule,
|
||||
AnalyticsInfoCommandModule,
|
||||
AnalyticsOffModule,
|
||||
AnalyticsOnModule,
|
||||
AnalyticsPromptModule,
|
||||
].sort();
|
||||
|
||||
builder(localYargs: Argv): Argv<AnalyticsCommandArgs> {
|
||||
return localYargs
|
||||
.positional('setting', {
|
||||
description: 'Directly enables or disables all usage analytics for the user.',
|
||||
choices: ['on', 'off', 'ci', 'prompt'],
|
||||
type: 'string',
|
||||
demandOption: true,
|
||||
})
|
||||
.option('global', {
|
||||
description: `Access the global configuration in the caller's home directory.`,
|
||||
alias: ['g'],
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
})
|
||||
.strict();
|
||||
}
|
||||
|
||||
async run({ setting, global }: Options<AnalyticsCommandArgs>): Promise<void> {
|
||||
const level = global ? 'global' : 'local';
|
||||
switch (setting) {
|
||||
case 'off':
|
||||
setAnalyticsConfig(level, false);
|
||||
break;
|
||||
case 'on':
|
||||
setAnalyticsConfig(level, true);
|
||||
break;
|
||||
case 'ci':
|
||||
setAnalyticsConfig(level, 'ci');
|
||||
break;
|
||||
case 'prompt':
|
||||
if (global) {
|
||||
await promptGlobalAnalytics(true);
|
||||
} else {
|
||||
await promptProjectAnalytics(true);
|
||||
}
|
||||
break;
|
||||
for (const module of subcommands) {
|
||||
localYargs = addCommandModuleToYargs(localYargs, module, this.context);
|
||||
}
|
||||
|
||||
return localYargs.demandCommand(1, demandCommandFailureMessage).strict();
|
||||
}
|
||||
|
||||
run(_options: Options<{}>): void {}
|
||||
}
|
||||
|
52
packages/angular/cli/src/commands/analytics/info/cli.ts
Normal file
52
packages/angular/cli/src/commands/analytics/info/cli.ts
Normal file
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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 { tags } from '@angular-devkit/core';
|
||||
import { Argv } from 'yargs';
|
||||
import { analyticsConfigValueToHumanFormat, createAnalytics } from '../../../analytics/analytics';
|
||||
import {
|
||||
CommandModule,
|
||||
CommandModuleImplementation,
|
||||
Options,
|
||||
} from '../../../command-builder/command-module';
|
||||
import { getWorkspaceRaw } from '../../../utilities/config';
|
||||
|
||||
export class AnalyticsInfoCommandModule
|
||||
extends CommandModule
|
||||
implements CommandModuleImplementation
|
||||
{
|
||||
command = 'info';
|
||||
describe = 'Prints analytics gathering and reporting configuration in the console.';
|
||||
longDescriptionPath?: string | undefined;
|
||||
|
||||
builder(localYargs: Argv): Argv {
|
||||
return localYargs.strict();
|
||||
}
|
||||
|
||||
async run(_options: Options<{}>): Promise<void> {
|
||||
const [globalWorkspace] = getWorkspaceRaw('global');
|
||||
const [localWorkspace] = getWorkspaceRaw('local');
|
||||
const globalSetting = globalWorkspace?.get(['cli', 'analytics']);
|
||||
const localSetting = localWorkspace?.get(['cli', 'analytics']);
|
||||
|
||||
const effectiveSetting = await createAnalytics(
|
||||
!!this.context.workspace /** workspace */,
|
||||
true /** skipPrompt */,
|
||||
);
|
||||
|
||||
this.context.logger.info(tags.stripIndents`
|
||||
Global setting: ${analyticsConfigValueToHumanFormat(globalSetting)}
|
||||
Local setting: ${
|
||||
this.context.workspace
|
||||
? analyticsConfigValueToHumanFormat(localSetting)
|
||||
: 'No local workspace configuration file.'
|
||||
}
|
||||
Effective status: ${effectiveSetting ? 'enabled' : 'disabled'}
|
||||
`);
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
The value of `setting` is one of the following.
|
||||
|
||||
- `on`: Enables analytics gathering and reporting for the user.
|
||||
- `off`: Disables analytics gathering and reporting for the user.
|
||||
- `ci`: Enables analytics and configures reporting for use with Continuous Integration,
|
||||
which uses a common CI user.
|
||||
- `prompt`: Prompts the user to set the status interactively.
|
||||
|
||||
For further details, see [Gathering an Viewing CLI Usage Analytics](cli/usage-analytics-gathering).
|
95
packages/angular/cli/src/commands/analytics/settings/cli.ts
Normal file
95
packages/angular/cli/src/commands/analytics/settings/cli.ts
Normal file
@ -0,0 +1,95 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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 { Argv } from 'yargs';
|
||||
import {
|
||||
promptGlobalAnalytics,
|
||||
promptProjectAnalytics,
|
||||
setAnalyticsConfig,
|
||||
} from '../../../analytics/analytics';
|
||||
import {
|
||||
CommandModule,
|
||||
CommandModuleImplementation,
|
||||
Options,
|
||||
} from '../../../command-builder/command-module';
|
||||
|
||||
interface AnalyticsCommandArgs {
|
||||
global: boolean;
|
||||
}
|
||||
|
||||
abstract class AnalyticsSettingModule
|
||||
extends CommandModule<AnalyticsCommandArgs>
|
||||
implements CommandModuleImplementation<AnalyticsCommandArgs>
|
||||
{
|
||||
longDescriptionPath?: string | undefined;
|
||||
|
||||
builder(localYargs: Argv): Argv<AnalyticsCommandArgs> {
|
||||
return localYargs
|
||||
.option('global', {
|
||||
description: `Configure analytics gathering and reporting globally in the caller's home directory.`,
|
||||
alias: ['g'],
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
})
|
||||
.strict();
|
||||
}
|
||||
|
||||
abstract override run({ global }: Options<AnalyticsCommandArgs>): void;
|
||||
}
|
||||
|
||||
export class AnalyticsOffModule
|
||||
extends AnalyticsSettingModule
|
||||
implements CommandModuleImplementation<AnalyticsCommandArgs>
|
||||
{
|
||||
command = 'off';
|
||||
describe = 'Disables analytics gathering and reporting for the user.';
|
||||
|
||||
run({ global }: Options<AnalyticsCommandArgs>): void {
|
||||
setAnalyticsConfig(global, false);
|
||||
}
|
||||
}
|
||||
|
||||
export class AnalyticsOnModule
|
||||
extends AnalyticsSettingModule
|
||||
implements CommandModuleImplementation<AnalyticsCommandArgs>
|
||||
{
|
||||
command = 'on';
|
||||
describe = 'Enables analytics gathering and reporting for the user.';
|
||||
run({ global }: Options<AnalyticsCommandArgs>): void {
|
||||
setAnalyticsConfig(global, true);
|
||||
}
|
||||
}
|
||||
|
||||
export class AnalyticsCIModule
|
||||
extends AnalyticsSettingModule
|
||||
implements CommandModuleImplementation<AnalyticsCommandArgs>
|
||||
{
|
||||
command = 'ci';
|
||||
describe =
|
||||
'Enables analytics and configures reporting for use with Continuous Integration, which uses a common CI user.';
|
||||
|
||||
run({ global }: Options<AnalyticsCommandArgs>): void {
|
||||
setAnalyticsConfig(global, 'ci');
|
||||
}
|
||||
}
|
||||
|
||||
export class AnalyticsPromptModule
|
||||
extends AnalyticsSettingModule
|
||||
implements CommandModuleImplementation<AnalyticsCommandArgs>
|
||||
{
|
||||
command = 'prompt';
|
||||
describe = 'Prompts the user to set the analytics gathering status interactively.';
|
||||
|
||||
async run({ global }: Options<AnalyticsCommandArgs>): Promise<void> {
|
||||
if (global) {
|
||||
await promptGlobalAnalytics(true);
|
||||
} else {
|
||||
await promptProjectAnalytics(true);
|
||||
}
|
||||
}
|
||||
}
|
@ -3,12 +3,13 @@ import { silentNg } from '../../../utils/process';
|
||||
export default async function () {
|
||||
// This test is use as a sanity check.
|
||||
const addHelpOutputSnapshot = JSON.stringify({
|
||||
'name': 'analytics',
|
||||
'command': 'ng analytics <setting>',
|
||||
'shortDescription': 'Configures the gathering of Angular CLI usage metrics.',
|
||||
'longDescriptionRelativePath': '@angular/cli/src/commands/analytics/long-description.md',
|
||||
'name': 'config',
|
||||
'command': 'ng config <json-path> [value]',
|
||||
'shortDescription':
|
||||
'Retrieves or sets Angular configuration values in the angular.json file for the workspace.',
|
||||
'longDescriptionRelativePath': '@angular/cli/src/commands/config/long-description.md',
|
||||
'longDescription':
|
||||
'The value of `setting` is one of the following.\n\n- `on`: Enables analytics gathering and reporting for the user.\n- `off`: Disables analytics gathering and reporting for the user.\n- `ci`: Enables analytics and configures reporting for use with Continuous Integration,\n which uses a common CI user.\n- `prompt`: Prompts the user to set the status interactively.\n\nFor further details, see [Gathering an Viewing CLI Usage Analytics](cli/usage-analytics-gathering).\n',
|
||||
'A workspace has a single CLI configuration file, `angular.json`, at the top level.\nThe `projects` object contains a configuration object for each project in the workspace.\n\nYou can edit the configuration directly in a code editor,\nor indirectly on the command line using this command.\n\nThe configurable property names match command option names,\nexcept that in the configuration file, all names must use camelCase,\nwhile on the command line options can be given dash-case.\n\nFor further details, see [Workspace Configuration](guide/workspace-config).\n\nFor configuration of CLI usage analytics, see [Gathering an Viewing CLI Usage Analytics](cli/usage-analytics-gathering).\n',
|
||||
'options': [
|
||||
{
|
||||
'name': 'global',
|
||||
@ -23,21 +24,27 @@ export default async function () {
|
||||
'description': 'Shows a help message for this command in the console.',
|
||||
},
|
||||
{
|
||||
'name': 'setting',
|
||||
'name': 'json-path',
|
||||
'type': 'string',
|
||||
'enum': ['on', 'off', 'ci', 'prompt'],
|
||||
'description': 'Directly enables or disables all usage analytics for the user.',
|
||||
'description':
|
||||
'The configuration key to set or query, in JSON path format. For example: "a[3].foo.bar[2]". If no new value is provided, returns the current value of this key.',
|
||||
'positional': 0,
|
||||
},
|
||||
{
|
||||
'name': 'value',
|
||||
'type': 'string',
|
||||
'description': 'If provided, a new value for the given configuration key.',
|
||||
'positional': 1,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { stdout } = await silentNg('analytics', '--help', '--json-help');
|
||||
const { stdout } = await silentNg('config', '--help', '--json-help');
|
||||
const output = JSON.stringify(JSON.parse(stdout.trim()));
|
||||
|
||||
if (output !== addHelpOutputSnapshot) {
|
||||
throw new Error(
|
||||
`ng analytics JSON help output didn\'t match snapshot.\n\nExpected "${output}" to be "${addHelpOutputSnapshot}".`,
|
||||
`ng config JSON help output didn\'t match snapshot.\n\nExpected "${output}" to be "${addHelpOutputSnapshot}".`,
|
||||
);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user