fln 1.1.2 → 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 +335 -179
- package/dist/api/fln.d.ts +1 -1
- package/dist/api/fln.js +69 -32
- package/dist/api/index.d.ts +2 -2
- package/dist/api/index.js +2 -2
- package/dist/api/types.d.ts +12 -1
- package/dist/cli/commandLine.js +73 -23
- package/dist/cli/help.d.ts +1 -1
- package/dist/cli/help.js +21 -9
- package/dist/cli/index.js +1 -1
- package/dist/cli/output/components/breakdown.js +2 -2
- package/dist/cli/output/components/errors.js +1 -1
- package/dist/cli/output/components/index.d.ts +4 -5
- package/dist/cli/output/components/index.js +4 -5
- package/dist/cli/output/components/summary.js +2 -2
- package/dist/cli/output/components/warnings.js +1 -1
- package/dist/cli/output/index.d.ts +4 -4
- package/dist/cli/output/index.js +4 -4
- package/dist/cli/output/renderer.d.ts +3 -5
- package/dist/cli/output/renderer.js +4 -7
- package/dist/cli/output/styles.d.ts +7 -7
- package/dist/config/defaults.d.ts +1 -1
- package/dist/config/defaults.js +1 -1
- package/dist/config/index.d.ts +6 -5
- package/dist/config/index.js +6 -5
- package/dist/config/initTemplate.d.ts +1 -0
- package/dist/config/initTemplate.js +32 -0
- package/dist/config/loader.d.ts +7 -2
- package/dist/config/loader.js +10 -4
- package/dist/config/resolver.d.ts +6 -3
- package/dist/config/resolver.js +32 -22
- package/dist/config/types.d.ts +19 -8
- package/dist/config/utils.d.ts +5 -2
- package/dist/config/utils.js +54 -48
- package/dist/core/ignoreMatcher.d.ts +4 -3
- package/dist/core/ignoreMatcher.js +32 -34
- package/dist/core/index.d.ts +7 -7
- package/dist/core/index.js +7 -7
- package/dist/core/renderOutput.d.ts +2 -2
- package/dist/core/renderOutput.js +50 -49
- package/dist/core/renderTree.d.ts +1 -1
- package/dist/core/scanTree.d.ts +2 -2
- package/dist/core/scanTree.js +48 -71
- package/dist/core/size.js +6 -20
- package/dist/core/statsCollector.d.ts +1 -1
- package/dist/core/types.d.ts +6 -6
- package/dist/index.d.ts +1 -3
- package/dist/index.js +1 -3
- package/dist/infra/datetime.js +8 -6
- package/dist/infra/deprecate.d.ts +2 -0
- package/dist/infra/deprecate.js +14 -0
- package/dist/infra/gitDiff.d.ts +2 -0
- package/dist/infra/gitDiff.js +33 -0
- package/dist/infra/index.d.ts +8 -6
- package/dist/infra/index.js +8 -6
- package/dist/infra/logger.d.ts +2 -2
- package/dist/infra/logger.js +15 -18
- package/dist/infra/outputWriter.d.ts +1 -1
- package/dist/infra/outputWriter.js +22 -4
- package/dist/infra/terminal.d.ts +4 -32
- package/dist/infra/terminal.js +26 -54
- package/dist/path/canonical.d.ts +1 -0
- package/dist/path/canonical.js +13 -0
- package/dist/path/ignoreSafe.d.ts +1 -0
- package/dist/path/ignoreSafe.js +15 -0
- package/dist/path/index.d.ts +6 -0
- package/dist/path/index.js +6 -0
- package/dist/path/normalize.d.ts +2 -0
- package/dist/path/normalize.js +9 -0
- package/dist/path/output.d.ts +4 -0
- package/dist/path/output.js +12 -0
- package/dist/path/posix.d.ts +1 -0
- package/dist/path/posix.js +4 -0
- package/dist/path/resolve.d.ts +1 -0
- package/dist/path/resolve.js +6 -0
- package/dist/pattern/index.d.ts +1 -0
- package/dist/pattern/index.js +1 -0
- package/dist/pattern/normalize.d.ts +2 -0
- package/dist/pattern/normalize.js +43 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +7 -3
- package/dist/cli/output/components/progressBar.d.ts +0 -6
- package/dist/cli/output/components/progressBar.js +0 -38
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { createReadStream } from "node:fs";
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { extname, join } from "node:path";
|
|
4
|
-
import { createOutputWriter, formatDateTime } from "../infra";
|
|
5
|
-
import { VERSION } from "../version";
|
|
6
|
-
import { renderTree } from "./renderTree";
|
|
7
|
-
import { formatByteSize } from "./size";
|
|
4
|
+
import { createOutputWriter, formatDateTime } from "../infra/index.js";
|
|
5
|
+
import { VERSION } from "../version.js";
|
|
6
|
+
import { renderTree } from "./renderTree.js";
|
|
7
|
+
import { formatByteSize } from "./size.js";
|
|
8
8
|
function getLanguageFromFilename(fileName) {
|
|
9
9
|
const extension = extname(fileName).replace(".", "");
|
|
10
10
|
return extension === "" ? "txt" : extension;
|
|
@@ -48,34 +48,38 @@ async function writeMarkdownContent(writer, filePath) {
|
|
|
48
48
|
if (lastCharacter !== "" && lastCharacter !== "\n")
|
|
49
49
|
await writer.write("\n");
|
|
50
50
|
}
|
|
51
|
-
function
|
|
52
|
-
if (node.type === "file")
|
|
53
|
-
yield node;
|
|
54
|
-
if (node.children)
|
|
55
|
-
for (const child of node.children)
|
|
56
|
-
yield* iterateFileNodes(child);
|
|
57
|
-
}
|
|
58
|
-
function filterSkippedNodes(node) {
|
|
51
|
+
function filterAndCollectFileNodes(node) {
|
|
59
52
|
if (node.skipReason)
|
|
60
|
-
return undefined;
|
|
61
|
-
if (
|
|
62
|
-
return { ...node };
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
53
|
+
return { filtered: undefined, fileNodes: [] };
|
|
54
|
+
if (node.type === "file")
|
|
55
|
+
return { filtered: { ...node }, fileNodes: [node] };
|
|
56
|
+
const allFileNodes = [];
|
|
57
|
+
const filteredChildren = [];
|
|
58
|
+
for (const child of node.children ?? []) {
|
|
59
|
+
const { filtered, fileNodes } = filterAndCollectFileNodes(child);
|
|
60
|
+
allFileNodes.push(...fileNodes);
|
|
61
|
+
if (filtered)
|
|
62
|
+
filteredChildren.push(filtered);
|
|
63
|
+
}
|
|
64
|
+
if (filteredChildren.length === 0)
|
|
65
|
+
return { filtered: undefined, fileNodes: allFileNodes };
|
|
66
|
+
return {
|
|
67
|
+
filtered: { ...node, children: filteredChildren },
|
|
68
|
+
fileNodes: allFileNodes
|
|
69
|
+
};
|
|
67
70
|
}
|
|
68
71
|
async function writeMarkdown(result, config) {
|
|
69
|
-
const writer = await createOutputWriter(config.
|
|
70
|
-
const outputRoot =
|
|
71
|
-
|
|
72
|
-
throw new Error("Root directory was skipped.");
|
|
72
|
+
const writer = await createOutputWriter(config.output, config.maxTotalSize);
|
|
73
|
+
const { filtered: outputRoot, fileNodes } = filterAndCollectFileNodes(result.root);
|
|
74
|
+
const effectiveRoot = outputRoot ?? { ...result.root, children: [] };
|
|
73
75
|
try {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
if (config.output !== "-") {
|
|
77
|
+
await writer.writeLine(`<!-- 🥞 fln ${VERSION} -->`);
|
|
78
|
+
await writer.writeLine("");
|
|
79
|
+
}
|
|
76
80
|
await writer.writeLine(`# Codebase Snapshot: ${result.projectName}`);
|
|
77
81
|
await writer.writeLine("");
|
|
78
|
-
await writer.writeLine(`Generated: ${config.
|
|
82
|
+
await writer.writeLine(`Generated: ${config.date ?? formatDateTime()} `);
|
|
79
83
|
await writer.writeLine(`Files: ${result.stats.files} | Directories: ${result.stats.directories}`);
|
|
80
84
|
await writer.writeLine("");
|
|
81
85
|
await writer.writeLine("---");
|
|
@@ -87,14 +91,14 @@ async function writeMarkdown(result, config) {
|
|
|
87
91
|
if (config.includeTree) {
|
|
88
92
|
await writer.writeLine("## Directory Tree");
|
|
89
93
|
await writer.writeLine("```text");
|
|
90
|
-
await writer.write(renderTree(
|
|
94
|
+
await writer.write(renderTree(effectiveRoot));
|
|
91
95
|
await writer.writeLine("```");
|
|
92
96
|
await writer.writeLine("");
|
|
93
97
|
await writer.writeLine("---");
|
|
94
98
|
await writer.writeLine("");
|
|
95
99
|
}
|
|
96
|
-
if (config.includeContents)
|
|
97
|
-
await writeMarkdownFiles(
|
|
100
|
+
if (config.includeContents && fileNodes.length > 0)
|
|
101
|
+
await writeMarkdownFiles(fileNodes, writer, config);
|
|
98
102
|
if (config.footer) {
|
|
99
103
|
await writer.writeLine("");
|
|
100
104
|
await writer.writeLine(config.footer);
|
|
@@ -109,15 +113,14 @@ async function writeMarkdown(result, config) {
|
|
|
109
113
|
throw error;
|
|
110
114
|
}
|
|
111
115
|
}
|
|
112
|
-
async function writeMarkdownFiles(
|
|
116
|
+
async function writeMarkdownFiles(fileNodes, outputWriter, renderConfig) {
|
|
113
117
|
await outputWriter.writeLine("## Source Files");
|
|
114
118
|
await outputWriter.writeLine("");
|
|
115
|
-
const fileNodes = Array.from(iterateFileNodes(rootNode));
|
|
116
119
|
for (let i = 0; i < fileNodes.length; i++) {
|
|
117
120
|
const node = fileNodes[i];
|
|
118
121
|
const language = getLanguageFromFilename(node.name);
|
|
119
122
|
const isLastFile = i === fileNodes.length - 1;
|
|
120
|
-
const filePath = join(renderConfig.
|
|
123
|
+
const filePath = join(renderConfig.input, node.path);
|
|
121
124
|
let fenceLength = 3;
|
|
122
125
|
if (!node.isBinary)
|
|
123
126
|
try {
|
|
@@ -144,37 +147,37 @@ async function writeMarkdownFiles(rootNode, outputWriter, renderConfig) {
|
|
|
144
147
|
}
|
|
145
148
|
}
|
|
146
149
|
async function writeJson(result, config) {
|
|
147
|
-
const writer = await createOutputWriter(config.
|
|
148
|
-
const outputRoot =
|
|
149
|
-
|
|
150
|
-
throw new Error("Root directory was skipped.");
|
|
150
|
+
const writer = await createOutputWriter(config.output, config.maxTotalSize);
|
|
151
|
+
const { filtered: outputRoot, fileNodes } = filterAndCollectFileNodes(result.root);
|
|
152
|
+
const effectiveRoot = outputRoot ?? { ...result.root, children: [] };
|
|
151
153
|
try {
|
|
152
154
|
await writer.write("{");
|
|
153
155
|
await writer.write(`"version":${JSON.stringify(VERSION)}`);
|
|
154
|
-
await writer.write(`,"generated":${JSON.stringify(config.
|
|
156
|
+
await writer.write(`,"generated":${JSON.stringify(config.date ?? formatDateTime())}`);
|
|
155
157
|
await writer.write(`,"projectName":${JSON.stringify(result.projectName)}`);
|
|
156
|
-
await writer.write(`,"
|
|
157
|
-
await writer.write(`,"
|
|
158
|
+
await writer.write(`,"input":${JSON.stringify(config.input)}`);
|
|
159
|
+
await writer.write(`,"rootDirectory":${JSON.stringify(config.input)}`);
|
|
160
|
+
const { outputSizeBytes: _, outputTokenCount: __, ...statsForJson } = result.stats;
|
|
161
|
+
await writer.write(`,"stats":${JSON.stringify(statsForJson)}`);
|
|
158
162
|
await writer.write(`,"options":${JSON.stringify({
|
|
159
163
|
includeTree: config.includeTree,
|
|
160
164
|
includeContents: config.includeContents,
|
|
161
165
|
format: config.format,
|
|
162
|
-
|
|
163
|
-
|
|
166
|
+
maxFileSize: config.maxFileSize,
|
|
167
|
+
maxTotalSize: config.maxTotalSize,
|
|
164
168
|
includeHidden: config.includeHidden,
|
|
165
|
-
|
|
169
|
+
gitignore: config.gitignore,
|
|
166
170
|
excludePatterns: config.excludePatterns,
|
|
167
171
|
includePatterns: config.includePatterns,
|
|
168
172
|
followSymlinks: config.followSymlinks,
|
|
169
173
|
banner: config.banner,
|
|
170
174
|
footer: config.footer
|
|
171
175
|
})}`);
|
|
172
|
-
await writer.write(`,"tree":${JSON.stringify(
|
|
173
|
-
await writer.write(`,"stats":${JSON.stringify(result.stats)}`);
|
|
176
|
+
await writer.write(`,"tree":${JSON.stringify(effectiveRoot)}`);
|
|
174
177
|
if (config.includeContents) {
|
|
175
178
|
await writer.write(",\"files\":[");
|
|
176
179
|
let isFirst = true;
|
|
177
|
-
for (const node of
|
|
180
|
+
for (const node of fileNodes) {
|
|
178
181
|
if (!isFirst)
|
|
179
182
|
await writer.write(",");
|
|
180
183
|
isFirst = false;
|
|
@@ -182,13 +185,11 @@ async function writeJson(result, config) {
|
|
|
182
185
|
await writer.write(`"path":${JSON.stringify(node.path)}`);
|
|
183
186
|
await writer.write(`,"language":${JSON.stringify(getLanguageFromFilename(node.name))}`);
|
|
184
187
|
await writer.write(`,"isBinary":${JSON.stringify(Boolean(node.isBinary))}`);
|
|
185
|
-
if (node.
|
|
186
|
-
await writer.write(`,"skipReason":${JSON.stringify(node.skipReason)}`);
|
|
187
|
-
if (node.isBinary || node.skipReason)
|
|
188
|
+
if (node.isBinary)
|
|
188
189
|
await writer.write(",\"content\":null");
|
|
189
190
|
else
|
|
190
191
|
try {
|
|
191
|
-
const filePath = join(config.
|
|
192
|
+
const filePath = join(config.input, node.path);
|
|
192
193
|
const content = await readFile(filePath, "utf8");
|
|
193
194
|
await writer.write(`,"content":${JSON.stringify(content)}`);
|
|
194
195
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type { FileNode } from "./types";
|
|
1
|
+
import type { FileNode } from "./types.js";
|
|
2
2
|
export declare function renderTree(root: FileNode): string;
|
package/dist/core/scanTree.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { Logger } from "../infra";
|
|
2
|
-
import type { ScanOptions, ScanResult } from "./types";
|
|
1
|
+
import type { Logger } from "../infra/index.js";
|
|
2
|
+
import type { ScanOptions, ScanResult } from "./types.js";
|
|
3
3
|
export declare function scanTree(options: ScanOptions, logger: Logger): Promise<ScanResult>;
|
package/dist/core/scanTree.js
CHANGED
|
@@ -2,12 +2,10 @@ import { lstat, open, readdir, readlink, realpath, stat } from "node:fs/promises
|
|
|
2
2
|
import { cpus } from "node:os";
|
|
3
3
|
import { relative, sep } from "node:path";
|
|
4
4
|
import ignore from "ignore";
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
return pathSegment.split(sep).join("/");
|
|
10
|
-
}
|
|
5
|
+
import pLimit from "p-limit";
|
|
6
|
+
import { toCanonicalRelative, toIgnoreSafePath, toPosixPath } from "../path/index.js";
|
|
7
|
+
import { normalizeIncludePattern } from "../pattern/index.js";
|
|
8
|
+
import { IgnoreMatcher } from "./ignoreMatcher.js";
|
|
11
9
|
function getFileScore(fileName) {
|
|
12
10
|
const lowerName = fileName.toLowerCase();
|
|
13
11
|
if (lowerName.startsWith("readme"))
|
|
@@ -21,6 +19,7 @@ function getFileScore(fileName) {
|
|
|
21
19
|
lowerName === "makefile" ||
|
|
22
20
|
lowerName === "dockerfile" ||
|
|
23
21
|
lowerName === "vcpkg.json" ||
|
|
22
|
+
lowerName === "pom.xml" ||
|
|
24
23
|
lowerName.startsWith(".env") ||
|
|
25
24
|
lowerName.includes(".config.") ||
|
|
26
25
|
lowerName.startsWith(".prettier") ||
|
|
@@ -55,34 +54,17 @@ function getFileScore(fileName) {
|
|
|
55
54
|
return 20;
|
|
56
55
|
return 10;
|
|
57
56
|
}
|
|
58
|
-
async function
|
|
57
|
+
async function inspectFile(filePath, fileSize) {
|
|
59
58
|
if (fileSize === 0)
|
|
60
|
-
return false;
|
|
59
|
+
return { isGenerated: false, isBinary: false };
|
|
61
60
|
const handle = await open(filePath, "r");
|
|
62
61
|
try {
|
|
63
62
|
const buffer = Buffer.alloc(Math.min(512, fileSize));
|
|
64
63
|
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return
|
|
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;
|
|
64
|
+
const header = buffer.toString("utf8", 0, Math.min(100, bytesRead));
|
|
65
|
+
const isGenerated = header.includes("<!-- 🥞 fln");
|
|
66
|
+
const isBinary = !isGenerated && buffer.slice(0, bytesRead).includes(0);
|
|
67
|
+
return { isGenerated, isBinary };
|
|
86
68
|
}
|
|
87
69
|
finally {
|
|
88
70
|
await handle.close();
|
|
@@ -100,37 +82,52 @@ export async function scanTree(options, logger) {
|
|
|
100
82
|
outputTokenCount: 0
|
|
101
83
|
};
|
|
102
84
|
const ignoreMatcher = new IgnoreMatcher({
|
|
103
|
-
|
|
85
|
+
input: options.input,
|
|
104
86
|
excludePatterns: options.excludePatterns,
|
|
105
|
-
|
|
87
|
+
gitignore: options.gitignore,
|
|
106
88
|
logger
|
|
107
89
|
});
|
|
108
|
-
const
|
|
90
|
+
const normalizedIncludePatterns = options.includePatterns
|
|
91
|
+
.map(pattern => normalizeIncludePattern(pattern, options.input))
|
|
92
|
+
.filter((p) => p !== null);
|
|
93
|
+
const includeMatcher = ignore().add(normalizedIncludePatterns);
|
|
109
94
|
const concurrencyLimit = Math.max(8, Math.min(64, cpus().length * 4));
|
|
110
|
-
const
|
|
95
|
+
const limit = pLimit(concurrencyLimit);
|
|
96
|
+
const excludedPathSet = new Set(options.excludedPaths
|
|
97
|
+
.map(path => toCanonicalRelative(path, options.input))
|
|
98
|
+
.filter((p) => p !== null && p !== ""));
|
|
111
99
|
const visitedRealPaths = new Set();
|
|
112
100
|
let processedItems = 0;
|
|
113
101
|
let totalEstimate = 0;
|
|
114
102
|
if (options.followSymlinks)
|
|
115
103
|
try {
|
|
116
|
-
const rootRealPath = await realpath(options.
|
|
104
|
+
const rootRealPath = await realpath(options.input);
|
|
117
105
|
visitedRealPaths.add(rootRealPath);
|
|
118
106
|
}
|
|
119
107
|
catch {
|
|
120
108
|
logger.debug("Failed to resolve root real path.");
|
|
121
109
|
}
|
|
122
|
-
const rootNode = await scanEntry(options.
|
|
110
|
+
const rootNode = await scanEntry(options.input, "");
|
|
123
111
|
if (!rootNode || rootNode.type !== "directory")
|
|
124
112
|
throw new Error("Root directory is empty or all files were excluded.");
|
|
125
113
|
return { projectName: options.projectName, root: rootNode, stats };
|
|
126
114
|
async function scanEntry(currentPath, relativePath, dirent) {
|
|
127
|
-
const normalizedRelativePath =
|
|
115
|
+
const normalizedRelativePath = toPosixPath(relativePath);
|
|
128
116
|
const name = dirent?.name ?? currentPath.split(sep).pop() ?? "";
|
|
129
117
|
if (normalizedRelativePath !== "" && excludedPathSet.has(normalizedRelativePath))
|
|
130
118
|
return undefined;
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
|
|
119
|
+
const pathForCheck = normalizedRelativePath === "" ? "" : (dirent?.isDirectory() ? `${normalizedRelativePath}/` : normalizedRelativePath);
|
|
120
|
+
const safePath = toIgnoreSafePath(pathForCheck, options.input);
|
|
121
|
+
const isExplicitlyIncluded = safePath !== null &&
|
|
122
|
+
safePath !== "" &&
|
|
123
|
+
includeMatcher.ignores(safePath);
|
|
124
|
+
const isDirectory = pathForCheck.endsWith("/");
|
|
125
|
+
if (normalizedIncludePatterns.length > 0 &&
|
|
126
|
+
!isExplicitlyIncluded &&
|
|
127
|
+
pathForCheck !== "" &&
|
|
128
|
+
!isDirectory)
|
|
129
|
+
return undefined;
|
|
130
|
+
if (!isExplicitlyIncluded && pathForCheck !== "" && ignoreMatcher.ignoresSafePath(safePath))
|
|
134
131
|
return undefined;
|
|
135
132
|
if (!options.includeHidden && name.startsWith(".") && name !== ".")
|
|
136
133
|
return undefined;
|
|
@@ -255,20 +252,23 @@ export async function scanTree(options, logger) {
|
|
|
255
252
|
if (options.onProgress)
|
|
256
253
|
options.onProgress(processedItems, Math.max(totalEstimate, processedItems));
|
|
257
254
|
let skipReason;
|
|
258
|
-
if (!input.isExplicitlyIncluded && await isGeneratedFile(input.currentPath, input.fileSize))
|
|
259
|
-
skipReason = "generated";
|
|
260
|
-
if (!skipReason && input.fileSize > options.maximumFileSizeBytes)
|
|
261
|
-
skipReason = "tooLarge";
|
|
262
255
|
let isBinary = false;
|
|
263
|
-
|
|
256
|
+
const needsRead = input.fileSize > 0 &&
|
|
257
|
+
(input.fileSize <= options.maxFileSize || !input.isExplicitlyIncluded);
|
|
258
|
+
if (needsRead)
|
|
264
259
|
try {
|
|
265
|
-
isBinary = await
|
|
260
|
+
const { isGenerated, isBinary: binary } = await inspectFile(input.currentPath, input.fileSize);
|
|
261
|
+
if (!input.isExplicitlyIncluded && isGenerated)
|
|
262
|
+
skipReason = "generated";
|
|
263
|
+
isBinary = binary;
|
|
266
264
|
}
|
|
267
265
|
catch (error) {
|
|
268
266
|
stats.errors++;
|
|
269
267
|
skipReason = "readError";
|
|
270
268
|
logger.warn(`Failed to read ${input.normalizedRelativePath || "."}: ${String(error)}`);
|
|
271
269
|
}
|
|
270
|
+
if (!skipReason && input.fileSize > options.maxFileSize)
|
|
271
|
+
skipReason = "tooLarge";
|
|
272
272
|
if (isBinary)
|
|
273
273
|
stats.binary++;
|
|
274
274
|
if (skipReason) {
|
|
@@ -297,11 +297,11 @@ export async function scanTree(options, logger) {
|
|
|
297
297
|
await ignoreMatcher.addGitignoreForDirectory(input.currentPath);
|
|
298
298
|
const entries = await readdir(input.currentPath, { withFileTypes: true });
|
|
299
299
|
totalEstimate = Math.max(totalEstimate, processedItems + entries.length);
|
|
300
|
-
const children = (await
|
|
300
|
+
const children = (await Promise.all(entries.map(entry => limit(() => {
|
|
301
301
|
const childPath = `${input.currentPath}${sep}${entry.name}`;
|
|
302
|
-
const childRelativePath = relative(options.
|
|
302
|
+
const childRelativePath = relative(options.input, childPath);
|
|
303
303
|
return scanEntry(childPath, childRelativePath, entry);
|
|
304
|
-
}))
|
|
304
|
+
}))))
|
|
305
305
|
.filter((node) => node !== undefined)
|
|
306
306
|
.sort((left, right) => {
|
|
307
307
|
if (left.type !== right.type)
|
|
@@ -324,26 +324,3 @@ export async function scanTree(options, logger) {
|
|
|
324
324
|
};
|
|
325
325
|
}
|
|
326
326
|
}
|
|
327
|
-
async function mapWithConcurrency(items, concurrency, mapper) {
|
|
328
|
-
if (items.length === 0)
|
|
329
|
-
return [];
|
|
330
|
-
const limit = Math.max(1, Math.min(concurrency, items.length));
|
|
331
|
-
if (limit === 1) {
|
|
332
|
-
const results = [];
|
|
333
|
-
for (const item of items)
|
|
334
|
-
results.push(await mapper(item));
|
|
335
|
-
return results;
|
|
336
|
-
}
|
|
337
|
-
const results = new Array(items.length);
|
|
338
|
-
let nextIndex = 0;
|
|
339
|
-
const workers = Array.from({ length: limit }, async () => {
|
|
340
|
-
while (true) {
|
|
341
|
-
const currentIndex = nextIndex++;
|
|
342
|
-
if (currentIndex >= items.length)
|
|
343
|
-
return;
|
|
344
|
-
results[currentIndex] = await mapper(items[currentIndex]);
|
|
345
|
-
}
|
|
346
|
-
});
|
|
347
|
-
await Promise.all(workers);
|
|
348
|
-
return results;
|
|
349
|
-
}
|
package/dist/core/size.js
CHANGED
|
@@ -1,27 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
const mebibyte = kibibyte * 1024;
|
|
3
|
-
const gibibyte = mebibyte * 1024;
|
|
1
|
+
import bytes from "bytes";
|
|
4
2
|
export function parseByteSize(input) {
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
if (!match)
|
|
3
|
+
const result = bytes.parse(input.trim());
|
|
4
|
+
if (result === null || result < 0)
|
|
8
5
|
throw new Error(`Invalid size: "${input}"`);
|
|
9
|
-
|
|
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);
|
|
6
|
+
return Math.floor(result);
|
|
16
7
|
}
|
|
17
8
|
export function formatByteSize(sizeBytes) {
|
|
18
|
-
|
|
19
|
-
|
|
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`;
|
|
9
|
+
const result = bytes(sizeBytes, { unitSeparator: " " });
|
|
10
|
+
return result ?? `${sizeBytes} B`;
|
|
25
11
|
}
|
|
26
12
|
export function formatTokenCount(count) {
|
|
27
13
|
if (count >= 1_000_000)
|
package/dist/core/types.d.ts
CHANGED
|
@@ -30,23 +30,23 @@ export type ScanResult = {
|
|
|
30
30
|
export type ProgressCallback = (current: number, total: number) => void;
|
|
31
31
|
export type ScanOptions = {
|
|
32
32
|
projectName: string;
|
|
33
|
-
|
|
33
|
+
input: string;
|
|
34
34
|
excludePatterns: string[];
|
|
35
35
|
includePatterns: string[];
|
|
36
36
|
excludedPaths: string[];
|
|
37
37
|
includeHidden: boolean;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
gitignore: boolean;
|
|
39
|
+
maxFileSize: number;
|
|
40
|
+
maxTotalSize: number;
|
|
41
41
|
followSymlinks: boolean;
|
|
42
42
|
onProgress?: ProgressCallback;
|
|
43
43
|
};
|
|
44
44
|
export type RenderOptions = {
|
|
45
|
-
|
|
45
|
+
output: string;
|
|
46
46
|
format: OutputFormat;
|
|
47
47
|
includeTree: boolean;
|
|
48
48
|
includeContents: boolean;
|
|
49
|
-
|
|
49
|
+
ansi: boolean;
|
|
50
50
|
banner?: string;
|
|
51
51
|
footer?: string;
|
|
52
52
|
};
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/infra/datetime.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
const generatedDateRegex = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/;
|
|
2
2
|
export function formatDateTime() {
|
|
3
3
|
const now = new Date();
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
return `${[
|
|
5
|
+
now.getFullYear(),
|
|
6
|
+
String(now.getMonth() + 1).padStart(2, "0"),
|
|
7
|
+
String(now.getDate()).padStart(2, "0")
|
|
8
|
+
].join("-")} ${[
|
|
9
|
+
String(now.getHours()).padStart(2, "0"),
|
|
10
|
+
String(now.getMinutes()).padStart(2, "0")
|
|
11
|
+
].join(":")}`;
|
|
10
12
|
}
|
|
11
13
|
export function parseGeneratedDate(value) {
|
|
12
14
|
if (!generatedDateRegex.test(value.trim()))
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare function warnDeprecated(oldName: string, newName: string, context?: string): void;
|
|
2
|
+
export declare function resolveOption<T, O extends Record<string, unknown> = Record<string, unknown>>(options: O, newKey: keyof O, deprecatedKey: keyof O, context: string, parse?: (value: unknown) => T | undefined): T | undefined;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function warnDeprecated(oldName, newName, context) {
|
|
2
|
+
const message = context ?
|
|
3
|
+
`fln: "${oldName}" is deprecated, use "${newName}" instead (${context})` :
|
|
4
|
+
`fln: "${oldName}" is deprecated, use "${newName}" instead`;
|
|
5
|
+
console.warn(message);
|
|
6
|
+
}
|
|
7
|
+
export function resolveOption(options, newKey, deprecatedKey, context, parse) {
|
|
8
|
+
const newVal = options[newKey];
|
|
9
|
+
const deprecatedVal = options[deprecatedKey];
|
|
10
|
+
if (deprecatedVal !== undefined && newVal === undefined)
|
|
11
|
+
warnDeprecated(String(deprecatedKey), String(newKey), context);
|
|
12
|
+
const raw = newVal ?? deprecatedVal;
|
|
13
|
+
return raw === undefined ? undefined : (parse ? parse(raw) : raw);
|
|
14
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { join, relative } from "node:path";
|
|
3
|
+
import { toPosixPath } from "../path/index.js";
|
|
4
|
+
export function getChangedFilesSince(ref, cwd) {
|
|
5
|
+
const result = spawnSync("git", ["diff", "--name-only", ref], {
|
|
6
|
+
cwd,
|
|
7
|
+
encoding: "utf8",
|
|
8
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
9
|
+
});
|
|
10
|
+
if (result.error)
|
|
11
|
+
throw new Error(`fln: git not found (${result.error.message}). Install git and ensure it is in PATH.`);
|
|
12
|
+
if (result.status !== 0) {
|
|
13
|
+
const stderr = (result.stderr ?? "").trim();
|
|
14
|
+
throw new Error(stderr ?
|
|
15
|
+
`fln: git diff failed: ${stderr}` :
|
|
16
|
+
`fln: git diff failed (exit ${result.status}). Not a git repository or invalid ref: ${ref}`);
|
|
17
|
+
}
|
|
18
|
+
const output = (result.stdout ?? "").trim();
|
|
19
|
+
if (!output)
|
|
20
|
+
return [];
|
|
21
|
+
return output.split("\n").filter(Boolean);
|
|
22
|
+
}
|
|
23
|
+
export function filterPathsUnderBase(gitPaths, cwd, inputBase) {
|
|
24
|
+
return gitPaths
|
|
25
|
+
.map(gitPath => {
|
|
26
|
+
const absolutePath = join(cwd, gitPath);
|
|
27
|
+
const relativeToInput = relative(inputBase, absolutePath);
|
|
28
|
+
if (relativeToInput.startsWith("..") || relativeToInput === "")
|
|
29
|
+
return null;
|
|
30
|
+
return toPosixPath(relativeToInput);
|
|
31
|
+
})
|
|
32
|
+
.filter((path) => path !== null && path !== "");
|
|
33
|
+
}
|
package/dist/infra/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
export * from "./countTokens";
|
|
2
|
-
export * from "./datetime";
|
|
3
|
-
export * from "./
|
|
4
|
-
export * from "./
|
|
5
|
-
export * from "./
|
|
6
|
-
export * from "./
|
|
1
|
+
export * from "./countTokens.js";
|
|
2
|
+
export * from "./datetime.js";
|
|
3
|
+
export * from "./deprecate.js";
|
|
4
|
+
export * from "./gitDiff.js";
|
|
5
|
+
export * from "./logger.js";
|
|
6
|
+
export * from "./outputWriter.js";
|
|
7
|
+
export * from "./terminal.js";
|
|
8
|
+
export * from "./usageTracker.js";
|
package/dist/infra/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
export * from "./countTokens";
|
|
2
|
-
export * from "./datetime";
|
|
3
|
-
export * from "./
|
|
4
|
-
export * from "./
|
|
5
|
-
export * from "./
|
|
6
|
-
export * from "./
|
|
1
|
+
export * from "./countTokens.js";
|
|
2
|
+
export * from "./datetime.js";
|
|
3
|
+
export * from "./deprecate.js";
|
|
4
|
+
export * from "./gitDiff.js";
|
|
5
|
+
export * from "./logger.js";
|
|
6
|
+
export * from "./outputWriter.js";
|
|
7
|
+
export * from "./terminal.js";
|
|
8
|
+
export * from "./usageTracker.js";
|
package/dist/infra/logger.d.ts
CHANGED