feat: add script-file input for lint-friendly external JS scripts

Closes #714

Inline `script` is a YAML string — invisible to linters and IDEs.
The common workaround was wrapping a `require` call inside the inline script,
which still needs boilerplate and assumes a path convention.

New optional `script-file` input that accepts a path to a JS file.
The file must `module.exports` an async function receiving the standard
IoC dependency bag (`github`, `octokit`, `getOctokit`, `context`, `core`,
`exec`, `glob`, `io`, `require`).

```yaml
- uses: actions/checkout@v4
- uses: actions/github-script@v9
  with:
    script-file: .github/scripts/my-script.js
```

`script` and `script-file` are mutually exclusive — exactly one must be provided.
Relative paths resolve against `$GITHUB_WORKSPACE`; absolute paths are used as-is.

- `action.yml` — adds `script-file` input; makes `script` optional
- `src/script-file.ts` — path resolution and script loading logic
- `src/args.ts` — `AsyncFunctionArguments` extracted from `async-function.ts` so neither execution path depends on the other
- `src/main.ts` — mutual-exclusion validation; dispatches to the right execution path
- `types/non-webpack-require.ts` — corrects `__non_webpack_require__` type from deprecated `NodeRequire` / wrong `NodeJS.RequireResolve` to `NodeJS.Require`
- `__test__/script-file.test.ts` — 10 tests covering path resolution, arg forwarding, error cases
- `README.md` — new `## Script file` section with usage, IoC bag table, path resolution rules
- `.github/fixtures/script-file/` — fixture JS files for integration tests
- `.github/workflows/integration.yml` — 10 new integration test jobs: happy path (relative path, absolute path, all IoC args, json/string encoding,
  require-in-file) and error cases (both inputs set, neither set, nonexistent file, non-function export, file:// protocol)
This commit is contained in:
Osher 2026-04-25 19:59:10 +03:00
parent 3a2844b7e9
commit 67c280d263
18 changed files with 519 additions and 97 deletions

36
dist/index.js vendored
View file

@ -65029,6 +65029,25 @@ function parseNumberArray(listString) {
// EXTERNAL MODULE: external "path"
var external_path_ = __nccwpck_require__(1017);
;// CONCATENATED MODULE: ./src/script-file.ts
function resolveScriptFilePath(scriptFile) {
if (scriptFile.startsWith('file://')) {
throw new Error('"script-file" must not use the "file://" protocol');
}
return external_path_.isAbsolute(scriptFile)
? scriptFile
: external_path_.resolve(process.env['GITHUB_WORKSPACE'], scriptFile);
}
async function callScriptFile(args, scriptFile, requireFn) {
const resolvedPath = resolveScriptFilePath(scriptFile);
const scriptFn = requireFn(resolvedPath);
if (typeof scriptFn !== 'function') {
throw new Error(`"script-file" must export a function, got ${typeof scriptFn}`);
}
return scriptFn(args);
}
;// CONCATENATED MODULE: ./src/wrap-require.ts
const wrapRequire = new Proxy(require, {
@ -65065,6 +65084,7 @@ const wrapRequire = new Proxy(require, {
process.on('unhandledRejection', handleError);
main().catch(handleError);
async function main() {
@ -65091,13 +65111,20 @@ async function main() {
opts.baseUrl = baseUrl;
}
const github = getOctokit(token, opts, retry, requestLog);
const script = core.getInput('script', { required: true });
const scriptInline = core.getInput('script');
const scriptFile = core.getInput('script-file');
if (scriptInline && scriptFile) {
throw new Error('Only one of "script" or "script-file" may be provided, not both');
}
if (!scriptInline && !scriptFile) {
throw new Error('One of "script" or "script-file" must be provided');
}
// Wrap getOctokit so secondary clients inherit retry, logging,
// orchestration ID, and the action's retries input.
// Deep-copy opts to prevent shared references with the primary client.
const configuredGetOctokit = createConfiguredGetOctokit(getOctokit, { ...opts, retry: { ...opts.retry }, request: { ...opts.request } }, retry, requestLog);
// Using property/value shorthand on `require` (e.g. `{require}`) causes compilation errors.
const result = await callAsyncFunction({
const args = {
require: wrapRequire,
__original_require__: require,
github,
@ -65108,7 +65135,10 @@ async function main() {
exec: exec,
glob: glob,
io: io
}, script);
};
const result = scriptFile
? await callScriptFile(args, scriptFile, require)
: await callAsyncFunction(args, scriptInline);
let encoding = core.getInput('result-encoding');
encoding = encoding ? encoding : 'json';
let output;