Skip to content

Commit 8c8ee15

Browse files
author
serverpod_cloud
committed
feat(scloud): 4561c92293dc121d182fcc56800fd77bdfc99a5d
1 parent 98ef4ef commit 8c8ee15

14 files changed

Lines changed: 923 additions & 231 deletions

serverpod_cloud_cli/bin/serverpod_cloud_cli.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import 'dart:async';
22
import 'dart:io';
33

4+
import 'package:cli_tools/analytics.dart';
45
import 'package:cli_tools/better_command_runner.dart' show ExitException;
56
import 'package:config/config.dart';
67
import 'package:serverpod_cloud_cli/command_logger/command_logger.dart';
78
import 'package:serverpod_cloud_cli/command_runner/cloud_cli_command_runner.dart';
9+
import 'package:serverpod_cloud_cli/persistent_storage/resource_manager.dart';
810
import 'package:serverpod_cloud_cli/shared/exceptions/exit_exceptions.dart';
911
import 'package:serverpod_cloud_cli/util/scloud_version.dart';
1012

@@ -46,6 +48,7 @@ Future<void> _main(final List<String> args, final CommandLogger logger) async {
4648
final runner = CloudCliCommandRunner.create(
4749
logger: logger,
4850
version: cliVersion,
51+
onAnalyticsEvent: _reportMixPanelEvent,
4952
);
5053
try {
5154
await runner.run(args);
@@ -73,3 +76,15 @@ https://github.com/serverpod/serverpod/issues
7376
${zonedError ? 'Zoned error' : ''}
7477
${error.runtimeType} $error''';
7578
}
79+
80+
void _reportMixPanelEvent(final String event) {
81+
_analytics.track(event: event);
82+
}
83+
84+
const _mixPanelToken = 'd0aaf1b76185d37938f3767bdb685760';
85+
final Analytics _analytics = MixPanelAnalytics(
86+
uniqueUserId: ResourceManager.uniqueUserId,
87+
projectToken: _mixPanelToken,
88+
version: cliVersion.canonicalizedVersion,
89+
endpoint: 'https://api-eu.mixpanel.com/track',
90+
);

serverpod_cloud_cli/lib/command_runner/cloud_cli_command_runner.dart

Lines changed: 78 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,22 @@ import 'package:serverpod_cloud_cli/command_runner/helpers/cloud_cli_service_pro
2424
import 'package:serverpod_cloud_cli/command_runner/helpers/cli_version_checker.dart';
2525
import 'package:serverpod_cloud_cli/constants.dart';
2626
import 'package:serverpod_cloud_cli/persistent_storage/resource_manager.dart';
27+
import 'package:serverpod_cloud_cli/util/activation_checker.dart';
2728
import 'package:serverpod_cloud_cli/util/common.dart';
2829
import 'package:serverpod_cloud_cli/util/pubspec_validator.dart';
2930
import 'package:serverpod_cloud_cli/util/scloud_config/scloud_config.dart';
3031
import 'package:serverpod_cloud_cli/util/scloud_version.dart';
3132

3233
import 'commands/admin/admin_command.dart';
34+
import 'commands/settings_command.dart';
3335
import 'completion/completion_script_carapace.dart';
3436
import 'completion/completion_script_completely.dart';
3537

3638
/// Represents the Serverpod Cloud CLI main command, its global options, and subcommands.
3739
class CloudCliCommandRunner extends BetterCommandRunner<GlobalOption, void> {
3840
final Version version;
3941
final CommandLogger logger;
40-
final CloudCliServiceProvider serviceProvider;
42+
final CloudCliServiceProvider _serviceProvider;
4143

4244
final VersionCommand _versionCommand;
4345

@@ -64,12 +66,26 @@ class CloudCliCommandRunner extends BetterCommandRunner<GlobalOption, void> {
6466
logger.configuration = _globalConfiguration;
6567
}
6668

69+
/// Gets the initialized service provider for the Serverpod Cloud CLI.
70+
/// Must not be called before the [run] method has been invoked.
71+
CloudCliServiceProvider get serviceProvider {
72+
if (!_serviceProvider.initialized) {
73+
_serviceProvider.initialize(
74+
globalConfiguration: globalConfiguration,
75+
logger: logger,
76+
);
77+
}
78+
return _serviceProvider;
79+
}
80+
6781
CloudCliCommandRunner._({
6882
required this.logger,
6983
required this.version,
70-
required this.serviceProvider,
84+
required final CloudCliServiceProvider serviceProvider,
85+
super.onAnalyticsEvent,
7186
super.setLogLevel,
72-
}) : _versionCommand = VersionCommand(logger: logger),
87+
}) : _serviceProvider = serviceProvider,
88+
_versionCommand = VersionCommand(logger: logger),
7389
super(
7490
'scloud',
7591
'Manage your Serverpod Cloud projects',
@@ -89,12 +105,14 @@ class CloudCliCommandRunner extends BetterCommandRunner<GlobalOption, void> {
89105
required final CommandLogger logger,
90106
final Version? version,
91107
final CloudCliServiceProvider? serviceProvider,
108+
final OnAnalyticsEvent? onAnalyticsEvent,
92109
bool? adminUserMode,
93110
}) {
94111
final runner = CloudCliCommandRunner._(
95112
logger: logger,
96113
version: version ?? cliVersion,
97114
serviceProvider: serviceProvider ?? CloudCliServiceProvider(),
115+
onAnalyticsEvent: onAnalyticsEvent,
98116
setLogLevel: ({
99117
final String? commandName,
100118
required final CommandRunnerLogLevel parsedLogLevel,
@@ -126,6 +144,7 @@ class CloudCliCommandRunner extends BetterCommandRunner<GlobalOption, void> {
126144
CloudDbCommand(logger: logger),
127145
CloudLaunchCommand(logger: logger),
128146
CloudUserCommand(logger: logger),
147+
CliUserSettingsCommand(logger: logger),
129148
if (adminUserMode) CloudAdminCommand(logger: logger, hidden: false),
130149
]);
131150

@@ -134,11 +153,6 @@ class CloudCliCommandRunner extends BetterCommandRunner<GlobalOption, void> {
134153

135154
@override
136155
Future<void> runCommand(final ArgResults topLevelResults) async {
137-
serviceProvider.initialize(
138-
globalConfiguration: globalConfiguration,
139-
logger: logger,
140-
);
141-
142156
if (globalConfiguration.version) {
143157
await _versionCommand.run();
144158
}
@@ -179,6 +193,52 @@ class CloudCliCommandRunner extends BetterCommandRunner<GlobalOption, void> {
179193
}
180194
}
181195

196+
@override
197+
Future<bool> determineAnalyticsSettings() async {
198+
if (onAnalyticsEvent == null) {
199+
return false;
200+
}
201+
202+
final analyticsOptionValue = globalConfiguration.analytics;
203+
if (analyticsOptionValue != null) {
204+
// explicitly set via option for this run
205+
return analyticsOptionValue;
206+
}
207+
208+
if (!_isTenantUser()) {
209+
return false;
210+
}
211+
212+
final analyticsEnabled = await _getAnalyticsSetting();
213+
return analyticsEnabled;
214+
}
215+
216+
Future<bool> _getAnalyticsSetting() async {
217+
final settings = serviceProvider.scloudSettings;
218+
final analyticsEnabled = await settings.enableAnalytics;
219+
if (analyticsEnabled != null) {
220+
return analyticsEnabled;
221+
}
222+
223+
final confirm = await logger.confirm(
224+
'Do you agree to sending anonymous command usage analytics to Serverpod?',
225+
defaultValue: true,
226+
);
227+
await settings.setEnableAnalytics(confirm);
228+
return confirm;
229+
}
230+
231+
/// Returns true if the user likely is a production tenant user.
232+
bool _isTenantUser() {
233+
if (!isActivatedFromPub()) {
234+
return false;
235+
}
236+
if (globalConfiguration.apiServer != HostConstants.serverpodCloudApi) {
237+
return false;
238+
}
239+
return true;
240+
}
241+
182242
@override
183243
String? get usageFooter =>
184244
'\nSee the full documentation at: https://docs.serverpod.cloud/';
@@ -300,7 +360,14 @@ Directory _getDefaultStorageDir() {
300360
enum GlobalOption<V> implements OptionDefinition<V> {
301361
quiet(BetterCommandRunnerFlags.quietOption),
302362
verbose(BetterCommandRunnerFlags.verboseOption),
303-
analytics(BetterCommandRunnerFlags.analyticsOption),
363+
364+
analytics(FlagOption(
365+
argName: BetterCommandRunnerFlags.analytics,
366+
argAbbrev: BetterCommandRunnerFlags.analyticsAbbr,
367+
envName: 'SERVERPOD_CLOUD_COMMAND_ANALYTICS',
368+
negatable: true,
369+
helpText: 'Toggles if analytics data is sent.',
370+
)),
304371

305372
version(
306373
FlagOption(
@@ -427,6 +494,8 @@ class GlobalConfiguration extends Configuration<GlobalOption> {
427494

428495
bool get version => value(GlobalOption.version);
429496

497+
bool? get analytics => optionalValue(GlobalOption.analytics);
498+
430499
Directory get scloudDir => value(GlobalOption.scloudDir);
431500

432501
Directory? get projectDir => optionalValue(GlobalOption.projectDir);
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import 'package:config/config.dart';
2+
import 'package:serverpod_cloud_cli/command_runner/cloud_cli_command.dart';
3+
4+
enum CliUserSettingsOption<V> implements OptionDefinition<V> {
5+
analytics(FlagOption(
6+
argName: 'analytics',
7+
negatable: true,
8+
helpText: 'Toggles if analytics data is sent.',
9+
));
10+
11+
const CliUserSettingsOption(this.option);
12+
13+
@override
14+
final ConfigOptionBase<V> option;
15+
}
16+
17+
class CliUserSettingsCommand extends CloudCliCommand<CliUserSettingsOption> {
18+
@override
19+
final name = 'settings';
20+
21+
@override
22+
final description = 'Manage local CLI user settings.';
23+
24+
CliUserSettingsCommand({required super.logger})
25+
: super(options: CliUserSettingsOption.values);
26+
27+
@override
28+
Future<void> runWithConfig(
29+
final Configuration<CliUserSettingsOption> commandConfig,
30+
) async {
31+
var settingSpecified = false;
32+
33+
if (commandConfig.optionalValue(CliUserSettingsOption.analytics)
34+
case final bool analytics) {
35+
final settings = runner.serviceProvider.scloudSettings;
36+
await settings.setEnableAnalytics(analytics);
37+
settingSpecified = true;
38+
logger.info('Analytics set to "$analytics".');
39+
}
40+
41+
if (!settingSpecified) {
42+
// show current settings
43+
final settings = runner.serviceProvider.scloudSettings;
44+
final analytics = await settings.enableAnalytics;
45+
logger.list(
46+
title: 'Local settings',
47+
['Analytics = ${analytics ?? 'not set'}'],
48+
);
49+
}
50+
}
51+
}

serverpod_cloud_cli/lib/command_runner/completion/completion_script_carapace.dart

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
/// This file is auto-generated.
22
library;
33

4-
import 'package:cli_tools/better_command_runner.dart' show CompletionTarget;
4+
import 'package:cli_tools/better_command_runner.dart' show CompletionTool;
55

66
const String _completionScript = r'''
77
# yaml-language-server: $schema=https://carapace.sh/schemas/command.json
88
name: scloud
99
persistentFlags:
1010
-q, --quiet: "Suppress all cli output. Is overridden by -v, --verbose."
1111
-v, --verbose: "Prints additional information useful for development. Overrides --q, --quiet."
12-
-a, --analytics: "Toggles if analytics data is sent. "
13-
--no-analytics: "Toggles if analytics data is sent. "
12+
-a, --analytics: "Toggles if analytics data is sent."
13+
--no-analytics: "Toggles if analytics data is sent."
1414
--version: "Prints the version of the Serverpod Cloud CLI."
1515
--scloud-dir=: "Override the directory path where Serverpod Cloud cache/authentication files are stored."
1616
-d, --project-dir=: "The path to the Serverpod Cloud project server directory."
@@ -31,22 +31,22 @@ commands:
3131
commands:
3232
- name: generate
3333
flags:
34-
-t, --target=!: "The target tool format"
34+
-t, --tool=!: "The completion tool to target"
3535
-e, --exec-name=: "Override the name of the executable"
3636
-f, --file=: "Write the specification to a file instead of stdout"
3737
completion:
3838
flag:
39-
target: ["completely", "carapace"]
39+
tool: ["completely", "carapace"]
4040
file: ["$files"]
4141
4242
- name: install
4343
flags:
44-
-t, --target=!: "The target tool format"
44+
-t, --tool=!: "The completion tool to target"
4545
-e, --exec-name=: "Override the name of the executable"
4646
-d, --write-dir=: "Override the directory to write the script to"
4747
completion:
4848
flag:
49-
target: ["completely", "carapace"]
49+
tool: ["completely", "carapace"]
5050
write-dir: ["$directories"]
5151
5252
- name: version
@@ -262,11 +262,18 @@ commands:
262262
flags:
263263
-p, --project=!: "The ID of the project.\nCan be omitted for existing projects that are linked. See `scloud project link --help`."
264264
265+
- name: settings
266+
flags:
267+
--analytics: "Toggles if analytics data is sent."
268+
--no-analytics: "Toggles if analytics data is sent."
269+
exclusiveFlags:
270+
- [analytics, no-analytics]
271+
265272
266273
''';
267274

268275
/// Embedded script for command line completion for `carapace`.
269276
const completionScriptCarapace = (
270-
target: CompletionTarget.carapace,
277+
tool: CompletionTool.carapace,
271278
script: _completionScript,
272279
);

0 commit comments

Comments
 (0)