@kici-dev/shared 0.1.13 → 0.1.14

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/dist/logger.js DELETED
@@ -1,181 +0,0 @@
1
- import "./chunk-gOLHoazu.js";
2
- import { toErrorMessage } from "./error.js";
3
- import { getRequestContext } from "./request-context.js";
4
- import winston from "winston";
5
- import pc from "picocolors";
6
- import DailyRotateFile from "winston-daily-rotate-file";
7
- //#region src/logger.ts
8
- let _serviceName;
9
- /**
10
- * Tracked set of loggers still waiting for the service name so they can
11
- * add their rotated-file transport with the right filename. Module-level
12
- * `createLogger()` calls resolve before the service's `setServiceName()`
13
- * runs; if we built the file transport eagerly, every such logger would
14
- * write to `kici-<instanceId>-*.log` (undefined service name) instead of
15
- * `<service>-<instanceId>-*.log`. Holding them here lets setServiceName
16
- * attach the correct transport once, in one place.
17
- */
18
- const _pendingFileTransportLoggers = /* @__PURE__ */ new Set();
19
- function buildFileTransport() {
20
- const dir = process.env.KICI_LOG_DIR;
21
- if (!dir || dir === "undefined") return void 0;
22
- return new DailyRotateFile({
23
- dirname: dir,
24
- filename: buildLogFilename(_serviceName),
25
- datePattern: "YYYY-MM-DD",
26
- maxSize: process.env.KICI_LOG_MAX_SIZE ?? "500m",
27
- maxFiles: `${process.env.KICI_LOG_RETENTION_DAYS ?? "7"}d`,
28
- format: winston.format.combine(winston.format.timestamp(), winston.format((info) => {
29
- if (_serviceName) info["service"] = _serviceName;
30
- const ctx = getRequestContext();
31
- if (ctx.requestId) info["requestId"] = ctx.requestId;
32
- if (ctx.runId) info["runId"] = ctx.runId;
33
- if (ctx.jobId) info["jobId"] = ctx.jobId;
34
- if (ctx.routingKey) info["routingKey"] = ctx.routingKey;
35
- if (ctx.traceId) info["traceId"] = ctx.traceId;
36
- if (ctx.spanId) info["spanId"] = ctx.spanId;
37
- return info;
38
- })(), winston.format.json()),
39
- zippedArchive: true
40
- });
41
- }
42
- /** Set the service name for all loggers in this process. Call once at startup. */
43
- function setServiceName(name) {
44
- _serviceName = name;
45
- for (const logger of _pendingFileTransportLoggers) {
46
- const transport = buildFileTransport();
47
- if (transport) logger.add(transport);
48
- }
49
- _pendingFileTransportLoggers.clear();
50
- }
51
- /** Get the current service name (for testing/inspection). */
52
- function getServiceName() {
53
- return _serviceName;
54
- }
55
- /**
56
- * Build the rotated log filename. When a stable instance ID is available in the
57
- * environment, append it so multiple processes (e.g. several orchestrators or
58
- * agents) can safely share one KICI_LOG_DIR without racing on the same file.
59
- *
60
- * Precedence matches the tier that owns each variable:
61
- * orchestrator (KICI_CLUSTER_INSTANCE_ID) > agent (KICI_AGENT_ID) > platform
62
- * (KICI_PLATFORM_INSTANCE_ID). Sanitize defensively to filesystem-safe characters.
63
- */
64
- function buildLogFilename(serviceName) {
65
- const base = serviceName ?? "kici";
66
- const suffix = (process.env.KICI_CLUSTER_INSTANCE_ID || process.env.KICI_AGENT_ID || process.env.KICI_PLATFORM_INSTANCE_ID || "").replace(/[^A-Za-z0-9_.-]+/g, "_");
67
- return suffix ? `${base}-${suffix}-%DATE%.log` : `${base}-%DATE%.log`;
68
- }
69
- /**
70
- * Pick the default JSON-vs-plain selection for `createLogger` callers that do
71
- * not pass an explicit `json` option. Honours the operator-controlled
72
- * `KICI_LOG_FORMAT` env var (`json` / `plain` / `auto`); anything else
73
- * (including typos and unset) falls back to TTY detection so a piped CLI
74
- * still produces machine-readable JSON.
75
- */
76
- function pickJsonDefault() {
77
- const envFormat = process.env.KICI_LOG_FORMAT;
78
- if (envFormat === "json") return true;
79
- if (envFormat === "plain") return false;
80
- return !process.stdout.isTTY;
81
- }
82
- /**
83
- * Create a winston logger instance.
84
- *
85
- * @param options - Logger configuration options
86
- * @returns Configured winston logger
87
- */
88
- function createLogger(options = {}) {
89
- const { json = pickJsonDefault(), level = "info", prefix } = options;
90
- const TOKEN_MASK_RE = /kat_[0-9a-f]{64}/gi;
91
- const maskTokens = (value) => {
92
- if (typeof value === "string") return value.replace(TOKEN_MASK_RE, "kat_***");
93
- if (Array.isArray(value)) return value.map(maskTokens);
94
- if (value !== null && typeof value === "object") {
95
- const masked = {};
96
- for (const [k, v] of Object.entries(value)) masked[k] = maskTokens(v);
97
- return masked;
98
- }
99
- return value;
100
- };
101
- const tokenMaskFormat = winston.format((info) => {
102
- if (typeof info.message === "string") info.message = info.message.replace(TOKEN_MASK_RE, "kat_***");
103
- for (const key of Object.keys(info)) {
104
- if (key === "level" || key === "message") continue;
105
- info[key] = maskTokens(info[key]);
106
- }
107
- return info;
108
- });
109
- const traceContextFormat = winston.format((info) => {
110
- if (_serviceName) info["service"] = _serviceName;
111
- const ctx = getRequestContext();
112
- if (ctx.requestId) info["requestId"] = ctx.requestId;
113
- if (ctx.runId) info["runId"] = ctx.runId;
114
- if (ctx.jobId) info["jobId"] = ctx.jobId;
115
- if (ctx.routingKey) info["routingKey"] = ctx.routingKey;
116
- if (ctx.traceId) info["traceId"] = ctx.traceId;
117
- if (ctx.spanId) info["spanId"] = ctx.spanId;
118
- return info;
119
- });
120
- const prettyFormat = winston.format.printf(({ level, message, requestId }) => {
121
- const traceStr = typeof requestId === "string" ? `${pc.dim(`[${requestId.slice(0, 8)}]`)} ` : "";
122
- const prefixStr = prefix ? `${prefix} ` : "";
123
- if (level === "info") return `${traceStr}${prefixStr}${message}`;
124
- let coloredLevel;
125
- switch (level) {
126
- case "error":
127
- coloredLevel = pc.red(level);
128
- break;
129
- case "warn":
130
- coloredLevel = pc.yellow(level);
131
- break;
132
- case "debug":
133
- coloredLevel = pc.gray(level);
134
- break;
135
- default: coloredLevel = level;
136
- }
137
- return `${traceStr}${coloredLevel}: ${prefixStr}${message}`;
138
- });
139
- const jsonFormat = winston.format.combine(winston.format.timestamp(), traceContextFormat(), tokenMaskFormat(), winston.format.json());
140
- const prettyPipeline = winston.format.combine(traceContextFormat(), tokenMaskFormat(), prettyFormat);
141
- const loggerInstance = winston.createLogger({
142
- level,
143
- format: json ? jsonFormat : prettyPipeline,
144
- transports: [new winston.transports.Console()]
145
- });
146
- if (process.env.KICI_LOG_DIR) if (_serviceName) {
147
- const transport = buildFileTransport();
148
- if (transport) loggerInstance.add(transport);
149
- } else _pendingFileTransportLoggers.add(loggerInstance);
150
- return loggerInstance;
151
- }
152
- /**
153
- * Default singleton logger instance for simple use cases. If KICI_LOG_DIR is
154
- * set but setServiceName() hasn't been called yet (CLI tools, scripts),
155
- * the file transport stays deferred and is attached only once a service
156
- * name is known — protecting against writing to `kici-<instanceId>-*.log`.
157
- */
158
- const logger = createLogger();
159
- /**
160
- * Wrap an async startup function so that any thrown error is logged
161
- * through the structured (JSON-aware) logger before the process exits.
162
- *
163
- * Without this guard, a top-level `await` rejection in an ESM entry
164
- * point is printed by Node.js's default handler (multi-line, not JSON),
165
- * which breaks log aggregators like ELK.
166
- */
167
- async function guardStartup(log, fn) {
168
- try {
169
- await fn();
170
- } catch (error) {
171
- log.error("Fatal startup error", {
172
- error: toErrorMessage(error),
173
- stack: error instanceof Error ? error.stack : void 0
174
- });
175
- process.exit(1);
176
- }
177
- }
178
- //#endregion
179
- export { buildLogFilename, createLogger, getServiceName, guardStartup, logger, setServiceName };
180
-
181
- //# sourceMappingURL=logger.js.map
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=logger.test.d.ts.map
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=package-manager.test.d.ts.map
@@ -1,42 +0,0 @@
1
- import { AsyncLocalStorage } from 'node:async_hooks';
2
- /** Trace context fields propagated through the request lifecycle. */
3
- export interface RequestContext {
4
- /** Unique trace ID for a webhook event (UUIDv4). Always present once set. */
5
- requestId: string;
6
- /** Workflow run ID, set when a workflow run is created. */
7
- runId?: string;
8
- /** Job ID, set when processing a specific job. */
9
- jobId?: string;
10
- /** Routing key (e.g. "github:12345"), set when handling a webhook for a source. */
11
- routingKey?: string;
12
- /** OTel trace ID, set when telemetry is active. */
13
- traceId?: string;
14
- /** OTel span ID, set when telemetry is active. */
15
- spanId?: string;
16
- }
17
- /**
18
- * AsyncLocalStorage instance for propagating request context
19
- * through async call chains without explicit parameter passing.
20
- *
21
- * Usage:
22
- * ```ts
23
- * requestContext.run({ requestId: crypto.randomUUID() }, async () => {
24
- * // All code in this callback (and its async descendants) can read the context
25
- * log.info('Processing webhook'); // auto-enriched with requestId
26
- * });
27
- * ```
28
- */
29
- export declare const requestContext: AsyncLocalStorage<RequestContext>;
30
- /**
31
- * Get the current request context, or an empty object if outside a `run()` scope.
32
- * Safe to call anywhere -- never throws.
33
- */
34
- export declare function getRequestContext(): Partial<RequestContext>;
35
- /**
36
- * Merge additional fields into the current request context.
37
- * No-op if called outside a `run()` scope.
38
- *
39
- * @param fields - Partial context fields to merge into the current store
40
- */
41
- export declare function enrichRequestContext(fields: Partial<RequestContext>): void;
42
- //# sourceMappingURL=request-context.d.ts.map
@@ -1,37 +0,0 @@
1
- import "./chunk-gOLHoazu.js";
2
- import { AsyncLocalStorage } from "node:async_hooks";
3
- //#region src/request-context.ts
4
- /**
5
- * AsyncLocalStorage instance for propagating request context
6
- * through async call chains without explicit parameter passing.
7
- *
8
- * Usage:
9
- * ```ts
10
- * requestContext.run({ requestId: crypto.randomUUID() }, async () => {
11
- * // All code in this callback (and its async descendants) can read the context
12
- * log.info('Processing webhook'); // auto-enriched with requestId
13
- * });
14
- * ```
15
- */
16
- const requestContext = new AsyncLocalStorage();
17
- /**
18
- * Get the current request context, or an empty object if outside a `run()` scope.
19
- * Safe to call anywhere -- never throws.
20
- */
21
- function getRequestContext() {
22
- return requestContext.getStore() ?? {};
23
- }
24
- /**
25
- * Merge additional fields into the current request context.
26
- * No-op if called outside a `run()` scope.
27
- *
28
- * @param fields - Partial context fields to merge into the current store
29
- */
30
- function enrichRequestContext(fields) {
31
- const store = requestContext.getStore();
32
- if (store) Object.assign(store, fields);
33
- }
34
- //#endregion
35
- export { enrichRequestContext, getRequestContext, requestContext };
36
-
37
- //# sourceMappingURL=request-context.js.map
package/dist/zx.d.ts DELETED
@@ -1,8 +0,0 @@
1
- /**
2
- * Initialize zx for cross-platform execution.
3
- * Sets the quote function required by zx 8+ on all platforms.
4
- * On Windows, also configures pwsh as shell.
5
- * Call this at the start of any script/binary entry point using zx.
6
- */
7
- export declare function initZx(): void;
8
- //# sourceMappingURL=zx.d.ts.map
package/dist/zx.js DELETED
@@ -1,78 +0,0 @@
1
- import "./chunk-gOLHoazu.js";
2
- import { execSync } from "node:child_process";
3
- import { $, quote, quotePowerShell, usePwsh } from "zx";
4
- //#region src/zx.ts
5
- const PWSH_INSTALL_DOCS = "https://learn.microsoft.com/en-us/powershell/scripting/install/install-powershell-on-windows";
6
- /**
7
- * Initialize zx for cross-platform execution.
8
- * Sets the quote function required by zx 8+ on all platforms.
9
- * On Windows, also configures pwsh as shell.
10
- * Call this at the start of any script/binary entry point using zx.
11
- */
12
- function initZx() {
13
- if (process.platform === "win32") {
14
- try {
15
- usePwsh();
16
- } catch {
17
- ensurePwshWindows();
18
- usePwsh();
19
- }
20
- $.quote = quotePowerShell;
21
- } else $.quote = quote;
22
- }
23
- /**
24
- * Ensure PowerShell Core (pwsh) is installed on Windows.
25
- * Attempts automatic installation via winget using the built-in powershell.exe.
26
- * If winget is unavailable, prints installation instructions and exits.
27
- */
28
- function ensurePwshWindows() {
29
- console.error("PowerShell Core (pwsh) is required but not found in PATH.\nKiCI uses pwsh for cross-platform command execution.\n");
30
- let hasWinget = false;
31
- try {
32
- execSync("powershell.exe -NoProfile -Command \"Get-Command winget -ErrorAction Stop\"", {
33
- stdio: "ignore",
34
- timeout: 1e4
35
- });
36
- hasWinget = true;
37
- } catch {}
38
- if (!hasWinget) {
39
- console.error(`Automatic installation is not possible (winget not found).
40
- Please install PowerShell Core manually:
41
- ${PWSH_INSTALL_DOCS}\n`);
42
- process.exit(1);
43
- }
44
- console.error("Attempting to install PowerShell Core via winget...\n");
45
- try {
46
- execSync("powershell.exe -NoProfile -Command \"winget install --id Microsoft.PowerShell --accept-source-agreements --accept-package-agreements -e --silent\"", {
47
- stdio: "inherit",
48
- timeout: 3e5
49
- });
50
- } catch {
51
- console.error(`
52
- Automatic installation failed.
53
- Please install PowerShell Core manually:
54
- ${PWSH_INSTALL_DOCS}\n`);
55
- process.exit(1);
56
- }
57
- try {
58
- const pwshPath = execSync("powershell.exe -NoProfile -Command \"(Get-Command pwsh -ErrorAction Stop).Source\"", {
59
- encoding: "utf-8",
60
- timeout: 1e4
61
- }).trim();
62
- if (pwshPath) {
63
- const pwshDir = pwshPath.replace(/\\pwsh\.exe$/i, "");
64
- process.env.PATH = `${pwshDir};${process.env.PATH}`;
65
- }
66
- } catch {
67
- console.error(`
68
- PowerShell Core was installed but cannot be found in PATH.
69
- Please restart your terminal or add pwsh to PATH manually.
70
- See: ${PWSH_INSTALL_DOCS}\n`);
71
- process.exit(1);
72
- }
73
- console.error("PowerShell Core installed successfully.\n");
74
- }
75
- //#endregion
76
- export { initZx };
77
-
78
- //# sourceMappingURL=zx.js.map