| // Copyright 2022 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // ## How to use feature flags: |
| // |
| // - define a feature flag in `src/all-feature-flags.js`. |
| // Check a few of the testing flags for examples |
| // |
| // - run `npm build`, which will *autogenerate* package.json configuration |
| // for your flags. You *DO NOT* edit package.json manually for this. |
| // |
| // - call `#enabled`: |
| // |
| // ``` |
| // import * as features from './features'; |
| // |
| // if (features.enabled('myFeature')) { ... } |
| // ``` |
| // |
| // Typescript will ensure you're only using defined flags |
| // |
| // ## How to name feature flags |
| // |
| // - use `camelCase` (this matches the vscode settings conventions) |
| // - use words that'd look good as a title, because they'll show up |
| // like that in the vscode settings page. |
| // |
| // For example, `showDevicePaneFromStatusBar` will become |
| // `Show Device Pane From Status Bar` to users on the setting page. |
| // |
| // - avoid "new" and words like that (unless referencing an external |
| // thing) -- anything new will be old one day, and then you'll |
| // be stuck with `newNew` and then `newNewNew`. Prefer versions |
| // (e.g. `devicePicker2`) or just clear descriptions when possible. |
| |
| import * as vscode from 'vscode'; |
| import * as flags from './all-feature-flags'; |
| |
| /// the current base stability level -- will be autodetected |
| /// based on version number & config on #init, but can be overriden |
| /// with the `fuchsia.featurePreview` flag (can change at runtime). |
| let stabilityLevel: flags.Stability = 'stable'; |
| |
| /// the current default stability, as detected by the extension version |
| let defaultStability: flags.Stability = 'stable'; |
| |
| let watcher: vscode.Disposable; |
| |
| /** |
| * represents a change to one of the feature flags in this plugin |
| */ |
| export class FeatureFlagChangeEvent { |
| private global; |
| constructor(private original: vscode.ConfigurationChangeEvent, global?: boolean) { |
| this.global = global ?? false; |
| } |
| |
| /** |
| * check if this change affects the given flag. |
| * |
| * It should never have false negatives, but it may have false positive in cases |
| * of large-scale config changes. |
| */ |
| affectsFlag(name: string): boolean { |
| if (this.global) { |
| // it'd be complicated to figure out which flags would've flipped, so prob fine to just tell everyone |
| // that they're affected |
| return true; |
| } |
| return this.original.affectsConfiguration(`fuchsia.features.${name}`); |
| } |
| } |
| const onDidChangeFeatureFlagEmitter: vscode.EventEmitter<FeatureFlagChangeEvent> |
| = new vscode.EventEmitter(); |
| |
| /** |
| * Event emitted when a feature flag changes value. |
| * |
| * If you need to activate or deactivate features based on flags, listen to this. |
| */ |
| export const onDidChangeFeatureFlag: vscode.Event<FeatureFlagChangeEvent> |
| = onDidChangeFeatureFlagEmitter.event; |
| |
| /** |
| * activates the feature flag system, assuming the given extension version. |
| * |
| * You can get the extension version with `ctx.extension.packageJSON.version`. |
| */ |
| export function activate(extensionVersion: string) { |
| const [_major, minor, _patch] = extensionVersion.split('.'); |
| const isPrerelease = parseInt(minor) % 2 === 1; // prerelease versions are always odd |
| |
| defaultStability = isPrerelease ? 'nightly' : 'stable'; |
| |
| setStabilityFromConfig(); |
| watcher = vscode.workspace.onDidChangeConfiguration((evt) => { |
| if (evt.affectsConfiguration('fuchsia.featurePreview')) { |
| setStabilityFromConfig(); |
| onDidChangeFeatureFlagEmitter.fire(new FeatureFlagChangeEvent(evt, true)); |
| return; |
| } |
| |
| if (evt.affectsConfiguration('fuchsia.features')) { |
| onDidChangeFeatureFlagEmitter.fire(new FeatureFlagChangeEvent(evt)); |
| } |
| }); |
| } |
| |
| /** |
| * deactives the feature flag system |
| */ |
| export function deactivate() { |
| if (watcher) { |
| watcher.dispose(); |
| } |
| } |
| |
| /** |
| * resets #stabilityLevel from either the #defaultStability or the override configuration |
| * (fuchsia.featurePreview) |
| */ |
| function setStabilityFromConfig() { |
| const override = vscode.workspace.getConfiguration('fuchsia').get('featurePreview'); |
| if (override === 'auto') { |
| stabilityLevel = defaultStability; |
| } else { |
| stabilityLevel = override as flags.Stability; |
| } |
| } |
| |
| /** checks if lhs >= rhs, in stability terms */ |
| function isAtLeast(lhs: flags.Stability, rhs: flags.Stability): boolean { |
| const levels = { |
| 'never': -1, |
| 'nightly': 0, |
| 'stable': 1, |
| }; |
| return levels[lhs] >= levels[rhs]; |
| } |
| |
| /** |
| * Check if the given flag with with the given stability level would be enabled |
| * in the current environment, either by configuration override or current |
| * global stability level. |
| * |
| * You should pass in a string, like `flags.enabled('myFlag')`, and typescript's |
| * typechecking will ensure it's a defined flag. |
| */ |
| export function enabled(flagName: flags.Known): boolean { |
| const opt = vscode.workspace.getConfiguration('fuchsia.features').get(flagName); |
| switch (opt) { |
| case 'auto': |
| const stability = flags.features[flagName].stability; |
| return isAtLeast(stability, stabilityLevel); |
| case 'enabled': |
| return true; |
| case 'disabled': |
| return false; |
| default: |
| throw new Error( |
| `feature flag ${flagName} set to invalid value "${opt}" (must be enabled|disabled|auto)`); |
| } |
| } |
| |
| // NB(sollyross): these are in a separate file so that we can load it in |
| // our build generator and inject the corresponding JSON into package.json |
| |
| /** |
| * The list of all feature flags. |
| */ |
| export { Stability, features } from './all-feature-flags'; |