fln 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 +21 -0
- package/README.md +330 -0
- package/dist/api/fln.d.ts +3 -0
- package/dist/api/fln.d.ts.map +1 -0
- package/dist/api/fln.js +71 -0
- package/dist/api/index.d.ts +3 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +2 -0
- package/dist/api/types.d.ts +34 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +1 -0
- package/dist/cli/commandLine.d.ts +2 -0
- package/dist/cli/commandLine.d.ts.map +1 -0
- package/dist/cli/commandLine.js +120 -0
- package/dist/cli/help.d.ts +2 -0
- package/dist/cli/help.d.ts.map +1 -0
- package/dist/cli/help.js +40 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +1 -0
- package/dist/cli/main.d.ts +3 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +9 -0
- package/dist/cli/output/components/breakdown.d.ts +2 -0
- package/dist/cli/output/components/breakdown.d.ts.map +1 -0
- package/dist/cli/output/components/breakdown.js +21 -0
- package/dist/cli/output/components/errors.d.ts +6 -0
- package/dist/cli/output/components/errors.d.ts.map +1 -0
- package/dist/cli/output/components/errors.js +13 -0
- package/dist/cli/output/components/progressBar.d.ts +7 -0
- package/dist/cli/output/components/progressBar.d.ts.map +1 -0
- package/dist/cli/output/components/progressBar.js +38 -0
- package/dist/cli/output/components/summary.d.ts +8 -0
- package/dist/cli/output/components/summary.d.ts.map +1 -0
- package/dist/cli/output/components/summary.js +12 -0
- package/dist/cli/output/components/warnings.d.ts +6 -0
- package/dist/cli/output/components/warnings.d.ts.map +1 -0
- package/dist/cli/output/components/warnings.js +11 -0
- package/dist/cli/output/formatter.d.ts +5 -0
- package/dist/cli/output/formatter.d.ts.map +1 -0
- package/dist/cli/output/formatter.js +28 -0
- package/dist/cli/output/index.d.ts +8 -0
- package/dist/cli/output/index.d.ts.map +1 -0
- package/dist/cli/output/index.js +4 -0
- package/dist/cli/output/renderer.d.ts +21 -0
- package/dist/cli/output/renderer.d.ts.map +1 -0
- package/dist/cli/output/renderer.js +121 -0
- package/dist/cli/output/styles.d.ts +23 -0
- package/dist/cli/output/styles.d.ts.map +1 -0
- package/dist/cli/output/styles.js +26 -0
- package/dist/config/defaults.d.ts +3 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +2 -0
- package/dist/config/index.d.ts +6 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +5 -0
- package/dist/config/loader.d.ts +3 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +11 -0
- package/dist/config/resolver.d.ts +8 -0
- package/dist/config/resolver.d.ts.map +1 -0
- package/dist/config/resolver.js +66 -0
- package/dist/config/types.d.ts +40 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +1 -0
- package/dist/config/utils.d.ts +7 -0
- package/dist/config/utils.d.ts.map +1 -0
- package/dist/config/utils.js +161 -0
- package/dist/core/ignoreMatcher.d.ts +15 -0
- package/dist/core/ignoreMatcher.d.ts.map +1 -0
- package/dist/core/ignoreMatcher.js +97 -0
- package/dist/core/index.d.ts +8 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +7 -0
- package/dist/core/renderOutput.d.ts +4 -0
- package/dist/core/renderOutput.d.ts.map +1 -0
- package/dist/core/renderOutput.js +218 -0
- package/dist/core/renderTree.d.ts +3 -0
- package/dist/core/renderTree.d.ts.map +1 -0
- package/dist/core/renderTree.js +23 -0
- package/dist/core/scanTree.d.ts +4 -0
- package/dist/core/scanTree.d.ts.map +1 -0
- package/dist/core/scanTree.js +348 -0
- package/dist/core/size.d.ts +4 -0
- package/dist/core/size.d.ts.map +1 -0
- package/dist/core/size.js +32 -0
- package/dist/core/statsCollector.d.ts +4 -0
- package/dist/core/statsCollector.d.ts.map +1 -0
- package/dist/core/statsCollector.js +28 -0
- package/dist/core/types.d.ts +53 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/infra/countTokens.d.ts +2 -0
- package/dist/infra/countTokens.d.ts.map +1 -0
- package/dist/infra/countTokens.js +186 -0
- package/dist/infra/datetime.d.ts +3 -0
- package/dist/infra/datetime.d.ts.map +1 -0
- package/dist/infra/datetime.js +15 -0
- package/dist/infra/index.d.ts +7 -0
- package/dist/infra/index.d.ts.map +1 -0
- package/dist/infra/index.js +6 -0
- package/dist/infra/logger.d.ts +19 -0
- package/dist/infra/logger.d.ts.map +1 -0
- package/dist/infra/logger.js +99 -0
- package/dist/infra/outputWriter.d.ts +15 -0
- package/dist/infra/outputWriter.d.ts.map +1 -0
- package/dist/infra/outputWriter.js +35 -0
- package/dist/infra/terminal.d.ts +91 -0
- package/dist/infra/terminal.d.ts.map +1 -0
- package/dist/infra/terminal.js +189 -0
- package/dist/infra/usageTracker.d.ts +3 -0
- package/dist/infra/usageTracker.d.ts.map +1 -0
- package/dist/infra/usageTracker.js +39 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +1 -0
- package/package.json +79 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import { lstat, open, readdir, readlink, realpath, stat } from "node:fs/promises";
|
|
2
|
+
import { cpus } from "node:os";
|
|
3
|
+
import { relative, sep } from "node:path";
|
|
4
|
+
import ignore from "ignore";
|
|
5
|
+
import { IgnoreMatcher } from "./ignoreMatcher";
|
|
6
|
+
function normalizePathSegment(pathSegment) {
|
|
7
|
+
if (sep === "/")
|
|
8
|
+
return pathSegment;
|
|
9
|
+
return pathSegment.split(sep).join("/");
|
|
10
|
+
}
|
|
11
|
+
function getFileScore(fileName) {
|
|
12
|
+
const lowerName = fileName.toLowerCase();
|
|
13
|
+
if (lowerName.startsWith("readme"))
|
|
14
|
+
return 0;
|
|
15
|
+
if (lowerName === "package.json" ||
|
|
16
|
+
lowerName.startsWith("tsconfig") ||
|
|
17
|
+
lowerName === "pyproject.toml" ||
|
|
18
|
+
lowerName === "cargo.toml" ||
|
|
19
|
+
lowerName === "go.mod" ||
|
|
20
|
+
lowerName === "cmakelists.txt" ||
|
|
21
|
+
lowerName === "makefile" ||
|
|
22
|
+
lowerName === "dockerfile" ||
|
|
23
|
+
lowerName === "vcpkg.json" ||
|
|
24
|
+
lowerName.startsWith(".env") ||
|
|
25
|
+
lowerName.includes(".config.") ||
|
|
26
|
+
lowerName.startsWith(".prettier") ||
|
|
27
|
+
lowerName.startsWith(".eslintrc"))
|
|
28
|
+
return 1;
|
|
29
|
+
if (lowerName.startsWith("index.") ||
|
|
30
|
+
lowerName.startsWith("main.") ||
|
|
31
|
+
lowerName.startsWith("app.") ||
|
|
32
|
+
lowerName.startsWith("server.") ||
|
|
33
|
+
lowerName.startsWith("mod.") ||
|
|
34
|
+
lowerName.startsWith("lib."))
|
|
35
|
+
return 2;
|
|
36
|
+
if (lowerName.includes("types") ||
|
|
37
|
+
lowerName.includes("interface") ||
|
|
38
|
+
lowerName.includes("schema") ||
|
|
39
|
+
lowerName.includes("config") ||
|
|
40
|
+
lowerName.includes("constants") ||
|
|
41
|
+
lowerName.endsWith(".d.ts") ||
|
|
42
|
+
lowerName.endsWith(".h") ||
|
|
43
|
+
lowerName.endsWith(".hpp"))
|
|
44
|
+
return 3;
|
|
45
|
+
if (lowerName.startsWith("license") ||
|
|
46
|
+
lowerName.startsWith("changelog") ||
|
|
47
|
+
lowerName.startsWith("contributing") ||
|
|
48
|
+
lowerName.startsWith("code_of_conduct") ||
|
|
49
|
+
lowerName.startsWith("security"))
|
|
50
|
+
return 15;
|
|
51
|
+
if (lowerName.includes(".test.") ||
|
|
52
|
+
lowerName.includes(".spec.") ||
|
|
53
|
+
lowerName.startsWith("test_") ||
|
|
54
|
+
lowerName.endsWith("_test.go"))
|
|
55
|
+
return 20;
|
|
56
|
+
return 10;
|
|
57
|
+
}
|
|
58
|
+
async function isBinaryFile(filePath, fileSize) {
|
|
59
|
+
if (fileSize === 0)
|
|
60
|
+
return false;
|
|
61
|
+
const handle = await open(filePath, "r");
|
|
62
|
+
try {
|
|
63
|
+
const buffer = Buffer.alloc(Math.min(512, fileSize));
|
|
64
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
|
|
65
|
+
for (let index = 0; index < bytesRead; index++)
|
|
66
|
+
if (buffer[index] === 0)
|
|
67
|
+
return true;
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
await handle.close();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async function isGeneratedFile(filePath, fileSize) {
|
|
75
|
+
if (fileSize === 0)
|
|
76
|
+
return false;
|
|
77
|
+
const handle = await open(filePath, "r");
|
|
78
|
+
try {
|
|
79
|
+
const buffer = Buffer.alloc(Math.min(100, fileSize));
|
|
80
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
|
|
81
|
+
const content = buffer.toString("utf8", 0, bytesRead);
|
|
82
|
+
return content.includes("<!-- 🥞 fln");
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
await handle.close();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export async function scanTree(options, logger) {
|
|
92
|
+
const stats = {
|
|
93
|
+
files: 0,
|
|
94
|
+
directories: 0,
|
|
95
|
+
binary: 0,
|
|
96
|
+
skipped: 0,
|
|
97
|
+
errors: 0,
|
|
98
|
+
totalSizeBytes: 0,
|
|
99
|
+
outputSizeBytes: 0,
|
|
100
|
+
outputTokenCount: 0
|
|
101
|
+
};
|
|
102
|
+
const ignoreMatcher = new IgnoreMatcher({
|
|
103
|
+
rootDirectory: options.rootDirectory,
|
|
104
|
+
excludePatterns: options.excludePatterns,
|
|
105
|
+
useGitignore: options.useGitignore,
|
|
106
|
+
logger
|
|
107
|
+
});
|
|
108
|
+
const includeMatcher = ignore().add(options.includePatterns);
|
|
109
|
+
const concurrencyLimit = Math.max(8, Math.min(64, cpus().length * 4));
|
|
110
|
+
const excludedPathSet = new Set(options.excludedPaths.map(pathItem => normalizePathSegment(pathItem)));
|
|
111
|
+
const visitedRealPaths = new Set();
|
|
112
|
+
let processedItems = 0;
|
|
113
|
+
let totalEstimate = 0;
|
|
114
|
+
if (options.followSymlinks)
|
|
115
|
+
try {
|
|
116
|
+
const rootRealPath = await realpath(options.rootDirectory);
|
|
117
|
+
visitedRealPaths.add(rootRealPath);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
logger.debug("Failed to resolve root real path.");
|
|
121
|
+
}
|
|
122
|
+
const rootNode = await scanEntry(options.rootDirectory, "");
|
|
123
|
+
if (!rootNode || rootNode.type !== "directory")
|
|
124
|
+
throw new Error("Root directory is empty or all files were excluded.");
|
|
125
|
+
return { projectName: options.projectName, root: rootNode, stats };
|
|
126
|
+
async function scanEntry(currentPath, relativePath, dirent) {
|
|
127
|
+
const normalizedRelativePath = normalizePathSegment(relativePath);
|
|
128
|
+
const name = dirent?.name ?? currentPath.split(sep).pop() ?? "";
|
|
129
|
+
if (normalizedRelativePath !== "" && excludedPathSet.has(normalizedRelativePath))
|
|
130
|
+
return undefined;
|
|
131
|
+
const isExplicitlyIncluded = normalizedRelativePath !== "" && includeMatcher.ignores(normalizedRelativePath);
|
|
132
|
+
if (!isExplicitlyIncluded && normalizedRelativePath !== "" && ignoreMatcher.ignores(normalizedRelativePath))
|
|
133
|
+
return undefined;
|
|
134
|
+
if (!options.includeHidden && name.startsWith(".") && name !== ".")
|
|
135
|
+
return undefined;
|
|
136
|
+
try {
|
|
137
|
+
let symlinkTarget;
|
|
138
|
+
const isSymlink = dirent ? dirent.isSymbolicLink() : undefined;
|
|
139
|
+
if (isSymlink) {
|
|
140
|
+
symlinkTarget = await readlink(currentPath);
|
|
141
|
+
if (!options.followSymlinks)
|
|
142
|
+
return {
|
|
143
|
+
name,
|
|
144
|
+
path: normalizedRelativePath,
|
|
145
|
+
type: "symlink",
|
|
146
|
+
size: 0,
|
|
147
|
+
target: symlinkTarget
|
|
148
|
+
};
|
|
149
|
+
const resolvedPath = await realpath(currentPath);
|
|
150
|
+
if (visitedRealPaths.has(resolvedPath)) {
|
|
151
|
+
stats.skipped++;
|
|
152
|
+
return {
|
|
153
|
+
name,
|
|
154
|
+
path: normalizedRelativePath,
|
|
155
|
+
type: "symlink",
|
|
156
|
+
size: 0,
|
|
157
|
+
target: symlinkTarget,
|
|
158
|
+
skipReason: "symlinkCycle"
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
visitedRealPaths.add(resolvedPath);
|
|
162
|
+
const statsResult = await stat(currentPath);
|
|
163
|
+
if (statsResult.isFile())
|
|
164
|
+
return await buildFileNode({
|
|
165
|
+
currentPath,
|
|
166
|
+
normalizedRelativePath,
|
|
167
|
+
name,
|
|
168
|
+
fileSize: statsResult.size,
|
|
169
|
+
symlinkTarget,
|
|
170
|
+
isExplicitlyIncluded
|
|
171
|
+
});
|
|
172
|
+
if (statsResult.isDirectory())
|
|
173
|
+
return await buildDirectoryNode({
|
|
174
|
+
currentPath,
|
|
175
|
+
normalizedRelativePath,
|
|
176
|
+
name,
|
|
177
|
+
symlinkTarget
|
|
178
|
+
});
|
|
179
|
+
return undefined;
|
|
180
|
+
}
|
|
181
|
+
if (dirent?.isDirectory())
|
|
182
|
+
return await buildDirectoryNode({
|
|
183
|
+
currentPath,
|
|
184
|
+
normalizedRelativePath,
|
|
185
|
+
name
|
|
186
|
+
});
|
|
187
|
+
if (dirent?.isFile()) {
|
|
188
|
+
const statsResult = await stat(currentPath);
|
|
189
|
+
return await buildFileNode({
|
|
190
|
+
currentPath,
|
|
191
|
+
normalizedRelativePath,
|
|
192
|
+
name,
|
|
193
|
+
fileSize: statsResult.size,
|
|
194
|
+
isExplicitlyIncluded
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
if (dirent)
|
|
198
|
+
return undefined;
|
|
199
|
+
const entryStats = await lstat(currentPath);
|
|
200
|
+
if (entryStats.isSymbolicLink()) {
|
|
201
|
+
symlinkTarget = await readlink(currentPath);
|
|
202
|
+
if (!options.followSymlinks)
|
|
203
|
+
return {
|
|
204
|
+
name,
|
|
205
|
+
path: normalizedRelativePath,
|
|
206
|
+
type: "symlink",
|
|
207
|
+
size: 0,
|
|
208
|
+
target: symlinkTarget
|
|
209
|
+
};
|
|
210
|
+
const resolvedPath = await realpath(currentPath);
|
|
211
|
+
if (visitedRealPaths.has(resolvedPath)) {
|
|
212
|
+
stats.skipped++;
|
|
213
|
+
return {
|
|
214
|
+
name,
|
|
215
|
+
path: normalizedRelativePath,
|
|
216
|
+
type: "symlink",
|
|
217
|
+
size: 0,
|
|
218
|
+
target: symlinkTarget,
|
|
219
|
+
skipReason: "symlinkCycle"
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
visitedRealPaths.add(resolvedPath);
|
|
223
|
+
}
|
|
224
|
+
const statsResult = options.followSymlinks ? await stat(currentPath) : entryStats;
|
|
225
|
+
if (statsResult.isFile())
|
|
226
|
+
return await buildFileNode({
|
|
227
|
+
currentPath,
|
|
228
|
+
normalizedRelativePath,
|
|
229
|
+
name,
|
|
230
|
+
fileSize: statsResult.size,
|
|
231
|
+
symlinkTarget,
|
|
232
|
+
isExplicitlyIncluded
|
|
233
|
+
});
|
|
234
|
+
if (statsResult.isDirectory())
|
|
235
|
+
return await buildDirectoryNode({
|
|
236
|
+
currentPath,
|
|
237
|
+
normalizedRelativePath,
|
|
238
|
+
name,
|
|
239
|
+
symlinkTarget
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
stats.errors++;
|
|
244
|
+
logger.warn(`Failed to access ${normalizedRelativePath || "."}: ${String(error)}`);
|
|
245
|
+
}
|
|
246
|
+
return undefined;
|
|
247
|
+
}
|
|
248
|
+
async function buildFileNode(input) {
|
|
249
|
+
stats.files++;
|
|
250
|
+
stats.totalSizeBytes += input.fileSize;
|
|
251
|
+
processedItems++;
|
|
252
|
+
if (totalEstimate === 0)
|
|
253
|
+
totalEstimate = Math.max(processedItems + 50, 100);
|
|
254
|
+
if (options.onProgress)
|
|
255
|
+
options.onProgress(processedItems, Math.max(totalEstimate, processedItems));
|
|
256
|
+
let skipReason;
|
|
257
|
+
if (!input.isExplicitlyIncluded && await isGeneratedFile(input.currentPath, input.fileSize))
|
|
258
|
+
skipReason = "generated";
|
|
259
|
+
if (!skipReason && input.fileSize > options.maximumFileSizeBytes)
|
|
260
|
+
skipReason = "tooLarge";
|
|
261
|
+
let isBinary = false;
|
|
262
|
+
if (!skipReason)
|
|
263
|
+
try {
|
|
264
|
+
isBinary = await isBinaryFile(input.currentPath, input.fileSize);
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
stats.errors++;
|
|
268
|
+
skipReason = "readError";
|
|
269
|
+
logger.warn(`Failed to read ${input.normalizedRelativePath || "."}: ${String(error)}`);
|
|
270
|
+
}
|
|
271
|
+
if (isBinary)
|
|
272
|
+
stats.binary++;
|
|
273
|
+
if (skipReason) {
|
|
274
|
+
stats.skipped++;
|
|
275
|
+
return {
|
|
276
|
+
name: input.name,
|
|
277
|
+
path: input.normalizedRelativePath,
|
|
278
|
+
type: "file",
|
|
279
|
+
size: input.fileSize,
|
|
280
|
+
isBinary,
|
|
281
|
+
target: input.symlinkTarget,
|
|
282
|
+
skipReason
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
name: input.name,
|
|
287
|
+
path: input.normalizedRelativePath,
|
|
288
|
+
type: "file",
|
|
289
|
+
size: input.fileSize,
|
|
290
|
+
isBinary,
|
|
291
|
+
target: input.symlinkTarget
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
async function buildDirectoryNode(input) {
|
|
295
|
+
stats.directories++;
|
|
296
|
+
await ignoreMatcher.addGitignoreForDirectory(input.currentPath);
|
|
297
|
+
const entries = await readdir(input.currentPath, { withFileTypes: true });
|
|
298
|
+
totalEstimate = Math.max(totalEstimate, processedItems + entries.length);
|
|
299
|
+
const children = (await mapWithConcurrency(entries, concurrencyLimit, async (entry) => {
|
|
300
|
+
const childPath = `${input.currentPath}${sep}${entry.name}`;
|
|
301
|
+
const childRelativePath = relative(options.rootDirectory, childPath);
|
|
302
|
+
return scanEntry(childPath, childRelativePath, entry);
|
|
303
|
+
}))
|
|
304
|
+
.filter((node) => node !== undefined)
|
|
305
|
+
.sort((left, right) => {
|
|
306
|
+
if (left.type !== right.type)
|
|
307
|
+
return left.type === "directory" ? 1 : -1;
|
|
308
|
+
if (left.type === "file" && right.type === "file") {
|
|
309
|
+
const scoreA = getFileScore(left.name);
|
|
310
|
+
const scoreB = getFileScore(right.name);
|
|
311
|
+
if (scoreA !== scoreB)
|
|
312
|
+
return scoreA - scoreB;
|
|
313
|
+
}
|
|
314
|
+
return left.name.localeCompare(right.name, undefined, { numeric: true, sensitivity: "base" });
|
|
315
|
+
});
|
|
316
|
+
return {
|
|
317
|
+
name: input.name,
|
|
318
|
+
path: input.normalizedRelativePath,
|
|
319
|
+
type: "directory",
|
|
320
|
+
size: 0,
|
|
321
|
+
children,
|
|
322
|
+
target: input.symlinkTarget
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
async function mapWithConcurrency(items, concurrency, mapper) {
|
|
327
|
+
if (items.length === 0)
|
|
328
|
+
return [];
|
|
329
|
+
const limit = Math.max(1, Math.min(concurrency, items.length));
|
|
330
|
+
if (limit === 1) {
|
|
331
|
+
const results = [];
|
|
332
|
+
for (const item of items)
|
|
333
|
+
results.push(await mapper(item));
|
|
334
|
+
return results;
|
|
335
|
+
}
|
|
336
|
+
const results = new Array(items.length);
|
|
337
|
+
let nextIndex = 0;
|
|
338
|
+
const workers = Array.from({ length: limit }, async () => {
|
|
339
|
+
while (true) {
|
|
340
|
+
const currentIndex = nextIndex++;
|
|
341
|
+
if (currentIndex >= items.length)
|
|
342
|
+
return;
|
|
343
|
+
results[currentIndex] = await mapper(items[currentIndex]);
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
await Promise.all(workers);
|
|
347
|
+
return results;
|
|
348
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"size.d.ts","sourceRoot":"","sources":["../../src/core/size.ts"],"names":[],"mappings":"AAIA,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAgBnD;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CASxD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOtD"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const kibibyte = 1024;
|
|
2
|
+
const mebibyte = kibibyte * 1024;
|
|
3
|
+
const gibibyte = mebibyte * 1024;
|
|
4
|
+
export function parseByteSize(input) {
|
|
5
|
+
const normalizedInput = input.trim().toLowerCase();
|
|
6
|
+
const match = normalizedInput.match(/^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)?$/);
|
|
7
|
+
if (!match)
|
|
8
|
+
throw new Error(`Invalid size: "${input}"`);
|
|
9
|
+
const value = Number(match[1]);
|
|
10
|
+
const unit = match[2] ?? "b";
|
|
11
|
+
const multiplier = unit === "kb" ? kibibyte :
|
|
12
|
+
unit === "mb" ? mebibyte :
|
|
13
|
+
unit === "gb" ? gibibyte :
|
|
14
|
+
1;
|
|
15
|
+
return Math.floor(value * multiplier);
|
|
16
|
+
}
|
|
17
|
+
export function formatByteSize(sizeBytes) {
|
|
18
|
+
if (sizeBytes >= gibibyte)
|
|
19
|
+
return `${(sizeBytes / gibibyte).toFixed(2)} GB`;
|
|
20
|
+
if (sizeBytes >= mebibyte)
|
|
21
|
+
return `${(sizeBytes / mebibyte).toFixed(2)} MB`;
|
|
22
|
+
if (sizeBytes >= kibibyte)
|
|
23
|
+
return `${(sizeBytes / kibibyte).toFixed(2)} KB`;
|
|
24
|
+
return `${sizeBytes} B`;
|
|
25
|
+
}
|
|
26
|
+
export function formatTokenCount(count) {
|
|
27
|
+
if (count >= 1_000_000)
|
|
28
|
+
return `≈ ${(count / 1_000_000).toFixed(1)}M`;
|
|
29
|
+
if (count >= 1000)
|
|
30
|
+
return `≈ ${(count / 1000).toFixed(1)}K`;
|
|
31
|
+
return `≈ ${count}`;
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"statsCollector.d.ts","sourceRoot":"","sources":["../../src/core/statsCollector.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAGxC,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,QAAQ,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAkBzE;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,EAAE,CAkB9D"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { extname } from "node:path";
|
|
2
|
+
export function collectExtensionStats(root) {
|
|
3
|
+
const stats = new Map();
|
|
4
|
+
function walk(node) {
|
|
5
|
+
if (node.type === "file" && !node.isBinary && !node.skipReason) {
|
|
6
|
+
const ext = extname(node.name) || "(no ext)";
|
|
7
|
+
stats.set(ext, (stats.get(ext) || 0) + 1);
|
|
8
|
+
}
|
|
9
|
+
if (node.children)
|
|
10
|
+
for (const child of node.children)
|
|
11
|
+
walk(child);
|
|
12
|
+
}
|
|
13
|
+
walk(root);
|
|
14
|
+
return stats;
|
|
15
|
+
}
|
|
16
|
+
export function collectProcessedFiles(root) {
|
|
17
|
+
const files = [];
|
|
18
|
+
function walk(node, parentPath = "") {
|
|
19
|
+
const currentPath = parentPath ? `${parentPath}/${node.name}` : node.name;
|
|
20
|
+
if (node.type === "file" && !node.isBinary && !node.skipReason)
|
|
21
|
+
files.push(currentPath);
|
|
22
|
+
if (node.children)
|
|
23
|
+
for (const child of node.children)
|
|
24
|
+
walk(child, currentPath);
|
|
25
|
+
}
|
|
26
|
+
walk(root);
|
|
27
|
+
return files;
|
|
28
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export type OutputFormat = "json" | "md";
|
|
2
|
+
export type SkipReason = "generated" | "readError" | "symlinkCycle" | "tooLarge" | "totalSizeLimit";
|
|
3
|
+
export type FileType = "directory" | "file" | "symlink";
|
|
4
|
+
export type LogLevel = "debug" | "normal" | "silent" | "verbose";
|
|
5
|
+
export type FileNode = {
|
|
6
|
+
name: string;
|
|
7
|
+
path: string;
|
|
8
|
+
type: FileType;
|
|
9
|
+
size: number;
|
|
10
|
+
children?: FileNode[];
|
|
11
|
+
target?: string;
|
|
12
|
+
isBinary?: boolean;
|
|
13
|
+
skipReason?: SkipReason;
|
|
14
|
+
};
|
|
15
|
+
export type ScanStats = {
|
|
16
|
+
files: number;
|
|
17
|
+
directories: number;
|
|
18
|
+
binary: number;
|
|
19
|
+
skipped: number;
|
|
20
|
+
errors: number;
|
|
21
|
+
totalSizeBytes: number;
|
|
22
|
+
outputSizeBytes: number;
|
|
23
|
+
outputTokenCount: number;
|
|
24
|
+
};
|
|
25
|
+
export type ScanResult = {
|
|
26
|
+
projectName: string;
|
|
27
|
+
root: FileNode;
|
|
28
|
+
stats: ScanStats;
|
|
29
|
+
};
|
|
30
|
+
export type ProgressCallback = (current: number, total: number) => void;
|
|
31
|
+
export type ScanOptions = {
|
|
32
|
+
projectName: string;
|
|
33
|
+
rootDirectory: string;
|
|
34
|
+
excludePatterns: string[];
|
|
35
|
+
includePatterns: string[];
|
|
36
|
+
excludedPaths: string[];
|
|
37
|
+
includeHidden: boolean;
|
|
38
|
+
useGitignore: boolean;
|
|
39
|
+
maximumFileSizeBytes: number;
|
|
40
|
+
maximumTotalSizeBytes: number;
|
|
41
|
+
followSymlinks: boolean;
|
|
42
|
+
onProgress?: ProgressCallback;
|
|
43
|
+
};
|
|
44
|
+
export type RenderOptions = {
|
|
45
|
+
outputFile: string;
|
|
46
|
+
format: OutputFormat;
|
|
47
|
+
includeTree: boolean;
|
|
48
|
+
includeContents: boolean;
|
|
49
|
+
useAnsi: boolean;
|
|
50
|
+
banner?: string;
|
|
51
|
+
footer?: string;
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,IAAI,CAAC;AAEzC,MAAM,MAAM,UAAU,GAAG,WAAW,GAAG,WAAW,GAAG,cAAc,GAAG,UAAU,GAAG,gBAAgB,CAAC;AAEpG,MAAM,MAAM,QAAQ,GAAG,WAAW,GAAG,MAAM,GAAG,SAAS,CAAC;AAExD,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEjE,MAAM,MAAM,QAAQ,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,UAAU,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,SAAS,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;AAExE,MAAM,MAAM,WAAW,GAAG;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY,EAAE,OAAO,CAAC;IACtB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,cAAc,EAAE,OAAO,CAAC;IACxB,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,YAAY,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,eAAe,EAAE,OAAO,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,OAAO,CAAC;AACtB,cAAc,QAAQ,CAAC;AACvB,cAAc,WAAW,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"countTokens.d.ts","sourceRoot":"","sources":["../../src/infra/countTokens.ts"],"names":[],"mappings":"AAqEA,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAuKhD"}
|