github-weekly-reporter 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +38 -0
- package/README.md +163 -0
- package/dist/cli/commands/deploy.d.ts +3 -0
- package/dist/cli/commands/deploy.d.ts.map +1 -0
- package/dist/cli/commands/deploy.js +55 -0
- package/dist/cli/commands/deploy.js.map +1 -0
- package/dist/cli/commands/generate.d.ts +3 -0
- package/dist/cli/commands/generate.d.ts.map +1 -0
- package/dist/cli/commands/generate.js +106 -0
- package/dist/cli/commands/generate.js.map +1 -0
- package/dist/cli/config.d.ts +11 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +16 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/config.test.d.ts +2 -0
- package/dist/cli/config.test.d.ts.map +1 -0
- package/dist/cli/config.test.js +32 -0
- package/dist/cli/config.test.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +13 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/collector/aggregate.d.ts +3 -0
- package/dist/collector/aggregate.d.ts.map +1 -0
- package/dist/collector/aggregate.js +34 -0
- package/dist/collector/aggregate.js.map +1 -0
- package/dist/collector/aggregate.test.d.ts +2 -0
- package/dist/collector/aggregate.test.d.ts.map +1 -0
- package/dist/collector/aggregate.test.js +88 -0
- package/dist/collector/aggregate.test.js.map +1 -0
- package/dist/collector/date-range.d.ts +7 -0
- package/dist/collector/date-range.d.ts.map +1 -0
- package/dist/collector/date-range.js +8 -0
- package/dist/collector/date-range.js.map +1 -0
- package/dist/collector/date-range.test.d.ts +2 -0
- package/dist/collector/date-range.test.d.ts.map +1 -0
- package/dist/collector/date-range.test.js +25 -0
- package/dist/collector/date-range.test.js.map +1 -0
- package/dist/collector/fetch-contributions.d.ts +12 -0
- package/dist/collector/fetch-contributions.d.ts.map +1 -0
- package/dist/collector/fetch-contributions.js +24 -0
- package/dist/collector/fetch-contributions.js.map +1 -0
- package/dist/collector/fetch-issues.d.ts +5 -0
- package/dist/collector/fetch-issues.d.ts.map +1 -0
- package/dist/collector/fetch-issues.js +31 -0
- package/dist/collector/fetch-issues.js.map +1 -0
- package/dist/collector/fetch-languages.d.ts +4 -0
- package/dist/collector/fetch-languages.d.ts.map +1 -0
- package/dist/collector/fetch-languages.js +42 -0
- package/dist/collector/fetch-languages.js.map +1 -0
- package/dist/collector/fetch-pull-requests.d.ts +5 -0
- package/dist/collector/fetch-pull-requests.d.ts.map +1 -0
- package/dist/collector/fetch-pull-requests.js +31 -0
- package/dist/collector/fetch-pull-requests.js.map +1 -0
- package/dist/collector/index.d.ts +3 -0
- package/dist/collector/index.d.ts.map +1 -0
- package/dist/collector/index.js +50 -0
- package/dist/collector/index.js.map +1 -0
- package/dist/collector/queries.d.ts +5 -0
- package/dist/collector/queries.d.ts.map +1 -0
- package/dist/collector/queries.js +81 -0
- package/dist/collector/queries.js.map +1 -0
- package/dist/deployer/index-page.d.ts +3 -0
- package/dist/deployer/index-page.d.ts.map +1 -0
- package/dist/deployer/index-page.js +51 -0
- package/dist/deployer/index-page.js.map +1 -0
- package/dist/deployer/index-page.test.d.ts +2 -0
- package/dist/deployer/index-page.test.d.ts.map +1 -0
- package/dist/deployer/index-page.test.js +29 -0
- package/dist/deployer/index-page.test.js.map +1 -0
- package/dist/deployer/index.d.ts +7 -0
- package/dist/deployer/index.d.ts.map +1 -0
- package/dist/deployer/index.js +16 -0
- package/dist/deployer/index.js.map +1 -0
- package/dist/deployer/week.d.ts +7 -0
- package/dist/deployer/week.d.ts.map +1 -0
- package/dist/deployer/week.js +14 -0
- package/dist/deployer/week.js.map +1 -0
- package/dist/deployer/week.test.d.ts +2 -0
- package/dist/deployer/week.test.d.ts.map +1 -0
- package/dist/deployer/week.test.js +21 -0
- package/dist/deployer/week.test.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/index.d.ts +4 -0
- package/dist/llm/index.d.ts.map +1 -0
- package/dist/llm/index.js +25 -0
- package/dist/llm/index.js.map +1 -0
- package/dist/llm/llm.test.d.ts +2 -0
- package/dist/llm/llm.test.d.ts.map +1 -0
- package/dist/llm/llm.test.js +24 -0
- package/dist/llm/llm.test.js.map +1 -0
- package/dist/llm/prompt.d.ts +3 -0
- package/dist/llm/prompt.d.ts.map +1 -0
- package/dist/llm/prompt.js +31 -0
- package/dist/llm/prompt.js.map +1 -0
- package/dist/llm/prompt.test.d.ts +2 -0
- package/dist/llm/prompt.test.d.ts.map +1 -0
- package/dist/llm/prompt.test.js +48 -0
- package/dist/llm/prompt.test.js.map +1 -0
- package/dist/llm/providers/anthropic.d.ts +3 -0
- package/dist/llm/providers/anthropic.d.ts.map +1 -0
- package/dist/llm/providers/anthropic.js +17 -0
- package/dist/llm/providers/anthropic.js.map +1 -0
- package/dist/llm/providers/gemini.d.ts +3 -0
- package/dist/llm/providers/gemini.d.ts.map +1 -0
- package/dist/llm/providers/gemini.js +13 -0
- package/dist/llm/providers/gemini.js.map +1 -0
- package/dist/llm/providers/openai.d.ts +3 -0
- package/dist/llm/providers/openai.d.ts.map +1 -0
- package/dist/llm/providers/openai.js +17 -0
- package/dist/llm/providers/openai.js.map +1 -0
- package/dist/llm/types.d.ts +11 -0
- package/dist/llm/types.d.ts.map +1 -0
- package/dist/llm/types.js +3 -0
- package/dist/llm/types.js.map +1 -0
- package/dist/renderer/helpers.d.ts +3 -0
- package/dist/renderer/helpers.d.ts.map +1 -0
- package/dist/renderer/helpers.js +22 -0
- package/dist/renderer/helpers.js.map +1 -0
- package/dist/renderer/index.d.ts +3 -0
- package/dist/renderer/index.d.ts.map +1 -0
- package/dist/renderer/index.js +45 -0
- package/dist/renderer/index.js.map +1 -0
- package/dist/renderer/renderer.test.d.ts +2 -0
- package/dist/renderer/renderer.test.d.ts.map +1 -0
- package/dist/renderer/renderer.test.js +111 -0
- package/dist/renderer/renderer.test.js.map +1 -0
- package/dist/renderer/themes.d.ts +18 -0
- package/dist/renderer/themes.d.ts.map +1 -0
- package/dist/renderer/themes.js +180 -0
- package/dist/renderer/themes.js.map +1 -0
- package/dist/types.d.ts +68 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +56 -0
- package/src/renderer/templates/partials/footer.hbs +3 -0
- package/src/renderer/templates/partials/header.hbs +7 -0
- package/src/renderer/templates/partials/heatmap.hbs +11 -0
- package/src/renderer/templates/partials/languages.hbs +19 -0
- package/src/renderer/templates/partials/narrative.hbs +10 -0
- package/src/renderer/templates/partials/repositories.hbs +25 -0
- package/src/renderer/templates/partials/stats.hbs +8 -0
- package/src/renderer/templates/report.hbs +27 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
GitHub Weekly Reporter License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 deariary
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to use,
|
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
8
|
+
the Software, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
1. Attribution Requirement for Commercial Use
|
|
11
|
+
|
|
12
|
+
If the Software is used for commercial purposes (including but not limited
|
|
13
|
+
to use by a for-profit organization, use in a product or service offered
|
|
14
|
+
for sale, or use that generates revenue), all generated output must retain
|
|
15
|
+
the "Powered by deariary" footer link pointing to https://deariary.com
|
|
16
|
+
without modification. The link must remain visible, functional, and not
|
|
17
|
+
marked with rel="nofollow", rel="sponsored", or rel="ugc".
|
|
18
|
+
|
|
19
|
+
2. Non-Commercial Use
|
|
20
|
+
|
|
21
|
+
Individuals using the Software for personal, non-commercial purposes may
|
|
22
|
+
remove or modify the footer link at their discretion.
|
|
23
|
+
|
|
24
|
+
3. Derivative Works
|
|
25
|
+
|
|
26
|
+
Derivative works and forks of this Software are subject to the same
|
|
27
|
+
conditions described above. This license must be included in all copies
|
|
28
|
+
or substantial portions of the Software.
|
|
29
|
+
|
|
30
|
+
4. No Warranty
|
|
31
|
+
|
|
32
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
33
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
34
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
35
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
36
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
37
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
38
|
+
DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# GitHub Weekly Reporter
|
|
2
|
+
|
|
3
|
+
Generate beautiful weekly GitHub activity reports with optional AI-powered narratives.
|
|
4
|
+
|
|
5
|
+
Collect your commits, pull requests, issues, and code reviews from the past week, render them as a polished static HTML page, and deploy to GitHub Pages.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx github-weekly-reporter generate \
|
|
11
|
+
-t $GITHUB_TOKEN \
|
|
12
|
+
-u your-username
|
|
13
|
+
|
|
14
|
+
npx github-weekly-reporter deploy \
|
|
15
|
+
-d ./report \
|
|
16
|
+
-r https://github.com/your-username/weekly-report.git
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- Weekly stats: commits, PRs opened/merged, issues, reviews
|
|
22
|
+
- Top repositories by activity
|
|
23
|
+
- Language breakdown (CSS-only chart)
|
|
24
|
+
- 7-day contribution heatmap
|
|
25
|
+
- AI-generated narrative summary (optional, supports OpenAI / Anthropic / Gemini)
|
|
26
|
+
- Light and dark themes
|
|
27
|
+
- Self-contained HTML (no external requests, no JavaScript required)
|
|
28
|
+
- Deploys to GitHub Pages with weekly archive
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install -g github-weekly-reporter
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Or run directly with npx:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx github-weekly-reporter <command>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Commands
|
|
43
|
+
|
|
44
|
+
### `generate`
|
|
45
|
+
|
|
46
|
+
Collect GitHub activity data and generate an HTML report.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
github-weekly-reporter generate [options]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
| Option | Env Variable | Config Key | Description |
|
|
53
|
+
|---|---|---|---|
|
|
54
|
+
| `-t, --token` | `GITHUB_TOKEN` | - | GitHub personal access token (required) |
|
|
55
|
+
| `-u, --username` | `GITHUB_USERNAME` | `username` | GitHub username (required) |
|
|
56
|
+
| `-o, --output` | - | `output` | Output directory (default: `./report`) |
|
|
57
|
+
| `--theme` | - | `theme` | `default` or `dark` |
|
|
58
|
+
| `--llm-provider` | `LLM_PROVIDER` | `llm.provider` | `openai`, `anthropic`, or `gemini` |
|
|
59
|
+
| `--llm-api-key` | `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` / `GEMINI_API_KEY` | - | LLM API key |
|
|
60
|
+
| `--llm-model` | `LLM_MODEL` | `llm.model` | Model name (e.g. `gpt-4o-mini`) |
|
|
61
|
+
|
|
62
|
+
Priority: CLI flag > environment variable > config file.
|
|
63
|
+
|
|
64
|
+
### `deploy`
|
|
65
|
+
|
|
66
|
+
Push generated report files to the `gh-pages` branch.
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
github-weekly-reporter deploy [options]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
| Option | Description |
|
|
73
|
+
|---|---|
|
|
74
|
+
| `-d, --directory` | Directory containing generated files (default: `./report`) |
|
|
75
|
+
| `-r, --repo` | Repository URL to push to |
|
|
76
|
+
|
|
77
|
+
## Config File
|
|
78
|
+
|
|
79
|
+
Create `.github-weekly-reporter.toml` in your project root:
|
|
80
|
+
|
|
81
|
+
```toml
|
|
82
|
+
username = "your-username"
|
|
83
|
+
theme = "dark"
|
|
84
|
+
output = "./report"
|
|
85
|
+
|
|
86
|
+
[llm]
|
|
87
|
+
provider = "openai"
|
|
88
|
+
model = "gpt-4o-mini"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Secrets (tokens, API keys) should be set via environment variables or CLI flags, not in the config file.
|
|
92
|
+
|
|
93
|
+
## Report Contents
|
|
94
|
+
|
|
95
|
+
| Section | Requires AI | Description |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| Weekly stats | No | Commits, PRs opened/merged, issues, reviews |
|
|
98
|
+
| Highlight repos | No | Most active repositories with counts |
|
|
99
|
+
| Language breakdown | No | CSS-only bar chart of languages used |
|
|
100
|
+
| Contribution graph | No | 7-day heatmap |
|
|
101
|
+
| AI narrative | Yes | Natural language summary of the week |
|
|
102
|
+
| Footer | - | "Powered by deariary" link |
|
|
103
|
+
|
|
104
|
+
## Themes
|
|
105
|
+
|
|
106
|
+
Two built-in themes:
|
|
107
|
+
|
|
108
|
+
- `default`: clean light theme
|
|
109
|
+
- `dark`: dark background, GitHub-inspired
|
|
110
|
+
|
|
111
|
+
## AI Narrative
|
|
112
|
+
|
|
113
|
+
When an LLM provider and API key are configured, the report includes an AI-generated summary. All three major providers are supported:
|
|
114
|
+
|
|
115
|
+
| Provider | Env Variable | Example Model |
|
|
116
|
+
|---|---|---|
|
|
117
|
+
| OpenAI | `OPENAI_API_KEY` | `gpt-4o-mini` |
|
|
118
|
+
| Anthropic | `ANTHROPIC_API_KEY` | `claude-sonnet-4-20250514` |
|
|
119
|
+
| Google Gemini | `GEMINI_API_KEY` | `gemini-2.0-flash` |
|
|
120
|
+
|
|
121
|
+
If the LLM call fails, the report is generated without the AI section. The action never fails due to LLM errors.
|
|
122
|
+
|
|
123
|
+
## GitHub Action Usage
|
|
124
|
+
|
|
125
|
+
```yaml
|
|
126
|
+
name: Weekly Report
|
|
127
|
+
on:
|
|
128
|
+
schedule:
|
|
129
|
+
- cron: '0 9 * * 1' # Every Monday 9:00 UTC
|
|
130
|
+
workflow_dispatch:
|
|
131
|
+
|
|
132
|
+
jobs:
|
|
133
|
+
report:
|
|
134
|
+
runs-on: ubuntu-latest
|
|
135
|
+
steps:
|
|
136
|
+
- uses: actions/checkout@v4
|
|
137
|
+
|
|
138
|
+
- uses: actions/setup-node@v4
|
|
139
|
+
with:
|
|
140
|
+
node-version: '20'
|
|
141
|
+
|
|
142
|
+
- run: npx github-weekly-reporter generate -t ${{ secrets.GITHUB_TOKEN }} -u ${{ github.actor }}
|
|
143
|
+
|
|
144
|
+
- run: npx github-weekly-reporter deploy -d ./report -r https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## URL Structure
|
|
148
|
+
|
|
149
|
+
Reports are archived by ISO week:
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
https://<user>.github.io/<repo>/ # Index page (links to all reports)
|
|
153
|
+
https://<user>.github.io/<repo>/2026/W14/ # Weekly report
|
|
154
|
+
https://<user>.github.io/<repo>/2026/W13/ # Previous week
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
See [LICENSE](./LICENSE) for details.
|
|
160
|
+
|
|
161
|
+
- Commercial use: "Powered by deariary" footer link must be retained
|
|
162
|
+
- Personal/non-commercial use: footer link may be removed
|
|
163
|
+
- Derivative works: same conditions apply
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/deploy.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA8CpC,eAAO,MAAM,cAAc,GAAI,SAAS,OAAO,KAAG,IAkBjD,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// deploy command: push generated report directory to gh-pages branch
|
|
2
|
+
import { deploy } from "../../deployer/index.js";
|
|
3
|
+
import { getWeekId } from "../../deployer/week.js";
|
|
4
|
+
const env = (key) => process.env[key];
|
|
5
|
+
const buildRepoUrl = (repo) => {
|
|
6
|
+
const repoSlug = repo ?? env("GITHUB_REPOSITORY");
|
|
7
|
+
if (!repoSlug) {
|
|
8
|
+
throw new Error("Repository required. Pass --repo or set GITHUB_REPOSITORY.");
|
|
9
|
+
}
|
|
10
|
+
// Already a full URL
|
|
11
|
+
if (repoSlug.startsWith("http") || repoSlug.startsWith("git@")) {
|
|
12
|
+
const token = env("GITHUB_TOKEN");
|
|
13
|
+
if (token && repoSlug.startsWith("https://github.com/")) {
|
|
14
|
+
return repoSlug.replace("https://github.com/", `https://x-access-token:${token}@github.com/`);
|
|
15
|
+
}
|
|
16
|
+
return repoSlug;
|
|
17
|
+
}
|
|
18
|
+
// owner/repo slug
|
|
19
|
+
const token = env("GITHUB_TOKEN");
|
|
20
|
+
if (token) {
|
|
21
|
+
return `https://x-access-token:${token}@github.com/${repoSlug}.git`;
|
|
22
|
+
}
|
|
23
|
+
return `https://github.com/${repoSlug}.git`;
|
|
24
|
+
};
|
|
25
|
+
const run = async (options) => {
|
|
26
|
+
const weekId = getWeekId();
|
|
27
|
+
console.log(`Deploying ${options.directory} to gh-pages...`);
|
|
28
|
+
await deploy({
|
|
29
|
+
repoUrl: options.repoUrl,
|
|
30
|
+
directory: options.directory,
|
|
31
|
+
message: `report: ${weekId.path}`,
|
|
32
|
+
});
|
|
33
|
+
console.log("Deployed successfully!");
|
|
34
|
+
};
|
|
35
|
+
export const registerDeploy = (program) => {
|
|
36
|
+
program
|
|
37
|
+
.command("deploy")
|
|
38
|
+
.description("Deploy generated report to GitHub Pages (gh-pages branch)")
|
|
39
|
+
.option("-d, --directory <dir>", "Directory containing generated report files", "./report")
|
|
40
|
+
.option("-r, --repo <slug>", "Repository (owner/repo or full URL, env: GITHUB_REPOSITORY)")
|
|
41
|
+
.action(async (opts) => {
|
|
42
|
+
try {
|
|
43
|
+
const repoUrl = buildRepoUrl(opts.repo);
|
|
44
|
+
await run({
|
|
45
|
+
directory: opts.directory,
|
|
46
|
+
repoUrl,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=deploy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deploy.js","sourceRoot":"","sources":["../../../src/cli/commands/deploy.ts"],"names":[],"mappings":"AAAA,qEAAqE;AAGrE,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAOnD,MAAM,GAAG,GAAG,CAAC,GAAW,EAAsB,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAElE,MAAM,YAAY,GAAG,CAAC,IAAwB,EAAU,EAAE;IACxD,MAAM,QAAQ,GAAG,IAAI,IAAI,GAAG,CAAC,mBAAmB,CAAC,CAAC;IAClD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAC;IAChF,CAAC;IAED,qBAAqB;IACrB,IAAI,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/D,MAAM,KAAK,GAAG,GAAG,CAAC,cAAc,CAAC,CAAC;QAClC,IAAI,KAAK,IAAI,QAAQ,CAAC,UAAU,CAAC,qBAAqB,CAAC,EAAE,CAAC;YACxD,OAAO,QAAQ,CAAC,OAAO,CAAC,qBAAqB,EAAE,0BAA0B,KAAK,cAAc,CAAC,CAAC;QAChG,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,kBAAkB;IAClB,MAAM,KAAK,GAAG,GAAG,CAAC,cAAc,CAAC,CAAC;IAClC,IAAI,KAAK,EAAE,CAAC;QACV,OAAO,0BAA0B,KAAK,eAAe,QAAQ,MAAM,CAAC;IACtE,CAAC;IACD,OAAO,sBAAsB,QAAQ,MAAM,CAAC;AAC9C,CAAC,CAAC;AAEF,MAAM,GAAG,GAAG,KAAK,EAAE,OAA6B,EAAiB,EAAE;IACjE,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAE3B,OAAO,CAAC,GAAG,CAAC,aAAa,OAAO,CAAC,SAAS,iBAAiB,CAAC,CAAC;IAC7D,MAAM,MAAM,CAAC;QACX,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,OAAO,EAAE,WAAW,MAAM,CAAC,IAAI,EAAE;KAClC,CAAC,CAAC;IACH,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;AACxC,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,OAAgB,EAAQ,EAAE;IACvD,OAAO;SACJ,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,2DAA2D,CAAC;SACxE,MAAM,CAAC,uBAAuB,EAAE,6CAA6C,EAAE,UAAU,CAAC;SAC1F,MAAM,CAAC,mBAAmB,EAAE,6DAA6D,CAAC;SAC1F,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACrB,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACxC,MAAM,GAAG,CAAC;gBACR,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,OAAO;aACR,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;YACxE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/generate.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA6GpC,eAAO,MAAM,gBAAgB,GAAI,SAAS,OAAO,KAAG,IAoBnD,CAAC"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// generate command: collect data, render HTML, write files to output directory
|
|
2
|
+
import { writeFile, mkdir, readdir } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { collectWeeklyData } from "../../collector/index.js";
|
|
5
|
+
import { renderReport } from "../../renderer/index.js";
|
|
6
|
+
import { generateNarrative } from "../../llm/index.js";
|
|
7
|
+
import { renderIndexPage } from "../../deployer/index-page.js";
|
|
8
|
+
import { getWeekId } from "../../deployer/week.js";
|
|
9
|
+
import { loadConfig } from "../config.js";
|
|
10
|
+
const env = (key) => process.env[key];
|
|
11
|
+
// Priority: CLI flag > env var > config file
|
|
12
|
+
const resolveOptions = async (cli) => {
|
|
13
|
+
const config = await loadConfig();
|
|
14
|
+
const token = cli.token ?? env("GITHUB_TOKEN");
|
|
15
|
+
if (!token) {
|
|
16
|
+
throw new Error("GitHub token required. Pass --token or set GITHUB_TOKEN.");
|
|
17
|
+
}
|
|
18
|
+
const username = cli.username ?? env("GITHUB_USERNAME") ?? config.username;
|
|
19
|
+
if (!username) {
|
|
20
|
+
throw new Error("GitHub username required. Pass --username, set GITHUB_USERNAME, or add to config file.");
|
|
21
|
+
}
|
|
22
|
+
const llmProvider = (cli.llmProvider ?? env("LLM_PROVIDER") ?? config.llm?.provider);
|
|
23
|
+
const llmApiKey = cli.llmApiKey
|
|
24
|
+
?? env("OPENAI_API_KEY")
|
|
25
|
+
?? env("ANTHROPIC_API_KEY")
|
|
26
|
+
?? env("GEMINI_API_KEY");
|
|
27
|
+
const llmModel = cli.llmModel ?? env("LLM_MODEL") ?? config.llm?.model;
|
|
28
|
+
return {
|
|
29
|
+
token,
|
|
30
|
+
username,
|
|
31
|
+
output: cli.output ?? config.output ?? "./report",
|
|
32
|
+
theme: (cli.theme ?? config.theme ?? "default"),
|
|
33
|
+
llmProvider,
|
|
34
|
+
llmApiKey,
|
|
35
|
+
llmModel,
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
const listReportDirs = async (dir) => {
|
|
39
|
+
const paths = [];
|
|
40
|
+
let entries = [];
|
|
41
|
+
try {
|
|
42
|
+
entries = await readdir(dir);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return paths;
|
|
46
|
+
}
|
|
47
|
+
for (const year of entries.filter((n) => /^\d{4}$/.test(n))) {
|
|
48
|
+
const weeks = await readdir(join(dir, year));
|
|
49
|
+
weeks
|
|
50
|
+
.filter((n) => /^W\d{2}$/.test(n))
|
|
51
|
+
.forEach((w) => paths.push(`${year}/${w}`));
|
|
52
|
+
}
|
|
53
|
+
return paths;
|
|
54
|
+
};
|
|
55
|
+
const run = async (options) => {
|
|
56
|
+
const weekId = getWeekId();
|
|
57
|
+
console.log(`Collecting data for ${options.username}...`);
|
|
58
|
+
const data = await collectWeeklyData(options.token, options.username);
|
|
59
|
+
if (options.llmProvider && options.llmApiKey && options.llmModel) {
|
|
60
|
+
console.log(`Generating AI narrative (${options.llmProvider}/${options.llmModel})...`);
|
|
61
|
+
data.aiNarrative = await generateNarrative(data, {
|
|
62
|
+
provider: options.llmProvider,
|
|
63
|
+
apiKey: options.llmApiKey,
|
|
64
|
+
model: options.llmModel,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
console.log(`Rendering report (theme: ${options.theme})...`);
|
|
68
|
+
const html = renderReport(data, options.theme);
|
|
69
|
+
// Write report to week directory
|
|
70
|
+
const reportDir = join(options.output, weekId.path);
|
|
71
|
+
await mkdir(reportDir, { recursive: true });
|
|
72
|
+
const reportPath = join(reportDir, "index.html");
|
|
73
|
+
await writeFile(reportPath, html, "utf-8");
|
|
74
|
+
console.log(`Report written to ${reportPath}`);
|
|
75
|
+
// Write index page
|
|
76
|
+
const allReports = await listReportDirs(options.output);
|
|
77
|
+
if (!allReports.includes(weekId.path))
|
|
78
|
+
allReports.push(weekId.path);
|
|
79
|
+
const indexHtml = renderIndexPage(allReports, options.theme);
|
|
80
|
+
const indexPath = join(options.output, "index.html");
|
|
81
|
+
await writeFile(indexPath, indexHtml, "utf-8");
|
|
82
|
+
console.log(`Index written to ${indexPath}`);
|
|
83
|
+
};
|
|
84
|
+
export const registerGenerate = (program) => {
|
|
85
|
+
program
|
|
86
|
+
.command("generate")
|
|
87
|
+
.description("Collect GitHub data and generate a weekly report")
|
|
88
|
+
.option("-t, --token <token>", "GitHub token (env: GITHUB_TOKEN)")
|
|
89
|
+
.option("-u, --username <username>", "GitHub username (env: GITHUB_USERNAME, config: username)")
|
|
90
|
+
.option("-o, --output <dir>", "Output directory (config: output)")
|
|
91
|
+
.option("--theme <theme>", "Report theme (config: theme)")
|
|
92
|
+
.option("--llm-provider <provider>", "LLM provider (env: LLM_PROVIDER, config: llm.provider)")
|
|
93
|
+
.option("--llm-api-key <key>", "LLM API key (env: OPENAI_API_KEY / ANTHROPIC_API_KEY / GEMINI_API_KEY)")
|
|
94
|
+
.option("--llm-model <model>", "LLM model name (env: LLM_MODEL, config: llm.model)")
|
|
95
|
+
.action(async (opts) => {
|
|
96
|
+
try {
|
|
97
|
+
const options = await resolveOptions(opts);
|
|
98
|
+
await run(options);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
//# sourceMappingURL=generate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../../../src/cli/commands/generate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/cli/config.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE;QACJ,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH,CAAC;AAIF,eAAO,MAAM,UAAU,GAAU,MAAK,MAAsB,KAAG,OAAO,CAAC,UAAU,CAQhF,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Load config from .github-weekly-reporter.toml
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { parse } from "smol-toml";
|
|
5
|
+
const CONFIG_FILENAME = ".github-weekly-reporter.toml";
|
|
6
|
+
export const loadConfig = async (dir = process.cwd()) => {
|
|
7
|
+
const path = resolve(dir, CONFIG_FILENAME);
|
|
8
|
+
try {
|
|
9
|
+
const content = await readFile(path, "utf-8");
|
|
10
|
+
return parse(content);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return {};
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/cli/config.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAEhD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAYlC,MAAM,eAAe,GAAG,8BAA8B,CAAC;AAEvD,MAAM,CAAC,MAAM,UAAU,GAAG,KAAK,EAAE,MAAc,OAAO,CAAC,GAAG,EAAE,EAAuB,EAAE;IACnF,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;IAC3C,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC9C,OAAO,KAAK,CAAC,OAAO,CAAe,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.test.d.ts","sourceRoot":"","sources":["../../src/cli/config.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { mkdtemp, writeFile, rm } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { loadConfig } from "./config.js";
|
|
6
|
+
describe("loadConfig", () => {
|
|
7
|
+
it("loads config from .github-weekly-reporter.toml", async () => {
|
|
8
|
+
const dir = await mkdtemp(join(tmpdir(), "gwr-config-"));
|
|
9
|
+
try {
|
|
10
|
+
await writeFile(join(dir, ".github-weekly-reporter.toml"), `username = "testuser"\ntheme = "dark"\n\n[llm]\nprovider = "openai"\nmodel = "gpt-4o-mini"\n`, "utf-8");
|
|
11
|
+
const config = await loadConfig(dir);
|
|
12
|
+
expect(config.username).toBe("testuser");
|
|
13
|
+
expect(config.theme).toBe("dark");
|
|
14
|
+
expect(config.llm?.provider).toBe("openai");
|
|
15
|
+
expect(config.llm?.model).toBe("gpt-4o-mini");
|
|
16
|
+
}
|
|
17
|
+
finally {
|
|
18
|
+
await rm(dir, { recursive: true, force: true });
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
it("returns empty object when file does not exist", async () => {
|
|
22
|
+
const dir = await mkdtemp(join(tmpdir(), "gwr-config-"));
|
|
23
|
+
try {
|
|
24
|
+
const config = await loadConfig(dir);
|
|
25
|
+
expect(config).toEqual({});
|
|
26
|
+
}
|
|
27
|
+
finally {
|
|
28
|
+
await rm(dir, { recursive: true, force: true });
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
//# sourceMappingURL=config.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.test.js","sourceRoot":"","sources":["../../src/cli/config.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,EAAE,CAAC,gDAAgD,EAAE,KAAK,IAAI,EAAE;QAC9D,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,aAAa,CAAC,CAAC,CAAC;QACzD,IAAI,CAAC;YACH,MAAM,SAAS,CACb,IAAI,CAAC,GAAG,EAAE,8BAA8B,CAAC,EACzC,8FAA8F,EAC9F,OAAO,CACR,CAAC;YAEF,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,CAAC;YACrC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACzC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAClC,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC5C,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAChD,CAAC;gBAAS,CAAC;YACT,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAClD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,KAAK,IAAI,EAAE;QAC7D,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,aAAa,CAAC,CAAC,CAAC;QACzD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,CAAC;YACrC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC7B,CAAC;gBAAS,CAAC;YACT,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAClD,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// CLI entrypoint
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { registerGenerate } from "./commands/generate.js";
|
|
5
|
+
import { registerDeploy } from "./commands/deploy.js";
|
|
6
|
+
const program = new Command()
|
|
7
|
+
.name("github-weekly-reporter")
|
|
8
|
+
.description("Generate beautiful weekly GitHub activity reports")
|
|
9
|
+
.version("0.1.0");
|
|
10
|
+
registerGenerate(program);
|
|
11
|
+
registerDeploy(program);
|
|
12
|
+
program.parse();
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAEA,iBAAiB;AAEjB,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE;KAC1B,IAAI,CAAC,wBAAwB,CAAC;KAC9B,WAAW,CAAC,mDAAmD,CAAC;KAChE,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,gBAAgB,CAAC,OAAO,CAAC,CAAC;AAC1B,cAAc,CAAC,OAAO,CAAC,CAAC;AAExB,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aggregate.d.ts","sourceRoot":"","sources":["../../src/collector/aggregate.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAE1E,eAAO,MAAM,qBAAqB,GAChC,cAAc,WAAW,EAAE,EAC3B,QAAQ,KAAK,EAAE,KACd,kBAAkB,EAmCpB,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Aggregate per-repository activity from PRs and issues
|
|
2
|
+
export const aggregateRepositories = (pullRequests, issues) => {
|
|
3
|
+
const repoMap = new Map();
|
|
4
|
+
const getOrCreate = (name) => {
|
|
5
|
+
const existing = repoMap.get(name);
|
|
6
|
+
if (existing)
|
|
7
|
+
return existing;
|
|
8
|
+
const repo = {
|
|
9
|
+
name,
|
|
10
|
+
commits: 0,
|
|
11
|
+
prsOpened: 0,
|
|
12
|
+
prsMerged: 0,
|
|
13
|
+
issuesOpened: 0,
|
|
14
|
+
issuesClosed: 0,
|
|
15
|
+
url: `https://github.com/${name}`,
|
|
16
|
+
};
|
|
17
|
+
repoMap.set(name, repo);
|
|
18
|
+
return repo;
|
|
19
|
+
};
|
|
20
|
+
pullRequests.forEach((pr) => {
|
|
21
|
+
const repo = getOrCreate(pr.repository);
|
|
22
|
+
repo.prsOpened += 1;
|
|
23
|
+
if (pr.state === "merged")
|
|
24
|
+
repo.prsMerged += 1;
|
|
25
|
+
});
|
|
26
|
+
issues.forEach((issue) => {
|
|
27
|
+
const repo = getOrCreate(issue.repository);
|
|
28
|
+
repo.issuesOpened += 1;
|
|
29
|
+
if (issue.state === "closed")
|
|
30
|
+
repo.issuesClosed += 1;
|
|
31
|
+
});
|
|
32
|
+
return [...repoMap.values()].sort((a, b) => b.prsOpened + b.issuesOpened - (a.prsOpened + a.issuesOpened));
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=aggregate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aggregate.js","sourceRoot":"","sources":["../../src/collector/aggregate.ts"],"names":[],"mappings":"AAAA,wDAAwD;AAIxD,MAAM,CAAC,MAAM,qBAAqB,GAAG,CACnC,YAA2B,EAC3B,MAAe,EACO,EAAE;IACxB,MAAM,OAAO,GAAG,IAAI,GAAG,EAA8B,CAAC;IAEtD,MAAM,WAAW,GAAG,CAAC,IAAY,EAAsB,EAAE;QACvD,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC;QAC9B,MAAM,IAAI,GAAuB;YAC/B,IAAI;YACJ,OAAO,EAAE,CAAC;YACV,SAAS,EAAE,CAAC;YACZ,SAAS,EAAE,CAAC;YACZ,YAAY,EAAE,CAAC;YACf,YAAY,EAAE,CAAC;YACf,GAAG,EAAE,sBAAsB,IAAI,EAAE;SAClC,CAAC;QACF,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACxB,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;IAEF,YAAY,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE;QAC1B,MAAM,IAAI,GAAG,WAAW,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC;QACxC,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC;QACpB,IAAI,EAAE,CAAC,KAAK,KAAK,QAAQ;YAAE,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;QACvB,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAC3C,IAAI,CAAC,YAAY,IAAI,CAAC,CAAC;QACvB,IAAI,KAAK,CAAC,KAAK,KAAK,QAAQ;YAAE,IAAI,CAAC,YAAY,IAAI,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAC/B,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACP,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,YAAY,CAAC,CAChE,CAAC;AACJ,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aggregate.test.d.ts","sourceRoot":"","sources":["../../src/collector/aggregate.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { aggregateRepositories } from "./aggregate.js";
|
|
3
|
+
describe("aggregateRepositories", () => {
|
|
4
|
+
it("aggregates PRs and issues by repository", () => {
|
|
5
|
+
const prs = [
|
|
6
|
+
{
|
|
7
|
+
title: "Fix bug",
|
|
8
|
+
url: "https://github.com/org/repo-a/pull/1",
|
|
9
|
+
repository: "org/repo-a",
|
|
10
|
+
state: "merged",
|
|
11
|
+
createdAt: "2026-04-01T00:00:00Z",
|
|
12
|
+
mergedAt: "2026-04-02T00:00:00Z",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
title: "Add feature",
|
|
16
|
+
url: "https://github.com/org/repo-a/pull/2",
|
|
17
|
+
repository: "org/repo-a",
|
|
18
|
+
state: "open",
|
|
19
|
+
createdAt: "2026-04-02T00:00:00Z",
|
|
20
|
+
mergedAt: null,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
title: "Update docs",
|
|
24
|
+
url: "https://github.com/org/repo-b/pull/1",
|
|
25
|
+
repository: "org/repo-b",
|
|
26
|
+
state: "merged",
|
|
27
|
+
createdAt: "2026-04-01T00:00:00Z",
|
|
28
|
+
mergedAt: "2026-04-01T00:00:00Z",
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
const issues = [
|
|
32
|
+
{
|
|
33
|
+
title: "Bug report",
|
|
34
|
+
url: "https://github.com/org/repo-a/issues/1",
|
|
35
|
+
repository: "org/repo-a",
|
|
36
|
+
state: "closed",
|
|
37
|
+
createdAt: "2026-04-01T00:00:00Z",
|
|
38
|
+
closedAt: "2026-04-02T00:00:00Z",
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
const result = aggregateRepositories(prs, issues);
|
|
42
|
+
expect(result).toHaveLength(2);
|
|
43
|
+
const repoA = result.find((r) => r.name === "org/repo-a");
|
|
44
|
+
expect(repoA).toBeDefined();
|
|
45
|
+
expect(repoA.prsOpened).toBe(2);
|
|
46
|
+
expect(repoA.prsMerged).toBe(1);
|
|
47
|
+
expect(repoA.issuesOpened).toBe(1);
|
|
48
|
+
expect(repoA.issuesClosed).toBe(1);
|
|
49
|
+
const repoB = result.find((r) => r.name === "org/repo-b");
|
|
50
|
+
expect(repoB).toBeDefined();
|
|
51
|
+
expect(repoB.prsOpened).toBe(1);
|
|
52
|
+
expect(repoB.prsMerged).toBe(1);
|
|
53
|
+
});
|
|
54
|
+
it("sorts by total activity (PRs + issues) descending", () => {
|
|
55
|
+
const prs = [
|
|
56
|
+
{
|
|
57
|
+
title: "PR1",
|
|
58
|
+
url: "",
|
|
59
|
+
repository: "org/less-active",
|
|
60
|
+
state: "open",
|
|
61
|
+
createdAt: "",
|
|
62
|
+
mergedAt: null,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
title: "PR2",
|
|
66
|
+
url: "",
|
|
67
|
+
repository: "org/more-active",
|
|
68
|
+
state: "open",
|
|
69
|
+
createdAt: "",
|
|
70
|
+
mergedAt: null,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
title: "PR3",
|
|
74
|
+
url: "",
|
|
75
|
+
repository: "org/more-active",
|
|
76
|
+
state: "merged",
|
|
77
|
+
createdAt: "",
|
|
78
|
+
mergedAt: "",
|
|
79
|
+
},
|
|
80
|
+
];
|
|
81
|
+
const result = aggregateRepositories(prs, []);
|
|
82
|
+
expect(result[0].name).toBe("org/more-active");
|
|
83
|
+
});
|
|
84
|
+
it("returns empty array when no activity", () => {
|
|
85
|
+
expect(aggregateRepositories([], [])).toEqual([]);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
//# sourceMappingURL=aggregate.test.js.map
|