Skip to content

Commit bb55043

Browse files
alan-agius4clydin
authored andcommitted
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 ```
1 parent afafa57 commit bb55043

File tree

9 files changed

+256
-91
lines changed

9 files changed

+256
-91
lines changed

packages/angular/cli/src/analytics/analytics.ts

+19-4
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,11 @@ export function isPackageNameSafeForAnalytics(name: string): boolean {
6363

6464
/**
6565
* Set analytics settings. This does not work if the user is not inside a project.
66-
* @param level Which config to use. "global" for user-level, and "local" for project-level.
66+
* @param global Which config to use. "global" for user-level, and "local" for project-level.
6767
* @param value Either a user ID, true to generate a new User ID, or false to disable analytics.
6868
*/
69-
export function setAnalyticsConfig(level: 'global' | 'local', value: string | boolean) {
69+
export function setAnalyticsConfig(global: boolean, value: string | boolean): void {
70+
const level = global ? 'global' : 'local';
7071
analyticsDebug('setting %s level analytics to: %s', level, value);
7172
const [config, configPath] = getWorkspaceRaw(level);
7273
if (!config || !configPath) {
@@ -79,6 +80,8 @@ export function setAnalyticsConfig(level: 'global' | 'local', value: string | bo
7980
throw new Error(`Invalid config found at ${configPath}. CLI should be an object.`);
8081
}
8182

83+
console.log(`Configured ${level} analytics to "${analyticsConfigValueToHumanFormat(value)}".`);
84+
8285
if (value === true) {
8386
value = uuidV4();
8487
}
@@ -89,6 +92,18 @@ export function setAnalyticsConfig(level: 'global' | 'local', value: string | bo
8992
analyticsDebug('done');
9093
}
9194

95+
export function analyticsConfigValueToHumanFormat(value: unknown): 'on' | 'off' | 'not set' | 'ci' {
96+
if (value === false) {
97+
return 'off';
98+
} else if (value === 'ci') {
99+
return 'ci';
100+
} else if (typeof value === 'string' || value === true) {
101+
return 'on';
102+
} else {
103+
return 'not set';
104+
}
105+
}
106+
92107
/**
93108
* Prompt the user for usage gathering permission.
94109
* @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) {
110125
},
111126
]);
112127

113-
setAnalyticsConfig('global', answers.analytics);
128+
setAnalyticsConfig(true, answers.analytics);
114129

115130
if (answers.analytics) {
116131
console.log('');
@@ -169,7 +184,7 @@ export async function promptProjectAnalytics(force = false): Promise<boolean> {
169184
},
170185
]);
171186

172-
setAnalyticsConfig('local', answers.analytics);
187+
setAnalyticsConfig(false, answers.analytics);
173188

174189
if (answers.analytics) {
175190
console.log('');

packages/angular/cli/src/command-builder/command-module.ts

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export interface CommandContext {
4343
positional: string[];
4444
options: {
4545
help: boolean;
46+
jsonHelp: boolean;
4647
} & Record<string, unknown>;
4748
};
4849
}

packages/angular/cli/src/command-builder/command-runner.ts

+4-18
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { VersionCommandModule } from '../commands/version/cli';
2929
import { colors } from '../utilities/color';
3030
import { AngularWorkspace } from '../utilities/config';
3131
import { CommandContext, CommandModuleError, CommandScope } from './command-module';
32+
import { addCommandModuleToYargs, demandCommandFailureMessage } from './utilities/command';
3233
import { jsonHelpUsage } from './utilities/json-help';
3334

3435
const COMMANDS = [
@@ -75,6 +76,7 @@ export async function runCommand(
7576
positional: positional.map((v) => v.toString()),
7677
options: {
7778
help,
79+
jsonHelp,
7880
...rest,
7981
},
8082
},
@@ -90,23 +92,7 @@ export async function runCommand(
9092
}
9193
}
9294

93-
const commandModule = new CommandModule(context);
94-
const describe = jsonHelp ? commandModule.fullDescribe : commandModule.describe;
95-
96-
localYargs = localYargs.command({
97-
command: commandModule.command,
98-
aliases: 'aliases' in commandModule ? commandModule.aliases : undefined,
99-
describe:
100-
// We cannot add custom fields in help, such as long command description which is used in AIO.
101-
// Therefore, we get around this by adding a complex object as a string which we later parse when generating the help files.
102-
describe !== undefined && typeof describe === 'object'
103-
? JSON.stringify(describe)
104-
: describe,
105-
deprecated: 'deprecated' in commandModule ? commandModule.deprecated : undefined,
106-
builder: (argv) => commandModule.builder(argv) as yargs.Argv,
107-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
108-
handler: (args: any) => commandModule.handler(args),
109-
});
95+
localYargs = addCommandModuleToYargs(localYargs, CommandModule, context);
11096
}
11197

11298
if (jsonHelp) {
@@ -142,7 +128,7 @@ export async function runCommand(
142128
'deprecated: %s': colors.yellow('deprecated:') + ' %s',
143129
'Did you mean %s?': 'Unknown command. Did you mean %s?',
144130
})
145-
.demandCommand()
131+
.demandCommand(1, demandCommandFailureMessage)
146132
.recommendCommands()
147133
.version(false)
148134
.showHelpOnFail(false)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import { Argv } from 'yargs';
10+
import { CommandContext, CommandModule, CommandModuleImplementation } from '../command-module';
11+
12+
export const demandCommandFailureMessage = `You need to specify a command before moving on. Use '--help' to view the available commands.`;
13+
14+
export function addCommandModuleToYargs<
15+
T,
16+
U extends Partial<CommandModuleImplementation> & {
17+
new (context: CommandContext): Partial<CommandModuleImplementation> & CommandModule;
18+
},
19+
>(localYargs: Argv<T>, commandModule: U, context: CommandContext): Argv<T> {
20+
const cmd = new commandModule(context);
21+
const describe = context.args.options.jsonHelp ? cmd.fullDescribe : cmd.describe;
22+
23+
return localYargs.command({
24+
command: cmd.command,
25+
aliases: cmd.aliases,
26+
describe:
27+
// We cannot add custom fields in help, such as long command description which is used in AIO.
28+
// Therefore, we get around this by adding a complex object as a string which we later parse when generating the help files.
29+
typeof describe === 'object' ? JSON.stringify(describe) : describe,
30+
deprecated: cmd.deprecated,
31+
builder: (argv) => cmd.builder(argv) as Argv<T>,
32+
handler: (args) => cmd.handler(args),
33+
});
34+
}

packages/angular/cli/src/commands/analytics/cli.ts

+34-50
Original file line numberDiff line numberDiff line change
@@ -6,61 +6,45 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import { join } from 'path';
109
import { Argv } from 'yargs';
1110
import {
12-
promptGlobalAnalytics,
13-
promptProjectAnalytics,
14-
setAnalyticsConfig,
15-
} from '../../analytics/analytics';
16-
import { CommandModule, Options } from '../../command-builder/command-module';
11+
CommandModule,
12+
CommandModuleImplementation,
13+
Options,
14+
} from '../../command-builder/command-module';
15+
import {
16+
addCommandModuleToYargs,
17+
demandCommandFailureMessage,
18+
} from '../../command-builder/utilities/command';
19+
import { AnalyticsInfoCommandModule } from './info/cli';
20+
import {
21+
AnalyticsCIModule,
22+
AnalyticsOffModule,
23+
AnalyticsOnModule,
24+
AnalyticsPromptModule,
25+
} from './settings/cli';
1726

18-
interface AnalyticsCommandArgs {
19-
setting: 'on' | 'off' | 'prompt' | 'ci' | string;
20-
global: boolean;
21-
}
27+
export class AnalyticsCommandModule extends CommandModule implements CommandModuleImplementation {
28+
command = 'analytics';
29+
describe =
30+
'Configures the gathering of Angular CLI usage metrics. See https://angular.io/cli/usage-analytics-gathering';
31+
longDescriptionPath?: string | undefined;
2232

23-
export class AnalyticsCommandModule extends CommandModule<AnalyticsCommandArgs> {
24-
command = 'analytics <setting>';
25-
describe = 'Configures the gathering of Angular CLI usage metrics.';
26-
longDescriptionPath = join(__dirname, 'long-description.md');
33+
builder(localYargs: Argv): Argv {
34+
const subcommands = [
35+
AnalyticsCIModule,
36+
AnalyticsInfoCommandModule,
37+
AnalyticsOffModule,
38+
AnalyticsOnModule,
39+
AnalyticsPromptModule,
40+
].sort();
2741

28-
builder(localYargs: Argv): Argv<AnalyticsCommandArgs> {
29-
return localYargs
30-
.positional('setting', {
31-
description: 'Directly enables or disables all usage analytics for the user.',
32-
choices: ['on', 'off', 'ci', 'prompt'],
33-
type: 'string',
34-
demandOption: true,
35-
})
36-
.option('global', {
37-
description: `Access the global configuration in the caller's home directory.`,
38-
alias: ['g'],
39-
type: 'boolean',
40-
default: false,
41-
})
42-
.strict();
43-
}
44-
45-
async run({ setting, global }: Options<AnalyticsCommandArgs>): Promise<void> {
46-
const level = global ? 'global' : 'local';
47-
switch (setting) {
48-
case 'off':
49-
setAnalyticsConfig(level, false);
50-
break;
51-
case 'on':
52-
setAnalyticsConfig(level, true);
53-
break;
54-
case 'ci':
55-
setAnalyticsConfig(level, 'ci');
56-
break;
57-
case 'prompt':
58-
if (global) {
59-
await promptGlobalAnalytics(true);
60-
} else {
61-
await promptProjectAnalytics(true);
62-
}
63-
break;
42+
for (const module of subcommands) {
43+
localYargs = addCommandModuleToYargs(localYargs, module, this.context);
6444
}
45+
46+
return localYargs.demandCommand(1, demandCommandFailureMessage).strict();
6547
}
48+
49+
run(_options: Options<{}>): void {}
6650
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import { tags } from '@angular-devkit/core';
10+
import { Argv } from 'yargs';
11+
import { analyticsConfigValueToHumanFormat, createAnalytics } from '../../../analytics/analytics';
12+
import {
13+
CommandModule,
14+
CommandModuleImplementation,
15+
Options,
16+
} from '../../../command-builder/command-module';
17+
import { getWorkspaceRaw } from '../../../utilities/config';
18+
19+
export class AnalyticsInfoCommandModule
20+
extends CommandModule
21+
implements CommandModuleImplementation
22+
{
23+
command = 'info';
24+
describe = 'Prints analytics gathering and reporting configuration in the console.';
25+
longDescriptionPath?: string | undefined;
26+
27+
builder(localYargs: Argv): Argv {
28+
return localYargs.strict();
29+
}
30+
31+
async run(_options: Options<{}>): Promise<void> {
32+
const [globalWorkspace] = getWorkspaceRaw('global');
33+
const [localWorkspace] = getWorkspaceRaw('local');
34+
const globalSetting = globalWorkspace?.get(['cli', 'analytics']);
35+
const localSetting = localWorkspace?.get(['cli', 'analytics']);
36+
37+
const effectiveSetting = await createAnalytics(
38+
!!this.context.workspace /** workspace */,
39+
true /** skipPrompt */,
40+
);
41+
42+
this.context.logger.info(tags.stripIndents`
43+
Global setting: ${analyticsConfigValueToHumanFormat(globalSetting)}
44+
Local setting: ${
45+
this.context.workspace
46+
? analyticsConfigValueToHumanFormat(localSetting)
47+
: 'No local workspace configuration file.'
48+
}
49+
Effective status: ${effectiveSetting ? 'enabled' : 'disabled'}
50+
`);
51+
}
52+
}

packages/angular/cli/src/commands/analytics/long-description.md

-9
This file was deleted.

0 commit comments

Comments
 (0)