diff --git a/CHANGELOG.md b/CHANGELOG.md index 03104d3..2165c03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,11 @@ # Changelog +## 5.1.0 +Task 6.1.0 +- Add _useAdditionalVariablesOnly_ parameter ([#29](https://github.com/qetza/replacetokens-task/issues/60)). + +Task 5.4.0 +- Add _useAdditionalVariablesOnly_ parameter ([#29](https://github.com/qetza/replacetokens-task/issues/60)). + ## 5.0.8 - Add links in README to migration documentation. diff --git a/README.md b/README.md index 48bdf0b..6deb598 100644 --- a/README.md +++ b/README.md @@ -277,6 +277,11 @@ If you are migrating from **v3 to v6** make sure to read this documentation firs # # Optional. Default: ) transformsSuffix: '' + + # Use only variables declared in 'additionalVariables'. + # + # Optional: Default: false + useAdditionalVariablesOnly: '' ``` ### Output @@ -354,6 +359,7 @@ The following **anonymous** data is send: - _transforms_ - _transformsPrefix_ - _transformsSuffix_ + - _useAdditionalVariablesOnly_ - the **number of** _sources_ entries - the **number of** _additionalVariables_ entries referencing file - the **number of** _additionalVariables_ entries referencing environment variables diff --git a/tasks/ReplaceTokensV5/CHANGELOG.md b/tasks/ReplaceTokensV5/CHANGELOG.md index fc586d0..513948b 100644 --- a/tasks/ReplaceTokensV5/CHANGELOG.md +++ b/tasks/ReplaceTokensV5/CHANGELOG.md @@ -1,4 +1,7 @@ # Changelog +## 5.4.0 +- Add _useAdditionalVariablesOnly_ parameter ([#29](https://github.com/qetza/replacetokens-task/issues/60)). + ## 5.3.3 - Fix telemetry account hash. diff --git a/tasks/ReplaceTokensV5/index.ts b/tasks/ReplaceTokensV5/index.ts index 0f8d740..8af88af 100644 --- a/tasks/ReplaceTokensV5/index.ts +++ b/tasks/ReplaceTokensV5/index.ts @@ -42,6 +42,7 @@ interface Options { readonly enableRecursion: boolean; readonly useLegacyEmptyFeature: boolean; readonly useDefaultValue: boolean; + readonly useAdditionalVariablesOnly: boolean; } interface Rule { @@ -256,8 +257,7 @@ var replaceTokensInString = function ( if (options.enableRecursion && names.includes(name)) throw new Error("recursion cycle with token '" + name + "'."); // replace value - let value: string = tl.getVariable(name); - if (name in externalVariables) value = externalVariables[name]; + let value: string = name in externalVariables || options.useAdditionalVariablesOnly ? externalVariables[name] : tl.getVariable(name); let usedDefaultValue: boolean = false; if ( @@ -549,7 +549,8 @@ async function run() { enableTransforms: tl.getBoolInput('enableTransforms', false), enableRecursion: tl.getBoolInput('enableRecursion', false), useLegacyEmptyFeature: tl.getBoolInput('useLegacyEmptyFeature', false), - useDefaultValue: tl.getBoolInput('useDefaultValue', false) + useDefaultValue: tl.getBoolInput('useDefaultValue', false), + useAdditionalVariablesOnly: tl.getBoolInput('useAdditionalVariablesOnly', false) }; const transformPrefix: string = tl.getInput('transformPrefix', false) || '('; const transformSuffix: string = tl.getInput('transformSuffix', false) || ')'; @@ -739,6 +740,7 @@ async function run() { telemetryEvent.enableRecursion = options.enableRecursion; telemetryEvent.useLegacyEmptyFeature = options.useLegacyEmptyFeature; telemetryEvent.useDefaultValue = options.useDefaultValue; + telemetryEvent.useAdditionalVariablesOnly = options.useAdditionalVariablesOnly; // process files rules.forEach(rule => { diff --git a/tasks/ReplaceTokensV5/task.json b/tasks/ReplaceTokensV5/task.json index 3d7a164..692ab2a 100644 --- a/tasks/ReplaceTokensV5/task.json +++ b/tasks/ReplaceTokensV5/task.json @@ -12,8 +12,8 @@ "author": "Guillaume ROUCHON", "version": { "Major": 5, - "Minor": 3, - "Patch": 3 + "Minor": 4, + "Patch": 0 }, "releaseNotes": "Migrate to Node10 handler (breaking change).
Add Node16 handler.", "instanceNameFormat": "Replace tokens in $(targetFiles)", @@ -251,6 +251,15 @@ "required": false, "helpMarkDown": "The separtor to use in variable names for nested objects in inline variables or variable files.
Example: {'My':{'Value':'Hello World!'}} will create a variable 'My.Value' with the value 'Hello World!'" }, + { + "name": "useAdditionalVariablesOnly", + "type": "boolean", + "label": "Use only file and inline variables", + "defaultValue": "false", + "groupName": "variables", + "required": false, + "helpMarkDown": "Use only variables declared in variable files and inline variables." + }, { "name": "enableRecursion", "type": "boolean", diff --git a/tasks/ReplaceTokensV5/telemetry.ts b/tasks/ReplaceTokensV5/telemetry.ts index bb51c27..9c3ebe3 100644 --- a/tasks/ReplaceTokensV5/telemetry.ts +++ b/tasks/ReplaceTokensV5/telemetry.ts @@ -125,4 +125,5 @@ export class TelemetryEvent { enableRecursion: boolean; useLegacyEmptyFeature: boolean; useDefaultValue: boolean; + useAdditionalVariablesOnly: boolean; } diff --git a/tasks/ReplaceTokensV5/tests/L0.ts b/tasks/ReplaceTokensV5/tests/L0.ts index 7352121..f7dab44 100644 --- a/tasks/ReplaceTokensV5/tests/L0.ts +++ b/tasks/ReplaceTokensV5/tests/L0.ts @@ -191,7 +191,7 @@ describe('ReplaceTokens v5 L0 suite', function () { tr.stdout.should.include('telemetry sent'); tr.stdout.should.match( - /\[\{"account":"494d0aad9d06c4ddb51d5300620122ce55366a9382b3cc2835ed5f0e2e67b4d0","pipeline":"b98ed03d3eec376dcc015365c1a944e3ebbcc33d30e3261af3f4e4abb107aa82","host":"server","os":"Windows","actionOnMissing":"warn","encoding":"auto","keepToken":false,"pattern":"#\\\\{\\\\s\*\(\(\?:\(\?!#\\\\{\)\(\?!\\\\s\*\\\\}#\)\.\)\*\)\\\\s\*\\\\}#","result":"success","rules":1,"rulesWithInputWildcard":0,"rulesWithNegativePattern":0,"rulesWithOutputPattern":0,"tokenPrefix":"#{","tokenSuffix":"}#","variableFiles":0,"verbosity":"normal","writeBOM":true,"useLegacyPattern":false,"enableTransforms":false,"transformPrefix":"\(","transformSuffix":"\)","transformPattern":"\\\\s\*\(\.\*\)\\\\\(\\\\s\*\(\(\?:\(\?!\\\\\(\)\(\?!\\\\s\*\\\\\)\)\.\)\*\)\\\\s\*\\\\\)\\\\s\*","defaultValue":"","tokenPattern":"default","actionOnNoFiles":"continue","inlineVariables":0,"enableRecursion":false,"useLegacyEmptyFeature":false,"useDefaultValue":false,"duration":\d+(?:\.\d+)?,"tokenReplaced":1,"tokenFound":1,"defaultValueReplaced":0,"fileProcessed":1,"transformExecuted":0,"eventType":"TokensReplaced","application":"replacetokens-task","version":"5.0.0"}]/ + /\[\{"account":"494d0aad9d06c4ddb51d5300620122ce55366a9382b3cc2835ed5f0e2e67b4d0","pipeline":"b98ed03d3eec376dcc015365c1a944e3ebbcc33d30e3261af3f4e4abb107aa82","host":"server","os":"Windows","actionOnMissing":"warn","encoding":"auto","keepToken":false,"pattern":"#\\\\{\\\\s\*\(\(\?:\(\?!#\\\\{\)\(\?!\\\\s\*\\\\}#\)\.\)\*\)\\\\s\*\\\\}#","result":"success","rules":1,"rulesWithInputWildcard":0,"rulesWithNegativePattern":0,"rulesWithOutputPattern":0,"tokenPrefix":"#{","tokenSuffix":"}#","variableFiles":0,"verbosity":"normal","writeBOM":true,"useLegacyPattern":false,"enableTransforms":false,"transformPrefix":"\(","transformSuffix":"\)","transformPattern":"\\\\s\*\(\.\*\)\\\\\(\\\\s\*\(\(\?:\(\?!\\\\\(\)\(\?!\\\\s\*\\\\\)\)\.\)\*\)\\\\s\*\\\\\)\\\\s\*","defaultValue":"","tokenPattern":"default","actionOnNoFiles":"continue","inlineVariables":0,"enableRecursion":false,"useLegacyEmptyFeature":false,"useDefaultValue":false,"useAdditionalVariablesOnly":false,"duration":\d+(?:\.\d+)?,"tokenReplaced":1,"tokenFound":1,"defaultValueReplaced":0,"fileProcessed":1,"transformExecuted":0,"eventType":"TokensReplaced","application":"replacetokens-task","version":"5.0.0"}]/ ); }, tr, @@ -1583,5 +1583,28 @@ describe('ReplaceTokens v5 L0 suite', function () { done ); }); + + it('should replace only with file or inline variables when specified', function (done: Mocha.Done) { + // arrange + let tp = path.join(__dirname, 'externalVariables', 'L0_UseAdditionalVariablesOnly.js'); + let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); + + process.env['__inputpath__'] = copyData('variables.json', 'useonlyexternalvariables.json'); + process.env['__variablespath__'] = path.join(data, 'externalvariables1.json'); + + // act + tr.run(); + + // assert + runValidation( + () => { + tr.succeeded.should.equal(true, 'task succeeded'); + + assertFilesEqual(process.env['__inputpath__'], path.join(data, 'variables.useadditionalvariablesonly.expected.json'), 'replaced output'); + }, + tr, + done + ); + }); }); }); diff --git a/tasks/ReplaceTokensV5/tests/_data/variables.useadditionalvariablesonly.expected.json b/tasks/ReplaceTokensV5/tests/_data/variables.useadditionalvariablesonly.expected.json new file mode 100644 index 0000000..56ebe7d --- /dev/null +++ b/tasks/ReplaceTokensV5/tests/_data/variables.useadditionalvariablesonly.expected.json @@ -0,0 +1,4 @@ +{ + "var1": "var1_value", + "var2": "" +} \ No newline at end of file diff --git a/tasks/ReplaceTokensV5/tests/externalVariables/L0_UseAdditionalVariablesOnly.ts b/tasks/ReplaceTokensV5/tests/externalVariables/L0_UseAdditionalVariablesOnly.ts new file mode 100644 index 0000000..5ec40b0 --- /dev/null +++ b/tasks/ReplaceTokensV5/tests/externalVariables/L0_UseAdditionalVariablesOnly.ts @@ -0,0 +1,41 @@ +import ma = require('azure-pipelines-task-lib/mock-answer'); +import tmrm = require('azure-pipelines-task-lib/mock-run'); +import path = require('path'); + +const taskPath = path.join(__dirname, '..', '..', 'index.js'); +const tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); + +// variables +process.env['VAR1'] = 'variables'; +process.env['VAR2'] = 'variables'; + +// inputs +tmr.setInput('enableTelemetry', 'false'); +tmr.setInput('targetFiles', 'input.json'); +tmr.setInput('writeBOM', 'true'); +tmr.setInput('variableFiles', 'variables.ext'); +tmr.setInput('useAdditionalVariablesOnly', 'true'); + +// sdk answers +let answers = { + checkPath: {}, + findMatch: { + 'input.json': [process.env['__inputpath__']], + 'variables.ext': [process.env['__variablespath__']] + }, + stats: {}, + exist: {} +}; +answers['stats'][process.env['__inputpath__']] = { + isDirectory: false +}; +answers['stats'][process.env['__variablespath__']] = { + isDirectory: false +}; +answers['exist'][process.env['__inputpath__']] = true; +answers['exist'][process.env['__variablespath__']] = true; + +tmr.setAnswers(answers); + +// act +tmr.run(); diff --git a/tasks/ReplaceTokensV6/CHANGELOG.md b/tasks/ReplaceTokensV6/CHANGELOG.md index 318f73c..7c828de 100644 --- a/tasks/ReplaceTokensV6/CHANGELOG.md +++ b/tasks/ReplaceTokensV6/CHANGELOG.md @@ -1,4 +1,7 @@ # Changelog +## 6.1.0 +- Add _useAdditionalVariablesOnly_ parameter ([#29](https://github.com/qetza/replacetokens-task/issues/60)). + ## 6.0.6 - Fix default case sensitivity in sources and additional variables matching ([#29](https://github.com/qetza/replacetokens-task/issues/29)). - Fix default directories and files starting with a dot in sources and additional variables matching ([#29](https://github.com/qetza/replacetokens-task/issues/29)). diff --git a/tasks/ReplaceTokensV6/README.md b/tasks/ReplaceTokensV6/README.md index dfcc87a..aee379b 100644 --- a/tasks/ReplaceTokensV6/README.md +++ b/tasks/ReplaceTokensV6/README.md @@ -277,6 +277,11 @@ If you are migrating from **v3 to v6** make sure to read this documentation firs # # Optional. Default: ) transformsSuffix: '' + + # Use only variables declared in 'additionalVariables'. + # + # Optional: Default: false + useAdditionalVariablesOnly: '' ``` ### Output @@ -354,6 +359,7 @@ The following **anonymous** data is send: - _transforms_ - _transformsPrefix_ - _transformsSuffix_ + - _useAdditionalVariablesOnly_ - the **number of** _sources_ entries - the **number of** _additionalVariables_ entries referencing file - the **number of** _additionalVariables_ entries referencing environment variables diff --git a/tasks/ReplaceTokensV6/index.ts b/tasks/ReplaceTokensV6/index.ts index c5e631b..5b4e4de 100644 --- a/tasks/ReplaceTokensV6/index.ts +++ b/tasks/ReplaceTokensV6/index.ts @@ -117,6 +117,7 @@ async function run() { // load additional variables const separator = tl.getInput('variableSeparator') || rt.Defaults.Separator; const additionalVariables = await getAdditionalVariables(options.root, separator, options.sources.caseInsensitive, options.sources.dot); + const useAdditionalVariablesOnly = tl.getBoolInput('useAdditionalVariablesOnly'); // set telemetry attributes telemetryEvent.setAttributes({ @@ -141,13 +142,18 @@ async function run() { transforms: options.transforms.enabled, 'transforms-prefix': options.transforms.prefix, 'transforms-suffix': options.transforms.suffix, + 'use-additional-variables-only': useAdditionalVariablesOnly, 'variable-files': variableFilesCount, 'variable-envs': variablesEnvCount, 'inline-variables': inlineVariablesCount }); // replace tokens - const result = await rt.replaceTokens(sources, (name: string) => (name in additionalVariables ? additionalVariables[name] : tl.getVariable(name)), options); + const result = await rt.replaceTokens( + sources, + (name: string) => (name in additionalVariables || useAdditionalVariablesOnly ? additionalVariables[name] : tl.getVariable(name)), + options + ); if (result.files === 0) { (msg => { diff --git a/tasks/ReplaceTokensV6/task.json b/tasks/ReplaceTokensV6/task.json index 04caf21..e320fa1 100644 --- a/tasks/ReplaceTokensV6/task.json +++ b/tasks/ReplaceTokensV6/task.json @@ -12,8 +12,8 @@ "author": "Guillaume ROUCHON", "version": { "Major": 6, - "Minor": 0, - "Patch": 6 + "Minor": 1, + "Patch": 0 }, "releaseNotes": "breaking changes, see [changelog](https://github.com/qetza/replacetokens-task/blob/master/tasks/ReplaceTokensV6/CHANGELOG.md)", "instanceNameFormat": "Replace tokens", @@ -133,6 +133,15 @@ "label": "Separator", "helpMarkDown": "The separtor to use when flattening keys in variables." }, + { + "name": "useAdditionalVariablesOnly", + "type": "boolean", + "required": false, + "defaultValue": false, + "visibleRule": "additionalVariables != \"\"", + "label": "Use only additional variables", + "helpMarkDown": "Use only variables declared in additional variables. Default: false" + }, { "name": "escapeType", "aliases": ["escape"], diff --git a/tasks/ReplaceTokensV6/tests/L0.ts b/tasks/ReplaceTokensV6/tests/L0.ts index d768310..e53fe93 100644 --- a/tasks/ReplaceTokensV6/tests/L0.ts +++ b/tasks/ReplaceTokensV6/tests/L0.ts @@ -233,7 +233,7 @@ describe('ReplaceTokens v6 L0 suite', function () { tr.stdout.should.include('telemetry sent'); tr.stdout.should.match( - /\[\{"eventType":"TokensReplaced","application":"replacetokens-task","version":"6.0.0","account":"494d0aad9d06c4ddb51d5300620122ce55366a9382b3cc2835ed5f0e2e67b4d0","pipeline":"b98ed03d3eec376dcc015365c1a944e3ebbcc33d30e3261af3f4e4abb107aa82","host":"cloud","os":"Windows","sources":3,"add-bom":false,"case-insenstive-paths":true,"encoding":"auto","escape":"auto","if-no-files-found":"ignore","include-dot-paths":true,"log-level":"info","missing-var-action":"none","missing-var-default":"","missing-var-log":"warn","recusrive":false,"separator":".","token-pattern":"default","transforms":false,"transforms-prefix":"\(","transforms-suffix":"\)","variable-files":0,"variable-envs":0,"inline-variables":0,"output-defaults":1,"output-files":2,"output-replaced":3,"output-tokens":4,"output-transforms":5,"result":"success","duration":\d+(?:\.\d+)?}]/ + /\[\{"eventType":"TokensReplaced","application":"replacetokens-task","version":"6.0.0","account":"494d0aad9d06c4ddb51d5300620122ce55366a9382b3cc2835ed5f0e2e67b4d0","pipeline":"b98ed03d3eec376dcc015365c1a944e3ebbcc33d30e3261af3f4e4abb107aa82","host":"cloud","os":"Windows","sources":3,"add-bom":false,"case-insenstive-paths":true,"encoding":"auto","escape":"auto","if-no-files-found":"ignore","include-dot-paths":true,"log-level":"info","missing-var-action":"none","missing-var-default":"","missing-var-log":"warn","recusrive":false,"separator":".","token-pattern":"default","transforms":false,"transforms-prefix":"\(","transforms-suffix":"\)","use-additional-variables-only":false,"variable-files":0,"variable-envs":0,"inline-variables":0,"output-defaults":1,"output-files":2,"output-replaced":3,"output-tokens":4,"output-transforms":5,"result":"success","duration":\d+(?:\.\d+)?}]/ ); }, tr); } finally { @@ -1077,4 +1077,44 @@ describe('ReplaceTokens v6 L0 suite', function () { actual.should.equal(expected); }, tr); }); + + it('useAdditionalVariablesOnly', async () => { + // arrange + const tp = path.join(__dirname, 'L0_NoMock.js'); + const tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); + + addVariables({ 'var.var': 'var', var_yaml2: 'var' }); + addSecrets({ 'var.secret': 'secret', 'var.yml2': 'secret' }); + + let input = path.join(tmp, 'file.txt'); + await fs.promises.copyFile(path.join(data, 'file.txt'), input); + input = path.resolve(input); + + process.env['__telemetryOptout__'] = 'true'; + process.env['__sources__'] = input; + process.env['__missingVarAction__'] = 'replace'; + process.env['__missingVarDefault__'] = 'DEFAULT'; + process.env['__root__'] = path.join(data, '..'); + process.env['__VARS__'] = '{ "var1": "env", "var2": "env" }'; + process.env['__useAdditionalVariablesOnly__'] = 'true'; + process.env['__additionalVariables__'] = ` +- '@**/_data/vars.*;!**/*.xml' +- '$__VARS__' +- var2: inline + var.yml2: inline +`; + + // act + await tr.runAsync(); + + // assert + runValidations(() => { + tr.succeeded.should.be.true; + + const actual = fs.readFileSync(input, 'utf8'); + const expected = fs.readFileSync(path.join(data, 'file.only_additional_variables.expected.txt'), 'utf8'); + + actual.should.equal(expected); + }, tr); + }); }); diff --git a/tasks/ReplaceTokensV6/tests/L0_NoMock.ts b/tasks/ReplaceTokensV6/tests/L0_NoMock.ts index f6ec229..dad91ff 100644 --- a/tasks/ReplaceTokensV6/tests/L0_NoMock.ts +++ b/tasks/ReplaceTokensV6/tests/L0_NoMock.ts @@ -27,6 +27,7 @@ if (process.env['__tokenSuffix__']) tmr.setInput('tokenSuffix', process.env['__t if (process.env['__transforms__']) tmr.setInput('enableTransforms', process.env['__transforms__']); if (process.env['__transformsPrefix__']) tmr.setInput('transformPrefix', process.env['__transformsPrefix__']); if (process.env['__transformsSuffix__']) tmr.setInput('transformSuffix', process.env['__transformsSuffix__']); +if (process.env['__useAdditionalVariablesOnly__']) tmr.setInput('useAdditionalVariablesOnly', process.env['__useAdditionalVariablesOnly__']); // mocks const axiosClone = Object.assign({}, require('axios')); diff --git a/tasks/ReplaceTokensV6/tests/L0_Run.ts b/tasks/ReplaceTokensV6/tests/L0_Run.ts index 97076dd..406c053 100644 --- a/tasks/ReplaceTokensV6/tests/L0_Run.ts +++ b/tasks/ReplaceTokensV6/tests/L0_Run.ts @@ -29,6 +29,7 @@ if (process.env['__tokenSuffix__']) tmr.setInput('tokenSuffix', process.env['__t if (process.env['__transforms__']) tmr.setInput('enableTransforms', process.env['__transforms__']); if (process.env['__transformsPrefix__']) tmr.setInput('transformPrefix', process.env['__transformsPrefix__']); if (process.env['__transformsSuffix__']) tmr.setInput('transformSuffix', process.env['__transformsSuffix__']); +if (process.env['__useAdditionalVariablesOnly__']) tmr.setInput('useAdditionalVariablesOnly', process.env['__useAdditionalVariablesOnly__']); // mocks const rtClone = Object.assign({}, require('@qetza/replacetokens')); diff --git a/tasks/ReplaceTokensV6/tests/_data/file.only_additional_variables.expected.txt b/tasks/ReplaceTokensV6/tests/_data/file.only_additional_variables.expected.txt new file mode 100644 index 0000000..c16a750 --- /dev/null +++ b/tasks/ReplaceTokensV6/tests/_data/file.only_additional_variables.expected.txt @@ -0,0 +1,10 @@ +var.var: DEFAULT +var.secret: DEFAULT +var1: env +var2: inline +var_json: file +var_yaml1: file +var_yaml2: file +var.yml1: file +var.yml2: inline +unknown: DEFAULT \ No newline at end of file diff --git a/vss-extension.json b/vss-extension.json index 3f848f4..285941b 100644 --- a/vss-extension.json +++ b/vss-extension.json @@ -2,7 +2,7 @@ "manifestVersion": 1, "id": "replacetokens", "name": "Replace Tokens", - "version": "5.0.8", + "version": "5.1.0", "public": true, "publisher": "qetza", "targets": [