Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add missingVarDefault and missingVarLog inputs #39

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ Simple GitHub Action to replace tokens in files.
- `files` - Glob expression, file name or array of glob/file name
- `tokenPrefix` - Prefix to use when matching tokens, defaults to `#{`
- `tokenSuffix` - Suffix to use when matching tokens, defaults to `}#`
- `missingVarDefault` - The default value to use when a variable is not found, defaults to `""`
- `missingVarLog` - The level to log variable not found messages. Accepted values `off`, `warn`, `error`, defaults to `off`
- `additionalVariables` - A JSON formatted string containing additional variable values, defaults to `"{}"`

## Example

Expand Down Expand Up @@ -36,4 +39,4 @@ If you want to use a different token format, you can specify a custom token pref
# Acknowledgements

- Inspired by the excellent https://marketplace.visualstudio.com/items?itemName=qetza.replacetokens Azure Pipelines task.
- Uses [replace-in-file](https://github.com/adamreisnz/replace-in-file) to do the actual replacement
- Uses [replace-in-file](https://github.com/adamreisnz/replace-in-file) to do the actual replacement
96 changes: 84 additions & 12 deletions __tests__/main.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import { promises as fs } from "fs";
import * as process from 'process'
import * as process from 'process';
import * as core from '@actions/core';
import { replaceTokens } from "../src/replace";
import { MissingVarLog } from "../src/missingVarLog";

let warningMock: jest.SpiedFunction<typeof core.warning>
let errorMock: jest.SpiedFunction<typeof core.error>

describe("basic functionality", () => {
beforeEach(async () => {
jest.clearAllMocks();

errorMock = jest.spyOn(core, 'error').mockImplementation();
warningMock = jest.spyOn(core, 'warning').mockImplementation();

await fs.writeFile("test.txt", "hello #{ACTOR}#", "utf8");
await fs.writeFile("test2.txt", "#{GREETING}# #{ACTOR}#", "utf8");
})
Expand All @@ -15,51 +25,113 @@ describe("basic functionality", () => {

test("replaces single token in file", async () => {
process.env["ACTOR"] = "world";
await replaceTokens("#{", "}#", ["test.txt"]);
await replaceTokens("#{", "}#", ["test.txt"], "", MissingVarLog.Off, "{}");

const content = await fs.readFile('test.txt', 'utf8');
expect(content).toBe("hello world")
expect(content).toBe("hello world");
});

test("replaces single token in file specified with glob", async () => {
process.env["ACTOR"] = "world";
await replaceTokens("#{", "}#", ["*.txt"]);
await replaceTokens("#{", "}#", ["*.txt"], "", MissingVarLog.Off, "{}");

const content = await fs.readFile('test.txt', 'utf8');
expect(content).toBe("hello world")
expect(content).toBe("hello world");

const content2 = await fs.readFile('test2.txt', 'utf8');
expect(content2).toBe(" world")
expect(content2).toBe(" world");
});

test("replaces multiple token in file", async () => {
process.env["GREETING"] = "hallo";
process.env["ACTOR"] = "welt";
await replaceTokens("#{", "}#", ["test2.txt"]);
await replaceTokens("#{", "}#", ["test2.txt"], "", MissingVarLog.Off, "{}");

const content = await fs.readFile('test2.txt', 'utf8');
expect(content).toBe("hallo welt")
expect(content).toBe("hallo welt");
});

test("returns list of changed files", async () => {
const result = await replaceTokens("#{", "}#", ["*.txt"]);
const result = await replaceTokens("#{", "}#", ["*.txt"], "", MissingVarLog.Off, "{}");

expect(result).toEqual([
"test.txt", "test2.txt"
]);
});

test("returns only list of changed files", async () => {
const result = await replaceTokens("#{", "}#", ["test.txt"]);
const result = await replaceTokens("#{", "}#", ["test.txt"], "", MissingVarLog.Off, "{}");

expect(result).toEqual([
"test.txt"
]);
});

test("does not throw when no match", async () => {
const result = await replaceTokens("#{", "}#", [""]);
const result = await replaceTokens("#{", "}#", [""], "", MissingVarLog.Off, "{}");

expect(result).toEqual([]);
});
});

test("does not log missing variable to console when missingVarLog is 'off'", async () => {
delete process.env["GREETING"];
process.env["ACTOR"] = "world";
await replaceTokens("#{", "}#", ["test2.txt"], "", MissingVarLog.Off, "{}");

const content = await fs.readFile('test2.txt', 'utf8');
expect(content).toBe(" world");
expect(warningMock).not.toHaveBeenCalled();
expect(errorMock).not.toHaveBeenCalled();
});

test("logs missing variable warning to console when missingVarLog is 'warn'", async () => {
delete process.env["GREETING"];
process.env["ACTOR"] = "world";
await replaceTokens("#{", "}#", ["test2.txt"], "", MissingVarLog.Warn, "{}");

const content = await fs.readFile('test2.txt', 'utf8');
expect(content).toBe(" world");
expect(warningMock).toHaveBeenCalled();
expect(errorMock).not.toHaveBeenCalled();
});

test("logs missing variable error to console when missingVarLog is 'error'", async () => {
delete process.env["GREETING"];
process.env["ACTOR"] = "world";
await replaceTokens("#{", "}#", ["test2.txt"], "", MissingVarLog.Error, "{}");

const content = await fs.readFile('test2.txt', 'utf8');
expect(content).toBe(" world");
expect(warningMock).not.toHaveBeenCalled();
expect(errorMock).toHaveBeenCalled();
});

test("does not log missing variable to console when missingVarLog is incorrect", async () => {
delete process.env["GREETING"];
process.env["ACTOR"] = "world";
await replaceTokens("#{", "}#", ["test2.txt"], "", 'NONE' as MissingVarLog, "{}");

const content = await fs.readFile('test2.txt', 'utf8');
expect(content).toBe(" world");
expect(warningMock).not.toHaveBeenCalled();
expect(errorMock).not.toHaveBeenCalled();
});

test("replaces token with value from missingVarDefault", async () => {
delete process.env["GREETING"];
process.env["ACTOR"] = "world";
await replaceTokens("#{", "}#", ["test2.txt"], "[MISSING_VALUE]", MissingVarLog.Off, "{}");

const content = await fs.readFile('test2.txt', 'utf8');
expect(content).toBe("[MISSING_VALUE] world");
});

test("replaces token with value from additionalVariables", async () => {
const additionalVariables = { GREETING: "Hello" };
process.env["ACTOR"] = "world";
await replaceTokens("#{", "}#", ["test2.txt"], "", MissingVarLog.Off, additionalVariables);

const content = await fs.readFile('test2.txt', 'utf8');
expect(content).toBe("Hello world");
});
});
9 changes: 9 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ name: "Replace tokens"
description: "Replace tokens in files"
author: "cschleiden"
inputs:
additionalVariables:
description: ""
default: ""
missingVarDefault:
description: ""
default: ""
missingVarLog:
description: ""
default: "off"
tokenPrefix:
description: ""
default: "#{"
Expand Down
71 changes: 67 additions & 4 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8711,6 +8711,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
Object.defineProperty(exports, "__esModule", ({ value: true }));
const core = __importStar(__nccwpck_require__(2186));
const replace_1 = __nccwpck_require__(5287);
const missingVarLog_1 = __nccwpck_require__(1367);
function getFiles() {
let files = core.getInput("files", {
required: true,
Expand All @@ -8721,13 +8722,20 @@ function getFiles() {
}
return [files];
}
function getMissingVarLog() {
const value = core.getInput("missingVarLog");
return Object.values(missingVarLog_1.MissingVarLog).includes(value) ? value : missingVarLog_1.MissingVarLog.Off;
}
function run() {
return __awaiter(this, void 0, void 0, function* () {
try {
const tokenPrefix = core.getInput("tokenPrefix") || "#{";
const tokenSuffix = core.getInput("tokenSuffix") || "}#";
const files = getFiles();
const result = yield (0, replace_1.replaceTokens)(tokenPrefix, tokenSuffix, Array.isArray(files) ? files : [files]);
const missingVarDefault = core.getInput("missingVarDefault") || "";
const missingVarLog = getMissingVarLog();
const additionalVariables = JSON.parse(core.getInput("additionalVariables") || "{}");
const result = yield (0, replace_1.replaceTokens)(tokenPrefix, tokenSuffix, Array.isArray(files) ? files : [files], missingVarDefault, missingVarLog, additionalVariables);
console.log(`Replaced tokens in files: ${result}`);
}
catch (error) {
Expand All @@ -8739,13 +8747,53 @@ function run() {
run();


/***/ }),

/***/ 1367:
/***/ ((__unused_webpack_module, exports) => {

"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.MissingVarLog = void 0;
var MissingVarLog;
(function (MissingVarLog) {
MissingVarLog["Off"] = "off";
MissingVarLog["Warn"] = "warn";
MissingVarLog["Error"] = "error";
})(MissingVarLog || (exports.MissingVarLog = MissingVarLog = {}));


/***/ }),

/***/ 5287:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {

"use strict";

var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
Expand All @@ -8757,11 +8805,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.replaceTokens = void 0;
const core = __importStar(__nccwpck_require__(2186));
const replace_in_file_1 = __nccwpck_require__(5983);
function replaceTokens(tokenPrefix, tokenSuffix, files) {
const missingVarLog_1 = __nccwpck_require__(1367);
function replaceTokens(tokenPrefix, tokenSuffix, files, missingVarDefault, missingVarLog, additionalVariables) {
return __awaiter(this, void 0, void 0, function* () {
const fromRegEx = new RegExp(`${escapeDelimiter(tokenPrefix)}(.+?)${escapeDelimiter(tokenSuffix)}`, "gm");
const matchRegEx = new RegExp(`${escapeDelimiter(tokenPrefix)}(.+?)${escapeDelimiter(tokenSuffix)}`);
const getVariable = (tokenName) => {
var _a;
return (_a = additionalVariables[tokenName]) !== null && _a !== void 0 ? _a : process.env[tokenName];
};
const result = yield (0, replace_in_file_1.replaceInFile)({
files,
allowEmptyPaths: true,
Expand All @@ -8770,9 +8824,18 @@ function replaceTokens(tokenPrefix, tokenSuffix, files) {
const m = match.match(matchRegEx);
if (m) {
const tokenName = m[1];
return process.env[tokenName] || "";
const value = getVariable(tokenName);
if (!!value) {
return value;
}
if (missingVarLog === missingVarLog_1.MissingVarLog.Error) {
core.error(`Variable not found: ${tokenName}`);
}
else if (missingVarLog == missingVarLog_1.MissingVarLog.Warn) {
core.warning(`Variable not found: ${tokenName}`);
}
}
return "";
return missingVarDefault;
}
});
return result.filter(r => r.hasChanged).map(r => r.file);
Expand Down
14 changes: 13 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as core from "@actions/core";
import { replaceTokens } from "./replace";
import { MissingVarLog } from "./missingVarLog";

function getFiles(): string[] {
let files =
Expand All @@ -14,15 +15,26 @@ function getFiles(): string[] {
return [files];
}

function getMissingVarLog(): MissingVarLog {
const value = core.getInput("missingVarLog") as MissingVarLog;
return Object.values(MissingVarLog).includes(value) ? value : MissingVarLog.Off;
}

async function run() {
try {
const tokenPrefix = core.getInput("tokenPrefix") || "#{";
const tokenSuffix = core.getInput("tokenSuffix") || "}#";
const files = getFiles();
const missingVarDefault = core.getInput("missingVarDefault") || "";
const missingVarLog = getMissingVarLog();
const additionalVariables = JSON.parse(core.getInput("additionalVariables") || "{}");
const result = await replaceTokens(
tokenPrefix,
tokenSuffix,
Array.isArray(files) ? files : [files]
Array.isArray(files) ? files : [files],
missingVarDefault,
missingVarLog,
additionalVariables
);
console.log(`Replaced tokens in files: ${result}`);
} catch (error) {
Expand Down
5 changes: 5 additions & 0 deletions src/missingVarLog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum MissingVarLog {
Off = "off",
Warn = "warn",
Error = "error"
}
24 changes: 21 additions & 3 deletions src/replace.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import * as core from "@actions/core";
import { replaceInFile } from "replace-in-file";
import { MissingVarLog } from "./missingVarLog";

export async function replaceTokens(
tokenPrefix: string,
tokenSuffix: string,
files: string[]
files: string[],
missingVarDefault: string,
missingVarLog: MissingVarLog,
additionalVariables: any
) {
const fromRegEx = new RegExp(
`${escapeDelimiter(tokenPrefix)}(.+?)${escapeDelimiter(tokenSuffix)}`,
Expand All @@ -13,6 +18,10 @@ export async function replaceTokens(
`${escapeDelimiter(tokenPrefix)}(.+?)${escapeDelimiter(tokenSuffix)}`
);

const getVariable = (tokenName: string): string | undefined => {
return additionalVariables[tokenName] ?? process.env[tokenName]
}

const result = await replaceInFile({
files,
allowEmptyPaths: true,
Expand All @@ -21,10 +30,19 @@ export async function replaceTokens(
const m = match.match(matchRegEx);
if (m) {
const tokenName = m[1];
return process.env[tokenName] || "";
const value = getVariable(tokenName);
if (!!value) {
return value;
}

if (missingVarLog === MissingVarLog.Error) {
core.error(`Variable not found: ${tokenName}`);
} else if (missingVarLog == MissingVarLog.Warn) {
core.warning(`Variable not found: ${tokenName}`);
}
}

return "";
return missingVarDefault;
}
});

Expand Down