Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
* @PKramek
.github/ @PKramek
src/ @PKramek
11 changes: 11 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
target-branch: "develop"
groups:
github-actions:
patterns:
- "*"
44 changes: 44 additions & 0 deletions .github/workflows/advance-main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: "Advance main"

on:
workflow_dispatch:

concurrency:
group: advance-main
cancel-in-progress: false

permissions: {}

jobs:
advance:
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: write
steps:
- name: Fast-forward main to develop
# Requires RepositoryRole admin bypass actor on the protect-main ruleset.
# Without it, GITHUB_TOKEN cannot update main directly and this step fails.
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
run: |
DEVELOP_SHA=$(gh api repos/"$REPO"/git/ref/heads/develop --jq '.object.sha')
# force=false ensures this is always a true fast-forward; rejects if main has diverged
gh api repos/"$REPO"/git/refs/heads/main \
--method PATCH \
--field sha="$DEVELOP_SHA" \
--field force=false

- name: Verify main matches develop
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
run: |
MAIN_SHA=$(gh api repos/"$REPO"/git/ref/heads/main --jq '.object.sha')
DEVELOP_SHA=$(gh api repos/"$REPO"/git/ref/heads/develop --jq '.object.sha')
if [[ "$MAIN_SHA" != "$DEVELOP_SHA" ]]; then
echo "ERROR: main ($MAIN_SHA) does not match develop ($DEVELOP_SHA) after advance"
exit 1
fi
echo "main and develop are in sync at $MAIN_SHA"
24 changes: 24 additions & 0 deletions .github/workflows/source-branch-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: "Source branch check"

on:
pull_request:
branches: [main]

permissions: {}

jobs:
check-source:
runs-on: ubuntu-latest
timeout-minutes: 5
permissions: {}
steps:
- name: Verify PR comes from develop or hotfix/*
env:
HEAD_REF: ${{ github.head_ref }}
run: |
if [[ "$HEAD_REF" != "develop" && ! "$HEAD_REF" =~ ^hotfix/ ]]; then
echo "ERROR: PRs to main must come from 'develop' or 'hotfix/*' branches."

Check warning on line 20 in .github/workflows/source-branch-check.yml

View workflow job for this annotation

GitHub Actions / lint

20:81 [line-length] line too long (86 > 80 characters)
echo "Source branch: $HEAD_REF"
exit 1
fi
echo "Source branch check passed: $HEAD_REF"
113 changes: 113 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Contributing

## Development setup

1. Fork and clone
2. Install the git hooks (one-time setup):

```bash
pip install pre-commit
pre-commit install # runs on git commit
pre-commit install --hook-type pre-push # runs on git push
```

If you open the repo in the devcontainer, this runs automatically.

3. Changes live in `src/claude-code/install.sh` and `test/claude-code/`
4. Open a pull request against `develop`

## Branching model

| Branch pattern | Purpose | PR target |
| -------------- | ------------------------- | --------- |
| `feat/*` | New features | `develop` |
| `fix/*` | Bug fixes | `develop` |
| `hotfix/*` | Critical production fixes | `main` |

- All feature and fix PRs target `develop`. Use squash merges to keep history clean.
- `hotfix/*` branches are the only branches (besides `develop`) allowed to open PRs
directly to `main`. This is enforced by CI (`source-branch-check.yml`).
- Direct commits to `main` and `develop` are blocked by pre-commit hooks and branch
protection rules.

## Pre-commit hooks

The following checks run automatically on every `git commit` and `git push`:

| Hook | What it checks |
| --------------------- | --------------------------------------------- |
| `shellcheck` | Shell script correctness (warnings and above) |
| `shfmt` | Shell script formatting (`-i 4 -ci`) |
| `prettier` | JSON, YAML, and Markdown formatting |
| `markdownlint` | Markdown style rules |
| `check-json` | JSON syntax validity |
| `check-yaml` | YAML syntax validity |
| `trailing-whitespace` | No trailing whitespace |
| `detect-private-key` | No accidentally committed secrets |
| `no-commit-to-branch` | Blocks direct commits to `main` and `develop` |

**Running manually:**

```bash
pre-commit run --all-files # check everything
pre-commit run prettier # check one hook
pre-commit run --files src/claude-code/install.sh # check one file
```

**If a hook fails:** fix the flagged issue and `git add` the changes before retrying.
Prettier and shfmt auto-fix in place — just stage the result. ShellCheck and markdownlint
report what to fix but won't rewrite your code.

## Maintainers

### Advancing main

When `develop` is ready for release, use the **Advance main** workflow:

1. Go to **Actions** → **Advance main** → **Run workflow**
2. The workflow fast-forwards `main` to the current tip of `develop` via the GitHub API
(no merge commit is created)
3. It verifies that `main` and `develop` point to the same SHA before completing

### Releasing a version

1. Update the `version` field in `src/claude-code/devcontainer-feature.json` as part of
the work merged to `develop` — this must be done **before** tagging, as the release
workflow validates that the tag version matches the JSON
2. Advance `main` using the workflow above
3. Tag the release from the updated `main`:

```bash
git checkout main
git pull origin main
git tag v<VERSION>
git push origin v<VERSION>
```

4. The `v*` tag push triggers the **Release** workflow, which:
- Runs ShellCheck validation
- Smoke-tests against 3 representative base images
- Verifies the tag version matches `devcontainer-feature.json`
- Publishes the feature to `ghcr.io/pkramek/claude-devcontainer/claude-code`
- Verifies the published feature is accessible

Tags are protected — no deletion or force-push.

### Hotfix workflow

For critical fixes that cannot wait for the normal `develop` cycle:

1. Create a `hotfix/*` branch from `main`
2. Make the fix and open a PR targeting `main` — the source-branch-check CI allows
`hotfix/*` to bypass the `develop`-only gate
3. After the hotfix merges to `main`, tag and release as described above
4. Open a second PR from `hotfix/*` (or from `main`) to `develop` to keep the branches
in sync

### First release: GHCR visibility

After the first tag push, the GHCR package is created as **private**. To make it public:

1. Go to the repository's **Packages** tab
2. Click the `claude-code` package → **Package settings**
3. Under **Danger Zone**, change visibility to **Public**
51 changes: 3 additions & 48 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,55 +154,10 @@ before a musl-compatible build is published. Check the CI badge before upgrading

## Contributing

1. Fork and clone
2. Install the git hooks (one-time setup):

```bash
pip install pre-commit
pre-commit install # runs on git commit
pre-commit install --hook-type pre-push # runs on git push
```

If you open the repo in the devcontainer, this runs automatically.

3. Changes live in `src/claude-code/install.sh` and `test/claude-code/`
4. Open a pull request against `develop`

### Pre-commit hooks

The following checks run automatically on every `git commit` and `git push`:

| Hook | What it checks |
| --------------------- | --------------------------------------------- |
| `shellcheck` | Shell script correctness (warnings and above) |
| `shfmt` | Shell script formatting (`-i 4 -ci`) |
| `prettier` | JSON, YAML, and Markdown formatting |
| `markdownlint` | Markdown style rules |
| `check-json` | JSON syntax validity |
| `check-yaml` | YAML syntax validity |
| `trailing-whitespace` | No trailing whitespace |
| `detect-private-key` | No accidentally committed secrets |
| `no-commit-to-branch` | Blocks direct commits to `main` and `develop` |

**Running manually:**

```bash
pre-commit run --all-files # check everything
pre-commit run prettier # check one hook
pre-commit run --files src/claude-code/install.sh # check one file
```

**If a hook fails:** fix the flagged issue and `git add` the changes before retrying. Prettier and
shfmt auto-fix in place — just stage the result. ShellCheck and markdownlint report what to fix but
won't rewrite your code.

## Publishing (Maintainers)

After the first release tag push, the GHCR package is created as **private**. To make it public:
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, branching model, pre-commit hooks,
and the release process.

1. Go to the repository's **Packages** tab
2. Click the `claude-code` package → **Package settings**
3. Under **Danger Zone**, change visibility to **Public**
Short version: fork, clone, install pre-commit hooks, open a PR against `develop`.

## License

Expand Down
Loading