@chappibunny/repolens 0.4.1
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 +219 -0
- package/README.md +899 -0
- package/RELEASE.md +52 -0
- package/bin/repolens.js +2 -0
- package/package.json +61 -0
- package/src/ai/document-plan.js +133 -0
- package/src/ai/generate-sections.js +271 -0
- package/src/ai/prompts.js +312 -0
- package/src/ai/provider.js +134 -0
- package/src/analyzers/context-builder.js +146 -0
- package/src/analyzers/domain-inference.js +127 -0
- package/src/analyzers/flow-inference.js +198 -0
- package/src/cli.js +271 -0
- package/src/core/config-schema.js +266 -0
- package/src/core/config.js +18 -0
- package/src/core/diff.js +45 -0
- package/src/core/scan.js +312 -0
- package/src/delivery/comment.js +139 -0
- package/src/docs/generate-doc-set.js +123 -0
- package/src/docs/write-doc-set.js +85 -0
- package/src/doctor.js +174 -0
- package/src/init.js +540 -0
- package/src/migrate.js +251 -0
- package/src/publishers/index.js +33 -0
- package/src/publishers/markdown.js +32 -0
- package/src/publishers/notion.js +325 -0
- package/src/publishers/publish.js +31 -0
- package/src/renderers/render.js +256 -0
- package/src/renderers/renderDiff.js +139 -0
- package/src/renderers/renderMap.js +224 -0
- package/src/utils/branch.js +93 -0
- package/src/utils/logger.js +26 -0
- package/src/utils/retry.js +55 -0
- package/src/utils/update-check.js +150 -0
package/src/init.js
ADDED
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createInterface } from "node:readline/promises";
|
|
4
|
+
import { info, warn } from "./utils/logger.js";
|
|
5
|
+
|
|
6
|
+
const DETECTABLE_ROOTS = [
|
|
7
|
+
"app",
|
|
8
|
+
"src/app",
|
|
9
|
+
"components",
|
|
10
|
+
"src/components",
|
|
11
|
+
"lib",
|
|
12
|
+
"src/lib",
|
|
13
|
+
"hooks",
|
|
14
|
+
"src/hooks",
|
|
15
|
+
"store",
|
|
16
|
+
"src/store",
|
|
17
|
+
"pages",
|
|
18
|
+
"src/pages"
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const DEFAULT_GITHUB_WORKFLOW = `name: RepoLens Documentation
|
|
22
|
+
|
|
23
|
+
on:
|
|
24
|
+
push:
|
|
25
|
+
branches:
|
|
26
|
+
- main
|
|
27
|
+
pull_request:
|
|
28
|
+
|
|
29
|
+
permissions:
|
|
30
|
+
contents: read
|
|
31
|
+
|
|
32
|
+
jobs:
|
|
33
|
+
publish:
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
|
|
36
|
+
steps:
|
|
37
|
+
- name: Checkout repository
|
|
38
|
+
uses: actions/checkout@v4
|
|
39
|
+
|
|
40
|
+
- name: Setup Node.js
|
|
41
|
+
uses: actions/setup-node@v4
|
|
42
|
+
with:
|
|
43
|
+
node-version: 20
|
|
44
|
+
|
|
45
|
+
- name: Generate and publish documentation
|
|
46
|
+
env:
|
|
47
|
+
NOTION_TOKEN: \${{ secrets.NOTION_TOKEN }}
|
|
48
|
+
NOTION_PARENT_PAGE_ID: \${{ secrets.NOTION_PARENT_PAGE_ID }}
|
|
49
|
+
run: npx repolens@latest publish
|
|
50
|
+
`;
|
|
51
|
+
|
|
52
|
+
const DEFAULT_ENV_EXAMPLE = `# Notion Publishing
|
|
53
|
+
NOTION_TOKEN=
|
|
54
|
+
NOTION_PARENT_PAGE_ID=
|
|
55
|
+
NOTION_VERSION=2022-06-28
|
|
56
|
+
|
|
57
|
+
# AI-Assisted Documentation (Optional)
|
|
58
|
+
# Enable AI features for natural language explanations
|
|
59
|
+
# REPOLENS_AI_ENABLED=true
|
|
60
|
+
# REPOLENS_AI_API_KEY=sk-...
|
|
61
|
+
# REPOLENS_AI_BASE_URL=https://api.openai.com/v1
|
|
62
|
+
# REPOLENS_AI_MODEL=gpt-4-turbo-preview
|
|
63
|
+
# REPOLENS_AI_TEMPERATURE=0.3
|
|
64
|
+
# REPOLENS_AI_MAX_TOKENS=2000
|
|
65
|
+
`;
|
|
66
|
+
|
|
67
|
+
const DEFAULT_REPOLENS_README = `# RepoLens Documentation
|
|
68
|
+
|
|
69
|
+
This repository is configured to use [RepoLens](https://github.com/CHAPIBUNNY/repolens) for automatic architecture documentation.
|
|
70
|
+
|
|
71
|
+
## š What RepoLens Created
|
|
72
|
+
|
|
73
|
+
- \`.repolens.yml\` ā Configuration file
|
|
74
|
+
- \`.github/workflows/repolens.yml\` ā GitHub Actions workflow
|
|
75
|
+
- \`.env.example\` ā Environment variables template
|
|
76
|
+
- \`README.repolens.md\` ā This guide
|
|
77
|
+
|
|
78
|
+
## š Quick Start
|
|
79
|
+
|
|
80
|
+
### Local Testing
|
|
81
|
+
|
|
82
|
+
\`\`\`bash
|
|
83
|
+
# Test documentation generation locally
|
|
84
|
+
npx repolens publish
|
|
85
|
+
\`\`\`
|
|
86
|
+
|
|
87
|
+
### Notion Publishing
|
|
88
|
+
|
|
89
|
+
If you configured Notion credentials during setup, documentation will publish automatically. Otherwise:
|
|
90
|
+
|
|
91
|
+
1. Copy \`.env.example\` to \`.env\`
|
|
92
|
+
2. Add your credentials:
|
|
93
|
+
- \`NOTION_TOKEN\` ā Get from https://www.notion.so/my-integrations
|
|
94
|
+
- \`NOTION_PARENT_PAGE_ID\` ā The page where docs will be published
|
|
95
|
+
|
|
96
|
+
### GitHub Actions
|
|
97
|
+
|
|
98
|
+
For automated publishing on every push:
|
|
99
|
+
|
|
100
|
+
1. Go to repository Settings ā Secrets ā Actions
|
|
101
|
+
2. Add secrets:
|
|
102
|
+
- \`NOTION_TOKEN\`
|
|
103
|
+
- \`NOTION_PARENT_PAGE_ID\`
|
|
104
|
+
3. Push to main branch
|
|
105
|
+
|
|
106
|
+
## š Generated Documentation
|
|
107
|
+
|
|
108
|
+
RepoLens generates documentation in two modes:
|
|
109
|
+
|
|
110
|
+
### Deterministic Mode (Default - Free)
|
|
111
|
+
- **System Overview** ā Technical profile and stats
|
|
112
|
+
- **Module Catalog** ā Complete code inventory
|
|
113
|
+
- **API Surface** ā Internal endpoints + external integrations
|
|
114
|
+
- **Route Map** ā Application routes
|
|
115
|
+
- **System Map** ā Unicode architecture diagram
|
|
116
|
+
- **Architecture Diff** ā Change tracking
|
|
117
|
+
|
|
118
|
+
### AI-Enhanced Mode (Optional - Requires API Key)
|
|
119
|
+
Adds 5 natural language documents readable by non-technical audiences:
|
|
120
|
+
- **Executive Summary** ā Project overview for leadership
|
|
121
|
+
- **Business Domains** ā What the system does by function
|
|
122
|
+
- **Architecture Overview** ā Layered technical analysis
|
|
123
|
+
- **Data Flows** ā How information moves through system
|
|
124
|
+
- **Developer Onboarding** ā Getting started guide
|
|
125
|
+
|
|
126
|
+
## š¤ Enabling AI Features
|
|
127
|
+
|
|
128
|
+
AI features add natural language explanations for non-technical stakeholders.
|
|
129
|
+
|
|
130
|
+
1. Get an OpenAI API key from https://platform.openai.com/api-keys
|
|
131
|
+
2. Add to your \`.env\` file:
|
|
132
|
+
\`\`\`bash
|
|
133
|
+
REPOLENS_AI_ENABLED=true
|
|
134
|
+
REPOLENS_AI_API_KEY=sk-...
|
|
135
|
+
\`\`\`
|
|
136
|
+
3. (Optional) Configure in \`.repolens.yml\`:
|
|
137
|
+
\`\`\`yaml
|
|
138
|
+
ai:
|
|
139
|
+
enabled: true
|
|
140
|
+
mode: hybrid
|
|
141
|
+
temperature: 0.3
|
|
142
|
+
|
|
143
|
+
features:
|
|
144
|
+
executive_summary: true
|
|
145
|
+
business_domains: true
|
|
146
|
+
architecture_overview: true
|
|
147
|
+
data_flows: true
|
|
148
|
+
developer_onboarding: true
|
|
149
|
+
\`\`\`
|
|
150
|
+
|
|
151
|
+
**Cost estimate**: $0.10-$0.40 per run for typical projects
|
|
152
|
+
|
|
153
|
+
See [AI.md](https://github.com/CHAPIBUNNY/repolens/blob/main/AI.md) for full documentation
|
|
154
|
+
- **Module Catalog** ā Detected code modules
|
|
155
|
+
- **API Surface** ā REST API endpoints
|
|
156
|
+
- **Route Map** ā Application routes
|
|
157
|
+
- **System Map** ā Architecture diagram (Unicode ASCII art)
|
|
158
|
+
|
|
159
|
+
## āļø Configuration
|
|
160
|
+
|
|
161
|
+
Edit \`.repolens.yml\` to customize:
|
|
162
|
+
|
|
163
|
+
- Scan patterns and ignore rules
|
|
164
|
+
- Module detection roots
|
|
165
|
+
- Documentation page titles
|
|
166
|
+
- Publishing targets
|
|
167
|
+
|
|
168
|
+
## š Learn More
|
|
169
|
+
|
|
170
|
+
- [RepoLens GitHub](https://github.com/CHAPIBUNNY/repolens)
|
|
171
|
+
- [Configuration Docs](https://github.com/CHAPIBUNNY/repolens#configuration)
|
|
172
|
+
- [Troubleshooting](https://github.com/CHAPIBUNNY/repolens#troubleshooting)
|
|
173
|
+
`;
|
|
174
|
+
|
|
175
|
+
async function fileExists(filePath) {
|
|
176
|
+
try {
|
|
177
|
+
await fs.access(filePath);
|
|
178
|
+
return true;
|
|
179
|
+
} catch {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function dirExists(dirPath) {
|
|
185
|
+
try {
|
|
186
|
+
const stat = await fs.stat(dirPath);
|
|
187
|
+
return stat.isDirectory();
|
|
188
|
+
} catch {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function detectRepoStructure(repoRoot) {
|
|
194
|
+
const detectedRoots = [];
|
|
195
|
+
|
|
196
|
+
for (const relativePath of DETECTABLE_ROOTS) {
|
|
197
|
+
const absolutePath = path.join(repoRoot, relativePath);
|
|
198
|
+
if (await dirExists(absolutePath)) {
|
|
199
|
+
detectedRoots.push(relativePath);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return detectedRoots;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function buildIncludePatterns(detectedRoots) {
|
|
207
|
+
return detectedRoots.map((root) => `${root}/**/*.{ts,tsx,js,jsx,md}`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function buildIgnorePatterns() {
|
|
211
|
+
return [
|
|
212
|
+
"tools/repolens/**",
|
|
213
|
+
"node_modules/**",
|
|
214
|
+
".next/**",
|
|
215
|
+
"dist/**",
|
|
216
|
+
"build/**",
|
|
217
|
+
"out/**",
|
|
218
|
+
".git/**",
|
|
219
|
+
"coverage/**",
|
|
220
|
+
"public/**",
|
|
221
|
+
"**/*.test.*",
|
|
222
|
+
"**/*.spec.*",
|
|
223
|
+
"**/__tests__/**",
|
|
224
|
+
"**/__mocks__/**",
|
|
225
|
+
"**/generated/**",
|
|
226
|
+
"**/dist/**",
|
|
227
|
+
"**/esm/**",
|
|
228
|
+
"**/imp/**",
|
|
229
|
+
"**/types/**",
|
|
230
|
+
"**/load-testing/**"
|
|
231
|
+
];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function buildRepoLensConfig(projectName, detectedRoots) {
|
|
235
|
+
const includePatterns = buildIncludePatterns(detectedRoots);
|
|
236
|
+
const ignorePatterns = buildIgnorePatterns();
|
|
237
|
+
|
|
238
|
+
const lines = [
|
|
239
|
+
`configVersion: 1`,
|
|
240
|
+
``,
|
|
241
|
+
`project:`,
|
|
242
|
+
` name: "${projectName}"`,
|
|
243
|
+
` docs_title_prefix: "RepoLens"`,
|
|
244
|
+
``,
|
|
245
|
+
`publishers:`,
|
|
246
|
+
` - markdown # Always generate local Markdown files`,
|
|
247
|
+
` - notion # Auto-detected: publishes if NOTION_TOKEN is set`,
|
|
248
|
+
``,
|
|
249
|
+
`# Optional: Configure Notion publishing behavior`,
|
|
250
|
+
`# notion:`,
|
|
251
|
+
`# branches:`,
|
|
252
|
+
`# - main # Only publish from main branch`,
|
|
253
|
+
`# - staging # Or add specific branches`,
|
|
254
|
+
`# includeBranchInTitle: false # Clean titles without [branch-name]`,
|
|
255
|
+
``,
|
|
256
|
+
`# Optional: GitHub integration for SVG diagram hosting`,
|
|
257
|
+
`# github:`,
|
|
258
|
+
`# owner: "your-username"`,
|
|
259
|
+
`# repo: "your-repo-name"`,
|
|
260
|
+
``,
|
|
261
|
+
`scan:`,
|
|
262
|
+
` include:`
|
|
263
|
+
];
|
|
264
|
+
|
|
265
|
+
if (includePatterns.length) {
|
|
266
|
+
for (const pattern of includePatterns) {
|
|
267
|
+
lines.push(` - "${pattern}"`);
|
|
268
|
+
}
|
|
269
|
+
} else {
|
|
270
|
+
lines.push(` - "src/**/*.{ts,tsx,js,jsx,md}"`);
|
|
271
|
+
lines.push(` - "app/**/*.{ts,tsx,js,jsx,md}"`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
lines.push(``);
|
|
275
|
+
lines.push(` ignore:`);
|
|
276
|
+
|
|
277
|
+
for (const pattern of ignorePatterns) {
|
|
278
|
+
lines.push(` - "${pattern}"`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
lines.push(``);
|
|
282
|
+
lines.push(`module_roots:`);
|
|
283
|
+
|
|
284
|
+
if (detectedRoots.length) {
|
|
285
|
+
for (const root of detectedRoots) {
|
|
286
|
+
lines.push(` - "${root}"`);
|
|
287
|
+
}
|
|
288
|
+
} else {
|
|
289
|
+
lines.push(` - "app"`);
|
|
290
|
+
lines.push(` - "src/app"`);
|
|
291
|
+
lines.push(` - "components"`);
|
|
292
|
+
lines.push(` - "src/components"`);
|
|
293
|
+
lines.push(` - "lib"`);
|
|
294
|
+
lines.push(` - "src/lib"`);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
lines.push(``);
|
|
298
|
+
lines.push(`outputs:`);
|
|
299
|
+
lines.push(` pages:`);
|
|
300
|
+
lines.push(` - key: "system_overview"`);
|
|
301
|
+
lines.push(` title: "System Overview"`);
|
|
302
|
+
lines.push(` description: "High-level snapshot of the repo and what RepoLens detected."`);
|
|
303
|
+
lines.push(``);
|
|
304
|
+
lines.push(` - key: "module_catalog"`);
|
|
305
|
+
lines.push(` title: "Module Catalog"`);
|
|
306
|
+
lines.push(` description: "Auto-detected modules with file counts."`);
|
|
307
|
+
lines.push(``);
|
|
308
|
+
lines.push(` - key: "api_surface"`);
|
|
309
|
+
lines.push(` title: "API Surface"`);
|
|
310
|
+
lines.push(` description: "Auto-detected API routes/endpoints."`);
|
|
311
|
+
lines.push(``);
|
|
312
|
+
lines.push(` - key: "arch_diff"`);
|
|
313
|
+
lines.push(` title: "Architecture Diff"`);
|
|
314
|
+
lines.push(` description: "Reserved for PR/merge change summaries."`);
|
|
315
|
+
lines.push(``);
|
|
316
|
+
lines.push(` - key: "route_map"`);
|
|
317
|
+
lines.push(` title: "Route Map"`);
|
|
318
|
+
lines.push(` description: "Detected app routes and API routes."`);
|
|
319
|
+
lines.push(``);
|
|
320
|
+
lines.push(` - key: "system_map"`);
|
|
321
|
+
lines.push(` title: "System Map"`);
|
|
322
|
+
lines.push(` description: "Unicode architecture diagram of detected modules."`);
|
|
323
|
+
lines.push(``);
|
|
324
|
+
|
|
325
|
+
return lines.join("\n");
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function detectProjectName(repoRoot) {
|
|
329
|
+
return path.basename(repoRoot) || "my-project";
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
async function promptNotionCredentials() {
|
|
333
|
+
// Skip prompts in CI environments or test mode
|
|
334
|
+
const isCI = process.env.CI ||
|
|
335
|
+
process.env.GITHUB_ACTIONS ||
|
|
336
|
+
process.env.GITLAB_CI ||
|
|
337
|
+
process.env.CIRCLECI ||
|
|
338
|
+
process.env.JENKINS_HOME ||
|
|
339
|
+
process.env.CODEBUILD_BUILD_ID;
|
|
340
|
+
|
|
341
|
+
const isTest = process.env.NODE_ENV === 'test' || process.env.VITEST;
|
|
342
|
+
|
|
343
|
+
if (isCI) {
|
|
344
|
+
info("āļø Skipping interactive prompts (CI environment detected)");
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (isTest) {
|
|
349
|
+
// Skip silently in test mode
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const rl = createInterface({
|
|
354
|
+
input: process.stdin,
|
|
355
|
+
output: process.stdout
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
try {
|
|
359
|
+
info("\nš Notion Setup (optional)");
|
|
360
|
+
const useNotion = await rl.question("Would you like to publish to Notion? (Y/n): ");
|
|
361
|
+
|
|
362
|
+
if (useNotion.toLowerCase() === 'n') {
|
|
363
|
+
info("Skipping Notion setup. You can configure it later via environment variables.");
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
info("\nTo find your Notion Parent Page ID:");
|
|
368
|
+
info(" 1. Open the Notion page where you want docs published");
|
|
369
|
+
info(" 2. Copy the page URL (looks like: notion.so/workspace/abc123...)");
|
|
370
|
+
info(" 3. The page ID is the 32-character code at the end\n");
|
|
371
|
+
|
|
372
|
+
const parentPageId = await rl.question("NOTION_PARENT_PAGE_ID: ");
|
|
373
|
+
|
|
374
|
+
if (!parentPageId || parentPageId.trim() === '') {
|
|
375
|
+
warn("No parent page ID provided. Skipping Notion configuration.");
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
info("\nTo get your Notion Integration Token:");
|
|
380
|
+
info(" 1. Go to https://www.notion.so/my-integrations");
|
|
381
|
+
info(" 2. Create a new integration or use an existing one");
|
|
382
|
+
info(" 3. Copy the 'Internal Integration Token'");
|
|
383
|
+
info(" 4. Share the parent page with your integration\n");
|
|
384
|
+
|
|
385
|
+
const token = await rl.question("NOTION_TOKEN: ");
|
|
386
|
+
|
|
387
|
+
if (!token || token.trim() === '') {
|
|
388
|
+
warn("No token provided. Skipping Notion configuration.");
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return {
|
|
393
|
+
parentPageId: parentPageId.trim(),
|
|
394
|
+
token: token.trim()
|
|
395
|
+
};
|
|
396
|
+
} finally {
|
|
397
|
+
rl.close();
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
async function ensureEnvInGitignore(repoRoot) {
|
|
402
|
+
const gitignorePath = path.join(repoRoot, ".gitignore");
|
|
403
|
+
|
|
404
|
+
try {
|
|
405
|
+
let gitignoreContent = '';
|
|
406
|
+
|
|
407
|
+
if (await fileExists(gitignorePath)) {
|
|
408
|
+
gitignoreContent = await fs.readFile(gitignorePath, "utf8");
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Check if .env is already in .gitignore
|
|
412
|
+
const lines = gitignoreContent.split('\n');
|
|
413
|
+
const hasEnvEntry = lines.some(line => line.trim() === '.env');
|
|
414
|
+
|
|
415
|
+
if (!hasEnvEntry) {
|
|
416
|
+
// Add .env to .gitignore
|
|
417
|
+
const newContent = gitignoreContent.trim() + (gitignoreContent.trim() ? '\n' : '') + '.env\n';
|
|
418
|
+
await fs.writeFile(gitignorePath, newContent, "utf8");
|
|
419
|
+
info("Added .env to .gitignore");
|
|
420
|
+
}
|
|
421
|
+
} catch (error) {
|
|
422
|
+
warn(`Could not update .gitignore: ${error.message}`);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
export async function runInit(targetDir = process.cwd()) {
|
|
427
|
+
const repoRoot = path.resolve(targetDir);
|
|
428
|
+
|
|
429
|
+
// Ensure target directory exists
|
|
430
|
+
await fs.mkdir(repoRoot, { recursive: true });
|
|
431
|
+
|
|
432
|
+
// Prompt for Notion credentials interactively
|
|
433
|
+
const notionCredentials = await promptNotionCredentials();
|
|
434
|
+
|
|
435
|
+
const repolensConfigPath = path.join(repoRoot, ".repolens.yml");
|
|
436
|
+
const workflowDir = path.join(repoRoot, ".github", "workflows");
|
|
437
|
+
const workflowPath = path.join(workflowDir, "repolens.yml");
|
|
438
|
+
|
|
439
|
+
const envExamplePath = path.join(repoRoot, ".env.example");
|
|
440
|
+
const envPath = path.join(repoRoot, ".env");
|
|
441
|
+
const readmePath = path.join(repoRoot, "README.repolens.md");
|
|
442
|
+
|
|
443
|
+
const configExists = await fileExists(repolensConfigPath);
|
|
444
|
+
const workflowExists = await fileExists(workflowPath);
|
|
445
|
+
|
|
446
|
+
const envExampleExists = await fileExists(envExamplePath);
|
|
447
|
+
const envExists = await fileExists(envPath);
|
|
448
|
+
const readmeExists = await fileExists(readmePath);
|
|
449
|
+
|
|
450
|
+
const projectName = detectProjectName(repoRoot);
|
|
451
|
+
const detectedRoots = await detectRepoStructure(repoRoot);
|
|
452
|
+
const configContent = buildRepoLensConfig(projectName, detectedRoots);
|
|
453
|
+
|
|
454
|
+
info(`Detected project name: ${projectName}`);
|
|
455
|
+
|
|
456
|
+
if (detectedRoots.length) {
|
|
457
|
+
info(`Detected module roots:`);
|
|
458
|
+
for (const root of detectedRoots) {
|
|
459
|
+
info(` - ${root}`);
|
|
460
|
+
}
|
|
461
|
+
} else {
|
|
462
|
+
info(`No known roots detected. Falling back to default config.`);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (!configExists) {
|
|
466
|
+
await fs.writeFile(repolensConfigPath, configContent, "utf8");
|
|
467
|
+
info(`Created ${repolensConfigPath}`);
|
|
468
|
+
} else {
|
|
469
|
+
info(`Skipped existing ${repolensConfigPath}`);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
await fs.mkdir(workflowDir, { recursive: true });
|
|
473
|
+
|
|
474
|
+
if (!workflowExists) {
|
|
475
|
+
await fs.writeFile(workflowPath, DEFAULT_GITHUB_WORKFLOW, "utf8");
|
|
476
|
+
info(`Created ${workflowPath}`);
|
|
477
|
+
} else {
|
|
478
|
+
info(`Skipped existing ${workflowPath}`);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (!envExampleExists) {
|
|
482
|
+
await fs.writeFile(envExamplePath, DEFAULT_ENV_EXAMPLE, "utf8");
|
|
483
|
+
info(`Created ${envExamplePath}`);
|
|
484
|
+
} else {
|
|
485
|
+
info(`Skipped existing ${envExamplePath}`);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Create .env file with collected credentials
|
|
489
|
+
if (notionCredentials && !envExists) {
|
|
490
|
+
const envContent = `NOTION_TOKEN=${notionCredentials.token}
|
|
491
|
+
NOTION_PARENT_PAGE_ID=${notionCredentials.parentPageId}
|
|
492
|
+
NOTION_VERSION=2022-06-28
|
|
493
|
+
`;
|
|
494
|
+
await fs.writeFile(envPath, envContent, "utf8");
|
|
495
|
+
info(`ā
Created ${envPath} with your Notion credentials`);
|
|
496
|
+
|
|
497
|
+
// Ensure .env is in .gitignore
|
|
498
|
+
await ensureEnvInGitignore(repoRoot);
|
|
499
|
+
} else if (notionCredentials && envExists) {
|
|
500
|
+
warn(`Skipped existing ${envPath} - your credentials were not overwritten`);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (!readmeExists) {
|
|
504
|
+
await fs.writeFile(readmePath, DEFAULT_REPOLENS_README, "utf8");
|
|
505
|
+
info(`Created ${readmePath}`);
|
|
506
|
+
} else {
|
|
507
|
+
info(`Skipped existing ${readmePath}`);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
info("\n⨠RepoLens initialization complete!\n");
|
|
511
|
+
|
|
512
|
+
if (notionCredentials) {
|
|
513
|
+
info("š Notion publishing is ready!");
|
|
514
|
+
info(" Your credentials are stored in .env (gitignored)\n");
|
|
515
|
+
info("Next steps:");
|
|
516
|
+
info(" 1. Review .repolens.yml to customize your documentation");
|
|
517
|
+
info(" 2. Run 'npx repolens publish' to generate your first docs (deterministic mode)");
|
|
518
|
+
info(" 3. (Optional) Enable AI features by adding to .env:");
|
|
519
|
+
info(" REPOLENS_AI_ENABLED=true");
|
|
520
|
+
info(" REPOLENS_AI_API_KEY=sk-...");
|
|
521
|
+
info(" See AI.md for full guide: https://github.com/CHAPIBUNNY/repolens/blob/main/AI.md");
|
|
522
|
+
info(" 4. For GitHub Actions, add these repository secrets:");
|
|
523
|
+
info(" - NOTION_TOKEN");
|
|
524
|
+
info(" - NOTION_PARENT_PAGE_ID");
|
|
525
|
+
info(" - REPOLENS_AI_API_KEY (if using AI features)");
|
|
526
|
+
info(" 5. Commit the generated files (workflow will run automatically)");
|
|
527
|
+
} else {
|
|
528
|
+
info("Next steps:");
|
|
529
|
+
info(" 1. Review .repolens.yml to customize your documentation");
|
|
530
|
+
info(" 2. To enable Notion publishing:");
|
|
531
|
+
info(" - Copy .env.example to .env and add your credentials, OR");
|
|
532
|
+
info(" - Add GitHub secrets: NOTION_TOKEN, NOTION_PARENT_PAGE_ID");
|
|
533
|
+
info(" 3. (Optional) Enable AI features by adding to .env:");
|
|
534
|
+
info(" REPOLENS_AI_ENABLED=true");
|
|
535
|
+
info(" REPOLENS_AI_API_KEY=sk-...");
|
|
536
|
+
info(" See: https://github.com/CHAPIBUNNY/repolens/blob/main/AI.md");
|
|
537
|
+
info(" 4. Run 'npx repolens publish' to test locally");
|
|
538
|
+
info(" 5. Commit the generated files");
|
|
539
|
+
}
|
|
540
|
+
}
|