blytz 1.1.0 → 1.2.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/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  - Updates an existing `README.md`
8
8
  - Creates a new `README.md` with `--init`
9
9
  - Replaces an existing `README.md` with `--force`
10
- - Reads `package.json` to build README sections automatically
10
+ - Reads `package.json` / `requirements.txt` to build README sections automatically
11
11
  - Generates a basic folder tree for the project structure section
12
12
 
13
13
  ## Install
@@ -16,12 +16,6 @@
16
16
  npm install -g blytz
17
17
  ```
18
18
 
19
- If you are developing locally from this repository, you can link it instead:
20
-
21
- ```bash
22
- npm link
23
- ```
24
-
25
19
  ## Usage
26
20
 
27
21
  Run the CLI from the root of the project you want to document:
@@ -96,9 +90,9 @@ blytz
96
90
 
97
91
  ## Notes
98
92
 
99
- - The CLI expects to run in a folder that contains `package.json`
93
+ - The CLI expects to run in a folder that contains `package.json` or `requirements.txt`
100
94
  - The CLI reads the local `README.md` in the current directory
101
- - The folder tree excludes `node_modules` and `.git`
95
+ - The folder tree excludes `node_modules` and `.git` and any folder that might be in `.gitignore`
102
96
 
103
97
  ## License
104
98
 
package/bin/README-NPM.md CHANGED
@@ -7,7 +7,7 @@
7
7
  - Updates an existing `README.md`
8
8
  - Creates a new `README.md` with `--init`
9
9
  - Replaces an existing `README.md` with `--force`
10
- - Reads `package.json` to build README sections automatically
10
+ - Reads `package.json` / `requirements.txt` to build README sections automatically
11
11
  - Generates a basic folder tree for the project structure section
12
12
 
13
13
  ## Install
@@ -16,12 +16,6 @@
16
16
  npm install -g blytz
17
17
  ```
18
18
 
19
- If you are developing locally from this repository, you can link it instead:
20
-
21
- ```bash
22
- npm link
23
- ```
24
-
25
19
  ## Usage
26
20
 
27
21
  Run the CLI from the root of the project you want to document:
@@ -96,9 +90,9 @@ blytz
96
90
 
97
91
  ## Notes
98
92
 
99
- - The CLI expects to run in a folder that contains `package.json`
93
+ - The CLI expects to run in a folder that contains `package.json` or `requirements.txt`
100
94
  - The CLI reads the local `README.md` in the current directory
101
- - The folder tree excludes `node_modules` and `.git`
95
+ - The folder tree excludes `node_modules` and `.git` and any folder that might be in `.gitignore`
102
96
 
103
97
  ## License
104
98
 
package/bin/cli.js CHANGED
@@ -2,8 +2,16 @@
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';
8
+ import {
9
+ buildLocalFileTree,
10
+ collectNodeDependencies,
11
+ collectPythonDependencies,
12
+ collectScripts,
13
+ getLicenseName
14
+ } from '../src/readmeContext.js';
7
15
 
8
16
  const args = process.argv.slice(2);
9
17
  const shouldShowHelp = args.includes('--help') || args.includes('-h');
@@ -12,113 +20,118 @@ const shouldForce = args.includes('--force');
12
20
  const shouldUpdate = args.length === 0 || args.includes('--update');
13
21
  const hasAction = shouldUpdate || shouldInit || shouldForce;
14
22
 
15
- if (shouldShowHelp) {
16
- console.log('Usage: blytz [--update|--init|--force]');
17
- process.exit(0);
18
- }
19
-
20
- if (!hasAction) {
21
- console.log('Usage: blytz [--update|--init|--force]');
22
- process.exit(0);
23
- }
24
-
25
- function buildFileTree(dirPath, depth = 0, maxDepth = 4) {
26
- if (depth >= maxDepth) {
27
- return {};
23
+ const supportsColor = process.stdout.isTTY && process.env.NO_COLOR !== '1';
24
+ const ANSI = {
25
+ reset: '\x1b[0m',
26
+ bold: '\x1b[1m',
27
+ dim: '\x1b[2m',
28
+ cyan: '\x1b[36m',
29
+ green: '\x1b[32m',
30
+ yellow: '\x1b[33m',
31
+ red: '\x1b[31m',
32
+ blue: '\x1b[34m'
33
+ };
34
+
35
+ function colorize(text, ...codes) {
36
+ if (!supportsColor) {
37
+ return text;
28
38
  }
29
39
 
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;
40
+ return `${codes.join('')}${text}${ANSI.reset}`;
46
41
  }
47
42
 
48
- function collectDependencies(packageJson) {
49
- return Object.keys(packageJson.dependencies || {}).sort();
43
+ function formatLabel(label, color) {
44
+ return colorize(label, ANSI.bold, color);
50
45
  }
51
46
 
52
- function collectPythonDependencies(requirementsContent) {
53
- return requirementsContent
54
- .split(/\r?\n/)
55
- .map(line => line.trim())
56
- .filter(line => line && !line.startsWith('#'))
57
- .filter(line => !line.startsWith('-'))
58
- .map(line => line.split(';')[0].trim())
59
- .map(line => line.split(/[=<>!~]/)[0].trim())
60
- .filter(Boolean)
61
- .sort((left, right) => left.localeCompare(right));
47
+ function printHelp(output = console.log) {
48
+ output([
49
+ formatLabel('Usage', ANSI.blue),
50
+ ` ${colorize('blytz', ANSI.bold, ANSI.green)} ${colorize('[command]', ANSI.dim)}`,
51
+ '',
52
+ formatLabel('Commands', ANSI.blue),
53
+ ` ${colorize('--update', ANSI.yellow)} ${colorize('Update the existing README.md using project metadata.', ANSI.dim)}`,
54
+ ` ${colorize('--init', ANSI.yellow)} ${colorize('Create a new README.md and prompt for title and description.', ANSI.dim)}`,
55
+ ` ${colorize('--force', ANSI.yellow)} ${colorize('Replace the existing README.md with a newly generated one.', ANSI.dim)}`,
56
+ ` ${colorize('--help, -h', ANSI.yellow)} ${colorize('Show this help message.', ANSI.dim)}`,
57
+ '',
58
+ ].join('\n'));
62
59
  }
63
60
 
64
- function collectScripts(packageJson) {
65
- const scripts = new Map();
61
+ if (shouldShowHelp) {
62
+ printHelp();
63
+ process.exit(0);
64
+ }
66
65
 
67
- for (const [name, command] of Object.entries(packageJson.scripts || {})) {
68
- scripts.set(name, [{ package: '(root)', command }]);
66
+ if (!hasAction) {
67
+ if (args.length > 0) {
68
+ const invalidCommand = args.find(arg => arg.startsWith('-')) || args[0];
69
+ console.error(`${formatLabel('Error', ANSI.red)} Command not available: ${colorize(invalidCommand, ANSI.bold, ANSI.red)}`);
70
+ console.error('');
71
+ printHelp(console.error);
72
+ process.exit(1);
69
73
  }
70
-
71
- return scripts;
74
+ printHelp();
75
+ process.exit(0);
72
76
  }
73
77
 
74
- function getLicenseName(licenseContent) {
75
- const firstLine = licenseContent
76
- .split(/\r?\n/)
77
- .map(line => line.trim())
78
- .find(Boolean);
79
-
80
- return firstLine || '';
78
+ async function promptForTitleAndDescription() {
79
+ const rl = readline.createInterface({
80
+ input: process.stdin,
81
+ output: process.stdout,
82
+ });
83
+
84
+ try {
85
+ const titleContent = (await rl.question(`${formatLabel('Title', ANSI.yellow)} ${colorize('(leave blank to use project name)', ANSI.dim)}: `)).trim();
86
+ const descriptionContent = (await rl.question(`${formatLabel('Description', ANSI.yellow)} ${colorize('(leave blank to use default intro)', ANSI.dim)}: `)).trim();
87
+ return { titleContent, descriptionContent };
88
+ } finally {
89
+ rl.close();
90
+ }
81
91
  }
82
92
 
83
- console.log("Scanning for project files...");
84
-
85
- const targetDir = process.cwd();
86
- const readmePath = path.join(targetDir, 'README.md');
87
- const packageJsonPath = path.join(targetDir, 'package.json');
88
- const requirementsPath = path.join(targetDir, 'requirements.txt');
89
- const licensePath = path.join(targetDir, 'LICENSE');
90
- const readmeExists = fs.existsSync(readmePath);
91
- const hasPackageJson = fs.existsSync(packageJsonPath);
92
- const hasRequirements = fs.existsSync(requirementsPath);
93
- const hasLicense = fs.existsSync(licensePath);
94
-
95
- if (!readmeExists && !shouldInit && !shouldForce) {
96
- console.error("Error: No README.md found in this directory. Try --init.");
97
- process.exit(1);
98
- }
93
+ async function main() {
94
+ console.log(`${formatLabel('Info', ANSI.cyan)} Scanning for project files...`);
95
+
96
+ const targetDir = process.cwd();
97
+ const readmePath = path.join(targetDir, 'README.md');
98
+ const packageJsonPath = path.join(targetDir, 'package.json');
99
+ const requirementsPath = path.join(targetDir, 'requirements.txt');
100
+ const licensePath = path.join(targetDir, 'LICENSE');
101
+ const readmeExists = fs.existsSync(readmePath);
102
+ const hasPackageJson = fs.existsSync(packageJsonPath);
103
+ const hasRequirements = fs.existsSync(requirementsPath);
104
+ const hasLicense = fs.existsSync(licensePath);
105
+
106
+ if (!readmeExists && !shouldInit && !shouldForce) {
107
+ console.error(`${formatLabel('Error', ANSI.red)} No README.md found in this directory. Try ${colorize('--init', ANSI.bold, ANSI.yellow)}.`);
108
+ process.exit(1);
109
+ }
99
110
 
100
- if (readmeExists && shouldInit && !shouldForce) {
101
- console.error("README.md already exists. Try --force.");
102
- process.exit(1);
103
- }
111
+ if (readmeExists && shouldInit && !shouldForce) {
112
+ console.error(`${formatLabel('Error', ANSI.red)} README.md already exists. Try ${colorize('--force', ANSI.bold, ANSI.red)}.`);
113
+ process.exit(1);
114
+ }
104
115
 
105
- if (!hasPackageJson && !hasRequirements) {
106
- console.error("Error: No package.json or requirements.txt found in this directory.");
107
- process.exit(1);
108
- }
116
+ if (!hasPackageJson && !hasRequirements) {
117
+ console.error(`${formatLabel('Error', ANSI.red)} No ${colorize('package.json', ANSI.bold)} or ${colorize('requirements.txt', ANSI.bold)} found in this directory.`);
118
+ process.exit(1);
119
+ }
109
120
 
110
- console.log("Files found. Processing README...");
121
+ console.log(`${formatLabel('Info', ANSI.cyan)} Files found. Processing README...`);
111
122
 
112
- try {
113
123
  if (shouldForce && readmeExists) {
114
124
  fs.unlinkSync(readmePath);
115
125
  }
116
126
 
117
- // 1. Read the raw text of both files
118
127
  const readmeContent = fs.existsSync(readmePath) ? fs.readFileSync(readmePath, 'utf-8') : '';
119
- const fileTree = buildFileTree(targetDir);
128
+ const fileTree = buildLocalFileTree(fs, path, targetDir, targetDir);
120
129
  const projectName = path.basename(targetDir);
121
130
  const licenseName = hasLicense ? getLicenseName(fs.readFileSync(licensePath, 'utf-8')) : '';
131
+ const shouldPromptMetadata = !shouldUpdate;
132
+ const { titleContent, descriptionContent } = shouldPromptMetadata
133
+ ? await promptForTitleAndDescription()
134
+ : { titleContent: '', descriptionContent: '' };
122
135
  let context;
123
136
  let projectType;
124
137
 
@@ -129,9 +142,11 @@ try {
129
142
  context = {
130
143
  packageJson,
131
144
  packages: [{ path: 'package.json', content: packageJson }],
132
- dependencies: collectDependencies(packageJson),
133
- scripts: collectScripts(packageJson),
145
+ dependencies: collectNodeDependencies([{ path: 'package.json', content: packageJson }]),
146
+ scripts: collectScripts([{ path: 'package.json', content: packageJson }]),
134
147
  fileTree,
148
+ titleContent,
149
+ descriptionContent,
135
150
  licenseName,
136
151
  username: packageJson.author || process.env.USERNAME || 'Unknown Author',
137
152
  projectName: packageJson.name || projectName,
@@ -144,9 +159,11 @@ try {
144
159
 
145
160
  context = {
146
161
  packages: [{ path: 'requirements.txt', content: requirementsContent }],
147
- dependencies: collectPythonDependencies(requirementsContent),
162
+ dependencies: collectPythonDependencies([{ path: 'requirements.txt', content: requirementsContent }]),
148
163
  scripts: new Map(),
149
164
  fileTree,
165
+ titleContent,
166
+ descriptionContent,
150
167
  licenseName,
151
168
  username: process.env.USERNAME || 'Unknown Author',
152
169
  projectName,
@@ -156,15 +173,15 @@ try {
156
173
  projectType = 'python';
157
174
  }
158
175
 
159
- // 3. Feed everything into your pure engine
160
176
  const updatedReadme = processReadme(readmeContent, projectType, context);
161
177
 
162
- // 4. Overwrite the existing README.md with the new content
163
178
  fs.writeFileSync(readmePath, updatedReadme, 'utf-8');
164
179
 
165
- console.log("Success! README.md has been auto-fixed.");
166
-
167
- } catch (error) {
168
- console.error("An error occurred during processing:", error.message);
169
- process.exit(1);
180
+ console.log(`${formatLabel('Success', ANSI.green)} README.md has been auto-fixed.`);
170
181
  }
182
+
183
+ (main()
184
+ .catch(error => {
185
+ console.error(`${formatLabel('Error', ANSI.red)} An error occurred during processing: ${error.message}`);
186
+ process.exit(1);
187
+ }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blytz",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "bin": {
5
5
  "blytz": "./bin/cli.js"
6
6
  },
@@ -2,16 +2,22 @@ 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
- const normalizedContent = (content || "").replace(/^##(?=\S)/gm, "## ");
5
+ export default function processReadme(content, projectType, context = {}) {
6
+ const { titleContent = "", descriptionContent = "", projectName = "" } = 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
- // Extract intro (title + description)
14
- const intro = sections[0].trim();
14
+ // Extract intro (title + description)
15
+ const intro = sections[0].trim();
16
+ const resolvedTitle = titleContent || projectName || "";
17
+ const resolvedDescription = descriptionContent || getDefaultContent("description", projectType, context);
18
+ const finalIntro = resolvedTitle
19
+ ? [`# ${resolvedTitle}`, resolvedDescription].filter(Boolean).join("\n\n")
20
+ : intro;
15
21
 
16
22
  // Parse sections
17
23
  sections.slice(1).forEach(section => {
@@ -22,14 +28,13 @@ export default function processReadme(content, projectType, context = {}) {
22
28
  sectionMap[title] = body;
23
29
  });
24
30
 
25
- // Required sections
26
- const requiredSections = [
27
- "description",
28
- "installation",
29
- "usage",
30
- "dependencies",
31
- "folder structure",
32
- "license",
31
+ // Required sections
32
+ const requiredSections = [
33
+ "installation",
34
+ "usage",
35
+ "dependencies",
36
+ "folder structure",
37
+ "license",
33
38
  "built by"
34
39
  ];
35
40
 
@@ -65,7 +70,7 @@ export default function processReadme(content, projectType, context = {}) {
65
70
  .join(" ");
66
71
 
67
72
  // Rebuild README
68
- let newReadme = intro ? intro + "\n\n" : "";
73
+ let newReadme = finalIntro ? finalIntro + "\n\n" : "";
69
74
 
70
75
  // Ordered sections
71
76
  requiredSections.forEach(section => {
@@ -80,4 +85,4 @@ export default function processReadme(content, projectType, context = {}) {
80
85
  });
81
86
 
82
87
  return newReadme.trim();
83
- }
88
+ }
@@ -0,0 +1,264 @@
1
+ export const DEFAULT_IGNORED_NAMES = new Set([
2
+ "node_modules",
3
+ ".git",
4
+ ".agent",
5
+ "dist",
6
+ "build",
7
+ "out",
8
+ "target",
9
+ "venv",
10
+ ".venv",
11
+ "__pycache__",
12
+ ".idea",
13
+ ".vscode",
14
+ ".DS_Store",
15
+ "Thumbs.db",
16
+ ".env",
17
+ ".env.local",
18
+ ".env.development",
19
+ ".env.production"
20
+ ]);
21
+
22
+ export function collectNodeDependencies(packages = []) {
23
+ const dependencies = new Set();
24
+
25
+ for (const pkg of packages) {
26
+ for (const dependency of Object.keys(pkg?.content?.dependencies || {})) {
27
+ dependencies.add(dependency);
28
+ }
29
+ }
30
+
31
+ return Array.from(dependencies).sort((left, right) => left.localeCompare(right));
32
+ }
33
+
34
+ export function collectPythonDependencies(files = []) {
35
+ const dependencies = new Set();
36
+
37
+ for (const file of files) {
38
+ if (!file?.content) {
39
+ continue;
40
+ }
41
+
42
+ file.content
43
+ .split(/\r?\n/)
44
+ .map(line => line.trim())
45
+ .filter(line => line && !line.startsWith("#"))
46
+ .filter(line => !line.startsWith("-"))
47
+ .map(line => line.split(";")[0].trim())
48
+ .map(line => line.split(/[=<>!~]/)[0].trim())
49
+ .filter(Boolean)
50
+ .forEach(dependency => dependencies.add(dependency));
51
+ }
52
+
53
+ return Array.from(dependencies).sort((left, right) => left.localeCompare(right));
54
+ }
55
+
56
+ export function collectScripts(packages = []) {
57
+ const scripts = new Map();
58
+
59
+ for (const pkg of packages) {
60
+ const packageDir = pkg.path === "package.json" ? "(root)" : pkg.path.replace("/package.json", "");
61
+
62
+ for (const [name, command] of Object.entries(pkg?.content?.scripts || {})) {
63
+ if (!scripts.has(name)) {
64
+ scripts.set(name, []);
65
+ }
66
+
67
+ scripts.get(name).push({ package: packageDir, command });
68
+ }
69
+ }
70
+
71
+ return scripts;
72
+ }
73
+
74
+ export function getLicenseName(licenseContent = "") {
75
+ const firstLine = licenseContent
76
+ .split(/\r?\n/)
77
+ .map(line => line.trim())
78
+ .find(Boolean);
79
+
80
+ return firstLine || "";
81
+ }
82
+
83
+ function escapeRegex(value) {
84
+ return value.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
85
+ }
86
+
87
+ function globToRegex(pattern) {
88
+ let regex = "";
89
+
90
+ for (let index = 0; index < pattern.length; index += 1) {
91
+ const char = pattern[index];
92
+ const nextChar = pattern[index + 1];
93
+
94
+ if (char === "*") {
95
+ if (nextChar === "*") {
96
+ regex += ".*";
97
+ index += 1;
98
+ } else {
99
+ regex += "[^/]*";
100
+ }
101
+ continue;
102
+ }
103
+
104
+ if (char === "?") {
105
+ regex += "[^/]";
106
+ continue;
107
+ }
108
+
109
+ regex += escapeRegex(char);
110
+ }
111
+
112
+ return regex;
113
+ }
114
+
115
+ export function parseGitignoreContent(content, baseDir = "") {
116
+ return content
117
+ .split(/\r?\n/)
118
+ .map(line => line.trim())
119
+ .filter(line => line && !line.startsWith("#"))
120
+ .map(line => {
121
+ const negated = line.startsWith("!");
122
+ const rawPattern = negated ? line.slice(1).trim() : line;
123
+
124
+ if (!rawPattern) {
125
+ return null;
126
+ }
127
+
128
+ const directoryOnly = rawPattern.endsWith("/");
129
+ const normalizedPattern = rawPattern.replace(/\/+$/, "").replace(/\\/g, "/");
130
+
131
+ if (!normalizedPattern) {
132
+ return null;
133
+ }
134
+
135
+ const anchored = normalizedPattern.startsWith("/");
136
+ const matcherPattern = anchored ? normalizedPattern.slice(1) : normalizedPattern;
137
+
138
+ return {
139
+ baseDir: baseDir.replace(/\\/g, "/"),
140
+ negated,
141
+ directoryOnly,
142
+ hasSlash: matcherPattern.includes("/"),
143
+ regex: new RegExp(`^${globToRegex(matcherPattern)}(?:/.*)?$`),
144
+ basenameRegex: new RegExp(`^${globToRegex(matcherPattern)}$`)
145
+ };
146
+ })
147
+ .filter(Boolean);
148
+ }
149
+
150
+ export function isIgnoredByGitignore(relativePath, isDirectory, gitignoreRules = []) {
151
+ const normalizedPath = relativePath.replace(/\\/g, "/");
152
+ let ignored = false;
153
+
154
+ for (const rule of gitignoreRules) {
155
+ if (rule.directoryOnly && !isDirectory) {
156
+ continue;
157
+ }
158
+
159
+ if (rule.baseDir && normalizedPath !== rule.baseDir && !normalizedPath.startsWith(`${rule.baseDir}/`)) {
160
+ continue;
161
+ }
162
+
163
+ const scopedPath = rule.baseDir
164
+ ? normalizedPath.slice(rule.baseDir.length).replace(/^\/+/, "")
165
+ : normalizedPath;
166
+ const pathParts = scopedPath.split("/");
167
+ const matches = rule.hasSlash
168
+ ? rule.regex.test(scopedPath)
169
+ : pathParts.some(part => rule.basenameRegex.test(part));
170
+
171
+ if (matches) {
172
+ ignored = !rule.negated;
173
+ }
174
+ }
175
+
176
+ return ignored;
177
+ }
178
+
179
+ export function parseLocalGitignore(fs, gitignorePath, baseDir = "") {
180
+ if (!fs.existsSync(gitignorePath)) {
181
+ return [];
182
+ }
183
+
184
+ return parseGitignoreContent(fs.readFileSync(gitignorePath, "utf-8"), baseDir);
185
+ }
186
+
187
+ export function buildLocalFileTree(fs, path, dirPath, rootDir = dirPath, inheritedGitignoreRules = [], depth = 0, maxDepth = 3) {
188
+ if (depth >= maxDepth) {
189
+ return {};
190
+ }
191
+
192
+ const relativeDir = path.relative(rootDir, dirPath).replace(/\\/g, "/");
193
+ const localGitignoreRules = parseLocalGitignore(
194
+ fs,
195
+ path.join(dirPath, ".gitignore"),
196
+ relativeDir === "" ? "" : relativeDir
197
+ );
198
+ const gitignoreRules = [...inheritedGitignoreRules, ...localGitignoreRules];
199
+ const tree = {};
200
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true })
201
+ .filter(entry => {
202
+ if (DEFAULT_IGNORED_NAMES.has(entry.name)) {
203
+ return false;
204
+ }
205
+
206
+ const entryPath = path.join(dirPath, entry.name);
207
+ const relativePath = path.relative(rootDir, entryPath);
208
+
209
+ return !isIgnoredByGitignore(relativePath, entry.isDirectory(), gitignoreRules);
210
+ })
211
+ .sort((left, right) => left.name.localeCompare(right.name));
212
+
213
+ for (const entry of entries) {
214
+ const entryPath = path.join(dirPath, entry.name);
215
+
216
+ if (entry.isDirectory()) {
217
+ tree[entry.name] = buildLocalFileTree(fs, path, entryPath, rootDir, gitignoreRules, depth + 1, maxDepth);
218
+ } else {
219
+ tree[entry.name] = null;
220
+ }
221
+ }
222
+
223
+ return tree;
224
+ }
225
+
226
+ export function buildRemoteFileTree(treeData, gitignoreRules = [], maxDepth = 3) {
227
+ const fileTree = {};
228
+
229
+ if (!treeData?.tree || !Array.isArray(treeData.tree)) {
230
+ return fileTree;
231
+ }
232
+
233
+ for (const item of treeData.tree) {
234
+ const parts = item.path.split("/");
235
+ const name = parts[parts.length - 1];
236
+ const isDirectory = item.type === "tree";
237
+
238
+ if (DEFAULT_IGNORED_NAMES.has(name) || isIgnoredByGitignore(item.path, isDirectory, gitignoreRules)) {
239
+ continue;
240
+ }
241
+
242
+ if (parts.length > maxDepth) {
243
+ continue;
244
+ }
245
+
246
+ let current = fileTree;
247
+
248
+ for (let index = 0; index < parts.length; index += 1) {
249
+ const part = parts[index];
250
+ const isFile = index === parts.length - 1 && item.type === "blob";
251
+
252
+ if (isFile) {
253
+ current[part] = null;
254
+ } else {
255
+ if (!current[part]) {
256
+ current[part] = {};
257
+ }
258
+ current = current[part];
259
+ }
260
+ }
261
+ }
262
+
263
+ return fileTree;
264
+ }
package/src/template.js CHANGED
@@ -1,23 +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(),
4
+ const {
5
+ packages = [],
6
+ dependencies = [],
7
+ scripts = new Map(),
8
8
  fileTree = null,
9
+ descriptionContent = "",
9
10
  licenseName = "",
10
11
  username = "Unknown",
11
- projectName = "this project",
12
- isMonorepo = false,
13
- } = context ?? {};
12
+ projectName = "this project",
13
+ isMonorepo = false,
14
+ } = context ?? {};
14
15
 
15
16
  const safeProjectType = projectType || "unknown";
16
17
  const safeSection = (section || "").toLowerCase().trim();
17
18
 
18
- switch (safeSection) {
19
- case "description":
20
- return getDescriptionContent(safeProjectType, projectName, isMonorepo);
19
+ switch (safeSection) {
20
+ case "description":
21
+ return getDescriptionContent(safeProjectType, projectName, isMonorepo, descriptionContent);
21
22
  case "installation":
22
23
  return getInstallationContent(safeProjectType, packages, isMonorepo);
23
24
  case "usage":
@@ -35,8 +36,12 @@ export default function getDefaultContent(section, projectType, context = {}) {
35
36
  }
36
37
  }
37
38
 
38
- function getDescriptionContent(projectType, projectName, isMonorepo) {
39
- const name = projectName || "this project";
39
+ function getDescriptionContent(projectType, projectName, isMonorepo, descriptionContent) {
40
+ if (descriptionContent) {
41
+ return descriptionContent;
42
+ }
43
+
44
+ const name = projectName || "this project";
40
45
  if (projectType === "node") {
41
46
  if (isMonorepo) {
42
47
  return `${name} is a Node.js monorepo containing multiple packages. Add a brief description of its purpose and what problem it solves.`;