github-script
github-script copied to clipboard
How to reference a separate script file which processes and returns output?
Discussed in https://github.com/actions/github-script/discussions/433
Originally posted by rdhar November 13, 2023 Hi, I would like to strip out snippets of JS code into separate files outside of YAML for ease of legibility. However, I'm struggling to understand how best to achieve something this.
Before/Current
script: |
const { data: list_comments } = await github.rest.issues.listComments({
issue_number: context.issue.number,
owner: context.repo.owner,
per_page: 100,
repo: context.repo.repo,
});
const get_comment = list_comments
.sort((a, b) => b.id - a.id)
.find((comment) => /^keyword/.test(comment.body));
return {
body: get_comment.body,
id: get_comment.id,
};
After/Proposed
script: |
require(process.env.GITHUB_ACTION_PATH + '/comment.js');
// File: comment.js
const { data: list_comments } = await github.rest.issues.listComments({
issue_number: context.issue.number,
owner: context.repo.owner,
per_page: 100,
repo: context.repo.repo,
});
const get_comment = list_comments
.sort((a, b) => b.id - a.id)
.find((comment) => /^keyword/.test(comment.body));
return {
body: get_comment.body,
id: get_comment.id,
};
With this, I get: "SyntaxError: await is only valid in async functions and the top level bodies of modules."
If I drop the await, then I get: "ReferenceError: github is not defined."
I'm sure I'm missing something obvious with module.exports = ({ github, context }) => { ... }, but I'm not sure how best to address this particular script which: makes an API call, processes the response, and returns the output in that specific order.
Really appreciate any thoughts/inputs, thanks for your time.
I would argue that first class external script support is a desirable feature! The default is to support await in code assigned to script, there is an eventual need to move the script into a file, and the likely expectation is to copy paste the content to a file and everything works exactly the same.
The workaround I have for now is:
...
script: |
const fn = require('${{ github.workspace }}/.github/fn.js')
await fn({
github,
context,
core,
glob,
io,
exec,
require
})
or
...
script: await require('${{ github.workspace }}/.github/fn.js')({ github, context, core, glob, io, exec, require })
In this example, I believe you would do something like the following, per the docs for async here:
script: |
const script = require(process.env.GITHUB_ACTION_PATH + '/comment.js');
await script({github, context, core})
// File: comment.js
module.exports = async ({ github, context, core }) => {
const { data: list_comments } = await github.rest.issues.listComments({
issue_number: context.issue.number,
owner: context.repo.owner,
per_page: 100,
repo: context.repo.repo,
});
const get_comment = list_comments
.sort((a, b) => b.id - a.id)
.find((comment) => /^keyword/.test(comment.body));
return {
body: get_comment.body,
id: get_comment.id,
};
}
If you have more that you need to do, you can require more scripts and await them after calling the first one, etc.
I would argue that first class external script support is a desirable feature! The default is to support await in code assigned to
script, there is an eventual need to move the script into a file, and the likely expectation is to copy paste the content to a file and everything works exactly the same.
Great to hear your support, @kilianc, and agree that first-class support for external script files would be ideal to match parity with run: bash script.sh per GitHub docs.
In this example, I believe you would do something like the following, per the docs for async here: If you have more that you need to do, you can
requiremore scripts andawaitthem after calling the first one, etc.
Brilliant, thank you for sharing, @yhakbar!
That script setup and module.exports = async... is exactly what was needed -- the only tweak was to replace the return { ... } with core.setOutput(), like so:
// Before
return {
body: get_comment.body,
id: get_comment.id,
};
// After
core.setOutput("body", get_comment.body);
core.setOutput("id", get_comment.id);
Similarly, I had to amend references to the output result within the action.yml workflow as well:
# Before
fromJSON(steps.comment.outputs.result)['body']
fromJSON(steps.comment.outputs.result)['id']
# After
steps.comment.outputs.body
steps.comment.outputs.id
To recognize your contribution, would you mind sharing your answer on the original Q&A #433 discussion, where I can accept it as the correct answer?