Skip to content
This repository has been archived by the owner on Mar 10, 2024. It is now read-only.

Commit

Permalink
Add support for external variable files (#136)
Browse files Browse the repository at this point in the history
  • Loading branch information
qetza committed Mar 6, 2020
1 parent d36c6fe commit 18b63f4
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 3 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ If your are using a YAML file, add a task with the following syntax:
Parameters include (in parenthesis the yaml name):
- **Root directory** (rootDirectory): the base directory for searching files. If not specified the default working directory will be used.
- **Target files** (targetFiles): the absolute or relative newline-separated paths to the files to replace tokens. Wildcards can be used (eg: `**\*.config` for all .config files in all sub folders).
- **Target files** (targetFiles): the absolute or relative newline-separated paths to the files to replace tokens. Wildcards can be used (eg: `**\*.config` for all _.config_ files in all sub folders).
> **Syntax**: {file path}[ => {output path}]
>
> - `web.config` will replace tokens in _web.config_ and update the file.
Expand Down Expand Up @@ -52,6 +52,8 @@ Parameters include (in parenthesis the yaml name):
- **Token prefix** (tokenPrefix): the prefix of the tokens to search in the target files.
- **Token suffix** (tokenSuffix): the suffix of the tokens to search in the target files.
- **Empty value** (emptyValue): the variable value that will be replaced with an empty string.
- **Variable files (JSON)** (variableFiles): the absolute or relative comma or newline-separated paths to the files containing additional variables. Wildcards can be used (eg: `vars\**\*.json` for all _.json_ files in all sub folders of _vars_). Variables declared in files overrides variables defined in the pipeline.
- **Variable separator** (variableSeparator): the separtor to use in variable names for nested objects and arrays in variable files. Example: `{ 'My': { 'Value': ['Hello World!'] } }` will create a variable _My.Value.0_ with the value _Hello World!_.

## Tips
If you want to use tokens in XML based configuration files to be replaced during deployment and also have those files usable for local development you can combine the [Replace Tokens task](https://marketplace.visualstudio.com/items?itemName=qetza.replacetokens) with the [XDT tranform task](https://marketplace.visualstudio.com/items?itemName=qetza.xdttransform):
Expand All @@ -64,6 +66,7 @@ If you want to use tokens in XML based configuration files to be replaced during
## Release notes
**New in 3.4.0**
- Add summary in logs with number of tokens found and replaced ([#126](https://github.com/qetza/vsts-replacetokens-task/issues/126)).
- Add support for variables in external JSON files ([#113](https://github.com/qetza/vsts-replacetokens-task/issues/113)).

**New in 3.3.1**
- **Breaking change**: If you were using negative pattern you need to use the semi colon `;` as a separator instead of new-line in _Target files_.
Expand Down
Binary file modified images/task-parameters.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
72 changes: 70 additions & 2 deletions task/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import iconv = require('iconv-lite');
import jschardet = require('jschardet');
import path = require('path');
import os = require('os');
import { isObject } from 'util';

const ENCODING_AUTO: string = 'auto';
const ENCODING_ASCII: string = 'ascii';
Expand Down Expand Up @@ -122,6 +123,7 @@ class Counter {

var logger: ILogger = new NullLogger();
var globalCounters: Counter = new Counter();
var fileVariables: {[name: string]: string} = {};

var mapEncoding = function (encoding: string): string {
switch (encoding)
Expand Down Expand Up @@ -192,6 +194,42 @@ var getEncoding = function (filePath: string): string {
}
}

var loadVariablesFromJson = function(
value: any,
name: string,
separator: string,
variables: { [name: string] : string; }): number
{
let count: number = 0;
let type: string = typeof(value);

let prefix: string = name;
if (name.length != 0)
prefix += separator;

if (value === null || type == "boolean" || type == "number" || type == "string")
{
variables[name] = (value === null ? "" : value) + "";

++count;
logger.debug(' loaded variable: ' + name);
}
else if (Array.isArray(value))
{
value.forEach((v: any, i: number) => {
count += loadVariablesFromJson(v, prefix + i, separator, variables);
});
}
else if (type == "object")
{
Object.keys(value).forEach(key => {
count += loadVariablesFromJson(value[key], prefix + key, separator, variables);
});
}

return count;
}

var replaceTokensInFile = function (
filePath: string,
outputPath: string,
Expand All @@ -217,6 +255,8 @@ var replaceTokensInFile = function (
++localCounter.Tokens;

let value: string = tl.getVariable(name);
if (name in fileVariables)
value = fileVariables[name];

if (!value)
{
Expand Down Expand Up @@ -399,6 +439,36 @@ async function run() {
})
});

let variableSeparator: string = tl.getInput('variableSeparator', false);
tl.getDelimitedInput('variableFiles', '\n', false).forEach((l: string) => {
if (l)
l.split(',').forEach((path: string) => {
if (path)
{
tl.findMatch(root, normalize(path)).forEach(filePath => {
if (tl.stats(filePath).isDirectory())
return;

if (!tl.exist(filePath))
{
logger.error('file not found: ' + filePath);

return;
}

logger.info('loading variables from: ' + filePath);

let encoding: string = getEncoding(filePath);
let variables: any = JSON.parse(iconv.decode(fs.readFileSync(filePath), encoding));

let count: number = loadVariablesFromJson(variables, '', variableSeparator, fileVariables);

logger.info(' ' + count + ' variable(s) loaded.');
});
}
});
});

// initialize task
let regex: RegExp = new RegExp(tokenPrefix + '((?:(?!' + tokenSuffix + ').)*)' + tokenSuffix, 'gm');
logger.debug('pattern: ' + regex.source);
Expand All @@ -407,9 +477,7 @@ async function run() {
rules.forEach(rule => {
tl.findMatch(root, rule.inputPatterns).forEach(filePath => {
if (tl.stats(filePath).isDirectory())
{
return;
}

if (!tl.exist(filePath))
{
Expand Down
19 changes: 19 additions & 0 deletions task/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,25 @@
"groupName": "advanced",
"required": false,
"helpMarkDown": "The variable value which will be replaced by an empty string."
},
{
"name": "variableFiles",
"type": "multiLine",
"label": "Variable files (JSON)",
"defaultValue": "",
"groupName": "advanced",
"required": false,
"helpMarkDown": "Absolute or relative comma or newline-separated paths to the files containing additional variables (wildcards can be used)."
},
{
"name": "variableSeparator",
"type": "string",
"label": "Variable separator",
"defaultValue": ".",
"groupName": "advanced",
"required": false,
"visibleRule": "variableFiles != \"\"",
"helpMarkDown": "The separtor to use in variable names for nested objects in variable files.<br/>Example: {'My':{'Value':'Hello World!'}} will create a variable 'My.Value' with the value 'Hello World!'"
}
],
"execution": {
Expand Down

0 comments on commit 18b63f4

Please sign in to comment.