@kici-dev/shared 0.1.12 → 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/cold-store/chunk-encoder.js +1 -1
- package/dist/cold-store/chunk-id.js +1 -1
- package/dist/cold-store/cold-store.js +1 -1
- package/dist/db-admin.d.ts +10 -0
- package/dist/db-admin.js +4 -0
- package/dist/env/define-env.js +27 -6
- package/dist/env/logger-env.d.ts +1 -1
- package/dist/graceful-shutdown.js +1 -1
- package/dist/idempotency.d.ts +1 -67
- package/dist/idempotency.js +2 -48
- package/dist/index.d.ts +1 -7
- package/dist/index.js +2 -8
- package/dist/package-manager-types.d.ts +1 -21
- package/dist/package-manager-types.js +2 -34
- package/dist/package-manager.d.ts +1 -51
- package/dist/package-manager.js +2 -130
- package/dist/ts-loader-hook.d.ts +1 -25
- package/dist/ts-loader-hook.js +2 -47
- package/package.json +3 -2
- package/sbom.spdx.json +60 -5
- package/dist/crypto.d.ts +0 -33
- package/dist/crypto.js +0 -67
- package/dist/error.d.ts +0 -16
- package/dist/error.js +0 -58
- package/dist/error.test.d.ts +0 -2
- package/dist/format-bytes.d.ts +0 -5
- package/dist/format-bytes.js +0 -15
- package/dist/format-bytes.test.d.ts +0 -2
- package/dist/format-duration.d.ts +0 -11
- package/dist/format-duration.js +0 -32
- package/dist/format-duration.test.d.ts +0 -2
- package/dist/idempotency.test.d.ts +0 -2
- package/dist/logger.d.ts +0 -60
- package/dist/logger.js +0 -181
- package/dist/logger.test.d.ts +0 -2
- package/dist/package-manager.test.d.ts +0 -2
- package/dist/request-context.d.ts +0 -42
- package/dist/request-context.js +0 -37
- package/dist/zx.d.ts +0 -8
- package/dist/zx.js +0 -78
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
|
package/dist/logger.test.d.ts
DELETED
|
@@ -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
|
package/dist/request-context.js
DELETED
|
@@ -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
|