GitHub Action
Inputs, outputs, job summary, and failure behavior of SteadyLink-io/upload-action, which uploads or replaces files from a GitHub workflow.
On this page
SteadyLink-io/upload-action uploads build output to SteadyLink from a GitHub Actions workflow, or replaces an existing file so its published link serves the new build. Use it for release downloads, documentation PDFs, and media that other sites link to. This page lists every input and output and explains exactly how files are matched and when the step fails.
The action is a composite action that runs @steadylink/cli through npx on the runner's Node.js (18 or later). It works on Linux, macOS, and Windows runners.
Upload files#
on:
release:
types: [published]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build
- uses: SteadyLink-io/upload-action@v1
id: steadylink
with:
api-key: ${{ secrets.STEADYLINK_API_KEY }}
bucket: releases
folder: ${{ github.ref_name }}
files: |
dist/*.zip
dist/*.dmg
visibility: public
- run: echo "Download: ${{ steps.steadylink.outputs.link }}"Store the key as a repository or environment secret. It needs the assets:read and assets:write scopes. Keys are created in Dashboard > Developers.
Replace a file in place#
Set replace to publish the build as a new revision of an existing file. The asset ID stays the same, so every existing link, embed, and QR code serves the new version. This is the way to keep a permanent "latest" download link.
- uses: SteadyLink-io/upload-action@v1
with:
api-key: ${{ secrets.STEADYLINK_API_KEY }}
replace: releases:downloads/app-latest.dmg # or an asset ID, or https://cdn.steadylink.io/a/{id}
files: build/App.dmgIn replace mode, files must match exactly one file. bucket, folder, and visibility are ignored.
Inputs#
Inputs
api-keystringRequired- SteadyLink API key with
assets:write. Passed to the CLI asSTEADYLINK_API_KEY. filesstringRequired- Files, folders, or globs, one per line or separated by spaces. See How files are matched.
bucketstring- Destination bucket ID, slug, or name. Required for uploads, ignored for replacements.
folderstring- Folder inside the bucket, for example
releases/v1.2. Empty means the bucket root. visibilitystringpublicorprivate(case-insensitive). Empty inherits the bucket default. Any other value fails the step.replacestring- Replace this existing file instead of uploading: an asset ID, a delivery link, or
bucket:path/to/file. api-urlstringDefaulthttps://api.steadylink.io- API origin.
cdn-urlstring- Delivery origin used for the returned links, for example a custom delivery domain. Empty uses
https://cdn.steadylink.io. cli-versionstringDefault0.2.0- Version of
@steadylink/clito run. working-directorystringDefault.- Directory that relative paths and globs resolve against.
Outputs#
Outputs
linkstring- Link of the first file that has one. Empty when no file produced a link.
linksJSON string- Array of every link, in upload order. Files without a link are left out. Read one with
fromJSON(steps.steadylink.outputs.links)[1]. resultsJSON string- Array with one object per file, described below.
Each entry in results:
| Field | Upload | Replace |
|---|---|---|
file | Key in the bucket, for example v1.4.0/app.zip | Key of the replaced file |
url | Stable link, or null | Stable link, or null |
assetId | Asset ID, or null | Asset ID, or null |
status | ready, scanning, committing, blocked, failed, or cancelled | replaced |
error | Error message, only for failed files | Not present |
version | Not present | New revision number |
[
{ "file": "v1.4.0/app.zip", "url": "https://cdn.steadylink.io/a/c81f0a6d-5e2b-4d7c-9a3e-1b6f8d2c4e07", "assetId": "c81f0a6d-5e2b-4d7c-9a3e-1b6f8d2c4e07", "status": "ready" },
{ "file": "v1.4.0/app.dmg", "url": "https://cdn.steadylink.io/a/9d4e2b7a-1c3f-4a8e-b6d0-7f2c5e1a9b38", "assetId": "9d4e2b7a-1c3f-4a8e-b6d0-7f2c5e1a9b38", "status": "ready" }
]Use the outputs in later steps, for example to post the link in a release note:
- run: gh release edit "$TAG" --notes "Download: ${{ steps.steadylink.outputs.link }}"
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ github.ref_name }}How files are matched#
The files input is split on new lines and spaces, so a path that contains a space cannot be given directly; match it with a glob such as dist/My?App.dmg instead. Each entry is then handled one of two ways:
- A plain path (no
*or?) is passed to the CLI as is. It must exist, or the step fails withNo such file or directory. A folder is uploaded recursively and keeps its structure below the folder:distputsdist/css/app.cssat{folder}/css/app.css. - A glob is matched against every file under
working-directory, skipping.gitandnode_modulesfolders.*matches within one path segment,**matches across segments (docs/**/*.pdfincludesdocs/a.pdfanddocs/x/y/b.pdf), and?matches one character. A glob that matches nothing fails the step withNo files match {pattern}.
Duplicate matches across entries are uploaded once.
Job summary#
When at least one file was processed, the action appends a table to the job summary:
### SteadyLink
| File | Status | Link |
| --- | --- | --- |
| `v1.4.0/app.zip` | ready | https://cdn.steadylink.io/a/c81f0a6d-... |
| `v1.4.0/app.dmg` | failed | Storage rejected the upload (HTTP 403) |Replacements show the new revision, for example replaced (v4). The step log also prints the CLI command it ran (without the key) and one file -> link line per file.
Failure behavior#
The step fails, and the workflow stops unless you set continue-on-error, when:
api-keyis empty,filesmatches nothing, a plain path does not exist,bucketis missing for an upload, orvisibilityis notpublicorprivate.replaceis set andfilesmatched zero or several files.- The CLI reports an error, such as an invalid key, a missing scope, an unknown bucket, or a file that failed the malware scan during a replacement. The error message is shown as a workflow annotation titled SteadyLink.
- Any file in an upload failed. The other files are still uploaded, and
link,links,results, and the job summary are still written, so a later step withif: always()can read them.
Uploads and replacements are not retried by the action. Re-running the job uploads the same keys again, which adds new revisions to those files rather than creating duplicates.
Security#
- Pass the key only through
secrets. It reaches the CLI as an environment variable and is never printed. - The CLI's config file is written under
RUNNER_TEMP, so the action never reads or changes a saved login on a self-hosted runner. - Pin
cli-version(the default is0.2.0) and the action's major version so a release cannot change behavior unexpectedly. - Give the key only
assets:readandassets:write, and consider a separate key per repository so you can revoke one without affecting others.