uninstallPlugin method

Future<PluginOperationResult> uninstallPlugin(
  1. String plugin, [
  2. PluginScope scope = PluginScope.user,
  3. bool deleteDataDirFlag = true
])

Uninstall a plugin.

Removes the plugin from settings and V2 installed data. When the last scope is removed, also cleans up cached versions, options, and the data directory.

Implementation

Future<PluginOperationResult> uninstallPlugin(
  String plugin, [
  PluginScope scope = PluginScope.user,
  bool deleteDataDirFlag = true,
]) async {
  assertInstallableScope(scope);

  final loadResult = await loadAllPluginsFn();
  final allPlugins = [...loadResult.enabled, ...loadResult.disabled];
  final foundPlugin = findPluginByIdentifier(plugin, allPlugins);

  final settingSource = scopeToSettingSource(scope);
  final settings = getSettingsEnabledPlugins(settingSource);

  String pluginId;
  String pluginName;

  if (foundPlugin != null) {
    pluginId =
        settings?.keys.firstWhere(
          (k) =>
              k == plugin ||
              k == foundPlugin.manifest.name ||
              k.startsWith('${foundPlugin.manifest.name}@'),
          orElse: () =>
              plugin.contains('@') ? plugin : foundPlugin.manifest.name,
        ) ??
        (plugin.contains('@') ? plugin : foundPlugin.manifest.name);
    pluginName = foundPlugin.manifest.name;
  } else {
    return PluginOperationResult(
      success: false,
      message: 'Plugin "$plugin" not found in installed plugins',
    );
  }

  // Check scope installation.
  final projectPath = getProjectPathForScope(scope);
  final installedData = loadInstalledPluginsV2();
  final installations = installedData[pluginId];
  final scopeInstallation = installations?.cast<PluginInstallationRecord?>().firstWhere(
    (i) => i!.scope == scope && i.projectPath == projectPath,
    orElse: () => null,
  );

  if (scopeInstallation == null) {
    return PluginOperationResult(
      success: false,
      message:
          'Plugin "$plugin" is not installed in ${scope.name} scope. '
          'Use --scope to specify the correct scope.',
    );
  }

  // Remove from settings.
  final newEnabled = Map<String, bool>.from(settings ?? {});
  newEnabled.remove(pluginId);
  updateSettings(settingSource, {'enabledPlugins': newEnabled});
  clearAllCaches();

  // Remove from V2 data.
  removePluginInstallation(pluginId, scope, projectPath);

  // Cleanup when this was the last scope.
  final updatedData = loadInstalledPluginsV2();
  final remaining = updatedData[pluginId];
  final isLastScope = remaining == null || remaining.isEmpty;

  if (isLastScope && scopeInstallation.installPath != null) {
    await markVersionOrphaned(scopeInstallation.installPath!);
  }
  if (isLastScope) {
    deletePluginOptions(pluginId);
    if (deleteDataDirFlag) {
      await deletePluginDataDir(pluginId);
    }
  }

  // Warn about reverse dependents.
  final reverseDeps = findReverseDependents(pluginId, allPlugins);
  final depWarn =
      reverseDeps.isNotEmpty
          ? '. Warning: the following plugins depend on this: '
              '${reverseDeps.join(', ')}'
          : '';

  return PluginOperationResult(
    success: true,
    message:
        'Successfully uninstalled plugin: $pluginName '
        '(scope: ${scope.name})$depWarn',
    pluginId: pluginId,
    pluginName: pluginName,
    scope: scope,
    reverseDependents: reverseDeps.isNotEmpty ? reverseDeps : null,
  );
}