| |
| |
| |
| |
| |
|
|
| import { |
| SettingScope, |
| isLoadableSettingScope, |
| type LoadableSettingScope, |
| type LoadedSettings, |
| } from '../config/settings.js'; |
|
|
| export interface ModifiedScope { |
| scope: SettingScope; |
| path: string; |
| } |
|
|
| export type FeatureActionStatus = 'success' | 'no-op' | 'error'; |
|
|
| export interface FeatureActionResult { |
| status: FeatureActionStatus; |
| featureName: string; |
| action: 'enable' | 'disable'; |
| |
| modifiedScopes: ModifiedScope[]; |
| |
| alreadyInStateScopes: ModifiedScope[]; |
| |
| error?: string; |
| } |
|
|
| |
| |
| |
| export interface FeatureToggleStrategy { |
| |
| |
| |
| |
| |
| needsEnabling( |
| settings: LoadedSettings, |
| scope: LoadableSettingScope, |
| featureName: string, |
| ): boolean; |
|
|
| |
| |
| |
| enable( |
| settings: LoadedSettings, |
| scope: LoadableSettingScope, |
| featureName: string, |
| ): void; |
|
|
| |
| |
| |
| |
| |
| isExplicitlyDisabled( |
| settings: LoadedSettings, |
| scope: LoadableSettingScope, |
| featureName: string, |
| ): boolean; |
|
|
| |
| |
| |
| disable( |
| settings: LoadedSettings, |
| scope: LoadableSettingScope, |
| featureName: string, |
| ): void; |
| } |
|
|
| |
| |
| |
| export function enableFeature( |
| settings: LoadedSettings, |
| featureName: string, |
| strategy: FeatureToggleStrategy, |
| ): FeatureActionResult { |
| const writableScopes = [SettingScope.Workspace, SettingScope.User]; |
| const foundInDisabledScopes: ModifiedScope[] = []; |
| const alreadyEnabledScopes: ModifiedScope[] = []; |
|
|
| for (const scope of writableScopes) { |
| if (isLoadableSettingScope(scope)) { |
| const scopePath = settings.forScope(scope).path; |
| if (strategy.needsEnabling(settings, scope, featureName)) { |
| foundInDisabledScopes.push({ scope, path: scopePath }); |
| } else { |
| alreadyEnabledScopes.push({ scope, path: scopePath }); |
| } |
| } |
| } |
|
|
| if (foundInDisabledScopes.length === 0) { |
| return { |
| status: 'no-op', |
| featureName, |
| action: 'enable', |
| modifiedScopes: [], |
| alreadyInStateScopes: alreadyEnabledScopes, |
| }; |
| } |
|
|
| const modifiedScopes: ModifiedScope[] = []; |
| for (const { scope, path } of foundInDisabledScopes) { |
| if (isLoadableSettingScope(scope)) { |
| strategy.enable(settings, scope, featureName); |
| modifiedScopes.push({ scope, path }); |
| } |
| } |
|
|
| return { |
| status: 'success', |
| featureName, |
| action: 'enable', |
| modifiedScopes, |
| alreadyInStateScopes: alreadyEnabledScopes, |
| }; |
| } |
|
|
| |
| |
| |
| export function disableFeature( |
| settings: LoadedSettings, |
| featureName: string, |
| scope: SettingScope, |
| strategy: FeatureToggleStrategy, |
| ): FeatureActionResult { |
| if (!isLoadableSettingScope(scope)) { |
| return { |
| status: 'error', |
| featureName, |
| action: 'disable', |
| modifiedScopes: [], |
| alreadyInStateScopes: [], |
| error: `Invalid settings scope: ${scope}`, |
| }; |
| } |
|
|
| const scopePath = settings.forScope(scope).path; |
|
|
| if (strategy.isExplicitlyDisabled(settings, scope, featureName)) { |
| return { |
| status: 'no-op', |
| featureName, |
| action: 'disable', |
| modifiedScopes: [], |
| alreadyInStateScopes: [{ scope, path: scopePath }], |
| }; |
| } |
|
|
| |
| const otherScope = |
| scope === SettingScope.Workspace |
| ? SettingScope.User |
| : SettingScope.Workspace; |
| const alreadyDisabledInOther: ModifiedScope[] = []; |
|
|
| if (isLoadableSettingScope(otherScope)) { |
| if (strategy.isExplicitlyDisabled(settings, otherScope, featureName)) { |
| alreadyDisabledInOther.push({ |
| scope: otherScope, |
| path: settings.forScope(otherScope).path, |
| }); |
| } |
| } |
|
|
| strategy.disable(settings, scope, featureName); |
|
|
| return { |
| status: 'success', |
| featureName, |
| action: 'disable', |
| modifiedScopes: [{ scope, path: scopePath }], |
| alreadyInStateScopes: alreadyDisabledInOther, |
| }; |
| } |
|
|