Skip to content

feat: run node-based actions from a uses reference on the host - #82

Merged
Mearman merged 4 commits into
mainfrom
feat/node-actions
Oct 11, 2026
Merged

Mearman merged 4 commits into
mainfrom
feat/node-actions

Conversation

@Mearman

@Mearman Mearman commented Oct 11, 2026

Copy link
Copy Markdown
Member

Adds node-action execution to @pipewright/pipeline-github-actions: the second half of running a uses: step without a container (#71), on top of #81's composite expansion. The runner and CLI wiring is #76; this changes no consumer yet.

runs.using of node20, node16, node12 or node24, with runs.main and the optional runs.pre, runs.pre-if, runs.post and runs.post-if, is modelled beside the composite schema, sharing the declared inputs and branding so the two forms cannot drift. The live metadata syntax documents only node20 and node24; node16 and node12 are gone from the docs but remain runtimes actions/runner names, so an action pinned to them still expands. Anything else a runs.using can hold is rejected with the metadata file and pointer, docker with its own message naming the container runtime it needs.

A node action expands like a composite one, at the workflow's own step or nested in one. Its main entry becomes one neutral run step whose command is the host's node invocation followed by the entry's path under the action's directory, each word quoted for a POSIX shell, because every host of this tool runs a step's command through one POSIX shell of its own. The mapped inputs ride in the step's environment as INPUT_<NAME>, spaces to underscores and upper-cased, which is the derivation the runner's own AddInputsToEnvironment applies, and GITHUB_ACTION_PATH is injected as for a composite action's commands. The node binary comes from an injected port (NodeRunner, a peer of ActionResolver) returning the command and its leading arguments, so the library names no binary and a host decides whether an entry runs under the node on PATH, a pinned install or an absolute path.

runs.pre and runs.post become run steps the caller places rather than steps in the list: pre before the job's own steps, post concatenated in expansion order and reversed after them, which is how the runner's own phases order them (pre steps join the job before its steps in JobExtension.cs, post steps pop off a stack in StepsRunner.cs). An enclosing composite action propagates a nested action's pre and post the same way, so a phase declared at any depth reaches the job's phases rather than the middle of a step list. The conditions default to the documented always() and evaluate through the base evaluator against the caller's context. A pre a local action declares is rejected with the pointer, because GitHub documents pre as not supported for local actions; a post needs no such rule.

The entry point is renamed expandAction with a richer return (ActionExpansion: steps, pre, post), and CompositeActionError becomes ActionExpansionError, since both now cover two forms of action. Nested handling changed with it: a nested node action now expands, where previously every non-composite nested action stayed a provider step, so only a docker:// reference, which resolves no tree to read, remains a provider step; metadata naming docker or an unknown interpreter is rejected at any depth, as the issue states.

Tests cover node20 and node16 fixtures and every runtime, the INPUT_ derivation including a name with spaces, pre and post with pre-if, the local-pre, docker, unknown-interpreter and malformed-document rejections by pointer, nested expansion carrying a post, and an end-to-end run: a real entry file executed through the expanded command with the host's node, writing GITHUB_OUTPUT.

Fixes #75

A node action's action.yml names a runtime under runs.using, an entry
file under runs.main, and optional entries around the job under runs.pre
and runs.post with their pre-if and post-if conditions. The schema
accepts the runtimes the runner itself names, node20, node16, node12 and
node24, and shares the declared inputs and branding with the composite
form so the two cannot drift apart. Anything else a runs.using can hold,
docker included, is left to the expansion to reject with the file and
pointer, because it names no execution this package performs.
A uses step naming a node action now expands like a composite one does.
Its main entry becomes one neutral run step whose command is the host's
node invocation followed by the entry's path, quoted word by word for a
POSIX shell, with the mapped inputs in the environment as INPUT_ names,
spaces to underscores and upper-cased the way the runner derives them,
and GITHUB_ACTION_PATH injected as for a composite action's commands.
The node binary itself comes from an injected port, so the library names
no binary and the host decides what runs the entry.

runs.pre and runs.post become run steps the caller places: pre before
the job's own steps, post concatenated in expansion order and reversed
after them, which is the order the runner's own phases use. A pre a
local action declares is rejected, as GitHub documents it unsupported;
metadata whose runs.using names neither a composite action nor a node
runtime is rejected with the file and pointer wherever the expansion
meets it, so only a docker:// reference, which resolves no tree, stays
a provider step. The entry point is renamed expandAction for the two
forms it now expands, and the error renamed ActionExpansionError to
match.
Fixtures hold node20 and node16 metadata with inputs, defaults and
hyphenated names, the INPUT_ derivation including a name with spaces,
pre and post with pre-if evaluated through the base evaluator, the
rejections for a local pre, an unknown interpreter and malformed node
metadata, and an end-to-end run: a real entry file executed through the
expanded command with the host's node, writing GITHUB_OUTPUT.
@Mearman

Mearman commented Oct 11, 2026

Copy link
Copy Markdown
Member Author

@codex security review

@Mearman
Mearman marked this pull request as ready for review October 11, 2026 19:20
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
🔒 Security Review ✅ Completed 2026-10-11T19:25:27.725349Z 9989af7 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@Mearman

Mearman commented Oct 11, 2026

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

Security review completed. No security issues were found in this pull request.

Reviewed commit: 9989af778d

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@Mearman
Mearman merged commit 4fad0af into main Oct 11, 2026
12 checks passed
@Mearman
Mearman deleted the feat/node-actions branch October 11, 2026 19:27
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 0.5.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Run node-based actions from a uses reference on the host

1 participant