
Research
NPM targeted by malware campaign mimicking familiar library names
Socket uncovered npm malware campaign mimicking popular Node.js libraries and packages from other ecosystems; packages steal data and execute remote code.
Eases NodeJS environment variable handling, like env-cmd or dotenv, but with powerfull features and extensibility for adding custom providers (as plugins) for load, pull and push the variables from different stores.
First, download and install NodeJS. Version 16
or higher is required.
Validate installed versions of node and npm with:
> node -v
v16.14.2
> npm -v
8.3.0
You can initialize a new npm project using:
> npm init
🔔 Make sure that you have NodeJS 14+ installed on your computer.
> npm install @achs/env
added 1 packages, and audited 1 packages in 1s
found 0 vulnerabilities
> _
> node_modules/.bin/env --help
Usage: env [command] [options..] [: subcmd [:]] [options..]
Commands:
env [options..] [: <subcmd> :]
env pull [options..]
env push [options..]
env schema [options..]
> _
> npx env --help
Usage: env [command] [options..] [: subcmd [:]] [options..]
Commands:
env [options..] [: <subcmd> :]
env pull [options..]
env push [options..]
env schema [options..]
> _
package.json
:{
...,
"scripts": {
// starts project injecting "dev" environment variables and debug log level
"start:dev": "env -e dev -m debug : node dist/main.js : --log debug",
// starts project injecting "prod" environment variables
"start:prod": "env -e prod -m debug : node dist/main.js",
...,
// builds project injecting "prod" environment variables
"build:prod": "env -e prod -m build : tsc",
...,
"env:schema": "env schema -e dev --ci",
// uploads environment "dev" variables
"env:push:dev": "env push -e dev",
// downloads environment "dev" variables
"env:pull:dev": "env pull -e dev"
},
...
}
file: dist/main.js
console.log(`My environment loaded is: ${process.env.ENV}`);
> npm run start:dev
13:31:59.865 INFO loading dev environment in debug mode
13:31:59.911 DEBUG using package-json provider
13:31:59.912 DEBUG using app-settings provider
13:31:59.914 DEBUG using secrets provider
13:32:00.109 DEBUG environment loaded:
{
NODE_ENV: 'development',
ENV: 'dev',
VERSION: '1.0.0',
NAME: '@my-app',
VAR1: true,
VAR2: true,
GROUP1__VAR1: 'G1V2',
ARR1: '1,val,true',
SECRET: '***'
}
13:32:00.116 INFO executing command > node dist/main.js
My environment loaded is: dev
13:32:00.232 INFO process finished successfully
> _
├── src/
│ ├── commands/ # lib commands handlers
│ │ ├── env.command.ts
│ │ ├── export.command.ts
│ │ ├── pull.command.ts
│ │ ├── push.command.ts
│ │ └── schema.command.ts
│ ├── interfaces/ # provider interfaces
│ ├── providers/ # integrated providers
│ │ ├── package-json.provider.ts
│ │ ├── app-settings.provider.ts
│ │ ├── local.provider.ts
│ │ └── azure-key-vault.provider.ts
│ ├── utils/
│ │ ├── command.util.ts
│ │ ├── interpolate.util.ts
│ │ ├── json.util.ts
│ │ ├── normalize.util.ts
│ │ ├── schema.util.ts
│ │ └── logger.ts
│ ├── arguments.ts # global arguments
│ ├── exec.ts # initialization logic (load config, commands, etc.)
│ └── main.ts
├── tests/ # integration tests
├── .eslintrc.json
├── jest.config.json
├── tsconfig.build.json
└── tsconfig.json
Options handling has the ability of replace arguments itself, using [[
and ]]
as delimiters.
So, in example for define your config file path, you must use your root argument,
supposing root has the value of "config", this definition [[root]]/any-config-file.json
will be
config/any-config-file.json
, or if your env argument is "dev", this definition
[[root]]/config-file.[[env]].json
will be config/config-file.dev.json
.
Option | Description | Type | Default | Required? |
---|---|---|---|---|
--help | Shows help | boolean | No | |
--e, --env | Environment for load | string | Yes | |
-m, --modes | Execution modes | string[] | [] | No |
--nd, --nestingDelimiter | Nesting level delimiter for flatten | string | __ | No |
--arrDesc, --arrayDescomposition | Whether serialize or break down arrays | boolean | false | No |
-x, --expand | Interpolates environment variables using itself | boolean | false | No |
-ci | Continuous Integration mode | boolean | false | No |
Option | Description | Type | Default | Required? |
---|---|---|---|---|
--root | Default environment folder path | string | env | No |
-c, --configFile | Config JSON file path | string | [[root]]/settings/settings.json | No |
-s, --schemaFile | Environment Schema JSON file path | string | [[root]]/settings/schema.json | No |
Option | Description | Type | Default | Required? |
---|---|---|---|---|
-r, --resolve | Whether merges new schema or override | merge, override | merge | No |
--null, --nullable | Whether variables are nullable by default | boolean | true | No |
--df, --detectFormat | Whether format of strings variables are included in schema | boolean | true | No |
Option | Description | Type | Default | Required? |
---|---|---|---|---|
--log, --logLevel | Log level | silly, trace, debug, info, warn, error | info | No |
env
Inject your environment variables into process.env
and executes a command.
env -e [env] [options..] [: subcmd [:]] [options..]
Examples:
> env -e dev -m test unit : npm test
> env -e dev -m debug : npm start : -c [[root]]/[[env]].env.json
> env -e prod -m build optimize : npm build
pull
Pulls environment variables from providers stores.
env pull -e [env] [options..]
Option | Description | Type | Default | Required? |
---|---|---|---|---|
-o, --overwrite | Overwrite local variables | boolean | false | No |
Examples:
> env pull -e dev
push
Pushes environment variables to providers stores.
env push -e [env] [options..]
Option | Description | Type | Default | Required? |
---|---|---|---|---|
-f, --force | Force push for secrets (replace all) | boolean | false | No |
Examples:
> env push -e dev
schema
Generates validation schema from providers output variables.
env schema -e [env] -m [modes] [options..]
Examples:
> env schema -e dev -m build
export
Export unified environment variables to a file from providers.
env export -e [env] -m [modes] [options..]
Option | Description | Type | Default | Required? |
---|---|---|---|---|
-u, -p, --uri | Uri for export file with variables | string | .env | No |
-f, --format | Format for export variables | string | dotenv | No |
Examples:
> env export -e dev -m build -f json --uri [[env]].env.json
Main feature of this library is using providers for get and set environment variables. So, you can define your own provider, but lib came with 3 integrated providers:
package-json
Load some info from your project package.json
.
Info read is:
{
"version": "1.0.0",
"project": "project-name",
"name": "@package-name",
"title": "app-name",
"description": "any description"
}
Option | Description | Type | Default | Required? |
---|---|---|---|---|
--vp, --varPrefix | Prefix for loaded variables | string | "" | No |
Examples:
> env -e dev -m build : react-script build : --vp REACT_APP_
app-settings
Non secrets loader for appsettings.json
.
appsettings.json
file has the format below:
{
"|DEFAULT|": {},
"|MODE|": {},
"|ENV|": {}
}
In example:
{
"|DEFAULT|": {
"VAR1": "v1_default"
},
"|MODE|": {
"build": {
"NODE_ENV": "production"
},
"debug": {
"NODE_ENV": "development"
},
"test": {
"NODE_ENV": "test"
}
},
"|ENV|": {
"dev": {
"C1": "V1",
"C2": "V2",
"C3": 3,
"GROUP1": {
"VAR1": null,
"VAR2": "G1V2",
"VAR3": true,
"GROUP2": {
"VAR1": "G1G2V1"
}
},
"C4": "23"
}
}
}
Option | Description | Type | Default | Required? |
---|---|---|---|---|
--ef, --envFile | Environment variables file path | string | [[root]]/appsettings.json | No |
--sp, --sectionPrefix | Prefix for env and modes in env file | string | :: | No |
package-json
Load some info from your project package.json
.
Info read is:
{
"version": "1.0.0",
"project": "project-name",
"name": "@package-name",
"title": "app-name",
"description": "any description"
}
Option | Description | Type | Default | Required? |
---|---|---|---|---|
--vp, --varPrefix | Prefix for loaded variables | string | "" | No |
Examples:
> env -e dev -m build : react-script build : --vp REACT_APP_
azure-key-vault
Azure Key Vault provider, allows to load secrets from vault store to env/secrets/[[env]].env.json
per environment.
Also, handles env/secrets/[[env]].local.env.json
for load local variables with precedence over base.
Option | Description | Type | Default | Required? |
---|---|---|---|---|
--secretFolder | Secret variables folder path | string | [[root]]/secrets | No |
--secretFile | Secret variables file path | string | [[secretFolder]]/[[env]].env.json | No |
--localSecretFile | Local secret variables file path | string | [[secretFolder]]/[[env]].local.env.json | No |
-k, --keys, --keysFile | Azure Key Vault SPN credentials files paths | string[] | ['../keys.json', '[[root]]/keys.json'] | No |
--url, --vaultUrl | Azure Key Vault server URL | string | Yes | |
--spn, --clientId, --id | SPN Client ID | string | Yes | |
-p --password, --pass, --clientSecret | SPN Client Secret Password | string | Yes | |
-t, --tenant | Azure Tenant ID | string | Yes | |
--mock | Mocks Azure Key Vault client (testing purpose) | string | false | No |
You can create your custom providers, in two ways:
How to load your provider is shown in Config Section.
In example, a provider exported by your NPM package written in TypeScript should be like:
import { CommandArguments, EnvProvider } from '@achs/env';
import { logger, readJson, writeJson } from '@achs/env/utils';
const KEY = 'my-unique-provider-key';
interface MyProviderCommandArguments extends CommandArguments {
anyExtraOption: boolean;
}
export const MyProvider: EnvProvider<MyProviderCommandArguments> = {
// unique identifier for provider
key: KEY,
// (optional) allows to provider adds new arguments/options
// to commands using yargs for builder
builder: (builder) => {
builder.options({
anyExtraOption: {
group: KEY,
alias: ['a', 'aeo'],
type: 'boolean',
default: false,
describe: 'Any option description',
},
});
},
// call on environment variables loading,
// may be a Promise
load: ({ env, modes, ...options }) => {
if (env === 'dev')
return {
NODE_ENV: 'development',
};
// you can return a list of JSON environment variables for merge
return [
{
NODE_ENV: 'production',
},
{
ANY_VAR: 'ANY_VALUE',
ANY_GROUP: {
INNER_VAR: 12,
},
},
];
},
// (optional) call on pulling variables from provider store,
// config may pass in your config file
pull: ({ env, modes, ...options }, config) => {
// anyway you want for pulling variables to cache
},
// (optional) call on pushing/updating variables to provider store,
// config may pass in your config file
push: ({ env, modes, ...options }, config) => {
// anyway you should do for pushing or updating your variables
},
};
You can configure any config argument inside you config file, but commonly providers are designed for this purpose.
{
"log": "silly",
// will hide values of keys SECRET and MY_API_KEY in logging
"logMaskValuesOfKeys": ["SECRET", "MY_API_KEY"],
// integrated providers and custom providers together
"providers": [
{
"path": "package-json"
},
{
"path": "app-settings"
},
{
"path": "azure-key-vault",
"config": {
"dev": {
"vaultUrl": "https://kv-desa-ittec-sti.vault.azure.net"
},
"qa": {
"vaultUrl": "https://kv-qa-ittec-sti.vault.azure.net"
}
}
},
{
"path": "local"
},
{
// custom NPM package
"path": "@npm-package",
"type": "module",
"config": {
"any-config": "any value"
}
},
{
// custom script inside project
"path": "scripts/custom-loader.js",
"type": "script"
}
]
}
Allows you to store your secrets in Azure Key Vault.
For load desired environment, add you npm script like env -e <env> -m <mode1[ mode2]> : <your-command>
.
In example: env -e dev -m debug : npm start
Your env/secrets
folder will contain files below:
This folder should contains environment variables files for system environments.
Your keys.json
file should contains you Azure Key Vault SPN credentials per environment:
{
"<env-name>": {
"vaultUrl": "<azure-key-vault-url>", // you can skip this var if present in config
"clientId": "<spn-client-id>",
"clientSecret": "<spn-secret-password>",
"tenantId": "<tenant-id>"
},
...
}
In example:
{
"dev": {
"clientId": "f176a774-239e-4cd3-8551-88fd9fb9b441",
"clientSecret": "WyBwkmcL8rGQe9B2fvRLDrqDuannE4Ku",
"tenantId": "6d4bbe0a-5654-4c69-a682-bf7dcdaed8e7"
},
"qa": {
"clientId": "5dcd9f45-7067-4387-94d8-e5e7066ba630",
"clientSecret": "60ec5e16430a46eba70dfea80d721b66",
"tenantId": "6d4bbe0a-5654-4c69-a682-bf7dcdaed8e7"
}
}
Your secrets will be grouped, using your "name" and "project" variables from package.json
file.
This file allows to load environment files locally first run time.
You can use two command scripts for refresh your local env files or publish/updates env files in the azure key vault from your local files.
env pull -e <env> [-o]
. (-o forces to replace your local file).env push -e <env>
.You can set your credentials variables from node environment variables.
In example:
user@machine:/mnt/c/Users/user$ AZURE_VAULT_URL=https://kv-desa-ittec-sti.vault.azure.net \
AZURE_CLIENT_ID=f176a774-239e-4cd3-8551-88fd9fb9b441 \
AZURE_CLIENT_SECRET=WyBwkmcL8rGQe9B2fvRLDrqDuannE4Ku \
AZURE_TENANT_ID=6d4bbe0a-5654-4c69-a682-bf7dcdaed8e7 \
node_modules/.bin/env pull -e dev -o
or
user@machine:/mnt/c/Users/user$ npx pull -e dev -o \
--vaultUrl https://kv-desa-ittec-sti.vault.azure.net \
--spn f176a774-239e-4cd3-8551-88fd9fb9b441 \
--password WyBwkmcL8rGQe9B2fvRLDrqDuannE4Ku \
--tenant 6d4bbe0a-5654-4c69-a682-bf7dcdaed8e7
This lib uses JSON schema for validate and retrieve secrets from store.
For each property in the file, loader will retrieve the value from Azure Key Vault.
When you push a new variable from any of your environment secrets
file, env.schema.json
will be updated automatically.
If you want to ignore to load some variable without delete it, you can remove
the variable from env.schema.json
.
You can organize your keys in nested objects, and declare project shared secrets (skips group separation) prefixing with '$' like:
{
// .dev.env.json
"$SHARED": "sharedValue",
"GROUP1": {
"$SHARED": "sharedValue2",
"VAR": "anyValue1",
...
},
"GROUP2": {
"VAR": "anyValue2",
"SUBGROUP1": {
"VAR": "anyValue1",
...
},
...
},
"VAR3": "anyValue3",
...
}
So, in your application you can use the variables as shown below:
const myVar1 = process.env.GROUP1__VAR;
const myVar2 = process.env.GROUP2__VAR;
const myVar2 = process.env.GROUP2__SUBGROUP1_VAR;
const myVar3 = process.env.VAR3;
// shared vars will load in every project
const mySharedVar1 = process.env.SHARED;
const mySharedNestedVar2 = process.env.GROUP1__SHARED;
(dev|qa|prod).env.json
(dev|qa|prod).local.env.json
(takes precedence over previous)Project uses ESLint, for code formatting and code styling normalizing.
For correct interpretation of linters, is recommended to use Visual Studio Code as IDE and install the plugins in .vscode folder at 'extensions.json', as well as use the config provided in 'settings.json'
For last changes see CHANGELOG.md file for details.
FAQs
Extensible environment variables handler for NodeJS apps
We found that @achs/env demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Socket uncovered npm malware campaign mimicking popular Node.js libraries and packages from other ecosystems; packages steal data and execute remote code.
Research
Socket's research uncovers three dangerous Go modules that contain obfuscated disk-wiping malware, threatening complete data loss.
Research
Socket uncovers malicious packages on PyPI using Gmail's SMTP protocol for command and control (C2) to exfiltrate data and execute commands.