Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 19 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,6 @@ Return a .env file as JSON:
dotenv --json
```

```shell
$ dotenv --json | jq 'to_entries | map(select(.key | startswith("DB_")))[] | "\(.key)=\(.value)"'
"DB_HOST=localhost"
"DB_USER=root"
"DB_PASS=password"
```

### Multiline Values

The default behavior is to output a single line value. If you want to output a multiline value,
Expand Down Expand Up @@ -81,11 +74,11 @@ dotenv <key> --delete
### RSA Key Pair

1. **Private Key:** Generate a new key using the `openssl` command. The private key is then stored in the .env file under the variable `RSA_KEY`.
2. **Public Key** The `dotenv` command, with the --multiline flag, retrieves the stored private key and pipes it back to openssl. `openssl` then generates a corresponding public key. This public key is stored in the `.env` file under the variable `RSA_PUB`.
2. **Public Key** The `dotenv` command, with the `--multiline` flag, retrieves the stored private key and pipes it back to openssl. `openssl` then generates a corresponding public key. This public key is stored in the `.env` file under the variable `RSA_PUB`.

```shell
openssl genpkey -algorithm RSA -outform PEM -pkeyopt rsa_keygen_bits:2048 2>/dev/null | dotenv RSA_KEY
dotenv RSA_KEY --multiline | openssl rsa -pubout 2>/dev/null | dotenv RSA_PUB
dotenv RSA_KEY -m | openssl rsa -pubout 2>/dev/null | dotenv RSA_PUB
```

### App Version
Expand All @@ -101,3 +94,20 @@ sed -i "s/^APP_VERSION=.*$/APP_VERSION=$NEW_VERSION/" .env
# Using dotenv
dotenv APP_VERSION --set $NEW_VERSION
```

### JSON Output

Make it pretty with `jq`:

```shell
dotenv --json | jq
```

Or filter the output:

```shell
$ dotenv --json | jq 'to_entries | map(select(.key | startswith("DB_")))[] | "\(.key)=\(.value)"'
"DB_HOST=localhost"
"DB_USER=root"
"DB_PASS=password"
```
2 changes: 1 addition & 1 deletion src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ async function app() {

if (options.json && options.returnAllKeys) {
log.debug('Outputting entire .env file as JSON');
log.info(options.envObject.toJsonString());
log.info(options.envObject.toJsonString(options.multiline));
} else if (options.action.delete) {
handlers.deleteKey(options);
} else if (options.action.set) {
Expand Down
4 changes: 0 additions & 4 deletions src/components/qualifyingRules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@ export function qualifyingRules(settings: Options) {
if (settings.action.set && !settings.singleKey) {
throw new RuleViolationError('Must specify a single key when using --set');
}
// - cannot have both --json and --multiline
if (settings.json && settings.multiline) {
throw new RuleViolationError('Cannot use --json and --multiline together');
}
// - cannot use --delete with any other options
if (settings.action.delete && (settings.action.set || settings.json || settings.multiline)) {
throw new RuleViolationError('Cannot use --delete with any other options');
Expand Down
10 changes: 7 additions & 3 deletions src/envObject.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class EnvValue {
export class EnvValue {
value: string;
lineStart: number;
lineEnd: number;
Expand Down Expand Up @@ -64,8 +64,12 @@ class EnvObject {
return obj;
}

toJsonString(): string {
return JSON.stringify(this.toObj());
toJsonString(pretty:boolean = false): string {
if (pretty) {
return JSON.stringify(this.toObj(), null, 2);
} else {
return JSON.stringify(this.toObj());
}
}
}

Expand Down
71 changes: 40 additions & 31 deletions src/envParser.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
import fs from 'node:fs';
import log from './log.js';
import EnvObject from "./envObject.js";
import EnvParseError from "./errors/EnvParseError.js";
import fs from 'node:fs';
import log from './log.js';

function handleValue(value: string, line: number, startLine: number, envLines: string[], envObject: EnvObject, key: string, quote: string) {
let multilineValue = value.slice(1);
while (!multilineValue.trim().endsWith(quote)) {
multilineValue += '\n' + envLines[++line];
import EnvObject, {EnvValue} from "./envObject.js";
import EnvParseError from "./errors/EnvParseError.js";

function getEndLine(envLines: string[], startLine: number, quoteType: string): number {
let endLine = startLine;
while (!envLines[endLine].trim().endsWith(quoteType)) {
endLine++;
}
return endLine;
}

function extractLines(envLines: string[], startLine: number, endLine: number, quoted: boolean): EnvValue {
let allLines = envLines.slice(startLine, endLine + 1);
allLines[0] = allLines[0].split('=')[1];

let blob = allLines.join('\n').trim();
if (quoted) {
blob = blob.slice(1, -1);
}
envObject[key] = {
value: multilineValue.slice(0, -1),
return {
value: blob,
lineStart: startLine,
lineEnd: line
lineEnd: endLine
};
}

Expand All @@ -21,44 +33,41 @@ function handleValue(value: string, line: number, startLine: number, envLines: s
* @param filePath
*/
function parseEnvFile(filePath: string): EnvObject {
const envContent: string = fs.readFileSync(filePath, 'utf8');
const envLines: string[] = envContent.split('\n');

const envContent: string = fs.readFileSync(filePath, 'utf8');
const envLines: string[] = envContent.split('\n');
const envObject: EnvObject = new EnvObject();

for (let line = 0; line < envLines.length; line++) {
const trimmedLine: string = envLines[line].trim();
const startLine: number = line;
for (let lineCurrent = 0; lineCurrent < envLines.length; lineCurrent++) {
const trimmedLine: string = envLines[lineCurrent].trim();
const lineStart: number = lineCurrent;

if (!trimmedLine) {
log.debug(`${line + 1} | Ignoring empty line`);
log.debug(`${lineCurrent + 1} | Ignoring empty line`);
} else if (trimmedLine.startsWith('#')) {
log.debug(`${line + 1} | Ignoring comment`);
log.debug(`${lineCurrent + 1} | Ignoring comment`);
} else {
const [key, ...valueParts] = trimmedLine.split('=');
const value: string = valueParts.join('=').trim();

if (key === trimmedLine) {
log.debug(`${line + 1} | Ignoring line without key=value: ${envLines[line]}`);
log.debug(`${lineCurrent + 1} | Ignoring line without key=value: ${envLines[lineCurrent]}`);
continue;
}

if (value.startsWith('"')) {
log.debug(`${line + 1} | key: ${key}, double quoted, ${value.endsWith('"') ? 'single line' : 'multiline'}`);
handleValue(value, line, startLine, envLines, envObject, key, '"');
log.debug(`${lineCurrent + 1} | key: ${key}, double quoted, ${value.endsWith('"') ? 'single line' : 'multiline'}`);
lineCurrent = getEndLine(envLines, lineStart, '"');
envObject[key] = extractLines(envLines, lineStart, lineCurrent, true);
} else if (value.startsWith("'")) {
log.debug(`${line + 1} | key: ${key}, single quoted, ${value.endsWith("'") ? 'single line' : 'multiline'}`);
handleValue(value, line, startLine, envLines, envObject, key, "'");
log.debug(`${lineCurrent + 1} | key: ${key}, single quoted, ${value.endsWith("'") ? 'single line' : 'multiline'}`);
lineCurrent = getEndLine(envLines, lineStart, "");
envObject[key] = extractLines(envLines, lineStart, lineCurrent, true);
} else {
log.debug(`${line + 1} | key: ${key}, un-quoted, single line`)
log.debug(`${lineCurrent + 1} | key: ${key}, un-quoted, single line`)
if (value.includes('"') || value.includes("'")) {
throw new EnvParseError(line + 1, `Invalid value: ${envLines[line]}`);
throw new EnvParseError(lineCurrent + 1, `Invalid value: ${envLines[lineCurrent]}`);
}
envObject[key] = {
value: value,
lineStart: line,
lineEnd: line
};
envObject[key] = extractLines(envLines, lineStart, lineCurrent, false);
}
}
}
Expand Down