diff --git a/index.js b/index.js index f322dfc..e796b89 100644 --- a/index.js +++ b/index.js @@ -3,12 +3,32 @@ const fs = require('fs') const path = require('path') const { promisify } = require('util') -function escapeNewlines (str) { - return str.replace(/\n/g, '\\n') +function escapeCharacters (str) { + let wrapper = '' + + // If the string contains a space or dollar sign, wrap it in single quotes. + if (str.match(/[\s$]/)) { + wrapper = '\'' + } + + // If the string contains a newline, escape it and wrap in double quotes. + // Also escape any dollar signs to prevent interpolation. + if (str.match(/\n/)) { + str = str.replace(/\$/g, '\\$').replace(/\n/g, '\\n') + wrapper = '"' + } + + // If we're wrapping the string, escape any wrapper characters. + if (wrapper !== '') { + str = str.replace(new RegExp(wrapper, 'g'), `\\${wrapper}`) + } + + // Wrap the string (if necessary) and return it. + return [wrapper, str, wrapper].join('') } function format (key, value) { - return `${key}=${escapeNewlines(value)}` + return `${key}=${escapeCharacters(value)}` } module.exports = async function updateDotenv (env) { diff --git a/index.test.js b/index.test.js index 2b79811..c124524 100644 --- a/index.test.js +++ b/index.test.js @@ -25,7 +25,7 @@ describe('update-dotenv', () => { test('properly writes multi-line strings', async () => { await updateDotenv({ FOO: 'bar\nbaz' }) - expect(fs.readFileSync('.env', 'UTF-8')).toEqual('FOO=bar\\nbaz') + expect(fs.readFileSync('.env', 'UTF-8')).toEqual('FOO="bar\\nbaz"') }) test('appends new variables to existing variables', async () => { @@ -33,4 +33,24 @@ describe('update-dotenv', () => { await updateDotenv({ SECOND: 'bar' }) expect(fs.readFileSync('.env', 'UTF-8')).toEqual('FIRST=foo\nSECOND=bar') }) + + test('wraps strings with spaces in quotes', async () => { + await updateDotenv({ FOO: 'foo bar' }) + expect(fs.readFileSync('.env', 'UTF-8')).toEqual('FOO=\'foo bar\'') + }) + + test('wraps strings with dollar signs in quotes', async () => { + await updateDotenv({ FOO: '$foo $bar' }) + expect(fs.readFileSync('.env', 'UTF-8')).toEqual('FOO=\'$foo $bar\'') + }) + + test('escapes single quotes', async () => { + await updateDotenv({ FOO: '\'foo\' "$and" \'bar\'' }) + expect(fs.readFileSync('.env', 'UTF-8')).toEqual('FOO=\'\\\'foo\\\' "$and" \\\'bar\\\'\'') + }) + + test('escapes double quotes', async () => { + await updateDotenv({ FOO: "\"foo\"\n'$and'\n\"bar\"" }) + expect(fs.readFileSync('.env', 'UTF-8')).toEqual('FOO="\\"foo\\"\\n\'\\$and\'\\n\\"bar\\""') + }) })