@spectratools/cli-shared 0.1.2 → 0.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/dist/chunk-MCKGQKYU.js +15 -0
- package/dist/chunk-YWGC33UJ.js +201 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +14 -1
- package/dist/middleware/index.js +1 -0
- package/dist/telemetry/index.d.ts +47 -0
- package/dist/telemetry/index.js +15 -0
- package/dist/testing/index.js +1 -0
- package/dist/utils/index.js +1 -0
- package/package.json +13 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
8
|
+
var __commonJS = (cb, mod) => function __require2() {
|
|
9
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
__require,
|
|
14
|
+
__commonJS
|
|
15
|
+
};
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__commonJS,
|
|
3
|
+
__require
|
|
4
|
+
} from "./chunk-MCKGQKYU.js";
|
|
5
|
+
|
|
6
|
+
// package.json
|
|
7
|
+
var require_package = __commonJS({
|
|
8
|
+
"package.json"(exports, module) {
|
|
9
|
+
module.exports = {
|
|
10
|
+
name: "@spectratools/cli-shared",
|
|
11
|
+
version: "0.2.0",
|
|
12
|
+
description: "Shared middleware, utilities, and testing helpers for spectra CLI tools",
|
|
13
|
+
type: "module",
|
|
14
|
+
license: "MIT",
|
|
15
|
+
author: "spectra-the-bot",
|
|
16
|
+
engines: {
|
|
17
|
+
node: ">=20"
|
|
18
|
+
},
|
|
19
|
+
exports: {
|
|
20
|
+
".": {
|
|
21
|
+
types: "./dist/index.d.ts",
|
|
22
|
+
default: "./dist/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./middleware": {
|
|
25
|
+
types: "./dist/middleware/index.d.ts",
|
|
26
|
+
default: "./dist/middleware/index.js"
|
|
27
|
+
},
|
|
28
|
+
"./utils": {
|
|
29
|
+
types: "./dist/utils/index.d.ts",
|
|
30
|
+
default: "./dist/utils/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./telemetry": {
|
|
33
|
+
types: "./dist/telemetry/index.d.ts",
|
|
34
|
+
default: "./dist/telemetry/index.js"
|
|
35
|
+
},
|
|
36
|
+
"./testing": {
|
|
37
|
+
types: "./dist/testing/index.d.ts",
|
|
38
|
+
default: "./dist/testing/index.js"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
scripts: {
|
|
42
|
+
build: "tsup",
|
|
43
|
+
typecheck: "tsc --noEmit -p tsconfig.json",
|
|
44
|
+
test: "vitest run",
|
|
45
|
+
prepublishOnly: "pnpm build"
|
|
46
|
+
},
|
|
47
|
+
devDependencies: {
|
|
48
|
+
typescript: "5.7.3",
|
|
49
|
+
vitest: "2.1.8"
|
|
50
|
+
},
|
|
51
|
+
dependencies: {
|
|
52
|
+
"@opentelemetry/api": "^1.9.0",
|
|
53
|
+
"@opentelemetry/exporter-metrics-otlp-http": "^0.213.0",
|
|
54
|
+
"@opentelemetry/exporter-trace-otlp-http": "^0.213.0",
|
|
55
|
+
"@opentelemetry/resources": "^2.6.0",
|
|
56
|
+
"@opentelemetry/sdk-metrics": "^2.6.0",
|
|
57
|
+
"@opentelemetry/sdk-node": "^0.213.0",
|
|
58
|
+
"@opentelemetry/sdk-trace-node": "^2.6.0",
|
|
59
|
+
"@opentelemetry/semantic-conventions": "^1.40.0",
|
|
60
|
+
ox: "^0.14.0"
|
|
61
|
+
},
|
|
62
|
+
files: ["dist", "README.md"],
|
|
63
|
+
main: "./dist/index.js",
|
|
64
|
+
types: "./dist/index.d.ts"
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// src/telemetry/init.ts
|
|
70
|
+
var sdk;
|
|
71
|
+
var initialized = false;
|
|
72
|
+
function isOtelEnabled() {
|
|
73
|
+
return !!(process.env.OTEL_EXPORTER_OTLP_ENDPOINT || process.env.SPECTRA_OTEL_ENABLED === "true");
|
|
74
|
+
}
|
|
75
|
+
function initTelemetry(serviceName) {
|
|
76
|
+
if (initialized) return;
|
|
77
|
+
initialized = true;
|
|
78
|
+
if (!isOtelEnabled()) return;
|
|
79
|
+
const sdkMod = __require("@opentelemetry/sdk-node");
|
|
80
|
+
const traceExpMod = __require("@opentelemetry/exporter-trace-otlp-http");
|
|
81
|
+
const metricExpMod = __require("@opentelemetry/exporter-metrics-otlp-http");
|
|
82
|
+
const metricsMod = __require("@opentelemetry/sdk-metrics");
|
|
83
|
+
const resourcesMod = __require("@opentelemetry/resources");
|
|
84
|
+
const semconvMod = __require("@opentelemetry/semantic-conventions");
|
|
85
|
+
let version = "0.0.0";
|
|
86
|
+
try {
|
|
87
|
+
const pkg = require_package();
|
|
88
|
+
version = pkg.version ?? version;
|
|
89
|
+
} catch {
|
|
90
|
+
}
|
|
91
|
+
const resolvedServiceName = process.env.OTEL_SERVICE_NAME || `spectra-${serviceName}`;
|
|
92
|
+
const resource = resourcesMod.resourceFromAttributes({
|
|
93
|
+
[semconvMod.ATTR_SERVICE_NAME]: resolvedServiceName,
|
|
94
|
+
[semconvMod.ATTR_SERVICE_VERSION]: version
|
|
95
|
+
});
|
|
96
|
+
const traceExporter = new traceExpMod.OTLPTraceExporter();
|
|
97
|
+
const metricExporter = new metricExpMod.OTLPMetricExporter();
|
|
98
|
+
sdk = new sdkMod.NodeSDK({
|
|
99
|
+
resource,
|
|
100
|
+
traceExporter,
|
|
101
|
+
metricReader: new metricsMod.PeriodicExportingMetricReader({
|
|
102
|
+
exporter: metricExporter
|
|
103
|
+
})
|
|
104
|
+
});
|
|
105
|
+
sdk.start();
|
|
106
|
+
const shutdown = () => {
|
|
107
|
+
sdk?.shutdown().then(
|
|
108
|
+
() => process.exit(0),
|
|
109
|
+
() => process.exit(1)
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
process.on("SIGTERM", shutdown);
|
|
113
|
+
process.on("SIGINT", shutdown);
|
|
114
|
+
}
|
|
115
|
+
function _getSdk() {
|
|
116
|
+
return sdk;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/telemetry/spans.ts
|
|
120
|
+
import { SpanStatusCode, context, trace } from "@opentelemetry/api";
|
|
121
|
+
var TRACER_NAME = "@spectratools/cli-shared";
|
|
122
|
+
var SENSITIVE_PATTERNS = [
|
|
123
|
+
/private[\s_-]?key/i,
|
|
124
|
+
/secret/i,
|
|
125
|
+
/password/i,
|
|
126
|
+
/passphrase/i,
|
|
127
|
+
/mnemonic/i,
|
|
128
|
+
/seed/i,
|
|
129
|
+
/token/i,
|
|
130
|
+
/api[\s_-]?key/i,
|
|
131
|
+
/auth/i
|
|
132
|
+
];
|
|
133
|
+
function sanitizeAttributes(attrs) {
|
|
134
|
+
const result = {};
|
|
135
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
136
|
+
const strKey = key.toLowerCase();
|
|
137
|
+
const strVal = typeof value === "string" ? value : "";
|
|
138
|
+
const isSensitive = SENSITIVE_PATTERNS.some(
|
|
139
|
+
(p) => p.test(strKey) || strVal.length > 0 && p.test(strVal)
|
|
140
|
+
);
|
|
141
|
+
if (isSensitive) continue;
|
|
142
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
143
|
+
result[key] = value;
|
|
144
|
+
} else if (value !== void 0 && value !== null) {
|
|
145
|
+
result[key] = String(value);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
function createCommandSpan(commandName, args) {
|
|
151
|
+
const tracer = trace.getTracer(TRACER_NAME);
|
|
152
|
+
const span = tracer.startSpan(`cli.command.${commandName}`);
|
|
153
|
+
if (args) {
|
|
154
|
+
const safe = sanitizeAttributes(args);
|
|
155
|
+
for (const [k, v] of Object.entries(safe)) {
|
|
156
|
+
span.setAttribute(`cli.arg.${k}`, v);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return span;
|
|
160
|
+
}
|
|
161
|
+
async function withSpan(name, fn) {
|
|
162
|
+
const tracer = trace.getTracer(TRACER_NAME);
|
|
163
|
+
const span = tracer.startSpan(name);
|
|
164
|
+
const ctx = trace.setSpan(context.active(), span);
|
|
165
|
+
try {
|
|
166
|
+
const result = await context.with(ctx, () => fn(span));
|
|
167
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
168
|
+
return result;
|
|
169
|
+
} catch (err) {
|
|
170
|
+
recordError(span, err);
|
|
171
|
+
throw err;
|
|
172
|
+
} finally {
|
|
173
|
+
span.end();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function recordError(span, error) {
|
|
177
|
+
span.setStatus({
|
|
178
|
+
code: SpanStatusCode.ERROR,
|
|
179
|
+
message: error instanceof Error ? error.message : String(error)
|
|
180
|
+
});
|
|
181
|
+
if (error instanceof Error) {
|
|
182
|
+
span.recordException(error);
|
|
183
|
+
} else {
|
|
184
|
+
span.recordException(new Error(String(error)));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// src/telemetry/shutdown.ts
|
|
189
|
+
async function shutdownTelemetry() {
|
|
190
|
+
const sdk2 = _getSdk();
|
|
191
|
+
if (!sdk2) return;
|
|
192
|
+
await sdk2.shutdown();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export {
|
|
196
|
+
initTelemetry,
|
|
197
|
+
createCommandSpan,
|
|
198
|
+
withSpan,
|
|
199
|
+
recordError,
|
|
200
|
+
shutdownTelemetry
|
|
201
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { ApiKeyAuthContext, CursorPaginationOptions, MissingApiKeyError, OffsetPaginationOptions, RateLimitOptions, RetryOptions, apiKeyAuth, createRateLimiter, paginateCursor, paginateOffset, withRateLimit, withRetry } from './middleware/index.js';
|
|
2
|
+
export { createCommandSpan, initTelemetry, recordError, shutdownTelemetry, withSpan } from './telemetry/index.js';
|
|
2
3
|
export { MockResponse, MockServer, RecordedRequest, createMockServer } from './testing/index.js';
|
|
3
4
|
export { HttpClientOptions, HttpError, RequestOptions, checksumAddress, createHttpClient, formatTimestamp, isAddress, truncate, weiToEth } from './utils/index.js';
|
|
5
|
+
import '@opentelemetry/api';
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,13 @@ import {
|
|
|
7
7
|
withRateLimit,
|
|
8
8
|
withRetry
|
|
9
9
|
} from "./chunk-74BQLM22.js";
|
|
10
|
+
import {
|
|
11
|
+
createCommandSpan,
|
|
12
|
+
initTelemetry,
|
|
13
|
+
recordError,
|
|
14
|
+
shutdownTelemetry,
|
|
15
|
+
withSpan
|
|
16
|
+
} from "./chunk-YWGC33UJ.js";
|
|
10
17
|
import {
|
|
11
18
|
createMockServer
|
|
12
19
|
} from "./chunk-QEANTXUE.js";
|
|
@@ -21,20 +28,26 @@ import {
|
|
|
21
28
|
HttpError,
|
|
22
29
|
createHttpClient
|
|
23
30
|
} from "./chunk-TKORXDDX.js";
|
|
31
|
+
import "./chunk-MCKGQKYU.js";
|
|
24
32
|
export {
|
|
25
33
|
HttpError,
|
|
26
34
|
MissingApiKeyError,
|
|
27
35
|
apiKeyAuth,
|
|
28
36
|
checksumAddress,
|
|
37
|
+
createCommandSpan,
|
|
29
38
|
createHttpClient,
|
|
30
39
|
createMockServer,
|
|
31
40
|
createRateLimiter,
|
|
32
41
|
formatTimestamp,
|
|
42
|
+
initTelemetry,
|
|
33
43
|
isAddress,
|
|
34
44
|
paginateCursor,
|
|
35
45
|
paginateOffset,
|
|
46
|
+
recordError,
|
|
47
|
+
shutdownTelemetry,
|
|
36
48
|
truncate,
|
|
37
49
|
weiToEth,
|
|
38
50
|
withRateLimit,
|
|
39
|
-
withRetry
|
|
51
|
+
withRetry,
|
|
52
|
+
withSpan
|
|
40
53
|
};
|
package/dist/middleware/index.js
CHANGED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Span } from '@opentelemetry/api';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Initialize OpenTelemetry SDK for a CLI service.
|
|
5
|
+
*
|
|
6
|
+
* This is a **lazy** initializer — if neither `OTEL_EXPORTER_OTLP_ENDPOINT` nor
|
|
7
|
+
* `SPECTRA_OTEL_ENABLED=true` is set, it returns immediately without importing
|
|
8
|
+
* any OTEL modules, ensuring zero overhead when instrumentation is disabled.
|
|
9
|
+
*
|
|
10
|
+
* @param serviceName - Logical name of the CLI service (used as fallback for `service.name`).
|
|
11
|
+
*/
|
|
12
|
+
declare function initTelemetry(serviceName: string): void;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Create a root span for a CLI command invocation.
|
|
16
|
+
*
|
|
17
|
+
* @param commandName - The command being executed (e.g. `"account balance"`).
|
|
18
|
+
* @param args - Optional key-value arguments to attach as span attributes (sensitive values are stripped).
|
|
19
|
+
* @returns A started `Span`. The caller is responsible for calling `span.end()`.
|
|
20
|
+
*/
|
|
21
|
+
declare function createCommandSpan(commandName: string, args?: Record<string, unknown>): Span;
|
|
22
|
+
/**
|
|
23
|
+
* Convenience wrapper that creates a child span, runs the given async function,
|
|
24
|
+
* and automatically ends the span. On error, the error is recorded on the span
|
|
25
|
+
* before being re-thrown.
|
|
26
|
+
*
|
|
27
|
+
* @param name - Span name.
|
|
28
|
+
* @param fn - Async function to execute within the span context.
|
|
29
|
+
* @returns The result of `fn`.
|
|
30
|
+
*/
|
|
31
|
+
declare function withSpan<T>(name: string, fn: (span: Span) => Promise<T>): Promise<T>;
|
|
32
|
+
/**
|
|
33
|
+
* Attach error details and stack trace to a span and mark it as errored.
|
|
34
|
+
*
|
|
35
|
+
* @param span - The span to annotate.
|
|
36
|
+
* @param error - The error value (may be an `Error` instance or arbitrary value).
|
|
37
|
+
*/
|
|
38
|
+
declare function recordError(span: Span, error: unknown): void;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Flush pending spans/metrics and shut down the OTEL SDK.
|
|
42
|
+
*
|
|
43
|
+
* Safe to call even if telemetry was never initialized — in that case it is a no-op.
|
|
44
|
+
*/
|
|
45
|
+
declare function shutdownTelemetry(): Promise<void>;
|
|
46
|
+
|
|
47
|
+
export { createCommandSpan, initTelemetry, recordError, shutdownTelemetry, withSpan };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createCommandSpan,
|
|
3
|
+
initTelemetry,
|
|
4
|
+
recordError,
|
|
5
|
+
shutdownTelemetry,
|
|
6
|
+
withSpan
|
|
7
|
+
} from "../chunk-YWGC33UJ.js";
|
|
8
|
+
import "../chunk-MCKGQKYU.js";
|
|
9
|
+
export {
|
|
10
|
+
createCommandSpan,
|
|
11
|
+
initTelemetry,
|
|
12
|
+
recordError,
|
|
13
|
+
shutdownTelemetry,
|
|
14
|
+
withSpan
|
|
15
|
+
};
|
package/dist/testing/index.js
CHANGED
package/dist/utils/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spectratools/cli-shared",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Shared middleware, utilities, and testing helpers for spectra CLI tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
"types": "./dist/utils/index.d.ts",
|
|
22
22
|
"default": "./dist/utils/index.js"
|
|
23
23
|
},
|
|
24
|
+
"./telemetry": {
|
|
25
|
+
"types": "./dist/telemetry/index.d.ts",
|
|
26
|
+
"default": "./dist/telemetry/index.js"
|
|
27
|
+
},
|
|
24
28
|
"./testing": {
|
|
25
29
|
"types": "./dist/testing/index.d.ts",
|
|
26
30
|
"default": "./dist/testing/index.js"
|
|
@@ -31,6 +35,14 @@
|
|
|
31
35
|
"vitest": "2.1.8"
|
|
32
36
|
},
|
|
33
37
|
"dependencies": {
|
|
38
|
+
"@opentelemetry/api": "^1.9.0",
|
|
39
|
+
"@opentelemetry/exporter-metrics-otlp-http": "^0.213.0",
|
|
40
|
+
"@opentelemetry/exporter-trace-otlp-http": "^0.213.0",
|
|
41
|
+
"@opentelemetry/resources": "^2.6.0",
|
|
42
|
+
"@opentelemetry/sdk-metrics": "^2.6.0",
|
|
43
|
+
"@opentelemetry/sdk-node": "^0.213.0",
|
|
44
|
+
"@opentelemetry/sdk-trace-node": "^2.6.0",
|
|
45
|
+
"@opentelemetry/semantic-conventions": "^1.40.0",
|
|
34
46
|
"ox": "^0.14.0"
|
|
35
47
|
},
|
|
36
48
|
"files": [
|