npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

craneship

v4.0.0

Published

Build, tag and push docker images, with the tags derived from the git ref.

Readme


Renamed in v4. This project was action-docker-image-publish up to v3. GitHub redirects the old repository path, so uses: tada5hi/action-docker-image-publish@v3 keeps working, but new workflows should use tada5hi/craneship. On npm the old package name stays frozen at 3.4.0; v4 and later are published as craneship.

How it works

The action derives the image tags from the git ref it is run for, so a workflow does not have to compute them:

  • refs/tags/v1.2.3 → pushes 1.2.3 (the v is stripped), plus any explicit registryTag.
  • refs/tags/[email protected] with gitTagPrefix: some-package → pushes 1.2.3, so monorepo tags work.
  • refs/heads/master → pushes only the explicit registryTag values (default latest).

The image is built once under a local id derived from the ref, then tagged and pushed for each resulting reference. A ref which has already been built during the same job is not rebuilt.

Usage

-   uses: actions/checkout@v4

-   uses: tada5hi/craneship@v4
    with:
        registryTag: 'latest'

That is the whole configuration for the common case: every other input defaults to the right thing from the workflow context.

The action builds the checked out workspace, so actions/checkout has to run first. If a workflow cannot check out, set clone: 'true' and the repository is cloned at the ref instead:

-   uses: tada5hi/craneship@v4
    with:
        clone: 'true'

Since v4 this is a composite action rather than a Docker action, so it no longer builds a container image of itself before doing any work. It runs directly on the runner, using the docker and git CLIs that are already installed there.

Recipes

Publish latest on every push to master

name: Publish

on:
    push:
        branches: [master]

permissions:
    contents: read
    packages: write

jobs:
    publish:
        runs-on: ubuntu-latest
        steps:
            -   uses: actions/checkout@v4
            -   uses: tada5hi/craneship@v4

Nothing else is needed: registryHost defaults to ghcr.io, the credentials default to github.actor / github.token, and registryTag defaults to latest.

Publish a version tag and move latest

on:
    push:
        tags: ['v*']

jobs:
    publish:
        runs-on: ubuntu-latest
        steps:
            -   uses: actions/checkout@v4
            -   uses: tada5hi/craneship@v4
                with:
                    registryTag: 'latest'

For refs/tags/v1.2.3 this pushes both :1.2.3 (derived from the tag) and :latest. Leave registryTag empty to push only :1.2.3.

Publish several tags at once

-   uses: tada5hi/craneship@v4
    with:
        registryTag: |
            latest
            stable
            1

registryTag and buildArgs are newline separated; blank lines and surrounding whitespace are ignored.

Monorepo: one image per package

Tags like [email protected] are matched with gitTagPrefix, and the prefix is stripped from the resulting image tag:

-   uses: tada5hi/craneship@v4
    with:
        gitTagPrefix: 'some-package'
        dockerFilePath: 'packages/some-package'
        registryRepository: '${{ github.repository_owner }}/some-package'

refs/tags/[email protected] → ghcr.io/<owner>/some-package:1.2.3. A tag which does not start with gitTagPrefix is skipped, so each package's workflow only reacts to its own releases.

A Dockerfile somewhere other than the root

-   uses: tada5hi/craneship@v4
    with:
        dockerFilePath: 'services/api'
        dockerFileName: 'Dockerfile.production'

dockerFilePath is relative to the repository root and becomes the build context; dockerFileName is resolved inside it.

Build args

-   uses: tada5hi/craneship@v4
    with:
        buildArgs: |
            NODE_VERSION=24
            BUILD_ENV=production

Each line becomes a --build-arg. Values are passed to docker as separate arguments without a shell, so spaces and metacharacters need no quoting.

Publish to Docker Hub instead of GHCR

-   uses: tada5hi/craneship@v4
    with:
        registryHost: 'docker.io'
        registryUser: ${{ secrets.DOCKERHUB_USERNAME }}
        registryPassword: ${{ secrets.DOCKERHUB_TOKEN }}
        registryRepository: 'some-org/some-image'

Publish by digest

A registryTag starting with sha is attached as a digest rather than a tag:

-   uses: tada5hi/craneship@v4
    with:
        registryTag: 'sha256:${{ github.sha }}'

→ ghcr.io/<owner>/<repo>@sha256:<sha>

Ignore git tags entirely

-   uses: tada5hi/craneship@v4
    with:
        gitTag: 'false'
        registryTag: 'edge'

With gitTag: 'false' a tag ref no longer contributes an image tag, so only registryTag is used.

Keep the built image for later steps

-   uses: tada5hi/craneship@v4
    with:
        cleanup: 'false'

-   run: docker images

By default the locally built image is removed once every tag has been pushed.

Clone a private repository

Only relevant together with clone: 'true', since the credentials are used for the clone and nothing else:

-   uses: tada5hi/craneship@v4
    with:
        clone: 'true'
        gitUser: ${{ github.actor }}
        gitPassword: ${{ secrets.SOME_PAT }}

Both default to github.actor / github.token, which is enough for the repository the workflow runs in. Override them to clone with a PAT. The credentials are percent-encoded into the clone url, so tokens containing @, : or / work.

Reuse the runner's Node.js

-   uses: actions/setup-node@v4
    with:
        node-version: 22

-   uses: tada5hi/craneship@v4
    with:
        node-version: ''

An empty node-version skips the action's own setup-node step.

Inputs

| Input | Description | Default | |----------------------|-------------------------------------------------------------------|----------------------------| | dockerFileName | Name of the Dockerfile | Dockerfile | | dockerFilePath | Relative path to the Dockerfile | '' | | buildArgs | Additional build args (KEY=VALUE), one per line | '' | | cleanup | Delete the built image at the end | true | | clone | Clone the repo at the ref instead of building the workspace | false | | gitTag | Create an image tag for a git tag | true | | gitTagPrefix | Only match git tags with this prefix | '' | | gitUser | User for cloning the repository | ${{ github.actor }} | | gitPassword | Password/token for cloning the repository | ${{ github.token }} | | registryHost | Registry host | ghcr.io | | registryUser | Registry user | ${{ github.actor }} | | registryPassword | Registry password | ${{ github.token }} | | registryRepository | Registry repository full name (e.g. project/repository) | ${{ github.repository }} | | registryTag | Registry image tags (e.g. latest), one per line | latest | | node-version | Node.js version to set up; empty string uses the existing install | 24 | | token | Deprecated and unused | '' |

Tagging

A tag is normalised before it becomes an image tag:

| Git ref | gitTagPrefix | Image tag | |--------------------------------------|----------------|-----------| | refs/tags/v1.2.3 | '' | 1.2.3 | | refs/tags/1.2.3 | '' | 1.2.3 | | refs/tags/[email protected] | some-package | 1.2.3 | | refs/tags/[email protected] | some-package | 1.2.3 | | refs/tags/vNext | '' | vNext |

The v prefix is only stripped when what follows is a valid semver version, so a non-version tag like vNext is left alone. A tag which does not start with gitTagPrefix is skipped entirely.

A registryTag starting with sha is attached as a digest (image@sha256:...) instead of a tag.

CLI

The same logic is available as a CLI, so it can run outside GitHub Actions:

npx craneship \
    --clone \
    --ref refs/tags/v1.2.3 \
    --repository tada5hi/some-repo \
    --registryHost ghcr.io \
    --registryUser tada5hi \
    --registryPassword "$TOKEN" \
    --registryRepository tada5hi/some-repo \
    --registryTag latest

Every flag defaults to the corresponding GITHUB_* environment variable (GITHUB_REF, GITHUB_REPOSITORY, GITHUB_ACTOR, GITHUB_TOKEN, …), so inside a workflow no flags are required. Boolean flags are negated with --no- (e.g. --no-cleanup, --no-gitTag).

--clone is usually what you want outside a workflow: without it the CLI builds the current working directory, with it the repository is cloned at --ref into .output and built from there.

Run craneship --help for the full list.

Programmatic API

The package is also a library. Every side effect sits behind an interface with an in-memory implementation, so publish() runs without a docker daemon:

import {
    DockerCLIClient,
    GitCLIClient,
    ConsolaLogger,
    NodeCommandRunner,
    publish,
} from 'craneship';

const runner = new NodeCommandRunner();

const result = await publish(options, {
    docker: new DockerCLIClient(runner),
    git: new GitCLIClient(runner),
    logger: new ConsolaLogger(),
});

console.log(result.images); // ['ghcr.io/tada5hi/some-repo:1.2.3']

Swap in MemoryDockerClient / MemoryGitClient / NoopLogger to exercise it in tests.

Requirements

  • Node.js >= 22.0.0
  • The docker and git CLIs on PATH (both are preinstalled on GitHub runners)

License

Made with 💚

Published under MIT License.