@logbrew/sdk 0.1.3 → 0.1.5

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 CHANGED
@@ -1,12 +1,20 @@
1
1
  {
2
2
  "name": "@logbrew/sdk",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Public LogBrew JavaScript SDK for building, validating, and flushing event batches.",
5
5
  "type": "module",
6
6
  "main": "./index.cjs",
7
+ "react-native": "./react-native.js",
7
8
  "types": "./index.d.ts",
9
+ "bin": {
10
+ "logbrew-release-artifacts": "./release-artifacts.js"
11
+ },
8
12
  "exports": {
9
13
  ".": {
14
+ "react-native": {
15
+ "types": "./react-native.d.ts",
16
+ "default": "./react-native.js"
17
+ },
10
18
  "import": {
11
19
  "types": "./index.d.ts",
12
20
  "default": "./index.js"
@@ -16,13 +24,42 @@
16
24
  "default": "./index.cjs"
17
25
  },
18
26
  "default": "./index.js"
27
+ },
28
+ "./vite-release-artifacts": {
29
+ "import": {
30
+ "types": "./vite-release-artifacts.d.ts",
31
+ "default": "./vite-release-artifacts.js"
32
+ },
33
+ "require": {
34
+ "types": "./vite-release-artifacts.d.cts",
35
+ "default": "./vite-release-artifacts.cjs"
36
+ },
37
+ "default": "./vite-release-artifacts.js"
19
38
  }
20
39
  },
21
40
  "files": [
22
41
  "index.cjs",
42
+ "core.cjs",
23
43
  "index.js",
24
44
  "index.d.ts",
25
45
  "index.d.cts",
46
+ "issue-stack.cjs",
47
+ "log-context.cjs",
48
+ "opentelemetry.cjs",
49
+ "react-native.js",
50
+ "react-native.d.ts",
51
+ "support-ticket.cjs",
52
+ "trace-context.cjs",
53
+ "winston.cjs",
54
+ "release-artifacts-common.js",
55
+ "release-artifacts-build.cjs",
56
+ "release-artifacts.js",
57
+ "release-artifacts-upload.js",
58
+ "release-artifacts-symbolication.js",
59
+ "vite-release-artifacts.cjs",
60
+ "vite-release-artifacts.js",
61
+ "vite-release-artifacts.d.ts",
62
+ "vite-release-artifacts.d.cts",
26
63
  "README.md",
27
64
  "examples"
28
65
  ],
@@ -0,0 +1,103 @@
1
+ import * as core from "./index.js";
2
+
3
+ export type {
4
+ ActionAttributes,
5
+ BaggageEntry,
6
+ ConsoleCaptureConfig,
7
+ ConsoleCaptureHandle,
8
+ ConsoleLike,
9
+ ConsoleMethodName,
10
+ CurrentOpenTelemetryTraceContextOptions,
11
+ DeliveryHealthSnapshot,
12
+ DroppedEvent,
13
+ EnvironmentAttributes,
14
+ Event,
15
+ EventFilter,
16
+ EventStore,
17
+ IssueAttributes,
18
+ IssueStackFrame,
19
+ JavaScriptErrorIssueOptions,
20
+ LogAttributes,
21
+ LogCorrelationTraceContext,
22
+ Metadata,
23
+ MetadataValue,
24
+ MetricAttributes,
25
+ NetworkMilestoneInput,
26
+ OpenTelemetryApiLike,
27
+ OpenTelemetryExportResult,
28
+ OpenTelemetryHrTimeLike,
29
+ OpenTelemetryReadableSpanLike,
30
+ OpenTelemetryReadableSpanOptions,
31
+ OpenTelemetrySpanContextLike,
32
+ OpenTelemetrySpanExporterConfig,
33
+ OpenTelemetrySpanExporterHandle,
34
+ OpenTelemetrySpanLike,
35
+ OpenTelemetrySpanLinkLike,
36
+ OpenTelemetrySpanProcessorConfig,
37
+ OpenTelemetrySpanProcessorHandle,
38
+ OpenTelemetryTimedEventLike,
39
+ OpenTelemetryTraceContextOptions,
40
+ PinoDestinationConfig,
41
+ PinoDestinationHandle,
42
+ PinoLogRecord,
43
+ ProductActionInput,
44
+ ReleaseAttributes,
45
+ Severity,
46
+ SeverityAlias,
47
+ SeverityInput,
48
+ SpanAttributes,
49
+ SpanEventSummary,
50
+ SpanLinkSummary,
51
+ StoredEvent,
52
+ SupportDiagnosticsValue,
53
+ SupportTicketCategory,
54
+ SupportTicketDraft,
55
+ SupportTicketDraftInput,
56
+ SupportTicketSource,
57
+ TimelineAttributesOptions,
58
+ TraceContextInput,
59
+ TraceparentContext,
60
+ TraceparentInput,
61
+ TraceparentSpanInput,
62
+ TracestateEntry,
63
+ Transport,
64
+ TransportResponse
65
+ } from "./index.js";
66
+
67
+ export {
68
+ createBaggage,
69
+ createIssueAttributesFromError,
70
+ createLogBrewOpenTelemetrySpanExporter,
71
+ createLogBrewOpenTelemetrySpanProcessor,
72
+ createLogBrewPinoDestination,
73
+ createNetworkMilestoneAttributes,
74
+ createProductActionAttributes,
75
+ createSupportTicketDraft,
76
+ createTraceContextHeaders,
77
+ createTraceparent,
78
+ createTraceparentHeaders,
79
+ createTracestate,
80
+ installLogBrewConsoleCapture,
81
+ LogBrewClient,
82
+ logAttributesFromConsoleArgs,
83
+ logAttributesFromPinoRecord,
84
+ logbrewLevelFromConsoleMethod,
85
+ logbrewTraceContextFromCurrentOpenTelemetrySpan,
86
+ logbrewTraceContextFromOpenTelemetrySpan,
87
+ logbrewTraceContextFromOpenTelemetrySpanContext,
88
+ parseBaggage,
89
+ parseTraceparent,
90
+ parseTracestate,
91
+ RecordingTransport,
92
+ SdkError,
93
+ spanAttributesFromOpenTelemetryReadableSpan,
94
+ spanAttributesFromTraceparent,
95
+ TransportError
96
+ } from "./index.js";
97
+
98
+ declare const sdk: Omit<
99
+ typeof core,
100
+ "createLogBrewWinstonTransport" | "logAttributesFromWinstonInfo"
101
+ >;
102
+
103
+ export default sdk;
@@ -0,0 +1,34 @@
1
+ import sdk from "./core.cjs";
2
+
3
+ export const {
4
+ createBaggage,
5
+ createIssueAttributesFromError,
6
+ createNetworkMilestoneAttributes,
7
+ createProductActionAttributes,
8
+ createLogBrewOpenTelemetrySpanExporter,
9
+ createLogBrewOpenTelemetrySpanProcessor,
10
+ createSupportTicketDraft,
11
+ createTraceContextHeaders,
12
+ createTraceparent,
13
+ createTraceparentHeaders,
14
+ createTracestate,
15
+ createLogBrewPinoDestination,
16
+ installLogBrewConsoleCapture,
17
+ LogBrewClient,
18
+ logbrewTraceContextFromCurrentOpenTelemetrySpan,
19
+ logbrewTraceContextFromOpenTelemetrySpan,
20
+ logbrewTraceContextFromOpenTelemetrySpanContext,
21
+ logAttributesFromConsoleArgs,
22
+ logAttributesFromPinoRecord,
23
+ logbrewLevelFromConsoleMethod,
24
+ parseBaggage,
25
+ parseTraceparent,
26
+ parseTracestate,
27
+ RecordingTransport,
28
+ SdkError,
29
+ spanAttributesFromOpenTelemetryReadableSpan,
30
+ spanAttributesFromTraceparent,
31
+ TransportError
32
+ } = sdk;
33
+
34
+ export default sdk;
@@ -0,0 +1,227 @@
1
+ "use strict";
2
+
3
+ const { spawnSync } = require("node:child_process");
4
+ const net = require("node:net");
5
+
6
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
7
+ const UPLOAD_OPTION_NAMES = new Set([
8
+ "allowHostedUpload",
9
+ "dryRun",
10
+ "endpoint",
11
+ "maxRetries",
12
+ "retryDelay",
13
+ "timeout",
14
+ "tokenEnv",
15
+ ]);
16
+
17
+ function optionalBoolean(options, name, integration, defaultValue = false) {
18
+ const value = options?.[name];
19
+ if (value === undefined || value === null) {
20
+ return defaultValue;
21
+ }
22
+ if (typeof value !== "boolean") {
23
+ throw new Error(`LogBrew ${integration} release-artifact upload option ${name} must be a boolean`);
24
+ }
25
+ return value;
26
+ }
27
+
28
+ function optionalString(options, name, integration) {
29
+ const value = options?.[name];
30
+ if (value === undefined || value === null) {
31
+ return null;
32
+ }
33
+ if (typeof value !== "string" || value.trim() === "") {
34
+ throw new Error(`LogBrew ${integration} release-artifact upload option ${name} must be a non-empty string`);
35
+ }
36
+ return value.trim();
37
+ }
38
+
39
+ function requiredString(options, name, integration) {
40
+ const value = optionalString(options, name, integration);
41
+ if (!value) {
42
+ throw new Error(`LogBrew ${integration} release-artifact upload requires ${name}`);
43
+ }
44
+ return value;
45
+ }
46
+
47
+ function optionalBoundedNumber(options, name, integration, minimum, maximum, integer = false) {
48
+ const value = options?.[name];
49
+ if (value === undefined || value === null) {
50
+ return null;
51
+ }
52
+ if (
53
+ typeof value !== "number" ||
54
+ !Number.isFinite(value) ||
55
+ value < minimum ||
56
+ value > maximum ||
57
+ (integer && !Number.isInteger(value))
58
+ ) {
59
+ const kind = integer ? "an integer" : "a number";
60
+ throw new Error(
61
+ `LogBrew ${integration} release-artifact upload option ${name} must be ${kind} from ${minimum} to ${maximum}`,
62
+ );
63
+ }
64
+ return value;
65
+ }
66
+
67
+ function normalizeReleaseArtifactProjectId(value, integration) {
68
+ if (value === undefined || value === null) {
69
+ return null;
70
+ }
71
+ if (typeof value !== "string" || !UUID_RE.test(value.trim())) {
72
+ throw new Error(`LogBrew ${integration} release-artifact option projectId must be a UUID`);
73
+ }
74
+ return value.trim().toLowerCase();
75
+ }
76
+
77
+ function parseUploadEndpoint(endpoint, integration) {
78
+ let parsed;
79
+ try {
80
+ parsed = new URL(endpoint);
81
+ } catch {
82
+ throw new Error(`LogBrew ${integration} release-artifact upload endpoint must be a valid URL`);
83
+ }
84
+ if (!new Set(["http:", "https:"]).has(parsed.protocol)) {
85
+ throw new Error(`LogBrew ${integration} release-artifact upload endpoint must use http or https`);
86
+ }
87
+ return parsed;
88
+ }
89
+
90
+ function isLoopbackEndpoint(parsed) {
91
+ const hostname = parsed.hostname.toLowerCase();
92
+ return (
93
+ hostname === "localhost" ||
94
+ hostname === "[::1]" ||
95
+ hostname === "::1" ||
96
+ (net.isIP(hostname) !== 0 && (hostname.startsWith("127.") || hostname === "::1"))
97
+ );
98
+ }
99
+
100
+ function normalizeReleaseArtifactUploadOptions(upload, { integration, projectId }) {
101
+ if (upload === undefined || upload === null) {
102
+ return null;
103
+ }
104
+ if (typeof upload !== "object" || Array.isArray(upload)) {
105
+ throw new Error(`LogBrew ${integration} release-artifact option upload must be an object`);
106
+ }
107
+ const unknownOption = Object.keys(upload).find((name) => !UPLOAD_OPTION_NAMES.has(name));
108
+ if (unknownOption) {
109
+ throw new Error(`LogBrew ${integration} release-artifact unknown upload option ${unknownOption}`);
110
+ }
111
+
112
+ const endpoint = requiredString(upload, "endpoint", integration);
113
+ const parsedEndpoint = parseUploadEndpoint(endpoint, integration);
114
+ const allowHostedUpload = optionalBoolean(upload, "allowHostedUpload", integration);
115
+ const hosted = !isLoopbackEndpoint(parsedEndpoint);
116
+ if (hosted && !allowHostedUpload) {
117
+ throw new Error(`LogBrew ${integration} release-artifact hosted upload requires allowHostedUpload`);
118
+ }
119
+ if (hosted && parsedEndpoint.protocol !== "https:") {
120
+ throw new Error(`LogBrew ${integration} hosted release-artifact upload endpoint must use https`);
121
+ }
122
+ if (hosted && (parsedEndpoint.username || parsedEndpoint.password)) {
123
+ throw new Error(`LogBrew ${integration} hosted release-artifact upload endpoint must not include embedded auth values`);
124
+ }
125
+ if (hosted && (parsedEndpoint.search || parsedEndpoint.hash)) {
126
+ throw new Error(`LogBrew ${integration} hosted release-artifact upload endpoint must not include query strings or fragments`);
127
+ }
128
+ if (hosted && !projectId) {
129
+ throw new Error(`LogBrew ${integration} release-artifact hosted upload requires projectId`);
130
+ }
131
+
132
+ const tokenEnv = optionalString(upload, "tokenEnv", integration);
133
+ if (tokenEnv && !/^[A-Za-z_][A-Za-z0-9_]*$/u.test(tokenEnv)) {
134
+ throw new Error(`LogBrew ${integration} release-artifact upload option tokenEnv must be an environment variable name`);
135
+ }
136
+
137
+ return {
138
+ endpoint,
139
+ allowHostedUpload,
140
+ tokenEnv,
141
+ dryRun: optionalBoolean(upload, "dryRun", integration),
142
+ maxRetries: optionalBoundedNumber(upload, "maxRetries", integration, 0, 10, true),
143
+ retryDelay: optionalBoundedNumber(upload, "retryDelay", integration, 0, 60),
144
+ timeout: optionalBoundedNumber(upload, "timeout", integration, 0, 300),
145
+ };
146
+ }
147
+
148
+ function appendOption(args, name, value) {
149
+ if (value !== null) {
150
+ args.push(name, String(value));
151
+ }
152
+ }
153
+
154
+ function createReleaseArtifactUploadArgs(buildDir, manifestPath, upload) {
155
+ const args = ["--build-dir", buildDir, "--manifest", manifestPath, "--endpoint", upload.endpoint];
156
+ appendOption(args, "--token-env", upload.tokenEnv);
157
+ if (upload.allowHostedUpload) {
158
+ args.push("--allow-hosted");
159
+ }
160
+ if (upload.dryRun) {
161
+ args.push("--dry-run");
162
+ }
163
+ appendOption(args, "--max-retries", upload.maxRetries);
164
+ appendOption(args, "--retry-delay", upload.retryDelay);
165
+ appendOption(args, "--timeout", upload.timeout);
166
+ return args;
167
+ }
168
+
169
+ function parseJson(stdout, command) {
170
+ try {
171
+ return JSON.parse(stdout);
172
+ } catch (error) {
173
+ throw new Error(`LogBrew release-artifact ${command} did not return JSON`, { cause: error });
174
+ }
175
+ }
176
+
177
+ function sanitizeValidationDiagnostic(message) {
178
+ const printable = Array.from(message, (character) => {
179
+ const codePoint = character.codePointAt(0);
180
+ return codePoint <= 31 || codePoint === 127 ? " " : character;
181
+ }).join("");
182
+ return printable.replace(/\s+/gu, " ").trim().slice(0, 200);
183
+ }
184
+
185
+ function validationErrors(report) {
186
+ if (!report?.validation || !Array.isArray(report.validation.errors)) {
187
+ return [];
188
+ }
189
+ return report.validation.errors
190
+ .filter((message) => typeof message === "string")
191
+ .map(sanitizeValidationDiagnostic)
192
+ .filter((message) => message !== "")
193
+ .slice(0, 3);
194
+ }
195
+
196
+ function safeReportStatus(report) {
197
+ const status = report?.status;
198
+ return typeof status === "string" && /^[a-z][a-z0-9_]{0,63}$/u.test(status) ? status : null;
199
+ }
200
+
201
+ function runReleaseArtifactCli(cliPath, command, args) {
202
+ const result = spawnSync(process.execPath, [cliPath, command, ...args], {
203
+ cwd: process.cwd(),
204
+ encoding: "utf8",
205
+ windowsHide: true,
206
+ });
207
+ const report = result.stdout ? parseJson(result.stdout, command) : null;
208
+ if (result.status !== 0) {
209
+ const details = validationErrors(report).join("; ") || safeReportStatus(report) || "execution_failed";
210
+ throw new Error(`LogBrew release-artifact ${command} failed: ${details}`);
211
+ }
212
+ return { report, stdout: result.stdout };
213
+ }
214
+
215
+ function formatReleaseArtifactUploadSummary(report) {
216
+ const artifactCount = Number.isSafeInteger(report?.artifactCount) && report.artifactCount >= 0 ? report.artifactCount : 0;
217
+ const artifactLabel = artifactCount === 1 ? "artifact" : "artifacts";
218
+ return `LogBrew release artifacts: ${safeReportStatus(report) ?? "unknown"} (${artifactCount} ${artifactLabel})`;
219
+ }
220
+
221
+ module.exports = {
222
+ createReleaseArtifactUploadArgs,
223
+ formatReleaseArtifactUploadSummary,
224
+ normalizeReleaseArtifactProjectId,
225
+ normalizeReleaseArtifactUploadOptions,
226
+ runReleaseArtifactCli,
227
+ };
@@ -0,0 +1,82 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+
5
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
6
+
7
+ export function sortJson(value) {
8
+ if (Array.isArray(value)) {
9
+ return value.map(sortJson);
10
+ }
11
+ if (value && typeof value === "object") {
12
+ return Object.fromEntries(
13
+ Object.keys(value)
14
+ .sort()
15
+ .map((key) => [key, sortJson(value[key])])
16
+ );
17
+ }
18
+ return value;
19
+ }
20
+
21
+ export function stableJson(value) {
22
+ return JSON.stringify(sortJson(value));
23
+ }
24
+
25
+ export function printJson(payload) {
26
+ process.stdout.write(`${JSON.stringify(sortJson(payload), null, 2)}\n`);
27
+ }
28
+
29
+ export function safeResolve(candidate, root) {
30
+ const resolvedRoot = fs.realpathSync(root);
31
+ const resolved = path.resolve(candidate);
32
+ const comparable = fs.existsSync(resolved) ? fs.realpathSync(resolved) : resolved;
33
+ const relative = path.relative(resolvedRoot, comparable);
34
+ if (relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative))) {
35
+ return comparable;
36
+ }
37
+ return null;
38
+ }
39
+
40
+ export function byteSize(filePath) {
41
+ return fs.statSync(filePath).size;
42
+ }
43
+
44
+ export function sha256File(filePath) {
45
+ const digest = crypto.createHash("sha256");
46
+ digest.update(fs.readFileSync(filePath));
47
+ return digest.digest("hex");
48
+ }
49
+
50
+ export function requireBuildDir(value) {
51
+ const buildDir = path.resolve(value);
52
+ if (!fs.existsSync(buildDir) || !fs.statSync(buildDir).isDirectory()) {
53
+ throw new Error(`build directory does not exist: ${value}`);
54
+ }
55
+ return fs.realpathSync(buildDir);
56
+ }
57
+
58
+ export function readJsonObject(filePath, label) {
59
+ let payload;
60
+ try {
61
+ payload = JSON.parse(fs.readFileSync(filePath, "utf8"));
62
+ } catch (error) {
63
+ throw new Error(`${label} is not valid JSON: ${error.message}`, { cause: error });
64
+ }
65
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
66
+ throw new Error(`${label} must be a JSON object`);
67
+ }
68
+ return payload;
69
+ }
70
+
71
+ export function normalizeProjectId(value, errorMessage, required = false) {
72
+ if (value === undefined || value === null) {
73
+ if (required) {
74
+ throw new Error(errorMessage);
75
+ }
76
+ return undefined;
77
+ }
78
+ if (typeof value !== "string" || UUID_RE.test(value.trim()) === false) {
79
+ throw new Error(errorMessage);
80
+ }
81
+ return value.trim().toLowerCase();
82
+ }