Skip to content

a totally tubular πŸ„πŸΌ library to parse and manipulate dotenv syntax

License

Notifications You must be signed in to change notification settings

glitchdotcom/dotenv

Repository files navigation

@glitchdotcom/dotenv

@glitchdotcom/dotenv is a totally tubular πŸ„πŸΌ library to parse and manipulate dotenv syntax while maintaining comments and whitespace. @glitchdotcom/dotenv has zero dependencies, is licensed under the MIT license, and has a simple, functional api.

install

# npm
npm install @glitchdotcom/dotenv

# yarn
yarn add @glitchdotcom/dotenv

# pnpm
pnpm install @glitchdotcom/dotenv

license, code of conduct, and contributing

@glitchdotcom/dotenv is licensed under the MIT license. a copy of this license can be found in the LICENSE file.

for this project's code of conduct, see the CODE_OF_CONDUCT.md file.

do you want to contribute to something ~amazing~ (this package)? yes?? well the CONTRIBUTING.md file should tell you everything you need to know! if it doesn't, let us know (which would make a perfect first contribution)!

quickstart

to see how dotenv syntax looks as a node list, you can play with an interactive parser over at glitch.com!

import * as dotenv from "@glitchdotcom/dotenv";
// or... const dotenv = require("@glitchdotcom/dotenv");

const envText = `SOME_KEY=some value`;

// node list represents the following as an array of objects...
// SOME_KEY=some value
let nodes = dotenv.textToNodes(envText);

// find index of the key value node with SOME_KEY as the key
const index = nodes.find((node) => node.key === "SOME_KEY");

// nodes now represents...
// ANOTHER_KEY=some value
nodes = dotenv.changeKey(nodes, index, "ANOTHER_KEY");

// nodes now represents...
// ANOTHER_KEY=another value
nodes = dotenv.changeValue(nodes, index, "another value");

// nodes now represents...
// ANOTHER_KEY=another value
// NEW_KEY=new value
nodes = dotenv.appendKeyValue(nodes, "NEW_KEY", "new value");

// nodes now represents...
// NEW_KEY=new value
nodes = dotenv.removeKeyValue(nodes, index);

// newText will be "NEW_KEY=new value"
const newText = dotenv.nodesToText(nodes);

// parsedText will be { NEW_KEY: "new value" }
const parsedText = dotenv.parseText(newText);

// parsedNodes will be { NEW_KEY: "new value" }
const parsedNodes = dotenv.parseNodes(nodes);

api

node types

dotenv exports a handful of consts that represent the different types of nodes. there are 3 node types, KEY_VALUE, COMMENT, and INVALID_LINE.

example:

import * as dotenv from "@glitchdotcom/dotenv";

let nodes = dotenv.textToNodes(`KEY=value
# some comment`);

let firstCommentNode = nodes.find((node) => node.type === dotenv.COMMENT);

if (!firstCommentNode) {
	// no comment node found!
} else {
	assert(firstCommentNode.comment === "some comment");
}

node objects and properties

KEY_VALUE nodes have the following properties: type (will always be "KEY_VALUE"), key (no whitespace), value (no whitespace), escapedValue (value including escaped newlines), fullKey (includes extraneous whitespace), fullValue (includes extraneous whitespace), quote (", ', or an empty string depending on what quotes are around the value), and originalQuote (the quotes that were around the value when the text was parsed). as an example, a key-value pair like this...

# extra whitespace intentional
  SOME_KEY= some value

...will be parsed into a node like this...

{
	type: "KEY_VALUE",
	key: "SOME_KEY",
	fullKey: "  SOME_KEY",
	value: "some value",
	escapedValue: "some value",
	fullValue: " some value",
	quote: "",
	originalQuote: ""
}

if the value contained escaped newlines and quotes around it like this...

# extra whitespace intentional
  SOME_KEY=" some \n value"

...then it will be parsed into something like this...

{
	type: "KEY_VALUE",
	key: "SOME_KEY",
	fullKey: "  SOME_KEY",
	value: " some \n value", // notice the extra space in front
	escapedValue: " some \\n value",
	fullValue: "\" some \\n value\"",
	quote: "\"", // quote and originalQuote contain the quote that was used
	originalQuote: "\""
}

COMMENT nodes have the following properties: type (will always be "COMMENT"), prefix (pound symbol and extraneous whitespace before comment body), and comment (the comment body). as an example, a comment line like this...

  # a comment! leading whitespace intentional

...will be parsed into a node like this...

{
	type: "COMMENT",
	prefix: "  # ", // note the whitespace in the prefix
	comment: "a comment! leading whitespace intentional"
}

INVALID_LINE nodes have the following properties: type (will always be INVALID_LINE), and line (the contents of the line). as an example, an invalid line like this...

i am an invalid line because i do not contain an equals sign!

...will be parsed into a node like this...

{
	type: "INVALID_LINE",
	line: "i am an invalid line because i do not contain an equals sign!"
}

an important thing to note with INVALID_LINE nodes is that an empty line is also an INVALID_LINE since it doesn't contain an equals sign! empty lines aren't ignored since one of the goals of @glitchdotcom/dotenv is to preserve as much whitespace as possible. we have to capture that data somehow, so we do it with an INVALID_LINE node that has an empty string as the line property.

{
	type: "INVALID_LINE",
	line: ""
}

textToNodes/nodesToText

textToNodes converts a string to a dotenv node list that can be manipulated. the node list represents the dotenv data contained within the string as an array of nodes. it maintains comments, empty/invalid lines, and extraneous whitespace. it does everything it can to return something even if the string passed to it is not valid dotenv syntax. nodesToText does the reverse of textToNodes. it takes a node list and converts it back into a string. like textToNodes, it maintains comments, empty/invalid lines, and extraneous whitespace.

example:

import * as dotenv from "@glitchdotcom/dotenv";

let nodes = dotenv.textToNodes(`KEY=value`);
let text = dotenv.nodesToText(nodes);

assert(text === `KEY=value`);

appendKeyValue

appendKeyValue adds a new key value pair to the end of the node list. like all dotenv manipulation functions, appendKeyValue is immutable and returns a new list instead of modifying the list in place.

example:

import * as dotenv from "@glitchdotcom/dotenv";

let nodes = dotenv.textToNodes(`KEY=value`);
nodes = dotenv.appendKeyValue(nodes, "SOMETHING", "anything");
let text = dotenv.nodesToText(nodes);

assert(
	text ===
		`KEY=value
SOMETHING=anything`,
);

changeKey

changeKey changes the key of a key-value node. using changeKey does not modify any whitespace that was present in the original text. like all dotenv manipulation functions, changeKey is immutable and returns a new list instead of modifying the list in place.

examples:

import * as dotenv from "@glitchdotcom/dotenv";

let nodes = dotenv.textToNodes(`KEY=value`);
let index = nodes.find((node) => node.key === "KEY");
nodes = dotenv.changeKey(nodes, index, "SOMETHING_ELSE");
let text = dotenv.nodesToText(nodes);

assert(text === `SOMETHING_ELSE=value`);

// now with whitespace!
let nodes = dotenv.textToNodes(` KEY   =value`);
let index = nodes.find((node) => node.key === "KEY"); // the `key` property does not include whitespace
nodes = dotenv.changeKey(nodes, index, "SOMETHING_ELSE");
let text = dotenv.nodesToText(nodes);

assert(text === ` SOMETHING_ELSE   =value`); // when converting back to text, the original whitespace is preserved!

changeValue

changeValue changes the value of a key-value node. it automatically escapes newlines and adds quotes around the value if necessary. like changeKey, the original whitespace is not modified. like all dotenv manipulation functions, changeValue is immutable and returns a new list instead of modifying the list in place.

example:

import * as dotenv from "@glitchdotcom/dotenv";

let nodes = dotenv.textToNodes(`KEY=  value`); // notice whitespace
let index = nodes.find((node) => node.key === "KEY");
nodes = dotenv.changeValue(nodes, index, "another\nvalue"); // new value contains a newline
let text = dotenv.nodesToText(nodes);

// the value is now quoted and the newline was escaped! also, the preceding whitespace
// was kept intact!
assert(text === `KEY=  "another\\nvalue"`);

removeKeyValue

removeKeyValue removes a key-value node from the node list. like all dotenv manipulation functions, removeKeyValue is immutable and returns a new list instead of modifying the list in place.

example:

import * as dotenv from "@glitchdotcom/dotenv";

let nodes = dotenv.textToNodes(`KEY=value
SOMETHING=anything`);
let index = nodes.find((node) => node.key === "SOMETHING");
nodes = dotenv.removeKeyValue(nodes, index); // removing the `SOMETHING` property
let text = dotenv.nodesToText(nodes);

assert(text === `KEY=value`);

parseText

parseText takes dotenv text and extracts the key-value pairs into an object (similar to the original dotenv package).

example:

import * as dotenv from "@glitchdotcom/dotenv";

let env = dotenv.parseText(`KEY=value`);

assert(env.KEY === `value`);

parseNodes

parseNodes takes a node list and extracts the key-value pairs into an object. generally you'd want to use parseText, however if you've already converted the text into a node list, parseNodes would skip the text parsing step.

example:

import * as dotenv from "@glitchdotcom/dotenv";
let nodes = dotenv.textToNodes(`KEY=value`);
let env = dotenv.parseNodes(nodes);
assert(env.KEY === `value`);

Made by Glitch

\ γ‚œoγ‚œ)γƒŽ

About

a totally tubular πŸ„πŸΌ library to parse and manipulate dotenv syntax

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages