blytz 1.0.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Aryan Sharma
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 deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,60 @@
1
+ ## Description
2
+
3
+ diglet is a Node.js application. Add a brief description of its purpose and what problem it solves.
4
+
5
+ ## Installation
6
+
7
+ Follow these steps to install the project:
8
+
9
+ ```bash
10
+ npm install
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ You can run the following scripts:
16
+
17
+ - `npm start`
18
+
19
+ ## Dependencies
20
+
21
+ This project uses the following dependencies:
22
+
23
+ - @octokit/app
24
+ - @octokit/rest
25
+ - dotenv
26
+ - express
27
+
28
+ ## Folder Structure
29
+
30
+ Project structure:
31
+
32
+ ```
33
+ ├── .env
34
+ ├── .gitignore
35
+ ├── bin
36
+ │ └── cli.js
37
+ ├── LICENSE
38
+ ├── package-lock.json
39
+ ├── package.json
40
+ ├── README.md
41
+ ├── server
42
+ │ ├── analytics.js
43
+ │ ├── bot.js
44
+ │ ├── github.js
45
+ │ └── server.js
46
+ └── src
47
+ ├── fileTree.js
48
+ ├── index.js
49
+ ├── processReadme.js
50
+ ├── projectReader.js
51
+ └── template.js
52
+ ```
53
+
54
+ ## License
55
+
56
+ Add your license information here.
57
+
58
+ ## Built By
59
+
60
+ Built with ❤️ by @Aryan Sharma
package/bin/cli.js ADDED
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+
6
+ import processReadme from '../src/processReadme.js';
7
+
8
+ const args = process.argv.slice(2);
9
+ const shouldShowHelp = args.includes('--help') || args.includes('-h');
10
+ const shouldInit = args.includes('--init');
11
+ const shouldForce = args.includes('--force');
12
+ const shouldUpdate = args.length === 0 || args.includes('--update');
13
+ const hasAction = shouldUpdate || shouldInit || shouldForce;
14
+
15
+ if (shouldShowHelp) {
16
+ console.log('Usage: fixme [--update|--init|--force]');
17
+ process.exit(0);
18
+ }
19
+
20
+ if (!hasAction) {
21
+ console.log('Usage: fixme [--update|--init|--force]');
22
+ process.exit(0);
23
+ }
24
+
25
+ function buildFileTree(dirPath, depth = 0, maxDepth = 4) {
26
+ if (depth >= maxDepth) {
27
+ return {};
28
+ }
29
+
30
+ const tree = {};
31
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true })
32
+ .filter(entry => entry.name !== 'node_modules' && entry.name !== '.git')
33
+ .sort((left, right) => left.name.localeCompare(right.name));
34
+
35
+ for (const entry of entries) {
36
+ const entryPath = path.join(dirPath, entry.name);
37
+
38
+ if (entry.isDirectory()) {
39
+ tree[entry.name] = buildFileTree(entryPath, depth + 1, maxDepth);
40
+ } else {
41
+ tree[entry.name] = null;
42
+ }
43
+ }
44
+
45
+ return tree;
46
+ }
47
+
48
+ function collectDependencies(packageJson) {
49
+ return Object.keys(packageJson.dependencies || {}).sort();
50
+ }
51
+
52
+ function collectScripts(packageJson) {
53
+ const scripts = new Map();
54
+
55
+ for (const [name, command] of Object.entries(packageJson.scripts || {})) {
56
+ scripts.set(name, [{ package: '(root)', command }]);
57
+ }
58
+
59
+ return scripts;
60
+ }
61
+
62
+ console.log("Scanning for project files...");
63
+
64
+ const targetDir = process.cwd();
65
+ const readmePath = path.join(targetDir, 'README.md');
66
+ const packageJsonPath = path.join(targetDir, 'package.json');
67
+ const readmeExists = fs.existsSync(readmePath);
68
+
69
+ if (!readmeExists && !shouldInit && !shouldForce) {
70
+ console.error("Error: No README.md found in this directory. Try --init.");
71
+ process.exit(1);
72
+ }
73
+
74
+ if (readmeExists && shouldInit && !shouldForce) {
75
+ console.error("README.md already exists. Try --force.");
76
+ process.exit(1);
77
+ }
78
+
79
+ if (!fs.existsSync(packageJsonPath)) {
80
+ console.error("Error: No package.json found in this directory.");
81
+ process.exit(1);
82
+ }
83
+
84
+ console.log("Files found. Processing README...");
85
+
86
+ try {
87
+ if (shouldForce && readmeExists) {
88
+ fs.unlinkSync(readmePath);
89
+ }
90
+
91
+ // 1. Read the raw text of both files
92
+ const readmeContent = fs.existsSync(readmePath) ? fs.readFileSync(readmePath, 'utf-8') : '';
93
+ const packageJsonData = fs.readFileSync(packageJsonPath, 'utf-8');
94
+
95
+ // 2. Parse package.json to build the context object your engine expects
96
+ const packageJson = JSON.parse(packageJsonData);
97
+ const fileTree = buildFileTree(targetDir);
98
+ const context = {
99
+ packageJson,
100
+ packages: [{ path: 'package.json', content: packageJson }],
101
+ dependencies: collectDependencies(packageJson),
102
+ scripts: collectScripts(packageJson),
103
+ fileTree,
104
+ username: packageJson.author || 'Unknown Author',
105
+ projectName: packageJson.name || 'this project',
106
+ hasPackageJson: true,
107
+ isMonorepo: false
108
+ };
109
+
110
+ // 3. Feed everything into your pure engine
111
+ // Hardcoding 'node' as projectType for now since we rely on package.json
112
+ const updatedReadme = processReadme(readmeContent, 'node', context);
113
+
114
+ // 4. Overwrite the existing README.md with the new content
115
+ fs.writeFileSync(readmePath, updatedReadme, 'utf-8');
116
+
117
+ console.log("Success! README.md has been auto-fixed.");
118
+
119
+ } catch (error) {
120
+ console.error("An error occurred during processing:", error.message);
121
+ process.exit(1);
122
+ }
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "blytz",
3
+ "version": "1.0.0",
4
+ "bin": {
5
+ "blytz": "./bin/cli.js"
6
+ },
7
+ "description": "An automated CLI tool to fix and maintain project READMEs",
8
+ "type": "module",
9
+ "scripts": {
10
+ "start": "node src/index.js"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/AryanSharma48/readme-auto-fixer.git"
15
+ },
16
+ "keywords": [
17
+ "cli",
18
+ "readme",
19
+ "automation",
20
+ "github",
21
+ "bot",
22
+ "readmefixer",
23
+ "readmeauto",
24
+ "readme-maintainer",
25
+ "readme-updater",
26
+ "readme-helper",
27
+ "readme-fixer",
28
+ "readme-bot",
29
+ "readme-automation",
30
+ "readme-enhancer",
31
+ "readme-improver"
32
+ ],
33
+ "author": "Aryan Sharma",
34
+ "license": "MIT",
35
+ "bugs": {
36
+ "url": "https://github.com/AryanSharma48/readme-auto-fixer/issues"
37
+ },
38
+ "homepage": "https://github.com/AryanSharma48/readme-auto-fixer#readme",
39
+ "dependencies": {
40
+ "@octokit/app": "^16.1.2",
41
+ "@octokit/rest": "^22.0.1",
42
+ "dotenv": "^17.4.0",
43
+ "express": "^5.2.1"
44
+ }
45
+ }
@@ -0,0 +1,99 @@
1
+ import fs from "fs/promises";
2
+ import path from "path";
3
+ import { fileURLToPath } from "url";
4
+
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+ const DATA_FILE = path.join(__dirname, "../data/analytics.json");
7
+
8
+ const DEFAULT_DATA = {
9
+ installs: [],
10
+ events: []
11
+ };
12
+
13
+ async function ensureDataDir() {
14
+ const dir = path.dirname(DATA_FILE);
15
+ try {
16
+ await fs.mkdir(dir, { recursive: true });
17
+ } catch (err) {
18
+ if (err.code !== "EEXIST") throw err;
19
+ }
20
+ }
21
+
22
+ async function readData() {
23
+ try {
24
+ const raw = await fs.readFile(DATA_FILE, "utf-8");
25
+ return JSON.parse(raw);
26
+ } catch (err) {
27
+ if (err.code === "ENOENT") {
28
+ return { ...DEFAULT_DATA };
29
+ }
30
+ if (err instanceof SyntaxError) {
31
+ console.error("Invalid JSON in analytics file, resetting...");
32
+ return { ...DEFAULT_DATA };
33
+ }
34
+ throw err;
35
+ }
36
+ }
37
+
38
+ async function writeData(data) {
39
+ await ensureDataDir();
40
+ await fs.writeFile(DATA_FILE, JSON.stringify(data, null, 2));
41
+ }
42
+
43
+ export async function trackInstall(installationId, accountLogin, accountType) {
44
+ const data = await readData();
45
+
46
+ const exists = data.installs.some(i => i.installationId === installationId);
47
+ if (!exists) {
48
+ data.installs.push({
49
+ installationId,
50
+ accountLogin,
51
+ accountType,
52
+ installedAt: new Date().toISOString()
53
+ });
54
+ await writeData(data);
55
+ }
56
+
57
+ return !exists;
58
+ }
59
+
60
+ export async function trackUninstall(installationId) {
61
+ const data = await readData();
62
+ const initialLength = data.installs.length;
63
+
64
+ data.installs = data.installs.filter(i => i.installationId !== installationId);
65
+
66
+ if (data.installs.length !== initialLength) {
67
+ await writeData(data);
68
+ return true;
69
+ }
70
+ return false;
71
+ }
72
+
73
+ export async function trackEvent(eventType, payload = {}) {
74
+ const data = await readData();
75
+
76
+ data.events.push({
77
+ type: eventType,
78
+ payload,
79
+ timestamp: new Date().toISOString()
80
+ });
81
+
82
+ await writeData(data);
83
+ }
84
+
85
+ export async function getStats() {
86
+ const data = await readData();
87
+
88
+ const eventCounts = data.events.reduce((acc, e) => {
89
+ acc[e.type] = (acc[e.type] || 0) + 1;
90
+ return acc;
91
+ }, {});
92
+
93
+ return {
94
+ totalInstalls: data.installs.length,
95
+ totalEvents: data.events.length,
96
+ eventsByType: eventCounts,
97
+ recentEvents: data.events.slice(-10).reverse()
98
+ };
99
+ }
package/server/bot.js ADDED
@@ -0,0 +1,210 @@
1
+ import { getOctokit } from "./github.js";
2
+ import processReadme from "../src/processReadme.js";
3
+
4
+ async function findAllPackageJsonPaths(octokit, owner, repo) {
5
+ try {
6
+ const { data: repoData } = await octokit.request("GET /repos/{owner}/{repo}", {
7
+ owner,
8
+ repo,
9
+ });
10
+ const defaultBranch = repoData.default_branch;
11
+
12
+ const { data: treeData } = await octokit.request("GET /repos/{owner}/{repo}/git/trees/{tree_sha}", {
13
+ owner,
14
+ repo,
15
+ tree_sha: defaultBranch,
16
+ recursive: "true",
17
+ });
18
+
19
+ const packageJsonPaths = treeData.tree
20
+ .filter(item =>
21
+ item.type === "blob" &&
22
+ item.path.endsWith("package.json") &&
23
+ !item.path.includes("node_modules/")
24
+ )
25
+ .map(item => item.path);
26
+
27
+ return { packageJsonPaths, treeData };
28
+ } catch (err) {
29
+ console.error("Error scanning repository tree:", err.message);
30
+ return { packageJsonPaths: [], treeData: null };
31
+ }
32
+ }
33
+
34
+ async function fetchPackageJson(octokit, owner, repo, path) {
35
+ try {
36
+ const { data } = await octokit.request("GET /repos/{owner}/{repo}/contents/{path}", {
37
+ owner,
38
+ repo,
39
+ path,
40
+ });
41
+ const content = JSON.parse(Buffer.from(data.content, "base64").toString());
42
+ return { path, content, error: null };
43
+ } catch (err) {
44
+ console.warn(`Failed to fetch/parse ${path}:`, err.message);
45
+ return { path, content: null, error: err.message };
46
+ }
47
+ }
48
+
49
+ async function fetchAllPackages(octokit, owner, repo) {
50
+ const { packageJsonPaths, treeData } = await findAllPackageJsonPaths(octokit, owner, repo);
51
+ const fileTree = treeData ? buildFileTree(treeData) : null;
52
+
53
+ if (packageJsonPaths.length === 0) {
54
+ return { packages: [], fileTree };
55
+ }
56
+
57
+ const results = await Promise.all(
58
+ packageJsonPaths.map(path => fetchPackageJson(octokit, owner, repo, path))
59
+ );
60
+
61
+ const packages = results.filter(pkg => pkg.content !== null);
62
+ return { packages, fileTree };
63
+ }
64
+
65
+ function aggregateDependencies(packages) {
66
+ const ignoreList = [
67
+ 'eslint', 'prettier', 'husky', 'lint-staged', 'stylelint',
68
+ 'typescript', 'vite', 'webpack', 'rollup', 'parcel', 'esbuild',
69
+ 'babel', 'tsc', 'ts-node', 'tsx', 'nodemon', 'concurrently',
70
+ 'jest', 'mocha', 'chai', 'vitest', 'cypress', 'playwright', 'supertest',
71
+ 'postcss', 'autoprefixer', 'sass', 'less', 'dotenv', 'cross-env', 'rimraf',
72
+ 'black', 'flake8', 'pylint', 'mypy', 'isort', 'autopep8', 'bandit',
73
+ 'pytest', 'coverage', 'tox', 'mock', 'hypothesis', 'pytest-cov',
74
+ 'setuptools', 'wheel', 'twine', 'build',
75
+ 'python-dotenv', 'pip-tools', 'virtualenv', 'pre-commit'
76
+ ];
77
+
78
+ const depsSet = new Set();
79
+
80
+ for (const pkg of packages) {
81
+ const { content } = pkg;
82
+ if (content?.dependencies) {
83
+ Object.keys(content.dependencies).forEach(dep => {
84
+ if (dep.startsWith('@types/')) return;
85
+ if (dep.startsWith('@babel/')) return;
86
+ if (dep.startsWith('@vitejs/')) return;
87
+ if (dep.startsWith('types-')) return;
88
+ if (ignoreList.includes(dep)) return;
89
+ depsSet.add(dep);
90
+ });
91
+ }
92
+ }
93
+
94
+ return Array.from(depsSet).sort();
95
+ }
96
+
97
+ function buildFileTree(treeData, maxDepth = 4) {
98
+ const fileTree = {};
99
+
100
+ if (!treeData?.tree || !Array.isArray(treeData.tree)) {
101
+ return fileTree;
102
+ }
103
+
104
+ for (const item of treeData.tree) {
105
+ if (
106
+ item.path === "node_modules" ||
107
+ item.path.startsWith("node_modules/") ||
108
+ item.path === ".git" ||
109
+ item.path.startsWith(".git/")
110
+ ) {
111
+ continue;
112
+ }
113
+
114
+ const parts = item.path.split("/");
115
+
116
+ if (parts.length > maxDepth) {
117
+ continue;
118
+ }
119
+
120
+ let current = fileTree;
121
+
122
+ for (let i = 0; i < parts.length; i++) {
123
+ const part = parts[i];
124
+ const isFile = i === parts.length - 1 && item.type === "blob";
125
+
126
+ if (isFile) {
127
+ current[part] = null;
128
+ } else {
129
+ if (!current[part]) {
130
+ current[part] = {};
131
+ }
132
+ current = current[part];
133
+ }
134
+ }
135
+ }
136
+
137
+ return fileTree;
138
+ }
139
+
140
+ function aggregateScripts(packages) {
141
+ const scriptsMap = new Map();
142
+
143
+ for (const pkg of packages) {
144
+ const { path, content } = pkg;
145
+ const packageDir = path === "package.json" ? "(root)" : path.replace("/package.json", "");
146
+
147
+ if (content?.scripts) {
148
+ for (const [name, command] of Object.entries(content.scripts)) {
149
+ if (!scriptsMap.has(name)) {
150
+ scriptsMap.set(name, []);
151
+ }
152
+ scriptsMap.get(name).push({ package: packageDir, command });
153
+ }
154
+ }
155
+ }
156
+
157
+ return scriptsMap;
158
+ }
159
+
160
+ export async function runBot(payload) {
161
+ try {
162
+ const installationId = payload.installation.id;
163
+ const owner = payload.repository.owner.login;
164
+ const repo = payload.repository.name;
165
+
166
+ const octokit = await getOctokit(installationId);
167
+
168
+ const { data } = await octokit.request("GET /repos/{owner}/{repo}/contents/{path}", {
169
+ owner,
170
+ repo,
171
+ path: "README.md",
172
+ });
173
+
174
+ const content = Buffer.from(data.content, "base64").toString();
175
+ const { packages, fileTree } = await fetchAllPackages(octokit, owner, repo);
176
+ const dependencies = aggregateDependencies(packages);
177
+ const scripts = aggregateScripts(packages);
178
+ const projectType = packages.length > 0 ? "node" : "unknown";
179
+
180
+ const context = {
181
+ packages,
182
+ dependencies,
183
+ scripts,
184
+ fileTree,
185
+ username: owner,
186
+ isMonorepo: packages.length > 1,
187
+ };
188
+
189
+ const newReadme = processReadme(content, projectType, context);
190
+
191
+ if (newReadme === content) {
192
+ console.log("No changes needed");
193
+ return;
194
+ }
195
+
196
+ await octokit.request("PUT /repos/{owner}/{repo}/contents/{path}", {
197
+ owner,
198
+ repo,
199
+ path: "README.md",
200
+ message: "Auto-update README",
201
+ content: Buffer.from(newReadme).toString("base64"),
202
+ sha: data.sha,
203
+ });
204
+
205
+ console.log("README updated successfully");
206
+
207
+ } catch (err) {
208
+ console.error("Bot error:", err.message);
209
+ }
210
+ }
@@ -0,0 +1,11 @@
1
+ import { App } from "@octokit/app";
2
+
3
+ const app = new App({
4
+ appId: process.env.APP_ID,
5
+ privateKey: process.env.PRIVATE_KEY.replace(/\\n/g, '\n'),
6
+ });
7
+
8
+ export async function getOctokit(installationId) {
9
+ const octokit = await app.getInstallationOctokit(installationId);
10
+ return octokit;
11
+ }
@@ -0,0 +1,67 @@
1
+ import express from "express";
2
+ import dotenv from "dotenv";
3
+ import { runBot } from "./bot.js";
4
+ import { trackInstall, trackUninstall, trackEvent, getStats } from "./analytics.js";
5
+
6
+ dotenv.config();
7
+
8
+ const app = express();
9
+ app.use(express.json());
10
+
11
+ app.get("/health", (req, res) => {
12
+ res.json({ status: "ok" });
13
+ });
14
+
15
+ app.get("/stats", async (req, res) => {
16
+ try {
17
+ const stats = await getStats();
18
+ res.json(stats);
19
+ } catch (err) {
20
+ console.error("Stats error:", err.message);
21
+ res.status(500).json({ error: "Failed to fetch stats" });
22
+ }
23
+ });
24
+
25
+ app.post("/webhook", async (req, res) => {
26
+ const event = req.headers["x-github-event"];
27
+
28
+ try {
29
+ if (event === "installation") {
30
+ const { action, installation } = req.body;
31
+
32
+ if (action === "created") {
33
+ await trackInstall(
34
+ installation.id,
35
+ installation.account.login,
36
+ installation.account.type
37
+ );
38
+ await trackEvent("installation", { action, installationId: installation.id });
39
+ } else if (action === "deleted") {
40
+ await trackUninstall(installation.id);
41
+ await trackEvent("installation", { action, installationId: installation.id });
42
+ }
43
+ }
44
+
45
+ if (event === "push") {
46
+ console.log("Push event received");
47
+
48
+ const { repository, installation } = req.body;
49
+ await trackEvent("push", {
50
+ repo: repository.full_name,
51
+ installationId: installation.id
52
+ });
53
+
54
+ await runBot(req.body);
55
+ }
56
+ } catch (err) {
57
+ console.error("Webhook error:", err.message);
58
+ }
59
+
60
+ res.sendStatus(200);
61
+ });
62
+
63
+ const PORT = process.env.PORT || 3000;
64
+
65
+ app.listen(PORT, () => {
66
+ console.log(`Server running on port ${PORT}`);
67
+ });
@@ -0,0 +1,26 @@
1
+ // recursively builds a tree string from a file tree object
2
+ function generateTree(fileTree, prefix = "") {
3
+ if (!fileTree || typeof fileTree !== "object") return "";
4
+
5
+ const entries = Object.entries(fileTree);
6
+ let tree = "";
7
+
8
+ entries.forEach(([name, value], index) => {
9
+ if (name === "node_modules" || name === ".git") return;
10
+
11
+ const isLast = index === entries.length - 1;
12
+ const connector = isLast ? "└── " : "├── ";
13
+ tree += `${prefix}${connector}${name}\n`;
14
+
15
+ // recurse into directories
16
+ if (value && typeof value === "object") {
17
+ tree += generateTree(value, prefix + (isLast ? " " : "│ "));
18
+ }
19
+ });
20
+
21
+ return tree;
22
+ }
23
+
24
+ export default function getProjectStructure(fileTree) {
25
+ return "```\n" + generateTree(fileTree) + "```";
26
+ }
package/src/index.js ADDED
@@ -0,0 +1,73 @@
1
+ import getDefaultContent from "./template.js";
2
+ import { detectProjectType } from "./projectReader.js";
3
+
4
+ export function generateReadme(input = {}) {
5
+ const {
6
+ readmeContent = "",
7
+ packageJson = null,
8
+ fileTree = null,
9
+ username = "Unknown",
10
+ projectName = null,
11
+ hasPackageJson = false,
12
+ hasRequirementsTxt = false
13
+ } = input ?? {};
14
+
15
+ const resolvedProjectName = projectName || packageJson?.name || "this project";
16
+
17
+ const context = {
18
+ packageJson,
19
+ fileTree,
20
+ username,
21
+ projectName: resolvedProjectName,
22
+ hasPackageJson,
23
+ hasRequirementsTxt
24
+ };
25
+
26
+ const projectType = detectProjectType(context);
27
+
28
+ // parse existing readme into sections
29
+ const sections = (readmeContent || "").split("## ");
30
+ const sectionMap = {};
31
+ const intro = sections[0].trim();
32
+
33
+ sections.slice(1).forEach(section => {
34
+ const lines = section.split("\n");
35
+ const title = lines[0].trim().toLowerCase();
36
+ const content = lines.slice(1).join("\n").trim();
37
+ sectionMap[title] = content;
38
+ });
39
+
40
+ const requiredSections = ["description", "installation", "usage", "dependencies", "folder structure", "built by"];
41
+ const autoManagedSections = ["installation", "usage", "dependencies", "folder structure"];
42
+
43
+ // fill in missing sections or update auto-managed ones
44
+ requiredSections.forEach(section => {
45
+ const newContent = getDefaultContent(section, projectType, context);
46
+ if (!(section in sectionMap)) {
47
+ sectionMap[section] = newContent;
48
+ } else if (autoManagedSections.includes(section) && sectionMap[section].trim() !== newContent.trim()) {
49
+ sectionMap[section] = newContent;
50
+ }
51
+ });
52
+
53
+ const formatTitle = title => title.split(" ").map(w => w[0].toUpperCase() + w.slice(1)).join(" ");
54
+
55
+ // build the final readme
56
+ let newReadme = intro ? intro + "\n\n" : "";
57
+ requiredSections.forEach(section => {
58
+ newReadme += `## ${formatTitle(section)}\n${sectionMap[section]}\n\n`;
59
+ });
60
+
61
+ // append any extra sections the user had
62
+ Object.keys(sectionMap).forEach(section => {
63
+ if (!requiredSections.includes(section)) {
64
+ newReadme += `## ${formatTitle(section)}\n\n ${sectionMap[section]}\n\n`;
65
+ }
66
+ });
67
+
68
+ return { content: newReadme, changed: readmeContent !== newReadme };
69
+ }
70
+
71
+ export { detectProjectType } from "./projectReader.js";
72
+ export { default as getDefaultContent } from "./template.js";
73
+ export { default as getProjectStructure } from "./fileTree.js";
@@ -0,0 +1,83 @@
1
+ import getDefaultContent from "./template.js";
2
+
3
+
4
+ //Core engine: processes README content and returns updated version
5
+ export default function processReadme(content, projectType, context = {}) {
6
+
7
+ const normalizedContent = (content || "").replace(/^##(?=\S)/gm, "## ");
8
+
9
+ // Split into sections
10
+ const sections = normalizedContent.split("## ");
11
+ const sectionMap = {};
12
+
13
+ // Extract intro (title + description)
14
+ const intro = sections[0].trim();
15
+
16
+ // Parse sections
17
+ sections.slice(1).forEach(section => {
18
+ const lines = section.split("\n");
19
+ const title = lines[0].trim().toLowerCase();
20
+ const body = lines.slice(1).join("\n").trim();
21
+
22
+ sectionMap[title] = body;
23
+ });
24
+
25
+ // Required sections
26
+ const requiredSections = [
27
+ "description",
28
+ "installation",
29
+ "usage",
30
+ "dependencies",
31
+ "folder structure",
32
+ "license",
33
+ "built by"
34
+ ];
35
+
36
+ // Auto-managed sections (safe to update)
37
+ const autoManaged = [
38
+ "installation",
39
+ "usage",
40
+ "dependencies",
41
+ "folder structure"
42
+ ];
43
+
44
+ // Diff function
45
+ const isDifferent = (a = "", b = "") => a.trim() !== b.trim();
46
+
47
+ // Add / update sections
48
+ requiredSections.forEach(section => {
49
+ const newContent = getDefaultContent(section, projectType, context);
50
+ const currentContent = (sectionMap[section] || "").trim();
51
+
52
+ if (!currentContent) {
53
+ sectionMap[section] = newContent;
54
+ } else if (autoManaged.includes(section)) {
55
+ if (isDifferent(currentContent, newContent)) {
56
+ sectionMap[section] = newContent;
57
+ }
58
+ }
59
+ });
60
+
61
+ // Format title
62
+ const formatTitle = (title) =>
63
+ title.split(" ")
64
+ .map(word => word[0].toUpperCase() + word.slice(1))
65
+ .join(" ");
66
+
67
+ // Rebuild README
68
+ let newReadme = intro ? intro + "\n\n" : "";
69
+
70
+ // Ordered sections
71
+ requiredSections.forEach(section => {
72
+ newReadme += `## ${formatTitle(section)}\n\n${sectionMap[section]}\n\n`;
73
+ });
74
+
75
+ // Extra sections (preserve user content)
76
+ Object.keys(sectionMap).forEach(section => {
77
+ if (!requiredSections.includes(section)) {
78
+ newReadme += `## ${formatTitle(section)}\n\n${sectionMap[section]}\n\n`;
79
+ }
80
+ });
81
+
82
+ return newReadme.trim();
83
+ }
@@ -0,0 +1,5 @@
1
+ export function detectProjectType(context) {
2
+ if (context?.hasPackageJson) return "node";
3
+ if (context?.hasRequirementsTxt) return "python";
4
+ return "unknown";
5
+ }
@@ -0,0 +1,113 @@
1
+ import getProjectStructure from "./fileTree.js";
2
+
3
+ export default function getDefaultContent(section, projectType, context = {}) {
4
+ const {
5
+ packages = [],
6
+ dependencies = [],
7
+ scripts = new Map(),
8
+ fileTree = null,
9
+ username = "Unknown",
10
+ projectName = "this project",
11
+ isMonorepo = false,
12
+ } = context ?? {};
13
+
14
+ const safeProjectType = projectType || "unknown";
15
+ const safeSection = (section || "").toLowerCase().trim();
16
+
17
+ switch (safeSection) {
18
+ case "description":
19
+ return getDescriptionContent(safeProjectType, projectName, isMonorepo);
20
+ case "installation":
21
+ return getInstallationContent(safeProjectType, packages, isMonorepo);
22
+ case "usage":
23
+ return getUsageContent(safeProjectType, scripts, isMonorepo);
24
+ case "dependencies":
25
+ return getDependenciesContent(dependencies, packages);
26
+ case "folder structure":
27
+ return getFolderStructureContent(fileTree);
28
+ case "license":
29
+ return "Add your license information here.";
30
+ case "built by":
31
+ return `Built with ❤️ by @${(username || "Unknown").trim()}`;
32
+ default:
33
+ return "";
34
+ }
35
+ }
36
+
37
+ function getDescriptionContent(projectType, projectName, isMonorepo) {
38
+ const name = projectName || "this project";
39
+ if (projectType === "node") {
40
+ if (isMonorepo) {
41
+ return `${name} is a Node.js monorepo containing multiple packages. Add a brief description of its purpose and what problem it solves.`;
42
+ }
43
+ return `${name} is a Node.js application. Add a brief description of its purpose and what problem it solves.`;
44
+ }
45
+ if (projectType === "python") return `${name} is a Python project. Add a brief description of its purpose and what problem it solves.`;
46
+ return `${name} - Add a brief description of your project, its purpose, and what problem it solves.`;
47
+ }
48
+
49
+ function getInstallationContent(projectType, packages, isMonorepo) {
50
+ if (projectType === "node") {
51
+ if (isMonorepo && packages.length > 1) {
52
+ const packageList = packages
53
+ .map(pkg => {
54
+ const dir = pkg.path === "package.json" ? "(root)" : pkg.path.replace("/package.json", "");
55
+ return `- \`${dir}\``;
56
+ })
57
+ .join("\n");
58
+
59
+ return `This is a monorepo with multiple packages:\n\n${packageList}\n\nTo install all dependencies:\n\n\`\`\`bash\n# Install root dependencies\nnpm install\n\n# Or install dependencies in each package\n${packages.map(pkg => {
60
+ const dir = pkg.path === "package.json" ? "." : pkg.path.replace("/package.json", "");
61
+ return `cd ${dir} && npm install`;
62
+ }).join("\n")}\n\`\`\``;
63
+ }
64
+ return "Follow these steps to install the project:\n\n```bash\nnpm install\n```";
65
+ }
66
+ if (projectType === "python") return "Install dependencies using:\n\n```bash\npip install -r requirements.txt\n```";
67
+ return "Add installation instructions here.";
68
+ }
69
+
70
+ function getUsageContent(projectType, scripts, isMonorepo) {
71
+ if (projectType === "node") {
72
+ if (scripts instanceof Map && scripts.size > 0) {
73
+ const scriptEntries = [];
74
+
75
+ for (const [name, locations] of scripts) {
76
+ if (isMonorepo && locations.length > 1) {
77
+ const packageNames = locations.map(l => l.package).join(", ");
78
+ scriptEntries.push(`- \`npm run ${name}\` (available in: ${packageNames})`);
79
+ } else if (locations.length === 1) {
80
+ const prefix = isMonorepo ? ` (in ${locations[0].package})` : "";
81
+ const cmd = name === "start" ? "npm start" : `npm run ${name}`;
82
+ scriptEntries.push(`- \`${cmd}\`${prefix}`);
83
+ } else {
84
+ scriptEntries.push(`- \`npm run ${name}\``);
85
+ }
86
+ }
87
+
88
+ return `You can run the following scripts:\n\n${scriptEntries.join("\n")}`;
89
+ }
90
+ return "Run the project using:\n\n```bash\nnpm start\n```";
91
+ }
92
+ if (projectType === "python") return "Run the project using:\n\n```bash\npython main.py\n```";
93
+ return "Add usage instructions here.";
94
+ }
95
+
96
+ function getDependenciesContent(dependencies, packages) {
97
+ if (dependencies && dependencies.length > 0) {
98
+ const isMonorepo = packages && packages.length > 1;
99
+ const header = isMonorepo
100
+ ? `This project uses the following dependencies (across ${packages.length} packages):\n\n`
101
+ : "This project uses the following dependencies:\n\n";
102
+
103
+ return header + dependencies.map(d => `- ${d}`).join("\n");
104
+ }
105
+ return "No dependencies found.";
106
+ }
107
+
108
+ function getFolderStructureContent(fileTree) {
109
+ if (!fileTree || typeof fileTree !== "object" || Object.keys(fileTree).length === 0) {
110
+ return "Project structure:\n\n```\n(No file tree provided)\n```";
111
+ }
112
+ return "Project structure:\n\n" + getProjectStructure(fileTree);
113
+ }