@sayknow-cli/utils 0.3.11 → 0.3.13
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/package.json +7 -8
- package/src/logger.ts +142 -54
- package/src/prompt.ts +205 -180
- package/dist/types/abortable.d.ts +0 -27
- package/dist/types/async.d.ts +0 -6
- package/dist/types/cli.d.ts +0 -126
- package/dist/types/color.d.ts +0 -82
- package/dist/types/dirs.d.ts +0 -163
- package/dist/types/env.d.ts +0 -68
- package/dist/types/fetch-retry.d.ts +0 -80
- package/dist/types/format.d.ts +0 -37
- package/dist/types/frontmatter.d.ts +0 -25
- package/dist/types/fs-error.d.ts +0 -31
- package/dist/types/glob.d.ts +0 -28
- package/dist/types/hook-fetch.d.ts +0 -16
- package/dist/types/index.d.ts +0 -30
- package/dist/types/json.d.ts +0 -4
- package/dist/types/logger.d.ts +0 -66
- package/dist/types/mermaid-ascii.d.ts +0 -11
- package/dist/types/mime.d.ts +0 -29
- package/dist/types/peek-file.d.ts +0 -9
- package/dist/types/postmortem.d.ts +0 -29
- package/dist/types/procmgr.d.ts +0 -35
- package/dist/types/prompt.d.ts +0 -18
- package/dist/types/ptree.d.ts +0 -108
- package/dist/types/ring.d.ts +0 -93
- package/dist/types/safe-stderr.d.ts +0 -1
- package/dist/types/sanitize-text.d.ts +0 -14
- package/dist/types/snowflake.d.ts +0 -25
- package/dist/types/spawn-env.d.ts +0 -4
- package/dist/types/stream.d.ts +0 -68
- package/dist/types/tab-spacing.d.ts +0 -12
- package/dist/types/temp.d.ts +0 -14
- package/dist/types/type-guards.d.ts +0 -3
- package/dist/types/which.d.ts +0 -37
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@sayknow-cli/utils",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.13",
|
|
5
5
|
"description": "Shared utilities for pi packages",
|
|
6
6
|
"homepage": "https://sayknow-cli.com",
|
|
7
7
|
"author": "jaybeyond",
|
|
@@ -21,17 +21,17 @@
|
|
|
21
21
|
"streams"
|
|
22
22
|
],
|
|
23
23
|
"main": "./src/index.ts",
|
|
24
|
-
"types": "./
|
|
24
|
+
"types": "./src/index.ts",
|
|
25
25
|
"scripts": {
|
|
26
26
|
"check": "biome check . && bun run check:types",
|
|
27
|
-
"check:types": "
|
|
27
|
+
"check:types": "tsc -p tsconfig.json --noEmit",
|
|
28
28
|
"lint": "biome lint .",
|
|
29
29
|
"test": "bun test",
|
|
30
30
|
"fix": "biome check --write --unsafe .",
|
|
31
31
|
"fmt": "biome format --write ."
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@sayknow-cli/natives": "0.3.
|
|
34
|
+
"@sayknow-cli/natives": "0.3.13",
|
|
35
35
|
"beautiful-mermaid": "^1.1.3",
|
|
36
36
|
"handlebars": "^4.7.9",
|
|
37
37
|
"winston": "^3.19.0",
|
|
@@ -44,16 +44,15 @@
|
|
|
44
44
|
"bun": ">=1.3.14"
|
|
45
45
|
},
|
|
46
46
|
"files": [
|
|
47
|
-
"src"
|
|
48
|
-
"dist/types"
|
|
47
|
+
"src"
|
|
49
48
|
],
|
|
50
49
|
"exports": {
|
|
51
50
|
".": {
|
|
52
|
-
"types": "./
|
|
51
|
+
"types": "./src/index.ts",
|
|
53
52
|
"import": "./src/index.ts"
|
|
54
53
|
},
|
|
55
54
|
"./*": {
|
|
56
|
-
"types": "./
|
|
55
|
+
"types": "./src/*.ts",
|
|
57
56
|
"import": "./src/*.ts"
|
|
58
57
|
},
|
|
59
58
|
"./*.js": "./src/*.ts"
|
package/src/logger.ts
CHANGED
|
@@ -11,8 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
13
13
|
import * as fs from "node:fs";
|
|
14
|
-
import winston from "winston";
|
|
15
|
-
import DailyRotateFile from "winston-daily-rotate-file";
|
|
14
|
+
import type * as winston from "winston";
|
|
16
15
|
import { getLogsDir } from "./dirs";
|
|
17
16
|
|
|
18
17
|
/** Ensure a logs directory exists; return the resolved path. */
|
|
@@ -23,28 +22,59 @@ function ensureDir(dir: string): string {
|
|
|
23
22
|
return dir;
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
25
|
+
type WinstonModule = typeof import("winston");
|
|
26
|
+
type DailyRotateFileCtor = typeof import("winston-daily-rotate-file");
|
|
27
|
+
type Logger = winston.Logger;
|
|
28
|
+
type Transport = winston.transport;
|
|
29
|
+
type LogLevel = "error" | "warn" | "info" | "debug";
|
|
30
|
+
type LogRecord = { level: LogLevel; message: string; context?: Record<string, unknown> };
|
|
31
|
+
|
|
32
|
+
let winstonModule: WinstonModule | undefined;
|
|
33
|
+
let dailyRotateFileCtor: DailyRotateFileCtor | undefined;
|
|
34
|
+
let winstonLogger: Logger | undefined;
|
|
35
|
+
let loggerInit: Promise<Logger> | undefined;
|
|
36
|
+
let flushingBufferedLogs = false;
|
|
37
|
+
const bufferedLogs: LogRecord[] = [];
|
|
38
|
+
/** Cap pre-init buffering so a hung/failed winston import cannot grow heap unboundedly. */
|
|
39
|
+
const MAX_BUFFERED_LOGS = 10_000;
|
|
40
|
+
let transportOptions: { console?: boolean; file?: boolean | string } = { file: true };
|
|
41
|
+
|
|
42
|
+
async function loadLoggingModules(): Promise<{ winston: WinstonModule; DailyRotateFile: DailyRotateFileCtor }> {
|
|
43
|
+
if (!winstonModule || !dailyRotateFileCtor) {
|
|
44
|
+
const [winstonImport, dailyRotateFileImport] = await Promise.all([
|
|
45
|
+
import("winston"),
|
|
46
|
+
import("winston-daily-rotate-file"),
|
|
47
|
+
]);
|
|
48
|
+
winstonModule = (winstonImport.default ?? winstonImport) as WinstonModule;
|
|
49
|
+
dailyRotateFileCtor = dailyRotateFileImport.default;
|
|
50
|
+
}
|
|
51
|
+
return { winston: winstonModule, DailyRotateFile: dailyRotateFileCtor };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Build the JSON log formatter after winston is loaded. */
|
|
55
|
+
function makeLogFormat(winston: WinstonModule): winston.Logform.Format {
|
|
56
|
+
return winston.format.combine(
|
|
57
|
+
winston.format.timestamp({ format: "YYYY-MM-DDTHH:mm:ss.SSSZ" }),
|
|
58
|
+
winston.format.printf(({ timestamp, level, message, ...meta }) => {
|
|
59
|
+
const entry: Record<string, unknown> = {
|
|
60
|
+
timestamp,
|
|
61
|
+
level,
|
|
62
|
+
pid: process.pid,
|
|
63
|
+
message,
|
|
64
|
+
};
|
|
65
|
+
// Flatten metadata into entry
|
|
66
|
+
for (const [key, value] of Object.entries(meta)) {
|
|
67
|
+
if (key !== "level" && key !== "timestamp" && key !== "message") {
|
|
68
|
+
entry[key] = value;
|
|
69
|
+
}
|
|
40
70
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
71
|
+
return JSON.stringify(entry);
|
|
72
|
+
}),
|
|
73
|
+
);
|
|
74
|
+
}
|
|
45
75
|
|
|
46
76
|
/** Build a rotating file transport, materializing the target directory lazily. */
|
|
47
|
-
function makeFileTransport(dir?: string):
|
|
77
|
+
function makeFileTransport(DailyRotateFile: DailyRotateFileCtor, dir?: string): Transport {
|
|
48
78
|
return new DailyRotateFile({
|
|
49
79
|
dirname: ensureDir(dir ?? getLogsDir()),
|
|
50
80
|
filename: "skc.%DATE%.log",
|
|
@@ -55,18 +85,83 @@ function makeFileTransport(dir?: string): winston.transport {
|
|
|
55
85
|
});
|
|
56
86
|
}
|
|
57
87
|
|
|
58
|
-
function makeConsoleTransport():
|
|
59
|
-
return new winston.transports.Console({ format:
|
|
88
|
+
function makeConsoleTransport(winston: WinstonModule): Transport {
|
|
89
|
+
return new winston.transports.Console({ format: makeLogFormat(winston) });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function applyTransports(
|
|
93
|
+
logger: Logger,
|
|
94
|
+
modules: { winston: WinstonModule; DailyRotateFile: DailyRotateFileCtor },
|
|
95
|
+
): void {
|
|
96
|
+
logger.clear();
|
|
97
|
+
if (transportOptions.file) {
|
|
98
|
+
logger.add(
|
|
99
|
+
makeFileTransport(
|
|
100
|
+
modules.DailyRotateFile,
|
|
101
|
+
typeof transportOptions.file === "string" ? transportOptions.file : undefined,
|
|
102
|
+
),
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
if (transportOptions.console) logger.add(makeConsoleTransport(modules.winston));
|
|
60
106
|
}
|
|
61
107
|
|
|
62
108
|
/** The winston logger instance. Default: file ON (TUI-safe), console OFF. */
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
109
|
+
async function getWinstonLogger(): Promise<Logger> {
|
|
110
|
+
if (winstonLogger) return winstonLogger;
|
|
111
|
+
loggerInit ??= (async () => {
|
|
112
|
+
const modules = await loadLoggingModules();
|
|
113
|
+
const logger = modules.winston.createLogger({
|
|
114
|
+
level: "debug",
|
|
115
|
+
format: makeLogFormat(modules.winston),
|
|
116
|
+
transports: [],
|
|
117
|
+
// Don't exit on error - logging failures shouldn't crash the app
|
|
118
|
+
exitOnError: false,
|
|
119
|
+
});
|
|
120
|
+
applyTransports(logger, modules);
|
|
121
|
+
winstonLogger = logger;
|
|
122
|
+
flushBufferedLogs();
|
|
123
|
+
return logger;
|
|
124
|
+
})();
|
|
125
|
+
return loggerInit;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function flushBufferedLogs(): void {
|
|
129
|
+
if (!winstonLogger || flushingBufferedLogs) return;
|
|
130
|
+
flushingBufferedLogs = true;
|
|
131
|
+
try {
|
|
132
|
+
// Loop: records buffered by reentrant writes during a flush pass (e.g.
|
|
133
|
+
// a transport callback that logs) are drained by the next pass instead
|
|
134
|
+
// of being stranded forever.
|
|
135
|
+
while (bufferedLogs.length > 0) {
|
|
136
|
+
for (const record of bufferedLogs.splice(0)) {
|
|
137
|
+
winstonLogger[record.level](record.message, record.context);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
} finally {
|
|
141
|
+
flushingBufferedLogs = false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function writeLog(level: LogLevel, message: string, context?: Record<string, unknown>): void {
|
|
146
|
+
try {
|
|
147
|
+
if (winstonLogger && !flushingBufferedLogs) {
|
|
148
|
+
winstonLogger[level](message, context);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
bufferedLogs.push({ level, message, context });
|
|
152
|
+
if (bufferedLogs.length > MAX_BUFFERED_LOGS) {
|
|
153
|
+
bufferedLogs.splice(0, bufferedLogs.length - MAX_BUFFERED_LOGS);
|
|
154
|
+
}
|
|
155
|
+
void getWinstonLogger().catch(() => {
|
|
156
|
+
bufferedLogs.length = 0;
|
|
157
|
+
// Allow a later write to retry initialization instead of pinning a
|
|
158
|
+
// rejected promise forever.
|
|
159
|
+
loggerInit = undefined;
|
|
160
|
+
});
|
|
161
|
+
} catch {
|
|
162
|
+
// Silently ignore logging failures
|
|
163
|
+
}
|
|
164
|
+
}
|
|
70
165
|
|
|
71
166
|
/**
|
|
72
167
|
* Replace the active log transports. Pass `console: true, file: false` for
|
|
@@ -74,11 +169,20 @@ const winstonLogger = winston.createLogger({
|
|
|
74
169
|
* logs piped into a process supervisor instead of the rotating file.
|
|
75
170
|
*/
|
|
76
171
|
export function setTransports(opts: { console?: boolean; file?: boolean | string }): void {
|
|
77
|
-
|
|
78
|
-
if (
|
|
79
|
-
winstonLogger
|
|
172
|
+
transportOptions = opts;
|
|
173
|
+
if (winstonLogger && winstonModule && dailyRotateFileCtor) {
|
|
174
|
+
applyTransports(winstonLogger, { winston: winstonModule, DailyRotateFile: dailyRotateFileCtor });
|
|
175
|
+
} else if (loggerInit) {
|
|
176
|
+
// Init in flight: re-apply once it settles so the new options can never
|
|
177
|
+
// be silently skipped by an ordering race inside the init closure.
|
|
178
|
+
void loggerInit
|
|
179
|
+
.then(logger => {
|
|
180
|
+
if (winstonModule && dailyRotateFileCtor) {
|
|
181
|
+
applyTransports(logger, { winston: winstonModule, DailyRotateFile: dailyRotateFileCtor });
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
.catch(() => {});
|
|
80
185
|
}
|
|
81
|
-
if (opts.console) winstonLogger.add(makeConsoleTransport());
|
|
82
186
|
}
|
|
83
187
|
|
|
84
188
|
/**
|
|
@@ -87,11 +191,7 @@ export function setTransports(opts: { console?: boolean; file?: boolean | string
|
|
|
87
191
|
* @param context - The context to log.
|
|
88
192
|
*/
|
|
89
193
|
export function error(message: string, context?: Record<string, unknown>): void {
|
|
90
|
-
|
|
91
|
-
winstonLogger.error(message, context);
|
|
92
|
-
} catch {
|
|
93
|
-
// Silently ignore logging failures
|
|
94
|
-
}
|
|
194
|
+
writeLog("error", message, context);
|
|
95
195
|
}
|
|
96
196
|
|
|
97
197
|
/**
|
|
@@ -100,11 +200,7 @@ export function error(message: string, context?: Record<string, unknown>): void
|
|
|
100
200
|
* @param context - The context to log.
|
|
101
201
|
*/
|
|
102
202
|
export function warn(message: string, context?: Record<string, unknown>): void {
|
|
103
|
-
|
|
104
|
-
winstonLogger.warn(message, context);
|
|
105
|
-
} catch {
|
|
106
|
-
// Silently ignore logging failures
|
|
107
|
-
}
|
|
203
|
+
writeLog("warn", message, context);
|
|
108
204
|
}
|
|
109
205
|
|
|
110
206
|
/**
|
|
@@ -113,11 +209,7 @@ export function warn(message: string, context?: Record<string, unknown>): void {
|
|
|
113
209
|
* @param context - The context to log.
|
|
114
210
|
*/
|
|
115
211
|
export function info(message: string, context?: Record<string, unknown>): void {
|
|
116
|
-
|
|
117
|
-
winstonLogger.info(message, context);
|
|
118
|
-
} catch {
|
|
119
|
-
// Silently ignore logging failures
|
|
120
|
-
}
|
|
212
|
+
writeLog("info", message, context);
|
|
121
213
|
}
|
|
122
214
|
|
|
123
215
|
/**
|
|
@@ -126,11 +218,7 @@ export function info(message: string, context?: Record<string, unknown>): void {
|
|
|
126
218
|
* @param context - The context to log.
|
|
127
219
|
*/
|
|
128
220
|
export function debug(message: string, context?: Record<string, unknown>): void {
|
|
129
|
-
|
|
130
|
-
winstonLogger.debug(message, context);
|
|
131
|
-
} catch {
|
|
132
|
-
// Silently ignore logging failures
|
|
133
|
-
}
|
|
221
|
+
writeLog("debug", message, context);
|
|
134
222
|
}
|
|
135
223
|
|
|
136
224
|
const LOGGED_TIMING_THRESHOLD_MS = 0.5;
|