blytz 1.0.4 → 1.1.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/bin/cli.js +115 -44
- package/package.json +49 -49
- package/src/processReadme.js +8 -6
- package/src/template.js +40 -27
package/bin/cli.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import fs from 'fs';
|
|
4
4
|
import path from 'path';
|
|
5
|
+
import readline from 'readline/promises';
|
|
5
6
|
|
|
6
7
|
import processReadme from '../src/processReadme.js';
|
|
7
8
|
|
|
@@ -49,6 +50,18 @@ function collectDependencies(packageJson) {
|
|
|
49
50
|
return Object.keys(packageJson.dependencies || {}).sort();
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
function collectPythonDependencies(requirementsContent) {
|
|
54
|
+
return requirementsContent
|
|
55
|
+
.split(/\r?\n/)
|
|
56
|
+
.map(line => line.trim())
|
|
57
|
+
.filter(line => line && !line.startsWith('#'))
|
|
58
|
+
.filter(line => !line.startsWith('-'))
|
|
59
|
+
.map(line => line.split(';')[0].trim())
|
|
60
|
+
.map(line => line.split(/[=<>!~]/)[0].trim())
|
|
61
|
+
.filter(Boolean)
|
|
62
|
+
.sort((left, right) => left.localeCompare(right));
|
|
63
|
+
}
|
|
64
|
+
|
|
52
65
|
function collectScripts(packageJson) {
|
|
53
66
|
const scripts = new Map();
|
|
54
67
|
|
|
@@ -59,64 +72,122 @@ function collectScripts(packageJson) {
|
|
|
59
72
|
return scripts;
|
|
60
73
|
}
|
|
61
74
|
|
|
62
|
-
|
|
75
|
+
function getLicenseName(licenseContent) {
|
|
76
|
+
const firstLine = licenseContent
|
|
77
|
+
.split(/\r?\n/)
|
|
78
|
+
.map(line => line.trim())
|
|
79
|
+
.find(Boolean);
|
|
63
80
|
|
|
64
|
-
|
|
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);
|
|
81
|
+
return firstLine || '';
|
|
72
82
|
}
|
|
73
83
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
84
|
+
async function promptForTitleAndDescription() {
|
|
85
|
+
const rl = readline.createInterface({
|
|
86
|
+
input: process.stdin,
|
|
87
|
+
output: process.stdout,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const titleContent = (await rl.question('Add title: ')).trim();
|
|
92
|
+
const descriptionContent = (await rl.question('Add description: ')).trim();
|
|
93
|
+
return { titleContent, descriptionContent };
|
|
94
|
+
} finally {
|
|
95
|
+
rl.close();
|
|
96
|
+
}
|
|
77
97
|
}
|
|
78
98
|
|
|
79
|
-
|
|
80
|
-
console.
|
|
81
|
-
|
|
82
|
-
|
|
99
|
+
async function main() {
|
|
100
|
+
console.log("Scanning for project files...");
|
|
101
|
+
|
|
102
|
+
const targetDir = process.cwd();
|
|
103
|
+
const readmePath = path.join(targetDir, 'README.md');
|
|
104
|
+
const packageJsonPath = path.join(targetDir, 'package.json');
|
|
105
|
+
const requirementsPath = path.join(targetDir, 'requirements.txt');
|
|
106
|
+
const licensePath = path.join(targetDir, 'LICENSE');
|
|
107
|
+
const readmeExists = fs.existsSync(readmePath);
|
|
108
|
+
const hasPackageJson = fs.existsSync(packageJsonPath);
|
|
109
|
+
const hasRequirements = fs.existsSync(requirementsPath);
|
|
110
|
+
const hasLicense = fs.existsSync(licensePath);
|
|
111
|
+
|
|
112
|
+
if (!readmeExists && !shouldInit && !shouldForce) {
|
|
113
|
+
console.error("Error: No README.md found in this directory. Try --init.");
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
83
116
|
|
|
84
|
-
|
|
117
|
+
if (readmeExists && shouldInit && !shouldForce) {
|
|
118
|
+
console.error("README.md already exists. Try --force.");
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!hasPackageJson && !hasRequirements) {
|
|
123
|
+
console.error("Error: No package.json or requirements.txt found in this directory.");
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
console.log("Files found. Processing README...");
|
|
85
128
|
|
|
86
|
-
try {
|
|
87
129
|
if (shouldForce && readmeExists) {
|
|
88
130
|
fs.unlinkSync(readmePath);
|
|
89
131
|
}
|
|
90
132
|
|
|
91
|
-
// 1. Read the raw text of both files
|
|
92
133
|
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
134
|
const fileTree = buildFileTree(targetDir);
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
135
|
+
const projectName = path.basename(targetDir);
|
|
136
|
+
const licenseName = hasLicense ? getLicenseName(fs.readFileSync(licensePath, 'utf-8')) : '';
|
|
137
|
+
const shouldPromptMetadata = !shouldUpdate;
|
|
138
|
+
const { titleContent, descriptionContent } = shouldPromptMetadata
|
|
139
|
+
? await promptForTitleAndDescription()
|
|
140
|
+
: { titleContent: '', descriptionContent: '' };
|
|
141
|
+
let context;
|
|
142
|
+
let projectType;
|
|
143
|
+
|
|
144
|
+
if (hasPackageJson) {
|
|
145
|
+
const packageJsonData = fs.readFileSync(packageJsonPath, 'utf-8');
|
|
146
|
+
const packageJson = JSON.parse(packageJsonData);
|
|
147
|
+
|
|
148
|
+
context = {
|
|
149
|
+
packageJson,
|
|
150
|
+
packages: [{ path: 'package.json', content: packageJson }],
|
|
151
|
+
dependencies: collectDependencies(packageJson),
|
|
152
|
+
scripts: collectScripts(packageJson),
|
|
153
|
+
fileTree,
|
|
154
|
+
titleContent,
|
|
155
|
+
descriptionContent,
|
|
156
|
+
licenseName,
|
|
157
|
+
username: packageJson.author || process.env.USERNAME || 'Unknown Author',
|
|
158
|
+
projectName: packageJson.name || projectName,
|
|
159
|
+
hasPackageJson: true,
|
|
160
|
+
isMonorepo: false
|
|
161
|
+
};
|
|
162
|
+
projectType = 'node';
|
|
163
|
+
} else {
|
|
164
|
+
const requirementsContent = fs.readFileSync(requirementsPath, 'utf-8');
|
|
165
|
+
|
|
166
|
+
context = {
|
|
167
|
+
packages: [{ path: 'requirements.txt', content: requirementsContent }],
|
|
168
|
+
dependencies: collectPythonDependencies(requirementsContent),
|
|
169
|
+
scripts: new Map(),
|
|
170
|
+
fileTree,
|
|
171
|
+
titleContent,
|
|
172
|
+
descriptionContent,
|
|
173
|
+
licenseName,
|
|
174
|
+
username: process.env.USERNAME || 'Unknown Author',
|
|
175
|
+
projectName,
|
|
176
|
+
hasPackageJson: false,
|
|
177
|
+
isMonorepo: false
|
|
178
|
+
};
|
|
179
|
+
projectType = 'python';
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const updatedReadme = processReadme(readmeContent, projectType, context);
|
|
183
|
+
|
|
115
184
|
fs.writeFileSync(readmePath, updatedReadme, 'utf-8');
|
|
116
185
|
|
|
117
186
|
console.log("Success! README.md has been auto-fixed.");
|
|
187
|
+
}
|
|
118
188
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
189
|
+
(main()
|
|
190
|
+
.catch(error => {
|
|
191
|
+
console.error("An error occurred during processing:", error.message);
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}));
|
package/package.json
CHANGED
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "blytz",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"bin": {
|
|
5
|
-
"blytz": "./bin/cli.js"
|
|
6
|
-
},
|
|
7
|
-
"readme": "README-NPM.md",
|
|
8
|
-
"files": [
|
|
9
|
-
"bin/",
|
|
10
|
-
"src/",
|
|
11
|
-
"bin/README-NPM.md"
|
|
12
|
-
],
|
|
13
|
-
"description": "An automated CLI tool to fix and maintain project READMEs",
|
|
14
|
-
"type": "module",
|
|
15
|
-
"scripts": {
|
|
16
|
-
"start": "node src/index.js",
|
|
17
|
-
"prepublishOnly": "node scripts/sync-readme.js prepublish",
|
|
18
|
-
"postpublish": "node scripts/sync-readme.js postpublish"
|
|
19
|
-
},
|
|
20
|
-
"keywords": [
|
|
21
|
-
"blytz",
|
|
22
|
-
"cli",
|
|
23
|
-
"readme",
|
|
24
|
-
"documentation",
|
|
25
|
-
"automation",
|
|
26
|
-
"github",
|
|
27
|
-
"npm",
|
|
28
|
-
"bot",
|
|
29
|
-
"readmefixer",
|
|
30
|
-
"readme-generator",
|
|
31
|
-
"devtools",
|
|
32
|
-
"markdown",
|
|
33
|
-
"workflow",
|
|
34
|
-
"productivity",
|
|
35
|
-
"automated-docs",
|
|
36
|
-
"repository-manager",
|
|
37
|
-
"scaffold",
|
|
38
|
-
"readme-maintainer",
|
|
39
|
-
"readme-updater"
|
|
40
|
-
],
|
|
41
|
-
"author": "Aryan Sharma",
|
|
42
|
-
"license": "MIT",
|
|
43
|
-
"dependencies": {
|
|
44
|
-
"@octokit/app": "^16.1.2",
|
|
45
|
-
"@octokit/rest": "^22.0.1",
|
|
46
|
-
"dotenv": "^17.4.0",
|
|
47
|
-
"express": "^5.2.1"
|
|
48
|
-
}
|
|
49
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "blytz",
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"bin": {
|
|
5
|
+
"blytz": "./bin/cli.js"
|
|
6
|
+
},
|
|
7
|
+
"readme": "README-NPM.md",
|
|
8
|
+
"files": [
|
|
9
|
+
"bin/",
|
|
10
|
+
"src/",
|
|
11
|
+
"bin/README-NPM.md"
|
|
12
|
+
],
|
|
13
|
+
"description": "An automated CLI tool to fix and maintain project READMEs",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"scripts": {
|
|
16
|
+
"start": "node src/index.js",
|
|
17
|
+
"prepublishOnly": "node scripts/sync-readme.js prepublish",
|
|
18
|
+
"postpublish": "node scripts/sync-readme.js postpublish"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"blytz",
|
|
22
|
+
"cli",
|
|
23
|
+
"readme",
|
|
24
|
+
"documentation",
|
|
25
|
+
"automation",
|
|
26
|
+
"github",
|
|
27
|
+
"npm",
|
|
28
|
+
"bot",
|
|
29
|
+
"readmefixer",
|
|
30
|
+
"readme-generator",
|
|
31
|
+
"devtools",
|
|
32
|
+
"markdown",
|
|
33
|
+
"workflow",
|
|
34
|
+
"productivity",
|
|
35
|
+
"automated-docs",
|
|
36
|
+
"repository-manager",
|
|
37
|
+
"scaffold",
|
|
38
|
+
"readme-maintainer",
|
|
39
|
+
"readme-updater"
|
|
40
|
+
],
|
|
41
|
+
"author": "Aryan Sharma",
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@octokit/app": "^16.1.2",
|
|
45
|
+
"@octokit/rest": "^22.0.1",
|
|
46
|
+
"dotenv": "^17.4.0",
|
|
47
|
+
"express": "^5.2.1"
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/processReadme.js
CHANGED
|
@@ -2,16 +2,18 @@ import getDefaultContent from "./template.js";
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
//Core engine: processes README content and returns updated version
|
|
5
|
-
export default function processReadme(content, projectType, context = {}) {
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
export default function processReadme(content, projectType, context = {}) {
|
|
6
|
+
const { titleContent = "" } = context ?? {};
|
|
7
|
+
|
|
8
|
+
const normalizedContent = (content || "").replace(/^##(?=\S)/gm, "## ");
|
|
8
9
|
|
|
9
10
|
// Split into sections
|
|
10
11
|
const sections = normalizedContent.split("## ");
|
|
11
12
|
const sectionMap = {};
|
|
12
13
|
|
|
13
14
|
// Extract intro (title + description)
|
|
14
|
-
const intro = sections[0].trim();
|
|
15
|
+
const intro = sections[0].trim();
|
|
16
|
+
const finalIntro = titleContent ? `# ${titleContent}` : intro;
|
|
15
17
|
|
|
16
18
|
// Parse sections
|
|
17
19
|
sections.slice(1).forEach(section => {
|
|
@@ -65,7 +67,7 @@ export default function processReadme(content, projectType, context = {}) {
|
|
|
65
67
|
.join(" ");
|
|
66
68
|
|
|
67
69
|
// Rebuild README
|
|
68
|
-
let newReadme =
|
|
70
|
+
let newReadme = finalIntro ? finalIntro + "\n\n" : "";
|
|
69
71
|
|
|
70
72
|
// Ordered sections
|
|
71
73
|
requiredSections.forEach(section => {
|
|
@@ -80,4 +82,4 @@ export default function processReadme(content, projectType, context = {}) {
|
|
|
80
82
|
});
|
|
81
83
|
|
|
82
84
|
return newReadme.trim();
|
|
83
|
-
}
|
|
85
|
+
}
|
package/src/template.js
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
import getProjectStructure from "./fileTree.js";
|
|
2
2
|
|
|
3
3
|
export default function getDefaultContent(section, projectType, context = {}) {
|
|
4
|
-
const {
|
|
5
|
-
packages = [],
|
|
6
|
-
dependencies = [],
|
|
7
|
-
scripts = new Map(),
|
|
8
|
-
fileTree = null,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
const {
|
|
5
|
+
packages = [],
|
|
6
|
+
dependencies = [],
|
|
7
|
+
scripts = new Map(),
|
|
8
|
+
fileTree = null,
|
|
9
|
+
descriptionContent = "",
|
|
10
|
+
licenseName = "",
|
|
11
|
+
username = "Unknown",
|
|
12
|
+
projectName = "this project",
|
|
13
|
+
isMonorepo = false,
|
|
14
|
+
} = context ?? {};
|
|
13
15
|
|
|
14
16
|
const safeProjectType = projectType || "unknown";
|
|
15
17
|
const safeSection = (section || "").toLowerCase().trim();
|
|
16
18
|
|
|
17
|
-
switch (safeSection) {
|
|
18
|
-
case "description":
|
|
19
|
-
return getDescriptionContent(safeProjectType, projectName, isMonorepo);
|
|
19
|
+
switch (safeSection) {
|
|
20
|
+
case "description":
|
|
21
|
+
return getDescriptionContent(safeProjectType, projectName, isMonorepo, descriptionContent);
|
|
20
22
|
case "installation":
|
|
21
23
|
return getInstallationContent(safeProjectType, packages, isMonorepo);
|
|
22
24
|
case "usage":
|
|
@@ -25,8 +27,8 @@ export default function getDefaultContent(section, projectType, context = {}) {
|
|
|
25
27
|
return getDependenciesContent(dependencies, packages);
|
|
26
28
|
case "folder structure":
|
|
27
29
|
return getFolderStructureContent(fileTree);
|
|
28
|
-
case "license":
|
|
29
|
-
return
|
|
30
|
+
case "license":
|
|
31
|
+
return getLicenseContent(licenseName);
|
|
30
32
|
case "built by":
|
|
31
33
|
return `Built with ❤️ by @${(username || "Unknown").trim()}`;
|
|
32
34
|
default:
|
|
@@ -34,8 +36,12 @@ export default function getDefaultContent(section, projectType, context = {}) {
|
|
|
34
36
|
}
|
|
35
37
|
}
|
|
36
38
|
|
|
37
|
-
function getDescriptionContent(projectType, projectName, isMonorepo) {
|
|
38
|
-
|
|
39
|
+
function getDescriptionContent(projectType, projectName, isMonorepo, descriptionContent) {
|
|
40
|
+
if (descriptionContent) {
|
|
41
|
+
return descriptionContent;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const name = projectName || "this project";
|
|
39
45
|
if (projectType === "node") {
|
|
40
46
|
if (isMonorepo) {
|
|
41
47
|
return `${name} is a Node.js monorepo containing multiple packages. Add a brief description of its purpose and what problem it solves.`;
|
|
@@ -93,21 +99,28 @@ function getUsageContent(projectType, scripts, isMonorepo) {
|
|
|
93
99
|
return "Add usage instructions here.";
|
|
94
100
|
}
|
|
95
101
|
|
|
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`
|
|
102
|
+
function getDependenciesContent(dependencies, packages) {
|
|
103
|
+
if (dependencies && dependencies.length > 0) {
|
|
104
|
+
const isMonorepo = packages && packages.length > 1;
|
|
105
|
+
const header = isMonorepo
|
|
106
|
+
? `This project uses the following dependencies (across ${packages.length} packages):\n\n`
|
|
101
107
|
: "This project uses the following dependencies:\n\n";
|
|
102
108
|
|
|
103
109
|
return header + dependencies.map(d => `- ${d}`).join("\n");
|
|
104
|
-
}
|
|
105
|
-
return "No dependencies found.";
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function
|
|
110
|
+
}
|
|
111
|
+
return "No dependencies found.";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function getLicenseContent(licenseName) {
|
|
115
|
+
if (licenseName) {
|
|
116
|
+
return `This project is licensed under the ${licenseName}. See the LICENSE file for details.`;
|
|
117
|
+
}
|
|
118
|
+
return "Add your license information here.";
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function getFolderStructureContent(fileTree) {
|
|
109
122
|
if (!fileTree || typeof fileTree !== "object" || Object.keys(fileTree).length === 0) {
|
|
110
123
|
return "Project structure:\n\n```\n(No file tree provided)\n```";
|
|
111
124
|
}
|
|
112
125
|
return "Project structure:\n\n" + getProjectStructure(fileTree);
|
|
113
|
-
}
|
|
126
|
+
}
|