@chappibunny/repolens 1.0.0 → 1.3.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/CHANGELOG.md +76 -0
- package/README.md +46 -16
- package/package.json +1 -1
- package/src/ai/generate-sections.js +0 -6
- package/src/ai/provider.js +19 -13
- package/src/cli.js +92 -1
- package/src/core/config-schema.js +26 -1
- package/src/init.js +0 -3
- package/src/migrate.js +89 -7
- package/src/publishers/github-wiki.js +461 -0
- package/src/publishers/index.js +23 -1
- package/src/utils/branch.js +27 -0
- package/src/utils/validate.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,13 +2,89 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to RepoLens will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 1.3.0
|
|
6
|
+
|
|
7
|
+
### ✨ New Feature: `repolens demo`
|
|
8
|
+
|
|
9
|
+
- **Zero-config local preview**: New `demo` command generates documentation locally without any API keys or publisher configuration. Works on any repository, even without a `.repolens.yml` — uses sensible default scan patterns.
|
|
10
|
+
- **Instant onboarding**: Run `npx @chappibunny/repolens demo` on any repo to see what RepoLens generates. Output written to `.repolens/` directory.
|
|
11
|
+
- **Config-aware**: If a `.repolens.yml` exists, demo uses it. Otherwise, defaults to scanning common source patterns (`js`, `ts`, `py`, `go`, `rs`, `java`, etc.) with standard ignore paths.
|
|
12
|
+
|
|
13
|
+
### 📝 Documentation
|
|
14
|
+
|
|
15
|
+
- Added demo command to all documentation: README, STABILITY, ROADMAP, copilot-instructions
|
|
16
|
+
- Added "Quick Preview" section to README usage guide
|
|
17
|
+
- Updated all version references to 1.3.0
|
|
18
|
+
|
|
19
|
+
## 1.2.0
|
|
20
|
+
|
|
21
|
+
### ✨ Config Migration
|
|
22
|
+
|
|
23
|
+
- **`repolens migrate` now patches `.repolens.yml`**: Automatically adds `configVersion: 1` to legacy config files that are missing it. Creates a `.repolens.yml.backup` before modifying. Supports both `.yml` and `.yaml` extensions, dry-run mode, and graceful handling of parse errors or missing configs.
|
|
24
|
+
|
|
25
|
+
### � Bug Fixes
|
|
26
|
+
|
|
27
|
+
- **GitHub Wiki empty pages**: Fixed branch mismatch where `git init` created a `main` branch but GitHub Wiki serves from `master`. Now explicitly initializes with `master` and pushes to `refs/heads/master`. Also added content validation to skip pages with empty content.
|
|
28
|
+
|
|
29
|
+
### �📝 Documentation
|
|
30
|
+
|
|
31
|
+
- Updated all documentation to reflect v1.2.0 version references
|
|
32
|
+
- Updated TROUBLESHOOTING.md with `migrate` auto-fix hint for missing `configVersion`
|
|
33
|
+
- Updated KNOWN_ISSUES.md planned improvements (removed shipped features)
|
|
34
|
+
- Updated ROADMAP.md with v1.1.0 shipped items
|
|
35
|
+
|
|
36
|
+
## 1.1.0
|
|
37
|
+
|
|
38
|
+
### ✨ GitHub Wiki Publisher UX Enhancement
|
|
39
|
+
|
|
40
|
+
Major upgrade to the wiki output quality — better information hierarchy, audience-aware navigation, and richer page structure.
|
|
41
|
+
|
|
42
|
+
- **Audience-grouped Home page**: Pages organized by reader (Stakeholders, Engineers, New Contributors, Change Tracking) instead of a flat list
|
|
43
|
+
- **Page descriptions**: Each link on Home includes a one-line summary of the page's purpose
|
|
44
|
+
- **Status rail**: Home page now shows a metadata table (project, branch, page count, publisher, source)
|
|
45
|
+
- **Recommended reading order**: Guided path through the docs for first-time readers
|
|
46
|
+
- **Grouped sidebar**: Navigation split into Overview and Architecture sections (plus Custom Pages)
|
|
47
|
+
- **Page metadata headers**: Every page gets a `[← Home](Home)` back-link, audience tag, and branch indicator
|
|
48
|
+
- **Cleaner footer**: Compact format with branch context and Home link
|
|
49
|
+
- **New constants**: `PAGE_DESCRIPTIONS`, `AUDIENCE_GROUPS`, `SIDEBAR_GROUPS`, `PAGE_AUDIENCE`
|
|
50
|
+
- **New helpers**: `getPageDisplayTitle()`, `getCustomPageKeys()`, `wikiLink()`, `pageHeader()`
|
|
51
|
+
|
|
52
|
+
### 🐛 Bug Fixes
|
|
53
|
+
- **Temperature still sent to GPT-5**: `DEFAULT_TEMPERATURE = 0.2` always leaked into API requests even after v1.0.1 fixes — the fallback chain (`temperature ?? aiConfig.temperature ?? DEFAULT_TEMPERATURE`) guaranteed it was never `undefined`. Removed the default entirely; temperature is now only sent when explicitly configured via `REPOLENS_AI_TEMPERATURE` env var or `ai.temperature` in config.
|
|
54
|
+
|
|
55
|
+
## 1.0.1
|
|
56
|
+
|
|
57
|
+
### 🐛 Bug Fixes
|
|
58
|
+
- **GPT-5 API compatibility**: Use `max_completion_tokens` instead of deprecated `max_tokens` parameter
|
|
59
|
+
- **GPT-5 temperature handling**: Omit `temperature` from API requests for models that only support the default value (e.g. gpt-5-mini)
|
|
60
|
+
- Removed all hardcoded `temperature: 0.2` overrides from AI section generators
|
|
61
|
+
- Removed `REPOLENS_AI_TEMPERATURE` from CI workflow and `.env.example`
|
|
62
|
+
- Updated `init` scaffolding to omit temperature from default config
|
|
63
|
+
|
|
64
|
+
### 🔧 Improvements
|
|
65
|
+
- Upgraded GitHub Actions: checkout v4.2.2, setup-node v4.2.0, Node.js 22
|
|
66
|
+
|
|
5
67
|
## 1.0.0
|
|
6
68
|
|
|
7
69
|
### 🎉 Stable Release
|
|
8
70
|
|
|
9
71
|
RepoLens v1.0.0 marks the first stable release with a frozen public API. All CLI commands, configuration schema, and plugin interfaces are now covered by semantic versioning guarantees. See [STABILITY.md](STABILITY.md) for the full contract.
|
|
10
72
|
|
|
73
|
+
### ✨ New Features
|
|
74
|
+
- **GitHub Wiki Publisher**: Publish documentation directly to your repository's Wiki tab
|
|
75
|
+
- Git-based: clones wiki repo, writes pages, commits and pushes
|
|
76
|
+
- Generates `Home.md` index, `_Sidebar.md` navigation, `_Footer.md`
|
|
77
|
+
- Branch filtering via `github_wiki.branches` in `.repolens.yml`
|
|
78
|
+
- Auto-detects repository from `GITHUB_REPOSITORY` env or git remote
|
|
79
|
+
- Token sanitization in error messages (never leaks `GITHUB_TOKEN`)
|
|
80
|
+
- Config: `sidebar` and `footer` toggles
|
|
81
|
+
- 17 tests covering publishing, branch filtering, config validation, and security
|
|
82
|
+
|
|
11
83
|
### 🐛 Bug Fixes
|
|
84
|
+
- **GPT-5 API compatibility**: Use `max_completion_tokens` instead of deprecated `max_tokens` parameter
|
|
85
|
+
- **GPT-5 temperature handling**: Omit `temperature` from API requests for models that only support the default value (e.g. gpt-5-mini)
|
|
86
|
+
- **Git identity in wiki publisher**: Set committer name/email (`RepoLens Bot`) so CI runners can commit
|
|
87
|
+
- **Publisher allowlist**: Added `github_wiki` to `validate.js` publisher validation (was only in `config-schema.js`)
|
|
12
88
|
- **Doctor false-success**: `repolens doctor` now correctly exits with code 2 when `runDoctor()` reports failures (previously always printed "validation passed")
|
|
13
89
|
- **Feedback exit code**: `repolens feedback` now exits with code 1 when feedback fails to send (previously exited 0)
|
|
14
90
|
- **Unknown flags**: `repolens --unknown-flag` now prints an error instead of silently running publish
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
AI-assisted documentation intelligence system that generates architecture docs for engineers AND readable system docs for stakeholders
|
|
18
18
|
|
|
19
|
-
**Current Status**: v1.
|
|
19
|
+
**Current Status**: v1.3.0 — Stable Release
|
|
20
20
|
|
|
21
21
|
RepoLens automatically generates and maintains living architecture documentation by analyzing your repository structure, extracting meaningful insights from your package.json, and creating visual dependency graphs. Run it once, or let it auto-update on every push.
|
|
22
22
|
|
|
@@ -124,7 +124,7 @@ RepoLens automatically detects:
|
|
|
124
124
|
✅ **Deterministic Fallback** - Always generates docs even if AI unavailable
|
|
125
125
|
✅ **Business Domain Inference** - Automatically maps code to business functions
|
|
126
126
|
✅ **Data Flow Analysis** - Understands how information moves through your system
|
|
127
|
-
✅ **Multiple Publishers** - Output to Notion, Confluence, Markdown, or all
|
|
127
|
+
✅ **Multiple Publishers** - Output to Notion, Confluence, GitHub Wiki, Markdown, or all four
|
|
128
128
|
✅ **Branch-Aware** - Prevent doc conflicts across branches
|
|
129
129
|
✅ **GitHub Actions** - Autonomous operation on every push
|
|
130
130
|
✅ **Team Notifications** - Discord integration with rich embeds (NEW in v0.6.0)
|
|
@@ -230,10 +230,12 @@ npm link
|
|
|
230
230
|
Install from a specific version:
|
|
231
231
|
|
|
232
232
|
```bash
|
|
233
|
-
npm install https://github.com/CHAPIBUNNY/repolens/releases/download/v1.
|
|
233
|
+
npm install https://github.com/CHAPIBUNNY/repolens/releases/download/v1.3.0/chappibunny-repolens-1.3.0.tgz
|
|
234
234
|
```
|
|
235
235
|
</details>
|
|
236
236
|
|
|
237
|
+
> **Having install problems?** See [TROUBLESHOOTING.md](TROUBLESHOOTING.md) for solutions to common issues like `ENOTEMPTY`, `npm ci` failures, and stale versions.
|
|
238
|
+
|
|
237
239
|
---
|
|
238
240
|
|
|
239
241
|
## 🎓 Complete Onboarding Guide
|
|
@@ -313,6 +315,14 @@ publishers:
|
|
|
313
315
|
```
|
|
314
316
|
Maximum visibility: Notion for async collaboration, Confluence for enterprise docs, Markdown for local backups.
|
|
315
317
|
|
|
318
|
+
**GitHub Wiki** (ideal for open source):
|
|
319
|
+
```yaml
|
|
320
|
+
publishers:
|
|
321
|
+
- github_wiki
|
|
322
|
+
- markdown
|
|
323
|
+
```
|
|
324
|
+
Docs live alongside your code — accessible from the repo's Wiki tab. Requires `GITHUB_TOKEN`.
|
|
325
|
+
|
|
316
326
|
### Step 3: Enable AI Features (Optional)
|
|
317
327
|
|
|
318
328
|
**AI-enhanced documentation adds natural language explanations for non-technical audiences.**
|
|
@@ -336,7 +346,6 @@ REPOLENS_AI_API_KEY=sk-xxxxxxxxxxxxx
|
|
|
336
346
|
# Optional: Customize provider
|
|
337
347
|
REPOLENS_AI_BASE_URL=https://api.openai.com/v1
|
|
338
348
|
REPOLENS_AI_MODEL=gpt-5-mini
|
|
339
|
-
REPOLENS_AI_TEMPERATURE=0.3
|
|
340
349
|
REPOLENS_AI_MAX_TOKENS=2000
|
|
341
350
|
```
|
|
342
351
|
|
|
@@ -346,7 +355,6 @@ REPOLENS_AI_MAX_TOKENS=2000
|
|
|
346
355
|
ai:
|
|
347
356
|
enabled: true # Enable AI features
|
|
348
357
|
mode: hybrid # hybrid, full, or off
|
|
349
|
-
temperature: 0.3 # Lower = more focused (0.0-1.0)
|
|
350
358
|
max_tokens: 2000 # Token limit per request
|
|
351
359
|
|
|
352
360
|
features:
|
|
@@ -648,6 +656,15 @@ Error: Process completed with exit code 1.
|
|
|
648
656
|
|
|
649
657
|
See [MIGRATION.md](MIGRATION.md) for detailed upgrade guide.
|
|
650
658
|
|
|
659
|
+
### Quick Preview (No API Keys)
|
|
660
|
+
|
|
661
|
+
Generate local documentation instantly — no Notion, Confluence, or GitHub tokens needed:
|
|
662
|
+
```bash
|
|
663
|
+
npx @chappibunny/repolens demo
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
Works on any repo, even without a `.repolens.yml`. Output is written to `.repolens/`.
|
|
667
|
+
|
|
651
668
|
### Get Help
|
|
652
669
|
|
|
653
670
|
```bash
|
|
@@ -944,11 +961,14 @@ features:
|
|
|
944
961
|
| `configVersion` | number | **Yes** | Schema version (must be `1`) |
|
|
945
962
|
| `project.name` | string | Yes | Project name |
|
|
946
963
|
| `project.docs_title_prefix` | string | No | Prefix for documentation titles (default: project name) |
|
|
947
|
-
| `publishers` | array | Yes | Output targets: `notion`, `confluence`, `markdown` (+ plugin publishers) |
|
|
964
|
+
| `publishers` | array | Yes | Output targets: `notion`, `confluence`, `github_wiki`, `markdown` (+ plugin publishers) |
|
|
948
965
|
| `plugins` | array | No | Plugin paths or npm package names |
|
|
949
966
|
| `notion.branches` | array | No | Branch whitelist for Notion publishing. Supports globs. |
|
|
950
967
|
| `notion.includeBranchInTitle` | boolean | No | Add `[branch-name]` to titles (default: `true`) |
|
|
951
968
|
| `confluence.branches` | array | No | Branch whitelist for Confluence publishing. Supports globs. |
|
|
969
|
+
| `github_wiki.branches` | array | No | Branch whitelist for GitHub Wiki publishing. Supports globs. |
|
|
970
|
+
| `github_wiki.sidebar` | boolean | No | Generate `_Sidebar.md` navigation (default: `true`) |
|
|
971
|
+
| `github_wiki.footer` | boolean | No | Generate `_Footer.md` (default: `true`) |
|
|
952
972
|
| `discord.enabled` | boolean | No | Enable Discord notifications (default: `true` if webhook set) |
|
|
953
973
|
| `discord.notifyOn` | string | No | Notification policy: `always`, `significant`, `never` (default: `significant`) |
|
|
954
974
|
| `discord.significantThreshold` | number | No | Change % threshold for notifications (default: `10`) |
|
|
@@ -962,8 +982,8 @@ features:
|
|
|
962
982
|
| `features` | object | No | Feature flags (boolean values) |
|
|
963
983
|
| `ai.enabled` | boolean | No | Enable AI-powered documentation |
|
|
964
984
|
| `ai.mode` | string | No | AI mode: `hybrid`, `full`, or `off` |
|
|
965
|
-
| `ai.temperature` | number | No | Generation temperature (0
|
|
966
|
-
| `ai.max_tokens` | number | No | Max tokens per request (>0) |
|
|
985
|
+
| `ai.temperature` | number | No | Generation temperature (0–2). Not supported by all models (e.g. gpt-5-mini ignores it) |
|
|
986
|
+
| `ai.max_tokens` | number | No | Max completion tokens per request (>0) |
|
|
967
987
|
|
|
968
988
|
---
|
|
969
989
|
|
|
@@ -993,6 +1013,13 @@ Optional for Discord notifications:
|
|
|
993
1013
|
|----------|----------|-------------|
|
|
994
1014
|
| `DISCORD_WEBHOOK_URL` | No | Discord webhook URL for team notifications |
|
|
995
1015
|
|
|
1016
|
+
Required for GitHub Wiki publisher:
|
|
1017
|
+
|
|
1018
|
+
| Variable | Required | Description |
|
|
1019
|
+
|----------|----------|-------------|
|
|
1020
|
+
| `GITHUB_TOKEN` | Yes | Personal access token or Actions `${{ secrets.GITHUB_TOKEN }}` |
|
|
1021
|
+
| `GITHUB_REPOSITORY` | No | `owner/repo` (auto-detected from git remote in Actions) |
|
|
1022
|
+
|
|
996
1023
|
**Local Development:** Create `.env` file in project root
|
|
997
1024
|
**GitHub Actions:** Add as repository secrets in Settings → Secrets and variables → Actions
|
|
998
1025
|
|
|
@@ -1077,7 +1104,7 @@ npm test
|
|
|
1077
1104
|
- Integration workflows
|
|
1078
1105
|
- Doctor command validation
|
|
1079
1106
|
|
|
1080
|
-
**Coverage:**
|
|
1107
|
+
**Coverage:** 180 tests passing across 15 test files
|
|
1081
1108
|
|
|
1082
1109
|
### Test Package Installation Locally
|
|
1083
1110
|
|
|
@@ -1088,7 +1115,7 @@ Simulates the full user installation experience:
|
|
|
1088
1115
|
npm pack
|
|
1089
1116
|
|
|
1090
1117
|
# Install globally from tarball
|
|
1091
|
-
npm install -g chappibunny-repolens-1.
|
|
1118
|
+
npm install -g chappibunny-repolens-1.3.0.tgz
|
|
1092
1119
|
|
|
1093
1120
|
# Verify
|
|
1094
1121
|
repolens --version
|
|
@@ -1137,6 +1164,7 @@ repolens/
|
|
|
1137
1164
|
│ │ ├── publish.js # Publishing pipeline
|
|
1138
1165
|
│ │ ├── notion.js # Notion API integration
|
|
1139
1166
|
│ │ ├── confluence.js # Confluence REST API integration
|
|
1167
|
+
│ │ ├── github-wiki.js # GitHub Wiki publisher (git-based)
|
|
1140
1168
|
│ │ └── markdown.js # Local Markdown generation
|
|
1141
1169
|
│ ├── integrations/
|
|
1142
1170
|
│ │ └── discord.js # Discord webhook notifications
|
|
@@ -1156,7 +1184,7 @@ repolens/
|
|
|
1156
1184
|
│ ├── telemetry.js # Opt-in error tracking + performance timers
|
|
1157
1185
|
│ ├── errors.js # Enhanced error messages with guidance
|
|
1158
1186
|
│ └── update-check.js # Version update notifications
|
|
1159
|
-
├── tests/ # Vitest test suite (
|
|
1187
|
+
├── tests/ # Vitest test suite (180 tests across 15 files)
|
|
1160
1188
|
├── .repolens.yml # Dogfooding config
|
|
1161
1189
|
├── package.json
|
|
1162
1190
|
├── CHANGELOG.md
|
|
@@ -1211,14 +1239,14 @@ See [RELEASE.md](./RELEASE.md) for detailed workflow.
|
|
|
1211
1239
|
|
|
1212
1240
|
## 🗺️ Roadmap
|
|
1213
1241
|
|
|
1214
|
-
**Current Status:** v1.
|
|
1242
|
+
**Current Status:** v1.3.0 — Stable Release
|
|
1215
1243
|
|
|
1216
1244
|
### v1.0 — Complete ✅
|
|
1217
1245
|
|
|
1218
|
-
- [x] CLI commands: `init`, `doctor`, `publish`, `migrate`, `watch`, `feedback`, `version`, `help`
|
|
1246
|
+
- [x] CLI commands: `init`, `doctor`, `publish`, `demo`, `migrate`, `watch`, `feedback`, `version`, `help`
|
|
1219
1247
|
- [x] Config schema v1 with validation (frozen)
|
|
1220
1248
|
- [x] Auto-discovery of `.repolens.yml`
|
|
1221
|
-
- [x] Publishers: Notion + Confluence + Markdown
|
|
1249
|
+
- [x] Publishers: Notion + Confluence + GitHub Wiki + Markdown
|
|
1222
1250
|
- [x] Branch-aware publishing with filtering
|
|
1223
1251
|
- [x] Smart tech stack detection from package.json
|
|
1224
1252
|
- [x] Unicode dependency diagrams (no external deps)
|
|
@@ -1227,7 +1255,7 @@ See [RELEASE.md](./RELEASE.md) for detailed workflow.
|
|
|
1227
1255
|
- [x] GitHub Actions automation (publish + release)
|
|
1228
1256
|
- [x] PR architecture diff comments
|
|
1229
1257
|
- [x] Performance guardrails (10k warning, 50k limit)
|
|
1230
|
-
- [x] Comprehensive test suite (
|
|
1258
|
+
- [x] Comprehensive test suite (180 tests across 15 files)
|
|
1231
1259
|
- [x] Security hardening (secret detection, injection prevention, fuzzing)
|
|
1232
1260
|
- [x] Discord notifications with rich embeds
|
|
1233
1261
|
- [x] Documentation coverage & health scoring
|
|
@@ -1250,7 +1278,7 @@ See [RELEASE.md](./RELEASE.md) for detailed workflow.
|
|
|
1250
1278
|
|
|
1251
1279
|
### Future
|
|
1252
1280
|
|
|
1253
|
-
- [ ] Additional publishers (
|
|
1281
|
+
- [ ] Additional publishers (Obsidian)
|
|
1254
1282
|
- [ ] VS Code extension
|
|
1255
1283
|
- [ ] GitHub App
|
|
1256
1284
|
|
|
@@ -1266,6 +1294,8 @@ MIT
|
|
|
1266
1294
|
|
|
1267
1295
|
## 💬 Support & Contact
|
|
1268
1296
|
|
|
1297
|
+
- **Troubleshooting**: [TROUBLESHOOTING.md](TROUBLESHOOTING.md) — installation, config, publishing, AI, and CI/CD issues
|
|
1298
|
+
- **Diagnostics**: Run `npx @chappibunny/repolens doctor` to validate your setup
|
|
1269
1299
|
- **Issues**: [GitHub Issues](https://github.com/CHAPIBUNNY/repolens/issues)
|
|
1270
1300
|
- **Discussions**: [GitHub Discussions](https://github.com/CHAPIBUNNY/repolens/discussions)
|
|
1271
1301
|
- **Email**: Contact repository maintainers
|
package/package.json
CHANGED
|
@@ -25,7 +25,6 @@ export async function generateExecutiveSummary(context) {
|
|
|
25
25
|
const result = await generateText({
|
|
26
26
|
system: SYSTEM_PROMPT,
|
|
27
27
|
user: createExecutiveSummaryPrompt(context),
|
|
28
|
-
temperature: 0.2,
|
|
29
28
|
maxTokens: 1500
|
|
30
29
|
});
|
|
31
30
|
|
|
@@ -47,7 +46,6 @@ export async function generateSystemOverview(context) {
|
|
|
47
46
|
const result = await generateText({
|
|
48
47
|
system: SYSTEM_PROMPT,
|
|
49
48
|
user: createSystemOverviewPrompt(context),
|
|
50
|
-
temperature: 0.2,
|
|
51
49
|
maxTokens: 1200
|
|
52
50
|
});
|
|
53
51
|
|
|
@@ -68,7 +66,6 @@ export async function generateBusinessDomains(context) {
|
|
|
68
66
|
const result = await generateText({
|
|
69
67
|
system: SYSTEM_PROMPT,
|
|
70
68
|
user: createBusinessDomainsPrompt(context),
|
|
71
|
-
temperature: 0.2,
|
|
72
69
|
maxTokens: 2000
|
|
73
70
|
});
|
|
74
71
|
|
|
@@ -89,7 +86,6 @@ export async function generateArchitectureOverview(context) {
|
|
|
89
86
|
const result = await generateText({
|
|
90
87
|
system: SYSTEM_PROMPT,
|
|
91
88
|
user: createArchitectureOverviewPrompt(context),
|
|
92
|
-
temperature: 0.2,
|
|
93
89
|
maxTokens: 1800
|
|
94
90
|
});
|
|
95
91
|
|
|
@@ -110,7 +106,6 @@ export async function generateDataFlows(flows, context) {
|
|
|
110
106
|
const result = await generateText({
|
|
111
107
|
system: SYSTEM_PROMPT,
|
|
112
108
|
user: createDataFlowsPrompt(flows, context),
|
|
113
|
-
temperature: 0.2,
|
|
114
109
|
maxTokens: 1800
|
|
115
110
|
});
|
|
116
111
|
|
|
@@ -131,7 +126,6 @@ export async function generateDeveloperOnboarding(context) {
|
|
|
131
126
|
const result = await generateText({
|
|
132
127
|
system: SYSTEM_PROMPT,
|
|
133
128
|
user: createDeveloperOnboardingPrompt(context),
|
|
134
|
-
temperature: 0.2,
|
|
135
129
|
maxTokens: 2200
|
|
136
130
|
});
|
|
137
131
|
|
package/src/ai/provider.js
CHANGED
|
@@ -4,7 +4,6 @@ import { warn, info } from "../utils/logger.js";
|
|
|
4
4
|
import { executeAIRequest } from "../utils/rate-limit.js";
|
|
5
5
|
|
|
6
6
|
const DEFAULT_TIMEOUT_MS = 60000;
|
|
7
|
-
const DEFAULT_TEMPERATURE = 0.2;
|
|
8
7
|
const DEFAULT_MAX_TOKENS = 2500;
|
|
9
8
|
|
|
10
9
|
export async function generateText({ system, user, temperature, maxTokens, config }) {
|
|
@@ -27,8 +26,8 @@ export async function generateText({ system, user, temperature, maxTokens, confi
|
|
|
27
26
|
const model = process.env.REPOLENS_AI_MODEL || "gpt-5-mini";
|
|
28
27
|
const timeoutMs = parseInt(process.env.REPOLENS_AI_TIMEOUT_MS || DEFAULT_TIMEOUT_MS);
|
|
29
28
|
|
|
30
|
-
// Use config values as fallback for temperature
|
|
31
|
-
const resolvedTemp = temperature ?? aiConfig.temperature ??
|
|
29
|
+
// Use config values as fallback for maxTokens; temperature only when explicitly set
|
|
30
|
+
const resolvedTemp = temperature ?? aiConfig.temperature ?? undefined;
|
|
32
31
|
const resolvedMaxTokens = maxTokens ?? aiConfig.max_tokens ?? DEFAULT_MAX_TOKENS;
|
|
33
32
|
|
|
34
33
|
// Validate configuration
|
|
@@ -81,21 +80,28 @@ async function callOpenAICompatibleAPI({ baseUrl, apiKey, model, system, user, t
|
|
|
81
80
|
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
82
81
|
|
|
83
82
|
try {
|
|
83
|
+
// Build request body — omit temperature if not supported by model
|
|
84
|
+
const body = {
|
|
85
|
+
model,
|
|
86
|
+
messages: [
|
|
87
|
+
{ role: "system", content: system },
|
|
88
|
+
{ role: "user", content: user }
|
|
89
|
+
],
|
|
90
|
+
max_completion_tokens: maxTokens
|
|
91
|
+
};
|
|
92
|
+
// Only send temperature when explicitly configured — some models
|
|
93
|
+
// (e.g. gpt-5-mini) reject any non-default value
|
|
94
|
+
if (temperature != null) {
|
|
95
|
+
body.temperature = temperature;
|
|
96
|
+
}
|
|
97
|
+
|
|
84
98
|
const response = await fetch(url, {
|
|
85
99
|
method: "POST",
|
|
86
100
|
headers: {
|
|
87
101
|
"Content-Type": "application/json",
|
|
88
102
|
"Authorization": `Bearer ${apiKey}`
|
|
89
103
|
},
|
|
90
|
-
body: JSON.stringify(
|
|
91
|
-
model,
|
|
92
|
-
messages: [
|
|
93
|
-
{ role: "system", content: system },
|
|
94
|
-
{ role: "user", content: user }
|
|
95
|
-
],
|
|
96
|
-
temperature,
|
|
97
|
-
max_tokens: maxTokens
|
|
98
|
-
}),
|
|
104
|
+
body: JSON.stringify(body),
|
|
99
105
|
signal: controller.signal
|
|
100
106
|
});
|
|
101
107
|
|
|
@@ -136,7 +142,7 @@ export function getAIConfig() {
|
|
|
136
142
|
provider: process.env.REPOLENS_AI_PROVIDER || "openai_compatible",
|
|
137
143
|
model: process.env.REPOLENS_AI_MODEL || "gpt-5-mini",
|
|
138
144
|
hasApiKey: !!process.env.REPOLENS_AI_API_KEY,
|
|
139
|
-
temperature: parseFloat(process.env.REPOLENS_AI_TEMPERATURE
|
|
145
|
+
temperature: process.env.REPOLENS_AI_TEMPERATURE ? parseFloat(process.env.REPOLENS_AI_TEMPERATURE) : undefined,
|
|
140
146
|
maxTokens: parseInt(process.env.REPOLENS_AI_MAX_TOKENS || DEFAULT_MAX_TOKENS)
|
|
141
147
|
};
|
|
142
148
|
}
|
package/src/cli.js
CHANGED
|
@@ -143,6 +143,7 @@ Commands:
|
|
|
143
143
|
doctor Validate your RepoLens setup
|
|
144
144
|
migrate Upgrade workflow files to current format
|
|
145
145
|
publish Scan, render, and publish documentation
|
|
146
|
+
demo Generate local docs without API keys (quick preview)
|
|
146
147
|
watch Watch for file changes and regenerate docs
|
|
147
148
|
feedback Send feedback to the RepoLens team
|
|
148
149
|
version Print the current RepoLens version
|
|
@@ -166,6 +167,7 @@ Examples:
|
|
|
166
167
|
repolens migrate --dry-run # Preview changes without applying
|
|
167
168
|
repolens publish # Auto-discovers .repolens.yml
|
|
168
169
|
repolens publish --config /path/.repolens.yml # Explicit config path
|
|
170
|
+
repolens demo # Quick local preview (no API keys)
|
|
169
171
|
repolens watch # Watch mode (Markdown only)
|
|
170
172
|
repolens --version
|
|
171
173
|
`);
|
|
@@ -424,6 +426,95 @@ async function main() {
|
|
|
424
426
|
return;
|
|
425
427
|
}
|
|
426
428
|
|
|
429
|
+
if (command === "demo") {
|
|
430
|
+
await printBanner();
|
|
431
|
+
info("Demo mode — generating local documentation (no API keys required)...");
|
|
432
|
+
|
|
433
|
+
const commandTimer = startTimer("demo");
|
|
434
|
+
const targetDir = getArg("--target") || process.cwd();
|
|
435
|
+
|
|
436
|
+
// Try to load existing config, otherwise use sensible defaults
|
|
437
|
+
let cfg;
|
|
438
|
+
try {
|
|
439
|
+
const configPath = getArg("--config") || await findConfig();
|
|
440
|
+
info(`Using config: ${configPath}`);
|
|
441
|
+
cfg = await loadConfig(configPath);
|
|
442
|
+
} catch {
|
|
443
|
+
info("No .repolens.yml found — using default scan patterns");
|
|
444
|
+
cfg = {
|
|
445
|
+
configVersion: 1,
|
|
446
|
+
project: { name: path.basename(targetDir) },
|
|
447
|
+
publishers: ["markdown"],
|
|
448
|
+
scan: {
|
|
449
|
+
include: ["**/*.{js,ts,jsx,tsx,mjs,cjs,py,go,rs,java,rb,php,cs,swift,kt}"],
|
|
450
|
+
ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.git/**", "**/vendor/**", "**/target/**", "**/__pycache__/**"],
|
|
451
|
+
},
|
|
452
|
+
module_roots: ["src", "lib", "app", "packages"],
|
|
453
|
+
outputs: {
|
|
454
|
+
pages: [
|
|
455
|
+
{ key: "system_overview", title: "System Overview" },
|
|
456
|
+
{ key: "module_catalog", title: "Module Catalog" },
|
|
457
|
+
{ key: "api_surface", title: "API Surface" },
|
|
458
|
+
{ key: "route_map", title: "Route Map" },
|
|
459
|
+
{ key: "system_map", title: "System Map" },
|
|
460
|
+
{ key: "executive_summary", title: "Executive Summary" },
|
|
461
|
+
{ key: "business_domains", title: "Business Domains" },
|
|
462
|
+
{ key: "architecture_overview", title: "Architecture Overview" },
|
|
463
|
+
{ key: "data_flows", title: "Data Flows" },
|
|
464
|
+
{ key: "developer_onboarding", title: "Developer Onboarding" },
|
|
465
|
+
],
|
|
466
|
+
},
|
|
467
|
+
__repoRoot: targetDir,
|
|
468
|
+
__configPath: path.join(targetDir, ".repolens.yml"),
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
try {
|
|
473
|
+
info("Scanning repository...");
|
|
474
|
+
const scanTimer = startTimer("scan");
|
|
475
|
+
const scan = await scanRepo(cfg);
|
|
476
|
+
stopTimer(scanTimer);
|
|
477
|
+
info(`Detected ${scan.modules?.length || 0} modules, ${scan.filesCount || 0} files`);
|
|
478
|
+
|
|
479
|
+
const rawDiff = getGitDiff("origin/main");
|
|
480
|
+
|
|
481
|
+
info("Generating documentation set...");
|
|
482
|
+
const renderTimer = startTimer("render");
|
|
483
|
+
const docSet = await generateDocumentSet(scan, cfg, rawDiff);
|
|
484
|
+
stopTimer(renderTimer);
|
|
485
|
+
|
|
486
|
+
info("Writing documentation to disk...");
|
|
487
|
+
const writeResult = await writeDocumentSet(docSet, targetDir);
|
|
488
|
+
|
|
489
|
+
const totalDuration = stopTimer(commandTimer);
|
|
490
|
+
|
|
491
|
+
info(`\n✓ Generated ${writeResult.documentCount} documents in ${writeResult.outputDir}`);
|
|
492
|
+
info("Browse your docs: open the .repolens/ directory");
|
|
493
|
+
info("\nTo publish to Notion, Confluence, or GitHub Wiki, run: repolens publish");
|
|
494
|
+
|
|
495
|
+
printPerformanceSummary();
|
|
496
|
+
|
|
497
|
+
trackUsage("demo", "success", {
|
|
498
|
+
duration: totalDuration,
|
|
499
|
+
fileCount: scan.filesCount || 0,
|
|
500
|
+
moduleCount: scan.modules?.length || 0,
|
|
501
|
+
documentCount: writeResult.documentCount,
|
|
502
|
+
usedConfig: Boolean(cfg.__configPath && cfg.__configPath !== path.join(targetDir, ".repolens.yml")),
|
|
503
|
+
});
|
|
504
|
+
} catch (err) {
|
|
505
|
+
stopTimer(commandTimer);
|
|
506
|
+
captureError(err, { command: "demo", targetDir });
|
|
507
|
+
trackUsage("demo", "failure");
|
|
508
|
+
error("Demo failed:");
|
|
509
|
+
error(err.message);
|
|
510
|
+
await closeTelemetry();
|
|
511
|
+
process.exit(EXIT_ERROR);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
await closeTelemetry();
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
|
|
427
518
|
if (command === "feedback") {
|
|
428
519
|
await printBanner();
|
|
429
520
|
info("Send feedback to the RepoLens team");
|
|
@@ -470,7 +561,7 @@ async function main() {
|
|
|
470
561
|
}
|
|
471
562
|
|
|
472
563
|
error(`Unknown command: ${command}`);
|
|
473
|
-
error("Available commands: init, doctor, migrate, publish, watch, feedback, version, help");
|
|
564
|
+
error("Available commands: init, doctor, migrate, publish, demo, watch, feedback, version, help");
|
|
474
565
|
process.exit(EXIT_ERROR);
|
|
475
566
|
}
|
|
476
567
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
const CURRENT_SCHEMA_VERSION = 1;
|
|
11
|
-
const SUPPORTED_PUBLISHERS = ["notion", "markdown", "confluence"];
|
|
11
|
+
const SUPPORTED_PUBLISHERS = ["notion", "markdown", "confluence", "github_wiki"];
|
|
12
12
|
const SUPPORTED_PAGE_KEYS = [
|
|
13
13
|
"system_overview",
|
|
14
14
|
"module_catalog",
|
|
@@ -193,6 +193,31 @@ export function validateConfig(config) {
|
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
+
// Validate GitHub Wiki configuration (optional)
|
|
197
|
+
if (config.github_wiki !== undefined) {
|
|
198
|
+
if (typeof config.github_wiki !== "object" || Array.isArray(config.github_wiki)) {
|
|
199
|
+
errors.push("github_wiki must be an object");
|
|
200
|
+
} else {
|
|
201
|
+
if (config.github_wiki.branches !== undefined) {
|
|
202
|
+
if (!Array.isArray(config.github_wiki.branches)) {
|
|
203
|
+
errors.push("github_wiki.branches must be an array");
|
|
204
|
+
} else {
|
|
205
|
+
config.github_wiki.branches.forEach((branch, idx) => {
|
|
206
|
+
if (typeof branch !== "string") {
|
|
207
|
+
errors.push(`github_wiki.branches[${idx}] must be a string`);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (config.github_wiki.sidebar !== undefined && typeof config.github_wiki.sidebar !== "boolean") {
|
|
213
|
+
errors.push("github_wiki.sidebar must be a boolean");
|
|
214
|
+
}
|
|
215
|
+
if (config.github_wiki.footer !== undefined && typeof config.github_wiki.footer !== "boolean") {
|
|
216
|
+
errors.push("github_wiki.footer must be a boolean");
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
196
221
|
// Validate Discord configuration (optional)
|
|
197
222
|
if (config.discord !== undefined) {
|
|
198
223
|
if (typeof config.discord !== "object" || Array.isArray(config.discord)) {
|
package/src/init.js
CHANGED
|
@@ -105,7 +105,6 @@ CONFLUENCE_PARENT_PAGE_ID=
|
|
|
105
105
|
# REPOLENS_AI_API_KEY=sk-...
|
|
106
106
|
# REPOLENS_AI_BASE_URL=https://api.openai.com/v1
|
|
107
107
|
# REPOLENS_AI_MODEL=gpt-5-mini
|
|
108
|
-
# REPOLENS_AI_TEMPERATURE=0.3
|
|
109
108
|
# REPOLENS_AI_MAX_TOKENS=2000
|
|
110
109
|
`;
|
|
111
110
|
|
|
@@ -206,7 +205,6 @@ AI features add natural language explanations for non-technical stakeholders.
|
|
|
206
205
|
ai:
|
|
207
206
|
enabled: true
|
|
208
207
|
mode: hybrid
|
|
209
|
-
temperature: 0.3
|
|
210
208
|
|
|
211
209
|
features:
|
|
212
210
|
executive_summary: true
|
|
@@ -607,7 +605,6 @@ function buildWizardConfig(answers) {
|
|
|
607
605
|
lines.push(`ai:`);
|
|
608
606
|
lines.push(` enabled: true`);
|
|
609
607
|
lines.push(` mode: hybrid`);
|
|
610
|
-
lines.push(` temperature: 0.3`);
|
|
611
608
|
lines.push(``);
|
|
612
609
|
lines.push(`features:`);
|
|
613
610
|
lines.push(` executive_summary: true`);
|
package/src/migrate.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import yaml from "js-yaml";
|
|
3
4
|
import { info, warn, error as logError } from "./utils/logger.js";
|
|
4
5
|
import { trackMigration } from "./utils/telemetry.js";
|
|
5
6
|
|
|
@@ -105,6 +106,78 @@ $2$3`
|
|
|
105
106
|
return migrated;
|
|
106
107
|
}
|
|
107
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Find .repolens.yml config file in the target directory
|
|
111
|
+
*/
|
|
112
|
+
async function findConfigFile(targetDir) {
|
|
113
|
+
const candidates = [".repolens.yml", ".repolens.yaml"];
|
|
114
|
+
for (const name of candidates) {
|
|
115
|
+
const filePath = path.join(targetDir, name);
|
|
116
|
+
try {
|
|
117
|
+
await fs.access(filePath);
|
|
118
|
+
return filePath;
|
|
119
|
+
} catch {
|
|
120
|
+
// not found, try next
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Migrate .repolens.yml config file — add configVersion: 1 if missing
|
|
128
|
+
* Returns { migrated: boolean, filePath: string|null }
|
|
129
|
+
*/
|
|
130
|
+
async function migrateConfigFile(targetDir, options = {}) {
|
|
131
|
+
const { dryRun = false } = options;
|
|
132
|
+
|
|
133
|
+
const configPath = await findConfigFile(targetDir);
|
|
134
|
+
if (!configPath) {
|
|
135
|
+
return { migrated: false, filePath: null, reason: "no-config" };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const filename = path.basename(configPath);
|
|
139
|
+
console.log(`\n📄 Checking config: ${filename}`);
|
|
140
|
+
|
|
141
|
+
const raw = await fs.readFile(configPath, "utf8");
|
|
142
|
+
let parsed;
|
|
143
|
+
try {
|
|
144
|
+
parsed = yaml.load(raw);
|
|
145
|
+
} catch {
|
|
146
|
+
warn(` ⚠️ Could not parse ${filename} — skipping config migration`);
|
|
147
|
+
return { migrated: false, filePath: configPath, reason: "parse-error" };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (!parsed || typeof parsed !== "object") {
|
|
151
|
+
warn(` ⚠️ ${filename} is empty or not a mapping — skipping config migration`);
|
|
152
|
+
return { migrated: false, filePath: configPath, reason: "invalid-structure" };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (parsed.configVersion != null) {
|
|
156
|
+
info(" ✅ configVersion already present");
|
|
157
|
+
return { migrated: false, filePath: configPath, reason: "already-present" };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
info(" 🔧 Missing configVersion — adding configVersion: 1");
|
|
161
|
+
|
|
162
|
+
// Prepend configVersion: 1 to preserve existing formatting
|
|
163
|
+
const migratedContent = `configVersion: 1\n${raw}`;
|
|
164
|
+
|
|
165
|
+
if (dryRun) {
|
|
166
|
+
info(" 🔍 DRY RUN: No changes written");
|
|
167
|
+
return { migrated: true, filePath: configPath, reason: "dry-run" };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Backup original
|
|
171
|
+
const backupPath = `${configPath}.backup`;
|
|
172
|
+
await fs.writeFile(backupPath, raw, "utf8");
|
|
173
|
+
info(` 💾 Backup saved: ${path.basename(backupPath)}`);
|
|
174
|
+
|
|
175
|
+
await fs.writeFile(configPath, migratedContent, "utf8");
|
|
176
|
+
info(` ✅ Migrated: ${filename}`);
|
|
177
|
+
|
|
178
|
+
return { migrated: true, filePath: configPath, reason: "migrated" };
|
|
179
|
+
}
|
|
180
|
+
|
|
108
181
|
/**
|
|
109
182
|
* Find all workflow files in .github/workflows
|
|
110
183
|
*/
|
|
@@ -170,18 +243,17 @@ export async function runMigrate(targetDir = process.cwd(), options = {}) {
|
|
|
170
243
|
|
|
171
244
|
const workflowFiles = await findWorkflowFiles(targetDir);
|
|
172
245
|
|
|
246
|
+
let migratedCount = 0;
|
|
247
|
+
let skippedCount = 0;
|
|
248
|
+
|
|
173
249
|
if (workflowFiles.length === 0) {
|
|
174
250
|
warn("⚠️ No workflow files found in .github/workflows/");
|
|
175
251
|
info("\n💡 If you haven't set up GitHub Actions yet:");
|
|
176
252
|
info(" Run: repolens init");
|
|
177
|
-
|
|
253
|
+
} else {
|
|
254
|
+
info(` Found ${workflowFiles.length} workflow file(s)`);
|
|
178
255
|
}
|
|
179
256
|
|
|
180
|
-
info(` Found ${workflowFiles.length} workflow file(s)`);
|
|
181
|
-
|
|
182
|
-
let migratedCount = 0;
|
|
183
|
-
let skippedCount = 0;
|
|
184
|
-
|
|
185
257
|
for (const workflowPath of workflowFiles) {
|
|
186
258
|
const filename = path.basename(workflowPath);
|
|
187
259
|
console.log(`\n📄 Checking: ${filename}`);
|
|
@@ -221,6 +293,16 @@ export async function runMigrate(targetDir = process.cwd(), options = {}) {
|
|
|
221
293
|
migratedCount++;
|
|
222
294
|
}
|
|
223
295
|
|
|
296
|
+
// === Config File Migration ===
|
|
297
|
+
const configResult = await migrateConfigFile(targetDir, { dryRun });
|
|
298
|
+
let configMigrated = false;
|
|
299
|
+
if (configResult.migrated) {
|
|
300
|
+
migratedCount++;
|
|
301
|
+
configMigrated = true;
|
|
302
|
+
} else if (configResult.reason === "no-config") {
|
|
303
|
+
info("\n📄 No .repolens.yml found — skipping config migration");
|
|
304
|
+
}
|
|
305
|
+
|
|
224
306
|
// Summary
|
|
225
307
|
console.log("\n" + "─".repeat(60));
|
|
226
308
|
console.log("📊 Migration Summary:");
|
|
@@ -248,7 +330,7 @@ export async function runMigrate(targetDir = process.cwd(), options = {}) {
|
|
|
248
330
|
trackMigration(migratedCount, skippedCount);
|
|
249
331
|
|
|
250
332
|
// Return results for telemetry tracking
|
|
251
|
-
return { migratedCount, skippedCount };
|
|
333
|
+
return { migratedCount, skippedCount, configMigrated };
|
|
252
334
|
|
|
253
335
|
} catch (err) {
|
|
254
336
|
logError(`Migration failed: ${err.message}`);
|
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
// GitHub Wiki Publisher for RepoLens
|
|
2
|
+
//
|
|
3
|
+
// Publishes rendered documentation to a GitHub repository's wiki.
|
|
4
|
+
// The wiki is a separate git repo at {repo-url}.wiki.git.
|
|
5
|
+
//
|
|
6
|
+
// Environment Variables:
|
|
7
|
+
// - GITHUB_TOKEN: Personal access token or Actions token (required)
|
|
8
|
+
// - GITHUB_REPOSITORY: owner/repo (auto-set in Actions, or detected from git remote)
|
|
9
|
+
//
|
|
10
|
+
// Config (.repolens.yml):
|
|
11
|
+
// github_wiki:
|
|
12
|
+
// branches: [main] # Optional branch filter
|
|
13
|
+
// sidebar: true # Generate _Sidebar.md (default: true)
|
|
14
|
+
// footer: true # Generate _Footer.md (default: true)
|
|
15
|
+
|
|
16
|
+
import { execSync } from "node:child_process";
|
|
17
|
+
import fs from "node:fs/promises";
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
import os from "node:os";
|
|
20
|
+
import { info, warn } from "../utils/logger.js";
|
|
21
|
+
import { getCurrentBranch, getBranchQualifiedTitle, normalizeBranchName } from "../utils/branch.js";
|
|
22
|
+
|
|
23
|
+
const PAGE_ORDER = [
|
|
24
|
+
"executive_summary",
|
|
25
|
+
"system_overview",
|
|
26
|
+
"business_domains",
|
|
27
|
+
"architecture_overview",
|
|
28
|
+
"module_catalog",
|
|
29
|
+
"route_map",
|
|
30
|
+
"api_surface",
|
|
31
|
+
"data_flows",
|
|
32
|
+
"change_impact",
|
|
33
|
+
"system_map",
|
|
34
|
+
"developer_onboarding",
|
|
35
|
+
"graphql_schema",
|
|
36
|
+
"type_graph",
|
|
37
|
+
"dependency_graph",
|
|
38
|
+
"architecture_drift",
|
|
39
|
+
"arch_diff",
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
const PAGE_TITLES = {
|
|
43
|
+
executive_summary: "Executive Summary",
|
|
44
|
+
system_overview: "System Overview",
|
|
45
|
+
business_domains: "Business Domains",
|
|
46
|
+
architecture_overview: "Architecture Overview",
|
|
47
|
+
module_catalog: "Module Catalog",
|
|
48
|
+
route_map: "Route Map",
|
|
49
|
+
api_surface: "API Surface",
|
|
50
|
+
data_flows: "Data Flows",
|
|
51
|
+
change_impact: "Change Impact",
|
|
52
|
+
system_map: "System Map",
|
|
53
|
+
developer_onboarding: "Developer Onboarding",
|
|
54
|
+
graphql_schema: "GraphQL Schema",
|
|
55
|
+
type_graph: "Type Graph",
|
|
56
|
+
dependency_graph: "Dependency Graph",
|
|
57
|
+
architecture_drift: "Architecture Drift",
|
|
58
|
+
arch_diff: "Architecture Diff",
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const PAGE_DESCRIPTIONS = {
|
|
62
|
+
executive_summary: "High-level project summary for non-technical readers.",
|
|
63
|
+
system_overview: "Snapshot of stack, scale, structure, and detected capabilities.",
|
|
64
|
+
business_domains: "Functional areas inferred from the repository and business logic.",
|
|
65
|
+
architecture_overview: "Layered technical view of the system and its major components.",
|
|
66
|
+
module_catalog: "Structured inventory of modules, directories, and responsibilities.",
|
|
67
|
+
route_map: "Frontend routes and page structure detected across the application.",
|
|
68
|
+
api_surface: "Detected endpoints, handlers, methods, and API structure.",
|
|
69
|
+
data_flows: "How information moves through the system and its major pathways.",
|
|
70
|
+
change_impact: "Contextual architecture changes and likely downstream effects.",
|
|
71
|
+
system_map: "Visual map of system relationships and dependencies.",
|
|
72
|
+
developer_onboarding: "What a new engineer needs to understand the repository quickly.",
|
|
73
|
+
graphql_schema: "GraphQL types, operations, and schema structure.",
|
|
74
|
+
type_graph: "Type-level relationships and structural coupling across the codebase.",
|
|
75
|
+
dependency_graph: "Module and package dependency relationships.",
|
|
76
|
+
architecture_drift: "Detected drift between intended and current architecture patterns.",
|
|
77
|
+
arch_diff: "Architecture-level diff across branches or revisions.",
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// Audience-based grouping for Home page
|
|
81
|
+
const AUDIENCE_GROUPS = [
|
|
82
|
+
{
|
|
83
|
+
title: "For Stakeholders",
|
|
84
|
+
emoji: "📊",
|
|
85
|
+
keys: ["executive_summary", "business_domains", "data_flows"],
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
title: "For Engineers",
|
|
89
|
+
emoji: "🔧",
|
|
90
|
+
keys: [
|
|
91
|
+
"architecture_overview", "module_catalog", "api_surface",
|
|
92
|
+
"route_map", "system_map", "graphql_schema", "type_graph",
|
|
93
|
+
"dependency_graph", "architecture_drift",
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
title: "For New Contributors",
|
|
98
|
+
emoji: "🚀",
|
|
99
|
+
keys: ["developer_onboarding", "system_overview"],
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
title: "Change Tracking",
|
|
103
|
+
emoji: "📋",
|
|
104
|
+
keys: ["change_impact", "arch_diff"],
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
// Sidebar grouping (compact navigation)
|
|
109
|
+
const SIDEBAR_GROUPS = [
|
|
110
|
+
{
|
|
111
|
+
title: "Overview",
|
|
112
|
+
keys: [
|
|
113
|
+
"executive_summary", "system_overview", "business_domains",
|
|
114
|
+
"data_flows", "developer_onboarding",
|
|
115
|
+
],
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
title: "Architecture",
|
|
119
|
+
keys: [
|
|
120
|
+
"architecture_overview", "module_catalog", "route_map",
|
|
121
|
+
"api_surface", "system_map", "graphql_schema", "type_graph",
|
|
122
|
+
"dependency_graph", "architecture_drift", "arch_diff", "change_impact",
|
|
123
|
+
],
|
|
124
|
+
},
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
// Audience labels for page metadata headers
|
|
128
|
+
const PAGE_AUDIENCE = {
|
|
129
|
+
executive_summary: "Stakeholders · Leadership",
|
|
130
|
+
system_overview: "All Audiences",
|
|
131
|
+
business_domains: "Stakeholders · Product",
|
|
132
|
+
architecture_overview: "Engineers · Tech Leads",
|
|
133
|
+
module_catalog: "Engineers",
|
|
134
|
+
route_map: "Engineers",
|
|
135
|
+
api_surface: "Engineers",
|
|
136
|
+
data_flows: "Stakeholders · Engineers",
|
|
137
|
+
change_impact: "Engineers · Tech Leads",
|
|
138
|
+
system_map: "All Audiences",
|
|
139
|
+
developer_onboarding: "New Contributors",
|
|
140
|
+
graphql_schema: "Engineers",
|
|
141
|
+
type_graph: "Engineers",
|
|
142
|
+
dependency_graph: "Engineers",
|
|
143
|
+
architecture_drift: "Engineers · Tech Leads",
|
|
144
|
+
arch_diff: "Engineers · Tech Leads",
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get the display title for a page key.
|
|
149
|
+
*/
|
|
150
|
+
function getPageDisplayTitle(key) {
|
|
151
|
+
return PAGE_TITLES[key] || key.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get custom page keys not in the standard order.
|
|
156
|
+
*/
|
|
157
|
+
function getCustomPageKeys(pageKeys) {
|
|
158
|
+
return pageKeys.filter((key) => !PAGE_ORDER.includes(key));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Detect the GitHub repository (owner/repo) from environment or git remote.
|
|
163
|
+
*/
|
|
164
|
+
function detectGitHubRepo() {
|
|
165
|
+
if (process.env.GITHUB_REPOSITORY) {
|
|
166
|
+
return process.env.GITHUB_REPOSITORY;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
const remoteUrl = execSync("git remote get-url origin", {
|
|
171
|
+
encoding: "utf8",
|
|
172
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
173
|
+
}).trim();
|
|
174
|
+
|
|
175
|
+
const httpsMatch = remoteUrl.match(/github\.com\/([^/]+\/[^/.]+)/);
|
|
176
|
+
if (httpsMatch) return httpsMatch[1];
|
|
177
|
+
|
|
178
|
+
const sshMatch = remoteUrl.match(/github\.com:([^/]+\/[^/.]+)/);
|
|
179
|
+
if (sshMatch) return sshMatch[1];
|
|
180
|
+
} catch {
|
|
181
|
+
// git command failed
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Build the authenticated wiki clone URL.
|
|
189
|
+
*/
|
|
190
|
+
function buildWikiCloneUrl(repo, token) {
|
|
191
|
+
return `https://x-access-token:${token}@github.com/${repo}.wiki.git`;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Convert a page key to a wiki-safe filename.
|
|
196
|
+
*/
|
|
197
|
+
function pageFileName(key) {
|
|
198
|
+
const title = getPageDisplayTitle(key);
|
|
199
|
+
return title.replace(/\s+/g, "-") + ".md";
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Build a wiki link for a page key.
|
|
204
|
+
*/
|
|
205
|
+
function wikiLink(key) {
|
|
206
|
+
const display = getPageDisplayTitle(key);
|
|
207
|
+
const slug = pageFileName(key).replace(/\.md$/, "");
|
|
208
|
+
return `[[${display}|${slug}]]`;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Generate the Home.md page — audience-grouped with descriptions and status.
|
|
213
|
+
*/
|
|
214
|
+
function generateHome(pageKeys, projectName, branch, repo) {
|
|
215
|
+
const title = getBranchQualifiedTitle(projectName + " Documentation", branch);
|
|
216
|
+
const lines = [
|
|
217
|
+
`# ${title}`,
|
|
218
|
+
"",
|
|
219
|
+
`> Architecture documentation for **${projectName}**, auto-generated by [RepoLens](https://github.com/CHAPIBUNNY/repolens).`,
|
|
220
|
+
"",
|
|
221
|
+
"## Status",
|
|
222
|
+
"",
|
|
223
|
+
`| | |`,
|
|
224
|
+
`|---|---|`,
|
|
225
|
+
`| **Project** | ${projectName} |`,
|
|
226
|
+
`| **Branch** | \`${branch}\` |`,
|
|
227
|
+
`| **Pages** | ${pageKeys.length} |`,
|
|
228
|
+
`| **Publisher** | GitHub Wiki |`,
|
|
229
|
+
`| **Source** | [RepoLens](https://github.com/CHAPIBUNNY/repolens) |`,
|
|
230
|
+
"",
|
|
231
|
+
"---",
|
|
232
|
+
"",
|
|
233
|
+
];
|
|
234
|
+
|
|
235
|
+
// Audience-grouped sections
|
|
236
|
+
for (const group of AUDIENCE_GROUPS) {
|
|
237
|
+
const activeKeys = group.keys.filter((k) => pageKeys.includes(k));
|
|
238
|
+
if (activeKeys.length === 0) continue;
|
|
239
|
+
|
|
240
|
+
lines.push(`## ${group.emoji} ${group.title}`, "");
|
|
241
|
+
for (const key of activeKeys) {
|
|
242
|
+
const desc = PAGE_DESCRIPTIONS[key] || "";
|
|
243
|
+
lines.push(`- ${wikiLink(key)}${desc ? " — " + desc : ""}`);
|
|
244
|
+
}
|
|
245
|
+
lines.push("");
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Custom / plugin pages
|
|
249
|
+
const customKeys = getCustomPageKeys(pageKeys);
|
|
250
|
+
if (customKeys.length > 0) {
|
|
251
|
+
lines.push("## 🧩 Custom Pages", "");
|
|
252
|
+
for (const key of customKeys.sort()) {
|
|
253
|
+
const desc = PAGE_DESCRIPTIONS[key] || "";
|
|
254
|
+
lines.push(`- ${wikiLink(key)}${desc ? " — " + desc : ""}`);
|
|
255
|
+
}
|
|
256
|
+
lines.push("");
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Recommended reading order
|
|
260
|
+
lines.push(
|
|
261
|
+
"---",
|
|
262
|
+
"",
|
|
263
|
+
"## 📖 Recommended Reading Order",
|
|
264
|
+
"",
|
|
265
|
+
"1. [[Executive Summary|Executive-Summary]] — Start here for a quick overview",
|
|
266
|
+
"2. [[System Overview|System-Overview]] — Understand the stack and scale",
|
|
267
|
+
"3. [[Architecture Overview|Architecture-Overview]] — Deep dive into system design",
|
|
268
|
+
"4. [[Developer Onboarding|Developer-Onboarding]] — Get started contributing",
|
|
269
|
+
"",
|
|
270
|
+
"---",
|
|
271
|
+
"",
|
|
272
|
+
`*This wiki is auto-generated. Manual edits will be overwritten on the next publish.*`,
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
return lines.join("\n");
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Generate _Sidebar.md — grouped navigation.
|
|
280
|
+
*/
|
|
281
|
+
function generateSidebar(pageKeys, projectName, branch) {
|
|
282
|
+
const title = getBranchQualifiedTitle(projectName, branch);
|
|
283
|
+
const lines = [`### ${title}`, "", "[[Home]]", ""];
|
|
284
|
+
|
|
285
|
+
for (const group of SIDEBAR_GROUPS) {
|
|
286
|
+
const activeKeys = group.keys.filter((k) => pageKeys.includes(k));
|
|
287
|
+
if (activeKeys.length === 0) continue;
|
|
288
|
+
|
|
289
|
+
lines.push(`**${group.title}**`, "");
|
|
290
|
+
for (const key of activeKeys) {
|
|
291
|
+
lines.push(`- ${wikiLink(key)}`);
|
|
292
|
+
}
|
|
293
|
+
lines.push("");
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Custom / plugin pages
|
|
297
|
+
const customKeys = getCustomPageKeys(pageKeys);
|
|
298
|
+
if (customKeys.length > 0) {
|
|
299
|
+
lines.push("**Custom Pages**", "");
|
|
300
|
+
for (const key of customKeys.sort()) {
|
|
301
|
+
lines.push(`- ${wikiLink(key)}`);
|
|
302
|
+
}
|
|
303
|
+
lines.push("");
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
lines.push("---", `*[RepoLens](https://github.com/CHAPIBUNNY/repolens)*`);
|
|
307
|
+
return lines.join("\n");
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Generate _Footer.md.
|
|
312
|
+
*/
|
|
313
|
+
function generateFooter(branch) {
|
|
314
|
+
const branchNote = branch !== "main" && branch !== "master"
|
|
315
|
+
? ` · Branch: \`${branch}\``
|
|
316
|
+
: "";
|
|
317
|
+
return [
|
|
318
|
+
"---",
|
|
319
|
+
`📚 Generated by [RepoLens](https://github.com/CHAPIBUNNY/repolens)${branchNote} · [← Home](Home)`,
|
|
320
|
+
].join("\n");
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Build a metadata header to prepend to each page.
|
|
325
|
+
*/
|
|
326
|
+
function pageHeader(key, branch) {
|
|
327
|
+
const audience = PAGE_AUDIENCE[key] || "All Audiences";
|
|
328
|
+
return [
|
|
329
|
+
`[← Home](Home)`,
|
|
330
|
+
"",
|
|
331
|
+
`> **Audience:** ${audience} · **Branch:** \`${branch}\` · **Generated by** [RepoLens](https://github.com/CHAPIBUNNY/repolens)`,
|
|
332
|
+
"",
|
|
333
|
+
"---",
|
|
334
|
+
"",
|
|
335
|
+
].join("\n");
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Run a git command in the specified directory.
|
|
340
|
+
* Token is never exposed in error output.
|
|
341
|
+
*/
|
|
342
|
+
function git(args, cwd) {
|
|
343
|
+
try {
|
|
344
|
+
return execSync(`git ${args}`, {
|
|
345
|
+
cwd,
|
|
346
|
+
encoding: "utf8",
|
|
347
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
348
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
|
|
349
|
+
});
|
|
350
|
+
} catch (err) {
|
|
351
|
+
const sanitized = (err.stderr || err.message || "").replace(
|
|
352
|
+
/x-access-token:[^\s@]+/g,
|
|
353
|
+
"x-access-token:***"
|
|
354
|
+
);
|
|
355
|
+
throw new Error(`Git command failed: git ${args.split(" ")[0]} — ${sanitized}`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Publish rendered pages to GitHub Wiki.
|
|
361
|
+
*/
|
|
362
|
+
export async function publishToGitHubWiki(cfg, renderedPages) {
|
|
363
|
+
const token = process.env.GITHUB_TOKEN;
|
|
364
|
+
if (!token) {
|
|
365
|
+
throw new Error(
|
|
366
|
+
"Missing GITHUB_TOKEN. Required for GitHub Wiki publishing.\n" +
|
|
367
|
+
"In GitHub Actions, this is available as ${{ secrets.GITHUB_TOKEN }}.\n" +
|
|
368
|
+
"Locally, create a PAT with repo scope: https://github.com/settings/tokens"
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const repo = detectGitHubRepo();
|
|
373
|
+
if (!repo) {
|
|
374
|
+
throw new Error(
|
|
375
|
+
"Could not detect GitHub repository. Set GITHUB_REPOSITORY=owner/repo\n" +
|
|
376
|
+
"or ensure git remote 'origin' points to a GitHub URL."
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const branch = getCurrentBranch();
|
|
381
|
+
const wikiConfig = cfg.github_wiki || {};
|
|
382
|
+
const includeSidebar = wikiConfig.sidebar !== false;
|
|
383
|
+
const includeFooter = wikiConfig.footer !== false;
|
|
384
|
+
const projectName = cfg.project?.name || repo.split("/")[1] || "RepoLens";
|
|
385
|
+
|
|
386
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "repolens-wiki-"));
|
|
387
|
+
|
|
388
|
+
try {
|
|
389
|
+
const cloneUrl = buildWikiCloneUrl(repo, token);
|
|
390
|
+
|
|
391
|
+
try {
|
|
392
|
+
git(`clone --depth 1 ${cloneUrl} .`, tmpDir);
|
|
393
|
+
} catch {
|
|
394
|
+
warn("Wiki repository not found — initializing. Enable the wiki tab in GitHub repo settings.");
|
|
395
|
+
// GitHub Wiki uses 'master' as default branch; ensure compatibility
|
|
396
|
+
// regardless of the local git init.defaultBranch setting
|
|
397
|
+
git("init -b master", tmpDir);
|
|
398
|
+
git(`remote add origin ${cloneUrl}`, tmpDir);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Only include pages that have actual content
|
|
402
|
+
const populatedPages = Object.entries(renderedPages).filter(
|
|
403
|
+
([, markdown]) => markdown && markdown.trim().length > 0
|
|
404
|
+
);
|
|
405
|
+
const pageKeys = populatedPages.map(([key]) => key);
|
|
406
|
+
|
|
407
|
+
// Write Home.md
|
|
408
|
+
const home = generateHome(pageKeys, projectName, branch, repo);
|
|
409
|
+
await fs.writeFile(path.join(tmpDir, "Home.md"), home, "utf8");
|
|
410
|
+
|
|
411
|
+
// Write each rendered page with metadata header
|
|
412
|
+
for (const [key, markdown] of populatedPages) {
|
|
413
|
+
const fileName = pageFileName(key);
|
|
414
|
+
const header = pageHeader(key, branch);
|
|
415
|
+
await fs.writeFile(path.join(tmpDir, fileName), header + markdown, "utf8");
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Generate sidebar
|
|
419
|
+
if (includeSidebar) {
|
|
420
|
+
const sidebar = generateSidebar(pageKeys, projectName, branch);
|
|
421
|
+
await fs.writeFile(path.join(tmpDir, "_Sidebar.md"), sidebar, "utf8");
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Generate footer
|
|
425
|
+
if (includeFooter) {
|
|
426
|
+
const footer = generateFooter(branch);
|
|
427
|
+
await fs.writeFile(path.join(tmpDir, "_Footer.md"), footer, "utf8");
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Stage, commit, push
|
|
431
|
+
git("config user.name \"RepoLens Bot\"", tmpDir);
|
|
432
|
+
git("config user.email \"repolens@users.noreply.github.com\"", tmpDir);
|
|
433
|
+
git("add -A", tmpDir);
|
|
434
|
+
|
|
435
|
+
try {
|
|
436
|
+
execSync("git diff --cached --quiet", { cwd: tmpDir, stdio: "pipe" });
|
|
437
|
+
info("GitHub Wiki is already up to date — no changes to push.");
|
|
438
|
+
return;
|
|
439
|
+
} catch {
|
|
440
|
+
// There are staged changes — continue to commit
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const branchLabel = branch !== "main" && branch !== "master" ? ` [${branch}]` : "";
|
|
444
|
+
const commitMsg = `docs: update RepoLens documentation${branchLabel}`;
|
|
445
|
+
git(`commit -m "${commitMsg}"`, tmpDir);
|
|
446
|
+
|
|
447
|
+
// GitHub Wiki serves from 'master'; push explicitly to master
|
|
448
|
+
git("push origin HEAD:refs/heads/master", tmpDir);
|
|
449
|
+
info(`GitHub Wiki published (${pageKeys.length} pages): https://github.com/${repo}/wiki`);
|
|
450
|
+
|
|
451
|
+
} finally {
|
|
452
|
+
await fs.rm(tmpDir, { recursive: true, force: true }).catch(() => {});
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Check if GitHub Wiki publishing secrets are available.
|
|
458
|
+
*/
|
|
459
|
+
export function hasGitHubWikiSecrets() {
|
|
460
|
+
return !!process.env.GITHUB_TOKEN;
|
|
461
|
+
}
|
package/src/publishers/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { publishToNotion } from "./publish.js";
|
|
2
2
|
import { publishToMarkdown } from "./markdown.js";
|
|
3
3
|
import { publishToConfluence, hasConfluenceSecrets } from "./confluence.js";
|
|
4
|
-
import {
|
|
4
|
+
import { publishToGitHubWiki, hasGitHubWikiSecrets } from "./github-wiki.js";
|
|
5
|
+
import { shouldPublishToNotion, shouldPublishToConfluence, shouldPublishToGitHubWiki, getCurrentBranch } from "../utils/branch.js";
|
|
5
6
|
import { info, warn } from "../utils/logger.js";
|
|
6
7
|
import { trackPublishing } from "../utils/telemetry.js";
|
|
7
8
|
import { collectMetrics } from "../utils/metrics.js";
|
|
@@ -80,6 +81,27 @@ export async function publishDocs(cfg, renderedPages, scanResult, pluginManager
|
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
83
|
|
|
84
|
+
// GitHub Wiki publishing (opt-in if secrets configured)
|
|
85
|
+
if (publishers.includes("github_wiki") || hasGitHubWikiSecrets() && publishers.includes("github_wiki")) {
|
|
86
|
+
if (!hasGitHubWikiSecrets()) {
|
|
87
|
+
info("Skipping GitHub Wiki publish: GITHUB_TOKEN not configured");
|
|
88
|
+
info("In GitHub Actions, add GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} to your env");
|
|
89
|
+
} else if (shouldPublishToGitHubWiki(cfg, currentBranch)) {
|
|
90
|
+
info(`Publishing to GitHub Wiki from branch: ${currentBranch}`);
|
|
91
|
+
try {
|
|
92
|
+
await publishToGitHubWiki(cfg, renderedPages);
|
|
93
|
+
publishedTo.push("github_wiki");
|
|
94
|
+
} catch (err) {
|
|
95
|
+
publishStatus = "failure";
|
|
96
|
+
throw err;
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
const allowedBranches = cfg.github_wiki?.branches?.join(", ") || "none configured";
|
|
100
|
+
warn(`Skipping GitHub Wiki publish: branch "${currentBranch}" not in allowed list (${allowedBranches})`);
|
|
101
|
+
info("To publish from this branch, add it to github_wiki.branches in .repolens.yml");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
83
105
|
// Run plugin publishers
|
|
84
106
|
if (pluginManager) {
|
|
85
107
|
const pluginPublishers = pluginManager.getPublishers();
|
package/src/utils/branch.js
CHANGED
|
@@ -112,6 +112,33 @@ export function shouldPublishToConfluence(config, currentBranch = getCurrentBran
|
|
|
112
112
|
});
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Check if current branch should publish to GitHub Wiki
|
|
117
|
+
* Based on config.github_wiki.branches setting
|
|
118
|
+
*/
|
|
119
|
+
export function shouldPublishToGitHubWiki(config, currentBranch = getCurrentBranch()) {
|
|
120
|
+
if (!config.github_wiki) {
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!config.github_wiki.branches || config.github_wiki.branches.length === 0) {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return config.github_wiki.branches.some(pattern => {
|
|
129
|
+
if (pattern === currentBranch) {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (pattern.includes("*")) {
|
|
134
|
+
const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
|
|
135
|
+
return regex.test(currentBranch);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return false;
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
115
142
|
/**
|
|
116
143
|
* Get branch-qualified page title
|
|
117
144
|
*/
|
package/src/utils/validate.js
CHANGED
|
@@ -135,7 +135,7 @@ function validatePublishers(publishers) {
|
|
|
135
135
|
return errors;
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
const validPublishers = ["notion", "markdown", "confluence"];
|
|
138
|
+
const validPublishers = ["notion", "markdown", "confluence", "github_wiki"];
|
|
139
139
|
|
|
140
140
|
if (Array.isArray(publishers)) {
|
|
141
141
|
for (const pub of publishers) {
|