{"id":21105557,"url":"https://github.com/felipesere/sane-flags","last_synced_at":"2025-05-17T09:40:11.751Z","repository":{"id":42235423,"uuid":"139322255","full_name":"felipesere/sane-flags","owner":"felipesere","description":"Explicit feature flags for JavaScript","archived":false,"fork":false,"pushed_at":"2023-01-06T01:34:10.000Z","size":584,"stargazers_count":13,"open_issues_count":11,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-04-14T12:50:35.897Z","etag":null,"topics":["featureflags","javascript"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/felipesere.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-07-01T11:09:28.000Z","updated_at":"2021-06-27T18:40:18.000Z","dependencies_parsed_at":"2023-02-05T01:32:03.827Z","dependency_job_id":null,"html_url":"https://github.com/felipesere/sane-flags","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/felipesere%2Fsane-flags","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/felipesere%2Fsane-flags/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/felipesere%2Fsane-flags/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/felipesere%2Fsane-flags/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/felipesere","download_url":"https://codeload.github.com/felipesere/sane-flags/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225444499,"owners_count":17475355,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["featureflags","javascript"],"created_at":"2024-11-20T00:07:35.082Z","updated_at":"2024-11-20T00:07:35.694Z","avatar_url":"https://github.com/felipesere.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://travis-ci.org/felipesere/sane-flags.svg?branch=master)](https://travis-ci.org/felipesere/sane-flags)\n[![Coverage Status](https://coveralls.io/repos/github/felipesere/sane-flags/badge.svg?branch=master)](https://coveralls.io/github/felipesere/sane-flags?branch=master)\n[![npm version](https://badge.fury.io/js/sane-flags.svg)](https://badge.fury.io/js/sane-flags)\n\n# Welcome to `Sane Flags`\n\n`Sane flags` is a small, focused library to add feature flags to your JavaScript application.\n\n## Be Explicit\n\n_You should be able to see all the available feature flags._\n\nBecause there might be subtle interactions between multiple flags, its key see what is available at any time.\nThis also allows you to better sunset flags.\n\n## features.js\n\nTo make feature flags easyily discoverable, it is recommended to have a file called something like `feature.js`.\nThis will hold all of your configuration and should be imported wherever you want to switch features on and off.\n\nHere is an example `features.js` file:\n```javascript\nvar saneFlags = require('sane-flags')\n\nvar features = saneFlags.wrap({\n  flags: {\n    dynamic_contact_form: {\n      description: 'New contact form that dynamically fills form based on accounts contacts.',\n      enabled: false\n    }\n  }\n})\n```\n\n## Use cases\n\n### Globally turn a feature on or off\n\nWhen you want to ship a piece of code, without having it really running in production.\nThis makes super sense if you practice continuous integration.\n\nGiven the following file containing all your features flags:\n\n```javascript\nvar saneFlags = require('sane-flags')\n\nmodule.exports = saneFlags.wrap({\n  flags: {\n    dynamic_contact_form: {\n      description: 'New contact form that dynamically fills form based on accounts contacts.',\n      enabled: false\n    }\n  }\n})\n```\n\nYou should be able to import that file and check for flags:\n\n```javascript\nvar features = require('./features')\n\nif(features.isEnabled('dynamic_contact_form')) {\n  // do something differently...\n}\n```\n\n### Hook into alternative sources\n\nMaybe you load a configuration from a remote system or the user can switch flags on and off at runtime.\nFor these cases, sane-flags allows you to hook in alternative `sources`.\nThese sources can be a simple function that gets a flag definition or an object that has a function called `isEnabled` and takes a flag defintion:\n\n```javascript\nvar naiveSource = function(flag) {\n  // some way to define if flag should be on\n}\n\nvar complexSource = {\n  isEnabled: function(flag) {\n    // some way to define if flag should be on\n  }\n}\n```\n\n```javascript\nmodule.exports = saneFlags.wrap({\n  flags: {\n    dynamic_contact_form: {\n      description: 'New contact form that dynamically fills form based on accounts contacts.',\n      enabled: false\n    }\n  },\n  sources: [complexSource]\n})\n```\n\nIts important that the entire `flag` object is passed in as an argument.\nThis forces you to define those flags and maintain our core principle: make flags explicit.\nIt also gives you the flexibility to add any attributues to the flag definition that you need to check them against a source.\n\nSee the [process environment flag](#flags-from-process-environment) source.\n\n### Handling different environments\n\nSometimes you want to have features enabled in lower environments but keep them off in others.\nFor such cases, `sane-flags` supports an `environments` key in the configuration and more complex values for `enabled`.\nIn the spirit of our explicit configuration, you will have to list all available environments and declare which one you are in:\n\n```javascript\nmodule.exports = saneFlags.wrap({\n  flags: {\n    dynamic_contact_form: {\n      description: 'New contact form that dynamically fills form based on accounts contacts.',\n      enabled: {\n        dev: true,\n        qa: false,\n        prod: false\n      }\n    }\n  },\n  environments: {\n    available: ['dev', 'qa', 'prod'],\n    current: process.env.APPLICATION_ENV\n  }\n})\n```\n\nTo be able to ensure consistency, any key underneath `enabled` must be present in `environments.available`.\nUsing `process.env.APPLICATION_ENV` is just an example here.\n\n## Consistency and Explicitness\n\n`sane-flags` is fairly strict in what it expects to see in your configuration.\nEvery flag MUST have a `description` and an `enabled` key.\nTo use the per-environment configuration of enabled, you MUST declare the available environments in the `environments` key.\nFailure to do so will throw an error when calling `wrap(config)` to avoid odd behaviour and enforce good practices as far as possible.\n\n\n## Insight\n\nSane-flags is able to tell you at any point in time what the state of feature flags are.\nThis is valuable for example when booting a service and printing the state of the flags to the console.\n\nGiven:\n```javascript\nfeatures = saneFlags.wrap({\n  flags: {\n    dynamic_contact_form: {\n      description:\n        'The new form that fills in form contacts from the current account',\n      enabled: true\n    },\n\n    disabled_feature: {\n      description: 'The feature we are working on but have disabled',\n      enabled: false\n    },\n    cool_feature: {\n      description: 'The feature we are working on but have disabled',\n      enabled: {\n        dev: true,\n        qa: false\n      }\n    }\n  },\n  environments: {\n    available: [\"dev\", \"qa\"],\n    current: \"qa\"\n  }\n})\n```\n\nThen `.state()` will print a JSON representation that you could turn into a table:\n\n```\nfeatures.state() // =\u003e [{name: 'dynamic_contact_form', enabled: true, description: '...'}, {name: 'disabled_feature', enabled: false, description: '...'}, ... ]\n```\n\n## Feature flags and tests\n\nWhile developing a new feature that is hidden behind a flag, it makes sense to temporarliy switch it on for specific unit tests.\n`sane-flags` provides to helper functions that take a closure  in which a certain flag can be enabled.\nOnce the closure is complete, `sane-flags` will disable the feature again to ensure there is no test pollution.\n\nHere are examples directly from the test suite:\n\nSynchronous:\n```javascript\nfeatures.enabling('disabled_feature', () =\u003e {\n  expect(features.isEnabled('disabled_feature')).to.eql(true)\n})\nexpect(features.isEnabled('disabled_feature')).to.eql(false)\n```\n\nAsync/Await:\n```javascript\nawait features.enablingAsync('disabled_feature', async () =\u003e {\n  wasItEnabled = await someFunctionHere()\n})\n```\n\nShould your closure throw an exception then `sane-flags` will correctly disable the feature again and rethrow the error.\n\nThere will be times where you either want to enable/disable combinations of features, possible across multiple tests.\nFor that case there is a `testBox` inspired by Sinons `sandbox`.\nYou can enable/disable multiple features on a `testBox` and reset them all at once:\n\n```javascript\nconst box = features.testBox()\nbox.enable('disabled_feature')\nbox.disable('enabled_feature')\n\nexpect(features.isEnabled('disabled_feature')).to.eql(true)\nexpect(features.isEnabled('enabled_feature')).to.eql(false)\n\nbox.reset()\n\nexpect(features.isEnabled('disabled_feature')).to.eql(false)\nexpect(features.isEnabled('enabled_feature')).to.eql(true)\n```\n\n## Extras\n\n### Flags from process environment\n\nIf you want to enable flags using the process environment, you can hook in the source provided by `sane-flags` and configure the flags with an extra property `environment_flag`:\n\n```javascript\nconst features = saneFlags.wrap({\n  flags: {\n    really_cool_feature: {\n      description: 'a feature which will be activated with a process variable',\n      enabled: false,\n      environment_flag: 'THIS_IS_THE_FLAG'\n    }\n  },\n  sources: [saneFlags.sources.processEnvSource]\n})\n```\n\nUsing a separate key to name the process environment flag to look for ensures your feature names are not coupled to a naming convention from the processes.\n\nFlags will be enabled if the environment varibale has a value of '1' or 'true'.\n\n\n## Making a new release.\n\nUse `npx np`. That simple.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffelipesere%2Fsane-flags","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffelipesere%2Fsane-flags","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffelipesere%2Fsane-flags/lists"}