{"id":25000186,"url":"https://github.com/thinkmill/devops-env-vars","last_synced_at":"2025-08-05T16:26:12.978Z","repository":{"id":57166285,"uuid":"64732621","full_name":"Thinkmill/devops-env-vars","owner":"Thinkmill","description":"Helper functions that encapsulate our treatment of environment vars for KeystoneJS apps","archived":false,"fork":false,"pushed_at":"2018-04-17T23:27:04.000Z","size":34,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":26,"default_branch":"master","last_synced_at":"2025-07-29T10:23:20.975Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Thinkmill.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-08-02T06:59:27.000Z","updated_at":"2018-04-30T01:42:16.000Z","dependencies_parsed_at":"2022-08-30T15:10:11.517Z","dependency_job_id":null,"html_url":"https://github.com/Thinkmill/devops-env-vars","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/Thinkmill/devops-env-vars","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Thinkmill%2Fdevops-env-vars","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Thinkmill%2Fdevops-env-vars/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Thinkmill%2Fdevops-env-vars/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Thinkmill%2Fdevops-env-vars/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Thinkmill","download_url":"https://codeload.github.com/Thinkmill/devops-env-vars/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Thinkmill%2Fdevops-env-vars/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268932385,"owners_count":24331308,"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","status":"online","status_checked_at":"2025-08-05T02:00:12.334Z","response_time":2576,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":[],"created_at":"2025-02-04T19:31:52.390Z","updated_at":"2025-08-05T16:26:12.937Z","avatar_url":"https://github.com/Thinkmill.png","language":"JavaScript","readme":"Devops: Environment Variables\n================================================================================\n\nA set of helper functions that encapsulate our treatment of environment vars for KeystoneJS apps and help us build a useful `config` object.\n\n\nUsage\n--------------------------------------------------------------------------------\n\nThe code block below demonstrates how this library can be used in a fictional KeystoneJS app, the `chumble-platform`.\n\n```js\nconst envLib = require('@thinkmill/devops-env-vars');\nconst path = require('path');\nconst dotenv = require('dotenv');\n\n\n// This doesn't actually stop the config from being loaded onto clients, it's just a warning for developers\nif (typeof window !== 'undefined') throw new Error(`You definitely shouldn't require ./config on the client`);\n\n// Determine the current APP_ENV\nconst APP_ENV = envLib.determineAppEnv(\n\tprocess.env.APP_ENV,\n\t[{ cidr: '72.67.5.0/16', env: 'live' }, { cidr: '72.68.5.0/16', env: 'staging' }, { cidr: '72.69.5.0/16', env: 'testing' }],\n);\n\n// Convert the APP_ENV to some handy flags\nconst flags = envLib.buildAppFlags(APP_ENV);\n\n// Attempt to read the local .env file for this APP_ENV\nif (!flags.IN_DEVELOPMENT) dotenv.config({ path: path.resolve(`../${APP_ENV}.env`) });\n\n// Extract the vars defined from process.env and apply validation and defaults\nconst config = envLib.mergeConfig(APP_ENV, flags, process.env, {\n\n\t// In development we can default the NODE_ENV but production envs should set it themselves\n\tNODE_ENV: { required: !flags.IN_DEVELOPMENT, default: 'development' },\n\n\t// If not supplied, Keystone will default to localhost (ie. in dev)\n\tMONGO_URI: { required: flags.IN_PRODUCTION, default: 'mongodb://localhost/chumble-platform' },\n\n\t// Used to encrypt user cookies; not important in dev\n\tJWT_TOKEN_SECRET: { required: flags.IN_PRODUCTION, default: 'dev-secret-goes-here' },\n\n\t// When not live, allow to be defaulted to a test key\n\tMANDRILL_API_KEY: { required: flags.IN_PRODUCTION, default: 'test-key-goes-here' },\n\n\t// Cloudinary creds; used by Types.CloudinaryImage\n\tCLOUDINARY_URL: { required: flags.IN_PRODUCTION, default: 'cloudinary://012345678902345:9FDRoGKGpYZVASNDwyTdJRKOIku@thinkmill' },\n\n\t// S3 credentials; used by Types.S3File\n\tS3_BUCKET: { required: flags.IN_PRODUCTION },\n\tS3_KEY: { required: flags.IN_PRODUCTION },\n\tS3_SECRET: { required: flags.IN_PRODUCTION },\n\n\t// Urban Airship details; used to notify users\n\tUA_APP_KEY: { required: flags.IN_PRODUCTION },\n\tUA_SECRET_KEY: { required: flags.IN_PRODUCTION },\n\tUA_MASTER_KEY: { required: flags.IN_PRODUCTION },\n\n\t// NewRelic app monitoring\n\tNEW_RELIC_LICENSE_KEY: { required: flags.IN_PRODUCTION },\n\tNEW_RELIC_APP_NAME: { required: flags.IN_PRODUCTION },\n\n\t// What port should the webserver bind to\n\tPORT: { required: flags.IN_PRODUCTION, default: 3000, type: Number },\n\n});\n\n// Support details\nconfig.FROM_EMAIL = 'support@chumble.com.au';\nconfig.FROM_NAME = 'Chumble Support';\nconfig.SUPPORT_PHONE_NUMBER = '1800 422 554';\n\n// Where should we address the plumbus API\nconfig.PLUMBUS_API_URL = ({\n\tlive:          'https://api.plumbus.net.au',\n\tstaging:       'https://api-staging.plumbus.net.au',\n\ttesting:       'https://api-testing.plumbus.net.au',\n\tdevelopment:   'http://localhost:7634',  // Use a local stub server in dev\n})[APP_ENV];\n\n// Are we disabling developer authentication to developer endpoints?\nconfig.ALLOW_UNAUTHENTICATED_ACCESS_TO_DEVELOPER_ENDPOINTS = IN_DEVELOPMENT;\n\n// Can calls to the /ploobis/create endpoint specify their own fleeb?\nconfig.ALLOW_FLEEB_TO_BE_SPECIFIED_ON_CREATE = true;\n\n// Can ploobis be reset even after email generation has commenced\nconfig.ALLOW_PLOOBIS_RESET_AFTER_EMAIL_GENERATION = !IN_LIVE;\n\n\n// Freeze and export the config vars\nmodule.exports = Object.freeze(config);\n```\n\nLets step though the code above in detail..\n\n\nClient-side Inclusion\n--------------------------------------------------------------------------------\n\nSince some of the config variables are also often needed client side, there's a temptation to simply require `config.js` there too.\nThis is a terrible idea for, hopefully, obvious reasons; it almost certainly exposes security-sensitive values to the end user.\nWe put this warning in place as a last ditch effort to prevent accidental inclusion.\n\n```js\n// This doesn't actually stop the config from being loaded onto clients, it's just a warning for developers\nif (typeof window !== 'undefined') throw new Error(`You definitely shouldn't require ./config on the client`);\n```\n\n\n`envLib.determineAppEnv(process.env.APP_ENV)`\n--------------------------------------------------------------------------------\n\nWe call `determineAppEnv()` to determines the current `APP_ENV`.\n\n```js\n// Determine the current APP_ENV\nconst APP_ENV = envLib.determineAppEnv(\n\tprocess.env.APP_ENV,\n\t[{ cidr: '72.67.5.0/16', env: 'live' }, { cidr: '72.68.5.0/16', env: 'staging' }, { cidr: '72.69.5.0/16', env: 'testing' }],\n);\n```\nIt inspects the servers IP address the `APP_ENV` value supplied by `process.env` (if present).\n\nThe valid `APP_ENV` are:\n\n* `live`\n* `staging`\n* `testing`\n* `development` (default)\n\nNote this differs significantly from `NODE_ENV`, the only recognised value if which is `production`.\nThe conventional relationship between `NODE_ENV` and `APP_ENV` is shown in the table below.\n\n| Environment | `APP_ENV` | `NODE_ENV` |\n| ----------- | --------- | ---------- |\n| live | 'live' | 'production' |\n| staging | 'staging' | 'production' |\n| testing | 'testing' | (`undefined` or any value != 'production') |\n| development | 'development' | (`undefined` or any value != 'production') |\n\n\n`envLib.buildAppFlags(APP_ENV)`\n--------------------------------------------------------------------------------\n\nOnce we have the `APP_ENV` we can use this function to build out a set of flags representing the different environments:\n\n```js\n// Convert the APP_ENV to some handy flags\nconst flags = envLib.buildAppFlags(APP_ENV);\n```\n\nThis is optional but gives us a convenient convention for describing other conditions in the `config.js` file.\n\nOne flag is created for each environment the app supports (usually 'live', 'staging', 'testing' and 'development')\nplus a flag for 'production', which is true if the environment is 'live' or 'staging'.\n\nFor example, if the `APP_ENV` was `staging`, the structure returned by the call above would be:\n\n```js\nconsole.log(flags);\n// { IN_LIVE: false, IN_STAGING: true, IN_TESTING: false, IN_DEVELOPMENT: false, IN_PRODUCTION: true }\n```\n\n\n`dotenv.config(..)`\n--------------------------------------------------------------------------------\n\nStandard practice is to seek out a `.env` file in the directory above the application root, named for the current `APP_ENV`:\n\n```js\n// Attempt to read the local .env file for this APP_ENV\nif (!flags.IN_DEVELOPMENT) dotenv.config({ path: path.resolve(`../${APP_ENV}.env`) });\n```\n\nThis file should contain any variables required for the environment but security sensitive, so not store in the repo.\nEg. Mandrill API keys, merchant account credentials, Mongo DB URIs, etc.\nOften these can be defaulted in development environments.\nThe code above skips this step when `IN_DEVELOPMENT` is true.\n\nIf the `.env` file isn't found a warning will be printed to `stderr` but the app will continue to load.\nSee the `dotenv` [package docs](https://www.npmjs.com/package/dotenv) for the expected/supported format of this file.\n\n**IMPORTANT:**\n\nThe `dotenv` package loads these variables directly into the `process.env` scope.\nThis is the default behaviour of `dotenv` and actually pretty useful if you have variables used by packages that don't accept values any other way.\nIn it's standard usage, no other part of this process alters the `process.env` scope; we mostly work out of the `config` object, created next.\n\n\n`envLib.mergeConfig(APP_ENV, flags, process.env, rules)`\n--------------------------------------------------------------------------------\n\nThe values loaded are next verified and assembled into the `config` object:\n\n```js\n// Extract the vars defined from process.env and apply validation and defaults\nconst config = envLib.mergeConfig(APP_ENV, flags, process.env, {\n\t// ..\n});\n```\n\nThe last argument to this function give us some simple defaulting and validation functionality.\nCombine with the `flags` object, it's a useful way of documenting the variables required in each environment.\n\nIn addition to the `APP_ENV` and `flags` values, **the `mergeConfig()` function will only return variables mentioned in this object**.\nThe `process.env` scope contains a lot of junk we don't want polluting our `config` object; this validation step acts as a whitelist.\n\nVariables are described with a `required` flag and, optionally, a default value.\nIf a variable is `required` but no present in `process.env` (after the `.env` file has been processed) an error will be raised, halting the app.\nIf a variable is both not `required`, not supplied and a `default` is specified, the `default` will be incorporated into the object returned.\n\nVariables definitions can optionally include a `type`, being either `Boolean`, `Number` or `String` (or unspecified).\nIf supplied, the value given by the environment will be interpreted as this type.\nIf an appropriate value can't be unambiguously determined (eg. a value of \"coffee\" suppled for a `Boolean` value) an error will be thrown.\n\n**IMPORTANT:**\n\nAs noted above, the `mergeConfig()` function does not modify the `process.env` scope.\nVariables that are defaulted based on the validation rules supplied will only exist in the object returned by `mergeConfig()`.\n\n\nOther Config Values\n--------------------------------------------------------------------------------\n\nMost apps will also use a number of values that don't need to be set externally (ie. by `process.env` or the `.env` file).\nPlacing these in the `config` object increases maintainability by removing the need to hardcode values and logic throughout an app.\n\nThey're usually either constants or values that are derived from the other environment variables.\n\n### Examples\n\nSupport contact details:\n\n```js\n// Support details\nconfig.FROM_EMAIL = 'support@chumble.com.au';\nconfig.FROM_NAME = 'Chumble Support';\nconfig.SUPPORT_PHONE_NUMBER = '1800 422 554';\n```\n\nAn the URL of an external system based on the current `APP_ENV`:\n\n```js\n// Where should we address the plumbus API\nconfig.PLUMBUS_API_URL = ({\n\tlive:          'https://api.plumbus.net.au',\n\tstaging:       'https://api-staging.plumbus.net.au',\n\ttesting:       'https://api-testing.plumbus.net.au',\n\tdevelopment:   'http://localhost:7634',  // Use a local stub server in dev\n})[APP_ENV];\n```\n\nIt can be useful to control specific functionality with feature flags:\n\n```js\n// Are we disabling developer authentication to developer endpoints?\nconfig.ALLOW_UNAUTHENTICATED_ACCESS_TO_DEVELOPER_ENDPOINTS = IN_DEVELOPMENT;\n\n// Can calls to the /ploobis/create endpoint specify their own fleeb?\nconfig.ALLOW_FLEEB_TO_BE_SPECIFIED_ON_CREATE = true;\n\n// Can ploobis be reset even after email generation has commenced\nconfig.ALLOW_PLOOBIS_RESET_AFTER_EMAIL_GENERATION = !IN_LIVE;\n```\n\n\nExporting the Values\n--------------------------------------------------------------------------------\n\nIn this example we [freeze](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze)\nthe config object before exporting it for use in our app.\nThis goes some way towards preventing other parts of the application from unintentionally setting config values.\n\n```js\n// Freeze and export the config vars\nmodule.exports = Object.freeze(config);\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthinkmill%2Fdevops-env-vars","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthinkmill%2Fdevops-env-vars","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthinkmill%2Fdevops-env-vars/lists"}