GitHub Action
js-eval-action
It's a GitHub Action for evaluating small pieces of JavaScript code passed as expression
input
or placed to a separate file.
Can be handy for implementing easy logic, math and string manipulation instead of using bash scripts.
Also, you can make own composite actions on base of js-eval-action by creating a single action.yml
file.
If you want to extract JS code to a file and get code completion and type checks you can install
js-eval-action-expression-context package.
Look at example dir to see this approach.
- id: getNextAttemptNumber
uses: cardinalby/js-eval-action@v1
env:
STEP_SIZE: 2
with:
data: '8'
expression: "parseInt(inputs.data) + parseInt(env.STEP_SIZE)"
# steps.getNextAttemptNumber.outputs.result == "10"
- id: checkNewVersion
uses: cardinalby/js-eval-action@v1
env:
OLD_VERSION: 1.2.3
NEW_VERSION: 1.3.0
with:
expression: |
({
greater: semver.gte(env.NEW_VERSION, env.OLD_VERSION),
compatible: semver.major(env.NEW_VERSION) === semver.major(env.OLD_VERSION)
})
extractOutputs: 'true'
# steps.checkNewVersion.outputs.greater == "true"
# steps.checkNewVersion.outputs.compatible == "true"
- name: Export env variables
uses: ./
env:
ENV_FILE: 'constants.env'
with:
jsFile: 'exportEnvs.js'
exportEnvs.js:
Object.entries(
dotenv.parse(fs.readFileSync(env.ENV_FILE).toString())
).forEach(
e => core.exportVariable(e[0], e[1])
)
Look at example dir to see the same code extracted as a composite action.
Validate dispatched_workflow inputs
- name: Validate workflow_dispatch inputs
uses: ./
env:
attempt: ${{ github.event.inputs.attemptNumber }}
max: ${{ github.event.inputs.maxAttempts }}
with:
expression: |
{
const attempt = parseInt(env.attempt), max = parseInt(env.max);
assert(attempt && max && max >= attempt);
}
# Will fail if github.event.inputs are invalid
- id: jsonExample
uses: cardinalby/js-eval-action@v1
env:
MY_VAR: '{"a": "hello", "b": "hell"}'
with:
jsonEnvs: MY_VAR
expression: "env.MY_VAR.a.indexOf(env.MY_VAR.b) !== -1"
# steps.jsonExample.outputs.result == "true"
- id: getDefaultBranch
uses: cardinalby/js-eval-action@v1
env:
# Required to use octokit
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
expression: |
(await octokit.rest.repos.get({
owner: context.repo.owner,
repo: context.repo.repo
})).data.default_branch
# steps.getDefaultBranch.outputs.result == "master"
For this particular case you can use a dedicated octokit/request-action.
- id: readYamlExample
uses: cardinalby/js-eval-action@v1
with:
expression: 'yaml.parse((await fs.readFile("fileInRepo.yml")).toString()).myProperty'
# steps.readYamlExample.outputs.result == "property value"
Actual self-test functional examples are in .github/workflows/test.yml
JS expression that returns a value.
- You have to set either
expression
input orjsFile
- By default, (if
extractOutputs
input isfalse
) the value will be serialized to string and put toresult
output. IfextractOutputs
isstrue
, the value has to be an object. Each property of it will be considered as a separate output and serialized to string. - Expression will be put to
async () => %EXPRESSION%
wrapper. - You can use
await
in the expression and return a Promise. When it is fulfilled, it's result will be taken. - You can use curly braces with return statement:
{ let x = 5; x += 2; return x; }
- To return the object directly wrap it into parentheses:
({out1: 5, out2: 10})
; - Do not pass untrusted string to
expression
!
Path to a file containing JS code that will be evaluated. Use instead of expression
if you want to
extract JavaScript code to the separate file.
- You have to set either
expression
input orjsFile
- This code is not wrapped like the
expression
input code, but evaluated directly instead and value is parsed according to the same rules. It's recommended to wrap it by yourself:(async () => 2 * 2)()
. Replace2 * 2
by your expression. - Look at js-eval-action-expression-context package for type declarations if you want to enable type checking and autocompletion in your JS file.
Requires the value returned by the expression to be an object. Each property of it will be considered as a separate output and serialized to string. To return the object directly in the expression wrap it into parentheses
Parse listed inputs as JSON (if you access them via inputs.NAME
in expression).
- Format: input names separated by
|
sign. Example:input1|input2
. - Use asterisk
*
to parse all inputs as JSON.
Parse listed env variables as JSON (if you access them via env.NAME
in expression).
- Format: input names separated by
|
sign. Example:input1|input2
. - Use asterisk
*
to parse all inputs as JSON.
Timeout of JS evaluation in milliseconds. No timeout if empty. If timeout reached, action fails with error
and timedOut
output set to true
.
Arbitrary data to be accessed inside JS expression. Doesn't have any other meaning.
You can also set other not documented inputs and access them in JS expression using inputs.NAME
,
but in this case GitHub runner will produce warnings about unknown inputs. To avoid it you can:
- Set env variables for the step and access them as
env.NAME
in the expression - Set env variables with
INPUT_
prefixes. For example,INPUT_XYZ
(upper case!) env variable is consideredxyz
input and doesn't produce a warning.
If extractOutputs
input is false
contains a result of the expression evaluation. undefined
otherwise.
If timeoutMs
input is set and execution was timed out, this output contains true
. false
otherwise.
If extractOutputs
input is true
, each property of the object returned by the expression will be
serialized and set as a separate output.
Value returned by the JS expression is serialized to string that will be set to output(s).
- If
extractOutputs
input isfalse
(default), expression result will be serialized and put toresult
output. - If
extractOutputs
input istrue
, expression result has to be an object. Each property of it will be serialized and set as a separate output.
Serialization rules:
JS value | String |
---|---|
true | true |
false | false |
123 | 123 |
"abc" | abc |
undefined | undefined |
{a: 3, b: "c"} | {"a":3,"b":"c"} |
["a", "b"] | ["a","b"] |
In the expression you can access the following objects:
Allows you to read inputs in form of inputs.inputName
. Note, that input names are not case-sensitive.
Normally all inputs are of string type. But if the input you read is marked as JSON input by
jsonInputs
, it will be parsed
(at the moment of access) and result value will be returned.
Allows you to read env variables in form of env.varName
. Note, that env variables names are case-sensitive.
Normally all env variables are of string type. But if the env variable you read is marked as JSON input by
jsonEnvs
, it will be parsed
(at the moment of access) and result value will be returned.
GitHub Actions context object.
Contains an instance of octokit if GITHUB_TOKEN
env variable is set. Usage example:
(await octokit.rest.repos.get({owner: context.repo.owner, repo: context.repo.repo})).data.name
Contains @actions/core library.
Contains semver library.
Contains yaml library.
Contains wildstring library.
Contains dotenv library.
Contains dotenv-expand exported function.
Contains fs-extra library.
Contains NodeJS path module.
Contains NodeJS assert module.
Note: console.assert()
doesn't cause an Error in NodeJS since version 10. It's the reason to
use assert(value)
, assert.deepStrictEqual(actual, expected)
, etc. instead.
Contains NodeJS buffer module.
and Buffer
constructor available separately from module.