diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f85c84b..4feaec9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -96,8 +96,5 @@ jobs: npm --prefix tasks/ReplaceTokensV5 ci npm --prefix tasks/ReplaceTokensV6 ci - - name: Build - run: npm run build - - name: Test run: npm run test diff --git a/CHANGELOG.md b/CHANGELOG.md index d0a6862..01ef45d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ # Changelog +## 5.0.6 +Task 6.0.5 +- Fix normalized variable names not supported as token name ([#15](https://github.com/qetza/replacetokens-task/issues/15)) ([#20](https://github.com/qetza/replacetokens-task/issues/20)). + ## 5.0.5 Task 5.3.3 - Fix telemetry account hash. diff --git a/README.md b/README.md index 85c1537..0db7739 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,6 @@ Please refer to the [release page](https://github.com/qetza/replacetokens-task/r ## Breaking changes in v6 The task was completely rewritten to use the npm package [@qetza/replacetokens](https://www.npmjs.com/package/@qetza/replacetokens) and be more similar with the new [ReplaceTokens GitHub Actions](https://github.com/marketplace/actions/replacetokens): - support only node 16 (mininum agent version 2.206.1) - - tokens **must** match real variable names and not normalized names done by the Azure Pipelines agent (token `BUILD_DEFINITIONNAME` will **not** match build variable `Build.DefinitionName`) - renamed input _targetFiles_ to _sources_ - migrated to [fast-glob](https://github.com/mrmlnc/fast-glob) for glob patterns causing syntax changes (must use forward slash (`/`) for directory separator whatever the OS) - removed support for comma-separated paths in _targetFiles_ diff --git a/tasks/ReplaceTokensV6/CHANGELOG.md b/tasks/ReplaceTokensV6/CHANGELOG.md index dcd7d29..d93add9 100644 --- a/tasks/ReplaceTokensV6/CHANGELOG.md +++ b/tasks/ReplaceTokensV6/CHANGELOG.md @@ -1,4 +1,7 @@ # Changelog +## 6.0.5 +- Fix normalized variable names not supported as token name ([#15](https://github.com/qetza/replacetokens-task/issues/15)) ([#20](https://github.com/qetza/replacetokens-task/issues/20)). + ## 6.0.4 - Update @qetza/replacetokens to 1.4.0. - Change telemetry provider. diff --git a/tasks/ReplaceTokensV6/README.md b/tasks/ReplaceTokensV6/README.md index a44288c..1fe2931 100644 --- a/tasks/ReplaceTokensV6/README.md +++ b/tasks/ReplaceTokensV6/README.md @@ -9,7 +9,6 @@ Please refer to the [release page](https://github.com/qetza/replacetokens-task/r ## Breaking changes in v6 The task was completely rewritten to use the npm package [@qetza/replacetokens](https://www.npmjs.com/package/@qetza/replacetokens) and be more similar with the new [ReplaceTokens GitHub Actions](https://github.com/marketplace/actions/replacetokens): - support only node 16 (mininum agent version 2.206.1) - - tokens **must** match real variable names and not normalized names done by the Azure Pipelines agent (token `BUILD_DEFINITIONNAME` will **not** match build variable `Build.DefinitionName`) - renamed input _targetFiles_ to _sources_ - migrated to [fast-glob](https://github.com/mrmlnc/fast-glob) for glob patterns causing syntax changes (must use forward slash (`/`) for directory separator whatever the OS) - removed support for comma-separated paths in _targetFiles_ diff --git a/tasks/ReplaceTokensV6/index.ts b/tasks/ReplaceTokensV6/index.ts index 5672278..7100381 100644 --- a/tasks/ReplaceTokensV6/index.ts +++ b/tasks/ReplaceTokensV6/index.ts @@ -63,7 +63,6 @@ async function run() { }, recursive: tl.getBoolInput('enableRecursion'), root: tl.getPathInput('rootDirectory', false, true), - separator: tl.getInput('variableSeparator') || rt.Defaults.Separator, token: { pattern: getChoiceInput('tokenPattern', [ @@ -111,19 +110,9 @@ async function run() { console.info('##[endgroup]'); }; - // load variables - const variables = await rt.parseVariables( - [ - JSON.stringify( - tl.getVariables().reduce((result, current) => { - result[current.name] = current.value; - return result; - }, {}) - ), - ...getAdditionalVariables() - ], - { normalizeWin32: true, root: options.root, separator: options.separator } - ); + // load additional variables + const separator = tl.getInput('variableSeparator') || rt.Defaults.Separator; + const additionalVariables = await getAdditionalVariables(options.root, separator); // set telemetry attributes telemetryEvent.setAttributes({ @@ -139,7 +128,7 @@ async function run() { 'missing-var-default': options.missing.default, 'missing-var-log': options.missing.log, recusrive: options.recursive, - separator: options.separator, + separator: separator, 'token-pattern': options.token.pattern, 'token-prefix': options.token.prefix, 'token-suffix': options.token.suffix, @@ -152,7 +141,7 @@ async function run() { }); // replace tokens - const result = await rt.replaceTokens(sources, variables, options); + const result = await rt.replaceTokens(sources, (name: string) => (name in additionalVariables ? additionalVariables[name] : tl.getVariable(name)), options); if (result.files === 0) { (msg => { @@ -228,22 +217,27 @@ var getChoiceInput = function (name: string, choices: string[], alias?: string): var variableFilesCount = 0; var variablesEnvCount = 0; var inlineVariablesCount = 0; -var getAdditionalVariables = function (): string[] { +var getAdditionalVariables = async function (root?: string, separator?: string): Promise<{ [key: string]: string }> { const input = tl.getInput('additionalVariables') || ''; - if (!input) return []; + if (!input) return {}; - switch (input[0]) { - case '@': // single string referencing a file - ++variableFilesCount; - return [input]; + return await rt.loadVariables( + (() => { + switch (input[0]) { + case '@': // single string referencing a file + ++variableFilesCount; + return [input]; - case '$': // single string referencing environment variable - ++variablesEnvCount; - return [input]; + case '$': // single string referencing environment variable + ++variablesEnvCount; + return [input]; - default: // yaml format - return getAdditionalVariablesFromYaml(input); - } + default: // yaml format + return getAdditionalVariablesFromYaml(input); + } + })(), + { normalizeWin32: true, root: root, separator: separator } + ); }; var getAdditionalVariablesFromYaml = function (input: string): string[] { diff --git a/tasks/ReplaceTokensV6/package-lock.json b/tasks/ReplaceTokensV6/package-lock.json index 6f0e4a7..0926bb5 100644 --- a/tasks/ReplaceTokensV6/package-lock.json +++ b/tasks/ReplaceTokensV6/package-lock.json @@ -9,7 +9,7 @@ "@opentelemetry/api": "^1.8.0", "@opentelemetry/sdk-trace-base": "^1.22.0", "@opentelemetry/semantic-conventions": "^1.22.0", - "@qetza/replacetokens": "^1.4.0", + "@qetza/replacetokens": "^1.5.0", "axios": "^1.6.7", "azure-pipelines-task-lib": "^4.1.0", "js-yaml": "^4.1.0", @@ -131,9 +131,9 @@ } }, "node_modules/@qetza/replacetokens": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@qetza/replacetokens/-/replacetokens-1.4.0.tgz", - "integrity": "sha512-4hSHJ2s5jtL0XXG8V5i1X7t/0xBUWsmDgVa2Xi70VsRGv6DW4GAW5BAsIq9JWHlnbFkRt4Kv6lSyrsiqlX0OIQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@qetza/replacetokens/-/replacetokens-1.5.0.tgz", + "integrity": "sha512-aYeXdReLRFs3O1olDbOD+Df5MY/qX5FBPkEi0Tb5d+y5CA08a+cbhQN8h7prF6uO1KffZbaC2WuLjSsFYBqvzA==", "dependencies": { "fast-glob": "^3.3.2", "iconv-lite": "^0.6.3", @@ -1146,9 +1146,9 @@ "integrity": "sha512-CAOgFOKLybd02uj/GhCdEeeBjOS0yeoDeo/CA7ASBSmenpZHAKGB3iDm/rv3BQLcabb/OprDEsSQ1y0P8A7Siw==" }, "@qetza/replacetokens": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@qetza/replacetokens/-/replacetokens-1.4.0.tgz", - "integrity": "sha512-4hSHJ2s5jtL0XXG8V5i1X7t/0xBUWsmDgVa2Xi70VsRGv6DW4GAW5BAsIq9JWHlnbFkRt4Kv6lSyrsiqlX0OIQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@qetza/replacetokens/-/replacetokens-1.5.0.tgz", + "integrity": "sha512-aYeXdReLRFs3O1olDbOD+Df5MY/qX5FBPkEi0Tb5d+y5CA08a+cbhQN8h7prF6uO1KffZbaC2WuLjSsFYBqvzA==", "requires": { "fast-glob": "^3.3.2", "iconv-lite": "^0.6.3", diff --git a/tasks/ReplaceTokensV6/package.json b/tasks/ReplaceTokensV6/package.json index 4e4bf01..9641ce8 100644 --- a/tasks/ReplaceTokensV6/package.json +++ b/tasks/ReplaceTokensV6/package.json @@ -8,7 +8,7 @@ "@opentelemetry/api": "^1.8.0", "@opentelemetry/sdk-trace-base": "^1.22.0", "@opentelemetry/semantic-conventions": "^1.22.0", - "@qetza/replacetokens": "^1.4.0", + "@qetza/replacetokens": "^1.5.0", "axios": "^1.6.7", "azure-pipelines-task-lib": "^4.1.0", "js-yaml": "^4.1.0", diff --git a/tasks/ReplaceTokensV6/task.json b/tasks/ReplaceTokensV6/task.json index 223a247..afffc0b 100644 --- a/tasks/ReplaceTokensV6/task.json +++ b/tasks/ReplaceTokensV6/task.json @@ -13,7 +13,7 @@ "version": { "Major": 6, "Minor": 0, - "Patch": 4 + "Patch": 5 }, "releaseNotes": "breaking changes, see [changelog](https://github.com/qetza/replacetokens-task/blob/master/tasks/ReplaceTokensV6/CHANGELOG.md)", "instanceNameFormat": "Replace tokens", diff --git a/tasks/ReplaceTokensV6/tests/.gitignore b/tasks/ReplaceTokensV6/tests/.gitignore new file mode 100644 index 0000000..bda32ed --- /dev/null +++ b/tasks/ReplaceTokensV6/tests/.gitignore @@ -0,0 +1 @@ +_tmp/ \ No newline at end of file diff --git a/tasks/ReplaceTokensV6/tests/L0.ts b/tasks/ReplaceTokensV6/tests/L0.ts index 20c3a72..91bdcc3 100644 --- a/tasks/ReplaceTokensV6/tests/L0.ts +++ b/tasks/ReplaceTokensV6/tests/L0.ts @@ -1,3 +1,4 @@ +import * as fs from 'fs'; import * as path from 'path'; import * as ttm from 'azure-pipelines-task-lib/mock-test'; import * as os from 'os'; @@ -5,12 +6,16 @@ import * as os from 'os'; require('chai').should(); const data = path.join(__dirname, '..', '..', 'tests', '_data'); +const tmp = path.join(__dirname, '..', '..', 'tests', '_tmp'); describe('ReplaceTokens v6 L0 suite', function () { this.timeout(10000); + beforeEach(() => { + fs.mkdirSync(tmp, { recursive: true }); + }); + afterEach(() => { - // clean env delete process.env['__sources__']; delete process.env['__addBOM__']; delete process.env['__additionalVariables__']; @@ -34,8 +39,22 @@ describe('ReplaceTokens v6 L0 suite', function () { delete process.env['__transformsPrefix__']; delete process.env['__transformsSuffix__']; delete process.env['__VARS__']; - delete process.env['VSTS_PUBLIC_VARIABLES']; - delete process.env['VSTS_SECRET_VARIABLES']; + + if (process.env['VSTS_PUBLIC_VARIABLES']) { + for (const name in JSON.parse(process.env['VSTS_PUBLIC_VARIABLES'])) { + delete process.env[name.replace(/[ \.]/g, '_').toUpperCase()]; + } + delete process.env['VSTS_PUBLIC_VARIABLES']; + } + + if (process.env['VSTS_SECRET_VARIABLES']) { + for (const name in JSON.parse(process.env['VSTS_SECRET_VARIABLES'])) { + delete process.env[`SECRET_${name.replace(/[ \.]/g, '_').toUpperCase()}`]; + } + delete process.env['VSTS_SECRET_VARIABLES']; + } + + fs.rmSync(tmp, { force: true, recursive: true }); }); function runValidations(validator: () => void, tr: ttm.MockTestRunner) { @@ -52,28 +71,16 @@ describe('ReplaceTokens v6 L0 suite', function () { function addVariables(variables: { [key: string]: string }) { process.env['VSTS_PUBLIC_VARIABLES'] = JSON.stringify(Object.keys(variables)); - for (const name of Object.keys(variables)) { - process.env[name.toUpperCase()] = variables[name]; - } - } - - function cleanVariables(names: string[]) { - for (const name of names) { - delete process.env[name.toUpperCase()]; + for (const [name, value] of Object.entries(variables)) { + process.env[name.replace(/[ \.]/g, '_').toUpperCase()] = value; } } function addSecrets(secrets: { [key: string]: string }) { process.env['VSTS_SECRET_VARIABLES'] = JSON.stringify(Object.keys(secrets)); - for (const name of Object.keys(secrets)) { - process.env[`SECRET_${name.toUpperCase()}`] = secrets[name]; - } - } - - function cleanSecrets(names: string[]) { - for (const name of names) { - delete process.env[`SECRET_${name.toUpperCase()}`]; + for (const [name, value] of Object.entries(secrets)) { + process.env[`SECRET_${name.replace(/[ \.]/g, '_').toUpperCase()}`] = value; } } @@ -182,31 +189,21 @@ describe('ReplaceTokens v6 L0 suite', function () { const tp = path.join(__dirname, 'L0_Run.js'); const tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); - const vars = { var_var: 'var', var_yaml2: 'var' }; - const secrets = { var_secret: 'secret', var_yml2: 'secret' }; - addVariables(vars); - addSecrets(secrets); - process.env['__sources__'] = '**/*.json\r\n**/*.xml\r\n**/*.yml'; - try { - // act - await tr.runAsync(); + // act + await tr.runAsync(); - // assert - runValidations(() => { - tr.succeeded.should.be.true; + // assert + runValidations(() => { + tr.succeeded.should.be.true; - tr.stdout.should.include('sources: ["**/*.json","**/*.xml","**/*.yml"]'); - tr.stdout.should.include('variables: {"VAR_SECRET":"secret","VAR_YML2":"secret","VAR_VAR":"var","VAR_YAML2":"var"}'); - tr.stdout.should.include( - 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"separator":".","token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' - ); - }, tr); - } finally { - cleanVariables(Object.keys(vars)); - cleanSecrets(Object.keys(secrets)); - } + tr.stdout.should.not.contain('loadVariables_variables'); + tr.stdout.should.include('sources: ["**/*.json","**/*.xml","**/*.yml"]'); + tr.stdout.should.include( + 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' + ); + }, tr); }); it('telemetry: success', async () => { @@ -214,11 +211,6 @@ describe('ReplaceTokens v6 L0 suite', function () { const tp = path.join(__dirname, 'L0_Run.js'); const tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); - const vars = { var_var: 'var', var_yaml2: 'var' }; - const secrets = { var_secret: 'secret', var_yml2: 'secret' }; - addVariables(vars); - addSecrets(secrets); - process.env['__sources__'] = '**/*.json\r\n**/*.xml\r\n**/*.yml'; process.env['SYSTEM_SERVERTYPE'] = 'hosted'; process.env['SYSTEM_COLLECTIONID'] = 'col01'; @@ -245,9 +237,6 @@ describe('ReplaceTokens v6 L0 suite', function () { delete process.env['SYSTEM_TEAMPROJECTID']; delete process.env['SYSTEM_DEFINITIONID']; delete process.env['AGENT_OS']; - - cleanVariables(Object.keys(vars)); - cleanSecrets(Object.keys(secrets)); } }); @@ -256,11 +245,6 @@ describe('ReplaceTokens v6 L0 suite', function () { const tp = path.join(__dirname, 'L0_Run.js'); const tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); - const vars = { var_var: 'var', var_yaml2: 'var' }; - const secrets = { var_secret: 'secret', var_yml2: 'secret' }; - addVariables(vars); - addSecrets(secrets); - process.env['SYSTEM_SERVERTYPE'] = 'hosted'; process.env['SYSTEM_COLLECTIONID'] = 'col01'; process.env['SYSTEM_TEAMPROJECTID'] = 'project01'; @@ -286,9 +270,6 @@ describe('ReplaceTokens v6 L0 suite', function () { delete process.env['SYSTEM_TEAMPROJECTID']; delete process.env['SYSTEM_DEFINITIONID']; delete process.env['AGENT_OS']; - - cleanVariables(Object.keys(vars)); - cleanSecrets(Object.keys(secrets)); } }); @@ -330,7 +311,7 @@ describe('ReplaceTokens v6 L0 suite', function () { tr.succeeded.should.be.true; tr.stdout.should.include( - 'options: {"addBOM":true,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"separator":".","token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' + 'options: {"addBOM":true,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' ); }, tr); }); @@ -349,11 +330,8 @@ describe('ReplaceTokens v6 L0 suite', function () { // assert runValidations(() => { - tr.stdout.should.include(`##vso[task.debug]loading variables from file '${path.join(data, 'vars.jsonc')}'`); - tr.stdout.should.include(`##vso[task.debug]loading variables from file '${path.join(data, 'vars.yml')}'`); - tr.stdout.should.include(`##vso[task.debug]loading variables from file '${path.join(data, 'vars.yaml')}'`); - - tr.stdout.should.include('variables: {"VAR_JSON":"file","VAR_YAML1":"file","VAR_YAML2":"file","VAR_YML1":"file","VAR_YML2":"file"}'); + tr.stdout.should.include('loadVariables_variables: ["@**/_data/vars.(json|jsonc|yml|yaml)"]'); + tr.stdout.should.include(`loadVariables_options: {"normalizeWin32":true,"root":"${process.env['__root__'].replace(/\\/g, '\\\\')}","separator":"."}`); }, tr); }); @@ -371,9 +349,8 @@ describe('ReplaceTokens v6 L0 suite', function () { // assert runValidations(() => { - tr.stdout.should.include("##vso[task.debug]loading variables from env '__VARS__'"); - - tr.stdout.should.include('variables: {"VAR1":"value1","VAR2":"value2"}'); + tr.stdout.should.include('loadVariables_variables: ["$__VARS__"]'); + tr.stdout.should.include(`loadVariables_options: {"normalizeWin32":true,"separator":"."}`); }, tr); }); @@ -393,7 +370,8 @@ describe('ReplaceTokens v6 L0 suite', function () { // assert runValidations(() => { - tr.stdout.should.include('variables: {"VAR1":"value1","VAR2":"value2"}'); + tr.stdout.should.include('loadVariables_variables: ["{\\"var1\\":\\"value1\\",\\"var2\\":\\"value2\\"}"]'); + tr.stdout.should.include(`loadVariables_options: {"normalizeWin32":true,"separator":"."}`); }, tr); }); @@ -402,12 +380,7 @@ describe('ReplaceTokens v6 L0 suite', function () { const tp = path.join(__dirname, 'L0_Run.js'); const tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); - const vars = { var_var: 'var', var_yaml2: 'var' }; - const secrets = { var_secret: 'secret', var_yml2: 'secret' }; - addVariables(vars); - addSecrets(secrets); - - process.env['__telemetry__'] = 'true'; + process.env['__telemetryOptout__'] = 'true'; process.env['__sources__'] = '**/*.json'; process.env['__root__'] = path.join(data, '..'); process.env['__VARS__'] = '{ "var1": "env", "var2": "env" }'; @@ -418,20 +391,14 @@ describe('ReplaceTokens v6 L0 suite', function () { var_yml2: inline `; - try { - // act - await tr.runAsync(); + // act + await tr.runAsync(); - // assert - runValidations(() => { - tr.stdout.should.include( - 'variables: {"VAR_SECRET":"secret","VAR_YML2":"inline","VAR_VAR":"var","VAR_YAML2":"file","VAR_JSON":"file","VAR_YAML1":"file","VAR_YML1":"file","VAR1":"env","VAR2":"inline"}' - ); - }, tr); - } finally { - cleanVariables(Object.keys(vars)); - cleanSecrets(Object.keys(secrets)); - } + // assert + runValidations(() => { + tr.stdout.should.include('loadVariables_variables: ["@**/_data/vars.*;!**/*.xml","$__VARS__","{\\"var2\\":\\"inline\\",\\"var_yml2\\":\\"inline\\"}"]'); + tr.stdout.should.include(`loadVariables_options: {"normalizeWin32":true,"root":"${process.env['__root__'].replace(/\\/g, '\\\\')}","separator":"."}`); + }, tr); }); it('charsToEscape', async () => { @@ -450,7 +417,7 @@ describe('ReplaceTokens v6 L0 suite', function () { tr.succeeded.should.be.true; tr.stdout.should.include( - 'options: {"addBOM":false,"encoding":"auto","escape":{"chars":"abcd","type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"separator":".","token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' + 'options: {"addBOM":false,"encoding":"auto","escape":{"chars":"abcd","type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' ); }, tr); }); @@ -471,7 +438,7 @@ describe('ReplaceTokens v6 L0 suite', function () { tr.succeeded.should.be.true; tr.stdout.should.include( - 'options: {"addBOM":false,"encoding":"abcd","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"separator":".","token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' + 'options: {"addBOM":false,"encoding":"abcd","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' ); }, tr); }); @@ -492,7 +459,7 @@ describe('ReplaceTokens v6 L0 suite', function () { tr.succeeded.should.be.true; tr.stdout.should.include( - 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"json"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"separator":".","token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' + 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"json"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' ); }, tr); }); @@ -667,7 +634,7 @@ describe('ReplaceTokens v6 L0 suite', function () { tr.succeeded.should.be.true; tr.stdout.should.include( - 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"keep","default":"","log":"warn"},"recursive":false,"separator":".","token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' + 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"keep","default":"","log":"warn"},"recursive":false,"token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' ); }, tr); }); @@ -688,7 +655,7 @@ describe('ReplaceTokens v6 L0 suite', function () { tr.succeeded.should.be.true; tr.stdout.should.include( - 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"abcd","log":"warn"},"recursive":false,"separator":".","token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' + 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"abcd","log":"warn"},"recursive":false,"token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' ); }, tr); }); @@ -709,7 +676,7 @@ describe('ReplaceTokens v6 L0 suite', function () { tr.succeeded.should.be.true; tr.stdout.should.include( - 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"off"},"recursive":false,"separator":".","token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' + 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"off"},"recursive":false,"token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' ); }, tr); }); @@ -730,7 +697,7 @@ describe('ReplaceTokens v6 L0 suite', function () { tr.succeeded.should.be.true; tr.stdout.should.include( - 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":true,"separator":".","token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' + 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":true,"token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' ); }, tr); }); @@ -751,7 +718,7 @@ describe('ReplaceTokens v6 L0 suite', function () { tr.succeeded.should.be.true; tr.stdout.should.include( - `options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"root":"${__dirname.replace(/\\/g, '\\\\')}","separator":".","token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}` + `options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"root":"${__dirname.replace(/\\/g, '\\\\')}","token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}` ); }, tr); }); @@ -762,6 +729,7 @@ describe('ReplaceTokens v6 L0 suite', function () { const tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); process.env['__sources__'] = '**/*.json'; + process.env['__additionalVariables__'] = 'var1: value1'; process.env['__separator__'] = ':'; // act @@ -771,9 +739,7 @@ describe('ReplaceTokens v6 L0 suite', function () { runValidations(() => { tr.succeeded.should.be.true; - tr.stdout.should.include( - 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"separator":":","token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' - ); + tr.stdout.should.include('loadVariables_options: {"normalizeWin32":true,"separator":":"}'); }, tr); }); @@ -793,7 +759,7 @@ describe('ReplaceTokens v6 L0 suite', function () { tr.succeeded.should.be.true; tr.stdout.should.include( - 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"separator":".","token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' + 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' ); tr.stdout.should.not.include('telemetry sent'); tr.stdout.should.not.include('##vso[task.debug]telemetry: ['); @@ -816,7 +782,7 @@ describe('ReplaceTokens v6 L0 suite', function () { tr.succeeded.should.be.true; tr.stdout.should.include( - 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"separator":".","token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' + 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' ); tr.stdout.should.not.include('telemetry sent'); tr.stdout.should.not.include('##vso[task.debug]telemetry: ['); @@ -839,7 +805,7 @@ describe('ReplaceTokens v6 L0 suite', function () { tr.succeeded.should.be.true; tr.stdout.should.include( - 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"separator":".","token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' + 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' ); tr.stdout.should.not.include('telemetry sent'); tr.stdout.should.not.include('##vso[task.debug]telemetry: ['); @@ -862,7 +828,7 @@ describe('ReplaceTokens v6 L0 suite', function () { tr.succeeded.should.be.true; tr.stdout.should.include( - 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"separator":".","token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' + 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' ); tr.stdout.should.not.include('telemetry sent'); tr.stdout.should.not.include('##vso[task.debug]telemetry: ['); @@ -885,7 +851,7 @@ describe('ReplaceTokens v6 L0 suite', function () { tr.succeeded.should.be.true; tr.stdout.should.include( - 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"separator":".","token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' + 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' ); tr.stdout.should.not.include('telemetry sent'); tr.stdout.should.not.include('##vso[task.debug]telemetry: ['); @@ -908,7 +874,7 @@ describe('ReplaceTokens v6 L0 suite', function () { tr.succeeded.should.be.true; tr.stdout.should.include( - 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"separator":".","token":{"pattern":"azpipelines"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' + 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"token":{"pattern":"azpipelines"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' ); }, tr); }); @@ -929,7 +895,7 @@ describe('ReplaceTokens v6 L0 suite', function () { tr.succeeded.should.be.true; tr.stdout.should.include( - 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"separator":".","token":{"pattern":"default","prefix":"[["},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' + 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"token":{"pattern":"default","prefix":"[["},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' ); }, tr); }); @@ -950,7 +916,7 @@ describe('ReplaceTokens v6 L0 suite', function () { tr.succeeded.should.be.true; tr.stdout.should.include( - 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"separator":".","token":{"pattern":"default","suffix":"]]"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' + 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"token":{"pattern":"default","suffix":"]]"},"transforms":{"enabled":false,"prefix":"(","suffix":")"}}' ); }, tr); }); @@ -971,7 +937,7 @@ describe('ReplaceTokens v6 L0 suite', function () { tr.succeeded.should.be.true; tr.stdout.should.include( - 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"separator":".","token":{"pattern":"default"},"transforms":{"enabled":true,"prefix":"(","suffix":")"}}' + 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"token":{"pattern":"default"},"transforms":{"enabled":true,"prefix":"(","suffix":")"}}' ); }, tr); }); @@ -992,7 +958,7 @@ describe('ReplaceTokens v6 L0 suite', function () { tr.succeeded.should.be.true; tr.stdout.should.include( - 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"separator":".","token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"[","suffix":")"}}' + 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"[","suffix":")"}}' ); }, tr); }); @@ -1013,8 +979,47 @@ describe('ReplaceTokens v6 L0 suite', function () { tr.succeeded.should.be.true; tr.stdout.should.include( - 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"separator":".","token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":"]"}}' + 'options: {"addBOM":false,"encoding":"auto","escape":{"type":"auto"},"missing":{"action":"none","default":"","log":"warn"},"recursive":false,"token":{"pattern":"default"},"transforms":{"enabled":false,"prefix":"(","suffix":"]"}}' ); }, tr); }); + + it('replace tokens', 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['__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.expected.txt'), 'utf8'); + + actual.should.equal(expected); + }, tr); + }); }); diff --git a/tasks/ReplaceTokensV6/tests/L0_IfNoFilesFound.ts b/tasks/ReplaceTokensV6/tests/L0_IfNoFilesFound.ts index e4096ec..54368a8 100644 --- a/tasks/ReplaceTokensV6/tests/L0_IfNoFilesFound.ts +++ b/tasks/ReplaceTokensV6/tests/L0_IfNoFilesFound.ts @@ -9,10 +9,15 @@ tmr.setInput('targetFiles', process.env['__sources__']); tmr.setInput('actionOnNoFiles', process.env['__ifNoFilesFound__']); // mocks -const rt = require('@qetza/replacetokens'); -const rtClone = Object.assign({}, rt); +const rtClone = Object.assign({}, require('@qetza/replacetokens')); +rtClone.loadVariables = function (variables, options) { + console.log(`loadVariables_variables: ${JSON.stringify(variables)}`); + console.log(`loadVariables_options: ${JSON.stringify(options)}`); + + return Promise.resolve({}); +}; rtClone.replaceTokens = function (sources, variables, options) { - return { defaults: 1, files: 0, replaced: 3, tokens: 4, transforms: 5 }; + return Promise.resolve({ defaults: 1, files: 0, replaced: 3, tokens: 4, transforms: 5 }); }; tmr.registerMock('@qetza/replacetokens', rtClone); diff --git a/tasks/ReplaceTokensV6/tests/L0_LogLevel.ts b/tasks/ReplaceTokensV6/tests/L0_LogLevel.ts index 35ddf81..81bdad5 100644 --- a/tasks/ReplaceTokensV6/tests/L0_LogLevel.ts +++ b/tasks/ReplaceTokensV6/tests/L0_LogLevel.ts @@ -9,8 +9,13 @@ tmr.setInput('targetFiles', process.env['__sources__']); tmr.setInput('verbosity', process.env['__logLevel__']); // mocks -const rt = require('@qetza/replacetokens'); -const rtClone = Object.assign({}, rt); +const rtClone = Object.assign({}, require('@qetza/replacetokens')); +rtClone.loadVariables = function (variables, options) { + console.log(`loadVariables_variables: ${JSON.stringify(variables)}`); + console.log(`loadVariables_options: ${JSON.stringify(options)}`); + + return Promise.resolve({}); +}; rtClone.replaceTokens = function (sources, variables, options) { console.debug('debug'); console.info('info'); @@ -19,7 +24,7 @@ rtClone.replaceTokens = function (sources, variables, options) { console.group('group'); console.groupEnd(); - return { defaults: 1, files: 2, replaced: 3, tokens: 4, transforms: 5 }; + return Promise.resolve({ defaults: 1, files: 2, replaced: 3, tokens: 4, transforms: 5 }); }; tmr.registerMock('@qetza/replacetokens', rtClone); diff --git a/tasks/ReplaceTokensV6/tests/L0_NoMock.ts b/tasks/ReplaceTokensV6/tests/L0_NoMock.ts new file mode 100644 index 0000000..f6ec229 --- /dev/null +++ b/tasks/ReplaceTokensV6/tests/L0_NoMock.ts @@ -0,0 +1,43 @@ +import * as tmrm from 'azure-pipelines-task-lib/mock-run'; +import * as path from 'path'; + +const taskPath = path.join(__dirname, '..', 'index.js'); +const tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); + +// inputs +if (process.env['__sources__']) tmr.setInput('targetFiles', process.env['__sources__']); +if (process.env['__addBOM__']) tmr.setInput('writeBOM', process.env['__addBOM__']); +if (process.env['__additionalVariables__']) tmr.setInput('additionalVariables', process.env['__additionalVariables__']); +if (process.env['__charsToEscape__']) tmr.setInput('charsToEscape', process.env['__charsToEscape__']); +if (process.env['__encoding__']) tmr.setInput('encoding', process.env['__encoding__']); +if (process.env['__escape__']) tmr.setInput('escapeType', process.env['__escape__']); +if (process.env['__escapeChar__']) tmr.setInput('escapeChar', process.env['__escapeChar__']); +if (process.env['__ifNoFilesFound__']) tmr.setInput('actionOnNoFiles', process.env['__ifNoFilesFound__']); +if (process.env['__logLevel__']) tmr.setInput('verbosity', process.env['__logLevel__']); +if (process.env['__missingVarAction__']) tmr.setInput('missingVarAction', process.env['__missingVarAction__']); +if (process.env['__missingVarDefault__']) tmr.setInput('defaultValue', process.env['__missingVarDefault__']); +if (process.env['__missingVarLog__']) tmr.setInput('actionOnMissing', process.env['__missingVarLog__']); +if (process.env['__recursive__']) tmr.setInput('enableRecursion', process.env['__recursive__']); +if (process.env['__root__']) tmr.setInput('rootDirectory', process.env['__root__']); +if (process.env['__separator__']) tmr.setInput('variableSeparator', process.env['__separator__']); +if (process.env['__telemetryOptout__']) tmr.setInput('telemetryOptout', process.env['__telemetryOptout__']); +if (process.env['__tokenPattern__']) tmr.setInput('tokenPattern', process.env['__tokenPattern__']); +if (process.env['__tokenPrefix__']) tmr.setInput('tokenPrefix', process.env['__tokenPrefix__']); +if (process.env['__tokenSuffix__']) tmr.setInput('tokenSuffix', process.env['__tokenSuffix__']); +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__']); + +// mocks +const axiosClone = Object.assign({}, require('axios')); +axiosClone.default = Object.assign({}, axiosClone.default); +axiosClone.default.post = function () { + console.log('telemetry sent'); + + return Promise.resolve(); +}; + +tmr.registerMock('axios', axiosClone); + +// act +tmr.run(); diff --git a/tasks/ReplaceTokensV6/tests/L0_Run.ts b/tasks/ReplaceTokensV6/tests/L0_Run.ts index f16ce1e..7e93bf3 100644 --- a/tasks/ReplaceTokensV6/tests/L0_Run.ts +++ b/tasks/ReplaceTokensV6/tests/L0_Run.ts @@ -30,12 +30,17 @@ if (process.env['__transformsSuffix__']) tmr.setInput('transformSuffix', process // mocks const rtClone = Object.assign({}, require('@qetza/replacetokens')); +rtClone.loadVariables = function (variables, options) { + console.log(`loadVariables_variables: ${JSON.stringify(variables)}`); + console.log(`loadVariables_options: ${JSON.stringify(options)}`); + + return Promise.resolve({}); +}; rtClone.replaceTokens = function (sources, variables, options) { console.log(`sources: ${JSON.stringify(sources)}`); - console.log(`variables: ${JSON.stringify(variables)}`); console.log(`options: ${JSON.stringify(options)}`); - return { defaults: 1, files: 2, replaced: 3, tokens: 4, transforms: 5 }; + return Promise.resolve({ defaults: 1, files: 2, replaced: 3, tokens: 4, transforms: 5 }); }; tmr.registerMock('@qetza/replacetokens', rtClone); diff --git a/tasks/ReplaceTokensV6/tests/_data/file.expected.txt b/tasks/ReplaceTokensV6/tests/_data/file.expected.txt new file mode 100644 index 0000000..a59ea05 --- /dev/null +++ b/tasks/ReplaceTokensV6/tests/_data/file.expected.txt @@ -0,0 +1,10 @@ +var.var: var +var.secret: secret +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/tasks/ReplaceTokensV6/tests/_data/file.txt b/tasks/ReplaceTokensV6/tests/_data/file.txt new file mode 100644 index 0000000..5099d22 --- /dev/null +++ b/tasks/ReplaceTokensV6/tests/_data/file.txt @@ -0,0 +1,10 @@ +var.var: #{var.VAR}# +var.secret: #{VAR_secret}# +var1: #{VAR1}# +var2: #{VAR2}# +var_json: #{var_json}# +var_yaml1: #{var_yaml1}# +var_yaml2: #{var_yaml2}# +var.yml1: #{var.yml1}# +var.yml2: #{var.yml2}# +unknown: #{unknown}# \ No newline at end of file diff --git a/tasks/ReplaceTokensV6/tests/_data/vars.yml b/tasks/ReplaceTokensV6/tests/_data/vars.yml index 09950f5..d5615fe 100644 --- a/tasks/ReplaceTokensV6/tests/_data/vars.yml +++ b/tasks/ReplaceTokensV6/tests/_data/vars.yml @@ -1,3 +1,3 @@ -var_yml1: 'file' +var.yml1: 'file' --- -var_yml2: 'file' \ No newline at end of file +var.yml2: 'file' \ No newline at end of file diff --git a/vss-extension.json b/vss-extension.json index ba19fdc..9399bf8 100644 --- a/vss-extension.json +++ b/vss-extension.json @@ -2,7 +2,7 @@ "manifestVersion": 1, "id": "replacetokens", "name": "Replace Tokens", - "version": "5.0.5", + "version": "5.0.6", "public": true, "publisher": "qetza", "targets": [