Skip to content

Commit

Permalink
add glob pattern in variable file paths support
Browse files Browse the repository at this point in the history
  • Loading branch information
qetza committed Mar 10, 2024
1 parent fd4a9bd commit a0b1ef8
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 30 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## v1.1.0
- Add support for JSON comments in _variables_ and in JSON variable files and environment variables
- Add support for multiple glob patterns separeted by a semi-colon (`;`) using [fast-glob](https://github.com/mrmlnc/fast-glob) in variable file paths

## v1.0.0
- Initial Release of the ReplaceTokens action
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ Please refer to the [release page](https://github.com/qetza/replacetokens-action
# A JSON serialized object containing the variables values.
# The object can be:
# - an object: properties will be parsed as key/value pairs
# - a string starting with '@': value is parsed as a path to a JSON file
# - a string starting with '@': value is parsed as multiple glob patterns separated
# by a semi-colon ';' using fast-glob syntax to JSON files
# - a string starting with '$': value is parsed as an environment variable name
# containing JSON encoded key/value pairs
# - an array: each item must be an object or a string and will be parsed as
Expand Down Expand Up @@ -139,7 +140,7 @@ Please refer to the [release page](https://github.com/qetza/replacetokens-action
# Optional. Default: false
recursive: ''

# The root path to use when reading input source files with relative paths.
# The root path to use when reading files with a relative path.
#
# Optional. Default: ${{ github.workspace }}
root: ''
Expand Down Expand Up @@ -232,11 +233,12 @@ Please refer to the [release page](https://github.com/qetza/replacetokens-action
sources: '**/*.yml'
variables: >
[
${{ toJSON(vars) }}, # variables
${{ toJSON(secrets) }}, # secrets
${{ toJSON(format('@{0}/tests/data/vars.json', github.workspace)) }}, # read from file
"$ENV_VARS", # read from env
{ "VAR2": "${{ github.event.inputs.var2 }}" } # inline values
${{ toJSON(vars) }}, # variables
${{ toJSON(secrets) }}, # secrets
${{ toJSON(format('@{0}/settings.json', github.workspace)) }}, # read from file
"@**/vars.(json|jsonc);!**/local/*" # read from files
"$ENV_VARS", # read from env
{ "VAR2": "${{ github.event.inputs.var2 }}" } # inline values
]
env:
ENV_VARS: '{ "VAR4": "env_value4" }'
Expand Down
5 changes: 3 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ inputs:
A JSON serialized object containing the variables values.
The object can be:
- an object: properties will be parsed as key/value pairs
- a string starting with '@': value is parsed as a path to a JSON file
- a string starting with '@': value is parsed as multiple glob patterns separated
by a semi-colon ';' using fast-glob syntax to JSON files
- a string starting with '$': value is parsed as an environment variable name
containing JSON encoded key/value pairs
- an array: each item must be an object or a string and will be parsed as specified
Expand Down Expand Up @@ -106,7 +107,7 @@ inputs:
description: 'Enable token replacements in values recusively.'
default: 'false'
root:
description: 'The root path to use for relative paths in sources.'
description: 'The root path to use when reading files with a relative path.'
default: ${{ github.workspace }}
separator:
description: >
Expand Down
30 changes: 22 additions & 8 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -43522,6 +43522,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.run = void 0;
const core = __importStar(__nccwpck_require__(2186));
const replacetokens_1 = __nccwpck_require__(9633);
const fg = __importStar(__nccwpck_require__(3664));
const strip_json_comments_1 = __importDefault(__nccwpck_require__(77));
async function run() {
const _debug = console.debug;
Expand All @@ -43532,8 +43533,6 @@ async function run() {
const _groupEnd = console.groupEnd;
try {
// read and validate inputs
const sources = core.getMultilineInput('sources', { required: true, trimWhitespace: true });
const variables = await parseVariables(core.getInput('variables', { required: true, trimWhitespace: true }));
const options = {
addBOM: core.getBooleanInput('add-bom'),
encoding: core.getInput('encoding') || replacetokens_1.Encodings.Auto,
Expand Down Expand Up @@ -43578,6 +43577,8 @@ async function run() {
suffix: core.getInput('transforms-suffix') || replacetokens_1.Defaults.TransformSuffix
}
};
const sources = core.getMultilineInput('sources', { required: true, trimWhitespace: true });
const variables = await parseVariables(core.getInput('variables', { required: true, trimWhitespace: true }), options.root || process.cwd());
// override console logs
const logLevel = parseLogLevel(getChoiceInput('log-level', ['debug', 'info', 'warn', 'error']));
console.debug = function (...args) {
Expand Down Expand Up @@ -43641,19 +43642,18 @@ function getChoiceInput(name, choices, options) {
return input;
throw new TypeError(`Unsupported value for input: ${name}\nSupport input list: '${choices.join(' | ')}'`);
}
async function parseVariables(input) {
async function parseVariables(input, root) {
input = input || '{}';
const variables = JSON.parse((0, strip_json_comments_1.default)(input));
let load = async (v) => {
if (typeof v === 'string') {
switch (v[0]) {
case '@':
core.debug(`loading variables from file '${v.substring(1)}'`);
return JSON.parse((0, strip_json_comments_1.default)((await (0, replacetokens_1.readTextFile)(v.substring(1))).content || '{}'));
case '$':
case '@': // single string referencing a file
return await loadVariablesFromFile(v.substring(1), root);
case '$': // single string referencing environment variable
core.debug(`loading variables from environment '${v.substring(1)}'`);
return JSON.parse((0, strip_json_comments_1.default)(process.env[v.substring(1)] || '{}'));
default:
default: // unsupported
throw new Error("Unsupported value for: variables\nString values starts with '@' (file path) or '$' (environment variable)");
}
}
Expand All @@ -43669,6 +43669,20 @@ async function parseVariables(input) {
}
return await load(variables);
}
async function loadVariablesFromFile(name, root) {
var files = await fg.glob(name.split(';').map(v => v.trim()), {
absolute: true,
cwd: root,
onlyFiles: true,
unique: true
});
const vars = [];
for (const file of files) {
core.debug(`loading variables from file '${file}'`);
vars.push(JSON.parse((0, strip_json_comments_1.default)((await (0, replacetokens_1.readTextFile)(file)).content || '{}')));
}
return (0, replacetokens_1.merge)(...vars);
}
var LogLevel;
(function (LogLevel) {
LogLevel[LogLevel["Debug"] = 0] = "Debug";
Expand Down
3 changes: 2 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
},
"dependencies": {
"@actions/core": "^1.10.1",
"@qetza/replacetokens": "^1.1.0"
"@qetza/replacetokens": "^1.1.0",
"fast-glob": "^3.3.2"
},
"devDependencies": {
"@types/jest": "^29.5.12",
Expand Down
42 changes: 34 additions & 8 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
Options,
TokenPatterns
} from '@qetza/replacetokens';
import * as fg from 'fast-glob';
import stripJsonComments from './strip-json-comments';

export async function run(): Promise<void> {
Expand All @@ -22,8 +23,6 @@ export async function run(): Promise<void> {

try {
// read and validate inputs
const sources = core.getMultilineInput('sources', { required: true, trimWhitespace: true });
const variables = await parseVariables(core.getInput('variables', { required: true, trimWhitespace: true }));
const options: Options = {
addBOM: core.getBooleanInput('add-bom'),
encoding: core.getInput('encoding') || Encodings.Auto,
Expand Down Expand Up @@ -73,6 +72,12 @@ export async function run(): Promise<void> {
}
};

const sources = core.getMultilineInput('sources', { required: true, trimWhitespace: true });
const variables = await parseVariables(
core.getInput('variables', { required: true, trimWhitespace: true }),
options.root || process.cwd()
);

// override console logs
const logLevel = parseLogLevel(getChoiceInput('log-level', ['debug', 'info', 'warn', 'error']));
console.debug = function (...args) {
Expand Down Expand Up @@ -137,22 +142,22 @@ function getChoiceInput(name: string, choices: string[], options?: core.InputOpt
throw new TypeError(`Unsupported value for input: ${name}\nSupport input list: '${choices.join(' | ')}'`);
}

async function parseVariables(input: string): Promise<{ [key: string]: any }> {
async function parseVariables(input: string, root: string): Promise<{ [key: string]: any }> {
input = input || '{}';
const variables = JSON.parse(stripJsonComments(input));

let load = async (v: any) => {
if (typeof v === 'string') {
switch (v[0]) {
case '@':
core.debug(`loading variables from file '${v.substring(1)}'`);
case '@': // single string referencing a file
return await loadVariablesFromFile(v.substring(1), root);

return JSON.parse(stripJsonComments((await readTextFile(v.substring(1))).content || '{}'));
case '$':
case '$': // single string referencing environment variable
core.debug(`loading variables from environment '${v.substring(1)}'`);

return JSON.parse(stripJsonComments(process.env[v.substring(1)] || '{}'));
default:

default: // unsupported
throw new Error(
"Unsupported value for: variables\nString values starts with '@' (file path) or '$' (environment variable)"
);
Expand All @@ -175,6 +180,27 @@ async function parseVariables(input: string): Promise<{ [key: string]: any }> {
return await load(variables);
}

async function loadVariablesFromFile(name: string, root: string): Promise<{ [key: string]: any }> {
var files = await fg.glob(
name.split(';').map(v => v.trim()),
{
absolute: true,
cwd: root,
onlyFiles: true,
unique: true
}
);

const vars: { [key: string]: any }[] = [];
for (const file of files) {
core.debug(`loading variables from file '${file}'`);

vars.push(JSON.parse(stripJsonComments((await readTextFile(file)).content || '{}')));
}

return merge(...vars);
}

enum LogLevel {
Debug,
Info,
Expand Down
3 changes: 3 additions & 0 deletions tests/data/vars.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"VAR4": "file_value4"
}
24 changes: 21 additions & 3 deletions tests/run.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ describe('run', () => {
getInputSpy.mockImplementation(name => {
switch (name) {
case 'variables':
return JSON.stringify(`@${path.join(__dirname, 'data/vars.jsonc')}`);
return JSON.stringify('@tests/**/*.(json|jsonc);!**/settings*');
default:
return '';
}
Expand All @@ -273,7 +273,18 @@ describe('run', () => {
// assert
expect(setFailedSpy).not.toHaveBeenCalled();

expect(replaceTokenSpy).toHaveBeenCalledWith(expect.anything(), { VAR3: 'file_value3' }, expect.anything());
expect(debugSpy).toHaveBeenCalledWith(
`loading variables from file '${path.join(__dirname, 'data/vars.json').replace(/\\/g, '/')}'`
);
expect(debugSpy).toHaveBeenCalledWith(
`loading variables from file '${path.join(__dirname, 'data/vars.jsonc').replace(/\\/g, '/')}'`
);

expect(replaceTokenSpy).toHaveBeenCalledWith(
expect.anything(),
{ VAR3: 'file_value3', VAR4: 'file_value4' },
expect.anything()
);
});

it('variables: env', async () => {
Expand All @@ -299,6 +310,8 @@ describe('run', () => {
// assert
expect(setFailedSpy).not.toHaveBeenCalled();

expect(debugSpy).toHaveBeenCalledWith("loading variables from environment 'ENV_VARS'");

expect(replaceTokenSpy).toHaveBeenCalledWith(expect.anything(), { VAR1: 'value1' }, expect.anything());
});

Expand All @@ -312,7 +325,7 @@ describe('run', () => {
return JSON.stringify([
{ VAR1: 'value1', VAR2: 'value2', VAR3: 'value3' },
'$ENV_VARS',
`@${path.join(__dirname, 'data/vars.jsonc')}`
`@${path.join(__dirname, 'data/vars.jsonc').replace(/\\/g, '/')}`
]);
default:
return '';
Expand All @@ -325,6 +338,11 @@ describe('run', () => {
// assert
expect(setFailedSpy).not.toHaveBeenCalled();

expect(debugSpy).toHaveBeenCalledWith(
`loading variables from file '${path.join(__dirname, 'data/vars.jsonc').replace(/\\/g, '/')}'`
);
expect(debugSpy).toHaveBeenCalledWith("loading variables from environment 'ENV_VARS'");

expect(replaceTokenSpy).toHaveBeenCalledWith(
expect.anything(),
{ VAR1: 'value1', VAR2: 'env_value2', VAR3: 'file_value3' },
Expand Down

0 comments on commit a0b1ef8

Please sign in to comment.