Skip to content
Dev Tools Beginner Tutorial

Open Source Your Side Project: License, README, Automated Releases

Turn a private script into a public repo with an MIT license and semantic-release-driven versioning.

Lenn Voss
Lenn Voss
Cloud & Infrastructure Writer · Sep 3, 2026 · 4 min read
Open Source Your Side Project: License, README, Automated Releases

What you'll build

You'll take a script that lives on your laptop and turn it into a public GitHub repo with an MIT license, a README, a contribution guide, and a CI workflow that reads your commit messages and publishes versioned GitHub Releases on its own. No manual tagging, ever.

Prerequisites

  • A GitHub account and git installed.
  • GitHub CLI 2.x, authenticated (gh auth login).
  • macOS or Linux shell. On Windows, use WSL or Git Bash.
  • No Node.js needed locally. CI installs it; semantic-release requires Node ≥ 22.14.0 there, and the workflow below handles that.

Verified against semantic-release 25.0.9, actions/checkout v7, actions/setup-node v7, and GitHub CLI 2.x in September 2026.

1. Start from your script

Substitute your own project; this stand-in makes the tutorial runnable end to end:

mkdir tidy-downloads && cd tidy-downloads
git init -b main

cat > tidy-downloads.sh <<'EOF'
#!/usr/bin/env bash
# Move files older than 30 days from ~/Downloads into ~/Downloads/archive
mkdir -p ~/Downloads/archive
find ~/Downloads -maxdepth 1 -type f -mtime +30 -exec mv {} ~/Downloads/archive/ \;
EOF
chmod +x tidy-downloads.sh

2. Add a license

A repo with no license is "all rights reserved" by default, so nobody can legally use, copy, or modify your code even though it's public. MIT is the standard permissive choice; compare options at choosealicense.com if you want copyleft instead.

Pull the official MIT text straight from GitHub's API:

gh api licenses/mit --jq .body > LICENSE

Open LICENSE and replace the [year] and [fullname] placeholders with the current year and your name.

3. Write the README and contribution guide

The README answers three questions in order: what is this, how do I install it, how do I run it.

cat > README.md <<'EOF'
# tidy-downloads

Moves files older than 30 days out of ~/Downloads into an archive folder.

## Install

    curl -fsSLO https://raw.githubusercontent.com/YOUR_USER/tidy-downloads/main/tidy-downloads.sh
    chmod +x tidy-downloads.sh

## Usage

    ./tidy-downloads.sh

## License

MIT
EOF

The contribution guide is where the release automation gets its rules. semantic-release decides version numbers from commit messages in the Conventional Commits format, so contributors have to follow it:

cat > CONTRIBUTING.md <<'EOF'
# Contributing

Commit messages must follow Conventional Commits, because releases
are cut automatically from them:

- `fix: ...` -> patch release (1.0.1)
- `feat: ...` -> minor release (1.1.0)
- `feat!: ...` or a `BREAKING CHANGE:` footer -> major release (2.0.0)
- `docs:`, `chore:`, `ci:` -> no release

Open an issue before large changes.
EOF

4. Configure semantic-release

By default semantic-release also tries to publish to npm. This project isn't an npm package, so list only the three plugins you need:

cat > .releaserc.json <<'EOF'
{
  "branches": ["main"],
  "plugins": [
    "@semantic-release/commit-analyzer",
    "@semantic-release/release-notes-generator",
    "@semantic-release/github"
  ]
}
EOF

Analyzer reads the commits, notes generator writes the changelog text, and the GitHub plugin creates the tag and release. Dropping @semantic-release/npm also means you don't need an npm token or trusted-publishing setup.

5. Add the release workflow

mkdir -p .github/workflows
cat > .github/workflows/release.yml <<'EOF'
name: Release
on:
  push:
    branches: [main]

permissions:
  contents: read

jobs:
  release:
    runs-on: ubuntu-latest
    permissions:
      contents: write        # push the version tag and create the release
      issues: write          # comment on issues closed by a release
      pull-requests: write
    steps:
      - uses: actions/checkout@v7
        with:
          fetch-depth: 0     # full history, so the last release tag is visible
      - uses: actions/setup-node@v7
        with:
          node-version: "lts/*"
      - name: Release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: npx semantic-release@25

GITHUB_TOKEN is injected by Actions automatically. You create no secrets at all.

6. Commit and publish

The commit type matters here: chore: initial commit would produce no release, while feat: triggers 1.0.0.

git add .
git commit -m "feat: initial release"
gh repo create tidy-downloads --public --source=. --push

The push kicks off the workflow immediately.

Verify it works

Watch the run finish, then list releases:

gh run watch
gh release list

Expected output:

TITLE   TYPE    TAG NAME  PUBLISHED
v1.0.0  Latest  v1.0.0    about 1 minute ago

In the Actions log you'll see the confirmation line Published release 1.0.0 on default channel, and the repo's Releases page now shows v1.0.0 with generated notes. From here on, push a fix: commit and CI cuts v1.0.1 with no action from you.

Troubleshooting

There are no relevant changes, so no new version is released. Your commits don't match Conventional Commits, or they're all non-releasing types like chore:. Amend the message (git commit --amend -m "fix: ...") or push a new fix:/feat: commit.

ENOGHTOKEN No GitHub token specified. The env: block with GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} is missing from the release step, or a typo renamed the variable. semantic-release only reads GH_TOKEN or GITHUB_TOKEN.

EGITNOPERMISSION Cannot push to the Git repository. The job can't push the version tag. Usually the contents: write permission is missing from the workflow, or a branch protection ruleset blocks the github-actions bot. Add the permission, or exempt the bot in the ruleset's bypass list.

Next steps

Add @semantic-release/changelog and @semantic-release/git to also maintain a CHANGELOG.md in the repo, or @semantic-release/npm with trusted publishing once you're ready to ship to a registry. Enforce commit format locally with commitlint, and add issue templates plus a CODE_OF_CONDUCT.md when contributors show up.

Sources & further reading

  1. Using semantic-release with GitHub Actions — semantic-release.gitbook.io
  2. semantic-release Configuration — semantic-release.gitbook.io
  3. Conventional Commits 1.0.0 — conventionalcommits.org
  4. gh repo create — cli.github.com
  5. actions/checkout releases — github.com
  6. actions/setup-node — github.com
Lenn Voss
Written by
Lenn Voss · Cloud & Infrastructure Writer

Lenn writes about cloud platforms, Kubernetes internals, and the infrastructure decisions that quietly make or break engineering organizations. Based in Berlin's vibrant tech scene, they have a talent for turning dense platform-engineering topics into prose that people actually finish reading.

Discussion 3

Join the discussion

Sign in or create an account to comment and vote.

Yuki Tanaka @distsys_yuki · 7 hours ago

semantic-release is solid for javascript projects, but how do you handle the case where your tool isn't a node package—like a go binary or python script? do you just vendor the release logic into your workflow, or is there a cleaner pattern you're using in practice?

Dana Reyes @hypewatch_dana · 11 hours ago

sounds nice in theory, but i burned a week once debugging semantic-release on a monorepo where conventional commits weren't enforced upstream — devs just... weren't following the format, and nothing published for three sprints. the automation only works if you actually have the discipline around it. how do you keep the commit message format from drifting once the novelty wears off?

Priya Nair @k8s_whisperer · 1 day ago

the semantic-release automation piece definitely saves headaches—we went through a few manual release cycles on one of our internal tools before wiring this up, and the number of times someone forgot to bump the patch version or tag inconsistently was embarrassing. the real win though is just not having to think about it once the workflow is running. makes handing off maintenance way less painful.

Related Reading