Skip to content
Open
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
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ jobs:
with:
go-version-file: go.mod

- name: Run gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Run tests
run: go test ./...

Expand Down
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ Write to a custom file:
go run ./cmd/ghactivities --output ./out/ghactivities.json
```

`ghactivities` creates missing parent directories for the requested `--output` path before writing JSON files.

Collect both public and private activity for a specific time window:

```bash
Expand Down Expand Up @@ -100,6 +102,8 @@ Notes:

By default, `ghactivities` writes a formatted JSON array to `./ghactivities.json`.

If the parent directories in `--output` do not exist yet, `ghactivities` creates them automatically.

If the rendered JSON exceeds `--max-length-size`, `ghactivities` automatically splits the result into numbered files that keep the same base name and extension:

- `./ghactivities_1.json`
Expand All @@ -116,6 +120,50 @@ go run ./cmd/ghactivities \

This produces either `./exports/activity.json` or, when splitting is needed, files like `./exports/activity_1.json`, `./exports/activity_2.json`, and so on.

Sample output:

```json
[
{
"type": "Issue",
"createdAt": "2026-01-05T09:12:33Z",
"title": "Document release process for ghactivities",
"url": "https://github.com/octo-org/ghactivities/issues/42",
"body": "Add the missing release checklist to the contributor docs.",
"repository": {
"owner": "octo-org",
"name": "ghactivities",
"visibility": "PUBLIC"
}
},
{
"type": "PullRequestComment",
"createdAt": "2026-01-08T14:48:10Z",
"prTitle": "Add support for export filtering",
"prUrl": "https://github.com/octo-org/ghactivities/pull/57",
"body": "Looks good overall. Please add coverage for empty export results.",
"url": "https://github.com/octo-org/ghactivities/pull/57#discussion_r1234567890",
"repository": {
"owner": "octo-org",
"name": "ghactivities",
"visibility": "PUBLIC"
}
},
{
"type": "Commit",
"createdAt": "2026-01-10T18:03:54Z",
"message": "Refine JSON export formatting for downstream tools",
"url": "https://github.com/octo-org/ghactivities/commit/1a2b3c4d5e6f7890abc1234567890def12345678",
"oid": "1a2b3c4d5e6f7890abc1234567890def12345678",
"repository": {
"owner": "octo-org",
"name": "ghactivities",
"visibility": "PUBLIC"
}
}
]
```

## Development setup

This repository uses `mise` for contributor setup and toolchain management.
Expand Down Expand Up @@ -173,6 +221,7 @@ go run ./cmd/ghactivities --help
GitHub Actions handles CI and release builds for `ghactivities`.

- `.github/workflows/ci.yml` runs `go test ./...`, builds `./bin/ghactivities`, and verifies the binary exists on pushes and pull requests to `main`.
- `.github/workflows/ci.yml` also runs `gitleaks` so secret scanning is enforced in CI.
- `.github/workflows/release.yml` runs tests and builds release binaries when a GitHub Release is created.
- Release assets are published for `linux/amd64`, `darwin/amd64`, `darwin/arm64`, and `windows/amd64`.

Expand Down
13 changes: 13 additions & 0 deletions internal/output/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import (
)

func WriteEventsToFiles(items []events.Event, output string, maxLengthSize int) ([]string, error) {
if err := ensureParentDir(output); err != nil {
return nil, err
}

content, err := marshalEvents(items)
if err != nil {
return nil, err
Expand All @@ -27,6 +31,15 @@ func WriteEventsToFiles(items []events.Event, output string, maxLengthSize int)
return splitAndWriteFiles(items, output, maxLengthSize)
}

func ensureParentDir(output string) error {
dir := filepath.Dir(output)
if err := os.MkdirAll(dir, 0o755); err != nil {
return fmt.Errorf("create output directory: %w", err)
}

return nil
}

func splitAndWriteFiles(items []events.Event, output string, maxLengthSize int) ([]string, error) {
dir := filepath.Dir(output)
ext := filepath.Ext(output)
Expand Down
27 changes: 27 additions & 0 deletions internal/output/files_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,33 @@ func TestWriteEventsToFilesSplitsFiles(t *testing.T) {
}
}

func TestWriteEventsToFilesCreatesParentDirectories(t *testing.T) {
tempDir := t.TempDir()
outputPath := filepath.Join(tempDir, "nested", "exports", "events.json")
items := []events.Event{makeIssueEvent(1)}

files, err := WriteEventsToFiles(items, outputPath, 1024*1024)
if err != nil {
t.Fatalf("WriteEventsToFiles returned error: %v", err)
}

if len(files) != 1 || files[0] != outputPath {
t.Fatalf("files = %v, want [%s]", files, outputPath)
}

if _, err := os.Stat(filepath.Dir(outputPath)); err != nil {
t.Fatalf("Stat returned error: %v", err)
}

content, err := os.ReadFile(outputPath)
if err != nil {
t.Fatalf("ReadFile returned error: %v", err)
}
if len(content) == 0 {
t.Fatal("content is empty")
}
}

func TestWriteEventsToFilesEmptyArray(t *testing.T) {
tempDir := t.TempDir()
outputPath := filepath.Join(tempDir, "events.json")
Expand Down
Loading