From e9fc683c4831cb8c353e19fffb1ac89769e03d79 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 31 Mar 2026 15:35:08 +0000 Subject: [PATCH] ci: add GitHub Actions, GoReleaser, Dependabot, and root README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GitHub Actions: - .github/workflows/ci.yml: three separate jobs (test, build, lint) on every PR to main; uses golangci-lint-action@v6 for linting. Requires all three to pass before merge (configure branch protection manually: Settings โ†’ Branches โ†’ require status checks: Test, Build, Lint). - .github/workflows/release.yml: triggered on semver tags (v*.*.*); runs GoReleaser with full git history for changelog generation and pushes a Homebrew formula to oxGrad/homebrew-tap via HOMEBREW_TAP_GITHUB_TOKEN secret. Dependabot (.github/dependabot.yml): - Weekly updates for gomod dependencies (prefix: chore(deps)) - Weekly updates for GitHub Actions pins (prefix: chore(ci)) GoReleaser (.goreleaser.yaml): - Builds for linux/darwin/windows ร— amd64/arm64 (excluding windows/arm64) - tar.gz archives (zip for Windows) + checksums.txt - Pushes knot.rb formula to oxGrad/homebrew-tap on each release README.md: - Installation section leading with `brew install oxGrad/tap/knot` - Release binary download and `go install` alternatives - Full CLI reference for all 6 commands - Knotfile format reference table - Editor integration pointers - Releasing section documenting the semver tag workflow https://claude.ai/code/session_018s5iF8MKZAobUjAuR1AHJ4 --- .github/dependabot.yml | 22 ++++ .github/workflows/ci.yml | 41 +++++++ .github/workflows/release.yml | 31 +++++ .goreleaser.yaml | 54 +++++++++ README.md | 221 ++++++++++++++++++++++++++++++++++ 5 files changed, 369 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml create mode 100644 .goreleaser.yaml create mode 100644 README.md diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..77bfe99 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,22 @@ +version: 2 + +updates: + # Keep Go dependencies up to date + - package-ecosystem: gomod + directory: / + schedule: + interval: weekly + labels: + - dependencies + commit-message: + prefix: "chore(deps)" + + # Keep GitHub Actions up to date + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + labels: + - dependencies + commit-message: + prefix: "chore(ci)" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c675ff5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,41 @@ +name: CI + +on: + pull_request: + branches: [main] + +jobs: + test: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + - run: go test ./... + + build: + name: Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + - run: go build ./... + + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + - uses: golangci/golangci-lint-action@v6 + with: + version: latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a448a15 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,31 @@ +name: Release + +on: + push: + tags: + - "v[0-9]*.[0-9]*.[0-9]*" + +permissions: + contents: write + +jobs: + release: + name: Release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # GoReleaser needs full history for changelog + + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + + - uses: goreleaser/goreleaser-action@v6 + with: + version: latest + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..27bc844 --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,54 @@ +# yaml-language-server: $schema=https://goreleaser.com/static/schema.json +version: 2 + +before: + hooks: + - go mod tidy + +builds: + - main: . + binary: knot + env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + - windows + goarch: + - amd64 + - arm64 + ignore: + - goos: windows + goarch: arm64 + +archives: + - formats: [tar.gz] + name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" + format_overrides: + - goos: windows + formats: [zip] + +checksum: + name_template: checksums.txt + +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" + - "^chore:" + +brews: + - name: knot + repository: + owner: oxGrad + name: homebrew-tap + token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}" + homepage: "https://github.com/oxGrad/knot" + description: "A lightweight, configurable dotfiles manager" + license: MIT + install: | + bin.install "knot" + test: | + system "#{bin}/knot", "--help" diff --git a/README.md b/README.md new file mode 100644 index 0000000..a273056 --- /dev/null +++ b/README.md @@ -0,0 +1,221 @@ +# ๐Ÿชข Knot + +**A lightweight, configurable dotfiles manager.** + +[![CI](https://github.com/oxGrad/knot/actions/workflows/ci.yml/badge.svg)](https://github.com/oxGrad/knot/actions/workflows/ci.yml) +[![Release](https://github.com/oxGrad/knot/actions/workflows/release.yml/badge.svg)](https://github.com/oxGrad/knot/actions/workflows/release.yml) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) + +**Knot** is a CLI tool for managing your dotfiles. Like GNU Stow, it relies on symlinks to keep your configuration files centralized in a single repository. Unlike Stow, Knot is **fully configurable** โ€” you explicitly define where files go, ignore specific files, and apply OS-specific rules without being forced into a rigid directory structure. + +## โœจ Features + +- **Intuitive CLI:** Simple commands like `knot tie` and `knot untie` +- **Configurable Routing:** Map any file in your dotfiles repo to any location on your system +- **Ignore Rules:** Exclude specific files (e.g. `README.md`, `.DS_Store`) per package +- **OS Conditions:** Conditionally tie packages based on the operating system (macOS vs Linux) +- **Safe by Default:** `knot plan` previews every change before anything is written +- **Validation:** `knot validate` checks your Knotfile for errors before you run anything + +## ๐Ÿš€ Installation + +### Homebrew (recommended) + +```bash +brew install oxGrad/tap/knot +``` + +This installs a pre-built binary for macOS (Intel + Apple Silicon) and Linux via the +[oxGrad/homebrew-tap](https://github.com/oxGrad/homebrew-tap) tap. + +### Download a release binary + +Pre-built binaries for Linux, macOS, and Windows are available on the +[Releases](https://github.com/oxGrad/knot/releases) page. + +```bash +# Example for Linux amd64 +curl -L https://github.com/oxGrad/knot/releases/latest/download/knot_linux_amd64.tar.gz | tar xz +sudo mv knot /usr/local/bin/ +``` + +### Go install + +```bash +go install github.com/oxgrad/knot@latest +``` + +## โš™๏ธ Configuration (`Knotfile`) + +Create a file named exactly `Knotfile` (no extension) at the root of your dotfiles repository. Knot searches upward from the current directory to find it automatically. + +```yaml +packages: + # A simple 1-to-1 mapping + nvim: + target: ~/.config/nvim + source: ./nvim + ignore: + - "README.md" + - ".DS_Store" + + # Map files directly to the home directory + zsh: + target: ~/ + source: ./zsh + + # OS-specific package โ€” only tied on macOS + yabai: + target: ~/.config/yabai + source: ./yabai + condition: + os: darwin +``` + +### Knotfile fields + +| Field | Required | Description | +|---|---|---| +| `source` | โœ… | Path to source directory (relative to `Knotfile`, or absolute; `~` supported) | +| `target` | โœ… | Destination directory where symlinks are created (`~` supported) | +| `ignore` | โ€” | List of glob patterns matched against file basenames | +| `condition.os` | โ€” | Only tie on this OS (`darwin`, `linux`, `windows`, `freebsd`) | + +A [JSON Schema](schema/knotfile.schema.json) is available for editor validation and auto-complete โ€” see [Editor Integration](#editor-integration). + +## ๐Ÿ› ๏ธ CLI Reference + +``` +knot tie [package...] [--all] Create symlinks for one or more packages +knot untie [package...] Remove symlinks for one or more packages +knot status Show current symlink state for all packages +knot plan [package...] [--all] Dry-run: preview what tie would do +knot validate Validate the Knotfile for errors and warnings +``` + +Global flags available on every command: + +``` +--config string Path to Knotfile (default: auto-discover upward from cwd) +--dry-run Print actions without executing them +``` + +### `knot tie` + +Creates symlinks for the specified packages. Skips files that are already correctly linked. +Warns on conflicts (target exists but is not the expected symlink) without overwriting. + +```bash +knot tie nvim zsh # tie specific packages +knot tie --all # tie every package in the Knotfile +knot tie nvim --dry-run # preview without writing +``` + +### `knot untie` + +Removes symlinks previously created by `knot tie`. + +```bash +knot untie nvim +``` + +### `knot status` + +Shows the current state of every managed symlink: + +``` +[OK] ~/.config/nvim/init.lua +[MISSING] ~/.zshrc +[CONFLICT] ~/.config/nvim/lazy-lock.json: target exists and is not a symlink +``` + +### `knot plan` + +Dry-run that shows exactly what `tie` would do, with a summary line: + +``` + + ~/.config/nvim/init.lua -> /dotfiles/nvim/init.lua + = ~/.config/nvim/options.lua (already linked) + +Plan: 1 to create, 0 to remove, 1 already linked, 0 conflicts +``` + +### `knot validate` + +Validates the Knotfile without touching the filesystem: + +```bash +knot validate +# Validating Knotfile: /home/user/dotfiles/Knotfile +# +# ERROR [yabai]: source directory "/home/user/dotfiles/yabai" does not exist +# +# Validation failed: 1 error(s), 0 warning(s) +``` + +Exit codes: `0` = valid ยท `1` = errors ยท `2` = warnings only + +## ๐Ÿ–ฅ๏ธ Editor Integration + +### Neovim + +A full Neovim plugin lives in [`editors/neovim/`](editors/neovim/). It provides filetype detection, +syntax highlighting, Treesitter YAML override, ๐Ÿชข devicon, and automatic `yaml-language-server` +schema configuration. See [`editors/neovim/README.md`](editors/neovim/README.md) for installation +instructions (lazy.nvim, packer.nvim, and manual). + +### YAML Language Server (VS Code and others) + +Add the Knotfile schema to your editor's YAML LS settings: + +```json +{ + "yaml.schemas": { + "https://raw.githubusercontent.com/oxgrad/knot/main/schema/knotfile.schema.json": "**/Knotfile" + } +} +``` + +Or use an inline modeline as the first line of any `Knotfile`: + +```yaml +# yaml-language-server: $schema=https://raw.githubusercontent.com/oxgrad/knot/main/schema/knotfile.schema.json +packages: + ... +``` + +See [`editors/yaml-language-server/README.md`](editors/yaml-language-server/README.md) for all +integration methods. + +## ๐Ÿ“ฆ Releasing a new version + +Releases are fully automated via [GoReleaser](https://goreleaser.com). Push a semver tag to `main`: + +```bash +git tag v1.2.3 +git push origin v1.2.3 +``` + +This triggers the release workflow which: +1. Builds binaries for Linux, macOS (Intel + Apple Silicon), and Windows +2. Creates a GitHub Release with archives and `checksums.txt` +3. Pushes an updated `knot.rb` formula to [oxGrad/homebrew-tap](https://github.com/oxGrad/homebrew-tap) + +> **Prerequisite:** A `HOMEBREW_TAP_GITHUB_TOKEN` repository secret must be set โ€” a GitHub PAT +> with `contents: write` permission on the `oxGrad/homebrew-tap` repository. + +## ๐Ÿค Contributing + +Pull requests are welcome. The CI pipeline runs on every PR to `main`: + +| Check | Tool | +|---|---| +| Tests | `go test ./...` | +| Build | `go build ./...` | +| Lint | `golangci-lint` | + +All three checks must pass before a PR can be merged. + +## License + +[MIT](https://opensource.org/licenses/MIT)