Skip to content

Commit 5faa00b

Browse files
committed
v2.0 :: replace a whole directory of files
1 parent 0abac69 commit 5faa00b

File tree

12 files changed

+198
-124
lines changed

12 files changed

+198
-124
lines changed

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
The MIT License (MIT)
22

3-
Copyright (c) 2022 Night Story Ltd
3+
Copyright (c) 2023 Night Story Ltd
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
# Replace environment variabels in files in a GitHub Action
1+
# Replace environment variables in files in a GitHub Action
22

33
## Inputs
44

55
- `input_file`
6-
- Path to file with the placeholders to replace.
7-
- `input_file`
8-
- Path to output file.
6+
- Path to file with the placeholders to replace. If directory, will replace all files in the directory.
7+
- `output_file`
8+
- Path to output file. If `input_file` is directory, will be a directory too.
99
- Optional, defaults to `input_file`
1010
- `pattern`
1111
- Which placeholders to replace.

action.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ branding:
55
color: blue
66
inputs:
77
input_file:
8-
description: 'Input file path'
8+
description: 'Path to file with the placeholders to replace. If directory, will replace all files in the directory.'
99
required: true
1010
output_file:
11-
description: 'Output file path'
11+
description: 'Path to output file. If `input_file` is directory, will be a directory too. Optional, defaults to `input_file`'
1212
default: ''
1313
required: false
1414
fail_on_missing_env:

lib/index.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/index.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/licenses.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,16 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
5858
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
5959
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
6060
THE SOFTWARE.
61+
62+
63+
uuid
64+
MIT
65+
The MIT License (MIT)
66+
67+
Copyright (c) 2010-2020 Robert Kieffer and other contributors
68+
69+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
70+
71+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
72+
73+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

lib/sourcemap-register.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "replace-env",
33
"description": "Replace environment variables in files",
4-
"version": "1.0.0",
4+
"version": "2.0.0",
55
"keywords": [
66
"github",
77
"actions",
@@ -21,12 +21,12 @@
2121
"homepage": "https://github.com/nightstory/replace-env#readme",
2222
"author": "nightstory",
2323
"dependencies": {
24-
"@actions/core": "^1.8.2"
24+
"@actions/core": "^1.9.1"
2525
},
2626
"devDependencies": {
27-
"@types/node": "^17.0.17",
28-
"@vercel/ncc": "^0.33.3",
29-
"typescript": "^4.5.5"
27+
"@types/node": "^20.7.0",
28+
"@vercel/ncc": "^0.38.0",
29+
"typescript": "^5.2.2"
3030
},
3131
"scripts": {
3232
"build": "node_modules/.bin/ncc build --license licenses.txt --source-map -m -q src/main.ts -o lib"

src/main.ts

Lines changed: 87 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,110 @@
11
import * as core from '@actions/core'
2-
import fs from 'fs';
2+
import fs from 'node:fs/promises'
3+
import path from 'node:path'
34

4-
import getOptions, {Options, ReplacementPattern, validateOptions} from './options'
5+
import getOptions, { Options, ReplacementPattern, validateOptions } from './options'
6+
import { checkPath } from './utils'
57

6-
const main = async () => {
8+
async function main() {
79
const options: Options = getOptions()
810

911
if (!validateOptions(options)) {
1012
process.exit(1)
1113
}
1214

13-
let pattern = /\${\w+}/gi;
14-
let matcher = /\${(?<var>\w+)}/i;
15+
let pattern = /\${\w+}/gi
16+
let matcher = /\${(?<var>\w+)}/i
1517

1618
switch (options.pattern) {
17-
case ReplacementPattern.SingleDollarBrackets:
18-
pattern = /\${\w+}/gi;
19-
matcher = /\${(?<var>\w+)}/i;
20-
break;
21-
case ReplacementPattern.DoubleDollarBrackets:
22-
pattern = /\${{\w+}}/gi;
23-
matcher = /\${{(?<var>\w+)}}/i;
24-
break;
25-
case ReplacementPattern.DoubleUnderscore:
26-
pattern = /__\w+__/gi;
27-
matcher = /__(?<var>\w+)__/i;
28-
break;
19+
case ReplacementPattern.SingleDollarBrackets: {
20+
pattern = /\${\w+}/gi
21+
matcher = /\${(?<var>\w+)}/i
22+
break
23+
}
24+
case ReplacementPattern.DoubleDollarBrackets: {
25+
pattern = /\${{\w+}}/gi
26+
matcher = /\${{(?<var>\w+)}}/i
27+
break
28+
}
29+
case ReplacementPattern.DoubleUnderscore: {
30+
pattern = /__\w+__/gi
31+
matcher = /__(?<var>\w+)__/i
32+
break
33+
}
2934
}
3035

31-
if (fs.existsSync(options.inputFile)) {
32-
const data = fs.readFileSync(options.inputFile, 'utf8');
33-
const res = data.replace(pattern, (c) => {
34-
const match = c.match(matcher);
36+
switch (checkPath(options.inputFile)) {
37+
case 'directory': {
38+
const resultDirectory = options.outputFile ?? options.inputFile
3539

36-
if (match === null) {
37-
core.warning(`[replace-env] error happened, invalid match for ${c}: ${match}`)
38-
return c;
40+
if (resultDirectory !== options.inputFile) {
41+
await fs.mkdir(resultDirectory, { recursive: true })
3942
}
4043

41-
let env = process.env[match[1]];
44+
const files = await fs.readdir(options.inputFile)
4245

43-
if (typeof env === 'undefined') {
44-
if (options.failOnMissingEnv) {
45-
core.error(`[replace-env] Environment Variable ${match[1]} not found!`);
46-
throw new Error('[replace-env] Environment Variable ${match[1]} not found!');
47-
} else {
48-
core.warning(`[replace-env] Environment Variable ${match[1]} not found!`);
49-
env = c;
50-
}
51-
} else {
52-
core.info(`[replace-env] Replacing Environment Variable ${match[1]}.`);
53-
}
46+
await Promise.all(
47+
files.map(async file => (
48+
await replaceFile({
49+
pattern, matcher,
50+
inputFile: path.join(options.inputFile, file),
51+
failOnMissingEnv: options.failOnMissingEnv,
52+
resultFile: path.join(resultDirectory, file),
53+
})
54+
)),
55+
)
56+
break
57+
}
58+
case 'file': {
59+
await replaceFile({
60+
pattern, matcher,
61+
inputFile: options.inputFile,
62+
failOnMissingEnv: options.failOnMissingEnv,
63+
resultFile: options.outputFile ?? options.inputFile,
64+
})
65+
break
66+
}
67+
case 'otherwise': {
68+
throw new Error('[replace-env] input_file does not exist or an error occured')
69+
}
70+
}
71+
}
5472

55-
return env;
56-
});
73+
async function replaceFile(
74+
{ inputFile, pattern, matcher, failOnMissingEnv, resultFile }: {
75+
pattern: RegExp, matcher: RegExp, failOnMissingEnv: boolean,
76+
inputFile: string, resultFile: string
77+
},
78+
) {
79+
const data = await fs.readFile(inputFile, 'utf8')
80+
const res = data.replace(pattern, (c) => {
81+
const match = c.match(matcher)
5782

58-
let resultFile = options.inputFile;
59-
if (options.outputFile !== null) {
60-
resultFile = options.outputFile;
83+
if (match === null) {
84+
core.warning(`[replace-env] error happened, invalid match for ${c}: ${match}`)
85+
return c
6186
}
6287

63-
fs.writeFileSync(resultFile, res);
64-
core.info(`[replace-env] File ${resultFile} saved.`);
65-
} else {
66-
throw new Error('[replace-env] input_file is missing')
67-
}
88+
let env = process.env[match[1]]
89+
90+
if (typeof env === 'undefined') {
91+
if (failOnMissingEnv) {
92+
core.error(`[replace-env] Environment Variable ${match[1]} not found!`)
93+
throw new Error('[replace-env] Environment Variable ${match[1]} not found!')
94+
} else {
95+
core.warning(`[replace-env] Environment Variable ${match[1]} not found!`)
96+
env = c
97+
}
98+
} else {
99+
core.info(`[replace-env] Replacing Environment Variable ${match[1]}.`)
100+
}
101+
102+
return env
103+
})
104+
105+
await fs.writeFile(resultFile, res)
106+
core.info(`[replace-env] File ${resultFile} saved.`)
68107
}
69108

70-
try {
71-
main()
72-
} catch (error) {
73-
core.setFailed(`${error}`)
74-
}
109+
main()
110+
.catch((error) => core.setFailed(`${error}`))

src/options.ts

Lines changed: 40 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -19,59 +19,57 @@ const patternMap: { [key: string]: ReplacementPattern } = {
1919

2020
const defaultPattern = 'single_dollar_brackets'
2121

22-
const findOption: (inputKey: string, envKey: string | null) => (string | null) =
23-
(inputKey, envKey) => {
24-
const input = core.getInput(inputKey)
22+
function findOption(inputKey: string, envKey: string | null) {
23+
const input = core.getInput(inputKey)
2524

26-
if (input.length === 0) {
27-
if (envKey !== null) {
28-
return process.env[envKey] ?? null
29-
} else {
30-
return null
31-
}
25+
if (input.length === 0) {
26+
if (envKey !== null) {
27+
return process.env[envKey] ?? null
3228
} else {
33-
return input
29+
return null
3430
}
31+
} else {
32+
return input
3533
}
34+
}
3635

37-
const requireOption: (inputKey: string, envKey: string | null) => string =
38-
(inputKey, envKey) => {
39-
const result = findOption(inputKey, envKey)
40-
if (!result) {
41-
core.setFailed(`input ${inputKey} (or env ${envKey}) is required but was missing`)
42-
process.exit(1)
43-
}
44-
return result!
45-
}
46-
47-
const getFlag: (inputKey: string, envKey: string | null, def: boolean) => boolean =
48-
(inputKey, envKey, def) => {
49-
const result = findOption(inputKey, envKey)
50-
return result ? result === 'true' : def
36+
function requireOption(inputKey: string, envKey: string | null) {
37+
const result = findOption(inputKey, envKey)
38+
if (!result) {
39+
core.setFailed(`input ${inputKey} (or env ${envKey}) is required but was missing`)
40+
process.exit(1)
5141
}
42+
return result!
43+
}
5244

53-
const getOptions: () => Options = () => ({
54-
inputFile: requireOption('input_file', null),
55-
outputFile: findOption('output_file', null),
56-
failOnMissingEnv: getFlag('fail_on_missing_env', null, false),
57-
pattern: patternMap[findOption('pattern', null) ?? defaultPattern]!,
58-
})
45+
function getFlag(inputKey: string, envKey: string | null, def: boolean) {
46+
const result = findOption(inputKey, envKey)
47+
return result ? result === 'true' : def
48+
}
5949

60-
export const validateOptions: (options: Options) => boolean =
61-
(o) => {
62-
let result = true
50+
function getOptions(): Options {
51+
return {
52+
inputFile: requireOption('input_file', null),
53+
outputFile: findOption('output_file', null),
54+
failOnMissingEnv: getFlag('fail_on_missing_env', null, false),
55+
pattern: patternMap[findOption('pattern', null) ?? defaultPattern]!,
56+
}
57+
}
6358

64-
if ([o.inputFile].some(v => v.length === 0)) {
65-
core.setFailed(`input_file must not be empty`)
66-
result = false
67-
}
59+
export function validateOptions(o: Options) {
60+
let result = true
6861

69-
if (o.outputFile !== null && o.outputFile.length === 0) {
70-
core.setFailed(`output_file must not be empty`)
71-
result = false
72-
}
62+
if ([ o.inputFile ].some(v => v.length === 0)) {
63+
core.setFailed(`input_file must not be empty`)
64+
result = false
65+
}
7366

74-
return result
67+
if (o.outputFile !== null && o.outputFile.length === 0) {
68+
core.setFailed(`output_file must not be empty`)
69+
result = false
7570
}
7671

72+
return result
73+
}
74+
7775
export default getOptions

0 commit comments

Comments
 (0)