fln 1.1.3 → 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.js +67 -30
- package/dist/api/types.d.ts +12 -1
- package/dist/cli/commandLine.js +68 -18
- package/dist/cli/help.d.ts +1 -1
- package/dist/cli/help.js +20 -8
- package/dist/cli/output/components/index.d.ts +0 -1
- package/dist/cli/output/components/index.js +0 -1
- package/dist/cli/output/renderer.d.ts +1 -3
- package/dist/cli/output/renderer.js +2 -5
- package/dist/config/defaults.d.ts +1 -1
- package/dist/config/defaults.js +1 -1
- package/dist/config/index.d.ts +1 -0
- package/dist/config/index.js +1 -0
- package/dist/config/initTemplate.d.ts +1 -0
- package/dist/config/initTemplate.js +32 -0
- package/dist/config/loader.d.ts +6 -1
- package/dist/config/loader.js +10 -4
- package/dist/config/resolver.d.ts +5 -2
- package/dist/config/resolver.js +31 -21
- package/dist/config/types.d.ts +18 -7
- package/dist/config/utils.d.ts +5 -2
- package/dist/config/utils.js +54 -48
- package/dist/core/ignoreMatcher.d.ts +3 -2
- package/dist/core/ignoreMatcher.js +32 -34
- package/dist/core/renderOutput.js +46 -45
- package/dist/core/scanTree.js +47 -70
- package/dist/core/size.js +6 -20
- package/dist/core/types.d.ts +6 -6
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -2
- 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 +2 -0
- package/dist/infra/index.js +2 -0
- package/dist/infra/logger.d.ts +1 -1
- package/dist/infra/logger.js +15 -18
- package/dist/infra/outputWriter.d.ts +1 -1
- package/dist/infra/outputWriter.js +21 -3
- 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
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 pLimit from "p-limit";
|
|
6
|
+
import { toCanonicalRelative, toIgnoreSafePath, toPosixPath } from "../path/index.js";
|
|
7
|
+
import { normalizeIncludePattern } from "../pattern/index.js";
|
|
5
8
|
import { IgnoreMatcher } from "./ignoreMatcher.js";
|
|
6
|
-
function normalizePathSegment(pathSegment) {
|
|
7
|
-
if (sep === "/")
|
|
8
|
-
return pathSegment;
|
|
9
|
-
return pathSegment.split(sep).join("/");
|
|
10
|
-
}
|
|
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
package/dist/infra/index.js
CHANGED
package/dist/infra/logger.d.ts
CHANGED
package/dist/infra/logger.js
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
import
|
|
1
|
+
import pc from "picocolors";
|
|
2
|
+
import { getTerminalInfo, renderBox, symbols } from "./terminal.js";
|
|
2
3
|
export function createLogger(options) {
|
|
3
|
-
const { useAnsi, logLevel } = options;
|
|
4
|
+
const { ansi: useAnsi, logLevel } = options;
|
|
4
5
|
const { width } = getTerminalInfo();
|
|
5
6
|
const isSilent = logLevel === "silent";
|
|
6
7
|
const isVerbose = logLevel === "verbose" || logLevel === "debug";
|
|
7
|
-
const formatMessage = (
|
|
8
|
-
if (!useAnsi)
|
|
9
|
-
return `${symbol} ${message}`;
|
|
10
|
-
return ` ${color}${symbol}${ansi.reset} ${message}`;
|
|
11
|
-
};
|
|
8
|
+
const formatMessage = (symbolOrColored, message) => `${useAnsi ? " " : ""}${symbolOrColored} ${message}`;
|
|
12
9
|
const writeInfo = (formatted) => {
|
|
13
10
|
if (!isSilent)
|
|
14
11
|
console.info(formatted);
|
|
@@ -17,24 +14,24 @@ export function createLogger(options) {
|
|
|
17
14
|
info: (message) => {
|
|
18
15
|
if (!isSilent)
|
|
19
16
|
if (useAnsi)
|
|
20
|
-
console.info(` ${
|
|
17
|
+
console.info(` ${pc.dim(message)}`);
|
|
21
18
|
else
|
|
22
19
|
console.info(message);
|
|
23
20
|
},
|
|
24
21
|
success: (message) => {
|
|
25
|
-
writeInfo(formatMessage(symbols.check
|
|
22
|
+
writeInfo(formatMessage(useAnsi ? pc.green(symbols.check) : symbols.check, message));
|
|
26
23
|
},
|
|
27
24
|
warn: (message) => {
|
|
28
25
|
if (!isSilent)
|
|
29
|
-
console.warn(formatMessage(symbols.warning
|
|
26
|
+
console.warn(formatMessage(useAnsi ? pc.yellow(symbols.warning) : symbols.warning, message));
|
|
30
27
|
},
|
|
31
28
|
error: (message) => {
|
|
32
|
-
console.error(formatMessage(symbols.cross
|
|
29
|
+
console.error(formatMessage(useAnsi ? pc.red(symbols.cross) : symbols.cross, message));
|
|
33
30
|
},
|
|
34
31
|
debug: (message) => {
|
|
35
32
|
if (!isSilent && isVerbose)
|
|
36
33
|
if (useAnsi)
|
|
37
|
-
console.info(` ${
|
|
34
|
+
console.info(` ${pc.dim(`${symbols.info} ${message}`)}`);
|
|
38
35
|
else
|
|
39
36
|
console.info(`${symbols.info} ${message}`);
|
|
40
37
|
},
|
|
@@ -45,9 +42,9 @@ export function createLogger(options) {
|
|
|
45
42
|
const boxWidth = Math.min(width - 4, 60);
|
|
46
43
|
const paddedText = text.padEnd(boxWidth - 4);
|
|
47
44
|
console.info("");
|
|
48
|
-
console.info(
|
|
49
|
-
console.info(`${
|
|
50
|
-
console.info(
|
|
45
|
+
console.info(pc.dim(`${symbols.boxTopLeft}${symbols.boxHorizontal.repeat(boxWidth - 2)}${symbols.boxTopRight}`));
|
|
46
|
+
console.info(`${pc.dim(symbols.boxVertical)}${pc.bold(paddedText)} ${pc.dim(symbols.boxVertical)}`);
|
|
47
|
+
console.info(pc.dim(`${symbols.boxBottomLeft}${symbols.boxHorizontal.repeat(boxWidth - 2)}${symbols.boxBottomRight}`));
|
|
51
48
|
console.info("");
|
|
52
49
|
}
|
|
53
50
|
else {
|
|
@@ -61,7 +58,7 @@ export function createLogger(options) {
|
|
|
61
58
|
return;
|
|
62
59
|
if (useAnsi) {
|
|
63
60
|
console.info("");
|
|
64
|
-
console.info(
|
|
61
|
+
console.info(pc.bold(title));
|
|
65
62
|
console.info("");
|
|
66
63
|
}
|
|
67
64
|
else {
|
|
@@ -72,7 +69,7 @@ export function createLogger(options) {
|
|
|
72
69
|
for (const [key, value] of Object.entries(items)) {
|
|
73
70
|
const paddedKey = key.padEnd(maxKeyLength);
|
|
74
71
|
if (useAnsi)
|
|
75
|
-
console.info(` ${
|
|
72
|
+
console.info(` ${pc.dim(paddedKey)} ${value}`);
|
|
76
73
|
else
|
|
77
74
|
console.info(` ${paddedKey} ${value}`);
|
|
78
75
|
}
|
|
@@ -85,7 +82,7 @@ export function createLogger(options) {
|
|
|
85
82
|
title,
|
|
86
83
|
content,
|
|
87
84
|
width: boxWidth,
|
|
88
|
-
useAnsi,
|
|
85
|
+
ansi: useAnsi,
|
|
89
86
|
showDivider
|
|
90
87
|
});
|
|
91
88
|
console.info("");
|
|
@@ -10,5 +10,5 @@ type OutputWriter = {
|
|
|
10
10
|
tokenCount: number;
|
|
11
11
|
}>;
|
|
12
12
|
};
|
|
13
|
-
export declare function createOutputWriter(
|
|
13
|
+
export declare function createOutputWriter(output: string, maxSizeBytes?: number): Promise<OutputWriter>;
|
|
14
14
|
export {};
|
|
@@ -3,11 +3,29 @@ import { createWriteStream } from "node:fs";
|
|
|
3
3
|
import { mkdir } from "node:fs/promises";
|
|
4
4
|
import { dirname } from "node:path";
|
|
5
5
|
import { countTokens } from "./countTokens.js";
|
|
6
|
-
export async function createOutputWriter(
|
|
7
|
-
|
|
6
|
+
export async function createOutputWriter(output, maxSizeBytes = 0) {
|
|
7
|
+
if (output === "-") {
|
|
8
|
+
let bytesWritten = 0;
|
|
9
|
+
let totalTokenCount = 0;
|
|
10
|
+
const write = async (text) => {
|
|
11
|
+
const textBytes = Buffer.byteLength(text);
|
|
12
|
+
if (maxSizeBytes > 0 && bytesWritten + textBytes > maxSizeBytes)
|
|
13
|
+
throw new Error(`Output size would exceed maximum of ${maxSizeBytes} bytes`);
|
|
14
|
+
bytesWritten += textBytes;
|
|
15
|
+
totalTokenCount += countTokens(text);
|
|
16
|
+
process.stdout.write(text);
|
|
17
|
+
};
|
|
18
|
+
return {
|
|
19
|
+
write,
|
|
20
|
+
writeLine: (text) => write(`${text}\n`),
|
|
21
|
+
getStats: () => ({ sizeBytes: bytesWritten, tokenCount: totalTokenCount }),
|
|
22
|
+
close: async () => ({ sizeBytes: bytesWritten, tokenCount: totalTokenCount })
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const outputDirectory = dirname(output);
|
|
8
26
|
if (outputDirectory !== ".")
|
|
9
27
|
await mkdir(outputDirectory, { recursive: true });
|
|
10
|
-
const stream = createWriteStream(
|
|
28
|
+
const stream = createWriteStream(output, { encoding: "utf8" });
|
|
11
29
|
let bytesWritten = 0;
|
|
12
30
|
let totalTokenCount = 0;
|
|
13
31
|
const write = async (text) => {
|
package/dist/infra/terminal.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ type TerminalInfo = {
|
|
|
5
5
|
export declare function getTerminalInfo(): TerminalInfo;
|
|
6
6
|
export declare function isTTY(): boolean;
|
|
7
7
|
export declare function shouldUseColors(): boolean;
|
|
8
|
-
export declare const
|
|
8
|
+
export declare const cursor: {
|
|
9
9
|
cursorHide: string;
|
|
10
10
|
cursorShow: string;
|
|
11
11
|
cursorUp: (lines: number) => string;
|
|
@@ -13,34 +13,6 @@ export declare const ansi: {
|
|
|
13
13
|
cursorTo: (column: number) => string;
|
|
14
14
|
clearLine: string;
|
|
15
15
|
clearLineRight: string;
|
|
16
|
-
reset: string;
|
|
17
|
-
bold: string;
|
|
18
|
-
dim: string;
|
|
19
|
-
black: string;
|
|
20
|
-
red: string;
|
|
21
|
-
green: string;
|
|
22
|
-
yellow: string;
|
|
23
|
-
blue: string;
|
|
24
|
-
magenta: string;
|
|
25
|
-
cyan: string;
|
|
26
|
-
white: string;
|
|
27
|
-
gray: string;
|
|
28
|
-
bgBlack: string;
|
|
29
|
-
bgRed: string;
|
|
30
|
-
bgGreen: string;
|
|
31
|
-
bgYellow: string;
|
|
32
|
-
bgBlue: string;
|
|
33
|
-
bgMagenta: string;
|
|
34
|
-
bgCyan: string;
|
|
35
|
-
bgWhite: string;
|
|
36
|
-
brightBlack: string;
|
|
37
|
-
brightRed: string;
|
|
38
|
-
brightGreen: string;
|
|
39
|
-
brightYellow: string;
|
|
40
|
-
brightBlue: string;
|
|
41
|
-
brightMagenta: string;
|
|
42
|
-
brightCyan: string;
|
|
43
|
-
brightWhite: string;
|
|
44
16
|
};
|
|
45
17
|
export declare const symbols: {
|
|
46
18
|
dot: string;
|
|
@@ -67,7 +39,7 @@ type ProgressBarOptions = {
|
|
|
67
39
|
total: number;
|
|
68
40
|
current: number;
|
|
69
41
|
width: number;
|
|
70
|
-
|
|
42
|
+
ansi: boolean;
|
|
71
43
|
label?: string;
|
|
72
44
|
suffix?: string;
|
|
73
45
|
};
|
|
@@ -76,7 +48,7 @@ type BoxOptions = {
|
|
|
76
48
|
title?: string;
|
|
77
49
|
content: string[];
|
|
78
50
|
width?: number;
|
|
79
|
-
|
|
51
|
+
ansi: boolean;
|
|
80
52
|
showDivider?: boolean;
|
|
81
53
|
};
|
|
82
54
|
export declare function renderBox(options: BoxOptions): string;
|
|
@@ -86,5 +58,5 @@ export type ProgressRenderer = {
|
|
|
86
58
|
finish: (message?: string) => void;
|
|
87
59
|
cleanup: () => void;
|
|
88
60
|
};
|
|
89
|
-
export declare function createProgressRenderer(label: string,
|
|
61
|
+
export declare function createProgressRenderer(label: string, ansi: boolean, isQuiet: boolean): ProgressRenderer;
|
|
90
62
|
export {};
|