Thanks for considering a contribution. nodeup is a small Go CLI with a
single maintainer and a strict, automated workflow — read this once before
opening an issue or a PR.
- Language / runtime: Go 1.24+
- Architecture: see the
How it workssection inREADME.mdfor the high-level pipeline. The single source of truth for implementation details is the Go source underinternal/plus inline godoc. - Branching: always branch from
main.mainis protected — every change ships via PR and is squash-merged with the source branch deleted. - Releases: tag-driven. Pushing a
v*.*.*tag tooriginfires the GoReleaser workflow (.github/workflows/release.yml→ Homebrew tap, Scoop bucket, npm wrapper).
Branch names use one of:
| Prefix | Use for |
|---|---|
feat/<scope>/<slug> |
New user-facing feature |
fix/<scope>/<slug> |
Bug fix |
chore/<scope>/<slug> |
Maintenance, deps, refactors with no behavior change |
docs/<slug> |
Documentation only |
ci/<slug> |
CI/CD only |
test/<scope>/<slug> |
Tests only |
refactor/<scope>/<slug> |
Refactor only |
<scope> is one of the commitlint scopes (see below). Examples:
feat/check/dist-index-fetcher, fix/detector/nvm-windows-registry,
docs/clarify-installation, ci/bump-golangci-lint.
Enforced by commitlint
in CI (wagoid/commitlint-github-action@v5). Local check: npx commitlint --edit.
Allowed types: feat, fix, docs, style, refactor, perf,
test, build, ci, chore, revert.
Allowed scopes: detector, manager, packages, node, config,
ui, platform, cli, deps, release, ci, docs, lint.
Breaking changes: append ! after the type/scope and add a
BREAKING CHANGE: footer in the commit body, e.g.:
feat(config)!: drop legacy manager=auto key
BREAKING CHANGE: config.manager=auto is no longer recognized. Use
config.manager=<name> explicitly, or omit the key for auto-detect.
Format:
<type>(<scope>): <subject> # imperative mood, <= 100 chars, no trailing period
<blank line>
<body wrapped at 100 cols> # explain *what* and *why*, not how
<blank line>
<footer> # BREAKING CHANGE: …, Refs #…, Signed-off-by: …
- One PR per logical change. Don't bundle unrelated fixes.
- Title = first commit subject. PR title goes through the same commitlint rules.
- Fill in
.github/PULL_REQUEST_TEMPLATE.md. The Type-of-change checklist maps to the SemVer bump. - CI must be green. Same 9-check matrix as
main:- 5 builds (linux/darwin × amd64/arm64 + windows/amd64)
- 1 lint (golangci-lint + commitlint)
- 3 OS tests (ubuntu/macos/windows)
- Squash-merge with the source branch deleted — same as PRs #1–#10.
- No force-pushes after review. Force-pushes during authoring are
fine (use
--force-with-lease). - Every PR must reference its source issue in the body — use
Closes #N(closing the issue on merge) orRefs #N(linked but not auto-closed). The.claude/skills/issue-workflowskill fills this in automatically from the issue number when the AI generates the PR body; do not strip it. A PR without an issue reference will be sent back for revision.
# All CI checks locally
make ci
# Just the Go bits
make build
make test
make lint
# One package
go test ./internal/detector/...
# One test (regex matched against func names)
go test ./internal/detector/... -run TestNvmmake ci runs tidy fmt vet lint test (in that order). The fmt
step rewrites Go files in place (gofmt -s -w . + goimports -w .),
so run it on a clean tree or be ready to commit the rewrites — a
contributor who only read the previous version of this paragraph
wouldn't expect make ci to mutate their working tree. There is
no separate "docs regen" step — docs are pure markdown and updated
by hand in PRs.
- Bug reports: use
.github/ISSUE_TEMPLATE/bug_report.md. Reproduce with--verbose(-v) and paste the relevant lines. Fill innodeup version, OS/arch, manager, manager version, and installed Node versions (nodeup list). - Feature requests: use
.github/ISSUE_TEMPLATE/feature_request.md. Read the in-template "Out-of-scope check" before filing —.nvmrc/.node-versionmanagement,npm-itself updates,yarn/pnpmglobals, and self-updates are explicitly out of scope for v1.x. - Security issues: do not open a public issue. Email the maintainer directly (see GitHub profile) and give 90 days for a coordinated fix.
- Go: enforced by
golangci-lintwitherrcheck,staticcheck,gocritic, etc. (.golangci.yml). Two-space tabs, no manual alignment columns, no exported names without a godoc comment. - Commits: squash-merged, so each commit's diff can be standalone. No "fix typo" / "fix lint" follow-ups in the same PR.
- User-facing output: goes through
internal/ui— neverfmt.Printlnfrom business logic. This keeps output testable and uniform (color, spinners, summary tables). - No new dependencies without a rationale line in the PR body. We
lean on the stdlib +
cobra+yaml.v3+ the Charm stack (lipgloss,bubbletea,huh— all of which live behind theinternal/uiboundary).