diff --git a/README.md b/README.md index 47acc25..30cdc2e 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,8 @@ This can be changed by options: * `--onlyUpdate` - prevents new keys to be appended, only existing keys are updated * `--smartAppend` - finds most similar group of variables and appends new variables between them, preserving alphabetical order. +To disable updating (overwriting) existing keys, and only append (supplement with) new keys, use `--onlyAppend` + To operate on files, specify input file(s) with `-i` and output file with `-o`. To modify first input file use `-m`. ### Modify files @@ -57,6 +59,7 @@ Options: -m, --modify Modify first input file -b, --beautify Beautifies resulting env file -u, --onlyUpdate Only updates existing values, without appending new values + -a, --onlyAppend Only append new values, without updating existing values -p, --smartAppend Smart appends variables depending on their names -r, --resilient Ignore files that cannot be read during update -s, --silent Mute all messages and errors @@ -144,6 +147,7 @@ Both `update` as well as `stringifyTokens` take `config` parameter: type Config = { beautify?: boolean; // if true, output will be beautified/normalized. Default: false enforceNewLineEnd?: boolean; // if true, output will have ensured \n character at end. Default: true + noUpdate?: boolean; // if true, only new values will be appended, but existing one wont be updated modifyMode?: ModifyMode; // decide if new keys should or should not be added, and where. Default: ModifyMode.APPEND }; ``` \ No newline at end of file diff --git a/src/bin.test.ts b/src/bin.test.ts index 5e22728..d87e190 100644 --- a/src/bin.test.ts +++ b/src/bin.test.ts @@ -215,12 +215,24 @@ describe('Update command', () => { expect(mockStdout).toHaveBeenCalledWith('SERVER_PORT=8080\nSERVER_HOST=localhost\nSERVER_PASSWORD=secret\nCLIENT_HOST=192.168.4.42\nCLIENT_PORT=3000\n'); }); + it('merge files without update', () => { + program.parse(['node', 'test', '-a', '-i', 'first.env', 'second.env', 'third.env']); + expect(mockExit).toBeCalled(); + expect(mockStdout).toHaveBeenCalledWith('SERVER_PORT=80\nSERVER_HOST=localhost\nSERVER_PASSWORD=secret\nCLIENT_HOST=192.168.4.42\nCLIENT_PORT=3000\n'); + }); + it('merge files with smart append', () => { program.parse(['node', 'test', '-p', '-i', 'first.env', 'second.env', 'third.env']); expect(mockExit).toBeCalled(); expect(mockStdout).toHaveBeenCalledWith('CLIENT_HOST=192.168.4.42\nCLIENT_PORT=3000\nSERVER_PASSWORD=secret\nSERVER_PORT=8080\nSERVER_HOST=localhost\n'); }); + it('merge files with smart append without update', () => { + program.parse(['node', 'test', '-p', '-a', '-i', 'first.env', 'second.env', 'third.env']); + expect(mockExit).toBeCalled(); + expect(mockStdout).toHaveBeenCalledWith('CLIENT_HOST=192.168.4.42\nCLIENT_PORT=3000\nSERVER_PASSWORD=secret\nSERVER_PORT=80\nSERVER_HOST=localhost\n'); + }); + it('merge files and set from parameter', () => { program.parse(['node', 'test', '-i', 'first.env', 'second.env', 'third.env', '--', 'SERVER_PASSWORD=updated value']); expect(mockExit).toBeCalled(); @@ -229,6 +241,14 @@ describe('Update command', () => { ); }); + it('merge files and set from parameter without update', () => { + program.parse(['node', 'test', '-a', '-i', 'first.env', 'second.env', 'third.env', '--', 'SERVER_PASSWORD=tried updated value', 'NEW_VALUE=append']); + expect(mockExit).toBeCalled(); + expect(mockStdout).toHaveBeenCalledWith( + 'SERVER_PORT=80\nSERVER_HOST=localhost\nSERVER_PASSWORD=secret\nCLIENT_HOST=192.168.4.42\nCLIENT_PORT=3000\nNEW_VALUE=append\n', + ); + }); + it('merge files and set from parameter and invalid parameter', () => { program.parse(['node', 'test', '-i', 'first.env', 'second.env', 'third.env', '--', 'SERVER_PASSWORD=updated value', 'INVA LID', 'LAST_ARGUMENT=correct']); expect(mockExit).toHaveBeenCalledWith(1); diff --git a/src/binlib.ts b/src/binlib.ts index 7e25206..cb8522e 100644 --- a/src/binlib.ts +++ b/src/binlib.ts @@ -101,6 +101,7 @@ export function makeProgram() { .option('-m, --modify', 'Modify first input file') .option('-b, --beautify', 'Beautifies resulting env file') .option('-u, --onlyUpdate', 'Only updates existing values, without appending new values') + .option('-a, --onlyAppend', 'Only append new values, without updating existing values') .option('-p, --smartAppend', 'Smart appends variables depending on their names') .option('-r, --resilient', 'Ignore files that cannot be read during update') .option('-s, --silent', 'Mute all messages and errors') @@ -128,6 +129,7 @@ export function makeProgram() { let sourceParsingError = ''; config.beautify = options.beautify || false; + config.noUpdate = options.onlyAppend || false; config.modifyMode = options.onlyUpdate ? ModifyMode.NO_APPEND : options.smartAppend ? ModifyMode.SMART_APPEND : ModifyMode.APPEND; const baseData = inputData.shift() || ''; diff --git a/src/manipulation.test.ts b/src/manipulation.test.ts index 75f19cb..6d88bb9 100644 --- a/src/manipulation.test.ts +++ b/src/manipulation.test.ts @@ -104,6 +104,64 @@ describe('Updating tokens', () => { ); }); + it('do not update in append only mode', () => { + expect(stringifyTokens(update([vtoken('ALA', 'ma kota')], [['ALA', 'ma psa']], { modifyMode: ModifyMode.APPEND, noUpdate: true }))).toEqual('ALA="ma kota"\n'); + expect( + stringifyTokens( + update( + [vtoken('ALA', 'ma kota')], + [ + ['ALA', 'ma psa'], + ['ALA', 'ma jednak kota'], + ], + { modifyMode: ModifyMode.APPEND, noUpdate: true }, + ), + ), + ).toEqual('ALA="ma kota"\n'); + + expect( + stringifyTokens( + update( + [vtoken('HOST', '127.0.0.1'), nltoken(), vtoken('PORT', '80'), nltoken(), vtoken('LOGIN', 'root')], + [['LOGIN', 'debian'], { name: 'PORT', value: '8080', comment: ' # debug only' }], + { modifyMode: ModifyMode.APPEND, noUpdate: true }, + ), + ), + ).toEqual('HOST=127.0.0.1\nPORT=80\nLOGIN=root\n'); + + expect( + stringifyTokens( + update( + [ + ctoken('###########'), + nltoken(), + ctoken('# Server'), + nltoken(), + ctoken('###########'), + nltoken('\n\n'), + vtoken('SERVER_HOST', '127.0.0.1'), + nltoken(), + vtoken('SERVER_PORT', '80'), + nltoken(), + vtoken('SERVER_LOGIN', 'root'), + nltoken('\n\n'), + ctoken('###########'), + nltoken(), + ctoken('# Client'), + nltoken(), + ctoken('###########'), + nltoken('\n\n'), + vtoken('CLIENT_LOGIN', 'john'), + ], + [['SERVER_LOGIN', 'debian'], { name: 'SERVER_PORT', value: '8080', comment: ' # debug only' }, ['SERVER_PASSWORD', 'secret']], + { modifyMode: ModifyMode.APPEND, noUpdate: true }, + ), + ), + ).toEqual( + '###########\n# Server\n###########\n\nSERVER_HOST=127.0.0.1\nSERVER_PORT=80\nSERVER_LOGIN=root\n\n###########\n# Client\n###########\n\nCLIENT_LOGIN=john\nSERVER_PASSWORD=secret\n', + ); + }); + it('delete value', () => { expect( stringifyTokens( @@ -227,6 +285,108 @@ describe('Updating tokens', () => { '###########\n# Server\n###########\n\nSERVER_HOST=127.0.0.1\nSERVER_PASSWORD=secret\nSERVER_PORT=8080 # debug only\nSERVER_LOGIN=debian\n\n###########\n# Client\n###########\n\nCLIENT_LOGIN=john\n', ); }); + + it('appends smart with no update config', () => { + expect( + stringifyTokens( + update( + [ + vtoken('SERVER_HOST', '127.0.0.1'), + vtoken('SERVER_PORT', '80'), + vtoken('SERVER_LOGIN', 'root'), + ctoken('#-------------------------'), + vtoken('CLIENT_LOGIN', 'john'), + vtoken('CLIENT_X_AXIS', '12'), + ctoken('#-------------------------'), + vtoken('AUTO_RUN', 'true'), + vtoken('AUTO_CLEAN', 'false'), + ], + [ + ['CLIENT_ACCESS', 'limited'], + ['SERVER_OUTPUT', '/dev/null'], + ['CLIENT_Z_AXIS', '100'], + ['ZOOM', '100%'], + ['AUTO_STOP', 'true'], + ['QUALITY', '90%'], + ['AUTO_APPEND', 'true'], + ['AUTO_ZERO', '000'], + ], + { modifyMode: ModifyMode.SMART_APPEND, noUpdate: true }, + ), + ), + ).toEqual( + 'SERVER_HOST=127.0.0.1\nSERVER_OUTPUT=/dev/null\nSERVER_PORT=80\nSERVER_LOGIN=root\n' + + '#-------------------------\n' + + 'CLIENT_ACCESS=limited\nCLIENT_LOGIN=john\nCLIENT_X_AXIS=12\nCLIENT_Z_AXIS=100\n' + + '#-------------------------\n' + + 'AUTO_APPEND=true\nAUTO_RUN=true\nAUTO_CLEAN=false\nAUTO_STOP=true\nAUTO_ZERO=000\n' + + 'QUALITY=90%\nZOOM=100%\n', + ); + + expect( + stringifyTokens( + update( + [], + [ + ['SERVER_HOST', '127.0.0.1'], + ['SERVER_PORT', '80'], + ['SERVER_LOGIN', 'root'], + ['CLIENT_LOGIN', 'john'], + ['CLIENT_X_AXIS', '12'], + ['AUTO_RUN', 'true'], + ['AUTO_CLEAN', 'false'], + ['CLIENT_ACCESS', 'limited'], + ['SERVER_OUTPUT', '/dev/null'], + ['CLIENT_Z_AXIS', '100'], + ['ZOOM', '100%'], + ['AUTO_STOP', 'true'], + ['QUALITY', '90%'], + ['AUTO_APPEND', 'true'], + ['AUTO_ZERO', '000'], + ], + { modifyMode: ModifyMode.SMART_APPEND, noUpdate: true }, + ), + ), + ).toEqual( + 'AUTO_APPEND=true\nAUTO_CLEAN=false\nAUTO_RUN=true\nAUTO_STOP=true\nAUTO_ZERO=000\n' + + 'CLIENT_ACCESS=limited\nCLIENT_LOGIN=john\nCLIENT_X_AXIS=12\nCLIENT_Z_AXIS=100\n' + + 'QUALITY=90%\n' + + 'SERVER_HOST=127.0.0.1\nSERVER_LOGIN=root\nSERVER_OUTPUT=/dev/null\nSERVER_PORT=80\n' + + 'ZOOM=100%\n', + ); + + expect( + stringifyTokens( + update( + [ + ctoken('###########'), + nltoken(), + ctoken('# Server'), + nltoken(), + ctoken('###########'), + nltoken('\n\n'), + vtoken('SERVER_HOST', '127.0.0.1'), + nltoken(), + vtoken('SERVER_PORT', '80'), + nltoken(), + vtoken('SERVER_LOGIN', 'root'), + nltoken('\n\n'), + ctoken('###########'), + nltoken(), + ctoken('# Client'), + nltoken(), + ctoken('###########'), + nltoken('\n\n'), + vtoken('CLIENT_LOGIN', 'john'), + ], + [['SERVER_LOGIN', 'debian'], { name: 'SERVER_PORT', value: '8080', comment: ' # debug only' }, ['SERVER_PASSWORD', 'secret']], + { modifyMode: ModifyMode.SMART_APPEND, noUpdate: true }, + ), + ), + ).toEqual( + '###########\n# Server\n###########\n\nSERVER_HOST=127.0.0.1\nSERVER_PASSWORD=secret\nSERVER_PORT=80\nSERVER_LOGIN=root\n\n###########\n# Client\n###########\n\nCLIENT_LOGIN=john\n', + ); + }); }); describe('Fixing token list', () => { diff --git a/src/manipulation.ts b/src/manipulation.ts index deba0a1..78d42d5 100644 --- a/src/manipulation.ts +++ b/src/manipulation.ts @@ -106,6 +106,9 @@ export function update(tokens: Token[], updateWith: VariableToUpdate[], config?: const updateVar: Variable | VariableToken = Array.isArray(u) ? { name: u[0], value: u[1] } : u; const tokenToUpdate = tokens.findIndex((t) => t.token === TokenType.VARIABLE && (t as VariableToken).name === updateVar.name); if (tokenToUpdate > -1) { + if (config?.noUpdate) { + return; + } // delete if (updateVar.value == null) { if (tokenToUpdate < tokens.length - 1 && tokens[tokenToUpdate + 1].token === TokenType.NEWLINE) { diff --git a/src/types.ts b/src/types.ts index f9c5f79..cf2d467 100644 --- a/src/types.ts +++ b/src/types.ts @@ -35,6 +35,7 @@ export type Variable = { export type Config = { beautify?: boolean; + noUpdate?: boolean; enforceNewLineEnd?: boolean; modifyMode?: ModifyMode; };