diff --git a/.github/instructions/go.instructions.md b/.github/instructions/go.instructions.md new file mode 100644 index 0000000..a956d62 --- /dev/null +++ b/.github/instructions/go.instructions.md @@ -0,0 +1,373 @@ +--- +description: 'Instructions for writing Go code following idiomatic Go practices and community standards' +applyTo: '**/*.go,**/go.mod,**/go.sum' +--- + +# Go Development Instructions + +Follow idiomatic Go practices and community standards when writing Go code. These instructions are based on [Effective Go](https://go.dev/doc/effective_go), [Go Code Review Comments](https://go.dev/wiki/CodeReviewComments), and [Google's Go Style Guide](https://google.github.io/styleguide/go/). + +## General Instructions + +- Write simple, clear, and idiomatic Go code +- Favor clarity and simplicity over cleverness +- Follow the principle of least surprise +- Keep the happy path left-aligned (minimize indentation) +- Return early to reduce nesting +- Prefer early return over if-else chains; use `if condition { return }` pattern to avoid else blocks +- Make the zero value useful +- Write self-documenting code with clear, descriptive names +- Document exported types, functions, methods, and packages +- Use Go modules for dependency management +- Leverage the Go standard library instead of reinventing the wheel (e.g., use `strings.Builder` for string concatenation, `filepath.Join` for path construction) +- Prefer standard library solutions over custom implementations when functionality exists +- Write comments in English by default; translate only upon user request +- Avoid using emoji in code and comments + +## Naming Conventions + +### Packages + +- Use lowercase, single-word package names +- Avoid underscores, hyphens, or mixedCaps +- Choose names that describe what the package provides, not what it contains +- Avoid generic names like `util`, `common`, or `base` +- Package names should be singular, not plural + +#### Package Declaration Rules (CRITICAL): +- **NEVER duplicate `package` declarations** - each Go file must have exactly ONE `package` line +- When editing an existing `.go` file: + - **PRESERVE** the existing `package` declaration - do not add another one + - If you need to replace the entire file content, start with the existing package name +- When creating a new `.go` file: + - **BEFORE writing any code**, check what package name other `.go` files in the same directory use + - Use the SAME package name as existing files in that directory + - If it's a new directory, use the directory name as the package name + - Write **exactly one** `package ` line at the very top of the file +- When using file creation or replacement tools: + - **ALWAYS verify** the target file doesn't already have a `package` declaration before adding one + - If replacing file content, include only ONE `package` declaration in the new content + - **NEVER** create files with multiple `package` lines or duplicate declarations + +### Variables and Functions + +- Use mixedCaps or MixedCaps (camelCase) rather than underscores +- Keep names short but descriptive +- Use single-letter variables only for very short scopes (like loop indices) +- Exported names start with a capital letter +- Unexported names start with a lowercase letter +- Avoid stuttering (e.g., avoid `http.HTTPServer`, prefer `http.Server`) + +### Interfaces + +- Name interfaces with -er suffix when possible (e.g., `Reader`, `Writer`, `Formatter`) +- Single-method interfaces should be named after the method (e.g., `Read` → `Reader`) +- Keep interfaces small and focused + +### Constants + +- Use MixedCaps for exported constants +- Use mixedCaps for unexported constants +- Group related constants using `const` blocks +- Consider using typed constants for better type safety + +## Code Style and Formatting + +### Formatting + +- Always use `gofmt` to format code +- Use `goimports` to manage imports automatically +- Keep line length reasonable (no hard limit, but consider readability) +- Add blank lines to separate logical groups of code + +### Comments + +- Strive for self-documenting code; prefer clear variable names, function names, and code structure over comments +- Write comments only when necessary to explain complex logic, business rules, or non-obvious behavior +- Write comments in complete sentences in English by default +- Translate comments to other languages only upon specific user request +- Start sentences with the name of the thing being described +- Package comments should start with "Package [name]" +- Use line comments (`//`) for most comments +- Use block comments (`/* */`) sparingly, mainly for package documentation +- Document why, not what, unless the what is complex +- Avoid emoji in comments and code + +### Error Handling + +- Check errors immediately after the function call +- Don't ignore errors using `_` unless you have a good reason (document why) +- Wrap errors with context using `fmt.Errorf` with `%w` verb +- Create custom error types when you need to check for specific errors +- Place error returns as the last return value +- Name error variables `err` +- Keep error messages lowercase and don't end with punctuation + +## Architecture and Project Structure + +### Package Organization + +- Follow standard Go project layout conventions +- Keep `main` packages in `cmd/` directory +- Put reusable packages in `pkg/` or `internal/` +- Use `internal/` for packages that shouldn't be imported by external projects +- Group related functionality into packages +- Avoid circular dependencies + +### Dependency Management + +- Use Go modules (`go.mod` and `go.sum`) +- Keep dependencies minimal +- Regularly update dependencies for security patches +- Use `go mod tidy` to clean up unused dependencies +- Vendor dependencies only when necessary + +## Type Safety and Language Features + +### Type Definitions + +- Define types to add meaning and type safety +- Use struct tags for JSON, XML, database mappings +- Prefer explicit type conversions +- Use type assertions carefully and check the second return value +- Prefer generics over unconstrained types; when an unconstrained type is truly needed, use the predeclared alias `any` instead of `interface{}` (Go 1.18+) + +### Pointers vs Values + +- Use pointer receivers for large structs or when you need to modify the receiver +- Use value receivers for small structs and when immutability is desired +- Use pointer parameters when you need to modify the argument or for large structs +- Use value parameters for small structs and when you want to prevent modification +- Be consistent within a type's method set +- Consider the zero value when choosing pointer vs value receivers + +### Interfaces and Composition + +- Accept interfaces, return concrete types +- Keep interfaces small (1-3 methods is ideal) +- Use embedding for composition +- Define interfaces close to where they're used, not where they're implemented +- Don't export interfaces unless necessary + +## Concurrency + +### Goroutines + +- Be cautious about creating goroutines in libraries; prefer letting the caller control concurrency +- If you must create goroutines in libraries, provide clear documentation and cleanup mechanisms +- Always know how a goroutine will exit +- Use `sync.WaitGroup` or channels to wait for goroutines +- Avoid goroutine leaks by ensuring cleanup + +### Channels + +- Use channels to communicate between goroutines +- Don't communicate by sharing memory; share memory by communicating +- Close channels from the sender side, not the receiver +- Use buffered channels when you know the capacity +- Use `select` for non-blocking operations + +### Synchronization + +- Use `sync.Mutex` for protecting shared state +- Keep critical sections small +- Use `sync.RWMutex` when you have many readers +- Choose between channels and mutexes based on the use case: use channels for communication, mutexes for protecting state +- Use `sync.Once` for one-time initialization +- WaitGroup usage by Go version: + - If `go >= 1.25` in `go.mod`, use the new `WaitGroup.Go` method ([documentation](https://pkg.go.dev/sync#WaitGroup)): + ```go + var wg sync.WaitGroup + wg.Go(task1) + wg.Go(task2) + wg.Wait() + ``` + - If `go < 1.25`, use the classic `Add`/`Done` pattern + +## Error Handling Patterns + +### Creating Errors + +- Use `errors.New` for simple static errors +- Use `fmt.Errorf` for dynamic errors +- Create custom error types for domain-specific errors +- Export error variables for sentinel errors +- Use `errors.Is` and `errors.As` for error checking + +### Error Propagation + +- Add context when propagating errors up the stack +- Don't log and return errors (choose one) +- Handle errors at the appropriate level +- Consider using structured errors for better debugging + +## API Design + +### HTTP Handlers + +- Use `http.HandlerFunc` for simple handlers +- Implement `http.Handler` for handlers that need state +- Use middleware for cross-cutting concerns +- Set appropriate status codes and headers +- Handle errors gracefully and return appropriate error responses +- Router usage by Go version: + - If `go >= 1.22`, prefer the enhanced `net/http` `ServeMux` with pattern-based routing and method matching + - If `go < 1.22`, use the classic `ServeMux` and handle methods/paths manually (or use a third-party router when justified) + +### JSON APIs + +- Use struct tags to control JSON marshaling +- Validate input data +- Use pointers for optional fields +- Consider using `json.RawMessage` for delayed parsing +- Handle JSON errors appropriately + +### HTTP Clients + +- Keep the client struct focused on configuration and dependencies only (e.g., base URL, `*http.Client`, auth, default headers). It must not store per-request state +- Do not store or cache `*http.Request` inside the client struct, and do not persist request-specific state across calls; instead, construct a fresh request per method invocation +- Methods should accept `context.Context` and input parameters, assemble the `*http.Request` locally (or via a short-lived builder/helper created per call), then call `c.httpClient.Do(req)` +- If request-building logic is reused, factor it into unexported helper functions or a per-call builder type; never keep `http.Request` (URL params, body, headers) as fields on the long-lived client +- Ensure the underlying `*http.Client` is configured (timeouts, transport) and is safe for concurrent use; avoid mutating `Transport` after first use +- Always set headers on the request instance you’re sending, and close response bodies (`defer resp.Body.Close()`), handling errors appropriately + +## Performance Optimization + +### Memory Management + +- Minimize allocations in hot paths +- Reuse objects when possible (consider `sync.Pool`) +- Use value receivers for small structs +- Preallocate slices when size is known +- Avoid unnecessary string conversions + +### I/O: Readers and Buffers + +- Most `io.Reader` streams are consumable once; reading advances state. Do not assume a reader can be re-read without special handling +- If you must read data multiple times, buffer it once and recreate readers on demand: + - Use `io.ReadAll` (or a limited read) to obtain `[]byte`, then create fresh readers via `bytes.NewReader(buf)` or `bytes.NewBuffer(buf)` for each reuse + - For strings, use `strings.NewReader(s)`; you can `Seek(0, io.SeekStart)` on `*bytes.Reader` to rewind +- For HTTP requests, do not reuse a consumed `req.Body`. Instead: + - Keep the original payload as `[]byte` and set `req.Body = io.NopCloser(bytes.NewReader(buf))` before each send + - Prefer configuring `req.GetBody` so the transport can recreate the body for redirects/retries: `req.GetBody = func() (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(buf)), nil }` +- To duplicate a stream while reading, use `io.TeeReader` (copy to a buffer while passing through) or write to multiple sinks with `io.MultiWriter` +- Reusing buffered readers: call `(*bufio.Reader).Reset(r)` to attach to a new underlying reader; do not expect it to “rewind” unless the source supports seeking +- For large payloads, avoid unbounded buffering; consider streaming, `io.LimitReader`, or on-disk temporary storage to control memory + +- Use `io.Pipe` to stream without buffering the whole payload: + - Write to `*io.PipeWriter` in a separate goroutine while the reader consumes + - Always close the writer; use `CloseWithError(err)` on failures + - `io.Pipe` is for streaming, not rewinding or making readers reusable + +- **Warning:** When using `io.Pipe` (especially with multipart writers), all writes must be performed in strict, sequential order. Do not write concurrently or out of order—multipart boundaries and chunk order must be preserved. Out-of-order or parallel writes can corrupt the stream and result in errors. + +- Streaming multipart/form-data with `io.Pipe`: + - `pr, pw := io.Pipe()`; `mw := multipart.NewWriter(pw)`; use `pr` as the HTTP request body + - Set `Content-Type` to `mw.FormDataContentType()` + - In a goroutine: write all parts to `mw` in the correct order; on error `pw.CloseWithError(err)`; on success `mw.Close()` then `pw.Close()` + - Do not store request/in-flight form state on a long-lived client; build per call + - Streamed bodies are not rewindable; for retries/redirects, buffer small payloads or provide `GetBody` + +### Profiling + +- Use built-in profiling tools (`pprof`) +- Benchmark critical code paths +- Profile before optimizing +- Focus on algorithmic improvements first +- Consider using `testing.B` for benchmarks + +## Testing + +### Test Organization + +- Keep tests in the same package (white-box testing) +- Use `_test` package suffix for black-box testing +- Name test files with `_test.go` suffix +- Place test files next to the code they test + +### Writing Tests + +- Use table-driven tests for multiple test cases +- Name tests descriptively using `Test_functionName_scenario` +- Use subtests with `t.Run` for better organization +- Test both success and error cases +- Consider using `testify` or similar libraries when they add value, but don't over-complicate simple tests + +### Test Helpers + +- Mark helper functions with `t.Helper()` +- Create test fixtures for complex setup +- Use `testing.TB` interface for functions used in tests and benchmarks +- Clean up resources using `t.Cleanup()` + +## Security Best Practices + +### Input Validation + +- Validate all external input +- Use strong typing to prevent invalid states +- Sanitize data before using in SQL queries +- Be careful with file paths from user input +- Validate and escape data for different contexts (HTML, SQL, shell) + +### Cryptography + +- Use standard library crypto packages +- Don't implement your own cryptography +- Use crypto/rand for random number generation +- Store passwords using bcrypt, scrypt, or argon2 (consider golang.org/x/crypto for additional options) +- Use TLS for network communication + +## Documentation + +### Code Documentation + +- Prioritize self-documenting code through clear naming and structure +- Document all exported symbols with clear, concise explanations +- Start documentation with the symbol name +- Write documentation in English by default +- Use examples in documentation when helpful +- Keep documentation close to code +- Update documentation when code changes +- Avoid emoji in documentation and comments + +### README and Documentation Files + +- Include clear setup instructions +- Document dependencies and requirements +- Provide usage examples +- Document configuration options +- Include troubleshooting section + +## Tools and Development Workflow + +### Essential Tools + +- `go fmt`: Format code +- `go vet`: Find suspicious constructs +- `golangci-lint`: Additional linting (golint is deprecated) +- `go test`: Run tests +- `go mod`: Manage dependencies +- `go generate`: Code generation + +### Development Practices + +- Run tests before committing +- Use pre-commit hooks for formatting and linting +- Keep commits focused and atomic +- Write meaningful commit messages +- Review diffs before committing + +## Common Pitfalls to Avoid + +- Not checking errors +- Ignoring race conditions +- Creating goroutine leaks +- Not using defer for cleanup +- Modifying maps concurrently +- Not understanding nil interfaces vs nil pointers +- Forgetting to close resources (files, connections) +- Using global variables unnecessarily +- Over-using unconstrained types (e.g., `any`); prefer specific types or generic type parameters with constraints. If an unconstrained type is required, use `any` rather than `interface{}` +- Not considering the zero value of types +- **Creating duplicate `package` declarations** - this is a compile error; always check existing files before adding package declarations diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0546c15..b1db6aa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,11 @@ jobs: - name: Get Version from Makefile id: get_version run: | - VERSION=$(grep "VERSION?=" Makefile | cut -d'=' -f2) + VERSION=$(grep "VERSION?=" Makefile | cut -d'=' -f2 | tr -d ' ') + if [[ -z "$VERSION" ]]; then + echo "Error: VERSION is empty" + exit 1 + fi echo "VERSION=$VERSION" >> $GITHUB_ENV echo "Detected version: $VERSION" @@ -56,4 +60,4 @@ jobs: files: | bin/* env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e69de29..605b170 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -0,0 +1,357 @@ +# Contributing to Journal CLI + +Thank you for your interest in contributing to Journal CLI! We welcome contributions from the community and appreciate your help in making this project better. + +## Table of Contents + +- [Code of Conduct](#code-of-conduct) +- [Getting Started](#getting-started) +- [Development Setup](#development-setup) +- [How to Contribute](#how-to-contribute) +- [Contribution Workflow](#contribution-workflow) +- [Coding Guidelines](#coding-guidelines) +- [Testing](#testing) +- [Submitting Pull Requests](#submitting-pull-requests) +- [Reporting Issues](#reporting-issues) +- [Community](#community) + +## Code of Conduct + +This project adheres to a simple [Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please be respectful, collaborative, and kind in all interactions. + +## Getting Started + +Before you begin: + +- Make sure you have Go 1.21 or higher installed +- Familiarize yourself with the project by reading the [README.md](README.md) +- Check out the [architecture.md](architecture.md) to understand the project structure +- Browse existing [issues](../../issues) and [pull requests](../../pulls) to see what's already being worked on + +## Development Setup + +1. **Fork the repository** on GitHub + +2. **Clone your fork**: + + ```bash + git clone https://github.com/YOUR_USERNAME/journal-cli.git + cd journal-cli + ``` + +3. **Add the upstream remote**: + + ```bash + git remote add upstream https://github.com/ORIGINAL_OWNER/journal-cli.git + ``` + +4. **Install dependencies**: + + ```bash + make deps + ``` + +5. **Build the project**: + + ```bash + make build + ``` + +6. **Run tests** to ensure everything is working: + ```bash + make test + ``` + +## How to Contribute + +There are many ways to contribute to Journal CLI: + +### 🐛 Report Bugs + +Found a bug? Please [open an issue](../../issues/new) with: + +- A clear, descriptive title +- Steps to reproduce the problem +- Expected vs. actual behavior +- Your environment (OS, Go version) +- Any relevant logs or screenshots + +### 💡 Suggest Features + +Have an idea? We'd love to hear it! [Open an issue](../../issues/new) describing: + +- The problem you're trying to solve +- Your proposed solution +- Any alternative solutions you've considered +- How this benefits other users + +### 📝 Improve Documentation + +Documentation improvements are always welcome: + +- Fix typos or clarify existing docs +- Add examples or use cases +- Improve code comments +- Create tutorials or guides + +### 🔧 Submit Code Changes + +Ready to code? Great! See the [Contribution Workflow](#contribution-workflow) below. + +## Contribution Workflow + +1. **Create a new branch** for your work: + + ```bash + git checkout -b feature/your-feature-name + # or + git checkout -b fix/your-bug-fix + ``` + +2. **Make your changes**: + + - Write clean, readable code + - Follow the [Coding Guidelines](#coding-guidelines) + - Add tests for new functionality + - Update documentation as needed + +3. **Format your code**: + + ```bash + make fmt + ``` + +4. **Run the linter**: + + ```bash + make lint + ``` + + > **Note**: If you don't have `golangci-lint` installed, get it from [golangci-lint.run](https://golangci-lint.run/usage/install/) + +5. **Run tests**: + + ```bash + make test + ``` + +6. **Check test coverage** (optional): + + ```bash + make coverage + ``` + +7. **Commit your changes**: + + ```bash + git add . + git commit -m "Brief description of your changes" + ``` + + **Commit message guidelines**: + + - Use the present tense ("Add feature" not "Added feature") + - Use the imperative mood ("Move cursor to..." not "Moves cursor to...") + - Limit the first line to 72 characters or less + - Reference issues and pull requests when relevant + +8. **Keep your branch up to date**: + + ```bash + git fetch upstream + git rebase upstream/main + ``` + +9. **Push to your fork**: + + ```bash + git push origin feature/your-feature-name + ``` + +10. **Open a Pull Request** on GitHub + +## Coding Guidelines + +### General Principles + +- **Keep it simple**: Prefer clarity over cleverness +- **Write idiomatic Go**: Follow [Effective Go](https://golang.org/doc/effective_go.html) guidelines +- **DRY (Don't Repeat Yourself)**: Extract common functionality into reusable functions +- **Single Responsibility**: Each function/package should do one thing well + +### Code Style + +- Use `gofmt` for formatting (run `make fmt`) +- Follow standard Go naming conventions +- Write meaningful variable and function names +- Add comments for exported functions and complex logic +- Keep functions small and focused + +### Project Structure + +``` +journal-cli/ +├── cmd/ # Command-line entry points +├── internal/ # Private application code +├── templates/ # Default template files +├── Makefile # Build automation +└── go.mod # Go module definition +``` + +### Dependencies + +- Minimize external dependencies +- Only add well-maintained, popular packages +- Discuss major dependency additions in an issue first + +## Testing + +We strive for high test coverage. When contributing: + +### Writing Tests + +- Add tests for all new functionality +- Update tests when modifying existing code +- Use table-driven tests where appropriate +- Test edge cases and error conditions + +### Running Tests + +```bash +# Run all tests +make test + +# Run tests with verbose output +make test-verbose + +# Generate coverage report +make coverage +``` + +### Test Coverage + +- Aim for at least 80% coverage for new code +- Check coverage with `make coverage` +- Don't sacrifice code quality for coverage metrics + +## Submitting Pull Requests + +### Before Submitting + +- [ ] Code follows the project's style guidelines +- [ ] All tests pass (`make test`) +- [ ] Linter passes (`make lint`) +- [ ] Code is formatted (`make fmt`) +- [ ] Documentation is updated +- [ ] Commit messages are clear and descriptive + +### PR Description + +Your pull request should include: + +- **What**: A clear description of what you changed +- **Why**: The motivation behind the change +- **How**: Any implementation details worth noting +- **Testing**: How you tested the changes +- **Screenshots**: If applicable (for UI changes) + +### Review Process + +- Maintainers will review your PR +- Address any feedback or requested changes +- Be patient and respectful during the review process +- Once approved, a maintainer will merge your PR + +### Draft PRs + +If you're working on something and want early feedback: + +- Open a **Draft Pull Request** +- Clearly state what feedback you're looking for +- We're happy to guide you through the process! + +## Reporting Issues + +### Security Issues + +If you discover a security vulnerability, please **DO NOT** open a public issue. Instead, email the maintainers directly (contact information in the repository). + +### Bug Reports + +When reporting bugs, include: + +- **Environment**: OS, Go version, Journal CLI version +- **Steps to reproduce**: Clear, numbered steps +- **Expected behavior**: What should happen +- **Actual behavior**: What actually happens +- **Logs/Screenshots**: Any relevant output or images +- **Additional context**: Anything else that might help + +### Feature Requests + +When requesting features: + +- Check if it's already been requested +- Explain the use case clearly +- Describe the desired behavior +- Consider how it fits with the project's goals + +## Community + +### Getting Help + +- **Questions**: Open a [discussion](../../discussions) or issue +- **Chat**: Join our community chat (if available) +- **Documentation**: Check the [README.md](README.md) and [architecture.md](architecture.md) + +### Recognition + +We value all contributions! Contributors will be: + +- Acknowledged in release notes +- Listed in the project's contributors +- Appreciated by the community 🎉 + +### Maintainer Responsibilities + +Maintainers will: + +- Review PRs in a timely manner +- Provide constructive feedback +- Help guide new contributors +- Maintain a welcoming environment + +--- + +## Quick Reference + +### Useful Commands + +```bash +make help # Show all available commands +make build # Build the binary +make test # Run tests +make lint # Run linter +make fmt # Format code +make coverage # Generate coverage report +make clean # Clean build artifacts +make run # Build and run +``` + +### Getting Started Checklist + +- [ ] Fork and clone the repository +- [ ] Set up development environment +- [ ] Run `make deps` to install dependencies +- [ ] Run `make test` to verify setup +- [ ] Create a feature branch +- [ ] Make your changes +- [ ] Run `make fmt && make lint && make test` +- [ ] Commit and push your changes +- [ ] Open a pull request + +--- + +Thank you for contributing to Journal CLI! Your efforts help make this project better for everyone. 🙏 + +If you have any questions, don't hesitate to ask. We're here to help!