Skip to content

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#

.github/workflows/release.yml
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.

YAML
- 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.dmg

In 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 as STEADYLINK_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.
visibilitystring
public or private (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-urlstringDefault https://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-versionstringDefault 0.2.0
Version of @steadylink/cli to 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:

FieldUploadReplace
fileKey in the bucket, for example v1.4.0/app.zipKey of the replaced file
urlStable link, or nullStable link, or null
assetIdAsset ID, or nullAsset ID, or null
statusready, scanning, committing, blocked, failed, or cancelledreplaced
errorError message, only for failed filesNot present
versionNot presentNew revision number
results
[
  { "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:

YAML
- 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 with No such file or directory. A folder is uploaded recursively and keeps its structure below the folder: dist puts dist/css/app.css at {folder}/css/app.css.
  • A glob is matched against every file under working-directory, skipping .git and node_modules folders. * matches within one path segment, ** matches across segments (docs/**/*.pdf includes docs/a.pdf and docs/x/y/b.pdf), and ? matches one character. A glob that matches nothing fails the step with No 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:

Text
### 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-key is empty, files matches nothing, a plain path does not exist, bucket is missing for an upload, or visibility is not public or private.
  • replace is set and files matched 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 with if: 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 is 0.2.0) and the action's major version so a release cannot change behavior unexpectedly.
  • Give the key only assets:read and assets:write, and consider a separate key per repository so you can revoke one without affecting others.

Next steps#