A batteries-included template for scaffolding new GitHub Actions.
A starting point for ESM, bundled GitHub Actions — ships with action.yml, an
example handler, Jest (with manual @actions/* mocks), ESLint/Prettier,
Changesets, Renovate, and issue/PR templates, plus a one-shot setup CLI
that wires it all to your action.
- Click Use this template on GitHub and create your repository.
yarn installyarn rename— an interactive CLI that collects the action name, description, owner, author, and your input/output variables, then rewritesaction.yml,package.json, the README, and everything else before removing itself (the entirescripts/directory).- Pass
--dry-runto preview every change without writing anything. - Supply
--name,--description,--owner, and/or--authorto skip the matching prompts.
- Pass
- Edit
src/main.jsto implement your action's logic.yarn buildbundlessrc/locally so you can smoke-test it, but the bundle is never committed — CI only confirms it still compiles (see Architecture below). - Run
yarn changesetto record any release-worthy change, then commit and push — Changesets handles versioning, the changelog, and tagging the release. The release workflow buildsdist/and commits it only to the release tag, somainstays source-only.
- Replaces the
{{ ACTION_NAME }}/{{ ACTION_DESCRIPTION }}/{{ ACTION_AUTHOR }}/{{ ACTION_AUTHOR_EMAIL }}/{{ OWNER }}placeholders across the repository. - Generates the
action.ymlinputs/outputs and the README tables from the variables you define. - Installs the action-only
package.json,jest.config.js, andREADME.mdfromscripts/template/over the repository's own copies. - Detects sensible defaults: the owner from the git remote, and the author from
gh, the GitHub Actions context, or your commit email.
The scaffolded action separates hand-written source from the generated
bundle, and never commits the bundle to main:
src/
index.js # entry shell: reads the `token` input, builds the octokit
# client, calls main(), and maps a thrown error to
# core.setFailed()
main.js # the action's logic
main.test.js # jest coverage for main.js
dist/
index.mjs # generated by `yarn build` (@vercel/ncc) — the
# dependency-free bundle action.yml actually runs.
# Gitignored: built fresh at release time, not committed.
GitHub runs uses: owner/repo@vX by checking out that ref and executing
dist/index.mjs directly, with no install step — so every consumer needs a
dependency-free bundle at that ref. Rather than committing dist/ to
main (which would put generated, node_modules-derived code in every PR
diff and dependency bump), the generated repo's release workflow builds
dist/ only when it publishes a release, commits it to a throwaway
commit reachable solely from the release tag (vX.Y.Z and the floating
vX), and pushes just that tag — main never sees it. The same bundle also
ships in the npm tarball via package.json's files allowlist, which
applies independently of .gitignore.
PR CI (linting.yml) still runs yarn build on every PR so a broken bundle
is caught before it can reach a release, without needing to diff a committed
artifact.
The bundle is ESM (dist/index.mjs, built with @vercel/ncc) because the
package is "type": "module" — the entry source and configs are ESM, and
ncc's ESM output correctly wires createRequire for the Node built-ins the
bundled dependencies need. The .mjs extension matters: it forces ESM
regardless of the nearest package.json's "type" field, so the bundle
doesn't depend on (or get invalidated by) ncc's auxiliary
dist/package.json, which the build step deletes.
yarn format # eslint
yarn lint # eslint
yarn test # jest
yarn coverage # jest with the 80% coverage thresholdBuilt and maintained by Allons-y Studio — a US-based studio specializing in design systems, front-end architecture, and accessibility.