blytz 1.0.0 → 1.0.2
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/README.md +105 -60
- package/bin/README-NPM.md +105 -0
- package/bin/cli.js +2 -2
- package/package.json +22 -10
- package/server/analytics.js +0 -99
- package/server/bot.js +0 -210
- package/server/github.js +0 -11
- package/server/server.js +0 -67
package/README.md
CHANGED
|
@@ -1,60 +1,105 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
##
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
1
|
+
# blytz
|
|
2
|
+
|
|
3
|
+
`blytz` is a small Node.js CLI that creates and updates a project's `README.md` from local project metadata.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
- Updates an existing `README.md`
|
|
8
|
+
- Creates a new `README.md` with `--init`
|
|
9
|
+
- Replaces an existing `README.md` with `--force`
|
|
10
|
+
- Reads `package.json` to build README sections automatically
|
|
11
|
+
- Generates a basic folder tree for the project structure section
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g blytz
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
If you are developing locally from this repository, you can link it instead:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm link
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
Run the CLI from the root of the project you want to document:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
blytz
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
You can also use the available flags:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
blytz --update
|
|
37
|
+
blytz --init
|
|
38
|
+
blytz --force
|
|
39
|
+
blytz --help
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Commands
|
|
43
|
+
|
|
44
|
+
### `blytz`
|
|
45
|
+
|
|
46
|
+
Updates the existing `README.md` in the current directory.
|
|
47
|
+
|
|
48
|
+
### `blytz --update`
|
|
49
|
+
|
|
50
|
+
Same as `blytz`. Refreshes the current `README.md` using the project metadata it finds.
|
|
51
|
+
|
|
52
|
+
### `blytz --init`
|
|
53
|
+
|
|
54
|
+
Creates a new `README.md` only if one does not already exist.
|
|
55
|
+
|
|
56
|
+
If a README already exists, the CLI stops and suggests using `--force`.
|
|
57
|
+
|
|
58
|
+
### `blytz --force`
|
|
59
|
+
|
|
60
|
+
Deletes the existing `README.md` and creates a fresh one.
|
|
61
|
+
|
|
62
|
+
### `blytz --help`
|
|
63
|
+
|
|
64
|
+
Prints the usage summary.
|
|
65
|
+
|
|
66
|
+
## Output sections
|
|
67
|
+
|
|
68
|
+
The generated README is built from these sections:
|
|
69
|
+
|
|
70
|
+
- Description
|
|
71
|
+
- Installation
|
|
72
|
+
- Usage
|
|
73
|
+
- Dependencies
|
|
74
|
+
- Folder Structure
|
|
75
|
+
- License
|
|
76
|
+
- Built By
|
|
77
|
+
|
|
78
|
+
## Project requirements
|
|
79
|
+
|
|
80
|
+
For best results, include the following in your `package.json`:
|
|
81
|
+
|
|
82
|
+
- `name`
|
|
83
|
+
- `description`
|
|
84
|
+
- `author`
|
|
85
|
+
- `scripts`
|
|
86
|
+
- `dependencies`
|
|
87
|
+
|
|
88
|
+
The more metadata the CLI can read, the better the generated README will be.
|
|
89
|
+
|
|
90
|
+
## Example
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
cd my-project
|
|
94
|
+
blytz
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Notes
|
|
98
|
+
|
|
99
|
+
- The CLI expects to run in a folder that contains `package.json`
|
|
100
|
+
- The CLI reads the local `README.md` in the current directory
|
|
101
|
+
- The folder tree excludes `node_modules` and `.git`
|
|
102
|
+
|
|
103
|
+
## License
|
|
104
|
+
|
|
105
|
+
MIT
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# blytz
|
|
2
|
+
|
|
3
|
+
`blytz` is a small Node.js CLI that creates and updates a project's `README.md` from local project metadata.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
- Updates an existing `README.md`
|
|
8
|
+
- Creates a new `README.md` with `--init`
|
|
9
|
+
- Replaces an existing `README.md` with `--force`
|
|
10
|
+
- Reads `package.json` to build README sections automatically
|
|
11
|
+
- Generates a basic folder tree for the project structure section
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g blytz
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
If you are developing locally from this repository, you can link it instead:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm link
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
Run the CLI from the root of the project you want to document:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
blytz
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
You can also use the available flags:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
blytz --update
|
|
37
|
+
blytz --init
|
|
38
|
+
blytz --force
|
|
39
|
+
blytz --help
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Commands
|
|
43
|
+
|
|
44
|
+
### `blytz`
|
|
45
|
+
|
|
46
|
+
Updates the existing `README.md` in the current directory.
|
|
47
|
+
|
|
48
|
+
### `blytz --update`
|
|
49
|
+
|
|
50
|
+
Same as `blytz`. Refreshes the current `README.md` using the project metadata it finds.
|
|
51
|
+
|
|
52
|
+
### `blytz --init`
|
|
53
|
+
|
|
54
|
+
Creates a new `README.md` only if one does not already exist.
|
|
55
|
+
|
|
56
|
+
If a README already exists, the CLI stops and suggests using `--force`.
|
|
57
|
+
|
|
58
|
+
### `blytz --force`
|
|
59
|
+
|
|
60
|
+
Deletes the existing `README.md` and creates a fresh one.
|
|
61
|
+
|
|
62
|
+
### `blytz --help`
|
|
63
|
+
|
|
64
|
+
Prints the usage summary.
|
|
65
|
+
|
|
66
|
+
## Output sections
|
|
67
|
+
|
|
68
|
+
The generated README is built from these sections:
|
|
69
|
+
|
|
70
|
+
- Description
|
|
71
|
+
- Installation
|
|
72
|
+
- Usage
|
|
73
|
+
- Dependencies
|
|
74
|
+
- Folder Structure
|
|
75
|
+
- License
|
|
76
|
+
- Built By
|
|
77
|
+
|
|
78
|
+
## Project requirements
|
|
79
|
+
|
|
80
|
+
For best results, include the following in your `package.json`:
|
|
81
|
+
|
|
82
|
+
- `name`
|
|
83
|
+
- `description`
|
|
84
|
+
- `author`
|
|
85
|
+
- `scripts`
|
|
86
|
+
- `dependencies`
|
|
87
|
+
|
|
88
|
+
The more metadata the CLI can read, the better the generated README will be.
|
|
89
|
+
|
|
90
|
+
## Example
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
cd my-project
|
|
94
|
+
blytz
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Notes
|
|
98
|
+
|
|
99
|
+
- The CLI expects to run in a folder that contains `package.json`
|
|
100
|
+
- The CLI reads the local `README.md` in the current directory
|
|
101
|
+
- The folder tree excludes `node_modules` and `.git`
|
|
102
|
+
|
|
103
|
+
## License
|
|
104
|
+
|
|
105
|
+
MIT
|
package/bin/cli.js
CHANGED
|
@@ -13,12 +13,12 @@ const shouldUpdate = args.length === 0 || args.includes('--update');
|
|
|
13
13
|
const hasAction = shouldUpdate || shouldInit || shouldForce;
|
|
14
14
|
|
|
15
15
|
if (shouldShowHelp) {
|
|
16
|
-
console.log('Usage:
|
|
16
|
+
console.log('Usage: blytz [--update|--init|--force]');
|
|
17
17
|
process.exit(0);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
if (!hasAction) {
|
|
21
|
-
console.log('Usage:
|
|
21
|
+
console.log('Usage: blytz [--update|--init|--force]');
|
|
22
22
|
process.exit(0);
|
|
23
23
|
}
|
|
24
24
|
|
package/package.json
CHANGED
|
@@ -1,34 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blytz",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"bin": {
|
|
5
5
|
"blytz": "./bin/cli.js"
|
|
6
6
|
},
|
|
7
|
+
"readme": "README-NPM.md",
|
|
8
|
+
"files": [
|
|
9
|
+
"bin/",
|
|
10
|
+
"src/",
|
|
11
|
+
"bin/README-NPM.md"
|
|
12
|
+
],
|
|
7
13
|
"description": "An automated CLI tool to fix and maintain project READMEs",
|
|
8
14
|
"type": "module",
|
|
9
15
|
"scripts": {
|
|
10
|
-
"start": "node src/index.js"
|
|
16
|
+
"start": "node src/index.js",
|
|
17
|
+
"prepublishOnly": "node scripts/sync-readme.js prepublish",
|
|
18
|
+
"postpublish": "node scripts/sync-readme.js postpublish"
|
|
11
19
|
},
|
|
12
20
|
"repository": {
|
|
13
21
|
"type": "git",
|
|
14
22
|
"url": "git+https://github.com/AryanSharma48/readme-auto-fixer.git"
|
|
15
23
|
},
|
|
16
24
|
"keywords": [
|
|
25
|
+
"blytz",
|
|
17
26
|
"cli",
|
|
18
27
|
"readme",
|
|
28
|
+
"documentation",
|
|
19
29
|
"automation",
|
|
20
30
|
"github",
|
|
31
|
+
"npm",
|
|
21
32
|
"bot",
|
|
22
33
|
"readmefixer",
|
|
23
|
-
"
|
|
34
|
+
"readme-generator",
|
|
35
|
+
"devtools",
|
|
36
|
+
"markdown",
|
|
37
|
+
"workflow",
|
|
38
|
+
"productivity",
|
|
39
|
+
"automated-docs",
|
|
40
|
+
"repository-manager",
|
|
41
|
+
"scaffold",
|
|
24
42
|
"readme-maintainer",
|
|
25
|
-
"readme-updater"
|
|
26
|
-
"readme-helper",
|
|
27
|
-
"readme-fixer",
|
|
28
|
-
"readme-bot",
|
|
29
|
-
"readme-automation",
|
|
30
|
-
"readme-enhancer",
|
|
31
|
-
"readme-improver"
|
|
43
|
+
"readme-updater"
|
|
32
44
|
],
|
|
33
45
|
"author": "Aryan Sharma",
|
|
34
46
|
"license": "MIT",
|
package/server/analytics.js
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,210 +0,0 @@
|
|
|
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
|
-
}
|
package/server/github.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
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
|
-
}
|
package/server/server.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
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
|
-
});
|